ASP.NET Core Web API使用小技巧

轉(zhuǎn)自:墨墨墨墨小宇 cnblogs.com/danvic712/p/11255423.html
一、前言
在目前的軟件開發(fā)的潮流中,不管是前后端分離還是服務(wù)化改造,后端更多的是通過構(gòu)建 API 接口服務(wù)從而為 web、app、desktop 等各種客戶端提供業(yè)務(wù)支持,如何構(gòu)建一個(gè)符合規(guī)范、容易理解的 API 接口是我們后端開發(fā)人員需要考慮的。
在本篇文章中,我將列舉一些我在使用 ASP.NET Core Web API 構(gòu)建接口服務(wù)時(shí)使用到的一些小技巧,因才疏學(xué)淺,可能會(huì)存在不對(duì)的地方,歡迎指出。
代碼倉儲(chǔ):https://github.com/Lanesra712/ingos-server
二、Step by Step
因?yàn)楸酒恼轮猩婕暗降囊恍┲R(shí)點(diǎn)在之前的文章中也已經(jīng)有具體的解釋了,所以這里只會(huì)說明如何在 ASP.NET Core Web API 中如何去使用,不會(huì)做過多的詳細(xì)介紹。
本篇文章中使用的代碼是基于 .NET Core 2.2 + .NET Standard 2.0 進(jìn)行構(gòu)建的,如果你采用的版本與我使用的不同,可能最終實(shí)現(xiàn)起來的代碼會(huì)有所不同,請(qǐng)?zhí)崆爸ぁ?/span>
同時(shí),本篇文章中所有示例代碼都會(huì)存在于前言中所列出的 github repo 中,我會(huì)嘗試將每個(gè)功能點(diǎn)的開發(fā)作為一次 commit,并且也會(huì)在后續(xù)進(jìn)行不定期的更新完善,最終搭建一個(gè)基于領(lǐng)域驅(qū)動(dòng)思想的后端項(xiàng)目模板,如果對(duì)你有幫助的話,歡迎持續(xù)關(guān)注。
1、使用小寫路由
在我之前的一篇文章中《構(gòu)建可讀性更高的 ASP.NET Core 路由》有提到過,因?yàn)?.NET 默認(rèn)采用 Pascal 的類命名方式,如果采用默認(rèn)生成的路由,最終構(gòu)建出的路由地址會(huì)存在大小寫混在一起的情況,雖然在 .NET Core 中大小寫的路由地址最終都會(huì)對(duì)于到正確的資源上,但是為了更好的符合前端的規(guī)范,所以這里我們首先按照之前的文章中所列出的方法去修改默認(rèn)生成的路由地址格式。
因?yàn)檫@里我們最終想要實(shí)現(xiàn)的是符合 Restful 風(fēng)格的 API 接口,所以這里我們首先需要將默認(rèn)生成的 URL 地址改為全小寫模式。
public void ConfigureServices(IServiceCollection services)
{
// 采用小寫的 URL 路由模式
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
}
如果你有看過構(gòu)建可讀性更高的 ASP.NET Core 路由這篇文章,你會(huì)發(fā)現(xiàn)其實(shí)我們最終實(shí)現(xiàn)的是 hyphen(-) 格式的 Url 地址,那么這里我們?yōu)槭裁床贿M(jìn)行后續(xù)的修改了呢?
如果你有查看 .NET Core 默認(rèn)模板中生成的 API Controller,仔細(xì)看下,這里其實(shí)是使用的特性路由,所以這里我們并不能通過 Startup.UseMvc 定義的傳統(tǒng)路由模板,或是直接在 Startup.Configure 中的 UseMvcWithDefaultRoute 方法去修改我們的生成的路由地址格式。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}2、允許跨域請(qǐng)求
不管是后端接口的服務(wù)化改造,還是只是單純的前后端分離項(xiàng)目開發(fā),我們的前端項(xiàng)目與后端接口通常不會(huì)部署在一起,所以我們需要解決前端訪問接口時(shí)會(huì)涉及到的跨域訪問的問題。
針對(duì)跨域請(qǐng)求,我們可以采用 jsonp、或者是通過給 nginx 服務(wù)器配置響應(yīng)的 header 參數(shù)頭信息、或者是使用 CORS,又或是其它的解決方案。你可以自由選擇,這里我采用在后端接口中直接配置對(duì)于 CORS 的支持。
在 .NET Core 中,已經(jīng)在 Microsoft.AspNetCore.Cors 這個(gè)類庫中添加了對(duì)于 CORS 的支持,因?yàn)檫@個(gè)類庫是存在于我們已經(jīng)安裝的 .NET Core SDK 中,所以這里我們并不需要通過 Nuget 進(jìn)行安裝,可以直接使用。
在 .NET Core 中配置 CORS 規(guī)則,我們可以通過在 Startup.ConfigureServices 這個(gè)方法中添加不同的授權(quán)策略,之后再針對(duì)某個(gè) Controller 或是 Action 通過添加 EnableCors 這個(gè) Attribute 的方式進(jìn)行配置,這里如果指定了 policy 策略名稱,則會(huì)使用指定的策略,如果沒有指定,則適用于系統(tǒng)的默認(rèn)配置。同樣的,我們也可以只設(shè)置一個(gè)策略,直接針對(duì)整個(gè)項(xiàng)目進(jìn)行配置,這里我采用對(duì)整個(gè)項(xiàng)目采用通用的跨域請(qǐng)求配置方案。
在配置 CORS 策略時(shí),我們可以設(shè)置只允許來源于某些 URL 地址的請(qǐng)求可以訪問,或者是指定接口只允許某些 HTTP 方法進(jìn)行訪問,或者是在請(qǐng)求的 header 中必須包含某些信息才可以訪問我們的接口。
在下面的代碼中,我定義了針對(duì)整個(gè)項(xiàng)目的跨域請(qǐng)求策略,這里我只是設(shè)置了對(duì)于接口請(qǐng)求方 URL 地址的控制,通過讀取配置文件中的數(shù)據(jù),從而達(dá)到只允許某些 IP 可以訪問的我們接口的目的。
public class Startup
{
// 默認(rèn)的跨域請(qǐng)求策略名稱
private const string _defaultCorsPolicyName = "Ingos.Api.Cors";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(
// 添加 CORS 授權(quán)過濾器
options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName)) ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// 配置 CORS 授權(quán)策略
services.AddCors(options => options.AddPolicy(_defaultCorsPolicyName,
builder => builder.WithOrigins(
Configuration["Application:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// 允許跨域請(qǐng)求訪問
app.UseCors(_defaultCorsPolicyName);
}
}例如在下面的設(shè)置中,我只允許這一個(gè)地址可以訪問我們的接口,如果需要指定多個(gè)的話,則可以通過英文的 , 進(jìn)行分隔。
"Application": {
"CorsOrigins": "http://127.0.0.1:5050"
}某些情況下,如果我們不想進(jìn)行限制的話,只需要將值改為 * 即可。
"Application": {
"CorsOrigins": "*"
}3、添加接口版本控制
在一些涉及到接口功能升級(jí)的場景下,當(dāng)我們需要修改接口邏輯而舊版本的接口無法停用的情況時(shí),為了減少對(duì)于原有接口的影響,我們可以采取為接口添加版本信息的形式,從而降低因采用不同版本而造成的影響。如果你想要詳細(xì)了解的話,可以查看這篇文章《ASP.NET Core 實(shí)戰(zhàn):構(gòu)建帶有版本控制的 API 接口》。
在實(shí)現(xiàn)具有版本控制的接口前,首先我們需要通過 Nuget 添加下面的兩個(gè) dll,因?yàn)槲沂窃?Ingos.Api.Core 這個(gè)類庫中進(jìn)行配置的,所以我安裝到了這個(gè)類庫下,你需要根據(jù)你自己的情況選擇最終是安裝到 Api 接口項(xiàng)目中還是在別的類庫下。
Install-Package Microsoft.AspNetCore.Mvc.Versioning
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer在安裝完成之后,我們就可以在 Startup.ConfigureServices 方法中,為項(xiàng)目中的接口配置版本信息,這里我采用的方案是將版本號(hào)添加到接口的 URL 地址中。
因?yàn)閷?duì)于所有中間件的配置都會(huì)在 Startup.ConfigureServices 方法中,為了保持該方法的純凈性,這里我寫了一個(gè)擴(kuò)展方法用于配置我們的 api 的版本,之后直接調(diào)用即可。
public static class ApiVersionExtension
{
/// <summary>
/// 添加 API 版本控制擴(kuò)展方法
/// </summary>
/// <param name="services">生命周期中注入的服務(wù)集合 <see cref="IServiceCollection"/></param>
public static void AddApiVersion(this IServiceCollection services)
{
// 添加 API 版本支持
services.AddApiVersioning(o =>
{
// 是否在響應(yīng)的 header 信息中返回 API 版本信息
o.ReportApiVersions = true;
// 默認(rèn)的 API 版本
o.DefaultApiVersion = new ApiVersion(1, 0);
// 未指定 API 版本時(shí),設(shè)置 API 版本為默認(rèn)的版本
o.AssumeDefaultVersionWhenUnspecified = true;
});
// 配置 API 版本信息
services.AddVersionedApiExplorer(option =>
{
// api 版本分組名稱
option.GroupNameFormat = " v VVVV";
// 未指定 API 版本時(shí),設(shè)置 API 版本為默認(rèn)的版本
option.AssumeDefaultVersionWhenUnspecified = true;
});
}
}擴(kuò)展方法最終實(shí)現(xiàn)方式如上面的代碼所示,之后我們就可以直接在 ConfigureServices 方法中直接進(jìn)行調(diào)用這個(gè)擴(kuò)展方法就可以了。
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Config api version
services.AddApiVersion();
}現(xiàn)在我們刪除項(xiàng)目創(chuàng)建時(shí)默認(rèn)生成的 ValuesController,在 Controllers 目錄下建立一個(gè) v1 文件夾,代表此文件夾下都是 v1 版本的控制器。添加一個(gè) UsersController 用來獲取系統(tǒng)的用戶資源,現(xiàn)在項(xiàng)目的文件結(jié)構(gòu)如下圖所示。

現(xiàn)在我們來改造我們的 UsersController,我們只需要在 Controller 或是 Action 上添加 ApiVersion 特性就可以指定當(dāng)前 Controller/Action 的版本信息。同時(shí),因?yàn)槲倚枰獙?API 的版本信息添加到生成的 URL 地址中,所以這里我們需要修改特性路由的模板,將我們的版本以占位符的形式添加到生成的路由 URL 地址中,修改完成后的代碼及實(shí)現(xiàn)的效果如下所示。
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
}
4、添加對(duì)于 Swagger 接口文檔的支持
在前后端分離開發(fā)的情況下,我們需要提供給前端開發(fā)人員一個(gè)接口文檔,從而讓前端開發(fā)人員知道以什么樣的 HTTP 方法或是傳遞什么樣的參數(shù)給后端接口,從而獲取到正確的數(shù)據(jù),而 Swagger 則提供了一種自動(dòng)生成接口文檔的方式,同時(shí)也提供類似于 Postman 的功能,可以實(shí)現(xiàn)對(duì)于接口的實(shí)時(shí)調(diào)用測試。
首先,我們需要通過 Nuget 添加 Swashbuckle.AspNetCore 這個(gè) dll 文件,之后我們就可以在此基礎(chǔ)上實(shí)現(xiàn)對(duì)于 Swagger 的配置。
Install-Package Swashbuckle.AspNetCore與上面配置 API 接口的版本信息相似,這里我依舊采用構(gòu)建擴(kuò)展方法的方式來實(shí)現(xiàn)對(duì)于 Swagger 中間件的配置。具體的配置過程可以查看我之前寫的文章(ASP.NET Core 實(shí)戰(zhàn):構(gòu)建帶有版本控制的 API 接口),這里只列出最終配置完成的代碼。
public static void AddSwagger(this IServiceCollection services)
{
// 配置 Swagger 文檔信息
services.AddSwaggerGen(s =>
{
// 根據(jù) API 版本信息生成 API 文檔
//
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
s.SwaggerDoc(description.GroupName, new Info
{
Contact = new Contact
{
Name = "Danvic Wang",
Email = "[email protected]",
Url = "https://yuiter.com"
},
Description = "Ingos.API 接口文檔",
Title = "Ingos.API",
Version = description.ApiVersion.ToString()
});
}
// 在 Swagger 文檔顯示的 API 地址中將版本信息參數(shù)替換為實(shí)際的版本號(hào)
s.DocInclusionPredicate((version, apiDescription) =>
{
if (!version.Equals(apiDescription.GroupName))
return false;
var values = apiDescription.RelativePath
.Split( / )
.Select(v => v.Replace("v{version}", apiDescription.GroupName)); apiDescription.RelativePath = string.Join("/", values);
return true;
});
// 參數(shù)使用駝峰命名方式
s.DescribeAllParametersInCamelCase();
// 取消 API 文檔需要輸入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
// 獲取接口文檔描述信息
var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
s.IncludeXmlComments(apiPath, true);
});
}當(dāng)我們配置完成后就可以在 Startup 類中去啟用 Swagger 文檔。
public void ConfigureServices(IServiceCollection services)
{
// 添加對(duì)于 swagger 文檔的支持
services.AddSwagger();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
// 啟用 Swagger 文檔
app.UseSwagger();
app.UseSwaggerUI(s =>
{
// 默認(rèn)加載最新版本的 API 文檔
foreach (var description in provider.ApiVersionDescriptions.Reverse())
{ s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
$"Sample API {description.GroupName.ToUpperInvariant()}");
}
});
}
因?yàn)槲覀冊(cè)谥霸O(shè)置構(gòu)建的 API 路由時(shí)包含了版本信息,所以在最終生成的 Swagger 文檔中進(jìn)行測試時(shí),我們都需要在參數(shù)列表中添加 API 版本這個(gè)參數(shù)。這無疑是有些不方便,所以這里我們可以通過繼承 IOperationFilter 接口,控制在生成 API 文檔時(shí)移除 API 版本參數(shù),接口的實(shí)現(xiàn)方法如下所示。
public class RemoveVersionFromParameter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters.Single(p => p.Name == "version");
operation.Parameters.Remove(versionParameter);
}
}當(dāng)我們實(shí)現(xiàn)自定義的接口后就可以在之前針對(duì) Swagger 的擴(kuò)展方法中調(diào)用這個(gè)過濾方法,從而實(shí)現(xiàn)移除版本信息的目的,擴(kuò)展方法中的添加位置如下所示。
public static void AddSwagger(this IServiceCollection services)
{
// 配置 Swagger 文檔信息
services.AddSwaggerGen(s =>
{
// 取消 API 文檔需要輸入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
});
}最終的實(shí)現(xiàn)效果如下圖所示,可以看到,參數(shù)列表中已經(jīng)沒有版本信息這個(gè)參數(shù),但是我們?cè)谶M(jìn)行接口測試時(shí)會(huì)自動(dòng)幫我們添加上版本參數(shù)信息。

