尝试从零开始构建我的商城 (一) :使用Abp vNext快速一个简单的商城项目
尝试从零开始构建我的商城 (一) :使用Abp vNext快速搭建一个简单的项目
前言
GitHub地址
此文目的
本文将尝试使用Abp vNext 搭建一个商城系统并尽可能从业务,架构相关方向优化升级项目结构,并会将每次升级所涉及的模块及特性以blog的方式展示出来。
代码实践
Abp vNext 使用 DDD 领域驱动设计 是非常方便的,但是由于本人认为自身没有足够的功力玩转DDD,所以开发中使用的是基于贫血模型的设计的开发,而不是遵照DDD的方式
Domain 领域层
1.添加引用
通过 Nuget 安装 Volo.Abp.Ddd.Domain
2.定义模块
创建 MyShopDomainModule.cs 作为Domain的模块,Domain不依赖任何外部模块,本体也没有什么相关配置,所以只需要继承AbpModule即可
namespace MyShop.Domain
{
    public class MyShopDomainModule:AbpModule
    {
    }
}
3.定义实体
BaseEntity 实体基类
定义BaseEntity并继承由Volo.Abp.Ddd.Domain提供的Entity并添加CreationTime属性
    public class BaseEntity :Entity<long>
    {
        public DateTime CreationTime { get; set; } = DateTime.Now;
    }
Product 商品
继承BaseEntity类 添加相关属性
   /// <summary>
    /// 商品信息
    /// </summary>
    public class Product :BaseEntity
    {
        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 分类id
        /// </summary>
        public long CategoryId { get; set; }
        /// <summary>
        /// 价格
        /// </summary>
        public decimal? Price { get; set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// 库存
        /// </summary>
        public decimal Stock { get; set; }
    }
Order 订单
继承BaseEntity类 添加相关属性
    /// <summary>
    /// 订单
    /// </summary>
    public class Order:BaseEntity
    {
        /// <summary>
        /// 订单号
        /// </summary>
        public Guid OrderNo { get; set; } = Guid.NewGuid();
        /// <summary>
        /// 用户
        /// </summary>
        public long UserId { get; set; }
        /// <summary>
        /// 订单状态
        /// </summary>
        public OrderStatus OrderStatus { get; set; }
        /// <summary>
        /// 总金额
        /// </summary>
        public decimal Total { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        public string Address { get; set; }
    }
    public enum OrderStatus
    {
        Created,
        Cancel,
        Paid,
    }
Application 应用层实现
1.添加引用
通过 Nuget 安装 Volo.Abp.Application
通过 Nuget 安装 Volo.Abp.AutoMapper
2.定义模块
创建 MyShopApplicationModule.cs ,继承自AbpModule,并且依赖Domain以及ApplicationContract层和后续使用的AutoMapper,所以对应DependsOn中需要添加对应依赖
    /// <summary>
    /// 项目模块依赖
    /// </summary>
    [DependsOn(typeof(MyShopApplicationContractModule),
        typeof(MyShopDomainModule))]
    /// 组件依赖
    [DependsOn(typeof(AbpAutoMapperModule))]
    public class MyShopApplicationModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            //添加ObjectMapper注入
            context.Services.AddAutoMapperObjectMapper<MyShopApplicationModule>();
            // Abp AutoMapper设置
            Configure<AbpAutoMapperOptions>(config =>
            {
                // 添加对应依赖关系Profile
                config.AddMaps<MyShopApplicationProfile>();
            });
        }
    }
