009. ASP.NET WEB API 用户关联esp32设备

我的前端注册的账号都会用person类对象存入数据库里,我现在想加上存储esp32设备的类,前端请求操作esp都是唯一的,不同用户之间不能控制别人的esp32

为了实现"不同用户只能控制自己的 ESP32 设备"这一需求,我们需要在数据库中建立**用户(User/Person)设备(Device)**之间的关联关系。

Linq_table 中增加对 Device 表的操作,关键在于查询时必须带上 UserId

第一步:修改数据库结构(创建设备表)

我们需要在 Linq_table.cs 中添加一个新表 Device。请找到你的 CreateDatabase 方法,修改成下面这样:

在我们的数据库文件里,会自动增加一个Device表格,他存的是每台esp32设备的信息以及属于哪个用户person表的关联信息,

注意:首次运行必须用管理员api彻底删除数据库,并且文件里的数据库文件也必须手动删除,否则,服务器只会去读取旧数据库

cs 复制代码
static void CreateDatabase(string connectionString)
{
    using (var connection = new SqliteConnection(connectionString))
    {
        connection.Open();

        // 1. 原有的 Person 表
        string sqlPerson = @"
        CREATE TABLE IF NOT EXISTS Person (
            Id INTEGER PRIMARY KEY AUTOINCREMENT,
            Name NOT NULL UNIQUE,
            Power TEXT NOT NULL,
            Email TEXT
        )";
        connection.Execute(sqlPerson);

        // 2. 新增 Device 表:多了一个 UserId 字段,用来指向它是谁的设备
        string sqlDevice = @"
        CREATE TABLE IF NOT EXISTS Device (
            Id INTEGER PRIMARY KEY AUTOINCREMENT,
            DeviceName TEXT NOT NULL,
            MacAddress TEXT NOT NULL,
            UserId INTEGER NOT NULL
        )";
        connection.Execute(sqlDevice);
    }
}

第二步:创建设备模型类

Models 文件夹下新建一个 Device.cs 文件,代码如下:

cs 复制代码
namespace Esp32_Server.Models
{
    public class Device
    {
        public int Id { get; set; }           // 设备的唯一编号
        public string DeviceName { get; set; } // 给设备起个名字,比如"客厅灯"
        public string MacAddress { get; set; } // ESP32 的硬件地址
        public int UserId { get; set; }        // 关键:这个设备属于哪个用户的 ID
    }
}

第三步:在数据库操作类中添加"查询"和"插入"ESP32设备的方法

Linq_table.cs 中添加这两个方法,这样我们就能根据用户 ID 去找属于他的设备了:

cs 复制代码
// 添加设备
public static void InsertDevice(string deviceName, string mac, int userId)
{
    using (var connection = new SqliteConnection(connectionString))
    {
        string sql = "INSERT INTO Device (DeviceName, MacAddress, UserId) VALUES (@DeviceName, @MacAddress, @UserId)";
        connection.Execute(sql, new { DeviceName = deviceName, MacAddress = mac, UserId = userId });
    }
}

// 查询某个用户的所有设备
public static List<Device> GetDevicesByUserId(int userId)
{
    using (var connection = new SqliteConnection(connectionString))
    {
        string sql = "SELECT * FROM Device WHERE UserId = @UserId";
        return connection.Query<Device>(sql, new { UserId = userId }).AsList();
    }
}

第四步:理解思路(先别写代码,看这里)

现在逻辑是这样的:

注册/登录时:系统知道你是谁(通过 JWT 令牌)。

添加设备时 :你不需要告诉服务器"我是谁",服务器从你的令牌里自动提取你的 UserId,然后把设备存入数据库,并备注上 UserId

查看设备时 :服务器只查询 WHERE UserId = 你的ID 的数据。这样,即便别人知道你的设备名称,他也查不到,因为他的 UserId 和你的不一样。

