03 配置 Clojurescript 支持环境,并完成登录功能

03 配置 Clojurescript 支持环境,并完成登录功能.png

配置 project.clj

添加相关依赖

文件:project.clj

;; ClojureScript 库
[org.clojure/clojurescript "1.10.439"]

配置前端编译器和 Figwheel

文件:project.clj

(defproject soul-talk "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}


  :dependencies [
            ;; Clojure 主运行时库
            [org.clojure/clojure "1.9.0"]

            ;; Ring 库
            [ring "1.7.1"]

            ;; 基于 Ring 的 Response 简化工具库
            [metosin/ring-http-response "0.9.1"]

            ;; 常用中间件集合
            [ring/ring-defaults "0.3.2"]

            ;; 路由库
            [compojure "1.6.1"]

            ;; Selmer 模板库
            [selmer "1.12.0"]

            ;; ring 前端库中间件
            [ring-webjars "0.2.0"]

            ;; JQuery 依赖,Bootstrap 需要
            [org.webjars/jquery "3.3.1-1"]

            ;; Bootstrap 
            [org.webjars/bootstrap "4.0.0-2"]

            ;; Popper 
            [org.webjars/popper.js "1.14.1"]

            ;; 字体库
            [org.webjars/font-awesome "5.5.0"]

            ;; ClojureScript 库
            [org.clojure/clojurescript "1.10.439"]]


  :plugins [
      ;; 基于 Lein 的 Ring 插件
      [lein-ring "0.12.4"]
      ;; Cljsbuild 编译器插件
      [lein-cljsbuild "1.1.7" :excludes [[org.clojure/clojure]]]
      ;; figwheel 环境插件
      [lein-figwheel "0.5.17-SNAPSHOT"]]

  
  ;; Ring 插件不通过 main 函数启动,只需要指定一个入口 Handler
  :ring {:handler soul-talk.core/app}
  
  
  ;; 不使用插件的时候,程序仍然从 main 函数启动
  ;; 启用 ClojureScript 之后,要关闭预编译 AOT
  :main ^:skip-aot soul-talk.core
  
  
  ;; 指定源文件和资源文件路径
  :source-paths ["src"]
  :resource-paths ["resources"]

  ;; 为 figwheel 指定 CSS 路径
  :figwheel {:css-dirs ["resources/public/css"]}

  ;; 设置自动清理路径
  :clean-targets ^{:protect false} [
    :target-path 
      ;; 下面的路径根据 cljsbuild 配置查找
      [:cljsbuild :builds :dev :compiler :output-dir]
      [:cljsbuild :builds :dev :compiler :output-to]]


  ;; 设置 cljsbuild 编译器参数
  :cljsbuild {
    :builds {
      ;; 开发环境
      :dev {
        ;; 源代码目录
        :source-paths ["src-cljs"] 
        ;; 开启 figwheel                    
        :figwheel     true                             
        :compiler {
          ;; 主命名空间
          :main                   soul-talk.core  
          ;; 依赖文件路径
          :asset-path             "js/out"    
          ;; 最终输出的文件        
          :output-to              "resources/public/js/main.js"   
          ;; 临时文件输出路径
          :output-dir             "resources/public/js/out"
          ;; 不优化    
          :optimizations          :none
          ;; 源代码
          :source-map-timestamp   true  
          ;; 打印格式               
          :pretty-print           true}}}}      


  :profiles {
        :user {
            :dependencies []
            :plugins [[lein-ancient "0.6.15"]]}}
            
)

配置中间件

默认的 default 中间件会启用“防止跨域攻击”中间件,先关闭他

文件:src/soul_talk/core.clj

;; 组合中间件
(def app
  (-> app-routes
      (wrap-nocache)
      (wrap-reload)
      (wrap-webjars) 
      
      ;; 常用中间件,关闭跨域攻击功能
      (wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false))))

静态资源

创建 base.html 静态页面父模板

注意:要引入 js/main.js 自定义脚本

文件:resources/base.html

<!DOCTYPE html>

<html lang="zh-cmn-Hans">

<head>
    <!-- 必须的标签 -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>使用 Clojure 建立个人网站</title>

    <!-- 样式表 -->
    {% style "/assets/bootstrap/css/bootstrap.min.css" %}
    {% style "/assets/font-awesome/css/all.css" %}
    <link rel="stylesheet" href="/css/site.css">

    <!-- 可扩展的样式表 -->
    {% block page-css %}
    {% endblock %}
</head>

<body>

<div class="container-fluid">
    <!-- 可扩展的区域 -->
    {% block content %}

    {% endblock %}
</div>

<!-- scripts -->
{% script "/assets/jquery/jquery.min.js" %}
{% script "/assets/font-awesome/js/all.js" %}
{% script "/assets/bootstrap/js/bootstrap.min.js" %}


