【WebApi】C#创建WebApi学习

1、创建WebApi项目

Prgram.cs代码保留如下

var builder = WebApplication.CreateBuilder(args); // Add services to the container. var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.Run(); 

2、Minmal APIs最小API使用

Prgram.cs中进行最小API使用

var builder = WebApplication.CreateBuilder(args); // Add services to the container. var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); //获取所有衬衫数据列表 app.MapGet("/shirts", () => { return "获取所有衬衫数据列表"; }); //获取指定ID的衬衫数据 app.MapGet("/shirts/{id}", (int id) => { return $"获取ID为 {id} 的衬衫数据"; }); //创建一件新的衬衫 app.MapPost("/shirts", () => { return "创建一件新的衬衫"; }); //更新指定ID的衬衫数据 app.MapPut("/shirts/{id}", (int id) => { return $"更新ID为 {id} 的衬衫数据"; }); //删除指定ID的衬衫数据 app.MapDelete("/shirts/{id}", (int id) => { return $"删除ID为 {id} 的衬衫数据"; }); app.Run(); 

3、ASP.NET Core中间件管道

app中的Use开头的方法都是中间件组件使用方法

4、Web API控制器实现Web API

方法1:将路由写在方法前面并指定操作动词

using Microsoft.AspNetCore.Mvc; namespace WebApiTest.Controllers { [ApiController] public class ShirtsController : ControllerBase { //获取所有衬衫数据列表 [HttpGet] [Route("api/shirts")] public string GetShirts() { return "获取所有衬衫数据列表"; } //获取指定ID的衬衫数据 [HttpGet] [Route("api/shirts/{id}")] public string GetShirtById(int id) { return $"获取ID为 {id} 的衬衫数据"; } //创建一件新的衬衫 [HttpPost] [Route("api/shirts")] public string CreateShirt() { return "创建一件新的衬衫"; } //更新指定ID的衬衫数据 [HttpPut] [Route("api/shirts/{id}")] public string UpdateShirt(int id) { return $"更新ID为 {id} 的衬衫数据"; } //删除指定ID的衬衫数据 [HttpDelete] [Route("api/shirts/{id}")] public string DeleteShirt(int id) { return $"删除ID为 {id} 的衬衫数据"; } } } 

方法2:将路由写在类前面,再在方法前面指定操作动词

using Microsoft.AspNetCore.Mvc; namespace WebApiTest.Controllers { [ApiController] [Route("/api/[controller]")] public class ShirtsController : ControllerBase { //获取所有衬衫数据列表 [HttpGet] public string GetShirts() { return "获取所有衬衫数据列表"; } //获取指定ID的衬衫数据 [HttpGet("{id}")] public string GetShirtById(int id) { return $"获取ID为 {id} 的衬衫数据"; } //创建一件新的衬衫 [HttpPost] public string CreateShirt() { return "创建一件新的衬衫"; } //更新指定ID的衬衫数据 [HttpPut("{id}")] public string UpdateShirt(int id) { return $"更新ID为 {id} 的衬衫数据"; } //删除指定ID的衬衫数据 [HttpDelete("{id}")] public string DeleteShirt(int id) { return $"删除ID为 {id} 的衬衫数据"; } } } 

5、基于控制器的Web API的路由

使用注解[Route("/shirts")]设置Web API的URL,可以在方法前使用,也可以在类前使用

方法前

类前

6、模型绑定,将Http请求中的数据映射到操作方法的参数

6.1 从路由绑定,在路由Route("/shirts/{id}")或在操作动词HttpGet("{id}")设置,在方法参数前设置[FromRoute]也可以省略

 [HttpGet("{id}")] public string GetShirtById([FromRoute]int id) { return $"获取ID为 {id} 的衬衫数据"; }

6.2 从查询字符串中绑定,方法参数前使用注解[FromQuery]

http://localhost:5186/api/shirts/1?color=红色

 [HttpGet("{id}")] public string GetShirtById([FromRoute]int id, [FromQuery]string color) { return $"获取ID为 {id} 的衬衫数据"; }

6.3 从请求头Header中绑定,方法参数前使用注解[FromHeader]

 [HttpGet("{id}")] public string GetShirtById([FromRoute]int id, [FromQuery]string color, [FromHeader]int size) { return $"获取ID为 {id} 的衬衫数据,衬衫颜色为{color},大小为{size}"; }

6.4 从请求体Body JSON格式中绑定,方法参数前使用注解[FromBody]

 [HttpPost] public string CreateShirt([FromBody]Shirt shirt) { return $"创建一件新的衬衫,{shirt.Id}, {shirt.Name}, {shirt.Color}, {shirt.Gender}, {shirt.Price}"; }

6.5 从请求体Body 表单中绑定,方法参数前使用注解[FromForm]

 [HttpPost] public string CreateShirt([FromForm]Shirt shirt) { return $"创建一件新的衬衫,{shirt.Id}, {shirt.Name}, {shirt.Color}, {shirt.Gender}, {shirt.Price}"; }

7、数据注解模型验证

8、ValidationAtteibute模型验证,继承ValidationAttribute

创建脚本Shirt_EnsureCorrectSizingAttribute.cs

using System.ComponentModel.DataAnnotations; using WebApiDemo.Models; namespace WebApiTest.Models.Validations { public class Shirt_EnsureCorrectSizingAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { var shirt = validationContext.ObjectInstance as Shirt; if(shirt != null && !string.IsNullOrEmpty(shirt.Gender)) { if(shirt.Gender.Equals("men", StringComparison.OrdinalIgnoreCase) && shirt.Size < 8) { return new ValidationResult("男性衬衫的尺码必须大于或等于8。"); } else if (shirt.Gender.Equals("women", StringComparison.OrdinalIgnoreCase) && shirt.Size < 6) { return new ValidationResult("女性衬衫的尺码必须大于或等于6。"); } } return ValidationResult.Success; } } } 

在属性前添加注解

9、Web API返回类型

返回类型使用IActionResult

正确返回使用  Ok(返回数据)

未找到使用     NotFound()

错误响应使用  BadRequest()

 [HttpGet("{id}")] public IActionResult GetShirtById([FromRoute]int id, [FromQuery]string color, [FromHeader]int size) { if(id <= 0) { //错误响应 return BadRequest(); } else if(id > 10) { //未找到 return NotFound(); } //正确响应 return Ok($"获取ID为 {id} 的衬衫数据,衬衫颜色为{color},大小为{size}"); }

10、操作过滤器进行模型验证,继承ActionFilterAttribute

用户输入传递的id可能不符合规范,可以通过操作过滤器进行模型验证对id进行验证

创建脚本Shirt_ValidateShirtIdFilterAttribute

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace WebApiTest.Filters.ActionFilters { public class Shirt_ValidateShirtIdFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); var id = context.ActionArguments["id"] as int?; if (id.HasValue) { if(id.Value <= 0) { context.ModelState.AddModelError("Id", "衬衫ID必须大于0。"); var problemDetails = new Microsoft.AspNetCore.Mvc.ValidationProblemDetails(context.ModelState) { Status = 400, Title = "请求参数错误", }; context.Result = new BadRequestObjectResult(problemDetails); } } } } } 

需要使用验证的方法前添加注解

11、异常过滤器实现异常处理

更新衬衫之前可能其他请求把该衬衫已经删掉,更新时可能会报错,模拟该情景

创建脚本Shirt_HandleUpdateExceptionFilterAttribute

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace WebApiTest.Filters.ExceptionFilters { public class Shirt_HandleUpdateExceptionFilterAttribute : ExceptionFilterAttribute { public int?[] shirtsId = new int?[] { 1, 2, 3, 4, 5 }; public override void OnException(ExceptionContext context) { base.OnException(context); //判断衬衫ID是否存在 暂时假设shirtsId为数据库中已有的衬衫ID列表 var strShirtId = context.RouteData.Values["id"] as string; if (int.TryParse(strShirtId, out int shirtId)) { if (shirtsId.FirstOrDefault(x => x == shirtId) == null) { context.ModelState.AddModelError("Id", $"衬衫已经不存在"); var problemDetails = new ValidationProblemDetails(context.ModelState) { Status = StatusCodes.Status404NotFound, }; context.Result = new NotFoundObjectResult(problemDetails); } } } } } 

在需要异常处理的方法前使用

12、Web API操作数据库

案例使用SqlServer数据库

12.1 下载安装Sql Server Developer Edition和SQL Server Management Studio

Sql Server Developer Edition

SQL Server Management Studio

打开SQL Server Management Studio连接本地数据库

12.2 安装需要使用的包

  • EntityFrameworkCore
  • EntityFrameworkCore.Design
  • EntityFrameworkCore.Tools
  • EntityFrameworkCore.SqlServer

1)打开 管理NuGet程序包

2)搜索安装指定的包

3)双击查看是否安装成功

12.3 创建数据库上下文

1)创建脚本ApplicationDbContext

using Microsoft.EntityFrameworkCore; using WebApiDemo.Models; namespace WebApiTest.Data { public class ApplicationDbContext : DbContext { public DbSet<Shirt> Shirts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //数据播种 modelBuilder.Entity<Shirt>().HasData( new Shirt { Id = 1, Name = "衬衫1", Color = "red", Gender = "men", Size = 10, Price = 100 }, new Shirt { Id = 2, Name = "衬衫2", Color = "blue", Gender = "men", Size = 12, Price = 140 }, new Shirt { Id = 3, Name = "衬衫3", Color = "黑色", Gender = "wemen", Size = 11, Price = 132 }, new Shirt { Id = 4, Name = "衬衫4", Color = "白色", Gender = "wemen", Size = 7, Price = 151 } ); } } } 

12.4 执行数据库迁移

1)添加连接字符串

2)获取连接字符串,视图->服务器资源管理器->数据连接

//需要将master替换为需要创建的数据库名称 Data Source=(local);Initial Catalog=master;Integrated Security=True;Trust Server Certificate=True

3)数据库上下文中创建构造函数指定连接数据库位置

