diff --git a/b/news/enterprise_news_article.sql b/b/news/enterprise_news_article.sql index 04c9193..e6fea1f 100644 --- a/b/news/enterprise_news_article.sql +++ b/b/news/enterprise_news_article.sql @@ -1,3 +1,30 @@ +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 '英文正文', + `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` ( `id` varchar(32) NOT NULL COMMENT '唯一标识符', `domain_name` varchar(64) NOT NULL COMMENT '所属域名', diff --git a/b/news/front_news_detail.dspy b/b/news/front_news_detail.dspy index e87c781..bd7cbe4 100644 --- a/b/news/front_news_detail.dspy +++ b/b/news/front_news_detail.dspy @@ -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') await sor.sqlExe(update_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, content_zh, content_en, + publish_time, read_count from enterprise_news_article where id = '%s' and status = '1' and del_flg = '0'; """ % ns.get('id') diff --git a/b/news/front_news_search.dspy b/b/news/front_news_search.dspy index 67d05f4..19b1035 100644 --- a/b/news/front_news_search.dspy +++ b/b/news/front_news_search.dspy @@ -13,10 +13,14 @@ async def front_news_search(ns={}): offset = (current_page - 1) * page_size conditions = ["domain_name = '%s'" % domain_name, "status = '1'", "del_flg = '0'"] - if ns.get('article_type'): - conditions.append("article_type = '%s'" % ns.get('article_type')) - if ns.get('title'): - conditions.append("title like '%%%%%s%%%%'" % ns.get('title')) + 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('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) db = DBPools() @@ -25,7 +29,8 @@ async def front_news_search(ns={}): 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'] 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, publish_time, read_count from enterprise_news_article where %s order by publish_time desc, update_time desc diff --git a/b/news/news_article_add.dspy b/b/news/news_article_add.dspy index cd6a313..cf4b4d3 100644 --- a/b/news/news_article_add.dspy +++ b/b/news/news_article_add.dspy @@ -4,16 +4,11 @@ async def news_article_add(ns={}): 'status': False, 'msg': '请传递url_link' } - if not ns.get('title'): + if not ns.get('title_zh'): return { '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] if 'localhost' in domain_name: @@ -26,11 +21,15 @@ async def news_article_add(ns={}): ns_dic = { 'id': uuid(), 'domain_name': domain_name, - 'title': ns.get('title'), - 'article_type': ns.get('article_type'), - 'summary': ns.get('summary'), + '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'), 'cover_img': ns.get('cover_img'), - 'content': ns.get('content'), + 'content_zh': ns.get('content_zh'), + 'content_en': ns.get('content_en'), 'status': status, 'publish_time': ns.get('publish_time'), 'read_count': 0, diff --git a/b/news/news_article_search.dspy b/b/news/news_article_search.dspy index de14ded..72ee9f4 100644 --- a/b/news/news_article_search.dspy +++ b/b/news/news_article_search.dspy @@ -14,10 +14,14 @@ async def news_article_search(ns={}): conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"] summary_conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"] - if ns.get('title'): - conditions.append("title like '%%%%%s%%%%'" % ns.get('title')) - if ns.get('article_type'): - conditions.append("article_type = '%s'" % ns.get('article_type')) + 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')) if ns.get('status'): conditions.append("status = '%s'" % ns.get('status')) where_clause = " and ".join(conditions) @@ -27,26 +31,46 @@ async def news_article_search(ns={}): try: 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'] - summary_sql = """ - select article_type, count(*) as article_count, ifnull(sum(read_count), 0) as read_count + summary_zh_sql = """ + select article_type_zh as article_type, count(*) as article_count, + ifnull(sum(read_count), 0) as read_count from enterprise_news_article 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) - summary_result = await sor.sqlExe(summary_sql, {}) - summary_mapping = {} - for summary_dic in summary_result: - summary_mapping[summary_dic.get('article_type')] = summary_dic - article_type_summary = [] + summary_en_sql = """ + select article_type_en as article_type, count(*) as article_count, + ifnull(sum(read_count), 0) as read_count + from enterprise_news_article + 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 ['企业动态', '产品动态', '行业洞察', '活动资讯']: - summary_dic = summary_mapping.get(article_type, {}) - article_type_summary.append({ + summary_dic = summary_zh_mapping.get(article_type, {}) + article_type_summary_zh.append({ 'article_type': article_type, 'article_count': summary_dic.get('article_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 = """ - 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, + summary_zh, summary_en, cover_img, content_zh, content_en, status, publish_time, read_count, update_time, create_at from enterprise_news_article where %s @@ -61,7 +85,8 @@ async def news_article_search(ns={}): 'status': True, 'msg': 'search news article success', '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': { 'total': total_count, 'page_size': page_size, diff --git a/b/news/news_article_update.dspy b/b/news/news_article_update.dspy index 1977efc..a8eac0c 100644 --- a/b/news/news_article_update.dspy +++ b/b/news/news_article_update.dspy @@ -8,21 +8,14 @@ async def news_article_update(ns={}): ns_dic = { 'id': ns.get('id') } - if 'title' in ns: - ns_dic['title'] = ns.get('title') - if 'article_type' in ns: - # if ns.get('article_type') not in ['企业动态', '产品动态', '行业洞察', '活动资讯']: - # return { - # 'status': False, - # 'msg': '文章类型错误' - # } - ns_dic['article_type'] = ns.get('article_type') - if 'summary' in ns: - ns_dic['summary'] = ns.get('summary') + 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) 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 ns.get('status') not in ['0', '1']: return { diff --git a/docs/superpowers/plans/2026-07-29-bilingual-enterprise-news.md b/docs/superpowers/plans/2026-07-29-bilingual-enterprise-news.md new file mode 100644 index 0000000..90725ec --- /dev/null +++ b/docs/superpowers/plans/2026-07-29-bilingual-enterprise-news.md @@ -0,0 +1,187 @@ +# 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.