這里需要注意,因?yàn)槲覀冃枰谧罱K生成的 Swagger 文檔中顯示出我們對(duì)于 Controller 或是 Action 添加的注釋信息,所以這里我們需要在 Web Api 項(xiàng)目的屬性選項(xiàng)中勾選上輸出 XML 文檔文件。同時(shí)如果你不想 VS 一直提示你有方法沒有添加參數(shù)信息,這里我們可以在取消顯示警告這里添加上 1591 這個(gè)參數(shù)。

5、構(gòu)建符合 Restful 風(fēng)格的接口
在沒有采用 Restful 風(fēng)格來構(gòu)建接口返回值時(shí),我們可能會(huì)習(xí)慣于在接口返回的信息中添加一個(gè)接口是否請(qǐng)求成功的標(biāo)識(shí),就像下面代碼中示例的這種返回形式。
{
sueecss: true
msg: ,
data: [{
id: 20190720214402 ,
name: zhangsan
}]
}但是,當(dāng)我們想要構(gòu)建符合 Restful 風(fēng)格的接口時(shí),我們就不能再這樣進(jìn)行設(shè)計(jì)了,我們應(yīng)該通過返回的 HTTP 響應(yīng)狀態(tài)碼來標(biāo)識(shí)這次訪問是否成功。一些比較常用的 HTTP 狀態(tài)碼如下表所示。

