WebSocket实现聊天室

news2024/11/19 9:34:16

需求

  • 实现用户登录功能
  • 展示用户好友列表功能
  • 实现用户历史消息展示
  • 实现单聊信息和群聊信息

效果展示

  • 用户登录
    在这里插入图片描述
  • 好友列表展示
    在这里插入图片描述
  • 历史消息展示
    在这里插入图片描述
  • 聊天

在这里插入图片描述

代码实现

说明:Springboot项目,页面是用 thymeleaf 整合的。

  • maven依赖
   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>
  • application.yml
spring:
  thymeleaf:
    cache: false
    suffix: .html
  • resource/templates目录下,创建页面
    1) login.html,点击登录调用了/user/login接口
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>

</head>
<body>

<div id="login" class="form-wrapper">
    <div class="header">
        登录
    </div>
</div>
<div>
    <span style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></span>
</div>
<form action="/user/login" method="post">

    <div >
        <div >
            <input th:type="text" th:name="username" placeholder="username">
        </div>
        <div >
            <input th:type="password" th:name="password" placeholder="password" >
        </div>
    </div>
    <div class="action" onclick="document.getElementById('lick1').click()">
        <div class="btn">
            确认
        </div>
    </div>
    <input th:type="submit" id="lick1">
</form>
</div>
</body>
</html>

2)chat.html,聊天主页面
[1]、退出登录按钮,调用了logout接口,把session中的token值清除了
[2]、好友列表,在跳转到chat页面的时候,调用了getUserList并且把用户列表数据注入到模型中,界面展示出来
[3]、连接websocket,调用了connectWebSocket()函数,调用了后端 websocket端点的onOpen方法
[4]、断开连接,调用了后台的onClose方法
[5]、发送消息,调用了后台的onMessage方法
[6]、查看历史消息,调用了后台的/history方法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html>
<head>
    <meta charset="UTF-8">
    <title>My WebSocket</title>
    <style>
        #message {
            margin-top: 40px;
            border: 1px solid gray;
            padding: 20px;
        }
    </style>
</head>
<body>

<div>
    用户token值:<span id="token" style="color: #ff0000" th:text="${session.token}"></span>        <div> <a href="/logout">退出登录</a></div>
</div>

<div>
    我的好友列表:<br/>
    <table border="1">
        <thead>

        </thead>
        <tbody>
        <tr th:each="user:${users}" style="color: blue">
           <a th:οnclick="aClick([[${user}]]);"  th:text="${user}" style="color: blue">  </a><br/>
        </tr>
        </tbody>
    </table>

</div>


昵称:<input type="text" id="nickname" th:value="${session.token}"/>
<button οnclick="conectWebSocket()">连接WebSocket</button>
<button οnclick="closeWebSocket()">断开连接</button>
<hr/>
<br/>
消息:<input id="text" type="text"/>
发送给谁: <input id="toUser" type="text">
<button οnclick="send()">发送消息</button>
<div id="message"></div>

历史消息:
<button οnclick="viewHistory()">查看历史消息</button>
<div id="history"></div>
</body>
<script type="text/javascript">
    var websocket = null;



    function conectWebSocket() {
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {

            let nickname = document.getElementById("nickname").value;
            if(nickname === ""){
                alert("请输入昵称");
                return;
            }

            websocket = new WebSocket("ws://localhost:8080/websocket/"+nickname);
        } else {
            alert('Not support websocket')
        }
        //连接发生错误的回调方法
        websocket.onerror = function () {
            setMessageInnerHTML("error");
        };
        //连接成功建立的回调方法
        websocket.onopen = function (event) {
            setMessageInnerHTML("Loc MSG: 成功建立连接");
        }
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
        //连接关闭的回调方法
        websocket.onclose = function () {
            setMessageInnerHTML("Loc MSG:关闭连接");
        }
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            websocket.close();
        }
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        var toUser = document.getElementById("toUser").value;

        var socketMsg = {msg:message,toUser:toUser};
        if(toUser == ''){
            //群聊
            socketMsg.type = 0;
        }else {
            //单聊
            socketMsg.type = 1;
        }

        websocket.send(JSON.stringify(socketMsg));
    }



    function aClick(e){
        console.log("aaa")
        console.log(e)
        document.getElementById("toUser").value = e;
    }


    function  viewHistory(){
        var toUser = document.getElementById("toUser").value;
        var httpRequest = new XMLHttpRequest(); //第一步:建立所需的对象
        httpRequest.open('GET', '/history?toUser='+toUser, true); //第二步:打开连接  将请求参数写在url中  ps:"./Ptest.php?name=test&nameone=testone"
        httpRequest.send(); //第三步:发送请求  将请求参数写在URL中
        httpRequest.onreadystatechange = function() {
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {
                console.log(httpRequest.responseText);

                document.getElementById('history').innerHTML += httpRequest.responseText + '<br/>';
            }
        };
    }

