From 5007fc9e24c6cd87ef8317e40ce5a096ed95c697 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 20 Jun 2025 12:35:40 +0800 Subject: [PATCH] . --- pkg/api/biz_api.go | 1 + pkg/controllers/controller_biz.go | 22 +++++++++++++ pkg/dal/core/core_biz.go | 36 +++++++++++++++++++++ pkg/dal/dao/news.go | 52 +++++++++++++++++++++++++++++++ pkg/proto/proto_biz.go | 8 ++--- pkg/routers/router_biz.go | 2 ++ 6 files changed, 117 insertions(+), 4 deletions(-) diff --git a/pkg/api/biz_api.go b/pkg/api/biz_api.go index 02dd445..9e3dab2 100644 --- a/pkg/api/biz_api.go +++ b/pkg/api/biz_api.go @@ -15,6 +15,7 @@ type BizApi interface { NewsDraftDelete(c *gin.Context) //草稿删除 NewsTag(c *gin.Context) //新闻打标签 NewsAsync(c *gin.Context) //异步同步数据 + NewsAsyncBatch(c *gin.Context) //一次性整批同步数据 NewsPullNew(c *gin.Context) //拉取新的数据 QaList(c *gin.Context) //Q&A列表 QaAdd(c *gin.Context) //Q&A新增 diff --git a/pkg/controllers/controller_biz.go b/pkg/controllers/controller_biz.go index 63f35db..79caa94 100644 --- a/pkg/controllers/controller_biz.go +++ b/pkg/controllers/controller_biz.go @@ -555,6 +555,28 @@ func (m *Controller) NewsAsync(c *gin.Context) { m.OK(c, resp, len(resp.List), total) } +func (m *Controller) NewsAsyncBatch(c *gin.Context) { + var req proto.NewsAsyncBatchReq + + if err := m.bindJSON(c, &req); err != nil { + log.Errorf("%s", err) + return + } + log.Infof("....................NewsAsyncBatch request: %+v", req) + ctx := sessions.GetContext(c) + var ok bool + if ctx != nil { + ok = m.CheckPrivilege(c, ctx, privilege.NewsAccess) + } + ok = ok + resp, total, code := m.BizCore.NewsAsyncBatch(ctx, &req, true) + if !code.Ok() { + m.Error(c, code) + return + } + m.OK(c, resp, len(resp.List), total) +} + func (m *Controller) NewsPullNew(c *gin.Context) { var req proto.NewsPullNewReq if err := m.bindJSON(c, &req); err != nil { diff --git a/pkg/dal/core/core_biz.go b/pkg/dal/core/core_biz.go index e8dc33a..d006870 100644 --- a/pkg/dal/core/core_biz.go +++ b/pkg/dal/core/core_biz.go @@ -911,6 +911,42 @@ func (m *BizCore) NewsAsync(ctx *itypes.Context, req *proto.NewsAsyncReq, needEx }, total, itypes.BizOK } +// NewsAsyncBatch:批量差异同步 +func (m *BizCore) NewsAsyncBatch(ctx *itypes.Context, req *proto.NewsAsyncBatchReq, needExtra bool) (resp *proto.NewsAsyncResp, total int64, code itypes.BizCode) { + const batchSize = 5000 // 防止 SQL 占位符过长,按需调整 + + if len(req.List) == 0 { + return &proto.NewsAsyncResp{List: []*models.NewsDO{}}, 0, itypes.BizOK + } + + var all []*models.NewsDO + + for start := 0; start < len(req.List); start += batchSize { + end := start + batchSize + if end > len(req.List) { + end = len(req.List) + } + + // ── ① 把这一块转成 DAO 参数 ── + pairs := make([]dao.OrgDigestPair, end-start) + for i, v := range req.List[start:end] { + pairs[i] = dao.OrgDigestPair{OrgId: v.Org_Id, Digest: v.Digest} + } + + // ── ② 一次 SQL 抓差异 ── + chunk, _, err := m.newsDAO.QueryAsyncBatch(pairs) + if err != nil { + return nil, 0, itypes.NewBizCodeDatabaseError(err.Error()) + } + + all = append(all, chunk...) + } + + return &proto.NewsAsyncResp{ + List: all, + }, int64(len(all)), itypes.BizOK +} + func (m *BizCore) NewsPullNew(ctx *itypes.Context, req *proto.NewsPullNewReq) (resp *proto.NewsPullNewResp, total int64, code itypes.BizCode) { dos, total, err := m.newsDAO.QueryPullNew(&dao.NewsPullNewCondition{ OrgIDs: req.OrgIDs, diff --git a/pkg/dal/dao/news.go b/pkg/dal/dao/news.go index 6641e4c..8ca1cfb 100644 --- a/pkg/dal/dao/news.go +++ b/pkg/dal/dao/news.go @@ -10,6 +10,12 @@ import ( "github.com/civet148/sqlca/v2" ) +// 客户端带来的本地状态 (org_id, digest) +type OrgDigestPair struct { + OrgId int64 + Digest string +} + type NewsAsyncCondition struct { Org_Id int64 Digest string @@ -393,6 +399,52 @@ func (dao *NewsDAO) QueryAsync(cond *NewsAsyncCondition) (dos []*models.NewsDO, return []*models.NewsDO{record}, 1, nil } +// 一次 SQL 抓取该批 org_id,对比 digest,返回需要下发的记录 +func (dao *NewsDAO) QueryAsyncBatch(pairs []OrgDigestPair) (dos []*models.NewsDO, total int64, err error) { + + if len(pairs) == 0 { + return []*models.NewsDO{}, 0, nil + } + + // ① 提取 org_id 列表,同时建映射表 clientDigest[org_id] = digest + orgIDs := make([]int64, 0, len(pairs)) + clientDigest := make(map[int64]string, len(pairs)) + for _, p := range pairs { + orgIDs = append(orgIDs, p.OrgId) + clientDigest[p.OrgId] = p.Digest + } + + // ② 一次 SQL:SELECT * FROM news WHERE org_id IN ( … ) + var rows []*models.NewsDO + // err 已在函数签名里声明 + _, err = dao.db. + Model(&rows). // 告诉 sqlca 目标对象是 []*models.NewsDO + Table(models.TableNameNews). + Where("org_id IN ?", orgIDs). + Query() // 用 Query 而不是 Find + if err != nil { + return nil, 0, err + } + + // ③ 在 Go 层比对 digest + for _, row := range rows { + cDigest := clientDigest[row.OrgId] // 客户端传来的 digest + + // 直接从 map[string]interface{} 里取 + var sDigest string + if d, ok := row.ExtraData["digest"].(string); ok { + sDigest = d + } + + // cDigest 为空 或 与服务器端不同,都需要下发 + if cDigest == "" || cDigest != sDigest { + dos = append(dos, row) + } + } + + return dos, int64(len(dos)), nil +} + func (dao *NewsDAO) QueryPullNew(cond *NewsPullNewCondition) (dos []*models.NewsDO, total int64, err error) { e := dao.db.Model(&dos). Table(models.TableNameNews). diff --git a/pkg/proto/proto_biz.go b/pkg/proto/proto_biz.go index 625e87a..ba8ac27 100644 --- a/pkg/proto/proto_biz.go +++ b/pkg/proto/proto_biz.go @@ -285,6 +285,10 @@ type NewsAsyncReq struct { Digest string `json:"digest"` } +type NewsAsyncBatchReq struct { + List []NewsAsyncReq `json:"list"` +} + type NewsAsyncResp struct { List []*models.NewsDO `json:"list"` } @@ -296,7 +300,3 @@ type NewsPullNewReq struct { type NewsPullNewResp struct { List []*models.NewsDO `json:"list"` } - -type NewsAsyncBatchReq struct { - List []NewsAsyncReq `json:"list"` -} diff --git a/pkg/routers/router_biz.go b/pkg/routers/router_biz.go index 56f7ce6..a1a3a9d 100644 --- a/pkg/routers/router_biz.go +++ b/pkg/routers/router_biz.go @@ -27,6 +27,7 @@ const ( RouterSubPathNewsDraftDelete = "/draft/delete" RouterSubPathNewsTag = "/tag" RouterSubPathNewsAsync = "/async" + RouterSubPathNewsAsyncBatch = "/asyncbatch" RouterSubPathNewsPullNew = "/pullnew" RouterSubPathQaList = "/list" RouterSubPathQaAdd = "/add" @@ -51,6 +52,7 @@ func InitRouterGroupBiz(r *gin.Engine, handlers api.BizApi) { groupNews.POST(RouterSubPathNewsList, handlers.NewsList) groupNews.POST(RouterSubPathNewsAsync, handlers.NewsAsync) + groupNews.POST(RouterSubPathNewsAsyncBatch, handlers.NewsAsyncBatch) groupNews.POST(RouterSubPathNewsPullNew, handlers.NewsPullNew) groupNews.Use(middleware.JWT()) //use JWT token middleware {