软件 – web百事通 https://www.askme-121.pw web互联网之家 Sat, 20 Apr 2024 05:44:01 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.5.3 https://www.askme-121.pw/wp-content/uploads/2023/12/cropped-05ee702f-4b38-40f3-915f-c8fc68b10a91-32x32.png 软件 – web百事通 https://www.askme-121.pw 32 32 PHP-FPM是如何工作的? https://www.askme-121.pw/php-fpm/ https://www.askme-121.pw/php-fpm/#respond Sat, 20 Apr 2024 05:43:41 +0000 https://www.askme-121.pw/?p=518 首先了解一下几个知识点。
CGI:是 Web Server 与 Web Application 之间数据交换的一种协议。
FastCGI:同 CGI,是一种通信协议,但比 CGI 在效率上做了一些优化。
PHP-CGI:是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序。
PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理。

CGI工作流程

1.如果客户端请求的是 index.html,那么Web Server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。

2.当Web Server收到 index.php 这个请求后,会启动对应的 CGI 程序,这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规定的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。

FastCGI工作流程

1.如果客户端请求的是 index.html,那么Web Server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。

2.当Web Server收到 index.php 这个请求后,FastCGI程序(FastCGI在启动时就初始化执行执行环境,每个CGI进程池各个CGI进程共享执行环境)在CGI进程池中选择一个CGI进程处理请求,再以CGI规定的格式返回处理后的结果,继续等待下一个请求。

PHP-FPM基本实现

PHP-FPM的实现就是创建一个master进程,在master进程中创建worker pool并让其监听socket,然后fork出多个子进程(work)。

这些子进程各自accept请求,子进程的处理非常简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理然后再返回。

在这期间是不会接收其它请求的,也就是说PHP-FPM的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求

PHP-FPM的master进程与worker进程之间不会直接进行通信,master通过共享内存获取worker进程的信息。

比如worker进程当前状态、已处理请求数等,当master进程要杀掉一个worker进程时则通过发送信号的方式通知worker进程。

PHP-FPM可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程

Worker工作流程

1.等待请求:worker进程阻塞在fcgi_accept_request()等待请求;

2.解析请求:fastcgi请求到达后被worker接收,然后开始接收并解析请求数据,直到request数据完全到达;

3.请求初始化:执行php_request_startup(),此阶段会调用每个扩展的:PHP_RINIT_FUNCTION()

4.编译、执行:由php_execute_script()完成PHP脚本的编译、执行;

5.关闭请求:请求完成后执行php_request_shutdown(),此阶段会调用每个扩展的:PHP_RSHUTDOWN_FUNCTION(),然后进入步骤(1)等待下一个请求。

Master进程管理

1.static: 这种方式比较简单,在启动时master按照pm.max_children配置fork出相应数量的worker进程,即worker进程数是固定不变的

2.dynamic: 动态进程管理,首先在fpm启动时按照pm.start_servers初始化一定数量的worker

运行期间如果master发现空闲worker数低于pm.min_spare_servers配置数(表示请求比较多,worker处理不过来了)则会fork worker进程,但总的worker数不能超过pm.max_children

如果master发现空闲worker数超过了pm.max_spare_servers(表示闲着的worker太多了)则会杀掉一些worker,避免占用过多资源,master通过这4个值来控制worker

3.ondemand: 这种方式一般很少用,在启动时不分配worker进程,等到有请求了后再通知master进程fork worker进程。

总的worker数不超过pm.max_children,处理完成后worker进程不会立即退出,当空闲时间超过pm.process_idle_timeout后再退出

PHP-FPM事件管理器

1.sp管道可读事件:这个事件是master用于处理信号的

2.fpm_pctl_perform_idle_server_maintenance_heartbeat():这是进程管理实现的主要事件。

master启动了一个定时器,每隔1s触发一次,主要用于dynamicondemand模式下的worker管理,master会定时检查各worker poolworker进程数,通过此定时器实现worker数量的控制

3.fpm_pctl_heartbeat():这个事件是用于限制worker处理单个请求最大耗时的,php-fpm.conf中有一个request_terminate_timeout的配置项,如果worker处理一个请求的总时长超过了这个值那么master将会向此worker进程发送kill -TERM信号杀掉worker进程,此配置单位为秒,默认值为0表示关闭此机制

4.fpm_pctl_on_socket_accept():ondemand模式下master监听的新请求到达的事件,因为ondemand模式下fpm启动时是不会预创建worker的,有请求时才会生成子进程,所以请求到达时需要通知master进程

]]>
https://www.askme-121.pw/php-fpm/feed/ 0
PHP开发api签名验证 https://www.askme-121.pw/php-api-sign/ https://www.askme-121.pw/php-api-sign/#respond Sat, 20 Apr 2024 04:17:40 +0000 https://www.askme-121.pw/?p=517 开发过程中,我们经常会与接口打交道,有的时候是调取别人网站的接口,有的时候是为他人提供自己网站的接口,但是在这调取的过程中都离不开签名验证。

我们在设计签名验证的时候,请注意要满足以下几点:

  • 可变性:每次的签名必须是不一样的。
  • 时效性:每次请求的时效,过期作废等。
  • 唯一性:每次的签名是唯一的。
  • 完整性:能够对传入数据进行验证,防止篡改。

这里介绍一种方式,是目前国内互联网公司常用的一种方式,其中淘宝的支付宝支付接口、淘宝开放平台接口、腾讯开放平台等应用的一种方式。

一、签名参数sign生成的方法

第1步: 将所有参数(注意是所有参数),除去sign本身,以及值是空的参数,按参数名字母升序排序。

第2步: 然后把排序后的参数按参数1值1参数2值2…参数n值n(这里的参数和值必须是传输参数的原始值,不能是经过处理的,如不能将”转成”后再拼接)的方式拼接成一个字符串。

第3步: 把分配给接入方的验证密钥key拼接在第2步得到的字符串前面。

第2步: 在上一步得到的字符串前面加上验证密钥key(这里的密钥key是接口提供方分配给接口接入方的),然后计算md5值,得到32位字符串,然后转成大写.

第4步: 计算第3步字符串的md5值(32位),然后转成大写,得到的字符串作为sign的值。

举例:

假设传输的数据是/interface.php?sign=sign_value&p2=v2& p1=v1&method=cancel&p3=&pn=vn(实际情况最好是通过post方式发送),其中sign参数对应的sign_value就是签名的值。

第一步,拼接字符串,首先去除sign参数本身,然后去除值是空的参数p3,剩下p2=v2&p1=v1&method=cancel& amp;pn=vn,然后按参数名字符升序排序,method=cancel&p1=v1&p2=v2&pn=vn.

第二步,然后做参数名和值的拼接,最后得到methodcancelp1v1p2v2pnvn
第三步,在上面拼接得到的字符串前加上验证密钥key,我们假设是abc,得到新的字符串abcmethodcancelp1v1p2v2pnvn

第四步,然后将这个字符串进行md5计算,假设得到的是abcdef,然后转为大写,得到ABCDEF这个值即为sign签名值。

注意,计算md5之前请确保接口与接入方的字符串编码一致,如统一使用utf-8编码或者GBK编码,如果编码方式不一致则计算出来的签名会校验失败。

二、签名验证方法

根据前面描述的签名参数sign生成的方法规则,计算得到参数的签名值,和参数中通知过来的sign对应的参数值进行对比,如果是一致的,那么就校验通过,如果不一致,说明参数被修改过。

三、下面直接看代码

<?php

// 设置一个公钥(key)和私钥(secret),公钥用于区分用户,私钥加密数据,不能公开
$key = "c4ca4238a0b923820dcc509a6f75849b";
$secret = "28c8edde3d61a0411511d3b1866f0636";

// 待发送的数据包
$data = array(
    'username' => 'abc@qq.com',
    'sex' => '1',
    'age' => '16',
    'addr' => 'guangzhou',
    'key' => $key,
    'timestamp' => time(),
);

// 获取sign
function getSign($secret, $data) {
    // 对数组的值按key排序
    ksort($data);
    // 生成url的形式
    $params = http_build_query($data);
    // 生成sign
    $sign = md5($params . $secret);
    return $sign;
}

// 发送的数据加上sign
$data['sign'] = getSign($secret, $data);

/**
 * 后台验证sign是否合法
 * @param  [type] $secret [description]
 * @param  [type] $data   [description]
 * @return [type]         [description]
 */
function verifySign($secret, $data) {
    // 验证参数中是否有签名
    if (!isset($data['sign']) || !$data['sign']) {
        echo '发送的数据签名不存在';
        die();
    }
    if (!isset($data['timestamp']) || !$data['timestamp']) {
        echo '发送的数据参数不合法';
        die();
    }
    // 验证请求, 10分钟失效
    if (time() - $data['timestamp'] > 600) {
        echo '验证失效, 请重新发送请求';
        die();
    }
    $sign = $data['sign'];
    unset($data['sign']);
    ksort($data);
    $params = http_build_query($data);
    // $secret是通过key在api的数据库中查询得到
    $sign2 = md5($params . $secret);
    if ($sign == $sign2) {
        die('验证通过');
    } else {
        die('请求不合法');
    }
}
]]>
https://www.askme-121.pw/php-api-sign/feed/ 0
用PHP实现SSO单点登录 https://www.askme-121.pw/php-sso/ https://www.askme-121.pw/php-sso/#respond Sat, 20 Apr 2024 02:33:31 +0000 https://www.askme-121.pw/?p=516 SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一,下面我们来看看吧。

简单讲一下 SSO 单点登录系统的接入的原理,前提是系统本身有完善的用户认证功能,即基本的用户登录功能,那实现起来就很方便了。

SSO 登录请求接口往往是接口加上一个回调地址,访问这个地址会跳转到回调地址并带上一个 ticket 参数,拿着这个 ticket 参数再请求接口可以获取到用户信息,如果存在用户则自动登录,不存在就新增用户并登录。

