【Node.js】Expoプッシュ通知を一斉送信するWebAPI｜配列を任意の数に分割をする

# はじめに
現在Expo ReactNativeを使ってネイティブアプリ開発をしています。
Expoを使ってプッシュ通知を送るためにプッシュ通知のTokenを1つ、１つPOSTリクエストを飛ばしてもいいのですが、Expoの仕様では最大100個のTokenをまとめてPOSTできる。そこでN個ずつに配列を分割して二次元配列にして処理する方法を紹介します。

# 例
以下のような長さ75の配列があるとする、それを10分割して余った分は10個未満になっても分割をする。
今回は長さ75の配列を10個づつ分割して二次元配列にする。

## 処理前
<img width="408" alt="スクリーンショット 2020-08-12 午後6.59.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252959/7aff717b-c971-14f9-165c-6e7a6f0a7251.png">

## 処理後
<img width="227" alt="スクリーンショット 2020-08-12 午後7.02.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252959/a14e6bde-3b56-ee0f-5738-be485fd90f78.png">


## コード
```js
const main = () =>{
  //TOKENにみたて複数の数字を配列にセット
  let arr = []
  for (let i = 0; i < 75; i++) {
    arr.push(i)
  }

  console.log(arr)

  let result_arr = []

  //分割数
  const division_count = 10

  //配列の数を分割数で割った値を切り上げた値
  const add_arr_count = Math.ceil(arr.length / division_count)

  
  //add_arr_countの分だけ空配列を追加する
  for (let i = 0; i < add_arr_count; i++) {
    result_arr.push([])
  }
  
  
  //いい感じに分割して配列にいれる
  arr.forEach((item,index)=>{
    result_arr[Math.floor(index/division_count)].push(item)
  })

  console.log(result_arr)
}


(async () => {
  main()
})()

```

# Expoを使って実際に通知を送る
私の環境ではNode.jsを使ってWebAPIを作り、まとめて送信できるようにしています。
また、Token一覧を管理しているのはMySQLを使っています。

**Request body**に
プッシュ通知のタイトルを設定する``push_title``とプッシュ通知の本文部分にあたる``push_body``載せてください

※本物のコードは外部から実行できないようにセキュリティ対策をしています

## コード

```js
const express = require('express') // expressモジュールを読み込む
const cors = require('cors') //クロスドメインでアクセスを許可する系のやつ
const bodyParser = require('body-parser') //いい感じにGET POSTを解釈するやつ
const util = require('util') // SQL Async/Await
const axios = require('axios')//npm install axios
const mysql = require('mysql')
const multer = require('multer') // multerモジュールを読み込む これがないとBODYの中身をうまく読み取らない

//↑ 各種、「npm install」してください
//↑ 各種、「npm install」してください
//↑ 各種、「npm install」してください
//　例1 npm i express
//　例2 npm i cors

const app = express() // expressアプリを生成する

app.use(bodyParser())
app.use(express.static('web')) // webフォルダの中身を公開する
app.use(cors())//CROS許可
app.use(multer().none()) // multerでブラウザから送信されたデータを解釈する

//サーバの受付ポート番号、SQL接続情報、設定ファイル読み込み
const config = require('./server_config.json')

// config.server.portのポートでサーバを立てる
app.listen(config.server.port, () => console.log('Listening on port ' + config.server.port))

//MySQL接続情報Async/Await★★★★★★★★★★★★★★★★★★★★★★★★★★★★
//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
const pool_config = {
  host: config.sql.host,
  user: config.sql.user,
  password: config.sql.password,
  port: config.sql.port,
  database: config.sql.database,
  timezone: config.sql.timezone //timezoneの指定省略の場合はシステムローカルになる
}
//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

//汎用的にMySQLを発行する
const runQuery = async (sql, data) => {
  console.log('runQuery...')
  console.log('sql: ' + sql)
  console.log('data: ' + data)

  const pool = mysql.createPool(pool_config)
  pool.query = util.promisify(pool.query)//これないと動かない

  const run_sql = mysql.format(sql, data)
  console.log('発行されたquery:\n' + run_sql + '\n')

  const result = await pool.query(run_sql)
  // console.log(JSON.stringify(result))
  pool.end() // mysqlのコネクションのプロセスを終了させる。
  return result
}

const uuid = require('node-uuid')

app.post('/api/v1/expoPushNotice', (req, res, next) => {
  (async () => {
    console.log('/api/v1/expoPushNotice...')
    
    //私の環境ではMySQLでpush_tokenというTableを作ってそこでTokenを保存しています
    let push_token_list = await runQuery(
      'select `push_token` from `push_token`',
      []
    )

    //通知を送るtoken
    let to_tokens = []
    //分割数
    const division_count = 100
    //配列の追加数 切り上げた値の数だけ配列を追加
    const add_arr_count = Math.ceil(push_token_list.length / division_count)

    console.log('add_arr_countの文だけから配列を追加する')
    //add_arr_countの文だけから配列を追加する
    for (let i = 0; i < add_arr_count; i++) {
      to_tokens.push([])
    }

    push_token_list.forEach((item, index) => {
      to_tokens[Math.floor(index/division_count)].push(item.push_token)
    })

    console.log('to_tokens: ')
    console.log(to_tokens)

    //APIコール
    console.log('pushSend...')
    const req_url = 'https://exp.host/--/api/v2/push/send'

    const headers = {
      headers: {
        Accept: 'application/json',
        'Accept-encoding': 'gzip, deflate',
        'Content-Type': 'application/json',
      },
    }
    //分割した分で別々にPOSTリクエストを飛ばす
    for (let i = 0; i < to_tokens.length; i++) {
      
      //bodyに好きなデータを載せると、アプリ側でいろいろできます。
      const result = await axios.post(
        req_url,
        {
          'to': to_tokens[i],
          'sound': 'default',
          'title': req.body.push_title,
          'body': req.body.push_body,
          'data': {
            
          },
        },
        headers
      )

      console.log('result: ')
      console.log(result)
    }


    res.json({
      status: 200, //
      message: 'リクエストは正常に受信されました',
    })

  })(res, next).catch((e) => {
    console.log('サーバエラー :' + e)
    console.log('res: ' + res)

    res.json({
      status: 500,
      message: 'サーバエラー: ' + e,
    })
  })
})

```

## Tokenを保存しているDB情報
```sql
CREATE TABLE `push_token` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `push_token` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `push_token` (`push_token`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

```

## サーバの設定ファイルを記述しているJSONファイル
```json
{
  "sql": {
    "host": "localhost",
    "user": "hoge",
    "password": "hoge",
    "port": 8889,
    "database": "fuga",
    "timezone": "jst"
  },
  "server":{
    "port": 3003
  }
}

```