首页 > 其他分享 >webScoket离线消息暂存,上线发送

webScoket离线消息暂存,上线发送

时间:2024-03-15 10:45:43浏览次数:26  
标签:function console webScoket 离线 用户 data key 暂存 userId

webScoket离线消息暂存,上线发送

用webScoket的即时聊天通讯,功能可群发单发,可对不在线用户发送消息时用户一上线立马就能收到消息,也可以查看未读数量

导入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
   <version>1.3.5.RELEASE</version>
</dependency>

添加websocket配置类

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。

后端代码

package com.zl.socket;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
 
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
 
import org.springframework.stereotype.Component;
 
import com.Utils.WebSocketMapUtil;
 
 
/**
 * 即时通讯
 * @author 史**
 *
 * 2018年9月17日
 */
@Component
@ServerEndpoint(value = "/websocket") 
public class MyWebSocket {
	private volatile static List<Session> sessions = Collections.synchronizedList(new ArrayList());
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //设置为静态的 公用一个消息map ConcurrentMap为线程安全的map  HashMap不安全
    private static ConcurrentMap<String, Map<String, List<Object>>> messageMap=new ConcurrentHashMap<>();
    /**
     * /**
     * 连接建立成功调用的方法
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     * @param userId 用户id
     * @throws Exception
     */
    @OnOpen
    public void onOpen(Session session) throws Exception{
        System.out.println("开始");
        this.session = session;
       // sessions.add((Session) this);
	  	 String key="";//当前用户id
	  	String objectUserId="";//对象id
	  	 if("".equals(session.getQueryString()) || session.getQueryString()==null) {
	  		
	  		key= "";
	  	 }else {
	  		 String keString=session.getQueryString();
	  		 if(keString.length()>1) {
	  			 key=keString.split(",")[0];
	 	  		 objectUserId=keString.split(",")[1];
	  		 }else {
	  			key=session.getQueryString();
	  		 }
	  		
	  	 }
	      WebSocketMapUtil.put(key,this);
	      if(messageMap.get(key)!=null) {
	    	  //说明在用户没有登录的时候有人给用户发送消息
	    	  //该用户所有未收的消息
	    	  Map<String, List<Object>> lists=messageMap.get(key);	
	    	  //对象用户发送的离线消息
	    	  List<Object> list= lists.get(objectUserId);
	    	  if(list!=null) {
	    		  for(int i=0;i<list.size();i++) {
	  	    		//封装消息类型   消息内容+"["+发送消息的人+";"+接收消息的人","+0
	  				  String message=list.get(i)+"["+objectUserId+";"+key+","+0;
	  				  onMessage(message);
	  	    	  }   
	    	  }
	    	 
	    	  // map中key(键)的迭代器对象
	    	  //用户接收完消息后删除 避免下次继续发送
	    	  Iterator iterator = lists.keySet().iterator();
	    	  while (iterator.hasNext()) {// 循环取键值进行判断
	    	  String keys = (String) iterator.next();//键
	    	  if (objectUserId.equals(keys)) {
		    	  iterator.remove(); // 移除map中以a字符开头的键对应的键值对
		    	 // messageMap.remove(key);
	    	  }
	    	  }
	    	  
	      }
	     
    }
 
    /**
     * 连接关闭调用的方法
     * @throws Exception 
     */
    @OnClose
    public void onClose() throws Exception{
        //从map中删除
    	System.out.println(session.getQueryString().split(",")[0]);
        WebSocketMapUtil.remove(session.getQueryString().split(",")[0]);
        System.out.println("关闭");
    }
 