要使用 PHP 实现单点登录(Single Sign-On,SSO),可以借助一些标准和协议,如OAuth 2.0、OpenID Connect 或 SAML(Security Assertion Markup Language)。

下面是一个使用 OAuth 2.0 实现 SSO 的简单示例:

创建认证服务器(Authorization Server):该服务器负责处理用户认证和发放访问令牌。

// authorization_server.php

// 用户登录验证逻辑,验证用户名和密码是否正确
function validateUser($username, $password) {
    // 进行用户验证逻辑,返回验证结果
    // ...
}

// 发放访问令牌给授权成功的用户
function issueAccessToken($username) {
    // 生成访问令牌并返回
    // ...
}

// 处理用户登录请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];

    // 验证用户名和密码
    if (validateUser($username, $password)) {
        // 发放访问令牌
        $accessToken = issueAccessToken($username);

        // 将访问令牌返回给客户端
        echo json_encode(['access_token' => $accessToken]);
        exit;
    } else {
        // 用户认证失败
        http_response_code(401);
        exit;
    }
}

创建客户端应用(Client Application):该应用将使用认证服务器发放的访问令牌来验证用户身份。

// client_application.php

// 验证访问令牌是否有效
function validateAccessToken($accessToken) {
    // 进行访问令牌验证逻辑,返回验证结果
    // ...
}

// 处理用户登录请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $accessToken = $_POST['access_token'];

    // 验证访问令牌
    if (validateAccessToken($accessToken)) {
        // 用户已验证通过,执行单点登录逻辑
        // ...
        echo 'Login successful';
        exit;
    } else {
        // 访问令牌验证失败
        http_response_code(401);
        exit;
    }
}

用户登录和单点登录流程:

  • 用户访问 Client Application,检查用户是否已登录。
  • 如果用户未登录,则跳转到认证服务器的登录页面。
  • 用户在认证服务器上提供用户名和密码进行登录认证。
  • 认证服务器验证用户名和密码,并发放访问令牌。
  • 用户被重定向回 Client Application,并将访问令牌发送到 Client Application。
  • Client Application 验证访问令牌的有效性,确认用户已成功登录,执行相应的单点登录逻辑。

请注意,上述示例只是一个简单的演示,实际的 SSO 实现可能更复杂,涉及到会话管理、安全性等方面的考虑。您可以根据具体的需求和使用的协议,进一步完善和扩展这个基本示例。

]]>
https://www.askme-121.pw/php-sso/feed/ 0
app开发框架有哪些 https://www.askme-121.pw/app/ https://www.askme-121.pw/app/#respond Mon, 18 Mar 2024 06:59:01 +0000 https://www.askme-121.pw/?p=514 APP开发框架是指在移动应用程序开发中,为了提高开发效率、降低开发成本和提高代码质量而使用的软件架构体系。下面将介绍一些常用的APP开发框架。

1. React Native

React Native是由Facebook开发的一种基于React的开源框架。它允许开发者使用JavaScript和React来构建原生移动应用程序。React Native可以让开发者快速构建高性能的移动应用程序,并且可以在不同平台上使用相同的代码。React Native通过使用原生组件来实现高性能,从而提高了应用程序的性能和响应能力。

2. Flutter

Flutter是Google开发的一种开源框架,它可以用于构建高性能的移动应用程序。Flutter使用Dart语言来编写代码,并且可以在iOS和Android平台上使用相同的代码。Flutter具有快速的开发周期、高质量的用户界面和出色的性能。Flutter使用自己的渲染引擎来实现高性能,从而提高了应用程序的性能和响应能力。

3. Ionic

Ionic是一个基于HTML5和CSS3的开源框架,可以用于构建跨平台的移动应用程序。Ionic使用AngularJS来构建应用程序,并且可以在不同平台上使用相同的代码。Ionic提供了大量的UI组件和工具来帮助开发者更快地构建应用程序。Ionic使用自己的渲染引擎来实现高性能,从而提高了应用程序的性能和响应能力。

4. Xamarin

Xamarin是由Microsoft开发的一种跨平台移动应用程序开发框架。它允许开发者使用C#和.NET框架来构建原生移动应用程序。Xamarin可以在iOS、Android和Windows Phone平台上使用相同的代码。Xamarin使用自己的渲染引擎来实现高性能,从而提高了应用程序的性能和响应能力。

5. PhoneGap

PhoneGap是一个基于HTML、CSS和JavaScript的开源框架,可以用于构建跨平台的移动应用程序。PhoneGap使用Web技术来构建应用程序,并且可以在不同平台上使用相同的代码。PhoneGap使用自己的渲染引擎来实现高性能,从而提高了应用程序的性能和响应能力。

以上是一些常用的APP开发框架,每个框架都有自己的特点和适用范围。选择一个适合自己的框架可以提高开发效率和代码质量,从而更快地构建高性能的移动应用程序。

]]>
https://www.askme-121.pw/app/feed/ 0
静态化API是什么?用Swoole如何去实现呢? https://www.askme-121.pw/static-api/ https://www.askme-121.pw/static-api/#respond Mon, 29 Jan 2024 06:25:32 +0000 https://www.askme-121.pw/?p=503 什么是静态化API?
静态化API可以理解成把一些接口的数据存储在服务器本地。常用的是存成json文件,也可以是放在swoole的table中,总之是用户不从数据库直接读取数据,而是从本地加载的方式来大幅提高性能,因为很多系统的性能瓶颈是在数据库的位置。

解决方案
方案1 easySwoole + crontab
方案2 easySwoole定时器
方案3 Swoole table
方案4 Redis

实现
这里做的分页的场景,不包含分页的源码,只从拿到了分页的数据看定时生成json和获取json的部分

原始的方法 – 读取mysql

这是原始的方法,每个用户访问都会去数据库里面读取一次,每一次分页也会访问数据库,会造成大量的资源开销。

public function lists0(){
    $condition = [];
    if(!empty($this->params['cat_id'])){
        $condition['cat_id'] = intval($this->params['cat_id']);
    }

    try {
        $videoModel = new VideoModel();
        $data = $videoModel->getVideoData($condition, $this->params['page'], $this->params['size']);
    } catch (\Exception $e) {
        //$e->getMessage();
        return $this->writeJson(Status::CODE_BAD_REQUEST,"服务异常");
    }

    if(!empty($data['lists'])){
        foreach ($data['lists'] as &$list){
            $list['create_time'] = date("Ymd H:i:s",$list['create_time']);
            $list['video_duration'] = gmstrftime("%H:%M:%S",$list["video_duration"]);
        }
    }

    return $this->writeJson(Status::CODE_OK,'OK',$data);
}

知识点:

1 获取以秒为单位的时间长度使用gmstrftime(“%H:%M:%S”,$list[“video_duration”])
2 在做模型的时候写一个基类,把连接数据库的工作放在这个基类的构造方法当中,这样每次实例化的时候就自动连接了,提高代码的复用性

<?php


namespace App\Model;

use EasySwoole\Core\Component\Di;

class Base
{
    public $db = "";

    public function __construct()
    {
        if(empty($this->tableName)){
            throw new \Exception("table error");
        }
        $db = Di::getInstance()->get("MYSQL");
        if($db instanceof \MysqliDb){
            $this->db = $db;
        }else{
            throw new \Exception("db error");
        }
    }

    public function add($data){
        if(empty($data) || !is_array($data)){
            return false;
        }
        return $this->db->insert($this->tableName,$data);
    }

}

方案一、方案二 使用easySwoole定时器以及contab的生成静态化API

这是直接读取静态化API的方法,其实就是读文件然后返回给前端

/**
 * 第二套方法 - 直接读取静态化json数据
 * @return bool
 */
public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoFile = EASYSWOOLE_ROOT."/webroot/video/json/".$catId.".json";
    $videoData = is_file($videoFile) ? file_get_contents($videoFile) : [];
    var_dump($videoData);
    $videoData = !empty($videoData) ? json_decode($videoData,true) : [];

    $count = count($videoData);
    // var_dump($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

这里的getPagingData使用了array_slice来切割数组来做分页。

/**
 * 获取分页信息
 * @param $count
 * @param $data
 * @return array
 */
public function getPagingData($count, $data){
    $totalPage = ceil($count / $this->params['size']);
    $data = $data ?? [];
    $data = array_slice($data, $this->params['from'], $this->params['size']);
    return [
        'total_page' => $totalPage,
        'page_size' => $this->params['page'],
        'count' => intval($count),
        'list' => $data
    ];

}

定时生成Json文件代码 – 核心部分 全局事件easySwooleEvent->mainServiceCreate中执行,这样easySwoole启动了之后就会执行,这里使用的原生的crontab,只能够精确到分

$cacheVideoObj = new VideoCache();
// 使用cronTab处理定时任务
// 这里是闭包 要use $cacheVideoObj之后才能获取,实例化不放在function中是为了防止每次都实例化浪费资源
CronTab::getInstance()
    ->addRule("test_bing_crontab", '*/1 * * * *', function () use($cacheVideoObj) {
        $cacheVideoObj->setIndexVideo();
    });

也可以使用easySwoole的定时器来实现也是放在mainServiceCreate中执行,这里的代码一定要注意要注册一个onWorkerStart。
然后指定一个进程去执行这个定时任务注意一下这里闭包里面又有一个闭包,外面的变量要use两次。
一定要注意easwoole定时器的使用,这里的坑比较多一定要注意,优势是swoole的定时器可以到毫秒级而contab只能到分级

// easySwoole自带定时器
$register->add(EventRegister::onWorkerStart,function (\swoole_server $server,$workerId)use($cacheVideoObj){
    //让第一个进程去执行,否则每个进程都会执行
    if($workerId == 0){
        Timer::loop(1000*2,function ()use ($cacheVideoObj){
            $cacheVideoObj->setIndexVideo();
        });
    }
});

方案三 Swoole table的解决方案

