メンバーカード機能説明について

2023/12/25  倉科空明

今回は、「雲原ファミリーメンバー」や「毛原村外村民証」等で使用している会員番号を表示する機能について説明して行こうと思います。 会員番号を表示する機能は、組織運営や営業などのサービスを便利にしようとしたときに使用する機会があると思います。 使用する技術はPythonとHTML、JavaScript、mysql、Liffとなります。そこまで難しくないのでmysqlやLiffを使ってみたいなと思っている方は少し触ってみるのもいいかもしれません。

全体の流れ

1:ラインのIDを読み取りサーバーを担当するプログラムに送信する

2:ラインのIDを受け取りmysqlのテーブルに同じラインのIDがあるのか確認する

3:同じラインのIDがあった場合は登録している会員番号を表示し、なかった場合はラインのIDと会員番号を登録する

4:クライアントを担当するプログラムに会員番号を送信する

5:会員番号を表示する

    会員番号を表示するプログラムは、全体的にサーバー側とクライアント側の2つのプログラムで分けることができます。 クライアント側ではHTML、LiffとJavaScriptを使用し、サーバー側はPython、mysqlを使用します。   

それぞれの説明

クライアント側のプログラムの説明

プログラム名:sample.html
<script>
let initializedLiff = null;
async function initializeLiffAsync() {
    try {
        if (!initializedLiff) {
            await liff.init({ liffId: 'リフのIDを入れる' });
            initializedLiff = liff;
        }
    } catch (error) {
        console.error(error);
        alert("LIFFの初期化中にエラーが発生しました。");
    }
}

async function getLineProfile() {
    await initializeLiffAsync();
    const profile = await liff.getProfile();
    return profile;
}

function displayMemberNumber(memberNumber) 
        {
            const memberInfoDiv = document.getElementById('memberInfo');
            memberInfoDiv.textContent = `会員番号: ${memberNumber}`;
        }


async function fetchMemberInfo() {
    const profile = await getLineProfile();
    const lineId = profile.userId;
    const displayName = profile.displayName;
    $.ajax({
        url: `サーバーを担当するプログラムの置いているURLを入れる?lineId=${lineId}&displayName=${displayName}`,
        method: 'GET',
        dataType: 'json',
        success: function (data) {
            console.log(data);
            displayMemberNumber(data.memberNumber);
        },
        error: function (error) {
            console.error(error);
            alert("会員情報の取得中にエラーが発生しました。");
        }
    });
}


window.onload = function () {
    fetchMemberInfo()
}
</script>           
                                    
    initializeLiffAsync()関数でLiffを使用して読み込んだラインIDをfetchMemberInfo()関数のajaxで非同期通信を利用してサーバー側のプログラムに送信します。 送られてきた会員番号であるdata.memberNumberをdisplayMemberNumber()関数を表示することができるようにしています。詳しく知りたい方は、参考文献の[1]と[2]に詳しくかかれているのでそちらをご覧ください。

サーバー側のプログラムの説明

プログラム名:main.py
#!/usr/bin/python3.7
# -*- coding: utf-8 -*-

import cgi
import json
from mysql_wrapper import MySQLWrapper
from config import *
from logger import Logger
log = Logger(__file__)

# フォームデータの取得
form = cgi.FieldStorage()
line_id = form.getvalue('lineId')
display_name = form.getvalue('displayName') 

# MySQLWrapperクラスのインスタンスを作成し、データベースに接続
db = MySQLWrapper(*MYSQL_ACCESS)
if line_id is not None and line_id != '':
    try:
    db.connect()
        # ラインのIDがすでに登録されているか確認
        select_query = "SELECT member_number, display_name FROM members WHERE line_id = %s"
        result = db.execute_query(select_query, (line_id,))

        # 新しいラインのIDの場合は新しい会員番号を生成
        if not result:
            # データベース内の最大の会員番号を取得して、それに1を加えることで新しい番号を生成
            get_max_number_query = "SELECT MAX(member_number) FROM members"
            max_number = db.execute_query(get_max_number_query)[0][0] or 0

            # 新しい会員番号を生成(常に4桁の数字に変換してゼロパディング)
            member_number = max_number + 1

            # データベースに新しい会員番号と名前を保存するクエリの実行
            insert_query = "INSERT INTO members (line_id, member_number, display_name) VALUES (%s, %s, %s)"
            db.execute_query(insert_query, (line_id, member_number, display_name))
            log.info_register(f"ユーザーID: {line_id}, ユーザー名: {display_name}, 新しい会員番号: {member_number}")
        else:

            # 既存のラインのIDの場合は保存されている会員番号と名前を取得
            
            member_number = result[0][0]
            existing_display_name =result[0][1]
            # ユーザー名が変更されていた場合、新しい名前でデータベースを更新
            if existing_display_name != display_name:
            update_query = "UPDATE members SET display_name = %s WHERE line_id = %s"
                db.execute_query(update_query, (display_name, line_id))
                log.info_register(f"ユーザーID: {line_id}, 新しい名前: {display_name}")

            log.info_output(f"ユーザーID: {line_id}, 名前: {display_name}, 会員番号: {member_number}")

        # 会員番号をJSON形式で返す(常に4桁の数字に変換してゼロパディング)
        response_data = {
            "memberNumber": str(member_number).zfill(4),
        }

        print("Content-type: application/json\n")
        print(json.dumps(response_data))  # JSON形式で出力
    except Exception as e:
        log.exception(str(e))
    finally:
        # データベース接続をクローズ
        db.disconnect()
