本文主要讲解了如何把ABP官方的在线生成解决方案运行起来,并说明了解决方案中项目间的依赖关系。然后手动实践了如何从0搭建了一个简化的解决方案。ABP官方的在线生成解决方案源码下载参考[3],手动搭建的简化的解决方案源码下载参考[4]。

一.ABP官方在线生成解决方案

1.将在线生成解决方案跑起来

首先进入页面https://abp.io/get-started,然后创建项目:

然后头脑中要有一个项目之间的依赖关系图,不清楚的可以参考《基于ABP实现DDD》:

截止目前为止,项目使用的.NET版本是6.0,ABP版本是5.3.3。
使用Rider打开项目Acme.BookStore后,会提示使用yarn安装package,安装包后:

在整个解决方案中搜索ConnectionStrings,发现其在Acme.BookStore.HttpApi.Host、Acme.BookStore.DbMigrator和Acme.BookStore.IdentityServer这3个启动项目中出现:

将ConnectionStrings中的内容替换为:

"ConnectionStrings": {"Default": "Server=127.0.0.1;Database=BookStore;Trusted_Connection=True;User ID=sa;Password=913292836;"},

然后开始运行Acme.BookStore.DbMigrator进行数据种子迁移:

出现上面图片输出结果,基本上表示迁移成功完成了,接下来看看数据库:

运行Acme.BookStore.Web项目如下:


启动后发现报错了,发现主要是3个问题:一个是Redis没有启动,另一个问题是IDS4服务没有启动,最后一个问题是Acme.BookStore.HttpApi.Host没有启动

启动IDS4服务的时候发现报错Volo.Abp.AbpException: Could not find the bundle file '/libs/abp/core/abp.css' for the bundle 'Basic.Global'!


在该项目下执行命令abp install-libs:

发现消息提示说ABP CLI有个更新的5.3.3版本,通过命令dotnet tool update -g Volo.Abp.Cli进行升级。再次运行Acme.BookStore.IdentityServer项目,发现不报错误了。如果在启动其它项目(特指Acme.BookStore.Web项目)的时候报同样的错误,那么同样执行命令abp install-libs即可解决问题。同时启动这3个项目如下:

启动成功后就可以见到熟悉的界面:

下面是项目Swagger的界面:

至此,已经把从官方下载下来的项目成功地运行起来了。

2.ABP运行流程

下面是在网上[1]找到的一张图,很清晰的说明了AspNet Core和ABP模块的运行流程,个人认为图上的Startup.ConfigureServices应该是Startup.Configure,已经在图中做了修改。

(1)AspNet Core运行流程
简单理解,基本上就是在Startup.ConfigureServices中进行依赖注入配置,然后在Startup.Configure中配置管道中间件,访问的时候就像洋葱模型。
(2)ABP模块运行流程

  • 在ABP模块中对Startup.ConfigureServices做了扩展,增加了PreConfigureServices和PostConfigureServices。对Startup.Configure也做了扩展,当然名字也修改了,Startup.Configure相当于是OnApplicationInitialization,同时增加了OnPreApplicationInitialization和OnPostApplicationInitialization。
  • 在ABP解决方案中有多个项目,每个项目都会有一个类继承自AbpModule。并且通过DependsOn描述了该模块依赖的模块。这样在ABP解决方案中就会有很多模块之间的依赖关系,通过拓扑排序算法对模块进行排序,从最深层的模块依次加载,直到启动所有模块。

(3)AbpModule抽象类中的方法
除了主要的PreConfigureServices()、ConfigureServices()、PostConfigureServices()、OnPreApplicationInitialization()、OnApplicationInitialization()、OnPostApplicationInitialization()方法外,还有一些其它的方法。abp\framework\src\Volo.Abp.Core\Volo\Abp\Modularity\AbpModule.cs

二.手动创建解决方案

0.创建解决方案

首先创建一个目录BookStoreHand用于存放解决方案:

然后创建一个解决方案,执行命令dotnet new sln -n Acme.BookStore:

用Rider打开后解决方案是空的,然后手动创建2个New Solution Folder,分别为src和test:

1.创建领域共享层和领域层

(1)Acme.BookStore.Domain.Shared[领域共享层]
通常定义的常量和枚举,都放在该项目中。通过命令dotnet new classlib -n Acme.BookStore.Domain.Shareddotnet sln ../Acme.BookStore.sln add Acme.BookStore.Domain.Shared创建领域共享层,并将其添加到解决方案当中:

然后就是创建模块类BookStoreDomainSharedModule如下:

namespace Acme.BookStore.Domain.Shared
{public class BookStoreDomainSharedModule: AbpModule{public override void ConfigureServices(ServiceConfigurationContext context){base.ConfigureServices(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}}
}

说明:接下来创建项目、添加项目间的引用等都使用Rider,而不再使用CLI操作,觉得CLI并不方便操作。基本思路顺序都是:创建项目,设置引用关系,创建模块,其它操作。
(2)Acme.BookStore.Domain[领域层]
该项目包含实体、值对象、领域服务、规约、仓储接口等。通过Rider创建Class Library项目Acme.BookStore.Domain如下:

Acme.BookStore.Domain项目依赖于Acme.BookStore.Domain.Shared项目:

创建模块类BookStoreDomainSharedModule如下:

[DependsOn(typeof(BookStoreDomainSharedModule) //依赖领域共享模块
)]
public class BookStoreDomainModule: AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){base.ConfigureServices(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

创建领域实体Book:

public class Book: Entity<int>
{public string BookName { get; set; } //名字public string Author { get; set; } //作者public DateTime PublishDate { get; set; } //出版日期public double Price { get; set; } //价格
}

创建仓储IBookRepository接口:

public interface IBookRepository: IRepository<Book, int>
{
}

2.创建基础设施层

(1)创建项目
基础设施层Acme.BookStore.EntityFrameworkCore是EF Core核心基础依赖项目,包含数据上下文、数据库映射、EF Core仓储实现等。通过Rider创建Class Library项目Acme.BookStore.EntityFrameworkCore如下:

Acme.BookStore.EntityFrameworkCore项目依赖于Acme.BookStore.Domain项目:

(2)创建模块
创建模块类BookStoreEntityFrameworkCoreModule如下:

[DependsOn(typeof(BookStoreDomainModule),typeof(AbpEntityFrameworkCoreSqlServerModule)
)]
public class BookStoreEntityFrameworkCoreModule: AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){context.Services.AddAbpDbContext<BookStoreDbContext>(options =>{// 给所有的实体都增加默认仓储options.AddDefaultRepositories(includeAllEntities: true);});Configure<AbpDbContextOptions>(options =>{options.UseSqlServer();});}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

(3)创建数据库上下文
创建数据库上下文BookStoreDbContext:

public class BookStoreDbContext: AbpDbContext<BookStoreDbContext>
{public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options) : base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(typeof(BookStoreDbContext).Assembly);}
}

ApplyConfigurationsFromAssembly应用来自IEntityTypeConfiguration中的配置。定义实体映射BookDbMapping如下:

public class BookDbMapping: IEntityTypeConfiguration<Book>
{public void Configure(EntityTypeBuilder<Book> builder){// 配置主键builder.HasKey(b => b.Id).HasName("Id");// 配置表和字段builder.ToTable("AbpBook");builder.Property(t => t.BookName).IsRequired().HasColumnName("BookName").HasComment("书名");builder.Property(t => t.Author).IsRequired().HasColumnName("Author").HasComment("作者");builder.Property(t => t.PublishDate).IsRequired().HasColumnName("PublishDate").HasComment("出版日期");builder.Property(t => t.Price).IsRequired().HasColumnName("Price").HasComment("价格");// 配置关系}
}

(4)创建仓储实现
定义IBookRepository的实现BookRepository如下:

public class BookRepository: EfCoreRepository<BookStoreDbContext, Book, int>, IBookRepository
{public BookRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) : base(dbContextProvider){}
}

3.创建应用契约层和应用层

(1)Acme.BookStore.Application.Contracts[应用契约层]
包含应用服务接口和数据传输对象。该项⽬被应⽤程序客户端引用,比如Web项目、API客户端项目。通过Rider创建Class Library项目Acme.BookStore.Application.Contracts:

Acme.BookStore.Application.Contracts项目依赖于Acme.BookStore.Domain.Shared项目如下:

创建模块类BookStoreApplicationContractsModule如下:

[DependsOn(typeof(BookStoreDomainSharedModule), //依赖于BookStoreDomainSharedModuletypeof(AbpObjectExtendingModule) //依赖于AbpObjectExtendingModule
)]
public class BookStoreApplicationContractsModule: AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){base.ConfigureServices(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

创建服务接口IBookAppService如下:

public interface IBookAppService: IApplicationService
{/// <summary>/// 获取书籍/// </summary>Task<BookDto> GetBookAsync(int id);
}

创建输出DTO为BookDto如下:

public class BookDto
{public int Id { get; set; } //主键public string BookName { get; set; } //名字public string Author { get; set; } //作者public DateTime PublishDate { get; set; } //出版日期public double Price { get; set; } //价格
}

(2)Acme.BookStore.Application[应用层]
实现在Contracts项目中定义的接⼝。通过Rider创建Class Library项目Acme.BookStore.Application如下:

Acme.BookStore.Application项目依赖于
Acme.BookStore.Application.Contracts和Acme.BookStore.Domain项目:

创建模块类BookStoreApplicationModule如下:

[DependsOn(typeof(AbpAutoMapperModule), //依赖于AutoMappertypeof(BookStoreDomainModule), //依赖于BookStoreDomainModuletypeof(BookStoreApplicationContractsModule) //依赖于BookStoreApplicationContractsModule
)]
public class BookStoreApplicationModule: AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){var services = context.Services;// 添加ObjectMapper注入services.AddAutoMapperObjectMapper<BookStoreApplicationModule>();// Abp AutoMapper设置Configure<AbpAutoMapperOptions>(config =>{config.AddMaps<BookStoreApplicationAutoMapperProfile>();});}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

创建自动映类BookStoreApplicationAutoMapperProfile如下:

public class BookStoreApplicationAutoMapperProfile: Profile
{public BookStoreApplicationAutoMapperProfile(){CreateMap<BookDto, Book>();CreateMap<Book, BookDto>();}
}

创建IBookAppService类的实现类BookAppService如下:

public class BookAppService: ApplicationService, IBookAppService
{private readonly IBookRepository _bookRepository;public BookAppService(IBookRepository bookRepository){_bookRepository = bookRepository;}public async Task<BookDto> GetBookAsync(int id){var queryable = await _bookRepository.GetQueryableAsync();var book = queryable.FirstOrDefault(t => t.Id == id);if (book == null){throw new ArgumentNullException(nameof(book));}return ObjectMapper.Map<Book, BookDto>(book);}
}

4.创建种子迁移

Acme.BookStore.DbMigrator是控制台应用程序,主要是迁移数据库结构并初始化种子数据。通过Rider创建ASP.NET Core Web Application的Empty项目Acme.BookStore.DbMigrator如下:

Acme.BookStore.DbMigrator项目依赖于Acme.BookStore.Application.Contracts和Acme.BookStore.EntityFrameworkCore项目如下:

创建模块类BookStoreDbMigratorModule如下:

[DependsOn(typeof(AbpAutofacModule),typeof(BookStoreEntityFrameworkCoreModule),typeof(BookStoreApplicationContractsModule))]
public class BookStoreDbMigratorModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){base.ConfigureServices(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

在Program.cs中添加services.AddHostedService()这个数据库迁移主机服务,在DbMigratorHostedService类中包含StartAsync()和StopAsync()这2个方法,在StartAsync()中获取BookStore数据库迁移服务BookStoreDbMigrationService,并执行数据库迁移方法MigrateAsync()。数据库迁移的思路基本上就是在Acme.BookStore.DbMigrator目录下执行命令:dotnet ef migrations add InitialCreate和dotnet ef database update,只不过使用的C#代码来实现的。自己通过Acme.BookStore.DbMigrator项目没有迁移成功,最后还是通过命令行实现迁移的。迁移结果如下:

说明:Program.cs、DbMigratorHostedService.cs和BookStoreDbMigrationService.cs的源码等完整项目源码参考[4]。

5.创建远程服务层

Acme.BookStore.HttpApi[远程服务层],简单理解就是很薄的控制层,该项目主要用于定义HTTP API,即应用服务层的包装器,将它们公开给远程客户端调用。通过Rider创建Class Library项目Acme.BookStore.HttpApi如下:

Acme.BookStore.HttpApi项目依赖于Acme.BookStore.Application.Contracts项目如下:

创建模块类BookStoreHttpApiModule如下:

[DependsOn(typeof(BookStoreApplicationContractsModule) //依赖于BookStoreApplicationContractsModule
)]
public class BookStoreHttpApiModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){base.ConfigureServices(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){base.OnApplicationInitialization(context);}
}

为了简要说明问题,创建一个简单的控制器类BookStoreController如下:

[RemoteService]
[Area("BookStore")]
[Route("api/app/book")]
public class BookStoreController: AbpControllerBase
{private readonly IBookAppService _bookAppService;public BookStoreController(IBookAppService bookAppService){_bookAppService = bookAppService;}[HttpGet][Route("get-book")]public Task<BookDto> GetBookAsync(int id){return _bookAppService.GetBookAsync(id);}
}

6.创建展示层

Acme.BookStore.HttpApi.Host这个是前后端分离时的项目命名方式。通过Rider创建ASP.NET Core Web Application的Empty项目Acme.BookStore.HttpApi.Host如下:

Acme.BookStore.HttpApi.Host项目依赖于Acme.BookStore.Application、Acme.BookStore.EntityFrameworkCore和Acme.BookStore.HttpApi项目如下:

创建模块类BookStoreHttpApiHostModule如下:

[DependsOn(typeof(BookStoreHttpApiModule), //依赖于BookStoreHttpApiModuletypeof(AbpAutofacModule), //依赖于AbpAutofacModuletypeof(BookStoreApplicationModule), //依赖于BookStoreApplicationModuletypeof(BookStoreEntityFrameworkCoreModule), //依赖于BookStoreEntityFrameworkCoreModuletypeof(AbpAspNetCoreSerilogModule), //依赖于AbpAspNetCoreSerilogModuletypeof(AbpSwashbuckleModule) //依赖于AbpSwashbuckleModule
)]
public class BookStoreHttpApiHostModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){var services = context.Services;var configuration = services.GetConfiguration();ConfigureConventionalControllers();ConfigureCors(context, configuration);ConfigureSwaggerServices(context, configuration);}private void ConfigureConventionalControllers(){Configure<AbpAspNetCoreMvcOptions>(options =>{options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly);});}private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration){context.Services.AddCors(options =>{options.AddPolicy("AllowAll",builder =>{builder.WithOrigins(configuration["App:CorsOrigins"].Split(",", StringSplitOptions.RemoveEmptyEntries).Select(o => o.RemovePostFix("/")).ToArray()).WithAbpExposedHeaders().SetIsOriginAllowedToAllowWildcardSubdomains().AllowAnyHeader().AllowAnyMethod().AllowCredentials();});});}private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration){context.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);options.CustomSchemaIds(type => type.FullName);});}public override void OnApplicationInitialization(ApplicationInitializationContext context){var env = context.GetEnvironment();var app = context.GetApplicationBuilder();var configuration = context.GetConfiguration();if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCors("AllowAll");if (configuration["UseSwagger"] == "true"){app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json", "Acme.BookStore API");});}app.UseRouting();app.UseConfiguredEndpoints();}
}

说明:HomeController.cs、Program.cs和配置文件等项目完整源码参考[4]。
将Acme.BookStore.HttpApi.Host项目启动起来后如下:

通过Swagger界面测试https://localhost:7016/api/app/book/get-book?id=1接口如下:

  奇怪的是在线生成解决方案的时候,UI框架选择了MVC,但是还是出现了这个项目,并且在启动Acme.BookStore.Web项目的时候,如果不启动Acme.BookStore.HttpApi.Host项目,还会报错Volo.Abp.AbpException: Remote service 'AbpMvcClient' was not found and there is no default configuration,并且还没有找到Acme.BookStore.Web项目在哪里用到了Acme.BookStore.HttpApi.Host项目。因为自己主要关注前后端分离的项目,所以就不纠结这个细节了。
  在线生成解决方案中还包括:Acme.BookStore.IdentityServer(认证授权项目),Acme.BookStore.HttpApi.Client(远程服务代理层),Acme.BookStore.Web(前后端不分离的展示层),Acme.BookStore.TestBase(其它项目共享或使用的类),Acme.BookStore.Domain.Tests(测试领域层对象),Acme.BookStore.EntityFrameworkCore.Tests(测试自定义仓储实现或EF Core映射),Acme.BookStore.Application.Tests(测试应用层对象),Acme.BookStore.HttpApi.Client.ConsoleTestApp(从.NET控制台中调用HTTP API)等。一篇文章放不下,后面继续解说实践。

参考文献:
[1]聊一聊ABP vNext的模块化系统:https://www.sohu.com/a/436373048_468635
[2]Abp vNext源码分析文章目录:https://www.cnblogs.com/myzony/p/10722506.html
[3]手动从0搭建ABP框架-ABP官方完整解决方案源码:https://url39.ctfile.com/f/2501739-625678611-787336?p=2096 (访问密码: 2096)
[4]手动从0搭建ABP框架-手动搭建简化解决方案源码:https://url39.ctfile.com/f/2501739-625678627-091eb9?p=2096 (访问密码: 2096)

手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践相关推荐