swoole_table一个基于共享内存和锁实现的超高性能,并发数据结构。用于解决多进程/多线程数据共享和同步加锁问题。性能十分强悍。使用起来有一点像是缓存。这里不再是生成一个json文件去读取了,读取table中数据给前端的方法,注意要use cache这个类,这里直接使用get就可以根据key获取到table中的数据,注意这个是一个单例模式,因此需要getInstance

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoFile = EASYSWOOLE_ROOT."/webroot/video/json/".$catId.".json";

    //  第三套方案 table
    $videoData = Cache::getInstance()->get("index_video_data_cat_id".$catId);
    $videoData = !empty($videoData) ? $videoData : [];

    $count = count($videoData);;
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

设置直接set(key,data)就可以了,注意这里的代码不是很严谨,比如要判断一下是不是设置成功了,没有设置成功发短信之类的,也要处理一下空值的场景,这里只是做一个演示。

<?php

namespace App\Lib\Cache;

use App\Model\Video as VideoModel;
use EasySwoole\Config;
use EasySwoole\Core\Component\Cache\Cache;

class Video
{
    public function setIndexVideo()
    {
        $catIds = array_keys(Config::getInstance()->getConf("category"));
        array_unshift($catIds, 0);

        $modelObj = new VideoModel();

        foreach ($catIds as $catId) {
            $condition = [];
            if (!empty($catId)) {
                $condition['cat_id'] = $catId;
            }

            try {
                $data = $modelObj->getVideoCacheData($condition);
            } catch (\Exception $e) {
                // 短信报警
                $data = [];
            }
            if (empty($data)) {

            }
            foreach ($data as &$list) {
                $list['create_time'] = date("Ymd H:i:s", $list["create_time"]);
                $list["video_duration"] = gmstrftime("%H:%M:%s", $list["video_duration"]);
            }
            // 由于table存在内存所以重启服务器时数据会丢失,要将配置中的PERSISTENT_TIME设置为1进行落盘操作
            Cache::getInstance()->set("index_video_data_cat_id".$catId, $data);
        }
    }
}

注意,一定要去config里面开启

'PERSISTENT_TIME'=>1//如果需要定时数据落地,请设置对应的时间周期,单位为秒

否则会在重启服务的时候没有数据,因为每次启动的时候swoole table会清空,要等到定时去set table的时候才会有数据,因此要开启数据落盘,这样会生成两个文件:

每次启动的时候由于还没有执行定时任务,就会先读取这两个落盘的文件中的数据,防止服务启动时等待table生成造成业务中断。

方案四 Redis

用redis来做数据缓存,每次从缓存里面度读先重写一下set方法,更加严谨一点

/**
 * 重写set方法 处理一下失效时间以及数组转json
 * @param $key
 * @param $value
 * @param $time
 * @return bool|string
 */
public function set($key, $value, $time){
    if(empty($key)){
        return '';
    }
    if(is_array($value)){
        $value = json_encode($value);
    }
    if(!$time){
        return $this->redis->set($key,$value);
    }
    return $this->redis->setex($key, $time, $value);
}

使用起来很简单啦,在之前的代码中

Di::getInstance()->get("REDIS")->set("index_video_data_cat_id".$catId, $data);

然后取出的数据的部分

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    //redis
    $videoData = Di::getInstance()->get("REDIS")->get("index_video_data_cat_id".$catId);
    $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
    $count = count($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}

高度封装

只需要在配置文件中进行配置即可选择相应方法。设置静态化API和获取静态化API的方法

<?php


namespace App\Lib\Cache;

use App\Model\Video as VideoModel;
use EasySwoole\Config;
use EasySwoole\Core\Component\Cache\Cache;
use EasySwoole\Core\Component\Di;
use EasySwoole\Core\Http\Message\Status;

class Video
{
    /**
     * 设置静态API的方法
     * @throws \Exception
     */
    public function setIndexVideo()
    {
        $catIds = array_keys(Config::getInstance()->getConf("category"));
        array_unshift($catIds, 0);

        // 获取配置
        try {
            $cacheType = Config::getInstance()->getConf("base.indexCacheType");
        } catch (\Exception $e) {
            return $this->writeJson(Status::CODE_BAD_REQUEST,"请求失败");
        }

        $modelObj = new VideoModel();

        foreach ($catIds as $catId) {
            $condition = [];
            if (!empty($catId)) {
                $condition['cat_id'] = $catId;
            }

            try {
                $data = $modelObj->getVideoCacheData($condition);
            } catch (\Exception $e) {
                // 短信报警
                $data = [];
            }
            if (empty($data)) {

            }

            foreach ($data as &$list) {
                $list['create_time'] = date("Ymd H:i:s", $list["create_time"]);
                $list["video_duration"] = gmstrftime("%H:%M:%s", $list["video_duration"]);
            }

            switch ($cacheType) {
                case 'file':
                    $res = file_put_contents($this->getVideoCatIdFile($catId), json_encode($data));
                    break;
                case 'table':
                    $res = Cache::getInstance()->set($this->getCatKey($catId), $data);
                    break;
                case 'redis':
                    $res = Di::getInstance()->get("REDIS")->set($this->getCatKey($catId), $data);
                    break;
                default:
                    throw new \Exception("请求不合法");
                    break;
            }

            if(empty($res)){
            //    记录日志
            //    报警

            }

        }

    }

    /**
     * 获取数据的方法
     * @param $catId
     * @return array|bool|mixed|null|string
     * @throws \Exception
     */
    public function getCache($catId){
        $cacheType = Config::getInstance()->getConf("base.indexCacheType");
        switch ($cacheType){
            case 'file':
                $videoFile = $this->getVideoCatIdFile($catId);
                $videoData = is_file($videoFile) ? file_get_contents($videoFile) : [];
                $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
                break;
            case 'table':
                $videoData = Cache::getInstance()->get($this->getCatKey($catId));
                $videoData = !empty($videoData) ? $videoData : [];
                break;
            case 'redis':
                $videoData = Di::getInstance()->get("REDIS")->get($this->getCatKey($catId));
                $videoData = !empty($videoData) ? json_decode($videoData,true) : [];
                break;
            default:
                throw new \Exception("请求不合法");
                break;
        }
        return $videoData;
    }

    public function getVideoCatIdFile($catId = 0){
        return EASYSWOOLE_ROOT . "/webroot/video/json/" . $catId . ".json";
    }


    public function getCatKey($catId = 0){
        return "index_video_data_cat_id".$catId;
    }
}

只需修改配置文件

<?php

return [
    "indexCacheType" => "redis" // redis file table
];

控制器获取数据给前端

public function lists(){
    $catId = !empty($this->params['cat_id']) ? intval($this->params['cat_id']) : 0;
    $videoData = (new VideoCache())->getCache($catId);
    $count = count($videoData);
    return $this->writeJson(Status::CODE_OK,'OK',$this->getPagingData($count,$videoData));
}
]]>
https://www.askme-121.pw/static-api/feed/ 0
异步定时多任务消息推送,用php怎么做 ? https://www.askme-121.pw/message-push/ https://www.askme-121.pw/message-push/#respond Sun, 21 Jan 2024 07:52:01 +0000 https://www.askme-121.pw/?p=501 要实现异步定时多任务消息推送,可以使用多种技术来实现,如RedisRabbitMQBeanstalkd等消息队列服务。

在设计数据表时,可以考虑创建一个 tasks 表,用于存储所有需要推送的任务信息。该表可以包含以下字段:
id:任务ID,自增长整数类型;
name:任务名称,用于描述该任务的作用;
type:任务类型,用于区分不同类型的任务;
data:任务数据,可以存储该任务需要用到的数据信息,可以是序列化后的字符串或JSON格式的数据;
status:任务状态,用于标识该任务的状态,如待处理、已处理等;
created_at:任务创建时间,用于记录该任务的创建时间;
updated_at:任务更新时间,用于记录该任务的最后更新时间;

在添加任务时,可以向 tasks 表中插入一条记录,设置好任务的名称、类型、数据等信息。然后,在需要推送任务时,可以通过查询 tasks 表,找到需要推送的任务信息,并将其加入到消息队列中,等待消息队列服务处理。

在处理任务时,可以使用类似下面的代码:

$queue = new RedisQueue('my_queue', $redis);

// 处理队列中的任务
while ($job = $queue->pop()) {
    $task = unserialize($job->payload);

    // 执行任务处理操作
    handleTask($task);

    // 标记任务为已处理
    $task->status = Task::STATUS_COMPLETED;
    $task->save();
}

其中,RedisQueue 是一个自定义的 Redis 队列类,用于将任务加入到 Redis 队列中。handleTask 函数则是用于处理任务的函数,根据实际需求进行编写即可。

可以使用 cron 或类似的定时任务工具来定时检查 tasks 表,并将需要推送的任务加入到消息队列中。具体的实现方式可以根据实际情况选择不同的技术方案和工具。

需要注意的是,在异步任务处理过程中,需要考虑异常情况的处理,例如任务执行失败、队列服务异常等情况。因此,在编写异步任务时,需要进行充分的异常处理和错误日志记录。

下面介绍几个具体的例子:

1、 使用 Laravel 框架提供的队列和计划任务功能实现消息推送:

// 定义消息推送任务类
class PushMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $message;

    public function __construct($message)
    {
        $this->message = $message;
    }

    public function handle()
    {
        // 处理推送消息的逻辑
    }
}

// 在计划任务中添加消息推送任务
$schedule->call(function () {
    PushMessage::dispatch('hello')->delay(now()->addMinutes(10));
})->everyMinute();

在上面的示例代码中,我们使用 Laravel 框架提供的队列和计划任务功能,定义了一个 PushMessage 消息推送任务类,并在计划任务中添加了一个延迟 10 分钟执行的消息推送任务。

2、 使用第三方的消息队列服务实现消息推送:

// 使用 beanstalkd 消息队列
use Pheanstalk\Pheanstalk;

// 连接到 beanstalkd 服务
$pheanstalk = new Pheanstalk('127.0.0.1');

// 向队列添加消息
$pheanstalk->useTube('push_message')->put('hello', Pheanstalk::DEFAULT_PRIORITY, 60);

// 处理消息队列中的消息
while (true) {
    $job = $pheanstalk->watch('push_message')->ignore('default')->reserve();
    $message = $job->getData();
    // 处理推送消息的逻辑
    $pheanstalk->delete($job);
}

在上面的示例代码中,我们使用了第三方的消息队列服务 beanstalkd,并实现了向队列添加消息和处理消息队列中的消息的逻辑。

3、 使用 Swoole 扩展实现异步定时任务和消息推送:

// 创建 Swoole 定时器和 HTTP 服务器
$timer = swoole_timer_tick(1000, function () {
    // 处理定时任务的逻辑
});
$http = new swoole_http_server('127.0.0.1', 9501);
$http->on('request', function ($request, $response) use ($timer) {
    // 处理 HTTP 请求的逻辑
    $response->end('hello');

    // 添加异步任务
    swoole_timer_after(10000, function () use ($response) {
        $response->end('world');
    });

    // 关闭定时器
    swoole_timer_clear($timer);
});
$http->start();

在上面的示例代码中,我们使用 Swoole 扩展创建了一个定时器和 HTTP 服务器,并在定时器回调函数中处理定时任务的逻辑,在 HTTP 请求回调函数中处理 HTTP 请求的逻辑,并添加了一个 10 秒后执行的异步任务。

]]>
https://www.askme-121.pw/message-push/feed/ 0
php操作ElasticSearch示例 https://www.askme-121.pw/php-elasticsearch/ https://www.askme-121.pw/php-elasticsearch/#respond Sat, 20 Jan 2024 07:08:11 +0000 https://www.askme-121.pw/?p=500 一、安装

通过composer安装

composer require 'elasticsearch/elasticsearch'

二、使用

创建ES类

<?php

require 'vendor/autoload.php';

//如果未设置密码
$es = \Elasticsearch\ClientBuilder::create()->setHosts(['xxx.xxx.xxx.xxx'])->build();

//如果es设置了密码
$es = \Elasticsearch\ClientBuilder::create()->setHosts(['http://username:password@xxx.xxx.xxx.xxx:9200'])->build();

三、新建ES数据库

index 对应关系型数据(以下简称MySQL)里面的数据库,而不是对应MySQL里面的索引

<?php
$params = [
    'index' => 'autofelix_db', #index的名字不能是大写和下划线开头
    'body' => [
        'settings' => [
            'number_of_shards' => 5,
            'number_of_replicas' => 0
        ]
    ]
];
$es->indices()->create($params);

四、创建表

  • 在MySQL里面,光有了数据库还不行,还需要建立表,ES也是一样的
  • ES中的type对应MySQL里面的表
  • ES6以前,一个index有多个type,就像MySQL中一个数据库有多个表一样
  • 但是ES6以后,每个index只允许一个type
  • 在定义字段的时候,可以看出每个字段可以定义单独的类型
  • 在first_name中还自定义了 分词器 ik,这是个插件,是需要单独安装的

<?php
$params = [
    'index' => 'autofelix_db',
    'type' => 'autofelix_table',
    'body' => [
        'mytype' => [
            '_source' => [
                'enabled' => true
            ],
            'properties' => [
                'id' => [
                    'type' => 'integer'
                ],
                'first_name' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word'
                ],
                'last_name' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word'
                ],
                'age' => [
                    'type' => 'integer'
                ]
            ]
        ]
    ]
];
$es->indices()->putMapping($params);

五、插入数据

  • 现在数据库和表都有了,可以往里面插入数据了
  • 在ES里面的数据叫文档
  • 可以多插入一些数据,等会可以模拟搜索功能

<?php
$params = [
    'index' => 'autofelix_db',
    'type' => 'autofelix_table',
    //'id' => 1, #可以手动指定id,也可以不指定随机生成
    'body' => [
        'first_name' => '飞',
        'last_name' => '兔',
        'age' => 26
    ]
];
$es->index($params);

六、 查询所有数据

<?php
$data = $es->search();
 
var_dump($data);

七、查询单条数据

  • 如果你在插入数据的时候指定了id,就可以查询的时候加上id
  • 如果你在插入的时候未指定id,系统将会自动生成id,你可以通过查询所有数据后查看其id
<?php
$params = [
    'index' => 'autofelix_db',
    'type' => 'autofelix_table',
    'id' =>  //你插入数据时候的id
];
$data = $es->get($params);

八、搜索

ES精髓的地方就在于搜索

<?php
$params = [
    'index' => 'autofelix_db',
    'type' => 'autofelix_table',
    'body' => [
        'query' => [
            'constant_score' => [ //非评分模式执行
                'filter' => [ //过滤器,不会计算相关度,速度快
                    'term' => [ //精确查找,不支持多个条件
                        'first_name' => '飞'
                    ]
                ]
            ]
        ]
    ]
];
 
$data = $es->search($params);
var_dump($data);

九、测试代码

基于Laravel环境,包含删除数据库,删除文档等操作

<?php
use Elasticsearch\ClientBuilder;
use Faker\Generator as Faker;
 
/**
 * ES 的 php 实测代码
 */
class EsDemo
{
    private $EsClient = null;
    private $faker = null;
 
    /**
     * 为了简化测试,本测试默认只操作一个Index,一个Type
     */
    private $index = 'autofelix_db';
    private $type = 'autofelix_table';
 
    public function __construct(Faker $faker)
{
        /**
         * 实例化 ES 客户端
         */
        $this->EsClient = ClientBuilder::create()->setHosts(['xxx.xxx.xxx.xxx'])->build();
        /**
         * 这是一个数据生成库
         */
        $this->faker = $faker;
    }
 
    /**
     * 批量生成文档
     * @param $num
     */
    public function generateDoc($num = 100) {
        foreach (range(1,$num) as $item) {
            $this->putDoc([
                'first_name' => $this->faker->name,
                'last_name' => $this->faker->name,
                'age' => $this->faker->numberBetween(20,80)
            ]);
        }
    }
 
    /**
     * 删除一个文档
     * @param $id
     * @return array
     */
    public function delDoc($id) {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'id' =>$id
        ];
        return $this->EsClient->delete($params);
    }
 
    /**
     * 搜索文档,query是查询条件
     * @param array $query
     * @param int $from
     * @param int $size
     * @return array
     */
    public function search($query = [], $from = 0, $size = 5) {
//        $query = [
//            'query' => [
//                'bool' => [
//                    'must' => [
//                        'match' => [
//                            'first_name' => 'Cronin',
//                        ]
//                    ],
//                    'filter' => [
//                        'range' => [
//                            'age' => ['gt' => 76]
//                        ]
//                    ]
//                ]
//
//            ]
//        ];
        $params = [
            'index' => $this->index,
//            'index' => 'm*', #index 和 type 是可以模糊匹配的,甚至这两个参数都是可选的
            'type' => $this->type,
            '_source' => ['first_name','age'], // 请求指定的字段
            'body' => array_merge([
                'from' => $from,
                'size' => $size
            ],$query)
        ];
        return $this->EsClient->search($params);
    }
 
    /**
     * 一次获取多个文档
     * @param $ids
     * @return array
     */
    public function getDocs($ids) {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'body' => ['ids' => $ids]
        ];
        return $this->EsClient->mget($params);
    }
 
    /**
     * 获取单个文档
     * @param $id
     * @return array
     */
    public function getDoc($id) {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'id' =>$id
        ];
        return $this->EsClient->get($params);
    }
 
    /**
     * 更新一个文档
     * @param $id
     * @return array
     */
    public function updateDoc($id) {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'id' =>$id,
            'body' => [
                'doc' => [
                    'first_name' => '张',
                    'last_name' => '三',
                    'age' => 99
                ]
            ]
        ];
        return $this->EsClient->update($params);
    }
 
    /**
     * 添加一个文档到 Index 的Type中
     * @param array $body
     * @return void
     */
    public function putDoc($body = []) {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            // 'id' => 1, #可以手动指定id,也可以不指定随机生成
            'body' => $body
        ];
        $this->EsClient->index($params);
    }
 
    /**
     * 删除所有的 Index
     */
    public function delAllIndex() {
        $indexList = $this->esStatus()['indices'];
        foreach ($indexList as $item => $index) {
            $this->delIndex();
        }
    }
 
    /**
     * 获取 ES 的状态信息,包括index 列表
     * @return array
     */
    public function esStatus() {
        return $this->EsClient->indices()->stats();
    }
 
    /**
     * 创建一个索引 Index (非关系型数据库里面那个索引,而是关系型数据里面的数据库的意思)
     * @return void
     */
    public function createIndex() {
        $this->delIndex();
        $params = [
            'index' => $this->index,
            'body' => [
                'settings' => [
                    'number_of_shards' => 2,
                    'number_of_replicas' => 0
                ]
            ]
        ];
        $this->EsClient->indices()->create($params);
    }
 
    /**
     * 检查Index 是否存在
     * @return bool
     */
    public function checkIndexExists() {
        $params = [
            'index' => $this->index
        ];
        return $this->EsClient->indices()->exists($params);
    }
 
    /**
     * 删除一个Index
     * @return void
     */
    public function delIndex() {
        $params = [
            'index' => $this->index
        ];
        if ($this->checkIndexExists()) {
            $this->EsClient->indices()->delete($params);
        }
    }
 
    /**
     * 获取Index的文档模板信息
     * @return array
     */
    public function getMapping() {
        $params = [
            'index' => $this->index
        ];
        return $this->EsClient->indices()->getMapping($params);
    }
 
    /**
     * 创建文档模板
     * @return void
     */
    public function createMapping() {
        $this->createIndex();
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'body' => [
                $this->type => [
                    '_source' => [
                        'enabled' => true
                    ],
                    'properties' => [
                        'id' => [
                            'type' => 'integer'
                        ],
                        'first_name' => [
                            'type' => 'text',
                            'analyzer' => 'ik_max_word'
                        ],
                        'last_name' => [
                            'type' => 'text',
                            'analyzer' => 'ik_max_word'
                        ],
                        'age' => [
                            'type' => 'integer'
                        ]
                    ]
                ]
            ]
        ];
        $this->EsClient->indices()->putMapping($params);
        $this->generateDoc();
    }
}
]]>
https://www.askme-121.pw/php-elasticsearch/feed/ 0
PHP+Swoole实现微信小程序客服即时通信聊天功能 https://www.askme-121.pw/php-swoole/ https://www.askme-121.pw/php-swoole/#respond Sat, 20 Jan 2024 06:51:11 +0000 https://www.askme-121.pw/?p=495 一、PHP7安装Swoole扩展

PHP swoole 扩展下载地址

Github:https://github.com/swoole/swoole-src/tags

php官方扩展库:http://pecl.php.net/package/swoole

开源中国:http://git.oschina.net/swoole/swoole/tags

1、自定义安装

# 下载

wget https://pecl.php.net/get/swoole-4.3.3.tgz

# 解压

tar zxf swoole-4.3.3.tgz

# 编译安装扩展

# 进入目录

cd swoole-4.3.3 

# 执行phpize命令,产生出configure可执行文件
# 如果不知道phpize路径在哪里 可以使用which phpize查看相应路径

/usr/bin/phpize   

# 进行配置  如果不知道php-config路径在哪里 可以使用which php-config   查看相应路径

./configure --with-php-config=/usr/bin/php-config   

# 编译和安装

make && make install 

vi /etc/php.ini

复制如下代码

extension=swoole.so

放到你所打开或新建的文件中即可,无需重启任何服务

# 查看扩展是否安装成功

php -m|grep swoole

2、宝塔面板安装PHP swoole扩展

如果感觉上述安装较为复杂,可以使用宝塔面板实现一键安装

二、配置nginx反向代理

1、使用xshell连接远程阿里云服务器

2、使用命令(find / -name nginx.conf)查找nginx.conf所在的配置文件

find / -name nginx.conf

3、使用命令(vim /etc/nginx/nginx.conf)查找进入到vim编辑器

vim /etc/nginx/nginx.conf

查看到可以引入/etc/nginx/conf.d/下的配置文件信息

4、使用命令(cd /etc/nginx/conf.d/)进入到该路径下,并新建配置文件:study.lishuo.net.conf

5、配置nginx反向代理,实现访问study.lishuo.net域名转发端口号到127.0.0.1:9511也就是转发到webscoket运行的端口号

# 反向代理的规则 study 这个名字自己随便起
upstream study{
  server 127.0.0.1:9511;
}
server {
        listen       80;
        server_name  study.lishuo.net;
        error_page 404 /404.html;
        location = /404.html {
        }
        location / {
          index index.php index.html index.htm;
          if (!-e $request_filename) {
                rewrite  ^(.*)$  /index.php?s=/$1  last;
          }
        #wss配置
        client_max_body_size 100m;
        proxy_redirect off;
        proxy_set_header Host $host;# http请求的主机域名
        proxy_set_header X-Real-IP $remote_addr;# 远程真实IP地址
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;#反向代理之后转发之前的IP地址
        proxy_read_timeout 604800s;#websocket心跳时间,默认是60s
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass http://study;
       }
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
         #添加下列信息,配置Nginx通过fastcgi方式处理您的PHP请求。
        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9001;   #Nginx通过本机的9000端口将PHP请求转发给PHP-FPM进行处理。
            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include fastcgi_params;   #Nginx调用fastcgi接口处理PHP请求。
        }
    }

三、微信小程序socket合法域名配置

1、登录到微信开放平台https://mp.weixin.qq.com/

2、开发=>开发管理=>开发设置,完成合法域名设置

3、到此配置已经完成了,接下来就是功能实现了,微信小程序+PHP代码

四、效果演示和代码

1、小程序端代码

小程序页面代码所在路径 /pages/contact/contact.wxml

<!--pages/contact/contact.wxml-->

<view>

<scroll-view scroll-y scroll-into-view='{{toView}}' style='height: {{scrollHeight}};'>
  <!-- <view class='scrollMsg'> -->
  <block wx:key wx:for='{{msgList}}' wx:for-index="index">

    <!-- 单个消息1 客服发出(左) -->
    <view wx:if='{{item.speaker=="server"}}' id='msg-{{index}}' style='display: flex; padding: 2vw 11vw 2vw 2vw;'>
      <view style='width: 11vw; height: 11vw;'>
        <image style='width: 11vw; height: 11vw; border-radius: 10rpx;' src='https://cdn.pixabay.com/photo/2020/02/10/12/47/girl-4836394__340.jpg'></image>
      </view>
      <view style='width: 4vw; height: 11vw; margin-left: 0.5vw; display: flex; align-items: center; z-index: 9;'>
        <view class="triangle_border_left"></view>
      </view>
      <view class='leftMsg'>{{item.content}}</view>
    </view>

    <!-- 单个消息2 用户发出(右) -->
    <view wx:else id='msg-{{index}}' style='display: flex; justify-content: flex-end; padding: 2vw 2vw 2vw 11vw;'>
      <view class='rightMsg'>{{item.content}}</view>
      <view style='width: 4vw; height: 11vw; margin-right: 0.5vw; display: flex; align-items: center; z-index: 9;'>
        <view class="triangle_border_right"></view>
      </view>
      <view style='width: 11vw; height: 11vw;'>
        <image style='width: 11vw; height: 11vw; border-radius: 10rpx;' src='https://cdn.pixabay.com/photo/2021/09/24/10/00/chick-6652163__340.jpg'></image>
      </view>
    </view>

  </block>
  <!-- </view> -->

  <!-- 占位 -->
  <view style='width: 100%; height: 18vw;'></view>
</scroll-view>

<view class='inputRoom' style='bottom: {{inputBottom}}'>
  <image style='width: 7vw; margin-left: 3.2vw;' src='https://img95.699pic.com/element/40030/6429.png_300.png' mode='widthFix'></image>
  <input bindconfirm='sendClick' adjust-position='{{false}}' value='{{inputVal}}' confirm-type='send' bindfocus='focus' bindblur='blur'></input>
</view>
</view>

小程序页面样式代码所在路径 /pages/contact/contact.wxss


/* pages/contact/contact.wxss */

page {
  background-color: #f1f1f1;
}

.inputRoom {
  width: 100vw;
  height: 16vw;
  border-top: 1px solid #cdcdcd;
  background-color: #f1f1f1;
  position: fixed;
  bottom: 0;
  display: flex;
  align-items: center;
  z-index: 20;
}

input {
  width: 76vw;
  height: 9.33vw;
  background-color: #fff;
  border-radius: 40rpx;
  margin-left: 2vw;
  padding: 0 3vw;
  font-size: 28rpx;
  color: #444;
}

.leftMsg {
  font-size: 35rpx;
  color: #444;
  line-height: 7vw;
  padding: 2vw 2.5vw;
  background-color: #fff;
  margin-left: -1.6vw;
  border-radius: 10rpx;
  z-index: 10;
}

.rightMsg {
  font-size: 35rpx;
  color: #444;
  line-height: 7vw;
  padding: 2vw 2.5vw;
  background-color: #96EB6A;
  margin-right: -1.6vw;
  border-radius: 10rpx;
  z-index: 10;
}

 /*向左*/
 .triangle_border_left {
  width: 0;
  height: 0;
  border-width: 10px 30px 30px 0;
  border-style: solid;
  border-color: transparent #fff transparent transparent;
          /*透明       黄   透明        透明 */
  margin: 40px auto;
  position: relative;
}


        /*向右*/
        .triangle_border_right {
          width: 0;
          height: 0;
          border-width: 0px 30px 20px 13px;
          border-style: solid;
          border-color: transparent transparent transparent #96EB6A;
                  /*透明       透明        透明         黄*/
          margin: 40px auto;
          position: relative;
      }

小程序配置文件代码所在路径 /pages/contact/contact.json


{
  "navigationBarTitleText":"柯作客服",
  "usingComponents": {
  
  }
}

小程序业务逻辑代码所在路径 /pages/contact/contact.js


// pages/contact/contact.js
const app = getApp();
var inputVal = '';
var msgList = [];
var windowWidth = wx.getSystemInfoSync().windowWidth;
var windowHeight = wx.getSystemInfoSync().windowHeight;
var keyHeight = 0;

/**
 * 初始化数据
 */
function initData(that) {
  //输入框的内容
  inputVal = '';
  //消息列表,包含客服和用户的聊天内容
  msgList = [{
      speaker: 'server',
      contentType: 'text',
      content: 'Hi,亲爱的小主,终于等到您啦!欢迎来到柯作店铺,很荣幸为您服务。'
    },
    {
      speaker: 'customer',
      contentType: 'text',
      content: '你高兴的太早了'
    }
  ]
  that.setData({
    msgList,
    inputVal
  })
}

Page({
  /**
   * 页面的初始数据
   */
  data: {
    scrollHeight: '100vh',
    inputBottom: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    //初始化websocket连接
    this.chat();
    //监听心跳的方法
    this.webSocketXin();
    //聊天方法
    initData(this);

    //监听消息
    wx.onSocketMessage(res=>{
         //追加到消息列表里
        msgList.push(JSON.parse(res.data))
        inputVal = '';
        this.setData({
          msgList,
          inputVal
        });
    })


  },
  //页面卸载时间
  onUnload(){
    wx.closeSocket();
  },
  /**
   * 获取聚焦
   */
  focus: function(e) {
    keyHeight = e.detail.height;
    this.setData({
      scrollHeight: (windowHeight - keyHeight) + 'px'
    });
    this.setData({
      toView: 'msg-' + (msgList.length - 1),
      inputBottom: keyHeight + 'px'
    })
    //计算msg高度
    // calScrollHeight(this, keyHeight);

  },

  //失去聚焦(软键盘消失)
  blur: function(e) {
    this.setData({
      scrollHeight: '100vh',
      inputBottom: 0
    })
    this.setData({
      toView: 'msg-' + (msgList.length - 1)
    })
  },

  /**
   * 发送点击监听
   */
  sendClick: function(e) {
    //客户发的信息
    let customerMsg = {
      uid: 10,
      speaker: 'customer',
      contentType: 'text',
      content: e.detail.value
    };

     //关闭心跳包
     this.webSocketXin(60000, false)
    //发送给websocket
    wx.sendSocketMessage({
      data: JSON.stringify(customerMsg),
      success:res=>{
        //重启心跳包
        this.webSocketXin(40000, true)
      }  
    })

    //追加到消息列表里
    msgList.push(customerMsg)
    inputVal = '';
    this.setData({
      msgList,
      inputVal
    });
  },
  /**
   * 退回上一页
   */
  toBackClick: function() {
    wx.navigateBack({})
  },
  /**
   * websocket
   */
  chat(){
     //进行连接php的socket
     wx.connectSocket({
       //wss 协议相当于你要有一个ssl证书,https
       //ws  就相当于不实用证书  http
      url: 'ws://study.lishuo.net',
      success: function () {
        console.log('websocket连接成功~')
      },
      fail: function () {
        console.log('websocket连接失败~')
      }
    })
  },


  /**
   * 监听websocket心跳连接的方法
   */
  webSocketXin(time=60000,status=true){
    var timing;
    if(status == true){
      timing = setInterval(function () {
        console.log("当前心跳已重新连接");
        //循环执行代码
        wx.sendSocketMessage({
          data: JSON.stringify({
            type: 'active'
          }),
          fail(res) {
            //关闭连接
            wx.closeSocket();
            //提示
            wx.showToast({
              title: '当前聊天已断开',
              icon:'none'
            })
            clearInterval(timing);
            console.log("当前心跳已关闭");
          }
        });
      }, time) //循环时间,注意不要超过1分钟  
    } else {
      //关闭定时器
      clearInterval(timing);
      console.log("当前心跳已关闭");
    }
  

  }



})

2、服务端代码(PHP代码)

wechat_websocket.php


<?php

//创建WebSocket Server对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9511);

//监听WebSocket连接打开事件
$ws->on('Open', function ($ws, $request) {
    echo $request->fd . '我连接上了';
});

//监听WebSocket消息事件
$ws->on('Message', function ($ws, $frame) {
    //把前台传过来的json字符串转成数组
    $params = json_decode($frame->data, true);
    //判断是否是心跳消息,如果是心跳消息
    if (isset($params['type']) && isset($params['type'])=='active'){
        echo '这是心跳监听消息';
    }else{
        //先判断当前用户有没有正在连接
        if (isset($params['uid']) && !empty($params['uid'] == 666)) {
            //去用户表查询当前用户  fd
            $fd = 2;
        } else {
            $fd = 1;
        }
        //客服id
        $ws->push($fd, json_encode($params, JSON_UNESCAPED_UNICODE));
    }
});

//监听WebSocket连接关闭事件
$ws->on('Close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});

$ws->start();

五、部署

1、把服务端代码上传到Linux操作系统里

2、然后切到该目录下进行运行php wechat_websocket.php

php wechat_websocket.php
]]>
https://www.askme-121.pw/php-swoole/feed/ 0
包管理工具npm、yarn 和 pnpm 三者比较及使用详解! https://www.askme-121.pw/npm/ https://www.askme-121.pw/npm/#respond Wed, 03 Jan 2024 14:09:37 +0000 https://www.askme-121.pw/?p=489 1、简介

在 Vue 项目中,我们需要使用许多第三方依赖库,如 Vue Router、Vuex、Axios、Element UI 等等。这些依赖库通常以 NPM 包的形式提供,而且在使用时需要进行版本管理。因此,我们需要使用一个包管理工具来统一管理这些依赖库,避免版本冲突和管理繁琐。

常见的 Vue 项目包管理工具包括:

NPM:NPM(Node Package Manager)是 Node.js 自带的包管理工具,也是最常用的包管理工具之一。它可以方便地安装、升级、卸载依赖包,还可以发布自己的包到 NPM 仓库。

Yarn:Yarn 是 Facebook 推出的包管理工具,具有速度快、缓存机制好等优点。与 NPM 相比,Yarn 可以更快地下载依赖包,并且支持离线模式。

PNPM:PNPM(Permissive NPM)是一款新兴的包管理工具,与 NPM 不同的是,PNPM 采用了类似硬链接的方式,将依赖包安装到每个项目的 node_modules 目录下,从而避免了大量的重复安装。pnpm 内部使用基于内容寻址的文件系统来存储磁盘上所有的文件,这样可以做到不会出现重复安装,在项目中需要使用到依赖的时候,pnpm 只会安装一次,之后再次使用都会直接硬链接指向该依赖,极大节省磁盘空间,并且加快安装速度。

这些包管理工具都提供了一种方便的方式来管理项目中的依赖库,并且都能够很好地集成到 Vue 项目中。

以下是这些工具的中文官网地址:

2、NPM

NPM 简介

NPM(Node Package Manager)是 Node.js 默认的包管理器,它是世界上最大的软件注册表之一,有超过 100 万个包(package)可供下载使用。NPM 可以让开发者轻松地分享、安装和管理 Node.js 中的代码包和依赖项,它是构建现代 JavaScript 应用程序的关键组件之一。

除了作为 Node.js 的包管理器之外,NPM 也可以用于前端开发。在前端开发中,NPM 主要用于安装、管理和更新前端项目的各种依赖包,例如 Vue、React、Angular 等常用框架。NPM 可以帮助开发者快速、方便地安装和管理这些依赖,以便在项目中使用它们。

总之,NPM 是一个非常重要的开发工具,它提供了丰富的功能和生态系统,帮助开发者更轻松、更高效地构建应用程序。

NPM 安装

安装好 Node.js 之后,npm 会自动安装在电脑上,因此无需另行安装。你可以在命令行中输入 npm -v 命令,检查 npm 是否已经正确安装。如果输出了版本号,则说明 npm 安装成功。

# 查看 Node.js 版本信息
node -v

# 查看 npm 版本信息
npm -v

NPM 常用命令

下面列举一些常用的 npm 命令及其详细介绍:

# 1、创建一个新的 Node.js 应用程序或模块,并在 package.json 文件中定义依赖项。
npm init

# 2、安装依赖项。如果在安装时没有指定包的版本号,则将安装最新版本的包。
npm install

# 3、安装指定的依赖项,并将其添加到 package.json 文件中的 dependencies 中。
npm install <package name> --save

# 4、安装指定的开发依赖项,并将其添加到 package.json 文件中的 devDependencies 中。
npm install <package name> --save-dev

# 5、将包安装为全局包,以便在系统的任何位置使用它们。
npm install -g <package name>

# 6、更新 package.json 文件中指定的所有包的版本。
npm update

# 7、更新指定的包的版本。
npm update <package name>

# 8、卸载指定的依赖项,并将其从 package.json 文件中的 dependencies 或 devDependencies 中删除。
npm uninstall <package name>

# 9、查看指定的包的详细信息,包括其版本、描述、关键字、维护者等。
npm view <package name>

# 10、搜索与指定关键字匹配的包。
npm search <keywords>

# 11、清除 npm 的缓存。
npm cache clean

# 12、启动 Node.js 应用程序。
npm start

# 13、运行测试套件。
npm test

# 14、运行 package.json 文件中指定的脚本。
npm run <script name>

# 15、列出当前项目中已安装的所有包及其依赖关系。
npm ls

# 16、检查当前项目中已安装的包是否有过时的版本。
npm outdated:

# 17、查看指定包的详细信息。
npm view <package name>

# 18、查看指定包的摘要信息。
npm info <package name>

# 19、将当前目录下的代码发布为一个 npm 包。
npm publish

# 20、查看当前登录的 npm 用户名。
npm whoami

NPM 创建项目

要创建一个 Vue 项目,可以使用 Vue CLI(Command Line Interface)命令行工具来生成基本的项目结构和文件。

vue init

vue init 是在 Vue CLI 2.x 版本中使用的创建项目的命令,可以快速创建一个模板项目,例如从一个 GitHub 模板中拉取 Vue 项目模板进行初始化。可以通过命令行交互式地选择需要的模板以及一些配置信息。vue init 指令已经被 Vue CLI 3.x 中的 vue create 替代,不再更新。

使用 vue init 创建 Vue 项目示例:

# 全局安装 Vue CLI 2.x
npm install -g vue-cli

# 查看 Vue 版本信息
vue --version

# 从 GitHub 上拉取 webpack 模板
vue init webpack my-project

# 安装依赖
cd my-project
npm install

# 运行项目
npm run dev

vue create

vue create 是在 Vue CLI 3.x 版本中使用的创建项目的命令,可以快速创建一个新的 Vue 项目,从而让开发者快速搭建起一个基本的 Vue 项目架构。

vue create 提供了默认的配置以及插件管理,开发者可以根据自己的需要进行配置和管理。

使用 vue create 创建 Vue 项目示例:

# 全局安装 Vue CLI 3.x
npm install -g @vue/cli

# 查看 Vue 版本信息
vue --version

# 创建一个新项目
vue create my-project

# 安装依赖
cd my-project
npm install

# 运行项目
npm run serve

在创建项目时,vue create 提供了更多的配置选项和插件,比如使用 TypeScript、使用 Vue Router、使用 Vuex 等等。同时,使用 vue create 创建项目可以直接在项目中添加插件,而不需要像 vue init 一样手动添加。

3、Yarn

Yarn 简介

Yarn 是一个 JavaScript 的包管理器,它是由 Facebook、Google、Tilde 以及其他社区成员共同维护的开源项目。Yarn 在 NPM 的基础上进行了改进和增强,旨在提高依赖包的安装速度和可靠性。

相对于 NPM,Yarn 有以下几个特点:

  • 快速:Yarn 通过并行安装依赖包,从而比 NPM 更快地完成依赖包的安装。
  • 可靠:Yarn 会生成一个锁定文件(yarn.lock),确保每次安装的依赖包版本是相同的,从而避免了版本不一致的问题。
  • 安全:Yarn 通过对依赖包的哈希校验,确保安装的依赖包是安全的,并且没有被篡改。
  • 离线模式:Yarn 可以在没有网络的情况下运行,从而提高了开发者的灵活性。

