OmniAICreator/Qiita-1.07M huggingface dataset:
TkinterでLog出力用windowを作ってみました

TkinterでWindowsでGUIを作ると、リアルタイムのprintデバッグが難しいため
メインウィンドウのほかにLog Windowを立ち上げた方が便利だと思い、作ってみました。
あくまでもサブウィンドウなのでToplevelでのWindowを立ち上げます。
(TkinterのToplevel windowsの使いかをちゃんと理解できていません)
ただのLog Windowでは面白くないので、Log種類によって表示/非表示ができるようなチェックボックスを入れてみました。
![LogWindow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506495/acc9ef62-6f6d-a7ee-827a-82705c4f2833.png)

追記: 出力したが、出力に追随してスクロールされなかったので view('end')を追加。
pythonの標準出力に対応するため write(str), flush()を追加

sys.stdout = Logwindow として使えます。



```python:IOLogWindow.py
import sys
import serial
import binascii
import time
import tkinter as tk
from tkinter import scrolledtext

TEXT_COLORS = {
    'MESSAGE' : 'black',
    'INPUT' : 'blue',
    'OUTPUT' : 'green',
    'ERROR' : 'red',
    'DEBUG' : 'yellow'    
    }

# Initial value = flag=True or False
class SimpleCheck(tk.Checkbutton):
    def __init__(self, parent, *args, **kw):
        self.flag = kw.pop('flag')
        self.var =  tk.BooleanVar()
        if self.flag:
            self.var.set(True)
        self.txt = kw["text"]
        tk.Checkbutton.__init__(self, parent, *args, **kw, variable=self.var)

    def get(self):
        return self.var.get()

class IOLogFrame(tk.Frame):
    def __init__(self, master):    
        tk.Frame.__init__(self, master)
        master.title("Log Window")

        #view/hide choice
        select_frame = tk.LabelFrame(master, text= "Log text disable",relief = 'groove')

        self.ckboxs = []
        for key in TEXT_COLORS:
            cb = SimpleCheck(select_frame, text=key, command=self.callback, flag=False)
            self.ckboxs.append(cb)
            cb.pack(side='left')
        select_frame.pack(side = 'top', fill = 'x')
        
        self.txt = scrolledtext.ScrolledText(master)
        self.txt.pack(fill=tk.BOTH, expand=1)
        for key in TEXT_COLORS:
            self.txt.tag_config(key, foreground=TEXT_COLORS[key])

    def callback(self):
        count = 0
        for key in TEXT_COLORS:
            if(self.ckboxs[count].get()):
                self.hide(key)
            else:
                self.view(key)
            count += 1

    def print(self, str, state='MESSAGE'):
        self.txt.insert(tk.END, str+'\n', state)
        self.txt.see(tk.END)

    def hide(self, tag):
        self.txt.tag_config(tag, elide=True)

    def view(self, tag):
        self.txt.tag_config(tag, elide=False)

    def write(self, str, state='MESSAGE'):
        self.txt.insert(tk.END, str+'\n', state)
        self.txt.see(tk.END)

    def flush(self):
        pass

#sample main window
class IOLogWindow(tk.Toplevel):
    def __init__(self, master):
        master.title("Main WIndow")
        tk.Toplevel.__init__(self, master)
        io = IOLogFrame(self)
        io.print("Message")
        io.print("--ERROR--", 'ERROR')
        io.print("--INPUT--", 'INPUT')
        io.print("--OUTPUT--", 'OUTPUT')
        io.print("--DEBUG--", 'DEBUG')    
        
if __name__ == '__main__':
    win = tk.Tk()
    io=IOLogWindow(win)
    win.mainloop()
MySQLで You can't specify target table 'xxxx' for update in FROM clause を回避する方法

#問題が発生...
select文の結果をwhere区に使おうとすると

```
You can't specify target table 'syainTbl' for update in FROM clause 
```

というエラーが発生。元々のSQL文は以下

```sql
update
    syainTbl
set 
    status = 999
where
    user_id in
    (select
        user_id
     from
     syainTbl where status = 2);
```

syainTblというテーブルのstatusというカラムの値が2のレコードのstatusを999に変更したい、
という文である。

#原因
どうやらMySQLでは同一テーブルのサブクエリからのUPDATE文はエラーが発生する模様。
上記の文では、```where区内のサブクエリにsyainTblを使用しているため、syainTblのカラムはupdateできない```
ということらしい。

#解決方法
以下の通り、select文でラップしてaliasを付けてあげるとうまくいく。

```sql
update
    syainTbl
set 
    status = 999
where
    user_id in
    (select
        user_id
     from
     (select user_id from syainTbl where status = 2)tmp);
```

where区の副問合せに直接テーブル名が指定されていなかったらOK、ってことなんでしょうか。
JavaScript 配列操作

### 配列操作

```javascript

// ! 配列の各要素を順番に取り出すには、以下のように記述します。
const fruits = ["apple", "orange", "banana"];

// * for文
for(let i = 0; i<fruits.length; i++){
	console.log(fruits[i])
}

// * forEach
fruits.forEach(function(fruit, index){
	console.log(index, fruit)
})

// ! 配列の要素を追加する
var array = ['b', 'c', 'd'];

// * 配列の最初にひとつ以上の要素を追加
array.unshift('a');
console.log(array)

// * 配列の末尾にひとつ以上の要素を追加
array.push('e')
console.log(array)

// *既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更
array.splice(1, 0, 'A');
console.log(array)

// ! 配列の要素を削除する
var array = ['a', 'b', 'c'];

// * 最初の要素を取り除く
array.shift();
console.log(array)

// *配列から最後の要素を取り除きその要素を返す
array.pop();
console.log(array)

// * splice
array.splice(1, 1);
console.log(array)

// *配列の全要素を削除する
array.length = 0;
console.log(array)

// ! 配列の要素を置換する
array.splice(1, 1, 'Z');
console.log(array)

// ! 配列の要素を結合
var a = [1, 2, 3, 4, 5];
a.join(); // '1,2,3'
a.join(', '); // '1, 2, 3'
a.join(' + '); // '1 + 2 + 3'
a.join('') // '123'

// ! 配列の検索
var array = [2, 9, 9];
array.indexOf(2); //0
array.indexOf(7); //-1 見つからない
array.indexOf(9);     // 1 見つかった時点で検索を終了してしまう

// ! 配列を連結する
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];

// * concat 返り血に新しい配列を
console.log(array1.concat(array2)); // ["a", "b", "c", "d", "e", "f"]

// * 複数の配列を連結
const num1 = [1, 2, 3];
const num2 = [4, 5, 6];
const num3 = [7, 8, 9];
const numbers = num1.concat(num2, num3);
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// ! 配列を並び替える
const months = ['March', 'Jan', 'Feb', 'Dec'];
// * sort 配列は各要素の文字列比較に基づき辞書順にソート
months.sort();
console.log(months); // ["Dec", "Feb", "Jan", "March"]

const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1); // [1, 100000, 21, 30, 4] 頭文字の数字の若いものから並んでしまう

// *数値を大小比較
const array1 = [1, 30, 4, 21, 100000];
array1.sort((a, b) => (a - b));
console.log(array1)// [1, 4, 21, 30, 100000]

// *元の配列を破壊したくない場合は、配列の複製を行ってから並び替え
const arr1 = ['a', 'b', 1, 3, 'c', 4, 2];
const arr2 = arr1.slice().sort();

console.log(arr1); // ["a", "b", 1, 3, "c", 4, 2];
console.log(arr2); // [1, 2, 3, 4, "a", "b", "c"]
```
nuxt plugins では process.client は undefined なので注意（axios のクライアント側エラー処理を書く時のハマりどころ）

# これは何

axios のクライアント側エラー処理を書く時にはまったところについて共有のための記事です。
`process.client`は使えないので、そもそもクライアント用とサーバ用でファイルを分けようという話です。 

# バージョン情報

```json
{
    "nuxt": "^2.11.0",
    "@nuxt/typescript-build": "^0.5.2",
    "@nuxt/typescript-runtime": "^0.3.3",
    "@nuxtjs/axios": "^5.9.5"
}
```

# トラブルについて

クライアントサイドでその処理が実行されるか、というのを制御できる`process.client`プロパティがnuxtアプリケーションではよく使われます。

plugins読み込みの時にはこのプロパティが設定されていないので`undefined`になってしまいます。

```ts:plugins/plugin.ts
if (process.client) {
  // 何か処理
}
```
と書くと、一見動くように見えるのですが、実際には`process.client`が`undefined`になるのでifの中の処理は常に実行されませんでした。

具体的に起こった問題は以下のようなもの

* サーバ側処理もクライアント側処理も同じプラグインファイルに書いていた
* クライアント側の処理をして問題なければ`return`して処理おわり、その後にサーバサイドの処理をして問題あれば`nuxt error`を吐くみたいな処理を書いていた
* そもそもifに入っていないので`return`で処理が止まらず、クライアント側のクライアントエラー（4xx）でも`nuxt error`を吐いて死んでしまう

# 解決策
clientとserverでそれぞれ実行する処理をファイル名を分けて、それぞれnuxt設定ファイルで読み込みます。

```ts:nuxt.config.ts
  plugins: [
    '@/plugins/axios.client.ts',
    '@/plugins/axios.server.ts',
  ],