3.实现相关服务
这里由于我们使用AutoMapper所以需要创建Profile并添加相关映射关系
Profile定义
namespace MyShop.Application.AutoMapper.Profiles
{
    public class MyShopApplicationProfile:Profile
    {
        public MyShopApplicationProfile()
        {
            CreateMap<Product, ProductItemDto>().ReverseMap();
            CreateMap<Order, OrderInfoDto>().ReverseMap();
        }
    }
}
服务实现
Abp 可以很方便的将我们的服务向外暴露,通过简单配置可以自动生成对应接口,只需要需要暴露的服务
继承ApplicationService。
Abp在确定服务方法的HTTP Method时使用命名约定:
Get: 如果方法名称以GetList,GetAll或Get开头.
Put: 如果方法名称以Put或Update开头.
Delete: 如果方法名称以Delete或Remove开头.
Post: 如果方法名称以Create,Add,Insert或Post开头.
Patch: 如果方法名称以Patch开头.
其他情况, Post 为 默认方式.
OrderApplicationService
public class OrderApplicationService : ApplicationService, IOrderApplicationService
    {
        private readonly IRepository<Order> _orderRepository;
        public OrderApplicationService(IRepository<Order> orderRepository)
        {
            _orderRepository = orderRepository;
        }
        public async Task<OrderInfoDto> GetAsync(long id)
        {
            var order = await _orderRepository.GetAsync(g => g.Id == id);
            return ObjectMapper.Map<Order, OrderInfoDto>(order);
        }
        public async Task<List<OrderInfoDto>> GetListAsync()
        {
            var orders = await _orderRepository.GetListAsync();
            return ObjectMapper.Map<List<Order>, List<OrderInfoDto>>(orders);
        }
    }
ProductApplicationService
 /// <summary>
    /// 商品服务
    /// </summary>
    public class ProductApplicationService : ApplicationService, IProductApplicationService
    {
        /// <summary>
        /// 自定义商品仓储
        /// </summary>
        private readonly IProductRepository _productRepository;
        /// <summary>
        /// 商品类别仓储
        /// </summary>
        private readonly IRepository<Category> _categoryRepository;
        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="productRepository">自定义商品仓储</param>
        /// <param name="categoryRepository">商品类别仓储</param>
        public ProductApplicationService(IProductRepository productRepository,
            IRepository<Category> categoryRepository)
        {
            _productRepository = productRepository;
            _categoryRepository = categoryRepository;
        }
        /// <summary>
        /// 获取商品信息
        /// </summary>
        /// <param name="id">商品id</param>
        /// <returns></returns>
        public async Task<ProductItemDto> GetAsync(long id)
        {
            return await Task.FromResult(Query(p => p.Id == id).FirstOrDefault(p =>p.Id == id));
        }
        /// <summary>
        /// 获取分页商品列表
        /// </summary>
        /// <param name="page">分页信息</param>
        /// <returns></returns>
        public IPagedResult<ProductItemDto> GetPage(BasePageInput page)
        {
            var query = Query()
                .WhereIf(!string.IsNullOrEmpty(page.Keyword), w => w.Name.StartsWith(page.Keyword));
            var count =  query.Count();
            var products = query.PageBy(page).ToList();
            return new PagedResultDto<ProductItemDto>(count,products);
        }
        /// <summary>
        /// 获取商品列表
        /// </summary>
        /// <returns></returns>
        public async Task<List<ProductItemDto>> GetListAsync()
        {
            var products = await _productRepository.GetListAsync();
            return ObjectMapper.Map<List<Product>, List<ProductItemDto>>(products);
        }
        private IQueryable<ProductItemDto> Query(Expression<Func<Product,bool>> expression = null)
        {
            var products = _productRepository.WhereIf(expression != null, expression);
            var categories = _categoryRepository;
            var data = from product in products
                       join category in categories on product.CategoryId equals category.Id into temp
                       from result in temp.DefaultIfEmpty()
                       select new ProductItemDto
                       {
                           Id = product.Id,
                           Description = product.Description,
                           Name = product.Name,
                           Price = product.Price,
                           Stock = product.Stock,
                           CategoryName = result.CategoryName,
                       };
            return data;
        }
    }
