Files
rogaining_srv/詳細機能設計書-外部連携.md
2025-08-20 19:15:19 +09:00

667 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 詳細機能設計書 - 外部システム連携
## 1. 概要
本文書は、ロゲイニング大会管理システムの外部システム連携機能について詳細に記述したものです。
### システム構成
- **Django RESTフレームワーク**: メインAPIPython
- **Ruby Sinatra Server**: 外部システム連携サーバーMobServer_gifuroge.rb
- **AWS S3**: ファイルストレージ
- **外部ロゲイニングシステム**: チーム登録情報との連携
## 2. 外部連携アーキテクチャ
```
Django API → Ruby Server → External Rogaining System
AWS S3 Storage
```
### 2.1 通信フロー
1. Django APIが外部システム連携要求を受信
2. Ruby Sinatraサーバーにリクエスト転送
3. Ruby Serverから外部ロゲイニングシステムに情報送信
4. 処理結果をS3にアップロード
5. レスポンスをDjango API経由でクライアントに返却
## 3. 外部連携API仕様
### 3.1 チーム登録API
#### POST /register_team
**目的**: 外部ロゲイニングシステムにチーム情報を登録する
**リクエスト仕様**:
```http
POST /register_team
Content-Type: application/json
{
"zekken_number": "string", # ゼッケン番号
"team_name": "string", # チーム名
"event_code": "string", # イベントコード
"class_name": "string", # 参加クラス
"member_count": "integer", # メンバー数
"password": "string" # チーム認証パスワード
}
```
**レスポンス仕様**:
```json
{
"status": "OK|ERROR",
"message": "処理結果メッセージ",
"team_id": "登録されたチームID",
"timestamp": "2024-01-01T12:00:00Z"
}
```
**処理フロー**:
1. リクエストパラメータの検証
2. team_tableからチーム情報の取得
3. 外部システムへの登録データ送信
4. 登録結果の確認とレスポンス返却
#### コード実装詳細
```ruby
# MobServer_gifuroge.rbから抜粋
app.post '/register_team' do
crossdomain
headjson
# パラメータ取得
zekken_number = params[:zekken_number]
team_name = params[:team_name]
event_code = params[:event_code]
# チーム情報の検証
team_data = getTeamDataByZekken_number(zekken_number, event_code)
if team_data['result'] == "ERROR"
return {
status: "ERROR",
message: "チーム情報が見つかりません"
}.to_json
end
# 外部システムへの登録処理
result = register_to_external_system(team_data)
# 結果の返却
{
status: result[:status],
message: result[:message],
team_id: result[:team_id],
timestamp: DateTime.now.iso8601
}.to_json
end
```
### 3.2 チーム名更新API
#### POST /update_team_name
**目的**: 外部システムに登録済みのチーム名を更新する
**リクエスト仕様**:
```http
POST /update_team_name
Content-Type: application/json
{
"zekken_number": "string", # ゼッケン番号
"new_team_name": "string", # 新しいチーム名
"event_code": "string" # イベントコード
}
```
**レスポンス仕様**:
```json
{
"status": "OK|ERROR",
"message": "更新結果メッセージ",
"old_name": "変更前のチーム名",
"new_name": "変更後のチーム名",
"timestamp": "2024-01-01T12:00:00Z"
}
```
## 4. スコアボード生成・配信システム
### 4.1 スコアボード自動生成
#### 機能概要
- チーム毎のスコアボードを自動生成
- ExcelファイルからPDFに変換
- AWS S3への自動アップロード
#### POST /generate_scoreboard
**処理フロー**:
1. GPS情報とチェックポイント通過記録の取得
2. Excel形式でのスコアボード生成
3. PDF変換処理
4. S3へのアップロード
5. 外部システムへの配信URL通知
```ruby
def makeScoreboard(zekken_number, event_code, reprintF = false, budleF = false)
# スコアボード生成処理
filepath = getScoreboardGeneral(zekken_number, event_code)
if filepath == "no_data"
return "no data error"
end
# PDF変換
command = "/home/mobilousInstance/jodconverter-cli-4.4.5-SNAPSHOT/bin/jodconverter-cli -o #{docpath_user}/#{pdffile}.xlsx #{docpath_user}/#{pdffile}.pdf"
system(command)
# S3アップロード
aws_url = s3Uploader("#{event_code}/scoreboard", docpath_user, pdffile + '.pdf')
# レポート情報の更新
if getFinalReport(zekken_number, event_code) == "no report"
inputFinalReport(zekken_number, event_code, aws_url)
else
changeFinalReport(zekken_number, event_code, aws_url)
end
aws_url
end
```
### 4.2 非同期処理による大量生成
```ruby
# キューイング処理
$queue = Queue.new
Thread.new do
loop do
begin
item = JSON.parse($queue.pop)
makeScoreboard(item["zekken_number"], item["event_code"], to_boolean(item["reprintF"]), false)
result = removeQueueMemory()
rescue => e
# エラーログ記録
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
error_message = "#{timestamp}=> #{item} : #{e.message}\n"
File.open(queue_error_log_base(), "a") do |file|
file.write(error_message)
end
end
end
end
```
## 5. AWS S3連携
### 5.1 設定情報
```ruby
def s3_key
return "AKIA6LVMTADSVEB5LZ2H"
end
def s3_Skey
return "KIbm47dqVBxSmeHygrh5ENV1uXzJMc7fLnJOvtUm"
end
def s3_bucket
return "sumasenrogaining"
end
def s3_region
return "us-west-2"
end
def s3_domain
return "https://sumasenrogaining.s3.us-west-2.amazonaws.com"
end
```
### 5.2 ファイルアップロード機能
```ruby
def s3Uploader(data_dir, filepath, filename)
bucket = s3_bucket().freeze
region = s3_region().freeze
access_key = s3_key().freeze
secret_key = s3_Skey().freeze
aws_storage = S3_StorageUtil.new(access_key, secret_key, region, bucket, data_dir)
aws_bucket = S3_FolderUtil.new("offlineRecog/work", aws_storage)
originPath = filepath
destPath = data_dir
filename = filename
aws_bucket.uploadFile(originPath, destPath, filename)
aws_url = s3_domain() + '/' + data_dir + '/' + filename
return aws_url
end
```
## 6. データベース連携
### 6.1 PostgreSQL接続設定
```ruby
def set_dbname
return "gifuroge"
end
# データベース接続
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
```
### 6.2 主要テーブル操作
#### team_table操作
```ruby
def getTeamDataByZekken_number(zekken_number, event_code)
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
anytable = UserPostgresTable.new
anytable.useTable(@pgconn, 'team_table')
where = "zekken_number = '#{zekken_number}' AND event_code = '#{event_code}'"
list = anytable.find2(where)
result = {}
list.each { |rec|
result["zekken_number"] = rec["zekken_number"]
result["team_name"] = rec["team_name"]
result["class_name"] = rec["class_name"]
}
@pgconn.disconnect
if result != {} && result != nil
result["result"] = "OK"
return result
else
result["result"] = "ERROR"
return result
end
end
```
#### gps_information操作
```ruby
def getGpsInfo(zekken_number, event_code)
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
anytable = UserPostgresTable.new
anytable.useTable(@pgconn, 'gps_information')
where = "zekken_number = '#{zekken_number}' AND event_code = '#{event_code}' ORDER BY serial_number"
list = anytable.find2(where)
result = []
count = 0
list.each { |rec|
result[count] = {"cp_number" => rec['cp_number']}
count += 1
}
@pgconn.disconnect
return result
end
```
## 7. 画像処理・サムネイル生成
### 7.1 画像リサイズ処理
```ruby
def image_size_width
return 150
end
def image_size_height
return 105
end
# 画像処理実装
filename = rec["cp#{i}_ph"].split('/').last
fullpath = docpath + '/' + filename
# S3からの画像取得
fullpath2 = s3_domain + '/' + URI.encode_www_form_component(event_code, enc=nil) + '/' + zekken_number + '/' + filename
URI.open(fullpath2) do |image|
File.open(fullpath, 'w') do |file|
file.write(image.read)
end
end
# サムネイル生成
extension = filename.split('.').last
filenameTh = filename.gsub(".#{extension}", "_th.#{extension}")
imageOri = Magick::Image.read(fullpath).first
imageTh = imageOri.scale(image_size_width(), image_size_height())
imageTh.write("temp/#{filenameTh}")
nextpath = docpath + '/' + filenameTh
FileUtils.mv("/var/www/temp/#{filenameTh}", nextpath)
result["cp#{i}_ph"] = nextpath
```
## 8. エラーハンドリング
### 8.1 エラーコード体系
```ruby
# エラーコード一覧
# STA-001 : パスワード入力のキャンセルに失敗した状態
# STA-002 : 処理には成功したがchat_statusのレコードデリートに失敗した状態
# STA-003 : 写真受付のキャンセルに失敗した状態
# USR-001 : LINEのユーザー名を取得できていない状態
# USR-002 : ログインに成功したが、user_tableのレコードアップデートに失敗した状態
# USR-003 : ログアウトの処理に失敗した状態
# DBS-001 : そのユーザーが何時間部門のユーザーなのかを認識できなくなった状態
def text_importantError(errorCode)
return "エラーコード:#{errorCode}\nゼッケン番号とエラーコードを添えて、運営に問い合わせて下さい。"
end
```
### 8.2 例外処理パターン
```ruby
begin
ret = anytable.exec(sql)
rescue => error
p error
return "UPDATE ERROR"
end
# データベース接続の確実な切断
@pgconn.disconnect
# キューエラーハンドリング
rescue => e
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
item_value = defined?(item) ? item.to_s : ""
error_message = "#{timestamp}=> #{item_value} : #{e.message}\n"
File.open(queue_error_log_base(), "a") do |file|
file.write(error_message)
end
end
```
## 9. セキュリティ対策
### 9.1 認証・認可
#### チーム認証
```ruby
def zekkenAuthorization(zekken, password)
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
anytable = UserPostgresTable.new
anytable.useTable(@pgconn, 'team_table')
where = "zekken_number = '#{zekken}' AND password = '#{password}'"
list = anytable.find2(where)
result = {}
if list == nil
return { "zekken_number" => "ERROR" }
end
list.each { |rec|
result["zekken_number"] = rec["zekken_number"]
result["team_name"] = rec["team_name"]
result["event_code"] = rec["event_code"]
}
@pgconn.disconnect
if result != {} && result != nil
result["result"] = "OK"
return result
else
result["result"] = "ERROR"
return result
end
end
```
#### 代理人認証
```ruby
def agentAuthorization(password)
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
anytable = UserPostgresTable.new
anytable.useTable(@pgconn, 'agent_table')
where = "password = '#{password}'"
list = anytable.find2(where)
result = ""
if list == nil
return "ERROR"
end
list.each { |rec|
result = rec["agent_id"]
}
@pgconn.disconnect
if result != "" && result != nil
return result
else
return "ERROR"
end
end
```
### 9.2 SQLインジェクション対策
```ruby
# パラメータのエスケープ処理
if team_name.include?("'")
team_name.gsub!("'", "''")
end
where = "team_name = '#{team_name}' AND event_code = '#{event_code}'"
```
### 9.3 CORS設定
```ruby
def crossdomain
headers 'Access-Control-Allow-Origin' => '*'
end
def headjson
headers 'Content-Type' => 'application/json; charset=utf-8'
end
```
## 10. ログ・監視
### 10.1 チャットログ
```ruby
def chatLogger(userId, talker, type, detail)
p "#{talker} : #{detail}"
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
anytable = UserPostgresTable.new
anytable.useTable(@pgconn, 'chat_log')
title = ["userid", "talker", "message_type", "message_detail", "create_at"]
record = {"userid" => userId, "talker" => talker, "message_type" => type, "message_detail" => detail, "create_at" => DateTime.now}
sql = anytable.makeInsertKeySet(title, record)
begin
ret = anytable.exec(sql)
rescue => error
p error
end
@pgconn.disconnect
return "OK"
end
```
### 10.2 キューエラーログ
```ruby
def queue_error_log_base()
return "/var/log/queue_error.log"
end
# エラーログ記録
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
error_message = "#{timestamp}=> #{item} : #{e.message}\n"
File.open(queue_error_log_base(), "a") do |file|
file.write(error_message)
end
```
## 11. パフォーマンス最適化
### 11.1 データベース接続管理
```ruby
# 接続の確実な切断
@pgconn.disconnect
# 接続プールの活用
def with_db_connection
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
begin
yield @pgconn
ensure
@pgconn.disconnect
end
end
```
### 11.2 画像処理の最適化
```ruby
# サムネイル生成のバッチ処理
def batch_thumbnail_generation(image_list)
image_list.each do |image_path|
imageOri = Magick::Image.read(image_path).first
imageTh = imageOri.scale(image_size_width(), image_size_height())
imageTh.write(thumbnail_path(image_path))
end
end
```
### 11.3 キューイング最適化
```ruby
# 非同期処理による負荷分散
Thread.new do
loop do
begin
item = JSON.parse($queue.pop)
process_queue_item(item)
rescue => e
log_queue_error(e, item)
end
end
end
```
## 12. 運用・保守
### 12.1 バックアップ戦略
```ruby
# データベースバックアップ
def backup_database(event_code)
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
backup_file = "backup_#{event_code}_#{timestamp}.sql"
system("pg_dump #{set_dbname()} > #{backup_file}")
# S3へのバックアップアップロード
s3Uploader("backups", ".", backup_file)
end
```
### 12.2 ヘルスチェック
```ruby
app.get '/health' do
crossdomain
headjson
{
status: "OK",
timestamp: DateTime.now.iso8601,
version: "1.0.0",
database: check_database_connection(),
s3: check_s3_connection()
}.to_json
end
def check_database_connection()
begin
@pgconn = UserPostgres.new
dbname = set_dbname()
@pgconn.connectPg("localhost", "mobilous", 0, dbname)
@pgconn.disconnect
return "OK"
rescue => e
return "ERROR: #{e.message}"
end
end
```
## 13. 今後の拡張計画
### 13.1 マイクロサービス化
- Ruby ServerをDockerコンテナ化
- APIゲートウェイの導入
- サービス間通信の最適化
### 13.2 リアルタイム機能
- WebSocketによるリアルタイム順位更新
- プッシュ通知機能の実装
### 13.3 多言語対応
- 国際大会対応のため多言語化
- タイムゾーン対応の強化
---
この詳細機能設計書により、外部システム連携の実装、運用、保守に必要な全ての技術的詳細が文書化されています。