```

他にも`mode`を書く方法もあるようですが、ファイル名だけをみた時にどちらで実行されるのか？というのがはっきり分かるので、ファイル名で分けたほうが個人的にはいいかなと思います。

# 補足

`process.client` は `undefined` になってしまうのですが、`process` オブジェクト自体は定義されているようでした。
これはnuxtがnodejsのインターフェースの拡張しているもののようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144064/e47e09cf-75c0-ff00-d85f-0064232c0883.png)

plugin のなかで `console.log(process);`すると以下のようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144064/21d94e64-ca9b-9d2c-3626-317b9cc09b6b.png)


`.browser` を使えばフロントかサーバかの判定はできるようですが、すでにnuxtでは非推奨になっていました。

# 参考
https://ja.nuxtjs.org/api/configuration-plugins
https://ja.nuxtjs.org/guide/plugins/
https://stackoverflow.com/questions/58146662/process-server-is-undefined-in-nuxt-js-modules
dockerでnpx @angular/cli newしてエラーが出る時の対処法

# 問題
npxを使うと@angular/cliをグローバルインストールせずにプロジェクトを作成できます。

```
npx @angular/cli new foo
```

しかしdockerで同じコマンドを実行するとエラーになります。

```
internal/modules/cjs/loader.js:1033
  throw err;
  ^

Error: Cannot find module '/root/.npm/_npx/800/lib/node_modules/@angular/cli/bin/postinstall/script.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:1030:15)
    at Function.Module._load (internal/modules/cjs/loader.js:899:27)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @angular/cli@10.0.2 postinstall: `node ./bin/postinstall/script.js`
npm ERR! Exit status 1
```

# 回避策
どうもrootユーザーなのが問題なようです。
--unsafe-permを指定したいところですがnpxには--unsafe-permオプションが無さそうなので環境変数で渡します。

```
npm_config_unsafe_perm=true npx @angular/cli new foo
```

これで無事にプロジェクトを作成できました。
【メモ】SSH設定+多段SSHを一発で行う

多段SSHを行う際に、何度もSSHを実行するのは面倒なので、一発で行きたいサーバにSSHする方法をメモ

こちらの記事が大変参考になりました。
ありがとうございます。

- [多段SSHやポートフォワーディングを '.ssh/config' に書く](https://qiita.com/lasta/items/41e95a2fdded18c34dae)

- [お前らのSSH Keysの作り方は間違っている](https://qiita.com/suthio/items/2760e4cff0e185fe2db9)
#SSH設定
以下を実行して既存のSSH Keysを確認

```sh
$ ls -al ~/.ssh/
drwx------@ .               # ~/.ssh のパーミッションにも注意
drwxr-xr-x+ ..
-rw-r--r--  config          # SSH の設定ファイル
-rw-r--r--  known_hosts
-rw-------  authorized_keys # 公開鍵置き場
-rw-------  id_rsa          # 秘密鍵
-rw-r--r--  id_rsa.pub      # 公開鍵
```
###パーミッションを設定
パーミッションが700じゃないと怒られる

```sh
$ chmod 700 ~/.ssh
```

#公開鍵の設定
SSHには、パスワード認証方式と公開鍵認証方式があるが、公開鍵認証方式の方が安全らしい
###1.公開鍵と秘密鍵を作成
- ローカル（クライアント）側で`ssh-keygen`コマンドで鍵のペアを作成

```sh
$ ssh-keygen -t rsa -C "comment "
```
`-t`: 鍵の暗号化を指定（デフォルト：「rsa」）
`-C`: コメントの追加（Githubに登録してあるEmailアドレスを入れるのが一般的らしい）
その他のオプションは[こちらを参考](https://www.atmarkit.co.jp/ait/articles/1908/02/news015.html)

すると、SSH Keysの保存先とパスフレーズの入力を求められる

``` sh                                   
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/lasta/.ssh/id_rsa): <Press Enter>
# 生成される鍵の保存先の指定(任意)
Enter passphrase (empty for no passphrase):              <Type A Passphrase> 
# 鍵へのパスワード(任意)
Enter same passphrase again:
```
※パスフレーズに関してはGitHubが出している[こちらを参考](https://www.atmarkit.co.jp/ait/articles/1908/02/news015.html)

- サーバ(ホスト)側へ公開鍵(id_rsa.pub)を転送

```sh
$ scp ~/.ssh/id_rsa.pub user@host:~/
```
- ホスト側で公開鍵を登録

```sh
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ cat id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
```

※Linux環境では、公開鍵の転送とホスト側での登録は以下のコマンドで１行で簡潔できる

```sh
$ ssh-copy-id -i ~/.ssh/id_rsa.pub user@host
```
`-i`は`Idenntityfile`を指定するオプションで、デフォルトが`~/.ssh/id_rsa.pub`なので省略可

- SSH接続の確認

```sh
$ ssh user@host
```

#多段SSH
本題の多段SSHを一発で行う方法。
以下は、client->host1(踏み台)->host2という場合の例
やり方は簡単

1. host2にclientの公開鍵を登録
clientの`id_rsa.pub`の中身をコピーして、host2の`authorized_keys`に書き込めばOK
2. `~/.ssh/config`に以下を書き込む

```sh

Host           host1　　　　　　　　　
HostName       host1.hoge.fuga
User           user　　　　　　　
IdentityFile   ~/.ssh/id_rsa

Host           host2
HostName       host2.foo.bar
User           user
IdentityFile   ~/.ssh/id_rsa
ProxyCommand   ssh -CW %h:%p host1
```
###書き方
`Host`: SSHするときの名前タグ
`HostName`: `$ ssh user@~`の@以下
`User`: `$ ssh user@~`のuser名
`IdentityFile`: id_rsaのパス

これで、

```
$ ssh host2
```
だけでSSH接続できる！！
エンベデッドシステムスペシャリスト試験に合格するためのテクニック

# 対象
高度区分(レベル4)を受験する人は、基本or応用情報技術者試験を合格していることが多いはず。
また組み込み系分野での開発経験がある前提ですが、参考書とかテキストを無理に購入する必要はないと思います。
(1冊で全部賄えるような参考書があれば別ですが、そんなのみたことがない)

そうでないなら、上記のどちらかもしくは両方を経験してから受験をすることをオススメします。

# 午前I・午前II
ひたすら過去問を解く、これに尽きます。
毎回新しい問題は出題されますが、60%の正答率で良いと考えるなら
過去問対策だけでほぼ問題ないと思います。

ここで躓くようであれば、応用情報技術者試験からやり直しましょう。
(午前I免除があれば、試験当日の疲労も少なく済みます)

# 午後I・午後II
まずは過去問からどのように出題されるのか傾向を掴むと良いと思います。

穴埋めの答えは、問題文のどこかに記載されています。(国語の解き方と同じ)
問題文に記載されていないことは、仕様の抜け穴ですので
それに関連する問いの解答に結び付くと思います。

計算問題については慣れもあると思いますが、
時間が掛かりそうであれば後回しにしても良いと思います。
午前同様、60%の正答率で良いと考え満点を取りに行く必要は全くありません。

IPAから出された採点講評にも目を通しておくと良いでしょう。
どういう解答を期待しているのか把握することも重要です。
【初心者向け】Flutterを最速で入門する3つの方法

## Flutterとは
__Flutter__はGoogleが開発している__クロスプラットフォームのUI構築用フレームワーク__です。
現状多く使われているのは__AndroidとiOS__ですが、将来的にはWebやDesktopアプリの制作も可能になるとのことです。
（現在WebはBetaテスト、DesktopはAlphaテスト中です）

公式ページに使っている企業が掲載されています
https://flutter.dev/showcase

言語は__Dart言語__が使われており、こちらもGoogle開発の言語です。
__JavaScriptに似た文法__ですが、JavaScriptよりも型関連の機能が充実しており、
__IDEを使うととても書きやすい__言語です。

またFlutterの記法はWebフロントエンドでよく使われる__宣言的記法__であり、
Webフロントエンド（__特にReact__）を経験したことのある人は、すぐに書けるようになります。

しかし、新しいフレームワークのため、どこから手をつけていいかわからない方も多いと思われます。
そんな方のために、自分が実際に取り組んだ方法をご紹介したいと思います。

## 1. 全体の流れを把握する
残念な話ですが、Flutterに関して公式の日本語ドキュメントは存在せず、
情報は英語がメインになります。

特に、最初に流れを把握するのが英語だときついという方は書籍を買うのがおすすめです。
自分にはこの本が良さそうでした。
[基礎から学ぶ Flutter](https://www.amazon.co.jp/dp/4863542941)

ただ、全体の流れに関してはどの本もあまり変わらないので、
自分がいいと思った書籍を買うといいと思います。

英語でも大丈夫、無料がいいという方は、Flutterの公式チュートリアルがおすすめです。
https://flutter.dev/docs/get-started/install


## 2. 公式ドキュメントを参照する
全体の流れを把握できたら、実際に自分でアプリを作成すると思いますが
このWidgetはどんな風に使うんだっけとなることが多いです。
このときは、素直に公式のドキュメントで検索すると早いです
https://flutter.dev/docs

ListViewの動きやレイアウト、アニメーション関連はYoutubeの公式動画を見るとわかりやすいです。
https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG

また、少し理解するのが難しくなるもしれませんが、IDEでWidgetのソースコードとコメントを読むと
どんな風に動くかが確実にわかるので、できる人であればおすすめです。

__Visual Stuido Code__と__Android Studio__は、調べたいクラスを`control + click`でソースコードを見ることができます。（Macだと`cmd + click`）

## 3. サンプルアプリを参考にする
実際にWidgetの動作を確認したい場合は、FlutterのWidgetのサンプルが大量に載ってあるアプリがストアにいくつもあるので、それを参考にするといいです。

自分は__Flutter Catalog__をよく使います。（アプリストアのURLはリンク先にあります）
Firebaseの実装もあって参考になります。
[Flutter Catalog - Github](https://github.com/X-Wei/flutter_catalog)

Flutter公式からも同様なアプリが出ています（こちらのストアはAndroidのみ）
[Flutter Gallery - Github](https://github.com/flutter/gallery)

## まとめ
まとめてみると、英語力が必要そうに見えますが
Googleのページ翻訳でも結構なんとかなる範囲です。

この記事を機にFlutterに触れてみる人が少しでも増えたら幸いです。
画面内特定文字列の表示位置探索のテスト

### 前回の続き
前回(https://qiita.com/tanakah/items/ee41c592cc4f2caaf56d) のツールでは、
ゲーム画像のチャット文字に特定文字列が表示されたかどうか(及びその位置)の検出方法として、
2値化した画像のパターン(高さ16ドットの列を2バイト=16bitで符号化)の辞書比較による探索でやっていました。
ゲーム画像ということで、フォント種類、文字幅が固定され、ノイズもないという緩い条件なので、
高速で確実に検出することができています。
ただ、この方法だと上下に1ドットでも探索ラインがずれると検出できないのですよね。
あらかじめ探索開始ラインを調整しておかないといけない。
そこでもうちょっと画像処理らしくやってみたいなと思っていました。

###パターンマッチングへの拡張
要するに、今までは特定ラインだけ走査して相関値を求めていたのを、複数ラインに拡張すればいいだけです。
(時間がかかるから手抜きしてました＾＾；)
例えば5×5のガウシアンフィルタで画像全体を走査するように、
相関値を求めるフィルタで画像を走査して画素値を逐一求める処理します。

で、このような空間フィルタリングは周波数領域での演算と同等なわけで
(というか、周波数領域で意味を持った処理を時間領域に変換することによって、フィルタサイズに応じた係数が決まる)
フーリエ変換を使った演算で位置検出できることを確認してみたくなりました。

###テスト
OpenCV2.4系でざっくり書いてみました。

```
  IplImage *image_co, *image_re, *image_im;
  IplImage *dict_co, *dict_re, *dict_im;
  IplImage *temp_re, *temp_im;
  IplImage *disp_gr;
