.net core web api+oracle+vue3搞了个小聊天室,有群聊和私聊

环境:,net 6+oracle 11g

使用identity框架实现了,真实用户的登录

使用JWT令牌保证了数据安全

使用Signalr框架实现了后端消息的主动推送

所需要的包:

后端代码;

第一步:先使用Identity框架建立用户与角色模型

dentity框架主要作用是,方便对用户以及用户的权限进行管理,创建两个实体后可以使用Manager<T>对其尽心管理,其内还封装了很多常用方法,例如;通过ID寻找name等。

框架还会对用户的密码经行加密处理,加密后在传入数据库当中。

1.MyRole类,dentity框架框架会默认自动生成一些列,可以不用自建列

2.Myuser类

3.为上方两个类设置上下文类,此处直接进行了配置,也可单独写一个配置类进行配置。

此处指明了表名是由于在连接Oracle的情况下,efcore转换成的SQL代码会把表明写的很长,所以指明具体表名。

4.在programe中对dentity框架进行注册

此处为了方便,所以把密码的要求降低了很多,实际应用中这是不好的。

对密码还有其他的配置可以,详情可自行查阅

最后,建立完实体之后记得,在nuget控制台中使用:Add-Mgration +迁移名,Update-Database进行数据库库表的创建。

第二步:对JWT Token进行配置

使用JWT,他相较于传统的session,在跨域方面更方便。JWT是无状态的,其内蕴含所需要的全部数据,省了服务器的开销,可以将其放在前端页面上。JWT中可以设置过期时间的,而不需要服务器注销。所以JWT多应用于分布式集群及API前后端分离的项目当中。

1.新建模型类,对用于对key与过期时间的读取

2.在appsettings.json文件中配置需要读取的key与过期时间,此处是为了方便,你也可以在VS自带的虚拟环境变量中配置,也可以直接配置在本地的环境变量中,或者配置在secret安全文件中

3.在programe中对JWT进行注册

4.添加JWT中间件

  app.UseAuthentication();//JWT的中间件

第三步:signalr实现聊天功能。

signalr能够将数据从后端主动推送到前端页面,

适合于需要通知个及时消息的应用例如:聊天,公告等。

适用于更新频率高的应用,例如:游戏,GPS等。

ASP.NET Core 中的SignalR 

可以自动处理连接管理。

可以同时向所有连接的客户端发送消息。 例如聊天室。

可以向特定客户端或客户端组,特定用户或用户组发送消息。

可以对其进行缩放,以处理不断增加的流量。

可以使服务器于客户端互相调用其方法

1.新建类继承Hub,并注入UserManager<T>,方便之后操作

2.在programe中添加SignalR服务

builder.Services.AddSignalR();//注册SignalR框架

3.在JWT服务中添加额外的配置,为了从前端读取到tokn令牌

(opt=>{
        opt.Events = new JwtBearerEvents
        {//websocket不支持自定义报文头
         //所以在前端我们把WJT通过URL中的QueryStrig传递
         //然后在服务器端的OnMessageReceived中把QueryStrig中的JWT读出给context.Token
         //jsignalr内置JET验证,没有这个signalr接收不到jwt的token与claims
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];//***1
                var path = context.Request.Path;
                if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ChatHub"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

4.添加SIGNALR中间件,后方的地址表示继承HUB类的地址,SIGNALR连接建立后,前端会访问此类中的方法。

app.MapHub<ChatHub>("/ChatHub");

5.在PROGRANME中注册跨域服务,因为是前后端分离的项目,所以需要前端与后端两台服务器,需要跨域穿透才能进行服务器的互相访问。此处的URL表示前端服务器的地址。

string[] urls = new[] { "http://localhost:5173" };//跨域穿透,表示前端页面IP
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins(urls)
    .AllowAnyMethod().AllowAnyHeader().AllowCredentials();
    });
});

第四步:登录验证的功能实现

1.新建controller类继承ControllerBase抽象类。

