utamaro’s blog

誰かの役に立つ情報を発信するブログ

SpringBootでクライアントからの日付をLocalDateTimeで受け取る方法

クライアントから2018-10-19T10:10という文字列を受け取ったときに、LocalDateTimeで受け取る方法を紹介します。

LocalDateTimeで受け取れると何かと便利です。Stringで受け取ると、それをDateに直したりするのが面倒なので、結構使える方法なのではないでしょうか。

それでは、javaのプログラムを紹介します。

プログラム

import lombok.Data;

@Data
public class SampleForm {
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime sampleDateTime;  // '2018-10-19T10:10'
}

controllerの実装の一部です

    // /xxxx?sampleDateTime=yyyy-MM-ddTHH:mm みたいなリクエストを受け取る
    @RequestMapping(method = RequestMethod.GET)
    public String sampleView(SampleForm form) {
        return "sample";
    }

@DateTimeFormatで指定しているDateTimeFormat.ISO.DATE_TIMEのパターンは、yyyy-MM-dd'T'HH:mm:ss.SSSXXXと設定されています。

クライアントからのリクエスト方法は様々だと思いますが、大抵はjsでデータを作ったり、datetime-localを使ったり、datepickerを使ったりしていると思います。

それぞれのフォーマットに合わせてDateTimeFormatを指定しても良いです。

そこらへんは仕様書を作って、フロント側との調整が必要になると思います。

独自のパターンを指定する場合は以下のように指定します。

@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")

SpringBootでPathVariable付きのリダイレクトをする方法

spring bootを使って、@PathVariableがついているurlにリダイレクトする際の方法について紹介します。

設定は、/redirect/fromにリクエストがあった際に、/redirect/to/{id}へリダイレクトするときの書き方です。

それぞれのurlの仕様について説明します。

/redirect/from

  • postでリクエストを受け付ける。
  • /redirect/to/777へリダイレクトする。

/redirect/to/{id}

  • getでリクエストを受け付ける。
  • redirect/to.htmlを表示する。(サンプルではhtmlまでは載せていません。)
  • idを@PathVariableで取得する。

サンプル

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

@Controller
@RequestMapping(value = "/redirect/")
public class ServicePaymentMethodStatusApiController {

    @RequestMapping(value="/from", method = RequestMethod.POST)
    public String redirectFrom(UriComponentsBuilder uriBuilder) {
        UriComponents uriComponent = uriBuilder
                .path("/redirect/to")
                .pathSegment("777")
                .build();
        // ↓ /redirect/to/777 が作成される。
        String redirectPath = uriComponent.toUri().getPath();
        return "redirect:" + redirectPath;
    }

    @RequestMapping(value="/to/{id}", method = RequestMethod.GET)
    public String redirectTo(@PathVariable("id") int id, UriComponentsBuilder uriBuilder) {
        // thymeleafやjspでレンダリング
        return "redirect/to";
    }
}

UriComponentsBuilderを使ってpathを組み立てています。

このbuilderを使わなくてもreturn "redirect:/redirect/to" + "/777"と書くこともできます。

ですが、上記のように書くと他のパラメータを追加する際に困ったことになります。

よほどのことがない限りは避けましょう。

pythonを使ってディレクトリのツリー構造をjsonで表示してみた

何に使えるのかわかりませんが、ディレクトリのツリー構造をjsonで表現するプログラムを紹介します。

サンプルとして用意したティレクトリ構造が以下のものです。

このディレクトリのtreeをjsonにしたいと思います。

dir/
├── dir2
│   ├── file1
│   └── file2
└── dir3
    └── dir4
        ├── file3
        └── file4

プログラム

jsonファイルを作成して保存するまで書こうと考えたのですが、printで表示してもいいかなと。

jsonが表示されれば良いのですし。

def file_tree(path='.'):
    dirs = os.listdir(path)
    buf_child = []
    root = {
        'path': path,
        'name': '',
        'child': buf_child,
    }
    for dir in dirs:
        dir_path = os.path.join(path, dir)
        if os.path.isdir(dir_path):
            # dirの場合
            child = file_tree(dir_path)
            child['name'] = dir
            buf_child.append(child)
        else:
            # dir以外
            buf_child.append({
                'path': dir_path,
                'name': dir,
                'child': [],
            })
    return root


if __name__ == '__main__':
    result = file_tree()
    print(json.dumps(result, indent=4))

再帰的に検索しています。

自分の実装があっていれば、深さ優先探索をしているはずです。

ディレクトリの場合は、もう一度検索して、ファイルのときはchildにデータを入れています。

モノがなくなったら、その結果をjsonにしてprintしてます。

