使用 Clojure 建立个人网站(七)

做为一个网站我们需要持久化我们的数据,所以我们现在要引入数据库,我们将使用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]))

这时我们就可以在注册页面操作了,输入正确的格式,点击保存,就可以看到注册成功,到数据库里查看,可以看到已经多了一条记录。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180906G1ZTXU00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券