# first-clojure **Repository Path**: marvinma/first-clojure ## Basic Information - **Project Name**: first-clojure - **Description**: Luminus Template Web Service Project - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-04-20 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 使用Luminus模板创建的第一个clojure学习项目 generated using Luminus version "3.28" 关于更多Luminus的知识,还得阅读[官方文档](http://www.luminusweb.net/docs) ## Prerequisites You will need [Leiningen][1] 2.0 or above installed. [1]: https://github.com/technomancy/leiningen ## 运行 To start a web server for the application, run: lein run ## 查看 模板默认的server端口在3000,所以本地使用浏览器访问3000端口即可,默认的content-path是/,即没有指定名称。 本项目在new时制定了service类型,因此可以使用swagger来调试接口,本地访问http://127.0.0.1:3000,效果如下: ![swagger](http://files.git.oschina.net/group1/M00/07/69/PaAvDFy8c-eAY9S6AAGadisJ20U245.jpg) ## License Copyright © 2019 FIXME ## 推文 [Clojure Web 编程之安全篇](http://blog.fnil.net/blog/clojure-web-bian-cheng-zhi-an-quan-pian/) [Clojure驱动的Web开发](https://www.liaoxuefeng.com/article/0014171500694729a42a2c8b7f245e0bd54612c88d78a03000) ## 学习时控制台敲过的指令 ``` # 查看当前控制台所在的namespace名称 *ns* => #object[clojure.lang.Namespace 0x6343a118 "user"] (+ 11 11 1111) (/ 1 2) (doc /) (Take 5 (repeat 10)) (1 2 3) => error '(1 2 3) => (1 2 3) (doc if) (def a-array [1 2 3]) (def b-array [1 2 3]) (idertical? a-array b-array) (def a-list (1 2 3)) (def b-list [1 2 3]) (type a-list) (type b-list) (conj a-list 9) (conj b-list 9) (def my-add (fn [x y] (+ x y))) (my-add 4 2) `#{1 2 3}` (type #{1 2 3}) (type {:a a}) (filter even? [1 2 3 ]) (map inc [1 2 3]) (use 'first-clojure.core) # 进入某个namespace (in-ns 'first-clojure.config) # 查看环境变量值 env # 修改生产环境里env的变量 (def env (assoc env :database-url "mysql://localhost:3306/learn_clojure?user=root&password=root&useSSL=false")) # 展开宏定义 (macroexpand '(when true (println "anc") (println "sdsd "))) => (if true (do (println "anc") (println "sdsd\n"))) # 从对象中获取某个key的value (get-in {:a {:b "haha" }} [:a :b]) # 给obj中的key赋值 (assoc-in {:a {:b "haha" }} [:a :b] "hoho") => {:a {:b "hoho"}} # 移除ojb中的key (dissoc {:id "123" :name "marvin"} :name) =>(dissoc {:id "123" :name "marvin"} :name) ``` ## 配置自己的api在swagger里显示 1、定义自己的接口文件 ``` (ns first-clojure.routes.guestbook (:require [first-clojure.db.core :as db])) (defn guestbook-routes [] ["/guestbook" {:swagger {:tags ["guestbook"]}} ["" {:get {:summary "plus with spec query parameters" :parameters {:query {:id int?}} :handler (fn [{{{:keys [id]} :query} :parameters}] {:status 200 :body {:user (db/get-guestbooks )}})} :post {:summary "plus with spec body parameters" :parameters {:body {:x int?, :y int?}} :responses {200 {:body {:total pos-int?}}} :handler (fn [{{{:keys [x y]} :body} :parameters}] {:status 200 :body {:total (+ x y)}})}}]]) ``` 2、将定义的接口添加到swagger的handle扫描中,在handle.clj文件ring/router配置后面增加 ``` (conj (service-routes) (guestbook-routes) )]) ``` 上面的`(guestbook-routes)`就是新加的,别忘了在前面require。 ![add api](http://files.git.oschina.net/group1/M00/07/78/PaAvDFzBXkqAHN3iAAJFm9a5wVA016.jpg) ## 自定义response 一般地,Ring没有内置能渲染Selmer模板的middleware,但是middleware不过是一个简单的函数,我们可以自己编写一个wrap-template-response,它在response中查找:body以及:body所包含的:model和:template,如果找到了,就通过Selmer渲染模板,并将渲染结果作为string放到response的:body中,服务器就可以读取response的:body并输出HTML: ```$xslt (ns cljweb.templating (:use ring.util.response [selmer.parser :as parser])) (parser/cache-off!) (parser/set-resource-path! (clojure.java.io/resource "templates")) (defn- try-render [response] (let [body (:body response)] (if (map? body) (let [[model template] [(:model body) (:template body)]] (if (and (map? model) (string? template)) (parser/render-file template model)))))) (defn wrap-template-response [handler] (fn [request] (let [response (handler request)] (let [render-result (try-render response)] (if (nil? render-result) response (let [templ-response (assoc response :body render-result)] (if (contains? (:headers response) "Content-Type") templ-response (content-type templ-response "text/html;charset=utf-8")))))))) ``` 如果response不是返回json,而是需要往response写入其他内容,可以用`io/input-stream`写入实现。 一个文件下载的接口 ```$xslt ["/download" {:get {:summary "downloads a file" :swagger {:produces ["image/png"]} :handler (fn [_] {:status 200 :headers {"Content-Type" "image/png"} :body (-> "public/img/warning_clojure.png" (io/resource) (io/input-stream))})}}] ``` 另一个例子: ```$xslt ``` ## 修改context path 按照文档,在core加载handle时指定。 但是指定后swagger不能访问了,修改了service-routes和swagger.json的路径后能访问了,都加了前缀,但是似乎不是这么用,待研究。 ```aidl (mount/defstate ^{:on-reload :noop} http-server :start (http/start (-> env (assoc :handler #'handler/app) (update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime))))) (update :port #(or (-> env :options :port) %)) (update :handler-path #(or (-> env :my-path) %)))) ``` [官方说明文档](http://www.luminusweb.net/docs/servers.html) ## 配置打印sql [https://coderwall.com/p/ikcvga/logging-sql-statements-in-clojure-for-jdbc](https://coderwall.com/p/ikcvga/logging-sql-statements-in-clojure-for-jdbc) properties配置文件么有起作用,可以删除,但是要记得在自己的db start的地方使用class加载driver,如: ```aidl (defstate ^:dynamic *db* :start ( do (Class/forName "net.sf.log4jdbc.DriverSpy") (if-let [jdbc-url (env :database-url)] (conman/connect! {:jdbc-url jdbc-url}) (do (log/warn "database connection URL was not found, please set :database-url in your config, e.g: dev-config.edn") *db*))) :stop (conman/disconnect! *db*)) ``` ## 远程调用 使用[http client](https://github.com/dakrone/clj-http)组件实现,例如: ```aidl (:body (http/get "http://jsonplaceholder.typicode.com/posts/1" {:as :json-kebab-keys})) => "{ \"userId\": 1, \"id\": 1, \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\", \"body\": \"quia et suscipit\ suscipit recusandae consequuntur expedita et cum\ reprehenderit molestiae ut ut quas totam\ nostrum rerum est autem sunt rem eveniet architecto\" }" ``` 在接口中使用: ```aidl ["/http/get" {:get {:summary "一个获取httpclient调用的例子" :handler (fn [_] {:status 200 :body {:code 1 :message "请求成功" :data (:body (http/get "http://cdn.imgs.3vyd.com/xh/admin/test.json" {:as :json}))}})}}] ["/http/post/json" {:get {:summary "以json body提交" :handler (fn [_] {:status 200 :body {:code 1 :message "请求成功" :data (:body (http/post "http://localhost:8185/management/public/doctor" {:form-params {:mobile "15092107093", :nickName "marvin.ma", :name "marvin", :openid "8"} :content-type :json}))}})}}] ["/http/post/form" {:get {:summary "以form body提交" :handler (fn [_] {:status 200 :body {:code 1 :message "请求成功" :data (:body (http/post "http://localhost:8185/management/oauth2/token" {:form-params {:username "test", :password "test123", :client_id "management-Client", :grant_type "password"} }))}})}}] ``` ## redis 的使用 引入redis的自定义函数,导入taoensso.carmine,使用wcar*(car/set "k" "v")等 ```aidl (ns alk-wxapi.routes.guestbook (:require [taoensso.carmine :as car :refer (wcar)] [alk-wxapi.db.redis :as redis])) ["/redis/set" {:get {:summary "set redis by key" :parameters {:query {:key string? :value string?}} :handler (fn [{{{:keys [key, value]} :query} :parameters}] {:status 200 :body (redis/wcar* (car/ping) (car/set key value))})}}] ["/redis/get" {:get {:summary "set redis by key" :parameters {:query {:key string?}} :handler (fn [{{{:keys [key]} :query} :parameters}] {:status 200 :body {:data {key (redis/wcar* (car/get key))} }})}}] ```