初中级PHP程序员如何进阶学习?

news2024/11/24 6:50:17

如果你是一个以PHP为主的开发人员,只会依赖现成的框架进行增删改查,想提高自己又不知道从何下手,你可以花点时间研究一下我这个开源项目:酷瓜云课堂,这个项目以PHP+JS 为主,负责主要的业务逻辑,部署自动化等用到了shell脚本,也用到了不少主流中间件和服务(docker,redis,xunsearh,beanstalk,supervisor,crontab),从点带面带你了解新东西,学会举一反三,你可以学到不少的东西。不要拘泥于语言和框架,你换成其他的语言,除了语言这部分,其他的东西也是大同小异的,变通和消化知识才是学习的终极目标。

酷瓜云课堂-开源知识付费解决方案

酷瓜云课堂,依托腾讯云基础服务架构,采用 C 扩展框架 Phalcon 开发,致力互联网课程点播,互联网课程直播,局域网课程点播,局域网课程直播,垂直于在线教育解决方案。

项目文档

  • 运行环境搭建
  • 系统服务配置
  • 客户终端配置

意见反馈

  • 码云平台
  • 官方社区

通过这个项目我能学到那些方面的知识呢?

容器化编排

这个项目用到了PHP,MySQL,Nginx,Redis,Beanstalk,Xunsearch等等。想想这些服务的按照配置就是一个头疼且枯燥的事情,所以我们使用了docker 和 docker-compose 来组织编排这些服务。我们把这个项目的容器化自动化单独设了一个项目:course-tencent-cloud-docker

客户端实现

这个项目的移动端对于性能和丝滑程度没有那么高的要求,所以我们选择了使用uniapp来实现多客户端,这个还算是一个比较有性价比的东西吧,当然也不是宣传的什么一套代码多端编译(有时条件编译写的你想吐)。我们把这个项目的移动端单独设了一个项目:course-tencent-cloud-app

数据库整体设计

数据结构是一个系统的基石和灵魂,对于以增删改查的业务系统来说,数据库毫无疑问是无比重要的。如何设计表,如何设计关联性,如何规划索引,如何做好预留,这个都是数据库设计该考虑的问题。这个项目中,对业务字段用途都做了很详细的说明(参看Models目录下的实体定义)。

业务逻辑的实现

比如如何限制多人同时使用一个账号登录,后面登录的踢出前面登录的,这也是很常见的一个需求吧?能力强的人自己思考思考能够实现,新手司机们可能是一头雾水。你可以对照这个项目的功能点,去看看各个功能点是如何实现的,如果是你你又会怎么实现,参考别人的思路是学习的重要途径。

计划任务的实现

很多业务逻辑的实现离不开计划任务,这个项目中没有使用 swoole 或者 workman 之类的定时器,使用的是linux 系统自带的 crontab+peppeocchi/php-cron-scheduler 来统一管理计划任务脚本。

(1)sheduler.php 统一任务脚本

use GO\Scheduler;

$scheduler = new Scheduler();

$script = __DIR__ . '/console.php';

$bin = '/usr/local/bin/php';

$scheduler->php($script, $bin, ['--task' => 'vod_event', '--action' => 'main'])
->everyMinute(5);

$scheduler->php($script, $bin, ['--task' => 'sync_learning', '--action' => 'main'])
->everyMinute(7);

$scheduler->run();

(2)crontab 设置执行频率

* * * * * www-data /usr/local/bin/php /var/www/html/ctc/scheduler.php 1>/dev/null 2>&1

缓存系统的实现

缓存可以大大减轻数据库呀了,也可以为数据交换提供便利,这个项目里面使用了opache缓存,文件缓存,redis缓存等。比如session共享,访问频率限制,商品秒杀就是基于redis的特性实现的。

(1)访问频率限制

