【小程序websocket前后端交互】uniapp写微信小程序聊天功能功能,websocket交互功能,心跳重连【详细注释,复制即用】

news2024/12/27 13:25:36

前言

这几天在做的一个需求,就是要写一个小程序端的页面,用于跟客服聊天。
然后就用到了websocket技术,以前我做过网页版的,但是做小程序后发现网页版的逻辑放过来没问题,但是很多的方法和api是不生效的,所以又查文档专门看了一下对于的api,踩了一些坑后来记录一下,方便下次复制!

pc端聊天功能模板(自带websocket,复制可直接用)

点击这里跳转
效果图
在这里插入图片描述

功能简介

1,页面信息展示,根据后台返回字段,分左边和右边展示
2,输入后点击发送按钮,可以发送消息,通过websocket传给后台
3,进入页面后链接websocket
4,websocket自带心跳重连设置,如果异常或者断开会一直重连,直到连上为止
5,离开页面时可以断开连接,并终止重连请求
6,登录和离线会给后端发送type为online的信息,0代表离线,1代表在线
7,页面滚动事件监听,当滚动条滚动到顶部时会触发方法
8,历史数据功能,进入页面后拉取历史数据
9,发送信息时滚动条永远保持在最底部

效果图

这是链接后台websocket,聊天的页面样式
在这里插入图片描述
后端历史记录的数据结构,分左右
在这里插入图片描述
在这里插入图片描述

代码(整体代码放上)

放下翻,有写注意事项和逻辑,里面详细解释了一下这个代码的功能实现逻辑和注意项

<template>
	<view>
		<view class="wrap">
			<view class="title">
				<view>{{ userName }}</view>
			</view>
			<view class="content_box" id="box" ref="scrollBox">
				<view class="timer">2022-08-02 11:08:07</view>
				<view :class="item.position == 'left' ? 'userbox2' : 'userbox'" v-for="(item, index) in chatList"
					:key="index" :id='"item"+index'>
					<view :class="item.position == 'left' ? 'nameInfo2' : 'nameInfo'">
						<view style="font-size: 14px">{{ item.position == 'left' ?item.uname:item.to_name  }}</view>
						<view :class="item.position == 'left' ? 'contentText2' : 'contentText'">
							{{ item.msn }}
						</view>
					</view>
					<view>
						<image class="touxiang" :src="item.position == 'left' ?item.uavatar:item.to_avatar" />
					</view>
				</view>
			</view>
			<view class="bottom">
				<textarea name="输入框" id="1" cols="20" rows="5" class="areaBox" v-model="inputValue"></textarea>
				<button style="height: 30px;color:#58df4d;font-size: 14px;line-height: 30px;"
					@click="sendOut">发送</button>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				page: 1,//聊天历史记录分页
				chatList: [],//聊天信息
				userName: "",//用户名
				inputValue: "",//输入内容
				scrollTop: 0,//滚动条距离顶部距离
				infoList: null,//用户信息

				path: "wss://test.jskwsx.com/msg", //websocket链接地址
				ws: null, //建立的连接
				lockReconnect: false, //是否真正建立连接
				timeout: 10 * 1000, //30秒一次心跳
				timeoutObj: null, //心跳心跳倒计时
				serverTimeoutObj: null, //心跳倒计时
				timeoutnum: null, //断开 重连倒计时
				closeType:1,//断开判断:0代表不重连,1代表重连
			}
		},
		onShow() {
			this.initWebpack();//初始化
			this.closeType=1//进入改为1,代表如果断开链接自动重连
			this.getlishiList()//历史记录
			this.userName=uni.getStorageSync("userinfo").nickname//拿到缓存中的用户信息
		},
		onLoad(options) {
			this.infoList = JSON.parse(options.urlee)//拿到上一个页面传过来的参数,内容是选中客服信息
			console.log('选中客服信息', this.infoList);
		},
		onPageScroll(e) {
			//监听滚动事件,如果滚动条等于0,代表滚动到最顶部,把分页加一,然后历史记录拉第二页数据,以此类推
			if (e.scrollTop == 0) {
				this.page++
				this.getlishiList(1)
				console.log('到顶部了')
			}
		},
		beforeDestroy() {
			this.closeType=0 //离开页面前改为0,代表离开后断开链接不再重连
			this.ws.send({
				data: JSON.stringify({
					type: "online",
					data: {
						online: 0,
						user_type: 'user',
						is_tourist: uni.getStorageSync("userinfo").id?0:1
					}
				})
			})
			// 离开页面后关闭连接
			this.ws.close();
			// 清除时间
			clearTimeout(this.timeoutObj);
			clearTimeout(this.serverTimeoutObj);
		},
		methods: {
			//获取历史记录
			getlishiList(type) {
				uni.request({
					url: 'https://zz.api.asdwqs.com/gzh/crmebchat/chatMessageList', //仅为示例,并非真实接口地址。
					method: 'POST',
					data: {
						accept_id: this.infoList.kf_id,
						page: this.page,
						limit: 10,
					},
					header: {
						token: uni.getStorageSync('token') //拿到缓存中的token
					},
					success: (res) => {
						console.log('历史记录:', res);
						let a = res.data.data.list
						this.chatList = a.concat(this.chatList)//用拿到的数据合并现有的数据,这样当加载第二页历史记录时,顺序不会乱
						if (type == 1) {//滚动到顶部触发方法会传入1,此时不需要调用滚动到最底部的方法
							return
						}
						this.setPageScrollTo()//滚动到最底部
					}
				});
			},
			//滚动条默认滚动到最底部
			setPageScrollTo(s, c) {
				let that = this
				this.$nextTick(() => {
					const query = uni.createSelectorQuery().in(this);
					query
						.select("#box")
						.boundingClientRect((rect) => {
							let height = rect.height;//拿到聊天框的高度
							console.log("聊天信息框高度: ", height);
							wx.pageScrollTo({
								scrollTop: height,//把距离顶部距离设置成聊天框高度,以此把滚动条顶到最底部
								duration: 100 // 滑动速度
							})
						})
						.exec();
				});
			},

			//发送消息
			sendOut() {
				this.chatList.push({
					msn: this.inputValue,
					position: "right",
					to_avatar: uni.getStorageSync("userinfo").avatar,
					to_name: uni.getStorageSync("userinfo").nickname
				})
				let parms = {
					content: this.inputValue,
					uid: uni.getStorageSync("userinfo").id,
					uname: uni.getStorageSync("userinfo").nickname,
					uavatar: uni.getStorageSync("userinfo").avatar,
					to_uid: this.infoList.kf_id,
					to_name: this.infoList.kfname,
					to_avatar: this.infoList.kf_avatar,
					type: 'text',
					channel_type: 'wechat',
				}
				//通过websocket发送信息到后台
				this.ws.send({
					data: JSON.stringify({
						type: "chat",
						data: parms
					})
				})
				this.inputValue = ''//点击发送后清空输入框
				this.setPageScrollTo()//滚动到最底部
				console.log('发送成功', this.inputValue);
			},

			// 初始化websocket链接
			initWebpack() {
				//实例
				this.ws = wx.connectSocket({
					url: this.path
				})
				//链接成功
				this.ws.onOpen((res) => {
					let that = this
					console.log("连接成功", that.ws.readyState);
					if (that.ws.readyState == 1) {
						wx.sendSocketMessage({ //发送消息到后台,和send一样,这是微信的写法
							data: JSON.stringify({
								type: "login",
								data: {
									id: uni.getStorageSync("userinfo").wechatUsers.id,
									channel_type: 'wechat',
									uid: uni.getStorageSync("userinfo").id,
									openid: 'ojV4k6tnkv4_F1dddc3VwLeJ_QLs'
								}
							})
						})
						this.ws.send({
							data: JSON.stringify({
								type: "online",
								data: {
									online: 1,
									user_type: 'user',
									is_tourist: uni.getStorageSync("userinfo").id?0:1
								}
							})
						})
					}
					that.start(); //链接成功后开启心跳
				})
				//链接异常
				this.ws.onError((res) => {
					console.log("出现错误");
					this.reconnect(); //重连
				})
				//链接断开
				this.ws.onClose((res) => {
					console.log("连接关闭");
					//断开链接时判断
					if(this.closeType==0){
						return
					}
					this.reconnect(); //重连
				})
				//后台返回消息
				this.ws.onMessage((res) => {
					let type = JSON.parse(res.data)
					//后台返回消息,通过type字段判断是不是别人发送给我的消息
					if (type.type == 'chat') {
						this.chatList.push(type.data)//把消息添加到信息列表渲染
						this.setPageScrollTo() //滚动到最底部
						console.log("收到后台信息:", JSON.parse(res.data));
					}
					
					this.reset(); //收到服务器信息,心跳重置
				})

			},
			//重新连接
			reconnect() {
				var that = this;
				//防止重复链接
				if (that.lockReconnect) {
					return;
				}
				that.lockReconnect = true;
				//没连接上会一直重连,设置延迟避免请求过多
				that.timeoutnum && clearTimeout(that.timeoutnum);
				that.timeoutnum = setTimeout(function() {
					that.initWebpack(); //新连接
					that.lockReconnect = false;
				}, 5000);
			},
			//重置心跳
			reset() {
				var that = this;
				clearTimeout(that.timeoutObj); //清除心跳倒计时
				clearTimeout(that.serverTimeoutObj); //清除超时关闭倒计时
				that.start(); //重启心跳
			},
			//开启心跳
			start() {
				var self = this;
				self.timeoutObj && clearTimeout(self.timeoutObj); //心跳倒计时如果有值就清除掉,防止重复
				self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj); //超时关闭倒计时如果有值就清除掉,防止重复
				self.timeoutObj = setTimeout(function() {
					if (self.ws.readyState == 1) {
						wx.sendSocketMessage({
							data: JSON.stringify({
								type: "ping"
							})
						})
					} else {
						self.reconnect();//重连
					}

					//如果超时了就关闭连接
					self.serverTimeoutObj = setTimeout(function() {
						self.ws.close();
					}, self.timeout);
				}, self.timeout);
			},
			//连接成功
		}
	}
</script>

<style scoped>
	.wrap {
		height: 100%;
		width: 100%;
		position: relative;
	}

	.touxiang {
		width: 50px;
		height: 50px;
		border-radius: 50%;
	}

	.areaBox {
		height: 40px;
	}

	.title {
		height: 40px;
		width: 100%;
		background-color: #eaeaea;
		display: flex;
		justify-content: center;
		align-items: center;
	}

	.bottom {
		min-height: 50px;
		width: 100%;
		border-top: 1px solid #eaeaea;
		background-color: #F1F1F1;
		position: fixed;
		bottom: 0;
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding: 0 5px;
		border-radius: 10px;
	}

	.content_box {
		/* 
  中间栏计算高度,110是包含了上下固定的两个元素高度90
  这里padding:10px造成的上下够加了10,把盒子撑大了,所以一共是20要减掉
  然后不知道是边框还是组件的原因,导致多出了一些,这里再减去5px刚好。不然会出现滚动条到顶或者底部的时候再滚动的话就会报一个错,或者出现滚动条变长一下的bug
  */
		height: calc(100% - 115px);
		overflow: auto;
		padding: 10px 10px 50px 10px;
	}

	.timer {
		text-align: center;
		color: #c2c2c2;
	}

	/* 发送的信息样式 */
	/* 
右边消息思路解释:首先大盒子userbox内放两个盒子,一个放头像,一个放用户名和发送的内容,我们先用flex让他横向排列。
然后把写文字的大盒子设置flex:1。这个属性的意思就是让这个元素撑满父盒子剩余位置。然后我们再把文字盒子设置flex,并把他对齐方式设置为尾部对齐就完成了基本的结构,然后微调一下就可以了
*/
	.userbox {
		width: 100%;
		display: flex;
		margin-bottom: 10px;
	}

	.nameInfo {
		/* 用flex:1把盒子撑开 */
		flex: 1;
		margin-right: 10px;
		/* 用align-items把元素靠右对齐 */
		display: flex;
		flex-direction: column;
		align-items: flex-end;
	}

	.contentText {
		background-color: #9eea6a;
		/* 把内容部分改为行内块元素,因为盒子flex:1把盒子撑大了,所以用行内块元素让内容宽度不根据父盒子来 */
		display: inline-block;
		/* 这四句是圆角 */
		border-top-left-radius: 10px;
		border-top-right-radius: 0px;
		border-bottom-right-radius: 10px;
		border-bottom-left-radius: 10px;
		/* 最大宽度限定内容输入到百分61换行 */
		max-width: 61%;
		padding: 5px 10px;
		/* 忽略多余的空白,只保留一个空白 */
		white-space: normal;
		/* 换行显示全部字符 */
		word-break: break-all;
		margin-top: 3px;
		font-size: 14px;
	}

	/* 接收的信息样式 */
	/* 
左边消息思路解释:跟上面一样,就是换一下位置,首先通过把最外层大盒子的排列方式通过flex-direction: row-reverse;属性翻转,也就是头像和文字盒子换位置
然后删除掉尾部对齐方式,因为不写这个默认是左对齐的。我们写的左边就没必要再写了。
*/
	.userbox2 {
		width: 100%;
		display: flex;
		flex-direction: row-reverse;
		margin-bottom: 10px;
	}

	.nameInfo2 {
		/* 用flex:1把盒子撑开 */
		flex: 1;
		margin-left: 10px;
	}

	.contentText2 {
		background-color: #9eea6a;
		/* 把内容部分改为行内块元素,因为盒子flex:1把盒子撑大了,所以用行内块元素让内容宽度不根据父盒子来 */
		display: inline-block;
		/* 这四句是圆角 */
		border-top-left-radius: 0px;
		border-top-right-radius: 10px;
		border-bottom-right-radius: 10px;
		border-bottom-left-radius: 10px;
		/* 最大宽度限定内容输入到百分61换行 */
		max-width: 61%;
		padding: 5px 10px;
		/* 忽略多余的空白,只保留一个空白 */
		white-space: normal;
		/* 换行显示全部字符 */
		word-break: break-all;
		margin-top: 3px;
		font-size: 14px;
	}
</style>

注意点

1,点击发送时,我们看到的并不是后台传过来的信息,是我们自己push进去的,然后信息通过websockt传给后台了,这样就不需要再获取一次消息了。
2,我这里后台的数据结构时分左右的,然后左右的不同也会有不同的字段,所以我标签上有判断,如果你们后端不这么传,自行更改
3,这里用css保留了空格等格式,如果不生效,可以直接用vue自带的v-html来把文字放进去

聊天功能逻辑简介

简单来说就是,我们前端的页面渲染是通过写两个style样式。一个是靠左边,一个靠右边。然后通过后台的数据内有一个position字段,是left我们就调用左边的样式,是right的就调用右边的样式。这样,页面就出来了。
然后我们通过websocket链接交互。
逻辑是,刚连接上时发送给后台当前用户的信息(type=login)和这个用户上线了的状态(type=online),然后发送消息的时候type=chat,并带上参数。参数为我的信息和我要发送的人的信息。然后接收的时候我们通过判断后台websocket传过来的数据内type等于什么来处理不同的数据。这一点需要你自行和后端商量什么type。然后离开页面的时候也发送一下type=online的消息,表示用户离线了。然后关闭链接

拓展项

【用户登录状态,在线离线】
这里我写了一个pc版的在线和离线。代码放上来讲解一下。
这里是截取的代码,是用来渲染用户列表的,是pc端截取出来的。大家看逻辑就行,这里用户列表自带状态显示上下线,所以我分了两个盒子来判断。
逻辑很简单,就是两个盒子,先判断当前这个用户的id和后台websocket发来的id是否一致。如果不一致,那么我们使用上面的盒子,也就是用户列表内自带的字段来判断上下线。
如果后台发了一个上下线的状态过来,我们就赋值给online_uid。这时候再判断,就会发现用户列表内某一个用户id是一致的,这时候这个用户就会使用下面的盒子,判断就变成了后台发送过来的状态来判断上下线了。

html

<!-- 用户状态判断:列表拉状态时,判断每一个用户id和ws推送的id是否一致,不一致的默认显示列表内的状态 -->
<div v-show="item.user_id!==online_uid">
    <div class="statebox" v-if="item.online_user==1"> 在线</div>
    <div class="statebox2" v-else> 离线</div>
</div>
<!-- 如果判断该用户id和ws传的uid一致,那么使用ws推送的状态 -->
<div v-show="item.user_id==online_uid">
   <div class="statebox" v-show="online_type==1&&item.user_id==online_uid"> 在线</div>
   <div class="statebox2" v-show="online_type==0&&item.user_id==online_uid"> 离线</div>
</div>

data

data() {
            return {
                online_type:0,//0是离线,1是在线
                online_uid:0,//用于判断uid是否一致
            };
        },

methods

            //接受后台信息回调
            onmessage(e) {
                let type = JSON.parse(e.data)
                if (type.type == 'reply') {//reply代表用户发送的消息
                    this.userInfoList.push(type.data)//把发送的消息添加进信息列表渲染
                }else if(type.type == 'online'){//online代表用户上下线
                    this.online_type=type.data.online//用户状态
                    this.online_uid=type.data.uid//用户id
                }
                console.log("收到后台信息:", JSON.parse(e.data));
            },

【发送图片,视频,商品详情等信息】
这里是很多聊天功能会拓展的部分,不可能只发送文字,目前我还没有写代码,先记录逻辑。

还记得我们上面代码里面发送消息的时候是有type=text这个属性吗,我们就通过这个type来区分。
比如我点击上传图片时,方法内给type改成img。那么后端发给我们,也就是img类型的,我们通过这个判断来完成功能,简单描述一下

消息分类:
1,text:文字信息
2,img:图片信息
3,shop:购物信息
4,video:视频信息
逻辑:position分左右不用动,创建四个div。用v-if来判断type类型属于哪一个就显示哪个div。同时,每个div内对应获取信息列表中该对象的一些字段渲染出来,比如type等于text就代表是文字信息,拿msn字段的文字去渲染。如果type等于img。就是图片信息,图片的这个div内获取url字段的图片地址去渲染。如果是shop字段。那么就对应获取比如名称,描述,价格,图片等字段渲染出来一个小卡片形式的结构。

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

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

相关文章

立方体的表面积 长方体的表面积 公里转换为米 温度对照

立方体的表面积 难度&#xff1a;青铜 时间限制&#xff1a;1秒 占用内存&#xff1a;64 M 输入立方体边长&#xff0c;输出立方体的表面积。不考虑非法输入。格式 输入格式&#xff1a;输入实型 输出格式&#xff1a;输出实型 #include<bits/stdc.h> using namespace s…

万字长文的CSS与JavaScript简易学习

近期学习web笔记&#xff0c;可供参考 目录 css: css导入方式&#xff1a; css选择器&#xff1a; javascript: javascript介绍&#xff1a; js引入方式&#xff1a; js书写语法&#xff1a; js变量&#xff1a; 5种原始类型&#xff1a; 运算符&#xff1a; JavaScr…

推荐一款基于.Net Core开发简约漂亮的 WPF UI库

今天给大家推荐一个开源WPF UI库。 项目简介 这是一款使用简单、UI评论的WPF UI库&#xff0c;借鉴了多个开源框架。UI简单清晰、大气。 技术架构 1、跨平台&#xff1a;这是基于.Net Core开发的系统&#xff0c;可以部署在Docker, Windows, Linux, Mac。 2、开发环境&…

微视网媒:沃尔沃质量有什么魅力 让大佬罗永浩、樊登纷纷翻牌S90?

在消费市场&#xff0c;选对代言人&#xff0c;产品就成功了一半&#xff0c;这话可是一点都没说错。 从一定程度上来说&#xff0c;代言人就是产品对外形象的展示&#xff0c;甚至有不少消费者还会因为代言人激情下单。 当然&#xff0c;成也代言&#xff0c;败也代言&#xf…

拉格朗日对偶问题的一些介绍

文章目录参考前言拉格朗日函数例1例2拉格朗日函数的对偶问题参考 “拉格朗日对偶问题”如何直观理解&#xff1f;“KKT条件” “Slater条件” “凸优化”打包理解 感觉有时间看视频的还是看视频比较好&#xff0c;本文只是记录一下以防以后忘记。 前言 还记得SVM里用到拉格朗…

Python代码的编写运行方式简介

Python代码的编写运行方式简介 Python编写方式 Python 是一种解释型的脚本编程语言&#xff0c;支持两种代码编写方式&#xff1a;交互命令行方式和运行.py代码文件方式。 Python的交互命令行方式和直接运行.py代码文件方式有什么区别呢&#xff1f; 交互模式&#xff0c;相当…

Python爬虫|采集开源众包的悬赏任务,自动翻页

前言 现在互联网,有很多网站提供一些接单外派的形式,提供给有能力的人或者团队去接单。比如说,很多人熟悉的猪八戒,程序员客栈,CODING 码市,开源众包等等平台,相信很多同学也都知道。 如果要第一时间了解某个接单平台发布的第一手悬赏任务,选择爬虫也是非常不错的选择…

websocket接口自动化集成pytest测试框架

01、websocket协议 1、介绍 WebSocket是一种在单个TCP通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455&#xff0c;并由RFC7936补充规范。WebSocket API也被W3C定为标准。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向…

【2020-10-26】JS逆向之同盾滑块

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言总结前言 用官网的做个例子吧&#xff1a;https://sec.xiaodun.com/onlineExperience/slidingPuzzle?Rbdjj?sourcetongdun&keywordtongdun 简单分析下这个…

项目整体管理

项目整体管理 1.1 项目整体管理概述 1.项目整体管理知识领域包括识别、确定、结合、统一与协调各项目管理过程组内不同过程与项目管理活动所需进行的各种过程和活动。 2.“整体管理”的基本任务就是为了按照实施组织确定的程序实现项目目标&#xff0c;将项目管理过程组中需要…

CalBioreagents 艾美捷芽孢杆菌多克隆抗体说明书

艾美捷CalBioreagents 芽孢杆菌多克隆抗体英文说明&#xff1a; CATALOG NUMBER: P043 PRODUCT DESCRIPTION: Rabbit anti-Bacillus polyclonal antibody. IMMUNOGEN: Immunogen was prepared from a pool of purified spores from Bacillus subtilis and Bacillus cereus.…

一句话需求,让你去抄人家产品,你遇到了咋办

打工者联盟为了抵抗996、拖欠工资、黑心老板、恶心公司&#xff0c;让我们组成打工者联盟。客观评价自己任职过的公司情况&#xff0c;为其他求职者竖起一座引路的明灯。https://book.employleague.cn/话说&#xff1a;工地有个包工头&#xff0c;找来两个瓦匠要承包工程。 瓦…

ZooKeeper基础

一、概念 架构&#xff1a; Client/ServerEnsemble&#xff08;集群&#xff0c;ZK服务器组&#xff09;&#xff0c;最小节点数为3ZK LeaderaZK Follower ZooKeeper数据模型 znode&#xff1a;用于存储数据&#xff0c;分为持久的&#xff08;default&#xff09;、临时的…

一次服务器非法重启后导致的故障排查记录

作者&#xff1a;JackTian 来源&#xff1a;公众号「杰哥的IT之旅」 ID&#xff1a;Jake_Internet 转载请联系授权&#xff08;微信ID&#xff1a;Hc220088&#xff09; 原文地址&#xff1a;一次服务器非法重启后导致的故障排查记录 大家好&#xff0c;我是杰哥。 前段时间遇…

贪婪算法(Huffman编码)

如果一个算法分阶段的工作&#xff0c;并且在每一个阶段都认为所做的决定是最好的&#xff0c;而不考虑将来的后果&#xff0c;这样的算法就叫做贪婪算法。贪婪算法只考虑当前局部的最优解&#xff0c;而不去考虑全局的情况&#xff0c;如果最终得到的结果是全局最优的&#xf…

麻雀优化CNN超参数用于回归MATLAB

在CNN模型的构建中&#xff0c;涉及到特别多的超参数&#xff0c;比如&#xff1a;学习率、训练次数、batchsize、各个卷积层的卷积核大小与卷积核数量&#xff08;feature map数&#xff09;&#xff0c;全连接层的节点数等。直接选择的话&#xff0c;很难选到一组满意的参数&…

如何理解关系型数据库的常见设计范式?

目录理清基础概念实体属性元组分组函数依赖完全函数依赖部分函数依赖传递函数依赖码全码理解六范式第一范式&#xff08;1NF&#xff09;第二范式&#xff08;2NF&#xff09;第三范式&#xff08;3NF&#xff09;巴斯-科德范式&#xff08;BCNF&#xff0c;Boyce-Codd Normal …

基于STM32的智能家居系统设计

目录 第1章 前言 1 1.1 课题研究的背景和实际意义 1 1.1.1课题背景 1 1.1.2实际意义 1 1.2国内外发展现状、存在问题以及前景 2 1.2.1发展现状 2 1.2.2存在问题 2 1.2.3发展前景 2 1.3 主要工作、内容安排及预期成果 3 1.3.1主要研究工作 3 1.3.2预期成果 3 第2章 总体设计方案…

安卓4.X版本ssl: sslv3 alert handshake failure 握手失败

低版本https握手失败错误查看接口的协议改写代码&#xff08;网络访问采用原生的HttpsURLConnection&#xff09;参考博文&#xff1a;https://www.cnblogs.com/lwbqqyumidi/p/12063489.htmlhttps://blog.csdn.net/qq_16117167/article/details/52621112错误 如图所示&#xf…

linux进程概念

目录 1、进程的基本概念 2、进程控制块 - PCB task_struct内容分类 3、查看进程 通过ps命令查看进程 通过proc查看进程 4、通过系统调用获取进程标示符 5、通过系统调用创建进程-fork初识 6、进程状态 操作系统进程状态 linux进程状态 僵尸进程 孤儿进程 僵尸进…