我们要实现的目标是:通过用户的 JWT 令牌自动识别用户身份,从而实现设备隔离。

第五步:创建设备控制器 (DeviceController)

Controllers 文件夹下创建一个名为 DeviceController.cs 的文件。这个控制器负责处理设备的所有操作。

cs 复制代码
using Esp32_Server.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace Esp32_Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize] // jwt令牌加密api标签
    public class DeviceController : ControllerBase
    {
        // 获取当前登录用户的用户名
        private string GetCurrentUserName()
        {
            return User.FindFirst(ClaimTypes.Name)?.Value;
        }

        // 获取当前登录用户的 ID
        private int GetCurrentUserId()
        {
            var name = GetCurrentUserName();
            var allPeople = Linq_table.GetAllPeople();
            var user = allPeople.FirstOrDefault(p => p.Name == name);
            return user?.Id ?? 0;
        }

        // 1. 添加设备:自动绑定当前登录用户
        [HttpPost]
        public IActionResult AddDevice([FromBody] Device device)
        {
            int userId = GetCurrentUserId();
            if (userId == 0) return Unauthorized("用户不存在");

            Linq_table.InsertDevice(device.DeviceName, device.MacAddress, userId);
            return Ok(new { message = "设备添加成功" });
        }

        // 2. 获取我的设备:只返回属于当前用户的数据
        [HttpGet]
        public IActionResult GetMyDevices()
        {
            int userId = GetCurrentUserId();
            var myDevices = Linq_table.GetDevicesByUserId(userId);
            return Ok(myDevices);
        }


      

        


    }
}

第六步:为什么这样做是安全的?

你可能会问:"为什么要这么麻烦去获取 UserId,而不是让前端直接传一个 UserId 过来?"

防止恶意篡改 :如果让前端传 UserId,黑客可以修改请求包,把 UserId 改成别人的 ID,从而操作别人的设备。

后端验证 :通过 User.FindFirst(ClaimTypes.Name) 获取的用户名是从 JWT 令牌中解析出来的。只要令牌没过期且签名正确,这个用户名就是绝对可信的。

第七步:测试

使用 Postman 测试带 JWT 认证的接口,核心在于如何在请求中携带 Token。请按照以下步骤操作:

第一步:获取 Token (登录)

  1. 在 Postman 中新建一个请求,方法选 POST
  2. 地址填入你的登录接口,例如:https://localhost:端口号/api/MemberLogin
  3. Body 选项卡中,选择 raw ,格式选 JSON,填入你的账号信息
cs 复制代码
{
        "id": 2,
        "name": "你的用户名",
        "power": "你的密码",
        "email": "zhangsan@example.com"
    }

4.点击 Send

如果成功,你会收到一个 JSON 响应:{"token": "eyJhbGci..."}复制这个 Token 字符串

第二步:测试添加设备 (带 Token 请求)

  1. 新建一个请求,方法选 POST ,地址填:https://localhost:端口号/api/Device
  2. Headers 选项卡中,添加一行:
    • Key : Authorization
    • Value : Bearer <粘贴你刚才复制的Token> (注意:Bearer 后面有一个空格)
  3. Body 选项卡中,选择 raw ,格式选 JSON,填入设备信息:
cs 复制代码
{
    "DeviceName": "客厅灯",
    "MacAddress": "AA:BB:CC:DD:EE:FF"
}

maca是esp32唯一的硬件码,每一个esp32都有一个唯一的硬件码

点击 Send 。如果返回 200 OK,说明设备已成功绑定到当前用户

第三步:测试隔离性 (核心测试)

为了验证"不同用户看不到别人的设备",你需要:

  1. 准备两个账号
    • 账号 A:添加一个设备"客厅灯"。
    • 账号 B:添加一个设备"卧室灯"。
  2. 测试账号 A 的查询
    • 方法 GET ,地址 https://localhost:端口号/api/Device
    • Headers 中填入 账号 A 的 Token。
    • 点击 Send,你应该只能看到"客厅灯"。
  3. 测试账号 B 的查询
    • 保持地址不变,将 Headers 中的 Token 替换为 账号 B 的 Token。
    • 点击 Send ,你应该只能看到"卧室灯",绝对看不到账号 A 的"客厅灯"。

