diff --git a/ocs/.dockerignore b/ocs/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..8ac3e180bc14f8a051f3cbfa5a23f61cab709dbf --- /dev/null +++ b/ocs/.dockerignore @@ -0,0 +1,6 @@ +.dockerignore +.git* +debian/Dockerfile + +# From .gitignore +target/ diff --git a/ocs/.env b/ocs/.env new file mode 100644 index 0000000000000000000000000000000000000000..9ab2cde021a7e4f8208bf22dc9e365fe6329f4c2 --- /dev/null +++ b/ocs/.env @@ -0,0 +1,2 @@ +WEB.ADDR=0.0.0.0:8080 + diff --git a/ocs/.gitignore b/ocs/.gitignore index ae9ea50078ef99e806d95bb3ac4275a15df0c52f..8faf6c27b56686a3ccd4a724cbad1b64d47cd45e 100644 --- a/ocs/.gitignore +++ b/ocs/.gitignore @@ -19,3 +19,27 @@ Cargo.lock # IDE .idea + +site/node_modules/ +site/docs/.vuepress/.cache/ +site/docs/.vuepress/.temp/ +site/docs/.vuepress/dist/ +# npm +site/package-lock.json +site/node_modules +site/yarn-error.log + +# vscode +site/.vscode + +#yarn +site/yarn.lock + + +# 百度链接推送 +site/urls.txt + +# mac +site/.DS_Store + +site/vdoing \ No newline at end of file diff --git a/ocs/Cargo.toml b/ocs/Cargo.toml index b8a14a416e02d843f6f6529463daeea68d087e06..5bdbf700699b6201a43e8080850f628e43125100 100644 --- a/ocs/Cargo.toml +++ b/ocs/Cargo.toml @@ -6,3 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +axum = "0.4" +config = "0.11" +dotenv = "0.15" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +tower-http = { version = "0.2.5", features = ["fs", "trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +rust-embed = "6.3" +mime_guess = "2" \ No newline at end of file diff --git a/ocs/README.md b/ocs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..78d81d35c102784b0f52af68d99764d1376882cd --- /dev/null +++ b/ocs/README.md @@ -0,0 +1,31 @@ +# openEuler Cheat Sheet + +## 运行 + +前端需要 `yarn install` 添加依赖包 +然后可以使用 `yarn dev` 进行预览 + +需要使用 `yarn build` 进行前端构建 +然后在后端运行 `cargo run` +后端构建需要使用 `cargo build --release` + +## 贡献软件包 + +在 `site/docs/02.软件包/`目录下创建 markdown 文件。 +文件名为 `序号.xxx.md` xxx 为软件包名 + +可以用 h2 标题创建一个 描述。 + +这里我们用到一个组件 ,需要提供操作系统的名称,和安装命令。 + +```md + +``` + +参考 `01.sudo.md` 文件 + +## 文档开发 + +使用 vuepress 构建和 vdoing 主题。参考 vuepress markdown 和 vdoing 书写规范。侧边栏需要修改 config.js。 + +在更新文档后运行 `yarn build` 构建前端界面 diff --git a/ocs/debian/Dockerfile b/ocs/debian/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..09fc02ee6aa1411c371003f38b6151250e4bf580 --- /dev/null +++ b/ocs/debian/Dockerfile @@ -0,0 +1,45 @@ +FROM rust:1.60-buster as builder + +RUN USER=root cargo new --bin ocs +WORKDIR ./ocs +COPY ./Cargo.toml ./Cargo.toml +COPY ./.env ./.env +COPY ./debian/config /usr/local/cargo + +RUN CARGO_HTTP_MULTIPLEXING=false cargo fetch + +RUN cargo build --release \ + && rm src/*.rs target/release/deps/ocs* + +ADD . ./ + +RUN cargo build --release + + +FROM debian:buster-slim + +ARG APP=/usr/src/app + +RUN apt-get update \ + && apt-get install -y ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + +EXPOSE 8080 + +ENV TZ=Etc/UTC \ + APP_USER=appuser + +RUN groupadd $APP_USER \ + && useradd -g $APP_USER $APP_USER \ + && mkdir -p ${APP} + +COPY --from=builder /ocs/target/release/ocs ${APP}/ocs +COPY --from=builder ocs/.env ${APP}/.env + + +RUN chown -R $APP_USER:$APP_USER ${APP} + +USER $APP_USER +WORKDIR ${APP} + +CMD ["./ocs"] \ No newline at end of file diff --git a/ocs/debian/config b/ocs/debian/config new file mode 100644 index 0000000000000000000000000000000000000000..401c3cefcdc460fb593238f22077bf598445c251 --- /dev/null +++ b/ocs/debian/config @@ -0,0 +1,5 @@ +[source.crates-io] +replace-with = 'tuna' + +[source.tuna] +registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git" diff --git a/ocs/netlify.toml b/ocs/netlify.toml new file mode 100644 index 0000000000000000000000000000000000000000..1a30e7cee61a5b970f9f3cedd532ca67df147077 --- /dev/null +++ b/ocs/netlify.toml @@ -0,0 +1,4 @@ +[build] +base = "site" +publish = "site/docs/.vuepress/public" +command = "yarn docs:build" diff --git a/ocs/openeuler/Dockerfile b/ocs/openeuler/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e537e19bff63f73ccdf3128c342c0bb01f52ff27 --- /dev/null +++ b/ocs/openeuler/Dockerfile @@ -0,0 +1,45 @@ +FROM rust:1.60-buster as builder + +RUN USER=root cargo new --bin ocs +WORKDIR ./ocs +COPY ./Cargo.toml ./Cargo.toml +COPY ./.env ./.env +COPY ./openeuler/config /usr/local/cargo + +RUN CARGO_HTTP_MULTIPLEXING=false cargo fetch + +RUN cargo build --release \ + && rm src/*.rs target/release/deps/ocs* + +ADD . ./ + +RUN cargo build --release + + +FROM openeuler/openeuler:22.03-lts + +ARG APP=/usr/src/app + +RUN dnf update \ + && dnf install -y ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + +EXPOSE 8080 + +ENV TZ=Etc/UTC \ + APP_USER=appuser + +RUN groupadd $APP_USER \ + && useradd -g $APP_USER $APP_USER \ + && mkdir -p ${APP} + +COPY --from=builder /ocs/target/release/ocs ${APP}/ocs +COPY --from=builder ocs/.env ${APP}/.env + + +RUN chown -R $APP_USER:$APP_USER ${APP} + +USER $APP_USER +WORKDIR ${APP} + +CMD ["./ocs"] \ No newline at end of file diff --git a/ocs/openeuler/config b/ocs/openeuler/config new file mode 100644 index 0000000000000000000000000000000000000000..401c3cefcdc460fb593238f22077bf598445c251 --- /dev/null +++ b/ocs/openeuler/config @@ -0,0 +1,5 @@ +[source.crates-io] +replace-with = 'tuna' + +[source.tuna] +registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git" diff --git a/ocs/screenshot/screenshot01.png b/ocs/screenshot/screenshot01.png new file mode 100644 index 0000000000000000000000000000000000000000..84e6960150c9dba3745cecd778fdc9c77c6a2d5e Binary files /dev/null and b/ocs/screenshot/screenshot01.png differ diff --git a/ocs/screenshot/screenshot02.png b/ocs/screenshot/screenshot02.png new file mode 100644 index 0000000000000000000000000000000000000000..33b727f52da80e153f0b851df6ca7ef6aa11509b Binary files /dev/null and b/ocs/screenshot/screenshot02.png differ diff --git a/ocs/screenshot/screenshot03.png b/ocs/screenshot/screenshot03.png new file mode 100644 index 0000000000000000000000000000000000000000..c0bb50ec33ed3bb121fda903e2122f50ab0b33d3 Binary files /dev/null and b/ocs/screenshot/screenshot03.png differ diff --git a/ocs/site b/ocs/site new file mode 160000 index 0000000000000000000000000000000000000000..2cbd5d01e13d557225c1f89a51053e0ccb3a1723 --- /dev/null +++ b/ocs/site @@ -0,0 +1 @@ +Subproject commit 2cbd5d01e13d557225c1f89a51053e0ccb3a1723 diff --git a/ocs/src/config.rs b/ocs/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..bac115b74f75e8157c2e906c6ad0f2c25a0460ee --- /dev/null +++ b/ocs/src/config.rs @@ -0,0 +1,27 @@ +//! 配置文件 + +use serde::Deserialize; + +/// Web配置 +#[derive(Deserialize)] +pub struct WebConfig { + /// Web服务监听地址 + pub addr: String, +} + +#[derive(Deserialize)] +pub struct Config { + /// Web配置 + pub web: WebConfig, +} + +impl Config { + /// 从环境变量中初始化配置 + pub fn from_env() -> Result { + let mut cfg = config::Config::new(); + // 尝试合并环境变量设置 + cfg.merge(config::Environment::new())?; + // 转换成我们自己的Config对象 + cfg.try_into() + } +} diff --git a/ocs/src/error.rs b/ocs/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..386c310ee343ff3419f236f8c1130ee18aec7c58 --- /dev/null +++ b/ocs/src/error.rs @@ -0,0 +1,77 @@ +//! 自定义错误 +use crate::response::Response; +use axum::{response::IntoResponse, Json}; +use std::fmt::Display; + +/// 错误的类型 +#[derive(Debug)] +pub enum AppErrorType { + /// 数据库错误 + DbError, + /// 未找到 + NotFound, +} + +/// 应用错误 +#[derive(Debug)] +pub struct AppError { + /// 错误信息 + pub message: Option, + /// 错误原因(上一级的错误) + pub cause: Option, + /// 错误类型 + pub error_type: AppErrorType, +} + +impl AppError { + /// 错误代码 + fn code(&self) -> i32 { + match self.error_type { + AppErrorType::DbError => 1, + AppErrorType::NotFound => 2, + } + } + /// 从上级错误中创建应用错误 + fn from_err(err: impl ToString, error_type: AppErrorType) -> Self { + Self { + message: None, + cause: Some(err.to_string()), + error_type, + } + } + /// 从字符串创建应用错误 + fn from_str(msg: &str, error_type: AppErrorType) -> Self { + Self { + message: Some(msg.to_string()), + cause: None, + error_type, + } + } + /// 数据库错误 + pub fn db_error(err: impl ToString) -> Self { + Self::from_err(err, AppErrorType::DbError) + } + /// 未找到 + pub fn not_found() -> Self { + Self::from_str("不存在的记录", AppErrorType::NotFound) + } +} +impl std::error::Error for AppError {} +impl Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +/// 实现 IntoResponse +impl IntoResponse for AppError { + fn into_response(self) -> axum::response::Response { + let code = (&self).code(); + let msg = match self.message { + Some(msg) => msg, + None => "有错误发生".to_string(), + }; + let res: Response<()> = Response::err(code, msg); + Json(res).into_response() + } +} diff --git a/ocs/src/handler/mod.rs b/ocs/src/handler/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..41345dc9ec11afb72acbc4ef0a6aeb116c3cdbe4 --- /dev/null +++ b/ocs/src/handler/mod.rs @@ -0,0 +1,2 @@ +mod staticHandler; +pub use staticHandler::*; diff --git a/ocs/src/handler/staticHandler.rs b/ocs/src/handler/staticHandler.rs new file mode 100644 index 0000000000000000000000000000000000000000..05d465e97a9665e5313b483ec0db5fb436530ff0 --- /dev/null +++ b/ocs/src/handler/staticHandler.rs @@ -0,0 +1,44 @@ +use axum::{http::{Uri, Response, header, StatusCode}, response::IntoResponse, body::{boxed, Full}}; +use rust_embed::RustEmbed; +#[derive(RustEmbed)] +#[folder = "site/docs/.vuepress/dist"] +struct Asset; +struct StaticFile(pub T); +impl IntoResponse for StaticFile +where + T: Into, +{ + fn into_response(self) -> axum::response::Response { + let path = self.0.into(); + match Asset::get(path.as_str()) { + Some(content) => { + let body = boxed(Full::from(content.data)); + let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream(); + Response::builder() + .header(header::CONTENT_TYPE, mime.as_ref()) + .body(body) + .unwrap() + } + None => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(boxed(Full::from(format!("{} not found", path)))) + .unwrap(), + } + } +} + + + +pub async fn static_handler(uri: Uri) -> impl IntoResponse { + let path = uri.path().trim_start_matches('/').to_string(); + StaticFile(path) +} + +pub async fn index_handler() -> impl IntoResponse { + static_handler("/index.html".parse().unwrap()).await +} + +pub async fn handler(pkgName: &str) -> impl IntoResponse { + let path = format!("/{}.html", pkgName); + StaticFile(path) +} diff --git a/ocs/src/main.rs b/ocs/src/main.rs index e7a11a969c037e00a796aafeff6258501ec15e9a..6d12b72d19cadbe364cca0e4b79a639b8e5c68e7 100644 --- a/ocs/src/main.rs +++ b/ocs/src/main.rs @@ -1,3 +1,83 @@ -fn main() { - println!("Hello, world!"); +mod config; +mod error; +mod handler; +mod response; +use crate::{ + config::Config, + handler::{index_handler, static_handler}, +}; +use axum::{ + handler::Handler, + http::StatusCode, + routing::{get, get_service}, + Router, +}; +use dotenv::dotenv; +use tokio::signal; +use tower_http::{services::ServeDir, trace::TraceLayer}; +use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt}; + +/// 定义自己的 Result +type Result = std::result::Result; + +#[tokio::main] +async fn main() { + // 初始化日志 + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG") + .unwrap_or_else(|_| "openEuler-cheat-sheet=debug,tower_http=debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + + dotenv().ok(); // 解析 .env 文件 + let cfg = Config::from_env().expect("初始化项目配置失败"); + + let app = Router::new() + .route("/", get(index_handler)) + .nest( + "/static", + get_service(ServeDir::new("../front/public")).handle_error(|err| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("处理静态资源出错: {:?}", err), + ) + }), + ) + .fallback(static_handler.into_service()) + .layer(TraceLayer::new_for_http()); + + let addr = cfg.web.addr; + tracing::info!("服务器监听于:{}", addr); + axum::Server::bind(&addr.parse().unwrap()) + .serve(app.into_make_service()) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); } + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + println!("signal received, starting graceful shutdown"); +} \ No newline at end of file diff --git a/ocs/src/response.rs b/ocs/src/response.rs new file mode 100644 index 0000000000000000000000000000000000000000..9b4ac018d92fde6be307eab9cfee35c2d44e48ff --- /dev/null +++ b/ocs/src/response.rs @@ -0,0 +1,24 @@ +//! 自定义响应 +use serde::Serialize; + +#[derive(Serialize)] +pub struct Response { + pub code: i32, + pub msg: String, + pub data: Option, +} + +impl Response +where + T: Serialize, +{ + pub fn new(code: i32, msg: String, data: Option) -> Self { + Self { code, msg, data } + } + pub fn ok(data: T) -> Self { + Self::new(0, "OK".to_string(), Some(data)) + } + pub fn err(code: i32, msg: String) -> Self { + Self::new(code, msg, None) + } +} \ No newline at end of file