将MongoDB.NET驱动程序与.NET Core WebAPI一起使用
目录
您可能也有兴趣
技术栈
安装
创建ASP.NET WebApi项目
配置
依赖注入和选项模型
MongoDB配置
MongoDB .NET驱动程序
模型
定义数据库上下文
添加存储库
添加主控制器
添加管理员控制器
启动设定
运行项目
使用Robo 3T
在GitHub上运行项目
允许跨域调用(CORS)
完全更新MongoDB文档
测试更新
异常管理
.NET Core中的JSON POST的模型绑定
查询嵌入/嵌套文档
项目已经房子啊GitHub上,您可以直接下载源代码或在本地克隆项目。
您可能也有兴趣
- 第1部分–使用MongoDB运行LINQ查询– 如何搜索旅行的好地方(MongoDb LINQ和.NET Core)
- 第2部分– MongoDB中的分页–如何真正避免性能下降?
- 第3部分– MongoDb和LINQ:如何汇总和加入集合
技术栈
ASP.NET Core Web API具有很大的优势,它可以用作HTTP服务,并且可以由任何客户端应用程序(从台式机到移动设备)进行订阅,也可以安装在Windows,macOS或Linux上。
MongoDB是一种流行的NoSQL数据库,是Web API的出色后端。它们更适合于文档存储类型,而不是关系数据库。本文将介绍如何构建与MongoDB异步连接的.NET Core Web API,并全面支持HTTP GET,PUT,POST和DELETE。
安装
这里是所有需要安装的东西:
- Visual Studio Community 2017,包括.NET Core选项
- MongoDB和Robo 3T
创建ASP.NET WebApi项目
启动Visual Studio,然后访问“文件”>“新建项目”>“.Net Core”>“ ASP.NET Core Web应用程序”。
然后
配置
开箱即用的配置支持多种文件格式(JSON,XML或INI)。默认情况下,WebApi项目模板启用了JSON格式。在设置文件中,顺序很重要,并且包含复杂的结构。这是一个具有2级设置结构的数据库连接示例。
AppSettings.json –更新文件:
{"MongoConnection": {"ConnectionString": "mongodb://admin:abc123!@localhost","Database": "NotesDb"},"Logging": {"IncludeScopes": false,"Debug": {"LogLevel": {"Default": "Warning"}},"Console": {"LogLevel": {"Default": "Warning"}}}
}
依赖注入和选项模型
构造函数注入是实现依赖注入(DI)的最常见方法之一,尽管不是唯一的一种。ASP.NET Core在其解决方案中使用构造函数注入,因此我们也将使用它。ASP.NET Core项目具有一个Startup.cs文件,该文件配置了应用程序将在其中运行的环境。Startup.cs文件还将服务放入ASP.NET Core的“服务”层,该层使依赖项注入成为可能。
为了映射自定义数据库连接设置,我们将添加一个新的Settings类。
namespace NotebookAppApi.Model
{public class Settings{public string ConnectionString;public string Database;}
}
这是我们修改Startup.cs以便在Options访问器模型中注入设置的方法:
public void ConfigureServices(IServiceCollection services)
{// Add framework services.services.AddMvc();services.Configure<Settings>(options =>{options.ConnectionString = Configuration.GetSection("MongoConnection:ConnectionString").Value;options.Database = Configuration.GetSection("MongoConnection:Database").Value;});
}
此外,在项目中,可以通过IOptions接口访问设置:
IOptions<Settings>
MongoDB配置
安装MongoDB之后,您需要配置访问权限以及数据所在的位置。
为此,请在本地创建一个名为mongod.cfg的文件。这将包括设置到MongoDB服务器的数据文件夹以及到MongoDB日志文件的路径,最初无需任何身份验证。请使用您自己的设置更新这些本地路径:
systemLog:destination: filepath: "C:\\tools\\mongodb\\db\\log\\mongo.log"logAppend: true
storage:dbPath: "C:\\tools\\mongodb\\db\\data"
在命令提示符下运行。这将启动MongoDB服务器,指向已经创建的配置文件(如果服务器安装在自定义文件夹中,请首先更新命令)
"C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --config C:\Dev\Data.Config\mongod.cfg
服务器启动后(您可以在日志文件中看到详细信息),在命令提示符下运行mongo.exe 。下一步是将管理员用户添加到数据库中。使用完整路径运行mongodb(例如:“C:\Program Files\MongoDB\Server\3.2\bin\mongo.exe”)。
然后将以下代码复制粘贴到控制台中:
use admin
db.createUser({user: "admin",pwd: "abc123!",roles: [ { role: "root", db: "admin" } ]}
);
exit;
然后停止服务器并更新配置文件,包括安全选项。
systemLog:destination: filepath: "C:\\tools\\mongodb\\db\\log\\mongo.log"logAppend: true
storage:dbPath: "C:\\tools\\mongodb\\db\\data"
security:authorization: enabled
从现在开始,我们将使用管理员用户连接到MongoDb 。有一个很好的做法,就是不要在正常操作中使用超级用户角色(在我们的案例中为Administrator),但是为了使事情变得简单,我们将继续只有一个用户。
MongoDB .NET驱动程序
要连接到MongoDB,请通过Nuget添加名为MongoDB.Driver的包。这是.NET的新官方驱动程序,完全支持ASP.NET Core应用程序。
模型
与笔记本中每个条目关联的模型类(POCO)包括以下内容:
using System;
using MongoDB.Bson.Serialization.Attributes;namespace NotebookAppApi.Model
{public class Note{[BsonId]// standard BSonId generated by MongoDbpublic ObjectId InternalId { get; set; }// external Id, easier to reference: 1,2,3 or A, B, C etc.public string Id { get; set; } public string Body { get; set; } = string.Empty;[BsonDateTimeOptions]// attribute to gain control on datetime serializationpublic DateTime UpdatedOn { get; set; } = DateTime.Now;public NoteImage HeaderImage { get; set; }public int UserId { get; set; } = 0;}
}
注意:默认情况下,使用参数 BsonDateTimeOptions,Bson序列化程序尝试将序列化为DateTime和UTC。如下添加特性[BsonDateTimeOptions(Kind = DateTimeKind.Local)],我们允许保存本地时间:
假设Note带有标题图像,下面是一个示例嵌入式类:
public class NoteImage
{public string Url { get; set; } = string.Empty;public string ThumbnailUrl { get; set; } = string.Empty;public long ImageSize { get; set; } = 0L;
}
定义数据库上下文
为了将访问数据库的功能保留在不同的位置,我们将添加一个NoteContext类。这将使用上面定义的设置。
public class NoteContext
{private readonly IMongoDatabase _database = null;public NoteContext(IOptions<Settings> settings){var client = new MongoClient(settings.Value.ConnectionString);if (client != null)_database = client.GetDatabase(settings.Value.Database);}public IMongoCollection<Note> Notes{get{return _database.GetCollection<Note>("Note");}}
}
添加存储库
使用存储库接口,我们将实现管理Notes所需的功能。这些还将使用依赖注入(DI)来从应用程序(例如,控制器部分)轻松访问:
public interface INoteRepository
{Task<IEnumerable<Note>> GetAllNotes();Task<Note> GetNote(string id);// query after multiple parametersTask<IEnumerable<Note>> GetNote(string bodyText, DateTime updatedFrom, long headerSizeLimit);// add new note documentTask AddNote(Note item);// remove a single document / noteTask<bool> RemoveNote(string id);// update just a single document / noteTask<bool> UpdateNote(string id, string body);// demo interface - full document updateTask<bool> UpdateNoteDocument(string id, string body);// should be used with high cautious, only in relation with demo setupTask<bool> RemoveAllNotes();
}
对数据库的访问将是异步的。我们在这里使用新的驱动程序,它提供了完整的异步堆栈。
举个例子:要获取所有Notes,我们提出一个异步请求:
public async Task<IEnumerable<Note>> GetAllNotes()
{var documents = await _context.Notes.Find(_ => true).ToListAsync();return documents;
}
这是所有基本CRUD操作的完整实现:
public class NoteRepository : INoteRepository
{private readonly NoteContext _context = null;public NoteRepository(IOptions<Settings> settings){_context = new NoteContext(settings);}public async Task<IEnumerable<Note>> GetAllNotes(){try{return await _context.Notes.Find(_ => true).ToListAsync();}catch (Exception ex){// log or manage the exceptionthrow ex;}}// query after Id or InternalId (BSonId value)//public async Task<Note> GetNote(string id){try{ObjectId internalId = GetInternalId(id);return await _context.Notes.Find(note => note.Id == id || note.InternalId == internalId).FirstOrDefaultAsync();}catch (Exception ex){// log or manage the exceptionthrow ex;}}// query after body text, updated time, and header image size//public async Task<IEnumerable<Note>> GetNote(string bodyText, DateTime updatedFrom, long headerSizeLimit){try{var query = _context.Notes.Find(note => note.Body.Contains(bodyText) &¬e.UpdatedOn >= updatedFrom &¬e.HeaderImage.ImageSize <= headerSizeLimit);return await query.ToListAsync();}catch (Exception ex){// log or manage the exceptionthrow ex;}}private ObjectId GetInternalId(string id){ObjectId internalId;if (!ObjectId.TryParse(id, out internalId))internalId = ObjectId.Empty;return internalId;}public async Task AddNote(Note item){try{await _context.Notes.InsertOneAsync(item);}catch (Exception ex){// log or manage the exceptionthrow ex;}}public async Task<bool> RemoveNote(string id){try{DeleteResult actionResult = await _context.Notes.DeleteOneAsync(Builders<Note>.Filter.Eq("Id", id));return actionResult.IsAcknowledged && actionResult.DeletedCount > 0;}catch (Exception ex){// log or manage the exceptionthrow ex;}}public async Task<bool> UpdateNote(string id, string body){var filter = Builders<Note>.Filter.Eq(s => s.Id, id);var update = Builders<Note>.Update.Set(s => s.Body, body).CurrentDate(s => s.UpdatedOn);try{UpdateResult actionResult = await _context.Notes.UpdateOneAsync(filter, update);return actionResult.IsAcknowledged&& actionResult.ModifiedCount > 0;}catch (Exception ex){// log or manage the exceptionthrow ex;}}public async Task<bool> UpdateNote(string id, Note item){try{ReplaceOneResult actionResult = await _context.Notes.ReplaceOneAsync(n => n.Id.Equals(id), item, new UpdateOptions { IsUpsert = true });return actionResult.IsAcknowledged&& actionResult.ModifiedCount > 0;}catch (Exception ex){// log or manage the exceptionthrow ex;}}// Demo function - full document updatepublic async Task<bool> UpdateNoteDocument(string id, string body){var item = await GetNote(id) ?? new Note();item.Body = body;item.UpdatedOn = DateTime.Now;return await UpdateNote(id, item);}public async Task<bool> RemoveAllNotes(){try{DeleteResult actionResult = await _context.Notes.DeleteManyAsync(new BsonDocument());return actionResult.IsAcknowledged&& actionResult.DeletedCount > 0;}catch (Exception ex){// log or manage the exceptionthrow ex;}}
}
为了使用DI模型访问NoteRepository,我们在ConfigureServices中添加了一行
services.AddTransient<INoteRepository, NoteRepository>();
其中:
瞬态(Transient):每次创建。
范围(Scoped):每个请求仅创建一次。
单例(Singleton):在首次请求时创建。每个后续请求都使用第一次创建的实例。
添加主控制器
首先,我们介绍主控制器。它提供了所有CRUD接口,可供外部应用程序使用。
该获取的行为有NoCache的指令,以确保Web客户端使经常对服务器的请求。
[Produces("application/json")]
[Route("api/[controller]")]
public class NotesController : Controller
{private readonly INoteRepository _noteRepository;public NotesController(INoteRepository noteRepository){_noteRepository = noteRepository;}[NoCache][HttpGet]public async Task<IEnumerable<Note>> Get(){return await _noteRepository.GetAllNotes();}// GET api/notes/5 - retrieves a specific note using either Id or InternalId (BSonId)[HttpGet("{id}")]public async Task<Note> Get(string id){return await _noteRepository.GetNote(id) ?? new Note();}// GET api/notes/text/date/size// ex: http://localhost:53617/api/notes/Test/2018-01-01/10000[NoCache][HttpGet(template: "{bodyText}/{updatedFrom}/{headerSizeLimit}")]public async Task<IEnumerable<Note>> Get(string bodyText, DateTime updatedFrom, long headerSizeLimit){return await _noteRepository.GetNote(bodyText, updatedFrom, headerSizeLimit) ?? new List<Note>();}// POST api/notes - creates a new note[HttpPost]public void Post([FromBody] NoteParam newNote){_noteRepository.AddNote(new Note{Id = newNote.Id,Body = newNote.Body,CreatedOn = DateTime.Now,UpdatedOn = DateTime.Now,UserId = newNote.UserId});}// PUT api/notes/5 - updates a specific note[HttpPut("{id}")]public void Put(string id, [FromBody]string value){_noteRepository.UpdateNoteDocument(id, value);}// DELETE api/notes/5 - deletes a specific note[HttpDelete("{id}")]public void Delete(string id){_noteRepository.RemoveNote(id);}
}
添加管理员控制器
这将是专用于管理任务的控制器(我们使用一些虚假数据来初始化数据库)。在实际的项目中,我们应该非常谨慎地使用这样的接口。仅出于开发目的和快速测试的目的,此方法可能很方便。
要使用它,我们只需将URL添加到浏览器中。运行下面的代码,将自动创建完整的设置(例如,新数据库,新集合,样本记录)。我们可以使用http://localhost:5000/api/system/init(使用IIS时)或 http://localhost:53617/api/system/init (使用IIS Express时,在此示例项目中默认启用) 。我们甚至可以扩展想法,添加更多命令。但是,如上所述,这类方案应仅用于开发,决不能部署到生产环境中。
[Route("api/[controller]")]
public class SystemController : Controller
{private readonly INoteRepository _noteRepository;public SystemController(INoteRepository noteRepository){_noteRepository = noteRepository;}// Call an initialization - api/system/init[HttpGet("{setting}")]public string Get(string setting){if (setting == "init"){_noteRepository.RemoveAllNotes();var name = _noteRepository.CreateIndex();_noteRepository.AddNote(new Note(){Id = "1",Body = "Test note 1",UpdatedOn = DateTime.Now,UserId = 1,HeaderImage = new NoteImage{ImageSize = 10,Url = "http://localhost/image1.png",ThumbnailUrl = "http://localhost/image1_small.png"}});_noteRepository.AddNote(new Note(){Id = "2",Body = "Test note 2",UpdatedOn = DateTime.Now,UserId = 1,HeaderImage = new NoteImage{ImageSize = 13,Url = "http://localhost/image2.png",ThumbnailUrl = "http://localhost/image2_small.png"}});_noteRepository.AddNote(new Note(){Id = "3",Body = "Test note 3",UpdatedOn = DateTime.Now,UserId = 1,HeaderImage = new NoteImage{ImageSize = 14,Url = "http://localhost/image3.png",ThumbnailUrl = "http://localhost/image3_small.png"}});_noteRepository.AddNote(new Note(){Id = "4",Body = "Test note 4",UpdatedOn = DateTime.Now,UserId = 1,HeaderImage = new NoteImage{ImageSize = 15,Url = "http://localhost/image4.png",ThumbnailUrl = "http://localhost/image4_small.png"}});return "Database NotesDb was created, and collection 'Notes' was filled with 4 sample items";}return "Unknown";}
}
启动设定
为了快速显示值,项目一旦运行,请更新文件launchSettings.json。
这是完整的文件内容,默认情况下指向api/notes网址。
{"iisSettings": {"windowsAuthentication": false,"anonymousAuthentication": true,"iisExpress": {"applicationUrl": "http://localhost:53617/","sslPort": 0}},"profiles": {"IIS Express": {"commandName": "IISExpress","launchBrowser": true,"launchUrl": "api/notes","environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development"}},"NotebookAppApi": {"commandName": "Project","launchBrowser": true,"launchUrl": "http://localhost:5000/api/notes","environmentVariables": {"ASPNETCORE_ENVIRONMENT": "Development"}}}
}
运行项目
在运行项目之前,请确保MongoDB正在运行(作为Windows服务或通过控制台应用程序运行,如上所述)。
首先运行初始化链接:
http://localhost:53617/api/system/init
然后运行默认的应用程序链接
http://localhost:53617/api/notes
使用Robo 3T
使用Robo 3T,我们可以检查数据库中的实际条目。使用凭据连接到数据库,我们可以看到所有记录。
即使唯一ID的名称为_id,MongoDb .NET Driver也会使用标签[BsonId]将其映射到我们的变量InternalId。
在GitHub上运行项目
该示例的完整资源可在GitHub-> https://github.com/fpetru/WebApiMongoDB上找到。
允许跨域调用(CORS)
作为不同的应用程序,它们运行在单独的域上,所有对ASP.NET WebAPI站点的调用实际上都是跨域调用。对于Angular 2,首先有一个预请求,然后是实际请求(OPTIONS请求)。在进行此预检查之前,我们首先确认允许跨域调用(CORS)。
我通过应用两个更改启用了CORS:
首先在Startup.cs的ConfigureServices()方法中注册CORS功能:
public void ConfigureServices(IServiceCollection services)
{// Add service and create Policy with optionsservices.AddCors(options => { options.AddPolicy("CorsPolicy",builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials());});// ....services.AddMvc();
}
然后在UseMVC之前,通过在Startup的Configure()方法中调用app.useCors()在应用程序中的每个请求上全局启用策略。
public void Configure(IApplicationBuilder app)
{// ...// global policy, if assigned here (it could be defined individually for each controller)app.UseCors("CorsPolicy");// ...// We define UseCors() BEFORE UseMvc, below just a partial callapp.UseMvc(routes => {
}
即使可以更进一步,更有选择性地应用它,本文其余部分也保持不变。
完全更新MongoDB文档
最初,示例项目仅包含属性的选择性更新。使用ReplaceOneAsync,我们可以更新整个文档。如果尚不存在,Upsert将创建该文档。
public async Task<ReplaceOneResult> UpdateNote(string id, Note item)
{return await _context.Notes.ReplaceOneAsync(n => n.Id.Equals(id), item, new UpdateOptions { IsUpsert = true });
}
测试更新
为了能够测试更新,我使用了Postman。它是测试API的出色工具。
我选择了命令类型POST,然后输入了本地URL,并添加了一个新的Header(Content-Type是application/json)。
然后将“主体”设置为“原始”并更新虚拟值。
使用RoboMongo,我们可以看到更新的值。
异常管理
从C#5.0开始, 该语言引入了async 和 await来简化任务并行库的使用。我们可以简单地使用try/catch块来捕获异常,如下所示:
public async Task<IEnumerable<Note>> GetAllNotes()
{try{return await _context.Notes.Find(_ => true).ToListAsync();}catch (Exception ex){// log or manage the exceptionthrow ex;}
}
通过这种方式,我们通过使用await异步地等待有故障的任务来完成它。这将重新引发原始存储的异常。
最初,我使用void作为返回值。更改返回类型后,异步方法中引发的异常将安全地保存在返回的Task实例中。当我们等待错误的方法时,保存在Task中的异常将被重新抛出,并保留其完整的堆栈跟踪。
public async Task AddNote(Note item)
{try{await _context.Notes.InsertOneAsync(item);}catch (Exception ex){// log or manage the exceptionthrow ex;}
}
.NET Core中的JSON POST的模型绑定
模型绑定是将原始HTTP请求转换为控制器上的操作方法调用的参数。
[FromBody]参数告诉.net core框架使用请求的content-type标头,以决定使用哪个已配置的IInputFormatters进行模型绑定。
默认情况下,当您在Startup.cs中调用AddMvc()时,将自动配置JSON格式(JsonInputFormatter)。如果需要,可以添加其他格式化程序,例如将XML绑定到对象。
[HttpPost]
public void Post([FromBody] NoteParam newNote)
要添加新的Note,我们首先需要将Content-Type设置为application/json。
然后,我们发送一个JSON对象,并成功添加了一个新的Note。由于未设置UserId,因此该对象将采用默认值。
查询嵌入/嵌套文档
MongoDB的CSharp驱动程序使对嵌入式文档的查询变得容易。在下面的示例中,我们混合使用了两个过滤器,一个过滤器比较主文档中的日期,另一个过滤器比较嵌套类的long 成员。
note.UpdatedOn >= updatedFrom && note.HeaderImage.ImageSize <= headerSizeLimit
使用IIS Express访问应用程序,我们可以使用Get函数,该函数包含所有带有Test的注释,该注释在2018-01-01之后创建,并且大小小于10000。项目启动后,可以使用浏览器中的下一个URL调用此函数:http://localhost:53617/api/notes/Test/2018-01-01/10000。
将MongoDB.NET驱动程序与.NET Core WebAPI一起使用相关推荐
- IIS部署asp.net core webapi
一.需要安装Windows Server Hosting,作用是让IIS有方向代理功能(Asp.Net Core Module负责反向代理工作),将请求转发到Kestrel,Windows serve ...
- core webapi缩略图_.Net Core WebApi上传图片的两种方式
我这边主要是为了上传图片,话不多说,上代码. 方式一:通过Form表单上传 后端: /// /// 上传图片,通过Form表单提交 /// /// [Route("Upload/FormIm ...
- ASP.Net Core WebApi几种版本控制对比
ASP.Net Core WebApi几种版本控制对比 原文:ASP.Net Core WebApi几种版本控制对比 一.版本控制的好处: (1)有助于及时推出功能, 而不会破坏现有系统. (2)它还 ...
- 为什么 ASP.NET Core WebAPI 继承 ControllerBase 而不是 Controller ?
咨询区 Alex Sanséau: 我是 ASP.NET Core Web API 的初学者,我在跟着文档创建 Controller 时,VS模板引擎给我生成了如下 Controller 模板代码,我 ...
- NET问答: 如何将 ASP.NET Core WebAPI 中抛出的异常封装成对象?
咨询区 rianjs: 在 ASP.NET Core WebAPI 中,我的 Controller 代码如下: [Route("create-license/{licenseKey}&quo ...
- 【源码解读】Vue与ASP.NET Core WebAPI的集成
在前面博文[Vue]Vue 与 ASP.NET Core WebAPI 的集成中,介绍了集成原理:在中间件管道中注册SPA终端中间件,整个注册过程中,终端中间件会调用node,执行npm start命 ...
- 【Vue】Vue与ASP.NET Core WebAPI的集成
SPA单页面应用已经遍地开花,熟知的三大框架,Angular.Vue和React,其中Angular与React均可集成至ASP.NET Core,且提供了相关了中间件.但是Vue没有: " ...
- ASP.NET CORE WEBAPI文件下载
最近要使用ASP.NET CORE WEBAPI用来下载文件,使用的.NET CORE 3.1.考虑如下场景: 文件是程序生成的. 文件应该能兼容各种格式. 浏览器可以感知进行下载. 准备 经过简单的 ...
- dotNET Core WebAPI 统一处理(返回值、参数验证、异常)
现在 Web 开发比较流行前后端分离 现在 Web 开发比较流行前后端分离,我们的产品也是一样,前端使用Vue,后端使用 dotNet Core WebAPI ,在写 API 的过程中有很多地方需要统 ...
最新文章
- [AlwaysOn Availability Groups]AlwaysOn健康诊断日志
- 【NLP】从头开始学词向量的预训练
- JQuery中元素的数据存储
- 辨析矩阵内积(hadamard、kronecker)
- Lync2013 升级错误总结8 Lync2013 日志总是提示进程 RtcHost(5724) 收到了一个无效的客户端证书...
- SpringBoot启动全流程源码解析(超详细版)
- fso封装类可以用仿dos命令操作文件
- 【Sublime Text 3】Sublime Text 3 - cracked 3092
- linux mysql5.7 实例初始化_mysql 5.7多实例单配置文件安装
- mysql可以授予的权限包括多选题_mysql中的权限有( )。 (5.0分)_学小易找答案
- ipad4越狱显示服务器维修,iPad越狱后怎么恢复出厂设置及恢复中所出现问题的解决方法...
- excel表格的绝对引用和相对引用
- 微信小程序开发者工具使用vant组件
- 递归求全排列的学习与理解
- python 网页自动化实现
- Python 手把手教你爬取淘宝的笔记本电脑数据
- Ubuntu系统镜像盘ISO:各版本大全、国内网速下载地址(阿里云)
- 无题(2012.5.11 摘自 人人网)
- PDF文件有修改权限如何取消
- 那位学软件测试的广州宝妈,后来怎样了?
热门文章
- python的numpy是什么_python中numpy是什么
- 苏宁大数据怎么运营_18个“硬核”数据告诉你,苏宁大数据如何火力全开护航618!...
- 游戏桌面壁纸|英雄联盟,热爱游戏的朋友看这里
- PSD分层情人节海报模板,让人眼前一亮
- srve0255e尚未定义要怎么办_斜视怎么办?日常的护理工作有哪些?
- 高考学文的能报计算机吗,高考志愿填报时,文科生能申报计算机类相关专业吗?...
- Ubuntu系统查看设备的内存信息
- OpenStack Glance(镜像服务)基础架构:Basic architecture
- srsLTE源码学习:度量中心:metrics_hub.h
- 如何用libpng输出一个编辑后的png图片?