実行結果

これをどうやって使うのか、何に使えるのか、まったく考えていません。

再帰的に検索するプログラムを突発的に作りたくなったのと、直近でtreeコマンドを使ってたのでなんとなく作ってみました。

きっと、このjsonをloadsで読み込んで、再帰的に表示するといい感じにコンソールに表示できるはずです。

{
    "path": "./dir",
    "name": "",
    "child": [
        {
            "path": "./dir/dir2",
            "name": "dir2",
            "child": [
                {
                    "path": "./dir/dir2/file1",
                    "name": "file1",
                    "child": []
                },
                {
                    "path": "./dir/dir2/file2",
                    "name": "file2",
                    "child": []
                }
            ]
        },
        {
            "path": "./dir/dir3",
            "name": "dir3",
            "child": [
                {
                    "path": "./dir/dir3/dir4",
                    "name": "dir4",
                    "child": [
                        {
                            "path": "./dir/dir3/dir4/file3",
                            "name": "file3",
                            "child": []
                        },
                        {
                            "path": "./dir/dir3/dir4/file4",
                            "name": "file4",
                            "child": []
                        }
                    ]
                }
            ]
        }
    ]
}

pythonで楽天apiを実行する方法

楽天の会員登録をする

これは登録画面に沿って登録をするだけです。

登録画面のurlを載せます。

【楽天】ログイン

楽天のアプリIDを発行する

アプリURLはhttp://localhost.comで登録しました。

やり方を調べたところ、自分のgithubのurlを入れるというのも見つけました。

apiを試していたところでは、このurlがlocalhostだったことで困ったことは起きませんでした。

apiを実行するためのプログラムを作成する

RakutenApiというクラスを作成して、継承して使用します。

RakutenApi
├─ Api1
└─ Api2

RakutenApiクラスは個別の共通のパラメータを入れたり、urlやparamsを生成するためのメソッドを用意しています。

import requests

class RakutenApi(object):

    root = 'https://app.rakuten.co.jp/services/api'
    api_path = ''
    common_params = {
        'applicationId': 'xxxxxx',  # アプリID
        'affiliateId': 'xxxxxxx.xxx.xx.xxxxx',  # アフィリエイトID
        'format': 'json',
        'formatVersion': 2,
    }

    def get_url(self):
        return self.root + self.api_path

    def gen_params(self, params):
        cp_params = self.common_params.copy()
        cp_params.update(params)
        return cp_params

    def get(self):
        pass

ジャンル検索apiを作成する

RakutenApiクラスを継承したIchibaGenreクラスを作成します。

↓のドキュメントを見つつ作成しています。 https://webservice.rakuten.co.jp/api/ichibagenresearch/

以下のコードを追加して、実行すると検索できます。

class IchibaGenre(RakutenApi):

    api_path = '/IchibaGenre/Search/20140222'

    genreId = 0

    def get(self):
        params = self.gen_params({'genreId': self.genreId})
        r = requests.get(self.get_url(), params)
        return r.text


def main():
    ichiba_genre = IchibaGenre()
    ichiba_genre.genreId = 200162
    r = ichiba_genre.get()
    print(r)


if __name__ == '__main__':
    main()

どうでもよいですが、classとdefとmainの間を2行空けるとキレイです。

実行結果

市場ジャンルを取得したときの結果です。(一部抜粋)

基本はchildrenを見て、更に絞り込みをします。

{
    "parents": [],
    "current": {
        "genreId": 200162,
        "genreName": "本・雑誌・コミック",
        "genreLevel": 1
    },
    "brothers": [
        {
            "genreId": 101240,
            "genreName": "CD・DVD",
            "genreLevel": 1
        },
        {
            "genreId": 100804,
            "genreName": "インテリア・寝具・収納",
            "genreLevel": 1
        }
    ],
    "children": [
        {
            "genreId": 101266,
            "genreName": "小説・エッセイ",
            "genreLevel": 2
        },
        {
            "genreId": 208663,
            "genreName": "資格・検定",
            "genreLevel": 2
        }
    ],
    "tagGroups": [
        {
            "tagGroupName": "限定特典",
            "tagGroupId": 1001050,
            "tags": [
                {
                    "tagId": 1011603,
                    "tagName": "楽天限定特典付き",
                    "parentTagId": 0
                },
                {
                    "tagId": 1011604,
                    "tagName": "店舗共通特典付き",
                    "parentTagId": 0
                }
            ]
        }
    ]
}

エクセルを使わないでマークダウンでapiのドキュメントを作成する

apiのドキュメントを作成する際にエクセルを使って見栄えの良い(?)ドキュメントを作成することはよくあります。