using Microsoft.EntityFrameworkCore; using WebApiDemo.Models; namespace WebApiTest.Data { public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions options) : base(options) { } public DbSet<Shirt> Shirts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //数据播种 modelBuilder.Entity<Shirt>().HasData( new Shirt { Id = 1, Name = "衬衫1", Color = "red", Gender = "men", Size = 10, Price = 100 }, new Shirt { Id = 2, Name = "衬衫2", Color = "blue", Gender = "men", Size = 12, Price = 140 }, new Shirt { Id = 3, Name = "衬衫3", Color = "黑色", Gender = "wemen", Size = 11, Price = 132 }, new Shirt { Id = 4, Name = "衬衫4", Color = "白色", Gender = "wemen", Size = 7, Price = 151 } ); } } }

4)在Progam.cs添加运行EntityFrameworkCore所需的服务

builder.Services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("ShirtStoreManagement")); });

5)创建迁移代码并进行迁移

控制台运行命令Add-Migration Init(Init可以替换为该次进行操作说明)

运行成功会创建在迁移文件夹下创建迁移代码

在控制台运行迁移代码,Update-Database Init(Init为迁移代码名称,不指定会执行最新的代码)

执行成功可以在查看数据库创建成功

12.5 使用EF Core实现Get端点

在ShirtsController构造函数中依赖注入Db上下文

 //获取所有衬衫数据列表 [HttpGet] public IActionResult GetShirts() { return Ok(db.Shirts.ToList()); }

