yaoqifa commited on
Commit
e5d9507
·
verified ·
1 Parent(s): 27cc849

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -72
app.py CHANGED
@@ -16,13 +16,23 @@ from pydantic import BaseModel
16
  import uvicorn
17
 
18
  # ==========================================
19
- # 1. 静态配置 (不要在这里读环境变量)
20
  # ==========================================
21
 
22
- # 并发限制
 
 
 
 
 
 
 
 
 
 
23
  CONCURRENCY_LIMITER = asyncio.Semaphore(3)
24
 
25
- # 基础分辨率
26
  AR_BASES = {
27
  "1:1 (SDXL标准 1024)": (1024, 1024),
28
  "16:9 (SDXL标准 768p)": (1344, 768),
@@ -36,92 +46,76 @@ SCALE_MULTIPLIERS = {
36
  "1k": 1.0, "2k": 1.5, "4k": 2.0
37
  }
38
 
39
- # 本地敏感词 (第一道防线)
40
  SENSITIVE_WORDS = [
41
  "nsfw", "nude", "naked", "sex", "porn", "hentai",
42
  "裸", "色情", "全裸", "blood", "kill", "murder",
43
- "血腥", "尸体", "毒品", "杀"
44
  ]
45
 
46
  # ==========================================
47
- # 2. 百度审核模块 (动态读取环境变量)
48
  # ==========================================
49
 
50
  # 全局缓存 Access Token
51
  _BAIDU_TOKEN_CACHE = {"token": None, "expires_at": 0}
52
 
53
  async def get_baidu_access_token():
54
- """获取百度 Access Token (运行时从环境变量读取)"""
55
-
56
- # --- 关键:运行时动态读取 ---
57
- ak = os.getenv("BAIDU_API_KEY", "").strip()
58
- sk = os.getenv("BAIDU_SECRET_KEY", "").strip()
59
-
60
- # --- 调试日志 (只打印一次) ---
61
- if not hasattr(get_baidu_access_token, "debug_printed"):
62
- print(f"\n🔍 [Debug] 正在尝试读取环境变量:")
63
- print(f" - BAIDU_API_KEY: {'✅ 已配置' if ak else '❌ 未配置'}")
64
- print(f" - BAIDU_SECRET_KEY: {'✅ 已配置' if sk else '❌ 未配置'}")
65
- get_baidu_access_token.debug_printed = True
66
-
67
- # 如果没配置,直接返回 None
68
- if not ak or not sk:
69
  return None
70
 
71
- # 检查缓存
72
  now = time.time()
73
  if _BAIDU_TOKEN_CACHE["token"] and now < _BAIDU_TOKEN_CACHE["expires_at"]:
74
  return _BAIDU_TOKEN_CACHE["token"]
75
 
76
- # 请求百度
77
  url = "https://aip.baidubce.com/oauth/2.0/token"
78
  params = {
79
  "grant_type": "client_credentials",
80
- "client_id": ak,
81
- "client_secret": sk
82
  }
83
 
84
  try:
85
  async with httpx.AsyncClient(timeout=10) as client:
86
  resp = await client.post(url, params=params)
87
  data = resp.json()
88
-
89
- if "error" in data:
90
- print(f"❌ [Baidu Error] Token 获取失败: {data.get('error_description')}")
91
- return None
92
-
93
  token = data.get("access_token")
94
  if token:
95
  _BAIDU_TOKEN_CACHE["token"] = token
96
- _BAIDU_TOKEN_CACHE["expires_at"] = now + (20 * 24 * 3600) # 缓存20天
97
  return token
98
  else:
 
99
  return None
100
  except Exception as e:
101
- print(f"⚠️ [System] 百度网络错误: {e}")
102
  return None
103
 
104
  async def check_baidu_text_censor(text):
105
- """审核文本"""
106
  token = await get_baidu_access_token()
107
  if not token:
108
- # 没Token就放行,保证服务可用
109
  return False, None
110
 
111
  url = f"https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={token}"
112
- headers = {'Content-Type': 'application/x-www-form-urlencoded'}
113
  data = {'text': text}
114
 
115
  try:
116
  async with httpx.AsyncClient(timeout=10) as client:
117
- resp = await client.post(url, data=data, headers=headers)
118
  result = resp.json()
119
 
120
  # conclusionType: 1.合规 2.不合规 3.疑似 4.审核失败
121
  conclusion_type = result.get('conclusionType')
122
 
123
  if conclusion_type == 2:
124
- msg_list = [item.get('msg', '违规内容') for item in result.get('data', [])]
 
 
 
125
  reason = ",".join(msg_list) if msg_list else "内容不合规"
126
  return True, reason
127
  elif conclusion_type == 3:
@@ -130,12 +124,15 @@ async def check_baidu_text_censor(text):
130
  return False, None
131
 
132
  except Exception as e:
133
- print(f"⚠️ [System] 审核接口异常: {e}")
134
  return False, None
135
 
136
- # ==========================================
137
- # 3. 图像生成模块
138
- # ==========================================
 
 
 
139
 
140
  class GenerateRequest(BaseModel):
141
  api_key: str
@@ -146,13 +143,6 @@ class GenerateRequest(BaseModel):
146
  custom_size: str
147
  format: str
148
 
149
- def contains_local_sensitive_words(text: str):
150
- text_lower = text.lower()
151
- for word in SENSITIVE_WORDS:
152
- if word.lower() in text_lower:
153
- return True, word
154
- return False, None
155
-
156
  def process_image_in_memory(image_bytes: bytes, target_format: str) -> str:
157
  try:
158
  with Image.open(BytesIO(image_bytes)) as img:
@@ -164,12 +154,12 @@ def process_image_in_memory(image_bytes: bytes, target_format: str) -> str:
164
  img.save(output_buffer, format="PNG")
165
  return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
166
  except Exception as e:
167
- raise Exception(f"图片处理失败: {str(e)}")
168
  finally:
169
  gc.collect()
170
 
171
  async def download_image_as_bytes(url):
172
- print(f"📥 [System] 下载图片: {url}")
173
  async with httpx.AsyncClient(timeout=60) as client:
174
  response = await client.get(url)
175
  if response.status_code == 200:
@@ -177,8 +167,11 @@ async def download_image_as_bytes(url):
177
  else:
178
  raise Exception(f"下载失败 HTTP {response.status_code}")
179
 
 
 
 
 
180
  async def core_generate(req: GenerateRequest):
181
- # 分辨率
182
  final_size = "1024x1024"
183
  ratio_str = "1:1"
184
  if req.ratio == "custom":
@@ -189,7 +182,6 @@ async def core_generate(req: GenerateRequest):
189
  final_size = f"{int(base_w * multiplier)}x{int(base_h * multiplier)}"
190
  ratio_str = req.ratio.split(" ")[0]
191
 
192
- # Prompt
193
  final_prompt = req.prompt
194
  if "gemini" in req.model.lower():
195
  if req.ratio == "custom":
@@ -197,8 +189,6 @@ async def core_generate(req: GenerateRequest):
197
  else:
198
  final_prompt = f"{req.prompt} --ar {ratio_str} --resolution {final_size}"
199
 
200
- # 初始化 OpenAI 客户端
201
- # 注意:max_retries=0 禁止自动重试,防止重复计费
202
  client = AsyncOpenAI(
203
  api_key=req.api_key,
204
  base_url="https://api.cloudapp.ink/v1",
@@ -209,7 +199,7 @@ async def core_generate(req: GenerateRequest):
209
  image_bytes = None
210
 
211
  if "gemini" in req.model.lower():
212
- print(f"🟣 [Chat] 请求 Gemini: {req.model}")
213
  response = await client.chat.completions.create(
214
  model=req.model,
215
  messages=[{"role": "user", "content": final_prompt}],
@@ -224,14 +214,14 @@ async def core_generate(req: GenerateRequest):
224
  if url_match:
225
  image_bytes = await download_image_as_bytes(url_match.group(1))
226
  elif "base64," in content:
227
- print(f"🟣 [Chat] Base64数据流")
228
  b64_str = content.split("base64,")[1].split(")")[0].strip()
229
  image_bytes = base64.b64decode(b64_str)
230
  else:
231
  raise Exception("未能识别图片内容")
232
 
233
  else:
234
- print(f"🔵 [Draw] 请求 Standard: {req.model} | {final_size}")
235
  response = await client.images.generate(
236
  model=req.model,
237
  prompt=final_prompt,
@@ -239,7 +229,7 @@ async def core_generate(req: GenerateRequest):
239
  size=final_size,
240
  response_format="b64_json"
241
  )
242
- print(f"🔵 [Draw] 响应成功")
243
 
244
  data = response.data[0]
245
  if getattr(data, 'b64_json', None):
@@ -247,16 +237,15 @@ async def core_generate(req: GenerateRequest):
247
  elif hasattr(data, 'url'):
248
  image_bytes = await download_image_as_bytes(data.url)
249
  else:
250
- raise Exception("API 未返回图片数据")
251
 
252
  return process_image_in_memory(image_bytes, req.format)
253
 
254
  # ==========================================
255
- # 4. FastAPI 启动入口
256
  # ==========================================
257
  app = FastAPI()
258
 
259
- # 挂载静态文件
260
  app.mount("/static", StaticFiles(directory="static"), name="static")
261
 
262
  @app.get("/")
@@ -273,41 +262,48 @@ async def generate_api(req: GenerateRequest, request: Request):
273
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
274
 
275
  print("\n" + "="*60)
276
- print(f"🚀 [Req] {timestamp} | IP: {client_host}")
277
  print(f"📌 Model: {req.model}")
278
-
279
- # --- 1. 本地敏感词拦截 ---
 
280
  is_local_sensitive, word = contains_local_sensitive_words(req.prompt)
281
  if is_local_sensitive:
282
- print(f"🚫 [Block-Local] 命中本地词库: {word}")
283
  return JSONResponse(
284
- content={"status": "error", "message": f"违规词汇拦截: {word}"},
 
 
 
285
  status_code=200
286
  )
287
 
288
- # --- 2. 百度云智能拦截 ---
289
  is_baidu_sensitive, reason = await check_baidu_text_censor(req.prompt)
290
  if is_baidu_sensitive:
291
- print(f"🚫 [Block-Baidu] 百度审核拦截: {reason}")
292
  return JSONResponse(
293
- content={"status": "error", "message": f"安全审核未通过: {reason}"},
 
 
 
294
  status_code=200
295
  )
296
 
297
- print(f"💡 Prompt: {req.prompt}")
298
  print("-" * 60)
299
 
300
- # --- 3. 执行生成 ---
301
  async with CONCURRENCY_LIMITER:
302
  try:
303
  start_time = time.time()
304
  base64_img = await core_generate(req)
 
305
  duration = time.time() - start_time
306
  print(f"✅ [Success] 耗时: {duration:.2f}s")
307
  return {"status": "success", "image_base64": base64_img}
308
 
309
  except Exception as e:
310
- print(f"❌ [Failed] 异常: {str(e)}")
311
  return JSONResponse(
312
  content={"status": "error", "message": str(e)},
313
  status_code=200
 
16
  import uvicorn
17
 
18
  # ==========================================
19
+ # 1. 配置区域
20
  # ==========================================
21
 
22
+ # --- 百度内容审核配置 ---
23
+ BAIDU_API_KEY = os.getenv("BAIDU_API_KEY", "")
24
+ BAIDU_SECRET_KEY = os.getenv("BAIDU_SECRET_KEY", "")
25
+
26
+ # --- 启动检查日志 ---
27
+ if BAIDU_API_KEY and BAIDU_SECRET_KEY:
28
+ print(f"✅ [System] 检测到百度 API Key: {BAIDU_API_KEY[:4]}****")
29
+ else:
30
+ print(f"⚠️ [System] 未检测到百度 API Key 或 SECRET KEY,审核功能将关闭!")
31
+
32
+ # --- 并发限制 ---
33
  CONCURRENCY_LIMITER = asyncio.Semaphore(3)
34
 
35
+ # --- 基础配置 ---
36
  AR_BASES = {
37
  "1:1 (SDXL标准 1024)": (1024, 1024),
38
  "16:9 (SDXL标准 768p)": (1344, 768),
 
46
  "1k": 1.0, "2k": 1.5, "4k": 2.0
47
  }
48
 
49
+ # --- 本地敏感词 (第一道防线) ---
50
  SENSITIVE_WORDS = [
51
  "nsfw", "nude", "naked", "sex", "porn", "hentai",
52
  "裸", "色情", "全裸", "blood", "kill", "murder",
53
+ "血腥", "尸体", "毒品"
54
  ]
55
 
56
  # ==========================================
57
+ # 2. 工具函数 (百度审核 & 图像处理)
58
  # ==========================================
59
 
60
  # 全局缓存 Access Token
61
  _BAIDU_TOKEN_CACHE = {"token": None, "expires_at": 0}
62
 
63
  async def get_baidu_access_token():
64
+ """获取百度 Access Token"""
65
+ # 如果环境变量没填,直接返回 None,跳过百度审核
66
+ if not BAIDU_API_KEY or not BAIDU_SECRET_KEY:
 
 
 
 
 
 
 
 
 
 
 
 
67
  return None
68
 
 
69
  now = time.time()
70
  if _BAIDU_TOKEN_CACHE["token"] and now < _BAIDU_TOKEN_CACHE["expires_at"]:
71
  return _BAIDU_TOKEN_CACHE["token"]
72
 
 
73
  url = "https://aip.baidubce.com/oauth/2.0/token"
74
  params = {
75
  "grant_type": "client_credentials",
76
+ "client_id": BAIDU_API_KEY,
77
+ "client_secret": BAIDU_SECRET_KEY
78
  }
79
 
80
  try:
81
  async with httpx.AsyncClient(timeout=10) as client:
82
  resp = await client.post(url, params=params)
83
  data = resp.json()
 
 
 
 
 
84
  token = data.get("access_token")
85
  if token:
86
  _BAIDU_TOKEN_CACHE["token"] = token
87
+ _BAIDU_TOKEN_CACHE["expires_at"] = now + (20 * 24 * 3600)
88
  return token
89
  else:
90
+ print(f"⚠️ 百度 Token 获取失败: {data}")
91
  return None
92
  except Exception as e:
93
+ print(f"⚠️ 百度 Token 网络错误: {e}")
94
  return None
95
 
96
  async def check_baidu_text_censor(text):
97
+ """调用百度 API 检查文本"""
98
  token = await get_baidu_access_token()
99
  if not token:
100
+ # 如果获取不到 token (未配置环境变量或接口挂了),默认放行
101
  return False, None
102
 
103
  url = f"https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={token}"
 
104
  data = {'text': text}
105
 
106
  try:
107
  async with httpx.AsyncClient(timeout=10) as client:
108
+ resp = await client.post(url, data=data)
109
  result = resp.json()
110
 
111
  # conclusionType: 1.合规 2.不合规 3.疑似 4.审核失败
112
  conclusion_type = result.get('conclusionType')
113
 
114
  if conclusion_type == 2:
115
+ msg_list = []
116
+ if 'data' in result:
117
+ for item in result['data']:
118
+ msg_list.append(item.get('msg', '违规内容'))
119
  reason = ",".join(msg_list) if msg_list else "内容不合规"
120
  return True, reason
121
  elif conclusion_type == 3:
 
124
  return False, None
125
 
126
  except Exception as e:
127
+ print(f"⚠️ 百度审核接口异常: {e}")
128
  return False, None
129
 
130
+ def contains_local_sensitive_words(text: str):
131
+ text_lower = text.lower()
132
+ for word in SENSITIVE_WORDS:
133
+ if word.lower() in text_lower:
134
+ return True, word
135
+ return False, None
136
 
137
  class GenerateRequest(BaseModel):
138
  api_key: str
 
143
  custom_size: str
144
  format: str
145
 
 
 
 
 
 
 
 
146
  def process_image_in_memory(image_bytes: bytes, target_format: str) -> str:
147
  try:
148
  with Image.open(BytesIO(image_bytes)) as img:
 
154
  img.save(output_buffer, format="PNG")
155
  return base64.b64encode(output_buffer.getvalue()).decode('utf-8')
156
  except Exception as e:
157
+ raise Exception(f"格式转换失败: {str(e)}")
158
  finally:
159
  gc.collect()
160
 
161
  async def download_image_as_bytes(url):
162
+ print(f"📥 [System] 正在下载图片: {url}")
163
  async with httpx.AsyncClient(timeout=60) as client:
164
  response = await client.get(url)
165
  if response.status_code == 200:
 
167
  else:
168
  raise Exception(f"下载失败 HTTP {response.status_code}")
169
 
170
+ # ==========================================
171
+ # 3. 核心生成逻辑
172
+ # ==========================================
173
+
174
  async def core_generate(req: GenerateRequest):
 
175
  final_size = "1024x1024"
176
  ratio_str = "1:1"
177
  if req.ratio == "custom":
 
182
  final_size = f"{int(base_w * multiplier)}x{int(base_h * multiplier)}"
183
  ratio_str = req.ratio.split(" ")[0]
184
 
 
185
  final_prompt = req.prompt
186
  if "gemini" in req.model.lower():
187
  if req.ratio == "custom":
 
189
  else:
190
  final_prompt = f"{req.prompt} --ar {ratio_str} --resolution {final_size}"
191
 
 
 
192
  client = AsyncOpenAI(
193
  api_key=req.api_key,
194
  base_url="https://api.cloudapp.ink/v1",
 
199
  image_bytes = None
200
 
201
  if "gemini" in req.model.lower():
202
+ print(f"🟣 [Chat模式] 请求 Gemini: {req.model}")
203
  response = await client.chat.completions.create(
204
  model=req.model,
205
  messages=[{"role": "user", "content": final_prompt}],
 
214
  if url_match:
215
  image_bytes = await download_image_as_bytes(url_match.group(1))
216
  elif "base64," in content:
217
+ print(f"🟣 [Chat模式] 检测到 Base64 流")
218
  b64_str = content.split("base64,")[1].split(")")[0].strip()
219
  image_bytes = base64.b64decode(b64_str)
220
  else:
221
  raise Exception("未能识别图片内容")
222
 
223
  else:
224
+ print(f"🔵 [绘图模式] 请求 Standard: {req.model} | 分辨率: {final_size}")
225
  response = await client.images.generate(
226
  model=req.model,
227
  prompt=final_prompt,
 
229
  size=final_size,
230
  response_format="b64_json"
231
  )
232
+ print(f"🔵 [绘图模式] 请求成功")
233
 
234
  data = response.data[0]
235
  if getattr(data, 'b64_json', None):
 
237
  elif hasattr(data, 'url'):
238
  image_bytes = await download_image_as_bytes(data.url)
239
  else:
240
+ raise Exception("API 未返回有效图片数据")
241
 
242
  return process_image_in_memory(image_bytes, req.format)
243
 
244
  # ==========================================
245
+ # 4. FastAPI App
246
  # ==========================================
247
  app = FastAPI()
248
 
 
249
  app.mount("/static", StaticFiles(directory="static"), name="static")
250
 
251
  @app.get("/")
 
262
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
263
 
264
  print("\n" + "="*60)
265
+ print(f"🚀 [Request] {timestamp} | IP: {client_host}")
266
  print(f"📌 Model: {req.model}")
267
+ print(f"💡 Prompt: {req.prompt}")
268
+
269
+ # 1. 本地敏感词检查
270
  is_local_sensitive, word = contains_local_sensitive_words(req.prompt)
271
  if is_local_sensitive:
272
+ print(f"🚫 [Blocked-Local] 命中本地词库: {word}")
273
  return JSONResponse(
274
+ content={
275
+ "status": "error",
276
+ "message": f"提示词包含违禁词汇 ({word}),请求被拒绝。"
277
+ },
278
  status_code=200
279
  )
280
 
281
+ # 2. 百度 API 检查 (动态读取环境变量)
282
  is_baidu_sensitive, reason = await check_baidu_text_censor(req.prompt)
283
  if is_baidu_sensitive:
284
+ print(f"🚫 [Blocked-Baidu] 百度审核拦截: {reason}")
285
  return JSONResponse(
286
+ content={
287
+ "status": "error",
288
+ "message": f"提示词未通过智能安全审核: {reason}"
289
+ },
290
  status_code=200
291
  )
292
 
 
293
  print("-" * 60)
294
 
295
+ # 3. 正常生成
296
  async with CONCURRENCY_LIMITER:
297
  try:
298
  start_time = time.time()
299
  base64_img = await core_generate(req)
300
+
301
  duration = time.time() - start_time
302
  print(f"✅ [Success] 耗时: {duration:.2f}s")
303
  return {"status": "success", "image_base64": base64_img}
304
 
305
  except Exception as e:
306
+ print(f"❌ [Failed] 错误: {str(e)}")
307
  return JSONResponse(
308
  content={"status": "error", "message": str(e)},
309
  status_code=200