ですが、以下のことが起きたりします。

  • キレイな表を作成することに力を入れてしまう
  • 更新されない
  • xxx_v1とかxxx_v2というファイルが増える

これをできるだけ起こさないように取り組んでいる方法を紹介します。

自分なりの方法

エクセルではなく、githubのreadmeを使ってapiのドキュメントを管理するようにしました。

マークダウンでドキュメントを作成するライブラリ(blue printやaglio、swagger)を使っても良いのですが、使うために導入が面倒です。

ある程度人にわかるように書けて、更新履歴がわかれば良いです。

どのように作成するかと言うと、↓のように作成します。

(swaggerを真似て、必要なところだけ書き方を模倣している形です)

# タスクを追加するapi

タスクを追加するapiです。

## /api/task

method: post

request_params
name:
    type: str
    valid:
        not blank

response
result:
    type: bool
message:
    type: str

この方法のメリットとしては

  • ドキュメントを作成するのに疲弊しない
  • 管理が楽になる(xxx_v1とかxxx_v2というファイルが増えない)

デメリットとしては

  • ドキュメントのフォーマットが安定しない
    • 一度フォーマットが決まれば、コピペである程度回避できるが
  • 表を作成しようとすると|で作ることになるので面倒

プログラミングを勉強するおすすめの方法

プログラミングを勉強する際の、おすすめの方法を紹介します。

私の場合、新しい言語を勉強する場合はじゃんけんゲームを作成します。

じゃんけんゲームを作成する理由は↓です。

理由

  • じゃんけんは簡単なアルゴリズムである
    • アルゴリズムを考えて悩むことが少ない
    • 人に実装方法を聞く場合でも、説明しやすい
  • オンライン対戦ゲームにしたり、複数人で対戦できるようにしたり、相手のAIを考えたりとできることがたくさんある
  • 基本文法を全て試せる
    • 変数
    • if文
    • for文
    • while文
    • switch文
    • etc...
  • cuiでの実装、guiでの実装どちらもできること

slackと合わせて、botとじゃんけんしてもよいです。

予めじゃんけんのアルゴリズムを組み、他の人とアルゴリズム勝負をしても良いかもしれません。

1回だけじゃんけんをするというプログラムから、実装が難しいゲームまでじゃんけんで勉強できるのです。

何かを説明する際に気をつけていること

私は、誰かに何かを説明することがとても苦手です。

それも、人と会話することが少なく、噛み噛みなためなのですが...

そんな私が説明時(1対1、もしくは1対多のとき)に気をつけていることを紹介します。

気をつけていること

気をつけていることは3つあります。

  • 話す前に見出しを紙に書くこと
  • 話しつつ図を書くこと
  • 話の区切りごとに相手の顔を見ること

それぞれについて説明します。

話す前に見出しを紙に書くこと

相手と何を話すのか、予め見出しを作っておきます。

すると、話す内容を大まかに想像することができます。

そのときに、実は話さなくていい内容があったりするので割と重要です。

(普通の人は話しつつこれができているらしいですが、、、私はできないです)

話しつつ図を書くこと

話をしつつどこかに図を書きます。

コピー用紙でも良いですし、ホワイトボードでも良いです。

とにかく、相手が見て分かる図を書きます。

例えば、「庭に豚が1頭、鶏が3羽、と牛1頭います。そして…」という説明を口頭で話したとします。

すると、相手は頭の中でそのイメージを浮かべます。

さらに、そのイメージを持ちつつ、「そして…」のあとに続く説明を理解しなければなりません。

ですが、豚と鶏と牛のそれぞれの頭数を見える位置に書いておくことで、イメージするための労力が要らなくなります。

また、こちらも自分が話している内容を確認しつつ話せるので漏れが少なくなります。

さらに、話が脱線したあとも戻って気安くなります。

ただ、話のスピードがどうしても遅くなってしまいます。

伝達漏れや、認識の齟齬による再説明とどちらが良いか考えると、何度も説明するよりゆっくり説明したほうが良いと考えています。

話の区切りごとに相手の顔を見ること

相手が理解しているかどうかを顔を見て判断します。

もし理解できていなさそうだったら、今の説明についてどう思ったか聞くことにしています。

理解できていない場合は感想があやふやです。

理解できているときは、突っ込んだ意見が聞けます。

話の理解度を適宜確認しつつ、できるだけ認識のズレをなくすのが目的です。

要するに、自分勝手に話さないことです。

まとめ

話すことも、書くことも苦手な私ですが、「説明は」わかりやすいと言われます。

「説明は」のところに何か含みがある気がしますが…

以上が、私が説明時に気をつけていることです。