</script>
</html>
  • 配置类,GetHttpConfiguration.java

作用:把Http请求中的参数,传递到WebSocket中

public class GetHttpConfiguration extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 通过getUserProperties()使得websocket连接类中可获取到配置类中得到的数据
        Map<String, Object> userProperties = sec.getUserProperties();
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        userProperties.put("token",httpSession.getAttribute("token").toString());
        super.modifyHandshake(sec, request, response);
    }
}

  • 拦截器,LoginInterceptor.java

作用:登录拦截器,必须要在session中有token的值,否则跳转去登录。

public class LoginInterceptor implements HandlerInterceptor {
    static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        HttpSession session = request.getSession();
        Object token = session.getAttribute("token");
        if (Objects.isNull(token)) {
            response.sendRedirect("/login");
            return false;
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
        logger.info("postHandle...");
    }
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        logger.info("afterCompletion...");
    }
}
  • 配置类,WebConfig.java

作用:配置拦截器,配置不登录的路径

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()) //可以把配置类加入bean 然后autowier得到 或者@Bean 返回值得到 JavaConfig 三种方法都可以
                .addPathPatterns("/**") //拦截的路径 **代表所有
                .excludePathPatterns("/login","/user/login"); //不拦截的路径
    }
}

  • 配置类,WebSocketConfig.java

作用:开启websocket支持

@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 扫描@ServerEndpoint,将@ServerEndpoint修饰的类注册为websocket
     * 如果使用外置tomcat,则不需要此配置
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
  • 模型类,History.java

作用:封装聊天消息历史类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class History {
   private String from;
   private String to;
   private String time;
   private String content;
}

  • 模型类,User.java

作用:封装用户登录

@Data
public class User {
    private String username;
    private String password;
}
  • 模型类,SocketMsg.java

作用:封装消息发送类

public class SocketMsg {
    private int type;   //聊天类型0:群聊,1:单聊.
    private String fromUser;//发送者.
    private String toUser;//接受者.
    private String msg;//消息
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public String getFromUser() {
        return fromUser;
    }
    public void setFromUser(String fromUser) {
        this.fromUser = fromUser;
    }
    public String getToUser() {
        return toUser;
    }
    public void setToUser(String toUser) {
        this.toUser = toUser;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}
  • html路由类,SsoController.java

作用:配置login登录页面视图,配置chat页面视图,配置logout页面视图,配置登录逻辑

@Controller
@CrossOrigin
public class SsoController {
    @Autowired
    private UserController userController;
    @RequestMapping(value = "login", method = {RequestMethod.POST, RequestMethod.GET})
    public String login() {
        return "login";
    }

    @RequestMapping(value = "chat", method = {RequestMethod.POST, RequestMethod.GET})
    public String chat(HttpSession session,Model model) {
        List<String> users = userController.getUserList(session);
        model.addAttribute("a","123");
        model.addAttribute("users",users);
        return "chat";
    }


    @RequestMapping("logout")
    public String logOut(HttpSession session){
        session.removeAttribute("token");
        return "login";
    }

