配置 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">©: 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