【Uniapp】小程序携带Token请求接口+无感知登录方案2.0

news2025/3/12 23:33:16

本次改进原文《【Uniapp】小程序携带Token请求接口+无感知登录方案》,在实际使用过程中我发现以下bug

  1. 若token恰好在用户访问接口时到期,就会直接查询为空,不反映token过期问题(例如:弹窗显示订单查询记录为空),并不是因为没有数据而是因为token过期了,接口返回了但是没有在前端显示
  2. token过期后需要重新启动小程序,才会获取到新的token
  3. 获取到token后,原接口不会继续请求,页面页面空白没有任何数据,数据需要下一次刷新才有

问题演示如下:

【审核中】

目录

  • 吐槽
    • token 是什么?
    • 设计思路(点击方法可跳转原文档)
    • 操作流程
      • 后端代码
      • 前端小程序封装📦代码
      • 演示
        • 1、不存在本地缓存、不存在redis记录 演示
        • 2、不存在本地缓存演示
        • 3、服务器端不存在redis记录 演示
    • 如何进行token鉴权
    • thinkphp5 redis补充
    • 2.0改进方案
    • 总结

在这里插入图片描述

吐槽

写本篇的原因是因为之前开发用的都不是微信小程序给的session作为token鉴权的,这次开发打算使用多端同步的uniapp开发小程序,方便后面转多端,所以我想尝试新的东西,另外在热榜中我看到一篇文章用"access_token作为token来请求验证接口、checkSession用来检测access_token有无过期",不得不使我感叹,现在的技术er这么差了吗?简直就是误人子弟!!

我们来说说为什么不能用access_token作为token

  • 【官方回答】access_token 是小程序全局唯一后台接口调用凭据,调用绝大多数后台接口时都需使用。开发者可以通过 getAccessToken 接口获取并进行妥善保存。 -【官方回答】
    获取小程序全局唯一后台接口调用凭据,token有效期为7200s,开发者需要进行妥善保存。

所以说,access_token 只是用来调用一些微信提供的api服务的,并且access_token 只有两个小时,你把access_token当作小程序的token?不仅不满足暴露这个问题,时间上也有限制

我们再来说说checkSession是用来检测什么的?

  • 登录态过期后开发者可以再调用 wx.login 获取新的用户登录态。调用成功说明当前 session_key 未过期,调用失败说明 session_key 已过期。

所以!checkSession是用来检测session_key而不是access_token的,access_token是根据小程序的appid和secret确定的,没有单一用户代表性

在这里插入图片描述

token 是什么?

token 顾名思义就是令牌,也就是一种身份标志。用于和服务器确定身份,它具有时效性,超过有效时间身份标志就会失效。
在这里插入图片描述

设计思路(点击方法可跳转原文档)

通过小程序客户端发起的**wx.login()** 获取临时登录凭证code ,并回传到开发者服务器,通过微信提供的 auth.code2Session 接口,换取 用户唯一标识 openid、 会话密钥 session_key。并通过以session_key为名,openid为值将数据存放到redis中,在这里我将时间设置为48h

  1. 若服务端token失效,客户端登陆状态也会失效,失效后重新登陆执行上述步骤;
  2. 若客户端checkSession失效或者本地数据缓存失效,则也会重新登录
    上述两个步骤保证小程序端的token都是最新的,缺点是不能及时性作废原先在服务器存储的数据只能等redis过期

以上设计逻辑思路满足下图:
在这里插入图片描述

操作流程

后端代码

以Thinkphp5.0.24为案例(20230614新增控制器写法,原来写的是原生php,二选一,建议第一种)

1.TP控制器登录接口方法(建议)

   //登录接口
    //  http://code.taila.club/index.php/index/api/login
    // 目录\控制器\方法
    public function login()
    { 
        $code=input('code');
        if ($code) {
            // 存在记录
            // $res=Db::table("sys")->where("id","1")->find();
            
            // $appid=$res["val1"];//小程序id
            // $secret=$res["val2"];//密钥
            $appid="w***********e";
            $secret="6***************6";
            $code=$_GET['code'];
            $url="https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code";
             $header = array(
       'Accept: application/json',
    );
    $curl = curl_init();
    //设置抓取的url
    curl_setopt($curl, CURLOPT_URL, $url);
    //设置头文件的信息作为数据流输出
    curl_setopt($curl, CURLOPT_HEADER, 0);
    // 超时设置,以秒为单位
    curl_setopt($curl, CURLOPT_TIMEOUT, 1);
 
    // 超时设置,以毫秒为单位
    // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);
 
    // 设置请求头
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    //设置获取的信息以文件流的形式返回,而不是直接输出。
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    //执行命令
    $data = curl_exec($curl);
 
    // 显示错误信息
    if (curl_error($curl)) {
        print "Error: " . curl_error($curl);
          die(
        json_encode(
            array(
            'code' => 100,
            'data' => '',
            'msg' => '请求出错!'
        ),480));
    } else {
        // 打印返回的内容
        $result=json_decode($data,true);
        if (array_key_exists("errcode",$result))
          {
        //   echo "键存在!";
       
         die(
        json_encode(
            array(
            'code' => 100,
            'data' => '',
            'msg' => '获取token失败!'.$result['errmsg']
        ),480));
        
          }
        else
          {
        //   echo "键不存在!";
              // 开启redius
       
         //写入redius session_key名命的openid数据 默认存储2天
        curl_close($curl);
        $Redis=new Redis();
        $Redis->set($result['session_key'],$result['openid'],24*60*60*2);
         die(
        json_encode(
            array(
            'code' => 200,
            'data' => $result,
            'msg' => '获取token成功'
        ),480));
          }
         
        

       
       
          
    }


          
        } else {
            // 已被处理或者不存在 请求重新登陆
            die(
        json_encode(
            array(
            'code' => 100,
            'data' => '',
            'msg' => '非法操作'
        ),480)
);
        }
        
    
       
    } 
  1. 在public文件夹创建php文件access_token.php(不建议)
    用于接收前端wx.login方法获得的code换回openid和session_key,并通过以session_key为名,openid为值将数据存放到redis中,在这里我将时间设置为48h
<?php
//小程序登录
$appid="";//小程序id
$secret="";//密钥
$code=$_GET['code'];
curl_get("https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code");
function curl_get($url){
 
   $header = array(
       'Accept: application/json',
    );
    $curl = curl_init();
    //设置抓取的url
    curl_setopt($curl, CURLOPT_URL, $url);
    //设置头文件的信息作为数据流输出
    curl_setopt($curl, CURLOPT_HEADER, 0);
    // 超时设置,以秒为单位
    curl_setopt($curl, CURLOPT_TIMEOUT, 1);
 
    // 超时设置,以毫秒为单位
    // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);
 
    // 设置请求头
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    //设置获取的信息以文件流的形式返回,而不是直接输出。
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    //执行命令
    $data = curl_exec($curl);
 
    // 显示错误信息
    if (curl_error($curl)) {
        print "Error: " . curl_error($curl);
          die(
        json_encode(
            array(
            'code' => 100,
            'msg' => '请求出错!'
        ),480));
    } else {
        // 打印返回的内容
        $result=json_decode($data,true);
        if (array_key_exists("errcode",$result))
          {
        //   echo "键存在!";
        if ($result['errcode']==0) {
            // code...
               // 开启redius
         ini_set('session.save_handler', 'redis');
         ini_set('session.save_path', 'tcp://127.0.0.1:6379');
         $redis = new redis();
         $redis->connect('127.0.0.1', 6379);
         //写入redius session_key名命的openid数据 默认存储2天
        curl_close($curl);
        $redis->set($result['session_key'],$result['openid'],24*60*60*2);
        die(
        json_encode(
            array(
            'code' => 200,
            'msg' => $result
        ),480)
);

        } else {
            die(
        json_encode(
            array(
            'code' => 100,
            'msg' => '获取token失败!'.$result['errmsg']
        ),480));
        }
        
          }
        else
          {
        //   echo "键不存在!";
         die(
        json_encode(
            array(
            'code' => 100,
            'msg' => '获取token失败'
        ),480));
          }
         
        

       
       
          
    }
}
 ?>
  1. 在tp框架中(application/index/controller)新建Api.php控制器
    用来检测服务器端的token是否存在,以便于让小程序做出重新登录操作
<?php
namespace app\index\controller;
use think\Db;
use think\cache\driver\Redis;
use app\index\controller\Base;
class Api extends Base
{
    
    // 验证session_key是否过期(服务器默认48h,到期后自动删除,查询不到表示过期)
    public function check_session()
    { 
        $session_key=input('session_key');
        $redis = new Redis();
        //读取数据
        $result= $redis->get($session_key);
        if ($result) {
            // 存在记录
           die(
        json_encode(
            array(
            'code' => 200,
            'msg' => 'token验证通过'
        ),480)
);
        } else {
            // 已被处理或者不存在 请求重新登陆
            die(
        json_encode(
            array(
            'code' => 100,
            'msg' => 'token验证失败,请重新登录'
        ),480)
);
        }
        
    
       
    }
}

前端小程序封装📦代码

  1. 客户端封装
  • 在项目中新建token.js文件在这里插入图片描述
    代码:
/**
 * token.js,全局校验方法,可以自己补充
 */
export default {
	login: function(session) {  
		let that = this;
		uni.showLoading({
		  title: '登录中...'
		});
				
		uni.login({
		  provider: 'weixin',
		  success: loginRes => {
		    console.log(loginRes);
		    that.code = loginRes.code;
		    //  将用户登录code传递到后台置换用户SessionKey、OpenId等信息
			uni.request({
			    url: 'https://serverhost/wx_token.php', //仅为示例,并非真实接口地址。
			    data: {
			        code:loginRes.code
			    },
				method: 'GET',
			    header: {
			        'content-type': 'application/x-www-form-urlencoded' //自定义请求头信息
			    },
			    success: (res) => {
			        console.log(res.data);
			       console.log(res.data.msg.session_key)
				   uni.hideLoading()
				   //存取session
				   uni.setStorageSync('session', res.data.msg.session_key);
				   //openid只需要服务通过session请求redis即可
			    }
			});
		
		   
		  },
		  fail: () => {
		    uni.showToast({ title: '获取 code 失败', icon: 'none' });
		  }
		});
		
	},
        //检测token 每次发起业务请求检测即可
	check_token: function(session) {  
		let that=this;
		//微信检测
		uni.checkSession({
		  success () {
		    //session_key 未过期,并且在本生命周期一直有效
			 console.log("未过期")
			
			 //没有过期在判断下存储是否存在 后需提交业务需要用到
			 const session = uni.getStorageSync('session');
			 	if (session=='') {
					console.log("session不存在");
					that.login()
			 	}else{
					//检测服务器的
					console.log("session存在-校验合法性");
					//验证检测服务器session有效性
					uni.request({
					    url: 'https://serverhost/index.php/index/Api/check_session', //仅为示例,并非真实接口地址。
					    data: {
					        session_key:session
					    },
						method: 'POST',
					    header: {
					        'content-type': 'application/x-www-form-urlencoded' //自定义请求头信息
					    },
					    success: (res) => {
							if (res.data.code!=200)
							{	
								
								//服务器token已过期 重新登录
								console.log("服务器token已过期 重新登录");
								that.login()
								
							}else{
								console.log("服务器token有效");
							}
						 
					    }
					});
					
					
					
				}
				
			 
		  },
		  fail () {
		    // session_key 已经失效,需要重新执行登录流程
		   console.log("session过期")
		   
		   that.login()
		  }
		})
		
		
	
		
	}
}
  • 在main.js中引用,挂载到全局
import token from '@/sdk/token.js'

// 挂载到全局
Vue.prototype.$token = token
  • 使用方法
 this.$token.check_token()

演示

uniapp打包成微信小程序运行后

1、不存在本地缓存、不存在redis记录 演示

  • 前端运行产生了新的token,记录在本地缓存中
    -在这里插入图片描述

  • 并且前端登陆后有一条新的记录到redis中
    -在这里插入图片描述

2、不存在本地缓存演示

  • 前端清除了上次的token,刷新后会无感登录获取最新的token并记录在本地缓存、redis中

在这里插入图片描述

  • 后端redis存在新的一个token记录,第一次的token等时间倒计时结束失效
    在这里插入图片描述