    @PostMapping("/user/login")
    public String login(HttpServletRequest request, HttpSession session, Map<String, Object> map, Model model) {
        String name = request.getParameter("username");
        String password = request.getParameter("password");
        if (UserController.userMap.containsKey(name)) {
            if (UserController.userMap.get(name).equals(password)) {
                session.setAttribute("token", name);
                return "redirect:/chat";
            }
        }

        model.addAttribute("msg", "请输入正确的账号和密码");
        return "login";
    }
}
  • 我的好友类,UserController.java

作用:模拟数据库用户,模拟用户的好友数据

@CrossOrigin
@RestController
public class UserController {
    //所有的用户
    public static Map<String, String> userMap = new HashMap<>();
    //每个用户对应的好友
    public static Map<String, List<String>> friendsMap = new HashMap<>();

    static {
        userMap.put("zhangsan", "123456");
        userMap.put("lisi", "123456");
        userMap.put("wangwu", "123456");
        userMap.put("zhaoliu", "123456");
        userMap.put("yangsilu", "123456");
        userMap.put("ranqilin", "123456");
        userMap.put("xuqiaodi", "123456");
        userMap.put("luowengang", "123456");

        friendsMap.put("zhangsan", List.of("lisi", "wangwu", "luowengang", "zhaoliu"));
        friendsMap.put("lisi", List.of("zhangsan", "wangwu", "ranqilin"));
        friendsMap.put("wangwu", List.of("zhangsan", "lisi", "xuqiaodi", "yangsilu"));

    }

    //获取我的好友
    @GetMapping("user/list")
    public List<String> getUserList(HttpSession session) {
        Object token = session.getAttribute("token");
        List<String> users = friendsMap.get(token.toString());
        return users;
    }
}
  • 聊天历史控制器,HistoryController.java

作用:获取用户聊天历史数据

@CrossOrigin
@RestController
public class HistoryController {


    @GetMapping("/history")
    public List<History> getHistory(@RequestParam("toUser")String toUser, HttpSession session){
        Object token = session.getAttribute("token");
        String fromUser = token.toString();

        List<String> datas = FileUtil.readFileLine();
        List<History> historyList = new ArrayList<>();
        for (String data : datas) {
            historyList.add(JSON.parseObject(data,History.class));
        }
        //排序
        List<History> collect = historyList.stream().filter(item -> {
            String from = item.getFrom();
            String to = item.getTo();
            boolean flag = checkIn(from, to, fromUser, toUser);
            return flag;
        }).sorted((x1,x2)-> x2.getTime().compareTo(x1.getTime())).
                collect(Collectors.toList());

        return collect;
    }

    private boolean checkIn(String from, String to, String fromUser, String toUser) {
        if(from.equals(fromUser) && to.equals(toUser)){
            return true;
        }
        if(from.equals(toUser) && to.equals(fromUser)){
            return true;
        }
        return false;
    }
}
  • websocket核心类,MyWebSocket.java

作用:1)onOpen方法,某个客户端的会话session存储到map中。每个客户端的socket对象存储在webSocketSet中。
2)onClose方法,连接关闭时候,把webSocket对象移除。
3)onError方法,如果连接发送异常会调用
4)OnMessage方法,客户端发送消息的方法。根据当前session的id,获取到发起方的Session对象,根据传递参数的用户名,获取到接收方的Session对象。判断接收方对象是否为空,不为空就可以发送消息。

@ServerEndpoint(value = "/websocket/{nickname}", configurator = GetHttpConfiguration.class)
@Component
public class MyWebSocket {

    //用来记录sessionId和该session进行绑定
    private static Map<String, Session> map = new ConcurrentHashMap<>();
    // sessionId和username的映射
    private static Map<String, String> sessionIdNameMap = new ConcurrentHashMap<>();


    //用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String nickname;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("nickname") String nickname, EndpointConfig config) {
        this.session = session;
        this.nickname = nickname;
        //在建立连接的时候,就保存频道号(这里使用的是session.getId()作为频道号)和session之间的对应关系
        map.put(session.getId(), session);
        Object token = config.getUserProperties().get("token");
        sessionIdNameMap.put(session.getId(), token.toString());

