From 015570b685d41e550708f4b5546cd03253b7b167 Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Wed, 16 Jul 2025 16:38:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BA=86topic=E7=9A=84?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/models/__pycache__/tweet.cpython-312.pyc | Bin 9772 -> 9880 bytes api/models/tweet.py | 8 +- api/routers/__pycache__/tweet.cpython-312.pyc | Bin 11869 -> 14645 bytes api/routers/tweet.py | 163 ++++++++++++++---- .../database_service.cpython-312.pyc | Bin 21332 -> 26190 bytes api/services/database_service.py | 144 +++++++++++++++- 6 files changed, 278 insertions(+), 37 deletions(-) diff --git a/api/models/__pycache__/tweet.cpython-312.pyc b/api/models/__pycache__/tweet.cpython-312.pyc index ec8ebc4b08d57270dcd81a80ae86282b6fa073af..45e950e372b9e52d4bcd36cb4a6ffc67717b854d 100644 GIT binary patch delta 481 zcmZ4EGsBnfG%qg~0}wPOmuGNmY~j8V*$f|^2`IXM~_d5Q#pdW#f*geKqQZqDq{Lvum#4K3{z!Z*}479@X^&^pRCWg6{*f z*yI$h)%@bDT%Q<#M3Fkk9R0}`q$M|-a~m-;dQHyad(P$v6fIsod7rrY<~sflOq!a^ zr67MPKiReE>Dmd;cI+tS1#zEk*z#mY*OQ$Sp3Yud$_^5lJVhu#HWQ><3rMsuJm3+% zA!~m_PVs@P?G1VRTQY8w8HKwUlQ#DXe`A`QC#1tv6g#;~%$X|%sJs!RFKTi=pUz}v z@ikmYKyDF;sF=J{{2*fz*pX8tUNXw20tG`>vJ}OERD^?wcn}c*A`*aM!U}T1lF56e zmNCxVoFcuCnQcE%pxAP<9jn&lAog0{e_ZZJjo}ls! ZXj8dr437*W*96Zm3_$9O-Q@eKz5oTXiR%CW delta 326 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 P6{Hl{yl+n5xD)&v2lX3& delta 2481 zcmb_eZA@EL7(VCT+uPpT4_aDm%LNK_P)ga@`2JXMP^KGfRX2wlLvi+ApbT5QEx2is znaqgUROU%GqA}S&Sy)U-V={lj{>I*sxEV|(M3s$2uu-9%rtLU$g%0~|pizkO@ zk+h=qY9VT(9Ib++5sRb((IA2lbmIqa$}*kzbq zezkCc&W53Y#7Wi#m|YD)QIJZA$00_v=Xi5&?oB0blMG_TdMWyxWGZv$)tl2rhh!A1 zvTU0m<>#=SIc(Qy%IS*!O!u=zH*lhtz%ZCn;K|~t#8VXpvOWw@%v3}L1$3Ub+2C45 z5QG(BHmnS*hM2=33}t&5j4IJfg+}xP&%3xV*9|`9z0@%9Qm-ljnBv2{NLg_^E>Pz= z1_#0#im*OY7t~X0Zl}~?^(I6*hsa_gE!$Ct+Kg_h`R$spW)lXJ!%$)dlSxoZ5*%jM zqiMt1|KO@HH>Xf91#v-B{M#7+O>!zG_MxveRnb2+JVbG$6@8>@E6|*n5Pg#0D|t@5 zDWw=cMyC>`b@j-V*9~=OJa6xd+FDQ?1JDT8lAhf>986p>3G;}5mU5f|jD`zX`ghYNt-<&5=;EJ{N%y2PNFR?F3n#*pPTjS=|Yugj%eKVSs#?FMfYjMCZ-!#Y0O@5(^ z49sX^X6zir4aUKhU3(J7&PChh$=T=Tnvf@An^DD#owwD7+19zS`F$~ULyT#7bl(p0 z-SE+U9uyqNJS^FjsS5@zxYQ-B#;s?T>NM^GX4$N97c$GcHEtV&JXSBfh-#g8n<;!P zl~bya0h?zAxz1PV1*|9IFlYwnO?D8<&#nR32IQ704)s-+K@Od&c47RbR-n1}xacc_ z4?6c_L%D}QFM$IXJWeXb$3QI|=uV zH8_cd;Ex#YfWvODoglB4-3)9 z+k2Whc(${;O97U4z%H6usx@MHnSyQ(F4K_63eL^a%es0uq_0B-mant4n~OSZHMD+X z`p8>e2K&(m<(>HhB)iuL3}SE^R@;;=H^UL6vX>tBl0DKUStRhdNt&lvYqP-JT})ge;Ty_5Y}mydv^D7*csp78;Z@2c-c3{a$C}!^Dn&DQc>J z0m2$2HvHX6E+GCq2PQ(2oT7dHP)ZS+7$1{l^07@ZIx)1eEGS z;eBD|*yX8f{xd=XqA#1xwN>y4OlqxZnh3XzwzPr>IDnz_zLJO!Xh3;jn#!pAqCYhs Gg8u;4^hziI diff --git a/api/routers/tweet.py b/api/routers/tweet.py index 919c4f3..40d6fee 100644 --- a/api/routers/tweet.py +++ b/api/routers/tweet.py @@ -53,13 +53,83 @@ router = APIRouter( ) +def _resolve_ids_to_names_with_mapping(db_service: DatabaseService, + styleIds: Optional[List[int]] = None, + audienceIds: Optional[List[int]] = None, + scenicSpotIds: Optional[List[int]] = None, + productIds: Optional[List[int]] = None) -> tuple: + """ + 将ID列表转换为名称列表,并返回ID到名称的映射关系 + + Args: + db_service: 数据库服务 + styleIds: 风格ID列表 + audienceIds: 受众ID列表 + scenicSpotIds: 景区ID列表 + productIds: 产品ID列表 + + Returns: + (styles, audiences, scenic_spots, products, id_name_mappings) 名称列表和映射关系元组 + """ + styles = [] + audiences = [] + scenic_spots = [] + products = [] + + # 建立ID到名称的映射字典 + id_name_mappings = { + 'style_mapping': {}, # {name: id} + 'audience_mapping': {}, # {name: id} + 'scenic_spot_mapping': {}, # {name: id} + 'product_mapping': {} # {name: id} + } + + # 如果数据库服务不可用,返回空列表 + if not db_service or not db_service.is_available(): + logger.warning("数据库服务不可用,无法解析ID") + return styles, audiences, scenic_spots, products, id_name_mappings + + # 解析风格ID + if styleIds: + style_records = db_service.get_styles_by_ids(styleIds) + for record in style_records: + style_name = record['styleName'] + styles.append(style_name) + id_name_mappings['style_mapping'][style_name] = record['id'] + + # 解析受众ID + if audienceIds: + audience_records = db_service.get_audiences_by_ids(audienceIds) + for record in audience_records: + audience_name = record['audienceName'] + audiences.append(audience_name) + id_name_mappings['audience_mapping'][audience_name] = record['id'] + + # 解析景区ID + if scenicSpotIds: + spot_records = db_service.get_scenic_spots_by_ids(scenicSpotIds) + for record in spot_records: + spot_name = record['name'] + scenic_spots.append(spot_name) + id_name_mappings['scenic_spot_mapping'][spot_name] = record['id'] + + # 解析产品ID + if productIds: + product_records = db_service.get_products_by_ids(productIds) + for record in product_records: + product_name = record['name'] + products.append(product_name) + id_name_mappings['product_mapping'][product_name] = record['id'] + + return styles, audiences, scenic_spots, products, id_name_mappings + def _resolve_ids_to_names(db_service: DatabaseService, styleIds: Optional[List[int]] = None, audienceIds: Optional[List[int]] = None, scenicSpotIds: Optional[List[int]] = None, productIds: Optional[List[int]] = None) -> tuple: """ - 将ID列表转换为名称列表 + 将ID列表转换为名称列表(保持向后兼容) Args: db_service: 数据库服务 @@ -71,39 +141,61 @@ def _resolve_ids_to_names(db_service: DatabaseService, Returns: (styles, audiences, scenic_spots, products) 名称列表元组 """ - styles = [] - audiences = [] - scenic_spots = [] - products = [] - - # 如果数据库服务不可用,返回空列表 - if not db_service or not db_service.is_available(): - logger.warning("数据库服务不可用,无法解析ID") - return styles, audiences, scenic_spots, products - - # 解析风格ID - if styleIds: - style_records = db_service.get_styles_by_ids(styleIds) - styles = [record['styleName'] for record in style_records] - - # 解析受众ID - if audienceIds: - audience_records = db_service.get_audiences_by_ids(audienceIds) - audiences = [record['audienceName'] for record in audience_records] - - # 解析景区ID - if scenicSpotIds: - spot_records = db_service.get_scenic_spots_by_ids(scenicSpotIds) - scenic_spots = [record['name'] for record in spot_records] - - # 解析产品ID - if productIds: - product_records = db_service.get_products_by_ids(productIds) - products = [record['name'] for record in product_records] - + styles, audiences, scenic_spots, products, _ = _resolve_ids_to_names_with_mapping( + db_service, styleIds, audienceIds, scenicSpotIds, productIds + ) return styles, audiences, scenic_spots, products +def _add_ids_to_topics(topics: List[Dict[str, Any]], id_name_mappings: Dict[str, Dict[str, int]]) -> List[Dict[str, Any]]: + """ + 为每个topic添加对应的ID字段 + + Args: + topics: 生成的选题列表 + id_name_mappings: 名称到ID的映射字典 + + Returns: + 包含ID字段的选题列表 + """ + enriched_topics = [] + + for topic in topics: + # 复制原topic + enriched_topic = topic.copy() + + # 添加ID字段 + enriched_topic['styleIds'] = [] + enriched_topic['audienceIds'] = [] + enriched_topic['scenicSpotIds'] = [] + enriched_topic['productIds'] = [] + + # 根据topic中的name查找对应的ID + if 'style' in topic and topic['style']: + style_name = topic['style'] + if style_name in id_name_mappings['style_mapping']: + enriched_topic['styleIds'] = [id_name_mappings['style_mapping'][style_name]] + + if 'targetAudience' in topic and topic['targetAudience']: + audience_name = topic['targetAudience'] + if audience_name in id_name_mappings['audience_mapping']: + enriched_topic['audienceIds'] = [id_name_mappings['audience_mapping'][audience_name]] + + if 'object' in topic and topic['object']: + spot_name = topic['object'] + if spot_name in id_name_mappings['scenic_spot_mapping']: + enriched_topic['scenicSpotIds'] = [id_name_mappings['scenic_spot_mapping'][spot_name]] + + if 'product' in topic and topic['product']: + product_name = topic['product'] + if product_name in id_name_mappings['product_mapping']: + enriched_topic['productIds'] = [id_name_mappings['product_mapping'][product_name]] + + enriched_topics.append(enriched_topic) + + return enriched_topics + + @router.post("/topics", response_model=TopicResponse, summary="生成选题") async def generate_topics( request: TopicRequest, @@ -121,8 +213,8 @@ async def generate_topics( - **productIds**: 产品ID列表 """ try: - # 将ID转换为名称 - styles, audiences, scenic_spots, products = _resolve_ids_to_names( + # 将ID转换为名称,并获取映射关系 + styles, audiences, scenic_spots, products, id_name_mappings = _resolve_ids_to_names_with_mapping( db_service, request.styleIds, request.audienceIds, @@ -139,9 +231,12 @@ async def generate_topics( products=products ) + # 为topics添加ID字段 + enriched_topics = _add_ids_to_topics(topics, id_name_mappings) + return TopicResponse( requestId=request_id, - topics=topics + topics=enriched_topics ) except Exception as e: logger.error(f"生成选题失败: {e}", exc_info=True) diff --git a/api/services/__pycache__/database_service.cpython-312.pyc b/api/services/__pycache__/database_service.cpython-312.pyc index 207d00b1864e0bd91ee0753e4e052664f91f40d8..7f6e3a441e3f84ca46b7dd8536dcfb546aa7103c 100644 GIT binary patch delta 2144 zcmb`HUrbw79LIZF+R{?s)tRZ5zeR z5EC9WaU1g_L(OpP4`VaNUBZ%$WpTB8n}5=WK44?A7h3U+h!4iScusF`y_D9(gqz&o zJ@o4DO#oGX!TA2cG0MLMRibi$KjPitu zA>;7t3}j0g6{};*%y`A=62>u8a^iVrPo7$fRV(AnQ}<%k#?EH2RWdh0^OmneuE2qas#(GURvIff_w>RDC zA!EP|rJ3kzePd}Z@uxj)({5jHf3No_3^K59_{advSck_C_YU_8<3aot&;$2|VAo&| z42F8xaW+hp7nAZJ6VYR9>C0cIKlv{8F)!@wh7luCuPkodxh57%-B{kZyXc0uwOl2h zIq%Z7Ss&`tL0p_Hv`Et-Dngv(O~AYP>c@>=5lWD;}Gb zc$_RBjW9uWBCwm_ALFul;8Pdo)*sGE!3#6#7WYNKgbu7O4j}lqQqL zLLE)2EUM}DTw4ieX?A7yG|j&KuO<_!Z6hdp+exqlAD1@?LbH(|6n#tZ)nR-!Qgp$5 zbVF-;>uhClbcgu;aeG&S>L%!5ada;E;Ghel&yFlnU*SK_T_a(Ugj=X>$RR%|Ul|#C z2Bx=UYGuQw(V?Vog*IjQ!M`6>EPuE~hMRqW2)>Mde=Kqr z2~EY#cbGYR*fg2^{4`}IOC~L!d?iVi=^x|f&q==-g}*ScvuYFx1C1-<1`?XWMRO-7 eq!uyGntVA`opI^p->EMA>FkUepBO+SSRnvWmL^~T diff --git a/api/services/database_service.py b/api/services/database_service.py index 16039d2..c13301d 100644 --- a/api/services/database_service.py +++ b/api/services/database_service.py @@ -505,4 +505,146 @@ class DatabaseService: Returns: 数据库是否可用 """ - return self.db_pool is not None \ No newline at end of file + return self.db_pool is not None + + # 名称到ID的反向查询方法 + + def get_style_id_by_name(self, style_name: str) -> Optional[int]: + """ + 根据风格名称获取风格ID + + Args: + style_name: 风格名称 + + Returns: + 风格ID,如果未找到则返回None + """ + if not self.db_pool: + logger.error("数据库连接池未初始化") + return None + + try: + conn = self.db_pool.get_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id FROM style WHERE styleName = %s AND isDelete = 0", + (style_name,) + ) + result = cursor.fetchone() + cursor.close() + conn.close() + + if result: + return result['id'] + else: + logger.warning(f"未找到风格: {style_name}") + return None + + except Exception as e: + logger.error(f"查询风格ID失败: {e}") + return None + + def get_audience_id_by_name(self, audience_name: str) -> Optional[int]: + """ + 根据受众名称获取受众ID + + Args: + audience_name: 受众名称 + + Returns: + 受众ID,如果未找到则返回None + """ + if not self.db_pool: + logger.error("数据库连接池未初始化") + return None + + try: + conn = self.db_pool.get_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id FROM targetAudience WHERE audienceName = %s AND isDelete = 0", + (audience_name,) + ) + result = cursor.fetchone() + cursor.close() + conn.close() + + if result: + return result['id'] + else: + logger.warning(f"未找到受众: {audience_name}") + return None + + except Exception as e: + logger.error(f"查询受众ID失败: {e}") + return None + + def get_scenic_spot_id_by_name(self, spot_name: str) -> Optional[int]: + """ + 根据景区名称获取景区ID + + Args: + spot_name: 景区名称 + + Returns: + 景区ID,如果未找到则返回None + """ + if not self.db_pool: + logger.error("数据库连接池未初始化") + return None + + try: + conn = self.db_pool.get_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id FROM scenicSpot WHERE name = %s AND isDelete = 0", + (spot_name,) + ) + result = cursor.fetchone() + cursor.close() + conn.close() + + if result: + return result['id'] + else: + logger.warning(f"未找到景区: {spot_name}") + return None + + except Exception as e: + logger.error(f"查询景区ID失败: {e}") + return None + + def get_product_id_by_name(self, product_name: str) -> Optional[int]: + """ + 根据产品名称获取产品ID + + Args: + product_name: 产品名称 + + Returns: + 产品ID,如果未找到则返回None + """ + if not self.db_pool: + logger.error("数据库连接池未初始化") + return None + + try: + conn = self.db_pool.get_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT id FROM product WHERE productName = %s AND isDelete = 0", + (product_name,) + ) + result = cursor.fetchone() + cursor.close() + conn.close() + + if result: + return result['id'] + else: + logger.warning(f"未找到产品: {product_name}") + return None + + except Exception as e: + logger.error(f"查询产品ID失败: {e}") + return None \ No newline at end of file