需要注意的是,Yarn 和 NPM 本质上都是包管理工具,它们都可以管理 JavaScript 项目的依赖包。在使用 Yarn 或 NPM 时,建议了解其使用方法和命令,以便更好地管理项目的依赖包。

Yarn 安装

如果您已经安装了 Node.js,可以通过 npm 来安装 Yarn。通过 npm 安装 Yarn 有两种方式:

1、全局安装

# 全局安装
npm install -g yarn

# 查看 Yarn 版本信息
yarn -v

这种方式会将 Yarn 安装到全局环境中,从而可以在任意位置使用 yarn 命令。

2、本地安装

# 本地安装
npm install yarn

# 查看 Yarn 版本信息
yarn -v

这种方式会将 Yarn 安装到当前目录的 node_modules 文件夹中。可以通过 ./node_modules/yarn/bin/yarn 命令来运行 Yarn。

可以使用以下命令来安装特定版本的 Yarn

# 查看可用的 Yarn 版本
npm view yarn versions

# 全局安装特定版本
npm install -g yarn@<version>

# 本地安装特定版本
npm install yarn@<version>

# 查看 Yarn 版本信息
yarn -v

Yarn 常用命令

下面列举一些常用的 Yarn 命令及其详细介绍:

# 1、该命令将引导您创建一个新的 package.json 文件。您可以提供一些信息,例如项目名称、版本号、作者、描述等。
yarn init

# 2、该命令将安装指定的软件包并将其添加到项目依赖项中。您可以使用 @ 版本前缀来安装特定版本的软件包。例如:yarn add package@1.0.0。
yarn add [package]

# 3、该命令将从项目依赖项中删除指定的软件包。
yarn remove [package]

# 4、该命令将更新指定软件包的版本。您可以使用 @ 版本前缀来更新特定版本的软件包。例如:yarn upgrade package@1.0.0。
yarn upgrade [package]

# 5、该命令将根据项目中的 package.json 文件安装所有依赖项。如果您在运行时指定了 --production 标志,则只会安装生产依赖项。
yarn install

# 6、该命令将清除 Yarn 的缓存。这可能会释放一些磁盘空间并解决某些依赖项问题。
yarn cache clean

# 7、该命令将全局安装指定的软件包。这通常用于安装全局命令行工具。
yarn global add [package]

# 8、该命令将从全局安装中删除指定的软件包。
yarn global remove [package]

# 9、该命令将列出当前项目中的所有依赖项。
yarn list

# 10、该命令将运行项目中指定的脚本。例如:yarn run start。
yarn run [script]

# 11、该命令将运行项目中的测试脚本。
yarn test

# 12、该命令将根据项目中的配置文件构建项目。
yarn build

# 13、该命令将发布您的软件包到 Yarn 存储库中。
yarn publish

# 14、该命令将显示有关指定软件包的详细信息。
yarn info [package]

# 15、该命令将使您登录到 Yarn 存储库。
yarn login

# 16、该命令将使您从 Yarn 存储库注销。
yarn logout

# 17、该命令将允许您将本地软件包链接到项目中。
yarn link [package]

# 18、该命令将从项目中取消链接指定的软件包。
yarn unlink [package]

# 19、该命令将显示指定软件包的依赖项信息。
yarn why [package]

# 20、该命令将显示项目依赖项的许可证信息。
yarn licenses ls

Yarn 创建项目

yarn create vue-app

yarn create vue-app 创建的是基于 Vue CLI 2.x 版本的项目,该版本的 Vue CLI 使用 webpack 进行打包,需要配置一些基本的项目信息和插件。

使用 yarn create vue-app 创建 Vue 2.x 项目:

# 创建一个 Vue 2.x 项目
yarn create vue-app my-project

# 安装依赖
cd my-project
yarn install

# 运行项目
yarn serve

yarn create vite-app

yarn create vite-app 创建的是基于 Vue CLI 3.x 版本的项目,该版本的 Vue CLI 使用了 vite 构建工具,具有更快的构建速度和更少的配置,适合较小的项目和快速原型开发。

使用 yarn create vite-app 创建 Vue 3.x 项目:

# 创建一个 Vue 3.x 项目
yarn create vite-app my-project --template vue

# 安装依赖
cd my-project
yarn install

# 运行项目
yarn serve

在上面的命令中,my-project 是项目名称,可以根据实际情况进行更改。同时,–template 参数用于指定创建的项目模板,如果不指定,默认是 JavaScript 模板。对于 Vue 3.x 项目,需要指定 –template vue。

PNPM

PNPM 简介

PNPM 是一个 Node.js 包管理工具,类似于 NPM 和 Yarn。PNPM 采用了一种不同于 NPM 和 Yarn 的方式来管理 Node.js 包,它使用硬链接来共享依赖包,从而减少了存储空间的占用和安装的时间。同时,PNPM 也可以像 NPM 和 Yarn 一样在全局和本地范围内安装和使用 Node.js 模块。PNPM 还提供了一些其他的功能,比如支持自动清理未使用的包,以及支持多个版本的 Node.js 和 NPM。

相比于 NPM 和 Yarn,PNPM 具有以下优点:

  • 节约磁盘空间:PNPM 使用硬链接来共享依赖包,因此只需要存储一份依赖包的副本,可以大大节约磁盘空间。
  • 加速安装:PNPM 可以同时下载和安装多个依赖包,从而加速安装过程。
  • 更好的稳定性:PNPM 可以在多个项目之间共享依赖包,从而减少了不同版本的包之间的冲突和版本不一致的问题。

PNPM 的主要缺点是相对于 NPM 和 Yarn,它的社区和生态系统还比较小,因此在某些方面可能缺乏支持和文档。

PNPM 安装

1、使用 npm 全局安装

# 使用 npm 全局安装
npm install -g pnpm

# 查看 pnpm 版本信息
pnpm --version

2、使用 yarn 全局安装

# 使用 yarn 全局安装
yarn global add pnpm

# 查看 pnpm 版本信息
pnpm --version

可以使用以下命令来安装特定版本的 pnpm

# 查看可用的 pnpm 版本
npm view pnpm versions

# 使用 npm 全局安装特定版本
npm install -g pnpm@<version>

# 使用 npm 本地安装特定版本
npm install pnpm@<version>

# 查看 pnpm 版本信息
pnpm --version

PNPM 常用命令

下面列举一些常用的 pnpm 命令及其详细介绍:

# 1、用于安装项目中的所有依赖。
pnpm install

# 2、用于安装指定的依赖包,例如 pnpm install react。
pnpm install [package]

# 3、用于全局安装指定的依赖包,例如 pnpm install --global typescript。
pnpm install --global [package]

# 4、用于更新项目中的所有依赖。
pnpm update

# 5、用于更新指定的依赖包,例如 pnpm update react。
pnpm update [package]

# 6、用于删除指定的依赖包,例如 pnpm remove react。
pnpm remove [package]

# 7、用于列出当前项目中已安装的所有依赖包。
pnpm list

# 8、用于列出当前系统中已全局安装的所有依赖包。
pnpm list --global

# 9、用于列出当前项目中已安装的所有依赖包及其直接依赖项。
pnpm list --depth=1

# 10、用于查看指定依赖包的详细信息,例如 pnpm info react。
pnpm info [package]

# 11、用于将指定的依赖包添加到 devDependencies,例如 pnpm add jest --save-dev。
pnpm add [package] --save-dev

# 12、用于将指定的依赖包添加到 dependencies,例如 pnpm add react --save。
pnpm add [package] --save

# 13、用于全局安装指定的依赖包,例如 pnpm add typescript --global。
pnpm add [package] --global

# 14、用于重新构建项目中的所有依赖。
pnpm rebuild

# 15、用于运行项目中的脚本命令,例如 pnpm run start。
pnpm run [script]

# 16、用于查看本地包存储状态。
pnpm store status

# 17、用于清理本地存储的未被任何项目使用的包。
pnpm store prune

# 18、用于将指定的依赖包添加到本地存储,以便其他项目使用。
pnpm store add [package]

# 19、用于启动本地包服务器。
pnpm server

# 20、用于在所有项目中执行指定命令,例如 pnpm recursive install。
pnpm recursive [command]

PNPM 创建项目

创建 Vue 项目的方式与 PNPM 无关,与使用的脚手架工具有关。

无论使用哪种方式创建 Vue 项目,都可以在项目目录下使用 PNPM 进行依赖安装。例如,在使用 Vue CLI 创建项目后,进入项目目录后可以使用 pnpm install 安装依赖。

pnpm create vue-app

pnpm create vue-app 创建的是基于 Vue CLI 2.x 版本的项目,该版本的 Vue CLI 使用 webpack 进行打包,需要配置一些基本的项目信息和插件。

使用 pnpm create vue-app 创建 Vue 2.x 项目:

# 创建一个 Vue 2.x 项目
pnpm create vue-app my-project

# 安装依赖
cd my-project
pnpm install

# 运行项目
pnpm serve

pnpm create vite-app

pnpm create vite-app 创建的是基于 Vue CLI 3.x 版本的项目,该版本的 Vue CLI 使用了 vite 构建工具,具有更快的构建速度和更少的配置,适合较小的项目和快速原型开发。

使用 pnpm create vite-app 创建 Vue 3.x 项目:

# 创建一个 Vue 3.x 项目
pnpm create vite-app my-project --template vue

# 安装依赖
cd my-project
pnpm install

# 运行项目
pnpm serve

在上面的命令中,my-project 是项目名称,可以根据实际情况进行更改。同时,–template 参数用于指定创建的项目模板,如果不指定,默认是 JavaScript 模板。对于 Vue 3.x 项目,需要指定 –template vue。

扩展

server 和 run dev

yarn serve 和 yarn run dev