  1. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十四节--后台工作者HangFire与ABP框架Abp.Hangfire及扩展...

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 HangFire与Quartz.NET相比主要是HangFire的内置提供集成化的控制台,方便后台查看及监控,对于 ...

  2. ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十七节--Quartz与ABP框架Abp.Quar...

    ABP+AdminLTE+Bootstrap Table权限管理系统一期 Github:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate- ...

  3. 后台工作者HangFire与ABP框架Abp.Hangfire及扩展

    同步博客园地址:www.cnblogs.com/anyushengcm- 同步简书地址:www.jianshu.com/p/ebe390e48- HangFire与Quartz.NET相比主要是Han ...

  4. 从零开始搭建ABP框架(Asp .Net Boilerplate)+MySQL(v8.0.22)

    从零开始搭建ABP框架(Asp .Net Boilerplate)+MySQL(v 8.0.22) 一.总体框架介绍(传统的WebAPI) 一.总体框架介绍 注意:VS2019版本必须在16.9.4以 ...

  5. ABP框架 v3.0 已发布!

    我们很高兴地宣布,ABP框架和ABP商业版3.0版已经发布.与常规的2周发布一个版本不同的是, 这个版本用了4周的时间.关闭了119个issue,合并了89个pull request 和主框架仓库中的 ...