我們知道 HTTP 共有四個(gè)謂詞方法,分別為 Get、Post、Put 和 Delete,在之前我們可能更多的是使用 Get 和 Post,對(duì)于 Put 和 Delete 方法可能并不會(huì)使用。
同樣的,如果我們需要?jiǎng)?chuàng)建符合 Restful 風(fēng)格的接口,我們則需要根據(jù)這四個(gè) HTTP 方法謂詞一些約定俗成的功能定義去定義對(duì)應(yīng)接口的 HTTP 方法。

例如,對(duì)于一個(gè)獲取所有資源的方法,我們可能會(huì)定義接口的默認(rèn)返回 HTTP 狀態(tài)碼為 200 或是 400,當(dāng)狀態(tài)碼為 200 時(shí),代表數(shù)據(jù)獲取成功,接口可以正常返回?cái)?shù)據(jù),當(dāng)狀態(tài)碼為 400 時(shí),則代表接口訪問出現(xiàn)問題,此時(shí)則返回錯(cuò)誤信息對(duì)象。
在 ASP.NET Core Web API 中,我們可以通過在Action上添加 ProducesResponseType 特性來定義接口的返回狀態(tài)碼。通過 F12 按鍵我們可以進(jìn)入 ProducesResponseType 這個(gè)特性,可以看到這個(gè)特性存在兩個(gè)構(gòu)造方法,我們可以只定義接口返回 HTTP 狀態(tài)碼或者是在定義接口返回的狀態(tài)碼時(shí)同時(shí)返回的具體對(duì)象信息。
上面給出的接口案例的示例代碼如下所示,從下圖中可以看到,Swagger 會(huì)自動(dòng)根據(jù)我們的 ProducesResponseType 特性來列出我們接口可能返回的 HTTP 狀態(tài)碼和對(duì)象信息。這里因?yàn)槭鞘纠绦?,UserListDto 并沒有定義具體的屬性信息,所以這里顯示的是一個(gè)不包含任何屬性的對(duì)象數(shù)組。
/// <summary>
/// 獲取全部的用戶信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get()
{
// 1、獲取資源數(shù)據(jù)
// 2、判斷數(shù)據(jù)獲取是否成功
if (true)
return Ok(new List<UserListDto>());
else
return BadRequest(new
{
statusCode = StatusCodes.Status400BadRequest,
description = "錯(cuò)誤描述",
msg = "錯(cuò)誤信息"
});
}
可能這里你可能會(huì)有疑問,當(dāng)接口返回的 HTTP 狀態(tài)碼為 400 時(shí),返回的信息是什么鬼,與我們定義的錯(cuò)誤信息對(duì)象字段不同???原來,在 ASP.NET Core 2.1 之后的版本中,對(duì)于 API 接口返回 400 的 HTPP 狀態(tài)碼會(huì)默認(rèn)返回 ProblemDetails 對(duì)象,因?yàn)檫@里我們并沒有將接口中的返回 BadRequest 中的錯(cuò)誤信息對(duì)象作為 ProducesResponseType 特性的構(gòu)造函數(shù)的參數(shù),所以這里就采用了默認(rèn)的錯(cuò)誤信息對(duì)象。
當(dāng)然,當(dāng)接口的 HTTP 返回狀態(tài)碼為 400 時(shí),最終還是會(huì)返回我們自定義的錯(cuò)誤信息對(duì)象,所以這里為了不造成前后端對(duì)接上的歧義,我們最好將返回的對(duì)象信息也作為參數(shù)添加到 ProducesResponseType 特性中。