public function checkRateLimit()
    {
        $settings = $this->getSettings('security.throttle');

        $settings['interval'] = max($settings['interval'], 60);
        $settings['rate_limit'] = max($settings['rate_limit'], 60);

        if ($settings['enabled'] == 0) {
            return true;
        }

        $cache = $this->getCache();

        $sign = $this->getRequestSignature();

        $cacheKey = $this->getCacheKey($sign);

        if ($cache->ttl($cacheKey) < 1) {
            $cache->save($cacheKey, 0, $settings['interval']);
        }

        $rateLimit = $cache->get($cacheKey);

        if ($rateLimit >= $settings['rate_limit']) {
            return false;
        }

        $cache->increment($cacheKey, 1);

        return true;
    }

(2)商品秒杀锁定

class Lock
{

    /**
     * @param string $itemId
     * @param int $expire
     * @return bool|string
     * @throws RedisException
     */
    public static function addLock($itemId, $expire = 10)
    {
        if (empty($itemId) || $expire <= 0) {
            return false;
        }

        /**
         * @var RedisCache $cache
         */
        $cache = Di::getDefault()->getShared('cache');

        $redis = $cache->getRedis();

        $lockId = Text::random(Text::RANDOM_ALNUM, 16);

        $keyName = self::getLockKey($itemId);

        $result = $redis->set($keyName, $lockId, ['nx', 'ex' => $expire]);

        return $result ? $lockId : false;
    }

    /**
     * @param string $itemId
     * @param string $lockId
     * @return bool
     * @throws RedisException
     */
    public static function releaseLock($itemId, $lockId)
    {
        if (empty($itemId) || empty($lockId)) {
            return false;
        }

        /**
         * @var RedisCache $cache
         */
        $cache = Di::getDefault()->getShared('cache');

        $redis = $cache->getRedis();

        $keyName = self::getLockKey($itemId);

        $redis->watch($keyName);

        /**
         * 监听key防止被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控
         */
        if ($lockId == $redis->get($keyName)) {
            $redis->multi()->del($keyName)->exec();
            return true;
        }

        $redis->unwatch();

        return false;
    }

    public static function getLockKey($itemId)
    {
        return sprintf('_LOCK_:%s', $itemId);
    }

消息队列的实现

这个项目中,对于一些特定事件和消息通知使用了消息队列来实现,消息队列使用的是中规中矩的 beanstalk 中间件,没有很复杂但是很强大,该有的也都有,消息队列的历史记录在 mysql 里面,生成一个任务就有一条记录,然后推送到 beanstalk。命令行模式下起了两个相关进程消费这些消息,使用了 supervisor 监控这两个进程,如果进程有问题会自动重启。

(1)task 后置操作自动推送消息

public function afterCreate()
{
        $beanstalk = $this->getBeanstalk();

        $tube = $this->isNoticeTask($this->item_type) ? 'notice' : 'main';

        $beanstalk->putInTube($tube, $this->id);
 }

(2)supervisor 监控配置

[program:queue-main-worker]
command=/usr/local/bin/php /var/www/html/ctc/console.php queue main_worker
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
startretries=30
startsecs=10

[program:queue-notice-worker]
command=/usr/local/bin/php /var/www/html/ctc/console.php queue notice_worker
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
startretries=30
startsecs=10

(3)消费队列 worker

/**
     * 启动main消费队列
     *
     * @command php console.php queue main_worker
     */
    public function mainWorkerAction()
    {
        $tube = 'main';

        echo "------{$tube} worker start ------" . PHP_EOL;

        $beanstalk = $this->getBeanstalk();

        $logger = $this->getLogger('queue');

        $config = $this->getConfig();

        while (true) {
            $job = $beanstalk->reserveFromTube($tube, 60);
            if ($job instanceof BeanstalkJob) {
                $taskId = $job->getBody();
                if ($config->get('env') == ENV_DEV) {
                    $logger->debug("tube:{$tube}, task:{$taskId} handling");
                }
                try {
                    $manager = new MainQueue();
                    $manager->handle($taskId);
                    $job->delete();
                } catch (\Throwable $e) {
                    $logger->error("tube:{$tube}, task:{$taskId} exception " . kg_json_encode([
                            'file' => $e->getFile(),
                            'line' => $e->getLine(),
                            'message' => $e->getMessage(),
                        ]));
                }
            } else {
                $this->keepDbConnection();
            }
        }
    }

注意: 命令行下,过长时间会导致数据库链接丢失,所以隔断时间向数据库发个消息(简单做个查询即可),保持链接的可用。

全文检索的实现

全文检索的实现,我们没有使用 ElasticSearch,也没有使用 Sphinx,而采用了国产的 Xunsearch,原因就是简单够用,对中小项目来说非常友好。

(1)文档定义

project.name = ctc_pro_article
project.default_charset = UTF-8

server.index = 127.0.0.1:8383
server.search = 127.0.0.1:8384

[id]
type = id

[title]
type = title

[cover]
type = string

[summary]
type = body

[category_id]
type = string
index = self
tokenizer = full

[owner_id]
type = string
index = self
tokenizer = full

[create_time]
type = string

[tags]
type = string

[category]
type = string

[owner]
type = string

[view_count]
type = string

[comment_count]
type = string

[like_count]
type = string

[favorite_count]
type = string

(2)索引操作

class ArticleIndexTask extends Task
{