Application.Contract 应用层抽象
1.添加引用
通过 Nuget 安装 Volo.Abp.Application.Contract
2.定义模块
创建 MyShopApplicationContractModule.cs ,继承自AbpModule,并且没有对外依赖
   public class MyShopApplicationContractModule:AbpModule
    {
    }
3.定义Contract
IOrderApplicationService
    public interface IOrderApplicationService:IApplicationService
    {
        Task<OrderInfoDto> GetAsync(long id);
        Task<List<OrderInfoDto>> GetListAsync();
    }
IProductApplicationService
    public interface IProductApplicationService :IApplicationService
    {
        Task<List<ProductItemDto>> GetListAsync();
        IPagedResult<ProductItemDto> GetPage(BasePageInput page);
        Task<ProductItemDto> GetAsync(long id);
    }
Admin.Application 管理后台应用层
这里和Application层差不多,但是为了方便编写代码所以Admin.Contract和Admin.Application 放在了一个项目中,这里我定义了IBaseAdminCRUDApplicationService接口并且实现了一个BaseAdminCRUDApplicationService实现了一些简单的CRUD的方法暂时作为后台数据管理的方法,并且为了区分后台管理接口和平台接口在自动注册为api时加上admin前缀
            service.Configure((AbpAspNetCoreMvcOptions options) =>
            {
                // 平台接口和后台管理接口暂时放在一个站点下
                options.ConventionalControllers.Create(typeof(Application.ProductApplicationService).Assembly);
                options.ConventionalControllers.Create(typeof(Application.OrderApplicationService).Assembly);
                // 区分接口 请求路径加上admin
                options.ConventionalControllers.Create(typeof(Admin.Application.Services.ProductApplicationService).Assembly, options =>
                 {
                     options.RootPath = "admin";
                 });
            });
1.添加引用
通过 Nuget 安装 Volo.Abp.Application
通过 Nuget 安装 Volo.Abp.AutoMapper
2.定义模块
创建 MyShopAdminApplicationModule.cs ,继承自AbpModule,并且依赖Domain以及ApplicationContract层和后续使用的AutoMapper,所以对应DependsOn中需要添加对应依赖
    [DependsOn(typeof(MyShopDomainModule))]
    [DependsOn(typeof(AbpAutoMapperModule))]
    public class MyShopAdminApplicationModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddAutoMapperObjectMapper<MyShopAdminApplicationModule>();
            Configure<AbpAutoMapperOptions>(config =>
            {
                config.AddMaps<MyShopAdminApplicationProfile>();
            });
        }
    }
EntityFreaworkCore数据访问层中实现Repository
通过Nuget添加 Volo.Abp.EntityFrameworkCore.MySQL
定义DbContext
    [ConnectionStringName("Default")]
    public class MyShopDbContext:AbpDbContext<MyShopDbContext>
    {
        public MyShopDbContext(DbContextOptions<MyShopDbContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.ConfigureProductStore();
            builder.ConfigureOrderStore();
            builder.ConfigureOrderItemStore();
            builder.ConfigureCategoryStore();
        }
        public DbSet<Product> Products { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }
        public DbSet<Category> Categories { get; set; }
    }
这里通过扩展方法分开了实体的一些定义
ConfigureProductStore
    public static class ProductCreatingExtension
    {
        public static void ConfigureProductStore(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));
            builder.Entity<Product>(option =>
            {
                option.ToTable("Product");
                option.ConfigureByConvention();
            });
        }
    }
ConfigureOrderStore
    public static class OrderCreatingExtension
    {
        public static void ConfigureOrderStore(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));
            builder.Entity<Order>(option =>
            {
                option.ToTable("Order");
                option.ConfigureByConvention();
            });
        }
    }
ConfigureOrderItemStore
    public static class OrderItemsCreatingExtension
    {
        public static void ConfigureOrderItemStore(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));
            builder.Entity<OrderItem>(option =>
            {
                option.ToTable("OrderItem");
                option.ConfigureByConvention();
            });
        }
    }
