router.py 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626
  1. import time
  2. import uuid
  3. import json
  4. import requests
  5. import stripe
  6. from typing import List, Dict, Any, Optional
  7. from app.core.logger import logger
  8. from app.core.config import settings
  9. from fastapi import APIRouter, Request, Response, Query, Depends, Body, UploadFile, File
  10. from fastapi.responses import RedirectResponse
  11. from sqlalchemy.ext.asyncio import AsyncSession
  12. from app.utils.validation_utils import validate_user_inputs
  13. from app.core.redis import get_redis_client
  14. from app.core.database import get_db
  15. from app.core.auth import get_current_user
  16. from redis.asyncio import Redis
  17. from app.utils.response import success, fail
  18. from app.models.user import VasUser
  19. from app.models.order import VasOrder
  20. from app.models.schema import VasSchema
  21. from app.models.product import VasProduct
  22. from app.models.payment import VasPayment
  23. from app.schemas.common import ApiResponse, PageResponse
  24. from app.schemas.troov import TroovRate, TroovCheckForbiddenInput, TroovProb
  25. from app.schemas.sms import ShortMessageDetail, SmsSendIn
  26. from app.schemas.configuration import ConfigurationCreate, ConfigurationUpdate, ConfigurationOut
  27. from app.schemas.email_authorizations import EmailContent, EmailAuthorizationCreate, EmailAuthorizationUpdate, EmailAuthorizationOut
  28. from app.schemas.emails import VasEmailCreate, VasEmailOut
  29. from app.schemas.card import CardCreate, CardOut
  30. from app.schemas.task import TaskCreate, TaskOut, TaskUpdate
  31. from app.schemas.short_url import ShortUrlCreate, ShortUrlOut
  32. from app.schemas.http_session import HttpSessionCreate, HttpSessionUpdate,HttpSessionOut
  33. from app.schemas.auth import SendBindCodeRequest, SendResetCodeRequest, BindEmailRequest, ResetPasswordRequest, LoginRequest, LoginData, AutoRegisterRequest, AutoRegisterData
  34. from app.schemas.user import VasUserCreate, VasUserUpdate, VasUserSetProfiles, VasUserOut
  35. from app.schemas.product import VasProductCreate, VasProductUpdate, VasProductOut
  36. from app.schemas.product_routing import VasProductRoutingCreate, VasProductRoutingOut
  37. from app.schemas.schema import VasSchemaCreate, VasSchemaUpdate, VasSchemaOut
  38. from app.schemas.order import VasOrderCreate, VasOrderAdjustPrice, VasOrderPatchUserInputs, VasOrderOut
  39. from app.schemas.payment import VasPaymentCreate, AdminUpdateStatusPayload, VasPaymentOut
  40. from app.schemas.payment_confirmation import VasPaymentConfirmationCreate, VasPaymentConfirmationUpdate, VasPaymentConfirmationOut
  41. from app.schemas.payment_qr import VasPaymentQrCreate, VasPaymentQrSetEnableIn, VasPaymentQrOut
  42. from app.schemas.payment_provider import VasPaymentProviderCreate, VasPaymentProviderUpdate, VasPaymentProviderOut
  43. from app.schemas.webhook import SMSHelperWebhookPayload
  44. from app.schemas.vas_task import VasTaskCreate, VasTaskUpdate, VasExpiringTaskItem, VasTaskOut
  45. from app.schemas.ticket import VasTicketCreate, VasTicketOut, VasTicketStatusUpdate, VasTicketMessageCreate, VasTicketMessageOut
  46. from app.schemas.slot_snapshot import SlotSnapshotCreate, SlotSnapshotOut, SlotOverviewOut
  47. from app.schemas.slot_refresh_status import RefreshBase, RefreshFail, RefreshStatusOut
  48. from app.schemas.telegram import TelegramIn, TelegramNoTokenIn
  49. from app.schemas.wechat import WechatIn, WechatNoTokenIn
  50. from app.schemas.whatsapp import WhatsappIn, WhatsappNoTokenIn
  51. from app.schemas.notification_outbox import NotificationOutboxCreate, NotificationOutboxUpdate, NotificationOutboxOut
  52. from app.schemas.resource import FileUploadOut
  53. from app.schemas.statistics import VasStatisticsOverviewOut
  54. from app.schemas.llm import ParseUserInputsPayload, ParseUserInputsOut
  55. from app.schemas.account import AccountResponse, AccountCreate, LockRequest
  56. from app.schemas.docker_remote import RemoteServerConfig, DockerStatusOut, DockerLogsRequest, DockerLogsOut, ConfigReadOut, ConfigReadRequest, ConfigUpdateRequest, LogReadRequest, LogReadOut, LogListOut, DockerContainerStatus, DockerActionRequest, ServerConfigItem, ServerListOut, RemoteActionRequest
  57. from app.schemas.order_event import VasOrderEventCreate, VasOrderEventOut
  58. from app.schemas.troov_session import TroovSessionCreate, TroovSessionUpdate, TroovSessionOut
  59. from app.services.docker_remote_service import DockerRemoteService
  60. from app.services.configuration_service import ConfigurationService
  61. from app.services.troov_service import TroovService
  62. from app.services.visametric_service import VisametricService
  63. from app.services.sms_service import save_short_message, query_short_message, send_sms
  64. from app.services.email_authorizations_service import EmailAuthorizationService
  65. from app.services.emails_service import EmailsService
  66. from app.services.short_url_service import ShortUrlService
  67. from app.services.task_service import TaskService
  68. from app.services.card_service import CardService
  69. from app.services.seaweedfs_service import SeaweedFSService
  70. from app.services.http_session_service import HttpSessionService
  71. from app.services.fake_service import FakeService
  72. from app.services.auth_service import AuthService
  73. from app.services.user_service import UserService
  74. from app.services.product_service import ProductService
  75. from app.services.product_routing_service import ProductRoutingService
  76. from app.services.order_service import OrderService
  77. from app.services.schema_service import SchemaService
  78. from app.services.payment_service import PaymentService
  79. from app.services.payment_provider_service import PaymentProviderService
  80. from app.services.payment_qr_service import PaymentQrService
  81. from app.services.vas_task_service import VasTaskService
  82. from app.services.webhook_service import WebhookService
  83. from app.services.notification_service import NotificationService
  84. from app.services.ticket_service import TicketService
  85. from app.services.telegram_service import TelegramService
  86. from app.services.wechat_service import WechatService
  87. from app.services.whatsapp_service import WhatsappService
  88. from app.services.notification_outbox_service import NotificationOutboxService
  89. from app.services.slot_snapshot_service import SlotSnapshotService
  90. from app.services.statistics_service import StatisticsService
  91. from app.services.llm_service import LlmService
  92. from app.services.slot_refresh_status_service import SlotRefreshStatusService
  93. from app.services.account_service import AccountService
  94. from app.services.order_event_service import OrderEventService
  95. from app.services.troov_session_service import TroovSessionService
  96. # 公共路由
  97. public_router = APIRouter()
  98. # 受保护路由
  99. protected_router = APIRouter()
  100. # 管理员级别路由
  101. admin_required_router = APIRouter()
  102. @admin_required_router.get("/ping", summary="心跳检测", tags=["测试接口"])
  103. async def ping():
  104. return {"message": "pong"}
  105. # -----------------------
  106. # Docker 远程控制 (预配置服务器)
  107. # -----------------------
  108. @admin_required_router.get("/remote/servers", summary="获取所有预配置服务器", tags=["Docker远程控制"], response_model=ApiResponse[ServerListOut])
  109. async def list_remote_servers(db: AsyncSession = Depends(get_db)):
  110. from app.services.remote_server_service import RemoteServerService
  111. servers_db = await RemoteServerService.get_all(db)
  112. servers = [
  113. ServerConfigItem(id=s.server_id, name=s.name, host=s.host)
  114. for s in servers_db
  115. ]
  116. return success(data=ServerListOut(servers=servers))
  117. @admin_required_router.post("/remote/server/docker/status", summary="获取预配置服务器容器状态", tags=["Docker远程控制"], response_model=ApiResponse[DockerStatusOut])
  118. async def server_docker_status(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  119. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  120. res = await DockerRemoteService.get_container_status(config)
  121. return success(data=DockerStatusOut(containers=res))
  122. @admin_required_router.post("/remote/server/docker/up", summary="启动预配置服务器Docker Compose", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  123. async def server_docker_up(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  124. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  125. res = await DockerRemoteService.docker_compose_up(config, request.services)
  126. return success(data=res)
  127. @admin_required_router.post("/remote/server/docker/down", summary="停止预配置服务器Docker Compose", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  128. async def server_docker_down(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  129. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  130. res = await DockerRemoteService.docker_compose_down(config, request.services)
  131. return success(data=res)
  132. @admin_required_router.post("/remote/server/docker/restart", summary="重启预配置服务器容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  133. async def server_docker_restart(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  134. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  135. res = await DockerRemoteService.docker_restart(config, request.container_name)
  136. return success(data=res)
  137. @admin_required_router.post("/remote/server/docker/start", summary="启动预配置服务器容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  138. async def server_docker_start(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  139. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  140. res = await DockerRemoteService.docker_start(config, request.container_name)
  141. return success(data=res)
  142. @admin_required_router.post("/remote/server/docker/stop", summary="停止预配置服务器容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  143. async def server_docker_stop(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  144. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  145. res = await DockerRemoteService.docker_stop(config, request.container_name)
  146. return success(data=res)
  147. @admin_required_router.post("/remote/server/docker/logs", summary="查看预配置服务器容器日志", tags=["Docker远程控制"], response_model=ApiResponse[DockerLogsOut])
  148. async def server_docker_logs(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  149. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  150. # 构造 DockerLogsRequest
  151. log_req = DockerLogsRequest(
  152. **config.model_dump(),
  153. container_name=request.container_name,
  154. lines=request.lines,
  155. follow=request.follow
  156. )
  157. res = await DockerRemoteService.docker_logs(config, log_req)
  158. return success(data=DockerLogsOut(logs=res))
  159. @admin_required_router.post("/remote/server/config/read", summary="读取预配置服务器配置文件", tags=["Docker远程控制"], response_model=ApiResponse[Dict[str, Any]])
  160. async def server_docker_config_read(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  161. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  162. res = await DockerRemoteService.read_config(config, request.config_file)
  163. return success(data={"config": res})
  164. @admin_required_router.post("/remote/server/config/update", summary="更新预配置服务器配置文件", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  165. async def server_docker_config_update(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  166. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  167. # 构造 ConfigUpdateRequest
  168. update_req = ConfigUpdateRequest(
  169. **config.model_dump(),
  170. config_file=request.config_file,
  171. key_path=request.key_path,
  172. value=request.value
  173. )
  174. res = await DockerRemoteService.update_config(config, update_req)
  175. return success(data=res)
  176. @admin_required_router.post("/remote/server/log/list", summary="列出预配置服务器日志文件", tags=["Docker远程控制"], response_model=ApiResponse[LogListOut])
  177. async def server_docker_logs_list(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  178. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  179. res = await DockerRemoteService.list_logs(config)
  180. return success(data=LogListOut(log_files=res))
  181. @admin_required_router.post("/remote/server/log/read", summary="读取预配置服务器日志文件内容", tags=["Docker远程控制"], response_model=ApiResponse[LogReadOut])
  182. async def server_docker_logs_read(request: RemoteActionRequest, db: AsyncSession = Depends(get_db)):
  183. config = await DockerRemoteService.get_server_config(db, request.server_id, request.project_path)
  184. # 构造 LogReadRequest
  185. read_req = LogReadRequest(
  186. **config.model_dump(),
  187. log_file=request.log_file,
  188. lines=request.lines,
  189. from_head=request.from_head,
  190. full=request.full
  191. )
  192. res = await DockerRemoteService.read_log(config, read_req)
  193. return success(data=LogReadOut(content=res))
  194. # -----------------------
  195. # Docker 远程控制 (直连模式 - 仅供调试)
  196. # -----------------------
  197. @admin_required_router.post("/remote/docker/status", summary="获取容器状态", tags=["Docker远程控制"], response_model=ApiResponse[DockerStatusOut])
  198. async def docker_status(config: RemoteServerConfig):
  199. res = await DockerRemoteService.get_container_status(config)
  200. return success(data=DockerStatusOut(containers=res))
  201. @admin_required_router.post("/remote/docker/up", summary="启动Docker Compose服务", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  202. async def docker_up(config: RemoteServerConfig, services: Optional[List[str]] = None):
  203. res = await DockerRemoteService.docker_compose_up(config, services)
  204. return success(data=res)
  205. @admin_required_router.post("/remote/docker/down", summary="停止Docker Compose服务", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  206. async def docker_down(config: RemoteServerConfig, services: Optional[List[str]] = None):
  207. res = await DockerRemoteService.docker_compose_down(config, services)
  208. return success(data=res)
  209. @admin_required_router.post("/remote/docker/restart", summary="重启容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  210. async def docker_restart(request: DockerActionRequest):
  211. res = await DockerRemoteService.docker_restart(request, request.container_name)
  212. return success(data=res)
  213. @admin_required_router.post("/remote/docker/start", summary="启动容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  214. async def docker_start(request: DockerActionRequest):
  215. res = await DockerRemoteService.docker_start(request, request.container_name)
  216. return success(data=res)
  217. @admin_required_router.post("/remote/docker/stop", summary="停止容器", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  218. async def docker_stop(request: DockerActionRequest):
  219. res = await DockerRemoteService.docker_stop(request, request.container_name)
  220. return success(data=res)
  221. @admin_required_router.post("/remote/docker/logs", summary="查看容器日志", tags=["Docker远程控制"], response_model=ApiResponse[DockerLogsOut])
  222. async def docker_logs(request: DockerLogsRequest):
  223. res = await DockerRemoteService.docker_logs(request, request)
  224. return success(data=DockerLogsOut(logs=res))
  225. @admin_required_router.post("/remote/config/read", summary="读取配置文件", tags=["Docker远程控制"], response_model=ApiResponse[Dict[str, Any]])
  226. async def docker_config_read(request: ConfigReadRequest):
  227. res = await DockerRemoteService.read_config(request, request.config_file)
  228. return success(data={"config": res})
  229. @admin_required_router.post("/remote/config/update", summary="更新配置文件", tags=["Docker远程控制"], response_model=ApiResponse[bool])
  230. async def docker_config_update(request: ConfigUpdateRequest):
  231. res = await DockerRemoteService.update_config(request, request)
  232. return success(data=res)
  233. @admin_required_router.post("/remote/log/list", summary="列出日志文件", tags=["Docker远程控制"], response_model=ApiResponse[LogListOut])
  234. async def docker_logs_list(config: RemoteServerConfig):
  235. res = await DockerRemoteService.list_logs(config)
  236. return success(data=LogListOut(log_files=res))
  237. @admin_required_router.post("/remote/log/read", summary="读取日志文件内容", tags=["Docker远程控制"], response_model=ApiResponse[LogReadOut])
  238. async def docker_logs_read(request: LogReadRequest):
  239. res = await DockerRemoteService.read_log(request, request)
  240. return success(data=LogReadOut(content=res))
  241. @admin_required_router.get("/sms/upload", summary="上报短信", tags=["短信接口"], response_model=ApiResponse[ShortMessageDetail])
  242. async def sms_upload(
  243. phone: str = Query(..., description="手机号"),
  244. message: str = Query(..., description="短信内容"),
  245. max_ttl: int = Query(300, description="短信保存时间(秒)"),
  246. redis_client: Redis = Depends(get_redis_client)
  247. ):
  248. """
  249. 保存短信到 Redis
  250. """
  251. received_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
  252. msg = await save_short_message(redis_client, phone, message, received_at, max_ttl)
  253. return success(data=msg)
  254. @admin_required_router.get("/sms/download", summary="读取短信", tags=["短信接口"], response_model=ApiResponse[List[ShortMessageDetail]])
  255. async def sms_download(
  256. phone: str = Query(..., description="手机号"),
  257. keyword: str = Query('', description="短信内容关键字"),
  258. sent_at: str = Query('', description="筛选时间(可选)"),
  259. redis_client: Redis = Depends(get_redis_client)
  260. ):
  261. """
  262. 查询短信(支持关键字和时间过滤)
  263. """
  264. obj = await query_short_message(redis_client, phone, keyword or None, sent_at or None)
  265. return success(data=obj)
  266. @admin_required_router.post("/sms/send", summary="发送短信", tags=["短信接口"], response_model=ApiResponse[str])
  267. async def sms_send(
  268. payload: SmsSendIn,
  269. ):
  270. res = await send_sms(
  271. send_to=payload.send_to,
  272. sender=payload.sender,
  273. content=payload.content,
  274. )
  275. return success(data=res)
  276. @admin_required_router.get("/troov/rate", summary="TROOV 查询rate", tags=["通用接口"], response_model=ApiResponse[List[TroovRate]])
  277. async def troov_rate(date: str = Query(..., description="查询的日期, 格式: YYYY-MM-DD"),
  278. redis_client: Redis = Depends(get_redis_client)):
  279. # 调用 service 层获取数据
  280. obj = await TroovService.get_rate_by_date(redis_client, date)
  281. return success(data=obj)
  282. @admin_required_router.post("/troov/book", summary="TROOV 查询ForbiddenUsers", tags=["通用接口"], response_model=ApiResponse)
  283. async def troov_check_forbiddenusers(
  284. payload: TroovCheckForbiddenInput,
  285. redis_client: Redis = Depends(get_redis_client)
  286. ):
  287. # 调用 service 层获取数据
  288. obj = await TroovService.check_for_forbiddenusers(redis_client, payload)
  289. return success(data=obj)
  290. @admin_required_router.get("/troov/list-probs", summary="TROOV 查询所有概率", tags=["通用接口"], response_model=ApiResponse[List[TroovProb]])
  291. async def troov_get_all_probs(
  292. redis_client: Redis = Depends(get_redis_client)
  293. ):
  294. obj = await TroovService.get_all_probs(redis_client)
  295. return success(data=obj)
  296. @admin_required_router.post("/troov/set-prob", summary="TROOV 修改概率", tags=["通用接口"], response_model=ApiResponse[List[TroovProb]])
  297. async def troov_set_prob(
  298. payload: TroovProb,
  299. redis_client: Redis = Depends(get_redis_client)
  300. ):
  301. obj = await TroovService.set_prob(redis_client, payload)
  302. return success(data=obj)
  303. @admin_required_router.delete("/troov/del-prob", summary="TROOV 删除概率", tags=["通用接口"], response_model=ApiResponse[List[TroovProb]])
  304. async def troov_del_prob(
  305. payload: TroovProb,
  306. redis_client: Redis = Depends(get_redis_client)
  307. ):
  308. obj = await TroovService.del_prob(redis_client, payload)
  309. return success(data=obj)
  310. @admin_required_router.post("/troov/reset-probs", summary="TROOV 重置概率", tags=["通用接口"], response_model=ApiResponse[List[TroovProb]])
  311. async def troov_reset_prob(
  312. date: str,
  313. redis_client: Redis = Depends(get_redis_client)
  314. ):
  315. obj = await TroovService.reset_probs(redis_client, date)
  316. return success(data=obj)
  317. @admin_required_router.post("/visametric/update_pnr", summary="VISAMETRIC 读取PNR", tags=["Visametric专用"], response_model=ApiResponse[VasTaskOut])
  318. async def visametric_update_pnr(
  319. task_id: int,
  320. db: AsyncSession = Depends(get_db)
  321. ):
  322. obj = await VisametricService.update_pnr(db, task_id)
  323. return success(data=obj)
  324. @admin_required_router.post("/visametric/cancel_appointment", summary="VISAMETRIC 取消预约", tags=["Visametric专用"], response_model=ApiResponse)
  325. async def visametric_cancel_appointment(
  326. task_id: int,
  327. db: AsyncSession = Depends(get_db),
  328. redis_client: Redis = Depends(get_redis_client)
  329. ):
  330. await VisametricService.cancel_appointment(db, redis_client, task_id)
  331. return success()
  332. @admin_required_router.post("/dynamic-configurations", summary="创建动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  333. async def dynamic_config_create(config_in: ConfigurationCreate, db: AsyncSession = Depends(get_db)):
  334. obj = await ConfigurationService.create(db, config_in)
  335. return success(data=obj)
  336. @admin_required_router.get("/dynamic-configurations/all", summary="读取所有动态配置", tags=["动态配置"], response_model=ApiResponse[List[ConfigurationOut]])
  337. async def dynamic_config_get_all(db: AsyncSession = Depends(get_db)):
  338. obj = await ConfigurationService.get_all(db)
  339. return success(data=obj)
  340. @admin_required_router.get("/dynamic-configurations/key/{config_key}", summary="根据Key读取动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  341. async def dynamic_config_get_by_key(config_key: str, db: AsyncSession = Depends(get_db)):
  342. config = await ConfigurationService.get_by_key(db, config_key)
  343. return success(data=config)
  344. @admin_required_router.put("/dynamic-configurations/key/{config_key}", summary="根据Key更新动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  345. async def dynamic_config_update_by_key(config_key: str, config_in: ConfigurationUpdate, db: AsyncSession = Depends(get_db)):
  346. config = await ConfigurationService.update_by_key(db, config_key, config_in)
  347. return success(data=config)
  348. @admin_required_router.delete("/dynamic-configurations/key/{config_key}", summary="根据Key删除动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  349. async def dynamic_config_delete_by_key(config_key: str, db: AsyncSession = Depends(get_db)):
  350. config = await ConfigurationService.delete_by_key(db, config_key)
  351. return success(data=config)
  352. @admin_required_router.post("/http-session", summary="创建http session", tags=["会话管理"],response_model=ApiResponse[HttpSessionOut])
  353. async def http_session_create(
  354. data: HttpSessionCreate,
  355. db: AsyncSession = Depends(get_db)
  356. ):
  357. logger.info(f"[Create HttpSession] sid={data.session_id}")
  358. obj = await HttpSessionService.create(db, data)
  359. return success(data=obj)
  360. @admin_required_router.delete("/http-session", summary="删除http session", tags=["会话管理"], response_model=ApiResponse)
  361. async def http_session_delete_by_sid(
  362. session_id: str = Query(...),
  363. db: AsyncSession = Depends(get_db)
  364. ):
  365. logger.info(f"[Delete HttpSession] sid={session_id}")
  366. await HttpSessionService.delete_by_sid(db, session_id)
  367. return success()
  368. @admin_required_router.put("/http-session", summary="更新http session", tags=["会话管理"], response_model=ApiResponse[HttpSessionOut])
  369. async def http_session_update_by_sid(
  370. session_id: str = Query(...),
  371. data: HttpSessionUpdate = Body(...),
  372. db: AsyncSession = Depends(get_db)
  373. ):
  374. logger.info(f"[Update HttpSession] sid={session_id}")
  375. obj = await HttpSessionService.update_by_sid(db, session_id, data)
  376. return success(data=obj)
  377. @admin_required_router.get("/http-session", summary="读取http session", tags=["会话管理"],response_model=ApiResponse[HttpSessionOut])
  378. async def http_session_get_by_sid(
  379. session_id: str = Query(...),
  380. db: AsyncSession = Depends(get_db)
  381. ):
  382. logger.info(f"[Get HttpSession] sid={session_id}")
  383. obj = await HttpSessionService.get_by_sid(db, session_id)
  384. return success(data=obj)
  385. @admin_required_router.get("/email-authorizations", summary="查询所有内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[List[EmailAuthorizationOut]])
  386. async def email_authorizations_get(db: AsyncSession = Depends(get_db)):
  387. obj = await EmailAuthorizationService.get_all(db)
  388. return success(data=obj)
  389. @admin_required_router.post("/email-authorizations", summary="创建内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  390. async def email_authorizations_create(data: EmailAuthorizationCreate, db: AsyncSession = Depends(get_db)):
  391. obj = await EmailAuthorizationService.create(db, data)
  392. return success(data=obj)
  393. @admin_required_router.get("/email-authorizations/{id}", summary="通过id查询内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  394. async def email_authorizations_get_by_id(id: int, db: AsyncSession = Depends(get_db)):
  395. email_auth = await EmailAuthorizationService.get_by_id(db, id)
  396. return success(data=email_auth)
  397. @admin_required_router.put("/email-authorizations/{id}", summary="通过id更新内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  398. async def email_authorizations_update_by_id(id: int, data: EmailAuthorizationUpdate, db: AsyncSession = Depends(get_db)):
  399. updated = await EmailAuthorizationService.update(db, id, data)
  400. return success(data=updated)
  401. @admin_required_router.delete("/email-authorizations/{id}", summary="通过id删除内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  402. async def email_authorizations_delete_by_id(id: int, db: AsyncSession = Depends(get_db)):
  403. deleted = await EmailAuthorizationService.delete(db, id)
  404. return success(data=deleted)
  405. @admin_required_router.get("/email-authorizations/email/{email}", summary="通过邮箱地址查询内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  406. async def email_authorizations_get_by_email(email: str, db: AsyncSession = Depends(get_db)):
  407. email_auth = await EmailAuthorizationService.get_by_email(db, email)
  408. return success(data=email_auth)
  409. @admin_required_router.post("/email-authorizations/fetch", summary="读取邮件, 仅限文本内容", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  410. async def email_authorizations_fetch_email(
  411. email: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  412. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  413. recipient: str = Query(..., description="收件人账号或者名字"),
  414. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  415. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  416. sentDate: str = Query(..., description="发件日期, UTC时间, 格式: yyyy-mm-dd hh:mm:ss"),
  417. expiry: int = Query(300, description="邮件有效期, 单位秒, 从sentDate 开始算起"),
  418. db: AsyncSession = Depends(get_db)
  419. ):
  420. auth = await EmailAuthorizationService.get_by_email(db, email)
  421. print(auth)
  422. result = await EmailAuthorizationService.fetch_email_authorizations(
  423. auth,
  424. sender=sender,
  425. recipient=recipient,
  426. subject_keywords=subjectKeywords,
  427. body_keywords=bodyKeywords,
  428. sent_date=sentDate,
  429. expiry=expiry,
  430. only_text=True
  431. )
  432. return success(data={"body": result})
  433. @admin_required_router.post("/email-authorizations/fetch-top", summary="从最近的几封邮件读取目标邮件,仅限文本内容, 性能会更好, 邮件多时有可能漏读", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  434. async def email_authorizations_fetch_email_from_topn(
  435. email: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  436. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  437. recipient: str = Query(..., description="收件人账号或者名字"),
  438. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  439. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  440. top: int = Query(10, description="指定从最近几封邮件读取"),
  441. db: AsyncSession = Depends(get_db)
  442. ):
  443. auth = await EmailAuthorizationService.get_by_email(db, email)
  444. result = await EmailAuthorizationService.fetch_email_authorizations_from_top_n(
  445. auth,
  446. sender=sender,
  447. recipient=recipient,
  448. subject_keywords=subjectKeywords,
  449. body_keywords=bodyKeywords,
  450. top=top,
  451. only_text=True
  452. )
  453. return success(data={"body": result})
  454. @admin_required_router.post("/email-authorizations/forward", summary="转发邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  455. async def email_authorizations_forward_email(
  456. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  457. forwardTo: str = Query(..., description="转发到哪个邮箱地址, 格式: xxx@xxx.xxx"),
  458. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  459. recipient: str = Query(..., description="收件人账号或者名字"),
  460. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  461. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  462. db: AsyncSession = Depends(get_db)
  463. ):
  464. auth = await EmailAuthorizationService.get_by_email(db, emailAccount)
  465. result = await EmailAuthorizationService.forward_first_matching_email(
  466. auth,
  467. forward_to = forwardTo,
  468. sender = sender,
  469. recipient = recipient,
  470. subject_keywords = subjectKeywords,
  471. body_keywords = bodyKeywords
  472. )
  473. return success(data={"body": result})
  474. @admin_required_router.post("/email-authorizations/forward2", summary="转发邮件(新)", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  475. async def email_authorizations_forward_email2(
  476. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  477. forwardTo: str = Query(..., description="转发到哪个邮箱地址, 格式: xxx@xxx.xxx"),
  478. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  479. recipient: str = Query(..., description="收件人账号或者名字"),
  480. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  481. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  482. db: AsyncSession = Depends(get_db)
  483. ):
  484. auth = await EmailAuthorizationService.get_by_email(db, emailAccount)
  485. result = await EmailAuthorizationService.forward_first_matching_email2(
  486. db,
  487. auth,
  488. forward_to = forwardTo,
  489. sender = sender,
  490. recipient = recipient,
  491. subject_keywords = subjectKeywords,
  492. body_keywords = bodyKeywords
  493. )
  494. return success(data={"body": result})
  495. @admin_required_router.post("/email-authorizations/sendmail", summary="发送邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  496. async def email_authorizations_send_email(
  497. emailAccount: str = Query(..., description="发件账号, 格式: xxx@xxx.xxx"),
  498. sendTo: str = Query(..., description="收件人邮箱账号"),
  499. subject: str = Query(..., description="邮件主题"),
  500. contentType: str = Query("text", description="内容格式,支持 text 和 html"),
  501. content: EmailContent = Body("", description="邮件内容"),
  502. db: AsyncSession = Depends(get_db)
  503. ):
  504. auth = await EmailAuthorizationService.get_by_email(db, emailAccount)
  505. result = await EmailAuthorizationService.send_email(
  506. auth,
  507. send_to = sendTo,
  508. subject = subject,
  509. content_type = contentType,
  510. content = content.body
  511. )
  512. return success(data={"body": result})
  513. @admin_required_router.post("/email-authorizations/sendmail-bulk", summary="群发送邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  514. async def email_authorizations_send_email_bulk(
  515. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  516. sendTo: str = Query(..., description="收件人邮箱账号,多个用逗号隔开"),
  517. subject: str = Query(..., description="邮件主题"),
  518. contentType: str = Query("text", description="内容格式,支持 text 和 html"),
  519. content: EmailContent = Body(..., description="邮件内容"),
  520. db: AsyncSession = Depends(get_db)
  521. ):
  522. auth = await EmailAuthorizationService.get_by_email(db, emailAccount)
  523. result = await EmailAuthorizationService.send_email_bulk(
  524. auth,
  525. send_to = sendTo,
  526. subject = subject,
  527. content_type = contentType,
  528. content = content.body
  529. )
  530. return success(data={"body": result})
  531. @admin_required_router.post("/emails/write", summary="写入邮件", tags=["邮箱接口"], response_model=ApiResponse[VasEmailOut])
  532. async def emails_write(
  533. payload: VasEmailCreate,
  534. db: AsyncSession = Depends(get_db)
  535. ):
  536. rec = await EmailsService.create(db, payload)
  537. return success(data=rec)
  538. @admin_required_router.get("/emails/max_uid", summary="获取最大邮件UID", tags=["邮箱接口"], response_model=ApiResponse[int])
  539. async def emails_get_max_uid(
  540. db: AsyncSession = Depends(get_db)
  541. ):
  542. uid = await EmailsService.get_max_uid(db)
  543. return success(data=uid)
  544. @admin_required_router.get("/account/list_all", summary="分页查询账号", tags=["账号管理"], response_model=ApiResponse[PageResponse[AccountResponse]])
  545. async def account_next(
  546. page: int = Query(0, description="第几页"),
  547. size: int = Query(10, description="分页大小"),
  548. keyword: str = Query("", description="查询条件"),
  549. db: AsyncSession = Depends(get_db)
  550. ):
  551. obj = await AccountService.list_all(db, page, size, keyword)
  552. return success(data=obj)
  553. @admin_required_router.get("/account/next", summary="获取下一个账号", tags=["账号管理"], response_model=ApiResponse[AccountResponse])
  554. async def account_next(
  555. pool_name: str,
  556. lock_duration: float = 60.0,
  557. db: AsyncSession = Depends(get_db)
  558. ):
  559. account = await AccountService.get_next_account(db, pool_name, lock_duration)
  560. return success(data=account)
  561. @admin_required_router.post("/account/add", summary="新增一个账号", tags=["账号管理"], response_model=ApiResponse[AccountResponse])
  562. async def account_add(
  563. payload: AccountCreate,
  564. db: AsyncSession = Depends(get_db)
  565. ):
  566. account = await AccountService.add_account(db, payload)
  567. return success(data=account)
  568. @admin_required_router.post("/account/lock", summary="锁定账号", tags=["账号管理"], response_model=ApiResponse)
  569. async def account_lock(
  570. payload: LockRequest,
  571. db: AsyncSession = Depends(get_db)
  572. ):
  573. await AccountService.manual_lock(db, payload)
  574. return success()
  575. @admin_required_router.post("/account/disable", summary="禁用账号", tags=["账号管理"], response_model=ApiResponse)
  576. async def account_disable(
  577. payload: LockRequest,
  578. db: AsyncSession = Depends(get_db)
  579. ):
  580. await AccountService.disable_account(db, payload)
  581. return success()
  582. @public_router.post("/resource/upload_file", summary="上传文件", tags=["文件管理"], response_model=ApiResponse[FileUploadOut])
  583. async def resource_upload_file(file: UploadFile = File(...)):
  584. result = await SeaweedFSService.upload(file)
  585. return success(data=result)
  586. @public_router.get("/resource/download_file", summary="下载文件", tags=["文件管理"])
  587. async def resource_get_file(fid: str):
  588. data = await SeaweedFSService.get(fid)
  589. content, mime = data
  590. return Response(content=content, media_type=mime)
  591. @admin_required_router.post("/s/generate", summary="生成短链接地址<压缩地址长度>", tags=["Short URL"], response_model=ApiResponse[ShortUrlOut])
  592. async def short_url_generate(
  593. data: ShortUrlCreate,
  594. db: AsyncSession = Depends(get_db),
  595. ):
  596. """生成短链接"""
  597. record = await ShortUrlService.create_short_url(db, data.long_url)
  598. return success(data=record)
  599. @public_router.get("/s/{short_key}", summary="访问短链接地址<自动重定向到真实链接地址>", tags=["Short URL"])
  600. async def short_url_request(short_key: str, db: AsyncSession = Depends(get_db)):
  601. """访问短链接自动重定向"""
  602. long_url = await ShortUrlService.get_long_url(db, short_key)
  603. return RedirectResponse(url=long_url, status_code=302)
  604. @admin_required_router.post("/tasks", summary="创建任务", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  605. async def task_create(data: TaskCreate, db: AsyncSession = Depends(get_db)):
  606. """创建任务"""
  607. task = await TaskService.create(db, data)
  608. return success(data=task)
  609. @admin_required_router.get("/tasks/{task_id:int}", summary="根据taskId读取任务状态", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  610. async def task_get_by_id(task_id: int, db: AsyncSession = Depends(get_db)):
  611. """获取任务"""
  612. task = await TaskService.get_by_id(db, task_id)
  613. return success(data=task)
  614. @admin_required_router.get("/tasks/pending", summary="获取等待执行的任务", tags=["任务管理接口"], response_model=ApiResponse[List[TaskOut]])
  615. async def task_get_pending(
  616. page: int = Query(0, description="第几页"),
  617. size: int = Query(10, description="分页大小"),
  618. command: str = Query(..., description="任务类型"),
  619. db: AsyncSession = Depends(get_db),
  620. ):
  621. """分页获取等待执行的任务"""
  622. obj = await TaskService.get_pending(db, command, page, size)
  623. return success(data=obj)
  624. @admin_required_router.put("/tasks/{task_id}", summary="根据taskId更新任务状态", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  625. async def task_update_by_id(task_id: int, data: TaskUpdate, db: AsyncSession = Depends(get_db)):
  626. """更新任务状态或结果"""
  627. updated = await TaskService.update(db, task_id, data)
  628. return success(data=updated)
  629. @admin_required_router.get("/task/pop", summary="任务出队(pop)", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  630. async def task_pop_task(
  631. queue_name: str,
  632. db: AsyncSession = Depends(get_db),
  633. ):
  634. task = await TaskService.pop_task(db, queue_name)
  635. return success(data=task)
  636. @admin_required_router.post("/tg/send_message", summary="推送电报消息", tags=["消息推送接口"], response_model=ApiResponse)
  637. async def tg_send_message(
  638. payload: TelegramIn
  639. ):
  640. await TelegramService.push_text(payload)
  641. return success()
  642. @admin_required_router.post("/tg/send_message_no_token", summary="推送电报消息(无需token)", tags=["消息推送接口"], response_model=ApiResponse)
  643. async def tg_send_message_no_token(
  644. payload: TelegramNoTokenIn
  645. ):
  646. await TelegramService.push_text_no_token(payload)
  647. return success()
  648. @admin_required_router.post("/wechat/send", summary="推送微信文本消息", tags=["消息推送接口"], response_model=ApiResponse)
  649. async def wechat_send(
  650. payload: WechatIn
  651. ):
  652. await WechatService.push_text(payload.api_token, payload.message)
  653. return success()
  654. @admin_required_router.post("/wechat/send_no_token", summary="推送微信文本消息(无需token)", tags=["消息推送接口"], response_model=ApiResponse)
  655. async def wechat_send_no_token(
  656. payload: WechatNoTokenIn
  657. ):
  658. await WechatService.push_text_no_token(payload.message)
  659. return success()
  660. @admin_required_router.post("/wechat/send_markdown_no_token", summary="推送微信Markdown消息(无需token)", tags=["消息推送接口"], response_model=ApiResponse)
  661. async def wechat_send_markdown_no_token(
  662. payload: WechatNoTokenIn
  663. ):
  664. await WechatService.push_markdown_no_token(payload.message)
  665. return success()
  666. @admin_required_router.post("/wechat/send_markdown", summary="推送微信Markdown消息", tags=["消息推送接口"], response_model=ApiResponse)
  667. async def wechat_send_markdown(
  668. payload: WechatIn
  669. ):
  670. await WechatService.push_markdown(payload.api_token, payload.message)
  671. return success()
  672. @admin_required_router.post("/whatsapp/send", summary="推送WhatsApp消息", tags=["消息推送接口"], response_model=ApiResponse)
  673. async def whatsapp_send(
  674. payload: WhatsappIn
  675. ):
  676. api_base_url = payload.api_base_url or "https://waha.visafly.top"
  677. await WhatsappService.send_text(
  678. api_base_url=api_base_url,
  679. session=payload.session,
  680. chat_id=payload.chat_id,
  681. message=payload.message,
  682. api_key=payload.api_key,
  683. )
  684. return success()
  685. @admin_required_router.post("/whatsapp/send_no_token", summary="推送WhatsApp消息(无需token)", tags=["消息推送接口"], response_model=ApiResponse)
  686. async def whatsapp_send_no_token(
  687. payload: WhatsappNoTokenIn
  688. ):
  689. await WhatsappService.send_text_no_token(
  690. chat_id=payload.chat_id,
  691. message=payload.message,
  692. )
  693. return success()
  694. @admin_required_router.post("/notification/outbox/create", summary="创建通知消息", tags=["消息推送接口"], response_model=ApiResponse[NotificationOutboxOut])
  695. async def notification_outbox_create(
  696. payload: NotificationOutboxCreate,
  697. db: AsyncSession = Depends(get_db)
  698. ):
  699. obj = await NotificationOutboxService.create(db, payload)
  700. return success(data=obj)
  701. @admin_required_router.post("/notification/outbox/update", summary="更新通知消息", tags=["消息推送接口"], response_model=ApiResponse[NotificationOutboxOut])
  702. async def notification_outbox_update(
  703. id: int,
  704. payload: NotificationOutboxUpdate,
  705. db: AsyncSession = Depends(get_db)
  706. ):
  707. obj = await NotificationOutboxService.update(db, id, payload)
  708. return success(data=obj)
  709. @admin_required_router.get("/notification/outbox/detail", summary="获取通知详情", tags=["消息推送接口"], response_model=ApiResponse[NotificationOutboxOut])
  710. async def notification_outbox_detail(
  711. id: int,
  712. db: AsyncSession = Depends(get_db)
  713. ):
  714. obj = await NotificationOutboxService.get(db, id)
  715. return success(data=obj)
  716. @admin_required_router.get("/notification/outbox/list", summary="分页查询通知列表", tags=["消息推送接口"], response_model=ApiResponse[PageResponse[NotificationOutboxOut]])
  717. async def notification_outbox_list(
  718. status: str = Query("", description="状态"),
  719. channel: str = Query("", description="渠道"),
  720. priority: int = Query(0, description="优先级"),
  721. msg_id: str = Query("", description="消息ID"),
  722. page: int = Query(1, description="第几页"),
  723. size: int = Query(20, description="分页大小"),
  724. db: AsyncSession = Depends(get_db)
  725. ):
  726. obj = await NotificationOutboxService.list(db, status, channel, priority, msg_id, page, size)
  727. return success(data=obj)
  728. @admin_required_router.post("/cards/publish", summary="创建新的消息卡片", tags=["信息卡片接口"], response_model=ApiResponse[CardOut])
  729. async def cards_publish(
  730. data: CardCreate = Body(...),
  731. db: AsyncSession = Depends(get_db)
  732. ):
  733. obj = await CardService.create(db, data)
  734. return success(data=obj)
  735. @public_router.get("/cards/view2", summary="根据关键词分页查询卡片, 可选择语言", tags=["信息卡片接口"], response_model=ApiResponse[PageResponse[CardOut]])
  736. async def cards_view_paginated2(
  737. keyword: str = Query("", description="查询的关键词"),
  738. page: int = Query(0, description="第几页"),
  739. size: int = Query(10, description="分页大小"),
  740. culture: str = Query("english", description="语言, 可设置 chinese, english"),
  741. db: AsyncSession = Depends(get_db)
  742. ):
  743. obj = await CardService.list_by_keyword(db, keyword, page, size, culture)
  744. return success(data=obj)
  745. @admin_required_router.get("/fake/orders", summary="生成虚假的订单信息", tags=["数据生成"], response_model=ApiResponse[List[VasOrderOut]])
  746. async def fake_generate_fake_orders(
  747. num: int = Query(1, description="生成几个数据"),
  748. product_id = Query(1, description="商品Id"),
  749. db: AsyncSession = Depends(get_db),
  750. current_user: VasUser = Depends(get_current_user),
  751. ):
  752. orders = await FakeService.generate_orders(db, num, product_id, current_user)
  753. return success(data=orders)
  754. @public_router.get("/slots/latest", summary="查询最近的slot", tags=["Slot数据"], response_model=ApiResponse[SlotSnapshotOut])
  755. async def slots_latest_get(
  756. country: str = Query("", description="目的国家"),
  757. city: str = Query("", description="递交城市"),
  758. visa_type: str = Query("", description="签证类型"),
  759. db: AsyncSession = Depends(get_db)
  760. ):
  761. res = await SlotSnapshotService.latest_for(db, country, city, visa_type)
  762. return success(data=res)
  763. @public_router.get("/slots/overview", summary="查询最近的slot", tags=["Slot数据"], response_model=ApiResponse[List[SlotOverviewOut]])
  764. async def slots_latest_get(
  765. city: str = Query("", description="递交城市"),
  766. db: AsyncSession = Depends(get_db)
  767. ):
  768. res = await SlotSnapshotService.get_slot_overview(db, city)
  769. return success(data=res)
  770. @admin_required_router.post("/slots/report", summary="上报最近的slot", tags=["Slot数据"], response_model=ApiResponse[SlotSnapshotOut])
  771. async def slots_report(
  772. payload: SlotSnapshotCreate,
  773. db: AsyncSession = Depends(get_db),
  774. redis_client: Redis = Depends(get_redis_client)
  775. ):
  776. res = await SlotSnapshotService.report(db, redis_client, payload)
  777. return success(data=res)
  778. @admin_required_router.post("/slot_refresh/start", summary="刷新slot开始", tags=["Slot Monitor 监控"], response_model=ApiResponse[RefreshStatusOut])
  779. async def slot_refresh_start(data: RefreshBase, db: AsyncSession = Depends(get_db)
  780. ):
  781. data = await SlotRefreshStatusService.refresh_start(db, data)
  782. return success(data=data)
  783. @admin_required_router.post("/slot_refresh/success", summary="刷新slot成功", tags=["Slot Monitor 监控"], response_model=ApiResponse[RefreshStatusOut])
  784. async def slot_refresh_success(data: RefreshBase, db: AsyncSession = Depends(get_db)
  785. ):
  786. data = await SlotRefreshStatusService.refresh_success(db, data)
  787. return success(data=data)
  788. @admin_required_router.post("/slot_refresh/fail", summary="刷新slot失败", tags=["Slot Monitor 监控"], response_model=ApiResponse[RefreshStatusOut])
  789. async def slot_refresh_fail(data: RefreshFail,db: AsyncSession = Depends(get_db)
  790. ):
  791. data = await SlotRefreshStatusService.refresh_fail(db, data)
  792. return success(data=data)
  793. @admin_required_router.get("/slot_refresh/status", summary="查询刷新纪录", tags=["Slot Monitor 监控"], response_model=ApiResponse[List[RefreshStatusOut]])
  794. async def slot_refresh_status(db: AsyncSession = Depends(get_db)):
  795. data = await SlotRefreshStatusService.list_all(db)
  796. return success(data=data)
  797. @public_router.post("/webhook/smshelper", summary="双开助手Webhook", tags=["webhook"], response_model=ApiResponse)
  798. async def webhook_smshelper(
  799. payload: SMSHelperWebhookPayload,
  800. db: AsyncSession = Depends(get_db),
  801. redis_client: Redis = Depends(get_redis_client)
  802. ):
  803. logger.info(f'smshelper webhook title={payload.title}, content={payload.content}')
  804. if "微信支付" in payload.title:
  805. res = await WebhookService.smshelper_payment_webhook(db, payload)
  806. return success()
  807. @public_router.post("/webhook/stripe", summary="Stripe Webhook", tags=["webhook"], response_model=ApiResponse)
  808. async def webhook_stripe(
  809. request: Request,
  810. db: AsyncSession = Depends(get_db),
  811. redis_client: Redis = Depends(get_redis_client)
  812. ):
  813. payload = await request.body()
  814. sig_header = request.headers.get("stripe-signature")
  815. # 3️⃣ 打调试日志(安全,不要打 secret)
  816. logger.info("Stripe webhook received")
  817. logger.info("Stripe-Signature: %s", sig_header)
  818. logger.info("Payload bytes length: %s", len(payload))
  819. logger.info("Payload raw: %s", payload.decode("utf-8", errors="ignore"))
  820. logger.info(
  821. "Webhook secret prefix: %s",
  822. settings.stripe_webhook_secret[:8] if settings.stripe_webhook_secret else None,
  823. )
  824. event = stripe.Webhook.construct_event(
  825. payload=payload,
  826. sig_header=sig_header,
  827. secret=settings.stripe_webhook_secret,
  828. )
  829. res = await WebhookService.stripe_payment_webhook(db, event)
  830. return success()
  831. @public_router.post("/auth/auto-register", summary="自动注册", tags=["用户管理"], response_model=ApiResponse[AutoRegisterData])
  832. async def vas_auto_register(
  833. payload: AutoRegisterRequest,
  834. request: Request,
  835. db: AsyncSession = Depends(get_db)
  836. ):
  837. user_agent = request.headers.get("user-agent", "unknown")
  838. client_host = request.client.host
  839. x_forwarded_for = request.headers.get("x-forwarded-for")
  840. if x_forwarded_for:
  841. client_ip = x_forwarded_for.split(",")[0].strip()
  842. else:
  843. client_ip = client_host
  844. res = await AuthService.auto_register(db, payload, client_ip, user_agent)
  845. return success(data=res)
  846. @public_router.post("/auth/send-bind-code", summary="发送邮箱注册码 绑定邮箱用", tags=["用户管理"], response_model=ApiResponse)
  847. async def vas_send_bind_code(
  848. payload: SendBindCodeRequest,
  849. current_user: VasUser = Depends(get_current_user),
  850. db: AsyncSession = Depends(get_db),
  851. redis_client: Redis = Depends(get_redis_client)
  852. ):
  853. await AuthService.send_bind_code(db, payload, current_user, redis_client)
  854. return success(message="verify email sent, please check your inbox")
  855. @public_router.post("/auth/send-reset-code", summary="发送邮箱注册码 重置密码用", tags=["用户管理"], response_model=ApiResponse)
  856. async def vas_send_reset_code(
  857. payload: SendResetCodeRequest,
  858. db: AsyncSession = Depends(get_db),
  859. redis_client: Redis = Depends(get_redis_client)
  860. ):
  861. await AuthService.send_reset_code(db, payload, redis_client)
  862. return success(message="verify email sent, please check your inbox")
  863. @protected_router.post("/auth/bind-email", summary="绑定邮箱", tags=["用户管理"], response_model=ApiResponse[LoginData])
  864. async def vas_bind_email(
  865. payload: BindEmailRequest,
  866. request: Request,
  867. current_user: VasUser = Depends(get_current_user),
  868. db: AsyncSession = Depends(get_db),
  869. redis_client: Redis = Depends(get_redis_client)
  870. ):
  871. user_agent = request.headers.get("user-agent", "unknown")
  872. client_host = request.client.host
  873. x_forwarded_for = request.headers.get("x-forwarded-for")
  874. if x_forwarded_for:
  875. client_ip = x_forwarded_for.split(",")[0].strip()
  876. else:
  877. client_ip = client_host
  878. res = await AuthService.bind_email(db, payload, current_user, redis_client, client_ip, user_agent)
  879. return success(data=res)
  880. @public_router.post("/auth/reset-password", summary="重置密码", tags=["用户管理"], response_model=ApiResponse)
  881. async def vas_reset_password(
  882. payload: ResetPasswordRequest,
  883. db: AsyncSession = Depends(get_db)
  884. ):
  885. res = await AuthService.reset_password(db, payload)
  886. return success(data=res)
  887. @public_router.post("/auth/login", summary="邮箱登录", tags=["用户管理"], response_model=ApiResponse[LoginData])
  888. async def vas_login(
  889. payload: LoginRequest,
  890. request: Request,
  891. db: AsyncSession = Depends(get_db)
  892. ):
  893. user_agent = request.headers.get("user-agent", "unknown")
  894. client_host = request.client.host
  895. x_forwarded_for = request.headers.get("x-forwarded-for")
  896. if x_forwarded_for:
  897. client_ip = x_forwarded_for.split(",")[0].strip()
  898. else:
  899. client_ip = client_host
  900. res = await AuthService.login(db, payload, client_ip, user_agent)
  901. return success(data=res)
  902. @admin_required_router.get("/user/list_all", summary="获取所有用户", tags=["用户管理"], response_model=ApiResponse[PageResponse[VasUserOut]])
  903. async def vas_user_list_all(
  904. page: int = Query(0, description="第几页"),
  905. size: int = Query(10, description="分页大小"),
  906. keyword: str = Query("", description="查询条件"),
  907. db: AsyncSession = Depends(get_db)
  908. ):
  909. users = await UserService.list_all(db, page, size, keyword)
  910. return success(data=users)
  911. @admin_required_router.get("/user/detail", summary="获取用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  912. async def vas_user_get_detail(
  913. user_id: str,
  914. db: AsyncSession = Depends(get_db)
  915. ):
  916. user = await UserService.get(db, user_id)
  917. return success(data=user)
  918. @admin_required_router.post("/user/update", summary="更新用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  919. async def vas_user_update(
  920. uid: str,
  921. payload: VasUserUpdate,
  922. db: AsyncSession = Depends(get_db)
  923. ):
  924. updated = await UserService.update(db, uid, payload)
  925. return success(data=updated)
  926. @protected_router.post("/user/set_profiles", summary="更新用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  927. async def vas_user_update(
  928. payload: VasUserSetProfiles,
  929. current_user: VasUser = Depends(get_current_user),
  930. db: AsyncSession = Depends(get_db)
  931. ):
  932. updated = await UserService.set_profiles(db, current_user, payload)
  933. return success(data=updated)
  934. @admin_required_router.get("/vas/statistics/overview", summary="系统概览", tags=["Visafly签证系统"], response_model=ApiResponse[VasStatisticsOverviewOut])
  935. async def vas_statistics_overview(
  936. db: AsyncSession = Depends(get_db)
  937. ):
  938. overview = await StatisticsService.overview(db)
  939. return success(data=overview)
  940. @admin_required_router.post("/vas/product/create", summary="创建商品", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  941. async def vas_product_create(
  942. payload: VasProductCreate,
  943. db: AsyncSession = Depends(get_db)
  944. ):
  945. created_product = await ProductService.create(db, payload)
  946. return success(data=created_product)
  947. @admin_required_router.post("/vas/product/update", summary="更新商品", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  948. async def vas_product_update(
  949. id: int,
  950. payload: VasProductUpdate,
  951. db: AsyncSession = Depends(get_db)
  952. ):
  953. product = await ProductService.update(db, id, payload)
  954. return success(data=product)
  955. @public_router.get("/vas/product/list-enable", summary="获取商品列表", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasProductOut]])
  956. async def vas_product_list(
  957. country: str = Query("", description="目的国家"),
  958. visa_type: str = Query("", description="签证类型"),
  959. page: int = Query(0, description="第几页"),
  960. size: int = Query(10, description="分页大小"),
  961. keyword: str = Query("", description="查询条件"),
  962. db: AsyncSession = Depends(get_db)
  963. ):
  964. products = await ProductService.list_enable_product(db, country, visa_type, page, size, keyword)
  965. return success(data=products)
  966. @admin_required_router.get("/vas/product/list", summary="获取商品列表", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasProductOut]])
  967. async def vas_product_list(
  968. country: str = Query("", description="目的国家"),
  969. visa_type: str = Query("", description="签证类型"),
  970. page: int = Query(0, description="第几页"),
  971. size: int = Query(10, description="分页大小"),
  972. keyword: str = Query("", description="查询条件"),
  973. db: AsyncSession = Depends(get_db)
  974. ):
  975. products = await ProductService.list_product(db, country, visa_type, page, size, keyword)
  976. return success(data=products)
  977. @public_router.get("/vas/product/detail", summary="获取商品列表", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  978. async def vas_product_get_by_id(
  979. product_id: int,
  980. db: AsyncSession = Depends(get_db)
  981. ):
  982. products = await ProductService.get(db, product_id)
  983. return success(data=products)
  984. @admin_required_router.post("/vas/product_routing/create", summary="创建商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductRoutingOut])
  985. async def vas_product_routing_create(
  986. payload: VasProductRoutingCreate,
  987. db: AsyncSession = Depends(get_db)
  988. ):
  989. payload = await ProductRoutingService.create(db, payload)
  990. return success(data=payload)
  991. @admin_required_router.delete("/vas/product_routing/delete", summary="删除商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse)
  992. async def vas_product_routing_date(
  993. id: int,
  994. db: AsyncSession = Depends(get_db)
  995. ):
  996. await ProductRoutingService.delete(db, id)
  997. return success()
  998. @admin_required_router.get("/vas/product_routing/list", summary="获取商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasProductRoutingOut]])
  999. async def vas_product_routing_list_by_product(
  1000. product_id: int,
  1001. db: AsyncSession = Depends(get_db)
  1002. ):
  1003. product_routings = await ProductRoutingService.list_by_product(db, product_id)
  1004. return success(data=product_routings)
  1005. @admin_required_router.post("/vas/llm/data_parsing", summary="llm数据解析", tags=["Visafly签证系统"], response_model=ApiResponse[ParseUserInputsOut])
  1006. async def vas_llm_data_parsing(
  1007. payload: ParseUserInputsPayload,
  1008. db: AsyncSession = Depends(get_db)
  1009. ):
  1010. out = await LlmService.handle_parse(db, payload)
  1011. return success(data=out)
  1012. @public_router.get("/vas/schema/detail", summary="获取schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  1013. async def vas_schema_get(
  1014. schema_id: int,
  1015. db: AsyncSession = Depends(get_db)
  1016. ):
  1017. schema = await SchemaService.get(db, schema_id)
  1018. return success(data=schema)
  1019. @admin_required_router.post("/vas/schema/create", summary="新增schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  1020. async def vas_schema_create(
  1021. payload: VasSchemaCreate,
  1022. db: AsyncSession = Depends(get_db)
  1023. ):
  1024. schema = await SchemaService.create(db, payload)
  1025. return success(data=schema)
  1026. @admin_required_router.post("/vas/schema/update", summary="更新schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  1027. async def vas_schema_update(
  1028. id: int,
  1029. payload: VasSchemaUpdate,
  1030. db: AsyncSession = Depends(get_db)
  1031. ):
  1032. schema = await SchemaService.update(db, id, payload)
  1033. return success(data=schema)
  1034. @admin_required_router.delete("/vas/schema/delete", summary="删除schema", tags=["Visafly签证系统"], response_model=ApiResponse)
  1035. async def vas_schema_delete(
  1036. id: int,
  1037. db: AsyncSession = Depends(get_db)
  1038. ):
  1039. await SchemaService.delete(db, id)
  1040. return success()
  1041. @admin_required_router.get("/vas/schema/list", summary="获取schema列表", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasSchemaOut]])
  1042. async def vas_schema_list(
  1043. db: AsyncSession = Depends(get_db)
  1044. ):
  1045. schemas = await SchemaService.list_all(db)
  1046. return success(data=schemas)
  1047. @protected_router.post("/vas/order/create", summary="创建订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1048. async def vas_order_create(
  1049. payload: VasOrderCreate,
  1050. current_user: VasUser = Depends(get_current_user),
  1051. db: AsyncSession = Depends(get_db),
  1052. redis_client: Redis = Depends(get_redis_client)
  1053. ):
  1054. product = await ProductService.get(db, payload.product_id)
  1055. # ① 获取产品绑定的 schema
  1056. schema = await SchemaService.get(db, product.schema_id)
  1057. # ② 校验 user_inputs
  1058. validate_user_inputs(
  1059. schema_json=schema.schema_json,
  1060. user_inputs=payload.user_inputs,
  1061. )
  1062. created_order = await OrderService.create(db, payload, product, current_user, redis_client)
  1063. return success(data=created_order)
  1064. @admin_required_router.post("/vas/order/create_by_admin", summary="管理员创建订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1065. async def vas_order_create_by_admin(
  1066. payload: VasOrderCreate,
  1067. current_user: VasUser = Depends(get_current_user),
  1068. db: AsyncSession = Depends(get_db)
  1069. ):
  1070. product = await ProductService.get(db, payload.product_id)
  1071. # ① 获取产品绑定的 schema
  1072. schema = await SchemaService.get(db, product.schema_id)
  1073. # ② 校验 user_inputs
  1074. validate_user_inputs(
  1075. schema_json=schema.schema_json,
  1076. user_inputs=payload.user_inputs,
  1077. )
  1078. created_order = await OrderService.create_by_admin(db, payload, product, current_user)
  1079. return success(data=created_order)
  1080. @admin_required_router.post("/vas/order/adjust-price", summary="管理员调整订单价格", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1081. async def vas_order_adjust_price(
  1082. order_id: str,
  1083. payload: VasOrderAdjustPrice,
  1084. db: AsyncSession = Depends(get_db),
  1085. current_user: VasUser = Depends(get_current_user)
  1086. ):
  1087. order = await OrderService.adjust_order_price(db, order_id, payload)
  1088. return success(data=order)
  1089. @admin_required_router.get("/vas/order/detail", summary="查询订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1090. async def vas_order_create(
  1091. order_id: str,
  1092. db: AsyncSession = Depends(get_db),
  1093. ):
  1094. order = await OrderService.get(db, order_id)
  1095. return success(data=order)
  1096. @admin_required_router.post("/vas/order/patch_user_inputs", summary="更新订单的用户信息", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1097. async def vas_order_patch_user_inputs(
  1098. order_id: str,
  1099. payload: VasOrderPatchUserInputs,
  1100. db: AsyncSession = Depends(get_db),
  1101. ):
  1102. order = await OrderService.patch_user_inputs(db, order_id, payload)
  1103. return success(data=order)
  1104. @protected_router.get("/vas/order/list_by_user", summary="查看所有订单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasOrderOut]])
  1105. async def vas_order_list_by_user(
  1106. page: int = Query(0, description="第几页"),
  1107. size: int = Query(10, description="分页大小"),
  1108. keyword: str = Query("", description="查询条件"),
  1109. current_user: VasUser = Depends(get_current_user),
  1110. db: AsyncSession = Depends(get_db)
  1111. ):
  1112. orders = await OrderService.list_by_user(db, current_user.id, page, size, keyword)
  1113. return success(data=orders)
  1114. @protected_router.get("/vas/order/list_all", summary="查看所有订单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasOrderOut]])
  1115. async def vas_order_list_all(
  1116. page: int = Query(0, description="第几页"),
  1117. size: int = Query(10, description="分页大小"),
  1118. keyword: str = Query("", description="查询条件"),
  1119. db: AsyncSession = Depends(get_db)
  1120. ):
  1121. orders = await OrderService.list_all(db, page, size, keyword)
  1122. return success(data=orders)
  1123. @admin_required_router.post("/vas/order/cancel", summary="取消订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  1124. async def vas_order_cancel(
  1125. order_id: str,
  1126. current_user: VasUser = Depends(get_current_user),
  1127. db: AsyncSession = Depends(get_db),
  1128. redis_client: Redis = Depends(get_redis_client)
  1129. ):
  1130. cancelled_order = await OrderService.cancel(db, order_id)
  1131. return success(data=cancelled_order)
  1132. @admin_required_router.post("/vas/order-event/create", summary="创建订单事件", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderEventOut])
  1133. async def vas_order_event_create(
  1134. event_data: VasOrderEventCreate,
  1135. db: AsyncSession = Depends(get_db)
  1136. ):
  1137. obj = await OrderEventService.create(db, event_data)
  1138. return success(data=obj)
  1139. @protected_router.get("/vas/order-event/list", summary="获取订单的所有事件", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasOrderEventOut]])
  1140. async def vas_list_order_events_by_order(
  1141. order_id: str,
  1142. db: AsyncSession = Depends(get_db)
  1143. ):
  1144. events = await OrderEventService.get_by_order_id(db, order_id)
  1145. return success(data=events)
  1146. @protected_router.get("/vas/payment_provider/list_enabled", summary="获取支付方式", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentProviderOut]])
  1147. async def vas_payment_provider_simple_get(
  1148. db: AsyncSession = Depends(get_db)
  1149. ):
  1150. providers = await PaymentProviderService.list_enabled(db)
  1151. return success(data=providers)
  1152. @admin_required_router.get("/vas/payment_provider/list_all", summary="获取支付方式", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentProviderOut]])
  1153. async def vas_payment_provider_list_all(
  1154. db: AsyncSession = Depends(get_db)
  1155. ):
  1156. providers = await PaymentProviderService.list_all(db)
  1157. return success(data=providers)
  1158. @admin_required_router.post("/vas/payment_provider/create", summary="新增支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentProviderOut])
  1159. async def vas_payment_provider_create(
  1160. payload: VasPaymentProviderCreate,
  1161. db: AsyncSession = Depends(get_db)
  1162. ):
  1163. provider = await PaymentProviderService.create(db, payload)
  1164. return success(data=provider)
  1165. @admin_required_router.post("/vas/payment_provider/update", summary="更新支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentProviderOut])
  1166. async def vas_payment_provider_update(
  1167. id: int,
  1168. payload: VasPaymentProviderUpdate,
  1169. db: AsyncSession = Depends(get_db)
  1170. ):
  1171. provider = await PaymentProviderService.update(db, id, payload)
  1172. return success(data=provider)
  1173. @admin_required_router.delete("/vas/payment_provider/delete", summary="删除支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse)
  1174. async def vas_payment_provider_delete(
  1175. id: int,
  1176. db: AsyncSession = Depends(get_db)
  1177. ):
  1178. await PaymentProviderService.delete(db, id)
  1179. return success()
  1180. @protected_router.post("/vas/payment/create", summary="创建支付", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentOut])
  1181. async def vas_payment_create(
  1182. payload: VasPaymentCreate,
  1183. db: AsyncSession = Depends(get_db),
  1184. redis_client: Redis = Depends(get_redis_client)
  1185. ):
  1186. res = await PaymentService.create_payment(db, payload, redis_client)
  1187. return success(data=res)
  1188. @protected_router.get("/vas/payment/detail", summary="获取支付详情", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentOut])
  1189. async def vas_payment_create(
  1190. payment_id: int,
  1191. db: AsyncSession = Depends(get_db),
  1192. ):
  1193. res = await PaymentService.get_by_id(db, payment_id)
  1194. return success(data=res)
  1195. @admin_required_router.post("/vas/payment/confirm_by_admin", summary="管理员确认支付", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentConfirmationOut])
  1196. async def vas_payment_confirm_by_admin(
  1197. id: int,
  1198. payload: VasPaymentConfirmationUpdate,
  1199. current_user: VasUser = Depends(get_current_user),
  1200. db: AsyncSession = Depends(get_db)
  1201. ):
  1202. res = await PaymentService.confirm_by_admin(db, id, payload, current_user)
  1203. return success(data=res)
  1204. @admin_required_router.post("/vas/payment/admin_update_status", summary="管理员更新支付状态", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentOut])
  1205. async def vas_payment_admin_update_status(
  1206. payment_id: int,
  1207. payload: AdminUpdateStatusPayload,
  1208. db: AsyncSession = Depends(get_db)
  1209. ):
  1210. res = await PaymentService.admin_update_status(db, payment_id, payload)
  1211. return success(data=res)
  1212. @protected_router.post("/vas/payment/confirm_by_user", summary="用户确认支付", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentConfirmationOut])
  1213. async def vas_payment_confirm_by_user(
  1214. payload: VasPaymentConfirmationCreate,
  1215. current_user: VasUser = Depends(get_current_user),
  1216. db: AsyncSession = Depends(get_db)
  1217. ):
  1218. res = await PaymentService.confirm_by_user(db, payload, current_user)
  1219. return success(data=res)
  1220. @admin_required_router.post("/vas/payment_confirmation/list_all", summary="查询所有待确认支付", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasPaymentConfirmationOut]])
  1221. async def vas_payment_confirm_list(
  1222. page: int = Query(0, description="第几页"),
  1223. size: int = Query(10, description="分页大小"),
  1224. keyword: str = Query("", description="查询条件"),
  1225. db: AsyncSession = Depends(get_db)
  1226. ):
  1227. res = await PaymentService.list_payment_confirmation(db, keyword, page, size)
  1228. return success(data=res)
  1229. @protected_router.get("/vas/payment/list_by_order", summary="获取某个订单下的所有payment记录", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentOut]])
  1230. async def vas_payment_list_by_order(
  1231. order_id: str,
  1232. db: AsyncSession = Depends(get_db)
  1233. ):
  1234. payments = await PaymentService.list_by_order(db, order_id)
  1235. return success(data=payments)
  1236. @admin_required_router.post("/vas/payment_qr/create", summary="新增收款码", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  1237. async def vas_payment_qr_create(payload: VasPaymentQrCreate, db: AsyncSession = Depends(get_db)):
  1238. qr = await PaymentQrService.create(db, payload)
  1239. return success(data=qr)
  1240. @protected_router.get("/vas/payment_qr/list_by_provider", summary="获取某个服务商的所有付款码", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentQrOut]])
  1241. async def vas_payment_qr_list_qrcode_by_provider(provider_id: int, db: AsyncSession = Depends(get_db)):
  1242. qr = await PaymentQrService.list_by_provider(db, provider_id)
  1243. return success(data=qr)
  1244. @protected_router.get("/vas/payment_qr/qrcode", summary="获取支付的QRCode", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  1245. async def vas_payment_qr_get_qrcode_by_id(id: int, db: AsyncSession = Depends(get_db)):
  1246. qr = await PaymentQrService.get_by_id(db, id)
  1247. return success(data=qr)
  1248. @admin_required_router.post("/vas/payment_qr/set_enable", summary="修改QRCode", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  1249. async def vas_payment_qr_update(
  1250. id: int,
  1251. payload: VasPaymentQrSetEnableIn,
  1252. db: AsyncSession = Depends(get_db)
  1253. ):
  1254. qr = await PaymentQrService.set_enable(db, id, payload)
  1255. return success(data=qr)
  1256. @admin_required_router.delete("/vas/payment_qr/delete", summary="删除QRCode", tags=["Visafly签证系统"], response_model=ApiResponse)
  1257. async def vas_payment_qr_update(
  1258. id: int,
  1259. db: AsyncSession = Depends(get_db)
  1260. ):
  1261. await PaymentQrService.delete(db, id)
  1262. return success()
  1263. @admin_required_router.get("/vas/task/list", summary="获取待执行的任务", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTaskOut]])
  1264. async def vas_task_list(
  1265. page: int = Query(0, description="第几页"),
  1266. size: int = Query(10, description="分页大小"),
  1267. keyword: str = Query("", description="查询条件"),
  1268. status: str = Query("", description="task 自定义索引"),
  1269. routing_key: str = Query("", description="task 自定义索引"),
  1270. script_version: str = Query("", description="脚本版本, 用来向后兼容"),
  1271. db: AsyncSession = Depends(get_db)
  1272. ):
  1273. tasks = await VasTaskService.list_task(db, status, routing_key, script_version, keyword, page, size)
  1274. return success(data=tasks)
  1275. @admin_required_router.get("/vas/task/expiring", summary="获取即将过期的任务列表", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasExpiringTaskItem]])
  1276. async def vas_task_get_expiring_tasks(
  1277. days: int = 3,
  1278. db: AsyncSession = Depends(get_db)
  1279. ):
  1280. """获取即将过期的任务列表"""
  1281. tasks = await VasTaskService.get_expiring_tasks(db, threshold_days=days)
  1282. return success(data=tasks)
  1283. @admin_required_router.post("/vas/task/update", summary="更新任务数据", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  1284. async def vas_task_update(
  1285. id: int,
  1286. payload: VasTaskUpdate,
  1287. db: AsyncSession = Depends(get_db)
  1288. ):
  1289. task = await VasTaskService.update(db, id, payload)
  1290. return success(data=task)
  1291. @admin_required_router.get("/vas/task/get_by_order", summary="根据订单查找任务", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasTaskOut]])
  1292. async def vas_task_pending(
  1293. order_id: str = Query(..., description="订单编号"),
  1294. script_version: str = Query("", description="脚本版本, 用来向后兼容"),
  1295. db: AsyncSession = Depends(get_db)
  1296. ):
  1297. tasks = await VasTaskService.get_active_task_by_order_id(db, order_id)
  1298. return success(data=tasks)
  1299. @admin_required_router.post("/vas/task/pause", summary="暂停任务", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  1300. async def vas_task_pause(task_id:int, db: AsyncSession = Depends(get_db)):
  1301. obj = await VasTaskService.pause(db, task_id)
  1302. return success(data=obj)
  1303. @admin_required_router.post("/vas/task/return_to_queue", summary="重新进入等候状态", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  1304. async def vas_task_return_to_queue(task_id:int, db: AsyncSession = Depends(get_db)):
  1305. obj = await VasTaskService.return_to_queue(db, task_id)
  1306. return success(data=obj)
  1307. @admin_required_router.post("/vas/task/manual_confirm", summary="设置任务完成", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  1308. async def vas_task_manual_confirm(
  1309. task_id:int,
  1310. db: AsyncSession = Depends(get_db),
  1311. redis_client: Redis = Depends(get_redis_client)
  1312. ):
  1313. obj = await VasTaskService.manual_confirm(db, task_id, redis_client)
  1314. return success(data=obj)
  1315. @admin_required_router.get("/vas/task/pop", summary="任务出队(pop)", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  1316. async def vas_task_pop_task(
  1317. queue_name: str,
  1318. db: AsyncSession = Depends(get_db),
  1319. ):
  1320. task = await VasTaskService.pop_vas_task(db, queue_name, 180)
  1321. return success(data=task)
  1322. @protected_router.post("/vas/ticket/create", summary="创建工单", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketOut])
  1323. async def vas_ticket_create(
  1324. data:VasTicketCreate,
  1325. current_user: VasUser = Depends(get_current_user),
  1326. db: AsyncSession = Depends(get_db),
  1327. redis_client: Redis = Depends(get_redis_client)
  1328. ):
  1329. obj = await TicketService.create(db, data, current_user, redis_client)
  1330. return success(data=obj)
  1331. @protected_router.get("/vas/ticket/list_by_user", summary="查看工单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketOut]])
  1332. async def vas_ticket_list_by_user(
  1333. page: int = Query(0, description="第几页"),
  1334. size: int = Query(10, description="分页大小"),
  1335. keyword: str = Query("", description="查询条件"),
  1336. current_user: VasUser = Depends(get_current_user),
  1337. db: AsyncSession = Depends(get_db)
  1338. ):
  1339. tickets = await TicketService.list_by_user(db, current_user.id, page, size, keyword)
  1340. return success(data=tickets)
  1341. @admin_required_router.get("/vas/ticket/list_all", summary="查看工单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketOut]])
  1342. async def vas_ticket_list_all(
  1343. page: int = Query(0, description="第几页"),
  1344. size: int = Query(10, description="分页大小"),
  1345. keyword: str = Query("", description="查询条件"),
  1346. db: AsyncSession = Depends(get_db)
  1347. ):
  1348. tickets = await TicketService.list_all(db, page, size, keyword)
  1349. return success(data=tickets)
  1350. @admin_required_router.post("/vas/tickets/status", summary="管理员更新工单状态", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketOut])
  1351. async def update_ticket_status(
  1352. ticket_id: int,
  1353. payload: VasTicketStatusUpdate,
  1354. db: AsyncSession = Depends(get_db),
  1355. user=Depends(get_current_user)
  1356. ):
  1357. res = await TicketService.update_status(
  1358. db=db,
  1359. ticket_id=ticket_id,
  1360. status=payload.status,
  1361. comment=payload.comment,
  1362. admin_id=user.id
  1363. )
  1364. return success(data=res)
  1365. @protected_router.post("/vas/tickets/send_message", summary="发送工单消息", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketMessageOut])
  1366. async def create_ticket_message(
  1367. ticket_id: int,
  1368. payload: VasTicketMessageCreate,
  1369. db: AsyncSession = Depends(get_db),
  1370. user=Depends(get_current_user)
  1371. ):
  1372. res = await TicketService.add_message(
  1373. db=db,
  1374. ticket_id=ticket_id,
  1375. sender_type=user.role,
  1376. sender_id=user.id,
  1377. content=payload.content,
  1378. attachments=payload.attachments
  1379. )
  1380. return success(data=res)
  1381. @protected_router.get("/vas/tickets/fetch_message", summary="分页获取工单会话", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketMessageOut]])
  1382. async def get_ticket_messages(
  1383. ticket_id: int,
  1384. page: int = 1,
  1385. size: int = 20,
  1386. db: AsyncSession = Depends(get_db)
  1387. ):
  1388. msgs = await TicketService.list_messages(
  1389. db=db,
  1390. ticket_id=ticket_id,
  1391. page=page,
  1392. size=size
  1393. )
  1394. return success(data=msgs)
  1395. # -----------------------
  1396. # Troov Session APIs
  1397. # -----------------------
  1398. @admin_required_router.post("/troov-session/add", summary="新增troov session", tags=["Troov"], response_model=ApiResponse[TroovSessionOut])
  1399. async def troov_session_add(
  1400. payload: TroovSessionCreate,
  1401. db: AsyncSession = Depends(get_db)
  1402. ):
  1403. obj = await TroovSessionService.add(db, payload)
  1404. return success(data=obj)
  1405. @admin_required_router.get("/troov-session/pop", summary="获取并锁定一个pending的troov session", tags=["Troov"], response_model=ApiResponse[TroovSessionOut])
  1406. async def troov_session_pop(
  1407. slot_date: str = Query("", description="slot日期筛选"),
  1408. slot_time: str = Query("", description="slot时间筛选"),
  1409. db: AsyncSession = Depends(get_db)
  1410. ):
  1411. obj = await TroovSessionService.pop(db, slot_date or None, slot_time or None)
  1412. return success(data=obj)
  1413. @admin_required_router.put("/troov-session/update", summary="更新troov session", tags=["Troov"], response_model=ApiResponse[TroovSessionOut])
  1414. async def troov_session_update(
  1415. session_id: str = Query(..., description="session_id"),
  1416. payload: TroovSessionUpdate = Body(...),
  1417. db: AsyncSession = Depends(get_db)
  1418. ):
  1419. obj = await TroovSessionService.update(db, session_id, payload)
  1420. return success(data=obj)
  1421. @admin_required_router.get("/troov-session/get", summary="根据session_id获取troov session", tags=["Troov"], response_model=ApiResponse[TroovSessionOut])
  1422. async def troov_session_get(
  1423. session_id: str = Query(..., description="session_id"),
  1424. db: AsyncSession = Depends(get_db)
  1425. ):
  1426. obj = await TroovSessionService.get_by_session_id(db, session_id)
  1427. return success(data=obj)
  1428. @admin_required_router.get("/troov-session/list", summary="分页获取troov session列表", tags=["Troov"], response_model=ApiResponse[PageResponse[TroovSessionOut]])
  1429. async def troov_session_list(
  1430. status: str = Query("", description="状态筛选"),
  1431. page: int = Query(0, description="第几页"),
  1432. size: int = Query(10, description="分页大小"),
  1433. keyword: str = Query("", description="查询条件"),
  1434. db: AsyncSession = Depends(get_db)
  1435. ):
  1436. obj = await TroovSessionService.list(db, status, keyword, page, size)
  1437. return success(data=obj)