...
  //// DFT(1)
  cvDFT(image_co, image_co, CV_DXT_FORWARD, dft_size);
  {
    cvSplit(image_co, image_re, image_im, NULL, NULL);
    //
    cvSplit(image_co, temp_re, temp_im, NULL, NULL);
    cvPow (temp_re, temp_re, 2.0); cvPow(temp_im, temp_im, 2.0);
    cvAdd(temp_re, temp_im, temp_re, NULL); cvPow(temp_re, temp_re, 0.5);
    // normalize
    cvDiv(image_re, temp_re, image_re); cvDiv(image_im, temp_re, image_im);
    cvMerge(image_re, image_im, NULL, NULL, image_co);
  }

  //// DFT(2)
  cvDFT(dict_co, dict_co, CV_DXT_FORWARD, dft_size);
  {
    cvSplit(dict_co, dict_re, dict_im, NULL, NULL);
    //
    cvSplit(dict_co, temp_re, temp_im, NULL, NULL);
    cvPow (temp_re, temp_re, 2.0); cvPow(temp_im, temp_im, 2.0);
    cvAdd(temp_re, temp_im, temp_re, NULL); cvPow(temp_re, temp_re, 0.5);
    // normalize
    cvDiv(dict_re, temp_re, dict_re); cvDiv(dict_im, temp_re, dict_im);
    cvMerge(dict_re, dict_im, NULL, NULL, image_co);
  }

  cvMulSpectrums(image_co, dict_co, image_co, CV_DXT_MUL_CONJ);

  //// IDFT
  cvDFT(image_co, image_co, CV_DXT_INVERSE);
  cvSplit(image_co, image_re, image_im, NULL, NULL);
  mycvSwapQuadrant(image_re, image_re);

  //// convert scale
  double re_min=0.0, re_max=1.0;
  CvPoint maxloc, minloc;
  cvMinMaxLoc(image_re, &re_min, &re_max, &minloc, &maxloc, NULL);
  Printf("maxpoint(%d,%d)\n", maxloc.x, maxloc.y);
  cvConvertScale(image_re, disp_gr, 255.0/(re_max-re_min), 255.0*(-re_min)/(re_max-re_min));
