Edited at
AtraeDay 9

【CS Hack 第1弾!!】アプリケーション内でファイルの受け渡しを実装する

More than 1 year has passed since last update.


はじめに

皆さん、カスタマーサクセスしてますか?価値あるプロダクトを作っていますか?

日々自分たちのカスタマーがより良い活動をできるよう、プロダクトを開発・運用しているかと思いますが、活動の中でのヒューマンエラーを無くす動きも大切です。

カスタマーサクセスの為に、検証作業も含め、頻繁にカスタマーとデータのやり取りをする場合(例: 提案資料、データの受け渡し...)、うっかりミスが致命的な問題につながり兼ねません。

メールやFBで気軽に連絡できる時代において、ヒューマンエラーによる情報漏えいはかなり大きな割合を占めております。

leak07.jpg

引用:情報漏洩の原因を徹底解析!原因と結果から学ぶ意識改革

カスタマーのより良い活動を支援しようと思ったのに・・・、その活動がサービスの停止へと導いてしまった、、、なんて事があったら悲しすぎますね。

そんなミスを防ぐ為に、今日はアプリケーション内でファイルをDLする機構を作ることで、セキュアな環境下でのやり取りを実現してみたいと思います。


イメージ図

スクリーンショット 2017-12-09 12.05.27.png


実装


サーバ側

まずは、AWS側の設定を記述します。


config/initializers/aws_s3.rb

Aws.config.update({

credentials: Aws::Credentials.new(
#ACCESS_KEY,
#SECRET_ACCESS_KEY
),
region: #region情報を
endpoint: #endpoint情報を
})

# もし参照パスにルールがあるなら記述しておきます
module AwsS3
BUCKET = 'test'.freeze
BASIC_URL = "https://s3.console.aws.amazon.com/s3/object/#{BUCKET}".freeze
end


ベースのモデルを作成。

class S3Client

def initialize(*_)
@client = Aws::S3::Client.new
end
end

まずはS3にあるファイルを引っ張ってくる処理。

例えばログインしているユーザに応じて、S3のパスを分けます。

class S3Reader < S3Client

def initialize(user)
super
@user_id = user.id
end

def prefix
"#{Rails.env}/#{@user_id}/"
end

def objects
contents = @client.list_objects(bucket: AwsS3::BUCKET, prefix: prefix).contents
contents.map { |content| S3Reader::Object.new(content, prefix) }
end
end

# objectの加工用
class S3Reader::Object
attr_reader :key, :name, :modified_at

def initialize(content, prefix)
@key = content.key
@name = content.key.gsub(prefix, '')
@modified_at = content.last_modified
end
end

controller側に下記を実装。するとS3からDL可能なObjectが引っ張られてきて表示されます。

class DownloadFilesController < ViewBaseController

def index
s3 = S3Reader.new(@current_user)
@objects = s3.objects.sort_by(&:modified_at)
end
end

S3にアップロードしたファイルが・・・

スクリーンショット 2017-12-09 12.23.08.png

アプリケーション側でも閲覧可能に。

スクリーンショット 2017-12-09 12.23.50.png

引っ張るところまで出来れば次はクリック後、DLできるようにします。

DLの方法としては、一定時間だけオブジェクトにアクセスできるURLを作成し、send_dateをすることで、ブラウザ側にDLされるようにします。

参照)

一定時間だけS3のオブジェクトにアクセスできるURLを生成する

class S3Downloader < S3Client

# 一時的なURLは120秒に設定
def download_url(key)
Aws::S3::Presigner.new(client: @client).presigned_url(
:get_object, bucket: AwsS3::BUCKET, key: key, expires_in: 120
)
end

def content(key)
S3Downloader::Object.new(open(download_url(key)), key)
end
end

class S3Downloader::Object
attr_reader :content, :key

def initialize(content, key)
@content = content
@key = key
end

def name
@key.split('/').present? ? @key.split('/')[-1] : 'download'
end

def read
@content.read
end

# ファイルの種類によってはsend_date時に上手くcontent_typeが出せない場合があるので、オーバーライドしておく
def type
@content.content_type
end
end

後は、リストをクリックした際のアクションをController側に実装。

class DownloadFilesController < ViewBaseController

def index
s3 = S3Reader.new(@current_user)
@objects = s3.objects.sort_by(&:modified_at)
end

def download
downloader = S3Downloader.new
content = downloader.content(params[:key])
send_data content.read, filename: content.name, type: content.type
end
end

これらを実施し、クリックすると・・・

ダウンロード.png

無事DLができました。


最後に

今回は第1弾ということで、まずはファイルをDLできる機構を作ってみました。

ログインすることでDLできるので、カスタマーには、『サービス内にアップロードしたので、DLしてくださいね。』と伝えるだけでいけますね。

ヒューマンエラーを出来る限り排除し、価値にフォーカスできる体制をつくるのもプロダクト開発において非常に重要だと感じています!

第二弾はこの機構に自動でファイルをアップロードしていく仕組みを構築します。

これで活用レポートだったり、定期的に送るデータは自動化してしまいましょう。