【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

Stack-Chan机器人完整入门指南:从零开始构建你的可爱机器人伙伴

Stack-Chan机器人完整入门指南:从零开始构建你的可爱机器人伙伴 【免费下载链接】stack-chanA JavaScript-driven M5Stack-embedded super-kawaii robot. 项目地址: https://gitcode.com/gh_mirrors/sta/stack-chan Stack-Chan是一个基于JavaScript驱动的M5Stack嵌入式超级可爱的机器人项目。这个开源项目让你能够轻松构建一个会眨眼、会转头、会说话的智能机器人伙伴。无论你是嵌入式开发新手还是经验丰富的开发者,都能快速上手这个充满乐趣的项目。 🎯 项目核心亮点 超强可爱属性:Stack-Chan拥有多种可爱的面部表情,能够进行眼神交流,让你的机器人充满个性魅力。 模块化设计:项目采用高度模块化的架构,支持多种舵机驱动、面部渲染器和功能扩展,让你的定制变得简单而灵活。 丰富功能生态:支持人脸追踪、语音对话、表情模仿等智能功能,为你的机器人注入灵魂。 📦 项目快速入门 环境准备与代码获取 首先克隆项目仓库到本地: git clone ht

By Ne0inhk
智能家居笔记Home-Assistant+小智AI

智能家居笔记Home-Assistant+小智AI

设备框架图 概述: 总体而言借用各种开源项目,致力做到好用可控。输入设备小智AI作为用户前端,后端用小智AI华南理工开源服务器。智能家庭中控采用树莓派5搭载HAOS,用homeassistant,包括手机APP。各种终端设备,支持zigbee通信协议,wifi通信协议,小米设备支持milot的设备可以连接,其他类似美的海尔的设备也看home assistant的插件支持程度,没有本身开源的使用舒适。 主机: thinkpad-S5-yoga 地址xxxxxxxxxxxxx 用户:xxxxxxxxxxx 服务备注服务端口frigate5000sambamnt/media/usbsharehttp/mnt/usb_share/podcast10086mediamtx.servicexxxxxxxxxxx8554napcatkoimilocomicamxxxxxxxxxxxxxxastra-color.servicezaokafei-fetch.timer/mnt/usb_share/podcast/zaokafeibambucam.serviceDesktop/bambustud

By Ne0inhk
小米 “养龙虾”:手机 Agent 落地,智能家居十年困局被撬开

小米 “养龙虾”:手机 Agent 落地,智能家居十年困局被撬开

3月6日,小米正式推出国内首个手机端类 OpenClaw Agent 应用 ——Xiaomi miclaw,开启小范围邀请封测。这款被行业与网友戏称为小米 “开养龙虾” 的新品,绝非大模型浪潮下又一款语音助手的常规升级,而是基于自研 MiMo 大模型、具备系统级权限、全场景上下文理解能力的端侧智能体。 作为深耕智能家居领域的行业媒体,《智哪儿》始终认为:智能家居行业过去十年的迭代,始终没能跳出 “被动执行” 的底层困局。而 miclaw 的落地,不止是小米在端侧 AI 赛道的关键落子,更是为整个智能家居行业的底层逻辑重构,提供了可落地的参考范本。需要清醒认知的是,目前该产品仍处于小范围封测阶段,复杂场景执行成功率、端侧功耗表现、第三方生态适配进度等核心体验,仍有待大规模用户实测验证。本文将结合具象场景、量化数据与多维度视角,客观拆解 miclaw 的突破价值、现实挑战,以及它对智能家居行业的长期影响。 01 复盘行业困局:智能家居十年 始终困在 “被动执行”

By Ne0inhk
Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座

Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座 在现代社交应用与办公协同工具的开发中,集成强大的机器人(Bot)交互能力是提升活跃度的关键。discord_interactions 库为 Flutter 开发者提供了一套完整的、遵循 Discord 官方协议的交互模型,涵盖了从 Slash Commands(斜杠命令)到 Webhook 签名验证的核心功能。本文将深入解析如何在 OpenHarmony(鸿蒙)环境下,结合鸿蒙的安全机制与网络特性,完美适配 discord_interactions 到你的鸿蒙应用中。 前言 随着鸿蒙系统(HarmonyOS)进入原生应用开发的新纪元,跨平台社交工具的适配需求日益增长。discord_interactions 作为一个纯

By Ne0inhk