PHP安全开发
安全开发

PHP
基础
增:insert into 表名(
列名 1,列名 2) value(‘列 1 值 1’, ‘列 2 值 2’);删:delete from 表名 where 列名 = ‘条件’;
改:update 表名 set 列名 = 数据 where 列名 = ‘条件’;
查:select * from 表名 where 列名=‘条件’;
mysqli_connect() 打开一个到 MySQL 的新的连接。
mysqli_select_db() 更改连接的默认数据库。
mysqli_query() 执行某个针对数据库的查询。
mysqli_fetch_row() 从结果集中取得一行,并作为枚举数组返回。 mysqli_close() 关闭先前打开的数据库连接。
参考:https://www.runoob.com/php/php-ref-mysqli.html
全局变量参考:https://www.w3school.com.cn/php/php_superglobals.asp#google_vignette
| $_SERVER[‘PHP_SELF’] | 返回当前执行脚本的文件名。 |
|---|---|
| $_SERVER[‘GATEWAY_INTERFACE’] | 返回服务器使用的 CGI 规范的版本。 |
| $_SERVER[‘SERVER_ADDR’] | 返回当前运行脚本所在的服务器的 IP 地址。 |
| $_SERVER[‘SERVER_NAME’] | 返回当前运行脚本所在的服务器的主机名(比如 www.w3school.com.cn)。 |
| $_SERVER[‘SERVER_SOFTWARE’] | 返回服务器标识字符串(比如 Apache/2.2.24)。 |
| $_SERVER[‘SERVER_PROTOCOL’] | 返回请求页面时通信协议的名称和版本(例如,“HTTP/1.0”)。 |
| $_SERVER[‘REQUEST_METHOD’] | 返回访问页面使用的请求方法(例如 POST)。 |
| $_SERVER[‘REQUEST_TIME’] | 返回请求开始时的时间戳(例如 1577687494)。 |
| $_SERVER[‘QUERY_STRING’] | 返回查询字符串,如果是通过查询字符串访问此页面。 |
| $_SERVER[‘HTTP_ACCEPT’] | 返回来自当前请求的请求头。 |
| $_SERVER[‘HTTP_ACCEPT_CHARSET’] | 返回来自当前请求的 Accept_Charset 头( 例如 utf-8,ISO-8859-1) |
| $_SERVER[‘HTTP_HOST’] | 返回来自当前请求的 Host 头。 |
| $_SERVER[‘HTTP_REFERER’] | 返回当前页面的完整 URL(不可靠,因为不是所有用户代理都支持)。 |
| $_SERVER[‘HTTPS’] | 是否通过安全 HTTP 协议查询脚本。 |
| $_SERVER[‘REMOTE_ADDR’] | 返回浏览当前页面的用户的 IP 地址。 |
| $_SERVER[‘REMOTE_HOST’] | 返回浏览当前页面的用户的主机名。 |
| $_SERVER[‘REMOTE_PORT’] | 返回用户机器上连接到 Web 服务器所使用的端口号。 |
| $_SERVER[‘SCRIPT_FILENAME’] | 返回当前执行脚本的绝对路径。 |
| $_SERVER[‘SERVER_ADMIN’] | 该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。 |
| $_SERVER[‘SERVER_PORT’] | Web 服务器使用的端口。默认值为 “80”。 |
| $_SERVER[‘SERVER_SIGNATURE’] | 返回服务器版本和虚拟主机名。 |
| $_SERVER[‘PATH_TRANSLATED’] | 当前脚本所在文件系统(非文档根目录)的基本路径。 |
| $_SERVER[‘SCRIPT_NAME’] | 返回当前脚本的路径。 |
| $_SERVER[‘SCRIPT_URI’] | 返回当前页面的 URI。 |
身份验证-Cookie 使用
1、客户端向服务器发送 HTTP 请求。
2、服务器检查请求头中是否包含 cookie 信息。
3、如果请求头中包含 cookie 信息,则服务器使用该 cookie 来识别客户端,否则服务 器将生成一个新的 cookie。
4、服务器在响应头中设置 cookie 信息并将其发送回客户端。
5、客户端接收响应并将 cookie 保存在本地。
6、当客户端发送下一次 HTTP 请求时,它会将 cookie 信息附加到请求头中。
7、服务器收到请求并检查 cookie 的有效性。
8、如果 cookie 有效,则服务器响应请求。否则,服务器可能会要求客户端重新登录。
setcookie(): 设置一个 cookie 并发送到客户端浏览器。
unset(): 用于删除指定的 cookie。
身份验证-Session 使用
1、客户端向服务器发送 HTTP 请求。
2、服务器为客户端生成一个唯一的 session ID,并将其存储在服务器端的存储器中 (如文件、数据库等)。
3、服务器将生成的 session ID 作为一个 cookie 发送给客户端。
4、客户端将 session ID 保存为一个 cookie,通常是在本地浏览器中存储。
5、当客户端在发送下一次 HTTP 请求时,它会将该 cookie 信息附加到请求头中,以便 服务器可以通过该 session ID 来识别客户端。
6、服务器使用 session ID 来检索存储在服务器端存储器中的与该客户端相关的 session 数据,从而在客户端和服务器之间共享数据。
session_start(): 启动会话,用于开始或恢复一个已经存在的会话。
$_SESSION: 用于存储和访问当前会话中的所有变量。
session_destroy(): 销毁当前会话中的所有数据。
session_unset(): 释放当前会话中的所有变量。
Session 存储路径:PHP.INI 中 session.save_path 设置路径
唯一性判断-Token 使用
1、生成 Token 并将其存储在 Session
2、生成 Token 并将其绑定在 Cookie 触发
3、尝试登录表单中带入 Token 验证逻辑
示例
gbook.php
<script src="/ueditor/ueditor.config.js">/*引入配置文件*/</script>
<script src="/ueditor/ueditor.all.js">/*引入源码文件*/</script><form id="form1" name="form1" method="post" action="">用户名:<input type="text" name="username" maxlength="2000"><br>内容:<textarea id="content" rows="10" cols="70" name="content" style="border:1px solid #E5E5E5;"></textarea><script type="text/javascript">UE.getEditor("content");//实例化编辑器传参,id为将要被替换的容器。</script><input type="submit" name="submit" id="submit" value="提交"></form><?php
include 'config.php';function add_gbook($con){$u = @$_POST['username'];if (isset($u)) {$c = @$_POST['content'];$i = @$_SERVER['REMOTE_ADDR'];$ua = @$_SERVER['HTTP_USER_AGENT'];$sql = "insert into gbook(`username`, `content`,`ipaddr`,`uagent`) value('$u', '$c','$i','$ua');";if (mysqli_query($con, $sql)) {echo "<script>alert('留言成功!')</script>";}}
}function show_gbook($con,$del){$sql1="select * from gbook";$data=mysqli_query($con,$sql1);while ($row=mysqli_fetch_row($data)) {echo '<hr>';echo '用户名:'.$row[0].'<br>';echo '内容:'.$row[1].'<br>';echo 'IP地址:'.$row[2].'<br>';echo 'UA浏览器:'.$row[3].'<br>';if($del=='del'){echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";}}
}add_gbook($con);
show_gbook($con,'x');//if (!$con)
//{
// die("连接错误: " . mysqli_connect_error());
//}else{
// $u=@$_POST['username'];
// if(isset($u)){
// $c=@$_POST['content'];
// $i=@$_SERVER['REMOTE_ADDR'];
// $ua=@$_SERVER['HTTP_USER_AGENT'];
// $sql="insert into gbook(`username`, `content`,`ipaddr`,`uagent`) value('$u', '$c','$i','$ua');";
// if(mysqli_query($con,$sql)){
// echo "<script>alert('留言成功!')</script>";
// $sql1="select * from gbook";
// $data=mysqli_query($con,$sql1);
// while ($row=mysqli_fetch_row($data))
// {
// echo '<hr>';
// echo '用户名:'.$row[0].'<br>';
// echo '内容:'.$row[1].'<br>';
// echo 'IP地址:'.$row[2].'<br>';
// echo 'UA浏览器:'.$row[3].'<br>';
// }
//
// }else{
// echo "<script>alert('留言失败!')</script>";
// }
//
// }
//
//}?>
demo01
<?php
$dbip='localhost';
$dbuser='root';
$dbpass='123456';
$dbname='demo01';
$con=mysqli_connect($dbip,$dbuser,$dbpass,$dbname);
gbook-admin.php
<?php
include '../config.php';
include '../gbook.php';show_gbook($con,'del');$delstr=@$_GET['del'];
if(isset($delstr)){$sql2="delete from gbook where username = '$delstr';";if(mysqli_query($con,$sql2)){echo "<script>alert('删除成功!')</script>";}
}
文件管理模块-上传-过滤机制
$_FILES:PHP 中一个预定义的超全局变量,用于在上传文件时从客户端接收文件,并将其保存到服务器上。它是一个包含上传文件信息的数组,包括文件名、类型、大小、临时文件名等信息。
$_FILES["表单值"]["name"] 获取上传文件原始名称
$_FILES["表单值"]["type"] 获取上传文件 MIME 类型
$_FILES["表单值"]["size"] 获取上传文件字节单位大小
$_FILES["表单值"]["tmp_name"] 获取上传的临时副本文件名
$_FILES["表单值"]["error"] 获取上传时发生的错误代码
move_uploaded_file() 将上传的文件移动到指定位置的函数
is_dir() 函数用于检查指定的路径是否是一个目录
opendir() 函数用于打开指定的目录,返回句柄,用来读取目录中的文件和子目录
readdir() 函数用于从打开的目录句柄中读取目录中的文件和子目录
open_basedir:PHP.INI 中的设置用来控制脚本程序访问目录
文件包含:
include() 在错误发生后脚本继续执行
require() 在错误发生后脚本停止执行
include_once() 如果已经包含,则不再执行
require_once() 如果已经包含,则不再执行
示例
<?php$name=$_FILES['f']['name'];
$type=$_FILES['f']['type'];
$size=$_FILES['f']['size'];
$tmp_name=$_FILES['f']['tmp_name'];
$error=$_FILES['f']['error'];//echo $name."<br>";
//echo $type."<br>";
//echo $size."<br>";
//echo $tmp_name."<br>";
//echo $error."<br>";
//if(move_uploaded_file($tmp_name,'upload/'.$name)){
// echo "文件上传成功!";
//}//上传文件后缀过滤 黑名单机制
//$black_ext=array('php','asp','jsp','aspx');
xxx.jpg xxx.png
//$fenge = explode('.',$name);
//$exts = end($fenge);
//if(in_array($exts,$black_ext)){
// echo '非法后缀文件'.$exts;
//}else{
// move_uploaded_file($tmp_name,'upload/'.$name);
// echo '<script>alert("上传成功")</script>';
//}//上传文件后缀过滤 白名单机制
//$allow_ext=array('png','jpg','gif','jpeg');
xxx.jpg xxx.png
//$fenge = explode('.',$name);
//$exts = end($fenge);
//if(!in_array($exts,$allow_ext)){
// echo '非法后缀文件'.$exts;
//}else{
// move_uploaded_file($tmp_name,'upload/'.$name);
// echo '<script>alert("上传成功")</script>';
//}//MIME文件类型过滤
$allow_type=array('image/png','image/jpg','image/jpeg','image/gif');
if(!in_array($type,$allow_type)){echo '非法后缀文件';
}else{move_uploaded_file($tmp_name,'upload/'.$name);echo '<script>alert("上传成功")</script>';}
?>
filemange.php
<?php
ini_set('open_basedir',__DIR__);
$path=$_GET['path'] ?? './';
$action = isset($_GET['a'])?$_GET['a']:'';
$path = isset($_GET['path'])?$_GET['path']:'.';
if(is_file($path))
{//获得文件名$file = basename($path);//获得路径$path = dirname($path);
}
//判断,不是目录
elseif(!is_dir($path))
{echo '我只会吃瓜!';
}
function getlist($path){$hd=opendir($path);while(($file_name=readdir($hd) )!== false){if($file_name != '.' && $file_name != '..'){$file_path = "$path/$file_name";$file_type = filetype($file_path);}$list[$file_type][] = array( //$file_type = dir 和 file $list['dir'] 和 $list['file']'file_name'=>$file_name, //文件名存储键值file_name'file_path'=>$file_path, //文件路径存储键值file_path'file_size'=>round(filesize($file_path)/1024), //通过换算文件大小存储键值file_path'file_time'=>date('Y/m/d H:i:s',filemtime($file_path)), //获取文件时间并存储键值file_path);}closedir($hd);return $list;
}$list=getlist($path);//接受方法 判断是怎么操作
//echo $action;
switch ($action){case 'del':unlink($file);//$cmd="del $file";//system($cmd);//echo $cmd;break;case 'down':header("Content-Type: application/octet-stream");header("Content-Disposition: attachment; filename=\"" . $file . "\"");header("Content-Length: " . filesize($file));readfile($file);break;case 'edit':$content=file_get_contents($file);echo '<form name="form1" method="post" action="">';echo "文件名:".$file."<br>";echo "文件内容:<br>";echo '<textarea name="code" style="resize:none;" rows="100" cols="100"">'.$content.'</textarea><br>';echo '<input type="submit" name="submit" id="submit" value="提交">';echo '</form>';break;
}//检测编辑后提交的事件 进入文件重新写入
if(isset($_POST['code'])){$f=fopen("$path/$file",'w+');fwrite($f,$_POST['code']);fclose($f);
}?><table width="100%" style="font-size: 10px;text-align: center;"><tr><th>图标</th><th>名称</th><th>日期</th><th>大小</th><th>路径</th><th>操作</th></tr><?php foreach ($list['dir'] as $v): ?><tr><td><img src="./img/list.png" width="20" height="20"></td><td><?php echo $v['file_name']?></td><td><?php echo $v['file_time']?></td><td>-</td><td><?php echo $v['file_path']?></td><td><a href="?path=<?php echo $v['file_path']?>">打开</a></td></tr><?php endforeach;?><?php foreach ($list['file'] as $v): ?><tr><td><img src="./img/file.png" width="20" height="20"></td><td><?php echo $v['file_name']?></td><td><?php echo $v['file_time']?></td><td><?php echo $v['file_size']?></td><td><?php echo $v['file_path']?></td><td><a href="?a=edit&path=<?php echo $v['file_path']?>">编辑</a><a href="?a=down&path=<?php echo $v['file_path']?>">下载</a><a href="?a=del&path=<?php echo $v['file_path']?>">删除</a></td></tr><?php endforeach;?></table>
相关文章:
PHP安全开发
安全开发 PHP 基础 增:insert into 表名(列名 1, 列名 2) value(‘列 1 值 1’, ‘列 2 值 2’); 删:delete from 表名 where 列名 ‘条件’; 改:update 表名 set 列名 数据 where 列名 ‘条件’; 查:select * from 表名 wher…...
【大模型从入门到精通32】开源库框架LangChain RAG 系统中的问答技术2
这里写目录标题 探索高级问答链类型MapReduce 和 Refine 技术 实用建议和最佳实践解决 RetrievalQA 限制结论进一步阅读和探索理论问题实践问题 探索高级问答链类型 MapReduce 和 Refine 技术 MapReduce 和 Refine 是设计用来规避由语言模型 (LM) 上下文窗口大小所导致的限制…...
MySQL 数据库管理
在 MySQL 中,数据库管理是非常基础但又至关重要的技能。无论是创建新的数据库、选择当前使用的数据库,还是查看数据库的相关信息,这些操作都是日常数据库管理中不可或缺的一部分。本文将详细介绍 MySQL 数据库管理的基本操作,包括…...
屏幕录制了一个视频,发现有些部分是不需要的,那么我们就用到视频剪辑的工具,利用必剪去删除中间的一部分视频,并且导出,然后利用格式工厂去压缩mp4文件的过程。
1、我们经常会去做一些视频教程或者软件的使用说明等等,做完了以后,会有增加字幕,或者去掉不需要一段视频。 2、打开必剪软件 3、点击【开始制作】 先将视频拖动到1的位置,然后将播放区中的视频,拖到2的区域ÿ…...
代码随想录跟练第六天——LeetCode
第454题.四数相加II 力扣题目链接(opens new window) 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] B[j] C[k] D[l] 0。 为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤…...
【Qt】常用控件QCalendarWidget的使用
常用控件QCalendarWidget的使用 QCalendarWidget表示一个日历 核心属性 属性说明 selectDate 当前选中的⽇期 minimumDate 最⼩⽇期 maximumDate 最⼤⽇期 firstDayOfWeek 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏. gridVisible 是否显⽰表格的边框 selectionMode…...
Nginx: 配置项之main段核心参数用法梳理
概述 我们了解下配置文件中的一个全局段,有哪些配置参数,包括后面的 events 字段,有哪些配置参数这里面也有一些核心参数, 对于我们Nginx运行的性能也是有很重要的帮助我们现在首先关注整个 main 段的一个核心参数用法所谓 main 段ÿ…...
密码学之RSA算法
文章目录 1. RSA算法介绍1.2 算法历史与发展1.3 算法应用场景 2. RSA密钥生成2.1 选择素数2.2 计算公钥和私钥2.3 密钥长度与安全性 3 算法原理3.1 加密原理3.2 加密方法3.3 加密示例3.4 代码实现 4. 总结 1. RSA算法介绍 1.2 算法历史与发展 RSA算法由Ron Rivest、Adi Shami…...
教你学习企业高性能web服务器-nginx
一、web服务介绍 1、Apache的三种模型 (1)Apache prefork 预派生模式,有一个主控制进程,然后生成多个子进程,使用select模型,最大并发1024每个子进程有一个独立的线程响应用户请求相对比较占用内存&…...
封装通用第三方平台用户表(微信开放平台)
文章目录 一. 注册微信开放平台1.1 开发者资质认证1.2 应用申请1.3 配置应用 二.通用数据库表设计三.入库实体类四. 对接第三方平台4.1 微信开放平台VO对象4.2 通用方法 我们的系统可能要对接很多第三方系统,为了便利用户授权使用和对多平台账户的管理。有必要设计通…...
【C++】_string类字符串详细解析(1)
假如没有给你生命,你连失败的机会都没有。你已经得到了最珍贵的,还需要抱怨什么!💓💓💓 目录 ✨说在前面 🍋知识点一:什么是string? •🌰1.string类的概念 •…...
【Linux】——进程概念(万字解读)
一 冯诺依曼体系结构 在此之前,我们先要理解我们计算机的冯诺依曼体系结构,因为是进程的基础 我们所有的操作其实都是基于这样一个模型,比如你在qq上,和别人发送消息,这个消息肯定是先通过输入设备进行输入…...
03 serv00搭建WordPress
第一步 下载 serv00 官方教程 按官方教程下载 WordPress 压缩包,解压,将 WordPress 项目文件夹重命名为 public_html(先删除原来的 public_html) 第二步 安装 完成以上步骤后访问你的网站,开始安装 WordPress …...
伪共享问题如何解决?
伪共享问题是多核处理器环境下常见的性能瓶颈之一,特别是在多线程编程中。想要解决它,就必须先了解缓存行的概念。 缓存行 缓存行是指在 CPU 缓存中最小的数据单位,通常包含一定数量的字节(例如,常见的缓存行大小为 …...
基于web框架的协同过滤的美食推荐系统【数据爬虫、管理系统、数据可更新、样式可调整】
文章目录 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主项目介绍研究背景研究的目的与意义协同过滤算法基于用户的协同过滤算法定义基于物品的协同过滤算法的定义 数据库设计db_food(美食信息表)db_collect(美食…...
Eureka中的多实例配置:如何处理微服务实例动态扩展与缩减
Eureka中的多实例配置:如何处理微服务实例动态扩展与缩减 1. 引言 在微服务架构中,服务的动态扩展与缩减是确保系统弹性和高可用性的关键因素。Eureka,作为一个服务注册和发现的组件,扮演着至关重要的角色。它由Netflix开源&…...
Ubuntu 22.04使用 IPTables 配置防火墙
网络安全管理是服务器安全的重要组成部分。在这将介绍在 Ubuntu 22.04 中使用名为 iptables 的软件包管理工具设置防火墙的过程。 IPTables简介 IPTables是一个功能强大的软件包管理工具,可用于大多数Linux发行版,包括Ubuntu 22.04。该工具允许管理员定…...
Java语言程序设计——篇十三(1)
🌿🌿🌿跟随博主脚步,从这里开始→博主主页🌿🌿🌿 欢迎大家:这里是我的学习笔记、总结知识的地方,喜欢的话请三连,有问题可以私信🌳🌳&…...
GB/T 5023.3-2008额定电压450/750V及以下聚氯乙烯绝缘电缆
聚氯乙烯绝缘电缆产品分为固定布线用无护套电缆、固定布线用护套电缆、轻型无护套软电缆、一般用途护套软电缆、安装用电线和屏蔽电线、特殊用途护套软电缆、聚氯乙烯绝缘阻燃/耐火电缆等产品。 GB/T 5023.3-2008额定电压450/750V及以下聚氯乙烯绝缘电缆 第3部分:固…...
深入单例模式
1. 饿汉模式 饿坏了,上来就先实例化一个对象,好处是代码简单,坏处是这个对象后面如果一直用不到,就是个浪费。 public class A{ private static A a new A(); private A(){} public static A getInstance(){ return a; } } 2. 懒…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