12.6 使用EF Core实现Get by Id端点

修改Shirt_ValidateShirtIdFilterAttribute,实现验证ID在数据库是否存在

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using WebApiTest.Data; namespace WebApiTest.Filters.ActionFilters { public class Shirt_ValidateShirtIdFilterAttribute : ActionFilterAttribute { private readonly ApplicationDbContext db; public Shirt_ValidateShirtIdFilterAttribute(ApplicationDbContext db) { this.db = db; } public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); var id = context.ActionArguments["id"] as int?; if (id.HasValue) { if(id.Value <= 0) { context.ModelState.AddModelError("Id", "衬衫ID必须大于0。"); var problemDetails = new ValidationProblemDetails(context.ModelState) { Status = 400, Title = "请求参数错误", }; context.Result = new BadRequestObjectResult(problemDetails); } else { var shirt = db.Shirts.Find(id.Value); if(shirt == null) { context.ModelState.AddModelError("Id", $"ID为{id.Value}的衬衫在数据库不存在"); var problemDetails = new ValidationProblemDetails(context.ModelState) { Status = 400, Title = "衬衫ID不存在", }; context.Result = new NotFoundObjectResult(problemDetails); } else { context.HttpContext.Items["shirt"] = shirt; } } } } } } 

在操作过滤器中查询数据库存在ID衬衫在context.HttpContext.Items中进行存在,然后在控制器中获取,避免重复进行数据库查询减少性能开销

控制器查询代码

 //获取指定ID的衬衫数据 [HttpGet("{id}")] [TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute))] public IActionResult GetShirtById([FromRoute]int id) { //正确响应 return Ok(HttpContext.Items["shirt"]); }