        webSocketSet.add(this);     //加入set中
        System.out.println("有新连接加入!当前在线人数为" + webSocketSet.size());
        this.session.getAsyncRemote().sendText("恭喜" + nickname + "成功连接上WebSocket-->频道号是:" + nickname + "当前在线人数为:" + webSocketSet.size());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        System.out.println("有一连接关闭!当前在线人数为" + webSocketSet.size());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("nickname") String nickname) {
        System.out.println("来自客户端的消息-->" + nickname + ":" + message);
        //群发消息
        // broadcast(nickname + ":" +message);

        //从客户端穿过来是json数据,转成SocketMsg对象,根据type判断是单聊还是群聊
        ObjectMapper objectMapper = new ObjectMapper();
        SocketMsg socketMsg;
        try {
            socketMsg = objectMapper.readValue(message, SocketMsg.class);
            if (socketMsg.getType() == 1) {
                //单聊,需要找到发送者和接受者
                socketMsg.setFromUser(session.getId()); //发送者
                Session fromSession = map.get(socketMsg.getFromUser());
                Session toSession = map.get(getSessionId(sessionIdNameMap, socketMsg.getToUser()));

                //发送给接受者
                if (toSession != null) {
                    String from = nickname + ":" + socketMsg.getMsg();
                    String to = nickname + ":" + socketMsg.getMsg();
                    fromSession.getAsyncRemote().sendText(from);
                    toSession.getAsyncRemote().sendText(to);
                    //保存消息
                    String time = getTime();
                    History history = History.builder()
                            .from(sessionIdNameMap.get(session.getId()))
                            .to(socketMsg.getToUser())
                            .time(time)
                            .content(nickname + ":" + socketMsg.getMsg())
                            .build();
                    FileUtil.toFile(JSON.toJSONString(history));
                } else {
                    fromSession.getAsyncRemote().sendText("系统消息:对方不在线或者您输入的频道号不对");
                }

            } else {
                //群发消息
                broadcast(nickname + ": " + socketMsg.getMsg());
                // 保存消息
            }

        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }


    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 群发自定义消息
     */
    public void broadcast(String message) {
        for (MyWebSocket item : webSocketSet) {
            //同步异步说明参考:http://blog.csdn.net/who_is_xiaoming/article/details/53287691
            //this.session.getBasicRemote().sendText(message);
            item.session.getAsyncRemote().sendText(message);//异步发送消息.
        }
    }

    public String getTime(){
        SimpleDateFormat format = new SimpleDateFormat("yyyy-dd-mm HH:mm:ss");
        return format.format(new Date());
    }

    public String getSessionId(Map<String, String> map, String name) {
        if (StringUtils.isBlank(name)) return null;
        for (Map.Entry<String, String> entry : map.entrySet()) {
            if (entry.getValue().equals(name)) {
                return entry.getKey();
            }
        }
        return null;
    }
}
  • 历史消息存储和读取工具类,FileUtil.java

作用:把历史消息存储到文件中。

public class FileUtil {

    private static final String file = "D:\\websocket-sse\\wechat\\history.txt";

    public static void main(String[] args) {
        readFileLine();
    }

    public static void toFile(String content) {
        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(file, true)));
            out.write(content + "\n");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();

            }

        }

    }


    public static List<String> readFileLine() {
        List<String> list = new ArrayList<>();
        try (FileInputStream in = new FileInputStream(file)) {
            Scanner sc = new Scanner(in, "UTF-8");
            while (sc.hasNext()) {
                String content = sc.nextLine();
                if (StringUtils.isNotBlank(content)) {
                    list.add(content);
                }
            }

        } catch (IOException e) {
        }
        return list;
    }

}