    /**
     * 收到客户端消息后调用的方法  单发
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     * @param tit  0 单发 1  群发
     * @throws IOException 
     */
    @OnMessage
    public void onMessage(String message){
    		String tit=message.substring(message.lastIndexOf(",")+1, message.length());
    	   System.out.println("收到");
    	   String userId=message.substring(message.lastIndexOf(";")+1,message.lastIndexOf(","));//接收消息的用户
    	   System.out.println("发给:"+userId);
    	   String sendUserId=message.substring(message.lastIndexOf("[")+1,message.lastIndexOf(";"));//发送消息的用户
    	   System.err.println("发消息的用户:"+sendUserId);
    	   message=message.substring(0, message.lastIndexOf("["));//发送的消息
    	   System.err.println("客户端发来的信息:"+message);
        try {
        	MyWebSocketController myWebSocket= ((MyWebSocketController) WebSocketMapUtil.get(userId));
            if(myWebSocket != null){
            	if("0".equals(tit)) {
            		//单发
            		myWebSocket.sendMessage(message);
            	}else if ("1".equals(tit)) {
            		//调用群发方法
          		  myWebSocket.sendMessageAll(message);
				}
              
            }else {
				//不在线
            	System.out.println("不在线");
            	if(messageMap.get(userId)==null) {
            		//用户不在线时 第一次给他发消息
            		Map<String, List<Object>> maps=new HashMap<>();//该用户的所有消息
            		List<Object> list=new ArrayList<>();//该用户发的离线消息的集合
            		list.add(message);
            		maps.put(sendUserId, list);
            		messageMap.put(userId, maps );
            	}else {
            		//不在线再次发送消息
            		//给用户的所有消息
            		Map<String,List<Object>> listObject=messageMap.get(userId);
            		List<Object> objects=new ArrayList<>();
            			if(listObject.get(sendUserId)!=null) {//这个用户给收消息的这个用户发过消息
            				//此用户给该用户发送过离线消息(此用户给该用户发过的所有消息)
            				objects=listObject.get(sendUserId);
            				objects.add(message);//加上这次发送的消息
            				//maps.put(sendUserId, objects);
            				//替换原来的map
            				listObject.put(sendUserId, objects);         				
            			}else {//这个用户没给该用户发送过离线消息
                			objects.add(message);
                			listObject.put(sendUserId, objects);
            			}
            		//Map<String, List<Object>> map=new HashMap<>();
//            		for(Map<String, List<Object>> map : listObject) {//遍历该用户的所有未发送的集合
//            			if(map.get(sendUserId).size()>0) {//这个用户给他发送过离线消息
//            				objects=map.get(sendUserId);
//            				objects.add(message);
//            			}
//            		}
            	
            		//maps.put(sendUserId, message);
            		messageMap.put(userId, listObject );
            		
            	}
			}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void one rror(Session session, Throwable error){
    	   System.out.println("错误");
        error.printStackTrace();
        
    }
 
 
    /**
     * 发送消息方法。
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
    	  // System.out.println("发消息");
        this.session.getBasicRemote().sendText(message);
    }
 
    /**
     * 群发消息方法。
     * @param message
     * @throws IOException
     */
    public void sendMessageAll(String message) throws IOException{
    	   System.out.println("群发");
        for(MyWebSocketController myWebSocket : WebSocketMapUtil.getValues()){
            myWebSocket.sendMessage(message);
        }
    }
    
    
    /**
     * 获取该用户未读的消息数量
     * @param userId 当前用户id
     * @param objectUserId  对象id
     * @return
     */
    public int getMessageCount(String userId,String objectUserId) {
    	//获取该用户所有未收的消息
    	Map<String, List<Object>> listMap=messageMap.get(userId);
    	if(listMap != null) {
    		List<Object> list=listMap.get(objectUserId);
    		if(list!=null) {
    			return listMap.get(objectUserId).size();
    		}else {
    			return 0;
    		}
        	
    	}else {
    		return 0;
    	}
    	
    }
}

这里是需要用到的一个工具类

package com.Utils;
 
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
 
import com.caesar.controller.MyWebSocketController;
 
 
 
 
/**
 * 工具类  用来存放删除获取用户
 * @author 史**
 *
 * 2018年9月17日
 */
public class WebSocketMapUtil {
    public static ConcurrentMap<String, MyWebSocketController> webSocketMap = new ConcurrentHashMap<String, MyWebSocketController>();
    public static void put(String key, MyWebSocketController myWebSocket){
        webSocketMap.put(key, myWebSocket);
    }
 
    public static MyWebSocketController get(String key){
         return webSocketMap.get(key);
    }
 
    public static void remove(String key){
         webSocketMap.remove(key);
    }
 
    public static Collection<MyWebSocketController> getValues(){
        return webSocketMap.values();
    }
 
}

前端代码

小程序的前台调用代码,html的代码调用也类似

// pages/message/chat/chat.js
//获取应用实例
const app = getApp();
/*路径 */
var apiURL = require('../../../apiURL');
Page({
 
  /**
   * 页面的初始数据
   */
  data: {
    receiveUserId:'',//接收消息的用户id
    chatRecord:[],//聊天记录
    objectuser:[],//聊天对象信息
    userInfo:[],//用户信息
    chatListId:'',//聊天室id
    status:'',//聊天室转态
    value:'',
    height:'',//设备高度
    scrollTop:0
  },
 
  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    console.log(options)
    var _this = this;
    // _this.getChatRecord();
    //将json字符串转为json对象
    //对象信息
    var objectuser=JSON.parse(options.objectuser);
    //聊天室id
    var chatListId = options.chatListId;
    if(chatListId == ''){//聊天室id为空说明不是从聊天室列表过来的 所以查询和该用户是否有聊天室
      _this.isChatList(options.objectuserid, app.globalData.userId);
    }else{
      //查聊天室的聊天记录
      _this.getChatRecord(chatListId, app.globalData.userId);
      _this.setData({
        chatListId: chatListId,
      })
    }
    //查用户信息
    _this.getUserInfo(app.globalData.userId);
    _this.data.receiveUserId = options.objectuserid;
 
    _this.setData({
      objectuser: objectuser,
    })
    if (!app.globalData.communication) {
      //通讯没有连接  向后台发起连接
      wx.connectSocket({//传入对象id    当前用户id和对象用户id
        url: apiURL.wxLinkMessage.linkMessages + app.globalData.userId + "," + options.objectuserid,
        header: {
          'content-type': 'application/json'
        },
 
        method: "GET"
      })
    }
   
    
 
  },
 
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {
   
    //连接成功
    wx.onSocketOpen(function () {
      console.log('连接成功');
      app.globalData.communication = true
    })
    //连接失败
    wx.onSocketError(function (res) {
      app.globalData.communication = false
      console.log('WebSocket连接打开失败,请检查!')
    })
 
    //接收
    wx.onSocketMessage(function (res) {
      console.log("接收服务器发过来的消息")
      console.log(res)
    })
    wx.onSocketClose(function (res) {
      console.log('WebSocket 已关闭!')
    })
  },
 
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
 
  },
 
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
   // console.log("++++++++++++++++++++++++++++++")
  },
 
  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
     app.globalData.communication=false;
      //关闭当前连接
      wx.closeSocket()
    
  },
 
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
 
  },
 
  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
 
  },
 
  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
 
  },
 
  sendMessage: function (e) {
    var _this=this;
    // console.log(e.detail.value.message)
    var message = e.detail.value.message
    if (_this.Trim(message) == null || _this.Trim(message) == ''){
      return wx.showToast({
        title: '信息不能为空',
        icon: 'none',
        duration: 2000
      })
    }
      //封装消息类型   消息内容 + "[" + 发送消息的人 + ";" + 接收消息的人","+0
      //0是单发1是群发
    var cotnt = message + "[" + app.globalData.userId + ";" + _this.data.receiveUserId + ',' + 0;
    //保存聊天记录
    _this.save(cotnt);
    wx.sendSocketMessage({
      // message + "[" + "1c3546026fe24ff08d28115d58063263" + ";" + '944b50fd0eb64c4c816a4f3bf3203842' + ',' + 0,
      data: cotnt,
    })
    _this.setData({
      value:''
    })
    if (_this.data.status == 0 || _this.data.status==''){
      //修改聊天室状态
      // console.log("修改")
      _this.update()
    }
  },
 
  /**
 * 去空格
 */
  Trim: function (str) {
    return str.replace(/(^\s*)|(\s*$)/g, "");
  },
 
  /**
   * 查询两个人的聊天记录
   */
  getChatRecord: function (chatListId,sendUserId){
   // console.log(sendUserId)
    var _this = this;
    wx.request({
      url: apiURL.wxChatRecord.getChatRecord,
      method: "POST",
      header: {
        'content-type': 'application/x-www-form-urlencoded'
 
      },
      data: { "chatListId": chatListId, "sendUserId": sendUserId },
      success: function (data) {
          console.log(data)
        var chatList = data.data.data;
        var len = 10000 * chatList.length;
        if (data.data.code == 200) {
          return _this.setData({
            chatRecord: chatList,
            scrollTop: len//定位在最下方 显示最后一条信息
          })
        }
        wx.showToast({
          title: '系统错误',
          icon: 'none',
          duration: 2000
        })
      }
    })
  },
 
 
 
  // getUserInfo: function () {
  //   var that = this
  //   wx.getSetting({
  //     success(res) {
  //       if (!res.authSetting['scope.userInfo']) {
  //         wx.authorize({
  //           scope: 'scope.userInfo',
  //           success() {
  //             that.UserLogin();
  //           }
  //         })
  //       }
  //       else {
  //         that.UserLogin();
  //       }
  //     }
  //   })
  // },
