做为一个网站我们需要持久化我们的数据,所以我们现在要引入数据库,我们将使用PostgreSQL, 具体的下载安装操作请查看官网,postgresql 有一个图形化的工具pgadmin, 我将使用它来操作我的数据库。
我们要加入在 project.clj 中添加依赖
[org.clojure/java.jdbc"0.7.8"]
[org.postgresql/postgresql"42.2.4"]
(nssoul-talk.models.db
(:require[clojure.java.jdbc:assql]))
(defdb-spec{:subprotocol"postgresql"
:subname"//localhost:5432/soul_talk"
:user"jiesoul"
:password"12345678"})
(defntest-db[]
(sql/querydb-spec"select 3*5 as result"))
然后我们将使用 repl 来验证这个数据库是否连接,因为 lein repl 启动时默认进入的命名空间是 user,所以我们可以新建一个 user.clj 文件来实现我们自己的一些方便操作。同时这个文件只会在开发的时候使用,因此我们最好和正式的代码分到不同的目录,但是还能操作,这就需要用到 lein dev 配置。
在项目目录下创建目录 evn/dev/clj 目录,修改 project.clj 文件加入相关配置
:figwheel
{:css-dirs["resources/public/css"]}
:profiles{:dev{:source-paths["env/dev/clj"]
:dependencies[[ring/ring-devel"1.6.3"]]}}
重启服务,在evn/dev/clj 目录下新建 user.clj 文件,内容如下:
在一个新的命令行,进入项目目录,运行:
lein repl
;; Connecting to local nREPL server...
;; Clojure1.9.0
;; nREPL server started on port51428on host127.0.0.1
在 repl 内输入:
(db/test-db)
=>({:result15})
这样我们的数据库也连接成功了。
我们可以使用相关的命令来创建数据表,但是在 Clojure 中有很多库可以让我们轻松的管理数据库,ragtime就是其中之一。在 project.clj 中加入依赖:
[ragtime"0.7.2"]
CREATETABLEIFNOTEXISTS users
(id serial primary key,
nameVARCHAR(30),
emailVARCHAR(30) unique ,
adminBOOLEAN,
last_loginTIME,
is_activeBOOLEAN,
passwordVARCHAR(200)NOTNULL);
在相同目录下新建 001-user-down.sql
DROPTABLEIFEXISTSusers;
在前面提到的 user.clj 中加入 ragtime 相关的函数
(nsuser
(:require[soul-talk.models.db:asdb:refer[db-spec]]
[ragtime.jdbc:asjdbc]
[ragtime.repl:asrag-repl]))
(defconfig
{:datastore(jdbc/sql-databasedb-spec)
:migrations(jdbc/load-resources"migrations")})
在 repl 中运行
(rag-repl/migrateconfig)
Applying001-user
=>nil
成功执行就是像上在代码块的,否则会出现错误信息。这时你去数据库查看,发现生成了 users 表,而且还生成了一个命名为 ragtime_migrations 的表,这个表存储的是 执行的文件及执行的时间。
我们可以运行回滚命令:
(rag-repl/rollbackconfig)
Rollingback001-user
=>nil
这时你再看 ragtime_migrations 表中已经没有数据,而 users 表也被删除了。
重新运行 (rag-repl/migrate config) ,建立好表。
现在我们在 db.clj 写一下用户相关的操作:
(defnsave-user![user]
(sql/insert!db-spec:usersuser))
(defnselect-user[id]
(sql/querydb-spec["SELECT * FROM users where email = ? "id]))
(defnselect-all-users[]
(sql/querydb-spec["SELECT * from users"]))
接下來我们要写注册的功能,同时我们也要把不同的操作用命名空间分开,首先我们的密码需要加密存储,所以我们要引入另一个库来做加密的功能,这个库就是buddy,这个包装了各种各样的加密和解密算法,我们可以直接使用,具体请看它的文档。同时还要处理时间,我们再加入clojure.java-time,如果你在 JDK8 以下可以使用clj-time.还有我们将使用 log 库timbre。
我们在 project.clj 加入依赖
[buddy"2.0.0"]
[clj-time"0.14.4"]
[com.taoensso/timbre"4.10.0"]
[com.fzakaria/slf4j-timbre"0.3.12"]
(nssoul-talk.routes.auth
(:require[soul-talk.models.db:asdb]
[ring.util.http-response:asresp]
[buddy.hashers:ashashers]
[taoensso.timbre:aslog]))
(defnregister![{:keys[session]}user]
(try
(db/save-user!
(->user
(dissoc:pass-confirm)
(update:passwordhashers/encrypt)))
(->{:result:ok}
(resp/ok)
(assoc:session(assocsession:identity(:nameuser))))
(catchExceptione
(log/errore)
(resp/internal-server-error
{:result:error
:message"注册用户时发生错误"}))))
然后我们依然是在 auth-validate.cljc 中添加验证逻辑,但是这次我们用一个写好的库bouncer,这个库能包装了很多验证。project.clj 加入依赖
[bouncer"1.0.1"]
(nssoul-talk.auth-validate
(:require[bouncer.core:asb]
[bouncer.validators:asv]))
;;....
(defnreg-errors[{:keys[pass-confirm]:asparams}]
(first
(b/validate
params
:email[[v/required:message"email 不能为空"]
[v/email:message"email 不合法"]]
:password[[v/required:message"密码不能为空"]
[v/min-count7:message"密码最少8位"]
[=pass-confirm:message"两次密码必须一样"]])))
然后回到 auth.clj 修改代码加入验证,写上路由:
(nssoul-talk.routes.auth
(:require[soul-talk.models.db:asdb]
[ring.util.http-response:asresp]
[buddy.hashers:ashashers]
[taoensso.timbre:aslog]
[soul-talk.auth-validate:refer[reg-errors]]
[compojure.core:refer[routesPOSTGET]]
[selmer.parser:asparser]))
(defnregister![{:keys[session]}user]
(if(reg-errorsuser)
(resp/precondition-failed{:result:error})
(try
(if-let[temp-user(db/select-user(:emailuser))]
(resp/precondition-failed{:result:error
:message"email 已被注册"})
(do
(db/save-user!
(->user
(dissoc:pass-confirm)
(update:passwordhashers/encrypt)))
(->{:result:ok}
(resp/ok))))
(catchExceptione
(do
(log/errore)
(resp/internal-server-error
{:result:error
:message"发生内部错误,请联系管理员"}))))))
(defauth-routes
(routes
(GET"/register"req(parser/render-file"register.html"req))
(POST"/register"req(register!req(:userreq)))))
在 core.clj 中加入上面的路由
(nssoul-talk.core
(:require[ring.adapter.jetty:asjetty]
;...
[soul-talk.routes.auth:refer[auth-routes]]))
(defapp
(->(routesauth-routesapp-routes);;这行修改为这样
(wrap-nocache)
(wrap-reload)
(wrap-webjars)
(wrap-format/wrap-restful-format:formats[:json-kw])
(wrap-defaults(assoc-inapi-defaults[:security:anti-forgery]false))))
然后我们可以复制 login.html 并命名为 register.html,并修改标题
{% extends "base.html" %}
{% block page-title %}Soul Talk Regiter {% endblock %}
{% block page-css %}
{% endblock %}
{% block content %}
{% endblock %}
{% block page-script %}
soul_talk.register.init();
{% endblock %}
同样的我们可以把 login.cljs 复制一份 命名为 register.cljs,并修改添加相应的组件。
(nssoul-talk.register
(:require[domina:asdom]
[reagent.core:asreagent:refer[atom]]
[soul-talk.auth-validate:asvalidate]
[ajax.core:asajax]
[reagent.session:assession]
[taoensso.timbre:aslog]))
(defnvalidate-invalid[inputvali-fun]
(if-not(vali-fun(.-valueinput))
(dom/add-class!input"is-invalid")
(dom/remove-class!input"is-invalid")))
(defnregister![reg-dateerrors]
(reset!errors(validate/reg-errors@reg-date))
(if-not@errors
(ajax/POST"/register"
{:format:json
:headers{"Accept""application/transit+json"}
:params@reg-date
:handler#(do
(session/put!:identity(:email@reg-date))
(reset!reg-date{})
(js/alert"注册成功")
(set!(..js/window-location-href)"/login"))
:error-handler#(reset!
errors
{:server-error(get-in%[:response"message"])})})
(let[error(vals@errors)
msg(ffirsterror)]
(js/alertmsg))))
(defnregister-component[]
(let[reg-data(atom{})
error(atomnil)]
(fn[]
[:div.container
[:div#loginForm.form-signin
[:h1.h3.mb-3.font-weight-normal.text-center"注册"]
[:div.form-group
[:label"邮箱"]
[:input#email.form-control
{:type"text"
:name"email"
:auto-focustrue
:placeholder"xx@xx.xx"
:on-change(fn[e]
(let[d(..e-target)]
(swap!reg-dataassoc:email(.-valued))
(validate-invaliddvalidate/validate-email)))
:value(:email@reg-data)}]
[:div.invalid-feedback"无效的 Email"]]
[:div.form-group
[:label"密码"]
[:input#password.form-control
{:type"password"
:name"password"
:placeholder"密码"
:on-change(fn[e]
(let[d(.-targete)]
(swap!reg-dataassoc:password(.-valued))
(validate-invaliddvalidate/validate-passoword)))
:value(:password@reg-data)}]
[:div.invalid-feedback"无效的密码"]]
[:div.form-group
[:label"重复密码"]
[:input#pass-confirm.form-control
{:type"password"
:name"pass-confirm"
:placeholder"重复密码"
:on-change(fn[e]
(let[d(.-targete)]
(swap!reg-dataassoc:pass-confirm(.-valued))
(validate-invaliddvalidate/validate-passoword)))
:value(:pass-confirm@reg-data)}]
[:div.invalid-feedback"无效的密码"]]
(when-not[error(:client-error@error)]
[:div#error.alert.alert-dangererror])
(when-not[error(:server-error@error)]
[:div#error.alert.alert-dangererror])
[:input#submit.btn.btn-primary.btn-lg.btn-block
{:type:submit
:value"保存"
:on-click#(register!reg-dataerror)}]
[:p.mt-5.mb-3.text-muted"© @2018"]]])))
(defnload-page[]
(reagent/render
[register-component]
(dom/by-id"app")))
(defn^:exportinit[]
(if(andjs/document
(.-getElementByIdjs/document))
(load-page)))
同样我们需要在 core.cljs 中加入引用,才能正确加载 register.cljs
(nssoul-talk.core
(:require[soul-talk.login:aslogin]
;;....
[soul-talk.register:asregister]))
这时我们就可以在注册页面操作了,输入正确的格式,点击保存,就可以看到注册成功,到数据库里查看,可以看到已经多了一条记录。
领取专属 10元无门槛券
私享最新 技术干货