1. 장고 스레드로 선착순쿠폰을 만들려고했는데 동시성 문제로 인해 레디스 루아스크립트를 채택하였다 레디스는 싱글스레드라 루아스크립트로 작성하면 atomic하게 처리한다 이를 통해 동시에 발생하는 여러 요청에서도 쿠폰 발급이 정확하게 처리되도록 보장할 수 있다
SADD -> coupon_id 중복을 방지한다(coupon_id는 uuid로 하였다)
SCARD -> 쿠폰 수를 반환해 인자로 받아올 쿠폰 개수를 3개로 제한한다.
generate_coupon_script = redis_client.register_script("""
local generated_coupons_key = KEYS[1]
local coupon_id = ARGV[1]
local limit = tonumber(ARGV[2])
local ttl = tonumber(ARGV[3])
local current_count = redis.call("SCARD", generated_coupons_key)
if current_count >= limit then
return -1
end
redis.call("SADD", generated_coupons_key, coupon_id)
if current_count == 0 then
redis.call("EXPIRE", generated_coupons_key, ttl)
end
return 1
""")
2. csrf 데코레이터 및 헤더 토큰을 만든다
from django.middleware.csrf import get_token
from django.views.decorators.csrf import csrf_protect
def csrf_token_view(request):
"""
CSRF 토큰을 생성하여 반환하는 API
"""
csrf_token = get_token(request)
return JsonResponse({"csrf_token": csrf_token})
@csrf_protect
def generate_coupon(request):
"""
CSRF 활성화된 쿠폰 생성 API
"""
3. user_id:2이 쿠폰을 발급하면, Redis의 coupon_user_key에는 쿠폰 ID에 매핑된 user_id:2이 저장된다 쿠폰 사용 시, Redis의 get 명령어로 해당 쿠폰의 발급 사용자(original_user_id)를 조회하여 현재 사용자와 비교하여. 현재 사용자와 발급 사용자가 다를 경우, 쿠폰 사용을 방지한다
# ttl은 30일로 설정하였다.
def generate_coupon_with_lua(user_id, ttl=2592000, limit=3):
signer = Signer()
coupon_id = str(uuid.uuid4()) # 원본 UUID
signed_coupon_id = signer.sign(coupon_id) # 서명된 쿠폰 ID
redis_key = f"user:{user_id}:generated_coupons"
coupon_user_key = f"coupon:{signed_coupon_id}:user"
result = generate_coupon_script(keys=[redis_key], args=[signed_coupon_id, limit, ttl])
if result == -1:
raise ValidationError("쿠폰 생성 한도를 초과했습니다. (최대 3개)")
# 사용자 ID와 서명된 쿠폰 ID 매핑 저장
redis_client.set(coupon_user_key, user_id, ex=ttl)
return signed_coupon_id
# 사용자와 쿠폰 매핑 확인
original_user_id = redis_client.get(coupon_user_key)
# redis_client.set(coupon_user_key, user_id, ex=ttl) 쿠폰아이디를 GET 조회하면 사용자 ID가 조회됨
if original_user_id.decode("utf-8") != user_id:
raise ValidationError("이 쿠폰은 다른 사용자에게 발급되었습니다.")
4. 만약 또다른 사용자가 coupon_id를 조작하려고하면 서명된 고유의 signed_coupon_id를 만들어 방지한다.
def validate_and_apply_coupon(user_id, signed_coupon_id, order_amount, discount=10000, limit=3, ttl=2592000):
signer = Signer()
# 서명 검증
try:
coupon_id = signer.unsign(signed_coupon_id) # 서명 검증
except Exception:
raise ValidationError("쿠폰 ID가 조작되었습니다.")
'portfolio' 카테고리의 다른 글
레디스(redis)와 익스프레스(express js)로 프로모션쿠폰 API생성 (0) | 2024.11.25 |
---|---|
파이썬 셀레니움을 통한 웹스크래핑 자동화(1. 스크롤기능, 2. 페이지네이션기능) (0) | 2024.06.10 |
HTML, CSS , image slider js웹디자인 포트폴리오 (0) | 2024.06.03 |
Django + Postgresql (트라이그램 유사성을 이용하여 유사한단어 검색하기) (0) | 2024.05.08 |
Django + Postgresql (ORM에서 searchVector를 이용하여 모델의 여러 필드검색), RSS feed 제작 (0) | 2024.05.08 |