电商在做独立系统或者APP的时候如何确保其安全性
?在网络高度发展的今天,信息安全是人们关心的永恒话题。保证信息的安全,包括各个方面,不单单指消费客户信息,还包括产品信息,系统收付款等等。总的来说,保证整个系统,重要API的参数安全是非常之重要的! 目前所有的系统架构都是采用前后端分离的系统架构,那么就不可能避免的需要服务对外提供API,那么如何保证对外的API的安全呢?以生鲜电商中API接口为例防止参数篡改和重放攻击1. 什么是API参数篡改?说明:API参数篡改就是恶意人通过抓包的方式获取到请求的接口的参数,通过修改相关的参数,达到欺骗服务器的目的,常用的防止篡改的方式是用签名以及加密的方式。2. 什么是API重发攻击?说明:API重放攻击: 就是把之前窃听到的数据原封不动的重新发送给接收方.3,常用的解决的方案常用的其他业务场景还有:发送短信接口支付接口基于timestamp和nonce的方案微信支付的接口就是这样做的timestamp的作用每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。HTTP请求从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间相比较,是否超过了60s,如果超过了则认为是非法的请求。一般情况下,从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了,如果修改timestamp参数为当前的时间戳,则signature参数对应的数字签名就会失效,因为不知道签名秘钥,没有办法生成新的数字签名。但这种方式的漏洞也是显而易见的,如果在60s之后进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效nonce的作用nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同。我们将每次请求的nonce参数存储到一个“集合”中,每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。nonce参数在首次请求时,已经被存储到了服务器上的“集合”中,再次发送请求会被识别并拒绝。nonce参数作为数字签名的一部分,是无法篡改的,因为不知道签名秘钥,没有办法生成新的数字签名。这种方式也有很大的问题,那就是存储nonce参数的“集合”会越来越大。nonce的一次性可以解决timestamp参数60s(防止重放攻击)的问题,timestamp可以解决nonce参数“集合”越来越大的问题。防篡改、防重放攻击 拦截器(用到了redis)在这不得不提的,一个稳定可靠的API也是至关重要的!电商数据安全稳定电商接口很重要public class SignAuthInterceptor implements HandlerInterceptor {
private RedisTemplate<String, String> redisTemplate;
private String key;
public SignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key) {
this.redisTemplate = redisTemplate;
this.key = key;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// 获取时间戳
String timestamp = request.getHeader("timestamp");
// 获取随机字符串
String nonceStr = request.getHeader("nonceStr");
// 获取签名
String signature = request.getHeader("signature");
// 判断时间是否大于xx秒(防止重放攻击)
long NONCE_STR_TIMEOUT_SECONDS = 60L;
if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {
throw new BusinessException("invalid timestamp");
}
// 判断该用户的nonceStr参数是否已经在redis中(防止短时间内的重放攻击)
Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
throw new BusinessException("invalid nonceStr");
}
// 对请求头参数进行签名
if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
throw new BusinessException("invalid signature");
}
// 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除
redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);
return true;
}
private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException {
Map<String, Object> params = new HashMap<>(16);
Enumeration<String> enumeration = request.getParameterNames();
if (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getParameter(name);
params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
}
String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
log.info("qs:{}", qs);
String sign = SecureUtil.md5(qs).toLowerCase();
log.info("sign:{}", sign);
return sign;
}
/**
* 按照字母顺序进行升序排序
*
* @param params 请求参数 。注意请求参数中不能包含key
* @return 排序后结果
*/
private String sortQueryParamString(Map<String, Object> params) {
List<String> listKeys = Lists.newArrayList(params.keySet());
Collections.sort(listKeys);
StrBuilder content = StrBuilder.create();
for (String param : listKeys) {
content.append(param).append("=").append(params.get(param).toString()).append("&");
}
if (content.length() > 0) {
return content.subString(0, content.length() - 1);
}
return content.toString();
}
}总结:做互联网应用,无论是生鲜小程序还是APP,安全永远都是第一位,安全做好了,其他的才能做得更好,当然,信息安全是一个永久的话题,并非通过本文就能够说得清楚的希望本文可以给大家一点思考与建议。
艾科瑞特科技(iCREIDT):2023年6月6日智能图像分析目标检测识别
艾科瑞特科技(iCREIDT):2023年6月6日智能图像分析目标检测识别人工智能技术应用、iCREDIT、目标识别、图像分析、目标追踪、MIDJOURNEY、目标追踪、目标检测、大数据、智能图像分析、艾科瑞特科技、艾科瑞特科技云市场、AI、目标检测分析、目标检测识别、CHATGPT、机器学习、DETECT、AIGC、人工智能技术、艾科瑞特、人工智能、YOLO涉外调查许可证编号(国家统计局):国统涉外证字第2116号互联网宗教信息服务许可证编号(国家民族宗教委员会):粤(2022)0000165气象信息服务单位备案(国家气象局):广东省气象信息服务单位备案号4420220022深圳艾科瑞特科技有限公司 [gf]a9[/gf] 版权所有随着人工智能技术的不断发展,越来越多的技术应用被引入到各个行业中,其中AI目标检测识别跟踪技术成为了备受关注的热门技术之一。该技术基于深度学习、计算机视觉等技术,通过对图像或视频进行处理和分析,从中准确识别出目标,并跟踪目标的行动动态,大大提升了图像处理的速度和准确率,广泛应用于安防监控、智能交通、无人驾驶、军事等各个领域。在安防监控场景中,AI目标检测识别跟踪技术可以帮助警方快速准确的发现和追踪嫌疑人,提升犯罪侦查的效率,并且减少了人工查找的时间和成本,为公共安全带来了极大的帮助。同时,该技术还可以对监控画面进行实时分析,并根据识别结果生成警报,为系统管理员提供实时的异常信息。在智能交通领域,AI目标检测识别跟踪技术可为车辆行驶和交通管理带来革命性的变化。通过技术的支持,在城市的道路中可以实现实时控制车流量,减少车辆拥堵,缓解交通压力,同时也可以精准的抓拍交通违法行为,如闯红灯、超速行驶、逆行等,减少交通事故和交通纠纷的发生。在无人驾驶领域,AI目标检测识别跟踪技术是实现自动驾驶的关键技术之一。借助此项技术,无人驾驶车辆可以通过对周围环境的感知和处理,判断路况、识别交通标志、分析路面情况等,以保证行驶的安全和稳定性。总的来说,AI目标检测识别跟踪技术是许多行业日益重视的核心技术,其商业价值也越来越被重视。有了这项技术的支持,企业可以提升产品的竞争力,加速产品研发和创新,降低操作和管理成本,最终更好地满足用户需求。在未来,随着技术不断的创新和发展,AI目标检测识别跟踪技术将会在更广泛更深入的领域得到应用,为人们的生活带来更多的便利和优势。
宝塔面板搭建Discuz论坛并发布互联网访问【无需云服务器】
前言Crossday Discuz! Board(以下简称 Discuz!)是一套通用的社区论坛软件系统,用户可以在不需要任何编程的基础上,通过简单的设置和安装,在互联网上搭建起具备完善功能、很强负载能力和可高度定制的论坛服务。下面我们在Linux上使用宝塔面板+Discuz+cpolar内网穿透工具结合,搭建一套发布到互联网环境的论坛服务,无需公网IP服务器、也无需设置路由器。1.安装基础环境安装PHP版本,由于Discuz支持的最高PHP7.4版本环境,我们在面板中安装PHP7.0版本,搜索PHP,即可看到,然后点击安装即可安装一个数据库,如果已经安装,可以跳过,点击面板上数据库,点击安装然后修改一下数据库密码,改成自己能记住的密码,用户名为root2.一键部署Discuz在面板中搜索一下关键字Discuz,然后点击一键部署设置参数,域名设置本机域名127.0.0.1,端口号可以自定义,然后设置数据库账号密码,数据库用户名和密码自己设置,点击提交,然后等待下载完成然后我们点击面板中的网站,即可看到我们刚刚部署的站点,接下来在面板中开放一个端口:8089打开浏览器,使用Linux 局域网ip+:8090(上面设置的端口号)进行访问,出现安装向导界面表示成功3.安装cpolar工具cpolar官网:https://www.cpolar.com/打开宝塔终端命令窗口,使用cpolar一件安装脚本:curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
token认证登录cpolar官网,点击左侧的验证,查看自己的认证token,之后将token贴在命令行里cpolar authtoken xxxxxxx向系统添加服务sudo systemctl enable cpolar启动cpolar服务sudo systemctl start cpolar在宝塔面板中选择安全.然后开放9200端口然后数据Linux局域网ip+:9200端口即可出现cpolar管理界面输入官网注册的账号登陆即可对隧道进行操作.4.配置域名访问Discuz我们在cpolar管理界面创建一个随机的8089的隧道:隧道名称:可自定义,注意不要重复协议:http本地地址:8089端口类型:随机域名地区:China vip点击创建然后打开在线隧道列表,查看创建隧道的公网地址,复制地址打开浏览器,使用上面的公网地址访问,既然出现了欢迎页,表示成功5.固定域名公网地址需升级至基础套餐或以上才支持配置固定域名登录cpolar官网后台,点击左侧仪表盘的>预留,找到保留二级子域名,为http隧道保留一个二级子域名。地区:选择服务器地区名称:填写您想要保留的二级子域名(可自定义)描述:即备注,可自定义填写本例保留一个名称为DiscuzTest的二级子域名。子域名保留成功后,我们将子域名复制下来,接下来需要将其配置绑定到隧道中去。登录cpolar web ui管理界面。点击左侧仪表盘的隧道管理——隧道列表,找到需要配置二级子域名的隧道(本例中为Discuz隧道),点击右侧的编辑修改隧道信息,将二级子域名配置到隧道中:域名类型:改为选择二级子域名Sub Domain:填写我们刚刚所保留的二级子域名(本例为typechoTest)修改完成后,点击更新隧道更新成功后,点击左侧仪表盘的状态——在线隧道列表,可以看到DiscuzTest隧道的公网地址,已经更新为二级子域名了,将公网地址复制下来。打开浏览器,我们来测试一下访问配置成功的二级子域名,出现安装向导界面表示成功,可以正常访问。现在,我们全网唯一的私有二级子域名,就创建好了。6.配置Discuz论坛点击我同意后,出现提示信息说版本太老,取消即可继续安装点击下一步选择全新安装然后需要配置数据库,我们填写数据库root用户名和root的密码,然后设置管理员密码,点击下一步然后就安装完成啦,点击访问然后我们已经搭建好了Discuz论坛,并且结合了cpolar映射的公网域名地址,可以远程访问啦,不受局域网络限制
【Linux问题合集001】Linux中如何将用户添加到sudo组中的步骤
看教程的前提我的linux当前用户是zhou,看以下步骤时将zhou看做你的liunx当前用户就行了:一、 以root用户登录到系统。在Linux系统中,root用户是具有完全系统管理权限的超级用户。要以root用户身份登录到系统,您可>以使用以下方法之一切换用户如果您当前已登录到系统,则可以使用su命令(表示“superuser”)切换到root用户。执行以下命令并>输入root用户的密码即可:su -注意:在输入密码时,终端中不会显示任何字符,但实际上您正在键入密码。请小心输入并确认密码>正确。二、将用户添加到sudo组中的步骤2.1、方法一1. 使用sudo命令如果您的用户帐户具有sudo权限,则可以使用sudo命令来执行需要root权限的命令。执行以下命令>并输入您的用户密码即可:sudo command
其中 command 是需要以root权限运行的命令。您将需要输入当前用户的密码以确认您具有sudo权>限。2. 使用图形界面在某些Linux系统中,您可以通过单击“Applications”(应用程序)菜单,然后在“System Tools”(系统>工具)下选择“Terminal”(终端)来打开终端。接下来,输入以下命令:su -在此方法下,您需要输入root用户的密码。注意,以root用户身份登录到系统具有很高的权限,并且需要小心操作,以免意外删除或更改重要>文件。因此,建议您仅在必要时使用root权限。3. 执行以下命令以将用户添加到sudo组中:usermod -aG sudo zhou //zhou是要你授予权限的用户名其中,zhou 是您要添加到sudo组中的用户名。请确保替换为您要添加的实际用户名。4. 重启系统以应用更改。现在,用户 zhou 应该已被授予sudo权限。用户可以使用sudo命令来执行需要root权限的命令。您可>以通过执行以下命令来测试用户是否已具有sudo权限:sudo command其中 command 是需要以root权限运行的命令。如果您被要求输入密码并成功执行命令,则表示用户>已具有sudo权限。请注意,授予用户所有权限可能存在安全风险,并且建议仅授予用户必要的权限。2.2、方法二、CentOS 7使用下面的方法在Linux系统中,通常情况下是使用sudo命令来临时赋予普通用户获得root权限的,而不是添加到>sudo用户组。但是,这取决于Linux发行版和配置。如果你使用的是CentOS 7,CentOS 7默认没有>sudo用户组。您可以使用以下命令将用户添加到wheel用户组中,wheel用户组在许多Linux发行版中都是默认的具>有sudo权限的用户组。usermod -aG wheel zhou \\把zhou换成你当前的用户就可以了注意:这里假设您要添加的用户是zhou,请将zhou替换为您实际要添加的用户名。添加用户到用户组后,您需要注销并重新登录才能使更改生效。然后,您可以测试一下用户是否具有sudo权限,输入以下命令:sudo echo "hello world"
如果用户具有sudo权限,则会提示您输入当前用户的密码。如果验证成功,则会打印“hello world”。如果未成功,则会提示输入密码的请求。我的是显示成功了:
【树与二叉树】二叉树链式结构及实现--万字详解介绍(下)
6.层序遍历:执行操作前需进行非空判断,防止对空指针进行操作。层序遍历操作原理示意图:层序遍历就是一层一层的遍历,在链式储存中,我们一般借助队列来实现层序遍历。利用的是队列的先进先出的性质。先让根入队,然后出队头数据,再让队头数据的左右孩子入队。每从队头删除掉一个元素,就让这个元素的两个孩子入队,直到队列为空为止。首先创建队列,并对队列进行初始化。接着让二叉树的根入队注意 记得修改队列元素的类型 typedef BinaryTreeNode* QDataType; //结构体指针。判断队列是否为空,如果队列为空,说明遍历已经结束,应当换行并销毁队列。若队列不为空,就将队头的节点拷贝出来,然后删除队头节点,把拷贝的队头节点数据进行打印,最后让拷贝接节点的左右孩子先后入队。如果孩子没有子节点,相当于使空 NULL 入队,并不影响访问结果。void TreeLevelOrder(BNode* root)
{
Q q;
QInit(&q);//初始化队列
if (root)//不是空树
{
QPush(&q, root);//push root
}
while (!QEmpty(&q))//队列不为空
{
BNode* front = QFront(&q);//取对头数据
QPop(&q);//这里只是pop了队列的头节点,但值被局部变量front保存下来了
//树的节点也依然存在
printf("%c ", front->data);//所以可以打印这个值
if (front->left)//不为空,入左树
{
QPush(&q, front->left);
}
if (front->right)//不为空,入右树
{
QPush(&q, front->right);
}
}
printf("\n");
QDestroy(&q);//及时销毁
}7.二叉树节点个数:执行操作前需进行非空判断,防止对空指针进行操作。对于二叉树节点数量的统计,采用的方式是任意选择一种遍历顺序(只依照遍历顺序,不访问节点),遍历整个树结构,每找到一个节点让计数变量加一即可。思路1 :使用前序 /中序 /后序遍历,全局变量记录但是以下代码有 Bug :如果采用前序重复遍历 2 次主要问题出在全局变量上,这里只需要在第 2 次遍历时置 0 即可(size=0)学习了以后的知识,会发现这种操作还有线程安全的问题思路 2:函数使用带返回值的方式,其内部的递归本质上是一个后序遍厉//不能直接用size 线程安全问题
void TreeSize(BNode* root, int* size)
{
if (root == NULL)
{
printf("TreeSize Get Error!\n");
return;
}
(*size)++;
TreeSize(root->left, size);
TreeSize(root->right, size);
}
//分治-分而治之
//其实这种就是递归的思想,在现实生活中也经常使用到
//比如 1 位校长要统计学校的人数,他不可能亲自去挨个数
//一般是通过院长、系主任、辅导员、班长、舍长的层层反馈才得到结果的
int BinaryTreeSize(BNode* root)
{
return root==NULL ? 0:TreeSize(root->left)+TreeSize(root->right)+1;
}8.求树的高度/深度核心思想 :当前树的深度 = max (左子树的深度,右子树的深度) + 1//二叉树的深度/高度
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = BinaryTreeDepth(root->left);
int rightDepth = BinaryTreeDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
9.第 K 层节点个数:执行操作前需进行非空判断,防止对空指针进行操作。若根节点为空,则节点的个数为0。如果我们要计算第 K 层的元素(节点)个数,首先从根节点开始统计,假设我们每向下一层 K 就减 1,那么当 K = 1 时,表示我们来到了第 K 层,然后计算 K = 1 时的节点个数返回值相加的结果即可。核心思想 :求当前树的第 k 层 = 左子树的第 k - 1 层 + 右子树的第 k - 1 层 (当 k = 1 时,说明此层就是目标层)int TreeKLevelSize(BNode* root, int k)
{
assert(k>0);
if (root == NULL)
{
printf("TreeKLevelSize Get Error!\n");
return 0;
}
if (k == 1)
{
return 1;
}
int leftk=TreeKLevelSize(root->left, k - 1);
//要及时存储递归后的值,不然忘了又要算一遍,浪费时间
int rightk=TreeKLevelSize(root->right, k - 1);
return leftk + rightk;
}8.叶节点个数:执行操作前需进行非空判断,防止对空指针进行操作。叶节点就是度为0的节点,即没有子树,我们同样使用递归进行统计。根节点进入函数后,应当首先判断根节点是否为叶节点,如果一个节点的左子树和右子树同时为空,说明这是一个叶节点;如果不是,其左子树的叶节点和右子树的叶节点之和就是当前节点以下的所以叶节点,形成递归。核心思想 :以 left 和 right 为标志,如果都为 NULL,则累加int TreeLeafSize(BNode* root)
{
if (root == NULL)
{
printf("TreeLeafSize Get Error!\n");
return 0;
}
else
{
return (root->left) == NULL && (root->right) == NULL ? 1 : TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
}10.查找值为x的节点:执行操作前需进行非空判断,防止对空指针进行操作。若节点为空,就返回空。若节点的值等于要查找的值,就返回该节点。若节点不为空,但节点的值不是我们要查找的值,就查找节点的左子树,如果查找的结果不为空,就返回该节点。若左子树的查找结果为空,就以同样的方式处理右子树。如果都找不到,就返回空。核心思想 :1、先判断是不是当前节点,是就返回,不是就继续找;2、先去左树找,找到就返回3、左树没找到,再找右树BNode* TreeFind(BNode* root, BDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BNode* lret = TreeFind(root->left, x);
if (lret)
{
return lret;
}
BNode* rret = TreeFind(root->right, x);
if (rret)
{
return rret;
}
return NULL;
}
//简化版本
BNode* TreeFind(BNode* root, BDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BNode* lret = TreeFind(root->left, x);
if (lret)
{
return lret;
}
returnTreeFind(root->right, x);
}
11.完全二叉树判断:执行操作前需进行非空判断,防止对空指针进行操作。判断是否为完全二叉树需要用到层序遍历的思想。若一个二叉树不是完全二叉树时,那么当我们对它进行层序遍历时,其中的部分节点就会是 NULL,于是我们可以通过这一点来判断一个二叉树是否为完全二叉树。前半部分与二叉树的层序遍历一样,建队列,根入队,队列不为空,进入while循环,在循环中删队头节点,然后让该节点的左右孩子入队。注意 这里循环停止的条件还要加上一个即堆顶的元素为空。在跳出循环后存在两种情况,第一种是队列已空,节点之间没有空,表明是完全二叉树,返回true;而第二种情况是队列不为空,但在访问队头节点时访问到了 NULL,这时我们需要再次进行循环,若队列不为空,就进入循环逐个查找并删除队头的节点,若发现不为空的节点,说明节点间有 NULL 相隔,即该二叉树不是完全二叉树,返回false。步骤:给一个辅助队列。如果 root 非空,则将 root 入队。然后给定循环队列为空则停止,取队头元素,并出队头;这时将取出元素的左右子树都放入,一旦出元素出到 NULL 这时结束循环。完全二叉树是连续的,一旦出现 NULL ,那么后面的元素都应该是空。如果空指针后还有非空元素,那么一定不是完全二叉树。这时继续出队列,如果出队列过程中遇到非空元素,则销毁队列返回假;否则不断出队列元素。如果循环结束还没有返回,说明后面都是空指针,这时销毁队列,返回真。bool BinaryTreeComplete(BTNode* root)
{
// 使用层序遍历思想
Q q;
QueueInit(&q);
// 如果非空,则入队列
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
// 一旦出队列到空,就出去判断
if (front == NULL)
{
break;
}
else
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
else
{
QueuePop(&q);
}
}
QueueDestroy(&q);
return true;
}
12.二叉树销毁:执行操作前需进行非空判断,防止对空指针进行操作。销毁二叉树需要把二叉树的每个节点都销毁,故采用后序遍历的顺序进行销毁。注意:节点里存放的是左右孩子的指针,若我们在传参时仅传递节点的指针类型,则函数中的左右孩子地址就是一份临时拷贝,将导致无法对每个节点的指针进行置空,故我们在销毁二叉树时,函数参数应当传递二级指针。先销毁左树,再销毁右树,最后销毁根注意pproot是t的拷贝,应该传二级指针(或C++引用取别名)void BinaryTreeDestory(BNode** pproot)//*& C++引用取别名
{
if (*pproot == NULL)
{
printf("BinaryTreeDestroy Error!\n");
return NULL;
}
BinaryTreeDestory(&(*pproot)->left);
BinaryTreeDestory(&(*pproot)->right);
free(*pproot);
pproot = NULL;
}四、链式存储结构完整代码:1.Heap.h:#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//队列(为层序遍历做准备):
typedef BinaryTreeNode* QDataType;//结构体指针
typedef struct QueueNode
{
QDataType data;
struct QNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
}Q;
void QInit(Q* p); //初始化队列
void QPush(Q* p, QDataType x); //入队
void QPop(Q* p); //出队
QDataType QFront(Q* p); //查看队头
bool QEmpty(Q* p); //查看队列容量
void QDestroy(Q* p); //队列的销毁
//二叉树的链式结构:
typedef char BDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left; // 指向当前节点左孩子
struct BinaryTreeNode* right; // 指向当前节点右孩子
BDataType data; // 当前节点值域
}BNode;
BNode* BuyNode(BDataType x); //二叉树节点创建
void PrevOrder(BNode* root); // 前序遍历
void InOrder(BNode* root); // 中序遍历
void PostOrder(BNode* root); // 后序遍历
void TreeLevelOrder(BNode* root); //层序遍历
void TreeSize(BNode* root, int* size); // 统计二叉树节点个数
int BinaryTreeSize(BTNode* root);// 二叉树节点个数
int BinaryTreeDepth(BTNode* root)//二叉树深度/高度
int TreeLeafSize(BNode* root); // 计算二叉树叶节点个数
int TreeKLevelSize(BNode* root, int k); // 计算第 K 层的节点个数
BNode* TreeFind(BNode* root, BDataType x); // 查找元素(节点)
bool BinaryTreeComplete(BNode* root); // 完全二叉树判断
2.Heap.c:#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//队列部分接口(为层序遍历做准备):
//初始化队列
void QInit(Q* p)
{
if (p == NULL)
{
printf("QueueINit fail\n");
return;
}
p->head = NULL;
p->tail = NULL;
}
//入队:
void QPush(Q* p, QDataType x)
{
if (p == NULL)
{
printf("QueuePush fail\n");
return;
}
//申请新节点:
QNode* newnode = (QNode*)malloc(sizeof(QNode));
newnode->data = x;
newnode->next = NULL;
//分情况插入:
if (p->head == NULL)
{
p->head = p->tail = newnode;
}
else
{
//将新节点连接在队尾:
p->tail->next = newnode;
//更新队尾:
p->tail = newnode;
}
}
//出队:
void QPop(Q* p)
{
if (p == NULL)
{
printf("QueuePop fail\n");
exit;
}
if (QEMpty(p))
{
printf("Queue is NUll\n");
return;
}
else
{
QNode* next = p->head->next; //记录第二数据
free(p->head); //释放原头节点
p->head = next; //更新头节点
//注意对删空队列的情况应进行区分处理
if (p->head == NULL)
{
p->tail = NULL;
}
}
}
//查看队头
QDataType QFront(Q* p)
{
if (p == NULL)
{
printf("QueueFront get fail\n");
return;
}
if (QEmpty(p))
{
printf("The Queue is NULl\n");
return;
}
return p->head->data;
}
//查看队列容量
bool QEmpty(Q* p)
{
if (p == NULL)
{
printf("QueueEmpty fail\n");
return;
}
return p->head == NULL;
}
//队列的销毁:
void QDestroy(Q* p)
{
if (p == NULL)
{
printf("QueueNodeDestroy fail\n");
exit;
}
QNode* cur = p->head;
while (cur != NULL)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
p->head = p->tail = NULL;
}
//二叉树节点创建:
BNode* BuyNode(BDataType x)
{
BNode* node = (BNode*)malloc(sizeof(BNode));
if (node == NULL)
{
printf("malloc Error!\n");
return;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
//前序遍历:
void PrevOrder(BNode* root)
{
if (root == NULL)
{
printf("PrevOrder Error!\n");
return;
}
printf("%c ", root->data); // 访问当前节点的值
PrevOrder(root->left); // 先递归访问当前节点的左子树
PrevOrder(root->right); // 再递归访问当中前节点的右子树
}
//中序遍历:
void InOrder(BNode* root)
{
if (root == NULL)
{
printf("InOrder Error!\n");
return;
}
InOrder(root->left); // 递归访问当前节点的左树
printf("%c ", root->data); // 访问当前节点的值
InOrder(root->right); // 最后递归访问当前节点的右树
}
//后序遍历:
void PostOrder(BNode* root)
{
if (root == NULL)
{
printf("PostQrder Error!\n");
return;
}
PostOrder(root->left); // 先递归访问左子树
PostOrder(root->right); // 再递归访问右子树
printf("%c ", root->data); // 最后访问当前节点的值
}
//层序遍历:
void TreeLevelOrder(BNode* root)
{
Q q;
QInit(&q);//初始化队列
if (root)//不是空树
{
QPush(&q, root);//push root
}
while (!QEmpty(&q))//队列不为空
{
BNode* front = QFront(&q);//取对头数据
QPop(&q);//这里只是pop了队列的头节点,但值被局部变量front保存下来了
//树的节点也依然存在
printf("%c ", front->data);//所以可以打印这个值
if (front->left)//不为空,入左树
{
QPush(&q, front->left);
}
if (front->right)//不为空,入右树
{
QPush(&q, front->right);
}
}
printf("\n");
QDestroy(&q);//及时销毁
}
//统计二叉树元素个数:
void TreeSize(BNode* root, int* size)
{
if (root == NULL)
{
printf("TreeSize Get Error!\n");
return;
}
(*size)++;
TreeSize(root->left, size);
TreeSize(root->right, size);
}
//计算叶节点个数:
int TreeLeafSize(BNode* root)
{
if (root == NULL)
{
printf("TreeLeafSize Get Error!\n");
return 0;
}
else
{
return (root->left) == NULL && (root->right) == NULL ? 1 : TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
}
//计算第 K 层的节点个数:
int TreeKLevelSize(BNode* root, int k)
{
if (root == NULL)
{
printf("TreeKLevelSize Get Error!\n");
return 0;
}
if (k == 1)
{
return 1;
}
return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}
//查找元素(节点):
BNode* TreeFind(BNode* root, BDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BNode* lret = TreeFind(root->left, x);
if (lret)
{
return lret;
}
BNode* rret = TreeFind(root->right, x);
if (rret)
{
return rret;
}
return NULL;
}
//完全二叉树判断:
bool BinaryTreeComplete(BNode* root)
{
Q q;
QInit(&q);
if (root)
{
QPush(&q, root);
}
while (!QEmpty(&q))
{
BNode* front = QFront(&q);
QPop(&q);
if (front == NULL)
{
break;
}
QPush(&q, front->left);
QPush(&q, front->right);
}
while (!QEmpty(&q))
{
BNode* front = QFront(&q);
QPop(&q);
if (front)
{
return false;
}
}
QDestroy(&q);
return true;
}
//二叉树销毁:
void BinaryTreeDestory(BNode** pproot)
{
if (*pproot == NULL)
{
printf("BinaryTreeDestroy Error!\n");
return NULL;
}
BinaryTreeDestory(&(*pproot)->left);
BinaryTreeDestory(&(*pproot)->right);
free(*pproot);
pproot = NULL;
}3.Test.c:#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode('A');
BTNode* node2 = BuyNode('B');
BTNode* node3 = BuyNode('C');
BTNode* node4 = BuyNode('D');
BTNode* node5 = BuyNode('E');
BTNode* node6 = BuyNode('F');
node1->_left = node2;
node1->_right = node3;
node2->_left = node4;
node3->_left = node5;
node3->_right = node6;
return node1;
}
int main()
{
int size=0;
BTNode* root = CreatBinaryTree();
//遍厉前中后序输出二叉树节点的个数
PrevOrder(root);
InOrder(root);
PostOrder(root);
printf("-----------------cur-----------------\n");
//优化二叉树节点的个数
printf("BinaryTreeSize:%d\n", TreeSize(root));
printf("-----------------cur-----------------\n");
//二叉树叶子节点个数
printf("BinaryTreeLeaSize:%d\n", TreeLeafSize(root));
printf("-----------------cur-----------------\n");
//二叉树第k层节点个数
printf("BinaryTreeLeveLKSize:%d\n", TreeLeveLKSize(root, 3));
printf("-----------------cur-----------------\n");
//二叉树的深度/高度
printf("BinaryTreeDepth:%d\n", BinaryTreeDepth(root));
printf("-----------------cur-----------------\n");
// 二叉树查找值为x的节点
BTNode* ret = TreeFind(root, 'D');
if(ret)
{
printf("找到了\n");
}
else
{
printf("没找到\n");
}
BinaryTreeDestory(root);
return 0;
}3.总结:今天我们认识并学习了二叉树链式结构的相关概念,并且对各接口功能进行了实现。到现在我们完成了二叉树的链式结构和顺序结构的学习。下一篇博客我们将完成一些二叉树基础OJ练习。希望我的文章和讲解能对大家的学习提供一些帮助。当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~
【服务器】安卓手机使用Termux搭建web服务
概述Termux是一个Android终端仿真应用程序,用于在 Android 手机上搭建一个完整的Linux 环境,能够实现Linux下的许多基本操作,不需要root权限Termux就可以正常运行。Apache是一个开源网页服务器软件,由于其跨平台和安全性,被广泛使用,是最流行的 Web 服务器软件之一。我们可以在Android手机上使用Termux来搭建Web服务器,同时做内网穿透,实现公网用户也可以访问,将Android变成小型的云服务器。下面介绍在安卓Termux上搭建apache服务创建个人站点并且结合cpolar内网穿透工具实现公网访问。1.搭建apache执行命令安装apache,安装相对简单,一键安装即可pkg install apache2然后启动apacheapachectl start启动的时候如出现以下问题解决方法,修改配置文件,如果没有安装vim,执行命令安装一下pkg install vim编辑修改配置文件vim $PREFIX/etc/apache2/httpd.conf找到ServerName解开注释,www.example.com改为127.0.0.1:8080,端口号可以自己改成自己喜欢的修改后再次启动,即可打开浏览器,输入http://127.0.0.1:8080,即可看到apahe欢迎页面,停止apacheapachectl stop重启apacheapachectl restart2.安装cpolar内网穿透cpolar官网:https://www.cpolar.com成功创建了运行站点的一个apache容器后,我们接下来用cpolar做内网穿透来实现公网环境下的访问。cpolar它是一个安全的内网穿透云服务,支持http/https/tcp协议,可以永久免费使用还不限制流量,支持映射80/443端口。它可以通过创建安全隧道,将本地服务暴露到公网上,让公网用户也可以正常访问内网服务,不需要公网IP,也不用设置路由器。创建一个sources.list.d的文件夹:mkdir -p $PREFIX/etc/apt/sources.list.d添加cpolar下载源文件echo "deb [trusted=yes] http://termux.cpolar.com termux extras" >> $PREFIX/etc/apt/sources.list.d/cpolar.list更新仓库pkg update安装cpolarpkg install cpolar安装termux服务注意:安装完成后记得关闭重启一下termux 才生效!!pkg install termux-services重启完termux后,然后启动cpolarsv up cpolar设置开机自启sv-enable cpolar这个是停止cpola服务sv down cpolarcpolar.yml主配置文件路径位置$PREFIX/etc/cpolar/cpolar.yml然后在手机浏览器我们输入http://localhost:9200即可看到cpolar管理界面,使用在cpolar官网注册的邮箱账号即可登陆3.公网访问配置手机浏览器打开cpolar web ui管理界面,我们点击左侧仪表盘的隧道管理——创建隧道,由于apache上面我们修改配置文件是8080端口(文章是8080,具体端口号以自己的端口为准),因此我们要来创建一条http隧道,指向8080端口:隧道名称:可自定义,注意不要重复协议:http本地地址:8080域名类型:选择随机域名地区:选择China VIP点击创建创建成功后打开在线隧道列表,可以看到公网访问的地址,有两种访问方式,一种是http,一种是https我们使用http方式,在浏览器输入公网地址,即可访问成功4.固定公网地址由于上面创建的是免费随机隧道,所生成的公网地址会在24小时内随机变化,为了方便长久稳定连接,我们可以固定这个公网地址,配置一个cpolar固定的二级子域名【无需备案】,或者也可以配置使用你自己的域名来访问,这里我们以配置固定的二级子域名来举例。需升级至基础套餐或以上才支持配置二级子域名登录cpolar官网后台,点击左侧仪表盘的预留,找到保留二级子域名,为http隧道保留一个二级子域名。地区:选择服务器地区名称:填写您想要保留的二级子域名(可自定义)描述:即备注,可自定义填写本例保留一个名称为mywebsitetest的二级子域名。子域名保留成功后,我们将子域名复制下来,接下来需要将其配置到隧道中去。登录cpolar web ui管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到需要配置二级子域名的隧道,点击右侧的编辑修改隧道信息,将二级子域名配置到隧道中:域名类型:改为选择二级子域名Sub Domain:填写我们刚刚所保留的二级子域名(本例为mywebsitetest)修改完成后,点击更新隧道更新成功后,点击左侧仪表盘的状态——在线隧道列表,可以看到隧道的公网地址,已经更新为二级子域名了.固定后我们使用固定的公网地址,在浏览器中打开,即可看到apache默认页面,这样一个固定的公网地址访问就设置好了5.添加站点上面我们访问的页面是apache默认页面,不是我们希望的页面,我们可以把自己喜欢的页面放上去,进入apache htdocs文件夹cd $PREFIX/share/apache2/default-site/htdocs安装下载工具wgetpkg install wget这里做演示,我们下载一个测试站点wget https://www.cpolar.com/static/downloads/meditation-app-master.tar.gz下载好后解压tar xzf meditation-app-master.tar.gz然后我们在浏览器上面输入上面我们的公网地址加上资源路径/meditation-app-master/index.html,就可看到我们的个人站点啦。现在只要保持隧道为正常在线状态,公网用户就可以通过这个固定公网地址来访问到我们在termux上搭建的web站点。
【服务器】使用Nodejs搭建HTTP web服务器
@[TOC]转载自内网穿透工具的文章:使用Nodejs搭建HTTP服务,并实现公网远程访问「内网穿透」前言Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation(原为 Node.js Foundation,已与 JS Foundation 合并)持有和维护,亦为 Linux 基金会的项目。Node.js 采用 Google 开发的 V8 运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于资料密集的即时应用程序。Node.js 大部分基本模块都用 JavaScript 语言编写。在 Node.js 出现之前,JavaScript 通常作为客户端程序设计语言使用,以JavaScript 写出的程序常在用户的浏览器上运行。Node.js 的出现使 JavaScript 也能用于服务端编程。Node.js 含有一系列内置模块,使得程序可以脱离 Apache HTTP Server 或 IIS,作为独立服务器运,下面将介绍如何简单几步实现远程公共网络下访问windwos node.js的服务端。1.安装Node.js环境官网下载node.js,我们选择64位一键安装下载 | Node.js安装好后我们打开cmd,输入命令有正常出来版本号,表示安装成功,一键安装版,默认会配置环境变量。node -v2.创建node.js服务这里我们在本地创建一个简单的nodejs服务,创建一个贪吃蛇页面小游戏来进行演示。首先在本地创建一个文件夹,并在文件夹中新建2个文件,一个是js文件和一个html文件,需要放在同个目录下,然后使用vscode打开。game.html文件nodetest.js文件在game.html添加如下html代码并保存,以下代码是一个html页面小游戏(贪吃蛇)<!DOCTYPE html>
<html>
<head>
<title>贪吃蛇</title>
<meta charset="UTF-8">
<meta name="keywords" content="贪吃蛇">
<meta name="Description" content="这是一个初学者用来学习的小游戏">
<style type="text/css">
*{margin:0;}
.map{margin:100px auto;
height:600px;
width:900px;
background:#00D0FF;
border:10px solid #AFAEB2;
border-radius:8px;
}
</style>
</head>
<body>
<div class="map">
<canvas id="canvas" height="600" width="900">
</canvas>
</div>
<script type="text/javascript">
//获取绘制工具
/*
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");//获取上下文
ctx.moveTo(0,0);
ctx.lineTo(450,450);*/
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
/*ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(450,450);
ctx.stroke();
*/
var snake =[];//定义一条蛇,画蛇的身体
var snakeCount = 6;//初始化蛇的长度
var foodx =0;
var foody =0;
var togo =0;
function drawtable()//画地图的函数
{
for(var i=0;i<60;i++)//画竖线
{
ctx.strokeStyle="black";
ctx.beginPath();
ctx.moveTo(15*i,0);
ctx.lineTo(15*i,600);
ctx.closePath();
ctx.stroke();
}
for(var j=0;j<40;j++)//画横线
{
ctx.strokeStyle="black";
ctx.beginPath();
ctx.moveTo(0,15*j);
ctx.lineTo(900,15*j);
ctx.closePath();
ctx.stroke();
}
for(var k=0;k<snakeCount;k++)//画蛇的身体
{
ctx.fillStyle="#000";
if (k==snakeCount-1)
{
ctx.fillStyle="red";//蛇头的颜色与身体区分开
}
ctx.fillRect(snake[k].x,snake[k].y,15,15);//前两个数是矩形的起始坐标,后两个数是矩形的长宽。
}
//绘制食物
ctx.fillStyle ="black";
ctx.fillRect(foodx,foody,15,15);
ctx.fill();
}
function start()//定义蛇的坐标
{
//var snake =[];//定义一条蛇,画蛇的身体
//var snakeCount = 6;//初始化蛇的长度
for(var k=0;k<snakeCount;k++)
{
snake[k]={x:k*15,y:0};
}
drawtable();
addfood();//在start中调用添加食物函数
}
function addfood()
{
foodx = Math.floor(Math.random()*60)*15; //随机产生一个0-1之间的数
foody = Math.floor(Math.random()*40)*15;
for (var k=0;k<snake;k++)
{
if (foodx==snake[k].x&&foody==sanke[k].y)//防止产生的随机食物落在蛇身上
{
addfood();
}
}
}
function move()
{
switch (togo)
{
case 1: snake.push({x:snake[snakeCount-1].x-15,y:snake[snakeCount-1].y}); break;//向左走
case 2: snake.push({x:snake[snakeCount-1].x,y:snake[snakeCount-1].y-15}); break;
case 3: snake.push({x:snake[snakeCount-1].x+15,y:snake[snakeCount-1].y}); break;
case 4: snake.push({x:snake[snakeCount-1].x,y:snake[snakeCount-1].y+15}); break;
case 5: snake.push({x:snake[snakeCount-1].x-15,y:snake[snakeCount-1].y-15}); break;
case 6: snake.push({x:snake[snakeCount-1].x+15,y:snake[snakeCount-1].y+15}); break;
default: snake.push({x:snake[snakeCount-1].x+15,y:snake[snakeCount-1].y});
}
snake.shift();//删除数组第一个元素
ctx.clearRect(0,0,900,600);//清除画布重新绘制
isEat();
isDead();
drawtable();
}
function keydown(e)
{
switch(e.keyCode)
{
case 37: togo=1; break;
case 38: togo=2; break;
case 39: togo=3; break;
case 40: togo=4; break;
case 65: togo=5; break;
case 68: togo=6; break;
}
}
function isEat()//吃到食物后长度加1
{
if(snake[snakeCount-1].x==foodx&&snake[snakeCount-1].y==foody)
{
addfood();
snakeCount++;
snake.unshift({x:-15,y:-15});
}
}
//死亡函数
function isDead()
{
if (snake[snakeCount-1].x>885||snake[snakeCount-1].y>585||snake[snakeCount-1].x<0||snake[snakeCount-1].y<0)
{
?
window.location.reload();
}
}
document.onkeydown=function(e)
{
keydown(e);
}
window.onload = function()//调用函数
{
start();
setInterval(move,150);
drawtable();
}
</script>
</body>
</html>nodetest.js文件添加如下js代码,以下代码意思是开启一个http服务,设置监听3000端口号const http = require('http');
?
//加载文件模块
const fs = require("fs");
?
?
const hostname = '127.0.0.1';
//端口
const port = 3000;
?
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
fs.readFile('./game.html', (err, data) => {
if (err) throw err;
console.log(data.toString);
res.end(data);
});
});
?
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});3. 访问node.js 服务当我们编写好相关代码后,我们开始启动服务.在vscode控制台输入命令【注意需要进入到相应的文件目录下执行命令】node .\nodetest.js有正常返回提示服务在本地3000端口下,我们打开浏览器,访问http://127.0.0.1:3000/,出现贪吃蛇界面表示成功【游戏控制:键盘上下左右键】4.内网穿透这里我们使用cpolar来进行内网穿透,支持http/https/tcp协议,不限制流量,无需公网IP,也不用设置路由器,使用简单。4.1 安装配置cpolar内网穿透cpolar官网:cpolar - 安全的内网穿透工具访问cpolar官网,注册一个账号,然后下载并安装客户端,具体安装教程可以参考官网文档教程。windows系统:在官网下载安装包后,双击安装包一路默认安装即可。linux系统:支持一键自动安装脚本,详细请参考官网文档——入门指南4.2 创建隧道映射本地端口cpolar安装成功后,在浏览器上访问本地9200端口http://localhost:9200,使用cpolar邮箱账号登录。点击左侧仪表盘的隧道管理——创建隧道,创建一个指向本地3000端口的http隧道隧道名称:可自定义命名,注意不要与已有的隧道名称重复协议:选择http本地地址:3000域名类型:免费选择随机域名地区:选择China vip点击创建隧道创建成功后,点击左侧的状态——在线隧道列表,查看所生成的公网地址,然后复制地址打开浏览器器,我们使用上面公网地址进行访问,至此,我们成功将本地·node.js 服务发布到了公网地址5.固定公网地址由于以上使用cpolar所创建的隧道使用的是随机公网地址,24小时内会随机变化,不利于长期远程访问。因此我们可以为其配置二级子域名,该地址为固定地址,不会随机变化。保留一个二级子域名登录cpolar官网,点击左侧的预留,选择保留二级子域名,设置一个二级子域名名称,点击保留,保留成功后复制保留的二级子域名名称保留成功后复制保留的二级子域名地址配置二级子域名访问http://127.0.0.1:9200/,登录cpolar web UI管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到所要配置的3000隧道,点击右侧的编辑修改隧道信息,将保留成功的二级子域名配置到隧道中域名类型:选择二级子域名Sub Domain:填写保留成功的二级子域名点击更新更新完成后,打开在线隧道列表,此时可以看到公网地址已经发生变化,地址名称也变成了保留过的二级子域名名称,将其复制下来然后使用固定http地址打开浏览器访问访问成功,现在这个公网地址是固定的了,不会随机变化。成功通过cpolar内网穿透,实现在外远程访问nodejs服务,无需公网IP ,也不用设置路由器。
【服务器】利用树莓派搭建 web 服务器-无需公网IP
@[TOC]转载自cpolar极点云的文章:在树莓派上搭建Web站点概述这非常适合设置您的第一个网站,不仅可以学习管理 wordpress 站点,还可以学习 Linux。您将需要一个树莓派(Raspberry Pi)、几个小时和一台计算机来下载映像。 树莓派(RPI) 是学习这些东西的完美设备。还可以查看 RaspberryPi 的官方项目站点 来源: https://projects.raspberrypi.org/en/projects/lamp-web-server-with-wordpress使用 Raspberry Pi Imager 安装 Raspberry Pi OS下载适用于您的操作系统的树莓派镜像工具。它支持大多数操作系统(Windows、Mac 和 Linux)。Raspberry Pi OS – Raspberry Pi运行并安装树莓派镜像工具选择 Raspberry Pi OS -> Raspberry Pi OS (32-bit)将 SD 卡插入计算机点击 Write 按钮设置 Apache Web 服务器Apache 是一种流行的 Web 服务器应用程序,您可以将其安装在 Raspberry Pi 上以允许它为网页提供服务。Apache 本身可以通过 HTTP 提供 HTML 文件。通过附加模块,它可以使用 PHP 等脚本语言提供动态网页。sudo apt-get install apache2 -y
sudo service apache2 restart测试 web 站点默认情况下,Apache 将测试 HTML 文件放在 web 文件夹中,您可以从您的 Pi 或网络上的另一台计算机查看该页面。在 Raspberry Pi 上打开 Apache 默认网页:通过从菜单中选择 Internet > Chromium Web 浏览器打开 Chromium。输入地址 http://localhost您应该在浏览器窗口中看到:安装静态样例站点为了让站点的内容更有意义,我们部署一个简单的静态演示站点--冥想放松站点。cd /var/www/html/
sudo rm *
sudo wget https://www.cpolar.com/static/downloads/meditation-app-master.tar.gz
sudo tar xzf meditation-app-master.tar.gz
sudo mv meditation-app-master/* .
sudo rm -rf meditation-app-master meditation-app-master.tar.g重新在浏览器打开,并刷新站点:http://localhost这是一个用于冥想的小工具站点,可以用它帮助自己工作之余放松2-10分钟。它可以选择不同冥想情景及放松时间(2-5-10分钟)。将web站点发布到公网目前,这个站点只能在局域网站可以被访问,公网用户是访问不到的。要想所有人访问你创建的漂亮站点,我们需要做如下工作。安装 Cpolar内网穿透cpolar官网:cpolar - 安全的内网穿透工具cpolar是一款内网穿透工具,可以将您的内网站点暴露到公网上,使所有人可以访问到你的站点。cpolar一键安装脚本:(国内用户)curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash或短链接安装方式:(国外用户)curl -sL https://git.io/cpolar | sudo bash查看cpolar版本信息cpolar version如果正常显示,则安装成功,如图:cpolar进行token认证如果您还没有cpolar账号,请去cpolar官网注册并登录后台获取认证tokencpolar authtoken xxxxxxxxxxxxxxxxxx生成cpolar随机域名网址cpolar http -region=cn_vip 80上图显示,cpolar将内网站点,发布到了一个随机域名网址:https://711d7522.vip.cpolar.cn我们打开浏览器试一下:https://711d7522.vip.cpolar.cn现在,我们已经发布了内网的web站点到公网,任何人都可以访问到它。回到终端窗口,按CTRL+C键,退出cpolar此时,公网地址不再能被访问。生成cpolar二级子域名虽然已经发布到公网,但域名是随机变化的,只适合临时测试使用。如果要长期使用,我们要配置二级子域名。操作步骤:升级到cpolar基础套餐登录到cpolar后台-->预留-->保留二级子域名,例如添加dev9,地区选择VIP China地区在前台终端测试域名cpolar http -subdomain=dev9 -region=cn_vip 80如果显示正常,则说明我们已经配置好了。用新的域名在浏览器里访问:https://dev9.vip.cpolar.cn正常则说明我们的固定二级子域名配置好了。将参数保存到cpolar配置文件中刚刚我们在前台运行cpolar程序,关掉后,域名消失,现在我们将参数保存到配置文件中。以支持开机后台自启动运行。编辑配置文件nano /usr/local/etc/cpolar/cpolar.yml如图:上图为样例配置文件,它会配置两个默认隧道:一个ssh隧道和一个website隧道。参数说明:authtoken: xxxxxxxxxxxx #认证token
?
tunnels:
ssh: #隧道名称,表示ssh,名称可以自定义
addr: 22 #端口号为22
proto: tcp #协议tcp
region: cn_vip #地区,cn_vip,可选:us,hk,cn,cn_vip
website: #隧道名称,用户可以自定义,但多隧道时,不可重复
addr: 8080 #本地Web站点端口
proto: http #协议http
region: cn_vip #地区,cn_vip,可选:us,hk,cn,cn_vip本例中,我们需要修改如下内容:将website隧道的默认8080端口修改为80添加一行 subdomain: "你的二级子域名"修改后的效果如图:注意: 配置文件是yaml格式的,缩进敏感,而且不能有TAB键。然后按CTRL+X,退出,提示你是否保存,回答Y,确认保存文件路径,回车测试修改后配置文件在前台启动所有隧道测试cpolar start-all如上图显示,则为正常,按CTRL+C退出如果报错,会提示配置文件某行有错误,请重新修改。直到类似上图正确输出。配置cpolar服务开机自启动配置cpolar开机自启动sudo systemctl enable cpolar守护进程方式,启动cpolarsudo systemctl start cpolar查看cpolar守护进程状态sudo systemctl status cpolar如图说明,启动状态成功重新启动sudo reboot重启后,检查cpolar隧道是否仍在线访问后台-->状态 cpolar - secure introspectable tunnels to localhost如图说明配置成功
【服务器】无公网IP,异地远程连接威联通NAS
@[TOC]转载自远程内网穿透的文章:无需公网IP,在外远程访问NAS威联通QNAP【内网穿透】前言购入威联通NAS后,很多用户对于如何在外在公网环境下的远程访问威联通NAS不太熟悉,但这又很有必要。所以,本次教程分享如何实现在外远程访问连接家里内网的威联通NAS,无需公网IP,也不用设置路由器,简单通过cpolar内网穿透来实现。cpolar:cpolar - 安全的内网穿透工具1. 威联通安装cpolar内网穿透注意:威联通需要是X64的CPU,我们通过安装docker版本来实现进入App Centrer,下载容器工具container station进入Container Station,然后选择创建,然后搜索cpolar,找到cpolar镜像,点击安装安装时配置一下网络,点击打开高级设置网络模式更改为host,然后点击创建创建好后在列表中可以看到cpolar容器打开浏览器访问威联通ip+:9200,就能访问到cpolar web UI管理界面,使用cpolar邮箱账号登录即可2. 内网穿透2.1 创建隧道cpolar安装成功之后,我们可以创建隧道映射内网端口,会获得相应的公网地址,实现在外也可以远程登录访问威联通管理界面。威联通管理界面端口默认是8080,因此我们来创建一个http协议的隧道,映射威联通8080端口。点击左侧仪表盘的隧道管理——创建隧道:隧道名称:可自定义,注意不要与已有的隧道名称重复协议:http本地地址:8080域名类型:免费选择随机域名地区:默认选择即可点击创建隧道创建成功后,点击左侧的状态——在线隧道列表,可以看到,刚刚创建的隧道已经有生成了相应的公网地址,将其复制下来2.2 测试公网远程访问复制公网地址打开浏览器访问,出现登录界面表示成功3. 配置固定二级子域名由于以上创建的是随机地址,地址在24小时内变化,为了更好的使用体验,可以固定http公网地址,为其配置一个固定二级子域名,该地址不会随机变化,同时提高带宽。需要注意,配置固定二级子域名需要将cpolar升级到基础版套餐或以上。3.1 保留二级子域名打开cpolar官网(www.cpolar.com),登录进入后台,点击左侧的预留,找到保留二级子域名,我们来为远程访问威联通NAS保留一个二级子域名地区:选择China二级域名:可自定义描述:即备注,可以自定义填写点击保留cpolar二级子域名保留后,我们将其复制下来3.2 配置二级子域名回到威联通cpolar的web ui 管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到我们刚刚创建的远程隧道,点击右侧的编辑修改隧道信息,将保留成功的二级子域名配置到隧道中域名类型:选择二级子域名Sub Domain:填写保留成功的二级子域名,本例为wlt点击更新隧道更新成功后,然后查看在线隧道列表,此时可以看到公网地址变成了我们设置的二级子域名地址,然后复制地址4. 使用固定二级子域名远程访问在浏览器上访问固定二级子域名,出现登录界面表示成功,接下来即可远程管理我们的威联通服务了,现在该公网地址也不会随机变化。
网络货运平台的服务对象有哪些?网络货运平台源码技术架构?
网络货运平台的服务对象有哪些?
中国物流行业中的货主、司机在空运、高库存、低周转等方面浪费了大量的能耗,网络货运平台为货主找车(托运人)、司机找货(实际承运人)提供了高效便捷的解决方案,也是平台服务的主要对象。如果没有其中任何一方,整个运输链将不存在。
1、托运人网络货运经营者利用网络平台为托运人提供真实、有效的货源及运力信息,并且对货源及车源信息进行管理。托运人和实际承运人签订运输合同,进行货源、运力资源的有效整合,实现信息精准配置,生成电子运单,完成线上交易。
2、实际承运人网络货运平台在线组织运力运输,用第三方平台对运输地点、轨迹、状态进行动态监控,对装货、卸货、结算等进行有效管控的功能和物流信息全流程跟踪、记录、存储、分析能力。实时采集实际承运车辆运输轨迹的动态信息,并在货物起运和确认送达时,实时采集和上传驾驶员地理位置信息,实现交易、运输、结算等各环节全过程透明化动态管理,在线实时结算运单,提升运输效率和服务质量。
3、平台方网络货运平台利用大数据、车辆北斗定位等信息技术手段,制定经营服务标准体系,提高网络货运服务品质,规范实际承运人运输行为,真实记录物流各环节交易数据,确保运输单证真实可靠,信息安全管理水平显著提升。
Java网络货运平台源码,自主研发,有演示
技术架构:spring boot、mybatis、redis、vue、element-ui开发语言:java、vue、uniapp开发工具:idea、vscode、hbuilder+前端框架:vue后端框架:spring boot数 据 库:mysql移 动 端:uniapp混合开发+原生插件