ConfigureCategoryStore
    public static class CategoryCreatingExtension
    {
        public static void ConfigureCategoryStore(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));
            builder.Entity<Category>(option =>
            {
                option.ToTable("Category");
                option.ConfigureByConvention();
            });
        }
    }
由于目前Abp vNext提供的 IRepository<TEntity,TKey> 接口已经满足我们当前的使用需要,也提供了IQuaryable 接口,所以灵活组装的能力也有,所以不另外实现自定义仓储,不过这里还是预先准备一个自定义的ProductRepsitory
在Domain层中定义IProductRepository
    public interface IProductRepository : IRepository<Product, long>
    {
        IQueryable<Product> GetProducts(long id);
    }
EntityFrameworkCore数据访问层中实现IProductRepository
继承EfCoreRepository以方便实现 IRepository接口中提供的基本方法和 DbContext的访问,并实现IProductRepository
   public class ProductRepository : EfCoreRepository<MyShopDbContext, Product, long>, IProductRepository
   {
       public ProductRepository(IDbContextProvider<MyShopDbContext> dbContextProvider) : base(dbContextProvider)
       {
       }
       public IQueryable<Product> GetProducts(long id)
       {
           return DbContext.Products.Where(p => p.Id == id);
       }
   }
定义模块,注入默认仓储
    [DependsOn(typeof(AbpEntityFrameworkCoreMySQLModule))]
    public class MyShopEntityFrameworkCoreModule :AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddAbpDbContext<MyShopDbContext>(option=>
            {
                // 如果只是用了 entity, 请将 includeAllEntities 设置为 true 否则会提示异常,导致所属仓储无法注入
                option.AddDefaultRepositories(true);
            });
            Configure<AbpDbContextOptions>(option=>
            {
                option.UseMySQL();
            });
        }
    }
Migration 迁移
1.添加引用
通过Nuget安装 Microsoft.EntityFrameworkCore.Tools
添加Domain,EntiyFrameworkCore数据访问层的引用
 <ItemGroup>
    <ProjectReference Include="..\MyShop.EntityFrameworkCore\MyShop.EntityFrameworkCore.csproj" />
    <ProjectReference Include="..\MyShop.Entity\MyShop.Domain.csproj" />
  </ItemGroup>
2.定义DbContext
这里我们使用了之前在EntifyFrameworkCore中定义的一些实体配置
    [ConnectionStringName("Default")]
    public class DbMigrationsContext : AbpDbContext<DbMigrationsContext>
    {
        public DbMigrationsContext(DbContextOptions<DbMigrationsContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            builder.ConfigureProductStore();
            builder.ConfigureOrderStore();
            builder.ConfigureOrderItemStore();
            builder.ConfigureCategoryStore();
        }
    }
3.定义模块
    [DependsOn(typeof(MyShopEntityFrameworkCoreModule))]
    public class MyShopEntityFrameworkCoreMigrationModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddAbpDbContext<DbMigrationsContext>();
        }
    }