    /**
     * 搜索测试
     *
     * @command: php console.php article_index search {query}
     * @param array $params
     * @throws \XSException
     */
    public function searchAction($params)
    {
        $query = $params[0] ?? null;

        if (!$query) {
            exit('please special a query word' . PHP_EOL);
        }

        $result = $this->searchArticles($query);

        var_export($result);
    }

    /**
     * 清空索引
     *
     * @command: php console.php article_index clean
     */
    public function cleanAction()
    {
        $this->cleanArticleIndex();
    }

    /**
     * 重建索引
     *
     * @command: php console.php article_index rebuild
     */
    public function rebuildAction()
    {
        $this->rebuildArticleIndex();
    }

    /**
     * 清空索引
     */
    protected function cleanArticleIndex()
    {
        $handler = new ArticleSearcher();

        $index = $handler->getXS()->getIndex();

        echo '------ start clean article index ------' . PHP_EOL;

        $index->clean();

        echo '------ end clean article index ------' . PHP_EOL;
    }

    /**
     * 重建索引
     */
    protected function rebuildArticleIndex()
    {
        $articles = $this->findArticles();

        if ($articles->count() == 0) return;

        $handler = new ArticleSearcher();

        $doc = new ArticleDocument();

        $index = $handler->getXS()->getIndex();

        echo '------ start rebuild article index ------' . PHP_EOL;

        $index->beginRebuild();

        foreach ($articles as $article) {
            $document = $doc->setDocument($article);
            $index->add($document);
        }

        $index->endRebuild();

        echo '------ end rebuild article index ------' . PHP_EOL;
    }

    /**
     * 搜索文章
     *
     * @param string $query
     * @return array
     * @throws \XSException
     */
    protected function searchArticles($query)
    {
        $handler = new ArticleSearcher();

        return $handler->search($query);
    }

