【クソアプリ】江戸時代もDX！?上様への納品物の領収証をPDFで発行してもらえるサービス

この記事は「クソアプリ Advent Calendar 2023 その２」の１１日目の記事です。

https://qiita.com/advent-calendar/2023/kuso-app

# 作ったもの

https://uesama-app.goosys.workers.dev/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/65f78c64-7267-dc48-d01c-c22b1fdd6279.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/7b322e78-fda7-dac4-6591-72e3b0513607.png)

- **上様に何か納品したとき**にお使いください
- 領収書（覚書）をPDFでダウンロードできます
- ※注意事項
    - 金額はランダムです
    - 日付は空です

# モチベーション

PDFで書類を生成するシステムってよくあると思うのですが、地味に大変ですよね。
各言語で各種便利なPDF操作ライブラリがあって、それにデータを埋め込みつつ書類を作って、いざ書き出してみたら盛大にずれてたとか、デプロイした環境に日本語フォントがないだとか。

なんかもっと簡単にできないかなと思ってたのですが、**HTMLで書類を作ってブラウザで印刷プレビューして、その内容がそのままPDFになれば**いろいろ楽なのではないかと思いました。

書類のHTMLのURLさえあれば、そのURLをヘッドレスChromeあたりが見に行ってくれてPDF保存してくれるといったことが可能ではないかなと。
そうすると、今後書類の種類が増えたとしても、HTMLベースの書類システムを作ってデプロイするだけでよく、特別なライブラリもフォントも何も要らずに誰でもささっと作ってしまえるといったことが実現できるのではないかなと。

# アーキテクチャ

![上様アーキテクチャ.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/808c4308-5c8a-f781-e038-cc7f078fd4be.png)

## PDF化システム

- AWS Serverless application (SAM)
- ランタイムはPython 3.9
- Docker Imageで起動できるやつ
    - いろいろ試しましたが上手くいかず、結局こちらをほぼそのまま使っています
    - https://qiita.com/sugo/items/7edf86482dcd837e34de
    - （ありがとうございます :bow: )
- ヘッドレスChromeとChrome DriverとSeleniumをインストール
- Lambdaのメモリは多めに（2048とか）
- API GatewayのAPI設定で「バイナリメディアタイプ」を設定しないとダウンロードしたPDFが壊れて開けないので注意

SAMでHelloWorldを新規作成して、適当にこんな感じ

```python:app.py
import base64
import json
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import subprocess
import os

def lambda_handler(event, context):
    options = webdriver.ChromeOptions()
    options.binary_location = '/opt/chrome/chrome'
    options.add_experimental_option("prefs", {
        "download.default_directory": "/tmp/download/"
    })
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument("--disable-gpu")
    options.add_argument("--single-process")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--disable-dev-tools")
    
    service = Service(executable_path="/opt/chromedriver")
    driver = webdriver.Chrome(options=options, service=service)

    # driver.get("https://goosysapp.net/")
    driver.get("https://uesama-print.goosys.workers.dev/")

    print(driver.title)
    
    parameters = {
        "printBackground": False,
        "paperWidth": 8.27,
        "paperHeight": 11.69,
        # "displayHeaderFooter": True,
    }

    pdf_base64 = driver.execute_cdp_cmd("Page.printToPDF", parameters)
    content = pdf_base64["data"]
    
    return {
        "statusCode": 200,
        "headers": {
            'Content-Length': len(content),
            'Content-Type': 'application/pdf',
            'Content-disposition': 'attachment;filename=uesama.pdf'
        },
        "isBase64Encoded": True,
        "body": content
    }
```

## サービスアプリケーション

- Cloudflare workersで適当に
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/8b64785a-2dd3-0dab-79e8-6a95e55949e0.png)
- https://uesama-app.goosys.workers.dev/

## 書類生成システム

- 同じくCloudflare workersで適当に
- https://uesama-print.goosys.workers.dev/
- 日本語はWebfontを指定
  - https://fonts.google.com/specimen/Yuji+Syuku?subset=japanese&preview.text=%E4%B8%8A%E6%A7%98&query=yuji&noto.script=Hira

# 所感

結構いい感じに出来たと思います。 :raised_hands:
（認証一切してないし、金額もランダムでDBも使ってないですが、その辺は実際にちゃんと作るときに考えればよいということで）

作っていて気付いたのですが、これ、印刷対象のHTML内で普通にJSが動きました。（金額をJSでランダム生成している）
なのでVueでもReactでも使ってデータを埋め込んでしまうことができるので、ReactToPDFとかやるよりワンステップ少なくて良いんじゃないかなと。

書類のレイアウトを作るのにも特殊技能が不要（HTMLだけ）なので、非エンジニアがExcelとかFigmaとかで作ったレイアウトをHTML変換したものにエンジニアがデータ埋め込みだけ作ればいいみたいな作業分担も可能なのかなと。

また、書類生成システムの方をCloudflare workersにしたのが天才的でした。
Web上のQuiq editで直接編集して即反映できるので、レイアウトの微調整も非エンジニア（HTMLちょっとわかる）の担当にしてしまえるかも。

あとは、これを基盤にして、作りたい書類が増えたらまたCloudflare workersでもpagesでもamplifyでもNetlifyでも好きなところにデプロイ（ちゃんと認証は作るんやで）して書類URLさえ作ってしまえば、同様にいくらでもPDFが作れるかなと。

# おわり

冒頭でアドカレの１１日目ですと言っていましたが、
只今、１２日目の日付になってしまいました。（12日02時19分）

・・・遅刻すみません :bow: