ruoyi-nbcio增加websocket与测试页面

   更多ruoyi-nbcio功能请看演示系统

   gitee源代码地址

   前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 

 

     为了后面流程发起等消息推送,所以需要集成websocket。

     1、后端增加websoket支持

       首先在framework模块里的pom.xml增加websocket

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

     2、增加websocket配置 

package com.ruoyi.framework.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** 开启WebSocket支持*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

3、增加websocket服务,当然这部分后面还要修改

package com.ruoyi.framework.websocket;import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.domain.BaseProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;// @ServerEndpoint 声明并创建了webSocket端点, 并且指明了请求路径
// id 为客户端请求时携带的参数, 用于服务端区分客户端使用/*** @ServerEndpoint 声明并创建了websocket端点, 并且指明了请求路径* uid 为客户端请求时携带的用户id, 用于区分发给哪个用户的消息* @author nbacheng* @date 2023-09-20
*/@ServerEndpoint("/websocket/{uid}")
@Component
public class WebSocketServer {// 日志对象private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。//千万不要用++private static AtomicInteger onlineCount = new AtomicInteger(0);// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();// private static ConcurrentHashMap<String,WebSocketServer> websocketList = new ConcurrentHashMap<>();// 与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;// 接收uidprivate String uid = "";/** 客户端创建连接时触发* */@OnOpenpublic void onOpen(Session session, @PathParam("uid") String uid) {this.session = session;webSocketSet.add(this); // 加入set中addOnlineCount(); // 在线数加1log.info("有新窗口开始监听:" + uid + ", 当前在线人数为" + getOnlineCount());this.uid = uid;try {sendMessage("连接成功");} catch (IOException e) {log.error("websocket IO异常");}}/*** 客户端连接关闭时触发**/@OnClosepublic void onClose() {webSocketSet.remove(this); // 从set中删除subOnlineCount(); // 在线数减1log.info("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 接收到客户端消息时触发*/@OnMessagepublic void onMessage(String message, Session session) {log.info("收到来自窗口" + uid + "的信息:" + message);// 群发消息for (WebSocketServer item : webSocketSet) {try {item.sendMessage(message);} catch (IOException e) {e.printStackTrace();}}}/*** 连接发生异常时候触发*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误");error.printStackTrace();}/*** 实现服务器主动推送(向浏览器发消息)*/public void sendMessage(String message) throws IOException {log.info("服务器消息推送:"+message);this.session.getAsyncRemote().sendText(message);}/*** 发送消息到所有客户端* 指定uid则向指定客户端发消息* 不指定uid则向所有客户端发送消息* */public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {log.info("推送消息到窗口" + uid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet) {try {// 这里可以设定只推送给这个sid的,为null则全部推送if (uid == null) {item.sendMessage(message);} else if (item.uid.equals(uid)) {item.sendMessage(message);}} catch (IOException e) {continue;}}}/*** * 给多个指定uid客户端发消息* * */public static void sendInfo(String message, @PathParam("uids") String[] uids ) throws IOException {log.info("推送消息到窗口" + uids + ",推送内容:" + message);for (String uid : uids) {sendInfo(message,uid);}}/*** 发送消息到所有客户端* 指定uid则向指定客户端发消息* 不指定uid则向所有客户端发送消息* */public static void sendInfo(BaseProtocol message, @PathParam("uid") String uid) throws IOException {log.info("推送消息到窗口" + uid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet) {try {// 这里可以设定只推送给这个sid的,为null则全部推送if (uid == null) {item.sendMessage(JSONUtil.toJsonStr(message));} else if (item.uid.equals(uid)) {item.sendMessage(JSONUtil.toJsonStr(message));}} catch (IOException e) {continue;}}}public static synchronized int getOnlineCount() {return onlineCount.get();}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount.incrementAndGet();}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount.decrementAndGet();}public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {return webSocketSet;}}

4、在导航条里增加一个消息

<el-tooltip content="消息" effect="dark" placement="bottom"><!--<message id="message" class="right-menu-item hover-effect"  /> --><header-notice id="message" class="right-menu-item-message hover-effect" /></el-tooltip>

界面就是

同时为了样式问题增加下面样式

right-menu-item-message {display: inline-block;padding: 0 8px;height: 100%;font-size: 18px;color: #5a5e66;vertical-align: text-bottom;width: 36px;&.hover-effect {cursor: pointer;transition: background .3s;&:hover {background: rgba(0, 0, 0, .025)}}}

5、增加HeaderNotice 组件,当然现在是测试,只作为websocket消息测试用,后续正式还需要修改。

<template><div><a-popover trigger="click" placement="bottomRight" :autoAdjustOverflow="true" :arrowPointAtCenter="true"overlayClassName="header-notice-wrapper" @visibleChange="handleHoverChange":overlayStyle="{ width: '400px', top: '50px' }"><template slot="content"><a-spin :spinning="loadding"><a-tabs><a-tab-pane :tab="msg1Title" key="1"><a-list><a-list-item :key="index" v-for="(record, index) in announcement1"><div style="margin-left: 5%;width: 50%"><p><a @click="showAnnouncement(record)">{{ record.titile }}</a></p><p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p></div><div style="text-align: right"><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag></div></a-list-item><div style="margin-top: 5px;text-align: center"><a-button @click="toMyAnnouncement()" type="dashed" block>查看更多</a-button></div></a-list></a-tab-pane><a-tab-pane :tab="msg2Title" key="2"><a-list><a-list-item :key="index" v-for="(record, index) in announcement2"><div style="margin-left: 5%;width: 50%"><p><a @click="showAnnouncement(record)">{{ record.titile }}</a></p><p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p></div><div style="text-align: right"><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag></div></a-list-item><div style="margin-top: 5px;text-align: center"><a-button @click="toMyAnnouncement()" type="dashed" block>查看更多</a-button></div></a-list></a-tab-pane><a-tab-pane :tab="msg3Title" key="3"><a-list><a-list-item :key="index" v-for="(record, index) in announcement3"><div style="margin-left: 5%;width: 50%"><p><a @click="showAnnouncement(record)">{{ record.titile }}</a></p><p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p></div><div style="text-align: right"><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag><a-tag @click="showAnnouncement(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag></div></a-list-item><div style="margin-top: 5px;text-align: center"><a-button @click="toMyAnnouncement()" type="dashed" block>查看更多</a-button></div></a-list></a-tab-pane></a-tabs></a-spin></template><span @click="fetchNotice" class="header-notice"><a-badge :count="msgTotal"><a-icon style="font-size: 16px; padding: 4px" type="bell" /></a-badge></span><show-announcement ref="ShowAnnouncement" @ok="modalFormOk"></show-announcement><dynamic-notice ref="showDynamNotice" :path="openPath" :formData="formData" /></a-popover></div>
</template><script>import ShowAnnouncement from './ShowAnnouncement'import store from '@/store/'import DynamicNotice from './DynamicNotice'export default {name: "HeaderNotice",components: {DynamicNotice,ShowAnnouncement,},data() {return {loadding: false,url: {listCementByUser: "/sys/annountCement/listByUser",editCementSend: "/sys/sysAnnouncementSend/editByAnntIdAndUserId",queryById: "/sys/annountCement/queryById",},hovered: false,announcement1: [],announcement2: [],announcement3: [],msg1Count: "0",msg2Count: "0",msg3Count: "0",msg1Title: "通知(0)",msg2Title: "",msg3Title: "",stopTimer: false,websock: null,lockReconnect: false,heartCheck: null,formData: {},openPath: ''}},computed: {msgTotal() {return parseInt(this.msg1Count) + parseInt(this.msg2Count) + parseInt(this.msg3Count);}},mounted() {//this.loadData();//this.timerFun();this.initWebSocket();// this.heartCheckFun();},destroyed: function() { // 离开页面生命周期函数this.websocketOnclose();},methods: {timerFun() {this.stopTimer = false;let myTimer = setInterval(() => {// 停止定时器if (this.stopTimer == true) {clearInterval(myTimer);return;}this.loadData()}, 6000)},loadData() {try {// 获取系统消息getAction(this.url.listCementByUser).then((res) => {if (res.success) {this.announcement1 = res.result.anntMsgList;this.msg1Count = res.result.anntMsgTotal;this.msg1Title = "通知(" + res.result.anntMsgTotal + ")";this.announcement2 = res.result.sysMsgList;this.msg2Count = res.result.sysMsgTotal;this.msg2Title = "系统消息(" + res.result.sysMsgTotal + ")";this.announcement3 = res.result.todealMsgList;this.msg3Count = res.result.todealMsgTotal;this.msg3Title = "待办消息(" + res.result.todealMsgTotal + ")";}}).catch(error => {console.log("系统消息通知异常", error); //这行打印permissionName is undefinedthis.stopTimer = true;console.log("清理timer");});} catch (err) {this.stopTimer = true;console.log("通知异常", err);}},fetchNotice() {if (this.loadding) {this.loadding = falsereturn}this.loadding = truesetTimeout(() => {this.loadding = false}, 200)},showAnnouncement(record) {putAction(this.url.editCementSend, {anntId: record.id}).then((res) => {if (res.success) {this.loadData();}});this.hovered = false;if (record.openType === 'component') {this.openPath = record.openPage;this.formData = {id: record.busId};this.$refs.showDynamNotice.detail(record.openPage);} else {this.$refs.ShowAnnouncement.detail(record);}},toMyAnnouncement() {this.$router.push({path: '/isps/userAnnouncement'});},modalFormOk() {},handleHoverChange(visible) {this.hovered = visible;},initWebSocket: function() {// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于httpsvar uid = store.getters.name;var url = process.env.VUE_APP_WS_API + "/websocket/" + uid;console.log("url=",url);this.websock = new WebSocket(url);this.websock.onopen = this.websocketOnopen;this.websock.onerror = this.websocketOnerror;this.websock.onmessage = this.websocketOnmessage;this.websock.onclose = this.websocketOnclose;},websocketOnopen: function() {console.log("WebSocket连接成功");//心跳检测重置//this.heartCheck.reset().start();},websocketOnerror: function(e) {console.log("WebSocket连接发生错误");this.reconnect();},websocketOnmessage: function(e) {console.log("-----接收消息-------", e);console.log("-----接收消息-------", e.data);var data = eval("(" + e.data + ")"); //解析对象if (data.cmd == "topic") {//系统通知//this.loadData();this.$notification.open({ //websocket消息通知弹出message: 'websocket消息通知',description: data.msgTxt,style: {width: '600px',marginLeft: `${335 - 600}px`,},});} else if (data.cmd == "user") {//用户消息//this.loadData();this.$notification.open({message: 'websocket消息通知',description: data.msgTxt,style: {width: '600px',marginLeft: `${335 - 600}px`,},});}//心跳检测重置//this.heartCheck.reset().start();},websocketOnclose: function(e) {console.log("connection closed (" + e + ")");if (e) {console.log("connection closed (" + e.code + ")");}this.reconnect();},websocketSend(text) { // 数据发送try {this.websock.send(text);} catch (err) {console.log("send failed (" + err.code + ")");}},openNotification(data) {var text = data.msgTxt;const key = `open${Date.now()}`;this.$notification.open({message: '消息提醒',placement: 'bottomRight',description: text,key,btn: (h) => {return h('a-button', {props: {type: 'primary',size: 'small',},on: {click: () => this.showDetail(key, data)}}, '查看详情')},});},reconnect() {var that = this;if (that.lockReconnect) return;that.lockReconnect = true;//没连接上会一直重连,设置延迟避免请求过多setTimeout(function() {console.info("尝试重连...");that.initWebSocket();that.lockReconnect = false;}, 5000);},heartCheckFun() {var that = this;//心跳检测,每20s心跳一次that.heartCheck = {timeout: 20000,timeoutObj: null,serverTimeoutObj: null,reset: function() {clearTimeout(this.timeoutObj);//clearTimeout(this.serverTimeoutObj);return this;},start: function() {var self = this;this.timeoutObj = setTimeout(function() {//这里发送一个心跳,后端收到后,返回一个心跳消息,//onmessage拿到返回的心跳就说明连接正常that.websocketSend("HeartBeat");console.info("客户端发送心跳");//self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了//  that.websock.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次//}, self.timeout)}, this.timeout)}}},showDetail(key, data) {this.$notification.close(key);var id = data.msgId;getAction(this.url.queryById, {id: id}).then((res) => {if (res.success) {var record = res.result;this.showAnnouncement(record);}})},}}
</script><style lang="css">.header-notice-wrapper {top: 50px !important;}
</style>
<style lang="less" scoped>.header-notice {display: inline-block;transition: all 0.3s;span {vertical-align: initial;}}
</style>

6、增加websocket测试页面,以便测试,地址根据自己需要进行填写

<template><div><el-input v-model="url" type="text" style="width: 100%" /> &nbsp; &nbsp;<br /><el-button @click="join" type="primary">连接</el-button><el-button @click="exit" type="danger">断开</el-button><br /><el-input type="textarea" v-model="message" :rows="9" /><el-button type="info" @click="send">发送消息</el-button><br /><br /><el-input type="textarea" v-model="text_content" :rows="9" /> 返回内容<br /><br /></div>
</template><script>
export default {data() {return {url: "ws://127.0.0.1:9060/websocket/ry",message: "",text_content: "",ws: null,};},methods: {join() {const wsuri = this.url;this.ws = new WebSocket(wsuri);const self = this;this.ws.onopen = function (event) {self.text_content = self.text_content + "已经打开连接!" + "\n";};this.ws.onmessage = function (event) {self.text_content = event.data + "\n";};this.ws.onclose = function (event) {self.text_content = self.text_content + "已经关闭连接!" + "\n";};},exit() {if (this.ws) {this.ws.close();this.ws = null;}},send() {if (this.ws) {const messageData = {msgTxt: this.message,cmd: 'user'}let strdata = JSON.stringify(messageData);console.log("strdata",JSON.stringify(messageData));this.ws.send(strdata);//this.ws.send(this.message);} else {alert("未连接到服务器");}},},
};
</script>

7、实际效果图

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

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

相关文章

matlab产生指定功率的噪声信号、固定SNR的信号

randn函数产生噪声信号 首先要理解信号的幅度和功率&#xff0c;例如信号的幅度为 U U U&#xff0c;那么信号的功率就是 U 2 U^2 U2&#xff0c;他们之间是平方的关系。 matlab中randn函数用法&#xff0c;产生正态分布的随机数或矩阵的函数。 randn&#xff1a;产生均值为0…

PostMan、ApiFox等工具Post请求中@RequestParam和@RequestBody的混合使用如何传参

方法签名 PostMapping("/mms/sendAudit")public R sendAudit(RequestParam("mmsId") Long mmsId,RequestParam("ecId") Long ecId,RequestBody(required false) SignMatchRule signMatchRule) {以ApiFox为例子 RequestParam的Params的参数正常…

Unity用相机实现的镜子效果

首先登场 场景中的元素 mirror是镜子&#xff0c;挂着我们的脚本&#xff0c;Quad是一个面片。Camera是用来生成RenderTexture给面片的。里面的test1是我用来调试位置的球。 镜子size是大小&#xff0c;x是-2&#xff0c;为了反转一下贴图 相机直接可以禁用掉&#xff0c;用…

C语言数组和指针笔试题(四)(一定要看)

目录 二维数组例题一例题二例题三例题四例题五例题六例题七例题八例题九例题十例题十一 结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412;个人主页 &#x1f978;&#x1f978;&#x1f978;C语言 &#x1f43f;️…

【笔记】Splay

【笔记】Splay 目录 简介右旋左旋 核心思想操作a. Splayb. 插入c. 删除 信息的维护例题AcWing 2437. SplayP3369 【模板】普通平衡树 简介 Splay 是一种平衡树&#xff0c;并且是一棵二叉搜索树&#xff08;BST&#xff09;。 它满足对于任意节点&#xff0c;都有左子树上任意…

0基础学习VR全景平台篇 第101篇:企业版功能-子账号分配管理

大家好&#xff0c;欢迎观看蛙色VR官方系列——后台使用课程&#xff01; 本期为大家带来蛙色VR平台&#xff0c;企业版教程-子账号分配管理功能&#xff01; 功能位置示意 一、本功能将用在哪里&#xff1f; 子账号分配管理功能&#xff0c;主要用于企业版用户为自己服务的终…

大模型助力企业数据驱动,火山引擎数智平台发布AI助手

9月19日&#xff0c;火山引擎在其举办的“V-Tech数据驱动科技峰会”上宣布&#xff0c;火山引擎数智平台VeDI推出“AI助手”&#xff0c;通过接入人工智能大模型&#xff0c;帮助企业提升数据处理和查询分析的效率。即使是不会写代码的运营人员&#xff0c;和大模型对话也能做好…

Java内存泄漏知识(软引用、弱引用等)

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 未经允许不得转载 目录 一、导读二、概览三、相关知识3.1 内存…

前后端分离的计算机毕设之基于springboot+vue的课程设计选题管理系统(内含源码+文档+教程)

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

唤醒手腕 2023年 B 站课程 Golang 语言详细教程笔记(更新中)

0001、1000集GO语言Flag毒誓 唤醒手腕UP猪Pig目标花费1000集进行讲解Go语言视频学习教程&#xff08;有趣的灵魂&#xff0c;适合小白&#xff0c;不适合巨佬&#xff09;&#xff0c;从2023年3月19日开始&#xff0c;将会一直每天更新&#xff0c;准备在2024年5月1日之前更新…

Pikachu靶场——SSRF 服务端请求伪造

文章目录 1 SSRF 服务端请求伪造1.1 SSRF(curl)1.1.1 漏洞防御 1.2 SSRF(file_get_content)1.2.1 漏洞防御1.2.3 SSRF 防御 1 SSRF 服务端请求伪造 SSRF(Server-Side Request Forgery:服务器端请求伪造) 其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&a…

孤网双机并联逆变器下垂控制策略MATLAB仿真模型

微❤关注“电气仔推送”获得资料 主体模块&#xff1a; 建议使用MATLAB2021b及以上版本打开&#xff01; 功率计算模块、下垂控制模块、电压电流双环控制模块 系统输出有功功率: 系统输出无功功率&#xff1a; 系统频率变化曲线: 参考文献&#xff1a; 微电网并网运行模式下…

四川众佰诚:现在的抖音小店怎么注册

抖音小店是一种新的电子商务形式&#xff0c;它允许用户在抖音平台上直接销售商品。下面是一篇关于如何注册抖音小店的步骤说明&#xff1a; 下载并安装抖音App: 首先&#xff0c;你需要在手机上下载并安装最新版本的抖音App。你可以在苹果App Store或者安卓应用商店找到它。 创…

助力交叉学科应用型数据科学人才培养,和鲸科技携手华为发布联合解决方案

时代高速发展&#xff0c;智能化的浪潮奔腾而来&#xff0c;以“加速行业智能化”为主题&#xff0c;第八届华为全联接大会&#xff08;HUAWEI CONNECT 2023&#xff09;于 9 月 20 日正式开幕。本次大会中&#xff0c;华为携手生态伙伴引领智慧教育新风尚&#xff0c;和鲸科技…

飞书与企业微信的异同

云文档 飞书的云文档会自动用游览器打开&#xff0c;不会直接在PC应用中打开&#xff08;移动端能在应用中打开&#xff09;。 飞书云文档能够插入视频、流程图、问卷等等 聊天消息交互 钉钉也有类似的功能&#xff0c;可以针对消息进行点赞等回复 钉钉的消息回复还有【收到…

第2讲:Vue开发环境的搭建及运行

Vue开发环境搭建步骤 1、安装node http://www.nodejs.com.cn/ 一般安装在根目录下&#xff0c;直接下一步下一步安装即可。如何检测安装完毕 node -v 2、第二步&#xff1a;安装vue-cli脚手架 npm install -g vue/cli &#xff0c;查看安装版本 vue --version 3、第…

C++常见设计模式

设计模式 设计模式的六大原则&#xff1a;https://zhuanlan.zhihu.com/p/110130347 适配器模式 假设Client想要一个正方形&#xff08;client Interface&#xff09;&#xff0c;但是提供的服务&#xff08;service&#xff09;是个圆形&#xff0c;那么我就把这个圆通过适配…

java-decompiler

Java Decompiler GitHub F:\Document_JD-GUI\jd-gui-windows-1.4.0 jd-gui-windows-1.4.0 JDK 1.7 jd-gui-windows-1.6.6 JDK 1.8 Releases java-decompiler/jd-gui GitHub

flutter web 优化和flutter_admin_template

文章目录 Flutter Admin TemplateLive demo: https://githubityu.github.io/live_flutter_adminWeb 优化 Setup登录注册英文 亮色主题 中文 暗黑主题管理员登录权限 根据权限动态添加路由 第三方依赖License最后参考学习 Flutter Admin Template Responsive web with light/da…

【操作系统笔记十四】科普:POSIX 是什么

注&#xff1a;本文转载自该文章posix是什么都不知道&#xff0c;还好意思说你懂Linux&#xff1f; Linux开发者越来越多&#xff0c;但是仍然有很多人整不明白POSIX是什么。本文就带着大家来了解一下到底什么是POSIX&#xff0c;了解他的历史和重要性。 一、什么是 POSIX&…