当前位置: 首页 > news >正文

成品网站源码的优化技巧宏福建设工程有限公司网站

成品网站源码的优化技巧,宏福建设工程有限公司网站,wordpress目录分页,广告设计培训学校本文提供了 ASP.NET Core 的性能最佳实践指南。译文原文地址#xff1a;https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?viewaspnetcore-3.1积极利用缓存这里有一篇文档在多个部分中讨论了如何积极利用缓存。有关详细信息#xff0c… 本文提供了 ASP.NET Core 的性能最佳实践指南。译文原文地址https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?viewaspnetcore-3.1积极利用缓存这里有一篇文档在多个部分中讨论了如何积极利用缓存。有关详细信息请参阅︰ https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?viewaspnetcore-3.1.了解代码中的热点路径在本文档中 代码热点路径 定义为频繁调用的代码路径以及执行时间的大部分时间。代码热点路径通常限制应用程序的扩展和性能并在本文档的多个部分中进行讨论。避免阻塞式调用ASP.NET Core 应用程序应设计为同时处理许多请求。异步 API 可以使用一个小池线程通过非阻塞式调用来处理数以千计的并发请求。线程可以处理另一个请求而不是等待长时间运行的同步任务完成。ASP.NET Core 应用程序中的常见性能问题通常是由于那些本可以异步调用但却采用阻塞时调用而导致的。同步阻塞会调用导致 线程池饥饿 和响应时间降级。不要:通过调用 Task.Wait 或 Task.Result 来阻止异步执行。在公共代码路径中加锁。ASP.NET Core 应用程序应设计为并行运行代码如此才能使得性能最佳。调用 Task.Run 并立即 await 。ASP.NET Core 本身已经是在线程池线程上运行应用程序代码了因此这样调用 Task.Run 只会导致额外的不必要的线程池调度。而且即使被调度的代码会阻止线程 Task.Run 也并不能避免这种情况这样做没有意义。要:确保 代码热点路径 全部异步化。如在进行调用数据读写、I/O 处理和长时间操作的 API 时存在可用的异步 API。那么务必选择异步 API 。但是不要 使用 Task.Run 来包装同步 API 使其异步化。确保 controller/Razor Page actions 异步化。整个调用堆栈是异步的就可以利用 async/await 模式的性能优势。使用性能分析程序 ( 例如 PerfView) 可用于查找频繁添加到 线程池 的线程。 Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start 事件表示新线程被添加到线程池。使用 IEumerableT 或 IAsyncEnumerableT 作为返回值在 Action 中返回 IEumerableT 将会被序列化器中进行同步迭代 。结果是可能导致阻塞或者线程池饥饿。想要要避免同步迭代集合可以在返回迭代集合之前使用 ToListAsync 使其异步化。从 ASP.NET Core 3.0 开始 IAsyncEnumerableT 可以用作为 IEumerableT 的替代方法以异步方式进行迭代。有关更多信息请参阅 Controller Action 的返回值类型。尽可能少的使用大对象.NET Core 垃圾收集器 在 ASP.NET Core 应用程序中起到自动管理内存的分配和释放的作用。自动垃圾回收通常意味着开发者不需要担心如何或何时释放内存。但是清除未引用的对象将会占用 CPU 时间因此开发者应最小化 代码热点路径 中的分配的对象。垃圾回收在大对象上代价特大 ( 85 K 字节) 。大对象存储在 large object heap 上需要 full (generation 2) garbage collection 来清理。与 generation 0 和 generation 1 不同generation 2 需要临时暂挂应用程序。故而频繁分配和取消分配大型对象会导致性能耗损。建议 :要 考虑缓存频繁使用的大对象。缓存大对象可防止昂贵的分配开销。要使用 ArrayPoolT 作为池化缓冲区以保存大型数组。不要 在代码热点路径 上分配许多短生命周期的大对象。可以通过查看 PerfView 中的垃圾回收 (GC) 统计信息来诊断并检查内存问题其中包括:垃圾回收挂起时间。垃圾回收中耗用的处理器时间百分比。有多少垃圾回收发生在 generation 0, 1, 和 2.有关更多信息请参阅 垃圾回收和性能。优化数据操作和 I/O与数据存储器和其他远程服务的交互通常是 ASP.NET Core 应用程序最慢的部分。高效读取和写入数据对于良好的性能至关重要。建议 :要 以异步方式调用所有数据访问 API 。不要 读取不需要的数据。编写查询时仅返回当前 HTTP 请求所必需的数据。要 考虑缓存从数据库或远程服务检索的频繁访问的数据 (如果稍微过时的数据是可接受的话) 。根据具体的场景可以使用 MemoryCache 或 DistributedCache。有关更多信息请参阅 https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?viewaspnetcore-3.1.要 尽量减少网络往返。能够单次调用完成就不应该多次调用来读取所需数据。要 在 Entity Framework Core 访问数据以用作只读情况时 使用 no-tracking 方式查询。EF Core 可以更高效地返回 no-tracking 查询的结果。要 使用过滤器和聚集 LINQ 查询 (例如 .Where .Select 或 .Sum 语句) 以便数据库执行过滤提高性能 。要 考虑 EF Core 可能在客户端解析一些查询运算符这可能导致查询执行效率低下。有关更多信息请参阅 客户端计算相关的性能问题。不要 在集合上使用映射查询这会导致执行 “N 1” SQL 查询。有关更多信息请参阅 优化子查询。请参阅 EF 高性能专题 以了解可能提高应用性能的方法:DbContext 池显式编译的查询在代码提交之前我们建议评估上述高性能方法的影响。编译查询的额外复杂性可能无法一定确保性能提高。可以通过使用 Application Insights 或使用分析工具查看访问数据所花费的时间来检测查询问题。大多数数据库还提供有关频繁执行的查询的统计信息这也可以作为重要参考。通过 HttpClientFactory 建立 HTTP 连接池虽然 HttpClient 实现了 IDisposable 接口但它其实被设计为可以重复使用单个实例。关闭 HttpClient 实例会使套接字在短时间内以 TIME_WAIT 状态打开。如果经常创建和释放 HttpClient 对象那么应用程序可能会耗尽可用套接字。在 ASP.NET Core 2.1 中引入了 HttpClientFactory 作为解决这个问题的办法。它以池化 HTTP 连接的方式从而优化性能和可靠性。建议 :不要 直接创建和释放 HttpClient 实例。要 使用 HttpClientFactory 来获取 HttpClient 实例。有关更多信息请参阅 使用 HttpClientFactory 以实现弹性 HTTP 请求。确保公共代码路径快若鹰隼如果你想要所有的代码都保持高速 高频调用的代码路径就是优化的最关键路径。优化措施包括:考虑优化应用程序请求处理管道中的 Middleware 尤其是在管道中排在更前面运行的 Middleware 。这些组件对性能有很大影响。考虑优化那些每个请求都要执行或每个请求多次执行的代码。例如自定义日志身份认证与授权或 transient 服务的创建等等。建议 :不要 使用自定义 middleware 运行长时任务 。要 使用性能分析工具 ( 如 Visual Studio Diagnostic Tools 或 PerfView) 来定位 代码热点路径。在 HTTP 请求之外运行长时任务对 ASP.NET Core 应用程序的大多数请求可以由调用服务的 controller 或页面模型处理并返回 HTTP 响应。对于涉及长时间运行的任务的某些请求最好使整个请求 - 响应进程异步。建议 :不要把等待长时间运行的任务完成作为普通 HTTP 请求处理的一部分。要 考虑使用 后台服务 或 Azure Function 处理长时间运行的任务。在应用外执行任务特别有利于 CPU 密集型任务的性能。要 使用实时通信如 SignalR以异步方式与客户端通信。缩小客户端资源复杂的 ASP.NET Core 应用程序经常包含很有前端文件例如 JavaScript CSS 或图片文件。可以通过以下方法优化初始请求的性能:打包将多个文件合并为一个文件。压缩通过除去空格和注释来缩小文件大小。建议 :要 使用 ASP.NET Core 的 内置支持 用于打包和压缩客户端资源文件的组件。要 考虑其他第三方工具如 Webpack用于复杂客户资产管理。压缩 Http 响应减少响应的大小通常会显着提高应用程序的响应性。而减小内容大小的一种方法是压缩应用程序的响应。有关更多信息请参阅 响应压缩。使用最新的 ASP.NET Core 发行版ASP.NET Core 的每个新发行版都包含性能改进。.NET Core 和 ASP.NET Core 中的优化意味着较新的版本通常优于较旧版本。例如 .NET Core 2.1 添加了对预编译的正则表达式的支持并从使用 SpanT 改进性能。ASP.NET Core 2.2 添加了对 HTTP/2 的支持。 ASP.NET Core 3.0 增加了许多改进 以减少内存使用量并提高吞吐量。如果性能是优先考虑的事情那么请升级到 ASP.NET Core 的当前版本。最小化异常异常应该尽可能少。 相对于正常代码流程来说抛出和捕获异常是缓慢的。 因此不应使用异常来控制正常程序流。建议 :不要 使用抛出或捕获异常作为正常程序流的手段特别是在 代码热点路径 中。要 在应用程序中包含用于检测和处理导致异常的逻辑。要 对意外的执行情况抛出或捕获异常。应用程序诊断工具 (如 Application Insights) 可以帮助识别应用程序中可能影响性能的常见异常。性能和可靠性下文将提供常见性能提示和已知可靠性问题的解决方案。避免在 HttpRequest/HttpResponse body 上同步读取或写入ASP.NET Core 中的所有 I/O 都是异步的。服务器实现了 Stream 接口它同时具有同步和异步的方法重载。应该首选异步方式以避免阻塞线程池线程。阻塞线程会导致线程池饥饿。不要使用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEnd。它会阻止当前线程等待结果。这是 sync over async 的示例。public class BadStreamReaderController : Controller {[HttpGet(/contoso)]public ActionResultContosoData Get(){var json new StreamReader(Request.Body).ReadToEnd();return JsonSerializer.DeserializeContosoData(json);} } 在上述代码中 Get 采用同步的方式将整个 HTTP 请求主体读取到内存中。如果客户端上载数据很慢那么应用程序就会出现看似异步实际同步的操作。应用程序看似异步实际同步因为 Kestrel 不 支持同步读取。应该采用如下操作: https://docs.microsoft.com/en-us/dotnet/api/System.IO.StreamReader.ReadToEndAsync 在读取时不阻塞线程。public class GoodStreamReaderController : Controller {[HttpGet(/contoso)]public async TaskActionResultContosoData Get(){var json await new StreamReader(Request.Body).ReadToEndAsync();return JsonSerializer.DeserializeContosoData(json);}} 上述代码异步将整个 HTTP request body 读取到内存中。[!WARNING] 如果请求很大那么将整个 HTTP request body 读取到内存中可能会导致内存不足 (OOM) 。OOM 可导致应用奔溃。有关更多信息请参阅 避免将大型请求主体或响应主体读取到内存中。应该采用如下操作: 使用不缓冲的方式完成 request body 操作:public class GoodStreamReaderController : Controller {[HttpGet(/contoso)]public async TaskActionResultContosoData Get(){return await JsonSerializer.DeserializeAsyncContosoData(Request.Body);} } 上述代码采用异步方式将 request body 序列化为 C# 对象。优先选用 Request.Form 的 ReadFormAsync应该使用 HttpContext.Request.ReadFormAsync 而不是 HttpContext.Request.Form。 HttpContext.Request.Form 只能在以下场景用安全使用。该表单已被 ReadFormAsync 调用并且数据已经被从 HttpContext.Request.Form 读取并缓存不要使用如下操作: 例如以下方式使用 HttpContext.Request.Form。 HttpContext.Request.Form 使用了 sync over async 这将导致线程饥饿.public class BadReadController : Controller {[HttpPost(/form-body)]public IActionResult Post(){var form HttpContext.Request.Form;Process(form[id], form[name]);return Accepted();} 应该使用如下操作: 使用 HttpContext.Request.ReadFormAsync 异步读取表单正文。public class GoodReadController : Controller {[HttpPost(/form-body)]public async TaskIActionResult Post(){var form await HttpContext.Request.ReadFormAsync();Process(form[id], form[name]);return Accepted();} 避免将大型 request body 或 response body 读取到内存中在 .NET 中大于 85 KB 的对象会被分配在大对象堆 (LOH )。大型对象的开销较大包含两方面:分配大对象内存时需要对被分配的内存进行清空这个操作成本较高。CLR 会保证清空所有新分配的对象的内存。将内存全部设置为 0LOH 只会在内存剩余不足时回收。LOH 需要在 full garbage collection 或者 Gen2 collection 进行回收。此 博文 很好描述了该问题:当分配大对象时它会被标记为 Gen 2 对象。而不像是 Gen 0 那样的小对象。这样的后果是如果你在使用 LOH 时耗尽内存 GC 会清除整个托管堆而不仅仅是 LOH 部分。因此它将清理 Gen 0, Gen 1 and Gen 2 (包括 LOH) 。这称为 full garbage collection是最耗时的垃圾回收。对于很多应用这是可以接受的。但绝对不适用于高性能 Web 服务器因为高性能 Web 服务器需要更多的内存用于处理常规 Web 请求 ( 从套接字读取解压缩解码 JSON 等等 )。天真地将一个大型 request 或者 response body 存储到单个 byte[] 或 string 中:这可能导致 LOH 的剩余空间快速耗尽。因此产生的 full GC 可能会导致应用程序的性能问题。使用同步 API 处理数据例如使用仅支持同步读取和写入的序列化器 / 反序列化器时 ( 例如 JSON.NET):将数据异步缓冲到内存中然后将其传递到序列化器 / 反序列化器。[!WARNING] 如果请求较大那么可能导致内存不足 (OOM) 。OOM 可导致应用奔溃。有关更多信息请参阅 避免将大型请求主体或响应主体读取到内存。ASP.NET Core 3.0 默认情况下使用 https://docs.microsoft.com/en-us/dotnet/api/system.text.json 进行 JSON 序列化这将带来如下好处。 https://docs.microsoft.com/en-us/dotnet/api/system.text.json:异步读取和写入 JSON 。针对 UTF-8 文本进行了优化。通常比 Newtonsoft.Json 更高的性能。不要将 IHttpContextAccessor.HttpContext 存储在字段中IHttpContextAccessor.HttpContext 返回当前请求线程中的 HttpContext. IHttpContextAccessor.HttpContext** 不应该 ** 被存储在一个字段或变量中。不要使用如下操作: 例如将 HttpContext 存储在字段中然后在后续使用该字段。public class MyBadType {private readonly HttpContext _context;public MyBadType(IHttpContextAccessor accessor){_context accessor.HttpContext;}public void CheckAdmin(){if (!_context.User.IsInRole(admin)){throw new UnauthorizedAccessException(The current user isnt an admin);}} } 以上代码在构造函数中经常得到 Null 或不正确的 HttpContext。应该采用如下操作:在字段中保存 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.AspNetCore.Http.IHttpContextAccessor?viewaspnetcore-3.1。在恰当的时机获取并使用 HttpContext 并检查是否为 null。public class MyGoodType {private readonly IHttpContextAccessor _accessor;public MyGoodType(IHttpContextAccessor accessor){_accessor accessor;}public void CheckAdmin(){var context _accessor.HttpContext;if (context ! null !context.User.IsInRole(admin)){throw new UnauthorizedAccessException(The current user isnt an admin);}} } 不要尝试在多线程下使用 HttpContextHttpContext 不是 线程安全的。从多个线程并行访问 HttpContext 可能会导致不符预期的行为例如线程挂起崩溃和数据损坏。不要使用如下操作: 以下示例将发出三个并行请求并在 HTTP 请求之前和之后记录传入的请求路径。请求路径将被多个线程 (可能并行) 访问。public class AsyncBadSearchController : Controller {[HttpGet(/search)]public async TaskSearchResults Get(string query){var query1 SearchAsync(SearchEngine.Google, query);var query2 SearchAsync(SearchEngine.Bing, query);var query3 SearchAsync(SearchEngine.DuckDuckGo, query);await Task.WhenAll(query1, query2, query3);var results1 await query1;var results2 await query2;var results3 await query3;return SearchResults.Combine(results1, results2, results3);}private async TaskSearchResults SearchAsync(SearchEngine engine, string query){var searchResults _searchService.Empty();try{_logger.LogInformation(Starting search query from {path}.,HttpContext.Request.Path);searchResults _searchService.Search(engine, query);_logger.LogInformation(Finishing search query from {path}.,HttpContext.Request.Path);}catch (Exception ex){_logger.LogError(ex, Failed query from {path},HttpContext.Request.Path);}return await searchResults;} 应该这样操作: 以下示例在发出三个并行请求之前从传入请求复制下文需要使用的数据。public class AsyncGoodSearchController : Controller {[HttpGet(/search)]public async TaskSearchResults Get(string query){string path HttpContext.Request.Path;var query1 SearchAsync(SearchEngine.Google, query,path);var query2 SearchAsync(SearchEngine.Bing, query, path);var query3 SearchAsync(SearchEngine.DuckDuckGo, query, path);await Task.WhenAll(query1, query2, query3);var results1 await query1;var results2 await query2;var results3 await query3;return SearchResults.Combine(results1, results2, results3);}private async TaskSearchResults SearchAsync(SearchEngine engine, string query,string path){var searchResults _searchService.Empty();try{_logger.LogInformation(Starting search query from {path}.,path);searchResults await _searchService.SearchAsync(engine, query);_logger.LogInformation(Finishing search query from {path}., path);}catch (Exception ex){_logger.LogError(ex, Failed query from {path}, path);}return await searchResults;} 请求处理完成后不要使用 HttpContextHttpContext 只有在 ASP.NET Core 管道处理活跃的 HTTP 请求时才可用。整个 ASP.NET Core 管道是由异步代理组成的调用链用于处理每个请求。当 Task 从调用链完成并返回时HttpContext 就会被回收。不要进行如下操作: 以下示例使用 async void 这将使得 HTTP 请求在第一个 await 时处理完成进而就会导致:在 ASP.NET Core 应用程序中 这是一个完全错误 的做法在 HTTP 请求完成后访问 HttpResponse。进程崩溃。public class AsyncBadVoidController : Controller {[HttpGet(/async)]public async void Get(){await Task.Delay(1000);// The following line will crash the process because of writing after the// response has completed on a background thread. Notice async void Get()await Response.WriteAsync(Hello World);} } 应该进行如下操作: 以下示例将 Task 返回给框架因此在操作完成之前 HTTP 请求不会完成。public class AsyncGoodTaskController : Controller {[HttpGet(/async)]public async Task Get(){await Task.Delay(1000);await Response.WriteAsync(Hello World);} } 不要在后台线程中使用 HttpContext不要使用如下操作: 以下示例使用一个闭包从 Controller 属性读取 HttpContext。这是一种错误做法因为这将导致:代码运行在 Http 请求作用域之外。尝试读取错误的 HttpContext。[HttpGet(/fire-and-forget-1)] public IActionResult BadFireAndForget() {_ Task.Run(async () {await Task.Delay(1000);var path HttpContext.Request.Path;Log(path);});return Accepted(); } 应该采用如下操作:在请求处理阶段将后台线程需要的数据全部进行复制。不要使用 controller 的所有引用[HttpGet(/fire-and-forget-3)] public IActionResult GoodFireAndForget() {string path HttpContext.Request.Path;_ Task.Run(async () {await Task.Delay(1000);Log(path);});return Accepted(); } 后台任务最好采用托管服务进行操作。有关更多信息请参阅 采用托管服务运行后台任务 。不要在后台线程获取注入到 controller 中的服务不要采用如下做法: 以下示例使用闭包从 controller 获取 DbContext 进行操作。这是一个错误的做法。这将导致代码云在请求的作用域之外。而 ContocoDbContext 是基于请求作用域的因此这样将引发 ObjectDisposedException。[HttpGet(/fire-and-forget-1)] public IActionResult FireAndForget1([FromServices]ContosoDbContext context) {_ Task.Run(async () {await Task.Delay(1000);context.Contoso.Add(new Contoso());await context.SaveChangesAsync();});return Accepted(); } 应该采用如下操作:注入 https://docs.microsoft.com/en-us/aspnet/core/Microsoft.Extensions.DependencyInjection.IServiceScopeFactory?viewaspnetcore-3.1 并且在后台线程中创建新的作用域。 IServiceScopeFactory 是一个单例对象所以这样没有问题。在后台线程中创建新作用域注入依赖的服务。不要引用 controller 的所有内容不要从请求中读取 ContocoDbContext。[HttpGet(/fire-and-forget-3)] public IActionResult FireAndForget3([FromServices]IServiceScopeFactoryserviceScopeFactory) {_ Task.Run(async () {await Task.Delay(1000);using (var scope serviceScopeFactory.CreateScope()){var context scope.ServiceProvider.GetRequiredServiceContosoDbContext();context.Contoso.Add(new Contoso());await context.SaveChangesAsync();}});return Accepted(); } 以下高亮的的代码说明:为后台操作创建新的作用域并且从中获取需要的服务。在正确的作用域中使用 ContocoDbContext即只能在请求作用域中使用该对象。[HttpGet(/fire-and-forget-3)] public IActionResult FireAndForget3([FromServices]IServiceScopeFactoryserviceScopeFactory) {_ Task.Run(async () {await Task.Delay(1000);using (var scope serviceScopeFactory.CreateScope()){var context scope.ServiceProvider.GetRequiredServiceContosoDbContext();context.Contoso.Add(new Contoso());await context.SaveChangesAsync();}});return Accepted(); } 不要在响应正文已经开始发送时尝试修改 status code 或者 headerASP.NET Core 不会缓冲 HTTP 响应正文。当正文一旦开始发送:Header 就会与正文的数据包一起发送到客户端。此时就无法修改 header 了。不要使用如下操作: 以下代码尝试在响应启动后添加响应头:app.Use(async (context, next) {await next();context.Response.Headers[test] test value; }); 在上述的代码中如果 next() 已经开始写入响应则 context.Response.Headers[test] test value; 将会抛出异常。应该采用如下操作: 以下示例检查 HTTP 响应在修改 Header 之前是否已启动。app.Use(async (context, next) {await next();if (!context.Response.HasStarted){context.Response.Headers[test] test value;} }); 应该采用如下操作: 以下示例使用 HttpResponse.OnStarting 来设置 Header这样便可以在响应启动时将 Header 一次性写入到客户端。通过这种方式响应头将在响应开始时调用已注册的回调进行一次性写入。如此这般便可以:在恰当的时候进行响应头的修改或者覆盖。不需要了解管道中的下一个 middleware 的行为。app.Use(async (context, next) {context.Response.OnStarting(() {context.Response.Headers[someheader] somevalue;return Task.CompletedTask;});await next(); }); 如果已开始写入响应主体则请不要调用 next ()仅当后续组件能够处理响应或时才调用它们因此如果当前已经开始写入响应主体后续操作就已经不再需要并有可能引发异常情况。托管于 IIS 应该使用 In-process 模式使用 in-process 模式托管 ASP.NET Core 应用程序将与 IIS 工作进程在同一进程中运行。In-process 模式拥有比 out-of-process 更加优秀的性能表现因为这样不需要将请求通过回环网络适配器进行代理中转。回环网络适配器是将本机发送的网络流量重新转回本机的的网络适配器。IIS 进程管理由 Windows Process Activation Service (WAS) 来完成。在 ASP.NET Core 3.0 和更高版本中的默认将采用 in-process 模式进行托管。有关更多信息请参阅 在 Windows 上使用 IIS 托管 ASP.NET CoreNewbe.Translations您所阅读的当前文章源自于 Newbe.Translations 项目参与的翻译贡献您可以通过右侧链接一同参与该项目https://www.newbe.pro/Newbe.Translations/Newbe.Translations/。翻译内容具有一定的时效性不会随着原文内容实时更新如果内容存在一定过时您也可以联系我们。本文作者 newbe36524本文链接 https://www.newbe.pro/Newbe.Translations/001-ASP.NET-Core-Performance-Best-Practices/版权声明 本博客所有文章除特别声明外均采用 BY-NC-SA 许可协议。转载请注明出处
http://www.zqtcl.cn/news/868747/