<!-- 引入自定义脚本-->
<script src="/js/main.js" type="text/javascript"></script>


<!-- 可扩展脚本-->
{% block page-script %}

{% endblock %}
</body>
</html>

修改 index.html

注意:页面接收 Handler 传来的一个变量,用于显示登录状态,但不是真正的 Session

文件:resources/index.html

<!-- 扩展 base.html -->
{% extends "base.html" %} 


<!-- 扩展 content 部分的内容 -->
{% block content %}
<div class="container">

    <header class="blog-header py-3">
        <div class="row flex-nowrap justify-content-between align-items-center">
            <div class="col-4 pt-1">
                <a class="text-muted" href="#">订阅</a>
            </div>
            <div class="col-4 text-center">
                <a class="blog-header-logo text-dark" href="#">Soul Talk</a>
            </div>
            
            <!-- 根据登录状态显示不同内容 -->
            <div class="col-4 d-flex justify-content-end align-items-center">
                {% if session.identity %}
                <span class="navbar-text">欢迎你 {{session.identity}} </span>
                <a class="btn btn-sm btn-outline-secondary" href="/logout">退出</a>
                {% else %}
                <a class="btn btn-sm btn-outline-secondary" href="/login">登录</a>
                {% endif %}
            </div>

        </div>
    </header>

    <div class="nav-scroller py-1 mb-2">
        <nav class="nav d-flex justify-content-between">
            <a class="p-2 text-muted" href="#">World</a>
            <a class="p-2 text-muted" href="#">U.S.</a>
            <a class="p-2 text-muted" href="#">Technology</a>
            <a class="p-2 text-muted" href="#">Design</a>
            <a class="p-2 text-muted" href="#">Culture</a>
            <a class="p-2 text-muted" href="#">Business</a>
            <a class="p-2 text-muted" href="#">Politics</a>
            <a class="p-2 text-muted" href="#">Opinion</a>
            <a class="p-2 text-muted" href="#">Science</a>
            <a class="p-2 text-muted" href="#">Health</a>
            <a class="p-2 text-muted" href="#">Style</a>
            <a class="p-2 text-muted" href="#">Travel</a>
        </nav>
    </div>

    <div class="jumbotron p-3 p-md-5 text-white rounded bg-dark">
        <div class="col-md-6 px-0">
            <h1 class="display-4 font-italic">Title of a longer featured blog post</h1>
            <p class="lead mb-0"><a href="#" class="text-white font-weight-bold">Continue reading...</a></p>
        </div>
    </div>
</div>

<main role="main" class="container">
    <div class="row">
        <div class="col-md-8 blog-main">
            <h3 class="pb-3 mb-4 font-italic border-bottom">
                From the Firehose
            </h3>

            <div class="blog-post">
                <h2 class="blog-post-title">Sample blog post</h2>
                <p class="blog-post-meta">January 1, 2014 by <a href="#">Mark</a></p>
                <ol>
                    <li>Vestibulum id ligula porta felis euismod semper.</li>
                    <li>Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</li>
                    <li>Maecenas sed diam eget risus varius blandit sit amet non magna.</li>
                </ol>
                <p>Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.</p>
            </div><!-- /.blog-post -->

            <div class="blog-post">
                <h2 class="blog-post-title">Another blog post</h2>
                <p class="blog-post-meta">December 23, 2013 by <a href="#">Jacob</a></p>

                <p>Cum sociis natoque penatibus et magnis <a href="#">dis parturient montes</a>, </p>

            </div><!-- /.blog-post -->

            <div class="blog-post">
                <h2 class="blog-post-title">New feature</h2>
                <p class="blog-post-meta">December 14, 2013 by <a href="#">Chris</a></p>

                <p>Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.</p>
            </div><!-- /.blog-post -->

            <nav class="blog-pagination">
                <a class="btn btn-outline-primary" href="#">Older</a>
                <a class="btn btn-outline-secondary disabled" href="#">Newer</a>
            </nav>

        </div><!-- /.blog-main -->

        <aside class="col-md-4 blog-sidebar">
            <div class="p-3 mb-3 bg-light rounded">
                <h4 class="font-italic">About</h4>
                <p class="mb-0">Etiam porta <em>sem malesuada magna</em> mollis euismod. </p>
            </div>

            <div class="p-3">
                <h4 class="font-italic">Archives</h4>
                <ol class="list-unstyled mb-0">
                    <li><a href="#">March 2014</a></li>
                    <li><a href="#">February 2014</a></li>
                    <li><a href="#">January 2014</a></li>
                    <li><a href="#">December 2013</a></li>
                    <li><a href="#">November 2013</a></li>
                    <li><a href="#">October 2013</a></li>
                    <li><a href="#">September 2013</a></li>
                    <li><a href="#">August 2013</a></li>
                    <li><a href="#">July 2013</a></li>
                    <li><a href="#">June 2013</a></li>
                    <li><a href="#">May 2013</a></li>
                    <li><a href="#">April 2013</a></li>
                </ol>
            </div>

            <div class="p-3">
                <h4 class="font-italic">Elsewhere</h4>
                <ol class="list-unstyled">
                    <li><a href="#">GitHub</a></li>
                    <li><a href="#">Twitter</a></li>
                    <li><a href="#">Facebook</a></li>
                </ol>
            </div>
        </aside><!-- /.blog-sidebar -->

    </div><!-- /.row -->

