enterprise_admin_proxy_router.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. """
  2. 企业管理员代理路由
  3. 把订单、消费、发票、审核、日志、模型等模块代理到企业管理员认证,
  4. 并在 service 层加 tenant_id 过滤。
  5. """
  6. from typing import Optional
  7. from datetime import date
  8. from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
  9. from sqlalchemy.orm import Session
  10. from app.database import get_db
  11. from app.dependencies.enterprise_auth import get_current_enterprise_admin
  12. from app.models.tenant import EnterpriseAdmin
  13. from app.models.user import User
  14. router = APIRouter(prefix="/api/enterprise-admin", tags=["企业管理员-代理"])
  15. def _tenant_user_ids(db: Session, tenant_id: int):
  16. return db.query(User.id).filter(User.tenant_id == tenant_id).all()
  17. # ==================== 订单管理 ====================
  18. @router.get("/orders")
  19. def list_orders(
  20. keyword: str = Query(None), status_filter: str = Query(None, alias="status"),
  21. payment_method: str = Query(None), start_date: str = Query(None),
  22. end_date: str = Query(None), amount_min: float = Query(None),
  23. amount_max: float = Query(None), sort_by: str = Query("created_at"),
  24. sort_order: str = Query("desc"), page: int = Query(1, ge=1),
  25. size: int = Query(20, ge=1, le=100),
  26. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  27. db: Session = Depends(get_db)
  28. ):
  29. from app.services.admin_order_service import AdminOrderService, AdminOrderException
  30. from app.schemas.admin_order_schema import OrderListParams
  31. params = OrderListParams(
  32. keyword=keyword, status=status_filter, payment_method=payment_method,
  33. start_date=start_date, end_date=end_date, amount_min=amount_min,
  34. amount_max=amount_max, sort_by=sort_by, sort_order=sort_order,
  35. page=page, size=size
  36. )
  37. service = AdminOrderService(db)
  38. orders, total = service.list_orders(params, tenant_id=current_admin.tenant_id)
  39. return {"code": 0, "message": "success", "data": {
  40. "items": [o.model_dump() for o in orders],
  41. "total": total, "page": page, "size": size,
  42. "pages": (total + size - 1) // size
  43. }}
  44. @router.get("/orders/export")
  45. def export_orders(
  46. keyword: str = Query(None), status_filter: str = Query(None, alias="status"),
  47. start_date: str = Query(None), end_date: str = Query(None),
  48. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  49. db: Session = Depends(get_db)
  50. ):
  51. from app.services.admin_order_service import AdminOrderService
  52. from app.schemas.admin_order_schema import OrderListParams
  53. params = OrderListParams(keyword=keyword, status=status_filter,
  54. start_date=start_date, end_date=end_date, page=1, size=10000)
  55. service = AdminOrderService(db)
  56. url = service.export_orders(params)
  57. return {"code": 0, "data": {"download_url": url}}
  58. @router.get("/orders/{order_id}")
  59. def get_order_detail(
  60. order_id: int,
  61. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  62. db: Session = Depends(get_db)
  63. ):
  64. from app.services.admin_order_service import AdminOrderService, AdminOrderException
  65. service = AdminOrderService(db)
  66. order = service.get_order_detail(order_id)
  67. # 验证订单属于本企业
  68. user = db.query(User).filter(User.id == order.user_id, User.tenant_id == current_admin.tenant_id).first()
  69. if not user:
  70. raise HTTPException(status_code=404, detail={"code": "NOT_FOUND", "message": "订单不存在"})
  71. return {"code": 0, "data": order.model_dump()}
  72. @router.post("/orders/{order_id}/confirm")
  73. def confirm_order(
  74. order_id: int, data: dict, request: Request,
  75. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  76. db: Session = Depends(get_db)
  77. ):
  78. from app.services.admin_order_service import AdminOrderService
  79. service = AdminOrderService(db)
  80. service.confirm_order(order_id, current_admin.id, data.get("reason", ""), request.client.host if request.client else "")
  81. return {"code": 0, "message": "确认成功"}
  82. @router.post("/orders/{order_id}/refund")
  83. def refund_order(
  84. order_id: int, data: dict, request: Request,
  85. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  86. db: Session = Depends(get_db)
  87. ):
  88. from app.services.admin_order_service import AdminOrderService
  89. service = AdminOrderService(db)
  90. service.refund_order(order_id, current_admin.id, data.get("reason", ""), request.client.host if request.client else "")
  91. return {"code": 0, "message": "退款成功"}
  92. # ==================== 账单管理 ====================
  93. @router.get("/bills")
  94. def list_bills(
  95. keyword: str = Query(None), biz_type: str = Query(None),
  96. module: str = Query(None), start_date: str = Query(None),
  97. end_date: str = Query(None), sort_by: str = Query("created_at"),
  98. sort_order: str = Query("desc"), page: int = Query(1, ge=1),
  99. size: int = Query(20, ge=1, le=100),
  100. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  101. db: Session = Depends(get_db)
  102. ):
  103. from app.services.admin_bill_service import AdminBillService
  104. from app.schemas.admin_order_schema import BillListParams
  105. params = BillListParams(keyword=keyword, biz_type=biz_type, module=module,
  106. start_date=start_date, end_date=end_date, sort_by=sort_by,
  107. sort_order=sort_order, page=page, size=size)
  108. service = AdminBillService(db)
  109. bills, total = service.list_bills(params, tenant_id=current_admin.tenant_id)
  110. return {"code": 0, "data": {
  111. "items": [b.model_dump() for b in bills],
  112. "total": total, "page": page, "size": size,
  113. "pages": (total + size - 1) // size
  114. }}
  115. # ==================== 消费记录 ====================
  116. @router.get("/consumptions")
  117. def list_consumptions(
  118. user_id: str = Query(None), order_no: str = Query(None),
  119. model_name: str = Query(None), start_date: date = Query(None),
  120. end_date: date = Query(None), invoiced: bool = Query(None),
  121. page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100),
  122. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  123. db: Session = Depends(get_db)
  124. ):
  125. from app.services.admin_consumption_service import AdminConsumptionService
  126. from app.schemas.admin_consumption_invoice_schema import ConsumptionListParams
  127. params = ConsumptionListParams(user_id=user_id, order_no=order_no,
  128. model_name=model_name, start_date=start_date, end_date=end_date,
  129. invoiced=invoiced, page=page, size=size)
  130. service = AdminConsumptionService(db)
  131. items, total = service.list_consumptions(params, tenant_id=current_admin.tenant_id)
  132. return {"code": 0, "data": {
  133. "items": [i.model_dump() for i in items],
  134. "total": total, "page": page, "size": size,
  135. "pages": (total + size - 1) // size
  136. }}
  137. # ==================== 发票管理 ====================
  138. @router.get("/invoices")
  139. def list_invoices(
  140. user_id: str = Query(None), inv_status: str = Query(None, alias="status"),
  141. invoice_type: str = Query(None), start_date: date = Query(None),
  142. end_date: date = Query(None), page: int = Query(1, ge=1),
  143. size: int = Query(20, ge=1, le=100),
  144. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  145. db: Session = Depends(get_db)
  146. ):
  147. from app.services.admin_invoice_service import AdminInvoiceService
  148. from app.schemas.admin_consumption_invoice_schema import InvoiceListParams
  149. params = InvoiceListParams(user_id=user_id, status=inv_status,
  150. invoice_type=invoice_type, start_date=start_date, end_date=end_date,
  151. page=page, size=size)
  152. service = AdminInvoiceService(db)
  153. items, total = service.list_invoices(params, tenant_id=current_admin.tenant_id)
  154. return {"code": 0, "data": {
  155. "items": [i.model_dump() for i in items],
  156. "total": total, "page": page, "size": size,
  157. "pages": (total + size - 1) // size
  158. }}
  159. @router.get("/invoices/{invoice_id}")
  160. def get_invoice_detail(
  161. invoice_id: int,
  162. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  163. db: Session = Depends(get_db)
  164. ):
  165. from app.services.admin_invoice_service import AdminInvoiceService
  166. service = AdminInvoiceService(db)
  167. detail = service.get_invoice_detail(invoice_id)
  168. return {"code": 0, "data": detail.model_dump()}
  169. @router.post("/invoices/{invoice_id}/approve")
  170. def approve_invoice(
  171. invoice_id: int, request: Request,
  172. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  173. db: Session = Depends(get_db)
  174. ):
  175. from app.services.admin_invoice_service import AdminInvoiceService
  176. service = AdminInvoiceService(db)
  177. service.approve_invoice(invoice_id, current_admin.id, request.client.host if request.client else "")
  178. return {"code": 0, "message": "审核通过"}
  179. @router.post("/invoices/{invoice_id}/reject")
  180. def reject_invoice(
  181. invoice_id: int, data: dict, request: Request,
  182. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  183. db: Session = Depends(get_db)
  184. ):
  185. from app.services.admin_invoice_service import AdminInvoiceService
  186. service = AdminInvoiceService(db)
  187. service.reject_invoice(invoice_id, current_admin.id, data.get("reason", ""), request.client.host if request.client else "")
  188. return {"code": 0, "message": "驳回成功"}
  189. # ==================== 内容审核 ====================
  190. @router.get("/review/pictures")
  191. def get_pictures(
  192. rev_status: str = Query(None, alias="status"), page: int = Query(1, ge=1),
  193. size: int = Query(20, ge=1, le=100),
  194. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  195. db: Session = Depends(get_db)
  196. ):
  197. from app.services.review_service import ReviewService
  198. service = ReviewService(db)
  199. result = service.get_picture_review_list(rev_status, None, None, None, page, size,
  200. tenant_id=current_admin.tenant_id)
  201. return {"code": 0, "data": result}
  202. @router.get("/review/videos")
  203. def get_videos(
  204. rev_status: str = Query(None, alias="status"), page: int = Query(1, ge=1),
  205. size: int = Query(20, ge=1, le=100),
  206. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  207. db: Session = Depends(get_db)
  208. ):
  209. from app.services.review_service import ReviewService
  210. service = ReviewService(db)
  211. result = service.get_video_review_list(rev_status, None, None, None, page, size,
  212. tenant_id=current_admin.tenant_id)
  213. return {"code": 0, "data": result}
  214. @router.get("/review/audios")
  215. def get_audios(
  216. rev_status: str = Query(None, alias="status"), page: int = Query(1, ge=1),
  217. size: int = Query(20, ge=1, le=100),
  218. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  219. db: Session = Depends(get_db)
  220. ):
  221. from app.services.review_service import ReviewService
  222. service = ReviewService(db)
  223. result = service.get_audio_review_list(rev_status, None, None, None, page, size,
  224. tenant_id=current_admin.tenant_id)
  225. return {"code": 0, "data": result}
  226. @router.post("/review/{content_type}/{content_id}/approve")
  227. def approve_content(
  228. content_type: str, content_id: int, action: dict = {},
  229. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  230. db: Session = Depends(get_db)
  231. ):
  232. from app.services.review_service import ReviewService
  233. service = ReviewService(db)
  234. service.approve_content(content_type, content_id, current_admin.id, action.get("remark"))
  235. return {"code": 0, "message": "审核通过"}
  236. @router.post("/review/{content_type}/{content_id}/reject")
  237. def reject_content(
  238. content_type: str, content_id: int, action: dict,
  239. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  240. db: Session = Depends(get_db)
  241. ):
  242. from app.services.review_service import ReviewService
  243. service = ReviewService(db)
  244. service.reject_content(content_type, content_id, current_admin.id,
  245. action.get("reason", ""), action.get("delete_content", False), action.get("ban_user", False))
  246. return {"code": 0, "message": "审核拒绝"}
  247. # ==================== 日志审计 ====================
  248. @router.get("/logs/api")
  249. def list_api_logs(
  250. user_id: str = Query(None), username: str = Query(None),
  251. phone: str = Query(None), module: str = Query(None),
  252. api_path: str = Query(None), start_date: str = Query(None),
  253. end_date: str = Query(None), page: int = Query(1, ge=1),
  254. size: int = Query(20, ge=1, le=100),
  255. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  256. db: Session = Depends(get_db)
  257. ):
  258. from app.services.log_service import LogService
  259. service = LogService(db)
  260. result = service.get_api_logs(start_date=start_date, end_date=end_date,
  261. user_id=user_id, username=username, phone=phone, module=module,
  262. api_path=api_path, page=page, size=size, tenant_id=current_admin.tenant_id)
  263. return {"code": 0, "data": result}
  264. @router.get("/logs/logins")
  265. def list_login_logs(
  266. user_type: str = Query(None), login_result: str = Query(None),
  267. start_date: str = Query(None), end_date: str = Query(None),
  268. page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100),
  269. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  270. db: Session = Depends(get_db)
  271. ):
  272. from app.services.log_service import LogService
  273. service = LogService(db)
  274. result = service.get_login_logs(start_date=start_date, end_date=end_date,
  275. user_type=user_type, login_result=login_result, page=page, size=size,
  276. tenant_id=current_admin.tenant_id)
  277. return {"code": 0, "data": result}
  278. # ==================== 模型管理(只读,不隔离) ====================
  279. @router.get("/models")
  280. def list_models(
  281. keyword: str = Query(None), category: str = Query(None),
  282. is_show_enabled: bool = Query(None), page: int = Query(1, ge=1),
  283. size: int = Query(20, ge=1, le=100),
  284. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  285. db: Session = Depends(get_db)
  286. ):
  287. from app.services.admin_model_service import AdminModelService
  288. from app.schemas.admin_schema import ModelListParams
  289. params = ModelListParams(keyword=keyword, category=category,
  290. is_show_enabled=is_show_enabled, page=page, size=size)
  291. service = AdminModelService(db)
  292. models, total = service.list_models(params)
  293. return {"code": 0, "data": {
  294. "items": [m.model_dump() for m in models],
  295. "total": total, "page": page, "size": size
  296. }}
  297. @router.get("/models/local")
  298. def list_local_models(
  299. keyword: str = Query(None), page: int = Query(1, ge=1),
  300. size: int = Query(20, ge=1, le=100),
  301. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  302. db: Session = Depends(get_db)
  303. ):
  304. from app.models.model import ModelNew
  305. from app.models.tenant import Tenant
  306. from sqlalchemy import or_, desc
  307. # 检查企业是否开启了私域模型功能
  308. tenant = db.query(Tenant).filter(Tenant.id == current_admin.tenant_id).first()
  309. if tenant and not getattr(tenant, "enable_local_model", True):
  310. raise HTTPException(status_code=403, detail="该企业未开启私域模型功能")
  311. query = db.query(ModelNew).filter(
  312. ModelNew.is_local == True,
  313. ModelNew.tenant_id == current_admin.tenant_id
  314. )
  315. if keyword:
  316. kw = f"%{keyword}%"
  317. query = query.filter(or_(ModelNew.model_code.ilike(kw), ModelNew.display_name.ilike(kw)))
  318. total = query.count()
  319. models = query.order_by(desc(ModelNew.created_at)).offset((page - 1) * size).limit(size).all()
  320. items = [{"id": m.id, "model_code": m.model_code, "title": m.display_name,
  321. "visibility": m.visibility, "user_id": m.user_id, "tenant_id": m.tenant_id,
  322. "created_at": m.created_at.isoformat() if m.created_at else None} for m in models]
  323. return {"code": 0, "data": {"items": items, "total": total, "page": page, "size": size}}
  324. # ==================== 系统配置 ====================
  325. @router.get("/config/branding")
  326. def get_branding_config(
  327. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  328. db: Session = Depends(get_db)
  329. ):
  330. """获取品牌配置,优先返回企业自己设置的值,没有则 fallback 到全局默认"""
  331. from app.models.config import SystemConfig
  332. import json as _json
  333. KEYS = ["system_name", "system_logo", "icp_number"]
  334. DEFAULTS = {"system_name": "智创空间", "system_logo": "", "icp_number": ""}
  335. result = {}
  336. for key in KEYS:
  337. # 先查企业自己的配置
  338. row = db.query(SystemConfig).filter(
  339. SystemConfig.tenant_id == current_admin.tenant_id,
  340. SystemConfig.config_key == key
  341. ).first()
  342. if row:
  343. try:
  344. result[key] = _json.loads(row.config_value)
  345. except Exception:
  346. result[key] = row.config_value
  347. continue
  348. # fallback 到全局
  349. row = db.query(SystemConfig).filter(
  350. SystemConfig.tenant_id == None,
  351. SystemConfig.config_key == key
  352. ).first()
  353. if row:
  354. try:
  355. result[key] = _json.loads(row.config_value)
  356. except Exception:
  357. result[key] = row.config_value
  358. else:
  359. result[key] = DEFAULTS[key]
  360. return {"code": 0, "data": result}
  361. @router.get("/config")
  362. def get_config(
  363. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  364. db: Session = Depends(get_db)
  365. ):
  366. from app.services.config_service import ConfigService
  367. service = ConfigService(db, tenant_id=current_admin.tenant_id)
  368. return service.get_all_configs()
  369. @router.put("/config")
  370. def update_config(
  371. update_data: dict,
  372. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  373. db: Session = Depends(get_db)
  374. ):
  375. from app.services.config_service import ConfigService
  376. from app.schemas.config_schema import ConfigUpdate
  377. service = ConfigService(db, tenant_id=current_admin.tenant_id)
  378. configs = [ConfigUpdate(**c) for c in update_data.get("configs", [])]
  379. service.update_configs(configs, current_admin.id)
  380. return {"message": "配置更新成功", "data": None}
  381. @router.get("/config/{config_key}/history")
  382. def get_config_history(
  383. config_key: str,
  384. page: int = Query(1, ge=1),
  385. size: int = Query(20, ge=1, le=100),
  386. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  387. db: Session = Depends(get_db)
  388. ):
  389. from app.services.config_service import ConfigService
  390. service = ConfigService(db, tenant_id=current_admin.tenant_id)
  391. history = service.get_config_history(config_key, page, size)
  392. return {"data": history}
  393. @router.post("/config/{config_key}/reset")
  394. def reset_config(
  395. config_key: str,
  396. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  397. db: Session = Depends(get_db)
  398. ):
  399. from app.services.config_service import ConfigService
  400. service = ConfigService(db, tenant_id=current_admin.tenant_id)
  401. service.reset_config(config_key, current_admin.id)
  402. return {"message": "配置重置成功", "data": None}
  403. @router.get("/local-config")
  404. def get_local_config(
  405. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  406. ):
  407. from app.services import local_config_service
  408. from app.schemas.local_config_schema import LocalConfigResponse
  409. result = local_config_service.get_all()
  410. return LocalConfigResponse(data=result)
  411. # ==================== 模型写操作 ====================
  412. @router.post("/models")
  413. def create_model(
  414. data: dict,
  415. request: Request,
  416. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  417. db: Session = Depends(get_db)
  418. ):
  419. from app.services.admin_model_service import AdminModelService
  420. from app.schemas.admin_schema import ModelCreateRequest
  421. service = AdminModelService(db)
  422. req = ModelCreateRequest(**data)
  423. model_id = service.create_model(req)
  424. return {"message": "模型创建成功", "model_id": model_id}
  425. @router.put("/models/{model_id}")
  426. def update_model(
  427. model_id: int,
  428. data: dict,
  429. request: Request,
  430. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  431. db: Session = Depends(get_db)
  432. ):
  433. from app.services.admin_model_service import AdminModelService
  434. from app.schemas.admin_schema import ModelUpdateRequest
  435. service = AdminModelService(db)
  436. req = ModelUpdateRequest(**data)
  437. service.update_model(model_id, req)
  438. return {"message": "模型更新成功", "model_id": model_id}
  439. @router.put("/models/{model_id}/price")
  440. def update_model_price(
  441. model_id: int,
  442. data: dict,
  443. request: Request,
  444. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  445. db: Session = Depends(get_db)
  446. ):
  447. from app.services.admin_model_service import AdminModelService
  448. from app.schemas.admin_schema import ModelPriceRequest
  449. service = AdminModelService(db)
  450. req = ModelPriceRequest(**data)
  451. service.update_model_price(model_id, req)
  452. return {"message": "价格更新成功", "model_id": model_id}
  453. @router.put("/models/{model_id}/status")
  454. def update_model_status(
  455. model_id: int,
  456. data: dict,
  457. request: Request,
  458. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  459. db: Session = Depends(get_db)
  460. ):
  461. from app.services.admin_model_service import AdminModelService
  462. from app.schemas.admin_schema import ModelStatusRequest
  463. service = AdminModelService(db)
  464. req = ModelStatusRequest(**data)
  465. service.update_model_status(model_id, req.field, req.value)
  466. return {"message": "状态更新成功", "model_id": model_id, req.field: req.value}
  467. # ==================== 企业私域模型创建/删除/测试 ====================
  468. @router.post("/models/local/test")
  469. async def test_local_model_connection(
  470. data: dict,
  471. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  472. db: Session = Depends(get_db)
  473. ):
  474. """企业管理员测试私域模型连接"""
  475. from app.services.local_model_service import LocalModelService
  476. from app.schemas.local_model import ConnectionTestRequest
  477. service = LocalModelService(db)
  478. categories = data.get('categories') or [0]
  479. result = await service.test_connection(
  480. data.get('base_url', ''),
  481. data.get('api_key'),
  482. data.get('model_name'),
  483. categories[0] if categories else 0,
  484. )
  485. return result
  486. @router.put("/models/local/{model_id}")
  487. async def update_local_model(
  488. model_id: int,
  489. data: dict,
  490. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  491. db: Session = Depends(get_db)
  492. ):
  493. """企业管理员更新本企业私域模型"""
  494. from app.models.model import ModelNew
  495. from app.services.crypto_utils import encrypt_api_key
  496. model = db.query(ModelNew).filter(
  497. ModelNew.id == model_id,
  498. ModelNew.is_local == True,
  499. ModelNew.tenant_id == current_admin.tenant_id,
  500. ).first()
  501. if not model:
  502. raise HTTPException(status_code=404, detail="模型不存在")
  503. if 'name' in data:
  504. model.display_name = data['name']
  505. if 'supplier' in data:
  506. model.supplier = data['supplier']
  507. if 'base_url' in data:
  508. model.base_url = data['base_url']
  509. if 'api_key' in data and data['api_key']:
  510. model.local_api_key = encrypt_api_key(data['api_key'])
  511. if 'categories' in data:
  512. model.categories = data['categories']
  513. if 'visibility' in data:
  514. model.visibility = data['visibility']
  515. db.commit()
  516. return {"code": 0, "message": "更新成功"}
  517. @router.post("/models/local")
  518. async def create_local_model(
  519. data: dict,
  520. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  521. db: Session = Depends(get_db)
  522. ):
  523. """企业管理员创建私域模型,自动绑定 tenant_id"""
  524. from app.services.local_model_service import LocalModelService
  525. import time
  526. from app.models.model import ModelNew
  527. from app.models.tenant import Tenant
  528. from app.services.crypto_utils import encrypt_api_key
  529. # 检查企业是否开启了私域模型功能
  530. tenant = db.query(Tenant).filter(Tenant.id == current_admin.tenant_id).first()
  531. if tenant and not getattr(tenant, "enable_local_model", True):
  532. raise HTTPException(status_code=403, detail="该企业未开启私域模型功能")
  533. base_url = data.get('base_url', '')
  534. if not base_url:
  535. raise HTTPException(status_code=400, detail="base_url 不能为空")
  536. service = LocalModelService(db)
  537. is_valid, err = service.validate_base_url(base_url)
  538. if not is_valid:
  539. raise HTTPException(status_code=400, detail=err)
  540. api_key = data.get('api_key')
  541. model_code = f"local_tenant_{current_admin.tenant_id}_{int(time.time() * 1000)}"
  542. model = ModelNew(
  543. model_code=model_code,
  544. display_name=data.get('name', model_code),
  545. supplier=data.get('supplier', 'Custom'),
  546. img="",
  547. categories=data.get('categories', [0]),
  548. is_local=True,
  549. user_id=None,
  550. tenant_id=current_admin.tenant_id,
  551. base_url=base_url,
  552. local_api_key=encrypt_api_key(api_key) if api_key else None,
  553. is_show_enabled=False,
  554. is_api_enabled=True,
  555. visibility=data.get('visibility', 'global'),
  556. )
  557. db.add(model)
  558. db.commit()
  559. db.refresh(model)
  560. return {"code": 0, "message": "创建成功", "data": {"id": model.id, "model_code": model.model_code}}
  561. @router.delete("/models/local/{model_id}")
  562. def delete_enterprise_local_model(
  563. model_id: int,
  564. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  565. db: Session = Depends(get_db)
  566. ):
  567. """企业管理员删除本企业的私域模型"""
  568. from app.models.model import ModelNew
  569. model = db.query(ModelNew).filter(
  570. ModelNew.id == model_id,
  571. ModelNew.is_local == True,
  572. ModelNew.tenant_id == current_admin.tenant_id
  573. ).first()
  574. if not model:
  575. raise HTTPException(status_code=404, detail="模型不存在或无权限")
  576. db.delete(model)
  577. db.commit()
  578. return {"code": 0, "message": "删除成功"}
  579. # ==================== 本地模型写操作 ====================
  580. @router.put("/local-models/{model_id}/visibility")
  581. def update_local_model_visibility(
  582. model_id: int,
  583. data: dict,
  584. request: Request,
  585. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  586. db: Session = Depends(get_db)
  587. ):
  588. from app.models.model import ModelNew
  589. model = db.query(ModelNew).filter(ModelNew.id == model_id, ModelNew.is_local == True).first()
  590. if not model:
  591. raise HTTPException(status_code=404, detail="本地模型不存在")
  592. model.visibility = data.get("visibility", model.visibility)
  593. db.commit()
  594. return {"message": "可见性更新成功", "model_id": model_id, "visibility": model.visibility}
  595. @router.delete("/local-models/{model_id}")
  596. def delete_local_model(
  597. model_id: int,
  598. request: Request,
  599. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  600. db: Session = Depends(get_db)
  601. ):
  602. from app.models.model import ModelNew
  603. model = db.query(ModelNew).filter(ModelNew.id == model_id, ModelNew.is_local == True).first()
  604. if not model:
  605. raise HTTPException(status_code=404, detail="本地模型不存在")
  606. db.delete(model)
  607. db.commit()
  608. return {"message": "本地模型已删除", "model_id": model_id}
  609. # ==================== 用户本地模型权限 ====================
  610. @router.get("/users/{user_id}/local-model-permissions")
  611. def get_user_local_model_permissions(
  612. user_id: str,
  613. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  614. db: Session = Depends(get_db)
  615. ):
  616. from app.services.user_local_model_permission_service import UserLocalModelPermissionService
  617. service = UserLocalModelPermissionService(db)
  618. return service.get_user_model_permissions(user_id)
  619. @router.put("/users/{user_id}/local-model-permissions/{model_id}")
  620. async def update_user_local_model_permission(
  621. user_id: str,
  622. model_id: int,
  623. data: dict,
  624. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  625. db: Session = Depends(get_db)
  626. ):
  627. from app.services.user_local_model_permission_service import UserLocalModelPermissionService
  628. service = UserLocalModelPermissionService(db)
  629. success = await service.update_user_model_permission(
  630. user_id=user_id, model_id=model_id, has_access=data.get("has_access", False)
  631. )
  632. if not success:
  633. raise HTTPException(status_code=400, detail="更新权限失败")
  634. return {"message": "权限更新成功"}
  635. @router.put("/users/{user_id}/local-model-permissions")
  636. async def update_user_all_local_model_permissions(
  637. user_id: str,
  638. data: dict,
  639. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  640. db: Session = Depends(get_db)
  641. ):
  642. from app.services.user_local_model_permission_service import UserLocalModelPermissionService
  643. service = UserLocalModelPermissionService(db)
  644. success = await service.update_user_all_model_permissions(
  645. user_id=user_id, has_access=data.get("has_access", False)
  646. )
  647. if not success:
  648. raise HTTPException(status_code=400, detail="更新权限失败")
  649. return {"message": "权限更新成功"}
  650. # ==================== 统计导出 ====================
  651. @router.get("/stats/users/export")
  652. async def export_user_stats(
  653. start_date: date = Query(...),
  654. end_date: date = Query(...),
  655. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  656. db: Session = Depends(get_db)
  657. ):
  658. from app.services.admin_stats_service import AdminStatsService
  659. service = AdminStatsService(db)
  660. file_url = await service.export_user_stats(start_date, end_date)
  661. return {"file_url": file_url}
  662. @router.get("/stats/revenue/export")
  663. async def export_revenue_stats(
  664. start_date: date = Query(...),
  665. end_date: date = Query(...),
  666. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  667. db: Session = Depends(get_db)
  668. ):
  669. from app.services.admin_stats_service import AdminStatsService
  670. service = AdminStatsService(db)
  671. file_url = await service.export_revenue_stats(start_date, end_date)
  672. return {"file_url": file_url}
  673. @router.get("/stats/business/export")
  674. async def export_business_stats(
  675. start_date: date = Query(...),
  676. end_date: date = Query(...),
  677. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  678. db: Session = Depends(get_db)
  679. ):
  680. from app.services.admin_stats_service import AdminStatsService
  681. service = AdminStatsService(db)
  682. file_url = await service.export_business_stats(start_date, end_date)
  683. return {"file_url": file_url}
  684. # ── 企业流水(租户隔离)────────────────────────────────────────────────────────
  685. from app.models.tenant import TenantBalanceLog
  686. from sqlalchemy import desc as _desc_tbl
  687. from datetime import datetime, time as _time
  688. @router.get("/balance-logs")
  689. def list_tenant_balance_logs(
  690. biz_type: str = Query(None, description="recharge/consume/adjust"),
  691. start_date: date = Query(None),
  692. end_date: date = Query(None),
  693. page: int = Query(1, ge=1),
  694. size: int = Query(20, ge=1, le=100),
  695. current_admin: EnterpriseAdmin = Depends(get_current_enterprise_admin),
  696. db: Session = Depends(get_db),
  697. ):
  698. """查询本企业余额流水(租户隔离)"""
  699. tenant_id = current_admin.tenant_id
  700. q = db.query(TenantBalanceLog).filter(TenantBalanceLog.tenant_id == tenant_id)
  701. if biz_type:
  702. q = q.filter(TenantBalanceLog.biz_type == biz_type)
  703. if start_date:
  704. q = q.filter(TenantBalanceLog.created_at >= datetime.combine(start_date, _time.min))
  705. if end_date:
  706. q = q.filter(TenantBalanceLog.created_at <= datetime.combine(end_date, _time.max))
  707. total = q.count()
  708. logs = q.order_by(_desc_tbl(TenantBalanceLog.created_at)).offset((page - 1) * size).limit(size).all()
  709. items = [
  710. {
  711. "id": l.id,
  712. "change_amount": float(l.change_amount),
  713. "balance_after": float(l.balance_after),
  714. "biz_type": l.biz_type,
  715. "biz_order_no": l.biz_order_no,
  716. "remark": l.remark,
  717. "created_at": l.created_at.isoformat() if l.created_at else None,
  718. }
  719. for l in logs
  720. ]
  721. # 累计统计(不受分页影响,基于当前筛选条件)
  722. from sqlalchemy import func as _func
  723. stats_q = db.query(TenantBalanceLog).filter(TenantBalanceLog.tenant_id == tenant_id)
  724. if biz_type:
  725. stats_q = stats_q.filter(TenantBalanceLog.biz_type == biz_type)
  726. if start_date:
  727. stats_q = stats_q.filter(TenantBalanceLog.created_at >= datetime.combine(start_date, _time.min))
  728. if end_date:
  729. stats_q = stats_q.filter(TenantBalanceLog.created_at <= datetime.combine(end_date, _time.max))
  730. total_recharge = float(stats_q.filter(TenantBalanceLog.change_amount > 0)
  731. .with_entities(_func.sum(TenantBalanceLog.change_amount)).scalar() or 0)
  732. total_consume = float(stats_q.filter(TenantBalanceLog.change_amount < 0)
  733. .with_entities(_func.sum(TenantBalanceLog.change_amount)).scalar() or 0)
  734. return {
  735. "items": items, "total": total, "page": page, "size": size,
  736. "total_recharge": total_recharge,
  737. "total_consume": total_consume,
  738. }