├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── assets ├── custom.js ├── zino-app-arch.png ├── zino-app-arch.svg ├── zino-logo.png ├── zino-logo.svg └── zino-web-wechat.png ├── book.toml ├── deploy.sh ├── src ├── SUMMARY.md ├── basics.md ├── basics │ ├── application.md │ ├── error.md │ ├── model.md │ ├── request.md │ ├── response.md │ ├── schedule.md │ └── state.md ├── guide.md ├── guide │ ├── config.md │ ├── deployment.md │ ├── development.md │ ├── new-app.md │ └── structure.md ├── integrations.md ├── integrations │ ├── actix-web.md │ ├── axum.md │ └── dioxus.md ├── introduction.md ├── orm.md ├── orm │ ├── default-controller.md │ ├── openapi.md │ ├── table-mapping.md │ └── trait-derive.md ├── resources.md ├── topics.md └── topics │ ├── accessor.md │ ├── authentication.md │ ├── cache.md │ ├── channel.md │ ├── chatbot.md │ ├── connector.md │ ├── datetime.md │ ├── extension.md │ ├── i18n.md │ ├── observability.md │ └── view.md └── theme └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | zino.cc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zino-docs-zh 2 | 3 | Zino开发框架中文教程 4 | 5 | - [引言](./src/introduction.md) 6 | - [入门指南](./src/guide.md) 7 | - [创建应用](./src/guide/new-app.md) 8 | - [目录结构](./src/guide/structure.md) 9 | - [配置文件](./src/guide/config.md) 10 | - [基础模块](./src/basics.md) 11 | - [应用接口抽象](./src/basics/application.md) 12 | - [状态管理](./src/basics/state.md) 13 | - [错误处理](./src/basics/error.md) 14 | - [请求上下文(TODO)](./src/basics/request.md) 15 | - [请求响应(TODO)](./src/basics/response.md) 16 | - [任务调度(TODO)](./src/basics/schedule.md) 17 | - [领域模型(TODO)](./src/basics/model.md) 18 | - [数据库ORM(TODO)](./src/orm.md) 19 | - [数据表映射(TODO)](./src/orm/table-mapping.md) 20 | - [Trait推导(TODO)](./src/orm/trait-derive.md) 21 | - [API自动生成(TODO)](./src/orm/default-controller.md) 22 | - [OpenAPI文档(TODO)](./src/orm/openapi.md) 23 | - [进阶专题(TODO)](./src/topics.md) 24 | - [身份认证(TODO)](./src/topics/authentication.md) 25 | - [可观测性(TODO)](./src/topics/observability.md) 26 | - [拓展机制(TODO)](./src/topics/extension.md) 27 | - [框架集成(TODO)](./src/integrations.md) 28 | - [actix-web(TODO)](./src/integrations/actix-web.md) 29 | - [axum(TODO)](./src/integrations/axum.md) 30 | - [dioxus(TODO)](./src/integrations/dioxus.md) 31 | - [更多资源](./src/resources.md) 32 | -------------------------------------------------------------------------------- /assets/custom.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | const script = document.createElement('script'); 3 | script.type = 'text/javascript'; 4 | script.src = 'https://giscus.app/client.js'; 5 | script.async = true; 6 | script.crossOrigin = 'anonymous'; 7 | script.setAttribute('data-repo', 'zino-rs/zino-docs-zh'); 8 | script.setAttribute('data-repo-id', 'R_kgDOJZESbg'); 9 | script.setAttribute('data-category', 'Announcements'); 10 | script.setAttribute('data-category-id', 'DIC_kwDOJZESbs4CecQK'); 11 | script.setAttribute('data-mapping', 'title'); 12 | script.setAttribute('data-script', '0'); 13 | script.setAttribute('data-reactions-enabled', '1'); 14 | script.setAttribute('data-emit-metadata', '0'); 15 | script.setAttribute('data-input-position', 'top'); 16 | script.setAttribute('data-theme', 'preferred_color_scheme'); 17 | script.setAttribute('data-lang', 'zh-CN'); 18 | script.setAttribute('data-loading', 'lazy'); 19 | document.getElementById('content').appendChild(script); 20 | }); -------------------------------------------------------------------------------- /assets/zino-app-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zino-rs/zino-docs-zh/20f1a0a405c1defb5abff7ee3b1e1c4253b8bbc3/assets/zino-app-arch.png -------------------------------------------------------------------------------- /assets/zino-app-arch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
model
model

logic

