0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonとFastAPIで打刻機能のflagで二重送信を防いだ方法 #FastAPI

0
Last updated at Posted at 2026-06-02

この記事では、Python + FastAPIを使用した打刻機能のstartボタン・stopボタンの二重押し問題をflagで防ぐ実装についての記録と実際の実装方法について解説しています。


問題の背景

打刻機能を実装する際、以下の2つの誤操作が発生しうると考えました。

  • startボタンを2回連続で押した場合
  • stopボタンを2回連続で押した場合

startの二重押しが発生すると、開始時刻が上書きされ記録が壊れます。stopの二重押しが発生すると、終了時刻のみが上書きされ合計時間の計算が崩れます。

フロントエンドだけでバリデーションを行う方法もありますが、今回はバックエンドにも同じチェックを置く設計を採用しました。


バックエンドにもバリデーションを置いた理由

フロントエンドだけにバリデーションを置いた場合、APIを直接叩かれると同じ問題が発生します。

今回のツールはFastAPIでエンドポイントを公開しているため、ブラウザのUIを経由せずにAPIを直接呼び出すことが可能です。この場合、フロントのバリデーションをすり抜けてしまいます。

バックエンドにも同じチェックを置くことで、どこから操作されても同じ動作を保証できます。

処理の全体像は以下の通りです。

double_press_prevention_flow.png

フロントとバックエンドの両層でflagを確認し、いずれかが不正な状態であれば処理を止める設計です。


解決方法

flagでボタンの状態を記憶し、これを元にチェックを行う設計にしました。

class TimeCheker():
    def __init__(self):
        self.flag = False

    def start_checker(self):
        if self.flag is True:
            return "not end"
        else:
            start_time = datetime.now()
            self.start = start_time
            self.flag = True
            return_time = datetime.now().strftime('%H:%M:%S')
            return 'start time is: ' + return_time

    def end_checker(self):
        if self.flag is False:
            return "not start"
        else:
            self.endtime = datetime.now()
            self.total = self.endtime - self.start 
            result = self.save_to_csv()
            self.flag = False
            return result

flagFalseの状態でstartボタンが押されると、時刻を記録してflagTrueにします。flagTrueのまま再度startボタンが押された場合はエラーを返し、時刻の上書きを防ぎます。stopボタンも同様の逆判定を行っています。

flag__init__Falseに初期化しているため、インスタンス生成時は必ずstart待ちの状態から始まります。


状態の永続保持について

このバリデーションが機能するには、startとstopで同一のflagを参照する必要があります。リクエストごとにインスタンスを生成する設計では、startでTrueにしたflagがstopのリクエスト時には別インスタンスのFalseを参照してしまいます。

このためFastAPIの実装では、TimeChekerのインスタンスをモジュールレベルで1度だけ生成し、複数のリクエストをまたいで同一インスタンスを参照する設計にしています。

# main.py
timer = TimeCheker()

@app.get("/timer/start")
def timer_start():
    result = timer.start_checker()
    return result

@app.post("/timer/stop")
def timer_end():
    result = timer.end_checker()
    return result

goalsのような機能ではリクエストごとにインスタンスを生成していますが、timerは状態を保持する必要があるため永続インスタンスとして管理しています。


まとめ

flagによる二重押し防止はシンプルな実装ですが、 インスタンスはモジュールレベルで永続化することが重要になります。(startとstopで同一のflagを参照するため)

モジュールレベルでの永続化が欠けるとバリデーションが意図した通りに機能しません。


リポジトリ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?