这一步测试非常繁杂,博主已经亲测过了,设备添加正常,隔离性正常,其他用户无法对这一用户的esp32进行增删改查,每个用户的设备都是只属于他自己,这里省略

B1.优化阶段

我测试没问题只有正确的账号才能查看到设备信息,但有个问题,设备类还需要一个bool属性用于后期控制等开关,最好再加一个int属性,后期用来控制pwm

这是一个非常好的迭代!在物联网开发中,bool(开关)和 int(PWM/亮度/速度)是控制设备最基础的两个参数。

我们按照之前的逻辑,分三步完成更新:

第一步:更新 Device 模型类

Models/Device.cs 中添加这两个属性:

cs 复制代码
namespace Esp32_Server.Models
{
    public class Device
    {
        public int Id { get; set; }
        public string DeviceName { get; set; }
        public string MacAddress { get; set; }
        public int UserId { get; set; }
        
        // 新增属性
        public bool IsOn { get; set; }      // 开关状态
        public int PwmValue { get; set; }   // PWM 值 (例如 0-255)
    }
}

第二步:更新数据库表结构

Linq_table.csCreateDatabase 方法中,建议修改 SQL 语句:

cs 复制代码
string sqlDevice = @"
CREATE TABLE IF NOT EXISTS Device (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    DeviceName TEXT NOT NULL,
    MacAddress TEXT NOT NULL,
    UserId INTEGER NOT NULL,
    IsOn INTEGER DEFAULT 0,    -- SQLite 中 bool 用 0/1 表示
    PwmValue INTEGER DEFAULT 0
)";

这行代码会在我们的数据库文件login.db内新增加一个Device表格,这个表是用于存储esp32设备对象的,

第三步:更新数据库操作方法

我们需要增加一个"更新设备状态"的方法,这样 ESP32 或前端才能修改开关和 PWM。

Linq_table.cs 中添加:

这个方法时,会去更新数据库内Device表内指定id的esp32设备属性信息,即我们可以修改引脚属性的开关状态信息等

cs 复制代码
public static void UpdateDeviceStatus(int id, int userId, bool isOn, int pwmValue)
{
    using (var connection = new SqliteConnection(connectionString))
    {
        // 关键:WHERE 后面一定要带上 UserId,防止用户修改别人的设备!
        string sql = @"UPDATE Device 
                       SET IsOn = @IsOn, PwmValue = @PwmValue 
                       WHERE Id = @Id AND UserId = @UserId";
        
        connection.Execute(sql, new { 
            Id = id, 
            UserId = userId, 
            IsOn = isOn ? 1 : 0, // 将 bool 转为 0 或 1
            PwmValue = pwmValue 
        });
    }
}

优化后,完整的数据库类

cs 复制代码
using Dapper;
using Esp32_Server.Member;
using Esp32_Server.Models;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Numerics;
namespace Esp32_Server.Controllers
{
    /// <summary>
    /// 数据库控制类
    /// </summary>
    public static class Linq_table
    {
        static string dbPath = "Login.db";// 1. 数据库文件路径
        static string connectionString = $"Data Source={dbPath}"; // 2. 连接字符串

        /// <summary>
        /// 初始化数据库,创建表,除了主程序加载外,其余一律不得调用
        /// </summary>
        /// <returns></returns>
        public static void Rest()
        {
            // 3. 创建数据库和表
            CreateDatabase(connectionString);

        }

