short_url_service.py 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. # app/services/short_url_service.py
  2. import string
  3. import random
  4. from sqlalchemy.ext.asyncio import AsyncSession
  5. from sqlalchemy import select
  6. from sqlalchemy.exc import IntegrityError
  7. from app.core.biz_exception import NotFoundError, BizLogicError
  8. from app.models.short_url import ShortUrl
  9. class ShortUrlService:
  10. @staticmethod
  11. def generate_short_key(length: int = 8) -> str:
  12. """生成随机短 Key(字母 + 数字)"""
  13. chars = string.ascii_letters + string.digits
  14. return ''.join(random.choices(chars, k=length))
  15. @staticmethod
  16. async def create_short_url(
  17. db: AsyncSession,
  18. long_url: str
  19. ) -> ShortUrl:
  20. """创建短链接(异步 + 并发安全)"""
  21. # 1️⃣ 是否已有相同 long_url
  22. stmt = select(ShortUrl).where(ShortUrl.long_url == long_url)
  23. existing = await db.scalar(stmt)
  24. if existing:
  25. raise BizLogicError("Short url already exist")
  26. # 2️⃣ 生成 short_key(循环直到成功)
  27. while True:
  28. short_key = ShortUrlService.generate_short_key()
  29. db_obj = ShortUrl(
  30. short_key=short_key,
  31. long_url=long_url
  32. )
  33. db.add(db_obj)
  34. try:
  35. await db.commit()
  36. await db.refresh(db_obj)
  37. return db_obj
  38. except IntegrityError:
  39. # short_key 冲突,重试
  40. await db.rollback()
  41. continue
  42. @staticmethod
  43. async def get_long_url(
  44. db: AsyncSession,
  45. short_key: str
  46. ) -> str:
  47. """通过短 key 获取原始长链接(异步)"""
  48. stmt = select(ShortUrl).where(ShortUrl.short_key == short_key)
  49. record = await db.scalar(stmt)
  50. if not record:
  51. raise NotFoundError("Short url not found")
  52. return record.long_url