    /**
     * 查找文章
     *
     * @return ResultsetInterface|Resultset|ArticleModel[]
     */
    protected function findArticles()
    {
        return ArticleModel::query()
            ->where('published = 1')
            ->andWhere('deleted = 0')
            ->execute();
    }

}

数据自动备份

数据的重要性这里就不强调了,假如那天你的数据库被黑客锁了,问你要高额的赎金,而你又没有最近数据的备份,要数据还是乖乖交钱?

这个项目里面使用 mysqldump 做定时备份,结合腾讯云的 coscmd 命令行工具,实现备份上传到腾讯云存储。

#!/usr/bin/env bash

#备份保留天数
KEEP_DAYS=15

#COSCMD命令路径(绝对路径)
COS_CMD=/usr/local/bin/coscmd

#COS配置文件路径(绝对路径)
COS_CONF_PATH=/root/.cos.conf

#本地目录(绝对路径,末尾不带"/")
LOCAL_DIR=/root/ctc-docker/mysql/data/backup

#远程目录(绝对路径,末尾不带"/")
REMOTE_DIR=/backup/database

docker exec -i ctc-mysql bash <<'EOF'

#数据库名称
DB_NAME=ctc

#数据库用户
DB_USER=ctc

#数据库密码
DB_PWD=1qaz2wsx3edc

#备份目录(末尾不带"/")
backup_dir=/var/lib/mysql/backup

#创建备份目录
if [ ! -d ${backup_dir} ]; then
  mkdir -p ${backup_dir}
fi

#导出数据
mysqldump --no-tablespaces -u ${DB_USER} -p${DB_PWD} ${DB_NAME} | gzip > ${backup_dir}/${DB_NAME}-$(date +%Y-%m-%d).sql.gz

exit
EOF

#删除过期备份
find ${LOCAL_DIR} -mtime +${KEEP_DAYS} -name "*.sql.gz" | xargs rm -f

#同步备份
echo y | ${COS_CMD} -c ${COS_CONF_PATH} upload -rs --delete ${LOCAL_DIR}/ ${REMOTE_DIR}/

升级更新的实现

(1)如果每次更新都要上传文件,比较异同,那未免太痛苦了。

(2)如果每次更新都要导入 xxx.sql 文件,那执行过那些操作如何管控呢。

我这个项目中,以git版本控制的方式应对(1)的问题,以migration数据迁移的方式应对(2)的问题,以shell 脚本为载体,执行相关程序调用,实现自动升级更新。

#!/usr/bin/env bash

#docker目录
DOCKER_DIR=/root/ctc-docker

#ctc目录
CTC_DIR=${DOCKER_DIR}/html/ctc

#static.sh
static_sh=${DOCKER_DIR}/static.sh

#config.php
config_php=${CTC_DIR}/config/config.php

#静态文件版本号
static_version=$(date +%s)

#成功信息输出
success_print() {
  echo -e "\033[32m $1 \033[0m"
}

#失败信息输出
error_print() {
  echo -e "\033[31m $1 \033[0m"
}

if [ ! -d "${CTC_DIR}" ]; then
  error_print "\n------ ctc dir not existed ------\n"
fi

#拉取docker更新
cd ${DOCKER_DIR} && git pull --no-rebase

#拉取ctc源码更新
cd ${CTC_DIR} && git pull --no-rebase

#同步静态资源文件
if [ -f "${static_sh}" ]; then
    bash "${static_sh}"
fi

#更新静态资源版本号
sed -i "s/\$config\['static_version'\].*/\$config['static_version'] = '${static_version}';/g" "${config_php}"

docker exec -i ctc-php bash <<'EOF'

#切换到ctc目录
cd /var/www/html/ctc || exit

#安装依赖包
composer install --no-dev
composer dump-autoload --optimize

#数据迁移
vendor/bin/phinx migrate

#程序升级
php console.php upgrade

#重启队列
supervisorctl restart queue-main-worker
supervisorctl restart queue-notice-worker

exit
EOF

success_print "\n------ upgrade finished -------\n"

整站迁移实现

如果说服务器搬家又要重复环境配置,系统安装,导入导出等等操作,那必定是一件痛苦的事情。这个项目里面使用脚本完成了相关操作,分整站备份和还原两部分。

(1)整站备份
(2)整站还原

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

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

相关文章

基于遗传算法的新能源电动汽车充电桩与路径选择MATLAB程序

主要内容&#xff1a; 根据城市间的距离&#xff0c;规划新能源汽车的行驶路径。要求行驶距离最短。 部分代码&#xff1a; %% 加载数据 %%遗传参数 load zby;%个城市坐标位置 NIND50; %种群大小 MAXGEN200; Pc0.9; %交叉概率 Pm0.2; %变异概率 GGAP0.…

初识Redis——Redis概述、安装、基本操作

目录 一、NoSQL介绍 1.1什么是NoSQL 1.2为什么会出现NoSQL技术 1.3NoSQL的类别 1.4传统的ACID是什么 1.5 CAP 1.5.1 经典CAP图 1.5.4 什么是BASE 二、Redis概述 2.1 什么是Redis 2.2 Redis能干什么 2.3 Redis的特点 2.4 Redis与memcached对比 2.5 Redis的安装 2.6 Docker安装 三…

基于Redisson的Redis结合布隆过滤器使用

一、场景 缓存穿透问题 一般情况下&#xff0c;先查询Redis缓存&#xff0c;如果Redis中没有&#xff0c;再查询MySQL。当某一时刻访问redis的大量key都在redis中不存在时&#xff0c;所有查询都要访问数据库&#xff0c;造成数据库压力顿时上升&#xff0c;这就是缓存穿透。…

【Python基础】- break和continue语句

在Python中&#xff0c;break和continue是用于控制循环语句的特殊关键字。 break语句用于跳出当前的循环&#xff08;for循环或while循环&#xff09;&#xff0c;并继续执行紧接着的循环外的代码。它通常用于满足某个条件时提前结束循环。例如&#xff0c;考虑以下示例&#…

《啊哈算法》第三章--枚举 与 暴力

文章目录 前言一、坑爹的奥数二、炸弹人三、火柴棍等式四、全排列总结 前言 前面我们学习了排序和栈 队列 链表&#xff0c;本节就学习暴力枚举的思想。 一、坑爹的奥数 题目1 □3 x 6528 3□ x 8256&#xff0c;在 □ 里填入相同数字使等式成立 代码如下 #include<ios…

PDF在线转PPT,不用下载软件网页在线即可转换!

PDF是我们经常在办公中使用的文件格式&#xff0c;它的兼容性和安全性使得它成为了传输文件的首选。而PPT则是我们常用的演示文稿格式&#xff0c;无论是在学校还是在公司&#xff0c;我们都需要制作演讲和汇报的PPT文件。由于这两种文件格式的重要性&#xff0c;我们经常需要进…

python的魔法函数

一、介绍 在Python中&#xff0c;魔法函数是以双下划线__开头和结尾的特殊函数。它们在类定义中用于实现特定的行为&#xff0c;例如运算符重载、属性访问、迭代等。 以下是一些常见的Python魔法函数&#xff1a; __init__: 这是一个特殊的构造函数&#xff0c;在创建类的实例…

JDBC中的Statement,PreparedStatement和CallableStatement

一旦获得连接&#xff0c;我们就可以与数据库进行交互。JDBC Statement、 CallableStatement 和 PreparedStatement 接口定义了方法和属性&#xff0c;这些方法和属性使您能够发送 SQL 命令并从数据库接收数据。 它们还定义了有助于弥合数据库中使用的 Java 和 SQL 数据类型之…

【阿Q送书第二期】《高并发架构实战:从需求分析到系统设计》

#挑战Open AI&#xff01;马斯克宣布成立xAI&#xff0c;你怎么看&#xff1f;# 文章目录 你想成为架构师嘛&#xff1f;架构经验高并发高并发架构实战特点值得推荐赠书规则 你想成为架构师嘛&#xff1f; 很多软件工程师的职业规划是成为架构师&#xff0c;但是要成为架构师很…

C语言-ubuntu下的命令

目录 linux命令 【1】打开关闭终端 【2】终端 【3】ls命令 【4】cd 切换路径 【5】新建 【6】删除 【7】复制 【8】移动 【9】常用快捷键 【10】vi编辑器 【11】简单编程步骤 任务&#xff1a; linux命令 【1】打开关闭终端 打开终端&#xff1a; 1. 直接点击 …

【1】Vite+Vue3 登录功能

一、介绍 在当今前端开发的领域里&#xff0c;快速、高效的项目构建工具以及使用最新技术栈是非常关键的。ViteVue3 组合为一体的项目实战示例专栏将带领你深入了解和掌握这一最新的前端开发工具和框架。 作为下一代前端构建工具&#xff0c;Vite 在开发中的启动速度和热重载…

sqlserver 获取根据特定符号分割字符串

CREATE function Get_StrArrayStrOfIndex (str varchar(1024), --要分割的字符串split varchar(10), --分隔符号index int --取第几个元素 ) returns varchar(1024) as begindeclare location intdeclare start intdeclare next intdeclare seed intset strltrim(rtrim(str))…

驾驶证——科目一笔记(三)

知识点1&#xff1a;红路灯 黄灯一直闪&#xff1a; 三个框的黄灯——信号暂时解除 一个框的黄灯——危险注意安全 知识点2&#xff1a;通行 看上半部分哪边有三角形 知识点2&#xff1a;禁停 知识点3&#xff1a;导向车道线 有齿可变&#xff0c;无齿导向&#xff08;按…

详解使用JAVA将Julian date(儒略日)日期转换为年月日

一、什么是Julian date 朱莉安日历和普通日历显示是不一样的我就举例演示一下 正常的日历显示 朱莉安的日历显示 174表示的是从2016年1月1日开始到今天已有174天了 普通日历是按月计数&#xff0c;朱莉安日历是按年计数 二、用java将julian日期转换为年月日 将2023199朱莉安…

微信小程序主体名称迁移流程

前言 因一些业务需求&#xff0c;需更换小程序主体名称 按理说&#xff0c;有两种方法&#xff0c; 第一种&#xff1a;重新注册小程序 第二种&#xff1a;在微信公众平台 → 设置 → 基本信息 → 主体信息 → 小程序迁移 第一种方法操作起来是很简单&#xff0c;但是呢&…

ubuntu 20.04 4090 显卡驱动安装 深度学习环境配置

1. 显卡驱动安装 准备工作&#xff1a; 换源安装输入法&#xff1a;重启的步骤先不管&#xff08;自选&#xff09;sudo apt update && sudo apt upgrade 禁用nouveau驱动&#xff08;这个驱动是ubuntu开源小组逆向破解NVIDIA的开源驱动&#xff0c;与英伟达的原有驱…

关于你欠缺的NoSQL中的redis和mongoDB

文章目录 前言一、在string list hash结构中&#xff0c;每个至少完成5个命令&#xff0c;包含插入 修改 删除 查询&#xff0c;list 和hash还需要增加遍历的操作命令1、STRING类型2、List类型数据的命令操作&#xff1a;3、举例说明list和hash的应用场景&#xff0c;每个至少一…

前端开发报表工具所必须的三大能力

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 数据分析一直以来都是业务决策中非常重要的一环&#xff0c;在数字化时代尤其如此。然…

轻松实现文件夹批量重命名!教你如何使用顺序编号批量改名文件夹!

我们需要对大量文件夹进行重命名时&#xff0c;手动一个个改名无疑是一项耗时且繁琐的任务。不过&#xff0c;别担心&#xff01;我们将向您介绍一种简单而高效的方法来实现文件夹的批量重命名&#xff1a;使用顺序编号。无论是对照片文件夹、音乐文件夹、还是其他文件夹进行改…

cuda 安装教程(win10)

cuda&#xff1a;compute unitifed device architecture-统一计算设备架构。 NVIDIA cuDNN is a GPU-accelerated library of primitives for deep neural networks. 目录 cuda完整安装&#xff1a; 检查显卡安装版本 下载版本 cuda安装步骤 cudnn下载安装(需要登陆账号&…