        /// <summary>
        /// 彻底清空数据库,包括删除所有数据并重置自增 ID
        /// 危险操作,除管理员外,一律不得调用
        /// </summary>
        public static void ClearAllData()
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction())
                {
                    try
                    {
                        // 1. 清空 Person 表
                        connection.Execute("DELETE FROM Person", transaction: transaction);
                        connection.Execute("DELETE FROM sqlite_sequence WHERE name='Person'", transaction: transaction);

                        // 2. 清空 Device 表
                        connection.Execute("DELETE FROM Device", transaction: transaction);
                        connection.Execute("DELETE FROM sqlite_sequence WHERE name='Device'", transaction: transaction);

                        transaction.Commit();
                    }
                    catch (Exception)
                    {
                        transaction.Rollback();
                        throw;
                    }
                }
            }
        }





        /// <summary>
        /// 根据id查询数据,如果没有找到返回null,除了主程序加载外,其余一律不得调用
        /// </summary>
        public static string Get(int id)
        {

            // 6. 查询单个
            var person1 = GetPersonById(id);
            if (person1 != null)
            {
                // string  str= person1.Name;
                string str = person1.Power;
                return (string)str;
            }
            return "404";
        }





        /// <summary>
        /// 创建表格,除了主程序加载外,其余一律不得调用
        /// </summary>
        /// <param name="connectionString"></param>
        static void CreateDatabase(string connectionString)
        {
            // 如果数据库文件不存在,会自动创建
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                // 创建 Person 表,设置表的Name属性NOT NULL UNIQUE(不能重复)
                string sql = @"
                CREATE TABLE IF NOT EXISTS Person (
                    Id INTEGER PRIMARY KEY AUTOINCREMENT,
                    Name NOT NULL UNIQUE,
                    Power TEXT NOT NULL,
                    Email TEXT
                )";

                connection.Execute(sql);

                // 2. 新增 Device 表:多了一个 UserId 字段,用来指向它是谁的设备
                string sqlDevice = @"
                    CREATE TABLE IF NOT EXISTS Device (
                        Id INTEGER PRIMARY KEY AUTOINCREMENT,
                        DeviceName TEXT NOT NULL,
                        MacAddress TEXT NOT NULL,
                        UserId INTEGER NOT NULL,
                        IsOn INTEGER DEFAULT 0,    -- SQLite 中 bool 用 0/1 表示
                        PwmValue INTEGER DEFAULT 0
                    )";

                connection.Execute(sqlDevice);
            }
        }



        /// <summary>
        /// 增加数据,返回自动增加的id号
        /// </summary>
        /// <param name="connectionString"></param>
        /// <param name="name"></param>
        /// <param name="age"></param>
        /// <param name="email"></param>
        /// <returns></returns>
        public static int InsertPerson(string name, string power, string email)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                // 插入数据并返回自增ID
                string sql = @"
                INSERT INTO Person (Name, Power, Email)
                VALUES (@Name, @Power, @Email);
                SELECT last_insert_rowid();";

                var newId = connection.ExecuteScalar<int>(sql, new
                {
                    Name = name,
                    Power = power,
                    Email = email
                });

                return newId;
            }
        }





        /// <summary>
        /// 查询所有数据,自动映射为模型类
        /// </summary>
        /// <returns></returns>
        public static List<Person> GetAllPeople()
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                string sql = "SELECT * FROM Person";

                // 使用 Dapper 查询,自动映射到 Person 对象
                var people = connection.Query<Person>(sql).AsList();
                return people;
            }
        }



        static Person GetPersonById(int id)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                string sql = "SELECT * FROM Person WHERE Id = @Id";

                return connection.QueryFirstOrDefault<Person>(sql, new { Id = id });
            }
        }



        /// <summary>
        /// 根据id 修改数据
        /// </summary>
        /// <param name="id"></param>
        /// <param name="name"></param>
        /// <param name="power"></param>
        /// <param name="email"></param>
        public static void UpdatePerson(int id, string name, string power, string email)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                string sql = @"
                UPDATE Person 
                SET Name = @Name, 
                    Power = @Power, 
                    Email = @Email 
                WHERE Id = @Id";

                int rows = connection.Execute(sql, new
                {
                    Id = id,
                    Name = name,
                    Power = power,
                    Email = email
                });
            }
        }


        /// <summary>
        /// 删除一条数据
        /// </summary>
        /// <param name="id"></param>
        public static void DeletePerson(int id)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                string sql = "DELETE FROM Person WHERE Id = @Id";

                int rows = connection.Execute(sql, new { Id = id });
            }
        }


        // 添加esp32设备
        public static void InsertDevice(string deviceName, string mac, int userId)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                string sql = "INSERT INTO Device (DeviceName, MacAddress, UserId) VALUES (@DeviceName, @MacAddress, @UserId)";
                connection.Execute(sql, new { DeviceName = deviceName, MacAddress = mac, UserId = userId });
            }
        }

        // 查询某个用户的所有esp32设备
        public static List<Device> GetDevicesByUserId(int userId)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                string sql = "SELECT * FROM Device WHERE UserId = @UserId";
                return connection.Query<Device>(sql, new { UserId = userId }).AsList();
            }
        }


        /// <summary>
        /// 控制esp32开关灯或pwm
        /// </summary>
        /// <param name="id"></param>
        /// <param name="userId"></param>
        /// <param name="isOn"></param>
        /// <param name="pwmValue"></param>
        public static void UpdateDeviceStatus(int id, int userId, bool isOn, int pwmValue)
        {
            using (var connection = new SqliteConnection(connectionString))
            {
                // 关键:WHERE 后面一定要带上 UserId,防止用户修改别人的设备!
                string sql = @"UPDATE Device 
                       SET IsOn = @IsOn, PwmValue = @PwmValue 
                       WHERE Id = @Id AND UserId = @UserId";

                connection.Execute(sql, new
                {
                    Id = id,
                    UserId = userId,
                    IsOn = isOn ? 1 : 0, // 将 bool 转为 0 或 1
                    PwmValue = pwmValue
                });
            }
        }


    }
}