3、服务器端不存在redis记录 演示

  • 删除第二次的token记录,刷新前端模拟器(不清除token),看看结果
    在这里插入图片描述

  • 刷新后,前端检测不到token记录执行重新登陆获取最新token
    在这里插入图片描述

以上已经对所有的可能做了一个实验,除了【更新新的token后,上次的token并不能及时失效】这个问题,找不到其他毛病了

如何进行token鉴权

  • 前端小程序每次发起业务请求时,先调用一次封装好的【check_token】用于检查本地有误存储token、token是否已经过期(微信决定)、服务器端redis是否存在(不存在没必要发起,因为还是会被拒绝)

  • 服务器端验证token是否有效只需要对token进行查询即可,存在即为成功,直接取出openid书写业务逻辑代码,失败让小程序重新登陆,这些根据返回码即可

还是看演示吧

  • 新建控制器Index.php(路径application/index/controller)

服务器端验证token是否有效只需要对token进行查询即可,存在即为成功,直接取出openid书写业务逻辑代码,失败让小程序重新登陆,这些根据返回码即可

<?php
// 访问路由 https://***/index.php/index/Api/index
namespace app\index\controller;
use think\Db;
use think\cache\driver\Redis;
use app\index\controller\Base;
class Index extends Base
{
    public function index()
    { 
        // 实例 获取openid
        $token=input('token');
        $redis = new Redis();
        $result= $redis->get($token);
       if ($result) {
           die(
        json_encode(
            array(
            'code' => 200,
             'data'=>$result,
            'msg' => '数据请求成功'
        ),480)
);
       } else {
           die(
        json_encode(
            array(
            'code' => 100,
            'data'=>'',
            'msg' => 'token失效或不存在!请重新获取'
        ),480)
);
       }
       
      
    
       
    }
}

发起正确的请求

在这里插入图片描述
如果在后面加了一个1
在这里插入图片描述

thinkphp5 redis补充

$redis->set('name','value','3600');//添加记录前两个分别表示名和值,后者单位秒
$redis->get($session_key);//根据名查询值

2.0改进方案

在上述测试中发现了以下问题:

  1. 若token恰好在用户访问接口时到期,就会直接查询为空,不反映token过期问题(例如:弹窗显示订单查询记录为空),并不是因为没有数据而是因为token过期了,接口返回了但是没有在前端显示
  2. token过期后需要重新启动小程序,才会获取到新的token
  3. 获取到token后,原接口不会继续请求,页面页面空白没有任何数据,数据需要下一次刷新才有

测试过程:
通过删除redis中的记录使token提前到期测试