/**
 * 查用户信息
 */
  getUserInfo:function(userId){
    var _this = this;
    //先从缓存拿取  缓存里没有再从 数据库拿取
    //console.log("查询用户信息走缓存")
    var userInfo = wx.getStorageSync(userId);
    // console.log(userInfo)
    if (userInfo) {
      //成功获取到数据绑定到页面
      _this.setData({
        userInfo: userInfo
      })
    } else {
      // console.log("缓存查不到用户信息,向后台发起请求")
      //查询不到向后台发起请求
      //查询用户信息
      wx.request({
        url: apiURL.wxUser.getBaseInfo,
        method: "POST",
        header: {
          'content-type': 'application/x-www-form-urlencoded' // post请求时需改成这个才行 否则后台接收不到参数
        },
        data: { "id": userId},
        dataType: "json",
        success: function (data) {
          // console.log(data)
          //获取失败弹窗
          if (data.data.code != 200) {
            wx.showToast({
              title: '系统错误',
              icon: 'none',
              duration: 2000
            })
 
          } else {
            // console.log("查询成功")
            // console.log(data.data)
            //成功获取数据 放到缓存 
            wx.setStorageSync(app.globalData.userId, data.data.result)
            //同时将数据绑定
            _this.setData({
              userInfo: data.data.result
            })
 
          }
 
        }
      })
    }
  },
