Spring Boot で Pager Helper 作った

View の Pagination の表示ロジック系が色々めんどくさかったので作った。

参考にたやつはこちら

qiita.com

public class Pager<T> {

    private static final int DISPLAY_PAGE_NUM = 5;

    private static final int DEFAULT_PAGE_POSITION = 3;

    private String url;

    private String queryString;

    private Page<T> page;

    private List<PageItem> items;

    public Pager(ServletWebRequest request, Page<T> page) {
        this(request, page, DEFAULT_DISPLAY_NUM, DEFAULT_PAGE_POSITION);
    }

    public Pager(ServletWebRequest request, Page<T> page, int displayPageNum, int pagePosition) {
        url = request.getRequest.getRequestURI();
        this.page = page;

        queryString = request.getParameterMap().entrySet().stream().filter(e -> !e.getKey().equals("page")).map(e -> e.getKey() + "=" + e.getValue()[0]).collect(Collectors.joining("&"));

        int currentPage = page.getNumber() + 1;
        int totalPageNum = page.getTotalPages();
        int start = 0;
        int end = totalPageNum;
        if (page.getTotalPages() <= displayPageNum) {
            start = 1;
        } else {
            start = (currentPage - pagePosition > 1) ? currentPage - pagePosition : 1;
            if (start + (displayPageNum - 1) > totalPageNum) {
                start = totalPageNum - (displayPageNum - 1);
            }
        }
        if (end > displayPageNum)  {
            end = start + (displayPageNum - 1);
        }

        items = IntStream.rangeClosed(start, end)
                         .mapToObj(i -> new PageItem(url, i - 1, (i - 1) == page.getNumber()))
                         .collect(Collectors.toList());
    }

    public boolean isFirst() {
        return page.isFirst();
    }

    public boolean isLast() {
        return page.isLast();
    }

    public List<T> getContent() {
        return page.getContent();
    }

    public List<PageItem> getItems() {
        return items;
    }

    public int getPrevPageNumber() {
        return page.previousPageable().getNumber();
    }

    public int getNextPageNumber() {
        return page.nextPageable().getNumber();
    }

    public String getPrevUrl() {
        return getUrl(getPrevPageNumber());
    }

     public String getNextUrl() {
         return getUrl(getNextPageNumber());
     }

     private String getUrl(int number) {
         String s = url + "?page=" + number;
         if (!Strings.isNullOrEmpty(queryString)) {
             s += "&" + queryString;
         }
         return s;
    }

    public class PageItem {

        private String url;

        private int number;

        private boolean current;

        public PageItem(String url, int number, boolean current) {
            this.url = url;
            this.number = number;
            this.current = current;
        }

        public int getNumber() {
            return number + 1;
        }

        public String getUrl() {
            return url;
        }

        public boolean isCurrent() {
            return current;
        }

    }

}

Spring Boot で Pagination

最近 Spring Boot をやっていて Pagination とかを試していてひと段落ついたのでメモ

前提として JPA 使用。

pagination をしたい時には基本的 repository に PagingAndSortingRepository を使う。

where と一緒に使いたい時には JpaSpecificationExecutor も使う。

Model

@Entity
@Table(name = "users")
public class User {

    @Id
    private Integer id;

    @Column
    private String name;

}

Repository

public interface UserRepository extends PagingAndSortingRepository<User, Integer>, JpaSpecificationExecutor<User> {
}

Service

@Service
public class UserService {

    private final UserRepository repository;

    public UserService(final UserRepository repository) {
        this.repository = repository;
    }

    public Page<User> search(Integer id, String name, Pageable pageable) {
        return repository.findAll(Specifications.where(idEquals(id)).and(nameEquals(name)), pageable);
    }

    private Specification<User> idEquals(Integer id) {
        if (id == null) {
            return null;
        }
        return (root, query, cb) -> cb.equals(root.get("id"), id);
    }

    private Specification<User> nameEquals(String name) {
        if (name == null || name.isEmpty()) {
            return null;
        }
        return (root, query, cb) -> cb.equals(root.get("name"), name);
    }

}

Controller

@Controller
@RequestMapping("users")
public class UserController {

    private final UserService service;

    public UserController(final UserService service) {
        this.service = service;
    }

    @GetMapping("")
    public String index(Model model, @RequestParam(value = "id", required = "required") Integer id, @RequestParam(value = "name", required = "required") String name, @Sort@PageableDefault(size = 20) Pageable pageable) {
        Page<User> users = service.search(id, name, pageable);
        model.addAttribute("id", id);
        model.addAttribute("name", name);
        model.addAttribute("users", users);
        return "users/index";
    }

}

Specification<T> を作成するところで null を返すと条件文を作成しない。

※参考

qiita.com

GAE/Go で LINE Bot を作ってみた

LINE Bot Trial に登録してから、何にも試してもいなかったので LINE Bot を作ってみた。とりあえず echo bot から。

LINE Bot Trial についてはググってください(今は登録できるのかな)。

Go で書きたい & 簡単に管理したいってことで GAE/Go でやってみました。 参考にしたのはこれ。

qiita.com

普通に大量のメッセージが来た時に捌けないので(遊びだから捌く必要はないが)、 callback を受けた時には、メッセージ送信まで行わず、タスクキューにキューを積んでそれでメッセージ送信などを行った。このフローは基本的に鉄則です。

