永栄コードのマージ開始
This commit is contained in:
@ -37,6 +37,7 @@ services:
|
||||
#entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""]
|
||||
#command: python3 manage.py runserver 0.0.0.0:8100
|
||||
|
||||
|
||||
networks:
|
||||
rog-api:
|
||||
driver: bridge
|
||||
|
||||
1299
rog/admin.py
1299
rog/admin.py
File diff suppressed because it is too large
Load Diff
148
rog/migration_scripts.py
Normal file
148
rog/migration_scripts.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""
|
||||
このコードは永栄コードをNoufferコードに統合するための一時変換コードです。
|
||||
一旦、完全にマイグレーションでき、ランキングや走行履歴が完成したら、不要になります。
|
||||
"""
|
||||
import psycopg2
|
||||
from PIL import Image
|
||||
import PIL.ExifTags
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
def get_gps_from_image(image_path):
|
||||
"""
|
||||
画像ファイルからGPS情報を抽出する
|
||||
Returns: (latitude, longitude) または取得できない場合は (None, None)
|
||||
"""
|
||||
try:
|
||||
with Image.open(image_path) as img:
|
||||
exif = {
|
||||
PIL.ExifTags.TAGS[k]: v
|
||||
for k, v in img._getexif().items()
|
||||
if k in PIL.ExifTags.TAGS
|
||||
}
|
||||
|
||||
if 'GPSInfo' in exif:
|
||||
gps_info = exif['GPSInfo']
|
||||
|
||||
# 緯度の計算
|
||||
lat = gps_info[2]
|
||||
lat = lat[0] + lat[1]/60 + lat[2]/3600
|
||||
if gps_info[1] == 'S':
|
||||
lat = -lat
|
||||
|
||||
# 経度の計算
|
||||
lon = gps_info[4]
|
||||
lon = lon[0] + lon[1]/60 + lon[2]/3600
|
||||
if gps_info[3] == 'W':
|
||||
lon = -lon
|
||||
|
||||
return lat, lon
|
||||
except Exception as e:
|
||||
print(f"GPS情報の抽出に失敗: {e}")
|
||||
|
||||
return None, None
|
||||
|
||||
def migrate_data():
|
||||
# コンテナ環境用の接続情報
|
||||
source_db = {
|
||||
'dbname': 'gifuroge',
|
||||
'user': 'admin', # 環境に合わせて変更
|
||||
'password': 'admin123456', # 環境に合わせて変更
|
||||
'host': 'localhost', # Dockerのサービス名
|
||||
'port': '5432'
|
||||
}
|
||||
|
||||
target_db = {
|
||||
'dbname': 'rogdb',
|
||||
'user': 'admin', # 環境に合わせて変更
|
||||
'password': 'admin123456', # 環境に合わせて変更
|
||||
'host': 'localhost', # Dockerのサービス名
|
||||
'port': '5432'
|
||||
}
|
||||
|
||||
source_conn = None
|
||||
target_conn = None
|
||||
source_cur = None
|
||||
target_cur = None
|
||||
|
||||
try:
|
||||
print("ソースDBへの接続を試みています...")
|
||||
source_conn = psycopg2.connect(**source_db)
|
||||
source_cur = source_conn.cursor()
|
||||
print("ソースDBへの接続が成功しました")
|
||||
|
||||
print("ターゲットDBへの接続を試みています...")
|
||||
target_conn = psycopg2.connect(**target_db)
|
||||
target_cur = target_conn.cursor()
|
||||
print("ターゲットDBへの接続が成功しました")
|
||||
|
||||
print("データの取得を開始します...")
|
||||
source_cur.execute("""
|
||||
SELECT serial_number, zekken_number, event_code, cp_number, image_address,
|
||||
goal_time, late_point, create_at, create_user,
|
||||
update_at, update_user, buy_flag, colabo_company_memo
|
||||
FROM gps_information
|
||||
""")
|
||||
|
||||
rows = source_cur.fetchall()
|
||||
print(f"取得したレコード数: {len(rows)}")
|
||||
|
||||
processed_count = 0
|
||||
for row in rows:
|
||||
(serial_number, zekken_number, event_code, cp_number, image_address,
|
||||
goal_time, late_point, create_at, create_user,
|
||||
update_at, update_user, buy_flag, colabo_company_memo) = row
|
||||
|
||||
latitude, longitude = None, None
|
||||
if image_address and os.path.exists(image_address):
|
||||
latitude, longitude = get_gps_from_image(image_address)
|
||||
|
||||
target_cur.execute("""
|
||||
INSERT INTO gps_checkins (
|
||||
path_order, zekken_number, event_code, cp_number,
|
||||
lattitude, longitude, image_address,
|
||||
image_receipt, image_QR, validate_location,
|
||||
goal_time, late_point, create_at,
|
||||
create_user, update_at, update_user,
|
||||
buy_flag, colabo_company_memo, points
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s
|
||||
)
|
||||
""", (
|
||||
serial_number,
|
||||
zekken_number, event_code, cp_number,
|
||||
latitude, longitude, image_address,
|
||||
True, True, True,
|
||||
goal_time, late_point, create_at,
|
||||
create_user, update_at, update_user,
|
||||
buy_flag if buy_flag is not None else False,
|
||||
colabo_company_memo if colabo_company_memo else '',
|
||||
0
|
||||
))
|
||||
|
||||
processed_count += 1
|
||||
if processed_count % 100 == 0:
|
||||
print(f"処理済みレコード数: {processed_count}")
|
||||
|
||||
target_conn.commit()
|
||||
print(f"移行完了: {processed_count}件のレコードを処理しました")
|
||||
|
||||
except Exception as e:
|
||||
print(f"エラーが発生しました: {e}")
|
||||
if target_conn:
|
||||
target_conn.rollback()
|
||||
|
||||
finally:
|
||||
if source_cur:
|
||||
source_cur.close()
|
||||
if target_cur:
|
||||
target_cur.close()
|
||||
if source_conn:
|
||||
source_conn.close()
|
||||
if target_conn:
|
||||
target_conn.close()
|
||||
print("すべての接続をクローズしました")
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate_data()
|
||||
142
rog/models.py
142
rog/models.py
@ -72,6 +72,31 @@ def remove_bom_inplace(path):
|
||||
fp.seek(-bom_length, os.SEEK_CUR)
|
||||
fp.truncate()
|
||||
|
||||
class GifurogeRegister(models.Model):
|
||||
event_code = models.CharField(max_length=100)
|
||||
time = models.IntegerField(choices=[(3, '3時間'), (5, '5時間')])
|
||||
owner_name_kana = models.CharField(max_length=100)
|
||||
owner_name = models.CharField(max_length=100)
|
||||
email = models.EmailField()
|
||||
password = models.CharField(max_length=100)
|
||||
owner_birthday = models.DateField(blank=True,null=True)
|
||||
owner_sex = models.CharField(max_length=10,blank=True,null=True)
|
||||
team_name = models.CharField(max_length=100)
|
||||
department = models.CharField(max_length=100)
|
||||
members_count = models.IntegerField()
|
||||
member2 = models.CharField(max_length=100, blank=True, null=True)
|
||||
birthday2 = models.DateField(blank=True,null=True)
|
||||
sex2 = models.CharField(max_length=10,blank=True,null=True)
|
||||
member3 = models.CharField(max_length=100, blank=True, null=True)
|
||||
birthday3 = models.DateField(blank=True,null=True)
|
||||
sex3 = models.CharField(max_length=10,blank=True,null=True)
|
||||
member4 = models.CharField(max_length=100, blank=True, null=True)
|
||||
birthday4 = models.DateField(blank=True,null=True)
|
||||
sex4 = models.CharField(max_length=10,blank=True,null=True)
|
||||
member5 = models.CharField(max_length=100, blank=True, null=True)
|
||||
birthday5 = models.DateField(blank=True,null=True)
|
||||
sex5 = models.CharField(max_length=10,blank=True,null=True)
|
||||
|
||||
|
||||
class CustomUserManager(BaseUserManager):
|
||||
|
||||
@ -345,7 +370,7 @@ class Member(models.Model):
|
||||
unique_together = ('team', 'user')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.team.zekken_number} - {self.user.lastname} {self.user.firstname}"
|
||||
return f"{self.team.team_name} - {self.user.lastname} {self.user.firstname}"
|
||||
|
||||
#
|
||||
class Category(models.Model):
|
||||
@ -504,6 +529,121 @@ class CheckinImages(models.Model):
|
||||
event_code = models.CharField(_("event code"), max_length=255)
|
||||
cp_number = models.IntegerField(_("CP numner"))
|
||||
|
||||
class GpsCheckin(models.Model):
|
||||
path_order = models.IntegerField(
|
||||
null=False,
|
||||
help_text="チェックポイントの順序番号"
|
||||
)
|
||||
zekken_number = models.TextField(
|
||||
null=False,
|
||||
help_text="ゼッケン番号"
|
||||
)
|
||||
event_code = models.TextField(
|
||||
null=False,
|
||||
help_text="イベントコード"
|
||||
)
|
||||
cp_number = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="チェックポイント番号"
|
||||
)
|
||||
lattitude = models.FloatField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="緯度:写真から取得"
|
||||
)
|
||||
longitude = models.FloatField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="経度:写真から取得"
|
||||
)
|
||||
image_address = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="チェックイン画像のパス"
|
||||
)
|
||||
image_receipt = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
default=False,
|
||||
help_text="レシート画像のパス"
|
||||
)
|
||||
image_qr = models.BooleanField(
|
||||
default=False,
|
||||
help_text="QRコードスキャンフラグ"
|
||||
)
|
||||
validate_location = models.BooleanField(
|
||||
default=False,
|
||||
help_text="位置情報検証フラグ:画像認識で検証した結果"
|
||||
)
|
||||
goal_time = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="ゴール時刻=ゴール時のみ使用される。画像から時刻を読み取り設定する。"
|
||||
)
|
||||
late_point = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="遅刻ポイント:ゴールの時刻が制限時間を超えた場合、1分につき-50点が加算。"
|
||||
)
|
||||
create_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="作成日時:データの作成日時"
|
||||
)
|
||||
create_user = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="作成ユーザー"
|
||||
)
|
||||
update_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="更新日時"
|
||||
)
|
||||
update_user = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="更新ユーザー"
|
||||
)
|
||||
buy_flag = models.BooleanField(
|
||||
default=False,
|
||||
help_text="購入フラグ:協賛店で購入した場合、無条件でTRUEにする。"
|
||||
)
|
||||
colabo_company_memo = models.TextField(
|
||||
null=False,
|
||||
default='',
|
||||
help_text="グループコード:複数のイベントで合算する場合に使用する"
|
||||
)
|
||||
points = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="ポイント:このチェックインによる獲得ポイント。通常ポイントと買い物ポイントは分離される。ゴールの場合には減点なども含む。"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'gps_checkins'
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['zekken_number', 'event_code', 'path_order'],
|
||||
name='unique_gps_checkin'
|
||||
)
|
||||
]
|
||||
indexes = [
|
||||
models.Index(fields=['zekken_number', 'event_code','path_order'], name='idx_zekken_event'),
|
||||
models.Index(fields=['create_at'], name='idx_create_at'),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.event_code}-{self.zekken_number}-{self.path_order}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# 作成時・更新時のタイムスタンプを自動設定
|
||||
from django.utils import timezone
|
||||
if not self.create_at:
|
||||
self.create_at = timezone.now()
|
||||
self.update_at = timezone.now()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class RogUser(models.Model):
|
||||
user=models.OneToOneField(CustomUser, on_delete=models.CASCADE)
|
||||
|
||||
11
rog/templates/admin/gifuroge_register_changelist.html
Normal file
11
rog/templates/admin/gifuroge_register_changelist.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n admin_urls %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
{{ block.super }}
|
||||
<li>
|
||||
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
|
||||
{% blocktranslate with name=opts.verbose_name %}Upload CSV{% endblocktranslate %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
11
rog/templates/admin/rog/gifurogeregister/change_list.html
Normal file
11
rog/templates/admin/rog/gifurogeregister/change_list.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n admin_urls %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
{{ block.super }}
|
||||
<li>
|
||||
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
|
||||
{% translate "Upload CSV" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
22
rog/templates/admin/rog/gifurogeregister/upload-csv.html
Normal file
22
rog/templates/admin/rog/gifurogeregister/upload-csv.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n admin_urls %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Upload CSV File</h1>
|
||||
CSV のフォーマット:
|
||||
イベントコード,時間(3 or 5),代表者かな,代表者名,メール,パスワード,代表者生年月日、代表者性別,チーム名,部門,メンバー数,メンバー2,誕生日2,性別2,メンバー3,誕生日3,性別3,メンバー4,誕生日4,性別4,メンバー5,誕生日5,性別5<br>
|
||||
(例)<br>
|
||||
event_code,time,owner_name_kana,owner_name,email,password,owner_birthday,owner_sex,team_name,department,members_count,member2,birthday2,sex2,member3,birthday3,sex3,member4,birthday4,sex4,member5,birthday5,sex5<br>
|
||||
FC岐阜,3,みやたあきら,宮田 明,hannivalscipio@gmail.com,Sachiko123,タヌキの宮家,一般,3,宮田幸子,1965-4-4,female,川本勇,1965-1-1,male,,,,,,<br>
|
||||
<br>
|
||||
この形式のCSVをアップロードすると、以下を実施する。<br>
|
||||
1)未登録のユーザーの登録をパスワードとともに生成<br>
|
||||
2)チームを生成し、メンバーを登録<br>
|
||||
3)イベントコードで示すイベントにエントリー<br>
|
||||
<br>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="file" name="csv_file" required>
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
134
rog/transfer.py
Normal file
134
rog/transfer.py
Normal file
@ -0,0 +1,134 @@
|
||||
import psycopg2
|
||||
from datetime import datetime
|
||||
import sys
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
def get_db_connection(dbname: str) -> psycopg2.extensions.connection:
|
||||
"""データベース接続を確立する"""
|
||||
try:
|
||||
return psycopg2.connect(
|
||||
dbname=dbname,
|
||||
user='your_username', # 実際のユーザー名に変更してください
|
||||
password='your_password', # 実際のパスワードに変更してください
|
||||
host='localhost' # 実際のホスト名に変更してください
|
||||
)
|
||||
except psycopg2.Error as e:
|
||||
print(f"データベース {dbname} への接続エラー: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def get_source_data() -> List[Dict]:
|
||||
"""rogdbからデータを取得する"""
|
||||
conn = get_db_connection('rogdb')
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT DISTINCT ON (rci.user_id, rci.cp_number)
|
||||
rci.team_name,
|
||||
rci.event_code,
|
||||
rci.cp_number,
|
||||
rci.checkinimage,
|
||||
rci.checkintime,
|
||||
rci.user_id,
|
||||
COALESCE(p.point, 0) as late_point
|
||||
FROM rog_checkinimages rci
|
||||
LEFT JOIN point p ON p.user_id = rci.user_id
|
||||
AND p.event_code = rci.event_code
|
||||
AND p.cp_number = rci.cp_number
|
||||
WHERE rci.event_code = 'FC岐阜'
|
||||
ORDER BY rci.user_id, rci.cp_number, rci.checkintime DESC
|
||||
""")
|
||||
|
||||
columns = [desc[0] for desc in cur.description]
|
||||
return [dict(zip(columns, row)) for row in cur.fetchall()]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def get_next_serial_number(cur) -> int:
|
||||
"""次のserial_numberを取得する"""
|
||||
cur.execute("SELECT nextval('gps_information_serial_number_seq')")
|
||||
return cur.fetchone()[0]
|
||||
|
||||
def insert_into_target(data: List[Dict]) -> Tuple[int, List[str]]:
|
||||
"""gifurogeデータベースにデータを挿入する"""
|
||||
conn = get_db_connection('gifuroge')
|
||||
inserted_count = 0
|
||||
errors = []
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
for record in data:
|
||||
try:
|
||||
serial_number = get_next_serial_number(cur)
|
||||
cur.execute("""
|
||||
INSERT INTO gps_information (
|
||||
serial_number,
|
||||
zekken_number,
|
||||
event_code,
|
||||
cp_number,
|
||||
image_address,
|
||||
goal_time,
|
||||
late_point,
|
||||
create_at,
|
||||
create_user,
|
||||
update_at,
|
||||
update_user,
|
||||
buy_flag,
|
||||
minus_photo_flag,
|
||||
colabo_company_memo
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""", (
|
||||
serial_number,
|
||||
record['team_name'],
|
||||
record['event_code'],
|
||||
record['cp_number'],
|
||||
record['checkinimage'],
|
||||
record['checkintime'].strftime('%Y-%m-%d %H:%M:%S'),
|
||||
record['late_point'],
|
||||
record['checkintime'],
|
||||
'system',
|
||||
record['checkintime'],
|
||||
'system',
|
||||
False,
|
||||
False,
|
||||
''
|
||||
))
|
||||
inserted_count += 1
|
||||
except psycopg2.Error as e:
|
||||
errors.append(f"Error inserting record for team {record['team_name']}: {str(e)}")
|
||||
|
||||
if inserted_count % 100 == 0:
|
||||
print(f"Processed {inserted_count} records...")
|
||||
|
||||
conn.commit()
|
||||
except psycopg2.Error as e:
|
||||
conn.rollback()
|
||||
errors.append(f"Transaction error: {str(e)}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return inserted_count, errors
|
||||
|
||||
def main():
|
||||
print("データ移行を開始します...")
|
||||
|
||||
# ソースデータの取得
|
||||
print("ソースデータを取得中...")
|
||||
source_data = get_source_data()
|
||||
print(f"取得したレコード数: {len(source_data)}")
|
||||
|
||||
# データの挿入
|
||||
print("データを移行中...")
|
||||
inserted_count, errors = insert_into_target(source_data)
|
||||
|
||||
# 結果の表示
|
||||
print("\n=== 移行結果 ===")
|
||||
print(f"処理したレコード数: {inserted_count}")
|
||||
print(f"エラー数: {len(errors)}")
|
||||
|
||||
if errors:
|
||||
print("\nエラーログ:")
|
||||
for error in errors:
|
||||
print(f"- {error}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
rogaining_autoprint/Dockerfile
Normal file
20
rogaining_autoprint/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# CUPSとその他必要なパッケージのインストール
|
||||
RUN apt-get update && apt-get install -y \
|
||||
cups \
|
||||
cups-client \
|
||||
gcc \
|
||||
libcups2-dev \
|
||||
python3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY ./app .
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
144
rogaining_autoprint/README
Normal file
144
rogaining_autoprint/README
Normal file
@ -0,0 +1,144 @@
|
||||
システム管理
|
||||
|
||||
1. 環境変数の設定
|
||||
|
||||
必要な環境変数:
|
||||
envCopyAWS_ACCESS_KEY_ID=your_access_key
|
||||
AWS_SECRET_ACCESS_KEY=your_secret_key
|
||||
AWS_REGION=us-west-2
|
||||
S3_BUCKET_NAME=sumasenrogaining
|
||||
LOCATION=美濃加茂
|
||||
PRINTER_NAME=Brother_HL_L3240CDW_series
|
||||
|
||||
|
||||
2.プリンター設定
|
||||
|
||||
3.1 CUPSの設定
|
||||
|
||||
# CUPSの状態確認
|
||||
systemctl status cups
|
||||
|
||||
# CUPSが動いていない場合は起動
|
||||
sudo systemctl start cups
|
||||
sudo systemctl enable cups
|
||||
|
||||
# プリンター一覧の確認
|
||||
lpstat -p -d
|
||||
|
||||
3.2 プリンターの権限設定
|
||||
|
||||
# プリンターデバイスの確認
|
||||
ls -l /dev/usb/lp*
|
||||
|
||||
# プリンターデバイスの権限設定
|
||||
sudo chmod 666 /dev/usb/lp0
|
||||
|
||||
3.3 ネットワークプリンターの場合
|
||||
|
||||
config.yamlのprinterセクションを以下のように設定:
|
||||
yamlCopyprinter:
|
||||
name: ipp://192.168.1.100:631/ipp/port1
|
||||
|
||||
4. Dockerイメージのビルドと起動
|
||||
4.1 イメージのビルド
|
||||
bashCopy# イメージのビルド
|
||||
docker-compose build
|
||||
4.2 コンテナの起動
|
||||
bashCopy# コンテナの起動(バックグラウンド)
|
||||
docker-compose up -d
|
||||
|
||||
# ログの確認
|
||||
docker-compose logs -f
|
||||
5. 動作確認
|
||||
5.1 ログの確認
|
||||
bashCopy# アプリケーションログの確認
|
||||
tail -f logs/app.log
|
||||
|
||||
# エラーログの確認
|
||||
tail -f logs/error.log
|
||||
5.2 テスト印刷
|
||||
bashCopy# テスト用PDFファイルをS3にアップロード
|
||||
aws s3 cp test.pdf s3://sumasenrogaining/美濃加茂/scoreboard/test.pdf
|
||||
5.3 プロセスの確認
|
||||
bashCopy# コンテナの状態確認
|
||||
docker-compose ps
|
||||
|
||||
# コンテナ内のプロセス確認
|
||||
docker-compose exec printer ps aux
|
||||
6. トラブルシューティング
|
||||
6.1 印刷できない場合
|
||||
|
||||
CUPSの状態確認
|
||||
|
||||
bashCopydocker-compose exec printer lpstat -t
|
||||
|
||||
プリンターの権限確認
|
||||
|
||||
bashCopydocker-compose exec printer ls -l /dev/usb/lp0
|
||||
|
||||
プリンター接続の確認
|
||||
|
||||
bashCopydocker-compose exec printer lpinfo -v
|
||||
6.2 S3接続エラーの場合
|
||||
|
||||
認証情報の確認
|
||||
|
||||
bashCopydocker-compose exec printer env | grep AWS
|
||||
|
||||
S3バケットへのアクセス確認
|
||||
|
||||
bashCopydocker-compose exec printer python3 -c "
|
||||
import boto3
|
||||
s3 = boto3.client('s3')
|
||||
response = s3.list_objects_v2(Bucket='sumasenrogaining')
|
||||
print(response)
|
||||
"
|
||||
6.3 ログの確認方法
|
||||
bashCopy# 直近のエラーログ
|
||||
docker-compose exec printer tail -f /logs/error.log
|
||||
|
||||
# アプリケーションログ
|
||||
docker-compose exec printer tail -f /logs/app.log
|
||||
7. 運用とメンテナンス
|
||||
7.1 定期的なメンテナンス
|
||||
bashCopy# ログローテーション確認
|
||||
ls -l logs/
|
||||
|
||||
# ディスク使用量確認
|
||||
du -sh data/ logs/
|
||||
|
||||
# 古いPDFファイルの削除
|
||||
find data/files -name "*.pdf" -mtime +30 -delete
|
||||
7.2 バックアップ
|
||||
bashCopy# 設定ファイルのバックアップ
|
||||
tar czf config-backup-$(date +%Y%m%d).tar.gz .env config.yaml
|
||||
|
||||
# 処理済みファイルログのバックアップ
|
||||
cp data/${LOCATION}.filelog data/${LOCATION}.filelog.bak
|
||||
7.3 アップデート手順
|
||||
bashCopy# コンテナの停止
|
||||
docker-compose down
|
||||
|
||||
# 新しいイメージのビルド
|
||||
docker-compose build --no-cache
|
||||
|
||||
# コンテナの再起動
|
||||
docker-compose up -d
|
||||
8. セキュリティ考慮事項
|
||||
8.1 認証情報の管理
|
||||
|
||||
.envファイルのパーミッションを600に設定
|
||||
AWSアクセスキーは定期的にローテーション
|
||||
本番環境では.envファイルをgitにコミットしない
|
||||
|
||||
8.2 ネットワークセキュリティ
|
||||
|
||||
コンテナネットワークは必要最小限の公開
|
||||
プリンターアクセスは内部ネットワークに制限
|
||||
AWS S3へのアクセスは特定のIPに制限
|
||||
|
||||
8.3 モニタリング
|
||||
|
||||
CloudWatchなどでS3アクセスログを監視
|
||||
印刷ジョブの異常を検知して通知
|
||||
エラーログの定期チェック
|
||||
174
rogaining_autoprint/app/main.py
Normal file
174
rogaining_autoprint/app/main.py
Normal file
@ -0,0 +1,174 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
import boto3
|
||||
import cups
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
from loguru import logger
|
||||
|
||||
class Config:
|
||||
def __init__(self):
|
||||
load_dotenv()
|
||||
with open('config.yaml', 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
# 環境変数で設定値を上書き
|
||||
self.config['s3']['bucket'] = os.getenv('S3_BUCKET_NAME', self.config['s3']['bucket'])
|
||||
self.config['s3']['prefix'] = self.config['s3']['prefix'].replace('${LOCATION}', os.getenv('LOCATION', ''))
|
||||
self.config['printer']['name'] = os.getenv('PRINTER_NAME', self.config['printer']['name'])
|
||||
|
||||
class S3Manager:
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
self.s3_client = boto3.client('s3')
|
||||
|
||||
def list_files(self) -> List[str]:
|
||||
"""S3バケットのファイル一覧を取得"""
|
||||
try:
|
||||
files = []
|
||||
paginator = self.s3_client.get_paginator('list_objects_v2')
|
||||
for page in paginator.paginate(
|
||||
Bucket=self.config.config['s3']['bucket'],
|
||||
Prefix=self.config.config['s3']['prefix']
|
||||
):
|
||||
if 'Contents' in page:
|
||||
files.extend([obj['Key'] for obj in page['Contents']])
|
||||
return files
|
||||
except Exception as e:
|
||||
logger.error(f"S3ファイル一覧の取得に失敗: {e}")
|
||||
return []
|
||||
|
||||
def download_file(self, key: str, local_path: str) -> bool:
|
||||
"""S3からファイルをダウンロード"""
|
||||
try:
|
||||
self.s3_client.download_file(
|
||||
self.config.config['s3']['bucket'],
|
||||
key,
|
||||
local_path
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"ファイルのダウンロードに失敗 {key}: {e}")
|
||||
return False
|
||||
|
||||
class PrinterManager:
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
self.conn = cups.Connection()
|
||||
|
||||
def print_file(self, filepath: str) -> bool:
|
||||
"""ファイルを印刷"""
|
||||
printer_config = self.config.config['printer']
|
||||
max_attempts = printer_config['retry']['max_attempts']
|
||||
delay = printer_config['retry']['delay_seconds']
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
job_id = self.conn.printFile(
|
||||
printer_config['name'],
|
||||
filepath,
|
||||
"Rogaining Score",
|
||||
printer_config['options']
|
||||
)
|
||||
logger.info(f"印刷ジョブを送信: {filepath} (Job ID: {job_id})")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"印刷に失敗 (試行 {attempt + 1}/{max_attempts}): {e}")
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(delay)
|
||||
else:
|
||||
return False
|
||||
|
||||
class FileManager:
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
self.file_log_path = Path(self.config.config['app']['save_path']) / self.config.config['app']['file_log']
|
||||
self.processed_files = self._load_processed_files()
|
||||
|
||||
def _load_processed_files(self) -> List[str]:
|
||||
"""処理済みファイルの一覧を読み込み"""
|
||||
try:
|
||||
if self.file_log_path.exists():
|
||||
with open(self.file_log_path, 'r') as f:
|
||||
return json.load(f)
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"処理済みファイル一覧の読み込みに失敗: {e}")
|
||||
return []
|
||||
|
||||
def save_processed_files(self):
|
||||
"""処理済みファイルの一覧を保存"""
|
||||
try:
|
||||
self.file_log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.file_log_path, 'w') as f:
|
||||
json.dump(self.processed_files, f)
|
||||
except Exception as e:
|
||||
logger.error(f"処理済みファイル一覧の保存に失敗: {e}")
|
||||
|
||||
def get_new_files(self, current_files: List[str]) -> List[str]:
|
||||
"""新規ファイルを特定"""
|
||||
return list(set(current_files) - set(self.processed_files))
|
||||
|
||||
def setup_logging(config: Config):
|
||||
"""ロギングの設定"""
|
||||
log_path = Path(config.config['app']['log_path'])
|
||||
log_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
logger.add(
|
||||
log_path / "app.log",
|
||||
rotation="1 day",
|
||||
retention="30 days",
|
||||
level="INFO"
|
||||
)
|
||||
logger.add(
|
||||
log_path / config.config['app']['error_log'],
|
||||
level="ERROR"
|
||||
)
|
||||
|
||||
def main():
|
||||
config = Config()
|
||||
setup_logging(config)
|
||||
|
||||
s3_manager = S3Manager(config)
|
||||
printer_manager = PrinterManager(config)
|
||||
file_manager = FileManager(config)
|
||||
|
||||
logger.info("スコアボード監視システムを開始")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# S3のファイル一覧を取得
|
||||
current_files = s3_manager.list_files()
|
||||
|
||||
# 新規ファイルを特定
|
||||
new_files = file_manager.get_new_files(current_files)
|
||||
|
||||
for file_key in new_files:
|
||||
if not file_key.endswith('.pdf'):
|
||||
file_manager.processed_files.append(file_key)
|
||||
continue
|
||||
|
||||
# 保存先パスの設定
|
||||
local_path = Path(config.config['app']['save_path']) / Path(file_key).name
|
||||
local_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ファイルのダウンロードと印刷
|
||||
if s3_manager.download_file(file_key, str(local_path)):
|
||||
if printer_manager.print_file(str(local_path)):
|
||||
file_manager.processed_files.append(file_key)
|
||||
file_manager.save_processed_files()
|
||||
|
||||
time.sleep(10) # ポーリング間隔
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"予期せぬエラーが発生: {e}")
|
||||
time.sleep(30) # エラー時は長めの待機
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
19
rogaining_autoprint/config.yaml
Normal file
19
rogaining_autoprint/config.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
app:
|
||||
save_path: /data/files
|
||||
log_path: /logs
|
||||
file_log: ${LOCATION}.filelog
|
||||
error_log: error.log
|
||||
|
||||
s3:
|
||||
bucket: ${S3_BUCKET_NAME}
|
||||
prefix: ${LOCATION}/scoreboard/
|
||||
|
||||
printer:
|
||||
name: ${PRINTER_NAME}
|
||||
options:
|
||||
PageSize: A4
|
||||
orientation: landscape
|
||||
sides: two-sided-long-edge
|
||||
retry:
|
||||
max_attempts: 3
|
||||
delay_seconds: 5
|
||||
20
rogaining_autoprint/docker-compose.yaml
Normal file
20
rogaining_autoprint/docker-compose.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
printer:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ./app:/app
|
||||
- ./data:/data
|
||||
- ./logs:/logs
|
||||
environment:
|
||||
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
|
||||
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
|
||||
- AWS_DEFAULT_REGION=${AWS_REGION}
|
||||
restart: always
|
||||
devices:
|
||||
- "/dev/usb/lp0:/dev/usb/lp0"
|
||||
privileged: true # プリンターアクセスに必要
|
||||
|
||||
5
rogaining_autoprint/requirements.txt
Normal file
5
rogaining_autoprint/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
boto3==1.34.11
|
||||
python-dotenv==1.0.0
|
||||
pycups==2.0.1
|
||||
PyYAML==6.0.1
|
||||
loguru==0.7.2
|
||||
406
tmp_point.txt
Normal file
406
tmp_point.txt
Normal file
@ -0,0 +1,406 @@
|
||||
Best Wishes 71 2024-10-26 00:42:27+00
|
||||
Best Wishes 71 2024-10-26 00:42:28+00
|
||||
Best Wishes 74 2024-10-26 00:57:09+00
|
||||
Best Wishes 74 2024-10-26 00:57:10+00
|
||||
Best Wishes 74 2024-10-26 00:57:11+00
|
||||
Best Wishes 74 2024-10-26 00:57:12+00
|
||||
Best Wishes 74 2024-10-26 00:57:13+00
|
||||
Best Wishes 74 2024-10-26 00:57:13+00
|
||||
Best Wishes 74 2024-10-26 00:57:14+00
|
||||
Best Wishes 74 2024-10-26 00:57:35+00
|
||||
Best Wishes 74 2024-10-26 00:57:37+00
|
||||
Best Wishes 74 2024-10-26 00:57:38+00
|
||||
Best Wishes 74 2024-10-26 00:57:38+00
|
||||
Best Wishes 72 2024-10-26 00:59:10+00
|
||||
Best Wishes 72 2024-10-26 00:59:10+00
|
||||
Best Wishes 5 2024-10-26 01:05:44+00
|
||||
Best Wishes 5 2024-10-26 01:05:46+00
|
||||
Best Wishes 5 2024-10-26 01:05:48+00
|
||||
Best Wishes 4 2024-10-26 01:09:38+00
|
||||
Best Wishes 4 2024-10-26 01:09:40+00
|
||||
Best Wishes 63 2024-10-26 01:13:20+00
|
||||
Best Wishes 63 2024-10-26 01:13:23+00
|
||||
Best Wishes 64 2024-10-26 01:15:04+00
|
||||
Best Wishes 64 2024-10-26 01:15:08+00
|
||||
Best Wishes 67 2024-10-26 01:44:27+00
|
||||
Best Wishes 67 2024-10-26 01:44:28+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:29+00
|
||||
Best Wishes 67 2024-10-26 01:44:30+00
|
||||
Best Wishes 68 2024-10-26 02:01:53+00
|
||||
Best Wishes 68 2024-10-26 02:01:56+00
|
||||
Best Wishes 69 2024-10-26 02:06:20+00
|
||||
Best Wishes 69 2024-10-26 02:06:23+00
|
||||
Best Wishes 69 2024-10-26 02:06:23+00
|
||||
Best Wishes 69 2024-10-26 02:06:23+00
|
||||
Best Wishes 70 2024-10-26 02:19:03+00
|
||||
Best Wishes 52 2024-10-26 02:51:14+00
|
||||
Best Wishes 52 2024-10-26 02:51:15+00
|
||||
Best Wishes 52 2024-10-26 02:51:15+00
|
||||
Best Wishes 52 2024-10-26 02:51:15+00
|
||||
Best Wishes 52 2024-10-26 02:51:17+00
|
||||
Best Wishes 52 2024-10-26 02:51:18+00
|
||||
Best Wishes 48 2024-10-26 02:55:16+00
|
||||
Best Wishes 48 2024-10-26 02:55:17+00
|
||||
Best Wishes 48 2024-10-26 02:55:19+00
|
||||
Best Wishes 51 2024-10-26 03:07:12+00
|
||||
Best Wishes 51 2024-10-26 03:07:12+00
|
||||
Best Wishes 51 2024-10-26 03:07:13+00
|
||||
Best Wishes 51 2024-10-26 03:07:13+00
|
||||
Best Wishes 51 2024-10-26 03:07:13+00
|
||||
Best Wishes 51 2024-10-26 03:07:14+00
|
||||
Best Wishes 51 2024-10-26 03:07:15+00
|
||||
Best Wishes 51 2024-10-26 03:07:15+00
|
||||
Best Wishes 51 2024-10-26 03:07:16+00
|
||||
Best Wishes 51 2024-10-26 03:07:17+00
|
||||
Best Wishes 51 2024-10-26 03:07:18+00
|
||||
Best Wishes 51 2024-10-26 03:07:19+00
|
||||
FC岐阜 71 2024-10-26 00:43:48+00
|
||||
FC岐阜 73 2024-10-26 00:54:18+00
|
||||
FC岐阜 73 2024-10-26 00:54:28+00
|
||||
FC岐阜 73 2024-10-26 00:54:33+00
|
||||
M sisters with D 71 2024-10-26 00:43:56+00
|
||||
M sisters with D 74 2024-10-26 00:53:43+00
|
||||
M sisters with D 64 2024-10-26 01:17:28+00
|
||||
M sisters with D 67 2024-10-26 01:35:51+00
|
||||
M sisters with D 61 2024-10-26 02:45:51+00
|
||||
M sisters with D 63 2024-10-26 02:55:07+00
|
||||
M sisters with D 8 2024-10-26 03:26:29+00
|
||||
To the next chapter 71 2024-10-26 00:43:14+00
|
||||
To the next chapter 71 2024-10-26 00:43:48+00
|
||||
To the next chapter 6 2024-10-26 01:14:09+00
|
||||
To the next chapter 10 2024-10-26 01:26:00+00
|
||||
To the next chapter 10 2024-10-26 01:26:24+00
|
||||
To the next chapter 9 2024-10-26 01:34:14+00
|
||||
To the next chapter 8 2024-10-26 01:38:45+00
|
||||
To the next chapter 8 2024-10-26 01:39:04+00
|
||||
To the next chapter 7 2024-10-26 01:43:50+00
|
||||
To the next chapter 46 2024-10-26 02:01:08+00
|
||||
To the next chapter 60 2024-10-26 02:14:02+00
|
||||
To the next chapter 55 2024-10-26 02:19:23+00
|
||||
To the next chapter 59 2024-10-26 02:32:25+00
|
||||
To the next chapter 61 2024-10-26 02:43:34+00
|
||||
To the next chapter 61 2024-10-26 02:43:51+00
|
||||
To the next chapter 62 2024-10-26 02:49:33+00
|
||||
To the next chapter 63 2024-10-26 02:53:32+00
|
||||
To the next chapter 63 2024-10-26 02:53:48+00
|
||||
To the next chapter 64 2024-10-26 02:57:50+00
|
||||
To the next chapter 65 2024-10-26 03:02:26+00
|
||||
To the next chapter 4 2024-10-26 03:10:58+00
|
||||
To the next chapter 3 2024-10-26 03:17:01+00
|
||||
To the next chapter 1 2024-10-26 03:25:46+00
|
||||
akira 50 2024-10-18 04:38:41+00
|
||||
akira 50 2024-10-18 04:38:42+00
|
||||
akira 49 2024-10-18 04:39:11+00
|
||||
akira 49 2024-10-18 04:39:15+00
|
||||
akira 71 2024-10-18 08:53:29+00
|
||||
akira 71 2024-10-19 02:26:52+00
|
||||
akira 71 2024-10-19 02:27:08+00
|
||||
akira 201 2024-10-19 02:40:56+00
|
||||
akira 200 2024-10-19 09:12:57+00
|
||||
akira 201 2024-10-20 00:12:44+00
|
||||
akira 201 2024-10-20 00:12:48+00
|
||||
akira 201 2024-10-20 00:14:57+00
|
||||
akira 201 2024-10-20 00:17:08+00
|
||||
akira 201 2024-10-20 01:40:29+00
|
||||
akira 201 2024-10-20 09:36:37+00
|
||||
akira 201 2024-10-20 09:36:58+00
|
||||
akira 201 2024-10-20 10:03:54+00
|
||||
akira 201 2024-10-20 12:18:54+00
|
||||
akira 201 2024-10-20 13:25:15+00
|
||||
akira 201 2024-10-20 13:30:20+00
|
||||
akira 201 2024-10-20 13:39:37+00
|
||||
akira 201 2024-10-20 13:42:25+00
|
||||
akira 200 2024-10-20 21:25:58+00
|
||||
akira 44 2024-10-20 21:27:26+00
|
||||
akira 44 2024-10-20 21:28:22+00
|
||||
best wishes - 71 2024-10-26 00:42:58+00
|
||||
best wishes - 74 2024-10-26 01:01:29+00
|
||||
best wishes - 5 2024-10-26 01:05:36+00
|
||||
best wishes - 4 2024-10-26 01:09:43+00
|
||||
best wishes - 63 2024-10-26 01:13:20+00
|
||||
best wishes - 68 2024-10-26 02:05:33+00
|
||||
best wishes - 68 2024-10-26 02:06:06+00
|
||||
best wishes - 54 2024-10-26 02:37:06+00
|
||||
best wishes - 57 2024-10-26 02:45:48+00
|
||||
best wishes - 53 2024-10-26 02:49:23+00
|
||||
best wishes - 52 2024-10-26 02:51:04+00
|
||||
best wishes - 48 2024-10-26 02:55:18+00
|
||||
best wishes - 51 2024-10-26 03:04:31+00
|
||||
best wishes - 33 2024-10-26 03:21:46+00
|
||||
しーくん 71 2024-10-26 00:43:21+00
|
||||
しーくん 71 2024-10-26 00:43:50+00
|
||||
しーくん 4 2024-10-26 01:10:14+00
|
||||
しーくん 4 2024-10-26 01:10:17+00
|
||||
しーくん 64 2024-10-26 01:19:27+00
|
||||
しーくん 65 2024-10-26 01:26:59+00
|
||||
しーくん 63 2024-10-26 03:09:32+00
|
||||
しーくん 63 2024-10-26 03:15:39+00
|
||||
しーくん 63 2024-10-26 03:16:07+00
|
||||
しーくん 74 2024-10-26 03:39:10+00
|
||||
たてない 71 2024-10-26 00:38:47+00
|
||||
たてない 71 2024-10-26 00:43:23+00
|
||||
たてない 71 2024-10-26 00:43:50+00
|
||||
たてない 71 2024-10-26 00:44:16+00
|
||||
たてない 71 2024-10-26 00:45:11+00
|
||||
たてない 71 2024-10-26 00:46:19+00
|
||||
たてない 71 2024-10-26 00:46:40+00
|
||||
たてない 5 2024-10-26 01:10:46+00
|
||||
たてない 3 2024-10-26 01:43:27+00
|
||||
たてない 4 2024-10-26 01:59:04+00
|
||||
たてない 63 2024-10-26 02:07:28+00
|
||||
たてない 63 2024-10-26 02:09:07+00
|
||||
たてない 63 2024-10-26 02:09:20+00
|
||||
たてない 63 2024-10-26 02:09:47+00
|
||||
たてない 64 2024-10-26 02:15:58+00
|
||||
たてない 64 2024-10-26 02:16:11+00
|
||||
たてない 62 2024-10-26 02:23:19+00
|
||||
たてない 61 2024-10-26 02:27:58+00
|
||||
たてない 61 2024-10-26 02:31:27+00
|
||||
たてない 61 2024-10-26 02:31:38+00
|
||||
たてない 61 2024-10-26 02:31:48+00
|
||||
たてない 61 2024-10-26 02:32:00+00
|
||||
たてない 7 2024-10-26 03:06:20+00
|
||||
たてない 8 2024-10-26 03:11:47+00
|
||||
たてない 8 2024-10-26 03:13:20+00
|
||||
たてない 9 2024-10-26 03:17:54+00
|
||||
たてない 9 2024-10-26 03:18:03+00
|
||||
とみ 74 2024-10-26 00:35:01+00
|
||||
とみ 74 2024-10-26 00:35:31+00
|
||||
とみ 74 2024-10-26 00:35:36+00
|
||||
とみ 74 2024-10-26 00:38:23+00
|
||||
とみ 73 2024-10-26 00:47:27+00
|
||||
なこさんず 71 2024-10-26 00:43:25+00
|
||||
なこさんず 71 2024-10-26 00:43:55+00
|
||||
なこさんず 69 2024-10-26 02:11:37+00
|
||||
なこさんず 66 2024-10-26 02:42:15+00
|
||||
なこさんず 61 2024-10-26 02:58:52+00
|
||||
なこさんず 61 2024-10-26 02:59:45+00
|
||||
なこさんず 62 2024-10-26 03:07:45+00
|
||||
なこさんず 63 2024-10-26 03:12:20+00
|
||||
なこさんず 63 2024-10-26 03:12:34+00
|
||||
なこさんず 3 2024-10-26 03:26:54+00
|
||||
むじょか 71 2024-10-26 00:43:06+00
|
||||
むじょか 71 2024-10-26 00:43:21+00
|
||||
むじょか 4 2024-10-26 01:06:08+00
|
||||
むじょか 3 2024-10-26 01:12:00+00
|
||||
むじょか 64 2024-10-26 01:20:39+00
|
||||
むじょか 65 2024-10-26 01:26:17+00
|
||||
むじょか 67 2024-10-26 01:44:53+00
|
||||
むじょか 68 2024-10-26 02:05:57+00
|
||||
むじょか 69 2024-10-26 02:09:25+00
|
||||
むじょか 70 2024-10-26 02:19:16+00
|
||||
むじょか 66 2024-10-26 02:29:29+00
|
||||
むじょか 58 2024-10-26 02:40:42+00
|
||||
むじょか 59 2024-10-26 02:47:30+00
|
||||
むじょか 61 2024-10-26 03:00:13+00
|
||||
むじょか 61 2024-10-26 03:01:57+00
|
||||
むじょか 61 2024-10-26 03:02:17+00
|
||||
むじょか 62 2024-10-26 03:13:21+00
|
||||
むじょか 63 2024-10-26 03:18:15+00
|
||||
むじょか 63 2024-10-26 03:19:21+00
|
||||
むじょか 5 2024-10-26 03:39:49+00
|
||||
ウエストサイド 71 2024-10-26 00:43:23+00
|
||||
ウエストサイド 71 2024-10-26 00:43:52+00
|
||||
ウエストサイド 4 2024-10-26 01:06:44+00
|
||||
ウエストサイド 64 2024-10-26 01:14:05+00
|
||||
ウエストサイド 65 2024-10-26 01:20:39+00
|
||||
ウエストサイド 69 2024-10-26 01:38:38+00
|
||||
ウエストサイド 68 2024-10-26 01:42:49+00
|
||||
ウエストサイド 63 2024-10-26 02:06:58+00
|
||||
ウエストサイド 63 2024-10-26 02:07:16+00
|
||||
ウエストサイド 63 2024-10-26 02:07:33+00
|
||||
ウエストサイド 62 2024-10-26 02:11:15+00
|
||||
ウエストサイド 62 2024-10-26 02:11:20+00
|
||||
ウエストサイド 61 2024-10-26 02:17:22+00
|
||||
ウエストサイド 61 2024-10-26 02:17:38+00
|
||||
ウエストサイド 61 2024-10-26 02:17:51+00
|
||||
ウエストサイド 59 2024-10-26 02:29:42+00
|
||||
ウエストサイド 59 2024-10-26 02:29:48+00
|
||||
ウエストサイド 58 2024-10-26 02:36:56+00
|
||||
ウエストサイド 55 2024-10-26 02:49:42+00
|
||||
ウエストサイド 53 2024-10-26 03:00:37+00
|
||||
ウエストサイド 52 2024-10-26 03:03:23+00
|
||||
ウエストサイド 48 2024-10-26 03:07:51+00
|
||||
ウエストサイド 56 2024-10-26 03:16:04+00
|
||||
ウエストサイド 60 2024-10-26 03:23:17+00
|
||||
チームエル 71 2024-10-26 00:42:13+00
|
||||
チームエル 71 2024-10-26 00:42:41+00
|
||||
チームエル 4 2024-10-26 01:06:10+00
|
||||
チームエル 64 2024-10-26 01:11:45+00
|
||||
チームエル 64 2024-10-26 01:11:55+00
|
||||
チームエル 67 2024-10-26 01:30:22+00
|
||||
チームエル 68 2024-10-26 01:55:38+00
|
||||
チームエル 69 2024-10-26 01:58:56+00
|
||||
チームエル 70 2024-10-26 02:11:30+00
|
||||
チームエル 54 2024-10-26 02:29:59+00
|
||||
チームエル 55 2024-10-26 02:47:49+00
|
||||
チームエル 60 2024-10-26 02:53:00+00
|
||||
チームエル 59 2024-10-26 03:03:31+00
|
||||
チームエル 58 2024-10-26 03:10:53+00
|
||||
チームエル 61 2024-10-26 03:21:21+00
|
||||
チームエル 61 2024-10-26 03:21:32+00
|
||||
チームエル 61 2024-10-26 03:22:22+00
|
||||
チームエル 61 2024-10-26 03:22:51+00
|
||||
チームエル 62 2024-10-26 03:24:50+00
|
||||
チームエル 63 2024-10-26 03:28:21+00
|
||||
チームエル 63 2024-10-26 03:28:32+00
|
||||
チームエル 63 2024-10-26 03:28:45+00
|
||||
チームエル 63 2024-10-26 03:29:07+00
|
||||
ベル 71 2024-10-26 00:42:47+00
|
||||
ベル 71 2024-10-26 00:43:40+00
|
||||
ベル 71 2024-10-26 00:44:42+00
|
||||
ベル 71 2024-10-26 00:45:02+00
|
||||
ベル 7 2024-10-26 01:15:15+00
|
||||
ベル 8 2024-10-26 01:19:36+00
|
||||
ベル 8 2024-10-26 01:20:38+00
|
||||
ベル 8 2024-10-26 01:20:50+00
|
||||
ベル 8 2024-10-26 01:21:03+00
|
||||
ベル 9 2024-10-26 01:24:20+00
|
||||
ベル 9 2024-10-26 01:24:27+00
|
||||
ベル 9 2024-10-26 01:24:32+00
|
||||
ベル 9 2024-10-26 01:24:39+00
|
||||
ベル 9 2024-10-26 01:24:43+00
|
||||
ベル 10 2024-10-26 01:33:49+00
|
||||
ベル 10 2024-10-26 01:34:05+00
|
||||
ベル 6 2024-10-26 01:49:03+00
|
||||
ベル 6 2024-10-26 01:49:09+00
|
||||
ベル 6 2024-10-26 01:49:12+00
|
||||
ベル 5 2024-10-26 02:08:20+00
|
||||
ベル 5 2024-10-26 02:08:26+00
|
||||
ベル 5 2024-10-26 02:08:33+00
|
||||
ベル 63 2024-10-26 02:22:51+00
|
||||
ベル 63 2024-10-26 02:23:40+00
|
||||
ベル 63 2024-10-26 02:23:47+00
|
||||
ベル 64 2024-10-26 02:28:03+00
|
||||
ベル 65 2024-10-26 02:34:15+00
|
||||
ベル 69 2024-10-26 02:49:23+00
|
||||
ベル 69 2024-10-26 02:49:30+00
|
||||
ベル 69 2024-10-26 02:49:32+00
|
||||
ベル 68 2024-10-26 02:55:18+00
|
||||
ベル 70 2024-10-26 03:11:45+00
|
||||
ベル 70 2024-10-26 03:24:31+00
|
||||
ベル 66 2024-10-26 03:25:04+00
|
||||
ベル 61 2024-10-26 03:34:27+00
|
||||
ベル 61 2024-10-26 03:34:44+00
|
||||
ベル 61 2024-10-26 03:34:50+00
|
||||
井口心平 202 2024-10-25 23:00:02+00
|
||||
井口心平 201 2024-10-25 23:14:21+00
|
||||
井口心平 200 2024-10-25 23:29:03+00
|
||||
井口心平 71 2024-10-26 00:43:01+00
|
||||
井口心平 71 2024-10-26 00:43:51+00
|
||||
井口心平 4 2024-10-26 01:04:27+00
|
||||
井口心平 64 2024-10-26 01:10:42+00
|
||||
井口心平 67 2024-10-26 01:22:53+00
|
||||
井口心平 69 2024-10-26 01:39:33+00
|
||||
井口心平 70 2024-10-26 01:46:14+00
|
||||
井口心平 54 2024-10-26 01:59:32+00
|
||||
井口心平 57 2024-10-26 02:04:46+00
|
||||
井口心平 53 2024-10-26 02:06:32+00
|
||||
井口心平 56 2024-10-26 02:09:34+00
|
||||
井口心平 52 2024-10-26 02:11:23+00
|
||||
井口心平 48 2024-10-26 02:13:32+00
|
||||
井口心平 47 2024-10-26 02:16:45+00
|
||||
井口心平 42 2024-10-26 02:21:00+00
|
||||
井口心平 43 2024-10-26 02:24:42+00
|
||||
井口心平 39 2024-10-26 02:35:17+00
|
||||
井口心平 37 2024-10-26 02:39:20+00
|
||||
井口心平 37 2024-10-26 02:39:56+00
|
||||
井口心平 38 2024-10-26 02:43:10+00
|
||||
井口心平 26 2024-10-26 02:55:07+00
|
||||
井口心平 25 2024-10-26 03:01:47+00
|
||||
井口心平 23 2024-10-26 03:12:26+00
|
||||
井口心平 21 2024-10-26 03:17:51+00
|
||||
井口心平 20 2024-10-26 03:23:01+00
|
||||
井口心平 20 2024-10-26 03:24:24+00
|
||||
井口心平 20 2024-10-26 03:24:33+00
|
||||
井口心平 17 2024-10-26 03:31:50+00
|
||||
井口心平 16 2024-10-26 03:35:13+00
|
||||
山本哲也 71 2024-10-26 00:43:06+00
|
||||
山本哲也 71 2024-10-26 00:43:22+00
|
||||
山本哲也 72 2024-10-26 01:00:13+00
|
||||
山本哲也 4 2024-10-26 01:04:33+00
|
||||
山本哲也 63 2024-10-26 01:07:42+00
|
||||
山本哲也 64 2024-10-26 01:09:10+00
|
||||
山本哲也 67 2024-10-26 01:20:44+00
|
||||
山本哲也 68 2024-10-26 01:38:33+00
|
||||
山本哲也 69 2024-10-26 01:40:20+00
|
||||
山本哲也 70 2024-10-26 01:47:00+00
|
||||
山本哲也 54 2024-10-26 01:59:18+00
|
||||
山本哲也 57 2024-10-26 02:04:37+00
|
||||
山本哲也 53 2024-10-26 02:06:26+00
|
||||
山本哲也 52 2024-10-26 02:08:48+00
|
||||
山本哲也 48 2024-10-26 02:10:31+00
|
||||
山本哲也 47 2024-10-26 02:12:42+00
|
||||
山本哲也 43 2024-10-26 02:18:59+00
|
||||
山本哲也 42 2024-10-26 02:22:53+00
|
||||
山本哲也 41 2024-10-26 02:32:01+00
|
||||
山本哲也 19 2024-10-26 02:43:16+00
|
||||
山本哲也 18 2024-10-26 02:49:42+00
|
||||
山本哲也 45 2024-10-26 02:59:37+00
|
||||
山本哲也 46 2024-10-26 03:08:36+00
|
||||
山本哲也 7 2024-10-26 03:23:31+00
|
||||
山本哲也 8 2024-10-26 03:25:33+00
|
||||
山本哲也 8 2024-10-26 03:26:20+00
|
||||
山本哲也 9 2024-10-26 03:28:14+00
|
||||
岐阜県もりあげ隊 71 2024-10-26 00:43:32+00
|
||||
岐阜県もりあげ隊 73 2024-10-26 00:54:25+00
|
||||
岐阜県もりあげ隊 73 2024-10-26 00:54:43+00
|
||||
岐阜県もりあげ隊 73 2024-10-26 00:54:48+00
|
||||
岐阜県もりあげ隊 5 2024-10-26 01:16:30+00
|
||||
岐阜県もりあげ隊 4 2024-10-26 01:35:10+00
|
||||
岐阜県もりあげ隊 65 2024-10-26 01:54:16+00
|
||||
岐阜県もりあげ隊 66 2024-10-26 02:03:32+00
|
||||
岐阜県もりあげ隊 59 2024-10-26 02:18:53+00
|
||||
岐阜県もりあげ隊 55 2024-10-26 03:20:35+00
|
||||
岐阜県もりあげ隊 55 2024-10-26 03:20:53+00
|
||||
岐阜県もりあげ隊 52 2024-10-26 03:32:12+00
|
||||
岐阜県もりあげ隊 48 2024-10-26 03:39:51+00
|
||||
岐阜県もりあげ隊 48 2024-10-26 03:40:07+00
|
||||
細田典匡 71 2024-10-26 00:43:19+00
|
||||
細田典匡 4 2024-10-26 01:04:20+00
|
||||
細田典匡 63 2024-10-26 01:07:27+00
|
||||
細田典匡 62 2024-10-26 01:09:31+00
|
||||
細田典匡 61 2024-10-26 01:11:32+00
|
||||
細田典匡 66 2024-10-26 01:15:23+00
|
||||
細田典匡 66 2024-10-26 01:16:01+00
|
||||
細田典匡 65 2024-10-26 01:17:54+00
|
||||
細田典匡 67 2024-10-26 01:29:32+00
|
||||
細田典匡 68 2024-10-26 01:42:42+00
|
||||
細田典匡 69 2024-10-26 01:44:41+00
|
||||
細田典匡 70 2024-10-26 01:50:56+00
|
||||
細田典匡 54 2024-10-26 01:59:12+00
|
||||
細田典匡 57 2024-10-26 02:04:43+00
|
||||
細田典匡 53 2024-10-26 02:06:39+00
|
||||
細田典匡 52 2024-10-26 02:08:55+00
|
||||
細田典匡 52 2024-10-26 02:09:07+00
|
||||
細田典匡 48 2024-10-26 02:10:43+00
|
||||
細田典匡 47 2024-10-26 02:12:33+00
|
||||
細田典匡 44 2024-10-26 02:19:38+00
|
||||
細田典匡 43 2024-10-26 02:22:07+00
|
||||
細田典匡 42 2024-10-26 02:24:43+00
|
||||
細田典匡 41 2024-10-26 02:32:22+00
|
||||
細田典匡 41 2024-10-26 02:32:36+00
|
||||
細田典匡 18 2024-10-26 02:39:01+00
|
||||
細田典匡 19 2024-10-26 02:44:48+00
|
||||
細田典匡 20 2024-10-26 02:49:11+00
|
||||
細田典匡 20 2024-10-26 02:49:26+00
|
||||
細田典匡 17 2024-10-26 02:57:47+00
|
||||
細田典匡 16 2024-10-26 03:01:51+00
|
||||
細田典匡 14 2024-10-26 03:06:45+00
|
||||
細田典匡 15 2024-10-26 03:12:55+00
|
||||
細田典匡 13 2024-10-26 03:23:22+00
|
||||
細田典匡 11 2024-10-26 03:36:13+00
|
||||
風呂の会 204 2024-10-25 22:15:14+00
|
||||
風呂の会 67 2024-10-26 01:35:21+00
|
||||
風呂の会 68 2024-10-26 01:54:06+00
|
||||
風呂の会 69 2024-10-26 01:58:26+00
|
||||
風呂の会 70 2024-10-26 02:12:51+00
|
||||
風呂の会 44 2024-10-26 03:03:42+00
|
||||
齋藤貴美子 71 2024-10-26 00:54:21+00
|
||||
齋藤貴美子 5 2024-10-26 01:06:29+00
|
||||
齋藤貴美子 6 2024-10-26 01:20:28+00
|
||||
Reference in New Issue
Block a user