12.7 使用EF Core实现Post端点

 //创建一件新的衬衫 [HttpPost] public IActionResult CreateShirt([FromBody]Shirt shirt) { this.db.Shirts.Add(shirt); this.db.SaveChanges(); return CreatedAtAction(nameof(GetShirtById), new { id = shirt.Id}, shirt); }

12.8 使用EF Core实现Put端点

修改代码Shirt_HandleUpdateExceptionFilterAttribute,实现验证当前ID数据是否还存在,可能存在当前操作之前数据被删除的可能

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using WebApiTest.Data; namespace WebApiTest.Filters.ExceptionFilters { public class Shirt_HandleUpdateExceptionFilterAttribute : ExceptionFilterAttribute { private readonly ApplicationDbContext db; public Shirt_HandleUpdateExceptionFilterAttribute(ApplicationDbContext db) { this.db = db; } public override void OnException(ExceptionContext context) { base.OnException(context); //判断衬衫ID是否存在 暂时假设shirtsId为数据库中已有的衬衫ID列表 var strShirtId = context.RouteData.Values["id"] as string; if (int.TryParse(strShirtId, out int shirtId)) { if (this.db.Shirts.FirstOrDefault(x => x.Id == shirtId) == null) { context.ModelState.AddModelError("Id", $"衬衫已经在数据库不存在,可能在此之前被删除了"); var problemDetails = new ValidationProblemDetails(context.ModelState) { Status = StatusCodes.Status404NotFound, }; context.Result = new NotFoundObjectResult(problemDetails); } } } } } 

控制器更新代码

 //更新指定ID的衬衫数据 [HttpPut("{id}")] [TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute))] [TypeFilter(typeof(Shirt_HandleUpdateExceptionFilterAttribute))] public IActionResult UpdateShirt(int id, [FromBody]Shirt shirt) { var shirtToUpdate = HttpContext.Items["shirt"] as Shirt; shirtToUpdate.Name = shirt.Name; shirtToUpdate.Size = shirt.Size; shirtToUpdate.Color = shirt.Color; shirtToUpdate.Gender = shirt.Gender; shirtToUpdate.Price = shirt.Price; this.db.SaveChanges(); return Ok($"ID为{id}衬衫更新成功"); }

12.9 使用EF Core实现Delete端点

 //删除指定ID的衬衫数据 [HttpDelete("{id}")] [TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute))] public IActionResult DeleteShirt(int id) { var shirtToDelete = HttpContext.Items["shirt"] as Shirt; this.db.Shirts.Remove(shirtToDelete); this.db.SaveChanges(); return Ok(shirtToDelete); }

13、Web Api安全机制,使用JWT生成令牌和验证令牌

1)创建应用程序信息类,Application.cs

namespace WebApiDemo.Authority { public class Application { public int ApplicationId { get; set; } public string? ApplicationName { get; set; } public string? ClientId { get; set; } public string? Secret { get; set; } public string? Scopes { get; set; } } } 

2)创建应用内容存储,AppPepository.cs,替代已存储数据库的应用程序注册信息

namespace WebApiDemo.Authority { public static class AppPepository { private static List<Application> _applications = new List<Application> { new Application { ApplicationId = 1, ApplicationName = "MVCWebApp", ClientId = "53D3C1E6-4587-4AD5-8C6E-A8E4BD59940E", Secret = "0673FC70-0514-4011-B4A3-DF9BC03201BC", Scopes = "read,write,delete" } }; public static Application? GetApplicationByClientId(string clientId) { return _applications.FirstOrDefault(app => app.ClientId == clientId); } } } 

3)创建应用凭证类,AppCredential.cs,用于接受认证请求参数

namespace WebApiDemo.Authority { public class AppCredential { public string? ClientId { get; set; } = string.Empty; public string? Secret { get; set; } = string.Empty; } } 

4)创建应用认证控制器接口,AuthorityController.cs

using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System.Text; using WebApiDemo.Authority; namespace WebApiDemo.Controllers { [ApiController] public class AuthorityController : ControllerBase { private readonly IConfiguration configuration; public AuthorityController(IConfiguration configuration) { this.configuration = configuration; } [HttpPost("auth")] public IActionResult Authenticate([FromBody] AppCredential credential) { if (Authenticator.Authenticate(credential.ClientId, credential.Secret)) { var expiresAt = DateTime.UtcNow.AddMinutes(10); return Ok(new { access_token = Authenticator.CreateToken(credential.ClientId, expiresAt, configuration["SecurityKey"] ?? string.Empty), expores_at = expiresAt }); } else { ModelState.AddModelError("Unanthorized", "未被授权"); var problemDetails = new ValidationProblemDetails(ModelState) { Status = StatusCodes.Status401Unauthorized, }; return new UnauthorizedObjectResult(problemDetails); } } } } 

