使用 Clojure 建立个人网站(二十三)

在做一个项目中,我们会时不时的把我们的代码重构,重构的目地就是让代码更清晰,K.I.S.S 原则是我们一直追求的,同时 Clojure 最出名的就是 sample made easy,所以我们要时不时的重构,我们也应用了 clojure 常见的 reloadable 风格,还有出名的 The Twelve-Factor App

我们要根据不同的环境来分配不同的配置,首先我们要加载不同的配置。这里我们要用到一个库来读取配置,cprop 库是我们使用的依赖库 mount 作者开发的另一个库,用来方便的加载配置文件,首先先加入库:

[cprop "0.1.13"]

然后在 src/clj 新建 soul-talk.config 命名空间,并加入读取配置的函数,同样的配置的读取也用 mount 启动:

(ns soul-talk.config
  (:require [mount.core :refer [args defstate]]
            [cprop.core :refer [load-config]]
            [cprop.source :as source]))

(defstate env :start (load-config
                       :merge
                       [(args)]))

然后我们在项目目录下 env/dev/clj 下新建 soul-talk.env 命名空间,先加入一个初始化的配置:

(ns soul-talk.env
  (:require [taoensso.timbre :as log]
            [selmer.parser :as parser]))

(def defaults
  {:init
   (fn []
     (parser/cache-off!)
     (log/info "====[soul_talk started successfully using the development profile]===="))
   :stop
   (fn []
     (log/info "====[soul_talk has shut down]====="))})

然后在 env/dev 下新建 resources 目录,这里用来放开发的资源文件,在这个目录下新建 config.cdn 文件:

{:dev true
 :port 300}

同时我们在这个目录下也建立 logback.xml 的配置,记录我们的日志:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type  ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>log/soul_talk.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/soul_talk.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- keep 30 days of history -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
        </encoder>
    </appender>
    <logger name="org.xnio.nio" level="warn">
        <AppenderRef ref="STDOUT"/>
        <AppenderRef ref="FILE"/>
    </logger>
    <logger name="com.zaxxer.hikari" level="warn">
        <AppenderRef ref="STDOUT"/>
        <AppenderRef ref="FILE"/>
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

然后我们要修改 src/clj 下 soul-talk.handler 中的函数,并且加入初始化的配置读取:

(ns soul-talk.handler
  (:require [soul-talk.middleware :as middleware]
            [soul-talk.layout :as layout]
            [soul-talk.env :refer [defaults]]
            [compojure.route :as route]
            [compojure.core :refer [routes wrap-routes]]
            [soul-talk.routes.home :refer [home-routes auth-routes]]
            [soul-talk.routes.services :refer [services-routes]]
            [mount.core :as mount]))

(mount/defstate init-app
  :start ((or (:init defaults) identity))
  :stop  ((or (:stop defaults) identity)))

(def app
  (-> (routes
        services-routes
        (-> #'home-routes
          (wrap-routes middleware/wrap-csrf))
        (-> #'auth-routes
          (wrap-routes middleware/wrap-csrf)
          (wrap-routes middleware/wrap-session-auth))
        (route/not-found
          (:body
            (layout/error-page
              {:status 404
               :title  "页面未找到"}))))
    (middleware/wrap-base)))

然后修改 soul-talk.core 命名空间:

(ns soul-talk.core
  (:require [ring.adapter.jetty :as jetty]
            [soul-talk.config :refer [env]]
            [soul-talk.handler :refer [app]]
            [mount.core :refer [defstate]]
            [mount.core :as mount]
            [taoensso.timbre :as log])
  (:gen-class))

(defn start-system []
  (-> #'app
      (jetty/run-jetty
        {:port 3000
         :join? false})))

(defstate ^{:on-reload :noop}
  system
  :start (start-system)
  :stop (.stop system))

(defn stop-app []
  (doseq [component (:stopped (mount/stop))]
    (log/info component "stopped"))
  (shutdown-agents))

(defn start-app [args]
  (doseq [component (-> args
                        mount/start-with-args
                        :started)]
    (log/info component "started"))
  (.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))

(defn -main [& args]
  (start-app args))

然后加到 project.clj 配置相关的:

:profiles 
{:dev {:source-paths   ["env/dev/clj"]
      :resource-paths ["env/dev/resources"]}}

我这里可能是 :dev 因为我用了lein-cljsbuild 的 map 配置,你那里可能是 vector 配置,你要查找 :id “dev”中的配置。

为了能打印出 mount 的日志,我们要引入 mount-up 包,

[tolitius/mount-up "0.1.1"]

这样我们在控制台就可以看到 mount 都加载了什么。

在env/dev/clj 下在 dev 命名空间中加入:

(ns dev
  (:require [soul-talk.models.db :refer [*db*]]
            [clojure.pprint :refer [pprint]]
            [clojure.tools.namespace.repl :as tn]
            [mount.tools.graph :refer [states-with-deps]]
            [figwheel :refer [start-fw stop-fw cljs]]
            [soul-talk.handler]
            [soul-talk.core]
            [soul-talk.config]
            [ragtime.jdbc :as jdbc]
            [ragtime.repl :refer [migrate rollback]]
            [mount.core :as mount]
            [cprop.core :refer [load-config]]
            [mount-up.core :as mu]))

(mu/on-upndown :info mu/log :before)

(defn start []
  (mount/start
    #'soul-talk.handler/init-app
    #'soul-talk.config/env
    #'soul-talk.models.db/*db*
    #'soul-talk.core/system))

下面我们来做打包的工作,我们要在 project.clj 中添加配置:

:profiles
  {:uberjar
        {:omit-source true
         :prep-tasks ["compile" ["cljsbuild" "once" "prod"]]
         :cljsbuild
         {:builds
          {:prod
           {:source-paths ["src/cljs" "src/cljc" "env/prod/cljs"]
            :compiler {:output-to "resources/public/js/main.js"
                       :optimizations :advanced
                       :pretty-print false}}}}

         :aot :all
         :uberjar-name "soul-talk.jar"
         :source-paths ["env/prod/clj"]
         :resource-paths ["env/prod/resources"]}}

:prep-tasks 这个选项表示我们先编译 cljs,然后再进行打 jar 包。

我们要在 env/prod/clj 下新建 evn.clj

(ns soul-talk.env
  (:require [taoensso.timbre :as log]))

(def defaults
  {:init
   (fn []
     (log/info "====[soul-talk started successfully]===="))
   :stop
   (fn []
     (log/info "====[soul-talk has shut down successfully]====="))})

在 env/prod/cljs 下新建 soul-talk.prod 的 cljs 命名空间:

(ns soul-talk.prod
  (:require [soul-talk.core :as core]))
;;关闭打印输出
(set! *print-fn* (fn [& _]))

(core/init!)

在 env/prod/resources 下新建 config.edn:

{:production true
 :port 3000}

同时新建 logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>log/soul_talk.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/soul_talk.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- keep 30 days of history -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
        </encoder>
    </appender>
    <logger name="org.xnio.nio" level="warn">
        <AppenderRef ref="FILE"/>
    </logger>
    <logger name="com.zaxxer.hikari" level="warn">
        <AppenderRef ref="FILE"/>
    </logger>
    <root level="INFO">
        <appender-ref ref="FILE" />
    </root>
</configuration>

这样我们在命令行运行, lein uberjar:

➜  soul-talk git:(master) ✗ lein uberjar
Compiling soul-talk.env
Compiling soul-talk.layout
Compiling soul-talk.middleware
Compiling soul-talk.core
Compiling soul-talk.models.tag-db
Compiling soul-talk.models.db
Compiling soul-talk.models.comment-db
Compiling soul-talk.models.post-tag-db
Compiling soul-talk.models.category-db
Compiling soul-talk.models.user-db
Compiling soul-talk.models.post-db
Compiling soul-talk.pagination
Compiling soul-talk.handler

.....
target/cljsbuild-compiler-0/cljs_time/core.cljs
WARNING: Use of undeclared Var cljs-time/core at line 634 target/cljsbuild-compiler-0/cljs_time/core.cljs
WARNING: No such namespace: cljs-time, could not locate cljs_time.cljs, cljs_time.cljc, or JavaScript source providing "cljs-time" (Please check that namespaces with dashes use underscores in the ClojureScript file name) at line 301 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs-time/core at line 301 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: No such namespace: cljs-time, could not locate cljs_time.cljs, cljs_time.cljc, or JavaScript source providing "cljs-time" (Please check that namespaces with dashes use underscores in the ClojureScript file name) at line 305 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs-time/core at line 305 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: No such namespace: cljs, could not locate cljs.cljs, cljs.cljc, or JavaScript source providing "cljs" at line 311 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: Use of undeclared Var cljs/core at line 311 target/cljsbuild-compiler-0/cljs_time/format.cljs
WARNING: domina is a single segment namespace at line 1 target/cljsbuild-compiler-0/domina.cljs
Successfully compiled ["resources/public/js/main.js"] in 82.849 seconds.
Created /Users/jiesoul/projects/private/soul-talk/target/soul-talk-0.1.0.jar
Created /Users/jiesoul/projects/private/soul-talk/target/soul-talk.jar

看到最后输出 jar 了,这样我们就可以直接在命令行运行了:

➜  soul-talk git:(master) ✗ java -jar target/soul-talk.jar
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.config/env started
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.models.db/*db* started
18-10-09 11:49:01 MacBook-Pro.local INFO [soul-talk.core:30] - #'soul-talk.core/system started

这样我们的服务就运行起来了,你可以打开http://localhost:3000/ 查看。

    原文作者:JIESOUL
    原文地址: https://zhuanlan.zhihu.com/p/46354885
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