相关文章:

  • 网站顾客评价网站中怎么做网站统计
  • 网站建设安全措施表白网站是怎么做的
  • 一个服务器可以做几个网站百度北京公司地址全部
  • 武侯区网站建设哪里好点个人社保缴费比例是多少
  • 创建属于自己的网站定制应用软件有哪些
  • 网站建设类岗位建设工程施工合同示范文本2023最新版
  • 建站设计公司wordpress 跨域 cros
  • 做网站的公司哪好工程设计东莞网站建设技术支持
  • 虹口网站开发开发公司设计管理部绩效考核
  • 网站改版升级通知国外服务器公司有哪些
  • 做网站的s标的软件深圳网站建设 湖南岚鸿
  • 设计网站建设的合同书公司装修合同范本
  • 门户网站的好处企业邮箱系统
  • 重庆企业建站公司有那些网站做结伴旅游的
  • 创意营销策划案例网站网页制作及优化
  • 网站上动画视频怎么做的建设兵团12师教育局网站
  • 博客网站开发思维导图app网站制作公司
  • 池州网站建设有哪些公司兴义网站seo
  • seo优化网站模板网站建设的七大优缺点
  • 天猫国际采取的跨境电商网络营销方式关键词排名优化公司推荐
  • 亳州建设网站做网站文字怎么围绕图片
  • 网站开发 项目计划外链建设给网站起的作用
  • 你好南京网站网站开发实施步骤和说明
  • 文化共享工程网站建设情况wordpress菠菜插件
  • 网站大气是什么意思哈尔滨做网站电话
  • 公司网站站群是什么化妆品网站设计欣赏
  • 网站公司未来计划ppt怎么做平潭做网站
  • 做网站和推广工资多少招聘网站建设价格
  • 网站建设 响应式 北京网架公司十大排名榜
  • 网站推广目标关键词是什么意思网站推广软件工具