作者 yangfu

Merge branch 'dev_content' into dev_preview

# Conflicts:
#	cmd/discuss/api/internal/logic/article/mini_create_article_logic.go
#	cmd/discuss/api/internal/logic/comment/mini_create_article_comment_logic.go
#	cmd/discuss/interanl/pkg/db/migrate.go
#	deploy/database/table.sql
... ... @@ -11,3 +11,4 @@ import "core/article.api"
import "core/role.api"
import "core/department.api"
import "core/article_category.api"
import "core/article_security.api"
\ No newline at end of file
... ...
@server(
prefix: v1/system
group: secuirty
middleware: LoginStatusCheck,LogRequest
jwt: SystemAuth
)
service Core {
@doc "内容安全-搜索"
@handler articleSecuritySearch
post /article_security/search (ArticleSecuritySearchRequest) returns (ArticleSecuritySearchResponse)
@doc "详情"
@handler articleSecurityGet
get /article_security/:id (ArticleSecurityGetRequest) returns (ArticleSecurityGetResponse)
@doc "内容安全-审核"
@handler articleSecurityAudit
post /article_security/audit (ArticleSecurityAuditRequest) returns (ArticleSecurityAuditResponse)
}
type (
ArticleSecurityGetRequest {
Id int64 `path:"id"`
}
ArticleSecurityGetResponse struct{
ArticleSecurity ArticleSecurityItem `json:"item"`
}
// ArticleSecuritySaveRequest struct{
// ArticleSecurity ArticleSecurityItem `json:"article_security"`
// }
// ArticleSecuritySaveResponse struct{}
//
// ArticleSecurityDeleteRequest struct{
// Id int64 `path:"id"`
// }
// ArticleSecurityDeleteResponse struct{}
//
// ArticleSecurityUpdateRequest struct{
// Id int64 `path:"id"`
// ArticleSecurity ArticleSecurityItem `json:"article_security"`
// }
// ArticleSecurityUpdateResponse struct{}
ArticleSecurityAuditRequest struct{
Id int64 `json:"id"` //id
Status int `json:"status"` // 1:成功 0:失败
}
ArticleSecurityAuditResponse struct{
}
ArticleSecuritySearchRequest struct{
Page int `json:"page,optional"`
Size int `json:"size,optional"`
ReviewStatus int `json:"reviewStatus,optional"` // 审核结果 1:待审核 2:通过 3:拒绝
Suggest string `json:"suggest"` // 建议 通过、风险、人工审核
ContentType int `json:"contentType"` // 内容类型 (1:文章 2:评论)
AuthorName string `json:"authorName"` // 作者名称
BeginTime int64 `json:"beginTime"` // 开始时间
EndTime int64 `json:"endTime"` // 结束时间
}
ArticleSecuritySearchResponse{
List []ArticleSecurityItem `json:"list"`
Total int64 `json:"total"`
}
ArticleSecurityItem struct{
Id int64 `json:"id"` // 唯一标识
ContentKeyWords string `json:"contentKeyWords"` // 内容关键字
Content ContentDetailItem `json:"content"` // 内容详情
Label string `json:"label"` // 风控标签
Prob int `json:"prob"` // 分值
Suggest string `json:"suggest"` // 建议 通过、风险、人工审核
Author string `json:"author"` // 发布人
ReleaseAt string `json:"releaseAt"` // 发布时间
ReviewStatus int `json:"reviewStatus"` // 审核结果 1:待审核 2:通过 3:拒绝
Reviewer string `json:"reviewer"` // 审核人
ReviewAt int64 `json:"reviewAt"` // 审核时间(人工处置时间)
CheckList []CheckDetailItem `json:"checkList"` // 检查列表
}
CheckDetailItem struct{
Label string `json:"label"` // 命中标签
Prob uint `json:"prob"` // 置信度。0-100,越高代表越有可能属于当前返回的标签(label)
Suggest string `json:"suggest"` // 建议
}
ContentDetailItem struct{
Id int64 `json:"id"` // 内容ID
Type int `json:"type"` // 内容类型 (1:文章 2:评论)
Text string `json:"text"` // 内容文本
}
)
\ No newline at end of file
... ...
... ... @@ -7,6 +7,7 @@ Timeout: 30000
# CertFile: ./key/fjmaimaimai.com_bundle.crt
# KeyFile: ./key/fjmaimaimai.com.key
LogRequest: true # 记录详细请求日志
ContentSecurityCheck: true # 内容安全检查(调用微信接口)
Log:
# Mode: file
... ...
... ... @@ -17,6 +17,7 @@ type Config struct {
ApiAuth ApiService
DebugSmsCode string `json:",optional,default=999512"`
LogRequest bool `json:",optional,default=true"`
ContentSecurityCheck bool `json:",optional,default=false"`
}
type ApiService struct {
... ...
... ... @@ -11,6 +11,7 @@ import (
department "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/department"
message "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/message"
role "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/role"
secuirty "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/secuirty"
tags "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/tags"
user "gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/handler/user"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
... ... @@ -809,4 +810,29 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
},
rest.WithPrefix("/v1/system"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.LoginStatusCheck, serverCtx.LogRequest},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/article_security/search",
Handler: secuirty.ArticleSecuritySearchHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/article_security/:id",
Handler: secuirty.ArticleSecurityGetHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/article_security/audit",
Handler: secuirty.ArticleSecurityAuditHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.SystemAuth.AccessSecret),
rest.WithPrefix("/v1/system"),
)
}
... ...
package secuirty
import (
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/result"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/secuirty"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
)
func ArticleSecurityAuditHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ArticleSecurityAuditRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := secuirty.NewArticleSecurityAuditLogic(r.Context(), svcCtx)
resp, err := l.ArticleSecurityAudit(&req)
result.HttpResult(r, w, resp, err)
}
}
... ...
package secuirty
import (
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/result"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/secuirty"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
)
func ArticleSecurityGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ArticleSecurityGetRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := secuirty.NewArticleSecurityGetLogic(r.Context(), svcCtx)
resp, err := l.ArticleSecurityGet(&req)
result.HttpResult(r, w, resp, err)
}
}
... ...
package secuirty
import (
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/result"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/secuirty"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
)
func ArticleSecuritySearchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ArticleSecuritySearchRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := secuirty.NewArticleSecuritySearchLogic(r.Context(), svcCtx)
resp, err := l.ArticleSecuritySearch(&req)
result.HttpResult(r, w, resp, err)
}
}
... ...
... ... @@ -3,6 +3,7 @@ package article
import (
"context"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/message"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/core"
"strconv"
"strings"
"text/template"
... ... @@ -205,6 +206,11 @@ func (l *MiniCreateArticleLogic) MiniCreateArticle(req *types.MiniArticleCreateR
if err != nil {
return xerr.NewErrMsgErr("创建文章失败", err)
}
// 内容安全检查
if err = core.ContentSecurityCheck(l.ctx, l.svcCtx, conn, "", core.NewContentFromArticle(newArticle, sectionList)); err != nil {
return err
}
return nil
}, true)
if err != nil {
... ...
... ... @@ -2,6 +2,7 @@ package comment
import (
"context"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/core"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/message"
... ... @@ -211,6 +212,11 @@ func (l *MiniCreateArticleCommentLogic) MiniCreateArticleComment(req *types.Mini
if err != nil {
return err
}
// 内容安全检查
if err = core.ContentSecurityCheck(l.ctx, l.svcCtx, conn, "", core.NewContentFromComment(&newComment)); err != nil {
return err
}
return nil
}, true)
... ...
package core
import (
"bytes"
"context"
"fmt"
"github.com/silenceper/wechat/v2/miniprogram/security"
"github.com/zeromicro/go-zero/core/logx"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"time"
)
func ContentSecurityCheck(ctx context.Context, svcCtx *svc.ServiceContext, conn transaction.Conn, openId string, content ContentBody) error {
if !svcCtx.Config.ContentSecurityCheck {
return nil
}
var (
scene security.MsgScene
)
if content.Type == 1 {
scene = security.MsgSceneSocialLog
} else {
scene = security.MsgSceneComment
}
var (
resp domain.MsgCheckDetail
)
respTmp, err := svcCtx.MiniProgram.GetSecurity().MsgCheck(&security.MsgCheckRequest{
OpenID: openId,
Scene: scene,
Content: content.Content,
})
if err != nil {
logx.Error(err)
return nil
}
resp = domain.MsgCheckDetail(respTmp)
var (
keyWords []string
prob int
)
for i := range resp.Detail {
item := resp.Detail[i]
if prob == 0 {
prob = int(item.Prob)
}
keyWords = append(keyWords, item.Keyword)
}
dm := &domain.ArticleSecurity{
ContentKeyWords: keyWords,
ContentType: content.Type,
ContentId: content.Id,
AuthorId: content.AuthorId,
Reviewer: 0,
ReviewStatus: domain.ReviewStatusWait,
Label: resp.Result.Label.String(),
Prob: prob,
Suggest: string(resp.Result.Suggest),
Detail: resp,
AutoReviewAt: time.Now().Unix(),
AutoReviewErrorCode: fmt.Sprintf("%d", resp.ErrCode),
}
if resp.Result.Suggest == security.CheckSuggestPass {
dm.ReviewStatus = domain.ReviewStatusPass
}
if dm, err = svcCtx.ArticleSecurityRepository.Insert(ctx, conn, dm); err != nil {
logx.Error(err)
return nil
}
return HandlerSecurityContent(ctx, svcCtx, conn, content, dm.ReviewStatus)
}
func HandlerSecurityContent(ctx context.Context, svcCtx *svc.ServiceContext, conn transaction.Conn, c ContentBody, status int) error {
if !svcCtx.Config.ContentSecurityCheck {
return nil
}
var (
article *domain.Article
comment *domain.ArticleComment
err error
)
if status == domain.ReviewStatusPass {
return nil
}
if c.Type == domain.TypeArticle {
if article, err = svcCtx.ArticleRepository.FindOne(ctx, conn, c.Id); err != nil {
return fmt.Errorf("文章不存在")
}
article.Show = domain.ArticleShowDisable
if _, err = svcCtx.ArticleRepository.UpdateWithVersion(ctx, conn, article); err != nil {
return err
}
} else if c.Type == domain.TypeComment {
if comment, err = svcCtx.ArticleCommentRepository.FindOne(ctx, conn, c.Id); err != nil {
return fmt.Errorf("评论不存在")
}
comment.Show = domain.CommentShowDisable
if _, err = svcCtx.ArticleCommentRepository.UpdateWithVersion(ctx, conn, comment); err != nil {
return err
}
}
return nil
}
type ContentBody struct {
Id int64
Type int
Content string
AuthorId int64
Summary string
}
func NewContentFromComment(c *domain.ArticleComment) ContentBody {
return ContentBody{
Id: c.Id,
Type: domain.TypeComment,
Content: c.Content,
AuthorId: c.FromUserId,
Summary: c.Content,
}
}
func NewContentFromArticle(c *domain.Article, sections []*domain.ArticleSection) ContentBody {
content := bytes.NewBuffer(nil)
for _, sec := range sections {
content.WriteString(sec.Content)
}
return ContentBody{
Id: c.Id,
Type: domain.TypeComment,
Content: content.String(),
AuthorId: c.AuthorId,
Summary: c.Summary,
}
}
... ...
package secuirty
import (
"context"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/logic/core"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/xerr"
"time"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ArticleSecurityAuditLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewArticleSecurityAuditLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ArticleSecurityAuditLogic {
return &ArticleSecurityAuditLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ArticleSecurityAuditLogic) ArticleSecurityAudit(req *types.ArticleSecurityAuditRequest) (resp *types.ArticleSecurityAuditResponse, err error) {
var (
conn = l.svcCtx.DefaultDBConn()
dm *domain.ArticleSecurity
)
if dm, err = l.svcCtx.ArticleSecurityRepository.FindOne(l.ctx, conn, req.Id); err != nil {
return nil, xerr.NewErrMsgErr("不存在", err)
}
// 不可编辑判断
if dm.ReviewStatus != domain.ReviewStatusWait {
return nil, xerr.NewErrMsgErr("内容已审核", err)
}
// 赋值
if req.Status == 1 {
dm.ReviewStatus = domain.ReviewStatusPass
} else {
dm.ReviewStatus = domain.ReviewStatusFail
}
dm.ReviewAt = time.Now().Unix()
// 更新
if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error {
dm, err = l.svcCtx.ArticleSecurityRepository.UpdateWithVersion(l.ctx, conn, dm)
if err != nil {
return err
}
if err = core.HandlerSecurityContent(l.ctx, l.svcCtx, conn, core.ContentBody{Id: dm.ContentId, Type: dm.ContentType}, dm.ReviewStatus); err != nil {
return err
}
// 更新文章/评论可见
return err
}, true); err != nil {
return nil, xerr.NewErrMsg("更新失败")
}
resp = &types.ArticleSecurityAuditResponse{}
return
}
... ...
package secuirty
import (
"context"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/pkg/xerr"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ArticleSecurityGetLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewArticleSecurityGetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ArticleSecurityGetLogic {
return &ArticleSecurityGetLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ArticleSecurityGetLogic) ArticleSecurityGet(req *types.ArticleSecurityGetRequest) (resp *types.ArticleSecurityGetResponse, err error) {
var (
conn = l.svcCtx.DefaultDBConn()
dm *domain.ArticleSecurity
reviewer *domain.User
)
// 货号唯一
if dm, err = l.svcCtx.ArticleSecurityRepository.FindOne(l.ctx, conn, req.Id); err != nil {
return nil, xerr.NewErrMsgErr("不存在", err)
}
if dm.Reviewer > 0 {
if reviewer, err = l.svcCtx.UserRepository.FindOne(l.ctx, conn, dm.Reviewer); err != nil {
return nil, err
}
}
resp = &types.ArticleSecurityGetResponse{
ArticleSecurity: NewTypesArticleSecurity(dm, reviewer),
}
return
}
... ...
package secuirty
import (
"context"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/svc"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/api/internal/types"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"strings"
"github.com/zeromicro/go-zero/core/logx"
)
type ArticleSecuritySearchLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewArticleSecuritySearchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ArticleSecuritySearchLogic {
return &ArticleSecuritySearchLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ArticleSecuritySearchLogic) ArticleSecuritySearch(req *types.ArticleSecuritySearchRequest) (resp *types.ArticleSecuritySearchResponse, err error) {
var (
conn = l.svcCtx.DefaultDBConn()
dms []*domain.ArticleSecurity
total int64
)
queryOptions := domain.NewQueryOptions().WithOffsetLimit(req.Page, req.Size).
WithKV("reviewStatus", req.ReviewStatus).
WithKV("suggest", req.Suggest).
WithKV("contentType", req.ContentType).
WithKV("authorName", req.AuthorName).
WithKV("beginTime", req.BeginTime).
WithKV("endTime", req.EndTime)
total, dms, err = l.svcCtx.ArticleSecurityRepository.Find(l.ctx, conn, queryOptions)
list := make([]types.ArticleSecurityItem, 0)
for i := range dms {
list = append(list, NewTypesArticleSecurity(dms[i], nil))
}
resp = &types.ArticleSecuritySearchResponse{
List: list,
Total: total,
}
return
}
func NewDomainArticleSecurity(item types.ArticleSecurityItem) *domain.ArticleSecurity {
return &domain.ArticleSecurity{}
}
func NewTypesArticleSecurity(item *domain.ArticleSecurity, reviewer *domain.User) types.ArticleSecurityItem {
result := types.ArticleSecurityItem{
Id: item.Id,
ContentKeyWords: strings.Join(item.ContentKeyWords, ","),
Label: item.Label,
Prob: item.Prob,
Suggest: describeSuggest(item.Suggest),
Author: item.AuthorName,
ReviewAt: item.CreatedAt,
ReviewStatus: item.ReviewStatus,
Reviewer: "",
}
if reviewer != nil {
result.Reviewer = reviewer.Name
}
for _, detail := range item.Detail.Detail {
result.CheckList = append(result.CheckList, types.CheckDetailItem{
Label: detail.Label.String(),
Prob: detail.Prob,
Suggest: describeSuggest(detail.Suggest),
})
}
return result
}
func describeContentType(t int) string {
if t == domain.TypeArticle {
return "文本-帖子"
}
if t == domain.TypeComment {
return "文本-评论"
}
return ""
}
func describeSuggest(s string) string {
if s == "risk" {
return "风险"
}
if s == "pass" {
return "通过"
}
if s == "review" {
return "人工审核"
}
return ""
}
... ...
... ... @@ -36,6 +36,7 @@ type ServiceContext struct {
ArticleCategoryRepository domain.ArticleCategoryRepository
ArticleAndTagRepository domain.ArticleAndTagRepository
ArticleDraftOperationRepository domain.ArticleDraftOperationRepository
ArticleSecurityRepository domain.ArticleSecurityRepository
CompanyRepository domain.CompanyRepository
DepartmentRepository domain.DepartmentRepository
... ... @@ -94,6 +95,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
ArticleAndTagRepository: repository.NewArticleAndTagRepository(cache.NewCachedRepository(mlCache)),
ArticleCategoryRepository: repository.NewArticleCategoryRepository(cache.NewCachedRepository(mlCache)),
ArticleDraftOperationRepository: repository.NewArticleDraftOperationRepository(cache.NewCachedRepository(mlCache)),
ArticleSecurityRepository: repository.NewArticleSecurityRepository(cache.NewCachedRepository(mlCache)),
CompanyRepository: repository.NewCompanyRepository(cache.NewCachedRepository(mlCache)),
DepartmentRepository: repository.NewDepartmentRepository(cache.NewCachedRepository(mlCache)),
... ...
... ... @@ -1849,3 +1849,62 @@ type CategoryOptionValue struct {
Label string `json:"label"` // 名称
Value int64 `json:"value"` // 分类ID
}
type ArticleSecurityGetRequest struct {
Id int64 `path:"id"`
}
type ArticleSecurityGetResponse struct {
ArticleSecurity ArticleSecurityItem `json:"item"`
}
type ArticleSecurityAuditRequest struct {
Id int64 `json:"id"` //id
Status int `json:"status"` // 1:成功 0:失败
}
type ArticleSecurityAuditResponse struct {
}
type ArticleSecuritySearchRequest struct {
Page int `json:"page,optional"`
Size int `json:"size,optional"`
ReviewStatus int `json:"reviewStatus,optional"` // 审核结果 1:待审核 2:通过 3:拒绝
Suggest string `json:"suggest"` // 建议 通过、风险、人工审核
ContentType int `json:"contentType"` // 内容类型 (1:文章 2:评论)
AuthorName string `json:"authorName"` // 作者名称
BeginTime int64 `json:"beginTime"` // 开始时间
EndTime int64 `json:"endTime"` // 结束时间
}
type ArticleSecuritySearchResponse struct {
List []ArticleSecurityItem `json:"list"`
Total int64 `json:"total"`
}
type ArticleSecurityItem struct {
Id int64 `json:"id"` // 唯一标识
ContentKeyWords string `json:"contentKeyWords"` // 内容关键字
Content ContentDetailItem `json:"content"` // 内容详情
Label string `json:"label"` // 风控标签
Prob int `json:"prob"` // 分值
Suggest string `json:"suggest"` // 建议 通过、风险、人工审核
Author string `json:"author"` // 发布人
ReleaseAt string `json:"releaseAt"` // 发布时间
ReviewStatus int `json:"reviewStatus"` // 审核结果 1:待审核 2:通过 3:拒绝
Reviewer string `json:"reviewer"` // 审核人
ReviewAt int64 `json:"reviewAt"` // 审核时间(人工处置时间)
CheckList []CheckDetailItem `json:"checkList"` // 检查列表
}
type CheckDetailItem struct {
Label string `json:"label"` // 命中标签
Prob uint `json:"prob"` // 置信度。0-100,越高代表越有可能属于当前返回的标签(label)
Suggest string `json:"suggest"` // 建议
}
type ContentDetailItem struct {
Id int64 `json:"id"` // 内容ID
Type int `json:"type"` // 内容类型 (1:文章 2:评论)
Text string `json:"text"` // 内容文本
}
... ...
syntax = "v1"
info(
title: "xx实例"
desc: "xx实例"
author: "author"
email: "email"
version: "v1"
)
@server(
prefix: article_security/v1
group: article_security
jwt: JwtAuth
)
service Core {
@doc "详情"
@handler article_securityGet
get /article_security/:id (ArticleSecurityGetRequest) returns (ArticleSecurityGetResponse)
@doc "保存"
@handler article_securitySave
post /article_security (ArticleSecuritySaveRequest) returns (ArticleSecuritySaveResponse)
@doc "删除"
@handler article_securityDelete
delete /article_security/:id (ArticleSecurityDeleteRequest) returns (ArticleSecurityDeleteResponse)
@doc "更新"
@handler article_securityUpdate
put /article_security/:id (ArticleSecurityUpdateRequest) returns (ArticleSecurityUpdateResponse)
@doc "搜索"
@handler article_securitySearch
post /article_security/search (ArticleSecuritySearchRequest) returns (ArticleSecuritySearchResponse)
}
type (
ArticleSecurityGetRequest {
Id int64 `path:"id"`
}
ArticleSecurityGetResponse struct{
ArticleSecurity ArticleSecurityItem `json:"article_security"`
}
ArticleSecuritySaveRequest struct{
ArticleSecurity ArticleSecurityItem `json:"article_security"`
}
ArticleSecuritySaveResponse struct{}
ArticleSecurityDeleteRequest struct{
Id int64 `path:"id"`
}
ArticleSecurityDeleteResponse struct{}
ArticleSecurityUpdateRequest struct{
Id int64 `path:"id"`
ArticleSecurity ArticleSecurityItem `json:"article_security"`
}
ArticleSecurityUpdateResponse struct{}
ArticleSecuritySearchRequest struct{
Page int `json:"page"`
Size int `json:"size"`
}
ArticleSecuritySearchResponse{
List []ArticleSecurityItem `json:"list"`
Total int64 `json:"total"`
}
ArticleSecurityItem struct{
}
)
// logic CRUD
// Save
//var (
// conn = l.svcCtx.DefaultDBConn()
// dm *domain.ArticleSecurity
//)
//// 唯一判断
//dm = NewDomainArticleSecurity(req.ArticleSecurity)
//if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error {
// dm, err = l.svcCtx.ArticleSecurityRepository.Insert(l.ctx, conn, dm)
// return err
//}, true); err != nil {
// return nil, xerr.NewErrMsg("保存失败")
//}
////resp = &types.ArticleSecuritySaveResponse{}
//return
//func NewDomainArticleSecurity(item types.ArticleSecurityItem) *domain.ArticleSecurity {
// return &domain.ArticleSecurity{
// }
//}
//
//func NewTypesArticleSecurity(item *domain.ArticleSecurity) types.ArticleSecurityItem {
// return types.ArticleSecurityItem{
// Id: item.Id,
// }
//}
// Get
//var (
// conn = l.svcCtx.DefaultDBConn()
// dm *domain.ArticleSecurity
//)
//// 货号唯一
//if dm, err = l.svcCtx.ArticleSecurityRepository.FindOne(l.ctx, conn, req.Id); err != nil {
// return nil, xerr.NewErrMsgErr("不存在", err)
//}
//resp = &types.ArticleSecurityGetResponse{
// ArticleSecurity: NewTypesArticleSecurity(dm),
//}
//return
// Delete
//var (
// conn = l.svcCtx.DefaultDBConn()
// dm *domain.ArticleSecurity
//)
//if dm, err = l.svcCtx.ArticleSecurityRepository.FindOne(l.ctx, conn, req.Id); err != nil {
// return nil, xerr.NewErrMsgErr("不存在", err)
//}
//if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error {
// if dm, err = l.svcCtx.ArticleSecurityRepository.Delete(l.ctx, conn, dm); err != nil {
// return err
// }
// return nil
//}, true); err != nil {
// return nil, xerr.NewErrMsgErr("移除失败", err)
//}
//return
// Search
//var (
// conn = l.svcCtx.DefaultDBConn()
// dms []*domain.ArticleSecurity
// total int64
//)
//
//queryOptions := domain.NewQueryOptions().WithOffsetLimit(req.Page, req.Size).
// WithKV("", "")
//total, dms, err = l.svcCtx.ArticleSecurityRepository.Find(l.ctx, conn, queryOptions)
//list := make([]types.ArticleSecurityItem, 0)
//for i := range dms {
// list = append(list, NewTypesArticleSecurity(dms[i]))
//}
//resp = &types.ArticleSecuritySearchResponse{
// List: list,
// Total: total,
//}
//return
// Update
//var (
// conn = l.svcCtx.DefaultDBConn()
// dm *domain.ArticleSecurity
//)
//if dm, err = l.svcCtx.ArticleSecurityRepository.FindOne(l.ctx, conn, req.Id); err != nil {
// return nil, xerr.NewErrMsgErr("不存在", err)
//}
//// 不可编辑判断
//// 赋值
//// 更新
//if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error {
// dm, err = l.svcCtx.ArticleSecurityRepository.UpdateWithVersion(l.ctx, conn, dm)
// return err
//}, true); err != nil {
// return nil, xerr.NewErrMsg("更新失败")
//}
//resp = &types.ArticleSecurityUpdateResponse{}
//return
... ...
syntax = "proto3";
option go_package ="./pb";
package pb;
message ArticleSecurityGetReq {
int64 Id = 1;
}
message ArticleSecurityGetResp{
ArticleSecurityItem User = 1;
}
message ArticleSecuritySaveReq {
}
message ArticleSecuritySaveResp{
}
message ArticleSecurityDeleteReq {
int64 Id = 1;
}
message ArticleSecurityDeleteResp{
}
message ArticleSecurityUpdateReq {
int64 Id = 1;
}
message ArticleSecurityUpdateResp{
}
message ArticleSecuritySearchReq {
int64 PageNumber = 1;
int64 PageSize = 2;
}
message ArticleSecuritySearchResp{
repeated ArticleSecurityItem List =1;
int64 Total =2;
}
message ArticleSecurityItem {
}
service ArticleSecurityService {
rpc ArticleSecurityGet(ArticleSecurityGetReq) returns(ArticleSecurityGetResp);
rpc ArticleSecuritySave(ArticleSecuritySaveReq) returns(ArticleSecuritySaveResp);
rpc ArticleSecurityDelete(ArticleSecurityDeleteReq) returns(ArticleSecurityDeleteResp);
rpc ArticleSecurityUpdate(ArticleSecurityUpdateReq) returns(ArticleSecurityUpdateResp);
rpc ArticleSecuritySearch(ArticleSecuritySearchReq) returns(ArticleSecuritySearchResp);
}
... ...
... ... @@ -29,6 +29,9 @@ func Migrate(db *gorm.DB) {
&models.UserSubscribe{},
&models.MessageSubscribe{},
&models.UserWechat{},
&models.ArticleDraftOperation{},
&models.ArticleCategory{},
&models.ArticleSecurity{},
}
db.AutoMigrate(modelsList...)
... ...
package models
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
"time"
)
type ArticleSecurity struct {
Id int64 // 唯一标识
ContentKeyWords []string // 内容关键字
ContentType int `json:"contentType,omitempty"` // 内容类型 (1:文章 2:评论)
ContentId int64 `json:"contentId,omitempty"` // 内容ID
AuthorId int64 `json:"authorId,omitempty"` // 发布人
AuthorName string `json:"authorName,omitempty"` // 发布人
Reviewer int64 `json:"reviewer,omitempty"` // 审核人
ReviewStatus int `json:"reviewStatus,omitempty"` // 审核状态 0:待审核 1:通过 2:拒绝
Label string // 标签
Prob int // 分值
Suggest string // 建议 通过、风险、人工审核
Detail domain.MsgCheckDetail `gorm:"type:jsonb;serializer:json"`
AutoReviewAt int64 `json:"autoReviewAt,omitempty"` // 自动审核时间
AutoReviewErrorCode string `json:"autoReviewErrorCode,omitempty"` // 自动审核错误码
ReviewAt int64 `json:"reviewAt,omitempty"` // 审核时间(人工处置时间)
CreatedAt int64
UpdatedAt int64
DeletedAt int64
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"`
Version int
}
func (m *ArticleSecurity) TableName() string {
return "article_security"
}
func (m *ArticleSecurity) BeforeCreate(tx *gorm.DB) (err error) {
m.CreatedAt = time.Now().Unix()
m.UpdatedAt = time.Now().Unix()
return
}
func (m *ArticleSecurity) BeforeUpdate(tx *gorm.DB) (err error) {
m.UpdatedAt = time.Now().Unix()
return
}
func (m *ArticleSecurity) CacheKeyFunc() string {
if m.Id == 0 {
return ""
}
return fmt.Sprintf("%v:cache:%v:id:%v", domain.ProjectName, m.TableName(), m.Id)
}
func (m *ArticleSecurity) CacheKeyFuncByObject(obj interface{}) string {
if v, ok := obj.(*ArticleSecurity); ok {
return v.CacheKeyFunc()
}
return ""
}
func (m *ArticleSecurity) CachePrimaryKeyFunc() string {
if len("") == 0 {
return ""
}
return fmt.Sprintf("%v:cache:%v:primarykey:%v", domain.ProjectName, m.TableName(), "key")
}
... ...
package repository
import (
"context"
"fmt"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/tiptok/gocomm/pkg/cache"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/models"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/domain"
"gorm.io/gorm"
)
type ArticleSecurityRepository struct {
*cache.CachedRepository
}
func (repository *ArticleSecurityRepository) Insert(ctx context.Context, conn transaction.Conn, dm *domain.ArticleSecurity) (*domain.ArticleSecurity, error) {
var (
err error
m = &models.ArticleSecurity{}
tx = conn.DB()
)
if m, err = repository.DomainModelToModel(dm); err != nil {
return nil, err
}
if tx = tx.Model(m).Save(m); tx.Error != nil {
return nil, tx.Error
}
dm.Id = m.Id
return repository.ModelToDomainModel(m)
}
func (repository *ArticleSecurityRepository) Update(ctx context.Context, conn transaction.Conn, dm *domain.ArticleSecurity) (*domain.ArticleSecurity, error) {
var (
err error
m *models.ArticleSecurity
tx = conn.DB()
)
if m, err = repository.DomainModelToModel(dm); err != nil {
return nil, err
}
queryFunc := func() (interface{}, error) {
tx = tx.Model(m).Updates(m)
return nil, tx.Error
}
if _, err = repository.Query(queryFunc, m.CacheKeyFunc()); err != nil {
return nil, err
}
return repository.ModelToDomainModel(m)
}
func (repository *ArticleSecurityRepository) UpdateWithVersion(ctx context.Context, transaction transaction.Conn, dm *domain.ArticleSecurity) (*domain.ArticleSecurity, error) {
var (
err error
m *models.ArticleSecurity
tx = transaction.DB()
)
if m, err = repository.DomainModelToModel(dm); err != nil {
return nil, err
}
oldVersion := dm.Version
m.Version += 1
queryFunc := func() (interface{}, error) {
tx = tx.Model(m).Select("*").Where("id = ?", m.Id).Where("version = ?", oldVersion).Updates(m)
if tx.RowsAffected == 0 {
return nil, domain.ErrUpdateFail
}
return nil, tx.Error
}
if _, err = repository.Query(queryFunc, m.CacheKeyFunc()); err != nil {
return nil, err
}
return repository.ModelToDomainModel(m)
}
func (repository *ArticleSecurityRepository) Delete(ctx context.Context, conn transaction.Conn, dm *domain.ArticleSecurity) (*domain.ArticleSecurity, error) {
var (
tx = conn.DB()
m = &models.ArticleSecurity{Id: dm.Identify().(int64)}
)
queryFunc := func() (interface{}, error) {
tx = tx.Where("id = ?", m.Id).Delete(m)
return m, tx.Error
}
if _, err := repository.Query(queryFunc, m.CacheKeyFunc()); err != nil {
return dm, err
}
return repository.ModelToDomainModel(m)
}
func (repository *ArticleSecurityRepository) FindOne(ctx context.Context, conn transaction.Conn, id int64) (*domain.ArticleSecurity, error) {
var (
err error
tx = conn.DB()
m = new(models.ArticleSecurity)
)
queryFunc := func() (interface{}, error) {
tx = tx.Model(m).Where("id = ?", id).First(m)
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil, domain.ErrNotFound
}
return m, tx.Error
}
cacheModel := new(models.ArticleSecurity)
cacheModel.Id = id
if err = repository.QueryCache(cacheModel.CacheKeyFunc, m, queryFunc); err != nil {
return nil, err
}
return repository.ModelToDomainModel(m)
}
func (repository *ArticleSecurityRepository) Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*domain.ArticleSecurity, error) {
var (
tx = conn.DB()
ms []*models.ArticleSecurity
dms = make([]*domain.ArticleSecurity, 0)
total int64
)
queryFunc := func() (interface{}, error) {
tx = tx.Model(&ms).Order("id desc")
if v, ok := queryOptions["authorName"]; ok {
tx.Where("author_name like ? ", fmt.Sprintf("%%%v%%", v))
}
if v, ok := queryOptions["contentType"]; ok {
tx.Where("content_type = ? ", v)
}
if v, ok := queryOptions["reviewStatus"]; ok {
tx.Where("review_status = ? ", v)
}
if v, ok := queryOptions["suggest"]; ok {
tx.Where("suggest = ? ", v)
}
if v, ok := queryOptions["beginTime"]; ok {
tx.Where("created_at >= ?", v)
}
if v, ok := queryOptions["endTime"]; ok {
tx.Where("created_at < ?", v)
}
if total, tx = transaction.PaginationAndCount(ctx, tx, queryOptions, &ms); tx.Error != nil {
return dms, tx.Error
}
return dms, nil
}
if _, err := repository.Query(queryFunc); err != nil {
return 0, nil, err
}
for _, item := range ms {
if dm, err := repository.ModelToDomainModel(item); err != nil {
return 0, dms, err
} else {
dms = append(dms, dm)
}
}
return total, dms, nil
}
func (repository *ArticleSecurityRepository) ModelToDomainModel(from *models.ArticleSecurity) (*domain.ArticleSecurity, error) {
to := &domain.ArticleSecurity{}
err := copier.Copy(to, from)
return to, err
}
func (repository *ArticleSecurityRepository) DomainModelToModel(from *domain.ArticleSecurity) (*models.ArticleSecurity, error) {
to := &models.ArticleSecurity{}
err := copier.Copy(to, from)
return to, err
}
func NewArticleSecurityRepository(cache *cache.CachedRepository) domain.ArticleSecurityRepository {
return &ArticleSecurityRepository{CachedRepository: cache}
}
... ...
package domain
import (
"context"
"github.com/silenceper/wechat/v2/miniprogram/security"
"gitlab.fjmaimaimai.com/allied-creation/sumifcc-discuss/cmd/discuss/interanl/pkg/db/transaction"
)
type ArticleSecurity struct {
Id int64 // 唯一标识
ContentKeyWords []string // 内容关键字
ContentType int `json:"contentType,omitempty"` // 内容类型 (1:文章 2:评论)
ContentId int64 `json:"contentId,omitempty"` // 内容ID
AuthorId int64 `json:"authorId,omitempty"` // 发布人
AuthorName string `json:"authorName,omitempty"` // 发布人
Reviewer int64 `json:"reviewer,omitempty"` // 审核人
ReviewStatus int `json:"reviewStatus,omitempty"` // 审核状态 1:待审核 2:通过 3:拒绝
Label string // 标签
Prob int // 分值
Suggest string // 建议 通过、风险、人工审核
Detail MsgCheckDetail `json:"detail,omitempty"`
AutoReviewAt int64 `json:"autoReviewAt,omitempty"` // 自动审核时间
AutoReviewErrorCode string `json:"autoReviewErrorCode,omitempty"` // 自动审核错误码
ReviewAt int64 `json:"reviewAt,omitempty"` // 审核时间(人工处置时间)
CreatedAt int64 `json:"createdAt,omitempty"`
UpdatedAt int64 `json:"updatedAt,omitempty"`
DeletedAt int64 `json:"deletedAt,omitempty"`
Version int `json:"version,omitempty"`
}
const (
ReviewStatusWait = iota + 1
ReviewStatusPass
ReviewStatusFail
)
const (
TypeArticle = 1
TypeComment = 2
)
type MsgCheckDetail security.MsgCheckResponse
type ArticleSecurityRepository interface {
Insert(ctx context.Context, conn transaction.Conn, dm *ArticleSecurity) (*ArticleSecurity, error)
Update(ctx context.Context, conn transaction.Conn, dm *ArticleSecurity) (*ArticleSecurity, error)
UpdateWithVersion(ctx context.Context, conn transaction.Conn, dm *ArticleSecurity) (*ArticleSecurity, error)
Delete(ctx context.Context, conn transaction.Conn, dm *ArticleSecurity) (*ArticleSecurity, error)
FindOne(ctx context.Context, conn transaction.Conn, id int64) (*ArticleSecurity, error)
Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*ArticleSecurity, error)
}
func (m *ArticleSecurity) Identify() interface{} {
if m.Id == 0 {
return nil
}
return m.Id
}
... ...
... ... @@ -63,3 +63,8 @@ CREATE TABLE `user_wechat`
`id` int(0) NOT NULL COMMENT '唯一标识',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `article_security` (
`id` int(0) NOT NULL COMMENT '唯一标识',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
\ No newline at end of file
... ...