
先日、GoogleSpreadSheet(スプシ)でユーザー管理をしているとあるホームページ運用者から、
そのシートを参照して、ホームページでログインシステムを作りたいと相談をされました。
GoogleAppsScript(GAS)を使うことで、思いの外簡単に構築自体はできたのですが、
スプレッドシートの参照方法について、一般的な鉄板方法と、個人的に堅牢な方法が違っていたので、
独自視点での、
GASにおけるスプシの扱いについて、備忘録を残しておきたいと思います。
ちなみに、今回のコードは、ログイン方式として、メールアドレスとパスワードがスプシに書かれてあって、それを参照するという内容です。
GASでスプシを参照する一般的なコード
function doPost(e) {
try {
// APIに送信された(ログイン画面で入力して送信された値)
const params = JSON.parse(e.postData.contents);
const email = params.email;
const password = params.password;
// スプレッドシートの取得
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = sheet.getDataRange().getValues(); // 全データを配列で取得
// ヘッダー(1行目)を除外して検索
let userFound = null;
for (let i = 1; i < data.length; i++) {
const row = data[i];
if (row[0] === email && row[1] === password) {
userFound = {
email: row[0],
name: row[2]
};
break;
}
}
// レスポンスデータの作成
let result = {};
if (userFound) {
result = {
success: true,
message: "ログイン成功",
user: userFound
};
} else {
result = {
success: false,
message: "メールアドレスまたはパスワードが正しくありません"
};
}
// JSON形式でレスポンスを返却
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
const errorResult = {
success: false,
message: "サーバーエラーが発生しました: " + error.toString()
};
return ContentService.createTextOutput(JSON.stringify(errorResult))
.setMimeType(ContentService.MimeType.JSON);
}
}
GASでスプシを参照する堅牢コード
function doPost(e) {
try {
// APIに送信された(ログイン画面で入力して送信された値)
const params = JSON.parse(e.postData.contents);
const send_mail = params.mail;
const send_pass = params.pass;
if(!send_mail || !send_pass){
return createJsonResponse({
status: "error",
message: `メールまたはパスワードが入力されていません`
});
}
// const send_mail = "geta1972@gmail.com";
// const send_pass = "176b23";
// console.log(email, password)
// 認証対象となるファイルIDとgidを指定
const FILE_ID = '対象スプレッドシートのID';
const GID = "対象シートのgid"; //(1番目のシートなら0、またはURL末尾の#gid=数字)
// 1. file_id からスプレッドシートを開く
const spreadsheet = SpreadsheetApp.openById(FILE_ID);
// 2. gid から該当するシートを取得する
const sheet = getSheetByGid(spreadsheet, GID);
if (!sheet) {
return createJsonResponse({
status: "error",
message: `指定されたシート(gid: ${GID})が存在しません`
});
}
// 3. シート内の全データを取得して照合
const data = sheet.getDataRange().getValues();
const headers = getHeaderString(data[0])
// A列: email, B列: password, C列: name と仮定(1行目のヘッダーを除外して検索)
let userFound = null;
for (let i = 1; i < data.length; i++) {
const row = data[i];
const mail = String(row[headers["メールアドレス"]]).trim();
const pass = String(row[headers["パスワード"]]).trim();
if (send_mail === mail && send_pass === pass) {
userFound = {};
for(const header_key in headers){
userFound[header_key] = row[headers[header_key]]
}
break;
}
}
console.log(userFound)
// 4. 認証結果のレスポンス作成
if (userFound) {
return createJsonResponse({
status: "success",
message: "ログインに成功しました",
user: userFound
});
} else {
return createJsonResponse({
status: "error",
message: "メールアドレスまたはパスワードが正しくありません"
});
}
} catch (error) {
return createJsonResponse({
status: "error",
message: "サーバー側でエラーが発生しました: " + error.toString()
});
}
}
/**
* 補助関数: gid からシートオブジェクトを検索・取得する
*/
function getSheetByGid(spreadsheet, targetGid) {
const sheets = spreadsheet.getSheets();
for (let i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetId().toString() === targetGid.toString()) {
return sheets[i];
}
}
return null;
}
/**
* 補助関数: JSONレスポンスを作成する
*/
function createJsonResponse(data) {
return ContentService.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}
/**
* ヘッダー(1行目)からメールとパスワードの列インデックスを判定
*/
function getHeaderString(header_line){
const headers = {}
for (let i = 1; i < header_line.length; i++) {
const key = String(header_line[i]).trim()
headers[key] = i
}
return headers
}
解説
この2つのコードの違いは、処理を細かく関数化している以外に、
スプシやシートに対しての参照方法が重要なんです。
一般的には、スプシのファイル名と、シート名を参照してアクセスするのに対して、堅牢コードのほうは、それぞれのIDを参照しています。
エンジニアの人であれば、何故IDの方が堅牢なのかは理解できると思いますが、名称で参照している場合に、スプシのファイル名やシート名って、簡単にファイルアクセスできている権限保持者の人であれば、誰でも簡単に変更ができるし、操作ミスで気がつかないうちに勝手に書き換わってしまうリスクがあります。
そうなった場合、「急に動きません」というようなシステム開発者に苦情が来ることになります。
もちろん、原因は問い合わせをしてきた本人ですが、きっと技術スキルレベル的に、「システムが悪いと決めつけていて、万が一にも自分が悪い」とは考えずに文句を言ってくるでしょう。
そんな事態を未然に防ぐための、堅牢な方法なのです。
あと、もう一つ、カラムとしての役割のでーたの1行目をheadersとして、一覧取得して、その名称が何行目にあるかを取得しているのが、
getHeaderString()という関数です。
これは、さすがに、カラムの名称まで変更されると、まともに動作しなくなるんですが、カラムの位置を変更してもいいように名称の位置を初期で保持して、「名称 -> カラム順番」としてその後に処理するようにしています。
ね、これで、かなり運用トラブルは防げるはずでしょ。
あとがき
こういうお作法って、システムをプログラミングするエンジニアじゃないと気がつかないし、指摘できないんですよ。
言われるがままに作るというスタンスのエンジニアは、まずこうした対処はできないし、その後の問い合わせ対応で四苦八苦している姿は、三度の飯よりもよく見かけます。
とりあえず、今回は「GAS + スプシの備忘録」を自分のために残しておきました。
誰かこのコードで助かる人がいたら幸いです。
きっと、GASをつかうプログラマー以外は、この記事をみても、全く意味ないでしょうね。
0 件のコメント:
コメントを投稿