init
This commit is contained in:
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
DATABASE_URL=mysql://kirara:kirara169482@localhost:3306/caszl
|
||||
UPLOAD_PATH=D:/Downloads/upload_path
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
11
.idea/caszl-front-rs.iml
generated
Normal file
11
.idea/caszl-front-rs.iml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/caszl-front-rs.iml" filepath="$PROJECT_DIR$/.idea/caszl-front-rs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
2618
Cargo.lock
generated
Normal file
2618
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "caszl-front-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum = "0.6.20"
|
||||
chrono = "0.4.31"
|
||||
dotenvy = "0.15.7"
|
||||
sea-orm = { version = "0.12.4", features = ["sqlx-mysql", "runtime-tokio-native-tls", "macros"] }
|
||||
serde = { version = "1.0.192", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
tokio = { version = "1.34.0", features = ["full"] }
|
1
src/dto.rs
Normal file
1
src/dto.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod article;
|
1
src/dto/article.rs
Normal file
1
src/dto/article.rs
Normal file
@ -0,0 +1 @@
|
||||
|
2
src/entity.rs
Normal file
2
src/entity.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod la_article;
|
||||
pub mod la_article_category;
|
32
src/entity/la_article.rs
Normal file
32
src/entity/la_article.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, DeriveEntityModel, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[sea_orm(table_name = "la_article")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: u32,
|
||||
pub cid: u32,
|
||||
pub title: String,
|
||||
pub intro: String,
|
||||
pub summary: Option<String>,
|
||||
pub image: String,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub content: Option<String>,
|
||||
pub author: String,
|
||||
pub visit: u32,
|
||||
pub sort: u32,
|
||||
pub is_show: u8,
|
||||
pub is_delete: u8,
|
||||
pub create_time: u32,
|
||||
pub update_time: u32,
|
||||
pub delete_time: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
22
src/entity/la_article_category.rs
Normal file
22
src/entity/la_article_category.rs
Normal file
@ -0,0 +1,22 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "la_article_category")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub sort: u16,
|
||||
pub is_show: u8,
|
||||
pub is_delete: u8,
|
||||
pub create_time: Option<u32>,
|
||||
pub update_time: Option<u32>,
|
||||
pub delete_time: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
1
src/handler.rs
Normal file
1
src/handler.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod article;
|
30
src/handler/article.rs
Normal file
30
src/handler/article.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::entity::la_article;
|
||||
use crate::params::article::{ArticleDetailParams, ArticleListParams};
|
||||
use crate::state::app::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Json;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QueryTrait};
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::response::article::{ArticleDetail, ArticleListItem};
|
||||
use crate::response::common::{BaseResponse, PageResponse};
|
||||
use crate::service::artile::ArticleService;
|
||||
|
||||
pub async fn article_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
query: Query<ArticleListParams>,
|
||||
) -> Result<Json<BaseResponse<PageResponse<ArticleListItem>>>, Json<BaseResponse<Vec<u8>>>> {
|
||||
let article_service = ArticleService { state };
|
||||
article_service.article_list(query.0).await
|
||||
}
|
||||
|
||||
pub async fn article_detail(
|
||||
State(state): State<Arc<AppState>>,
|
||||
query: Query<ArticleDetailParams>,
|
||||
) -> Result<Json<BaseResponse<ArticleDetail>>, Json<BaseResponse<Vec<u8>>>> {
|
||||
let article_service = ArticleService { state };
|
||||
article_service.article_detail(query.0).await
|
||||
}
|
31
src/main.rs
Normal file
31
src/main.rs
Normal file
@ -0,0 +1,31 @@
|
||||
mod dto;
|
||||
mod entity;
|
||||
mod handler;
|
||||
mod params;
|
||||
mod repository;
|
||||
mod response;
|
||||
mod routes;
|
||||
mod service;
|
||||
mod state;
|
||||
|
||||
use crate::state::app::AppState;
|
||||
use axum::{Server, ServiceExt};
|
||||
use sea_orm::Database;
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
let db_url = env::var("DATABASE_URL").expect(".env 文件中没有设置 DATABASE_URL");
|
||||
|
||||
let db_conn = Database::connect(&db_url).await.expect("数据库链接失败");
|
||||
|
||||
let app_state = AppState { db_conn };
|
||||
|
||||
let app = routes::create_routes(Arc::new(app_state));
|
||||
Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
1
src/params.rs
Normal file
1
src/params.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod article;
|
17
src/params/article.rs
Normal file
17
src/params/article.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArticleListParams {
|
||||
pub page_no: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub cid: Option<u32>,
|
||||
pub sort: Option<String>,
|
||||
pub keyword: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArticleDetailParams {
|
||||
pub id: String,
|
||||
}
|
1
src/repository.rs
Normal file
1
src/repository.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod article;
|
1
src/repository/article.rs
Normal file
1
src/repository/article.rs
Normal file
@ -0,0 +1 @@
|
||||
|
2
src/response.rs
Normal file
2
src/response.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod article;
|
||||
pub mod common;
|
42
src/response/article.rs
Normal file
42
src/response/article.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::entity::la_article;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArticleListItem {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub image: Option<String>,
|
||||
pub intro: Option<String>,
|
||||
pub visit: u32,
|
||||
pub collect: bool,
|
||||
pub create_time: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArticleDetail {
|
||||
pub id: u32,
|
||||
pub cid: Option<u32>,
|
||||
pub category: Option<String>,
|
||||
pub title: String,
|
||||
pub intro: Option<String>,
|
||||
pub summary: Option<String>,
|
||||
pub image: Option<String>,
|
||||
pub content: Option<String>,
|
||||
pub author: String,
|
||||
pub visit: u32,
|
||||
pub sort: u32,
|
||||
pub is_collect: u32,
|
||||
pub create_time: String,
|
||||
pub update_time: Option<String>,
|
||||
pub prev: Option<SiblingArticle>,
|
||||
pub next: Option<SiblingArticle>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SiblingArticle {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
}
|
19
src/response/common.rs
Normal file
19
src/response/common.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use axum::http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BaseResponse<T: Serialize> {
|
||||
pub code: u16,
|
||||
pub message: String,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageResponse<T: Serialize> {
|
||||
pub page_no: u64,
|
||||
pub page_size: u64,
|
||||
pub count: u64,
|
||||
pub lists: Vec<T>,
|
||||
}
|
20
src/routes.rs
Normal file
20
src/routes.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use crate::handler::article::{article_detail, article_list};
|
||||
use crate::state::app::AppState;
|
||||
use axum::routing::{get, IntoMakeService};
|
||||
use axum::{Router, ServiceExt};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn create_routes(app_state: Arc<AppState>) -> IntoMakeService<Router> {
|
||||
Router::new()
|
||||
.nest(
|
||||
"/api",
|
||||
Router::new()
|
||||
.nest("/article", Router::new().route("/list", get(article_list)))
|
||||
.nest(
|
||||
"/pc",
|
||||
Router::new().route("/articleDetail", get(article_detail)),
|
||||
),
|
||||
)
|
||||
.with_state(app_state)
|
||||
.into_make_service()
|
||||
}
|
1
src/service.rs
Normal file
1
src/service.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod artile;
|
202
src/service/artile.rs
Normal file
202
src/service/artile.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::la_article;
|
||||
use crate::params::article::{ArticleDetailParams, ArticleListParams};
|
||||
use crate::response::article::{ArticleDetail, ArticleListItem, SiblingArticle};
|
||||
use crate::response::common::{BaseResponse, PageResponse};
|
||||
use crate::state::app::AppState;
|
||||
use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QueryTrait};
|
||||
|
||||
|
||||
pub struct ArticleService {
|
||||
pub state: Arc<AppState>,
|
||||
}
|
||||
|
||||
impl ArticleService {
|
||||
pub fn new(state: &Arc<AppState>) -> Self {
|
||||
Self {
|
||||
state: Arc::clone(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn article_list(
|
||||
&self,
|
||||
params: ArticleListParams,
|
||||
) -> Result<Json<BaseResponse<PageResponse<ArticleListItem>>>, Json<BaseResponse<Vec<u8>>>>
|
||||
{
|
||||
let page_no = params.page_no.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let articles_selection = la_article::Entity::find()
|
||||
.filter(la_article::Column::IsDelete.ne(1))
|
||||
.filter(la_article::Column::IsShow.ne(0));
|
||||
let count = match articles_selection.clone().count(&self.state.db_conn).await {
|
||||
Ok(total) => total,
|
||||
Err(_) => 0,
|
||||
};
|
||||
|
||||
let articles_result = articles_selection
|
||||
.apply_if(Some(params.sort), |mut query, v| {
|
||||
match v {
|
||||
Some(sort_str) => {
|
||||
// 最热
|
||||
if sort_str == "hot" {
|
||||
query
|
||||
.order_by_desc(la_article::Column::Visit)
|
||||
.order_by_desc(la_article::Column::Id)
|
||||
}
|
||||
// 最新
|
||||
else if sort_str == "new" {
|
||||
query.order_by_desc(la_article::Column::Id)
|
||||
} else {
|
||||
query
|
||||
.order_by_desc(la_article::Column::Sort)
|
||||
.order_by_desc(la_article::Column::Id)
|
||||
}
|
||||
}
|
||||
None => query,
|
||||
}
|
||||
})
|
||||
.paginate(&self.state.db_conn, params.page_size.unwrap_or(20))
|
||||
.fetch_page(params.page_no.unwrap_or(1) - 1)
|
||||
.await;
|
||||
match articles_result {
|
||||
Ok(articles) => {
|
||||
let mut article_list: Vec<ArticleListItem> = vec![];
|
||||
for article in articles {
|
||||
let dt = Utc.timestamp_opt(article.create_time as i64, 0);
|
||||
let article_item = ArticleListItem {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
image: Some(article.image),
|
||||
intro: Some(article.intro),
|
||||
visit: article.visit,
|
||||
collect: false,
|
||||
create_time: dt.unwrap().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
};
|
||||
article_list.push(article_item);
|
||||
}
|
||||
let page_data = PageResponse {
|
||||
count,
|
||||
page_no,
|
||||
page_size,
|
||||
lists: article_list,
|
||||
};
|
||||
let response = BaseResponse {
|
||||
code: StatusCode::OK.as_u16(),
|
||||
message: "成功".to_string(),
|
||||
data: page_data,
|
||||
};
|
||||
Ok(Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
let err_resp = BaseResponse {
|
||||
code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||
message: format!("{}", err),
|
||||
data: vec![],
|
||||
};
|
||||
Err(Json(err_resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn article_detail(
|
||||
&self,
|
||||
params: ArticleDetailParams,
|
||||
) -> Result<Json<BaseResponse<ArticleDetail>>, Json<BaseResponse<Vec<u8>>>> {
|
||||
let id: u32 = match params.id.parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
let err_resp = BaseResponse {
|
||||
code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||
message: "请输入正确的文章 id".to_string(),
|
||||
data: vec![],
|
||||
};
|
||||
return Err(Json(err_resp));
|
||||
}
|
||||
};
|
||||
|
||||
match la_article::Entity::find_by_id(id)
|
||||
.filter(la_article::Column::IsDelete.eq(0))
|
||||
.filter(la_article::Column::IsShow.eq(1))
|
||||
.one(&self.state.db_conn)
|
||||
.await
|
||||
{
|
||||
Ok(Some(article)) => {
|
||||
// 上一篇文章
|
||||
let prev_result = la_article::Entity::find()
|
||||
.filter(la_article::Column::IsDelete.eq(0))
|
||||
.filter(la_article::Column::IsShow.eq(1))
|
||||
.filter(la_article::Column::Id.lt(article.id))
|
||||
.order_by_desc(la_article::Column::Sort)
|
||||
.order_by_desc(la_article::Column::Id)
|
||||
.one(&self.state.db_conn)
|
||||
.await;
|
||||
let prev = match prev_result {
|
||||
Ok(Some(article)) => Some(SiblingArticle {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
}),
|
||||
_ => None
|
||||
};
|
||||
let next_result = la_article::Entity::find()
|
||||
.filter(la_article::Column::IsDelete.eq(0))
|
||||
.filter(la_article::Column::IsShow.eq(1))
|
||||
.filter(la_article::Column::Id.gt(article.id))
|
||||
.order_by_asc(la_article::Column::Sort)
|
||||
.order_by_asc(la_article::Column::Id)
|
||||
.one(&self.state.db_conn)
|
||||
.await;
|
||||
let next = match next_result {
|
||||
Ok(Some(article)) => Some(SiblingArticle {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
}),
|
||||
_ => None
|
||||
};
|
||||
let article_detail = ArticleDetail {
|
||||
id: article.id,
|
||||
cid: Option::from(article.cid),
|
||||
category: None,
|
||||
title: article.title,
|
||||
intro: Option::from(article.intro),
|
||||
summary: article.summary,
|
||||
image: Option::from(article.image),
|
||||
content: article.content,
|
||||
author: article.author,
|
||||
visit: article.visit,
|
||||
sort: article.sort,
|
||||
is_collect: 0,
|
||||
create_time: "".to_string(),
|
||||
update_time: None,
|
||||
prev,
|
||||
next,
|
||||
};
|
||||
let resp = BaseResponse {
|
||||
code: StatusCode::OK.as_u16(),
|
||||
message: "成功".to_string(),
|
||||
data: article_detail,
|
||||
};
|
||||
Ok(Json(resp))
|
||||
}
|
||||
Ok(None) => {
|
||||
let err_resp = BaseResponse {
|
||||
code: StatusCode::MULTIPLE_CHOICES.as_u16(),
|
||||
message: "文章数据不存在!".to_string(),
|
||||
data: vec![],
|
||||
};
|
||||
Err(Json(err_resp))
|
||||
}
|
||||
Err(err) => {
|
||||
let err_resp = BaseResponse {
|
||||
code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||
message: format!("{}", err),
|
||||
data: vec![],
|
||||
};
|
||||
Err(Json(err_resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
src/state.rs
Normal file
1
src/state.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod app;
|
5
src/state/app.rs
Normal file
5
src/state/app.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
pub struct AppState {
|
||||
pub db_conn: DatabaseConnection,
|
||||
}
|
Reference in New Issue
Block a user