Merge branch 'main' of git.opencomputing.cn:yumoqing/kboss

This commit is contained in:
hrx 2026-07-31 15:07:35 +08:00
commit c1bacfd965
9 changed files with 419 additions and 65 deletions

View File

@ -1,3 +1,34 @@
# 新版本表结构
CREATE TABLE `enterprise_news_article` (
`id` varchar(32) NOT NULL COMMENT '唯一标识符',
`domain_name` varchar(64) NOT NULL COMMENT '所属域名',
`title_zh` varchar(100) DEFAULT NULL COMMENT '中文标题',
`title_en` varchar(100) DEFAULT NULL COMMENT '英文标题',
`summary_zh` varchar(255) DEFAULT NULL COMMENT '中文摘要',
`summary_en` varchar(255) DEFAULT NULL COMMENT '英文摘要',
`article_type_zh` varchar(20) DEFAULT NULL COMMENT '中文文章类型',
`article_type_en` varchar(20) DEFAULT NULL COMMENT '英文文章类型',
`content_zh` text DEFAULT NULL COMMENT '中文正文',
`content_en` text DEFAULT NULL COMMENT '英文正文',
`cover_img_zh` varchar(255) DEFAULT NULL COMMENT '中文封面图片',
`cover_img_en` varchar(255) DEFAULT NULL COMMENT '英文封面图片',
`publish_time_zh` date DEFAULT NULL COMMENT '中文发布时间',
`publish_time_en` date DEFAULT NULL COMMENT '英文发布时间',
`title` varchar(100) DEFAULT NULL COMMENT '文章标题',
`article_type` varchar(20) DEFAULT NULL COMMENT '文章类型(企业动态/产品动态/行业洞察/活动资讯)',
`summary` varchar(255) DEFAULT NULL COMMENT '文章摘要',
`cover_img` varchar(255) DEFAULT NULL COMMENT '封面图片',
`content` text DEFAULT NULL COMMENT '文章正文',
`status` varchar(1) DEFAULT '0' COMMENT '状态0-草稿/1-已发布)',
`publish_time` date DEFAULT NULL COMMENT '发布时间',
`read_count` int(11) DEFAULT 0 COMMENT '阅读数据',
`del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志0-正常/1-已删除)',
`update_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
`create_at` timestamp NULL DEFAULT current_timestamp() COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='企业文章表';
# 旧版本表结构
CREATE TABLE `enterprise_news_article` ( CREATE TABLE `enterprise_news_article` (
`id` varchar(32) NOT NULL COMMENT '唯一标识符', `id` varchar(32) NOT NULL COMMENT '唯一标识符',
`domain_name` varchar(64) NOT NULL COMMENT '所属域名', `domain_name` varchar(64) NOT NULL COMMENT '所属域名',

View File

@ -11,7 +11,9 @@ async def front_news_detail(ns={}):
update_sql = """update enterprise_news_article set read_count = ifnull(read_count, 0) + 1 where id = '%s' and status = '1' and del_flg = '0';""" % ns.get('id') update_sql = """update enterprise_news_article set read_count = ifnull(read_count, 0) + 1 where id = '%s' and status = '1' and del_flg = '0';""" % ns.get('id')
await sor.sqlExe(update_sql, {}) await sor.sqlExe(update_sql, {})
search_sql = """ search_sql = """
select id, title, article_type, summary, cover_img, content, publish_time, read_count select id, title_zh, title_en, article_type_zh, article_type_en,
summary_zh, summary_en, cover_img_zh, cover_img_en,
content_zh, content_en, publish_time_zh, publish_time_en, read_count
from enterprise_news_article from enterprise_news_article
where id = '%s' and status = '1' and del_flg = '0'; where id = '%s' and status = '1' and del_flg = '0';
""" % ns.get('id') """ % ns.get('id')

View File

@ -13,10 +13,14 @@ async def front_news_search(ns={}):
offset = (current_page - 1) * page_size offset = (current_page - 1) * page_size
conditions = ["domain_name = '%s'" % domain_name, "status = '1'", "del_flg = '0'"] conditions = ["domain_name = '%s'" % domain_name, "status = '1'", "del_flg = '0'"]
if ns.get('article_type'): if ns.get('article_type_zh'):
conditions.append("article_type = '%s'" % ns.get('article_type')) conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
if ns.get('title'): if ns.get('article_type_en'):
conditions.append("title like '%%%%%s%%%%'" % ns.get('title')) conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
if ns.get('title_zh'):
conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
if ns.get('title_en'):
conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
where_clause = " and ".join(conditions) where_clause = " and ".join(conditions)
db = DBPools() db = DBPools()
@ -25,10 +29,15 @@ async def front_news_search(ns={}):
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count'] total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
search_sql = """ search_sql = """
select id, title, article_type, summary, cover_img, publish_time, read_count select id, title_zh, title_en, article_type_zh, article_type_en,
summary_zh, summary_en, cover_img_zh, cover_img_en,
publish_time_zh, publish_time_en, read_count
from enterprise_news_article from enterprise_news_article
where %s where %s
order by publish_time desc, update_time desc order by greatest(
ifnull(publish_time_zh, '1000-01-01'),
ifnull(publish_time_en, '1000-01-01')
) desc, update_time desc
limit %s offset %s; limit %s offset %s;
""" % (where_clause, page_size, offset) """ % (where_clause, page_size, offset)
result = await sor.sqlExe(search_sql, {}) result = await sor.sqlExe(search_sql, {})

View File

@ -4,16 +4,11 @@ async def news_article_add(ns={}):
'status': False, 'status': False,
'msg': '请传递url_link' 'msg': '请传递url_link'
} }
if not ns.get('title'): if not ns.get('title_zh'):
return { return {
'status': False, 'status': False,
'msg': '请传递标题' 'msg': '请传递中文标题'
} }
# if ns.get('article_type') not in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
# return {
# 'status': False,
# 'msg': '文章类型错误'
# }
domain_name = ns.get('url_link').split("//")[1].split("/")[0] domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name: if 'localhost' in domain_name:
@ -26,13 +21,19 @@ async def news_article_add(ns={}):
ns_dic = { ns_dic = {
'id': uuid(), 'id': uuid(),
'domain_name': domain_name, 'domain_name': domain_name,
'title': ns.get('title'), 'title_zh': ns.get('title_zh'),
'article_type': ns.get('article_type'), 'title_en': ns.get('title_en'),
'summary': ns.get('summary'), 'article_type_zh': ns.get('article_type_zh'),
'cover_img': ns.get('cover_img'), 'article_type_en': ns.get('article_type_en'),
'content': ns.get('content'), 'summary_zh': ns.get('summary_zh'),
'summary_en': ns.get('summary_en'),
'cover_img_zh': ns.get('cover_img_zh'),
'cover_img_en': ns.get('cover_img_en'),
'content_zh': ns.get('content_zh'),
'content_en': ns.get('content_en'),
'status': status, 'status': status,
'publish_time': ns.get('publish_time'), 'publish_time_zh': ns.get('publish_time_zh') or None,
'publish_time_en': ns.get('publish_time_en') or None,
'read_count': 0, 'read_count': 0,
'del_flg': '0' 'del_flg': '0'
} }
@ -41,8 +42,13 @@ async def news_article_add(ns={}):
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
try: try:
await sor.C('enterprise_news_article', ns_dic) await sor.C('enterprise_news_article', ns_dic)
if status == '1' and not ns.get('publish_time'): if status == '1':
publish_sql = """update enterprise_news_article set publish_time = current_timestamp() where id = '%s';""" % ns_dic.get('id') publish_sql = """
update enterprise_news_article
set publish_time_zh = ifnull(publish_time_zh, current_date()),
publish_time_en = ifnull(publish_time_en, current_date())
where id = '%s';
""" % ns_dic.get('id')
await sor.sqlExe(publish_sql, {}) await sor.sqlExe(publish_sql, {})
return { return {
'status': True, 'status': True,

View File

@ -8,11 +8,15 @@ async def news_article_publish(ns={}):
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
try: try:
publish_time = ns.get('publish_time') publish_time_zh = "'%s'" % ns.get('publish_time_zh') if ns.get('publish_time_zh') else "ifnull(publish_time_zh, current_date())"
if publish_time: publish_time_en = "'%s'" % ns.get('publish_time_en') if ns.get('publish_time_en') else "ifnull(publish_time_en, current_date())"
publish_sql = """update enterprise_news_article set status = '1', publish_time = '%s' where id = '%s' and del_flg = '0';""" % (publish_time, ns.get('id')) publish_sql = """
else: update enterprise_news_article
publish_sql = """update enterprise_news_article set status = '1' where id = '%s' and del_flg = '0';""" % ns.get('id') set status = '1',
publish_time_zh = %s,
publish_time_en = %s
where id = '%s' and del_flg = '0';
""" % (publish_time_zh, publish_time_en, ns.get('id'))
await sor.sqlExe(publish_sql, {}) await sor.sqlExe(publish_sql, {})
return { return {
'status': True, 'status': True,

View File

@ -14,10 +14,14 @@ async def news_article_search(ns={}):
conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"] conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
summary_conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"] summary_conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
if ns.get('title'): if ns.get('title_zh'):
conditions.append("title like '%%%%%s%%%%'" % ns.get('title')) conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
if ns.get('article_type'): if ns.get('title_en'):
conditions.append("article_type = '%s'" % ns.get('article_type')) conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
if ns.get('article_type_zh'):
conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
if ns.get('article_type_en'):
conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
if ns.get('status'): if ns.get('status'):
conditions.append("status = '%s'" % ns.get('status')) conditions.append("status = '%s'" % ns.get('status'))
where_clause = " and ".join(conditions) where_clause = " and ".join(conditions)
@ -27,30 +31,53 @@ async def news_article_search(ns={}):
try: try:
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count'] total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
summary_sql = """ summary_zh_sql = """
select article_type, count(*) as article_count, ifnull(sum(read_count), 0) as read_count select article_type_zh as article_type, count(*) as article_count,
ifnull(sum(read_count), 0) as read_count
from enterprise_news_article from enterprise_news_article
where %s where %s
group by article_type; and article_type_zh is not null and article_type_zh != ''
group by article_type_zh;
""" % " and ".join(summary_conditions) """ % " and ".join(summary_conditions)
summary_result = await sor.sqlExe(summary_sql, {}) summary_en_sql = """
summary_mapping = {} select article_type_en as article_type, count(*) as article_count,
for summary_dic in summary_result: ifnull(sum(read_count), 0) as read_count
summary_mapping[summary_dic.get('article_type')] = summary_dic from enterprise_news_article
article_type_summary = [] where %s
and article_type_en is not null and article_type_en != ''
group by article_type_en
order by article_type_en;
""" % " and ".join(summary_conditions)
summary_zh_result = await sor.sqlExe(summary_zh_sql, {})
summary_en_result = await sor.sqlExe(summary_en_sql, {})
summary_zh_mapping = {}
for summary_dic in summary_zh_result:
summary_zh_mapping[summary_dic.get('article_type')] = summary_dic
article_type_summary_zh = []
for article_type in ['企业动态', '产品动态', '行业洞察', '活动资讯']: for article_type in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
summary_dic = summary_mapping.get(article_type, {}) summary_dic = summary_zh_mapping.get(article_type, {})
article_type_summary.append({ article_type_summary_zh.append({
'article_type': article_type, 'article_type': article_type,
'article_count': summary_dic.get('article_count', 0), 'article_count': summary_dic.get('article_count', 0),
'read_count': summary_dic.get('read_count', 0) 'read_count': summary_dic.get('read_count', 0)
}) })
article_type_summary_en = []
for summary_dic in summary_en_result:
article_type_summary_en.append({
'article_type': summary_dic.get('article_type'),
'article_count': summary_dic.get('article_count', 0),
'read_count': summary_dic.get('read_count', 0)
})
search_sql = """ search_sql = """
select id, domain_name, title, article_type, summary, cover_img, status, content, select id, domain_name, title_zh, title_en, article_type_zh, article_type_en,
publish_time, read_count, update_time, create_at summary_zh, summary_en, cover_img_zh, cover_img_en, content_zh, content_en,
status, publish_time_zh, publish_time_en, read_count, update_time, create_at
from enterprise_news_article from enterprise_news_article
where %s where %s
order by publish_time desc, update_time desc order by greatest(
ifnull(publish_time_zh, '1000-01-01'),
ifnull(publish_time_en, '1000-01-01')
) desc, update_time desc
limit %s offset %s; limit %s offset %s;
""" % (where_clause, page_size, offset) """ % (where_clause, page_size, offset)
result = await sor.sqlExe(search_sql, {}) result = await sor.sqlExe(search_sql, {})
@ -61,7 +88,8 @@ async def news_article_search(ns={}):
'status': True, 'status': True,
'msg': 'search news article success', 'msg': 'search news article success',
'data': result, 'data': result,
'article_type_summary': article_type_summary, 'article_type_summary_zh': article_type_summary_zh,
'article_type_summary_en': article_type_summary_en,
'pagination': { 'pagination': {
'total': total_count, 'total': total_count,
'page_size': page_size, 'page_size': page_size,

View File

@ -8,21 +8,16 @@ async def news_article_update(ns={}):
ns_dic = { ns_dic = {
'id': ns.get('id') 'id': ns.get('id')
} }
if 'title' in ns: for field in [
ns_dic['title'] = ns.get('title') 'title_zh', 'title_en', 'article_type_zh', 'article_type_en',
if 'article_type' in ns: 'summary_zh', 'summary_en', 'content_zh', 'content_en',
# if ns.get('article_type') not in ['企业动态', '产品动态', '行业洞察', '活动资讯']: 'cover_img_zh', 'cover_img_en', 'publish_time_zh', 'publish_time_en'
# return { ]:
# 'status': False, if field in ns:
# 'msg': '文章类型错误' if field in ['publish_time_zh', 'publish_time_en']:
# } ns_dic[field] = ns.get(field) or None
ns_dic['article_type'] = ns.get('article_type') else:
if 'summary' in ns: ns_dic[field] = ns.get(field)
ns_dic['summary'] = ns.get('summary')
if 'cover_img' in ns:
ns_dic['cover_img'] = ns.get('cover_img')
if 'content' in ns:
ns_dic['content'] = ns.get('content')
if 'status' in ns: if 'status' in ns:
if ns.get('status') not in ['0', '1']: if ns.get('status') not in ['0', '1']:
return { return {
@ -30,15 +25,17 @@ async def news_article_update(ns={}):
'msg': '状态错误' 'msg': '状态错误'
} }
ns_dic['status'] = ns.get('status') ns_dic['status'] = ns.get('status')
if 'publish_time' in ns:
ns_dic['publish_time'] = ns.get('publish_time')
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
try: try:
await sor.U('enterprise_news_article', ns_dic) await sor.U('enterprise_news_article', ns_dic)
if ns.get('status') == '1' and not ns.get('publish_time'): if ns.get('status') == '1':
publish_sql = """update enterprise_news_article set publish_time = ifnull(publish_time, current_timestamp()) where id = '%s';""" % ns.get('id') publish_sql = """
update enterprise_news_article
set publish_time_zh = ifnull(publish_time_zh, current_date()),
publish_time_en = ifnull(publish_time_en, current_date())
where id = '%s';
""" % ns.get('id')
await sor.sqlExe(publish_sql, {}) await sor.sqlExe(publish_sql, {})
return { return {
'status': True, 'status': True,

View File

@ -0,0 +1,233 @@
# Bilingual Enterprise News Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Update enterprise news APIs to store, filter, and return Chinese and English article fields without maintaining legacy content fields.
**Architecture:** Keep the existing DSPY endpoint structure and database table. Add and update endpoints write the eight language-specific columns; search and detail endpoints return those columns directly. Shared publication, image, date, status, and read-count behavior remains unchanged.
**Tech Stack:** DSPY Python endpoints, async DBPools/sor database access, MySQL.
---
### Task 1: Add and update bilingual content
**Files:**
- Modify: `b/news/news_article_add.dspy`
- Modify: `b/news/news_article_update.dspy`
- [ ] **Step 1: Update add validation**
Replace the legacy `title` requirement with:
```python
if not ns.get('title_zh'):
return {
'status': False,
'msg': '请传递中文标题'
}
```
- [ ] **Step 2: Update add insert fields**
Write these fields in `ns_dic`:
```python
'title_zh': ns.get('title_zh'),
'title_en': ns.get('title_en'),
'article_type_zh': ns.get('article_type_zh'),
'article_type_en': ns.get('article_type_en'),
'summary_zh': ns.get('summary_zh'),
'summary_en': ns.get('summary_en'),
'content_zh': ns.get('content_zh'),
'content_en': ns.get('content_en'),
```
Do not write `title`, `article_type`, `summary`, or `content`.
- [ ] **Step 3: Update editable fields**
In `news_article_update.dspy`, loop through the eight bilingual fields and copy only keys present in `ns`:
```python
for field in [
'title_zh', 'title_en', 'article_type_zh', 'article_type_en',
'summary_zh', 'summary_en', 'content_zh', 'content_en'
]:
if field in ns:
ns_dic[field] = ns.get(field)
```
- [ ] **Step 4: Verify static diagnostics**
Run IDE lint diagnostics for both files. Expected: no new errors.
### Task 2: Update backend search and detail
**Files:**
- Modify: `b/news/news_article_search.dspy`
- Verify: `b/news/news_article_detail.dspy`
- [ ] **Step 1: Add bilingual filters**
Build optional conditions for exact type matching and fuzzy title matching:
```python
if ns.get('title_zh'):
conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
if ns.get('title_en'):
conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
if ns.get('article_type_zh'):
conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
if ns.get('article_type_en'):
conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
```
- [ ] **Step 2: Return bilingual list fields**
Select:
```sql
id, domain_name,
title_zh, title_en,
article_type_zh, article_type_en,
summary_zh, summary_en,
content_zh, content_en,
cover_img, status, publish_time, read_count, update_time, create_at
```
- [ ] **Step 3: Return bilingual type summaries**
Aggregate Chinese and English article types separately and return:
```python
'article_type_summary_zh': article_type_summary_zh,
'article_type_summary_en': article_type_summary_en,
```
Each item keeps `article_type`, `article_count`, and `read_count`.
- [ ] **Step 4: Verify backend detail**
`news_article_detail.dspy` already uses `select *`; confirm it returns the new columns without changing publication behavior.
- [ ] **Step 5: Verify static diagnostics**
Run IDE lint diagnostics for backend search and detail. Expected: no new errors.
### Task 3: Update frontend search and detail
**Files:**
- Modify: `b/news/front_news_search.dspy`
- Modify: `b/news/front_news_detail.dspy`
- [ ] **Step 1: Add bilingual frontend filters**
Support `title_zh`, `title_en`, `article_type_zh`, and `article_type_en` using the same matching rules as backend search.
- [ ] **Step 2: Return bilingual frontend list fields**
Select:
```sql
id, title_zh, title_en, article_type_zh, article_type_en,
summary_zh, summary_en, cover_img, publish_time, read_count
```
- [ ] **Step 3: Return bilingual frontend detail fields**
After incrementing `read_count`, select:
```sql
id, title_zh, title_en, article_type_zh, article_type_en,
summary_zh, summary_en, cover_img, content_zh, content_en,
publish_time, read_count
```
- [ ] **Step 4: Verify publication and read-count behavior**
Confirm both frontend queries retain `status = '1' AND del_flg = '0'`, and detail still increments `read_count` once.
- [ ] **Step 5: Verify static diagnostics**
Run IDE lint diagnostics for both frontend files. Expected: no new errors.
### Task 4: Final verification
**Files:**
- Verify: `b/news/news_article_add.dspy`
- Verify: `b/news/news_article_update.dspy`
- Verify: `b/news/news_article_search.dspy`
- Verify: `b/news/news_article_detail.dspy`
- Verify: `b/news/front_news_search.dspy`
- Verify: `b/news/front_news_detail.dspy`
- [ ] **Step 1: Check legacy-field removal**
Search the six endpoints for writes or selected output fields named exactly `title`, `article_type`, `summary`, or `content`. Expected: none except comments or compatibility-neutral code.
- [ ] **Step 2: Check diff scope**
Run:
```powershell
git diff -- b/news
```
Expected: only the requested bilingual endpoint changes plus the user's existing SQL schema update.
- [ ] **Step 3: Check whitespace**
Run:
```powershell
git diff --check -- b/news
```
Expected: no output and exit code 0.
### Task 5: Add bilingual cover images and publish times
**Files:**
- Modify: `b/news/news_article_add.dspy`
- Modify: `b/news/news_article_update.dspy`
- Modify: `b/news/news_article_publish.dspy`
- Modify: `b/news/news_article_search.dspy`
- Modify: `b/news/front_news_search.dspy`
- Modify: `b/news/front_news_detail.dspy`
- [ ] **Step 1: Replace shared write fields**
Use `cover_img_zh`, `cover_img_en`, `publish_time_zh`, and
`publish_time_en` in add and update. Do not write `cover_img` or
`publish_time`.
- [ ] **Step 2: Apply publication defaults**
When status becomes `1`, set each missing publish-time column with:
```sql
publish_time_zh = ifnull(publish_time_zh, current_date()),
publish_time_en = ifnull(publish_time_en, current_date())
```
- [ ] **Step 3: Update publish endpoint**
Accept both publish-time parameters and use `current_date()` for either
missing value while setting `status = '1'`.
- [ ] **Step 4: Update list and detail output**
Return both cover-image and publish-time columns. Sort lists by:
```sql
greatest(
ifnull(publish_time_zh, '1000-01-01'),
ifnull(publish_time_en, '1000-01-01')
) desc, update_time desc
```
- [ ] **Step 5: Verify**
Run static field-contract checks, IDE diagnostics, and
`git diff --check -- b/news`. Expected: all pass.

View File

@ -0,0 +1,44 @@
# 中英文企业动态接口设计
## 目标
企业文章支持运营同时录入中文和英文内容。接口直接使用
`enterprise_news_article` 表中的中英文字段,不再读写旧字段
`title``article_type``summary``content`
## 字段
- 中文:`title_zh``article_type_zh``summary_zh``content_zh`
- 英文:`title_en``article_type_en``summary_en``content_en`
- 中文扩展:`cover_img_zh``publish_time_zh`
- 英文扩展:`cover_img_en``publish_time_en`
- 共用:`status``read_count`
新增文章仅要求 `title_zh` 必填,其余中英文字段允许为空。
文章发布时,如果未传中英文发布时间,两个字段都默认当天日期。
## 接口调整
- `news_article_add.dspy`:接收并写入全部中英文字段。
- `news_article_update.dspy`:按传入字段更新中英文内容。
- `news_article_search.dspy`:支持中英文标题和类型筛选,返回全部中英文字段;类型汇总分别使用中英文类型。
- `news_article_detail.dspy`:返回全部中英文字段。
- `front_news_search.dspy`:支持中英文标题和类型筛选,返回全部中英文字段。
- `front_news_detail.dspy`:返回全部中英文字段,并保持阅读量加一。
- `news_article_publish.dspy`:支持分别设置中英文发布时间,未传时均默认当天。
下架、删除接口不涉及语言字段,不调整。
## 兼容边界
旧字段已由数据库移除非空约束,本次接口不再维护旧字段。调用方需要改为传入和读取中英文新字段。
旧字段 `cover_img``publish_time` 同样不再读写。列表按两个发布时间中的较晚日期倒序排列,再按更新时间倒序排列。
## 验证
- 新增时缺少 `title_zh` 返回校验错误。
- 新增、更新后详情能完整返回中英文字段。
- 后台和前端列表能按中英文标题、类型筛选。
- 新增、编辑、列表和详情能分别读写中英文封面及发布时间。
- 发布时未传中英文发布时间,两个字段均写入当天日期。
- 前端详情仍仅允许读取已发布文章,并正确增加阅读量。