同時(shí),除了上面示例的接口中通過返回 OK 方法和 BadRequest 方法來表明接口的返回 HTTP 狀態(tài)碼,在 ASP.NET Core Web API 中還有下列繼承于 ObjectResult 的方法來表明接口返回的狀態(tài)碼,對(duì)應(yīng)信息如下。

6、使用 Web API 分析器
在上面的示例中,因?yàn)槲覀冃枰付ń涌谛枰祷氐?HTTP 狀態(tài)碼,所以我們需要提前添加好 ProducesResponseType 特性,在某些時(shí)候我們可能在代碼中添加了一種 HTTP 狀態(tài)碼的返回結(jié)果,可是卻忘了添加特性描述,那么有沒有一種便捷的方式提示我們呢?
在 ASP.NET Core 2.2 及以后更新的 ASP.NET Core 版本中,我們可以通過 Nuget 去添加 Microsoft.AspNetCore.Mvc.Api.Analyze 這個(gè)包,從而實(shí)現(xiàn)對(duì)我們的 API 進(jìn)行分析,首先我們需要將這個(gè)包添加到我們的 API 項(xiàng)目中。
Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers例如在下面的接口代碼中,我們根據(jù)用戶的唯一標(biāo)識(shí)去尋找用戶數(shù)據(jù),當(dāng)獲取不到數(shù)據(jù)的時(shí)候,返回的 HTTP 狀態(tài)碼為 400,而我們只添加了 HTTP 狀態(tài)碼為 200 的特性說明。此時(shí),分析器將 HTTP 404 狀態(tài)代碼的缺失特性說明做為一個(gè)警告,并提供了修復(fù)此問題的選項(xiàng),我們進(jìn)行修復(fù)后就可以自動(dòng)添加特性。
/// <summary>
/// 獲取用戶詳細(xì)信息
/// </summary>
/// <param name="id">用戶唯一標(biāo)識(shí)</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
// 1、根據(jù) Id 獲取用戶信息
UserEditDto user = null;
if (user == null)
return NotFound();
else
return Ok(user);
}
但是,在自動(dòng)完成文檔補(bǔ)全后其實(shí)還是需要我們進(jìn)行一些操作的,例如,如果我們需要指定返回值的 Type 類型,還是需要我們自己手動(dòng)添加到 ProducesResponseType 特性上的。
在進(jìn)行特性補(bǔ)齊的時(shí)候,分析器也幫我們填加了一個(gè)ProducesDefaultResponseType特性。
通過在微軟的文檔中指向的 Swagger 文檔(Swagger Default Response)中可以了解到,如果我們接口不管是什么狀態(tài),最終返回的 response 響應(yīng)結(jié)構(gòu)都是相同的,我們就可以直接使用 ProducesDefaultResponseType 特性來指定 response 的響應(yīng)結(jié)構(gòu),而不需要每個(gè) HTTP 狀態(tài)都添加一個(gè)特性。
三、總結(jié)
在本篇文章中,主要介紹了一些我在使用 ASP.NET Core Web API 的過程中使用到的一些小技巧,以及在以前踩過坑后的一些解決方案,如果對(duì)你能有一點(diǎn)的幫助的話,不勝榮幸。同時(shí),如果你有更好的解決方案,或者是針對(duì)一些你之前踩過的 Web API 坑的解決方案,也歡迎你在評(píng)論區(qū)中提出


臥槽!微信可以改彩色昵稱了!??!

臥槽,微信狀態(tài)被玩壞了,附大量微信狀態(tài)視屏素材!