/**
 * 根据双方id查聊天室是否存在
 */
  isChatList: function (currentUserId, objectUserId){
    var _this = this;
    wx.request({
      url: apiURL.wxChatList.isGetChatList,
      method: "POST",
      header: {
        'content-type': 'application/x-www-form-urlencoded'
 
      },
      data: { "currentUserId": currentUserId, "objectUserId": objectUserId },
      success: function (data) {
        // console.log(data)
        //查聊天室的聊天记录
        _this.getChatRecord(data.data.data.id, app.globalData.userId);
        _this.setData({
          chatListId: data.data.data.id,
          status: data.data.data.status,
          
        })
      }
         
    })
  },
  /**
   * 保存聊天记录
   */
  save: function (cotnt){
    var _this=this;
    wx.request({
      url: apiURL.wxChatRecord.save,
      method: "POST",
      header: {
        'content-type': 'application/x-www-form-urlencoded'
      },
      data: { "message": cotnt },
      success: function (res) {
        // console.log(res.data.data)
        var chatRecord=  _this.data.chatRecord;
        chatRecord.push(res.data.data)
        // console.log(chatRecord)
        var len = 1000 * chatRecord.length;
        _this.setData({
          users: res.data,
          userId: app.globalData.userId,
          chatRecord:chatRecord,
          scrollTop: len,//使新发出的信息显示在最下面
        })
 
 
      }
    })
  },
 
  /**
   * 修改聊天室转态
   */
  update: function (){
    var _this=this;
    wx.request({
      url: apiURL.wxChatList.update,
      method: "POST",
      header: {
        'content-type': 'application/x-www-form-urlencoded'
      },
      data: { "id": _this.data.chatListId, "status": 1},
      success: function (res) {
        // console.log(res.data)
        if(res.data.code==200){
          _this.setData({
            status: 1
          })
        }
       
 
 
      }
    })
  },
 
  /**
   * 跳到对象用户详情
   */
  sendOtherUser(e){
    console.log(e)
    var _this=this;
    wx.navigateTo({
      url: '../../other/personDetails/personDetails?userId=' + _this.data.receiveUserId,
    })
  },
  /**
   * 查看我的详情
   */
  sendUser:function(){
    wx.navigateTo({
      url: '../../my/my?userId=' + app.globalData.userId,
    })
  }
  
})