logic
router
router
middleware
middleware
controller
controller
schedule
schedule
service
service
rpc
rpc
config
config
state
state
application
application
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /assets/zino-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zino-rs/zino-docs-zh/20f1a0a405c1defb5abff7ee3b1e1c4253b8bbc3/assets/zino-logo.png -------------------------------------------------------------------------------- /assets/zino-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 32 | 33 | 34 | 37 | 38 | -------------------------------------------------------------------------------- /assets/zino-web-wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zino-rs/zino-docs-zh/20f1a0a405c1defb5abff7ee3b1e1c4253b8bbc3/assets/zino-web-wechat.png -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Zino开发框架中文教程" 3 | authors = ["photino"] 4 | language = "zh" 5 | multilingual = false 6 | src = "src" 7 | 8 | [rust] 9 | edition = "2021" 10 | 11 | [output.html] 12 | default-theme = "light" 13 | additional-css = ["theme/style.css"] 14 | additional-js = ["assets/custom.js"] 15 | git-repository-url = "https://github.com/zino-rs/zino-docs-zh" 16 | edit-url-template = "https://github.com/zino-rs/zino-docs-zh/edit/main/{path}" 17 | cname = "zino.cc" 18 | 19 | [output.html.playground] 20 | editable = false 21 | copyable = true 22 | copy-js = true 23 | line-numbers = true 24 | runnable = false -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | ## build static website for book 2 | mdbook build 3 | 4 | ## init git repo 5 | cd book 6 | cp ../CNAME ./ 7 | mkdir assets 8 | cp ../assets/** assets/ 9 | git init 10 | git config user.name "photino" 11 | git config user.email "panzan89@163.com" 12 | git add . 13 | git commit -m 'deploy' 14 | git branch -M gh-pages 15 | git remote add origin https://github.com/zino-rs/zino-docs-zh 16 | 17 | ## push to github pages 18 | git push -u -f origin gh-pages -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [引言](./introduction.md) 4 | - [入门指南](./guide.md) 5 | - [创建应用](./guide/new-app.md) 6 | - [目录结构](./guide/structure.md) 7 | - [配置文件](./guide/config.md) 8 | - [基础模块](./basics.md) 9 | - [应用接口抽象](./basics/application.md) 10 | - [状态管理](./basics/state.md) 11 | - [错误处理](./basics/error.md) 12 | - [请求上下文](./basics/request.md) 13 | - [请求响应(TODO)]() 14 | - [任务调度(TODO)]() 15 | - [领域模型(TODO)]() 16 | - [数据库ORM(TODO)]() 17 | - [数据表映射(TODO)]() 18 | - [Trait推导(TODO)]() 19 | - [API自动生成(TODO)]() 20 | - [OpenAPI文档(TODO)]() 21 | - [进阶专题(TODO)]() 22 | - [身份认证(TODO)]() 23 | - [可观测性(TODO)]() 24 | - [拓展机制(TODO)]() 25 | - [框架集成(TODO)]() 26 | - [actix-web(TODO)]() 27 | - [axum(TODO)]() 28 | - [dioxus(TODO)]() 29 | - [更多资源](./resources.md) 30 | -------------------------------------------------------------------------------- /src/basics.md: -------------------------------------------------------------------------------- 1 | # 基础模块 2 | 3 | Zino框架的主要功能都由[`zino-core`]提供,这是与具体Web框架(如[`actix-web`]或[`axum`])无关的抽象, 4 | 其中最基础的就是以下七大模块: 5 | 6 | - [`application`]:应用接口抽象,这是与其他框架差异最大的地方; 7 | - [`state`]:应用状态管理,大量使用惰性初始化的全局变量; 8 | - [`error`]:通用的错误处理,搭配[`bail!`]、[`warn!`]、[`reject!`]使用; 9 | - [`request`]:请求上下文,通过[`RequestContext`] trait提供方法; 10 | - [`response`]:构建请求响应,统一处理不同的`content-type`; 11 | - [`schedule`]:任务调度,提供[`Scheduler`]、[`AsyncScheduler`]抽象; 12 | - [`model`]:领域模型抽象,与具体的数据库和ORM无关。 13 | 14 | [`zino-core`]: https://docs.rs/zino-core 15 | [`actix-web`]: https://docs.rs/actix-web 16 | [`axum`]: https://docs.rs/axum 17 | [`application`]: https://docs.rs/zino-core/latest/zino_core/application/index.html 18 | [`state`]: https://docs.rs/zino-core/latest/zino_core/state/index.html 19 | [`error`]: https://docs.rs/zino-core/latest/zino_core/error/index.html 20 | [`request`]: https://docs.rs/zino-core/latest/zino_core/request/index.html 21 | [`response`]: https://docs.rs/zino-core/latest/zino_core/response/index.html 22 | [`schedule`]: https://docs.rs/zino-core/latest/zino_core/schedule/index.html 23 | [`model`]: https://docs.rs/zino-core/latest/zino_core/model/index.html 24 | [`bail!`]: https://docs.rs/zino-core/latest/zino_core/macro.bail.html 25 | [`warn!`]: https://docs.rs/zino-core/latest/zino_core/macro.warn.html 26 | [`reject!`]: https://docs.rs/zino-core/latest/zino_core/macro.reject.html 27 | [`RequestContext`]: https://docs.rs/zino-core/latest/zino_core/request/trait.RequestContext.html 28 | [`Scheduler`]: https://docs.rs/zino-core/latest/zino_core/schedule/trait.Scheduler.html 29 | [`AsyncScheduler`]: https://docs.rs/zino-core/latest/zino_core/schedule/trait.AsyncScheduler.html 30 | -------------------------------------------------------------------------------- /src/basics/application.md: -------------------------------------------------------------------------------- 1 | # 应用接口抽象 2 | 3 | Zino框架的应用接口抽象由[`Application`] trait定义,它包含一个关联类型和两个必须实现的方法: 4 | 5 | ```rust 6 | pub trait Application { 7 | type Routes; 8 | 9 | fn register(self, routes: Self::Routes) -> Self; 10 | fn run_with(self, scheduler: T); 11 | } 12 | ``` 13 | 其中[`register`]用来注册路由,[`run_with`]用来加载异步任务并运行应用。 14 | 需要注意的是,异步任务的执行涉及到异步运行时的选择, 15 | 而[`zino-core`]本身并没有限定只能使用特定的运行时[^runtime], 16 | 所以需要实现者自行在[`run_with`]方法的实现中指定。对于同步任务,不涉及到异步运行时的选择, 17 | 我们就在[`Application`]的[`spawn`]方法中提供了默认实现。 18 | 19 | 这就是Zino框架的起点!我们只要给其他Web框架实现这个trait,就能把这个框架的功能集成到Zino中,并使应用的启动方式保存一致: 20 | ```rust 21 | mod router; 22 | mod schedule; 23 | 24 | use zino::prelude::*; 25 | 26 | fn main() { 27 | zino::Cluster::boot() 28 | .register(router::routes()) 29 | .register_debug(router::debug_routes()) 30 | .spawn(schedule::job_scheduler()) 31 | .run_with(schedule::async_job_scheduler()) 32 | } 33 | ``` 34 | 35 | 目前我们已经为[`actix-web`]、[`axum`]、[`dioxus-desktop`]实现了[`Application`] trait, 36 | 它们对应的关联类型[`Routes`]分别为: 37 | 38 | - [`actix-web`]:引入`ActixCluster`类型,基于[`ServiceConfig`]来定义路由。 39 | 40 | ```rust 41 | pub type RouterConfigure = fn(cfg: &mut actix_web::web::ServiceConfig); 42 | 43 | impl Application for ActixCluster { 44 | type Routes = Vec; 45 | } 46 | ``` 47 | 48 | - [`axum`]:引入`AxumCluster`类型,基于[`Router`]来定义路由。 49 | 50 | ```rust 51 | impl Application for AxumCluster { 52 | type Routes = Vec; 53 | } 54 | ``` 55 | 56 | - [`dioxus-desktop`]:引入`DioxusDesktop`类型,基于[`Routable`]泛型约束来定义路由。 57 | 58 | ```rust 59 | impl Application for DioxusDesktop 60 | where 61 | R: dioxus_router::routable::Routable, 62 | ::Err: Display, 63 | { 64 | type Routes = R; 65 | } 66 | ``` 67 | 68 | 可以看到,在以上框架的[`Application`]实现中,我们并没有定义自己的路由类型, 69 | 这就使得[`actix-web`]和[`axum`]中的路由、中间件可以直接在我们的Zino框架中使用。 70 | 确保充分理解了这一点,对我们的应用开发至关重要。 71 | 72 | [^runtime]: 虽然大部分情况下我们还是会优先选择[`tokio`]。 73 | 74 | [`zino-core`]: https://docs.rs/zino-core 75 | [`tokio`]: https://docs.rs/tokio 76 | [`actix-web`]: https://crates.io/crates/actix-web 77 | [`axum`]: https://crates.io/crates/axum 78 | [`dioxus-desktop`]: https://crates.io/crates/dioxus-desktop 79 | [`dioxus-router`]: https://crates.io/crates/dioxus-router 80 | [`Application`]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html 81 | [`Routes`]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html#associatedtype.Routes 82 | [`register`]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html#tymethod.register 83 | [`run_with`]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html#tymethod.run_with 84 | [`spawn`]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html#method.spawn 85 | [`ServiceConfig`]: https://docs.rs/actix-web/latest/actix_web/web/struct.ServiceConfig.html 86 | [`Router`]: https://docs.rs/axum/latest/axum/struct.Router.html 87 | [`Routable`]: https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.Routable.html 88 | -------------------------------------------------------------------------------- /src/basics/error.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | 在Zino框架中,我们定义了一个通用的错误类型[`Error`],主要目的是实现以下功能: 4 | 5 | 1. 基于字符串将任意错误包装成同一类型; 6 | 2. 支持错误溯源,并能追溯到原始错误; 7 | 3. 支持传递自定义错误信息上下文类型; 8 | 4. 支持[`tracing`],自动记录错误信息。 9 | 10 | 这四条需求对Zino框架至关重要,这也是为什么我们没有采用社区中流行的错误处理库,比如[`anyhow`]。 11 | 在实际应用开发中,我们往往并不会对具体的错误类型做不同的处理[^new_error],而是直接返回错误消息, 12 | 所以我们采取基于字符串的错误处理: 13 | ```rust 14 | #[derive(Debug)] 15 | pub struct Error { 16 | message: SharedString, 17 | source: Option>, 18 | context: Option>, 19 | } 20 | ``` 21 | 其中[`SharedString`]是Zino中用来优化静态字符串处理的类型[^benchmark]。 22 | 我们可以调用[`sources`]方法返回一个迭代器进行错误溯源,也可以使用[`root_source`]方法来追溯到原始错误。 23 | 24 | 对于任意满足`Send + 'static`约束的类型,我们可以将它作为错误信息上下文来传递: 25 | ```rust 26 | pub fn set_context(&mut self, context: T) { 27 | self.context = Some(Box::new(context)); 28 | } 29 | 30 | pub fn get_context(&self) -> Option<&T> { 31 | self.context 32 | .as_ref() 33 | .and_then(|ctx| ctx.downcast_ref::()) 34 | } 35 | ``` 36 | 37 | 对于任意实现了[`std::error::Error`] trait的错误类型,我们可以将它转换为[`Error`]类型: 38 | ```rust 39 | impl From for Error { 40 | fn from(err: E) -> Self { 41 | Self { 42 | message: err.to_string().into(), 43 | source: err.source().map(|err| Box::new(Self::new(err.to_string()))), 44 | context: Some(Box::new(err)), 45 | } 46 | } 47 | } 48 | ``` 49 | 这样在需要返回`Result`的函数中,我们就可以很方便地使用`?`运算符。 50 | 需要注意的是,我们的[`Error`]类型本身并没有实现[`std::error::Error`]。 51 | 52 | 通过为[`Error`]类型实现[`std::fmt::Display`],我们可以提供对[`tracing`]的集成,让它自动记录错误信息: 53 | ```rust 54 | impl std::fmt::Display for Error { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | let message = self.message(); 57 | if let Some(source) = &self.source { 58 | let source = source.message(); 59 | let root_source = self.root_source().map(|err| err.message()); 60 | if root_source != Some(source) { 61 | tracing::error!(root_source, source, message); 62 | } else { 63 | tracing::error!(root_source, message); 64 | } 65 | } else { 66 | tracing::error!(message); 67 | } 68 | write!(f, "{message}") 69 | } 70 | } 71 | ``` 72 | 每当我们调用`.to_string()`时,`tracing::error!`就会自动生成一条记录。 73 | 74 | [^new_error]: 当然,你也可以自定义可枚举的错误类型,并为其实现[`std::error::Error`] trait。 75 | [^benchmark]: 我们的[`Error`]类型对于静态字符串的处理有巨大的性能优势,具体可以参考我们的[`box_error`]测试。 76 | 77 | [`anyhow`]: https://docs.rs/anyhow 78 | [`tracing`]: https://docs.rs/tracing 79 | [`Error`]: https://docs.rs/zino-core/latest/zino_core/error/struct.Error.html 80 | [`SharedString`]: https://docs.rs/zino-core/latest/zino_core/type.SharedString.html 81 | [`sources`]: https://docs.rs/zino-core/latest/zino_core/error/struct.Error.html#method.sources 82 | [`root_source`]: https://docs.rs/zino-core/latest/zino_core/error/struct.Error.html#method.root_source 83 | [`box_error`]: https://github.com/zino-rs/zino/blob/main/zino-core/benches/box_error.rs 84 | [`std::error::Error`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html 85 | [`std::fmt::Display`]: https://doc.rust-lang.org/nightly/std/fmt/trait.Display.html -------------------------------------------------------------------------------- /src/basics/model.md: -------------------------------------------------------------------------------- 1 | # 领域模型(TODO) 2 | -------------------------------------------------------------------------------- /src/basics/request.md: -------------------------------------------------------------------------------- 1 | # 请求上下文 2 | 3 | 与[`actix-web`]、[`axum`]、[`ntex`]等框架不同,我们并不推荐使用提取器(Extractor)模式[^extractor], 4 | 而是采取类似于[`Express`]和[`Fiber`]那种把很多方法都挂在请求上下文的做法,这会带来几个好处: 5 | 6 | 1. 保持`controller`中只有`Request`一个参数,便于批量实现接口以及代码自动生成; 7 | 2. 允许使用者动态判断是否需要从请求中获取信息,避免使用不必要的`Option`类型; 8 | 3. 能够提供非异步的实现,比如从URI中提取查询参数这种本来就应该是同步的[^axum-query]。 9 | 10 | 当然,由于我们提供了对[`actix-web`]、[`axum`]、[`ntex`]等框架的集成,它们所支持的Handler都是可以直接在Zino中使用的, 11 | 也包括提取器模式。 12 | 13 | 我们通过[`RequestContext`]这一trait来提供请求上下文方法,它包含以下必须要实现的方法: 14 | 15 | ```rust 16 | pub trait RequestContext { 17 | type Method: AsRef; 18 | type Headers; 19 | 20 | fn request_method(&self) -> &Self::Method; 21 | 22 | fn original_uri(&self) -> &Uri; 23 | fn matched_route(&self) -> Cow<'_, str>; 24 | 25 | fn header_map(&self) -> &Self::Headers; 26 | fn get_header(&self, name: &str) -> Option<&str>; 27 | fn client_ip(&self) -> Option; 28 | 29 | fn get_context(&self) -> Option; 30 | 31 | fn get_data(&self) -> Option; 32 | fn set_data( 33 | &mut self, 34 | value: T 35 | ) -> Option; 36 | 37 | async fn read_body_bytes(&mut self) -> Result, Error>; 38 | } 39 | ``` 40 | 值得注意的是,我们提供的`read_body_bytes`方法不会消费`Request`对象[^axum-body],但是它的重复调用并不保证会给出相同的结果, 41 | 具体处理方式由实现决定。 42 | 43 | [^extractor]: 有些人总觉得Rust中的提取器很神奇,其实无外乎就是泛型加上宏批量实现罢了,Rust本身并不支持可变参数函数。 44 | [^axum-query]: `axum`的`Query`提取器尽管提供了同步的[`try_from_uri`]方法,但它实现的[`from_request_parts`]却是异步的。 45 | [^axum-body]: 在`axum`中,实现了[`FromRequest`]的提取器总会消费`Request`对象,所以它们只能使用一次,并且只能作为`Handler`的最后一个参数。 46 | 47 | [`actix-web`]: https://crates.io/crates/actix-web 48 | [`axum`]: https://crates.io/crates/axum 49 | [`ntex`]: https://crates.io/crates/ntex 50 | [`Express`]: https://expressjs.com/en/5x/api.html#req 51 | [`Fiber`]: https://docs.gofiber.io/api/ctx 52 | [`RequestContext`]: https://docs.rs/zino-core/latest/zino_core/request/trait.RequestContext.html 53 | [`try_from_uri`]: https://docs.rs/axum/latest/axum/extract/struct.Query.html#method.try_from_uri 54 | [`from_request_parts`]: https://docs.rs/axum/latest/axum/extract/trait.FromRequestParts.html#tymethod.from_request_parts 55 | [`FromRequest`]: https://docs.rs/axum/latest/axum/extract/trait.FromRequest.html 56 | -------------------------------------------------------------------------------- /src/basics/response.md: -------------------------------------------------------------------------------- 1 | # 请求响应(TODO) 2 | -------------------------------------------------------------------------------- /src/basics/schedule.md: -------------------------------------------------------------------------------- 1 | # 任务调度(TODO) 2 | -------------------------------------------------------------------------------- /src/basics/state.md: -------------------------------------------------------------------------------- 1 | # 状态管理 2 | 3 | 在Zino框架中,应用状态是由[`State`]类型提供的: 4 | ```rust 5 | #[derive(Debug, Clone)] 6 | pub struct State { 7 | env: Env, 8 | config: Table, 9 | data: T, 10 | } 11 | ``` 12 | 它包含有运行环境[`Env`]、TOML格式的配置文件[`Table`]以及自定义数据类型`T`。 13 | 14 | 不同于很多Web框架,我们的应用状态管理是基于惰性初始化的全局变量来实现的,主要考量如下: 15 | 16 | 1. 应用状态通常存在于整个运行期间,也就是说它的生命周期为`'static`; 17 | 2. 应用状态通常只需要初始化加载一次,并且在运行期间并不会被修改; 18 | 3. 尽可能避免在`controller`或`service`方法中传递应用状态参数。 19 | 20 | 回到具体实现上,最核心的几行代码就是 21 | ```rust 22 | static SHARED_STATE: LazyLock = LazyLock::new(|| { 23 | let mut state = State::default(); 24 | state.load_config(); 25 | state 26 | }); 27 | ``` 28 | 这定义了一个全局共享的静态变量,通过[`State::shared`]方法可以得到它的一个`&'static`引用, 29 | 然后就可以在整个应用中到处使用。 30 | 31 | ## 示例:如何使用Redis? 32 | 33 | ```toml 34 | # config/config.dev.toml 35 | 36 | [redis] 37 | host = "127.0.0.1" 38 | port = 6379 39 | database = "dbnum" 40 | username = "some_user" 41 | password = "hsfU4Y3aRbxVNuLpVG5T+wb9jIDdQyaUIiPgeQrP0ZRM1g" 42 | ``` 43 | 44 | ```rust 45 | //! src/extension/redis.rs 46 | 47 | use parking_lot::Mutex; 48 | use redis::{Client, Connection, RedisResult}; 49 | use zino_core::{state::State, LazyLock}; 50 | 51 | #[derive(Debug, Clone, Copy)] 52 | pub struct Redis; 53 | 54 | impl Redis { 55 | #[inline] 56 | pub fn get_value(key: &str) -> RedisResult { 57 | REDIS_CONNECTION.lock().get(key) 58 | } 59 | 60 | #[inline] 61 | pub fn set_value(key: &str, value: &str, seconds: u64) -> RedisResult<()> { 62 | REDIS_CONNECTION.lock().set_ex(key, value, seconds) 63 | } 64 | } 65 | 66 | static REDIS_CLIENT: LazyLock = LazyLock::new(|| { 67 | let config = State::shared() 68 | .get_config("redis") 69 | .expect("the `redis` field should be a table"); 70 | let database = config 71 | .get_str("database") 72 | .expect("the `database` field should be a str"); 73 | let authority = State::format_authority(config, Some(6379)); 74 | let url = format!("redis://{authority}/{database}"); 75 | Client::open(url) 76 | .expect("fail to create a connector to the redis server") 77 | }); 78 | 79 | static REDIS_CONNECTION: LazyLock> = LazyLock::new(|| { 80 | let connection = REDIS_CLIENT.get_connection() 81 | .expect("fail to establish a connection to the redis server"); 82 | Mutex::new(connection) 83 | }); 84 | ``` 85 | 这是Zino框架中推荐的使用模式:在配置文件里编写Redis连接信息[^password],通过Lazy的全局变量初始化Client; 86 | 然后定义一个空结构体,进而封装一些自定义方法。仔细想想,这不就类似于其他语言中的“单例模式”吗? 87 | 88 | [^password]: 配置项中的密码也可以先写为明文,运行后会在终端里提醒你修改为加密后的。 89 | 90 | [`State`]: https://docs.rs/zino-core/latest/zino_core/state/struct.State.html 91 | [`Env`]: https://docs.rs/zino-core/latest/zino_core/state/enum.Env.html 92 | [`Table`]: https://docs.rs/toml/latest/toml/type.Table.html 93 | [`State::shared`]: https://docs.rs/zino-core/latest/zino_core/state/struct.State.html#method.shared 94 | -------------------------------------------------------------------------------- /src/guide.md: -------------------------------------------------------------------------------- 1 | # 入门指南 2 | 3 | ## 快速开始 4 | 5 | 你可以从代码仓库中的示例[`actix-app`]、[`axum-app`]、[`dioxus-desktop`]或者[`ntex-app`]开始体验Zino。 6 | 7 | ```bash 8 | git clone https://github.com/zino-rs/zino.git 9 | cd zino/examples/axum-app 10 | cargo run 11 | ``` 12 | 13 | 如果你能在终端中看到日志输出,那就表明`axum-app`成功运行了!需要注意的是: 14 | - 当前Zino框架运行在`Rust 1.75+`,请使用[`rustup`]安装合适的版本; 15 | - 示例`axum-app`中需要连接MySQL数据库,具体配置参见[`axum-app/config/config.dev.toml`][axum-app-config]。 16 | 17 | [`actix-app`]: https://github.com/zino-rs/zino/tree/main/examples/actix-app 18 | [`axum-app`]: https://github.com/zino-rs/zino/tree/main/examples/axum-app 19 | [`dioxus-desktop`]: https://github.com/zino-rs/zino/tree/main/examples/dioxus-desktop 20 | [`ntex-app`]: https://github.com/zino-rs/zino/tree/main/examples/ntex-app 21 | [`rustup`]: https://rust-lang.github.io/rustup/ 22 | [axum-app-config]: https://github.com/zino-rs/zino/blob/main/examples/axum-app/config/config.dev.toml -------------------------------------------------------------------------------- /src/guide/config.md: -------------------------------------------------------------------------------- 1 | # 配置文件 2 | 3 | Zino框架支持根据运行环境来加载配置,不同环境的配置项定义在`config/config.{env}.toml`文件中。 4 | 具体运行环境的选择,按照以下优先顺序来判断: 5 | 6 | 1. 应用启动时传入的`--env`参数,如`cargo run -- --env=dev`; 7 | 2. 如果命令参数不存在,将尝试读取环境变量`ZINO_APP_ENV`[^dotenv]; 8 | 3. 假如环境变量也不存在,将根据`cfg!(debug_assertions)`给出默认值:取值为`true`,则运行环境为`dev`, 9 | 否则为`prod`。 10 | 11 | 开发环境`dev`和生产环境`prod`是预定义的两个取值。如有需要,你也可以自行添加其他运行环境,比如`test`环境, 12 | 对应的配置文件为`config/config.test.toml`。 13 | 14 | 当然,除了Rust社区中最常用的TOML格式,我们也支持JSON格式的配置文件, 15 | 可以通过环境变量`ZINO_APP_CONFIG_FORMAT`进行选择。默认情况下,配置文件是从本地加载的; 16 | 如果你需要从远程URL加载,那就请设置环境变量`ZINO_APP_CONFIG_URL`, 17 | 此时配置文件格式是通过请求响应的`content_type`来判断的。 18 | 19 | 鉴于Zino框架的配置项比较多,在项目开发的初始阶段我们推荐你采用默认值,这样可以省略绝大多数的配置项。 20 | 如果确有需要,在项目开发的过程中再逐渐添加。 21 | 22 | 一个最简单的配置文件示例如下: 23 | ```toml 24 | name = "DataCube" 25 | version = "1.0" 26 | ``` 27 | 需要注意的是,这里的`name`和`version`是指应用的名称和版本,与`Cargo.toml`里`[package]`的`name`和`version`不是一个概念。 28 | 29 | 在这里,我们只列出全局配置项,具体功能的配置项将在后面的功能模块里给出。 30 | 31 | - `name`:应用名称 32 | ```toml 33 | name = "DataCube" 34 | ``` 35 | - `version`: 应用版本 36 | ```toml 37 | version = "1.0" 38 | ``` 39 | - `secret`: 应用密文,用于推导`Application`的[`secret_key`][docsrs-secret-key]。 40 | 当缺失时,会根据应用名称和版本自动生成。 41 | ```toml 42 | secret = "SecretPhrase" 43 | ``` 44 | 45 | [^dotenv]: 当开启了`feature = "dotenv"`时,Zino框架也会自动加载项目目录中的`.env`文件。 46 | 47 | [docsrs-secret-key]: https://docs.rs/zino-core/latest/zino_core/application/trait.Application.html#method.secret_key 48 | -------------------------------------------------------------------------------- /src/guide/deployment.md: -------------------------------------------------------------------------------- 1 | # 应用部署(TODO) 2 | -------------------------------------------------------------------------------- /src/guide/development.md: -------------------------------------------------------------------------------- 1 | # 开发测试(TODO) 2 | -------------------------------------------------------------------------------- /src/guide/new-app.md: -------------------------------------------------------------------------------- 1 | # 创建应用 2 | 3 | 我们使用Rust的构建工具`Cargo`来管理应用。首先新建一个项目 4 | ```bash 5 | cargo new zino-app --bin 6 | ``` 7 | 然后在`Cargo.toml`中添加以下依赖 8 | ```toml 9 | [package] 10 | name = "zino-app" 11 | version = "0.1.0" 12 | edition = "2021" 13 | 14 | [dependencies] 15 | zino = { version = "0.22", features = ["axum"] } 16 | ``` 17 | 这里我们使用的是`axum`框架。如果要用`actix-web`框架,那就把`features`替换为`["actix"]`。 18 | 进而,我们在`src`目录的`main.rs`中添加以下代码: 19 | ```rust 20 | use zino::prelude::*; 21 | 22 | fn main() { 23 | zino::Cluster::boot().run() 24 | } 25 | ``` 26 | 此时,我们的应用已经可以运行了: 27 | ```bash 28 | cargo run 29 | ``` 30 | 打开浏览器地址`http://localhost:6080/rapidoc`,你将能够看到RapiDoc文档页面。 31 | 32 | 这是一个极简的示例,没有太多实际功能。但是如果你在项目目录中添加一个`public`目录,那么这就可以作为静态文件服务器, 33 | 并且Zino框架会自动使用`public/index.html`来渲染根路由`/`。在前后端分离的项目中,这一特性可用于部署打包后的单页面应用。 -------------------------------------------------------------------------------- /src/guide/structure.md: -------------------------------------------------------------------------------- 1 | # 目录结构 2 | 3 | > Zino开发框架的应用目录组织方式只是一种推荐,可根据实际需求进行调整。尤其是`src`目录,可以使用Rust模块自由组织, 4 | 我们并没有严格限定各个模块之间的调用关系,只要能通过编译皆可。 5 | 6 | 我们采用了类似于[Egg.js][eggjs-structure]的应用目录约定规范: 7 | 8 | ```shell 9 | zino-app 10 | ├─ Cargo.toml 11 | ├─ config 12 | │ ├─ config.dev.toml 13 | │ ├─ config.prod.toml 14 | │ ├─ locale 15 | │ │ ├─ en-US.ftl 16 | │ │ └─ zh-CN.ftl 17 | │ ├─ opa 18 | │ │ └─ user.opa 19 | │ └─ openapi 20 | │ ├─ OPENAPI.toml 21 | │ ├─ auth.toml 22 | │ └─ user.toml 23 | ├─ local 24 | │ ├─ data 25 | │ │ └─ mock 26 | │ │ ├─ logs.ndjson 27 | │ │ └─ users.csv 28 | │ └─ docs 29 | │ └─ rapidoc.html 30 | ├─ logs 31 | ├─ public 32 | │ ├─ 404.html 33 | │ ├─ data 34 | │ │ └─ logs.ndjson 35 | │ └─ index.html 36 | ├─ src 37 | │ ├─ controller 38 | │ │ ├─ mod.rs 39 | │ │ ├─ stats.rs 40 | │ │ ├─ task.rs 41 | │ │ └─ user.rs 42 | │ ├─ extension 43 | │ │ ├─ casbin.rs 44 | │ │ ├─ header.rs 45 | │ │ └─ mod.rs 46 | │ ├─ logic 47 | │ │ ├─ mod.rs 48 | │ │ ├─ task.rs 49 | │ │ └─ user.rs 50 | │ ├─ main.rs 51 | │ ├─ middleware 52 | │ │ ├─ access.rs 53 | │ │ └─ mod.rs 54 | │ ├─ router 55 | │ │ └─ mod.rs 56 | │ ├─ schedule 57 | │ │ ├─ job.rs 58 | │ │ └─ mod.rs 59 | │ └─ service 60 | │ ├─ mod.rs 61 | │ ├─ task.rs 62 | │ └─ user.rs 63 | └─ templates 64 | ├─ layout.html 65 | └─ output.html 66 | ``` 67 | 68 | * `Cargo.toml`为应用的Cargo配置文件。 69 | * `config/config.{env}.toml`用于编写不同运行环境的配置文件。 70 | * `config/locale/{lang-id}.ftl`于编写i18n多语言文件,目前仅支持[`Fluent`]规范。 71 | * `config/opa/{policy}.rego`用于编写OpenPolicyAgent策略。 72 | * `config/openapi/{tag}.toml`用于编写OpenAPI规范文档。 73 | * `local/`为本地静态资源目录,`data/`为本地数据目录,`docs/`为文档目录。 74 | * `logs/`用于日志文件输出。 75 | * `public/`为通过网络访问的静态资源目录,`index.html`为默认首页文件,`404.html`为404文件,`data/`为共享的数据目录。 76 | * `src/controller/`用于编写控制器。 77 | * `src/extension/`用于编写辅助函数。 78 | * `src/logic/`用于编写业务逻辑。 79 | * `src/main.rs`用于启动应用以及自定义初始化。 80 | * `src/middleware/`用于编写中间件。 81 | * `src/router/`用于配置URL路由规则。 82 | * `src/schedule/`用于编写定时任务。 83 | * `src/service/`用于编写业务接口服务,供`controller`调用。 84 | * `templates/`用于编写HTML模板文件,目前支持[`Tera`]和[`MiniJinja`]模板。 85 | 86 | [eggjs-structure]: https://www.eggjs.org/zh-CN/basics/structure 87 | [`Fluent`]: https://projectfluent.org/ 88 | [`Tera`]: https://docs.rs/tera 89 | [`MiniJinja`]: https://docs.rs/minijinja 90 | -------------------------------------------------------------------------------- /src/integrations.md: -------------------------------------------------------------------------------- 1 | # 框架集成(TODO) 2 | -------------------------------------------------------------------------------- /src/integrations/actix-web.md: -------------------------------------------------------------------------------- 1 | # actix-web(TODO) 2 | -------------------------------------------------------------------------------- /src/integrations/axum.md: -------------------------------------------------------------------------------- 1 | # axum(TODO) 2 | -------------------------------------------------------------------------------- /src/integrations/dioxus.md: -------------------------------------------------------------------------------- 1 | # dioxus(TODO) 2 | -------------------------------------------------------------------------------- /src/introduction.md: -------------------------------------------------------------------------------- 1 | # 引言 2 | 3 | [`Zino`][zino]致力于打造基于[Rust][rust]语言的新一代组装式应用开发框架, 4 | 提供一站式跨平台多端解决方案,可用于后端API开发、桌面应用开发等。 5 | 我们奉行『约定优于配置』的原则,提供开箱即用的功能模块,极大提升开发效率; 6 | 并通过应用接口抽象与[`actix-web`][actix-web]、[`axum`][axum]、[`dioxus`][dioxus]、[`ntex`][ntex]等框架集成, 7 | 打通社区生态。 8 | 9 | ![Star History Chart](https://api.star-history.com/svg?repos=photino/zino&type=Timeline) 10 | 11 | [rust]: https://www.rust-lang.org/ 12 | [zino]: https://github.com/photino/zino 13 | [actix-web]: https://crates.io/crates/actix-web 14 | [axum]: https://crates.io/crates/axum 15 | [ntex]: https://crates.io/crates/ntex 16 | [dioxus]: https://crates.io/crates/dioxus 17 | -------------------------------------------------------------------------------- /src/orm.md: -------------------------------------------------------------------------------- 1 | # 数据库ORM(TODO) 2 | -------------------------------------------------------------------------------- /src/orm/default-controller.md: -------------------------------------------------------------------------------- 1 | # API自动生成(TODO) 2 | -------------------------------------------------------------------------------- /src/orm/openapi.md: -------------------------------------------------------------------------------- 1 | # OpenAPI文档(TODO) 2 | -------------------------------------------------------------------------------- /src/orm/table-mapping.md: -------------------------------------------------------------------------------- 1 | # 数据表映射(TODO) 2 | -------------------------------------------------------------------------------- /src/orm/trait-derive.md: -------------------------------------------------------------------------------- 1 | # Trait推导(TODO) 2 | -------------------------------------------------------------------------------- /src/resources.md: -------------------------------------------------------------------------------- 1 | # 更多资源 2 | 3 | ## 微信公众号 4 | 5 | ![Zino开发框架](https://zino.cc/assets/zino-web-wechat.png) 6 | 7 | ## 知乎专栏 8 | 9 | [Zino开发框架技术解读](https://www.zhihu.com/column/c_1629805160351281152) 10 | -------------------------------------------------------------------------------- /src/topics.md: -------------------------------------------------------------------------------- 1 | # 进阶专题(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/accessor.md: -------------------------------------------------------------------------------- 1 | # 存储服务(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/authentication.md: -------------------------------------------------------------------------------- 1 | # 身份认证(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/cache.md: -------------------------------------------------------------------------------- 1 | # 缓存(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/channel.md: -------------------------------------------------------------------------------- 1 | # 消息订阅(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/chatbot.md: -------------------------------------------------------------------------------- 1 | # 聊天机器人(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/connector.md: -------------------------------------------------------------------------------- 1 | # 数据源访问(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/datetime.md: -------------------------------------------------------------------------------- 1 | # 日期处理(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/extension.md: -------------------------------------------------------------------------------- 1 | # 辅助函数(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/i18n.md: -------------------------------------------------------------------------------- 1 | # 国际化(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/observability.md: -------------------------------------------------------------------------------- 1 | # 可观测性(TODO) 2 | -------------------------------------------------------------------------------- /src/topics/view.md: -------------------------------------------------------------------------------- 1 | # HTML模板(TODO) 2 | -------------------------------------------------------------------------------- /theme/style.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width:1080px) { 2 | .sidetoc { 3 | display: none !important; 4 | } 5 | } 6 | 7 | table { 8 | margin-left: 0 !important; 9 | } 10 | 11 | @media only screen and (max-width: 2560px) { 12 | .nav-wide-wrapper { 13 | display: none; 14 | } 15 | 16 | .nav-wrapper { 17 | display: block; 18 | } 19 | } 20 | 21 | @media only screen and (max-width: 2560px) { 22 | .sidebar-visible .nav-wide-wrapper { 23 | display: none; 24 | } 25 | 26 | .sidebar-visible .nav-wrapper { 27 | display: block; 28 | } 29 | } 30 | 31 | code { 32 | word-break: break-word; 33 | } 34 | 35 | code.editable, 36 | .ace_scroller { 37 | top: 10px; 38 | } 39 | 40 | .content p { 41 | text-align: justify; 42 | } --------------------------------------------------------------------------------