`
ihuashao
  • 浏览: 4711512 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

.NET三层经典架构PetShop3.0分析之数据访问层

阅读更多

关于一些Microsoft PetShop的来由、如何安装,所表现业务流程,数据库表结构等基本的信息的资料请大家参考下面文章

http://msdn.microsoft.com/library/en-us/dnbda/html/bdasamppet.asp

另外建议先看一下这篇文章:

http://msdn.microsoft.com/library/en-us/dnbda/html/petshop3x.asp

本文将以设计和实现紧密结合的方式来分析,这也是我们广大实践型的软件开发人员的风格。先看一下设计图和具体实现VS.NET工程的表格。

MSPetShop 3.0 系统结构图:

<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 337.5pt; HEIGHT: 172.5pt" alt="" type="#_x0000_t75"><img alt="" o:href="http://msdn.microsoft.com/library/en-us/dnbda/html/petshop3x_08.gif" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-20008530.gif"></shape>

从图中可以看到系统大体分为Presentation,Business Logic,Data Access 三层,每层中又有子层。每层(也包括子层)各司其职,又互相协作,本文顺序以此图为准,从下到上分析。

对应上图,具体的.NET Project实现列表(借用MS文章中的列表不用翻译了吧)

Project

Purpose

BLL

Home for business logic components

ConfigTool

Administration application used to encrypt connection strings and create event log source

DALFactory

Classes used to determine which database access assembly to load

IDAL

Set of interfaces which need to be implemented by each DAL implementation

Model

Thin data classes or business entities

OracleDAL

Oracle specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Post-Build

Project to run post compile actions such as adding assemblies to the GAC or COM+

Pre-Build

Project to remove assemblies from the GAC or unregister assemblies from COM+

SQLServerDAL

Microsoft SQL Server specific implementation of the Pet Shop DAL which uses the IDAL interfaces

Utility

Set of helper classes including a wrapper for the DPAPI

Web

Web pages and controls

Solution Items

Miscellaneous items used to build the application such as Pet Shop.snk key file used to sign application assemblies

另外我写这篇文章时是一边看源码一边写,所以建意大家最好安装一个Petshop3,因为时间仓促,水平有限,如我有不对之处请给我发Email更正。email:cocoboy79@163.com qq:364941

首先我们来看一下DAL层。

一:Data Access Layer:

1 PetShop.Utility如下图:(上表中Utility为其实现工程)

<shape id="_x0000_i1027" style="WIDTH: 140.25pt; HEIGHT: 1in" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-20008532.png"></shape>

正如上表所描述,这个名字空间有两个类一个是ConnectionInfo用于加密解密数据库连接信息,另一个DataProtector调用了Crypt32.dll和kernel32.dll实现一些底层数据安全操作,这个类要在下面的PetShop.XXXDAL名字空间中调用,可见Petshop.Utility只是起到的是数据访问辅助工具的作用。

2 PetShop.SQLServerDAL ――系统结构图中DAL层中的SqlServer DAL子层实现

<shape id="_x0000_i1028" style="WIDTH: 119.25pt; HEIGHT: 117.75pt" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-20008534.png"></shape>

SqlHelper类实际上是封装了关于此系统中数据库操作访问的一些常用功能,其中它还会调用上面的PetShop.Utility中的ConectionInfo类方法加密解密连接字符串,如:ConnectionInfo.DecryptDBConnectionString方法。SqlHelper类是基于Microsoft Data Access Application Block for .NET。这个东西是用来帮助用户更好的在.NET的访问数据。如MS一段话:Are you involved in the design and development of data access code for .NET-based applications? Have you ever felt that you write the same data access code again and again? Have you wrapped data access code in helper functions that let you call a stored procedure in one line? If so, the Microsoft® Data Access Application Block for .NET is for you。其实可以自已写一个类似SqlHelper的东西,以实现一般化的对数据库的操作,以在各项目中重用,当然也可以使用现在的MS为你做好的这个SqlHelper或是Microsoft® Data Access Application Block for .NET,避免不同项目中总是写同样的重复的数据库访问程序。有时间最好还是看一下SqlHelper的具体程序实现思路以及所提到的那个Microsoft Data Access Application Block for .NET。不过这里我们的SqlHelper应该只是部分实现。更全面信息请参看:http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp

Account类对用户帐户进行操作如Insert,Update,SignIn,其中这些对数据库的操作,使用了上面的SqlHelper类来实现。另外Inventory和Order,Product,Profile和Account类的都是同样对数据库相关表进行操作,程序风格一致,这些类中对数据库的操作都是通过此名字空间下的SqlHelper类进行的,例如,下面语句:

private const string SQL_INSERT_SIGNON = "INSERT INTO SignOn VALUES (@UserId, @Password)";

private const string PARM_USER_ID = "@UserId";

private const string PARM_PASSWORD = "@Password";

来定义一个sql语句,以及声明其中可变参数,然后像下面这样用SqlHelper类的合适的方法执行:

SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_SIGNON, signOnParms);

最后在SQLHelper.ExecuteNonQuery实现中,再调用ado.net中的相关类最终执行对数据库的操作,可见SqlHelper在这里又封装了一下ado.net相关类以优化数据操作。正如SqlHelper.cs中注释提示:The SqlHelper class is intended to encapsulate high performance, scalable best practices for common uses of SqlClient.下面是SqlHelper. ExecuteNonQuery的实现内容:

public static int ExecuteNonQuery(string connString, CommandType cmdType, string cmdText, params SqlParameter[] cmdParms) {

//注:运行时cmdText的实参就是SQL_INSERT_SIGNON

SqlCommand cmd = new SqlCommand();

using (SqlConnection conn = new SqlConnection(connString)) {

PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);

int val = cmd.ExecuteNonQuery();

cmd.Parameters.Clear();

return val;

}

}

另外Inventory和Order,Product,Profile和Account类的声明都是像public class Account : IAccount这样实现某个相关的接口,像IAccount这样的接口是在PetShop.IDAL中声明的,见后面介绍。

3 PetShop.OracleDAL ―――系统结构图中 DAL层的OracleDAL子层实现

<shape id="_x0000_i1029" style="WIDTH: 129pt; HEIGHT: 121.5pt" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-20008536.png"></shape>

个人认为结构应该同上面的PetShop. SQLServerDAL,另外SqlHelper变成了OraHelper,在OraHelper中当然具体实现了对特定的Oracle数据库的联接操作,看一下源程序很明显原来的 SqlCommand cmd = new SqlCommand(); 变成了OracleCommand cmd = new OracleCommand();。

注意一下:在系统结构图中的DAL层还有两个XXX DAAB的子层,它们对应的实现在哪里呢? 下面对应一下:

以下是左边是图中 DataAccessLayer的各部分,右边是具体实现所在名字空间或类

SqlServer DAL――PetShop.SQLServerDAL名字空间

Sql DAAB――PetShop.SqlServerDal.SqlHelper类

Oracle DAL――PetShop.OracleDAL名字空间

Oracle DAAB――PetShop.OracleDAL.OraHelper类

4 PetShop.IDAL 数据访问接口――对应系统结构图中DAL Interface

<shape id="_x0000_i1030" style="WIDTH: 122.25pt; HEIGHT: 110.25pt" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-20008538.png"></shape>

接口是一种系列‘功能’的声明或名单,接口没有实现细节,如下接口IAccount定义也可以看出IAccount只有声明:

using System;

using PetShop.Model;

namespace PetShop.IDAL

{

// Inteface for the Account DAL

public interface IAccount

{

// Authenticate a user

AccountInfo SignIn(string userId, string password);

/// Get a user's address stored in the database

AddressInfo GetAddress(string userId);

/// Insert an account into the database

void Insert(AccountInfo account);

/// Update an account in the database

void Update(AccountInfo Account);

}

}

您只需要调用接口,而不用管接口是如何实现的那么接口没有实现,调用它有什么用?实际上接口的实现是由某个类来做的,那么这里的IAccount接口是由PetShop.SqlServerDAL.Account类或是PetShop.OracleDAL.Account类来实现的,从他们的定义可以看到:

public class Account : IAccount {…….}

为什么是两个类都实现同一接口又是‘或’呢?因为这里使用接口的目的就是为了统一‘外观’,当上层BLL层调用此接口方法时不用知道这个接口由哪个类实现的。那谁来确定使用哪个类的实现?请再看下面。

PetShop.IDAL下的其它接口和IAccount一样,故在此略过。)

5 PetShop.DALFactory 数据访问工厂

<shape id="_x0000_i1026" style="WIDTH: 120pt; HEIGHT: 107.25pt" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-200085310.png"></shape>

工厂模式是设计模式的一种,以我理解就像Factory这个词一样,对于用户来说,工厂里产品如何生产的你不用知道,你只要去用工厂里生产出来的东西就可以了。MSPetShop3.0用工厂模式来实现了对SqlServer和Oracle数据库访问的操作,而用户(business Logic Layer)不用知道也不用关心后台用的是哪一种数据库,它只要用接口就行了,接口中定义了要用的方法,当调用接口时会根据具体的情况再去调用底层数据访问操作。而现在这个DALFactory就是关键,当BLL层要操作数据库时,DALFactory会根据具体情况再去使用本文上面介绍的SqlServerDAL和OracleDAL中的一个。这样系统上层只管调用,而下层来实现细节,上级只管发号施令,下级去干活。对于上层来说实现细节被隐藏了。

那么DALFactory是如何决定应该用SqlServerDAL还是用OracleDAL的呢?我们接着分析。

以下是PetShop.DALFactory.Account类的实现:

namespace PetShop.DALFactory {

/// <summary>

/// Factory implementaion for the Account DAL object

/// </summary>

public class Account

{

public static PetShop.IDAL.IAccount Create() //<<<<ß----这里返回接口

{

/// Look up the DAL implementation we should be using

string path = System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];

string className = path + ".Account";

// Using the evidence given in the config file load the appropriate assembly and class

return (PetShop.IDAL.IAccount) Assembly.Load(path).CreateInstance(className);

}

}

}

以下则是web.config中<appSettings>节点中的一部分

<add key="WebDAL" value="PetShop.SQLServerDAL" />

<add key="OrdersDAL" value="PetShop.SQLServerDAL" />

<add key="Event Log Source" value=".NET Pet Shop" />

上面的Create()方法返回IAccount接口,用System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];则可以得到Web.config的<appsettings>节点中的关于系统中应该使用哪个数据访问层(SqlserverDAL还是OracleDAL)的信息。因为我在安装PetShop3.0时选择的是Sqlserver所以在此是:value="PetShop.SQLServerDAL",如果用的是Oracle那就是value="PetShop.OracleDAL" 了吧!而且这个文件也应该是可以更改的。接下来className=path+.Account返回的应该是PetShop.SQLServerDAL.Account,然后再用Assembly.Load加载PetShop.SQLServerDAL.dll,同时创建PetShop.SQLServerDAL.Account的实例,并以接口(PetShop.IDAL.IAccount)类型返回。这样BLL调用IAccount接口时就会用PetShop.SQLServerDAL.Account类的实现代码。(回上面第4再看一下)

看!这样根据系统当前Web.config文件的配置描述(这也应该是系统运行时实际的配置),BLL层只要像下面这样:

// Get an instance of the account DAL using the DALFactory

IAccount dal = PetShop.DALFactory.Account.Create();

AccountInfo account = dal.SignIn(userId, password);//<<ß----看看上面第4点的IAccount接口

就可以直接调用接口方法通过下层DAL层操作数据库了(在此具体为用户账号相关操作),而BLL层并不用知道应该通过SqlserverDAL还是OracleDAL访问数据库,这由都DAL Factory决定,你用的是什么数据库以及底层细节,更不用BLL知道,这样做的好处是对于BLL层以及更上层的程序不会或很少机率会因为底层程序变动影响,因为BLL层中调用接口就行了,只要那个接口定义没变,一切仍然OK.

6 PetShop.ConfigTool

<shape id="_x0000_i1031" style="WIDTH: 167.25pt; HEIGHT: 1in" type="#_x0000_t75" o:ole=""><img alt="" o:title="" src="http://www.csdn.net/Develop/ArticleImages/21/21158/CSDN_Dev_Image_2003-9-200085312.png"></shape>

首先在..\Microsoft\PetShop\ConfigTool\中有一个app.config文件,看一下其中内容,分别定义了两种数据库的联接字符串,在app.config中有一行 <add key="WebConfigFileLocation" value="Web\Web.config" /> 则标识出给asp.net程序使用的web.config配置文件的相对位置。然后看一下PetShopConnectionString的EncryptConnectionString方法的源码,这个类中先是从当前目录的app.config文件中读出web.config文件的位置,如下:

public static readonly string CONFIGFILE = ConfigurationSettings.AppSettings["WebConfigFileLocation"];

然后语句

XmlDocument doc = new XmlDocument();

doc.Load(CONFIGFILE);

加载Web.config文件,最后将加密的连接字符串写入Web.config对应的XML节点中。以供Asp.net应用程序使用。其中的加密还是使用PetShop.Utility。

ConfigConsole,调用PetShopConnectionString类EncryptConnectionString执行对联接字符串进行加密。另外PetShopEventLog类也是在ConfigConsole中使用的。用于记录程序日志。

所以在最后部署时Web.config的连接字符串是加密的。

7 PetShop.Model 业务实体模型

<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype>

http://www.surfsky.com/bbs/myfiles/7.bmp

这个本来想在分析BLL层时再说,但是在SqlServerDALOracleDAL中都使用了这些Model,无论怎么样,上层的程序执行最终结果都是要操作数据库,而数据库是关系型,不是面向对象的,那就得把平面的‘表’结合业务规则抽象成类,这样想办法让上层(BLL及以上)以为自已在操作类而不是数据库表,从而使‘它们’感觉没有数据库的存在,上层只管面向对象编程就可以了。类似现在所说的O-R MAPPING,但O-R MAPPING比这种简单的数据到对象的持久化要复杂。据说可以在表结构有变化的情况下,上层应用程序代码不用更改,只要改O-R MAPPING的相关设置就可以了。

上面第2节中已看到Petshop的SqlServerDal和OracleDal中定义的sql语句,然后根据上层的调用,把sql语句传给SqlHelper执行,再来看看SqlServerDal的一段程序:

private void SetAccountParameters(SqlParameter[] parms, AccountInfo acc) {

parms[0].Value = acc.Email;

parms[1].Value = acc.Address.FirstName;

parms[2].Value = acc.Address.LastName;

parms[3].Value = acc.Address.Address1;

parms[4].Value = acc.Address.Address2;

parms[5].Value = acc.Address.City;

parms[6].Value = acc.Address.State;

parms[7].Value = acc.Address.Zip;

parms[8].Value = acc.Address.Country;

parms[9].Value = acc.Address.Phone;

parms[10].Value = acc.UserId;

}

parms[x]就是那些有声明为常量的有参数的Sql语句中的参数,这里用Model中的AccountInfo的各Field和这些参数Mapping。所以对于BLL层,只知道这些Model就可以了,反正最后在SqlServerDAL或是OracleDALModel的成员们会Mapping到参数中以存取数据库(为什么不直接Mapping到数据集的字段呢?我想应该是因为这里数据库操作直接给SqlHeper的原因,不然就没必要用SqlHelper了。于是才又多了图中的xxx DAAB层,这样Mapping给参数再传给DAAB来处理)

Data Access Layer总结:

DAL完成数据库访问任务,上层(BLL)直需调用接口即可,不用知道具体访问细节,用Factory模式来实现,使用运行时读取web.config的方法来得到连接配置信息,最后选用SqlServerDAL或OracleDAL之一的相对具体子层,同时使用SqlHelper或OraHelper之一来完成数据库操作。

分享到:
评论

相关推荐

    petshop3.0的资料

    4. **三层架构**:PetShop 3.0采用了业务逻辑层(Business Logic Layer, BLL)、数据访问层(Data Access Layer, DAL)和表示层(Presentation Layer)的三层架构设计,这种设计模式有利于代码的重用和模块化,是Web...

    Winform的petshop3.0的开发模式

    1. **分层架构**:PetShop3.0采用了常见的三层架构,包括表现层(Presentation Layer)、业务逻辑层(Business Logic Layer, BLL)和数据访问层(Data Access Layer, DAL)。这种架构有助于代码的组织和维护,提高了...

    微软PetShop合集(2.0,3.0,4.0,5.0)

    它使用了三层架构,即表示层、业务逻辑层和数据访问层,为开发者提供了理解Web应用程序架构的良好起点。 2. PetShop 3.0:随着.NET Framework 2.0的发布,PetShop 3.0引入了ASP.NET 2.0的新特性,如母版页、主题、...

    .Net petshop 4.0+文档

    《.Net PetShop 4.0:微软分层架构的典范》 .Net PetShop 4.0 是微软推出的一个开源示例项目,旨在演示如何利用 .NET Framework 构建一个完整的、分层的企业级应用程序。这个项目对于初学者,尤其是那些在IT行业中...

    PetShop全版本(2.0-5.0)

    此外,还引入了ADO.NET进行数据库操作,展示了如何构建三层架构的应用程序。 2. PetShop 3.0: 随着ASP.NET 2.0的发布,PetShop 3.0引入了更多的改进和新特性。这一版加强了数据访问层,使用了Entity Framework的...

    C#三层结构图个人网络收集

    7. **经典三层结构**:经典的三层架构通常包括表示层(UI)、业务逻辑层( BLL )和数据访问层(DAL)。表示层负责用户交互,业务逻辑层处理业务规则,数据访问层则负责与数据库的交互。 8. **微软Duwamish7**:...

    PetShop的系统架构设计

    PetShop在后续版本中对此进行了优化,例如PetShop2.0没有严格区分数据访问层,导致职责混乱,而PetShop3.0和4.0则重新明确了分层,引入了缓存和异步处理以改善性能。 在.NET中的标准BS(Browser-Server)分层结构中...

    petshop 架构 登录界面

    这种架构通常包括三个主要部分:表现层(Presentation Layer)、业务逻辑层(Business Logic Layer)和数据访问层(Data Access Layer)。表现层负责与用户交互,处理用户请求并显示结果。业务逻辑层处理应用程序的...

    Petshop4.0系统架构设计

    Petshop4.0采用了典型的三层架构模式:数据访问层、业务逻辑层(或称领域层)以及表示层。每一层都有其特定的功能和职责范围,这种分层架构能够帮助开发者更好地组织代码,确保各部分之间的松散耦合,从而提高系统的...

    PetShop 4.0详细分析.doc

    PetShop 2.0的架构并不完全遵循这个标准,其数据访问层并不明显,可能更倾向于混合式设计。 随着PetShop版本的迭代,架构的演变反映了设计原则的优化和调整。理解这些变化可以帮助我们更好地适应技术发展,提升软件...

    Petshop图解

    在 PetShop 的架构中,主要分为三层:数据访问层、业务逻辑层和表示层。 **数据访问层**(DAL,Data Access Layer)是系统的基础,负责与数据库的交互。它实现了对数据表的基本 CRUD 操作(Create、Read、Update、...

    PetShop 架构

    在【系统架构设计】方面,PetShop采用的是典型的"三层"架构,这包括数据访问层(DAL)、业务逻辑层(BLL,也称为领域层)和表示层。这种分层设计有几个关键优点: 1. **结构清晰**:将系统功能划分为不同的层,使得...

    petshop4.0详解

    PetShop的演变历程显示了架构的改进,如PetShop 2.0的非典型三层结构,以及PetShop 3.0和4.0引入了更清晰的数据访问层和优化性能的策略,如缓存和异步处理。 在PetShop 4.0中,`DAL Interface`抽象了数据访问逻辑,...

    PetShop_3.0_&amp;_4.0_&amp;_5.0 源码合集及详解

    PetShop是一个范例,微软用它来展示.Net企业系统开发的能力。 在软件体系架构设计中,分层式结构是最常见,...微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或成为领域层)、表示层.

    Petshop4.doc

    Petshop4的架构设计细致周全,涵盖了表现层、模型层、业务逻辑层和数据访问层等多个层面,形成了一个完整而复杂的项目体系。其中,各个项目模块紧密相连,共同构建出一个高效稳定的企业级应用。 1. **WEB**:表示层...

    Microsoft .NET Pet Shop 3.x_ .NET Pet Shop 的设计模式与体系结构.docx

    2.0版本的体系结构采用了传统的三层架构,包括表示层(Web UI)、业务逻辑层(BLL)和数据访问层(DAL)。每个层都负责特定的任务,如UI处理用户交互,BLL执行业务规则,而DAL处理数据操作。 6. **.NET Pet Shop ...

    Microsoft .NET Pet Shop 5.0 最新源码+详解

    Entity Framework提供了概念模型,可以映射到数据库模式,简化了数据访问层的编写,降低了数据库依赖,提升了代码的可维护性。 四、ASP.NET MVC 虽然Pet Shop 5.0的具体实现没有明确指出是否使用了ASP.NET MVC,但...

    ASP.NET课件001

    学生可以访问多个官方和第三方网站获取相关资料和示例代码,如Microsoft的官方网站(gotdotnet.com、msdn.microsoft.com)、ASP.NET官方社区(asp.net)以及各种示例项目,例如Duwamish 7.0(网上图书销售)、Fitch ...

    Net Pet Shop 3详解

    开发语言采用ASP.NET,编程语言为C#,数据访问层使用ADO.NET,同时利用Web窗体、Web服务、服务器控件、用户控件和自定义控件构建UI。CSS用于样式控制,物理工程分层设计增强了代码组织,数据库迁移和加密配置文件...

Global site tag (gtag.js) - Google Analytics