完成后端数据库的连接,后端配置文件的读,前端动态页面的选择
This commit is contained in:
commit
792346d43d
14
backend/Cargo.toml
Normal file
14
backend/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "echoes"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { version = "0.5", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
toml = "0.8.19"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tokio-postgres = "0.7.12"
|
||||||
|
once_cell = "1.20.2"
|
||||||
|
async-trait = "0.1.83"
|
22
backend/src/config.rs
Normal file
22
backend/src/config.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub info: Info,
|
||||||
|
pub database: Database,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Info {
|
||||||
|
pub install: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Database {
|
||||||
|
pub ilk : String,
|
||||||
|
pub address : String,
|
||||||
|
pub prot : u32,
|
||||||
|
pub user : String,
|
||||||
|
pub password : String,
|
||||||
|
pub dbname : String,
|
||||||
|
}
|
10
backend/src/config.toml
Normal file
10
backend/src/config.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[info]
|
||||||
|
install = false
|
||||||
|
|
||||||
|
[database]
|
||||||
|
ilk = "postgresql"
|
||||||
|
address = "localhost"
|
||||||
|
prot = 5432
|
||||||
|
user = "postgres"
|
||||||
|
password = "postgres"
|
||||||
|
dbname = "echoes"
|
61
backend/src/main.rs
Normal file
61
backend/src/main.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
mod sql;
|
||||||
|
mod config;
|
||||||
|
use rocket::{get, launch, routes, Route};
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
|
use rocket::serde::{ Serialize};
|
||||||
|
use rocket::response::status;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use tokio::sync::Mutex as TokioMutex;
|
||||||
|
use tokio_postgres::types::ToSql;
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
static GLOBAL_SQL: Lazy<Arc<TokioMutex<Option<Box<dyn sql::Database >>>>>
|
||||||
|
= Lazy::new(|| Arc::new(TokioMutex::new(None)));
|
||||||
|
|
||||||
|
// 获取数据库连接
|
||||||
|
async fn initialize_sql() {
|
||||||
|
let sql_instance = sql::loading().await;
|
||||||
|
let mut lock = GLOBAL_SQL.lock().await;
|
||||||
|
*lock = sql_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网站初始化
|
||||||
|
#[get("/install")]
|
||||||
|
fn install() -> status::Custom<()> {
|
||||||
|
status::Custom(Status::Ok, ())
|
||||||
|
}
|
||||||
|
// sql查询
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SSql{
|
||||||
|
key:String,
|
||||||
|
value:String,
|
||||||
|
}
|
||||||
|
#[get("/sql")]
|
||||||
|
async fn ssql() -> status::Custom<Json<Vec<SSql>>> {
|
||||||
|
let sql_instance=GLOBAL_SQL.lock().await;
|
||||||
|
let sql =sql_instance.as_ref().unwrap();
|
||||||
|
let query = "SELECT * FROM info";
|
||||||
|
let params: &[&(dyn ToSql + Sync)] = &[];
|
||||||
|
let data = sql.query(query, params).await.expect("查询数据失败");
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for row in data {
|
||||||
|
let key=row.get(0);
|
||||||
|
let value=row.get(1);
|
||||||
|
vec.push(SSql{
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
status::Custom(Status::Ok, Json(vec))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
async fn rocket() -> _ {
|
||||||
|
initialize_sql().await;
|
||||||
|
rocket::build()
|
||||||
|
.mount("/api", routes![install,ssql])
|
||||||
|
}
|
53
backend/src/sql/mod.rs
Normal file
53
backend/src/sql/mod.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
mod postgresql;
|
||||||
|
use std::fs;
|
||||||
|
use tokio_postgres::{Error, Row};
|
||||||
|
use toml;
|
||||||
|
use crate::config::Config;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
// 所有数据库类型
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Database: Send + Sync {
|
||||||
|
async fn query(&self,
|
||||||
|
query: &str,
|
||||||
|
params: &[&(dyn tokio_postgres::types::ToSql + Sync)])
|
||||||
|
-> Result<Vec<Row>, Error>;
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
data: &str,
|
||||||
|
params: &[&(dyn tokio_postgres::types::ToSql + Sync)],
|
||||||
|
)
|
||||||
|
-> Result<u64, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 加载对应数据库
|
||||||
|
pub async fn loading() -> Option<Box<dyn Database>> {
|
||||||
|
let config_string = fs::read_to_string("./src/config.toml")
|
||||||
|
.expect("Could not load config file");
|
||||||
|
let config: Config = toml::de::from_str(config_string.as_str()).expect("Could not parse config");
|
||||||
|
let address = config.database.address;
|
||||||
|
let port = config.database.prot;
|
||||||
|
let user = config.database.user;
|
||||||
|
let password = config.database.password;
|
||||||
|
let dbname = config.database.dbname;
|
||||||
|
let sql_instance: Box<dyn Database>;
|
||||||
|
|
||||||
|
match config.database.ilk.as_str() {
|
||||||
|
"postgresql" => {
|
||||||
|
let sql = postgresql::connect(address, port, user, password, dbname).await;
|
||||||
|
match sql {
|
||||||
|
Ok(conn) => {
|
||||||
|
sql_instance = Box::new(conn);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Database connection failed {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
_ => { return None }
|
||||||
|
};
|
||||||
|
Some(sql_instance)
|
||||||
|
}
|
65
backend/src/sql/postgresql.rs
Normal file
65
backend/src/sql/postgresql.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use tokio_postgres::{NoTls, Error, Row, Client, Connection, Socket};
|
||||||
|
use crate::sql;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio_postgres::tls::NoTlsStream;
|
||||||
|
|
||||||
|
pub struct Postgresql {
|
||||||
|
pub client: tokio_postgres::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect(
|
||||||
|
address: String,
|
||||||
|
port: u32,
|
||||||
|
user: String,
|
||||||
|
password: String,
|
||||||
|
dbname: String,
|
||||||
|
) -> Result<Postgresql, Error> {
|
||||||
|
let connection_str = format!(
|
||||||
|
"host={} port={} user={} password={} dbname={}",
|
||||||
|
address, port, user, password, dbname
|
||||||
|
);
|
||||||
|
|
||||||
|
let client:Client;
|
||||||
|
let connection:Connection<Socket, NoTlsStream>;
|
||||||
|
let link = tokio_postgres::connect(&connection_str, NoTls).await;
|
||||||
|
match link {
|
||||||
|
Ok((clie,conne)) => {
|
||||||
|
client = clie;
|
||||||
|
connection = conne;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Failed to connect to postgresql: {}", err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = connection.await {
|
||||||
|
eprintln!("postgresql connection error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Postgresql { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Postgresql {
|
||||||
|
|
||||||
|
}
|
||||||
|
#[async_trait]
|
||||||
|
impl sql::Database for Postgresql {
|
||||||
|
async fn query(&self, query: & str,
|
||||||
|
params: &[&(dyn tokio_postgres::types::ToSql + Sync)]
|
||||||
|
) -> Result<Vec<Row>, Error> {
|
||||||
|
let rows = self.client.query(query, params).await?;
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
data: &str,
|
||||||
|
params: &[&(dyn tokio_postgres::types::ToSql + Sync)],
|
||||||
|
) -> Result<u64, Error> {
|
||||||
|
let rows_affected = self.client.execute(data, params).await?;
|
||||||
|
Ok(rows_affected)
|
||||||
|
}
|
||||||
|
}
|
30
frontend/package.json
Normal file
30
frontend/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "echoes",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.28.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.13.0",
|
||||||
|
"@types/react": "^18.3.12",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
|
"eslint": "^9.13.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.14",
|
||||||
|
"globals": "^15.11.0",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"typescript-eslint": "^8.11.0",
|
||||||
|
"vite": "^5.4.10"
|
||||||
|
}
|
||||||
|
}
|
8
frontend/src/install/page.tsx
Normal file
8
frontend/src/install/page.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import react from 'react';
|
||||||
|
const page:react.FC=()=>{
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
安装
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
export default page;
|
3
frontend/src/main.css
Normal file
3
frontend/src/main.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
24
frontend/src/main.tsx
Normal file
24
frontend/src/main.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import "./main.css"
|
||||||
|
import DynamicPage from "./page/page.tsx"
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import {createContext} from "react";
|
||||||
|
|
||||||
|
export const serverAddressContext=createContext("localhost:8080")
|
||||||
|
// 动态路由
|
||||||
|
const RouterListener: React.FC = () => {
|
||||||
|
let pathname = location.pathname.split("/");
|
||||||
|
console.log(pathname)
|
||||||
|
return (
|
||||||
|
<serverAddressContext.Provider value={"localhost:8080"}>
|
||||||
|
<DynamicPage pageName="home"/>
|
||||||
|
</serverAddressContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<RouterListener/>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
36
frontend/src/page/page.tsx
Normal file
36
frontend/src/page/page.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React from "react";
|
||||||
|
const THEMEPATH= "../../themes"
|
||||||
|
import {serverAddressContext} from "../main.tsx";
|
||||||
|
// 动态获取当前主题
|
||||||
|
const getCurrentTheme = async (): Promise<string> => {
|
||||||
|
return new Promise<string>((resolve) => {
|
||||||
|
resolve("default");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取页面
|
||||||
|
const loadPage = (theme: string, pageName: string) => {
|
||||||
|
return import(/* @vite-ignore */`${THEMEPATH}/${theme}/${pageName}/page`).catch(() => import((/* @vite-ignore */`${THEMEPATH}/default/page/page`)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态加载页面
|
||||||
|
const DynamicPage: React.FC<{ pageName: string }> = ({pageName}) => {
|
||||||
|
const serverAddress = React.useContext(serverAddressContext)
|
||||||
|
console.log(serverAddress)
|
||||||
|
const [Page, setPage] = React.useState<React.ComponentType | null>(null);
|
||||||
|
const [theme, setTheme] = React.useState<string>("");
|
||||||
|
React.useEffect(() => {
|
||||||
|
getCurrentTheme().then((theme) => {
|
||||||
|
setTheme(theme);
|
||||||
|
loadPage(theme, pageName).then((module) => {
|
||||||
|
setPage(() => module.default);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if(!Page){
|
||||||
|
return <div>loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Page/>;
|
||||||
|
}
|
||||||
|
export default DynamicPage;
|
7
frontend/themes/default/404/page.tsx
Normal file
7
frontend/themes/default/404/page.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
const Page: React.FC = () => {
|
||||||
|
return <div>404</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default Page;
|
5
frontend/themes/default/page/page.tsx
Normal file
5
frontend/themes/default/page/page.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import React from "react";
|
||||||
|
const Page: React.FC = () => {
|
||||||
|
return <div>主页</div>;
|
||||||
|
};
|
||||||
|
export default Page;
|
Loading…
Reference in New Issue
Block a user