5)设置密钥

6)创建应用认证身份验证逻辑类,Authenticator.cs

using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; namespace WebApiDemo.Authority { public static class Authenticator { public static bool Authenticate(string clientId, string secret) { var app = AppPepository.GetApplicationByClientId(clientId); if (app == null) return false; return (app.ClientId == clientId && app.Secret == secret); } public static string CreateToken(string clientId, DateTime expiresAt, string strSecretKey) { //安全算法 //签名密钥 //负载(声明) //生成签名 //算法 var signingCredentials = new SigningCredentials( new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(strSecretKey)), SecurityAlgorithms.HmacSha256Signature); //负载(声明) var app = AppPepository.GetApplicationByClientId(clientId); var clamsDictionary = new Dictionary<string, object> { { "AppName", app?.ApplicationName??string.Empty }, //{ "Read", (app?.Scopes??string.Empty).Contains("read") ? "true" : "false" }, //{ "Write", (app?.Scopes??string.Empty).Contains("write") ? "true" : "false" } }; var scopes = app?.Scopes?.Split(',') ?? Array.Empty<string>(); if(scopes.Length > 0) { foreach(var scope in scopes) { clamsDictionary.Add(scope.Trim().ToLower(), "true"); } } var tokenDescriptor = new SecurityTokenDescriptor { SigningCredentials = signingCredentials, Claims = clamsDictionary, Expires = expiresAt, NotBefore = DateTime.UtcNow, }; var tokenHandler = new JsonWebTokenHandler(); return tokenHandler.CreateToken(tokenDescriptor); } public static async Task<IEnumerable<Claim>?> VerifyTokenAsync(string tokenString, string securityKey) { if(string.IsNullOrWhiteSpace(tokenString) || string.IsNullOrWhiteSpace(securityKey)) { return null; } var keyBytes = System.Text.Encoding.UTF8.GetBytes(securityKey); var tokenHandle = new JsonWebTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuer = false, IssuerSigningKey = new SymmetricSecurityKey(keyBytes), ValidateIssuerSigningKey = false, ValidateAudience = false, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; try { var result = await tokenHandle.ValidateTokenAsync(tokenString, validationParameters); if(result.SecurityToken != null) { var tokenObject = tokenHandle.ReadJsonWebToken(tokenString); return tokenObject.Claims ?? Enumerable.Empty<Claim>(); } else { return null; } } catch (SecurityTokenMalformedException) { return null; } catch (SecurityTokenExpiredException) { return null; } catch (SecurityTokenInvalidSignatureException) { return null; } catch (Exception) { throw; } } } } 

7)通过过滤器方式实现JWT令牌验证

using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using WebApiDemo.Attributes; using WebApiDemo.Authority; namespace WebApiDemo.Filters.AuthFilters { public class JwtTokenAuthFilterAttribute : Attribute, IAsyncAuthorizationFilter { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { //1. 从请求头中获取授权标识 if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeader)) { context.Result = new UnauthorizedResult(); return; } string tokenString = authHeader.ToString(); //2. 去掉Bearer前缀 if (tokenString.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { tokenString = tokenString.Substring("Bearer ".Length).Trim(); } else { context.Result = new UnauthorizedResult(); return; } //3. 获取配置和安全密钥 var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>(); var securityKey = configuration?["SecurityKey"] ?? string.Empty; //4. 验证令牌 并且 提取声明 //if(!await Authenticator.VerifyTokenAsync(tokenString, securityKey)) //{ // context.Result = new UnauthorizedResult(); //} var claims = await Authenticator.VerifyTokenAsync(tokenString, securityKey); if (claims == null) { context.Result = new UnauthorizedResult(); // 无效令牌 401 } else { //5. 获取声明需求 var requiredClaism = context.ActionDescriptor.EndpointMetadata .OfType<RequiredClaimAttribute>() .ToList(); if(requiredClaism != null && !requiredClaism.All(rc => claims.Any(c => c.Type.Equals(rc.ClaimType, StringComparison.OrdinalIgnoreCase) && c.Value.Equals(rc.ClaimValue, StringComparison.OrdinalIgnoreCase)))) { context.Result = new StatusCodeResult(403); // 权限不足 403 } } } } } 

8)授权、权限作用域校验类,RequiredClaimAttribute.cs

namespace WebApiDemo.Attributes { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple = true)] public class RequiredClaimAttribute : Attribute { public string ClaimType { get; } public string ClaimValue { get; } public RequiredClaimAttribute(string claimType, string claimValue) { this.ClaimType = claimType; this.ClaimValue = claimValue; } } } 

Read more

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人 * 📋 文章目录结构 * 1.3 一键安装 OpenClaw(推荐) * 1.4 通过 npm 手动安装 * 1.5 运行 Onboard 向导 * 1.6 验证安装 * 步骤二:配置 Coding Plan 模型 * 🅰️ 选项 A:阿里百炼 Coding Plan * A.1 订阅与获取凭证 * A.2 在 OpenClaw 中配置 * A.3 可用模型列表

91n边缘计算设备部署轻量TensorFlow模型全流程

91n边缘计算设备部署轻量TensorFlow模型全流程 在工厂车间的流水线上,一台不起眼的小型嵌入式设备正实时分析摄像头传来的图像——它没有连接云端,也不依赖高性能GPU,却能在200毫秒内判断出产品表面是否存在划痕,并立即触发报警。这背后的核心技术,正是基于“91n”类边缘计算设备与轻量化TensorFlow模型的深度融合。 这类设备算力有限、内存紧张,却承担着工业智能化转型中最关键的一环:让AI真正落地到生产现场。而要实现这一目标,不仅需要合适的硬件平台,更离不开一套高效、稳定、可规模化的软件部署方案。TensorFlow Lite 正是在这样的需求背景下脱颖而出,成为当前工业级边缘AI应用的主流选择。 TensorFlow Lite 的工程实践价值 为什么是 TensorFlow Lite?这个问题的答案,藏在每一次模型转换、每一行推理代码和每一个实际部署案例中。 作为 TensorFlow 针对移动端和嵌入式场景优化的轻量版本,TFLite 并非简单地“裁剪”功能,而是从底层重新设计了推理引擎。它的核心逻辑可以概括为三个阶段:模型转换 → 解释器加载 → 本地推理

6层高速PCB设计,立创-逻辑派FPGA-G1开发板,万字笔记。基于立创EDA高速PCB,FPGA,GW2A-LV18PG256C8/17、GD32F303CBT6学习笔记

6层高速PCB设计,立创-逻辑派FPGA-G1开发板,万字笔记。基于立创EDA高速PCB,FPGA,GW2A-LV18PG256C8/17、GD32F303CBT6学习笔记

个人声明:本文章为个人学习PCB六层板设计的学习记录。官方资料请参考嘉立创的相关教程。 我用的是嘉立创EDA的专业版。最后我会放上立创开源广场的连接,大家可以去看一下,跟着官方学习一下,官方非常权威 开源广场的地址我放在文章中,因为需要一个DXF文件,需要导入到EDA 并且六层以下都可以免费打板,对我帮助非常大,尤其是像我这种刚入门的新手来说,给予了很多试错机会,毕竟每个月可以免费打两次。而且立创EDA还是免费的,打开网页就能画板子,相当方便快捷。 一.笔记前资料准备 立创·逻辑派FPGA-G1是一款面向学习和开发的国产FPGA开发板,它的一大特点是采用了FPGA与ARM Cortex-M内核相结合的异构架构,并提供了非常完善的开源资料。 主控:GW2A-LV18PG256C8/17、GD32F303CBT6 FPGA逻辑单元:20KHz。 ARM主频:120MHz。 DDR3内存:2Gbit FPGA端存储:FLASH16M/64M/128M ARM端存储:TF卡2GB/4GB/16GB/32GB FPGA端8P接口支持:Gowin程序下载、GAO在线逻辑仿真

前端视角 | 从零搭建并启动若依后端(环境配置)

前端视角 | 从零搭建并启动若依后端(环境配置)

前言 作为前端开发,因前后端联调需求需启动若依Java后端,本文记录从环境准备到后端启动的完整流程,适配本地已有JDK17(安卓项目)、MySQL8.0(Node后端)的场景,全程不破坏原有开发环境。 一、环境准备(核心:不卸载原有环境,按需适配) 若依官方推荐 JDK >=1.8(推荐1.8版本) Mysql >=5.7.0 (推荐5.7版本) Maven >=3.0 Redis >=5.0 非官方推荐 安装开发工具(推荐 IntelliJ IDEA 社区版) * 作用:打开、编译、运行 Java 代码的工具,