</main><!-- /.container -->

<footer class="blog-footer">
    <p>Blog template built for <a href="https://getbootstrap.com/">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.</p>
    <p>
        <a href="#">Back to top</a>
    </p>
</footer>


<!-- 这里扩展 content 结束 -->
{% endblock %}

创建 login.html

文件:resources/login.html

{% extends "base.html" %}
{% block page-title %}Soul Talk Login {% endblock %}
{% block page-css %}
<link rel="stylesheet" href="/css/login.css">
{% endblock %}
{% block content %}
<div class="container text-center">
    <form class="form-signin" action="/login" method="post" id="loginForm">
        <img src="" alt="" class="mb-4">
        <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
        <label for="email" class="sr-only">Email Address</label>
        <input type="text" id="email" name="email" class="form-control" placeholder="Email Address" required autofocus/>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" class="form-control" placeholder="Password" required />
        <div class="check-box mb-3">
            <label>
                <input type="checkbox" value="Remember me">记住我
            </label>
        </div>
        <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
        <p class="mt-5 mb-3 text-muted">&copy: 2018</p>
    </form>
</div>
{% endblock %}

ClojureScript

新建 src-cljs 目录,在这个目录下新建 soul_talk/core.cljs 文件

(ns soul-talk.core)

(defn main []
  (enable-console-print!)
  (prn "Hello, Clojurescript"))

(main)

Clojure

添加登录相关处理器

修改 src/soul_talk/core.clj,添加处理登录请求的处理器

;; 渲染 index.html 页面
(defn home-handle [request]
  (parser/render-file "index.html"  request))
  
  
;; Get 登录页面
(defn login-page [request]
  (parser/render-file "login.html" {}))


;; Post 登录数据
(defn handle-login [email password request]
  (if (and (= email "jiesoul@gmail.com") (= password "12345678"))
    ;; 如果登录成功,则在 Session 中添加信息
    (home-handle (assoc-in request [:session :identity] email))
    ;; 如果失败,则返回登陆页面,并向页面中传送错误信息
    (login-page (assoc request :error "用户名密码不对"))))

注意一:这里的 session 不是系统 session,而只是一个传给模板的变量,仅仅对渲染页面起作用,其他页面就看不到这个 session 了。如果要使用系统 session,必须在返回的键值对中加入 :session 键:

(-> (redirect "/") (assoc :session {:identity email}))

注意二:这里 Post 完毕后直接返回了 Index.html 的 HTML ,因此 URL 还是 http://localhost:3000/login ,这是不正确的做法,应该重定向到 /index.html

添加退出登录处理器

退出流程:清空 Session 信息,跳转到首页即可

(ns soul-talk.core
  (:require ......
    ;; 引入重定向函数
    [ring.util.response :refer [redirect]]))
    
    
;; 退出登录,清空 Session ,调整到首页
(defn handle-logout [request]
  (do
    (assoc request :session {})
    (redirect "/")))

注意:这里清除的同样也只是一个模板变量,而不是系统 session 。如果要清楚系统 session ,需要将返回键值对中的 :session 键设置为空:

(-> (redirect "/") (assoc :session {}))

配置路由

将登录和退出处理器添加到路由规则中

文件:src/soul_talk/core.clj

(ns soul-talk.core
  (:require ......
    ;; 引入相关函数
    [compojure.core :refer [routes GET defroutes POST]]))
    
    
(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "这是关于我的页面"))
    
    
    ;; 登录路由,Get 和 POST
    (GET "/login" request (login-page request))
    (POST "/login" [email password :as req] (handle-login email password req))
    
    ;; 退出登录路由==========
    (GET "/logout" request (handle-logout request))

    (route/not-found error-page)))

启动程序

启动服务

lein ring server-headless

编译 ClojureScript

lein figwheel

设置 Git 忽略文件

因为 ClojureScript 会下载很多依赖文件,同时产生很多编译输出文件,包括最终的输出文件 main.js ,都设置在了 /resources/public/js 中。这些都是动态的,不需要 Git 跟踪。

另外 FigWheel 的日志文件 figwheel_server.log 也不需要发布,因此都可以放到忽略文件中

figwheel_server.log
/resources/public/js

推荐阅读更多精彩内容