问题以及解决方法

1. 无法与前端建立连接

使用ServerEndpointExporter但没用使用外置tomcat容器

错误原因:ServerEndpointExporter需要外置tomcat容器运行环境,但平常我们都是使用SpringBoot内置tomcat,导致ServerEndpointExporter在运行时一直报错。

解决方案:在pom.xml文件中,排除SpringBoot自带的嵌入tomcat,添加外置的tomcat依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<!-- 移除嵌入式tomcat插件 -->
	<exclusions>
    	<exclusion>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 添加外置的tomcat依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

2. 使用拦截器或过滤器但没有对请求放行

错误原因:使用拦截器或过滤器但没有对请求放行,特别是使用Spring Security或Shiro,很容易遗忘放行请求路径

解决方案:在相应的配置文件中放行请求路径

filterMap.put("/websocket/**", "anon");//开放webSocket路径

3. 解决在web页面中动态口令场景下的连接与断开问题

在页面维持期间,动态口令需要一直不断的向前端推送实时口令,但是何时断开以及是谁断开就成了一个问题

解决方式如下:
创建一个WebSocketConnectStatusController类,
在前端发起websocket请求前,由前端生成一个特定的长度的key,
(1)进入动态口令页面,首先调用doIn接口并传入key,该key由前端保留备用;后端使用静态map来存放key,并将value设为0。
(2)建立连接,同时在session中传入上一步生成的key

@RestController
@RequestMapping("/webSocketConnectStatus")
public class WebSocketConnectStatusController {

    public static Map<String, String> map = new HashMap<>();

    @GetMapping("/doIn")
    @ApiOperation(value = "doIn方法")
    public String doIn(String key){

        //System.out.println("调用了doIn---");

        map.put(key,"0");

        return "开始获取动态码了";
    }

    @GetMapping("/doOut")
    @ApiOperation(value = "doOut方法")
    public String doOut(String key){

        //System.out.println("调用了doOut---");

        map.put(key,"1");

        return "改完状态了,动态码获取结束";
    }

}

(3)当用户离开页面时,调用doOut接口,并传入之前保留的本次连接的key,后端将静态map中对应key的value更新为1,此时后端不再向前端推送新的口令,循环结束。