qiita.com

そこまでネットで調べたりだが実装では詰まることはほとんどなかった(LINE の API を叩くのは SDK を使った)。

唯一あったのは http の default client じゃダメだってこと。

下記がコード

gist.github.com

ISUCON6 に参加して負けてきた。

去年同様 gacharion と一緒に ISUCON6 に参加してきました(土曜日)。

担当的には、アプリ 自分で、インフラ gacharion って感じです。 結果は全然ダメで、 最高で約 7K しかでなかった。

最終的なコードはこんな感じ。

github.com

やったこと

  • html の base の継承とか element をやめて一枚のファイルにした。
  • keyword のリストとってくるところで、 filesort するし、コード見ても keyword のリストしか入らないじゃんと思い、 distinct(keyword) にした(最終的には最長マッチが先にするための order by だったのに気づいてやめた)
  • isutar の isuda への統合。
  • star はオンメモリのみ(永続化なし)で行けるなと思い(initialize で truncate しているので)、オンメモリでやった。
  • htmlify のキャッシュ(メモリもあまりすぎているしと思って、結構雑)
  • キーワードをオンメモリ
  • 正規表現をキーワードのリストで回して replace に置き換える

考えたこと

  • htmlify の結果を DB に保存して使えばいけると思ったが、キーワード追加した時にどうするかの案が思いつかずやめ
  • initialize で全てのエントリに対して htmlify をかけてキャッシュをきかせる(5 秒じゃ無理だと思い、 goroutine でやる。ずっと CPU 100 % で張り付いてしまうのでやめた)
  • 差分で置換をすれば早くなると思ったが、キーワード追加のことを考えると無理となってやめた

ほとんどの時間、 htmlify でどうすればいいかを考えていたりしたがいい案が思いつかずにタイムアップ。

自分もずっと htmlify のことだけで、他の案を考えなかったので、何も指示も出せず gacharion がすることがほとんど何もすることができなかった気がする(やっぱり何かまとめ役が必要な気がする)。

キーワード追加の対応を諦め、事前キャッシュとかしていれば 10K は行けたのかなと思ったりはした(それでも普通に敗退だけど)。

strings.Replacer について知っておけばもうちょっとマシな感じにはなったかもしれない。

予選通過の人のやっていたことを見ると、やっていることや考えていたことは間違ってはいない感じがしたので良かったかなと思ったりはしている。その対応の精度とか深さとかは全然相手になるレベルではないけど。

面白い問題で楽しめたので良かった。

また来年もあったら参加したい。

運営の皆様お疲れ様でした。

みんなの Go

Go 好きならみんな読むだろう「みんなの Go」を読んだのでざっと感想を。

みんなのGo言語[現場で使える実践テクニック]

みんなのGo言語[現場で使える実践テクニック]

目次

  • 第1章 Goによるチーム開発のはじめ方とコードを書く上での心得
  • 第2章 マルチプラットフォームで動作する社内ツールのつくり方Windows
  • 第3章 実用的なアプリケーションを作るために
  • 第4章 コマンドラインツールを作る
  • 第5章 The Dark Arts Of Reflection
  • 第6章 Goのテストに関するツールセット

読んでみた感想ですが、やっぱり現場で使っている人の知見が詰まっていて為になるなと思いました。

自分も普段書いていたりしているので知っていることも多かったですが、さらに知識を深めることができたかなと思います。 特に5 章の部分は、基本的に reflect を使うことがなかったので、ほとんど知らないことだらけでした。 今後自分が実際使うことは少ないと思いますが、ライブラリとかになると使っているところもある気がするので、読むときには役立つ気がします。

詰まったときに、見返すと解決できたりするので、手元には置いておきたくなる本です。

Web アプリケーションとかの話(DB のコネクションとかセッションとか)があったりするともっといいなとは思いました(それだけで一冊できますが)。

AWS SNS を使用して FCM 経由で Push Notification

これから Android に対して Push 通知を行おうとした場合、 Google Cloud Message (GCM) ではなく、 Firebase Cloud Message (FCM) を使用すると思います。

AWS SNS を使用する場合、少しだけ GCM の場合と違うので、そのメモ。

FCM の場合の AWS SNS 設定は GCM で設定して大丈夫です。

AWS SNS のコンソールから Push 通知を行う場合、 GCM の JSON 形式は以下のようになると思います。

{
    "GCM": {
        "data": {
            "message": "Push!!!"
        }
    }
}

これだと、 Push は来るが、クライアント側でエラーが出ました(たまたまでははず)。 下記の場合だと、正常に動作しました。

{
    "GCM": {
        "notification": {
            "text": "Push!!!"
        }
    }
}

これに書いてある通りにやったらできました。

stackoverflow.com

pythonのTestでテストケースの動的追加

チェック処理は同じでデータが違うときなどにいちいちtest_aaa、test_bbbと定義するのがめんどくさい時に以下のようにすると楽になる話。

from unittest import TestCase

import requests


class Test(TestCase):
    pass


for user_id in xrange(1, 10):
    def wrapper(user_id):
        def f(self):
            response = requests.get('/users/{}'.format(user_id))
            self.assertEqual(response.status_code, 200)
        return f

    setattr(Test, 'test_user_{}'.format(user_id), wrapper(user_id))