• NET Core3前后端分离开发框架


    NET Core前后端分离快速开发框架

    https://www.cnblogs.com/coldairarrow/p/11870993.html

    引言

    时间真快,转眼今年又要过去了。回想今年,依次开源发布了Colder.Fx.Net.AdminLTE(254Star)Colder.Fx.Core.AdminLTE(335Star)DotNettySocket(82Star)IdHelper(47Star),这些框架及组件都是本着以实际出发,实事求是的态度,力求提高开发效率(我自己都是第一个使用者),目前来看反响不错。但是随着前端和后端技术的不断变革,尤其是前端,目前大环境已经是前后端完全分离为主的开发模式,在这样的大环境和必然趋势之下,传统的MVC就显得有些落伍了。在这样的背景下,一款前后端分离的.NET开发框架就显得尤为必要,由此便定了框架的升级目标:前后端分离

    首先后端技术的选择,从目前的数据来看,.NET Core的发展远远快于.NET Framework,最简单的分析就是Colder.Fx.Core.AdminLTE发布比Colder.Fx.Net.AdminLTE晚,但是星星却后来居上而且比前者多30%,并且这个差距在不断扩大,由点及面的分析可以看出我们广大.NET开发人员学习的热情和积极向上的态度,并不是某些人所认为的那么不堪(走自己的路,让别人说去吧)。大环境上微软积极拥抱开源,大力发展.NET Core, 可以说前途一片光明。因此后端决定采用 .NET Core3.0 ,不再浪费精力去支持.NET Framework。

    然后是前端技术选择,首选是三大js框架选择,也是从实际出发,Vue相对其它而言更加容易上手,并且功能也毫不逊色,深得各种大小公司喜欢,如果偏要说缺点的话,那就是对TS支持不行,但是即将发布Vue3.0肯定会改变这一缺陷。选择了Vue之后,然后就是UI框架的选择了,这里的选择更多了,我选择了Ant Design Vue,理由便是简洁方便,十分符合我的设计理念。

    技术选型完毕之后便进入研发,由于鄙人前端比较菜,因此需要从头学Vue2.x全家桶,从开始到现在差不多经历3个月,在预期之内。其实学习并使用前端的Vue2.x全家桶并不难,还是比较容易上手的,所以在此给没有前后端分离开发经验的老哥打一记预防针,不要退缩,要知难而上,学习永无止境。

    某些老哥可能比较直接粗暴,嫌我BBB唠叨,下面直接上地址
    代码(GitHub):https://github.com/Coldairarrow/Colder.Admin.AntdVue
    文档(GitHub):https://github.com/Coldairarrow/Colder.Admin.AntdVue/wiki
    代码(码云镜像):https://gitee.com/Coldairarrow/Colder.Admin.AntdVue
    文档(码云镜像):https://gitee.com/Coldairarrow/Colder.Admin.AntdVue/wikis
    在线预览地址http://coldairarrow.gitee.io/coler.antdvue.preview/#/Home/Introduce
    由于服务器是阿里云的86服务器,带宽1M小水管,因此将前端部署到码云上了,在此多谢码云,后端部署在阿里云86服务器CentOS7上。整个技术栈使用.NET Core + PostggreSQL+ Ant Design Vue + CentOS7+Nginx+Dokcer+jenkins,囊括了从快速开发到自动化部署一条龙,开源免费并具有高性能、高移植性、高拓展性(小公司创业选型+个人接单利器

    简介

    本框架为.NET Core3.0+Ant Design Vue版

    本框架旨在极大的提高开发效率

    使用技术栈:
    后端:采用.NET Core平台,ASP.NET Core3.0,C#语言(使用反射等高级技术),Entity FrameworkCore(数据库ORM框架)。
    使用数据仓储模式,抽象化数据库操作(CRUD等)、支持事务处理以及分布式事务处理(跨库)
    支持数据库读写分离、分库分表及事务(仅支持单表操作,不支持多表) 全面采用Autofac作为IOC容器,面向接口编程,全面解耦
    集成多种工具类库以及操作拓展
    数据库:支持SqlServer,PostgreSQL,MySQL,Oracle(框架使用简单工厂,工厂方法,抽象工厂,可轻松更换数据库),Redis作为分布式缓存
    前端:Vue2.x全家桶+Ant Design Vue,其中集成常用组件,力求方便项目开发。

    具体技术实施:
    项目采用前后端完全分离模式,并采用严格分层模式,极大的增加聚合度,降低耦合度,
    提高代码的健壮性,可维护性。
    前后端通过JWT进行身份验证,通过数据接口操作数据,统一使用JSON作为数据格式,并使用默认接口签名算法保证接口的安全性。

    功能架构部分详解:
    快速开发:此功能为框架的核心,通过选择数据库中的表,就能自动生成对应的实体层、业务逻辑层、控制器、前端页面Vue文件,无需编写代码即可生成基本的CRUD操作。
    接口密钥管理:管理接口的密钥,若开启接口签名的规则,则前端需要通过给接口签名才能够正常访问后台接口。
    权限管理:使用基本的RBAC权限控制,支持操作权限、接口权限以及数据权限

    框架主要功能及特色如下

    功能详细描述
    用户登录 用户登录、密码修改
    系统用户管理 系统用户管理
    角1色管理 角色管理
    部门管理 部门管理、天然多级
    权限管理 使用RBAC权限体系,基于角色的权限管理,支持菜单权限、操作权限(按钮权限)、接口权限、数据权限
    系统日志 支持多彩控制台、文件、数据库和ElasticSearch记录日志,框架全局异常自动捕捉,多级别记录
    快速开发 通过数据库直接生成实体层、业务逻辑层、控制器层以及前端页面Vue代码,无需编码即可实现CURD
    数据库操作封装 使用基于EF的仓储模式、封装常用的CURD,支持单库事物和分布式事物
    多数据库支持 使用基于EF的仓储模式,支持各大主流关系型数据库(SQLServer、MySQL、PostgreSQL和Oracle)
    超强移植性 使用抽象工厂封装数据库操作层,切换数据库0代码修改
    支持软删除 一键切换删除模式,支持物理删除和软删除,对业务操作透明
    缓存支持 支持系统自带缓存和Redis缓存、封装操作接口、简单易用
    前后端完全分离 前端使用Vue2.x全家桶+Ant Design Vue,界面简洁美观,组件化开发
    集成JWT验证 框架使用JWT作为身份验证,摆脱Cookie苦海,分布式拓展毫无压力
    集成对外接口签名算法 框架对外接口可以开启超强严格签名校验(防抵赖、防伪造、防重复调用),保障系统安全性
    页面响应式 前端为单页应用,无iframe,各大主流浏览器支持友好
    拓展 其它各种帮助类库及插件

    其相关版本请看下表:

    .NET版本前端UI地址
    Core3.0 Ant Design Vue https://github.com/Coldairarrow/Colder.Admin.AntdVue
    Core2.2 AdminLTE https://github.com/Coldairarrow/Colder.Fx.Core.AdminLTE
    .NET4.72 AdminLTE https://github.com/Coldairarrow/Colder.Fx.Net.AdminLTE
    .NET4.52 Easyui https://github.com/Coldairarrow/Coldairarrow.Fx.Net.Easyui.GitHub
    Core2.1 Easyui https://github.com/Coldairarrow/Coldairarrow.Fx.Core.Easyui.GitHub
    .NET4.0 Easyui https://github.com/Coldairarrow/Coldairarrow.Fx.Net40.Easyui.GitHub

    后台效果展示如下:
    MG22Bd.png

    环境搭建

    开发环境要求:

    操作系统:Windows 10
    后端开发工具:Visual Studio 2019+
    前端开发工具:Visual Studio Code,安装nodejs,yarn
    SDK:安装.NET Core SKD 3.0 及以上
    数据库:SQLServer2008 R2及以上

    基础数据库构建:

    • 数据库创建
      目录"/docs/初始化文件"中有所需的数据库资料
      MGo70U.png
      先手动创建数据库,然后执行对应的SQL脚本即可

    • 连接字符串配置
      打开src目录下Colder.Admin.AntdVue的解决方案,如下图

    MGTuB8.png

    如下图所示依次展开05.Coldairarrow.Api/appsettings.json,配置数据库连接字符串,name不用修改,connectionString改为上述创建的数据库(若不清楚数据库连接字符串请自行百度搜索教程)

    数据库设计规范

    由于本框架支持自动生成代码的核心功能,此功能是根据数据库的表结构来生成代码的
    因此规定每张表都有一个主键,列名为Id,类型为字符串,实际添加数据时默认使用SnowflakeId(雪花Id,Twitter设计的分布式趋势自增Id,若不清楚请自行百度相关资料),表中的每个列都需要有描述信息(建议这样操作,若不按照这个标准则需要一些额外的改动才能够成功运行)。每张表都需要Id、CreateTime、CreatorId、CreatorRealName这几个必带字段

    MG7p2n.png

    运行

    后端:打开解决方案=>还原nuget包=>配置数据库=>运行(05.Coldairarrow.Api设为启动项目)
    后台成功运行后会自动打开swagger
    MGHcTO.png
    前端: 确保已安装nodejsyarn
    用VS Code 打开文件夹srcColdairarrow.Web
    MGHEOP.png
    输入命令:yarn
    输入命令:yarn run serve
    成功运行后即可打开登录页面
    MGH56I.png
    输入账号:Admin,密码:123456进入后台主页
    MG22Bd.png

    使用教程

    全局配置

    在01.Coldairarrow.Util中的GlobalSwitch类中,设置了各个参数,其中RunModelDatabaseType需要重点关注一下,其它参数请看注释。

    快速开发

    此功能为本框架的核心功能,能够自动生产完整的可运行代码,具体使用如下:

    • 配置数据源

    首选需要有数据库源,因为代码生成是根据数据库表来生成的。
    菜单:开发=>数据库连接
    MYpg8P.png
    若列表中没有目标数据源,则添加数据库连接
    MYphDg.png
    数据连接名、连接字符串、数据库类型即可。添加完成后即可看到连接字符串信息。

    • 生成代码

    有了数据库连接之后,即可进行代码生成。
    菜单:开发=>代码生成

    MYpb80.png

    选择数据库,然后勾选需要生成代码的数据库表,点击生成代码会弹出生成选项(这里演示勾选 Base_BuildTest,其余表全是系统基础表,不要勾选,否则会被覆盖,导致异常,请勾选自己的业务表进行生成):

    MYi3M6.png

    生成选项中可以选择需要生成的类型,默认生成全部:实体层、业务层、接口层(即控制器)和页面(Vue前端页面代码)。
    生成区域请输入业务模块名(例如系统叫Base_Manage),请按具体业务填入(必填)
    这里示例填写TestManage,点击生成按钮,即可完成代码生成。生成后的代码在项目解决方案中,将代码文件包括进入项目

    默认生成后的文件会被自动包括到解决方案中,若没有则需要点击显示所有文件按钮,即可看到生成后的新文件

    生成的实体层、业务逻辑层、控制器层代码:
    MYiWJs.png
    MYFAfA.png
    前端生成的代码:
    MYiXWR.png

    后端代码添加后需要重新编译下(F7),编译后好可以看到有新的接口
    MYQYcD.png
    前端生成代码后会自动保存并编译(别的文件修改保存也会自动编译,毕竟编译一次挺慢的),不放心可以Ctrl+C停止,然后再yarn run serve重新运行

    代码生成完毕,但是要展示到菜单上需要进行配置,并且所有业务菜单都是动态的(权限控制)
    打开菜单:系统管理=>权限管理=>新建
    MYPsUJ.png
    MYFUmT.png
    菜单名:即需要显示的菜单名
    上级菜单:菜单是多级树状的,可以放在某个菜单下面
    类型:可以选择菜单或页面,这里是具体的页面,所以选页面
    路径:页面的路径,菜单可以不配置,这里配置为/TestManage/Base_BuildTest/List,这里设置规则为:views文件夹为根路径(即"/"),向下展开依次为/TestManage/Base_BuildTest/List,最后真正的列表页为List.vue文件,表单页为EditForm.vue文件
    是否需要权限:若为“否”,则此页面不限制权限,即所有人都能看
    图标:菜单显示图标,具体到开发=>图标选择页进行选择
    排序:若需要需要对菜单进行排序则设置,默认0,类型为int
    页面权限:页面拥有的权限(权限值唯一,作为操作权限即接口权限的依据),详见权限管理

    确认保存,然后刷新整个页面(F5),即可看到全新生成的菜单“生成测试”
    MYkvqK.png
    MYAFxI.png
    MYAVqf.png

    整个代码生成过程,无需编写代码即可完成一张表的CRUD,当然需要根据具体业务中进行相应的修改,本次示例中字段比较少,但是当一张表的字段很多时,那么此功能能够将开发效率提高几个档次。

    管理员登录

    MGH56I.png
    默认超级管理员账号为:Admin
    密码为:123456

    系统用户管理

    管理系统登录的用户
    菜单:系统=>用户管理
    点击右侧设置权限

    系统角色管理

    管理系统角色,角色是权限的载体,合理分配角色有利于权限管理
    菜单:系统=>角色管理
    操作中可以设置角色的权限,详情见<权限管理>模块

    权限管理

    一般情况下,后台管理系统多少会涉及权限管理,因此本框架提供了一个灵活、高效、简洁的权限管理系统。

    首先,权限分为两种,即操作权限数据权限,其中操作权限主要包括菜单权限和按钮权限,在角色编辑表单中,给角色勾选需要的权限即可,用户可以分配多个角色,继承所有角色的权限。
    MYET39.png
    如上图,按需勾选,有人会疑问为什么只有增、改和删的权限而没有查的权限,其实这里考虑到了当勾选“用户管理”时,就已经默认拥有“查”的权限了,这里再设置“查”权限岂不是多此一举,可以根据实际业务需求添加诸如申请、审核等权限,灵活应用。
    这里的树状是和菜单一一对应的,勾选哪些菜单就拥有哪些菜单

    下面介绍下最常用的按钮权限
    MYVTIS.png

    如上图,在需要控制权限的按钮使用v-if="hasPerm('Base_User.Add')",表示只有当用户拥有权限值Base_User.Add才显示此按钮,权限值就是权限表单中配置的权限值

    这里的按钮级权限控制只能够在前端控制操作入口,若系统对安全性要求较高,则需要控制接口权限
    MYZNo8.png

    如上图,在需要权限控制的接口加上ApiPermission特性即可,参数也为权限值,只有拥有此权限才能访问此接口

    数据权限比较复杂,若采用纯SQL方式,那么会更加复杂,本框架全程采用EF作为ORM框架,通过对IQueryable< T >进行过滤,即可完成数据权限控制,详细使用方式见用户管理

    更详细的使用方式,请参考源代码中的用户管理模块(菜单权限、操作权限、数据权限、联表查询、业务层AOP等使用方式均可参考此模块)

    接口秘钥管理

    作为对外接口签名的AppId和AppSecret管理
    菜单:系统=>接口秘钥管理

    系统日志

    菜单:系统=>系统日志

    单库事务

    使用方式如下:

    BaseBusiness<Base_UnitTest> _baseBus  = new BaseBusiness<Base_UnitTest>();
    /*RunTransaction传入执行具体数据库操作的Action,操作中若出现异常会自动回滚事务,业务只需根据返回的transActionRes进行处理,返回类型为元组(bool Success, Exception ex),Success表示事务是否成功,ex为事务失败异常信息*/
    var transActionRes = _baseBus.RunTransaction(() =>
    {
        var newData = _newData.DeepClone();
        newData.Id = Guid.NewGuid().ToString();
        newData.UserId = IdHelper.GetId();
        newData.UserName = IdHelper.GetId();
        _baseBus.Insert(_newData);
        _baseBus.Insert(newData);
    });

    跨库事务

    使用方式如下:

    //第一个数据库
    IRepository _bus1 = DbFactory.GetRepository();
    //另一个数据库
    IRepository _bus2 = DbFactory.GetRepository("BaseDb_Test");
    _bus1.DeleteAll<Base_UnitTest>();
    _bus2.DeleteAll<Base_UnitTest>();
    Base_UnitTest data1 = new Base_UnitTest
    {
        Id = Guid.NewGuid().ToString(),
        UserId = "1",
        UserName = Guid.NewGuid().ToString()
    };
    Base_UnitTest data2 = new Base_UnitTest
    {
        Id = data1.Id,
        UserId = "1",
        UserName = Guid.NewGuid().ToString()
    };
    Base_UnitTest data3 = new Base_UnitTest
    {
        Id = Guid.NewGuid().ToString(),
        UserId = "2",
        UserName = Guid.NewGuid().ToString()
    };
    
    new Action(() =>
    {
        //创建并执行事务,使用方式与单库一致
        var succcess = DistributedTransactionFactory.GetDistributedTransaction(_bus1, _bus2)
            .RunTransaction(() =>
            {
                _bus1.ExecuteSql("insert into Base_UnitTest(Id) values('10') ");
                _bus1.Insert(data1);
                _bus1.Insert(data2);
                _bus2.Insert(data1);
                _bus2.Insert(data3);
            });
        Assert.AreEqual(succcess.Success, false);
        Assert.AreEqual(_bus1.GetIQueryable<Base_UnitTest>().Count(), 0);
        Assert.AreEqual(_bus2.GetIQueryable<Base_UnitTest>().Count(), 0);
    })();
    

    读写分离分库分表

    本框架支持数据库读写分离分库分表(即Sharding),并且支持主流关系型数据库(SQLServer、Oracle、MySQL、PostgreSQL),理论上只要EF支持那么本框架支持。
    由于技术原因以及结合实际情况,目前本框架仅支持单表的Sharding,即支持单表的CRUD、分页、统计(数量、最大值、最小值、平均值),支持跨库(表分散在不同的数据库中,不同类型数据库也支持)。具体如何使用如下:

    • Sharding配置
      首先、要进行分库分表操作,那么必要的配置必不可少。配置代码如下:
    ShardingConfigBootstrapper.Bootstrap()
        //添加数据源
        .AddDataSource("BaseDb", DatabaseType.SqlServer, dbBuilder =>
        {
            //添加物理数据库
            dbBuilder.AddPhsicDb("BaseDb", ReadWriteType.ReadAndWrite);
        })
        //添加抽象数据库
        .AddAbsDb("BaseDb", absTableBuilder =>
        {
            //添加抽象数据表
            absTableBuilder.AddAbsTable("Base_UnitTest", tableBuilder =>
            {
                //添加物理数据表
                tableBuilder.AddPhsicTable("Base_UnitTest_0", "BaseDb");
                tableBuilder.AddPhsicTable("Base_UnitTest_1", "BaseDb");
                tableBuilder.AddPhsicTable("Base_UnitTest_2", "BaseDb");
            }, new ModShardingRule("Base_UnitTest", "Id", 3));
        });

    上述代码中完成了Sharding的配置:
    ShardingConfigBootstrapper.Bootstrap()在一个项目中只能执行一次,所以建议放到Application_Start中(ASP.NET Core中的Startup)
    AddDataSource是指添加数据源,数据源可以看做抽象数据库,一个数据源包含了一组同类型的物理数据库,即实际的数据库。一个数据源至少包含一个物理数据库,多个物理数据库需要开启主从复制或主主复制,通过ReadWriteType(写、读、写和读)参数来指定数据库的操作类型,通常将写库作为主库,读库作为从库。同一个数据源中的物理数据库类型相同,表结构也相同。
    配置好数据源后就可以通过AddAbsDb来添加抽象数据库,抽象数据库中需要添加抽象数据表。如上抽象表Base_UnitTest对应的物理表就是Base_UnitTest_0、Base_UnitTest_1与Base_UnitTest_2,并且这三张表都属于数据源BaseDb。分表配置当然需要分表规则(即通过一种规则找到具体数据在哪张表中)。
    上述代码中使用了最简单的取模分片规则
    源码如下:

    可以看到其使用方式及优缺点。
    另外还有一致性HASH分片规则

    雪花Id的mod分片规则

    上述的分片规则各有优劣,都实现IShardingRule接口,实际上只需要实现FindTable方法即可实现自定义分片规则。
    实际使用中个人推荐使用雪花Id的mod分片规,这也是为什么前面数据库设计规范中默认使用雪花Id作为数据库主键的原因(PS,之前版本使用GUID作为主键被各种嫌弃,这次看你们怎么说)

    • 使用方式
      配置完成,下面开始使用,使用方式非常简单,与平常使用基本一致
      首先获取分片仓储接口IShardingRepository
    IShardingRepository _db = DbFactory.GetRepository().ToSharding();

    然后即可进行数据操作:

    Base_UnitTest _newData  = new Base_UnitTest
    {
        Id = Guid.NewGuid().ToString(),
        UserId = "Admin",
        UserName = "超级管理员",
        Age = 22
    };
    List<Base_UnitTest> _insertList = new List<Base_UnitTest>
    {
        new Base_UnitTest
        {
            Id = Guid.NewGuid().ToString(),
            UserId = "Admin1",
            UserName = "超级管理员1",
            Age = 22
        },
        new Base_UnitTest
        {
            Id = Guid.NewGuid().ToString(),
            UserId = "Admin2",
            UserName = "超级管理员2",
            Age = 22
        }
    };
    //添加单条数据
    _db.Insert(_newData);
    //添加多条数据
    _db.Insert(_insertList);
    //清空表
    _db.DeleteAll<Base_UnitTest>();
    //删除单条数据
    _db.Delete(_newData);
    //删除多条数据
    _db.Delete(_insertList);
    //删除指定数据
    _db.Delete<Base_UnitTest>(x => x.UserId == "Admin2");
    //更新单条数据
    _db.Update(_newData);
    //更新多条数据
    _db.Update(_insertList);
    //更新单条数据指定属性
    _db.UpdateAny(_newData, new List<string> { "UserName", "Age" });
    //更新多条数据指定属性
    _db.UpdateAny(_insertList, new List<string> { "UserName", "Age" });
    //更新指定条件数据
    _db.UpdateWhere<Base_UnitTest>(x => x.UserId == "Admin", x =>
    {
        x.UserId = "Admin2";
    });
    //GetList获取表的所有数据
    var list=_db.GetList<Base_UnitTest>();
    //GetIQPagination获取分页后的数据
    var list=_db.GetIShardingQueryable<Base_UnitTest>().GetPagination(pagination);
    //Max
    var max=_db.GetIShardingQueryable<Base_UnitTest>().Max(x => x.Age);
    //Min
    var min=_db.GetIShardingQueryable<Base_UnitTest>().Min(x => x.Age);
    //Average
    var min=_db.GetIShardingQueryable<Base_UnitTest>().Average(x => x.Age);
    //Count
    var min=_db.GetIShardingQueryable<Base_UnitTest>().Count();
    //事务,使用方式与普通事务一致
    bool succcess = _db.RunTransaction(() =>
    {
        _db.Insert(_newData);
        var newData2 = _newData.DeepClone();
        _db.Insert(newData2);
    }).Success;
    Assert.AreEqual(succcess, false);

    上述操作中表面上是操作Base_UnitTest表,实际上却在按照一定规则使用Base_UnitTest_0~2三张表,使分片对业务操作透明,极大提高开发效率
    具体使用方式请参考单元测试源码:
    "srcColdairarrow.UnitTestsDataRepositoryShardingTest.cs"

    常见疑问

    如何进行联表查询

    框架使用EF+LINQ进行联表操作,核心在于对IQueryable< T >的使用,另可网上搜EF+LINQ的相关教程。

    示例如下:
    Base_User表左连接Base_Department表获取DepartmentName属性

    //定义数据模型类
    public class Base_UserTestDTO : Base_User
    {
        public string DepartmentName { get; set; }
    }
    
    //即BaseBusiness中的Service
    var db = DbFactory.GetRepository();
    Expression<Func<Base_User, Base_Department, Base_UserTestDTO>> select = (a, b) => new Base_UserTestDTO
    {
        DepartmentName = b.Name
    };
    select = select.BuildExtendSelectExpre();
    var q = from a in db.GetIQueryable<Base_User>().AsExpandable()
            join b in db.GetIQueryable<Base_Department>() on a.DepartmentId equals b.Id into ab
            from b in ab.DefaultIfEmpty()
            select @select.Invoke(a, b);
    //筛选
    var where = LinqHelper.True<Base_UserTestDTO>();
    where = where.And(x => x.UserName == "Admin");
    
    //筛选并获取分页数据
    var list = q.Where(where).GetPagination(new Pagination()).ToList();

    源码可参考Base_UserBusiness.GetDataList

    如何切换数据库类型

    在01.Coldairarrow.Util项目中的GlobalSwitch,将DatabaseType改为需要的即可,对应的数据库连接字符串当然也要改为对应数据库的
    MYK1bR.png

    如何使用多个数据库

    在具体的Business类中重写父类BaseBusiness的构造函数即可,按照自己的需求重写对应的构造函数,同时需要确保数据库连接字符串已添加

    MYMFzD.png

    MYMVLd.png

    若需要同时使用多个数据库,或者需要多线程操作数据库,需要使用
    MYMsOJ.png

    结语

    欢迎使用本框架,若觉得不错,请比心

    Github欢迎星星:https://github.com/Coldairarrow

    博客园欢迎点赞:https://www.cnblogs.com/coldairarrow/

    QQ群2:579202910
    个人QQ:862520575(欢迎技术支持及商务合作,提供.NET Core + Linux + Nginx+ jenkins + git整套持续集成快速开发平台

  • 相关阅读:
    OA 办公自动化系统:权限管理模块的实现原理思路
    Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/hibernate/annotations/Entity
    Spring —— 三种配置数据源的方式:spring内置、c3p0、dbcp
    使用JdbcTemplate简化JDBC操作 实现数据库操作
    Spring aop——前置增强和后置增强 使用注解Aspect和非侵入式配置
    ( 解压缩版 免安装版 或 zip版 )如何修改mysql5.6.24 字符编码
    手机开发插件安装Xamarin
    JQuery对象转dom ,dom转jQuery
    读取XML
    【Android】完善Android学习(六:API 4.0)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/11895889.html
Copyright © 2020-2023  润新知