我的前端注册的账号都会用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 (登录)
- 在 Postman 中新建一个请求,方法选 POST。
- 地址填入你的登录接口,例如:
https://localhost:端口号/api/MemberLogin。 - 在 Body 选项卡中,选择 raw ,格式选 JSON,填入你的账号信息
cs
{
"id": 2,
"name": "你的用户名",
"power": "你的密码",
"email": "zhangsan@example.com"
}
4.点击 Send
如果成功,你会收到一个 JSON 响应:{"token": "eyJhbGci..."}。复制这个 Token 字符串

第二步:测试添加设备 (带 Token 请求)
- 新建一个请求,方法选 POST ,地址填:
https://localhost:端口号/api/Device。 - 在 Headers 选项卡中,添加一行:
- Key :
Authorization - Value :
Bearer <粘贴你刚才复制的Token>(注意:Bearer后面有一个空格)
- Key :
- 在 Body 选项卡中,选择 raw ,格式选 JSON,填入设备信息:
cs
{
"DeviceName": "客厅灯",
"MacAddress": "AA:BB:CC:DD:EE:FF"
}
maca是esp32唯一的硬件码,每一个esp32都有一个唯一的硬件码
点击 Send 。如果返回 200 OK,说明设备已成功绑定到当前用户

第三步:测试隔离性 (核心测试)
为了验证"不同用户看不到别人的设备",你需要:
- 准备两个账号 :
- 账号 A:添加一个设备"客厅灯"。
- 账号 B:添加一个设备"卧室灯"。
- 测试账号 A 的查询 :
- 方法 GET ,地址
https://localhost:端口号/api/Device。 - Headers 中填入 账号 A 的 Token。
- 点击 Send,你应该只能看到"客厅灯"。
- 方法 GET ,地址
- 测试账号 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.cs 的 CreateDatabase 方法中,建议修改 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进行增删改查,每个用户的设备都是只属于他自己,这里省略