NewLife/YuQue

自动同步语雀内容到本系统,对外呈现内容页面
大石头 authored at 2022-04-11 11:41:35
439a11d
Tree
1 Parent(s) 488f290
Summary: 25 changed files with 2244 additions and 9 deletions.
Modified +0 -3
Modified +13 -1
Modified +1 -1
Modified +1 -1
Modified +1 -1
Added +8 -0
Added +9 -0
Added +13 -0
Added +68 -0
Added +0 -0
Added +598 -0
Added +230 -0
Added +476 -0
Added +204 -0
Added +411 -0
Added +56 -0
Added +28 -0
Added +45 -0
Modified +1 -1
Modified +1 -1
Added +9 -0
Added +9 -0
Added +21 -0
Added +28 -0
Added +13 -0
Modified +0 -3
diff --git a/.gitignore b/.gitignore
index 8b53321..c1cb5bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,8 +18,5 @@ bld/
 /Data
 /Log
 *.log
-*.htm
 *.nuspec
 *.nupkg
-/BinTest
-/BinUnitTest
Modified +13 -1
diff --git a/NewLife.YuQue.sln b/NewLife.YuQue.sln
index 6584a84..4902483 100644
--- a/NewLife.YuQue.sln
+++ b/NewLife.YuQue.sln
@@ -16,7 +16,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 
 		.github\workflows\test.yml = .github\workflows\test.yml
 	EndProjectSection
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewLife.YuQue", "NewLife.YuQue\NewLife.YuQue.csproj", "{CC0B8977-F646-4211-BC1F-CD2BCA28971D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewLife.Yuque", "NewLife.YuQue\NewLife.Yuque.csproj", "{CC0B8977-F646-4211-BC1F-CD2BCA28971D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewLife.YuqueWeb", "NewLife.YuQueWeb\NewLife.YuqueWeb.csproj", "{D7B1576C-2B4D-4733-A6A3-DAEFCA95B8B4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YqWeb", "YQWeb\YqWeb.csproj", "{271C5F95-20EF-43E8-A6E6-22FDCD1B06F6}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -36,6 +40,14 @@ Global
 		{CC0B8977-F646-4211-BC1F-CD2BCA28971D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{CC0B8977-F646-4211-BC1F-CD2BCA28971D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{CC0B8977-F646-4211-BC1F-CD2BCA28971D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D7B1576C-2B4D-4733-A6A3-DAEFCA95B8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D7B1576C-2B4D-4733-A6A3-DAEFCA95B8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D7B1576C-2B4D-4733-A6A3-DAEFCA95B8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D7B1576C-2B4D-4733-A6A3-DAEFCA95B8B4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{271C5F95-20EF-43E8-A6E6-22FDCD1B06F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{271C5F95-20EF-43E8-A6E6-22FDCD1B06F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{271C5F95-20EF-43E8-A6E6-22FDCD1B06F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{271C5F95-20EF-43E8-A6E6-22FDCD1B06F6}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
Modified +1 -1
diff --git a/NewLife.YuQue/Models/BookDetail.cs b/NewLife.YuQue/Models/BookDetail.cs
index 144752f..2f75c4d 100644
--- a/NewLife.YuQue/Models/BookDetail.cs
+++ b/NewLife.YuQue/Models/BookDetail.cs
@@ -48,7 +48,7 @@ namespace NewLife.YuQue.Models
         [DataMember(Name = "likes_count")]
         public Int32 Likes { get; set; }
 
-        /// <summary> 订阅数量</summary>
+        /// <summary>订阅数量</summary>
         [DataMember(Name = "watches_count")]
         public Int32 Watches { get; set; }
 
Modified +1 -1
diff --git a/NewLife.YuQue/NewLife.YuQue.csproj b/NewLife.YuQue/NewLife.YuQue.csproj
index b24b10b..3f70d17 100644
--- a/NewLife.YuQue/NewLife.YuQue.csproj
+++ b/NewLife.YuQue/NewLife.YuQue.csproj
@@ -11,7 +11,7 @@
     <FileVersion>$(Version)</FileVersion>
     <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
     <Deterministic>false</Deterministic>
-    <OutputPath>..\..\Bin</OutputPath>
+    <OutputPath>..\Bin</OutputPath>
     <GenerateDocumentationFile>True</GenerateDocumentationFile>
     <ImplicitUsings>enable</ImplicitUsings>
     <LangVersion>latest</LangVersion>
Modified +1 -1
diff --git a/NewLife.YuQue/YuqueClient.cs b/NewLife.YuQue/YuqueClient.cs
index c207f98..3a08d5d 100644
--- a/NewLife.YuQue/YuqueClient.cs
+++ b/NewLife.YuQue/YuqueClient.cs
@@ -754,7 +754,7 @@ namespace NewLife.YuQue
         }
         #endregion
 
-        #region 属性
+        #region 辅助
         /// <summary>性能追踪</summary>
         public ITracer Tracer { get; set; }
 
Added +8 -0
diff --git a/NewLife.YuqueWeb/appsettings.Development.json b/NewLife.YuqueWeb/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/NewLife.YuqueWeb/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
Added +9 -0
diff --git a/NewLife.YuqueWeb/appsettings.json b/NewLife.YuqueWeb/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/NewLife.YuqueWeb/appsettings.json
@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}
Added +13 -0
diff --git a/NewLife.YuqueWeb/Areas/Yuque/YuqueArea.cs b/NewLife.YuqueWeb/Areas/Yuque/YuqueArea.cs
new file mode 100644
index 0000000..39254bd
--- /dev/null
+++ b/NewLife.YuqueWeb/Areas/Yuque/YuqueArea.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel;
+using NewLife.Cube;
+
+namespace NewLife.YuqueWeb.Areas.Yuque;
+
+/// <summary>语雀管理区域注册</summary>
+[DisplayName("语雀管理")]
+[Menu(-2, true, Icon = "fa-tachometer")]
+public class YuqueArea : AreaBase
+{
+    /// <inheritdoc />
+    public YuqueArea() : base(nameof(YuqueArea).TrimEnd("Area")) { }
+}
\ No newline at end of file
Added +68 -0
diff --git a/NewLife.YuqueWeb/Entity/Model.xml b/NewLife.YuqueWeb/Entity/Model.xml
new file mode 100644
index 0000000..49e087d
--- /dev/null
+++ b/NewLife.YuqueWeb/Entity/Model.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Tables xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://www.newlifex.com http://www.newlifex.com/Model2022.xsd" NameSpace="NewLife.YuQueWeb.Entity" ConnName="YuQue" Output="" BaseClass="Entity" Version="11.0.2022.0405" Document="https://www.yuque.com/smartstone/xcode/model" xmlns="http://www.newlifex.com/Model2022.xsd">
+  <Table Name="Book" Description="知识库。管理知识库">
+    <Columns>
+      <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+      <Column Name="Code" DataType="String" Description="编码。路径唯一标识,默认取Slug" />
+      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
+      <Column Name="Type" DataType="String" Description="类型" />
+      <Column Name="Enable" DataType="Boolean" Description="启用" />
+      <Column Name="UserName" DataType="String" Description="用户" />
+      <Column Name="Docs" DataType="Int32" Description="文章数" />
+      <Column Name="Likes" DataType="Int32" Description="点赞数" />
+      <Column Name="Watches" DataType="Int32" Description="订阅数" />
+      <Column Name="Sync" DataType="Boolean" Description="同步。是否自动同步远程内容" />
+      <Column Name="Slug" DataType="String" Description="路径" />
+      <Column Name="Namespace" DataType="String" Description="全路径" />
+      <Column Name="SyncTime" DataType="DateTime" Description="同步时间。最后一次同步数据的时间" />
+      <Column Name="CreateUser" DataType="String" Description="创建者" Model="False" />
+      <Column Name="CreateUserID" DataType="Int32" Description="创建人" Model="False" />
+      <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
+      <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
+      <Column Name="UpdateUser" DataType="String" Description="更新者" Model="False" />
+      <Column Name="UpdateUserID" DataType="Int32" Description="更新人" Model="False" />
+      <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" />
+      <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" />
+      <Column Name="Remark" DataType="String" Length="500" Description="备注" />
+    </Columns>
+    <Indexes>
+      <Index Columns="Code" Unique="True" />
+      <Index Columns="Name" />
+    </Indexes>
+  </Table>
+  <Table Name="Document" Description="文档。文档内容">
+    <Columns>
+      <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+      <Column Name="Code" DataType="String" Description="编码。路径唯一标识,默认取Slug" />
+      <Column Name="Title" DataType="String" Master="True" Nullable="False" Description="标题" />
+      <Column Name="BookId" DataType="Int32" Description="知识库" />
+      <Column Name="Enable" DataType="Boolean" Description="启用" />
+      <Column Name="UserName" DataType="String" Description="用户" />
+      <Column Name="Format" DataType="String" Description="格式" />
+      <Column Name="Hits" DataType="Int32" Description="点击量" />
+      <Column Name="Likes" DataType="Int32" Description="点赞数" />
+      <Column Name="Comments" DataType="Int32" Description="评论数" />
+      <Column Name="Body" DataType="String" Length="-1" Description="正文。Markdown格式" />
+      <Column Name="BodyHtml" DataType="String" Length="-1" Description="HTML正文" />
+      <Column Name="ContentUpdateTime" DataType="DateTime" Description="内容更新时间" />
+      <Column Name="PublishTime" DataType="DateTime" Description="发布时间" />
+      <Column Name="FirstPublishTime" DataType="DateTime" Description="首次发布" />
+      <Column Name="WordCount" DataType="Int32" Description="单词数" />
+      <Column Name="Cover" DataType="String" Description="封面" />
+      <Column Name="CreateUser" DataType="String" Description="创建者" Model="False" />
+      <Column Name="CreateUserID" DataType="Int32" Description="创建人" Model="False" />
+      <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
+      <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
+      <Column Name="UpdateUser" DataType="String" Description="更新者" Model="False" />
+      <Column Name="UpdateUserID" DataType="Int32" Description="更新人" Model="False" />
+      <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" />
+      <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" />
+      <Column Name="Remark" DataType="String" Length="500" Description="备注" />
+    </Columns>
+    <Indexes>
+      <Index Columns="Code" Unique="True" />
+      <Index Columns="BookId" />
+      <Index Columns="Title" />
+    </Indexes>
+  </Table>
+</Tables>
\ No newline at end of file
Added +0 -0
diff --git a/NewLife.YuqueWeb/Entity/xcodetool.exe b/NewLife.YuqueWeb/Entity/xcodetool.exe
new file mode 100644
index 0000000..c3742e5
Binary files /dev/null and b/NewLife.YuqueWeb/Entity/xcodetool.exe differ
Added +598 -0
diff --git a/NewLife.YuqueWeb/Entity/YuQue.htm b/NewLife.YuqueWeb/Entity/YuQue.htm
new file mode 100644
index 0000000..20d9e6a
--- /dev/null
+++ b/NewLife.YuqueWeb/Entity/YuQue.htm
@@ -0,0 +1,598 @@
+<style>
+    table {
+        border-collapse: collapse;
+        border: 1px solid;
+        border-color: rgb(211, 202, 221);
+    }
+
+    table thead,
+    table tr {
+        border-top-width: 1px;
+        border-top-style: solid;
+        border-top-color: rgb(211, 202, 221);
+    }
+
+    table {
+        border-bottom-width: 1px;
+        border-bottom-style: solid;
+        border-bottom-color: rgb(211, 202, 221);
+    }
+
+    table td,
+    table th {
+        padding: 5px 10px;
+        font-size: 14px;
+        font-family: Verdana;
+        color: rgb(95, 74, 121);
+    }
+
+    table tr:nth-child(even) {
+        background: rgb(223, 216, 232)
+    }
+
+    table tr:nth-child(odd) {
+        background: #FFF
+    }
+</style>
+<h3>知识库(Book)</h3>
+<table>
+    <thead>
+        <tr>
+            <th>名称</th>
+            <th>显示名</th>
+            <th>类型</th>
+            <th>长度</th>
+            <th>精度</th>
+            <th>主键</th>
+            <th>允许空</th>
+            <th>备注</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td>Id</td>
+            <td>编号</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td title="自增">AI</td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Code</td>
+            <td>编码</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td title="唯一索引">UQ</td>
+            <td></td>
+            <td>路径唯一标识,默认取Slug</td>
+        </tr>
+
+        <tr>
+            <td>Name</td>
+            <td>名称</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Type</td>
+            <td>类型</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Enable</td>
+            <td>启用</td>
+            <td>Boolean</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UserName</td>
+            <td>用户</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Docs</td>
+            <td>文章数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Likes</td>
+            <td>点赞数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Watches</td>
+            <td>订阅数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Sync</td>
+            <td>同步</td>
+            <td>Boolean</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td>是否自动同步远程内容</td>
+        </tr>
+
+        <tr>
+            <td>Slug</td>
+            <td>路径</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Namespace</td>
+            <td>全路径</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>SyncTime</td>
+            <td>同步时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>最后一次同步数据的时间</td>
+        </tr>
+
+        <tr>
+            <td>CreateUser</td>
+            <td>创建者</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateUserID</td>
+            <td>创建人</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateIP</td>
+            <td>创建地址</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateTime</td>
+            <td>创建时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUser</td>
+            <td>更新者</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUserID</td>
+            <td>更新人</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateIP</td>
+            <td>更新地址</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateTime</td>
+            <td>更新时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Remark</td>
+            <td>备注</td>
+            <td>String</td>
+            <td>500</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+    </tbody>
+</table>
+<br></br>
+<h3>文档(Document)</h3>
+<table>
+    <thead>
+        <tr>
+            <th>名称</th>
+            <th>显示名</th>
+            <th>类型</th>
+            <th>长度</th>
+            <th>精度</th>
+            <th>主键</th>
+            <th>允许空</th>
+            <th>备注</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td>Id</td>
+            <td>编号</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td title="自增">AI</td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Code</td>
+            <td>编码</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td title="唯一索引">UQ</td>
+            <td></td>
+            <td>路径唯一标识,默认取Slug</td>
+        </tr>
+
+        <tr>
+            <td>Title</td>
+            <td>标题</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>BookId</td>
+            <td>知识库</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Enable</td>
+            <td>启用</td>
+            <td>Boolean</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UserName</td>
+            <td>用户</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Format</td>
+            <td>格式</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Hits</td>
+            <td>点击量</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Likes</td>
+            <td>点赞数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Comments</td>
+            <td>评论数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Body</td>
+            <td>正文</td>
+            <td>String</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>Markdown格式</td>
+        </tr>
+
+        <tr>
+            <td>BodyHtml</td>
+            <td>HTML正文</td>
+            <td>String</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>ContentUpdateTime</td>
+            <td>内容更新时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>PublishTime</td>
+            <td>发布时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>FirstPublishTime</td>
+            <td>首次发布</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>WordCount</td>
+            <td>单词数</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Cover</td>
+            <td>封面</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateUser</td>
+            <td>创建者</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateUserID</td>
+            <td>创建人</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateIP</td>
+            <td>创建地址</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>CreateTime</td>
+            <td>创建时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUser</td>
+            <td>更新者</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUserID</td>
+            <td>更新人</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateIP</td>
+            <td>更新地址</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateTime</td>
+            <td>更新时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Remark</td>
+            <td>备注</td>
+            <td>String</td>
+            <td>500</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+    </tbody>
+</table>
+<br></br>
Added +230 -0
diff --git "a/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.Biz.cs" "b/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.Biz.cs"
new file mode 100644
index 0000000..f94c0ad
--- /dev/null
+++ "b/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.Biz.cs"
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using System.Web.Script.Serialization;
+using System.Xml.Serialization;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Model;
+using NewLife.Reflection;
+using NewLife.Threading;
+using NewLife.Web;
+using XCode;
+using XCode.Cache;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+using XCode.Membership;
+using XCode.Shards;
+
+namespace NewLife.YuQueWeb.Entity
+{
+    /// <summary>文档。文档内容</summary>
+    public partial class Document : Entity<Document>
+    {
+        #region 对象操作
+        static Document()
+        {
+            // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
+            //var df = Meta.Factory.AdditionalFields;
+            //df.Add(nameof(BookId));
+
+            // 过滤器 UserModule、TimeModule、IPModule
+            Meta.Modules.Add<UserModule>();
+            Meta.Modules.Add<TimeModule>();
+            Meta.Modules.Add<IPModule>();
+        }
+
+        /// <summary>验证并修补数据,通过抛出异常的方式提示验证失败。</summary>
+        /// <param name="isNew">是否插入</param>
+        public override void Valid(Boolean isNew)
+        {
+            // 如果没有脏数据,则不需要进行任何处理
+            if (!HasDirty) return;
+
+            // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
+            if (Title.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Title), "标题不能为空!");
+
+            // 建议先调用基类方法,基类方法会做一些统一处理
+            base.Valid(isNew);
+
+            // 在新插入数据或者修改了指定字段时进行修正
+            // 处理当前已登录用户信息,可以由UserModule过滤器代劳
+            /*var user = ManageProvider.User;
+            if (user != null)
+            {
+                if (isNew && !Dirtys[nameof(CreateUserID)]) CreateUserID = user.ID;
+                if (!Dirtys[nameof(UpdateUserID)]) UpdateUserID = user.ID;
+            }*/
+            //if (isNew && !Dirtys[nameof(CreateTime)]) CreateTime = DateTime.Now;
+            //if (!Dirtys[nameof(UpdateTime)]) UpdateTime = DateTime.Now;
+            //if (isNew && !Dirtys[nameof(CreateIP)]) CreateIP = ManageProvider.UserHost;
+            //if (!Dirtys[nameof(UpdateIP)]) UpdateIP = ManageProvider.UserHost;
+
+            // 检查唯一索引
+            // CheckExist(isNew, nameof(Code));
+        }
+
+        ///// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
+        //[EditorBrowsable(EditorBrowsableState.Never)]
+        //protected override void InitData()
+        //{
+        //    // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用
+        //    if (Meta.Session.Count > 0) return;
+
+        //    if (XTrace.Debug) XTrace.WriteLine("开始初始化Document[文档]数据……");
+
+        //    var entity = new Document();
+        //    entity.Code = "abc";
+        //    entity.Title = "abc";
+        //    entity.BookId = 0;
+        //    entity.Enable = true;
+        //    entity.UserName = "abc";
+        //    entity.Format = "abc";
+        //    entity.Hits = 0;
+        //    entity.Likes = 0;
+        //    entity.Comments = 0;
+        //    entity.Body = "abc";
+        //    entity.BodyHtml = "abc";
+        //    entity.ContentUpdateTime = DateTime.Now;
+        //    entity.PublishTime = DateTime.Now;
+        //    entity.FirstPublishTime = DateTime.Now;
+        //    entity.WordCount = 0;
+        //    entity.Cover = "abc";
+        //    entity.CreateUser = "abc";
+        //    entity.CreateUserID = 0;
+        //    entity.CreateIP = "abc";
+        //    entity.CreateTime = DateTime.Now;
+        //    entity.UpdateUser = "abc";
+        //    entity.UpdateUserID = 0;
+        //    entity.UpdateIP = "abc";
+        //    entity.UpdateTime = DateTime.Now;
+        //    entity.Remark = "abc";
+        //    entity.Insert();
+
+        //    if (XTrace.Debug) XTrace.WriteLine("完成初始化Document[文档]数据!");
+        //}
+
+        ///// <summary>已重载。基类先调用Valid(true)验证数据,然后在事务保护内调用OnInsert</summary>
+        ///// <returns></returns>
+        //public override Int32 Insert()
+        //{
+        //    return base.Insert();
+        //}
+
+        ///// <summary>已重载。在事务保护范围内处理业务,位于Valid之后</summary>
+        ///// <returns></returns>
+        //protected override Int32 OnDelete()
+        //{
+        //    return base.OnDelete();
+        //}
+        #endregion
+
+        #region 扩展属性
+        /// <summary>知识库</summary>
+        [XmlIgnore, IgnoreDataMember]
+        //[ScriptIgnore]
+        public Book Book => Extends.Get(nameof(Book), k => Book.FindById(BookId));
+
+        /// <summary>知识库</summary>
+        [Map(nameof(BookId), typeof(Book), "Id")]
+        public String BookName => Book?.Name;
+
+        #endregion
+
+        #region 扩展查询
+        /// <summary>根据编号查找</summary>
+        /// <param name="id">编号</param>
+        /// <returns>实体对象</returns>
+        public static Document FindById(Int32 id)
+        {
+            if (id <= 0) return null;
+
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Id == id);
+
+            // 单对象缓存
+            return Meta.SingleCache[id];
+
+            //return Find(_.Id == id);
+        }
+
+        /// <summary>根据编码查找</summary>
+        /// <param name="code">编码</param>
+        /// <returns>实体对象</returns>
+        public static Document FindByCode(String code)
+        {
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Code.EqualIgnoreCase(code));
+
+            return Find(_.Code == code);
+        }
+
+        /// <summary>根据知识库查找</summary>
+        /// <param name="bookId">知识库</param>
+        /// <returns>实体列表</returns>
+        public static IList<Document> FindAllByBookId(Int32 bookId)
+        {
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.BookId == bookId);
+
+            return FindAll(_.BookId == bookId);
+        }
+
+        /// <summary>根据标题查找</summary>
+        /// <param name="title">标题</param>
+        /// <returns>实体列表</returns>
+        public static IList<Document> FindAllByTitle(String title)
+        {
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.Title.EqualIgnoreCase(title));
+
+            return FindAll(_.Title == title);
+        }
+        #endregion
+
+        #region 高级查询
+        /// <summary>高级查询</summary>
+        /// <param name="code">编码。路径唯一标识,默认取Slug</param>
+        /// <param name="title">标题</param>
+        /// <param name="bookId">知识库</param>
+        /// <param name="start">更新时间开始</param>
+        /// <param name="end">更新时间结束</param>
+        /// <param name="key">关键字</param>
+        /// <param name="page">分页参数信息。可携带统计和数据权限扩展查询等信息</param>
+        /// <returns>实体列表</returns>
+        public static IList<Document> Search(String code, String title, Int32 bookId, DateTime start, DateTime end, String key, PageParameter page)
+        {
+            var exp = new WhereExpression();
+
+            if (!code.IsNullOrEmpty()) exp &= _.Code == code;
+            if (!title.IsNullOrEmpty()) exp &= _.Title == title;
+            if (bookId >= 0) exp &= _.BookId == bookId;
+            exp &= _.UpdateTime.Between(start, end);
+            if (!key.IsNullOrEmpty()) exp &= _.Code.Contains(key) | _.Title.Contains(key) | _.UserName.Contains(key) | _.Format.Contains(key) | _.Body.Contains(key) | _.BodyHtml.Contains(key) | _.Cover.Contains(key) | _.CreateUser.Contains(key) | _.CreateIP.Contains(key) | _.UpdateUser.Contains(key) | _.UpdateIP.Contains(key) | _.Remark.Contains(key);
+
+            return FindAll(exp, page);
+        }
+
+        // Select Count(Id) as Id,Category From Document Where CreateTime>'2020-01-24 00:00:00' Group By Category Order By Id Desc limit 20
+        //static readonly FieldCache<Document> _CategoryCache = new FieldCache<Document>(nameof(Category))
+        //{
+        //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty
+        //};
+
+        ///// <summary>获取类别列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>
+        ///// <returns></returns>
+        //public static IDictionary<String, String> GetCategoryList() => _CategoryCache.FindAllName();
+        #endregion
+
+        #region 业务操作
+        #endregion
+    }
+}
\ No newline at end of file
Added +476 -0
diff --git "a/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.cs" "b/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.cs"
new file mode 100644
index 0000000..b68f759
--- /dev/null
+++ "b/NewLife.YuqueWeb/Entity/\346\226\207\346\241\243.cs"
@@ -0,0 +1,476 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.Serialization;
+using System.Web.Script.Serialization;
+using System.Xml.Serialization;
+using XCode;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+
+namespace NewLife.YuQueWeb.Entity
+{
+    /// <summary>文档。文档内容</summary>
+    [Serializable]
+    [DataObject]
+    [Description("文档。文档内容")]
+    [BindIndex("IU_Document_Code", true, "Code")]
+    [BindIndex("IX_Document_BookId", false, "BookId")]
+    [BindIndex("IX_Document_Title", false, "Title")]
+    [BindTable("Document", Description = "文档。文档内容", ConnName = "YuQue", DbType = DatabaseType.None)]
+    public partial class Document
+    {
+        #region 属性
+        private Int32 _Id;
+        /// <summary>编号</summary>
+        [DisplayName("编号")]
+        [Description("编号")]
+        [DataObjectField(true, true, false, 0)]
+        [BindColumn("Id", "编号", "")]
+        public Int32 Id { get => _Id; set { if (OnPropertyChanging("Id", value)) { _Id = value; OnPropertyChanged("Id"); } } }
+
+        private String _Code;
+        /// <summary>编码。路径唯一标识,默认取Slug</summary>
+        [DisplayName("编码")]
+        [Description("编码。路径唯一标识,默认取Slug")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Code", "编码。路径唯一标识,默认取Slug", "")]
+        public String Code { get => _Code; set { if (OnPropertyChanging("Code", value)) { _Code = value; OnPropertyChanged("Code"); } } }
+
+        private String _Title;
+        /// <summary>标题</summary>
+        [DisplayName("标题")]
+        [Description("标题")]
+        [DataObjectField(false, false, false, 50)]
+        [BindColumn("Title", "标题", "", Master = true)]
+        public String Title { get => _Title; set { if (OnPropertyChanging("Title", value)) { _Title = value; OnPropertyChanged("Title"); } } }
+
+        private Int32 _BookId;
+        /// <summary>知识库</summary>
+        [DisplayName("知识库")]
+        [Description("知识库")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("BookId", "知识库", "")]
+        public Int32 BookId { get => _BookId; set { if (OnPropertyChanging("BookId", value)) { _BookId = value; OnPropertyChanged("BookId"); } } }
+
+        private Boolean _Enable;
+        /// <summary>启用</summary>
+        [DisplayName("启用")]
+        [Description("启用")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Enable", "启用", "")]
+        public Boolean Enable { get => _Enable; set { if (OnPropertyChanging("Enable", value)) { _Enable = value; OnPropertyChanged("Enable"); } } }
+
+        private String _UserName;
+        /// <summary>用户</summary>
+        [DisplayName("用户")]
+        [Description("用户")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UserName", "用户", "")]
+        public String UserName { get => _UserName; set { if (OnPropertyChanging("UserName", value)) { _UserName = value; OnPropertyChanged("UserName"); } } }
+
+        private String _Format;
+        /// <summary>格式</summary>
+        [DisplayName("格式")]
+        [Description("格式")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Format", "格式", "")]
+        public String Format { get => _Format; set { if (OnPropertyChanging("Format", value)) { _Format = value; OnPropertyChanged("Format"); } } }
+
+        private Int32 _Hits;
+        /// <summary>点击量</summary>
+        [DisplayName("点击量")]
+        [Description("点击量")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Hits", "点击量", "")]
+        public Int32 Hits { get => _Hits; set { if (OnPropertyChanging("Hits", value)) { _Hits = value; OnPropertyChanged("Hits"); } } }
+
+        private Int32 _Likes;
+        /// <summary>点赞数</summary>
+        [DisplayName("点赞数")]
+        [Description("点赞数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Likes", "点赞数", "")]
+        public Int32 Likes { get => _Likes; set { if (OnPropertyChanging("Likes", value)) { _Likes = value; OnPropertyChanged("Likes"); } } }
+
+        private Int32 _Comments;
+        /// <summary>评论数</summary>
+        [DisplayName("评论数")]
+        [Description("评论数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Comments", "评论数", "")]
+        public Int32 Comments { get => _Comments; set { if (OnPropertyChanging("Comments", value)) { _Comments = value; OnPropertyChanged("Comments"); } } }
+
+        private String _Body;
+        /// <summary>正文。Markdown格式</summary>
+        [DisplayName("正文")]
+        [Description("正文。Markdown格式")]
+        [DataObjectField(false, false, true, -1)]
+        [BindColumn("Body", "正文。Markdown格式", "")]
+        public String Body { get => _Body; set { if (OnPropertyChanging("Body", value)) { _Body = value; OnPropertyChanged("Body"); } } }
+
+        private String _BodyHtml;
+        /// <summary>HTML正文</summary>
+        [DisplayName("HTML正文")]
+        [Description("HTML正文")]
+        [DataObjectField(false, false, true, -1)]
+        [BindColumn("BodyHtml", "HTML正文", "")]
+        public String BodyHtml { get => _BodyHtml; set { if (OnPropertyChanging("BodyHtml", value)) { _BodyHtml = value; OnPropertyChanged("BodyHtml"); } } }
+
+        private DateTime _ContentUpdateTime;
+        /// <summary>内容更新时间</summary>
+        [DisplayName("内容更新时间")]
+        [Description("内容更新时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("ContentUpdateTime", "内容更新时间", "")]
+        public DateTime ContentUpdateTime { get => _ContentUpdateTime; set { if (OnPropertyChanging("ContentUpdateTime", value)) { _ContentUpdateTime = value; OnPropertyChanged("ContentUpdateTime"); } } }
+
+        private DateTime _PublishTime;
+        /// <summary>发布时间</summary>
+        [DisplayName("发布时间")]
+        [Description("发布时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("PublishTime", "发布时间", "")]
+        public DateTime PublishTime { get => _PublishTime; set { if (OnPropertyChanging("PublishTime", value)) { _PublishTime = value; OnPropertyChanged("PublishTime"); } } }
+
+        private DateTime _FirstPublishTime;
+        /// <summary>首次发布</summary>
+        [DisplayName("首次发布")]
+        [Description("首次发布")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("FirstPublishTime", "首次发布", "")]
+        public DateTime FirstPublishTime { get => _FirstPublishTime; set { if (OnPropertyChanging("FirstPublishTime", value)) { _FirstPublishTime = value; OnPropertyChanged("FirstPublishTime"); } } }
+
+        private Int32 _WordCount;
+        /// <summary>单词数</summary>
+        [DisplayName("单词数")]
+        [Description("单词数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("WordCount", "单词数", "")]
+        public Int32 WordCount { get => _WordCount; set { if (OnPropertyChanging("WordCount", value)) { _WordCount = value; OnPropertyChanged("WordCount"); } } }
+
+        private String _Cover;
+        /// <summary>封面</summary>
+        [DisplayName("封面")]
+        [Description("封面")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Cover", "封面", "")]
+        public String Cover { get => _Cover; set { if (OnPropertyChanging("Cover", value)) { _Cover = value; OnPropertyChanged("Cover"); } } }
+
+        private String _CreateUser;
+        /// <summary>创建者</summary>
+        [DisplayName("创建者")]
+        [Description("创建者")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("CreateUser", "创建者", "")]
+        public String CreateUser { get => _CreateUser; set { if (OnPropertyChanging("CreateUser", value)) { _CreateUser = value; OnPropertyChanged("CreateUser"); } } }
+
+        private Int32 _CreateUserID;
+        /// <summary>创建人</summary>
+        [DisplayName("创建人")]
+        [Description("创建人")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("CreateUserID", "创建人", "")]
+        public Int32 CreateUserID { get => _CreateUserID; set { if (OnPropertyChanging("CreateUserID", value)) { _CreateUserID = value; OnPropertyChanged("CreateUserID"); } } }
+
+        private String _CreateIP;
+        /// <summary>创建地址</summary>
+        [DisplayName("创建地址")]
+        [Description("创建地址")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("CreateIP", "创建地址", "")]
+        public String CreateIP { get => _CreateIP; set { if (OnPropertyChanging("CreateIP", value)) { _CreateIP = value; OnPropertyChanged("CreateIP"); } } }
+
+        private DateTime _CreateTime;
+        /// <summary>创建时间</summary>
+        [DisplayName("创建时间")]
+        [Description("创建时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("CreateTime", "创建时间", "")]
+        public DateTime CreateTime { get => _CreateTime; set { if (OnPropertyChanging("CreateTime", value)) { _CreateTime = value; OnPropertyChanged("CreateTime"); } } }
+
+        private String _UpdateUser;
+        /// <summary>更新者</summary>
+        [DisplayName("更新者")]
+        [Description("更新者")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UpdateUser", "更新者", "")]
+        public String UpdateUser { get => _UpdateUser; set { if (OnPropertyChanging("UpdateUser", value)) { _UpdateUser = value; OnPropertyChanged("UpdateUser"); } } }
+
+        private Int32 _UpdateUserID;
+        /// <summary>更新人</summary>
+        [DisplayName("更新人")]
+        [Description("更新人")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("UpdateUserID", "更新人", "")]
+        public Int32 UpdateUserID { get => _UpdateUserID; set { if (OnPropertyChanging("UpdateUserID", value)) { _UpdateUserID = value; OnPropertyChanged("UpdateUserID"); } } }
+
+        private String _UpdateIP;
+        /// <summary>更新地址</summary>
+        [DisplayName("更新地址")]
+        [Description("更新地址")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UpdateIP", "更新地址", "")]
+        public String UpdateIP { get => _UpdateIP; set { if (OnPropertyChanging("UpdateIP", value)) { _UpdateIP = value; OnPropertyChanged("UpdateIP"); } } }
+
+        private DateTime _UpdateTime;
+        /// <summary>更新时间</summary>
+        [DisplayName("更新时间")]
+        [Description("更新时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("UpdateTime", "更新时间", "")]
+        public DateTime UpdateTime { get => _UpdateTime; set { if (OnPropertyChanging("UpdateTime", value)) { _UpdateTime = value; OnPropertyChanged("UpdateTime"); } } }
+
+        private String _Remark;
+        /// <summary>备注</summary>
+        [DisplayName("备注")]
+        [Description("备注")]
+        [DataObjectField(false, false, true, 500)]
+        [BindColumn("Remark", "备注", "")]
+        public String Remark { get => _Remark; set { if (OnPropertyChanging("Remark", value)) { _Remark = value; OnPropertyChanged("Remark"); } } }
+        #endregion
+
+        #region 获取/设置 字段值
+        /// <summary>获取/设置 字段值</summary>
+        /// <param name="name">字段名</param>
+        /// <returns></returns>
+        public override Object this[String name]
+        {
+            get
+            {
+                switch (name)
+                {
+                    case "Id": return _Id;
+                    case "Code": return _Code;
+                    case "Title": return _Title;
+                    case "BookId": return _BookId;
+                    case "Enable": return _Enable;
+                    case "UserName": return _UserName;
+                    case "Format": return _Format;
+                    case "Hits": return _Hits;
+                    case "Likes": return _Likes;
+                    case "Comments": return _Comments;
+                    case "Body": return _Body;
+                    case "BodyHtml": return _BodyHtml;
+                    case "ContentUpdateTime": return _ContentUpdateTime;
+                    case "PublishTime": return _PublishTime;
+                    case "FirstPublishTime": return _FirstPublishTime;
+                    case "WordCount": return _WordCount;
+                    case "Cover": return _Cover;
+                    case "CreateUser": return _CreateUser;
+                    case "CreateUserID": return _CreateUserID;
+                    case "CreateIP": return _CreateIP;
+                    case "CreateTime": return _CreateTime;
+                    case "UpdateUser": return _UpdateUser;
+                    case "UpdateUserID": return _UpdateUserID;
+                    case "UpdateIP": return _UpdateIP;
+                    case "UpdateTime": return _UpdateTime;
+                    case "Remark": return _Remark;
+                    default: return base[name];
+                }
+            }
+            set
+            {
+                switch (name)
+                {
+                    case "Id": _Id = value.ToInt(); break;
+                    case "Code": _Code = Convert.ToString(value); break;
+                    case "Title": _Title = Convert.ToString(value); break;
+                    case "BookId": _BookId = value.ToInt(); break;
+                    case "Enable": _Enable = value.ToBoolean(); break;
+                    case "UserName": _UserName = Convert.ToString(value); break;
+                    case "Format": _Format = Convert.ToString(value); break;
+                    case "Hits": _Hits = value.ToInt(); break;
+                    case "Likes": _Likes = value.ToInt(); break;
+                    case "Comments": _Comments = value.ToInt(); break;
+                    case "Body": _Body = Convert.ToString(value); break;
+                    case "BodyHtml": _BodyHtml = Convert.ToString(value); break;
+                    case "ContentUpdateTime": _ContentUpdateTime = value.ToDateTime(); break;
+                    case "PublishTime": _PublishTime = value.ToDateTime(); break;
+                    case "FirstPublishTime": _FirstPublishTime = value.ToDateTime(); break;
+                    case "WordCount": _WordCount = value.ToInt(); break;
+                    case "Cover": _Cover = Convert.ToString(value); break;
+                    case "CreateUser": _CreateUser = Convert.ToString(value); break;
+                    case "CreateUserID": _CreateUserID = value.ToInt(); break;
+                    case "CreateIP": _CreateIP = Convert.ToString(value); break;
+                    case "CreateTime": _CreateTime = value.ToDateTime(); break;
+                    case "UpdateUser": _UpdateUser = Convert.ToString(value); break;
+                    case "UpdateUserID": _UpdateUserID = value.ToInt(); break;
+                    case "UpdateIP": _UpdateIP = Convert.ToString(value); break;
+                    case "UpdateTime": _UpdateTime = value.ToDateTime(); break;
+                    case "Remark": _Remark = Convert.ToString(value); break;
+                    default: base[name] = value; break;
+                }
+            }
+        }
+        #endregion
+
+        #region 字段名
+        /// <summary>取得文档字段信息的快捷方式</summary>
+        public partial class _
+        {
+            /// <summary>编号</summary>
+            public static readonly Field Id = FindByName("Id");
+
+            /// <summary>编码。路径唯一标识,默认取Slug</summary>
+            public static readonly Field Code = FindByName("Code");
+
+            /// <summary>标题</summary>
+            public static readonly Field Title = FindByName("Title");
+
+            /// <summary>知识库</summary>
+            public static readonly Field BookId = FindByName("BookId");
+
+            /// <summary>启用</summary>
+            public static readonly Field Enable = FindByName("Enable");
+
+            /// <summary>用户</summary>
+            public static readonly Field UserName = FindByName("UserName");
+
+            /// <summary>格式</summary>
+            public static readonly Field Format = FindByName("Format");
+
+            /// <summary>点击量</summary>
+            public static readonly Field Hits = FindByName("Hits");
+
+            /// <summary>点赞数</summary>
+            public static readonly Field Likes = FindByName("Likes");
+
+            /// <summary>评论数</summary>
+            public static readonly Field Comments = FindByName("Comments");
+
+            /// <summary>正文。Markdown格式</summary>
+            public static readonly Field Body = FindByName("Body");
+
+            /// <summary>HTML正文</summary>
+            public static readonly Field BodyHtml = FindByName("BodyHtml");
+
+            /// <summary>内容更新时间</summary>
+            public static readonly Field ContentUpdateTime = FindByName("ContentUpdateTime");
+
+            /// <summary>发布时间</summary>
+            public static readonly Field PublishTime = FindByName("PublishTime");
+
+            /// <summary>首次发布</summary>
+            public static readonly Field FirstPublishTime = FindByName("FirstPublishTime");
+
+            /// <summary>单词数</summary>
+            public static readonly Field WordCount = FindByName("WordCount");
+
+            /// <summary>封面</summary>
+            public static readonly Field Cover = FindByName("Cover");
+
+            /// <summary>创建者</summary>
+            public static readonly Field CreateUser = FindByName("CreateUser");
+
+            /// <summary>创建人</summary>
+            public static readonly Field CreateUserID = FindByName("CreateUserID");
+
+            /// <summary>创建地址</summary>
+            public static readonly Field CreateIP = FindByName("CreateIP");
+
+            /// <summary>创建时间</summary>
+            public static readonly Field CreateTime = FindByName("CreateTime");
+
+            /// <summary>更新者</summary>
+            public static readonly Field UpdateUser = FindByName("UpdateUser");
+
+            /// <summary>更新人</summary>
+            public static readonly Field UpdateUserID = FindByName("UpdateUserID");
+
+            /// <summary>更新地址</summary>
+            public static readonly Field UpdateIP = FindByName("UpdateIP");
+
+            /// <summary>更新时间</summary>
+            public static readonly Field UpdateTime = FindByName("UpdateTime");
+
+            /// <summary>备注</summary>
+            public static readonly Field Remark = FindByName("Remark");
+
+            static Field FindByName(String name) => Meta.Table.FindByName(name);
+        }
+
+        /// <summary>取得文档字段名称的快捷方式</summary>
+        public partial class __
+        {
+            /// <summary>编号</summary>
+            public const String Id = "Id";
+
+            /// <summary>编码。路径唯一标识,默认取Slug</summary>
+            public const String Code = "Code";
+
+            /// <summary>标题</summary>
+            public const String Title = "Title";
+
+            /// <summary>知识库</summary>
+            public const String BookId = "BookId";
+
+            /// <summary>启用</summary>
+            public const String Enable = "Enable";
+
+            /// <summary>用户</summary>
+            public const String UserName = "UserName";
+
+            /// <summary>格式</summary>
+            public const String Format = "Format";
+
+            /// <summary>点击量</summary>
+            public const String Hits = "Hits";
+
+            /// <summary>点赞数</summary>
+            public const String Likes = "Likes";
+
+            /// <summary>评论数</summary>
+            public const String Comments = "Comments";
+
+            /// <summary>正文。Markdown格式</summary>
+            public const String Body = "Body";
+
+            /// <summary>HTML正文</summary>
+            public const String BodyHtml = "BodyHtml";
+
+            /// <summary>内容更新时间</summary>
+            public const String ContentUpdateTime = "ContentUpdateTime";
+
+            /// <summary>发布时间</summary>
+            public const String PublishTime = "PublishTime";
+
+            /// <summary>首次发布</summary>
+            public const String FirstPublishTime = "FirstPublishTime";
+
+            /// <summary>单词数</summary>
+            public const String WordCount = "WordCount";
+
+            /// <summary>封面</summary>
+            public const String Cover = "Cover";
+
+            /// <summary>创建者</summary>
+            public const String CreateUser = "CreateUser";
+
+            /// <summary>创建人</summary>
+            public const String CreateUserID = "CreateUserID";
+
+            /// <summary>创建地址</summary>
+            public const String CreateIP = "CreateIP";
+
+            /// <summary>创建时间</summary>
+            public const String CreateTime = "CreateTime";
+
+            /// <summary>更新者</summary>
+            public const String UpdateUser = "UpdateUser";
+
+            /// <summary>更新人</summary>
+            public const String UpdateUserID = "UpdateUserID";
+
+            /// <summary>更新地址</summary>
+            public const String UpdateIP = "UpdateIP";
+
+            /// <summary>更新时间</summary>
+            public const String UpdateTime = "UpdateTime";
+
+            /// <summary>备注</summary>
+            public const String Remark = "Remark";
+        }
+        #endregion
+    }
+}
\ No newline at end of file
Added +204 -0
diff --git "a/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.Biz.cs" "b/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.Biz.cs"
new file mode 100644
index 0000000..9c2d88e
--- /dev/null
+++ "b/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.Biz.cs"
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+using System.Web.Script.Serialization;
+using System.Xml.Serialization;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Model;
+using NewLife.Reflection;
+using NewLife.Threading;
+using NewLife.Web;
+using XCode;
+using XCode.Cache;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+using XCode.Membership;
+using XCode.Shards;
+
+namespace NewLife.YuQueWeb.Entity
+{
+    /// <summary>知识库。管理知识库</summary>
+    public partial class Book : Entity<Book>
+    {
+        #region 对象操作
+        static Book()
+        {
+            // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
+            //var df = Meta.Factory.AdditionalFields;
+            //df.Add(nameof(Docs));
+
+            // 过滤器 UserModule、TimeModule、IPModule
+            Meta.Modules.Add<UserModule>();
+            Meta.Modules.Add<TimeModule>();
+            Meta.Modules.Add<IPModule>();
+        }
+
+        /// <summary>验证并修补数据,通过抛出异常的方式提示验证失败。</summary>
+        /// <param name="isNew">是否插入</param>
+        public override void Valid(Boolean isNew)
+        {
+            // 如果没有脏数据,则不需要进行任何处理
+            if (!HasDirty) return;
+
+            // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
+            if (Name.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Name), "名称不能为空!");
+
+            // 建议先调用基类方法,基类方法会做一些统一处理
+            base.Valid(isNew);
+
+            // 在新插入数据或者修改了指定字段时进行修正
+            // 处理当前已登录用户信息,可以由UserModule过滤器代劳
+            /*var user = ManageProvider.User;
+            if (user != null)
+            {
+                if (isNew && !Dirtys[nameof(CreateUserID)]) CreateUserID = user.ID;
+                if (!Dirtys[nameof(UpdateUserID)]) UpdateUserID = user.ID;
+            }*/
+            //if (isNew && !Dirtys[nameof(CreateTime)]) CreateTime = DateTime.Now;
+            //if (!Dirtys[nameof(UpdateTime)]) UpdateTime = DateTime.Now;
+            //if (isNew && !Dirtys[nameof(CreateIP)]) CreateIP = ManageProvider.UserHost;
+            //if (!Dirtys[nameof(UpdateIP)]) UpdateIP = ManageProvider.UserHost;
+
+            // 检查唯一索引
+            // CheckExist(isNew, nameof(Code));
+        }
+
+        ///// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
+        //[EditorBrowsable(EditorBrowsableState.Never)]
+        //protected override void InitData()
+        //{
+        //    // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用
+        //    if (Meta.Session.Count > 0) return;
+
+        //    if (XTrace.Debug) XTrace.WriteLine("开始初始化Book[知识库]数据……");
+
+        //    var entity = new Book();
+        //    entity.Code = "abc";
+        //    entity.Name = "abc";
+        //    entity.Type = "abc";
+        //    entity.Enable = true;
+        //    entity.UserName = "abc";
+        //    entity.Docs = 0;
+        //    entity.Likes = 0;
+        //    entity.Watches = 0;
+        //    entity.Sync = true;
+        //    entity.Slug = "abc";
+        //    entity.Namespace = "abc";
+        //    entity.SyncTime = DateTime.Now;
+        //    entity.CreateUser = "abc";
+        //    entity.CreateUserID = 0;
+        //    entity.CreateIP = "abc";
+        //    entity.CreateTime = DateTime.Now;
+        //    entity.UpdateUser = "abc";
+        //    entity.UpdateUserID = 0;
+        //    entity.UpdateIP = "abc";
+        //    entity.UpdateTime = DateTime.Now;
+        //    entity.Remark = "abc";
+        //    entity.Insert();
+
+        //    if (XTrace.Debug) XTrace.WriteLine("完成初始化Book[知识库]数据!");
+        //}
+
+        ///// <summary>已重载。基类先调用Valid(true)验证数据,然后在事务保护内调用OnInsert</summary>
+        ///// <returns></returns>
+        //public override Int32 Insert()
+        //{
+        //    return base.Insert();
+        //}
+
+        ///// <summary>已重载。在事务保护范围内处理业务,位于Valid之后</summary>
+        ///// <returns></returns>
+        //protected override Int32 OnDelete()
+        //{
+        //    return base.OnDelete();
+        //}
+        #endregion
+
+        #region 扩展属性
+        #endregion
+
+        #region 扩展查询
+        /// <summary>根据编号查找</summary>
+        /// <param name="id">编号</param>
+        /// <returns>实体对象</returns>
+        public static Book FindById(Int32 id)
+        {
+            if (id <= 0) return null;
+
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Id == id);
+
+            // 单对象缓存
+            return Meta.SingleCache[id];
+
+            //return Find(_.Id == id);
+        }
+
+        /// <summary>根据编码查找</summary>
+        /// <param name="code">编码</param>
+        /// <returns>实体对象</returns>
+        public static Book FindByCode(String code)
+        {
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Code.EqualIgnoreCase(code));
+
+            return Find(_.Code == code);
+        }
+
+        /// <summary>根据名称查找</summary>
+        /// <param name="name">名称</param>
+        /// <returns>实体列表</returns>
+        public static IList<Book> FindAllByName(String name)
+        {
+            // 实体缓存
+            if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.Name.EqualIgnoreCase(name));
+
+            return FindAll(_.Name == name);
+        }
+        #endregion
+
+        #region 高级查询
+        /// <summary>高级查询</summary>
+        /// <param name="code">编码。路径唯一标识,默认取Slug</param>
+        /// <param name="name">名称</param>
+        /// <param name="start">更新时间开始</param>
+        /// <param name="end">更新时间结束</param>
+        /// <param name="key">关键字</param>
+        /// <param name="page">分页参数信息。可携带统计和数据权限扩展查询等信息</param>
+        /// <returns>实体列表</returns>
+        public static IList<Book> Search(String code, String name, DateTime start, DateTime end, String key, PageParameter page)
+        {
+            var exp = new WhereExpression();
+
+            if (!code.IsNullOrEmpty()) exp &= _.Code == code;
+            if (!name.IsNullOrEmpty()) exp &= _.Name == name;
+            exp &= _.UpdateTime.Between(start, end);
+            if (!key.IsNullOrEmpty()) exp &= _.Code.Contains(key) | _.Name.Contains(key) | _.Type.Contains(key) | _.UserName.Contains(key) | _.Slug.Contains(key) | _.Namespace.Contains(key) | _.CreateUser.Contains(key) | _.CreateIP.Contains(key) | _.UpdateUser.Contains(key) | _.UpdateIP.Contains(key) | _.Remark.Contains(key);
+
+            return FindAll(exp, page);
+        }
+
+        // Select Count(Id) as Id,Category From Book Where CreateTime>'2020-01-24 00:00:00' Group By Category Order By Id Desc limit 20
+        //static readonly FieldCache<Book> _CategoryCache = new FieldCache<Book>(nameof(Category))
+        //{
+        //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty
+        //};
+
+        ///// <summary>获取类别列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>
+        ///// <returns></returns>
+        //public static IDictionary<String, String> GetCategoryList() => _CategoryCache.FindAllName();
+        #endregion
+
+        #region 业务操作
+        #endregion
+    }
+}
\ No newline at end of file
Added +411 -0
diff --git "a/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.cs" "b/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.cs"
new file mode 100644
index 0000000..e0c5f95
--- /dev/null
+++ "b/NewLife.YuqueWeb/Entity/\347\237\245\350\257\206\345\272\223.cs"
@@ -0,0 +1,411 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.Serialization;
+using System.Web.Script.Serialization;
+using System.Xml.Serialization;
+using XCode;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+
+namespace NewLife.YuQueWeb.Entity
+{
+    /// <summary>知识库。管理知识库</summary>
+    [Serializable]
+    [DataObject]
+    [Description("知识库。管理知识库")]
+    [BindIndex("IU_Book_Code", true, "Code")]
+    [BindIndex("IX_Book_Name", false, "Name")]
+    [BindTable("Book", Description = "知识库。管理知识库", ConnName = "YuQue", DbType = DatabaseType.None)]
+    public partial class Book
+    {
+        #region 属性
+        private Int32 _Id;
+        /// <summary>编号</summary>
+        [DisplayName("编号")]
+        [Description("编号")]
+        [DataObjectField(true, true, false, 0)]
+        [BindColumn("Id", "编号", "")]
+        public Int32 Id { get => _Id; set { if (OnPropertyChanging("Id", value)) { _Id = value; OnPropertyChanged("Id"); } } }
+
+        private String _Code;
+        /// <summary>编码。路径唯一标识,默认取Slug</summary>
+        [DisplayName("编码")]
+        [Description("编码。路径唯一标识,默认取Slug")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Code", "编码。路径唯一标识,默认取Slug", "")]
+        public String Code { get => _Code; set { if (OnPropertyChanging("Code", value)) { _Code = value; OnPropertyChanged("Code"); } } }
+
+        private String _Name;
+        /// <summary>名称</summary>
+        [DisplayName("名称")]
+        [Description("名称")]
+        [DataObjectField(false, false, false, 50)]
+        [BindColumn("Name", "名称", "", Master = true)]
+        public String Name { get => _Name; set { if (OnPropertyChanging("Name", value)) { _Name = value; OnPropertyChanged("Name"); } } }
+
+        private String _Type;
+        /// <summary>类型</summary>
+        [DisplayName("类型")]
+        [Description("类型")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Type", "类型", "")]
+        public String Type { get => _Type; set { if (OnPropertyChanging("Type", value)) { _Type = value; OnPropertyChanged("Type"); } } }
+
+        private Boolean _Enable;
+        /// <summary>启用</summary>
+        [DisplayName("启用")]
+        [Description("启用")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Enable", "启用", "")]
+        public Boolean Enable { get => _Enable; set { if (OnPropertyChanging("Enable", value)) { _Enable = value; OnPropertyChanged("Enable"); } } }
+
+        private String _UserName;
+        /// <summary>用户</summary>
+        [DisplayName("用户")]
+        [Description("用户")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UserName", "用户", "")]
+        public String UserName { get => _UserName; set { if (OnPropertyChanging("UserName", value)) { _UserName = value; OnPropertyChanged("UserName"); } } }
+
+        private Int32 _Docs;
+        /// <summary>文章数</summary>
+        [DisplayName("文章数")]
+        [Description("文章数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Docs", "文章数", "")]
+        public Int32 Docs { get => _Docs; set { if (OnPropertyChanging("Docs", value)) { _Docs = value; OnPropertyChanged("Docs"); } } }
+
+        private Int32 _Likes;
+        /// <summary>点赞数</summary>
+        [DisplayName("点赞数")]
+        [Description("点赞数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Likes", "点赞数", "")]
+        public Int32 Likes { get => _Likes; set { if (OnPropertyChanging("Likes", value)) { _Likes = value; OnPropertyChanged("Likes"); } } }
+
+        private Int32 _Watches;
+        /// <summary>订阅数</summary>
+        [DisplayName("订阅数")]
+        [Description("订阅数")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Watches", "订阅数", "")]
+        public Int32 Watches { get => _Watches; set { if (OnPropertyChanging("Watches", value)) { _Watches = value; OnPropertyChanged("Watches"); } } }
+
+        private Boolean _Sync;
+        /// <summary>同步。是否自动同步远程内容</summary>
+        [DisplayName("同步")]
+        [Description("同步。是否自动同步远程内容")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("Sync", "同步。是否自动同步远程内容", "")]
+        public Boolean Sync { get => _Sync; set { if (OnPropertyChanging("Sync", value)) { _Sync = value; OnPropertyChanged("Sync"); } } }
+
+        private String _Slug;
+        /// <summary>路径</summary>
+        [DisplayName("路径")]
+        [Description("路径")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Slug", "路径", "")]
+        public String Slug { get => _Slug; set { if (OnPropertyChanging("Slug", value)) { _Slug = value; OnPropertyChanged("Slug"); } } }
+
+        private String _Namespace;
+        /// <summary>全路径</summary>
+        [DisplayName("全路径")]
+        [Description("全路径")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("Namespace", "全路径", "")]
+        public String Namespace { get => _Namespace; set { if (OnPropertyChanging("Namespace", value)) { _Namespace = value; OnPropertyChanged("Namespace"); } } }
+
+        private DateTime _SyncTime;
+        /// <summary>同步时间。最后一次同步数据的时间</summary>
+        [DisplayName("同步时间")]
+        [Description("同步时间。最后一次同步数据的时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("SyncTime", "同步时间。最后一次同步数据的时间", "")]
+        public DateTime SyncTime { get => _SyncTime; set { if (OnPropertyChanging("SyncTime", value)) { _SyncTime = value; OnPropertyChanged("SyncTime"); } } }
+
+        private String _CreateUser;
+        /// <summary>创建者</summary>
+        [DisplayName("创建者")]
+        [Description("创建者")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("CreateUser", "创建者", "")]
+        public String CreateUser { get => _CreateUser; set { if (OnPropertyChanging("CreateUser", value)) { _CreateUser = value; OnPropertyChanged("CreateUser"); } } }
+
+        private Int32 _CreateUserID;
+        /// <summary>创建人</summary>
+        [DisplayName("创建人")]
+        [Description("创建人")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("CreateUserID", "创建人", "")]
+        public Int32 CreateUserID { get => _CreateUserID; set { if (OnPropertyChanging("CreateUserID", value)) { _CreateUserID = value; OnPropertyChanged("CreateUserID"); } } }
+
+        private String _CreateIP;
+        /// <summary>创建地址</summary>
+        [DisplayName("创建地址")]
+        [Description("创建地址")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("CreateIP", "创建地址", "")]
+        public String CreateIP { get => _CreateIP; set { if (OnPropertyChanging("CreateIP", value)) { _CreateIP = value; OnPropertyChanged("CreateIP"); } } }
+
+        private DateTime _CreateTime;
+        /// <summary>创建时间</summary>
+        [DisplayName("创建时间")]
+        [Description("创建时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("CreateTime", "创建时间", "")]
+        public DateTime CreateTime { get => _CreateTime; set { if (OnPropertyChanging("CreateTime", value)) { _CreateTime = value; OnPropertyChanged("CreateTime"); } } }
+
+        private String _UpdateUser;
+        /// <summary>更新者</summary>
+        [DisplayName("更新者")]
+        [Description("更新者")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UpdateUser", "更新者", "")]
+        public String UpdateUser { get => _UpdateUser; set { if (OnPropertyChanging("UpdateUser", value)) { _UpdateUser = value; OnPropertyChanged("UpdateUser"); } } }
+
+        private Int32 _UpdateUserID;
+        /// <summary>更新人</summary>
+        [DisplayName("更新人")]
+        [Description("更新人")]
+        [DataObjectField(false, false, false, 0)]
+        [BindColumn("UpdateUserID", "更新人", "")]
+        public Int32 UpdateUserID { get => _UpdateUserID; set { if (OnPropertyChanging("UpdateUserID", value)) { _UpdateUserID = value; OnPropertyChanged("UpdateUserID"); } } }
+
+        private String _UpdateIP;
+        /// <summary>更新地址</summary>
+        [DisplayName("更新地址")]
+        [Description("更新地址")]
+        [DataObjectField(false, false, true, 50)]
+        [BindColumn("UpdateIP", "更新地址", "")]
+        public String UpdateIP { get => _UpdateIP; set { if (OnPropertyChanging("UpdateIP", value)) { _UpdateIP = value; OnPropertyChanged("UpdateIP"); } } }
+
+        private DateTime _UpdateTime;
+        /// <summary>更新时间</summary>
+        [DisplayName("更新时间")]
+        [Description("更新时间")]
+        [DataObjectField(false, false, true, 0)]
+        [BindColumn("UpdateTime", "更新时间", "")]
+        public DateTime UpdateTime { get => _UpdateTime; set { if (OnPropertyChanging("UpdateTime", value)) { _UpdateTime = value; OnPropertyChanged("UpdateTime"); } } }
+
+        private String _Remark;
+        /// <summary>备注</summary>
+        [DisplayName("备注")]
+        [Description("备注")]
+        [DataObjectField(false, false, true, 500)]
+        [BindColumn("Remark", "备注", "")]
+        public String Remark { get => _Remark; set { if (OnPropertyChanging("Remark", value)) { _Remark = value; OnPropertyChanged("Remark"); } } }
+        #endregion
+
+        #region 获取/设置 字段值
+        /// <summary>获取/设置 字段值</summary>
+        /// <param name="name">字段名</param>
+        /// <returns></returns>
+        public override Object this[String name]
+        {
+            get
+            {
+                switch (name)
+                {
+                    case "Id": return _Id;
+                    case "Code": return _Code;
+                    case "Name": return _Name;
+                    case "Type": return _Type;
+                    case "Enable": return _Enable;
+                    case "UserName": return _UserName;
+                    case "Docs": return _Docs;
+                    case "Likes": return _Likes;
+                    case "Watches": return _Watches;
+                    case "Sync": return _Sync;
+                    case "Slug": return _Slug;
+                    case "Namespace": return _Namespace;
+                    case "SyncTime": return _SyncTime;
+                    case "CreateUser": return _CreateUser;
+                    case "CreateUserID": return _CreateUserID;
+                    case "CreateIP": return _CreateIP;
+                    case "CreateTime": return _CreateTime;
+                    case "UpdateUser": return _UpdateUser;
+                    case "UpdateUserID": return _UpdateUserID;
+                    case "UpdateIP": return _UpdateIP;
+                    case "UpdateTime": return _UpdateTime;
+                    case "Remark": return _Remark;
+                    default: return base[name];
+                }
+            }
+            set
+            {
+                switch (name)
+                {
+                    case "Id": _Id = value.ToInt(); break;
+                    case "Code": _Code = Convert.ToString(value); break;
+                    case "Name": _Name = Convert.ToString(value); break;
+                    case "Type": _Type = Convert.ToString(value); break;
+                    case "Enable": _Enable = value.ToBoolean(); break;
+                    case "UserName": _UserName = Convert.ToString(value); break;
+                    case "Docs": _Docs = value.ToInt(); break;
+                    case "Likes": _Likes = value.ToInt(); break;
+                    case "Watches": _Watches = value.ToInt(); break;
+                    case "Sync": _Sync = value.ToBoolean(); break;
+                    case "Slug": _Slug = Convert.ToString(value); break;
+                    case "Namespace": _Namespace = Convert.ToString(value); break;
+                    case "SyncTime": _SyncTime = value.ToDateTime(); break;
+                    case "CreateUser": _CreateUser = Convert.ToString(value); break;
+                    case "CreateUserID": _CreateUserID = value.ToInt(); break;
+                    case "CreateIP": _CreateIP = Convert.ToString(value); break;
+                    case "CreateTime": _CreateTime = value.ToDateTime(); break;
+                    case "UpdateUser": _UpdateUser = Convert.ToString(value); break;
+                    case "UpdateUserID": _UpdateUserID = value.ToInt(); break;
+                    case "UpdateIP": _UpdateIP = Convert.ToString(value); break;
+                    case "UpdateTime": _UpdateTime = value.ToDateTime(); break;
+                    case "Remark": _Remark = Convert.ToString(value); break;
+                    default: base[name] = value; break;
+                }
+            }
+        }
+        #endregion
+
+        #region 字段名
+        /// <summary>取得知识库字段信息的快捷方式</summary>
+        public partial class _
+        {
+            /// <summary>编号</summary>
+            public static readonly Field Id = FindByName("Id");
+
+            /// <summary>编码。路径唯一标识,默认取Slug</summary>
+            public static readonly Field Code = FindByName("Code");
+
+            /// <summary>名称</summary>
+            public static readonly Field Name = FindByName("Name");
+
+            /// <summary>类型</summary>
+            public static readonly Field Type = FindByName("Type");
+
+            /// <summary>启用</summary>
+            public static readonly Field Enable = FindByName("Enable");
+
+            /// <summary>用户</summary>
+            public static readonly Field UserName = FindByName("UserName");
+
+            /// <summary>文章数</summary>
+            public static readonly Field Docs = FindByName("Docs");
+
+            /// <summary>点赞数</summary>
+            public static readonly Field Likes = FindByName("Likes");
+
+            /// <summary>订阅数</summary>
+            public static readonly Field Watches = FindByName("Watches");
+
+            /// <summary>同步。是否自动同步远程内容</summary>
+            public static readonly Field Sync = FindByName("Sync");
+
+            /// <summary>路径</summary>
+            public static readonly Field Slug = FindByName("Slug");
+
+            /// <summary>全路径</summary>
+            public static readonly Field Namespace = FindByName("Namespace");
+
+            /// <summary>同步时间。最后一次同步数据的时间</summary>
+            public static readonly Field SyncTime = FindByName("SyncTime");
+
+            /// <summary>创建者</summary>
+            public static readonly Field CreateUser = FindByName("CreateUser");
+
+            /// <summary>创建人</summary>
+            public static readonly Field CreateUserID = FindByName("CreateUserID");
+
+            /// <summary>创建地址</summary>
+            public static readonly Field CreateIP = FindByName("CreateIP");
+
+            /// <summary>创建时间</summary>
+            public static readonly Field CreateTime = FindByName("CreateTime");
+
+            /// <summary>更新者</summary>
+            public static readonly Field UpdateUser = FindByName("UpdateUser");
+
+            /// <summary>更新人</summary>
+            public static readonly Field UpdateUserID = FindByName("UpdateUserID");
+
+            /// <summary>更新地址</summary>
+            public static readonly Field UpdateIP = FindByName("UpdateIP");
+
+            /// <summary>更新时间</summary>
+            public static readonly Field UpdateTime = FindByName("UpdateTime");
+
+            /// <summary>备注</summary>
+            public static readonly Field Remark = FindByName("Remark");
+
+            static Field FindByName(String name) => Meta.Table.FindByName(name);
+        }
+
+        /// <summary>取得知识库字段名称的快捷方式</summary>
+        public partial class __
+        {
+            /// <summary>编号</summary>
+            public const String Id = "Id";
+
+            /// <summary>编码。路径唯一标识,默认取Slug</summary>
+            public const String Code = "Code";
+
+            /// <summary>名称</summary>
+            public const String Name = "Name";
+
+            /// <summary>类型</summary>
+            public const String Type = "Type";
+
+            /// <summary>启用</summary>
+            public const String Enable = "Enable";
+
+            /// <summary>用户</summary>
+            public const String UserName = "UserName";
+
+            /// <summary>文章数</summary>
+            public const String Docs = "Docs";
+
+            /// <summary>点赞数</summary>
+            public const String Likes = "Likes";
+
+            /// <summary>订阅数</summary>
+            public const String Watches = "Watches";
+
+            /// <summary>同步。是否自动同步远程内容</summary>
+            public const String Sync = "Sync";
+
+            /// <summary>路径</summary>
+            public const String Slug = "Slug";
+
+            /// <summary>全路径</summary>
+            public const String Namespace = "Namespace";
+
+            /// <summary>同步时间。最后一次同步数据的时间</summary>
+            public const String SyncTime = "SyncTime";
+
+            /// <summary>创建者</summary>
+            public const String CreateUser = "CreateUser";
+
+            /// <summary>创建人</summary>
+            public const String CreateUserID = "CreateUserID";
+
+            /// <summary>创建地址</summary>
+            public const String CreateIP = "CreateIP";
+
+            /// <summary>创建时间</summary>
+            public const String CreateTime = "CreateTime";
+
+            /// <summary>更新者</summary>
+            public const String UpdateUser = "UpdateUser";
+
+            /// <summary>更新人</summary>
+            public const String UpdateUserID = "UpdateUserID";
+
+            /// <summary>更新地址</summary>
+            public const String UpdateIP = "UpdateIP";
+
+            /// <summary>更新时间</summary>
+            public const String UpdateTime = "UpdateTime";
+
+            /// <summary>备注</summary>
+            public const String Remark = "Remark";
+        }
+        #endregion
+    }
+}
\ No newline at end of file
Added +56 -0
diff --git a/NewLife.YuqueWeb/NewLife.YuqueWeb.csproj b/NewLife.YuqueWeb/NewLife.YuqueWeb.csproj
new file mode 100644
index 0000000..82e0a34
--- /dev/null
+++ b/NewLife.YuqueWeb/NewLife.YuqueWeb.csproj
@@ -0,0 +1,56 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <AssemblyTitle>语雀Web</AssemblyTitle>
+    <Description>自动语雀内容到本系统,对外呈现内容页面</Description>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2022 NewLife</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>5.0.*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\Bin</OutputPath>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+
+    <PropertyGroup>
+    <IsPackable>true</IsPackable>
+    <PackageId>NewLife.YuqueWeb</PackageId>
+    <Authors>$(Company)</Authors>
+    <PackageProjectUrl>https://www.yuque.com/smartstone/cube</PackageProjectUrl>
+    <PackageIcon>leaf.png</PackageIcon>
+    <RepositoryUrl>https://github.com/NewLifeX/NewLife.Cube</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+    <PackageTags>新生命团队;X组件;NewLife;$(AssemblyName)</PackageTags>
+    <PackageReleaseNotes>新增MenuAttribute优化控制器菜单管理;新增魔方管理区域;增强OAuth配置</PackageReleaseNotes>
+    <PackageLicenseExpression>MIT</PackageLicenseExpression>
+    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
+    <PublishRepositoryUrl>true</PublishRepositoryUrl>
+    <EmbedUntrackedSources>true</EmbedUntrackedSources>
+    <IncludeSymbols>true</IncludeSymbols>
+    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="NewLife.Cube.Core" Version="5.0.2022.401" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\NewLife.YuQue\NewLife.Yuque.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\Doc\leaf.png" Link="leaf.png" PackagePath="\" />
+  </ItemGroup>
+
+</Project>
Added +28 -0
diff --git a/NewLife.YuqueWeb/Properties/launchSettings.json b/NewLife.YuqueWeb/Properties/launchSettings.json
new file mode 100644
index 0000000..d0076b5
--- /dev/null
+++ b/NewLife.YuqueWeb/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:46271",
+      "sslPort": 0
+    }
+  },
+  "profiles": {
+    "NewLife.YuQueWeb": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "applicationUrl": "http://localhost:5210",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
Added +45 -0
diff --git a/NewLife.YuqueWeb/YuqueService.cs b/NewLife.YuqueWeb/YuqueService.cs
new file mode 100644
index 0000000..6987249
--- /dev/null
+++ b/NewLife.YuqueWeb/YuqueService.cs
@@ -0,0 +1,45 @@
+using System.Reflection;
+using NewLife.Common;
+using NewLife.Cube;
+using NewLife.Log;
+using NewLife.YuqueWeb.Areas.Yuque;
+using XCode.DataAccessLayer;
+
+namespace NewLife.YuQueWeb;
+
+/// <summary>语雀服务</summary>
+public static class YuqueService
+{
+    /// <summary>添加语雀</summary>
+    /// <param name="services"></param>
+    /// <returns></returns>
+    public static IServiceCollection AddYuque(this IServiceCollection services)
+    {
+        using var span = DefaultTracer.Instance?.NewSpan(nameof(AddYuque));
+
+        XTrace.WriteLine("{0} Start 配置语雀 {0}", new String('=', 32));
+        Assembly.GetExecutingAssembly().WriteVersion();
+
+        XTrace.WriteLine("{0} End   配置语雀 {0}", new String('=', 32));
+
+        return services;
+    }
+
+    /// <summary>使用语雀</summary>
+    /// <param name="app"></param>
+    /// <param name="env"></param>
+    /// <returns></returns>
+    public static IApplicationBuilder UseYuque(this IApplicationBuilder app, IWebHostEnvironment env)
+    {
+        using var span = DefaultTracer.Instance?.NewSpan(nameof(UseYuque));
+
+        XTrace.WriteLine("{0} Start 初始化语雀 {0}", new String('=', 32));
+
+        // 自动检查并添加菜单
+        AreaBase.RegisterArea<YuqueArea>();
+
+        XTrace.WriteLine("{0} End   初始化语雀 {0}", new String('=', 32));
+
+        return app;
+    }
+}
\ No newline at end of file
Modified +1 -1
diff --git a/Test/Test.csproj b/Test/Test.csproj
index a933d03..8c62ad0 100644
--- a/Test/Test.csproj
+++ b/Test/Test.csproj
@@ -7,7 +7,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\NewLife.YuQue\NewLife.YuQue.csproj" />
+    <ProjectReference Include="..\NewLife.YuQue\NewLife.Yuque.csproj" />
   </ItemGroup>
 
 </Project>
Modified +1 -1
diff --git a/XUnitTest/XUnitTest.csproj b/XUnitTest/XUnitTest.csproj
index 8352ff0..e11ff0c 100644
--- a/XUnitTest/XUnitTest.csproj
+++ b/XUnitTest/XUnitTest.csproj
@@ -18,7 +18,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\NewLife.YuQue\NewLife.YuQue.csproj" />
+    <ProjectReference Include="..\NewLife.YuQue\NewLife.Yuque.csproj" />
   </ItemGroup>
 
 </Project>
Added +9 -0
diff --git a/YqWeb/appsettings.Development.json b/YqWeb/appsettings.Development.json
new file mode 100644
index 0000000..770d3e9
--- /dev/null
+++ b/YqWeb/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+  "DetailedErrors": true,
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
Added +9 -0
diff --git a/YqWeb/appsettings.json b/YqWeb/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/YqWeb/appsettings.json
@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}
Added +21 -0
diff --git a/YqWeb/Program.cs b/YqWeb/Program.cs
new file mode 100644
index 0000000..f11e9ed
--- /dev/null
+++ b/YqWeb/Program.cs
@@ -0,0 +1,21 @@
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddRazorPages();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
+{
+    app.UseExceptionHandler("/Error");
+}
+app.UseStaticFiles();
+
+app.UseRouting();
+
+app.UseAuthorization();
+
+app.MapRazorPages();
+
+app.Run();
Added +28 -0
diff --git a/YqWeb/Properties/launchSettings.json b/YqWeb/Properties/launchSettings.json
new file mode 100644
index 0000000..b8521f2
--- /dev/null
+++ b/YqWeb/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:33992",
+      "sslPort": 0
+    }
+  },
+  "profiles": {
+    "YQWeb": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "applicationUrl": "http://localhost:5153",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
Added +13 -0
diff --git a/YqWeb/YqWeb.csproj b/YqWeb/YqWeb.csproj
new file mode 100644
index 0000000..f8c2544
--- /dev/null
+++ b/YqWeb/YqWeb.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\NewLife.YuQueWeb\NewLife.YuqueWeb.csproj" />
+  </ItemGroup>
+
+</Project>