注意:首次运行必须用管理员api彻底删除数据库,并且文件里的数据库文件也必须手动删除,否则,服务器只会去读取旧数据库

第四步:在控制器中添加更新接口

DeviceController.cs 中添加一个 PUT 接口

cs 复制代码
[HttpPut("status")]
public IActionResult UpdateStatus([FromBody] DeviceStatusUpdateDto dto)
{
    int userId = GetCurrentUserId();
    // 修改设备状态
    Linq_table.UpdateDeviceStatus(dto.DeviceId, userId, dto.IsOn, dto.PwmValue);
    return Ok(new { message = "状态更新成功" });
}

// 定义一个简单的数据传输对象 (DTO)
public class DeviceStatusUpdateDto
{
    public int DeviceId { get; set; }
    public bool IsOn { get; set; }
    public int PwmValue { get; set; }
}

完整的esp32设备控制类

cs 复制代码
using Esp32_Server.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace Esp32_Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize] // jwt令牌加密api标签
    public class DeviceController : ControllerBase
    {
        // 获取当前登录用户的用户名
        private string GetCurrentUserName()
        {
            return User.FindFirst(ClaimTypes.Name)?.Value;
        }

        // 获取当前登录用户的 ID
        private int GetCurrentUserId()
        {
            var name = GetCurrentUserName();
            var allPeople = Linq_table.GetAllPeople();
            var user = allPeople.FirstOrDefault(p => p.Name == name);
            return user?.Id ?? 0;
        }

        // 1. 添加设备:自动绑定当前登录用户
        [HttpPost]
        public IActionResult AddDevice([FromBody] Device device)
        {
            int userId = GetCurrentUserId();
            if (userId == 0) return Unauthorized("用户不存在");

            Linq_table.InsertDevice(device.DeviceName, device.MacAddress, userId);
            return Ok(new { message = "设备添加成功" });
        }

        // 2. 获取我的设备:只返回属于当前用户的数据
        [HttpGet]
        public IActionResult GetMyDevices()
        {
            int userId = GetCurrentUserId();
            var myDevices = Linq_table.GetDevicesByUserId(userId);
            return Ok(myDevices);
        }


        /// <summary>
        /// 修改一个esp32设置的开关状态或pwm值
        /// 传入的是一个DeviceStatusUpdateDto类对象,他是数据传输的中转类,DeviceStatusUpdateDto会去修改存在数据库内的 Device类数据表
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut("status")]
        public IActionResult UpdateStatus([FromBody] DeviceStatusUpdateDto dto)
        {
            int userId = GetCurrentUserId();
            // 修改设备状态
            Linq_table.UpdateDeviceStatus(dto.DeviceId, userId, dto.IsOn, dto.PwmValue);
            return Ok(new { message = "状态更新成功" });
        }

        


    }
}