4.执行迁移
打开程序包管理器控制台,由于我们的连接字符串定义在Api层,我已我们需要将Api设置为启动项目,并将程序包管理器控制台默认程序切换为我们的迁移层Migration并执行一下指令
1.添加迁移文件
Add-Migration "Init"
执行完后我们会在Migrations文件夹目录下得到相关的迁移文件
2.执行迁移
Update-Database
执行完成后就可以打开数据库查看我们生成的表了
Api 接口层
1.建立项目
接下来建立AspNetCore WebAPI 项目 并命名为MyShop.Api作为我们对外暴露的API,并添加Abp Vnext AspNetCore.MVC包
通过Nuget 安装 Volo.Abp.AspNetCore.Mvc
StartUp
namespace MyShop.Api
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            // 注册模块
            services.AddApplication<MyShopApiModule>();
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // 初始化
            app.InitializeApplication();
        }
    }
}
Program
由于我们使用了 Abp vNext 中的Autofac 所以需要添加 Abp vNext Autofac 模块 并注册Autofac
通过Nuget 安装 Volo.Abp.Autofac
namespace MyShop.Api
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .UseAutofac();
    }
}
MyShopApiModule
这里我们使用了Swagger 作为Api文档自动生成,所以需要添加相关Nuget包,并且依赖了MyShopEntityFrameworkCoreModule和 MyShopApplicationModule,MyShopAdminApplicationModule 所以需要添加相关依赖及项目引用,
并且使用了Api作为迁移连接字符串所在项目,所以还需要添加EF的Design包
Nuget
通过Nuget 安装 Swashbuckle.AspNetCore
通过Nuget 安装 Microsoft.EntityFrameworkCore.Design
项目引用
MyShop.AdminApplication
MyShop.Application
MyShop.EntitFrameworkCore
MyShop.EntitFrameworkCore.DbMigration
namespace MyShop.Api
{
    // 注意是依赖于AspNetCoreMvc 而不是 AspNetCore
    [DependsOn(typeof(AbpAspNetCoreMvcModule),typeof(AbpAutofacModule))]
    [DependsOn(typeof(MyShopApplicationModule),typeof(MyShopEntityFrameworkCoreModule),typeof(MyShopAdminApplicationModule))]
    public class MyShopApiModule :AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var service = context.Services;
            // 跨域
            context.Services.AddCors(options =>
            {
                options.AddPolicy("AllowAll", builder =>
                {
                    builder.AllowAnyOrigin()
                        .SetIsOriginAllowedToAllowWildcardSubdomains()
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
            });
            // 自动生成控制器
            service.Configure((AbpAspNetCoreMvcOptions options) =>
            {
                options.ConventionalControllers.Create(typeof(Application.ProductApplicationService).Assembly);
                options.ConventionalControllers.Create(typeof(Application.OrderApplicationService).Assembly);
                options.ConventionalControllers.Create(typeof(Admin.Application.Services.ProductApplicationService).Assembly, options =>
                 {
                     options.RootPath = "admin";
                 });
            });
            // swagger
            service.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo()
                {
                    Title = "MyShopApi",
                    Version = "v0.1"
                });
                options.DocInclusionPredicate((docName, predicate) => true);
                options.CustomSchemaIds(type => type.FullName);
            });
        }
        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            var env = context.GetEnvironment();
            var app = context.GetApplicationBuilder();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("AllowAll");
            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyShopApi");
            });
            app.UseRouting();
            app.UseConfiguredEndpoints();
        }
    }
}
至此我们的简单的项目就搭建好了 点击运行 查看 https://localhost:5001/swagger/index.html 就可以查看我们Application和Admin.Application中定义的接口了.
尝试从零开始构建我的商城 (一) :使用Abp vNext快速一个简单的商城项目的更多相关文章
- Django入门第一步:构建一个简单的Django项目
		
Django入门第一步:构建一个简单的Django项目 1.简介 Django是一个功能完备的Python Web框架,可用于构建复杂的Web应用程序.在本文中,将通过示例跳入并学习Django.您将 ...
 - 尝试从零开始构建我的商城 (二) :使用JWT保护我们的信息安全,完善Swagger配置
		
前言 GitHub地址 https://github.com/yingpanwang/MyShop/tree/dev_jwt 此文对应分支 dev_jwt 此文目的 上一篇文章中,我们使用Abp vN ...
 - 构建一个简单的Maven项目
		