把Manager与JWT的配置注入。

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class IdentityController : ControllerBase
    {
        private readonly UserManager<MyUser> userMG;
        private readonly RoleManager<MyRole> roleMG;
        private readonly IOptions<JWTOptions> options; 
        //通过builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"))
        //读取注册服务后要用IOptions<>及逆行依赖注入
        //private readonly JWTOptions options;

        public IdentityController(UserManager<MyUser> userMG, RoleManager<MyRole> roleMG, IOptions<JWTOptions> options)
        {
            this.userMG = userMG;
            this.roleMG = roleMG;
            this.options = options;
        }

2.实现LOgin方法,

在controller类中

新建一个loginmodel模型类,类内有名子及密码两个属性。

login方法具体思路,使用UuserManager属性的方法,通过前端传入的name,找到对应的用户。

之后检测密码正确性。NEW一个List<Claim>对象,方便用于存储Claim,Hub 类具有一个 Context 属性,该属性可以获得与链接相关的用户的 ClaimsPrincipal ,所以我们把用户的某些信息加入List<Claim>后,再把这个对象加入JWT实现传值。

此外,构建JWT还需要,JWT的key,与过期时间。

最后返回JWT字符串。

 [HttpPost]
 //[SkipJwtVsersionFilter]
 public async Task<ActionResult<string>> Login(LoginModel lml)
 {
     var user1=await userMG.FindByNameAsync(lml.Name);//取USER
     Console.WriteLine(lml.Name,lml.PassWord);
     var success = await userMG.CheckPasswordAsync(user1, lml.PassWord.ToString());//验证密码
     if (!success) {//失败则返回
         return BadRequest("failed please rewirte!!");
     }
     //user1.JwtVersion += 1;
     //await userMG.UpdateAsync(user1);
     //给JWT的PayLoad添加数据
     var claims = new List<Claim>();
     claims.Add(new Claim(ClaimTypes.NameIdentifier,user1.Id.ToString()));
     claims.Add(new Claim(ClaimTypes.Name,user1.UserName));
     //为了在令牌中加入user的ROLE
     var role = await userMG.GetRolesAsync(user1);
     foreach (var r in role)
     {
         claims.Add(new Claim(ClaimTypes.Role,r));
     }
   

     string key = options.Value.Key;
    
     //签名密钥,要求至少32位,从已注入的配置中取
     double time = options.Value.ExporeSeconds;
     //Console.WriteLine(time);
     DateTime ex = DateTime.Now.AddSeconds(time);
     //过期时间

     // 配置令牌的代码,照抄以前的代码
     byte[] secBytes = Encoding.UTF8.GetBytes(key);
     SymmetricSecurityKey secKey = new SymmetricSecurityKey(secBytes);
     var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
     //head
     var tokenDescriptor = new JwtSecurityToken(claims: claims,
         expires: ex, signingCredentials: credentials);
     //payload
     string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
     //signature
     //最后生成的JWT
     return jwt;
 }

第五步:群聊与私聊功能实现

1.在上方创建的继承HUB抽象类的类中写入群聊方法

具体思路:从前端去的name以后直接使用群聊方法对所有人发送消息,

注意Send A sync后第一个变量要和前端监听返回值的方法中的变量名相同

 public Task PublicAllMessage(string msg)
 {
     var userName = this.Context.User.FindFirstValue(ClaimTypes.Name);
     var time=DateTime.Now;
     string returnmsg = $"==用户=={userName}==说:{msg}------{time}";
      this.Clients.All.SendAsync("userMessage", returnmsg);//userMessag时前端接收变量的名字
     return Task.FromResult("ok"); 
 }

2.在此类中写入私聊方法

具体思路:前端给予私聊接者的Name,通过其取得具体user和Id,在Hub类context属性中取得发送者与ID之后,构建完数据字符串之后。首先使用.User()方法对发送者发送消息。只有由于发送者并不知道是否成功发送,所以对于发送者也要同步发送消息。但是当给自己发送消息时,会出现两条消息,所以,对于消息同步及你想一个判断,如果给自己发消息就不同步。

 public async Task<string> SendPrivateMessageAsync(string toUserName, string Message, string senderName)
 {
     var user1 = await hubUser.FindByNameAsync(toUserName);
     string toUserId = user1.Id.ToString();
     var senderUserId = this.Context.UserIdentifier;
     string senderUserName = this.Context.User.FindFirstValue(ClaimTypes.Name);
     var a = $"{senderUserName}====给用户={toUserName}发送:------{Message}";
     await this.Clients.User(toUserId).SendAsync("privateMsg", a);//给对方客户端推送
     if (senderUserId!=toUserId) { 
     await this.Clients.User(senderUserId).SendAsync("privateMsg", a);}
     //给对自己客户也要推送,表示知道消息发出,如果是自己给自己发则不用,否则会同时显示两条消息
     return "ok";
     

 }

第六步:前端代码

使用代码前,记得安装axios

具体实现思路就是像后端Login发送axios请求,如果登录成功就建立SignalR连接。

通过触发方法中的Connection.Invoke()方法调用后端的私聊与群聊方法。

登录成功后,使用Connection.ON()方法监听后端传回的值,放到之后页面上显示

<script >
import {defineProps,reactive,onMounted} from 'vue';
import * as signalR from '@microsoft/signalr';
import axios from 'axios';
let connection;

export default{
  //name:"Login",
  setup()
  {
      const state=reactive({userMessage:"",messages:[],pmessages:[],uname:"",upassword:"",privateMsg:"",touser:""});//页面上两个睡醒
      //const privatest=reactive({userMessage:"",messages:[],uname:"",upassword:"",touser:""})
      const login1=async function()
      {
        //请求数据的名字要和后端的名字一样才可以
            const payload={Name:state.uname,PassWord:state.upassword};//请求数据的名字要和后端的名字一样才可以
            axios.post("https://localhost:7176/api/Identity/Login",payload)//向后端(登陆验证)发送请求,亲求payload中的数据,包括TOKEN
            .then(async suc=>{//login success
                        const token=suc.data;//api/Identity/Login方法的返回值
                        const options={skipNegotiation:true,transport:signalR.HttpTransportType.WebSockets}//身份验证通过就建立signalr连接
                        options.accessTokenFactory=()=>token;

                        connection=new signalR.HubConnectionBuilder()//通过signalR与服务器建立连接
                          .withUrl('https://localhost:7176/ChatHub',options)//把token等连接字串,通过URL的QueryString传给后端的ChatHub
                           .withAutomaticReconnect().build();

                        await connection.start();//开始连接
                        alert("Login success");   

                        connection.on("privateMsg",msg => {
                                state.messages.push(msg);
                                //alert(msg);
                              });//监听私聊,意思时signalr连接在就监听
                             
                        connection.on("userMessage",msg=>{
                                state.messages.push(msg);
                              });//监听后端传回的值,放到state的messages中
                                
            })
            .catch(err=>{//login defead
                  alert("please rewirte!!!!!!!!!!!!");
            });
      };

      const privateMsgFunction=async function(e){
        if(e.keyCode!=13) return;
        await connection.invoke("SendPrivateMessageAsync",state.touser,state.privateMsg,state.uname);
        state.privateMsg="";
      };


      const textMsgOnkeypress=async function(a)
      {
        if(a.keyCode!=13) return;
        await connection.invoke("PublicAllMessage",state.userMessage);//调用后端PublicAllMessage方法,并传值
        state.userMessage="";
      };
      
  // onMounted(async function()
  // {
  //         // connection=new signalR.HubConnectionBuilder()//通过signalR与服务器建立连接
  //         // .withUrl('https://localhost:7176/ChatHub'
  //         // ,{skipNegotiation:true
  //         //       ,transport:signalR.HttpTransportType.WebSockets
  //         //       ,accessTokenFactory:state.token})//从后端取的值
  //         // .withAutomaticReconnect().build();
  //         // await connection.start();
  //         // connection.on("userMessage",msg=>{
  //         //         state.messages.push(msg);
  //         //       })//监听后端传回的值,放到state的messages中
  //         });
          
  return {state,textMsgOnkeypress,login1,privateMsgFunction};
}}
</script>

<template>
  <h3>一个登录一个用户,不同用户间实时交流</h3>
  <p></p>
  <div>
    账户:<input type="text" v-model="state.uname">
     密码:<input type="password" v-model="state.upassword"> 
     <button v-on:click="login1">登录</button>
  </div>
  <p>私聊功能</p>
  <div>
    私聊用户名称:<input type="text" v-model="state.touser"/>
    私聊内容:<input type="text" v-model="state.privateMsg" v-on:keypress="privateMsgFunction"/>
    <!-- <button v-on:click="privateMsgFunction">发送</button> -->
    <p>私聊功能</p>
  </div>
  <h4>群聊请输入文字:</h4>
  <input type="text"  v-model="state.userMessage"  
  v-on:keypress="textMsgOnkeypress"/>
  <div>
    <ul> 
      <li v-for="(msg,p) in state.messages" :key="p">{{msg}}</li>
     
    </ul>
  </div>
</template>

具体页面效果,我前端不行,写起来太麻烦了,好不好看无所谓,能实现具体功能就行了

总结:

加深了对

identity框架实,JWT令牌,Signalr的印象,

而且找到了自己写的代码的BUG,虽然是因为自己失误产生的BUG,但是自己花了两天一点点排除问题,解决了问题,还是收获了很多。

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值