feat: block and dangerous users
This commit is contained in:
@@ -8,6 +8,7 @@ use crate::schema;
|
|||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
|
use rocket::futures::future;
|
||||||
use rocket::futures::join;
|
use rocket::futures::join;
|
||||||
use rocket::serde::{json::json, Serialize};
|
use rocket::serde::{json::json, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -30,39 +31,59 @@ pub struct CommentOutput {
|
|||||||
name_id: i32,
|
name_id: i32,
|
||||||
is_tmp: bool,
|
is_tmp: bool,
|
||||||
create_time: DateTime<Utc>,
|
create_time: DateTime<Utc>,
|
||||||
|
is_blocked: bool,
|
||||||
|
blocked_count: Option<i32>,
|
||||||
// for old version frontend
|
// for old version frontend
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
|
blocked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<CommentOutput> {
|
pub async fn c2output<'r>(
|
||||||
|
p: &'r Post,
|
||||||
|
cs: &Vec<Comment>,
|
||||||
|
user: &CurrentUser,
|
||||||
|
rconn: &RdsConn,
|
||||||
|
) -> Vec<CommentOutput> {
|
||||||
let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]);
|
let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]);
|
||||||
cs.iter()
|
let name_ids_iter = cs.iter().map(|c| match hash2id.get(&c.author_hash) {
|
||||||
.filter_map(|c| {
|
Some(id) => *id,
|
||||||
let name_id: i32 = match hash2id.get(&c.author_hash) {
|
None => {
|
||||||
Some(id) => *id,
|
let x = hash2id.len().try_into().unwrap();
|
||||||
None => {
|
hash2id.insert(&c.author_hash, x);
|
||||||
let x = hash2id.len().try_into().unwrap();
|
x
|
||||||
hash2id.insert(&c.author_hash, x);
|
}
|
||||||
x
|
});
|
||||||
}
|
future::join_all(cs.iter().zip(name_ids_iter).map(|(c, name_id)| async move {
|
||||||
};
|
if c.is_deleted {
|
||||||
if c.is_deleted {
|
None
|
||||||
// TODO: block
|
} else {
|
||||||
None
|
let is_blocked =
|
||||||
} else {
|
BlockedUsers::check_blocked(rconn, user.id, &user.namehash, &c.author_hash)
|
||||||
Some(CommentOutput {
|
.await
|
||||||
cid: c.id,
|
.unwrap_or_default();
|
||||||
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content),
|
Some(CommentOutput {
|
||||||
author_title: c.author_title.to_string(),
|
cid: c.id,
|
||||||
can_del: c.check_permission(user, "wd").is_ok(),
|
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content),
|
||||||
name_id: name_id,
|
author_title: c.author_title.to_string(),
|
||||||
is_tmp: c.is_tmp,
|
can_del: c.check_permission(user, "wd").is_ok(),
|
||||||
create_time: c.create_time,
|
name_id: name_id,
|
||||||
timestamp: c.create_time.timestamp(),
|
is_tmp: c.is_tmp,
|
||||||
})
|
create_time: c.create_time,
|
||||||
}
|
is_blocked: is_blocked,
|
||||||
})
|
blocked_count: if user.is_admin {
|
||||||
.collect()
|
BlockCounter::get_count(rconn, &c.author_hash).await.ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
timestamp: c.create_time.timestamp(),
|
||||||
|
blocked: is_blocked,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getcomment?<pid>")]
|
#[get("/getcomment?<pid>")]
|
||||||
@@ -72,7 +93,7 @@ pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) ->
|
|||||||
return Err(APIError::PcError(IsDeleted));
|
return Err(APIError::PcError(IsDeleted));
|
||||||
}
|
}
|
||||||
let cs = p.get_comments(&db, &rconn).await?;
|
let cs = p.get_comments(&db, &rconn).await?;
|
||||||
let data = c2output(&p, &cs, &user);
|
let data = c2output(&p, &cs, &user, &rconn).await;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ use rocket::request::{FromRequest, Outcome, Request};
|
|||||||
use rocket::response::{self, Responder};
|
use rocket::response::{self, Responder};
|
||||||
use rocket::serde::json::{json, Value};
|
use rocket::serde::json::{json, Value};
|
||||||
|
|
||||||
|
macro_rules! code0 {
|
||||||
|
() => (
|
||||||
|
Ok(json!({"code": 0}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[catch(401)]
|
#[catch(401)]
|
||||||
pub fn catch_401_error() -> &'static str {
|
pub fn catch_401_error() -> &'static str {
|
||||||
"未登录或token过期"
|
"未登录或token过期"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC};
|
use crate::api::{CurrentUser, JsonAPI, PolicyError::*, UGC};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::rds_models::*;
|
use crate::rds_models::*;
|
||||||
|
use crate::schema;
|
||||||
use chrono::offset::Local;
|
use chrono::offset::Local;
|
||||||
|
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::serde::json::json;
|
use rocket::serde::json::json;
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
|
||||||
use crate::schema;
|
|
||||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct DeleteInput {
|
pub struct DeleteInput {
|
||||||
@@ -20,14 +20,11 @@ pub struct DeleteInput {
|
|||||||
|
|
||||||
#[post("/delete", data = "<di>")]
|
#[post("/delete", data = "<di>")]
|
||||||
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
||||||
let mut p: Post;
|
let (author_hash, p) = match di.id_type.as_str() {
|
||||||
let mut c: Comment;
|
|
||||||
let author_hash: &str;
|
|
||||||
match di.id_type.as_str() {
|
|
||||||
"cid" => {
|
"cid" => {
|
||||||
c = Comment::get(&db, di.id).await?;
|
let mut c = Comment::get(&db, di.id).await?;
|
||||||
c.soft_delete(&user, &db).await?;
|
c.soft_delete(&user, &db).await?;
|
||||||
p = Post::get(&db, &rconn, c.post_id).await?;
|
let mut p = Post::get(&db, &rconn, c.post_id).await?;
|
||||||
update!(
|
update!(
|
||||||
p,
|
p,
|
||||||
posts,
|
posts,
|
||||||
@@ -39,20 +36,21 @@ pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: Rds
|
|||||||
p.refresh_cache(&rconn, false).await;
|
p.refresh_cache(&rconn, false).await;
|
||||||
p.clear_comments_cache(&rconn).await;
|
p.clear_comments_cache(&rconn).await;
|
||||||
|
|
||||||
author_hash = &c.author_hash;
|
(c.author_hash.clone(), p)
|
||||||
}
|
}
|
||||||
"pid" => {
|
"pid" => {
|
||||||
p = Post::get(&db, &rconn, di.id).await?;
|
let mut p = Post::get(&db, &rconn, di.id).await?;
|
||||||
p.soft_delete(&user, &db).await?;
|
p.soft_delete(&user, &db).await?;
|
||||||
|
|
||||||
// 如果是删除,需要也从0号缓存队列中去掉
|
// 如果是删除,需要也从0号缓存队列中去掉
|
||||||
p.refresh_cache(&rconn, true).await;
|
p.refresh_cache(&rconn, true).await;
|
||||||
|
|
||||||
author_hash = &p.author_hash;
|
(p.author_hash.clone(), p)
|
||||||
}
|
}
|
||||||
_ => return Err(APIError::PcError(NotAllowed)),
|
_ => { Err(NotAllowed) }?,
|
||||||
}
|
};
|
||||||
|
|
||||||
if user.is_admin && !user.namehash.eq(author_hash) {
|
if user.is_admin && !user.namehash.eq(&author_hash) {
|
||||||
Systemlog {
|
Systemlog {
|
||||||
user_hash: user.namehash.clone(),
|
user_hash: user.namehash.clone(),
|
||||||
action_type: LogType::AdminDelete,
|
action_type: LogType::AdminDelete,
|
||||||
@@ -73,18 +71,17 @@ pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: Rds
|
|||||||
}
|
}
|
||||||
.create(&rconn)
|
.create(&rconn)
|
||||||
.await?;
|
.await?;
|
||||||
BannedUsers::add(&rconn, author_hash).await?;
|
BannedUsers::add(&rconn, &author_hash).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!({
|
code0!()
|
||||||
"code": 0
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct ReportInput {
|
pub struct ReportInput {
|
||||||
pid: i32,
|
pid: i32,
|
||||||
|
#[field(validate = len(0..1000))]
|
||||||
reason: String,
|
reason: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,12 +96,57 @@ pub async fn report(ri: Form<ReportInput>, user: CurrentUser, db: Db, rconn: Rds
|
|||||||
Systemlog {
|
Systemlog {
|
||||||
user_hash: user.namehash,
|
user_hash: user.namehash,
|
||||||
action_type: LogType::Report,
|
action_type: LogType::Report,
|
||||||
target: format!("#{} {}", ri.pid, if ri.reason.starts_with("评论区") { "评论区" } else {""}),
|
target: format!(
|
||||||
|
"#{} {}",
|
||||||
|
ri.pid,
|
||||||
|
if ri.reason.starts_with("评论区") {
|
||||||
|
"评论区"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
),
|
||||||
detail: ri.reason.clone(),
|
detail: ri.reason.clone(),
|
||||||
time: Local::now(),
|
time: Local::now(),
|
||||||
}.create(&rconn)
|
}
|
||||||
|
.create(&rconn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
code0!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct BlockInput {
|
||||||
|
#[field(name = "type")]
|
||||||
|
content_type: String,
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/block", data = "<bi>")]
|
||||||
|
pub async fn block(bi: Form<BlockInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
||||||
|
let mut blk = BlockedUsers::init(user.id.ok_or_else(|| NotAllowed)?, &rconn);
|
||||||
|
|
||||||
|
let nh_to_block = match bi.content_type.as_str() {
|
||||||
|
"post" => Post::get(&db, &rconn, bi.id).await?.author_hash,
|
||||||
|
"comment" => Comment::get(&db, bi.id).await?.author_hash,
|
||||||
|
_ => { Err(NotAllowed) }?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if nh_to_block.eq(&user.namehash) {
|
||||||
|
{ Err(NotAllowed) }?;
|
||||||
|
}
|
||||||
|
|
||||||
|
blk.add(&nh_to_block).await?;
|
||||||
|
let curr = BlockCounter::count_incr(&rconn, &nh_to_block).await?;
|
||||||
|
|
||||||
|
if curr >= BLOCK_THRESHOLD || user.is_admin {
|
||||||
|
DangerousUser::add(&rconn, &nh_to_block).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"curr": curr,
|
||||||
|
"threshold": BLOCK_THRESHOLD,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ pub struct PostOutput {
|
|||||||
can_del: bool,
|
can_del: bool,
|
||||||
attention: bool,
|
attention: bool,
|
||||||
hot_score: Option<i32>,
|
hot_score: Option<i32>,
|
||||||
|
is_blocked: bool,
|
||||||
|
blocked_count: Option<i32>,
|
||||||
// for old version frontend
|
// for old version frontend
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
likenum: i32,
|
likenum: i32,
|
||||||
reply: i32,
|
reply: i32,
|
||||||
|
blocked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
@@ -54,6 +57,9 @@ pub struct CwInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> PostOutput {
|
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> PostOutput {
|
||||||
|
let is_blocked = BlockedUsers::check_blocked(rconn, user.id, &user.namehash, &p.author_hash)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
PostOutput {
|
PostOutput {
|
||||||
pid: p.id,
|
pid: p.id,
|
||||||
text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, p.content),
|
text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, p.content),
|
||||||
@@ -70,10 +76,15 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Pos
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// 单个洞还有查询评论的接口,这里挂了不用报错
|
// 单个洞还有查询评论的接口,这里挂了不用报错
|
||||||
p.get_comments(db, rconn)
|
Some(
|
||||||
.await
|
c2output(
|
||||||
.ok()
|
p,
|
||||||
.map(|cs| c2output(p, &cs, user))
|
&p.get_comments(db, rconn).await.unwrap_or(vec![]),
|
||||||
|
user,
|
||||||
|
rconn,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
can_del: p.check_permission(user, "wd").is_ok(),
|
can_del: p.check_permission(user, "wd").is_ok(),
|
||||||
attention: Attention::init(&user.namehash, &rconn)
|
attention: Attention::init(&user.namehash, &rconn)
|
||||||
@@ -81,10 +92,17 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Pos
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
hot_score: user.is_admin.then(|| p.hot_score),
|
hot_score: user.is_admin.then(|| p.hot_score),
|
||||||
|
is_blocked: is_blocked,
|
||||||
|
blocked_count: if user.is_admin {
|
||||||
|
BlockCounter::get_count(rconn, &p.author_hash).await.ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
// for old version frontend
|
// for old version frontend
|
||||||
timestamp: p.create_time.timestamp(),
|
timestamp: p.create_time.timestamp(),
|
||||||
likenum: p.n_attentions,
|
likenum: p.n_attentions,
|
||||||
reply: p.n_comments,
|
reply: p.n_comments,
|
||||||
|
blocked: is_blocked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,9 +171,7 @@ pub async fn publish_post(
|
|||||||
.await?;
|
.await?;
|
||||||
Attention::init(&user.namehash, &rconn).add(p.id).await?;
|
Attention::init(&user.namehash, &rconn).add(p.id).await?;
|
||||||
p.refresh_cache(&rconn, true).await;
|
p.refresh_cache(&rconn, true).await;
|
||||||
Ok(json!({
|
code0!()
|
||||||
"code": 0
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/editcw", data = "<cwi>")]
|
#[post("/editcw", data = "<cwi>")]
|
||||||
@@ -164,7 +180,7 @@ pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsCo
|
|||||||
p.check_permission(&user, "w")?;
|
p.check_permission(&user, "w")?;
|
||||||
update!(p, posts, &db, { cw, to cwi.cw.to_string() });
|
update!(p, posts, &db, { cw, to cwi.cw.to_string() });
|
||||||
p.refresh_cache(&rconn, false).await;
|
p.refresh_cache(&rconn, false).await;
|
||||||
Ok(json!({"code": 0}))
|
code0!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getmulti?<pids>")]
|
#[get("/getmulti?<pids>")]
|
||||||
|
|||||||
54
src/cache.rs
54
src/cache.rs
@@ -1,5 +1,6 @@
|
|||||||
use crate::models::{Comment, Post, User};
|
use crate::models::{Comment, Post, User};
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
|
use crate::rds_models::init;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
use rocket::serde::json::serde_json;
|
use rocket::serde::json::serde_json;
|
||||||
@@ -9,6 +10,7 @@ const INSTANCE_EXPIRE_TIME: usize = 60 * 60;
|
|||||||
|
|
||||||
const MIN_LENGTH: isize = 200;
|
const MIN_LENGTH: isize = 200;
|
||||||
const MAX_LENGTH: isize = 900;
|
const MAX_LENGTH: isize = 900;
|
||||||
|
const CUT_LENGTH: isize = 100;
|
||||||
|
|
||||||
macro_rules! post_cache_key {
|
macro_rules! post_cache_key {
|
||||||
($id: expr) => {
|
($id: expr) => {
|
||||||
@@ -21,11 +23,7 @@ pub struct PostCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PostCache {
|
impl PostCache {
|
||||||
pub fn init(rconn: &RdsConn) -> Self {
|
init!();
|
||||||
PostCache {
|
|
||||||
rconn: rconn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn sets(&mut self, ps: &Vec<&Post>) {
|
pub async fn sets(&mut self, ps: &Vec<&Post>) {
|
||||||
if ps.is_empty() {
|
if ps.is_empty() {
|
||||||
@@ -98,9 +96,13 @@ impl PostCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_all(&mut self) {
|
pub async fn clear_all(&mut self) {
|
||||||
let mut keys = self.rconn.scan_match::<String, String>(post_cache_key!("*")).await.unwrap(); //.collect::<Vec<String>>().await;
|
let mut keys = self
|
||||||
// colllect() does not work
|
.rconn
|
||||||
// also see: https://github.com/mitsuhiko/redis-rs/issues/583
|
.scan_match::<String, String>(post_cache_key!("*"))
|
||||||
|
.await
|
||||||
|
.unwrap(); //.collect::<Vec<String>>().await;
|
||||||
|
// colllect() does not work
|
||||||
|
// also see: https://github.com/mitsuhiko/redis-rs/issues/583
|
||||||
let mut ks_for_del = Vec::new();
|
let mut ks_for_del = Vec::new();
|
||||||
while let Some(key) = keys.next_item().await {
|
while let Some(key) = keys.next_item().await {
|
||||||
ks_for_del.push(key);
|
ks_for_del.push(key);
|
||||||
@@ -108,9 +110,10 @@ impl PostCache {
|
|||||||
if ks_for_del.is_empty() {
|
if ks_for_del.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.rconn.del(ks_for_del).await.unwrap_or_else(|e| {
|
self.rconn
|
||||||
warn!("clear all post cache fail, {}", e)
|
.del(ks_for_del)
|
||||||
});
|
.await
|
||||||
|
.unwrap_or_else(|e| warn!("clear all post cache fail, {}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +123,7 @@ pub struct PostCommentCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PostCommentCache {
|
impl PostCommentCache {
|
||||||
pub fn init(pid: i32, rconn: &RdsConn) -> Self {
|
init!(i32, "hole_v2:cache:post_comments:{}");
|
||||||
PostCommentCache {
|
|
||||||
key: format!("hole_v2:cache:post_comments:{}", pid),
|
|
||||||
rconn: rconn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set(&mut self, cs: &Vec<Comment>) {
|
pub async fn set(&mut self, cs: &Vec<Comment>) {
|
||||||
self.rconn
|
self.rconn
|
||||||
@@ -179,22 +177,20 @@ pub struct PostListCommentCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PostListCommentCache {
|
impl PostListCommentCache {
|
||||||
pub async fn init(mode: u8, rconn: &RdsConn) -> Self {
|
pub fn init(mode: u8, rconn: &RdsConn) -> Self {
|
||||||
let mut cacher = PostListCommentCache {
|
Self {
|
||||||
key: format!("hole_v2:cache:post_list:{}", &mode),
|
key: format!("hole_v2:cache:post_list:{}", &mode),
|
||||||
mode: mode,
|
mode: mode,
|
||||||
rconn: rconn.clone(),
|
rconn: rconn.clone(),
|
||||||
length: 0,
|
length: 0,
|
||||||
};
|
}
|
||||||
cacher.set_and_check_length().await;
|
|
||||||
cacher
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_and_check_length(&mut self) {
|
async fn set_and_check_length(&mut self) {
|
||||||
let mut l = self.rconn.zcard(&self.key).await.unwrap();
|
let mut l = self.rconn.zcard(&self.key).await.unwrap();
|
||||||
if l > MAX_LENGTH {
|
if l > MAX_LENGTH {
|
||||||
self.rconn
|
self.rconn
|
||||||
.zremrangebyrank::<&String, ()>(&self.key, MIN_LENGTH, -1)
|
.zremrangebyrank::<&String, ()>(&self.key, MAX_LENGTH - CUT_LENGTH, -1)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
warn!("cut list cache failed, {}, {}", e, &self.key);
|
warn!("cut list cache failed, {}, {}", e, &self.key);
|
||||||
@@ -204,7 +200,8 @@ impl PostListCommentCache {
|
|||||||
self.length = l;
|
self.length = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn need_fill(&self) -> bool {
|
pub async fn need_fill(&mut self) -> bool {
|
||||||
|
self.set_and_check_length().await;
|
||||||
self.length < MIN_LENGTH
|
self.length < MIN_LENGTH
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +241,7 @@ impl PostListCommentCache {
|
|||||||
pub async fn put(&mut self, p: &Post) {
|
pub async fn put(&mut self, p: &Post) {
|
||||||
// 其他都是加到最前面的,但热榜不是。可能导致MIN_LENGTH到MAX_LENGTH之间的数据不可靠
|
// 其他都是加到最前面的,但热榜不是。可能导致MIN_LENGTH到MAX_LENGTH之间的数据不可靠
|
||||||
// 影响不大,先不管了
|
// 影响不大,先不管了
|
||||||
if p.is_deleted || (self.mode > 0 && p.is_reported) {
|
if p.is_deleted || (self.mode > 0 && p.is_reported) {
|
||||||
self.rconn.zrem(&self.key, p.id).await.unwrap_or_else(|e| {
|
self.rconn.zrem(&self.key, p.id).await.unwrap_or_else(|e| {
|
||||||
warn!(
|
warn!(
|
||||||
"remove from list cache failed, {} {} {}",
|
"remove from list cache failed, {} {} {}",
|
||||||
@@ -286,12 +283,7 @@ pub struct UserCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UserCache {
|
impl UserCache {
|
||||||
pub fn init(token: &str, rconn: &RdsConn) -> Self {
|
init!(&str, "hole_v2:cache:user:{}");
|
||||||
UserCache {
|
|
||||||
key: format!("hole_v2:cache:user:{}", token),
|
|
||||||
rconn: rconn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set(&mut self, u: &User) {
|
pub async fn set(&mut self, u: &User) {
|
||||||
self.rconn
|
self.rconn
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
api::systemlog::get_systemlog,
|
api::systemlog::get_systemlog,
|
||||||
api::operation::delete,
|
api::operation::delete,
|
||||||
api::operation::report,
|
api::operation::report,
|
||||||
|
api::operation::block,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.register(
|
.register(
|
||||||
|
|||||||
@@ -218,8 +218,8 @@ impl Post {
|
|||||||
start: i64,
|
start: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> QueryResult<Vec<Self>> {
|
) -> QueryResult<Vec<Self>> {
|
||||||
let mut cacher = PostListCommentCache::init(order_mode, &rconn).await;
|
let mut cacher = PostListCommentCache::init(order_mode, &rconn);
|
||||||
if cacher.need_fill() {
|
if cacher.need_fill().await {
|
||||||
let pids =
|
let pids =
|
||||||
Self::_get_ids_by_page(db, order_mode.clone(), 0, cacher.i64_minlen()).await?;
|
Self::_get_ids_by_page(db, order_mode.clone(), 0, cacher.i64_minlen()).await?;
|
||||||
let ps = Self::get_multi(db, rconn, &pids).await?;
|
let ps = Self::get_multi(db, rconn, &pids).await?;
|
||||||
@@ -327,7 +327,6 @@ impl Post {
|
|||||||
self.set_instance_cache(rconn),
|
self.set_instance_cache(rconn),
|
||||||
future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move {
|
future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move {
|
||||||
PostListCommentCache::init(mode, &rconn.clone())
|
PostListCommentCache::init(mode, &rconn.clone())
|
||||||
.await
|
|
||||||
.put(self)
|
.put(self)
|
||||||
.await
|
.await
|
||||||
})),
|
})),
|
||||||
@@ -342,7 +341,7 @@ impl Post {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
PostCache::init(&rconn).clear_all().await;
|
PostCache::init(&rconn).clear_all().await;
|
||||||
PostListCommentCache::init(2, rconn).await.clear().await
|
PostListCommentCache::init(2, rconn).clear().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,31 @@ use redis::{AsyncCommands, RedisResult};
|
|||||||
use rocket::serde::json::serde_json;
|
use rocket::serde::json::serde_json;
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
macro_rules! init {
|
||||||
|
($ktype:ty, $formatter:expr) => {
|
||||||
|
pub fn init(k: $ktype, rconn: &RdsConn) -> Self {
|
||||||
|
Self {
|
||||||
|
key: format!($formatter, k),
|
||||||
|
rconn: rconn.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
() => {
|
||||||
|
pub fn init(rconn: &RdsConn) -> Self {
|
||||||
|
Self {
|
||||||
|
rconn: rconn.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list";
|
const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list";
|
||||||
const KEY_BANNED_USERS: &str = "hole_v2:banned_user_hash_list";
|
const KEY_BANNED_USERS: &str = "hole_v2:banned_user_hash_list";
|
||||||
|
const KEY_BLOCKED_COUNTER: &str = "hole_v2:blocked_counter";
|
||||||
|
const KEY_DANGEROUS_USERS: &str = "hole_thu:dangerous_users"; //兼容一下旧版
|
||||||
|
|
||||||
const SYSTEMLOG_MAX_LEN: isize = 1000;
|
const SYSTEMLOG_MAX_LEN: isize = 1000;
|
||||||
|
pub const BLOCK_THRESHOLD: i32 = 10;
|
||||||
|
|
||||||
pub struct Attention {
|
pub struct Attention {
|
||||||
key: String,
|
key: String,
|
||||||
@@ -14,12 +36,7 @@ pub struct Attention {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Attention {
|
impl Attention {
|
||||||
pub fn init(namehash: &str, rconn: &RdsConn) -> Self {
|
init!(&str, "hole_v2:attention:{}");
|
||||||
Attention {
|
|
||||||
key: format!("hole_v2:attention:{}", namehash),
|
|
||||||
rconn: rconn.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add(&mut self, pid: i32) -> RedisResult<()> {
|
pub async fn add(&mut self, pid: i32) -> RedisResult<()> {
|
||||||
self.rconn.sadd(&self.key, pid).await
|
self.rconn.sadd(&self.key, pid).await
|
||||||
@@ -108,3 +125,62 @@ impl BannedUsers {
|
|||||||
rconn.clone().del(KEY_BANNED_USERS).await
|
rconn.clone().del(KEY_BANNED_USERS).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BlockedUsers {
|
||||||
|
pub key: String,
|
||||||
|
rconn: RdsConn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockedUsers {
|
||||||
|
init!(i32, "hole_v2:blocked_users:{}");
|
||||||
|
|
||||||
|
pub async fn add(&mut self, namehash: &str) -> RedisResult<()> {
|
||||||
|
self.rconn.sadd(&self.key, namehash).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn has(&mut self, namehash: &str) -> RedisResult<bool> {
|
||||||
|
self.rconn.sismember(&self.key, namehash).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_blocked(
|
||||||
|
rconn: &RdsConn,
|
||||||
|
viewer_id: Option<i32>,
|
||||||
|
viewer_hash: &str,
|
||||||
|
author_hash: &str,
|
||||||
|
) -> RedisResult<bool> {
|
||||||
|
Ok(match viewer_id {
|
||||||
|
Some(id) => Self::init(id, rconn).has(author_hash).await?,
|
||||||
|
None => false,
|
||||||
|
} || (DangerousUser::has(rconn, author_hash).await?
|
||||||
|
&& !DangerousUser::has(rconn,viewer_hash).await?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BlockCounter;
|
||||||
|
|
||||||
|
impl BlockCounter {
|
||||||
|
pub async fn count_incr(rconn: &RdsConn, namehash: &str) -> RedisResult<i32> {
|
||||||
|
rconn.clone().hincr(KEY_BLOCKED_COUNTER, namehash, 1).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_count(rconn: &RdsConn, namehash: &str) -> RedisResult<i32> {
|
||||||
|
rconn.clone().hget(KEY_BLOCKED_COUNTER, namehash).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DangerousUser;
|
||||||
|
|
||||||
|
impl DangerousUser {
|
||||||
|
pub async fn add(rconn: &RdsConn, namehash: &str) -> RedisResult<()> {
|
||||||
|
rconn
|
||||||
|
.clone()
|
||||||
|
.sadd::<&str, &str, ()>(KEY_DANGEROUS_USERS, namehash)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn has(rconn: &RdsConn, namehash: &str) -> RedisResult<bool> {
|
||||||
|
rconn.clone().sismember(KEY_DANGEROUS_USERS, namehash).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use init;
|
||||||
|
|||||||
Reference in New Issue
Block a user