这里用Maven Archetype插件从空白开始创建简单的项目. 熟悉Maven 项目和Maven的核心概念. 关键词:构建生命周期(build lifecycle), Maven仓库(reposi ...
 - [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)
		
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code 1 2 template < class _Ty, cl ...
 - 基于Maven + SSM (Spring、SpringMVC、Mybatis)构建一个简单的测试项目
		
最近在公司实习期间的培训交流中有机会接触到SSM,然后自己花费1周的时间投入学习.谈不上深刻理解其中原理,所以没有涉及理论知识,只是浅层次的学习如何使用,在此将学习过程记录整理出来,一方面自己备用:另 ...
 - idea构建一个简单的maven_web项目
		
软件说明 好啦,开始创建mave的web项目啦!
 - idea+Spring+Mybatis+jersey+jetty构建一个简单的web项目
		
一.先使用idea创建一个maven项目. 二.引入jar包,修改pom.xml <dependencies> <dependency> <groupId>org. ...
 - 从零开始,做一个简单的Vuetify项目,图标安装成功
		
安装Vuefity的时候,碰到一些坑,经过一番折腾,终于成功,记录正确的姿势如下: 创建一个Vue项目: vue init webpack-simple vular-admin 进入项目目录: cd ...
 - 使用spring 4.0 + maven 构建超简单的web项目
		
一.需求 使用spring去管理web项目,是目前非常流行的一种思路,本文将介绍使用maven+spring 4.0.2 来构建一个简单的web项目. 二.实现 1.新建一个maven项目,如下图所示 ...
 
随机推荐
- 基于Springboot+Mybatis+Element UI开发的钢贸供应链系统
			
小蓝钢贸云供应链系统以销售.采购.库存及财务一体化的设计理念,从供应商到客户的销售流程,实现订单.货物.资金的全面管控,并能对成本进行准确的跟踪与分析,为销售决策提供依据. 基于SpringBoot2 ...
 - Qt 展示pdf内容(新窗口或嵌入,pdfjs,linux)
			
前言:初学Qt,在网上查找了诸多资料,有什么poppler.mupdf啊巴拉巴拉的,结果一个比一个费劲,最后还是采用pdfjs较为方便高效,为方便相关问题搜索,写了一下内容. 需求描述:Qt应用中不支 ...
 - Halcon使用骨架法处理激光条并拟合直线
			
dev_close_window () * 设置颜色 dev_set_color ('green') * 读取图像 read_image (Image, 'images3/1') * 获得图像尺寸 g ...
 - Oracle学习(十四)分表分区
			
一.前言 大数据量的查询,不仅查询速度非常慢,而且还会导致数据库经常宕机,在尝试添加索引及查询方式修改后,还有没有更有效的解决方案呢? 分库.分表.分区这些概念咱就应该了解一下. 二.分表 假如一个大 ...
 - pwnable.kr-cmd1-witeup
			
执行分析程序,知过滤掉了flag.sh.tmp字段,但在linux下可通过通配符匹配文件哦. 哦对,参数输入的命令中,没有环境变量的支持,所有文件和命令必须用绝对路径哦.
 - SPJ方法
			
https://www.cnblogs.com/ztz11/p/10657351.html luogu https://blog.csdn.net/qwerty1125/article/details ...
 - 极简 Node.js 入门 - 4.4 可写流
			
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
 - Apache shiro权限基本使用
			
l shiro框架的核心功能:认证.授权.会话管理.加密 Application Code:应用程序代码,由开发人员负责开发的 Subject:框架提供的接口,代表当前用户对象 SecurityMan ...
 - 剑指offer-二叉树
			
1. 平衡二叉树 输入一棵二叉树,判断该二叉树是否是平衡二叉树. 解: 要么是一颗空树,要么左右子树都是平衡二叉树且左右子树深度之差不超过1 1 # class TreeNode: 2 # def _ ...
 - 零基础小白必看篇:从0到1构建Python Web框架
			
造轮子是最好的一种学习方式,本文尝试从0开始造个Python Web框架的轮子,我称它为ToyWebF. 本文操作环境为:MacOS,文中涉及的命令,请根据自己的系统进行替换. ToyWebF的简单特 ...