```

###結果

入力画像 と 検出文字列画像
![yahoo_capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/b96954f6-b49c-8b0a-9d03-d871979f75d2.png)　![yahoo_capture_words.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/9336b6cf-286b-9353-526c-727613e0a9ad.png)

DFT画像(normalize前)
![yahoo_capture_dft.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/21641089-d5ae-5ec6-3be6-25e0cfa2a5c7.png)　![yahoo_capture_words_dft.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/3f17089d-df1e-984d-80ef-cb3e93fdcf3f.png)

IDFT(DFT×DFT) 画像 (わかりにくいですが、"おそれ"の位置に輝点)
![yahoo_capture_idft.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/47228e74-4c8e-7d20-cc54-24aa6f36e8ba.png)

検出条件を絞っていったときにこういう方法もあるわな、と覚えておくことにします。
(ちなみに、入力画像の太字の"おそれ"が検出できないように、この方法だと拡大縮小には対応しないんですよね、
対応するためにはLogPolar変換使ったりするんでしょうけど、まぁそこまでいいわっていう)
フォレンジック勉強法

#はじめに
フォレンジックを独学で勉強しようとすると、何からやればよいのかわからないという声を聞くので、業務で必要だから身につけた私の視点で、どうやれば良いかを解説してみます。

#必要なもの
必要最低限だと、以下だと思います。
- 根気
- PC
- 大容量のHDD（市販の外付けHDDでOK）
- インターネット環境

#保全
具体的な手順は以下の2つの記事を参照してください。保全する対象は仮想マシン上に作成したWindowsマシンが良いでしょう。保全するだけであれば、Windowsはインストールした直後のものでも問題ありませんので、評価版の状態でも問題ありません。
保全先は用意した外付けHDDにする点だけ間違えないようにしてください。
https://qiita.com/log0417/items/05bb733c11b040282b75
https://qiita.com/log0417/items/55b7e42bb5af67a4acec

#アーティファクトの確認
Windowsには様々なアーティファクトが存在します。OSをインストールした直後であっても、ある程度確認できるはずですので、[SANSのポスター](https://www.sans.org/security-resources/posters/dfir)などを参考に、各アーティファクトに記録される情報を確認します。
また、log2timelineやSlueth kitなどでタイムラインも作成してみます。

#調査
実際のフォレンジック調査では、複数のアーティファクトを組み合わせて調査対象における操作を特定します。
実際にインシデントが起こったマシンを調査してみるのが望ましいですが、業務外でそのような環境を整えようとするとなかなか大変ですので、例えば以下のような操作によって、アーティファクトにどのような変化が起こるかを観察してみると良いと思います。
-ファイルの作成、変更、削除（タイムスタンプが変わります）
-外部からリモートデスクトップで接続する
-外部からファイル共有でアクセスする
-Webサイトを閲覧する、ファイルをダウンロードする、Gmailを閲覧、メールを送信する
-Outlookからメールを送受信してみる

なお、上記によってどのアーティファクトにどのような痕跡が残るかについては、SANSのポスターの「Windows Forensic Analysis」や「Hunt Evil」が参考になります。

#まとめ
独学でできるだけ頑張る方法を記載しました。とはいえ、実際のインシデントに遭遇しないとなかなか身につかないかもしれません。インターネット上には少数ながら練習用のイメージデータが公開されていますので、上記を一通り試した後に探してみてください。
Pythonで配列内の要素を交換する方法、また配列を逆順にする方法。

Pythonで配列内の要素の交換を行う方法、また配列を逆順にする方法を書いていきます。
よろしくお願いします。

```
n = [*range(1,6)]
for i in range(len(n)//2):
    n[i], n[len(n)-i-1] = [len(n)-i-1], n[i]
#先頭と一番後ろ、二番目と後ろから二番目、を交換しています。
```

配列を逆向きにするのが目的の場合。

```
n = [*range(1,6)][::-1]
```

```
n = [*range(1,6)]
reverse_n = sorted(n,reverse=True)
#sortedは非破壊的でnそのものは変更せず、逆順になったnを返します。
```

```
n = [*range(1,6)]
n.sort(reverse=True)
#sortは破壊的でnそのものを変更します。返す値はNoneです。
```

以上です。
ありがとうございました。
centOS　7インストールエラー

centOS 7 (2003)のインストールが失敗
30GBの未割当領域にwin10とデュアルブートでインストールを試みるも
インストール画面のパーティション選択をすると途端にエラーが発生
インストール中断しか選択できない始末となった。

パーティション選択画面から何も選択しないで完了を押してもエラーが発生することから
どうやらインストーラー側に問題がありそうである。

ダウンロード・インストール環境は以下の通り
URL:http://ftp.tsukuba.wide.ad.jp/Linux/centos/7.8.2003/isos/x86_64/CentOS-7-x86_64-DVD-2003.iso
USB:8 GB (FAT32)
(ISO書き込みにはRufusを使用)

ファイルサイズもDVDバージョン(4.5GB)なので問題なさそうである。
(centOSはOS初期のファイルサイズがUbuntuに比べて大きい気がする。9GB近いファイルは間違いなくDVDには入らない。USBだって意外と手持ちのものはサイズが小さい。おそらくcentOS8ではすでにDVDインストールを前提としたインストールは、除外したように思われれる。サーバー利用が基本であるし、一度インストールすればもはごみとなるインストールファイルの合理化も進んでいるようだ....やはりUbuntuは優しい)

考えられる理由としてはcentOSのダウンロードにFlashGetを利用したことくらいである。
このFlashGetは大容量のファイルダウンロードを高速に終わらせるソフトだが、
ファイルを分割して他サーバーにダウンロードさせ、その断片ファイルを自分のPCで結合することで高速化を実現している。
そのため、結合がきちんとできていないと思わぬエラーに遭遇することがある。

よって、時間はかかるがChromeの標準ダウンローダーで再度上記のcentOSをダウンロード。
Rufusでインストールするも今度はブートローダーがインストールされず起動しない....orz

ということで、UnetbootinでcentOSのUSBを作った
Unetbootin:http://unetbootin.github.io/
しかし、これも駄目であった。

次に、Etcherを使ってブートUSBを作成。
balenaEtcher:https://www.balena.io/etcher/
このソフトは圧倒的に書き込みスピードが速い。しかし、作成後のUSBはWindowsでは認識されないようになる。
無事ブートもできたものの、結局最初と同じエラーが発生。

次記事に続く
【Jetbrains】ToolboxからインストールしたIDEの起動コマンドの再設定

## 困った

JetbrainsのJetbrains Toolboxを使っていてアップデートを行うと起動コマンドのパスが変更されて起動されなくなることが頻繁にあった。

解決方法をよく忘れるのでここに解決方法を記載してくれてるリンクを残しておきます。
[Toolbox-AppでIDEをアップデートしたらTools-Create-Command-line-Launcher-が消えてしまいました](https://support.samuraism.com/hc/ja/articles/360021410293-Toolbox-App%E3%81%A7IDE%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E3%81%97%E3%81%9F%E3%82%89Tools-Create-Command-line-Launcher-%E3%81%8C%E6%B6%88%E3%81%88%E3%81%A6%E3%81%97%E3%81%BE%E3%81%84%E3%81%BE%E3%81%97%E3%81%9F)
Zoomのアーキテクチャ調査（その１）

https://qiita.com/ynwa0826/items/66ea83d4b851df4ce37d
https://webrtchacks.com/zoom-avoids-using-webrtc/

この辺を見て、Zoomのアーキテクチャを調べてみた。まだ途中。

# 通信概要

```
blob:https://zoom.us/49180403-9b2e-4fa5-bd85-9766f486f6c7:1 ready to start!
blob:https://zoom.us/454d0bbf-202e-4993-b4bf-e9098edd8de9:1 ready to start!
blob:https://zoom.us/05fc8168-cdef-490f-8c36-88e0c04ffb6d:1 ready to dec video!
blob:https://zoom.us/0847d4b6-0d6f-488f-a64f-709fb674afdd:1 ready to dec video!
blob:https://zoom.us/454d0bbf-202e-4993-b4bf-e9098edd8de9:1 JsAudioDec.js receive startMedia
blob:https://zoom.us/49180403-9b2e-4fa5-bd85-9766f486f6c7:1 JsAudioDec.js receive startMedia
blob:https://zoom.us/49180403-9b2e-4fa5-bd85-9766f486f6c7:1 Open_Audio_WebSocket_Connect
blob:https://zoom.us/49180403-9b2e-4fa5-bd85-9766f486f6c7:1 Open_Audio_WebSocket_Connect
blob:https://zoom.us/454d0bbf-202e-4993-b4bf-e9098edd8de9:1 Open_Audio_WebSocket_Connect
blob:https://zoom.us/454d0bbf-202e-4993-b4bf-e9098edd8de9:1 Open_Audio_WebSocket_Connect
blob:https://zoom.us/50d63bcc-9fd8-46fe-836b-8837b3a6dadc:1 Open_Sharing_WebSocket_Connect
blob:https://zoom.us/f30fc6c3-9411-444f-a40f-e7618219ce64:1 Open_Sharing_WebSocket_Connect
blob:https://zoom.us/05fc8168-cdef-490f-8c36-88e0c04ffb6d:1 Open_Video_WebSocket_Connect
blob:https://zoom.us/0847d4b6-0d6f-488f-a64f-709fb674afdd:1 Open_Video_WebSocket_Connect
blob:https://zoom.us/f30fc6c3-9411-444f-a40f-e7618219ce64:1 sharing_websocket_on_close
blob:https://zoom.us/05fc8168-cdef-490f-8c36-88e0c04ffb6d:1 video_websocket_on_close
blob:https://zoom.us/0847d4b6-0d6f-488f-a64f-709fb674afdd:1 video_websocket_on_close
blob:https://zoom.us/50d63bcc-9fd8-46fe-836b-8837b3a6dadc:1 sharing_websocket_on_close
```

WebRTCは使っていないことを改めて確認。
WebSocketは
- Videoで2本
- 画面共有で2本
- 音声で4本
最低2本必要なのは送受信それぞれで別のWebSocketを使っているからだろうと想定するのだけど、何故音声だけ4本？

# プロトコル調査

```
Request URL: wss://zoomtyn70rwg.ty.zoom.us/wc/media/XXXXXXXXXXXX?type=a&cid=FF4D2EFA-A8F2-7F18-9CF5-YYYYYYYYYYYYYYY&mode=5
Request Method: GET
Status Code: 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: XXXXXXXXXXXXXXXXX
Upgrade: websocket
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Host: zoomtyn70rwg.ty.zoom.us
Origin: https://zoom.us
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: YYYYYYYYYYYYYYYYY
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
type: a
cid: FF4D2EFA-A8F2-7F18-9CF5-YYYYYYYYYYYYY
mode: 5
```

modeが1/2/5とあり、
画面共有が1/2
音声が2/5それぞれ2本ずつ
Videoが2/5それぞれ1本ずつ

modeはどう考えても内部通信モードを指していると考えられるとの、これ以外にそれほど変化がないことから
つまり音声だけは送受信に2本どうしても必要だったからこうなっている？


## 補足
https://qiita.com/yusuke84/items/428affe75affa16f104d
chrome://webrtc-internalsも一応見えるが、media系の情報は出てこないので、Iceとかその辺の制御系だけ利用し、それ以外は独自なんだろう。
【CSS】Calc関数

★Calc関数とは
・通常CSSコーディングでコンテンツのサイズ指定をする場合はpxや%で指定するが、CSS3より実装されたCalc()関数ではプロパティ値を計算式で求めることができる。
・使用できる演算子は以下
　四則計算・・・加算(+)、減算(-)、乗算(*)、除算（/）のみ
・使用できる型は以下
　length型、frequency型、angle型、time型、number型、integer型

上記以外での使用はできない。
また、異なる単位を数値として計算式に記述することも可能。

```CSS:Calc関数の記述方法
testcss1{
  width: calc(50%/2 -4*2px - 2*1em);
}
```
**注意点**
・Calc関数内で、「-」や「+」を使用する場合は必ずスペースが必要となる。(「/」や「*」では不要)
<br>

・Calc()関数内に入れ子としてCalc関数の記載も可能

```CSS:Calc関数の入れ子
testcss2{
  width: calc( 50%/ calc(20em*2));
}
```
上記の場合は以下と同等の式となる

```CSS:Calc関数の入れ子結果
testcss2{
  width: calc(50%/(20em*2));
}
```
Azure Synapse Analytics 最初の一歩

##Azure Synapse Analytics とは？
これについては公式ドキュメントや他の記事を読んでもらった方がよいが下図のようにModern Data Warehouseで複雑になりがちな処理（ETL、集計、MLなど）を一カ所に集約して簡素化したサービスである。Simple is best. 
[Azure Synapse Analytics についてまとめてみた](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33998/e7cc6f5a-3828-0058-18a5-3b6e3db871df.png)

###従来の構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33998/06c0724e-6639-c204-d370-994bc9cd668a.png)


###Azure Synapse Analytics 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33998/f9e588dc-e583-d0da-0448-26fda5685bc7.png)


Azure Synapse Analytics の全体像は下図のとおりだ。一見複雑に見えるかもしれないが、SQLオンデマンドが追加されたものの基本的にはSQL Data Warehouse、Spark, Data Factoryといった既存サービスの組み合わせだ。Azure Synapse Analytics Workspaceという入れ物で纏めて管理できるようになっていおり各サービスの相互接続性が上がっている。これはSynapseに閉じた話ではなくAzure Synapse Link for Azure Cosmos DBなど外部のサービスとの連携も強化ポイントだ。そのため既存のサービスを理解できていれば難易度は非常に低いであろう。また、Synapose Analytics Studio という管理UIが直感的で使い易いと感じた。ツールの習得難易度の低さもハードルを下げる要因であろう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33998/03d2886f-afeb-b7a1-37ed-b65d52cb9b8e.png)


###適用範囲
いままで複数のサービスやコンポーネントを組み合わせていた Modern Data Warehouse や ML のシナリオではAzure Synapse Anaylticsで纏めて管理できる分に便利であろう。おそらくLambda architectureでも強みを発揮すると思う。ただしリアルタイム処理についてCosmos DBやAzure Stream Analyticsを活用したドキュメントは見かけたが記事執筆時点でその他のパターンが見つけられていないので調査が必要だ。ベースはただのSparkなのでDelta、Streamingなども使えるのではないだろうか。またDevOpsやCI/CDまわりも気になるところである。例えばAzure Data FactoryにはGit連携の機能があったがSynapseには見当たらない。（もし既に詳しい人がいたらご教授願いたい。）また、IaCについてARMはあったがterraformはまだ未対応（2020/07/10時点）なので今後に期待だ。


###試してみよう
前述したようにSynapose Analytics Studioはとても直感的で使い易い。Data Factory はほぼそのままで、 SQL Server向けのUIも提供されている。もちろん既存のツールであるSQL Server ManagementやVisual Studioなどもそのまま活用できる。
また試してみる分にはハンズオントレーニングやチュートリアルがおすすめである。
[Azure-Samples - Synapse](https://github.com/Azure-Samples/Synapse)

[Azure Synapse Analytics エンドツーエンド ソリューション - ハンズオントレーニング](https://github.com/takeokams/MCW-Azure-Synapse-Analytics-end-to-end-solution?_lrsc=ff7bfc8c-b761-4482-b14b-46694ae26f36)
 *2020/07/13時点では動かなかったので今後のUpdateに期待

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33998/3355efec-669e-0b24-1a15-6677a382a515.png)
Goでのswitchを用いた条件分岐

# 背景
Javaで散々苦しんだswitch文、Goにも出てきたのでまとめます

# 環境
go version go1.14.4
windows10

# まとめ
## JavaとGoのswitch文の大きな違い
### Java
条件に合致した際に次の処理を実施しないようにするにはbreakが必要
### Go
条件に合致したら次の処理は実施されない。
実施したい場合はfallthroughを入れる必要がある
 ※型でのswitchの場合は使用できない

## ソースと実行例

```Go:main.go
package main

import (
	"fmt"
)

// 型アサーションを用いたスイッチの使用例
func main() {

	// 変数aをすべての型を取れるinterface{}型として定義し、999.999を入れる
	var a interface{} = 999.999

	// aの型でswitchする
	// [変数名].(type)で型が取得できる
	switch a.(type) {
	case bool:
		fmt.Printf("bool")
	case int, uint:
		fmt.Printf("int or uint")
	case string:
		fmt.Printf("string")
	case float64:
		fmt.Printf("float")
	case rune:
		fmt.Printf("rune")
	default:
		fmt.Println("don't know")
	}
}
```

実行例

```
C:\go>go run main.go
float
```
999.999はfloat64の型に入るのでその出力。
後の実装のrune, defaultは実行されない。
Accessで勤怠管理をしたい

Access勤怠管理システムを作りたくて奮闘しています。
アドバイスお願いします。
シンプルな勤怠管理システムが作りたくてフォームに出勤、退勤、外出、戻りのボタンを
設置しただけの簡単ものを
つくりました。出勤ボタンにはAddNewとnow()をつかってボタンを押すとテーブルに押した
時刻をテーブルに記録できるようにしました。
しかし、退勤や外出などのボタンを同じように作ると別のレコードに登録されていきます。
出勤と同じレコードに退勤などが記録されるようにするにはどうしたらいいですか？
shellで受け取ったフルパスを使用したファイルリネーム

#概要
shellにファイルのフルパスを引数として渡したときに、もともとのファイルネームを利用しつつ名前を変えたい
例：
引数→ /home/desk/a.txt
出力→ /home/desk/bc_a.txt

##ソース

```
a=/home/desk/a.txt
echo $(dirname ${a})/bc_$(basename ${a})
```
##実行結果
```
/home/desk/bc_a.txt
```

#コマンド詳細
dirnameコマンドは引数のディレクトリ部分を抜き出してくれる
basenameはファイルネーム部分を抜き出してくれる
抜き出し条件も細かく決めることが可能なため、必要に応じて要調査
(拡張子なしで抜き出すなど）
7/10/2020期サイエンス誌内容集約 in 日本語

このシリーズは科学界で最も権威ある Science誌の最新期を短い日本語に要約して皆さんにお届けします。この雑誌は週刊誌なので、毎週の内容を1~2回に分けてその週のうちに更新を目指します。各論文のタイトルから原文に飛べるので、大学院生、新しい技術を求める起業家、科学に興味ある方はぜひご覧になってください。


#[歯と顎の由来に関する新しい説(Teeth and jaws)](https://science.sciencemag.org/content/369/6500/211)
地球で最初に誕生した脊髄動物はウナギのような顎がない魚類である。
その後、声を発しコミュニケーションを取るためか、獲物を取るためなど
環境に適応するために歯や顎が生まれたとされてきたが、
Vaškaninováらの研究により、もしかしたらこの変化はもっと先天的(原文：ancestral)
なものかもしれない。


#[毒素開発に関する新しい手法(Engineering a toxin)](https://science.sciencemag.org/content/369/6500/161)
現在、任意に選んだ[Gタンパク質共役受容体](https://ja.wikipedia.org/wiki/Gタンパク質共役受容体)(タンパク質の一種)にのみ作用する薬を開発するのが難しい状態が続いている。
そんな中、Maedaらはヘビの神経毒素を観察することにより、その中の一つMT7という毒素はM1AChRという受容体にのみ作用することを発見した。さらに、彼らはMT7を別の受容体M2AChRに作用させるような手法を開発したため、これが特定の受容体にのみ作用する薬の開発の基盤になると期待されている。

#[遺伝的疾患に関する因子を発見(An inherited disorder makes WAVEs)](https://science.sciencemag.org/content/369/6500/202)

[WAVE regulatory complex(WRC)](https://en.wikipedia.org/wiki/WAVE_regulatory_complex)は免疫に関する[アクチン](https://ja.wikipedia.org/wiki/アクチン)タンパク質の形成を仕切る複合物だと知られているが、その詳細な作用はまだ明らかになっていない。
Cookらは、WRCを形成する遺伝子にミンセンス変異(missence variants)が見られる五人の患者を調査した所、この遺伝的疾患にWRCが関わる可能性が高いことを発見した。


#[長距離で強い相互作用を引き起こす(Strongly coupled at distance)](https://science.sciencemag.org/content/369/6500/174)

量子技術の領域においては複合(hybrid)な量子システムの構築が期待されている。
しかし、強い相互作用の下で起きる量子現象を引き起こすためには、今まで極近距離に原子を近づける必要があった。そこで、Kargらはレーザーを用いれば原子がお互い近くにいなくても強い相互作用が起きることを示した。

#[北極海において浮遊生物が栄える新たな要因(Food for thought)](https://science.sciencemag.org/content/369/6500/198)
地球温暖化により気温が上昇し、開放海域が広がった北極海において、
植物性の浮遊生物の数がここ数十年上昇し続けてきた。
従来では、気温上昇が浮遊生物の大幅な増殖の原因であると考えられてきたが、
Lewisらにより、増殖速度が予想より早く、何らかの原因で外部から栄養となり得るものが流れ込んできたことがわかった。したがって、今後数十年北極海の生命活動はより活発になると予想できる。

#[血漿を用いて「賢さ」を伝播できる(Plasma transfers exercise benefit in mice)](https://science.sciencemag.org/content/369/6500/167)
運動は体の様々部分に良い影響をもたらしてくれることは昔からわかっている。
さらに、Horowitzらの研究により、これは血漿を介して別の個体に給与することができることが判明した。
彼らは運動を行なった若いネズミの血漿を、運動をしていない高齢なネズミに輸血した所、
高齢なネズミの認知能力が高まったことを観測した。

#[リンパ管機能の解明(Roles of organ-specific lymphatic vessels)](https://science.sciencemag.org/content/369/6500/eaax4063)
リンパ管は全身に渡り様々臓器とやり取りを行なうことで知られている。
今回 Petrovaらはさらに臓器との関わり、血液系統の維持、大腸液への影響、ガンの誘発性など新たな視点でリンパ系統の詳細についてまとめた。この分野の入門者におすすめ。
勉強ノート：Tornado その３

# Source
https://www.tornadoweb.org/en/stable/guide/running.html

# Running and Deploying
Tornadoは自身のHTTPサーバを持っていて、他のpython製webフレームワークとデプロイの仕方が異なる。
WSGIの設定をするのではなく、main()関数を書く。

```py
def main():
  app = make_app()
  app.listen(8888)
  IOLoop.current().start()

if __name__ == '__main__':
  main()
```
注）プロセスごとにOpenできるファイルの上限を/etc/security/limits.confで設定しておく。

マルチプロセスモードで起動したい場合は、main関数を以下のように設定する。

```py
def main():
  app = make_app()
  server = tornado.httpserer.HTTPServer(app)
  server.bind(8888)
  server.start(0)
  IOLoop.current().start()
```
注）各子プロセスはそれぞれIOLoopをもつ。このように実行するとZeroDowntime updateは難しい。
それぞれのプロセスは別のポートをlistenするようにデプロイするのが良さそう。

## Nginxなどの後ろで動かす時
HTTPServerにxheaders=Trueと設定しておく。そうすると、X-Real-IPなどのヘッダでユーザのIPを渡すようになる。

## 静的ファイル
static_pathをsettingsに追加する。そうすると、TornadoからStaticファイルを配布できる。
ブラウザのキャッシュを使ってパフォーマンスを改善するのに、static_url("images/logo.png")が使える。関数の引数は適当な静的ファイル。

## debug mode and automatic reloading
Applicationのコンストラクタでdebug=Trueを設定することでデバッグモードでアプリケーションを起動できる。デバッグモードではオートリロードがONになっていたり、キャッシュが向こうになっていたりする。
WebAPI調べたけどよくわからなかった人へ

# はじめに
WebAPIについて調べたとき、大体の記事は文の量が多いと思います。
専門用語の説明とかから始まってそっから本番の説明が始まる。

**しんどい。。。。**
そう思ったので切り口になるようなものを書こうと思った次第です。

WebAPIについてのイメージができるようになるぐらいになればいいなとおもいます。

# この記事の対象
WebAPIについて調べてあきらめた人


# これだけ伝えたい！！！
WebAPIの大体の作成理由は
**自分が持っているデータを誰かが使えるように公開したい**
この一点かなと私はおもいます。

郵便番号のWebAPIを上に当てはめると
例 https://github.com/madefor/postal-code-api

このAPIは自分が持っている（作った）郵便番号のデータを不特定多数の人に公開したい
って感じです。

これ以外にも顧客データをある会社にだけ公開したいとかにも当てはまります

公開にも色々あって、参照編集追加削除全部できる公開の仕方もありますし、参照しかできない公開の仕方もあります。

# おわりに
ほかにも色々言いたいことはありますがこれだけで終わりです。

こういう初めての人向けの記事にありがちですが、この記事に書いた内容は単なる一例であり、WebAPI自体の使い道はめちゃくちゃ多岐にわたります。しかも日々作り方も思想も更新されていきます。ですので一個調べてこうなんだ！などとは思わず色んなサイト、本等で調べて自分の考えとして定着させるのがいいと思います。

初投稿になりますので間違っている部分、改善した方がいい部分がありましたら遠慮なくコメント等に書いてください！勉強になるので！


以下余談です
私は以前会社の研修でAPI作ってって言われ、CRUD機能だけ(APIではない)を作って提出し爆死しました。このような人はいないかもしれないですが、このようなひとをもう生み出さないためこれを書きました。
あのころの自分に一つだけ伝えるとしたらこれかなって思いますが、わかんなかったらわかる人に聞きましょう！それが一番早いです！
MySQLのver.を間違えてインストールしたので削除した

macbookpro 
macos catalina ver.10.15.5
プロセッサ　1.4 GHz クアッドコアIntel Core i5
グラフィックス　Intel Iris Plus Graphics 645 1536 MB

**行ったこと**
homebrewでmysql8.0をインストール

**ゴール**
mysql 8.0をアンインストールし,mysql５.7をインストールしたい

**これから行うこと**
macbookのターミナルで実行する

``$ brew uninstall mysql``

以前の不要なファイルも削除する

``sudo rm -rf /usr/local/Cellar/mysql*``
``sudo rm -rf /usr/local/bin/mysql*``
``sudo rm -rf /usr/local/var/mysql*``
``sudo rm -rf /usr/local/etc/my.cnf``
``sudo rm -rf /usr/local/share/mysql*``
``sudo rm -rf /usr/local/opt/mysql*``
``sudo rm -rf /etc/my.cnf``

削除できたので下記のコマンドでインストールする
``$ brew install mysql@5.7``

これでできました。

参考にした記事
[どうしよう！困った時のMac上のMySQLのアンインストール＆再インストール、動作確認手順](https://qiita.com/akiko-pusu/items/aef52b723da2cb5dc596)
SvelteでCollapseを実装する

Reactでは[react-collapse](https://www.npmjs.com/package/react-collapse)、Vueでは[vue2-transitions](https://www.npmjs.com/package/vue2-transitions)を使用してCollapseの動きをつけてたが、Svelteならsvelte/transitionのslideを使用することで楽勝で実装できた。

できたものはこんな感じ。
![svelte.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54330/216ca485-ec5a-c2d1-5e3c-7d281116f499.gif)


コード量はこんなもん（装飾(style)は除く）

```js
<script>
import { slide } from 'svelte/transition'

const contents = ['content1', 'content2', 'content3']
let showContent = ''

const handleClick = (payload) => {
  // 現状開いてる場合は閉じる
  showContent = payload === showContent ? '' : payload
}
</script>

{#each contents as content}
<div>
  <button type="button" on:click={() => handleClick(content)}>{content}</button>
  {#if showContent === content}
  <p transition:slide={{ duration: 400 }}>{content}</p>
  {/if}
</div>
{/each}
```

一応、作ったものへのリンクを貼っておく
https://svelte.dev/repl/90d1f85a07134b3aa7d9d265f836d771?version=3.24.0

SvelteはMotion/Transitionがかなり充実してるので、カルーセルとかも案外簡単に自作できそう。
Java 関数型インターフェースについて

##関数型インターフェースとは
抽象メソッドを1つだけ持つインターフェースのことで、ラムダ式やメソッド参照を渡す、代入先にすることができます。

##関数型インターフェースの条件
**・抽象メソッドを一つだけ持てる
・Objectクラスのpublicメソッドである抽象メソッドは含まれない
・defaultメソッドとstaticメソッドを複数持たせることができる**

関数型インターフェースの定義は下記のように書きます。

```main.java

@FunctionalInterface
public interface Sample {
    public abstract void Hello(String name);
}
```

**@FunctionalInterface**というアノテーションをつけることで、関数型インターフェースだということを明示し、これによってインタフェースが関数型インターフェースの条件を満たしていない場合にコンパイルエラーを出すことができます。




##種類
java.util.functionパッケージで、関数型インターフェースにはいくつかの種類が提供されています。受け取る引数と返す結果のパターンから大体4種類に分けられます。
基本的な関数型インターフェースは以下のとおりです。


| 種類       | 抽象メソッド |    できること    |
|:-----------------|------------------:|:------------------:|
| Supplier<T>           |  T get() |    引数がなく、T型の戻り値を返す |
| Consumer<T> |void accept(T t) |  処理を返す（値は返さない）。  |
|Function<T, R> | R apply(T t) |引数としてTを受け取り、結果としてRを返す |
|Predicate<T> |boolean test(T t)|T型の引数を受け取り、booleanの値を結果として返す|



他にも、引数が2つの場合のインターフェース等種類があるので、用途に応じて使い分けます。
ExcelよりOracleDBへアクセスする新方法

##Officeだけ利用許可された端末よりOracleデータベースへアクセス方法

今回紹介したいのは、Office だけ利用許可された端末よりOracleデータベースへアクセス方法です。

####Officeだけ利用許可端末とは、主に以下：
 1.本番サーバ環境、
 2.サーバ接続用中継端末、
 3.エンドーユーザー端末のシミュレーション端末、
 4.エンドーユーザー端末。
 5.その他

Officeだけ利用許可される端末から、データソースを設定して、
Excelからアクセスできますが、データソースの設定だけでも大変の事でしょう。

Excel VBAよりデータソースの設定不要、OracleDBへ簡単に接続できるツールを
以下のように紹介します。
![Excel VBAでOracleDBへ接続方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239418/2d62aa57-ce2b-a540-45e9-b188de977912.png)

上記画像表示のように、OracleDBへ接続に必要な情報は：
+ HOST/IP, 
+ PORT(ディフォルト:1521)
+ ユーザーID
+ パスワード
の４つだけです。

複数のSQLを一括CRUDでもできます。
オープンソースとして以下URLに公開しております。ソースのダウンロードは
SRC:https://github.com/lqwangxg/Xlsw.git

ツールのダウンロードは
https://github.com/lqwangxg/XlsWxg

※技術説明１：完全にExcel VBA+ADOでOracleへアクセスにはOracle Clientのインストールが必要です。本ツールはOracle.ManagedDataAccess.dll、.netframework4.5 を利用しているので、Oracle Clientに依頼せず、高効率でデータのCRUDが可能になります。

※技術説明２：またDB接続や、CRUD処理ロジックは.netライブラリに実装していますので、Oralce以外、他のDB（例えばMySQL, Postgresql等）へ接続したい拡張機能の実装も簡単に出来ます。

----
本文は弊社（MBPSMARTEC株式会社）のホームページにも掲載しております。
[ExcelよりOracleDBへアクセスする新方法](https://mbpsmartec.co.jp/ja/excel%e3%82%88%e3%82%8aoracledb%e3%81%b8%e3%82%a2%e3%82%af%e3%82%bb%e3%82%b9%e3%81%99%e3%82%8b%e6%96%b0%e6%96%b9%e6%b3%95/)

iot、bigdata、intra-martプラットフォーム等関連技術文書を後続追加掲載させて頂きます。
[iot関連技術紹介](https://mbpsmartec.co.jp/ja/product/)
[bigdata関連紹介](https://mbpsmartec.co.jp/ja/solution/)
[業界初Intra-mart + IoT + BIGDATA + モバイル端末対応](https://mbpsmartec.co.jp/ja/si-service/)
TkinterでBinaryで表示可能なSpinboxを作る

先ほど投稿した記事の続き
https://qiita.com/Nomisugi/items/cb2fa4f26cdf0a7888c5

Python TkinterのSpinboxでBinary対応させるコードを書いてみる。
TkinterのSpinboxは内部的にもBinaryに対応しておらず、加算結果を0か1で戻します。
その性質を利用して、コードを記述してみました。
このサンプルコードは8bit固定コードとなります。
![BIN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506495/3da51d7e-01e1-6b57-3109-19e1580bd900.png)

```python
: Binspinbox.py
import tkinter as tk

class BinSpinbox(tk.Spinbox):
    def __init__(self, *args, **kwargs):
        self.var = tk.StringVar()
        super().__init__(*args, **kwargs, textvariable=self.var, from_=0,to=0xff,
                         increment=1, command=self.cange )
        self.val = 0

    def set(self, val):
        self.val = val
        self.var.set("0b{:08b}".format(int(val)))

    def get(self):
        hstr = self.var().get()
        return int(hstr, 2)

    def cange(self):
        val = super().get()
        print(val)
        if(val == '1'):
            self.val = self.val+1
            if(self.val > 0xff):
                self.val = 0x00
        else:
            if(self.val == 0x00 ):
                self.val = 0xff
            else:
                self.val = self.val-1
        self.set(self.val)


if __name__ == "__main__":
    print("BinSpinbox")
    win = tk.Tk()
    hex = BinSpinbox(win)
    hex.set(0x55)
    hex.pack()

    win.title("BinSpinbox test")
    win.mainloop()
vue-property-decoratorでchart.js

# vue-property-decorator + chart.js

vue-proeprty-decoratorとchart.jsでグラフを表示させたかった。

## インストール

VueとTypeScriptとvue-perperty-decoratorは各自でインストールしてください。

Vueでchart.jsを使うには以下の二つをインストールする必要があります。

```terminal
npm i chart.js
npm i vue-chartjs
```

## コンポーネント

子コンポーネント

```@/components/Chart.vue
<script lang="ts">
	import {Component, Mixins, Prop} from "vue-property-decorator";
	import {Line, mixins} from "vue-chartjs";
	import {ChartData, ChartOptions} from "chart.js";

	@Component
	export default class Chart extends Mixins(Line, mixins.reactiveProp) {
		@Prop()
		public chartData!: ChartData

		@Prop()
		public chartOptions!: ChartOptions

		public mounted() {
			this.renderChart(this.chartData, this.chartOptions)
		}
	}
</script>
```

親コンポーネント

```Sample.vue
<template>
    <chart v-bind:chart-data=chartData v-bind:chart-options=chartOptions />
</template>

<script lang="ts">
	import {Component, Vue} from "vue-property-decorator";
	import Chart from "@/components/Chart.vue";

	@Component({
		components: {Chart, Panel}
	})
	export default class DashBoard extends Vue {
		public chartData: Chart.ChartData = {
			labels: ["左", "真ん中", "右"],
			datasets: [
				{
					type: "line",
					label: "グラフ",
					data: [10, 20, 30],
					fill: true,
					borderColor: "#AAFFAA"
				}
			]
		}
		public cpuUsageChartOptions: Chart.ChartOptions = {};
	}
</script>
```
Git

##🌼Gitの基本
Gitとは、
プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムである。


####Gitの準備
１回目だけ実行

```
$ git init
```

####共有するファイルを選択

```
$ git add ファイル名
```


####選択したファイルを記録

```
$ git commit -m "コメント"
```


####リモートを登録

```
$ git remote add origin(リモート名) URL
```


####リモートのmasterブランチにファイルをアップロード

```
$ git push origin master
```

####リモートのmasterブランチのファイルをダウンロードする

```
$ git pull origin master
```

####変更したファイルを確認

```
$ git status
```
<img width="653" alt="スクリーンショット 2020-07-05 19.35.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479140/e76a37fb-496c-d737-44be-0554a0bacda7.png">


####現在の変更内容の確認

```
$ git diff
```
<img width="680" alt="スクリーンショット 2020-07-05 19.38.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479140/761e143a-6394-3289-cffc-5655890f69ba.png">
<img width="629" alt="スクリーンショット 2020-07-05 19.40.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/479140/ba9381cf-b938-8078-dc67-bf961e2fdb26.png">


####過去のコミットのログの確認

```
$ git log
```


####過去の変更内容の確認

```
$ git log -p
```
Hamachi(VPN)でネットワークアダプターエラーが出た場合

# 1 Hamachiでネットワークアダプターエラーが出た

WindowsのHamachi LogMeIn Hamachiをアンインストールして再インストールする。


Raspberry piコマンド
sudo /etc/init.d/logmein-hamachi force-reload
$sudo hamachi login
$sudo hamachi attach [Your Hamachi Email Address]
$sudo hamachi set-nick [Whatever Nickname You Make]
$sudo hamachi join [Network ID] [Password]
タイムスタンプをミリ秒単位でISO8601表記に変換する

意外と手こずったので忘備録がわりにQiitaに残します。PHPで日時計算するには、[Carbon](https://github.com/briannesbitt/Carbon)が定石なので、Carbonを入れていることを前提とします。

<font color="red"><b>2020.7.28 コメントいただいたので修正しました！</b></font>

# やり方
タイムスタンプを秒単位でISO8601形式にするために`Carbon::toIso8601String()`が準備されていますが、秒単位で固定のメソッドです。似たようなメソッドでUTC固定の`Carbon::toIso8601ZuluString($unitPresion='second')`は引数を与えることによってミリ秒単位で変換できますが。なので下記のような記述になります。

```php
<?php

use Carbon\Carbon;

require 'vendor/autoload.php';

// ISO8601 形式
$iso8601 = Carbon::createFromTimestampMs($timestamp, 'Asia/Tokyo')->toIso8601String();

// ISO8601 形式(ミリ秒)
$iso8601m = Carbon::createFromTimestampMs($timestamp, 'Asia/Tokyo')
    ->rawFormat(\DateTime::RFC3339_EXTENDED);

echo $timestamp;
echo $iso8601;
echo $iso8601m;
```

結果

```console
1594618689760
2020-07-13T14:38:09+09:00
2020-07-13T14:38:09.760+09:00
```

~~全然スマートな方法じゃないんですけど、多分一番罠を踏み抜かないやり方かなあと思います。
`Carbon::getTimeFormatByPrecision`の引数は~~

- ~~"minute"~~
- ~~"second"~~
- ~~"millisecond"~~
- ~~"microsecond"~~

~~が渡せるようです。デフォルト値は`second`です~~

~~参照：https://github.com/briannesbitt/Carbon/blob/9a6737a39deb3cbf2531e1b5f3aa4714a5212b23/src/Carbon/CarbonInterface.php#L2162-L2169~~


# PHPに対する愚痴
そもそもISO8601形式のフォーマットって`DateTime::ISO8601`でなく`DateTime::ATOM`で定義されてるってどういう経緯なんですかね？便利どころかすごく紛らわしいと思うんですけど、なぜこういう仕様なのか？不思議です。で、PHP7系からミリ秒対応されたと聞いたんですが、`DateTime::ATOM`はミリ秒ないし、~~ミリ秒のあるフォーマットは追加されてなさそうだし、なんか不親切に感じます。~~<font color ="red"><b>\DateTime::RFC3339_EXTENDEDがありました！PHP7.0.0系以降追加されてました。</b></font>

軽微だしCarbonにあとでpull requestでも投げてみるかな…

# 参考
- [PHPのDateTime::ISO8601はISO8601ではない - Qiita](https://qiita.com/hogesuke_1/items/6d887c5669adeb1d1b08)
勉強ノート：Tornado その２

# source
https://www.tornadoweb.org/en/stable/guide/templates.html
https://www.tornadoweb.org/en/stable/guide/security.html

# Templates and UI
デフォルトでは、同じディレクトリ中のテンプレートファイルを探す。場所を指定したいときには、"template_path"を指定する。
ハンドラーごとに違う場所を探したい場合は、"RequestHandler.get_template_path"に設定する。


## Templateの書き方
Tornadoで使われるテンプレートには制御文と式がある。
制御文は{% %}で指定する。例：{% if len(items) > 2 %}のように中にPythonの制御文をいれる。
式は{{ }}で指定する。例：{{ items[0] }}のようにPythonの式をいれる。

localeを指定できる。locale.name=en_US。
localeによって変更したい部分は_("  ")で指定する。
RequestHandler.get_user_localeをオーバーライドしてデフォルトのlocaleを変更できる。

## UI module
TornadoはUIモジュールをサポートしている。tornado.web.UIModuleを継承したクラスを作成して、Tornadoのui_modulesに作成した.pyファイルの名前を追加することで使用できる。

```python
settings = {"ui_modules": uimodules,} #uimodules.pyを作成した場合
```
作成したUIModuleはテンプレートの中で使用できる。例えば、Entryモジュールを使用したい場合、{% module %}文で呼び出せる。

```html
{% for entry in entries %}
  {% module Entry(entry) %}
{% end %}
```
UIモジュールの中でCSSやjavascriptを使用することもできる。それぞれembedded_css(), embedded_javascript()などのメソッドとしてUIモジュールクラスにかく。
CSSは\<head>で読み込まれ、javascriptは\</body>の直前で読み込まれる。

# Authentication and security

## Cookiesの設定
RequestHandler中のset_cookie（"cookie_name", "cookie_val"）でユーザのブラウザにクッキーを設定できる。偽造防止のために、set_secure_cookie, get_secure_cookieメソッドを使うこともできる。セキュア版を使う時は、cookie_secretにシークレット鍵名を指定する必要がある。secure cookieにはコード化された値とタイムスタンプを含んでいて、get_secure_cookieはcookieの期限が切れているとNone（cookieが設定されていない場合と同じ）を返す。デフォルトの期限は30日。

## ユーザー認証
self.current_user()をハンドラが呼び出されるごとに実行してユーザー認証を行う。デフォルトでcurrent_userの値はNone。cookieにuser名を保存しておいて、それを認証に使う場合、get_current_user()でuser cookieの値を返すようにオーバーライドする。

```python
class BaseHandler(tornado.web.RequestHandler):
  def get_current_user(self):
    return self.get_secure_cookie("user")

class MainHandler(BaseHandler):
  def get(self):
    if not self.current_user:
      self.redirect("/login") 
      return
    name = tornado.escape.xhtml_escape(self.current_user)
    self.write("Hello, " + name)　#current_userが有効なら、Helloメッセージを出す。

class LoginHandler(BaseHandler):
  def get(self):
    self.write('<html><body><form action="/login" method="post">'
                   'Name: <input type="text" name="name">'
                   '<input type="submit" value="Sign in">'
                   '</form></body></html>')

  def post(self):
      self.set_secure_cookie("user", self.get_argument("name"))
      self.redirect("/")

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

```
decolatorを使った書き方もできる。
@authenticatedは```python if not self.current_user: self.redirect()```と同じ作用。

## サードパーティ認証
tornado.authモジュールを使うと、Google, Facebookなどと連携した認証・認可を行える。

## CSRF protection
applicationにxsrf_cookiesを設定する。
```python settings={"xsrf_cookies": True}```
tornadoのアプリが_xsrfクッキーをセットする。正しい値を持たないリクエストを拒否する。POSTメソッドを使う場合、UIModuleのxsrf_form_html()モジュールが使える。
【bcrypt】has_secure_passwordのpresence: trueを解除する方法

# bcrptを利用しているけど、パスワードは任意で設定したい
bcrptで投稿にパスワードを設定できるようにしたのですが、
デフォルトで空の投稿は弾かれるように設定されているため、
それを解除する方法をご紹介します。

※投稿にパスワードを設定する方法は以下で紹介してます。
> https://qiita.com/hiruhiru/items/9dffc729f5192df243a8

## バリテーションを無効にする
has_secure_passwordの横に(validations: false)をつけるだけです。

```ruby:qiita.rb
class Post < ApplicationRecord
  has_secure_password(validations: false)
end
```

## まとめ
バリテーションを無効にすることで空の投稿もできるようになりました。
LIFFで繰り返しログイン画面が表示されるバグに出会った時にやったこと

沼って数日間吹っ飛ばしたのでメモ。未来の誰かの時間を削減できると嬉しい。

## 事象
個人情報が含まれるので動画は投稿できないが、次のようなLINEのログイン画面が半無限に表示されるというバグ。("ログイン"を押してもすぐに再度 `access.line.me` にリダイレクトされる)("半"無限というのは何回もリトライするとタイミング次第でうまく行く時があるから)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150569/70e1e76d-722e-e432-7bda-f70ab86c92fa.png" width="300px" />

## 再現条件
なんらか `liff.init` の時のURLとエンドポイントURL(もしくはエンドポイントURL + アクセス時に後ろについていたパス？)が違う場合に発現する模様です。(正直LIFFのSDK読み込んだわけではないので断言はできない)(難読化されたものを読みたくない & 読めない)

私の場合はランディング後のページ切り替えが速過ぎたせいで再現したみたいでした。(ちなみになぜかAndroidでしか再現しませんでした)
私が今開発しているアプリでは `/` にアクセスした後、ログイン状態によって `/signin` のようなパスに切り替えていました。

## 解決策

解決策のヒントはここに書いてありました。ありがたや
https://www.line-community.me/question/5e956551851f7402cd9699a2/line-app%E3%81%8B%E3%82%89liff-line-me%E3%82%92%E9%96%8B%E3%81%84%E3%81%9F%E5%BE%8C%E3%81%AB%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%81%95%E3%82%8C%E3%82%8B

> LIFFとして登録しているエンドポイントのURLと、
クエリパラメータが付いたURLを比較している行を発見しました。
相違している場合、locationを書き換えている模様です。
（クエリパラメータを付与している時点で100％相違します）

今回の私の事象に対しては、パスを書き換えずにコンポーネントを出し分けるようにしまして、そうすると無事再現しなくなりました。
上記が原因である場合、Androidだけでしか再現しないのが腑に落ちないのでもしかしたら別の原因かもしれないんですが、似たようなバグが出ている場合にお役に立てれば幸いです。
環境変数で配列もハッシュも扱いたい

# how
JSONにして、アプリケーション側でパースする
以上。



# 例
## 環境変数を入れる
### bashで
```shell-session
$ CORS_DOMAINS_JSON='["sample.com", "example.com"]'
```

### docker-compose.ymlで
```yml:docker-compose.yml
    environment:
      - CORS_DOMAINS_JSON=["sample.com", "example.com"]
```

## 環境変数をパースして使う
### railsのrack-corsとか
```rb:config/application.json
    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins JSON.parse(ENV.fetch('CORS_DOMAINS_JSON') { '[]' })
        resource "*",
          credentials: true,
          headers: :any,
          methods: [:get, :post, :options, :head, :patch, :delete]
      end
    end
```

その他各使用環境に合わせてjsonをパースするだけ
きれいで見やすいパワポを作るテクニック

## フォントとフォントサイズ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384142/af428bc0-0b54-c791-5384-abac8a7752f3.png)

ある程度太さがあって、遠目からでも見やすいフォントがよい。

サイズは、必ず24px以上を守る。

32px あたりが見やすい。

作成中、Ctrl+ホイールで25%くらいに縮小してみると、
遠くから見た人の気持ちになれる。


## 色
真っ黒な文字は、目に焼き付くので長時間は見づらい。
少し薄い文字色を使用すると、読みやすい印象になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384142/5ab19092-e7df-9dd9-2086-d4f94bcc4609.png)


## アイコンや画像をつける
1スライドに文字だけだと読みづらい。
関連しているっぽいアイコンをつけるとよし。

![グッジョブの手の無料アイコン2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384142/0f60a5d7-e336-44ac-6266-670704534b16.png)

##### おすすめサイト
ICOOON MONO
https://icooon-mono.com/

## 背景色
背景色は基本白がオススメ。
なぜなら、簡単にダサくなってしまうから。
色を付けるなら、薄い色にする。