Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ShareDocs の紹介

benjamin
September 22, 2017
160

ShareDocs の紹介

benjamin

September 22, 2017
Tweet

Transcript

  1. 三嶋拓 株式会社アットウェア 2017/09/22

  2. 概要 • ナレッジ共有ツール • 2017/01 に OSS として公開 ◦ もともとは社内で使ってたナレッジ共有ツール

    ◦ 「Scala で実装した社内ナレッジ共有ツールを OSS にして公開しました」 ▪ http://tech.atware.co.jp/sharedocs/ • github リポジトリ ◦ https://github.com/atware/sharedocs
  3. 話すこと • ShareDocs とは ◦ 作った経緯など ◦ なにができるのか • とりあえず実装しておきたい追加機能の実装例

    ◦ 記事投稿時の通知 ◦ 記事へのコメント時の通知 ◦ コントリビューション数の監視と通知 ShareDocsの紹介します ※ SkinnyFrameworkの話はあまり出てこないです m(_ _)m
  4. ShareDocs http://tech.atware.co.jp/sharedocs/

  5. ShareDocs https://www.slideshare.net/RyujiYamashita/skinny-framework-scala/7

  6. ShareDocs • とりあえずアプリケーションの動きを見たい • 環境構築 ◦ README 見る ◦ 手順通りにやると

    ./sharedocs db:migrate でエラー吐く ▪ SharedocsEnv を作ってからやり直し • 必要に応じて環境変数を変える ◦ LOGIN_PROVIDOR=google # google+、LDAP、メール ◦ DATABASE****
  7. ShareDocs https://www.slideshare.net/RyujiYamashita/skinny-framework-scala/16

  8. ShareDocs • 自分が開発する時に参考にしたもの ◦ Skinny Framework Getting Started 日本語版 ▪

    http://seratch.hatenablog.jp/entry/2016/05/23/220911 ▪ 流れで ShareDocs も見れるようになる ◦ Skinny Framwework - Dependency Injection - ▪ http://skinny-framework.org/documentation/dependency-injection.ht ml • とりあえず controller.Controllers のコードを掘っていけばいい
  9. とりあえず追加で入れときたい機能 • 記事が投稿された時に Slack に通知する • 記事にコメントがついたときに Slack に通知する •

    コントリビューション数が一定閾値に達した時に Slack に通知する あるともうちょい盛り上がれる
  10. 記事投稿時 & 記事コメント時の通知 • ArticlesController#create • 記事作成時に ExternalServiceIntegration#onPostCreated class ArticlesController

    extends ApplicationController { def create = { ... // Notify to external service val externalServiceIntegration = inject[ExternalServiceIntegration] externalServiceIntegration.onPostCreated(loginUser.get, article) ... } } }
  11. 記事投稿時 & 記事コメント時の通知 • ****Integration という名前のクラスを探して DI • **** の部分は

    SkinnyConfig で読む class IntegrationsModule extends scaldi.Module { private val DEFAULT_SERVICE = "Null" val service = SkinnyConfig.stringConfigValue("externalIntegration.service").map { configValue => configValue.capitalize } getOrElse DEFAULT_SERVICE import scala.reflect.runtime.universe val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader) val module = runtimeMirror.staticModule(s"integration.${service}Integration") val obj = runtimeMirror.reflectModule(module) val integration = obj.instance bind[ExternalServiceIntegration] to integration.asInstanceOf[ExternalServiceIntegration] }
  12. 記事投稿時 & 記事コメント時の通知 • sharedocs.conf • **** の部分は sharedocs.conf(application.conf) で指定する

    これ
  13. 記事投稿時 & 記事コメント時の通知 • ExternalServiceIntegration という抽象クラスを継承したクラスを作成する • onPostCreated ◦ 記事が投稿された時にしたい処理を実装する

    • onCommentCreated ◦ 記事にコメントがついた時の処理を実装する • デフォルトでは NullIntegration という実装が使われる(何も処理しない) ◦ ****Integration の実装は環境変数 EXTERNAL_INTEGRATION_SERVICE=**** で指定する object NullIntegration extends ExternalServiceIntegration { override def onCommentCreated(commenter: User, comment: Comment)(implicit ctx: SkinnyContext): Unit = {} override def onPostCreated(author: User, article: Article)(implicit ctx: SkinnyContext): Unit = {} }
  14. 記事投稿時 & 記事コメント時の通知 • Slack の Web API 呼ぶ •

    https://api.slack.com/methods/chat.postMessage class Slack(channelName: String, token: String, botImageUrl: String) extends JSONStringOps with Logging { def post(message: String): Unit = { val request = Request(Slack.urlChatPostMessage) .queryParams("token" -> token) .queryParams("channel" -> channelName) .queryParams("text" -> message) .queryParams("icon_url" -> botImageUrl) .enableThrowingIOException(true) val response = HTTP.post(request) if (response.status == 404) logger.info(s"Failed to post a message to the channel $channelName") else logger.info(response.textBody) } }
  15. 記事投稿時 & 記事へのコメント時の通知 • 記事投稿時 object SlackIntegration extends ExternalServiceIntegration with

    LoggerProvider { override def onPostCreated(author: User, article: Article)(implicit ctx: SkinnyContext): Unit = { import scala.concurrent.ExecutionContext.Implicits.global val articleUrl = s"""${Util.baseURL(ctx.request)}/articles/{article.articleId}?${OGP_PARAM_NAME}""" val message = s""" |${author.name}さんが新しい記事を投稿しました! |「${article.title}」 |$articleUrl """.stripMargin Slack().postAsync(message) }
  16. 記事投稿時 & 記事へのコメント時の通知 • 記事へのコメント時 override def onCommentCreated(commenter: User, comment:

    Comment)(implicit ctx: SkinnyContext): Unit = { val article = Article.findById(comment.articleId).get val articleUrl = s"""${Util.baseURL(ctx.request)}/articles/${comment.articleId}#comment-${comment.commentId}""" val message: String = s""" |${commenter.name}さんが${author.name}さんの記事にコメントしました! |「${article.title}」へのコメント |$articleUrl """.stripMargin Slack().postAsync(message) } }
  17. • SkinnyWorker • 定期実行させたい処理を作るときに使える • コントリビューション数が一定閾値に達するごとにチャットに通知したい コントリビューション数を監視して通知

  18. • SkinnyWorker を継承したクラスを作成する • init() に初期化時の処理を実装する コントリビューション数を監視して通知 class ContributionCelebrationWorker extends

    SkinnyWorker { val THRESHOLD = 5 val BASE_URL = "http://localhost:8080" val cache = new Cache[UserId, Long]() def init(): Unit = { User.findAll().map(u => u.userId).foreach(userId => { val contribution = User.calcContribution(userId) cache.put(userId, contribution) }) } }
  19. • 全ユーザのコントリビューション数を取得する • 閾値チェック & 通知 & キャッシュ コントリビューション数を監視して通知 override

    def execute(): Unit = { def shouldCelebrateContribution(before: Long, now: Long): Boolean = (before / THRESHOLD < now / THRESHOLD) && (now % THRESHOLD > 0) User.findAll().foreach(user => { val contribution = User.calcContribution(user.userId) cache.get(user.userId).foreach(before => { if (shouldCelebrateContribution(before, contribution)) { val message = s""" |${contribution / THRESHOLD * THRESHOLD} Contribution! | ${user.name} |$BASE_URL/users/${user.userId}""".stripMargin Slack().post(message) }}) cache.put(user.userId, contribution) })
  20. • Bootstrap クラスで worker を起動する • skinnyWorkerService.everyFixedSeconds(worker, 30) などで実行するタイミン グを指定する

    コントリビューション数を監視して通知 class Bootstrap extends SkinnyLifeCycle { scalikejdbc.GlobalSettings.loggingSQLAndTime = scalikejdbc.LoggingSQLAndTimeSettings( singleLineMode = true) val worker = new ContributionCelebrationWorker override def initSkinnyApp(ctx: ServletContext): Unit = { skinnyWorkerService.everyFixedSeconds(worker, 30) ctx.mount(classOf[SkinnySessionInitializer], "/*") Controllers.mount(ctx) } }
  21. その他 • メール認証できてなかった問題 ◦ http://miyako.hatenablog.jp/entry/2017/02/13/222604 ▪ > 「実はメール認証した時にメールが飛ばない(--;)」 ◦ docker

    イメージにも SMTP サーバ含まれてない ▪ 入れる予定 • タイポとかの指摘はコメントするまでもないので private に伝えられる機能が欲しい
  22. まとめ • ShareDocs は社内ナレッジ共有ツールとして使える OSS • SkinnyFramework 製で、ベター Java として

    Scala 触りたい時にも使える • ちょっとした追加機能の実装例 ◦ 記事投稿時 & 記事へのコメント時の通知 ▪ ExternalServiceIntegration 抽象クラスを継承したクラスを作成する ▪ sharedocs.conf の externalIntegration.service でクラス名のプレフィックスを指定する ▪ 指定したクラスを DI して使ってくれる ◦ コントリビューション数の監視 ▪ 定期実行系の処理は SkinnyWorker を使うのが手っ取り早い ▪ Bootstrap クラスで worker を起動する ▪ SkinnyLifeCycle.skinnyWorkerService#everyFixedSeconds などで実行するタイミングを指 定する