多对多tag的处理
This commit is contained in:
parent
adf1d94c29
commit
5d75c53356
27
app.sql
27
app.sql
|
@ -49,4 +49,29 @@ VALUES (1, '标题-GLKUSroPOR', '内容-GLKUSroPOR'),
|
||||||
(2, '标题-pM0TURxhwC', '内容-pM0TURxhwC'),
|
(2, '标题-pM0TURxhwC', '内容-pM0TURxhwC'),
|
||||||
(1, '标题-svNJmWaqRo', '内容-svNJmWaqRo'),
|
(1, '标题-svNJmWaqRo', '内容-svNJmWaqRo'),
|
||||||
(3, '标题-8XWiTUSfhB', '内容-8XWiTUSfhB'),
|
(3, '标题-8XWiTUSfhB', '内容-8XWiTUSfhB'),
|
||||||
(2, '标题-yvwE32TLkg', '内容-yvwE32TLkg');
|
(2, '标题-yvwE32TLkg', '内容-yvwE32TLkg');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE tags
|
||||||
|
(
|
||||||
|
id int unsigned PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(20) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO tags (name)
|
||||||
|
VALUES ('标签1'),
|
||||||
|
('标签2'),
|
||||||
|
('标签3');
|
||||||
|
|
||||||
|
CREATE TABLE article_tags
|
||||||
|
( -- 文章标签
|
||||||
|
id int unsigned PRIMARY KEY AUTO_INCREMENT, -- 自增主键
|
||||||
|
article_id INT NOT NULL REFERENCES articles (id),
|
||||||
|
tag_id INT NOT NULL REFERENCES tags (id)
|
||||||
|
);
|
||||||
|
INSERT INTO article_tags(article_id, tag_id)
|
||||||
|
VALUES (1, 1),
|
||||||
|
(2, 1),
|
||||||
|
(2, 2),
|
||||||
|
(3, 1),
|
||||||
|
(1, 3);
|
|
@ -44,6 +44,19 @@ impl Related<super::category::Entity> for Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Related<super::tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::article_tag::Relation::Tag.def()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(super::article_tag::Relation::Article.def().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// INSERT/UPDATE/DELETE 等属于写操作,需使用 ActiveModel。
|
// INSERT/UPDATE/DELETE 等属于写操作,需使用 ActiveModel。
|
||||||
/// [DeriveEntityModel] 这个 derive 根据我们定义的[Model],生成了包括 [ActiveModel]在内的多个数据结构
|
/// [DeriveEntityModel] 这个 derive 根据我们定义的[Model],生成了包括 [ActiveModel]在内的多个数据结构
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -0,0 +1,39 @@
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "article_tags")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub id: u32,
|
||||||
|
pub article_id: u32,
|
||||||
|
pub tag_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter)]
|
||||||
|
pub enum Relation {
|
||||||
|
Article,
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
match self {
|
||||||
|
Relation::Article => Entity::belongs_to(super::article::Entity)
|
||||||
|
.from(Column::ArticleId)
|
||||||
|
.to(super::article::Column::Id)
|
||||||
|
.into(),
|
||||||
|
Relation::Tag => Entity::belongs_to(super::tag::Entity)
|
||||||
|
.from(Column::TagId)
|
||||||
|
.to(super::tag::Column::Id)
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
pub mod category;
|
pub mod category;
|
||||||
pub mod article;
|
pub mod article;
|
||||||
|
pub mod article_tag;
|
||||||
|
pub mod tag;
|
|
@ -0,0 +1,40 @@
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "tags")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl RelationTrait for Relation {
|
||||||
|
fn def(&self) -> RelationDef {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有给标签实体定义关系,而是通过 Related<super::article::Entity>告诉 SeaORM如何去找所需要的文章实体。
|
||||||
|
impl Related<super::article::Entity> for Entity {
|
||||||
|
// 某个实体是否与另一个实体相关
|
||||||
|
fn to()-> RelationDef {
|
||||||
|
super::article_tag::Relation::Article.def()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 某个实体是否通过另一个实体关联 即tag通过article_tag进行关联
|
||||||
|
// 表示通过标签实体关联到某个实体的关系,通过 Tag 的反向关系定义进行关联。
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(super::article_tag::Relation::Tag.def().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use axum::Form;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, EntityTrait, NotSet, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, EntityTrait, NotSet, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
|
|
||||||
use crate::entity::{article, category};
|
use crate::entity::{article, category, tag};
|
||||||
use crate::err::AppError;
|
use crate::err::AppError;
|
||||||
use crate::form::ArticleForm;
|
use crate::form::ArticleForm;
|
||||||
use crate::handler::{get_conn, HtmlResponse, log_error, redirect, RedirectResponse, render};
|
use crate::handler::{get_conn, HtmlResponse, log_error, redirect, RedirectResponse, render};
|
||||||
|
@ -95,4 +95,36 @@ pub async fn add(
|
||||||
.map_err(log_error(handler_name))?;
|
.map_err(log_error(handler_name))?;
|
||||||
|
|
||||||
redirect("/article?msg=文章添加成功")
|
redirect("/article?msg=文章添加成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_with_tags( State(state): State<Arc<AppState>>) -> Result<String> {
|
||||||
|
let handler_name = "article/list_with_tags";
|
||||||
|
let conn = get_conn(&state);
|
||||||
|
|
||||||
|
let list = article::Entity::find()
|
||||||
|
.find_with_related(tag::Entity)
|
||||||
|
.all(conn)
|
||||||
|
.await
|
||||||
|
.map_err(AppError::from)
|
||||||
|
.map_err(log_error(handler_name))?;
|
||||||
|
|
||||||
|
let mut ss = vec![];
|
||||||
|
|
||||||
|
for item in list {
|
||||||
|
let (article, tags) = item;
|
||||||
|
let tags = tags
|
||||||
|
.iter()
|
||||||
|
.map(|tag| format!("【#{} - {}】", &tag.id, &tag.name))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(",")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let s = format!(
|
||||||
|
"文章ID: {}, 文章标题: {}, 标签: {}",
|
||||||
|
&article.id, &article.title, tags,
|
||||||
|
);
|
||||||
|
ss.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ss.join("\n").to_string())
|
||||||
}
|
}
|
|
@ -20,7 +20,8 @@ pub fn init() -> axum::Router<Arc<AppState>> {
|
||||||
|
|
||||||
let article_router = axum::Router::new()
|
let article_router = axum::Router::new()
|
||||||
.route("/", get(handler::article::index))
|
.route("/", get(handler::article::index))
|
||||||
.route("/add", get(handler::article::add_ui).post(handler::article::add));
|
.route("/add", get(handler::article::add_ui).post(handler::article::add))
|
||||||
|
.route("/tags", get(handler::article::list_with_tags));
|
||||||
|
|
||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
.route("/", get(handler::index))
|
.route("/", get(handler::index))
|
||||||
|
|
Loading…
Reference in New Issue