  6. ABP框架 v2.7.0已经发布!

    ABP框架和ABP商业版 v2.7已经发布.我们没有为2.4,2.5和2.6发布博客文章,所以这篇文章也将涵盖这几个版本中新增内容和过去的2个月里我们完成了什么. 关于发布周期与开发 之前说过我们已经 ...

  7. ABP框架v2.0 和 ABP商业版

    ABP框架2.0版已经在本周公布.这篇文章解释了为什么我们发布了一个抢先主版本,和2.0版本中的变化. 除了v2.0版本,我们很高兴地宣布ABP商业版,这是建立在开源ABP框架的之上的一套专业的模块, ...

  8. ABP框架搭建项目系列教程基础版

    我现在要着手一个新的项目,也打算用这个框架,所以想要将我一步一步用这个框架的做项目的步骤和想法跟大家分享出来. 经过前面十二篇的基础教程,现在终于该做个总结了. 第一篇,我们建议新手朋友们先通过ABP ...

  9. 一步一步使用ABP框架搭建正式项目系列教程之本地化详解

    返回总目录<一步一步使用ABP框架搭建正式项目系列教程> 本篇目录 扯扯本地化 ABP中的本地化 小结 扯扯本地化 本节来说说本地化,也有叫国际化.全球化的,不管怎么个叫法,反正道理都是一 ...

  10. 如何搭建NET开发项目的框架——ABP框架

    前两天我在写一个博客的教程,使用土耳其大牛的ABP框架(ASP.NET Boilerplate),这个框架在中国还不是很火热,我算是走在了前端吧~.那么这个框架如何搭建一个运营类的平台呢?首先我们要归 ...

最新文章

  1. 原创 | 疫情之下,这些数字经济赛道危中有机
  2. 【深度学习】深入浅出YOLOv3目标检测算法和实现(图片和视频)
  3. matplotlib里的fig和ax的区别。
  4. 提高PHP代码质量需要注意的地方三
  5. monthdiff oracle_timestampdiff
  6. spark.mllib源码阅读:GradientBoostedTrees
  7. aspnetcore.webapi实践k8s健康探测机制 - kubernetes
  8. Django Python MySQL Linux 开发环境搭建
  9. android adjust,【报Bug】安卓 adjust-position设置为false 页面依然被顶起 ios是好的
  10. fatal error: openssl/evp.h: 没有那个文件或目录
  11. python封装exe后其机器能用么_python打包成exe格式后,在部分机子上没法运行
  12. 杭电5253连接的管道
  13. 若泽数据 巨人_面部识别巨人拒绝分享有关其算法数据集的详细信息
  14. 极客大学架构师训练营 性能优化 性能测试指标 性能测试 性能优化 CDN 网络 硬盘 缓存 异步 集群 第13课 听课总结
  15. select 默认选中问题
  16. 计算机考研909考试大纲,山东大学2019年909数据结构考研大纲
  17. 德玛西亚皇子背景故事
  18. CGAL 4.11 官方文档 软件包概述 ——多边形类
  19. 某喷码机品牌U盘存储的配置文件简记
  20. 计算机毕业设计 SSM汽车维修保养平台 汽车维修维护平台 汽车信息管理系统Java Vue MySQL数据库 远程调试 代码讲解

热门文章

  1. 2022年某市新型智慧城市一网统管顶层设计方案(WORD)
  2. 为什么创业者要假装读过《从0到1》和《创业维艰》?
  3. 36 Questions for Increasing Closeness
  4. 5分钟三句代码实现抖音自动下载
  5. EJB是什么?(节选)
  6. python中水量_Python居然还能用于巨大的工程项目!比如三峡发电量估算系统!
  7. 字符串处理(六)atoi、atof、atol和atoll
  8. 70.(cesium篇)cesium接入天地图影像与注记(经纬度)
  9. 中国微型电动车行业市场供需与战略研究报告
  10. IplImage结构体