Skip to content

Commit

Permalink
refactor:重构项目
Browse files Browse the repository at this point in the history
  • Loading branch information
Rockytkg committed Dec 6, 2024
1 parent 38e557a commit cfb39bc
Show file tree
Hide file tree
Showing 12 changed files with 929 additions and 675 deletions.
103 changes: 103 additions & 0 deletions coreApi/AiServiceClient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import logging
import time
from typing import Dict, Any

import requests
from requests.exceptions import RequestException

logger = logging.getLogger(__name__)


def generate_article(
config: Any,
title: str,
job_info: Dict[str, Any],
count: int = 500,
max_retries: int = 3,
retry_delay: int = 1,
) -> str:
"""
生成日报、周报、月报。
Args:
config (Any): 配置管理器,负责提供所需的 API 配置信息。
title (str): 文章标题。
job_info (Dict[str, Any]): 包含工作相关信息的字典。
count (int): 文章字数下限,默认为 500。
max_retries (int): 最大重试次数,默认为 3。
retry_delay (int): 每次重试的延迟时间(秒),默认为 1。
Returns:
str: 返回生成的文章内容。
Raises:
ValueError: 如果达到最大重试次数或发生解析错误时。
"""
# 准备请求头和 API URL
headers = {
"Authorization": f"Bearer {config.get_value('config.ai.apikey')}",
}
api_url = config.get_value("config.ai.apiUrl").rstrip("/") + "/v1/chat/completions"

# 动态生成系统提示词,支持更灵活的扩展
system_prompt = (
f"根据用户提供的信息撰写一篇文章,内容流畅且符合中文语法规范,"
f"不得使用 Markdown 语法,字数不少于 {count} 字。"
f"文章需与职位描述相关,并符合以下模板:"
f"\n\n模板:\n实习地点:xxxx\n\n工作内容:\n\nxxxxxx\n\n工作总结:\n\nxxxxxx\n\n"
f"遇到问题:\n\nxxxxxx\n\n自我评价:\n\nxxxxxx"
)

# 准备请求载荷
data = {
"model": config.get_value("config.ai.model"),
"messages": [
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": (
f"相关资料:报告标题:{title},工作地点:{job_info.get('jobAddress', '未知')}; "
f"公司名:{job_info.get('practiceCompanyEntity', {}).get('companyName', '未知')}; "
f"岗位职责:{job_info.get('quartersIntroduce', '未提供')}; "
f"公司所属行业:{job_info.get('practiceCompanyEntity', {}).get('tradeValue', '未提供')}"
),
},
],
}

# 重试逻辑
for attempt in range(max_retries):
try:
logger.info(f"第 {attempt + 1} 次尝试生成文章")
response = requests.post(
url=api_url, headers=headers, json=data, timeout=30
)
response.raise_for_status()

# 解析响应内容
content = (
response.json()
.get("choices", [])[0]
.get("message", {})
.get("content", "")
)
if not content.strip():
raise ValueError("AI 返回的内容为空或格式不正确")

logger.info("文章生成成功")
return content
except RequestException as e:
logger.warning(f"网络请求错误(第 {attempt + 1} 次尝试): {e}")
if attempt == max_retries - 1:
logger.error(f"达到最大重试次数。最后一次错误: {e}")
raise ValueError(f"生成文章失败,达到最大重试次数: {e}")
time.sleep(retry_delay)
except (KeyError, IndexError) as e:
logger.error(f"解析响应时发生错误: {e}")
raise ValueError(f"解析响应时发生错误: {e}")
except Exception as e:
logger.error(f"未知错误(第 {attempt + 1} 次尝试): {e}")
raise ValueError(f"生成文章失败,未知错误: {e}")

# 若所有尝试均失败,返回提示
raise ValueError("文章生成失败,所有重试均未成功")
136 changes: 136 additions & 0 deletions coreApi/FileUploadApi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import requests
import time
import logging
from typing import List