测试

  • 启动项目
  • 浏览器访问 localhost:8080/login,进入登录页面 ,分别用两个不同的浏览器,登录zhangsan/123456和lisi/123456
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 分别都点击 连接WebSocket
    在这里插入图片描述
  • 在lisi的登录界面,点击好友列表中的zhangsan,代表和zhangsan聊天,自动在发送给谁中展示了zhangsan;在zhangsan的登录界面中,点击好友列表中的lisi,代表和lisi聊天,自动在发送给谁中展示了lisi。

在这里插入图片描述

  • 查看历史记录
    在这里插入图片描述
  • 发送消息,在消息文本框中输入要发送的消息,然后点击发送消息
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135672.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

π122E31兼容ISO7221CD 200Mbps高速率 双通道数字隔离器

π122E31兼容ISO7221CD 200Mbps高速率 双通道数字隔离器&#xff0c;具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。 产品传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现 3.0kVrms 隔离耐压等级和 DC 到…

MySQL数据库的安装与实现

MySQL在win系统中的安装 第1步&#xff1a;下载安装&#xff08;在windows系统中安装&#xff09; http://downloads.mysql.com/archives/community/ 我选择安装的是5.7.31&#xff0c;一般MySQL主要分为两个版本&#xff0c;一个是5.7系列&#xff0c;一个是5.8系列&#xf…

Linux 音频驱动

1 I.MX6ULL 开发板通过此接口外接了一个 WM8960 音频 DAC 芯片。 2 在信号处理领域&#xff0c;外界的声音是模拟信号&#xff0c;处理器能理解的是数字信号&#xff0c;因此这里就涉及到一个模拟信号转换为数字信号的过程&#xff0c;而完成这个功能的就是 ADC 芯片。 如果处…

MySQL的锁

把那些可能会被多个线程同时操作的资源称为临界资源&#xff0c;加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。数据库作为用户共享的一个资源&#xff0c;如何保证数据并发访问一致性也是所有数据库必须解决的问题&#xff0c;如何加锁是数据库并发访问性能…

字节前端高频手写面试题(持续更新中)

Promise // 模拟实现Promise // Promise利用三大手段解决回调地狱&#xff1a; // 1. 回调函数延迟绑定 // 2. 返回值穿透 // 3. 错误冒泡// 定义三种状态 const PENDING PENDING; // 进行中 const FULFILLED FULFILLED; // 已成功 const REJECTED REJECTED; // 已…

Vite构建工具

什么是构建工具&#xff1a;打包:将我们写的浏览器不认识的代码交给构建工具进行编译处理的过程就叫做打包&#xff0c;打包完成以后会给我们一个浏览器可以认识的文件 一个构建工具他到底承担了哪些脏活累活: 1. 模块化开发支持:支持直接从node_modules里引入代码&#xff0b…

十三、Kubernetes yaml资源清单详解

1、概述 kubectl提供了各种命令&#xff0c;来管理集群中的pod&#xff0c;但是这些命令都是为了方便运维测试&#xff0c;实际生产部署还得用yaml文件来部署&#xff0c;所以弄清楚各类资源的字段是非常重要的。 资源清单就是k8s当中用来定义pod的文件&#xff0c;语法格式遵…

C#语言实例源码系列-实现ID卡的识别

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

Vlan的原理与配置

传统以太网的问题 规模大了&#xff0c;之后导致性能也很差&#xff0c;广播会增加 解决&#xff1a;用Vlan&#xff0c;不受地域限制&#xff0c;同一Vlan内的设备才能直接进行二层通信 实验 首先配置以上vlan与电脑ip 配置IP之后可以进行ping命令测试刚开始是连通的&#x…

计算机视觉实战----AlexNet网络及使用colab跑YoloV5代码

系列文章目录 文章目录系列文章目录前言一、用colab薅羊毛二、使用百度飞浆操作三、二、使用步骤1.引入库2.读入数据总结前言 一、用colab薅羊毛 Colaboratory 简称“Colab”&#xff0c;是 Google Research 团队开发的一款产品。在 Colab 中&#xff0c;任何人都可以通过浏览…

分享107个PHP源码,总有一款适合您