else:
    # line_idが存在しない場合の処理(エラー処理など)
    log.warning("Invalid or missing Line ID received. No database operation performed.")

プログラム名:mysql_wrapper.py
import mysql.connector

class MySQLWrapper:
def __init__(self, host, user, password, database):
        self.host = host
        self.user = user
        self.password = password
        self.database = database
        self.connection = None

        def connect(self):
        try:
            self.connection = mysql.connector.connect(
                host=self.host,
                user=self.user,
                password=self.password,
            )
            if not self.database_exists():
                self.create_database()
            cursor = self.connection.cursor()
            cursor.execute(f"USE {self.database}")
            self.connection.commit()

        except mysql.connector.Error as err:
            raise Exception(f"Error connecting to MySQL: {err}")

        def disconnect(self):
        if self.connection:
            self.connection.close()

        def database_exists(self):
        cursor = self.connection.cursor()
        cursor.execute("SHOW DATABASES")
        databases = [row[0] for row in cursor.fetchall()]
        return self.database in databases

        def create_database(self):
        cursor = self.connection.cursor()
        cursor.execute(f"CREATE DATABASE {self.database}")
        self.connection.commit()

        def execute_query(self, query, params=None):
        cursor = self.connection.cursor(buffered=True)
        try:
            if params:
                cursor.execute(query, params)
            else:
                cursor.execute(query)

            # SELECTクエリの場合、実行結果を取得
            if query.upper().startswith('SELECT'):
                result = cursor.fetchall()
            else:
                result = None  # SELECT以外のクエリでは結果をNoneとする

            self.connection.commit()
            return result
        except mysql.connector.Error as err:
            self.connection.rollback()
            raise Exception(f"Error executing query: {err}")
        finally:
            cursor.close()

            def get_columns(self, table_name):
        query = f"SHOW COLUMNS FROM {table_name}"
        cursor = self.connection.cursor()
        cursor.execute(query)
        columns = [column[0] for column in cursor.fetchall()]
        cursor.close()
        return columns
    
        def update_column(self, table_name, column_name, new_value, condition_column, condition_value):
        try:
            query = f"UPDATE {table_name} SET {column_name} = %s WHERE {condition_column} = %s"
            self.execute_query(query, (new_value, condition_value))
        except Exception as e:
            raise Exception(f"Error updating {column_name} column: {e}")

プログラム名:config.py

# MySQLデータベースの接続情報

MYSQL_HOST = "   "
MYSQL_USER = "   "
MYSQL_PASSWORD = "   "
MYSQL_DATABASE = "   "
MYSQL_ACCESS = [MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE]

from mysql_wrapper import MySQLWrapper
wrapper = MySQLWrapper(
    host=MYSQL_HOST,
    user=MYSQL_USER,
    password=MYSQL_PASSWORD,
    database=MYSQL_DATABASE
)
wrapper.connect()
create_table_query = """
CREATE TABLE IF NOT EXISTS members (
    line_id VARCHAR(40) PRIMARY KEY, 
    member_number INT NOT NULL,
    display_name VARCHAR(255) NOT NULL
)
"""
wrapper.execute_query(create_table_query)

INFO_TABLE_USERS = {
    "columns": ["line_id", "member_number", "display_name"],
}
                                    
    上のサーバー側のプログラムはmysqlを使用するため、「mysql_wrapper.py」と「config.py」*1というプログラムを事前に準備しておきます。 参考文献の[1]におmysql_wrapper.pyはクラスを使用してmain.pyでデータの保存等で使う関数を事前に決めており、config.pyでmysqlを使用するための設定を書き込んでいます。詳しくは、参考文献の[3]と[4]に同じようなものが詳しくかかれているのでそちらをご参考にして下さい。 sample.htmlから送られてきたラインのIDをcgiを使用して受け取り、MySQLWrapperクラスのexecute_query関数を使用してラインIDがすでに登録しているか確認します。 新しいラインのIDだった場合は、execute_query関数を使用してテーブルの会員番号の最大値を変数に代入したものをプラス1してexecute_query関数を使用して保存してresponse_data変数に会員番号を入れて値をsample.htmlに返すようにしています。 既存のラインのIDだった場合は、登録した会員番号をmember_number変数に代入してresponse_data変数に会員番号を入れて値をsample.htmlに返すようにしています。   
    ---------------------------------------------------------------------------
    *1:mysql_wrapper.pyとconfig.pyのコードは、水野翔太さんに作成していただきました。
参考文献:
[1]https://jquery.keicode.com/basics/ajax.php
[2]https://developer.mozilla.org/ja/docs/Web/API/Node/textContent
[3]https://dev.classmethod.jp/articles/query-with-mysql-connector-python/
[4]https://github.com/slvher/pyMySQL-wrapper/blob/master/mysql_wrapper.py
[5]https://wa3.i-3-i.info/word12356.html