logger = logging.getLogger(__name__)


def build_upload_key(snowFlakeId: str, userId: str) -> str:
"""
生成唯一的文件上传路径。
根据传入的雪花ID和用户ID,生成一个唯一的文件上传路径。
路径格式为:upload/{snowFlakeId}/{当前日期}/report/{userId}_{当前时间戳}.jpg
Args:
snowFlakeId (str): 用于标识文件唯一性的雪花ID。
userId (str): 用于标识文件所有者的用户ID。
Returns:
str: 生成的文件上传路径。
"""
return (
f"upload/{snowFlakeId}"
f"/{time.strftime('%Y-%m-%d', time.localtime())}"
f"/report/{userId}_{int(time.time() * 1000000)}.jpg"
)


def upload_image(
url: str,
headers: dict,
image_data: bytes,
token: str,
key: str,
max_retries: int = 3,
retry_delay: int = 5,
) -> str:
"""
上传单张图片并处理错误。
上传图片到服务器,并返回成功上传的图片标识符。如果上传失败,函数将重试指定的次数,每次重试之间会有指数增长的延迟。
Args:
url (str): 上传图片的目标URL。
headers (dict): 请求头信息。
image_data (bytes): 要上传的图片数据。
token (str): 用于身份验证的令牌。
key (str): 上传图片的唯一标识符。
max_retries (int): 最大重试次数,默认为3次。
retry_delay (int): 初始重试延迟时间(秒),默认为5秒。每次重试时,延迟时间会指数增长。
Returns:
str: 成功上传的图片标识符(去除前缀 "upload/")。如果上传失败且达到最大重试次数,则抛出 ValueError 异常。
Raises:
ValueError: 如果上传失败且达到最大重试次数,则抛出此异常。
"""
data = {
"token": token,
"key": key,
"x-qn-meta-fname": f"{int(time.time() * 1000)}.jpg",
}

files = {"file": (key, image_data, "application/octet-stream")}

for attempt in range(max_retries):
try:
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status() # 如果响应状态不是200,将引发HTTPError异常

# 解析响应中的 key
response_data = response.json()
if "key" in response_data:
return response_data["key"].replace("upload/", "")
else:
logger.warning("上传成功,但响应中没有key字段")
return ""
except requests.exceptions.RequestException as e:
logger.error(f"上传失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
# 指数回退,重试前等待一段时间
wait_time = retry_delay * (
2**attempt
) # 每次重试时延长等待时间(指数回退)
logger.info(f"等待 {wait_time} 秒后重试...")
time.sleep(wait_time)
else:
logger.error(f"上传失败,已达到最大重试次数 {max_retries}")
raise ValueError(f"上传失败,已达到最大重试次数 {max_retries}")


def upload(
token: str,
snowFlakeId: str,
userId: str,
images: List[bytes],
) -> str:
"""
上传图片(支持一次性上传多张图片)
上传图片到服务器,并返回成功上传的图片链接,链接之间用逗号分隔。
Args:
token (str): 上传文件的认证令牌。
snowFlakeId (str): 组织ID,用于标识上传的唯一性。
userId (str): 用户ID,用于标识上传者。
images (List[bytes]): 图片的二进制数据列表。
Returns:
str: 成功上传的图片链接,用逗号分隔。
"""
url = "https://up.qiniup.com/"
headers = {
"host": "up.qiniup.com",
"accept-encoding": "gzip",
"user-agent": "Dart / 2.17(dart:io)",
}

successful_keys = []

for image_data in images:
key = build_upload_key(snowFlakeId, userId)

try:
# 上传图片并获取上传后的 key
uploaded_key = upload_image(url, headers, image_data, token, key)

if uploaded_key:
successful_keys.append(uploaded_key)

except Exception as e:
logger.error(f"图片上传失败:{str(e)}")

return ",".join(successful_keys)
Loading

0 comments on commit cfb39bc

Please sign in to comment.