yarn serve 和 yarn run dev 都是用来启动前端项目开发环境的命令,但是对应的使用场景和实现方式不同。

通常情况下,yarn run dev 是在 package.json 文件中配置的一个脚本命令,用于启动开发环境的服务。比如:

{
  "scripts": {
    "dev": "webpack-dev-server --open"
  }
}

在上面的示例中,运行 yarn run dev 命令时,实际上执行的是 webpack-dev-server –open 命令,用于启动一个开发环境的服务器,实时监控文件变化并重新构建项目。

而 yarn serve 则是一个独立的命令,需要先安装 serve 这个包。它的作用也是启动一个本地服务器,用于开发环境的调试和测试。例如,我们可以通过以下命令来安装和使用 serve:

# 安装 serve 包
yarn global add serve

# 在当前目录启动一个服务器,默认使用 5000 端口
yarn serve

在默认情况下,yarn serve 会在当前目录下启动一个本地服务器,可以在浏览器中通过 http://localhost:5000 访问。与 yarn run dev 相比,yarn serve 不需要在项目中配置脚本命令,更加方便快捷。

pnpm server 和 pnpm run dev

pnpm server 和 pnpm run dev 都是运行项目的命令,但是具体的区别取决于项目的配置文件和脚本配置。

通常来说,pnpm run dev 是在项目中定义的脚本命令,用于在开发模式下启动项目。开发模式一般会启用热更新、调试模式等功能,方便开发人员实时调试和查看项目的效果。

而 pnpm server 则是一个比较通用的命令,可以在终端中直接输入来启动一个 web 服务器,并将当前目录作为网站的根目录。该命令不仅仅可以用于前端项目,还可以用于一些静态网站的开发和部署。

需要注意的是,具体命令的使用方式和效果取决于项目的具体配置和脚本命令,如果不确定应该使用哪个命令,可以参考项目的文档或者询问开发人员。

Node.js 版本和 Vue.js 版本的关联

Node.js 版本和 Vue.js 版本之间存在一定的关联,不同的 Vue.js 版本对 Node.js 版本有不同的要求。具体来说,Vue.js 2.x.x 支持的 Node.js 版本为 8.9 或更高版本,Vue.js 3.x.x 支持的 Node.js 版本为 10 或更高版本。

Node.js 是 Vue.js 的运行环境,因此 Vue.js 的版本兼容性取决于Node.js 的版本兼容性。

需要注意的是,Node.js 版本过低或过高都可能导致 Vue.js 项目无法正常运行。因此,在使用 Vue.js 时,需要根据具体的 Vue.js 版本要求选择合适的 Node.js 版本,并且保持 Node.js 版本的稳定性和更新。

Node.js 版本和 npm版本的关联

Node.js 版本和 npm 版本有一定的关联性,因为 npm 是随 Node.js 一起安装的。通常情况下,当你升级 Node.js 时,npm 的版本也会随之更新。但是,有时候可能需要手动升级 npm 版本,以满足项目的需求。

通过 node -v 命令查看安装的 Node.js 版本,再通过 npm -v 命令查看对应的 npm 版本。

需要注意的是,在 Node.js v0.6.x 及以下版本中,npm 是一个独立的包管理器,需要手动安装和升级。从 Node.js v0.8.x 开始,npm 被作为默认的包管理器一同安装。因此,在这些较老的 Node.js 版本中,可能需要手动安装或升级 npm 版本。

Node.js 版本和 Yarn 版本的关联

Yarn 是一款独立于 Node.js 的包管理工具,但它需要 Node.js 的运行环境。

Node.js 版本和 Yarn 版本的关联如下:

Yarn 1.x.x 版本要求 Node.js 版本 >= 10.13.0 。

Yarn 2.x.x 版本要求 Node.js 版本 >= 12.13.0 。

这是因为 Yarn 2.x.x 版本使用了 Node.js 12.x.x 的新特性,而旧版 Node.js 不支持这些特性。因此,在安装 Yarn 2.x.x 之前,必须先安装 Node.js 12.13.0 或更高版本。

需要注意的是,不同操作系统下 Yarn 的最新版本可能不同,因此在安装 Yarn 时需要根据自己的操作系统和 Node.js 版本选择合适的 Yarn 版本。

Node.js 版本和 PNPM 版本的关联

PNPM 是一款独立于 Node.js 的包管理工具,但它需要 Node.js 的运行环境。

Node.js 版本和 PNPM 版本的关联与 Node.js 版本和 npm 版本的关联类似,因为 PNPM 是基于 npm 的改进版本。所以,在安装 PNPM 时需要先安装 Node.js 和 npm。一般来说,PNPM 会支持与当前 Node.js 版本兼容的最新版本,具体兼容版本可以在 PNPM 的官方文档中查看。另外,PNPM 还提供了对特定 Node.js 版本的支持,可以使用 PNPM 的特定版本来安装适用于特定 Node.js 版本的 PNPM,例如使用 npm i -g pnpm@6.14.11 命令安装 PNPM 的 6.14.11 版本,以适用于 Node.js v14.x 的版本。总之,PNPM 的版本需要与当前的 Node.js 版本兼容,才能保证正常使用。

Vue.js 版本和 npm、Yarn、PNPM 版本的关联

Vue.js 版本和包管理工具(npm、Yarn、PNPM)的版本之间并没有直接的关联。Vue.js 官方会在每个版本发布时指定支持的 Node.js 版本和 npm 版本的最小要求,这些要求和包管理工具的版本没有直接的关系,但是包管理工具需要在指定的 Node.js 版本和 npm 版本的基础上运行才能正常工作。

比如,Vue.js 2.x 支持的最小 Node.js 版本是 4.0.0,支持的最小 npm 版本是 3.0.0。如果要使用 Yarn 或 PNPM 来管理依赖,需要确保安装的 Yarn 或 PNPM 版本能够在 Node.js 4.0.0 和 npm 3.0.0 的环境中正常工作。

需要注意的是,Vue CLI 的某些版本可能会要求使用特定版本的包管理工具。比如,Vue CLI 3.0 要求使用 Yarn 1.x,而不支持 Yarn 2.x。在使用 Vue CLI 时,建议查看官方文档中的要求和建议,以保证开发环境的兼容性。

总的来说,选择使用哪种包管理工具取决于个人需求和项目需求。如果需要快速安装依赖项并具有良好的缓存机制,则可以选择 pnpm。如果需要具有大量社区支持和广泛的生态系统,则可以选择 npm。如果需要支持工作区和锁定文件,则可以选择 yarn。

]]>
https://www.askme-121.pw/npm/feed/ 0
vite和webpack的区别 https://www.askme-121.pw/vite-vs-webpack/ https://www.askme-121.pw/vite-vs-webpack/#respond Wed, 03 Jan 2024 11:56:51 +0000 https://www.askme-121.pw/?p=488 vite和webpack是什么?

1、vite是什么

vite是一个由Vue.js作者尤雨溪开发的构建工具,它利用了ES Module Imports,在开发环境下可以实现按需编译,加快了开发速度。而在生产环境下,它使用Rollup进行打包,提供更好的tree-shaking、代码压缩和性能优化。

vite主要解决了现有工具(如webpack、rollup)在大型项目开发过程中存在的启动慢、热更新慢等问题。vite致力于为现代前端工作流提供一个更快、更轻的解决方案。

2、webpack是什么

webpack是一个静态模块打包器,可以把各种资源如JavaScript、CSS、图片等都视为模块,然后将这些模块打包成一份或多份优化的资源。webpack提供了丰富的API和生态,用户可以通过插件和loader来扩展webpack的功能。

webpack通过一种叫做loader的机制来处理非JavaScript类型的文件,并且可以把这些文件打包成合适的格式供浏览器使用。除此之外,webpack还具有代码拆分、优化、模块热替换等强大功能。

比如在一个React项目中,我们可以使用Babel Loader把JSX和ES6语法转换为浏览器可识别的JavaScript语法,使用CSS Loader和Style Loader来处理CSS文件,使用File Loader来处理图片等文件。

无论是在开发环境中进行模块热替换,还是在生产环境中进行代码拆分和优化,webpack都能够很好的完成任务,它在前端构建工具中有着广泛的应用。

vite和webpack的区别:

1、基础概念不同;
2、编译方式不同;
3、开发效率不同;
4、扩展性不同;
5、应用场景不同。
总的来说,vite以其更快的编译速度和更低的内存占用率,给前端开发带来了全新的体验,而webpack凭借其高度的自定义性和成熟的生态,仍是前端构建工具的重要选择。

一、基础概念不同

webpack是一个模块打包器,它可以把许多不同类型的模块和资源文件打包为静态资源。它具有高度的可配置性,可以通过插件和loader扩展其功能。

vite,由Vue.js作者尤雨溪开发并维护,是一个基于浏览器原生 ES imports 的开发服务器。它能够提供丰富的功能,如快速冷启动、即时热更新和真正的按需编译等。

二、编译方式不同

webpack在编译过程中,会将所有模块打包为一个bundle.js文件,然后再运行这个文件。

而vite在开发模式下,没有打包的步骤,它利用了浏览器的ES Module Imports特性,只有在真正需要时才编译文件。在生产模式下,vite使用Rollup进行打包,提供更好的tree-shaking,代码压缩和性能优化。

三、开发效率不同

webpack的热更新是全量更新,即使修改一个小文件,也会重新编译整个应用,这在大型应用中可能会导致编译速度变慢。

vite的热更新是增量更新,只更新修改的文件,所以即使在大型应用中也能保持极快的编译速度。

四、扩展性不同

webpack有着成熟的插件生态,几乎可以实现任何你想要的功能,扩展性非常强。

vite虽然也支持插件,但相比webpack的生态,还有一些距离。

五、应用场景不同

webpack由于其丰富的功能和扩展性,适合于大型、复杂的项目。

而vite凭借其轻量和速度,更适合于中小型项目和快速原型开发。

]]>
https://www.askme-121.pw/vite-vs-webpack/feed/ 0