改进思路:

  • 将后端查询失败的接口和token失效的接口返回码调整(我这里成功200 查询失败100 token失效400,这里与上面无关,开发者自己写接口知道这个就行
  • 将前端代码接口请求中的requests返回判断中加入

代码解释: 告知用户token失效,自动获取,然后获取后调用自身方法传递刚刚的参数重新执行

else if(res.data.code==400){
that.$token.toast("token已过期,正在获取",1500)
that.$token.check_token();
setTimeout(function() {
that.get_book_detail(id);
}, 1200);
}

完整代码示例:(参考下方代码就明白了)

get_book_detail(id){
//获取图书详情数据库
let that=this;
that.$token.request("index.php/index/Api/query_book_details","POST",{token:uni.getStorageSync('token'),
id:id
}).then(res => {
console.log(res.data)
if (res.data.code==200) {
that.$token.toast(res.data.msg,1500)
that.book_data=res.data.data;
that.get_book_detail_api(res.data.data.book_isbn)
// 由于速度更不上,查询大概在2-3s,现在做了调整
//that.htmlContent=res.data.data.details.contentIntro.c;
// 轮播图
var swiper=[
res.data.data.book_cover
// res.data.data.details.coverImage.middle];
that.swiper=swiper;
// that.showPopup2 = true;//开启浮窗
} else if(res.data.code==400){
that.$token.toast("token已过期,正在获取",1500)
that.$token.check_token();
setTimeout(function() {
that.get_book_detail(id);
}, 1200);
}
else{
that.$token.toast("未查询到结果",1500)
}
})
},
  • 所有的前端请求都根据上一步整改,即可解决

成功演示如下:
在这里插入图片描述

总结

以上就是今天对uniapp结合微信小程序携带Token请求接口无感知的登录方案,如果您喜欢请收藏起来!

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

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

相关文章

使用python脚本将视频素材转化为图片数据集并打标

1.前言 在深度学习的训练过程中&#xff0c;第一步要做的制作数据集。本文所介绍的内容就是在面临视频素材时&#xff0c;如何将这个视频素材转换成图片&#xff0c;并将生成的图片给打上标签。 2.视频转图片的代码展示 # codingutf-8 import cv2 import osroot "F:\H…

MySQL数据库及基础操作

MySQL数据库 一、数据库的基本概念二、数据库发展史1、第一代数据库2、第二代数据库3、第三代数据库 三、当今主流数据库介绍四、关系型数据库1、结构2、理解关系数据库 五、非关系数据库六、数据库基本操作1、常用的数据类型2、查看数据库结构3、SQL语句3.1 DDL3.2 DML3.3 DQL…

pycharm 命令行创建版本库

pycharm中已建好的项目如何上传自已的git服务器中 通过命令行创建一个新的版本库 touch README.mdgit initgit add README.mdgit commit -m "first commit"git remote add origin ssh://XXXXip:29418/newbb.gitgit push -u origin master 通过命令行推送一个已存在的…

数据挖掘课件01-07

1、数挖的定义、产生原因&#xff0c;解决问题 数据挖掘就是寻找数据中隐含的知识并用于产生商业价值。 数据挖掘产生动因&#xff1a;海量数据、维度众多、问题复杂 解决问题&#xff1a;分类问题&#xff0c;聚类问题、回归问题、关联问题、 分类算法&#xff1a;C4.5&…

微信如何设置快速回复?

目前微信线上沟通已经成为绝大多数人士的首选&#xff0c;尤其不管是企业还是个人&#xff0c;都会利用微信打造私域。而做微信沟通的话&#xff0c;大家最为关心的就是怎么做到最快捷的回复对方。 可以看看这个功能&#xff0c;提前设置好常见问题的回复话术&#xff0c;可个…

JS + 浮动 + 递归实现图片瀑布流懒加载

思路 页面 pege 分成左浮动的数列 lineBox&#xff0c;每列中图片 sinImg 依次向下摆放每加载一张图片时&#xff0c;判断页面中哪列的高度最小&#xff0c;将当前图片放到最小的那列尾部监听当前图片 onload 事件&#xff0c;当前图片加载完成后&#xff0c;再加载下一张图片…

【python】前方弹幕高能:教你一键实现自动化指定直播间发送弹幕,为你喜欢的女主播疯狂打call叭~

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 不知道你们平时看不看直播&#xff0c;每次看到界面中的滚动弹幕&#xff0c;还挺有意思。 无中生事—— 前几天看到喜欢的主播打比赛呢~ 精彩时刻恨不得双手打字打call的飞快&#xff0c;那激动的无语轮次了都&#…

Android生态下的Kotlin有哪些更新#GoogleIO 2023

Android生态下Kotlin有哪些更新#GoogleIO 2023 自Android官方宣布Kotlin作为Android开发的第一语言&#xff0c;过去将近6年时间。下面是GoogleIO 2023宣布的Android生态的4项重要更新。 Kotlin编译器2.0版本 如果你是一位Kotlin开发者&#xff0c;你可能会对听到这个消息感…

PMP®增持CSPM-2等级证书,免重新考试与学习,申请简单!

好消息&#xff01;好消息&#xff01;好消息&#xff01; 2023年起&#xff0c;持有PMP证书的朋友可以直接增持一个同等级证书CSPM-2&#xff0c;不用重新考试&#xff0c;不用重新学习&#xff0c;原PMP证书不影响正常使用&#xff0c;相当于多了一个国标项目管理领域的证书…

前端和后端分别是什么?

从技术工具来看&#xff1a; 前端&#xff1a;常见的 html5、JavaScript、jQuery... 后端&#xff1a;spring、tomcet、JVM&#xff0c;MySQL... 毕竟&#xff0c;如果这个问题问一个老后端&#xff0c;他掰掰手指可以给你罗列出一堆的名词来&#xff0c;比如设计模式、数据库…

Golang gin middleware的编写与使用 context.Next函数

中间件 在web应用服务中&#xff0c;完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。 在实际的业务开发和处理中&#xff0c;会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度…

【软件分析/静态分析】chapter3 课程03/04 数据流分析的应用(Data Flow Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a;南京大学《软件分析》课程03&#xff08;Data Flow Analysis I&#xff09;_哔哩哔哩_bilibili 南京大学《软件分析》课程04&#xff08;Data Flow Analysis II&#xff09;_哔哩哔哩_bilibili 这篇文章总结…

识别一切模型RAM(Recognize Anything Model)及其前身 Tag2Text 论文解读

img 总览 大家好&#xff0c;我是卷了又没卷&#xff0c;薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师&#xff0c;带来最新的前沿AI知识和工具&#xff0c;欢迎大家交流~ 继MetaAI 的 SAM后&#xff0c;OPPO 研究院发布识别一切模型&#xff08;Recognize Any…

MySQL如何保证数据的可靠性(保证数据不丢失)

1. 结论&#xff1a; 只要redo log 和 binlog 保证持久化到磁盘&#xff0c;就能确保MySQL异常重启后&#xff0c;数据可以恢复。 2. 机制 WAL机制&#xff0c;&#xff08;Write Ahead Log&#xff09;&#xff1a; 事务先写入日志&#xff0c;后持久化到磁盘。 3. binlog…

华为OD机试真题 JavaScript 实现【非严格递增连续数字序列】【2022Q4 100分】

一、题目描述 输入一个字符串仅包含大小写字母和数字&#xff0c;求字符串中包含的最长的非严格递增连续数字序列的长度&#xff0c;比如122889属于非严格递增连续数字序列。 二、输入描述 输入一个字符串仅包含大小写字母和数字&#xff0c;输入的字符串最大不超过255个字符…

合金氢化物动力学与瞬时流量计算

在经典的合金氢化物动力学描述中&#xff0c;有一种是用JMAK方程来描述和拟合合金的吸放氢过程&#xff0c;方程很简洁&#xff1a;&#xff0c;其中是反应程度或者百分比&#xff0c;表示合金氢化物吸氢或者放氢的程度&#xff0c;是该合金吸氢或放氢的一种特征常数&#xff0…

57、基于51单片机智能硬币分拣分类机电子存钱罐报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+参考PPT+元器件清单等)

摘 要 近年来&#xff0c;随着我国经济的发展和社会的进步&#xff0c;邮政事业得到了空前发展。邮政通信网的技术含量不断增加&#xff0c;技术装备水平也在不断的提高&#xff0c;邮件处理已基本实现机械化&#xff0c;并且朝着自动化的方向迈进。本文着眼于我国当前邮政事…

Unity编辑器扩展-第一集-在菜单栏加入自己的按钮

一、概述 unity自己本身就是一个大的程序&#xff0c;我们看见的所有功能&#xff0c;都是用程序写出来的&#xff0c;但是根据各行各业不同的需求&#xff0c;有些时候我们制作时&#xff0c;想要自己编辑一些原有的功能。 二、本节目标效果展示 1.在菜单栏加入属于自己的一…

【前端基础篇】CSS选择器 和 CSS属性

前言&#xff1a;CSS 简介 CSS 概述 CSS ( Cascading Style Sheet ) 层叠样式表&#xff0c;用来修饰 HTML&#xff0c;使得效果更加多样化CSS 在 HTML4.0 中引入&#xff0c;一般在开发过程中&#xff0c;会使用单独的 CSS 文件进行开发&#xff0c;然后将这个独立 CSS 文件引…

Unity编辑器扩展-第二集-按钮排序/分组/放入右键菜单

第一集链接&#xff1a;Unity编辑器扩展-第一集-在菜单栏加入自己的按钮_菌菌巧乐兹的博客-CSDN博客 一、本节目标效果展示 1.按钮排序 变成 2.按钮分组 仔细看&#xff0c;有个灰色的杠杠 3.放入右键菜单 4.皮一下 二、按钮排序具体流程 第一集讲&#xff0c;如果想放入…