后端推送消息的具体逻辑如下:

	@OnMessage
    public void onMessage(String message, Session session) {

        String key = session.getQueryString();//获取session中的key

        try {

            while ("0".equals(WebSocketConnectStatusController.map.get(key))){
                Thread.sleep(1000);//每秒推送一次
                sendMessage("生成你的动态码");
            }

            WebSocketConnectStatusController.map.remove(key);//清空本次链接的key

        } catch (IOException e) {
            System.out.println("IO异常");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

只有当循环结束时才能正常断开连接,否则会报IO异常


原文章地址:
https://blog.csdn.net/qq_39897814/article/details/93597408
https://blog.csdn.net/qq_38589118/article/details/129004248

标签:function,console,webScoket,离线,用户,data,key,暂存,userId
From: https://www.cnblogs.com/1399z3blog/p/18074908

相关文章

  • salt-minion离线安装
    1.在nexus做salt官方的yum源代理http://xxxx/repository/gw-yum-proxys代理https://repo.saltproject.io/salt/py3/2.在yum仓库里上传repo文件及salt.sh安装文件latest.repo 文件[salt-repo]name=SaltrepoforRHEL/CentOS7PY3baseurl=http://xxxx/repository/gw-yum-......
  • python(pip)包/模块:如何离线安装?
    1、生成requirements.txt文件如果有同环境服务器,可直接生成requirements.txt,会把当前服务器下的包和版本写入文件中。pipfreeze>requirements.txt如安装指定包,创建requirements.txt,输入包名==版本号//只输入包名,默认最新版本。例:xlwt==1.3.02、下载包在requirements.t......
  • element ui 中文离线文档(百度云盘下载)
    一般内网开发上不了网,用离线版本比较方便,下载地址:https://download.csdn.net/download/li836779537/88355878?spm=1001.2014.3001.5503下载后里面有个index.hrml双击打开就可以用效果如下:......
  • 高德地图 离线地图jsapi2.0 插件引用
    当引用一个插件,但是这个插件没有时会报错:UncaughtTypeError:AMap.ControlBarisnotaconstructor这里引用了罗盘这个插件这时候需要翻看高德的离线js了目前有两种逻辑:一个是同级目录下下载一个对应插件的js。还有一个是目录下有mapsplugin.js,通过这个插件来加载的,那么......
  • docsify-cli 的安装包怎么离线下载
    要在没有网络连接的情况下离线下载docsify-cli的安装包,你可以按照以下步骤进行操作:1.在有网络连接的环境下,在你的终端中执行以下命令,来下载docsify-cli的安装包:npmpackdocsify-cli这将会在当前目录下生成一个类似docsify-cli-x.x.x.tgz的文件,其中x.x.x是版本号2.......
  • Logstash系列---【centos7离线安装logstash7.8.0】
    1.安装包下载地址一般根据es的版本来确定logstash的版本,一般保持一致即可。Logstash和es版本对应关系:https://www.elastic.co/cn/support/matrix#matrix_compatibility。Logstash下载地址:https://www.elastic.co/cn/downloads/past-releases/logstash-7-8-02.解压并复制配置......
  • sshpass的离线安装与卸载
    sshpass的离线安装与卸载一.安装1.解压安装tar-zxvfsshpass-1.05.tar.gzcdsshpass-1.06./configure  #需要安装gcc环境makemakeinstall 2.验证安装成功 sshpass-V 二.卸载cdsshpass-1.05/makeuninstallmakeclean   ......
  • 离线部署docker-ce
    下载包删除或者备份原来的所有的yum源文件。然后添加阿里的yum源:wget-O/etc/yum.repos.d/CentOS-Base.repohttp://mirrors.aliyun.com/repo/Centos-7.repo或者curl-o/etc/yum.repos.d/CentOS-Base.repohttp://mirrors.aliyun.com/repo/Centos-7.repo更新:清除缓存......
  • Win7离线安装.Net 4.8
    安装补丁:先打上一个KB2813430的补丁,然后再安装net-framework4.8即可成功。32位系统补丁下载地址:https://www.microsoft.com/zh-CN/download/details.aspx?id=3911064位系统补丁下载地址:https://www.microsoft.com/zh-CN/download/details.aspx?id=39115安装补丁后还不行下载......
  • 基于AntSK与LLamaSharp打造私人定制的离线AI知识库
          随着人工智能的不断发展,AI已经逐渐成为我们日常生活中不可分割的一部分。今天,我为大家带来的是一个我近期投入研究的开源项目——AntSK,它结合了LLamaSharp,不仅带来了高效便捷的本地离线AI知识库搭建方法,更是无需借助公司账户,个人开发者也能轻松搭建和使用。项......