March 28, 2020
By: Kevin
团购的逻辑
基本情况
- 历时两小时
- 技术实现
- clojure数据结构写入edn
- 从edn恢复数据结构
- java-time java.utils.Date的转化和时间计算
- core.async模拟异步事件
拼团的基本流程
- 用户发起团购
- 其他用户可以加入团购
- 用户团购有倒计时, 到时候凑不够人需要给所有的用户退款
- 服务器中间重启的时候需要能够恢复现场
+---------------+
| init group |
+-----+---------+
| +--------------+
|<------+ people add.. |
| +--------------+
24 hours | +--------------+
|<------+ more added.. |
| +--------------+
| +--------------+
|<------+ .......... |
+------v-------+-+------------+
| end group |
+----------------+
拼团的功能点
除了最后一部分功能均有实现
- [100%] process
- [X] 倒计时机制
- [X] 组织db
- [X] 记录每个拼团活动的状态
- [X] 记录倒计时任务中关参加的用户
- [X] 持久化存储
- [X] 读取db
- [X] 写入db
- [X] 子任务
- [X] 倒计时开始
- [X] 时间计算
- [X] 加入新的拼团
- [X] 更新每个团的用户情况
- [X] 拼团结束
- [X] 凑够人数了, 拼团成功
- [X] 没有凑够人数, 拼团失败, 需要给用户退款
- [X] 服务器重启后的定时任务恢复
- [X] 要求我们前面的步骤有磁盘存储(或者数据库)
- [X] 还没到时间的拼团我们恢复进行
- 服务器重启后过期的定时任务的清理
- [X] 过期的拼团
- [X] 成功凑够了人数: 修改状态为拼团成功
- [X] 没有凑够人数:需要给用户退款
- [X] 失败的退款
- [-] 退款没有收到回掉的情况,需要和服务器确认退款情况
拼团的逻辑实现
(require '[chime :refer [chime-ch]]
'[java-time :as jt]
'[clojure.core.async :as a :refer [<! go-loop]])
(def db (atom {}))
(add-watch db :watcher
(fn [key atom old-state new-state]
(prn "-- Atom Changed --")
(prn "old-state")
(clojure.pprint/pprint old-state)
(prn "new-state")
(clojure.pprint/pprint new-state)))
(defn save-to-db []
(spit "group.edn" @db))
(defn recover-from-db []
(reset! db (clojure.edn/read-string (slurp "group.edn" ))))
(str (java.util.UUID/randomUUID))
;; => "fb16dd4c-1284-467f-be6f-7982d5fa9971"
(defn init-group [group-id user-id end-time]
(swap! db assoc group-id {:end-time (java.util.Date/from end-time)
:starter user-id
:status :拼团中
:price 100
:members {user-id {:pay-status :paied}}})
(save-to-db))
(defn- group-open? [group-id]
(if (= :拼团中 (get-in @db [group-id :status]) )
true
false))
(defn join-group [group-id user-id]
(when (group-open? group-id)
(swap! db assoc-in [group-id :members user-id] {:pay-status :paied})
(save-to-db)))
(defn group-full? [group-id]
(if (> (count (get-in @db [group-id :members])) 2)
true
false))
(defn user-refund [group-id user-id]
(a/go
(prn "开始给用户" user-id "参加的团" group-id "退款开始")
(swap! db assoc-in [group-id :members user-id :pay-status] :refunding)
(save-to-db)
(<! (a/timeout (rand-int 2000)))
(prn "开始给用户" user-id "参加的团" group-id "退款成功")
(swap! db assoc-in [group-id :members user-id :pay-status] :refunded)
(save-to-db)))
(defn group-refund [group-id]
(doseq [user-id (keys (get-in @db [group-id :members]))]
(user-refund group-id user-id)))
(defn group-failed-for-not-enough-people [group-id]
(group-refund group-id)
(swap! db assoc-in [group-id :status ] :拼团没凑够人)
(save-to-db))
(defn group-end-handling [group-id]
(if (group-full? group-id)
(do (swap! db assoc-in [group-id :status ] :拼团成功结束)
(save-to-db))
(group-failed-for-not-enough-people group-id)
))
(defn timer [group-id end-time]
(let [chimes (chime-ch [end-time])]
(go
(when-let [msg (<! chimes)]
(prn "拼团" group-id "结束")
(group-end-handling group-id)))))
(defn start-a-group-activity [user-id group-id end-time]
(init-group group-id user-id end-time)
(timer group-id end-time))
(defn future? [date]
(jt/after? (.toInstant date) (jt/instant)))
(defn past? [date]
(not (future? date)))
(defn missed-groups []
(reduce-kv (fn [m k v]
(when (and (past? (:end-time v) )
(= :拼团中 (:status v)))
(assoc m k v)))
{}
@db))
(defn handle-missed-group [group]
(let [group-id (first (keys group))]
(if (group-full? group-id)
(timer group-id (jt/plus (jt/instant) (jt/seconds 1)))
(group-refund group-id))))
(defn refund-for-missed-groups []
(let [groups-need-refund (missed-groups)]
(reduce-kv (fn [m k v]
(handle-missed-group k))
{}
groups-need-refund)))
(defn normal-groups []
(reduce-kv (fn [m k v]
(when (future? (:end-time v) )
(assoc m k v)))
{}
@db))
(defn resume-normal-groups []
(->> (normal-groups)
(reduce-kv
(fn [m k v]
(timer k (.toInstant (:end-time v))))
{})
))
(defn recover-all []
(recover-from-db)
(refund-for-missed-groups)
(resume-normal-groups))
(defn resume-groups []
(let [current (jt/instant)]))
(defn two [] (jt/plus (jt/instant) (jt/seconds 2)))
(defn ten [] (jt/plus (jt/instant) (jt/seconds 20)))
(defn one-m [] (jt/plus (jt/instant) (jt/minutes 2)))
(comment
@db
(reset! db {})
(save-to-db)
(recover-from-db)
(timer "fb16dd4c-1284-467f-be6f-7982d5fa9971" two)
(init-group "fb16dd4c-1284-467f-be6f-7982d5fa9971" 1 two)
(start-a-group-activity 1 "fb16dd4c-1284-467f-be6f-7982d5fa9971" (one-m))
(start-a-group-activity 2 "fb16dd4c-1284-467f-be6f-7982d5fa9972" (two))
(join-group "fb16dd4c-1284-467f-be6f-7982d5fa9971" 2 )
(join-group "fb16dd4c-1284-467f-be6f-7982d5fa9971" 3 )
(resume-normal-groups)
(recover-all)
)