链接&#xff1a;https://pan.baidu.com/s/1Su77mBUx87vk0lzSLyvnyw?pwdyo96 提取码&#xff1a;yo96 PHP源码 分享107个PHP源码&#xff0c;总有一款适合您 page_count 1 # 每个栏目开始业务content"text/html; charsetgb2312"base_url "https://down.c…

基于 Spring Boot 的 RESTful API 设计与实现

RESTful 是一种规范&#xff0c;符合 RESTful 的 Api 就是 RESTful Api。简单的说就是可联网设备利用 HTTP 协议通过 GET、POST、DELETE、PUT、PATCH 来操作具有 URI 标识的服务器资源&#xff0c;返回统一格式的资源信息&#xff0c;包括 JSON、XML、CSV、ProtoBuf、其他格式。…

OpenVINS 官方文档 第一部分

参考链接&#xff1a;OpenVINS https://docs.openvins.com/index.html 1. Getting Started 欢迎来到OpenVIINS项目&#xff01;以下指南将帮助新用户下载软件并在我们支持的数据集上运行。此外&#xff0c;我们还提供有关如何在我们的系统上运行您自己的传感器的信息&#xff0…

《500强高管谈VE价值工程》-对经营变革期下VE的期待

文章出处&#xff1a;日本VE协会杂志 文章翻译&#xff1a;泰泽项目部 关注泰泽&#xff1a;实现高利润企业 《500强高管谈VE价值工程》-对经营变革期下VE的期待 作者: 鹿岛建设常务董事小野馨喜 随着21世纪的到来&#xff0c;社会结构和经济环境正在发生重大转变&…

MobPush 创建推送

功能说明 MobPush提供遵循REST规范的HTTP接口&#xff0c;适用各开发语言环境调用。 IP绑定 工作台可以绑定服务器IP地址&#xff0c;未绑定之前所有IP均可进行REST API的调用&#xff0c;绑定后进仅绑定的IP才有调用权限。 调用地址 POSThttp://api.push.mob.com/v3/push/c…

东南亚开年大促必爆商品;2023Lazada家居家饰需求品类来袭

为持续提升商家体验&#xff0c;保障经营效率&#xff0c;2023年Lazada平台将调整Birthday sale生日大促的活动时间节奏 2023年6个国家站点&#xff08;印尼、马来西亚、菲律宾、新加坡、泰国、越南&#xff09;均会将生日大促提前至2023年3月3日开始。 家居家饰需求清单&…

什么是快速排序?

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注&#xff01; 作者| 慕课网精英讲师 JdreamZhang 快速排序&#xff08;Quick Sort&#xff09;&#xff0c;是计算机科学与技术领域中非常经典的一种排序算法&#xff0…

华为云工程师HCIA——桌面云解决方案概述

一、桌面云解决方案概述 瘦终端和胖终端 瘦终端&#xff1a;提供屏幕&#xff0c;和很小的计算能力胖终端&#xff1a;提供主要计算能力 桌面云架构VDI与IDV 华为桌面云解决方案逻辑架构 FusionCompute云平台架构 桌面云的优势 二、桌面云组件介绍 接入和访问控制层 WI&…

图像处理(2)——图像特征提取LBP

图像处理&#xff08;2&#xff09;——图像特征提取LBP 其实现在大家都说图像处理&#xff0c;其实计算机是不认识图片的&#xff0c;之所以可以处理图像&#xff0c;其实图像就是一个个矩阵&#xff0c;其实是数字&#xff0c;转而其实都是在处理数字。深度学习在图像上的建…

数字图像处理实验——数字图像处理初步

一、实验目的与要求 1.熟悉及掌握在MATLAB中能够处理哪些格式的图像&#xff1b; 2.熟练掌握在MATLAB中如何读取图像及图像的属性信息&#xff08;大小、颜色、亮度&#xff08;灰度&#xff09;、宽度、高度等&#xff09;&#xff1b; 3.掌握如何在MATLAB中按照指定要求存储一…