- 投稿日:2020-09-18T22:27:57+09:00
MySQLのバックアップの自動化
MySQLのバックアップを自動で取るために設定した手順です。
サーバはAWSです。
mkdir -pv /home/ec2-user/backup
バックアップ用のフォルダを作ります。
mkdir /home/ec2-user/cron
cron用のフォルダを作ります。
vim /home/ec2-user/cron/backup_mysql.sh
mysqldump を行うシェルスクリプトを作成し、下記の内容を書き込みます。
!/bin/sh
path='/home/ec2-user/backup'
db_name=("db_name_01")
date=`date +%y%m%d`
date_old=`date --date "7 days ago" +%y%m%d`
for i in ${db_name[@]}
do
mysqldump --user=XXX --password=YYY --skip-lock-tables --no-create-db --single-transaction --databases ${i} > ${path}/${i}_${date}.sql
chmod 700 ${path}/${i}_${date}.sql
rm -f ${path}/${i}_${date_old}.sql
done
chmod 700 /home/ec2-user/cron/backup_mysql.sh
DBのパスワードを記載しているため、パーミッションを絞る。
/home/ec2-user/cron/backup_mysql.sh
動作確認。
ls -l /home/ec2-user/backup/
成功していれば、このようなファイルができてます。
crontab -e
2 3 * * * /home/ec2-user/cron/backup_mysql.sh
「crontab -e」で毎日03:02に実行する。
mysql -u user -p db_name < /home/ec2-user/backup/file_name.sql
リストアが必要になった場合はこちらです。
- 投稿日:2020-09-18T20:04:34+09:00
MySQLとExcelの連携について
はじめに
会社でのデータ管理は、基本的にすべてExcel。
ファイルの管理や、バージョン管理が大変。
そのためWeb化&データベース化の推進をしていきたい。
Excel文化が根強いため、ひとまずExcelベースでデータベース化を目指す。
今回は標題のとおり、ExcelとMySQLとの連携を調査。と、言っている私もプログラミングは少しかじったレベルのため学習しつつチャレンジ。
調査環境
- Server version: 5.7.29 MySQL Community Server (GPL)
- Microsoft(R) Excel for Office 365 MSO (16.0.12527.20612) 32 ビット
MySQL for Excel
システム
Excelアドイン
概要
Excel上のアドイン機能からMySQLの読み込み編集が可能.
英語。機能紹介
レビュー
アドイン単体のインストールのため、導入は初心者でも楽
また、データベースの知識を伝えずに機能を使える。
しかし、VBAとの連携機能はなく(?)、汎用性・自動化は難しいか。導入方法
If you already have a MySQL server installed.
Visual Studio 2010 Tools for Office Runtime is not installed がないと言われているので,
Executeを押して不足しているRequirementをインストール。Click Next >
Requirements がInstall され準備ができたので、次のStepに進めるように。
ODBC
システム環境
ODBCドライバ ⇔ Excel
ODBCドライバ ⇔ VBA概要
Open Data Base Connectivity の略。
ODBCドライバを使うことで、Excel・VBAとデータの入出力をすることができる。機能紹介
ODBC 接続と読み込み(Excel)
接続設定
DSN: Data Source Name
Input Database Profile
Execute Test
テーブルの取り込み
ODBC DB操作1(VBA)
SQL_CONN
でユーザー情報とDB名を設定。SQL_Order
でSQL文を設定。SQL_Order
を実行する。
- CREATE、INSERTはSQL文がそのまま実行される。
- SELECTのときは取得したレコードをRecords(配列変数)に返すので、お好みで調理。
- SQL文ごとに
SQL_Order
を複製して使用することを想定。Sub SQL_Order() ' このプロシージャでSQL文を設定して、RunSQL(Function)に渡すと、 SQL文を実行する。' ' また、SQL文がSELECTの場合は、Records(配列)に結果を返す。' ' データベースの設定は、SQL_CONN(Function)で行う。' Dim SQL As String Dim Records As Variant ' SELECT' SQL = "SELECT * FROM usage_history" retVal = RunSQL(SQL, Records) End Sub '----------------------------------------------------------' Function RunSQL(ByVal SQL, ByRef Records) ' SQL処理のコントローラ。' ' Conn(DB接続) → Exec(SQL実行)' ' → GetDataArray(データを配列に取得) → Close(後処理)' On Error GoTo ErrorTrap Dim adoCon As Object ' ADOコネクション' Dim adoRs As Object ' ADOレコードセット' Dim i As Long ' Conn' retVal = SQL_CONN(adoCon, adoRs) ' Exec' retVal = SQL_EXEC(adoCon, adoRs, SQL) ' Get Data Array' If LCase(Left(SQL, 6)) = "select" Then ' SELECTのときだけ配列生成' ReDim Records(adoRs.RecordCount, adoRs.Fields.Count - 1) retVal = SQL_RECORD(adoRs, Records) ' Close' retVal = SQL_CLOSE(adoCon, adoRs) Else adoCon.Close: Set adoCon = Nothing End If Exit Function ErrorTrap: MsgBox (Err.Description) End Function '----------------------------------------------------------' Function SQL_CONN(ByRef adoCon, ByRef adoRs) On Error GoTo ErrorTrap ' ADOコネクションを作成' Set adoCon = CreateObject("ADODB.Connection") ' ODBC接続' adoCon.Open _ "DRIVER={MySQL ODBC 8.0 Unicode Driver};" & _ " SERVER=localhost;" & _ " PORT=3306;" & _ " DATABASE=admin_pc_usage;" & _ " STMT=SET NAMES sjis;" & _ " UID=root;" & _ " PWD=;" Exit Function ErrorTrap: Set adoRs = Nothing Set adoCon = Nothing MsgBox (Err.Description) End Function '----------------------------------------------------------' Function SQL_EXEC(ByRef adoCon, ByRef adoRs, ByVal SQL) 'adoRs.RecordCountを取得するため、カーソルの設定をクライアント&静的に変更。' ' SQLの実行' Set adoRs = CreateObject("ADODB.Recordset") adoRs.CursorLocation = adUseClient adoRs.Open _ Source:=SQL, _ ActiveConnection:=adoCon, _ CursorType:=adOpenStatic End Function '----------------------------------------------------------' Function SQL_RECORD(ByVal adoRs, ByRef Records) ' 配列を生成する。1行目に列タイトル、2行目~にレコード。' i = 0 Do Until adoRs.EOF For j = 0 To adoRs.Fields.Count - 1 If i = 0 Then: Records(i, j) = adoRs.Fields(j).Name Records(i + 1, j) = adoRs(j).Value Next j adoRs.MoveNext i = i + 1 Loop End Function '----------------------------------------------------------' Function SQL_CLOSE(ByVal adoCon, ByVal adoRs) On Error GoTo ErrorTrap3 ' 解放処理' adoRs.Close adoCon.Close Set adoRs = Nothing Set adoCon = Nothing ErrorTrap3: Set adoRs = Nothing Set adoCon = Nothing MsgBox (Err.Description) End Function '----------------------------------------------------------'ODBC DB操作2(VBA)
Create Table
Sub Order_CreateTable() ' このプロシージャでSQL文を設定して、RunSQL(Function)に渡すと、 SQL文を実行する。' ' また、SQL文がSELECTの場合は、Records(配列)に結果を返す。' ' データベースの設定は、SQL_CONN(Function)で行う。' Dim SQL As String Dim Records As Variant ' CREATE' DataCnt = Range("C6").End(xlDown).Row - 6 SQL = "Create Table " & Range("D4") & "(" '& vbCrLf' For r = 0 To DataCnt - 1 ' Col_Name' buf = "`" & Range("C" & r + 7) & "`" & " " SQL_BODY = SQL_BODY + buf ' TYPE' buf = Range("D" & r + 7) & " " SQL_BODY = SQL_BODY + buf ' NULL' If Range("E" & r + 7) = 1 Then: _ SQL_BODY = SQL_BODY + "NOT NULL " ' Auto_Increment' If Range("F" & r + 7) = 1 Then SQL_BODY = SQL_BODY + "AUTO_INCREMENT " mem_AI = Range("C" & r + 7) End If ' Comment' If Range("H" & r + 7) <> "" Then SQL_BODY = SQL_BODY + "COMMENT '" & Range("H" & r + 7) & "'" End If ' Next' SQL_BODY = SQL_BODY & "," '& vbCrLf' SQL_BODY = Replace(SQL_BODY, " ,", ",") Next r SQL = SQL & SQL_BODY If mem_AI <> "" Then: _ SQL = SQL & "PRIMARY KEY (`" & mem_AI & "` ))" retVal = RunSQL(SQL, Records) End SubInsert Record
Sub Order_InsertRecord() Dim SQL As String Dim Records As Variant ' INSERT' 'INSERT INTO admin_pc_usage.年休申請' '(申請者, 申請日, 種別, 備考, 登録日)' 'VALUES('', '', '', '', '');' SQL = "insert into " & Range("D6") & "(申請者, 申請日, 種別, 備考, 登録日) " & " values " buf = "('" & Range("D8") & "','" & Range("D9") & "','" & Range("D10") & "','" _ & Range("D11") & "','" & Now() & "');" SQL = SQL & buf retVal = RunSQL(SQL, Records) End SubSelect
Sub Order_SELECT() Dim SQL As String Dim Records As Variant ' SELECT' SQL = "SELECT * FROM " & Range("D4") retVal = RunSQL(SQL, Records) WriteRecords (Records) End SubUPDATE
Sub Order_getRecord() ' UPDATE するレコードを選択して取得' UserForm1.Show End SubPrivate Sub CommandButton2_Click() Dim lRow As Long, i As Long Dim ListNo As Long ListNo = ListBox1.ListIndex If ListNo <= 0 Then MsgBox "いずれかの行を選択してください" Exit Sub End If With Worksheets("UPDATE") .Range("D7") = ListBox1.List(ListNo, 0) .Range("D8") = ListBox1.List(ListNo, 1) .Range("D9") = ListBox1.List(ListNo, 2) .Range("D10") = ListBox1.List(ListNo, 3) .Range("D11") = ListBox1.List(ListNo, 4) End With Unload UserForm1 End Sub Private Sub CommandButton3_Click() Unload UserForm1 End Sub Private Sub UserForm_Initialize() Dim SQL As String Dim Records As Variant ' SELECT' SQL = "SELECT * FROM 年休申請" retVal = RunSQL(SQL, Records) Header = WorksheetFunction.Index(Records, 1) With ListBox1 .ColumnCount = 6 .ColumnWidths = "40;40;60;40;80;70" .List = Records End With ListBox1.List = Records End SubSub Order_UPDATE() Dim SQL As String Dim Records As Variant ' UPDATE' 'Update 年休申請' 'SET 申請者='', 申請日='', 種別='', 備考='', 登録日=''' 'WHERE `申請ID`=0;' SQL = "Update " & Range("D4") & _ " SET 申請者='" & Range("D8") & "', 申請日='" & Range("D9") & "', 種別='" & Range("D10") & _ "', 備考='" & Range("D11") & "', 登録日='" & Now() & "'" & _ " WHERE `申請ID`=" & Range("D7") & ";" retVal = RunSQL(SQL, Records) End Subレビュー
- 導入はインストーラを統一すれば簡単。
- Excel上で使用する場合は、ユーザーDSNの設定が必要だが、手順書でカバー可能なレベル。
- 開発は前者(MySQL for Excel) より難易度高いが、割と簡単な部類。
- 要VBA、SQL文基礎。ネット検索で方法がすぐ出てくるレベル。
- 機能性は、VBAでCRUD全てできるので充分。
- 運用面もVBAで処理できるため期待大。
ODBCのインストール
mysql-connector-odbc-8.0.21-win32.msi
Visual Studio 2019 x86 Redistributableがないとエラー
インストールする
https://aka.ms/vs/16/release/vc_redist.x86.exeODBCドライバのインストール確認(windowsが64bitの場合)
C:\Windows\SysWOW64\odbcad32.exe
のodbcad32.exeをダブルクリックで実行。- ドライバータブをクリックし「MySQL ODBC 8.0 Unicode Driver」があればインストールは成功。
「8.0」はバージョン番号のため環境により違いあり。
- 投稿日:2020-09-18T18:20:25+09:00
MySQL 全テーブル全レコード削除クエリ
select concat('delete from ', TABLE_NAME, ';') from ( select * from information_schema.tables where table_schema=database() ) as table1;
- 投稿日:2020-09-18T17:01:49+09:00
〇〇のセールを見逃さないサービス作りました。【AV半額オブザーバー】
はじめに
みなさんは、AVのセールを見逃して後悔した事はありませんか?
私にはあります。と言うことでAVの割引を見逃さない為のサービスを作りました。具体的に言うと、割引になってる作品を人気順の一覧で見れるページと、会員登録から『指定の作品が割引になったとき』のみ通知を送るページを通して、より便利な購入体験を提供します。
https://hao.japaneast.cloudapp.azure.com (18歳未満はアクセス禁止です)
出来ること
①割引作品を人気順に表示
これが一応メインコンテンツですね。紳士淑女のみなさんはFanzaから定期的に似たようなメールが届くかもしれませんが、メールをいちいち確認するのって面倒じゃないですか?
しかもキャンペーンページは一時的な物なのでブックマーク出来ません。なので割引作品の一覧を毎日更新し、人気順に表示するリストを作成しました。両サイドにはリストの前後40件の作品画像を背景として使用しています。一覧性という面ではかなり拘った仕様なので、使っていただけると幸いです。
②女優別で割引作品をリスト化
例によって黒塗りの多い画面ですが、女優さんの名前を登録すると出演作品が割引になった場合自動でリストに表示する事ができます。お気に入りの女優さんの作品がセールになってないか確認したい。そういう時にぜひご活用ください。
③マイリストを設定
URLを直接入力することで監視リストに動画を追加することができます。Fanzaにも似たような機能はあるのですが、ポチポチって押してるとすぐリストの件数が膨れ上がりがちなので、「これぞ!」と思うお気に入り動画の記録にオススメです。
開発の舞台裏
「個人開発だし、どうせフロントはJSで書くからNode.js使おう」
技術選定に関しては多分ここから始まったと思います。PHPという選択肢もあったのですが、今更感が強かったので今回は不採用。逆にフロントは詳しくなかったので資料が充実しているVue.jsを選びました。インフラはKeyVaultなる素敵機能を使ってみたかったのでAzureです。
(大正義KeyVault君、実際これが無ければ普通にAWS選んでた気がする)
大まかな開発の流れとしては
①設計
②バッチの実装
③バックエンドの実装(ルーティングとか主要なAPI)
④フロント(htmlやVue.js)の実装
⑤バックエンドの修正(フロントを実装した事でバグや考慮漏れが多数発覚)
⑥デザイン
⑦インフラ周りの設定とか
⑧リリースという感じだったような気がします。一応番号は振りましたが④⑤⑥に関しては「プロトタイプが欲しい」「今日はデザインの気分じゃない」「あれ実装してないじゃん」みたいな理由で結構反復横跳びしてますね。
まあ時系列順に振り返ると、設計に関しては結構グダりました。
というのも、今までオープン系のアプリ開発しかやったことがないし、ましてや設計なんて初めてなので、「DBから作ろうぜ!」って発想になる訳です。まあこれも依存の方向から考えると一定の合理性はあるのですが、そうなると問題なのが「アレこうした方が良くね(仕様変更)(仕様漏れ)」からの「DB設計直さなきゃ駄目じゃん……」からの「既存設計に引きずられて苦しい………(死)」となる黄金パターン。今回はどっぷり嵌りました。
ねー、WebバックエンドのDB設計なんて機能が増えれば当然カラム増えるのになんで最初にやったんだろね。
今から思えば裏側はモックに留めてフロントから仕様をガチッと固めてしまうのが最速な気がしますが、その反省は次回に活かすとします。
そしてある程度設計が固まってから実装に取り掛かったのが5月の頭ですね。
この段階ではフロントエンド全然わからんマンだったのと、バックエンドもいきなりWebサーバ書くのは脆弱性作りそうだったので消去法でバッチから作り始めました。(その結果、後で半分以上書き直しになった)特に大変だった所は無いと思いますが、JSをガッツリ書くのは何気に初めてだったので、バッチ処理全体を書くのに丸々1か月も掛けてしまいました。Promiseが全然分からなくて死んでたり、コードのどこが非同期処理で動いてるのか今一掴めずに「forが動かねぇ……」って絶望してたのも今となっては懐かしい思い出です。
6月は丸々ルーティングとHTML、ログイン認証の実装に使いました。
後述しますが、特にログイン機能の実装は結構大変でした。まず認証が通らなかったり、逆にどんな値でも通ったり、通ってもセッションを保持できなかったりとてんやわんやです。SNS認証とか実装しなくて本当に良かった7月はようやく機能とデザインの実装です。
CRUDとかバックエンドの側の実装は6月から並行でちょいちょい着手してたのですが、いざ動かしてみたら変な所で止まったりバグッたりと散々だったので、実質書き直しになりましたね。デザインに関してはTailwindCSSを使って見たのですが、今思えば教養として素のCSSを書いたほうが良かったかもしれません。TailwindCSS、作る時は便利なんですがHTMLが汚くなるし、
sm:invisible
するとSafariで表示崩れるし、運用フェーズだと都度都度ビルド要るしでなかなか面倒。
8月前半は細かいデバッグや表示の追加をメインに行いました。レスポンシブの対応とかもそうですが実際使ってみると思ったより使い勝手微妙だなみたいな感じになったので位置を修正したり、ツールチップを実装したりしました。表示に関してはあまりゴテゴテするのも良くないかもとか思ったけど、余白を生かしたデザインとかアレはアレで高度芸能(素人は手を出さない方が無難)なので、今回は出来るだけ詰め込んでみました。次回作はもっとゴリゴリに表示を動かしたいを思います。8月後半は予定を繰り上げてAzure上にVMを立ち上げ、テスト環境を構築。
まあ私のTwitter見てる方は既にご存知かもしれませんが、詰まりに詰まり散らかしました。うん、何というか3ヶ月半掛けて作った動くはずのアプリが突然動かなくなったら焦るよね。
まあ原因の9割はmysqlのせいだったんですけど、テーブル名にキャメルケースを使ってはいけない。大事な事なのでもう一回言うけど、テーブル名にキャメルケースを使ってはいけない。Do you understand?
データベースオブジェクトの命名規約9月はほぼ消化試合でしたね。本番移行って言っても丸々同じ手順を繰り返すだけですし、まあ強いて言えば、ここに来てダダ下がりしたモチベの維持が大変でした。リアルでも色々忙しかったのもあり、ここの工程はボリューム的には正味一週間ぐらいで終わりそうな感じだったのですが、何やかんやで丸1か月掛かりました。
ちなみにAzureのVMやKeyVaultで幾らか詰まると思ったのですが、その辺りは意外にも公式ドキュメントだけですんなり構築できました。流石マイクロソフト謹製のPaaSですね。ネタに出来なくて残念です
出来なかった事
・TypeScript
・Nuxt.jsとかのナウいフレームワーク
・綺麗なコード
・自動テストとか
まあ今回の1件でJavaScriptとSQLさえ書ければ全てが解決するという事を証明してしまったので、学習意欲はわりかし低めです。下2つは時間があったら極めたいなと思いつつ「時間があったら」などと逃げ口上かましてる時点でやる気無いんだよなぁ みつお
それにTypeScriptは記法の癖が強いし、Nuxt.jsも何というか色々勝手にやってくれる代わりに覚えること多いしで、個人的にあんまり好きじゃないです。あときれいなコードは目指したかったんだけど、それが開発速度を担保してくれるかって言うとそうでもないので、動いて読めてそこそこ整理されてればいいかって感じに妥協しました。
一つ反省点を上げるとすれば、自動テストを導入するかどうかをさておいても、テスタブルな実装は目指すべきだったなと思います。具体的な所で言うと、APサーバの実装で内部の処理とレスポンスの返却を同じメソッドに書いてしまった結果、単体でテスト出来ないコードが生まれたりって感じですね。インターフェースいちいち考えるの面倒くさかったとは言え、最後のテストが地味に大変だったので、ここはもうちょっと拘っても良かった気がします。
頑張った点とか
①自動ログイン
これは今日日どんなサイトでも付いてる機能なのでどうにか実装したかったのですが、結構手間取りました。というのもnode-modulesにあるpassport君は自動ログインをサポートしてないので、認証の発火とCookieへの読み書き、ハッシュ化したトークンの生成や管理は自前で実装する必要があります。
(あれ?これもう自分でミドルウェア実装したほうが早くない?)まあ最終的にランダムかつ一意なハッシュを定期的にセットし、passport経由で認証を通すことに成功しました。まあメッセージ返す部分は結局解決できなくて、ログイン認証の前に「ログイン認証のメッセージ」を返すAPIを叩くことで解決しました。悔しい……
②女優検索DBはmysqlなのですが、流石にLike検索で返すのは遅い&不便なので、ngramによるフルテキストキーを貼ってあいまい検索を実装しています。細かい調整とかは出来なかったのですが、まあまあ実用範囲かなと、本当はサジェストとかやりたかったんですが、それは次回の宿題ですね。
③ツールチップ
これはログイン先にあるマイページでの演出なのですが、URLや縮小画像にマウスカーソルを持っていくと近傍にオリジナルのパッケージ写真を表示するようになっています。実装としてはシンプルにJSでイベントを発火してdiv要素の内容とCSSを都度都度DOMで書き換える感じです。ただ何故かz-indexが適用されず、悩みに悩んで一日詰まった結果、div要素の位置を5回ぐらい変更してようやく適応される位置を見つけることが出来ました。
④reCAPTCHA
このサービス、実はそこそこセキュリティにも力を入れていて、ログイン画面にはDDoSにも対応したBOTの検出機能を実装したりしています。ただ登録画面に関しては人とBOTを区別するのは難しいのでreCAPTCHAを使用しました。セキュリティ上バックにも実装が必要って言うのは分かりますが、フロント側で一度作り込んだJSを書き換えるのは結構ヒヤヒヤしました。登録時に実際のURLが必要ですが、実装するなら最初からテンプレートを組み込んでコメントアウトしておいた方が良いです。
ちなみにバックの実装はこんな感じの関数を書いて適宜叩けばOKなので意外と楽でしたね。reCAPTCHAfunction reCaptcha(token){ if(!token){ console.log("Error token is null"); return false; } let options = { url: 'https://www.google.com/recaptcha/api/siteverify', method: 'POST', form: { secret:/*シークレット*/, response:token }, json: true } request(options, (error, response, body) => { if (error) { console.log(error); return false; } // 閾値により判定する if (body.score < 0.5) { return false; }else{ return true; } }); }
⑤Slackの活用
通知の送信用として使用しているG Suiteには2000通/日の送信制限があります。まあ制限にかかることは無いと思いますが、容量を無駄に削るのも嫌なので、自分宛ての通知(お問い合わせとか鯖の情報とか)はバックエンドからSlackAPIを経由して送信するようにしています。これが中々便利で、メールと違ってスマホにプッシュ通知が出るので通知を見逃すという事もありません。これから個人開発を始めようという方はぜひ使ってみて下さい。
詰まった所とか
①Promise
最初はメソッドチェーンとか面倒臭いなって感想しか出てこなかったんですが、短い関数でちゃっちゃっとPromise返したら、async/awaitで普通に同期実行できると知ってすぐに手のひらクルーしました。
便利ですねこれ。………………え?まだコールバック関数とか使ってる人居るんですか?
Promise自体の説明に関してはやめ太郎さんの書かれた記事が日本一分かりやすいので是非どうぞ
4歳娘「パパ、Promiseやasync/awaitって何?」〜Promise編〜
②passport前述の通りログイン関係でPassportを使っているのですが、ログインした後セッションを有効化出来ないという現象が発生しました。具体的にはユーザー情報が入ってるはずのreq.userが何故かundefinedのまま……………
まあ結論から言えば犯人はAxios君でしたね。axios.defaults.withCredentials = true;を適当な所に書いてあげれば、認証情報を含むCookieをサーバに送信できるようになります。
③Cheerioスクレイピングと言えばCheerio君。ただコイツには結構苦労させられました。結論から言えば近傍5つ以内にClassが振ってない要素を取得するには新卒Sierを秒で退職してMicrosoftに就職を成功させるぐらいの豪運を必要とします。実質砂金掘りですね。
必要な要素が取れないときは闇雲に試行回数を増やすよりも、別のページから同じ情報が取れないか探したり、PCの電源ケーブルを抜き差ししたり、懐かしのゲームを楽しむのが肝要です。実際私も自棄になり、開発をほったらかして一日中エスコン6をプレイしていたら何故か要素が取れない問題が解決しました。
あと
each
で要素の取得をぶん回すとき、attr("href")
は先にfind("a")
を指定しないとちゃんと拾ってくれないので気を付けましょう。
④Vue.jsまあこれに関しては後で別の記事に起こそうと思いますが、特に苦労した部分だけでも紹介したいと思います。
・v-modelの変更を監視してイベントを発火したい。まあ誰しも一度はやると思いますが、
<input v-model="hoge" @change="huga()">
みたいな感じのやつです。
ちなみに↑のコードはv-model
と@change
でイベントリスナーが二重になってるので@change
は動きません。
一応公式ではv-modelをv-bindに書き換える方法が推奨ですが、v-bindだと何故かバインド出来ない現象が発生したので別の対策を講じます。(←今思えば普通にタイポだった可能性ある)対策、new Vue()の時にmodelのリスナーを追加する
let vue = new Vue({ el:'#app', data:{}, watch:{/*監視したいv-modelの名前*/:function(){ //変更があった時に走らせたい処理を記述 }} });
・new Vue()の中からMethodsのメソッドを呼びたい。当然ですが
this
が必要です。この例に限らず、Vueでundefined
って怒られた時はまずthis
の付け忘れを疑いましょう。
⑤LinuxこれLinux(Ubuntu)君は全然悪くないけど、環境移行する時に結構詰まったので覚え書きを残しておきます。
まずアプリを雑にインストールするとWindowsとバージョンが違うという事を肝に命じておくべきだと思う。Node.jsとか結構顕著で、何と12.18.3 LTSだと
try{hoge()}catch{/*何もしない*/}
みたいな変な文法のコードを書いても普通に動いたりする。動くなやあと一度やった変更はメモったほうが良いですね。ミスした時もリカバリが効きやすいのと、環境立て直す時「何かCronの動きがおかしい」→「時刻設定がグリニッジ標準時でした」とかやりがちなので対策したほうがベターです。
⑥mysqlリピートアフターミー、テーブル名にキャメルケースを使ってはいけない。
何故かって言うと雑にmysqlをインストールした時、WindowsとLinuxでバージョンが違うから。8.0(win)だと
CREATE TABLE
の時に名前を全部小文字にしやがるしてくれるけど、5.7(Lin)だと普通に通る。なので突然テーブルが見つかりませんとか言われて死にます。死にました。あとLinux版は権限周りが固いっぽいので、雑にパスワードを設定すると
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
で怒られます。良い子のみんなはRSA2048に記号挟んだパスワードをぶち込んでいこうな!
まとめ
作り終わった今だから思うのですが、半分以上無駄な機能を作ってしまった気がします。作った当初はDMMにお気に入り機能があると知らなかったのでガッツリ作り込む気満々でしたが、ただ単に「半額〇〇の一覧をブックマークできるサイト」というコンセプトならログイン機能とか付けずにトップページだけ作って2~3ヶ月でリリースするのが最適解でした。
まあそのおかげでCRUDの実装とかも出来ましたし、Linux触ったり、SQLをガッツリ書く経験も積めたので結果オーライだと思います。これで私も真にフルスタックエンジニアの仲間入りですね。
まあそんな与太は置いておくとしても、やはり個人開発に関して大切なのは『モチベの維持』と『まあいいかの精神』だと思います。というのも納期が無い開発で仕様なんて固まってる訳ないですし、設計はグダグダ、人手は足りてないといった具合に個人開発はおおよそ炎上or凍結プロジェクトの前提条件を満たした状態でスタートします。今回モチベの維持としては主にGitHubを利用しました。定期的にPushしないと芝が枯れるという危機感は進捗にかなり貢献してくれますね。後はまあ今のバイト先が普通にブラックなのでそこから脱出したいという思いも地味にあったりします。
『まあいいかの精神』と言うのは、一定以上のクオリティを求めない事ですね。今回実はパフォーマンスに関しては結構ザルでして、VMのスペックに関しては同接100~200ぐらいしか見込んでないですし、アプリ自体もDBにインデックス張ったぐらいで計算量の最適化はやってなかったりと中々お粗末な仕上がりです。
ただ、同人界隈でも「出ない神本より出る糞本」という言葉がありますし、「それでも要件は満たすから良いだろ」ぐらいの割り切りが肝要だと自分は思います。それに今回アプリを作ることで、間違いなく自分の実力は伸びました。実を言うと9月にモチベが枯れかけた理由も半分以上それで、急に実力が上がったせいか「今更これを公開するのか?」みたいな躊躇いと気恥ずかしさを乗り越える必要がありました。ここだけの秘密ですよ。
あとここまで辿り着けた要因の一つとして、Twitterで進捗を呟く度に「いいね」を下さるファボ魔(約2名)の存在も大きかったと思います。多分アレが無ければ8月ぐらいで折れていた疑惑ある。大変感謝です。
それとこれは余談なのですが、次回作については既に設計が上がっています。詳しいコンセプトはまだ秘密ですが、今度こそきっと皆様の度肝を抜くサービスをお届け出来ると思います。それではまた次回、goodbye
- 投稿日:2020-09-18T15:50:29+09:00
TypeORM x AuroraDB(MySQL), transaction の正しい扱い方
TypeORM x AuroraDB(MySQL)
TypeORM つかってて、以下のように怒られる人むけの研究記録。
AuroraDB(MySQL)限定の話かどうかはわかりません。
Query runner already released. Cannot run queries anymore.
MissingRequiredParameter: Missing required key 'transactionId' in params
Transaction <base64string> is not found
環境
- typeorm 0.2.26
- typeorm-aurora-data-driver 1.4.0
この環境におけるTransactionの経験則
経験則1: (当たり前だけど)閉じたtransactionは利用できない
let tx:any; await getConnection().transaction(async tx => { await tx.save(MyEntity, entity); }); // transaction() が返った時点で commit され、txは閉じている // Error: "Query runner already released. Cannot run queries anymore." await tx.save(MyEntity, entity);経験則2: 暗黙の transaction が存在する
TypeORMにおける単純なEntityのsaveを考える。
const entity1 = new MyEntity(); const entity2 = new MyEntity(); await entity1.save(); await entity2.save();一見、トランザクションなど存在しないが、
aurora-data-api-driver
内部にログを仕込んでみると以下のようになる。TX START { transactionId: 'bnOf7HfepOTZplVAduk=' } TX QUERY { transactionId: 'bnOf7HfepOTZplVAduk=', query: 'INSERT INTO ...' } TX QUERY { transactionId: 'bnOf7HfepOTZplVAduk=', query: 'SELECT `MyEntity` ...' } TX COMMIT { transactionId: 'bnOf7HfepOTZplVAduk=' } TX START { transactionId: 'KCdxQMJb1xyWXZMcbYI=' } TX QUERY { transactionId: 'KCdxQMJb1xyWXZMcbYI=', query: 'INSERT INTO ...' } TX QUERY { transactionId: 'KCdxQMJb1xyWXZMcbYI=', query: 'SELECT `MyEntity` ...' } TX COMMIT { transactionId: 'KCdxQMJb1xyWXZMcbYI=' }実はtransactionが暗黙的に生成されてた。
具体的にどの処理で、までは調べてない。
Write系は基本transactionと理解しておけば事故が減ると思う。ほとんどは暗黙のtransaction + 経験則3 が複合して怒られているエラー。
経験則3: transactionの並列や入れ子はエラーの原因となる
厳密なパターンまで言語化できていないが、
以下のような場合に(きっと処理の順番やタイミング次第で)エラーになることがある。
MissingRequiredParameter: Missing required key 'transactionId' in params
Transaction <base64string> is not found
のエラーが出た場合、経験則3を疑う。
- 並列の例
// ❌ だめな(ことがある)例 await Promise.all([ entity1.save(), // 暗黙的にtx1が生成 entity2.save(), // 暗黙的にtx2が生成 ]); // tx1とtx2が並列に走る // ✅ うまくいく例 await getConnection().transaction(async (tx) => { await Promise.all([ tx.save(MyEntity, entity1), tx.save(MyEntity, entity2), ]); // 単一txで並列に処理するならok });
- 入れ子の例
// ❌ だめな(ことがある)例 await getConnection().transaction(async (tx1) => { // tx1 が生成 await tx1.save(MyEntity, entity1); // 例えばこの中で await entity2.save(); が行われていると // 暗黙的に tx2 が生成 await something(); }); // ✅ うまくいく例1 await getConnection().transaction(async (tx) => { await tx.save(MyEntity, entity1); // 例えばこんなふうに、単一txで処理されるよう工夫する const entity2 = something(); await tx.save(MyEntity, entity2); }); // ✅ うまくいく例2 await getConnection().transaction(async (tx) => { await tx.save(MyEntity, entity1); }); await something(); // 例えばこんなふうに、txが入れ子にならないよう、外に出す経験則4: @Transaction() はtransactionを引き継げない
@Transaction() でデコることによって、functionをtransactionalにできる。
例えばこんな感じだね@Transaction() async function processA(@TransactionManager tx: EntityManager) { const entity1 = new MyEntity(); await tx.save(entity1); } @Transaction() async function processB(@TransactionManager tx: EntityManager) { const entity2 = new MyEntity(); await tx.save(entity2); } // 実行時、勝手にtxが注入され、それぞれ transaction として動作する await processA(); await processB();ここで、processAとprocessBを同一transaction内で行うにはどうしたらよいか?
答え: できない (できるなら教えて!!)// ❌ できそうだけどできない例 getConnection().transaction(async tx1 => { // みんなtx1を使って処理してね〜 await processA(tx1); // 問答無用でtx2が生成される await processB(tx1); // 問答無用でtx3が生成される });まとめ
- 本環境における TypeORM の write 処理は基本的に transaction である。
- transactionを扱うときには、他のtransactionは閉じていなければならない。
- @Transaction()は思った通りtransactionを引き継げない。
と理解して実装しておけば、エラーの発生や意図しない挙動は起きなくなるはずだ。
- 投稿日:2020-09-18T15:08:40+09:00
【Laravel】クエリ実行前にMySQLのDBコネクションのPDOにsetAttributeする。
メモとして残します。
■やり方
下記の例では現在のDBコネクションに
PDO::ATTR_EMULATE_PREPARES
をtrueでセットしています。(\DB::connection()->getPdo())->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);■余談
laravelでは
PDO::ATTR_EMULATE_PREPARES
はデフォルトFalseですが、
LOAD DATA LOCAL INFILE
ステートメントを実行するときにエラーになってしまうため、PDO::ATTR_EMULATE_PREPARESをTrueにしてやる必要があります。
しかし、configのdatabase.phpで常にtrueにするのはさすがにちょっと、、、
と、なったなっため、瞬間的にTrueにすることで解決しました。
- 投稿日:2020-09-18T15:00:40+09:00
【MySQL】「LOAD DATA LOCAL INFILE」ステートメントを用いてCSVファイル等からデータをインポート
自分用のメモです。
■やり方
下記の例では取り込むファイルは郵便局マスタ「x-ken-all.csv」
文字コードはSJISで、セパレータがカンマ(,)フィールドのデリミタがダブルクォート(")
です。SET character_set_database = sjis; LOAD DATA LOCAL INFILE 'D:/hoge/x-ken-all.csv' INTO TABLE zipcodes FIELDS TERMINATED BY ',' ENCLOSED BY '"' ( areacode, @nonimportcolumn, zipcode, pref_name_kana, city_name_kana, div_name_kana, pref_name, city_name, div_name, @nonimportcolumn, @nonimportcolumn, @nonimportcolumn, @nonimportcolumn, @nonimportcolumn, @nonimportcolumn );
- 投稿日:2020-09-18T00:16:31+09:00
dumpされた改行入りのCSVファイルをtableにインポートした話
概要
またまた業務を進めるにあたって、MYSQLコマンドで程々にいい感じの手順をまとめておきたくなったので
メモがてら更新。mysqlコマンドでインポート
CSVファイルをそのまんまテーブルに突っ込みたくなったときのやつ。
LOAD DATA LOCAL INFILE TBL.txt INTO TABLE (テーブル名) FIELDS TERMINATED BY ',';mysqlでテーブル内の改行を再置換
これはたまに使う。文字列を一斉置換するやつ。
UPDATE (対象のテーブル名) SET (対象のカラム名)=REPLACE((対象のカラム名), "[[br]]", "\n") WHERE (WHERE句の条件);参考記事