B2.esp32设备数据库操作测试

前面我们已经掌握了,使用令牌token去请求添加一个esp32设备存进云服务器数据库内,现在

1.我们先调用查看本用户内所有的esp32设备api

请求方式: GET

报文头: 封装 token令牌

URL: http://localhost:5264/api/device

我们成功查阅到,我们的账户内有一个esp32设备

2.前端发起修改esp32数据库请求

这一步模拟手机app向云服务器发起修改esp32状态的指令,即控制一个引脚属性由于false变为true或由true变位false等,即给服务器远程开关灯指令

修改Device数据库请求方式

请求方式: PUT

报文头: 封装token

URL: http://localhost:5264/api/device/status

Bady josn: 内容是这个辅助类

cs 复制代码
public class DeviceStatusUpdateDto
{
    public int DeviceId { get; set; }
    public bool IsOn { get; set; }
    public int PwmValue { get; set; }
}

这里的id是指Device表里添加的第一个esp32设备,

这里提示我们esp32设备状态已经更新,我们再次用前面的查看账户内的esp32信息api,看看这台esp32引脚属性有没有成功修改为 true

这里提示只有id=2的设备,ison属性没修改成功,这是因为,数据库没彻底删除前,之前测试用过的id=1,不能被重复运用,这是数据库安全性所致,由于我们多次测试,这个id=1被占用过,数据库自动把我们之前添加的esp32设备分配给了id=2,我们只需要修改一下报文再测试看看

修改成功后,再次GET 查看, esp32引脚属性成功改为true

3.交叉隔离测试

这一步测试非常繁杂,博主已经亲测过了,设备添加正常,隔离性正常,其他用户无法对这一用户的esp32进行增删改查,每个用户的设备都是只属于他自己,这里省略

相关推荐
鹅城剑仙3 小时前
Spring Boot 微服务架构设计与最佳实践
spring boot·后端·微服务
杨先生哦4 小时前
2026 热端攻防:AI 驱动 Web 前端安全全景透析
前端·笔记·安全·web安全
李白的天不白4 小时前
SmartAdmin(基于 Spring Boot 框架)中配置跨域请求 VUE3 设置请求头
java·前端
一个被程序员耽误的厨师4 小时前
01-设计篇-我用前端那一套手艺造了一个AI-Native工具
前端·ai-native
不吃糖葫芦34 小时前
vue3实现拓扑图编辑功能(谨以此纪念我当前的最后一份前端工作)
前端
大家的林语冰4 小时前
超越 TypeScript,Flow 强势回归,语法高仿 TS,功能更丰富,类型更安全!
前端·javascript·typescript
এ慕ོ冬℘゜4 小时前
jQuery 高可用多图上传组件(企业级封装 + 踩坑全解 + 可直接上线)
前端·javascript·jquery
Full Stack Developme4 小时前
Spring Integration 教程
java·后端·spring
爱勇宝4 小时前
AI 时代,前端工程师的话语权正在下降?
前端·后端