NET中使用Identity+CodeFirst+Jwt实现登录、鉴权
目录
前言
一、创建上下文类
1.自定义MyContext上下文类继承IdentityDbContext
2.在Program中添加AddDbContext服务
二、使用Migration数据迁移
1.在控制台中 依次使用add-migration 、updatebase 命令
2.如何修改表名
3.如何自定义字段
三、使用Identity实现登录、修改密码
1.在Program中 添加AddIdentityCore服务、AddRoleManager、AddUserManager配置
2.在控制器注入UserManager、RoleManager服务
四、使用JWT实现权限验证
1.在启动类Program.cs中配置Swagger可以输入身份验证方式
2.配置类信息、AddAuthentication服务
3.在登录的接口中返回token
4.在需要鉴权的接口加上 [Authorize]
总结
前言
identity
ASP.NET Core提供了标识(identity)框架,它采用RBAC(role-based access control,基于角色的访问控制)策略,内置了对用户、角色等表的管理及相关的接口,从而简化了系统的开发。
CodeFirst
先创建实体类,再通过实体类反向的创建数据库和表结构
什么是JWT?
JSON WEB Token,是一种基于JSON的、用于在网络上声明某种主张的令牌(token)
JWT组成
JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)
头信息指定了该JWT使用的签名算法,HS256 表示使用了 HMAC-SHA256 来生成签名。
消息体包含了JWT的意图
未签名的令牌由base64url编码的头信息和消息体拼接而成(使用"."分隔),签名则通过私有的key计算而成。
最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用"."分隔)就是JWT了
典型的JWT的格式:xxxxx.yyyyy.zzzzz
一、创建上下文类
安装Microsoft.EntityFrameworkCore
安装Microsoft.AspNetCore.Identity.EntityFrameworkCore
1.自定义MyContext上下文类继承IdentityDbContext
示例如下:
public class MyContext : IdentityDbContext{public MyContext(DbContextOptions<MyContext> options) : base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder); }}
2.在Program中添加AddDbContext服务
示例如下:
builder.Services.AddDbContext<MyContext>(options =>
{var connectionStr = builder.Configuration.GetConnectionString("SqlServer:Connection");options.UseSqlServer(connectionStr);
});
在配置文件中appsettings.json配置连接字符串
"ConnectionStrings": {"sqlserver": {"Connection": "Server=服务器名称;User Id=账号;Password=密码;Database=数据库;MultipleActiveResultSets=true;Encrypt=True;TrustServerCertificate=True;"}}
二、使用Migration数据迁移
安装Microsoft.EntityFrameworkCore.SqlServer
安装Microsoft.EntityFrameworkCore.Tools
1.在控制台中 依次使用add-migration 、updatebase 命令
如图所示
执行成功后 去数据库看数据库已经建立好了
效果如下:
2.如何修改表名
生成的表都默认是带有AspNet 觉得不喜欢,那怎么修改呢
使用 FluentAPI配置
示例如下:
public class UserConfig : IEntityTypeConfiguration<IdentityUser>{public void Configure(EntityTypeBuilder<IdentityUser> builder){builder.ToTable("User");}}public class RoleConfig : IEntityTypeConfiguration<IdentityRole>{public void Configure(EntityTypeBuilder<IdentityRole> builder){builder.ToTable("Role");}}public class UserRoleConfig : IEntityTypeConfiguration<IdentityUserRole<string>>{public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder){builder.ToTable("UserRole");}}
在OnModelCreating方法中加入
// 反射中找项目下所有 继承IEntityTypeConfiguration的配置modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
再次执行add-migration 、updatebata 命令
再去数据库查看
已经成功修改我们想要的表名了
3.如何自定义字段
比如我想在用户表中添加年龄字段,创建新的用户类去继承IdentityUser类
示例如下:
public class User: IdentityUser{/// <summary>/// 年龄/// </summary>public int? Age { get; set; }/// <summary>/// 备注/// </summary>public string ReMark { get; set; }}
在UserConfig类中修改成User
public class UserConfig : IEntityTypeConfiguration<User>{public void Configure(EntityTypeBuilder<User> builder){builder.Property(x => x.Id).HasColumnOrder(1);//字段排序builder.Property(x => x.Age).IsRequired(false); //指定类型int ,可以为空builder.Property(x => x.ReMark).HasMaxLength(200).IsRequired(false); //指定长度 ,可以为空builder.ToTable("User");}}
注意:上下文MyContext:IdentityDbContext需要修改成MyContext:IdentityDbContext<User>
这时候 再去执行migration命令,再去看数据库,已经加上了
效果如下:
三、使用Identity实现登录、修改密码
1.在Program中 添加AddIdentityCore服务、AddRoleManager、AddUserManager配置
示例如下:
builder.Services.AddIdentityCore<User>(options =>
{//配置用户名options.User = new UserOptions{RequireUniqueEmail = false, //要求Email唯一//AllowedUserNameCharacters = "abcdefgABCDEFG123456789" //允许的用户名字符,默认是 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+};//配置密码options.Password = new PasswordOptions{RequiredLength = 6, //要求密码最小长度,默认是 6 个字符RequireDigit = true, //要求有数字RequiredUniqueChars = 1, //要求至少要出现的字母数RequireLowercase = false, //要求小写字母RequireNonAlphanumeric = false, //要求特殊字符RequireUppercase = false //要求大写字母};//锁定账户options.Lockout = new LockoutOptions{AllowedForNewUsers = true, // 新用户锁定账户DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1), //锁定时长,默认是 5 分钟MaxFailedAccessAttempts = 3 //登录错误最大尝试次数,默认 5 次};//数据库存储options.Stores = new StoreOptions{MaxLengthForKeys = 128, // 主键的最大长度 ,如果不设置,主键则是 max 的字符串长度ProtectPersonalData = false //保护用户数据,要求实现 IProtectedUserStore 接口}; //令牌配置options.Tokens = new TokenOptions{AuthenticatorIssuer = "Identity", //认证的消费者AuthenticatorTokenProvider = "MyAuthenticatorTokenProvider", //认证令牌的提供者ChangeEmailTokenProvider = "MyChangeEmailTokenProvider", //更换邮箱的令牌提供者ChangePhoneNumberTokenProvider = "MyChangePhoneNumberTokenProvider", //更换手机号的令牌提供者EmailConfirmationTokenProvider = "MyEmailConfirmationTokenProvider", //验证邮箱的令牌提供者PasswordResetTokenProvider = "MyPasswordResetTokenProvider", //重置密码的令牌提供者ProviderMap = new Dictionary<string, TokenProviderDescriptor>()};//声明配置options.ClaimsIdentity = new ClaimsIdentityOptions{RoleClaimType = "IdentityRole",UserIdClaimType = "IdentityId",SecurityStampClaimType = "SecurityStamp",UserNameClaimType = "IdentityName"};//在登录的时候,如果手机号或邮箱没有激活/确认,则无法登录。options.SignIn = new SignInOptions{RequireConfirmedEmail = true, //要求激活邮箱RequireConfirmedPhoneNumber = true //要求激活手机号};
});
var idBuilder = new IdentityBuilder(typeof(User), typeof(IdentityRole), builder.Services);
idBuilder.AddEntityFrameworkStores<MyContext>().AddDefaultTokenProviders().AddRoleManager<RoleManager<IdentityRole>>().AddUserManager<UserManager<User>>();
2.在控制器注入UserManager、RoleManager服务
示例如下:
[ApiController][Route("[controller]/[action]")]public class UserController : ControllerBase{private readonly UserManager<User> _userManager;public UserController(UserManager<User> userManager){_userManager = userManager;}/// <summary>/// 创建用户/// </summary>/// <returns></returns>[HttpPost]public async Task<IActionResult> CreateUser(LoginRequest loginRequest){ User user = await _userManager.FindByNameAsync(loginRequest.UserName);if (user == null){user = new User{UserName = loginRequest.UserName};var result = await _userManager.CreateAsync(user, loginRequest.Password);if (!result.Succeeded){return BadRequest(result.Errors);} }return Ok();}/// <summary>/// 登录/// </summary>/// <param name="loginRequest"></param>/// <returns></returns>[HttpPost]public async Task<IActionResult> Login(LoginRequest loginRequest){string userName = loginRequest.UserName;string password = loginRequest.Password;var user = await _userManager.FindByNameAsync(userName);if (user == null){return NotFound($"用户名{userName}不存在!");}var islocked = await _userManager.IsLockedOutAsync(user);if (islocked){return BadRequest("用户已锁定!");}var success = await _userManager.CheckPasswordAsync(user, password);if (success){return Ok();}else{var r = await _userManager.AccessFailedAsync(user);if (!r.Succeeded){return BadRequest("访问失败信息写入错误!");}else{return BadRequest("失败!");}}}/// <summary>/// 修改密码/// </summary>/// <param name="req"></param>/// <returns></returns>[HttpPost]public async Task<IActionResult> ChangePassword(ChangePasswordRequest req){ var user = await _userManager.FindByNameAsync(req.UserName);if (user == null){return NotFound($"用户名{req.UserName}不存在!");}var result = await _userManager.ChangePasswordAsync(user,req.oldPassword,req.newPassWord);if (!result.Succeeded){return BadRequest("修改失败!");}return Ok("Success");}}public record LoginRequest(string UserName, string Password);public record ChangePasswordRequest(string UserName, string oldPassword,string newPassWord);
四、使用JWT实现权限验证
安装Microsoft.AspNetCore.Authentication.JwtBearer
1.在启动类Program.cs中配置Swagger可以输入身份验证方式
示例如下:
builder.Services.AddSwaggerGen(options =>
{options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme{Description = "请输入token,格式为 Bearer xxxxxxxx(注意中间必须有空格)",Name = "Authorization",//jwt默认的参数名称In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)Type = SecuritySchemeType.ApiKey,BearerFormat = "JWT",Scheme = "Bearer"});//添加安全要求options.AddSecurityRequirement(new OpenApiSecurityRequirement {{new OpenApiSecurityScheme{Reference =new OpenApiReference{Type = ReferenceType.SecurityScheme,Id ="Bearer"}},new string[]{ }}});});
2.配置类信息、AddAuthentication服务
示例如下:
public class JWTOptions{/// <summary>/// 颁发者 /// </summary>public string Issuer { get; set; }/// <summary>/// 接收者 /// </summary>public string Audience { get; set; } /// <summary>/// 密钥/// </summary>public string SigningKey { get; set; }/// <summary>/// 过期时间/// </summary>public int ExpireSeconds { get; set; }}
在配置文件appsettings.json中加入以下信息
"JWT": {"Issuer": "我是小小鱼","Audience": "我是小小鱼","SigningKey": "fasdfad&9045dafz222#fadpio@0232","ExpireSeconds": "3600"}
在添加AddAuthentication服务
builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);var secKey = new SymmetricSecurityKey(keyBytes);x.TokenValidationParameters = new(){ValidateIssuer = true,//是否验证IssuerValidateAudience = true,//是否验证AudienceValidateIssuerSigningKey = true,//是否验证SecurityKeyValidIssuer = jwtOpt.Issuer,ValidAudience=jwtOpt.Audience,IssuerSigningKey = secKey,ValidateLifetime = true, //是否验证失效时间ClockSkew = TimeSpan.FromSeconds(4)};
});
创建一个Jwt辅助类
public class JwtHelper{public static string BuildToken(IEnumerable<Claim> claims, JWTOptions options){DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);var secKey = new SymmetricSecurityKey(keyBytes);var credentials = new SigningCredentials(secKey,SecurityAlgorithms.HmacSha256Signature);var tokenDescriptor = new JwtSecurityToken(options.Issuer,options.Audience,expires: expires,signingCredentials: credentials, claims: claims);return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);}}
3.在登录的接口中返回token
示例如下:
/// <summary>/// 登录/// </summary>/// <param name="loginRequest"></param>/// <returns></returns>[HttpPost]public async Task<IActionResult> Login(LoginRequest loginRequest,[FromServices] IOptions<JWTOptions> jwtOptions){string userName = loginRequest.UserName;string password = loginRequest.Password;var user = await _userManager.FindByNameAsync(userName);if (user == null){return NotFound($"用户名{userName}不存在!");}var islocked = await _userManager.IsLockedOutAsync(user);if (islocked){return BadRequest("用户已锁定!");}var success = await _userManager.CheckPasswordAsync(user, password);if (success){var claims = new List<Claim>();claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));claims.Add(new Claim(ClaimTypes.Name, user.UserName));var roles = await _userManager.GetRolesAsync(user);foreach (string role in roles){claims.Add(new Claim(ClaimTypes.Role, role));}string Token = JwtHelper.BuildToken(claims, jwtOptions.Value);return Ok(Token);}else{var r = await _userManager.AccessFailedAsync(user);if (!r.Succeeded){return BadRequest("访问失败信息写入错误!");}else{return BadRequest("失败!");}}}
效果如下
4.在需要鉴权的接口加上 [Authorize]
示例如下:
/// <summary>/// 获取用户信息/// </summary>/// <returns></returns>[HttpPost][Authorize]public async Task<IActionResult> GetUser() {var claimsPrincipal = this.HttpContext.User;var name = claimsPrincipal.Claims.FirstOrDefault(r => r.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.Value;var user = await _userManager.FindByNameAsync(name);if (user == null){return BadRequest("token有误");}return Ok($"获取用户名:{user.UserName},邮箱:{user.Email}");}
运行效果
总结
以上简单用Identity框架在通过migration命令建库建表,再使用 FluentAPI配置表名、字段,用dentity框架封装的UserManager实现登录、修改密码,以及通过token实现鉴权
相关文章:

NET中使用Identity+CodeFirst+Jwt实现登录、鉴权
目录 前言 一、创建上下文类 1.自定义MyContext上下文类继承IdentityDbContext 2.在Program中添加AddDbContext服务 二、使用Migration数据迁移 1.在控制台中 依次使用add-migration 、updatebase 命令 2.如何修改表名 3.如何自定义字段 三、使用Identity实现登录、修改密码 …...
详解Keras3.0 API: Optimizers
Optimizers 优化器(Optimizer)是深度学习中用于更新模型参数的一种方法,它的目标是最小化损失函数。在训练神经网络时,我们通常使用梯度下降法来更新参数,而优化器就是实现这一过程的工具。优化器的主要作用是在每次迭…...

【数据结构】字符串匹配|BF算法|KMP算法|next数组的优化
字符串匹配算法是在实际工程中经常遇到的问题,也是各大公司笔试面试的常考题目,本文主要介绍BF算法(最好想到的算法,也最好实现)和KMP算法(最经典的) 一、BF算法 BF算法,即暴力(Bru…...

阿里云 ACK One 新特性:多集群网关,帮您快速构建同城容灾系统
云布道师 近日,阿里云分布式云容器平台 ACK One[1]发布“多集群网关”[2](ACK One Multi-cluster Gateways)新特性,这是 ACK One 面向多云、多集群场景提供的云原生网关,用于对多集群南北向流量进行统一管理。 基于 …...

vscode自定义代码片段
前言 代码片段,指的是能够帮助输入重复代码模式,比如初始页面的模板。通过 snippet ,我们仅仅输入一小段字符串,就可以在代码片引擎的帮助下,生成预定义的模板代码,接着我们还可以通过在预定义的光标位置之…...

【贪心算法】专题练习一
欢迎来到Cefler的博客😁 🕌博客主页:那个传说中的man的主页 🏠个人专栏:题目解析 🌎推荐文章:题目大解析(3) 前言 1.什么是贪心算法?——贪婪鼠目寸光 贪心策…...

【JMeter】使用nmon进行性能资源监控
一、前言 在工作中可能会遇到需要在压测的时候对Linux服务器进行性能资源监控的情况。这时可以用nmon来对服务器进行监控。 二、nmon的下载安装 1.查看系统信息 shell cat /etc/os-release 结果为 shell PRETTY_NAME"Debian GNU/Linux 12 (bookworm)" NAME&…...

Unity预设体
目录 预设体是什么? 如何创建预设体? 如何修改预设体? 如何删除预设体? 预设体是什么? Unity中的预设体(Prefab)是一种可重复使用的游戏对象模板。它允许开发者创建一个或多个游戏对象&…...

Elasticsearch 写入优化探索:是什么影响了refresh 耗时?
1、问题背景: 数据写入后,refresh耗时过长,能达到1s-5s。 想通过测试,探索确认影响refresh的因素,比如:写入操作是新增还是更新,deleted文档占比是否有影响,是否有其他索引配置&…...
Java8新特性——函数式接口
目录 一、介绍 二、示例 (一)Consumer 源码解析 测试示例 (二)Comparator (三)Predicate 三、应用 四、总结 一、介绍 FunctionalInterface是一种信息注解类型,用于指明接口类型声明…...

Epson打印机连接wifi
环境 Epson L3153 打印机联通无线光猫 背景 最近家里的联通宽带不太稳定,经常断网。今天打了联通客服电话,师傅上门来,说可能是光猫用的时间太长了,换了一个新的联通光猫,问题解决。 wifi的名称是 CU_Y3ft 和 CU_Y3…...
Chapter 7 - 6. Congestion Management in Ethernet Storage Networks以太网存储网络的拥塞管理
Dedicated and Converged Ethernet Network专用和融合以太网网络 Just because a network is configured as a converged Ethernet network (lossy and lossless traffic), doesn’t necessarily mean that lossy and lossless traffic runs on it simultaneously. For exampl…...

【论文笔记】NeuRAD: Neural Rendering for Autonomous Driving
原文链接:https://arxiv.org/abs/2311.15260 1. 引言 神经辐射场(NeRF)应用在自动驾驶中,可以创建可编辑的场景数字克隆(可自由编辑视角和场景物体),以进行仿真。但目前的方法或者需要大量的训…...
通信原理 | 分贝dB、功率、功率谱、功率谱密度、信噪比
文章目录 分贝功率和分贝的关系能量谱功率谱功率谱和功率谱密度是不同的功率谱密度随机信号和确知信号信噪比基本定义分贝表示应用分贝 分贝:(用dB表示)是量度两个相同单位之间数量比例的计量单位,主要用于度量声音强度。 1贝尔(B)=10分布(dB),即1B = 10dB 分贝是以美国…...
Go中的Context是什么?
在 Go 编程语言(通常称为 Golang)中,术语 "上下文 "指的是上下文包及其定义的上下文类型。上下文包用于跨 API 边界和进程间传输截止日期、取消信号和其他请求范围值。 上下文包的主要目的是管理并发或分布式系统中操作的生命周期…...

碳排放预测 | 基于ARIMA和GM(1,1)的碳排放预测(Matlab)
目录 预测效果基本介绍模型描述ARIMA模型GM(1,1)模型 程序设计参考资料 预测效果 基本介绍 基于ARIMA和GM(1,1)的碳排放预测(Matlab) 基于ARIMA(自回归移动平均模型)和GM(1,1)(灰色预测模型)的碳排放预测是…...

FPFA.一种二倍频电路代码描述以及测量详情
一、前言 1、因为需要倍频电路所以找了个二倍频的电路,通过fpga实际测量发现经过倍频后的电路峰值降低。不过这个也正常,因为该电路只要过触发点就会开始发生波形变化,而电路的触发值不是峰值。 2、继续对电路做倍频后信号做二…...

dotnet命令创建C#项目,VSCode打开
在命令行中创建项目并运行 1.首先安装.net 下载地址:.NET | 构建。测试。部署。 2.在 cmd 控制台输入 dotnet --vesion 检查版本号是否正常 3.我用git bash环境输入命令创建项目 // 创建文件夹 mkdir MyVSCode // 进入该文件夹 cd MyVSCode/ // 创建控制台项目 dotnet …...

在GitHub找开源项目
在 GitHub 的搜索框里: 使用搜索关键词可以在 GitHub 上快速的找你需要的开源项目: 限制搜索范围 通过 in 关键词 (大小写不敏感) 限制搜索范围: 公式搜索范围in:name xxx项目名包含xxxin:description xxx项目描述包含xxxin:readme xxx项目…...

GAMES101-LAB1
文章目录 一、问题简述二、框架准备三、作业参考3.1 模型矩阵3.1 参考代码 3.2 投影矩阵3.2.1 压扁操作(透视投影)3.2.2 正交投影3.2.3 参考代码 四、附件 一、问题简述 接下来的三次作业,将模拟一个基于CPU的光栅化渲染器的简化版本本次作业的任务是实现一个旋转矩…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
从实验室到产业:IndexTTS 在六大核心场景的落地实践
一、内容创作:重构数字内容生产范式 在短视频创作领域,IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色,生成的 “各位吴彦祖们大家好” 语音相似度达 97%,单条视频播放量突破百万…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...
STL 2迭代器
文章目录 1.迭代器2.输入迭代器3.输出迭代器1.插入迭代器 4.前向迭代器5.双向迭代器6.随机访问迭代器7.不同容器返回的迭代器类型1.输入 / 输出迭代器2.前向迭代器3.双向迭代器4.随机访问迭代器5.特殊迭代器适配器6.为什么 unordered_set 只提供前向迭代器? 1.迭代器…...
iOS 项目怎么构建稳定性保障机制?一次系统性防错经验分享(含 KeyMob 工具应用)
崩溃、内存飙升、后台任务未释放、页面卡顿、日志丢失——稳定性问题,不一定会立刻崩,但一旦积累,就是“上线后救不回来的代价”。 稳定性保障不是某个工具的功能,而是一套贯穿开发、测试、上线全流程的“观测分析防范”机制。 …...

C++ Saucer 编写Windows桌面应用
文章目录 一、背景二、Saucer 简介核心特性典型应用场景 三、生成自己的项目四、以Win32项目方式构建Win32项目禁用最大化按钮 五、总结 一、背景 使用Saucer框架,开发Windows桌面应用,把一个html页面作为GUI设计放到Saucer里,隐藏掉运行时弹…...

Spring AI中使用ChatMemory实现会话记忆功能
文章目录 1、需求2、ChatMemory中消息的存储位置3、实现步骤1、引入依赖2、配置Spring AI3、配置chatmemory4、java层传递conversaionId 4、验证5、完整代码6、参考文档 1、需求 我们知道大型语言模型 (LLM) 是无状态的,这就意味着他们不会保…...