模仿知乎——實現一個多使用者線上問答平臺
online-questioning 從零開始開發線上問答平臺, 這是我模仿知乎做的一個貼吧類問答交流平臺
專案github地址:https://github.com/guomzh/online-questioning , 歡迎各位star和與我交流
本文持續更新,未完待續…
使用到的技術棧:
1、spring/springboot
* intercepter攔截器實現登入許可權控制
* javax.mali郵件服務,如有新評論時發郵件通知使用者,註冊時郵件驗證
* ioc專案容器中物件管理,對容器中beans操作
* aop平臺日誌操作記錄
* maven管理整個專案依賴
2、mybatis操作資料庫主要業務資料
3、前端模板引擎freemarker ,渲染整個前端模板
4、演算法設計:trie字首樹實現網站敏感詞過濾
5、Redis實現非同步佇列,利用多執行緒實現非同步事件處理,主要針對一些耗時操作,進行非同步執行,如發郵件,評論後發站內私信通知等
6、使用 Redis 資料結構中的 set 集合實現使用者對問題評論的點贊點踩功能
7、solr匯入mysql資料,建立問題和標題文件庫,利用ik-analyzer進行中文分詞,使用者可以進行站內全文搜尋
開發通用的新模組流程:
1、資料庫設計
2、Model:模型定義,與資料庫相匹配
3、Dao層:資料操作
4、Service:服務包裝
5、Controller:業務入口,資料互動
6、單元測試
註冊模組:
- 使用者名稱合法性檢測(長度,敏感詞,重複,特殊字元)
- 密碼長度要求
- 密碼salt加密,密碼強度檢查(md5庫)
- 使用者註冊郵件啟用
我在實現使用者註冊郵箱啟用時自己的實現思路:
當用戶提交登錄檔單資訊時,把表單資訊存到redis的hash資料結構中,同時產生一個對應的key,
這時釋出一個非同步事件,傳送一封郵件,同時把這個key放到連結中發到使用者的註冊郵箱中,當用戶
訪問郵箱中這個連結時,在redis中查出這個key對應的註冊資訊,並存到資料庫中完成註冊
//資訊存到redis中
String register_ticket=OnlineQUtil.MD5(email);
redisAdapter.hset(register_ticket,"email",email);
redisAdapter.hset(register_ticket,"username",username);
redisAdapter.hset(register_ticket,"password", password);
redisAdapter.expire(register_ticket,60 *15);
redisAdapter.sadd("email",email);
//釋出非同步事件
eventProducer.fireEvent(new EventModel(EventType.REGISTER)
.setExt("register_ticket",register_ticket)
.setExt("email",email));
//傳送郵件
@Override
public void doHandle(EventModel model) {
Map<String ,Object> map=new HashMap<>();
map.put("url","http://127.0.0.1:8080/regVerify?p="+model.getExt("register_ticket"));
mailSender.sendWithHTMLTemplate(model.getExt("email"),"<我的知乎——線上問答平臺>註冊啟用郵件",
"mails/register_email.html", map);
}
//從redis中讀取註冊資訊,完成註冊
if(redisAdapter.exists(p)){
try {
String email=redisAdapter.hget(p,"email");
String username=redisAdapter.hget(p,"username");
String password=redisAdapter.hget(p,"password");
}
...
}
登入模組
1、伺服器密碼校驗/三方校驗,token(sessionId或者cookie的一個key)登記
* 伺服器端token關聯userId
* 客戶端儲存token(本地或者cookie)
2、伺服器端/客戶端token設定有效期(記住登入)
3、登出:伺服器端/客戶端token刪除或者session清理
問題釋出模組
- HTML/敏感詞過濾
//html過濾
question.setContent(HtmlUtils.htmlEscape(question.getContent()));
敏感詞過濾通過Trie樹儲存敏感詞彙,匹配文字串,對匹配到的敏感詞打碼或者刪除
- 多執行緒
關於多執行緒的一些運用知識回顧如下:
Future作用:進行執行緒與執行緒間通訊
1. 返回非同步結果:Future<Integer>future =service.submit(new Callable<Integer>{ });
2. 阻塞等待返回結果(future.get())
3. timeout(future.get(100,TimeUnit.MILLISECONDS))
4. 獲取執行緒中的Exception
評論中心
訊息中心(贊,評論通知,私信通知,回答採納等)
Redis資料結構使用場景
List | Set | SortedSet | Hash | KV |
---|---|---|---|---|
用途 | 棧操作,雙向列表,使用於最新列表,關注列表 | 適用於無順序的集合,點贊點踩,抽獎,已讀,共同好友 | 排行榜,優先佇列 | 物件屬性,不定長 |
api | lpush | sdiff | zadd | hset |
api | lpop | smembers | zscore | hget |
api | blpop | sinter | zrange | hgetAll |
api | lindex | scard | zcount | hexists |
api | lrange | zrank | hkeys | |
api | lrem | zrevrank | hvals | |
api | linsert | |||
api | lset | |||
api | rpush |
例如:實現PV(page views)點選量功能等
- 點贊(Set) 操作:sadd, srem, sismembers等
- 關注(Set)
- 排行榜(SortedSet)
- 驗證碼(KV)
- 快取(序列化存KV)
- 非同步佇列(中間層)
- 判斷佇列(中間層)
非同步佇列實現
例如:實現評論後給提問者發一條站內通知功能
問題評論郵件通知功能實現,手寫一個非同步實現程式碼
思路:
* 自己手動實現一個非同步佇列
* 用redis做非同步訊息佇列實現,利用多執行緒來實現非同步傳送郵件
* 用javax.mail傳送郵件,使用smtps協議
技術關鍵流程:
1、定義訊息事件介面
public interface EventHandler {
void doHandle(EventModel model);
List<EventType> getSupportEventTypes();
}
2、定義訊息模型,可以用map的資料結構來實現
3、定義生產者,釋出訊息到redis構建的非同步佇列中(利用redis的阻塞佇列操作)
public class EventProducer {
@Autowired
private RedisAdapter redisAdapter;
public boolean fireEvent(EventModel eventModel) {
try {
String json = JSONObject.toJSONString(eventModel);
String key = RedisKeyUtil.getEventQueueKey();
redisAdapter.lpush(key, json);
return true;
} catch (Exception e) {
return false;
}
}
}
4、釋出事件
5、定義消費者,開啟多執行緒非同步處理事件
Thread thread =new Thread(new Runnable() {
@Override
public void run() {
while (true){
String key= RedisKeyUtil.getEventQueueKey();
List<String> events =redisAdapter.brpop(0,key);
for(String message:events){
//訊息佇列的第一個值可能是key,返回值的原因
if(message.equals(key)){
continue;
}
EventModel eventModel= JSON.parseObject(message,EventModel.class);
if(!config.containsKey(eventModel.getType())){
logger.error("不能識別的事件");
continue;
}
for(EventHandler handler :config.get(eventModel.getType())){
handler.doHandle(eventModel);
}
}
}
}
});
thread.start();
關注服務,如A關注了某人,某問題
分一下功能點:
* 首頁問題關注數
* 詳情頁問題關注列表
* 粉絲/關注人列表
* 關注介面設計,關注列表分頁
* 關注非同步事件處理
儲存結構:
redis: zset / list
平臺內容排序演算法:
1、Score = (P-1)/(T+2)^G
P表示投票數,G表示分值根據時間降低速率,相當於重力加速度,T表示釋出到現在時間間隔,單位小時
2、f(t,x,y,z)=log z + yt/45000
t等於釋出到現在的時間差,如一個差86400秒
x等於贊數-踩數
如果x大於0則y=1,x小於0則y=-1,x=0則y=0
z等於x的絕對值,如果x=0,則z=1
未完待續。。。