超详细！ES 安装配置全攻略及 PHP 简单使用
遭遇了 “黑天鹅” 事件近期真是遭遇了 “黑天鹅” 事件，公司因卷入非法问题，面临一系列棘手处理，无奈之下只得遣散全体员工，我也因此瞬间失业。原本按部就班的工作节奏戛然而止，每天醒来，看着毫无安排的一天，心里满是茫然与失落。与其在这种不确定中焦虑徘徊，倒不如主动出击，给自己找点事儿做。我打算投身到文档撰写中，既能有效填补空闲时间，让生活重回正轨，也能借此梳理过往积累的知识与经验，为未来求职筑牢根基，让这段意外失业的日子，不至于虚度 。回顾上一篇我们一同探索了 Sphinx 这个强大的全文搜索引擎，从它的功能特性到安装配置，相信大家对如何利用 Sphinx 搭建高效搜索系统有了清晰认知。不过技术的世界从不止步，在搜索领域，还有一位 “大咖”——Elasticsearch（简称 ES），它凭借分布式、高扩展、实时搜索等卓越优势，在企业级应用中大放异彩。这一篇，就让我们把目光聚焦 ES，深入剖析它的安装配置流程，探寻如何在 PHP 开发环境里，巧妙借助 ES 的力量，实现更智能、更高速的数据检索，解锁不一样的搜索体验。什么是Elasticsearch？它有什么优缺点？和 sphinx 相比，差异在哪里？Elasticsearch 是基于 Lucene 的分布式开源搜索引擎，用于全文搜索、数据分析等。优点是分布式、实时性强、功能丰富。缺点是硬件要求高、索引维护成本高。与 Sphinx 相比，ES 分布式和扩展性更好，功能更全面；Sphinx 在特定场景性能较好，配置相对简单，更适合中小规模数据。安装和配置Elasticsearch官网下载地址服务器：CentOS7,Java版本21.0.2(Elasticsearch基于Java开发)没有Java环境的自行去下载合适的版本，并把java加到环境变量中。sudo vim /etc/profile export JAVA_HOME=/www/server/java/jdk-21.0.2 export PATH=$JAVA_HOME/bin:$PATH安装# 创建存放Elasticsearch软件目录 mkdir -p /usr/local/es/ # 进入/usr/local/es/文件夹 cd /usr/local/es/ # 下载对应的glibc版本的sphinx软件包 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.17.2-linux-x86_64.tar.gz # 解压 tar -zxvf elasticsearch-8.17.2-linux-x86_64.tar.gz # es 禁止以root用户运行，所以我们创建一个组和用户，用于启动es # 创建组 elasticsearch groupadd elasticsearch # 创建用户，指定用户所在的组 useradd youer -g elasticsearch # 设置用户密码 passwd youer配置# 进入/usr/local/es/elasticsearch-8.17.2/config 配置目录，修改以下配置 cd /usr/local/es/elasticsearch-8.17.2/config vim elasticsearch.yml # 找到以下配置并且修改 =============================================================================== # 集群名称，用于标识一个 Elasticsearch 集群， # 同一集群内的节点需要具有相同的集群名称才能相互通信并组成集群。 # 这里设置为 "my-es"，你可以根据实际情况修改为合适的名称。 cluster.name: my-es # 节点名称，用于在集群中唯一标识一个节点。 # 每个节点都应有一个独特的名称，这里设置为 "node-1"， # 可以根据实际需求进行调整。 node.name: node-1 # 绑定的网络地址，"0.0.0.0" 表示该节点将监听所有可用的网络接口， # 使得其他机器可以通过网络访问该 Elasticsearch 节点。 # 这在生产环境中需要谨慎配置，以确保安全性。 network.host: 0.0.0.0 # HTTP 服务端口，Elasticsearch 通过此端口提供 HTTP 接口供外部访问， # 这里设置为 9200，是 Elasticsearch 默认的 HTTP 端口， # 可根据实际情况修改，但要避免与其他服务端口冲突。 http.port: 9200 # 种子节点列表，用于在节点启动时发现集群中的其他节点。 # 这里指定了 "127.0.0.1"，表示在本地寻找其他节点。 # 在分布式集群环境中，应填写其他实际节点的 IP 地址。 discovery.seed_hosts: ["127.0.0.1"] # 初始化主节点列表，用于在集群启动时确定哪些节点有资格成为主节点。 # 这里设置为 ["node-1"]，意味着 node-1 节点有资格成为主节点。 # 主节点负责管理集群的状态和元数据等重要任务。 cluster.initial_master_nodes: ["node-1"] # 这一行被注释掉了，原本是用于控制系统调用过滤器的配置。 # bootstrap.system_call_filter: false # 禁止节点在启动时锁定内存。 # Elasticsearch 默认会尝试锁定内存以提高性能，但在某些环境下可能会失败。 # 设置为 false 表示不进行内存锁定，可根据实际情况调整。 bootstrap.memory_lock: false # 启用 HTTP CORS（跨域资源共享）功能。 # 当设置为 true 时，允许来自不同域名的请求访问 Elasticsearch 的 HTTP 接口。 # 这在浏览器或其他跨域访问场景中很有用。 http.cors.enabled: true # 允许的跨域来源，这里设置为 "*" 表示允许所有来源的请求。 # 在生产环境中，建议明确指定具体的域名，以提高安全性。 http.cors.allow-origin: "*" ===============================================================================Elasticsearch配置JVM参数Elasticsearch配置JVM参数合理配置JVM参数对其性能和稳定性至关重要。-Xms 设定JVM堆内存初始大小，避免频繁内存分配；-Xmx 限制堆内存最大是多少，防止过度占用系统资源。建议二者设为相同值，依实际情况调整。cd /usr/local/es/elasticsearch-8.17.2/config vim jvm.options # 我的设置是根据我服务器的配置来的，你们根据实际情况而定 -Xms512m -Xmx512m增加ES最大文件、进程数限制，保其稳定运行/etc/security/limits.conf 通用资源限制配置文件，可全面设限。/etc/security/limits.d/20-nproc.conf 用于补充细化，配置优先级或更高。sudo vim /etc/security/limits.conf ============================================ # 指定用户设置软限制的最大打开文件描述符数量 youer soft nofile 65536 # 指定用户设置硬限制的最大打开文件描述符数量 youer hard nofile 65536 # 指定用户设置软限制的最大进程数量 youer soft nproc 4096 # 指定用户设置硬限制的最大进程数量 youer hard nproc 4096 ============================================sudo vim /etc/security/limits.conf ====================================== youer soft nproc 4096 youer hard nproc 4096 ====================================== 或者 ===================================== * soft nproc 4096 root soft nproc unlimited =====================================调整系统的内核参数,保证Elasticsearch应用跑起来sudo vim /etc/sysctl.conf ================================ # 设置虚拟内存中最大内存映射区域的数量 # 对于像 Elasticsearch 这类应用，在启动和运行过程中会进行大量的内存映射操作 # 若该值过小，可能会导致 Elasticsearch 启动失败或运行过程中出现内存映射错误 # 这里将其设置为 655360，为应用提供更充足的内存映射资源 vm.max_map_count=655360 # 设置系统允许的最大文件数 # 系统在运行时会涉及大量文件的打开、读写等操作 # 增大该值可以确保系统有足够的资源来处理更多的文件操作 # 避免因文件描述符耗尽而导致应用无法正常工作 # 这里设置为 65535 fs.file-max = 65535 ================================ # 读取配置并应用新的内核参数，使修改后的配置立即生效 sudo sysctl -p安装ik插件ik插件下载，下载与之对应的ik中文分词插件cd /usr/local/es/ wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.17.2.zip unzip elasticsearch-analysis-ik-8.17.2.zip mv elasticsearch-analysis-ik-8.17.2 /usr/local/es/elasticsearch-8.17.2/plugins/权限# 给文件夹赋权和用户以及组 sudo chown -R youer:elasticsearch /usr/local/es/elasticsearch-8.17.2 sudo chmod -R 755 /usr/local/es/elasticsearch-8.17.2启动# 切换到youer用户 su youer # 启动es cd /usr/local/es/elasticsearch-8.17.2/bin ./elasticsearch -d查看端口和es服务# 查看端口 ps -ef | grep elasticsearch|grep -v "grep" netstat -an | grep 9200 # 对外防火墙放开端口 # 查询是否开启 sudo firewall-cmd --query-port=9200/tcp # 开启 sudo firewall-cmd --add-port=9200/tcp --permanent # 重启生效 sudo firewall-cmd --reloadphp使用安装类库 composer require elasticsearch/elasticsearchdeclare(strict_types=1); set_time_limit(-1); require __DIR__ . "./vendor/autoload.php"; use Elastic\Elasticsearch\ClientBuilder; try { $client = ClientBuilder::create() ->setHosts(['http://142.171.102.86:9200']) ->setSSLVerification(false) ->build(); // 定义用于创建 Elasticsearch 索引的参数数组 $t = $_GET['type'] ?? 1; switch ($t) { case 1://创建索引时配置 IK 分词器 $params = [ // 指定要创建的索引的名称为 my_ik_index // 在 Elasticsearch 里，索引是存储文档的逻辑容器，类似关系型数据库中的数据库 // 后续对该索引的操作（如添加文档、搜索等）都会使用此名称定位 'index' => 'my_ik_index', // 包含索引设置和映射信息的数组，是创建索引请求的核心部分 'body' => [ // 用于设置索引的各种参数，这里主要关注与分词分析相关的设置 'settings' => [ // 配置索引的分析器和分词器 // 分析器负责将文本转换为适合索引和搜索的词项，分词器是分析器的核心，负责拆分文本 'analysis' => [ // 定义自定义分析器的部分，可以配置多个分析器，每个有唯一名称 'analyzer' => [ // 自定义分析器 ik_max_word_analyzer 'ik_max_word_analyzer' => [ // 表示这是一个自定义分析器 // 除内置分析器外，可自定义组合分词器、过滤器等创建特定分析器 'type' => 'custom', // 指定该分析器使用 ik_max_word 作为分词器 // ik_max_word 是 IK 分词器的一种模式，会对文本做最细粒度拆分 'tokenizer' => 'ik_max_word' ], // 自定义分析器 ik_smart_analyzer 'ik_smart_analyzer' => [ // 表示这是一个自定义分析器 'type' => 'custom', // 指定该分析器使用 ik_smart 作为分词器 // ik_smart 是 IK 分词器的另一种模式，会对文本做最粗粒度拆分 'tokenizer' => 'ik_smart' ] ] ] ], // 定义索引中文档的结构和字段类型，以及每个字段使用的分析器 'mappings' => [ // 定义索引中各个字段的属性，每个字段有唯一名称，可配置类型、分词器等 'properties' => [ // 定义 title 字段 'title' => [ // 指定 title 字段的类型为 text，用于存储文本数据 // text 类型字段在索引时会进行分词处理以支持全文搜索 'type' => 'text', // 指定在对 title 字段进行索引时使用 ik_max_word_analyzer 分析器 // 这样在添加文档到索引时，title 字段文本会被 ik_max_word 分词器细粒度拆分 'analyzer' => 'ik_max_word_analyzer', // 指定在对 title 字段进行搜索时使用 ik_smart_analyzer 分析器 // 搜索时，搜索关键词会被 ik_smart 分词器粗粒度拆分以提高搜索准确性 'search_analyzer' => 'ik_smart_analyzer' ], // 定义 content 字段 'content' => [ // 指定 content 字段的类型为 text，用于存储文本数据 'type' => 'text', // 指定在对 content 字段进行索引时使用 ik_max_word_analyzer 分析器 // 索引时，content 字段文本会被 ik_max_word 分词器细粒度拆分 'analyzer' => 'ik_max_word_analyzer', // 指定在对 content 字段进行搜索时使用 ik_smart_analyzer 分析器 // 搜索时，搜索关键词会被 ik_smart 分词器粗粒度拆分以提高搜索准确性 'search_analyzer' => 'ik_smart_analyzer' ] ] ] ] ]; $response = $client->indices()->create($params); if ($response['acknowledged']) { echo "索引创建成功！"; } else { echo "索引创建失败！"; } break; case 2://添加文档 $params = [ 'index' => 'my_ik_index', 'id' => '1', 'body' => [ 'title' => '自然语言处理技术', 'content' => '自然语言处理是人工智能的一个重要领域。' ] ]; $response = $client->index($params); // 判断添加文档操作是否成功 if (isset($response['result']) && $response['result'] === 'created') { echo "文档添加成功，文档 ID 为: " . $response['_id']; } else { echo "文档添加失败，错误信息: " . json_encode($response); } break; case 3://搜索文档 $query = "领域"; $params = [ 'index' => 'my_ik_index', 'body' => [ 'query' => [ 'multi_match' => [ 'query' => $query, 'fields' => ['title', 'content'], 'analyzer' => 'ik_smart_analyzer' ] ] ] ]; $response = $client->search($params); if (isset($response['hits']['total']['value']) && $response['hits']['total']['value'] > 0) { echo "搜索成功，共找到 " . $response['hits']['total']['value'] . " 个匹配文档： "; foreach ($response['hits']['hits'] as $hit) { $source = json_encode($hit['_source'], JSON_UNESCAPED_UNICODE); $source = str_replace($query, "{$query}",$source); echo "文档 ID: " . $hit['_id'] . ", 得分: " . $hit['_score'] . ", 内容: " . $source . " "; } } elseif (isset($response['hits']['total']['value']) && $response['hits']['total']['value'] === 0) { echo "搜索成功，但未找到匹配的文档。"; } else { echo "搜索失败，错误信息: " . json_encode($response); } break; case 4://删除文档 $params = [ 'index' => 'my_index' ]; // 执行删除索引操作 $response = $client->indices()->delete($params); // 判断删除是否成功 if (isset($response['acknowledged']) && $response['acknowledged'] === true) { echo "索引删除成功。"; } else { echo "索引删除失败，错误信息: " . json_encode($response); } break; case 5://获取集群健康状态 // 调用 _cluster/health API 获取集群健康状态 $params = [ 'index' => null, // 不指定特定索引，获取整个集群的健康状态 ]; $response = $client->cluster()->health($params); // 解析和处理返回结果 $status = $response['status']; echo "Elasticsearch 集群健康状态: ". $status. "\n"; switch ($status) { case 'green': echo "集群状态良好，所有主分片和副本分片都可用。\n"; break; case 'yellow': echo "集群状态正常，但存在部分副本分片不可用。\n"; break; case 'red': echo "集群状态异常，存在主分片不可用的情况。\n"; break; default: echo "未知的集群健康状态。\n"; } break; } } catch (Exception $e) { echo "发生错误：" . $e->getMessage(); } 查询结果访问 http://142.171.102.86:9200查询
一步到位！Sphinx 安装配置全流程，PHP 轻松对接
遭遇了 “黑天鹅” 事件近期真是遭遇了 “黑天鹅” 事件，公司因卷入非法问题，面临一系列棘手处理，无奈之下只得遣散全体员工，我也因此瞬间失业。原本按部就班的工作节奏戛然而止，每天醒来，看着毫无安排的一天，心里满是茫然与失落。与其在这种不确定中焦虑徘徊，倒不如主动出击，给自己找点事儿做。我打算投身到文档撰写中，既能有效填补空闲时间，让生活重回正轨，也能借此梳理过往积累的知识与经验，为未来求职筑牢根基，让这段意外失业的日子，不至于虚度 。什么是Sphinx? 有什么优缺点呢？phinx 是开源全文搜索引擎。优点是高性能、功能丰富、可扩展性好、多语言支持、开源成本低；缺点是配置复杂、对内存要求高、实时性有限、缺乏图形化管理界面。安装和配置sphinx官网下载页面服务器：CentOS7，glibc版本2.17安装# 创建存放sphinx软件目录 mkdir -p /usr/local/sphinx/ # 进入/usr/local/sphinx/文件夹 cd /usr/local/sphinx/ # 下载对应的glibc版本的sphinx软件包 wget https://sphinxsearch.com/files/sphinx-3.7.1-da9f8a4-linux-amd64-glibc2.17.tar.gz # 解压 tar -zxvf sphinx-3.7.1-da9f8a4-linux-amd64-glibc2.17.tar.gz配置# 进入到配置目录 cd /usr/local/sphinx/sphinx-3.7.1/etc/ # 创建文件，把配置粘贴进去 vim sphinx.conf ====================================================== # 通用配置部分，定义一些全局的设置 common { # 数据目录，用于存放索引文件和其他相关数据 datadir = ./sphinxdata } # 数据源配置，这里以 MySQL 数据库为例 source names_1_source { # 数据源类型为 MySQL type = mysql # 数据库主机地址，使用 127.0.0.1 以确保端口配置生效 sql_host = 127.0.0.1 # 数据库用户名 sql_user = active # 数据库密码 sql_pass = PcjcrN53sR8bdZyx # 要连接的数据库名 sql_db = active # 数据库端口号，默认是 3306 sql_port = 3306 # SQL 查询语句，用于从数据库中获取要索引的数据 sql_query = SELECT id, surname, name, first, second, gender, type, score, details FROM names_1 # 在执行查询前设置字符集为 UTF-8，确保中文等字符能正确处理 sql_query_pre = SET NAMES utf8mb4 } # 索引配置 index names_1_index { # 索引类型为普通索引 type = plain # 关联的数据源 source = names_1_source # 要进行全文搜索的字段 field = surname, details # 定义属性，用于排序、过滤等操作 attr_uint = gender, type # 无符号整数类型的属性 attr_float = score # 浮点数类型的属性 attr_string = name, first, second # 字符串类型的属性 # 字符集表，涵盖了数字、英文字母（大小写统一）、中文、日文、韩文等字符范围 # charset_table = # 最小词长度，设置为 1 表示单字符也会被索引，适用于中文和单字母搜索 min_word_len = 1 # 词形处理方式，这里设置为不进行词形处理 morphology = none # N - gram 长度，设置为 1 表示按单字符进行 N - gram 索引，有助于中文搜索 ngram_len = 1 # 指定用于 N - gram 索引的字符范围，这里包含了中文、日文、韩文等字符 ngram_chars = U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, U+A492..U+A4C6 } # 搜索服务配置 searchd { # 监听的地址和端口，客户端通过此端口连接搜索服务 listen = 9312 # 搜索服务的日志文件路径，记录服务的运行信息 log = no # 查询日志文件路径，记录用户的查询请求，方便后续分析和优化 query_log = no # 进程 ID 文件路径，用于管理搜索服务进程 # pid_file = "" # 启用无缝索引切换，在重建索引时不影响搜索服务的正常运行 seamless_rotate = 1 # 是否预先打开所有索引文件，根据服务器内存和性能情况调整 preopen_indexes = 0 # 自动删除旧的索引文件，节省磁盘空间 unlink_old = 1 # 客户端连接超时时间（秒） client_timeout = 30 } ======================================================测试 数据库active下的表names_1大概是这样的启动sphinx和停止# 可读权限 sudo chmod -R a+r /usr/local/sphinx/sphinx-3.7.1 # 启动 ./searchd -c /usr/local/sphinx/sphinx-3.7.1/etc/sphinx.conf # 停止 ./searchd --stop -c /usr/local/sphinx/sphinx-3.7.1/etc/sphinx.conf创建和更新索引sudo chmod -R a+r /usr/local/sphinx/sphinx-3.7.1 sudo chmod u+w /usr/local/sphinx/sphinx-3.7.1/bin/indexer cd /usr/local/sphinx/sphinx-3.7.1/bin/ ./indexer -c /usr/local/sphinx/sphinx-3.7.1/etc/sphinx.conf --all --rotate # ./indexer是 Sphinx 索引器程序。 # -c /usr/local/sphinx/sphinx-3.7.1/etc/sphinx.conf指定配置文件路径。 # --all对配置里所有索引操作。 # --rotate在searchd运行时无缝更新索引，不中断搜索服务。查看端口和sphinx服务# 查看端口 ps -ef | grep searchd|grep -v "grep" netstat -an | grep 9312 # 对外防火墙放开端口 # 查询是否开启 sudo firewall-cmd --query-port=9312/tcp # 开启 sudo firewall-cmd --add-port=9312/tcp --permanent # 重启生效 sudo firewall-cmd --reloadphp 使用首先复制一份/usr/local/sphinx/sphinx-3.7.1/api/sphinxapi.php 文件到你的项目目录。<?php declare(strict_types=1); require __DIR__.'./vendor/autoload.php'; require __DIR__."/sphinxapi.php"; use Fukuball\Jieba\Jieba; //use Fukuball\Jieba\Posseg; ini_set('memory_limit','512M'); //分词 Jieba::init(); //Posseg::init(); $searchQuery = 'Firaxis'; if (hasChinese($searchQuery)) { $cutWord = Jieba::cut($searchQuery); $searchQuery = implode('|', $cutWord); } echo '查找关键词：'.$searchQuery.' '; // 创建 Sphinx 客户端对象 $cl = new SphinxClient(); // 设置 Sphinx 服务器的主机和端口 $cl->SetServer('127.0.0.1',9312); $t = $_GET['type'] ?? 1; switch ($t) { case 1: // 设置搜索的偏移量和返回结果数量 $cl->SetLimits(0, 10); // 设置排序模式为按相关性排序 $cl->SetSortMode(SPH_SORT_RELEVANCE); $index = 'names_1_index'; // $cl->SetFilterString('details','Firaxis'); $result = $cl->query($searchQuery, $index); if ($result === false) { echo '查询错误: '. $cl->GetLastError(); } else { // 处理查询结果 if ($result['total'] > 0) { foreach ($result['matches'] as $id => $match) { echo '文档ID: '. $id. ',姓名：'.$match['attrs']['name'].',score：'.$match['attrs']['score']. ',相关性得分: '. $match['weight']. ' '; // 可以根据索引中的字段获取更多信息 } } else { echo '没有找到匹配的结果'; } } break; case 2: //TODO 更新数据库 $index = 'names_1_index'; $attrs = ['score']; $values = [ 4048551 => [100], // 文档 ID 为 1 的文档，将 'price' 属性更新为 100 4048552 => [200] // 文档 ID 为 2 的文档，将 'price' 属性更新为 200 ]; $updated = $cl->UpdateAttributes($index, $attrs, $values); if ($updated >= 0) { echo "成功更新 $updated 条文档的属性。"; } else { echo "更新属性时出错: ". $cl->GetLastError(); } break; } function hasChinese($str) { for ($i = 0; $i < mb_strlen($str); $i++) { $char = mb_substr($str, $i, 1); $ord = mb_convert_encoding($char, 'UCS-2BE', 'UTF-8'); $code = hexdec(bin2hex($ord)); if ($code >= 0x4E00 && $code <= 0x9FA5) { return true; } } return false; } 查询结果[post cid="388" /]
当你使用get_browser函数时出现一下错误提示Warning: get_browser(): browscap ini directive not set in ...因为get_browser的实现原理是通过查找browscap.ini配置文件中的浏览器信息，从而尝试检测用户的浏览器所具有的功能。既然依赖于browscap.ini配置文件，就需要去下载这个配置文件，因为这个浏览器版本等都是处于经常变化的状态，所以这种文件必须到browscap官网下载而且保持更新。下载browscap配置文件，版本自行根据需要下载，然后配置php.ini，重启php-fpm[browscap] browscap=/www/server/php/74/etc/lite_php_browscap.ini下载地址这是browscap.ini的一个特殊版本，仅供PHP用户使用！php_browscap.ini (23,321 KB)这是php_browscap.ini的一个更大版本，包含了所有新属性。full_php_browscap.ini (113,097 KB)这是php_browscap.ini文件的较小版本，包含主要的浏览器和搜索引擎。此文件适用于大多数网站。lite_php_browscap.ini (1,114 KB)
所谓的压缩，无非就是把换行空格等符号替换为空css<?php header('Content-type: text/css'); ob_start("compress"); function compress($buffer) { $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer); $buffer = str_replace(array(" ", "\r", "\n", "\t", ' ', ' ', ' '), '', $buffer); return $buffer; } //文件太多的话可以自行写一个递归 include('第一个CSS文件'); //例如当前PHP文件下的 ./test.css include('第二个CSS文件'); //复制调用更多css文件 ./test1.css ob_end_flush(); //浏览器打开，输出压缩后的内容 ?>js<?php error_reporting(E_ALL & ~E_NOTICE); if (extension_loaded('zlib')) { ob_start('ob_gzhandler'); } header("content-type:application/x-javascript; charset: UTF-8"); header("cache-control: must-revalidate"); $offset = 60 * 60 * 24; //js文件的距离现在的过期时间，这里设置为一天 $expire = "expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT"; header($expire); ob_start("compress"); function compress($buffer) { $buffer = preg_replace('/\/\/.*/', '', $buffer); $buffer = str_replace(array(" ", "\r", "\n", "\t", ' ', ' ', ' '), '', $buffer); return $buffer; } include('js文件一'); //例如当前PHP文件下的main.js include('js文件二'); //复制调用更多JS文件 if (extension_loaded('zlib')) { ob_end_flush(); //输出压缩后的内容 } ?>
(防抖) 前后端防重复提交常用的那些方法
防抖（Debounce）是一种防止重复提交的策略，它通过延迟一定时间来合并连续的操作，以确保只执行一次。前端 JavaScript 实现function debounce(func, delay) { let timer; return function() { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, arguments); }, delay); }; } const submitForm = debounce(function() { // 在这里执行表单提交操作 }, 1000); // 延迟 1 秒执行 Submit 防抖Session Token防抖// 生成随机的 token $token = md5(uniqid()); // 存储 token 到会话 $_SESSION['submit_token'] = $token; // 在表单中嵌入 token echo ' '; // 处理表单提交时的校验 if ($_POST['submit_token'] === $_SESSION['submit_token']) { // 处理表单提交 // 清除会话中的 token unset($_SESSION['submit_token']); }Token防抖（利用随机生成的 token 来防止重复提交）$token = md5(uniqid()); echo ' '; if ($_POST['submit_token'] === $token) { // 处理表单提交 }时间戳防抖$currentTime = time(); $lastSubmitTime = $_SESSION['last_submit_time'] ?? 0; if ($currentTime - $lastSubmitTime > 5) { // 处理表单提交 $_SESSION['last_submit_time'] = $currentTime; } IP地址防抖 （利用IP地址来防止相同IP的重复提交）$userIP = $_SERVER['REMOTE_ADDR']; $lastSubmitIP = $_SESSION['last_submit_ip'] ?? ''; if ($userIP !== $lastSubmitIP) { // 处理表单提交 $_SESSION['last_submit_ip'] = $userIP; } Cookie防抖 （利用Cookie来防止在一段时间内的重复提交）if (!isset($_COOKIE['submit_cookie'])) { // 处理表单提交 setcookie('submit_cookie', 'submitted', time() + 60); // 60秒内不允许重复提交 }延时防抖（在最后一次操作后的一段时间内，只执行一次提交操作）if (!isset($_SESSION['submit_timer'])) { $_SESSION['submit_timer'] = time(); } if (time() - $_SESSION['submit_timer'] > 10) { // 处理表单提交 $_SESSION['submit_timer'] = time(); } 数据库唯一性约束（利用数据库的唯一性约束来防止重复插入数据）try { // 尝试插入数据，如果插入失败会抛出异常 // 添加数据库的唯一性索引或唯一性约束以防止重复数据 } catch (Exception $e) { // 处理插入失败的情况 } 缓存防抖（利用缓存系统来记录提交状态）$cacheKey = 'submit_status_' . $userIP; if (!cache_get($cacheKey)) { // 处理表单提交 cache_set($cacheKey, 'submitted', 60); // 60秒内不允许重复提交 }验证码防抖（要求用户输入特定的验证码来提交表单，防止恶意重复提交）if ($_POST['captcha'] === $_SESSION['captcha_code']) { // 处理表单提交 // 清除验证码，以防止多次使用同一个验证码 unset($_SESSION['captcha_code']); }
什么是MongoDBMongoDB 是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。他支持的数据结构非常松散，是类似json的bson格式，因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大，其语法有点类似于面向对象的查询语言，几乎可以实现类似关系数据库单表查询的绝大部分功能，而且还支持对数据建立索引。特点高性能、易部署、易使用，存储数据非常方便。面向集合存储，易存储对象类型的数据。模式自由。支持动态查询。支持完全索引，包含内部对象。支持查询。支持复制和故障恢复。使用高效的二进制数据存储，包括大型对象（如视频等）。自动处理碎片，以支持云计算层次的扩展性。支持RUBY，PYTHON，JAVA，C ，PHP，C#等多种语言。文件存储格式为BSON（一种JSON的扩展）。使用原理- 所谓“面向集合”（Collection-Oriented），意思是数据被分组存储在数据集中，被称为一个集合（Collection)。每个集合在数据库中都有一个唯一的标识名，并且可以包含无限数目的文档。 - 集合的概念类似关系型数据库（RDBMS）里的表（table），不同的是它不需要定义任何模式（schema)。Nytro MegaRAID技术中的闪存高速缓存算法，能够快速识别数据库内大数据集中的热数据，提供一致的性能改进。 - 模式自由（schema-free)，意味着对于存储在mongodb数据库中的文件，我们不需要知道它的任何结构定义。如果需要的话，你完全可以把不同结构的文件存储在同一个数据库里。 - 存储在集合中的文档，被存储为键-值对的形式。键用于唯一标识一个文档，为字符串类型，而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON（Binary Serialized Document Format）。 适用场景- MongoDB 的主要目标是在键/值存储方式（提供了高性能和高度伸缩性）和传统的RDBMS 系统（具有丰富的功能）之间架起一座桥梁，它集两者的优势于一身。根据官方网站的描述，Mongo 适用于以下场景。 - 网站数据：Mongo 非常适合实时的插入，更新与查询，并具备网站实时数据存储所需的复制及高度伸缩性。 - 缓存：由于性能很高，Mongo 也适合作为信息基础设施的缓存层。在系统重启之后，由Mongo 搭建的持久化缓存层可以避免下层的数据源过载。 - 大尺寸、低价值的数据：使用传统的关系型数据库存储一些数据时可能会比较昂贵，在此之前，很多时候程序员往往会选择传统的文件进行存储。 - 高伸缩性的场景：Mongo 非常适合由数十或数百台服务器组成的数据库，Mongo 的路线图中已经包含对MapReduce 引擎的内置支持。 - 用于对象及JSON 数据的存储：Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。 不适场景- 高度事务性的系统：例如，银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。 - 传统的商业智能应用：针对特定问题的BI 数据库会产生高度优化的查询方式。对于此类应用，数据仓库可能是更合适的选择。 - 需要SQL 的问题。 Mognodb数据库连接<?php // 默认格式 (这里采用默认连接本机的27017端口，当然也可以连接远程主机。如 192.168.0.4:27017,如果端口是27017，端口可以省略。) $m = new Mongo(); //实例 (数据库的用户名和密码都是admin) $m = new Mongo("mongodb://127.0.0.1:27017/admin:admin");插入数据<?php //这里采用默认连接本机的27017端口，当然你也可以连接远程主机如192.168.0.4:27017 //如果端口是27017，端口可以省略 $m = new Mongo("mongodb://127.0.0.1:27017/admin:admin"); //选择comedy数据库，如果以前没该数据库会自动创建，也可以用$m->selectDB("comedy"); $db = $m->comedy; //选择comedy里面的collection集合，相当于RDBMS里面的表，也可以使用 $collection = $db->collection; $db->selectCollection("collection"); /*********添加一个元素**************/ $obj = array("title" => "php1", "author" => "Bill Watterson"); //将$obj 添加到$collection 集合中 $collection->insert($obj); /*********添加另一个元素**************/ $obj = array("title" => "huaibei", "online" => true); $collection->insert($obj); //$query = array("title" => "huaibei"); $query = array("_id" => $obj['_id']); $cursor = $collection->find($query); //遍历所有集合中的文档 foreach ($cursor as $obj) { echo $obj["title"] . "\n"; echo $obj["_id"] . "\n"; } //断开MongoDB连接 $m->close();带条件的查询mysql: id = 123 mongo: array(‘id'=>123) mysql: name link '%bar%' mongo: array(‘name' => new MongoRegex(‘/.*bar.*/i')) mysql: where id > 10 mongo: array(‘id' => array(‘$gt' => 10)) mysql: where id >= 10 mongo: array(‘id' => array(‘$gte' => 10)) mysql: where id < 10 mongo: array(‘id' => array(‘$lt' => 10)) mysql: where id <= 10 mongo: array(‘id' => array(‘$lte' => 10)) mysql: where id > 1 and id < 10 mongo: array(‘id' => array(‘$gt' => 1,'$lt' => 10)) mysql: where id <> 10 mongo: array(‘id' => array(‘$ne' => 10)) mysql: where id in(123) mongo: array(‘id' => array(‘$in' => array(1,2,3))) mysql: where id not in(123) mongo: array(‘id' => array(‘$nin' => array(1,2,3))) mysql: where id = 2 or id = 9 mongo: array(‘id' => array(‘$or' => array(array(‘id'=>2),array(‘id'=>9)))) mysql: order by name asc mongo: array(‘sort'=>array(‘name'=>1)) mysql: order by name desc mongo: array(‘sort'=>array(‘name'=>-1)) mysql: limit 0,2 mongo: array(‘limit'=>array(‘offset'=>0,'rows'=>2)) mysql: select name,email mongo: array(‘name','email') mysql: select count(name) mongo: array(‘COUNT') //注意：COUNT为大写查询时，每个Object插入时都会自动生成一个独特的_id,它相当于RDBMS中的主键，用于查询时非常方便 （_id每一都不同，很像自动增加的id）<?php $param = array("name" => "joe"); $collection->insert($param); $joe = $collection->findOne(array("_id" => $param['_id'])); print_R($joe); $m->close(); //返回结果：Array ( [_id] => MongoId Object ( [$id] => 4fd30e21870da83416000002 ) [name] => joe )更改字段值<?php $sign = array("title" => 'php1'); $param = array("title" => 'php1','author'=>'test'); $joe = $collection->update($sign, $param);删除一个数据库$m -> dropDB(“comedy”);列出所有可用数据库$m->listDBs(); //无返回值创建一个MongoDB对象<?php $mo = new Mongo(); $db = new MongoDB($mo,'dbname');//通过创建方式获得一个MongoDB对象删除当前DB<?php $db = $mo->dbname; $db->drop();获得当前数据库名<?php $db = $mo->dbname; $db->_tostring();选择想要的collection//A: $mo = new Mongo(); $coll = $mo->dbname->collname;//获得一个collection对象 //B： $db = $mo->selectDB('dbname'); $coll = $db->collname; //C: $db = $mo->dbname; $coll = $db->collname; //D: $db = $mo->dbname; $coll = $db->selectCollectoin('collname');//获得一个collection对象插入数据（MongoCollection对象）$coll = $mo->db->foo; $a = array('a'=>'b'); $options = array('safe'=>true); $rs =$coll->insert($a,$options);删除数据库中的记录（MongoCollection对象）$coll = $mo->db->coll; $c = array('a'=>1,'s'=>array('$lt'=>100)); $options = array('safe'=>true);更新数据库中的记录（MongoCollection对象）$coll = $mo->db->coll; $c = array('a'=>1,'s'=>array('$lt'=>100)); $newobj = array('e'=>'f','x'=>'y'); $options = array('safe'=>true,'multiple'=>true);查询collection获得单条记录（MongoCollection类）$coll = $mo->db->coll; $query = array('s'=>array('$lt'=>100)); $fields = array('a'=>true,'b'=>true); $rs = $coll->findOne($query,$fields);查询collection获得多条记录（MongoCollection类）$coll = $mo->db->coll; $query = array('s'=>array('$lt'=>100)); $fields = array('a'=>true,'b'=>true); $cursor = $coll->find($query,$fields); //排序 $cursor->sort(array(‘字段'=>-1));(-1倒序，1正序) //跳过部分记录 $cursor->skip(100);跳过100行 //只显示部分记录 $cursor->limit(100);只显示100行 返回一个游标记录对象MongoCursor。 针对游标对象MongoCursor的操作(MongoCursor类)$cursor = $coll->find($query,$fields); while($cursor->hasNext()){ $r = $cursor->getNext(); var_dump($r); } //或者 $cursor = $coll->find($query,$fields); foreache($cursor as $k=>$v){ var_dump($v); } //或者 $cursor = $coll->find($query,$fields); $array= iterator_to_array($cursor);PHP操作Mongodb封装类完整实例<?php /** * Mongodb 基本操作API，支持基本类似关系统型数据库的操作接口 * * * [说明] * * 1：该版本API实现了 Mongodb 中最基本的插入/修改/查询/删除操作的封装 * 2：其它更高级的操作可通过 $this->getMongo() 得到原生的对象，更多API请自行查阅 Mongo PHP手册,后续版本将会对增加更多的原生API封装 * 3：该类所有API接口中的 $query 查询参数的使用请以下有关 [查询条件说明文档] * 4: 如果要存储中文字符，则一定要使用 utf8 的编码． * 5：有了本类接口基本可以按关系型数据库的概念完成Mongodb的大部分开发操作。 * * [查询条件说明文档] * * 参数：array('id'=>1) * 等同：where id=1 * * 参数：array('id'=>1,'num'=>10) * 等同：where id=1 and num=10 * * 参数：array('id'=>array($mongo->cmd('>')=>5)) * 等同：where id>5 * * 参数：array('id'=>array($mongo->cmd('!=')=>5)) * 等同：where id!=5 * * 参数：array('id'=>array($mongo->cmd('>')=>5, $mongo->cmd('<')=>10)) * 等同：where id>5 and id<10 * * 参数：array('id'=>array($mongo->cmd('in')=>array(2,5,6))) * 等同：where id in (2,5,6) * * 参数：array('id'=>array($mongo->cmd('%')=>array(2,1))) * 等同：where id % 2 = 1 * * 参数：array($mongo->cmd('or') => array( array('id'=>array($mongo->cmd('>')=>5)), array('id'=>array($mongo->cmd('<')=>10)) ) ) * 等同：where id>5 or id<10 * * 参数：array('username' => new mongoRegex("/yhp.*/")) * 等同 where username like "%yhp%" **/ class Library_Mongodb { /** * Mongodb 对象句柄 * * @var object Mongo */ private $_mongo = null; /** * 当前选择的数据库 * * @var object MongoDB */ private $_db = null; /** * 修改器命令前缀 * * @var string */ private $_cmd = '$'; /** * 调试模式 TRUE 打开 FALSE 关闭 * @var boolean */ const DEBUG = TRUE; /** * 查询条件映射关系 * * @var array */ private $_condMap = array( '<' => 'lt', // id > 1 '<=' => 'lte', // id <= 10 '>' => 'gt', // id > 5 '>=' => 'gte', // id >= 4 '!=' => 'ne', // id != 4 '%' => 'mod', // id % 4 = 0 'in' => 'in', // id in (1,2,3,4) 'notin' => 'nin',// id not in (1,2,3,4) 'or' => 'or', // id=1 or id=2 'not' => 'not', // !(id=1) ); /** * 构造函数 * * @param array $config 服务器配置,默认为: * array( * 'host'=>'localhost', // 主机名或IP地址 * 'port'=>27017, // 端口 * 'cmd'=>'$', // 修改器命令前缀 * ) */ public function __construct($config = array('host' => 'xxx', 'port' => 27017, 'username' => 'xxx', 'password' => 'xxx', 'db' => 'xxx', 'cmd' => '$')){ $server = sprintf("mongodb://%s:%s@%s:%s/%s", $config['username'], $config['password'], $config['host'], $config['port'], $config['db']); // echo "connect\n"; try { $this->_mongo = new MongoClient($server, array('connect'=>true));// 立即连接 }catch (MongoConnectionException $e){ if(self::DEBUG) { echo $e->getMessage(); } return false; } $this->selectDB($config['db']); // 命令前缀 if(!isset($config['cmd'])){ $this->_cmd = ini_get('mongo.cmd'); if($this->_cmd == ''){ $this->_cmd = '$'; } } } /* ==================================== 基本操作接口API ================================= */ /** * 向集合(表)中插入新文档 * * 说明： * 1:类似mysql中的: insert into $colName set id=1,name='name1'; * * @param string $colName 集合名 * @param array $sets 数据,如: array('id'=>1,'name'=>'name1') * @param boolean $safe 是否安全操作 false:不等待服务器的响应直接返回 true:等待服务器的响应(数据非常重要时推荐) * @param boolean $fsync 操作后是否立即更新到碰盘,默认情况下由服务器决定 * * @return boolean */ public function insert($colName, $sets, $safe=false, $fsync=false){ $col = $this->_getCol($colName); try { $col->insert($sets,array('w'=>$safe,'fsync'=>$fsync)); return true; }catch (MongoCursorException $e){ return false; } } /** * 保存文档 * * 说明： * 1:如果 $sets 中有字段 "_id" 的话，则更新对应的文档；否则插入新文档 * * @param string $colName 集合名 * @param array $sets 数据,如: array('id'=>1,'name'=>'name1') * @param boolean $safe 是否安全操作 false:不等待服务器的响应直接返回 true:等待服务器的响应(数据非常重要时推荐) * @param boolean $fsync 操作后是否立即更新到碰盘,默认情况下由服务器决定 * * @return boolean */ public function save($colName, $sets, $safe=false, $fsync=false){ // 处理 '_id' 字段 $sets = $this->_parseId($sets); $ret = $this->_getCol($colName)->save($sets,array('w'=>$safe,'fsync'=>$fsync)); return $ret; } /** * 删除集合中的文档记录 * * 说明： * 1：类似mysql中的: delete from $colName where id=1; * * @param string $colName 集合名 * @param array $query 查询条件,如果为空数组的话，则会删除所有记录．具体请看 [查询条件说明文档] * @param boolean $delAll 是否删除所以条例查询的记录,默认为 true,当为 false是，类似效果 delete from tab where id=1 limit 1; * @param boolean $safe 是否安全操作 false:不等待服务器的响应直接返回 true:等待服务器的响应(数据非常重要时推荐) * @param boolean $fsync 操作后是否立即更新到碰盘,默认情况下由服务器决定 * * @return boolean */ public function delete($colName,$query=array(),$delAll=true,$safe=false,$fsync=false){ // 自动处理 '_id' 字段 $query = $this->_parseId($query); // 删除选项 $options = array( 'justOne' => !$delAll, 'w' => $safe, 'fsync' => $fsync, ); $col = $this->_getCol($colName); return $col->remove($query,$options); } /** * 删除整个集合 * * 说明： * 1：集合中的索引也会被删除 * * @param string $colName 集合名 * * @return array */ public function dropCol($colName){ return $this->_getCol($colName)->drop(); } /** * 更新集合文档记录 * * 说明: * 1：类似mysql中的: update $colName set name='mongo' where id=10; * * @param string $colName 集合名 * @param array $newDoc 要更新的文档记录 * @param array $query 查询条件,如果为空数组则更新所有记录．具体请看 [查询条件说明文档] * @param string $option 操作选项,可选择项如下； * * 'set'：只修改指定的字段（默认值,如果这个键不存在，则创建它。存在则更新）. * 示例: update('user', array('name'=>'mongo'), array('id'=>10)); * 类似: update user set name='mongo' where id=10; * * 'inc'：将指定的字段累加/减(如果值为负数则是相减,不存在键则创建。字段类型一定要是数字) * 示例：update('user', array('num'=>1), array('id'=>10), 'inc'); * 类似: update user set num=num+1 where id=10; * * 'push'：将文档添加到指定键中（数组），如果键不存在则会自动创建，存在则添加到该键的尾端。 * 示例：update('user', array('comm'=>array('commid'=>1,'title'=>'title1')), array('id'=>1), 'push'); * 解说：为 id=1 的记录添加一个 comm 的评论字段，该字段对应一个 array('commid'=>1,'title'=>'title1') 的新文档。 * * 'pop':将指定键中的文档删除（数组） * 示例：update('user', array('comm'=>array('commid'=>1)), array('id'=>1), 'pop'); * 解说：删除 id=1 的记录中 comm 对应的文档集合中 'commid'=>1 对应的文档. * * 'unset':在文档中删除指定的键 * 示例：update('user', array('name'=>1), array('id'=>1), 'unset'); * 解说: 将 user 集合中将 id=1 对应的文档中的 name 字段删除 * * 'pull':删除文档中匹配其值的键 * 示例：update('user', array('name'=>'youname'), array('id'=>1), 'pull'); * 解说：将 user 集合中将 id=1 对应的文档中的 name='youname' 的字段删除 * * 'addToSet':如果值不存在就添加（避免重复添加） * 示例：update('user', array('names'=>'youname'), array('id'=>1), 'addToSet'); * 解说：向 user 集合中 id=1 对应的文档中的 names 字段添加 'youname' 这个值(不存在时才添加) * * 'replace'：用 $newDoc 新文档替换 $query 所找到的文档 * 示例：update('user', array('newid'=>1,'newnames'=>'name1'), array('id'=>1), 'replace'); * 解说：将 user 集合中 id=1 对应的文档用 array('newid'=>1,'newnames'=>'name1') 的新文档替换 * * @param boolean $upAll 是否更新找到的所有记录 * @param boolean $upsert 如果查询条件不存在时，是否以查询条件和要更新的字段一起新建一个集合 * @param boolean $safe 是否安全删除 false:不等待服务器的响应直接返回 true:等待服务器的响应(数据非常重要时推荐) * @param boolean $fsync 操作后是否立即更新到碰盘,默认情况下由服务器决定 * * @return boolean */ public function update($colName,$newDoc,$query=array(),$option='set',$upAll=true,$upsert=false,$safe=false,$fsync=false){ // 自动处理 '_id' 字段 $query = $this->_parseId($query); // 得到集合 $col = $this->_getCol($colName); // 重新组合新文档 if($option != 'replace'){ $newDoc = array($this->cmd($option) => $newDoc); } // 更新条件 $options = array( 'upsert' => $upsert, 'multiple' => $upAll, 'w' => $safe, 'fsync' => $fsync, ); return $col->update($query,$newDoc,$options); } /** * 查询文档集,返回二维数组 * * 说明: * 1:类似mysql中的 select * from table * * 示例：select('user'); * 类似：select * from user; * * 示例：select('user',array('id','name')); * 类似：select id,name from user; * * 示例：select('user',array('id','name'),array('id'=>1)); * 类似：select id,name from user where id=1; * * 示例：select('user',array('id','name'),array('id'=>1),array('num'=>1)); * 类似：select id,name from user where id=1 order by num asc; * * 示例：select('user',array('id','name'),array('id'=>1),array('num'=>1),10); * 类似：select id,name from user where id=1 order by num asc limit 10; * * 示例：select('user',array('id','name'),array('id'=>1),array('num'=>1),10,2); * 类似：select id,name from user where id=1 order by num asc limit 2,10; * * * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param array $fields 结果集返回的字段, array():表示返回所有字段 array('id','name'):表示只返回字段 "id,name" * @param array $sort 排序字段, array('id'=>1):表示按id字段升序 array('id'=>-1):表示按id字段降序 array('id'=>1, 'age'=>-1):表示按id升序后再按age降序 * @param int $limit 取多少条记录 * @param int $skip 跳过多少条(从多少条开始) * * @return array */ public function select($colName,$query=array(),$fields=array(),$sort=array(),$limit=0,$skip=0){ // 得到集合 $col = $this->_getCol($colName); // 自动处理 '_id' 字段 $query = $this->_parseId($query); // 结果集偏历 $cursor = $col->find($query,$fields); // 排序 if($sort){ $cursor->sort($sort); } // 跳过记录数 if($skip > 0){ $cursor->skip($skip); } // 取多少行记录 if($limit > 0){ $cursor->limit($limit); } $result = array(); foreach($cursor as $row){ $result[] = $this->_parseArr($row); } return $result; } /** * 统计文档记录数 * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param int $limit 取多少条记录 * @param int $skip 跳过多少条 * @return unknown */ public function count($colName,$query=array(),$limit=0,$skip=0){ return $this->_getCol($colName)->count($query,$limit,$skip); } /** * 返回集合中的一条记录(一维数组) * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param array $fields 结果集返回的字段, array():表示返回所有字段 array('id','name'):表示只返回字段 "id,name" * * @return array */ public function fetchRow($colName,$query=array(), $fields=array()){ // 得到集合名 $col = $this->_getCol($colName); // 自动处理 '_id' 字段 $query = $this->_parseId($query); // 处理结果集 return $this->_parseArr($col->findOne($query,$fields)); } /** * 返回符合条件的文档中字段的值 * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param string $fields 要取其值的字段,默认为 "_id" 字段,类似mysql中的自增主键 * * @return mixed */ public function fetchOne($colName,$query=array(), $fields='_id'){ $ret = $this->fetchRow($colName,$query,array($fields)); return isset($ret[$fields]) ? $ret[$fields] : false; } /** * 返回查询文档集合集中指定字段的值(一维数组) * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param string $fields 要取其值的字段,默认为 "_id" 字段,类似mysql中的自增主键 * * @return array */ public function fetchCol($colName,$query=array(), $fields='_id'){ $result = array(); $list = $this->select($colName,$query,array($fields)); foreach ($list as $row){ $result[] = $row[$fields]; } return $result; } /** * 返回指定下标的查询文档集合(二维数组) * * @param string $colName 集合名 * @param array $query 查询条件,具体请看 [查询条件说明文档] * @param string $fields 要取其值的字段,默认为 "_id" 字段,类似mysql中的自增主键 * * @return array */ public function fetchAssoc($colName,$query=array(), $fields='_id'){ $result = array(); $list = $this->select($colName,$query); foreach ($list as $row){ $key = $row[$fields]; $result[][$key] = $row; } return $result; } /* ==================================== 辅助操作接口API ================================= */ /** * 返回命令或命令前缀 * * @param string $option 命令，如果为空时则返回命令前缀 * * @return string */ public function cmd($option=''){ // 只返回命令前缀 if($option == ''){ return $this->_cmd; } // 如果是操作符 if(isset($this->_condMap[$option])){ $option = $this->_condMap[$option]; } return $this->_cmd.$option; } /** * 选择或创建数据库(注意：新创建的数据库如果在关闭连接前没有写入数据将会被自动删除) * * @param string $dbname 数据库名 */ public function selectDB($dbname){ $this->_db = $this->_mongo->selectDB($dbname); } /** * 得到所有的数据库 * * @param boolean $onlyName 是否只返回数据库名的数组 * @return array */ public function allDB($onlyName=false){ $ary = $this->_mongo->listDBs(); if($onlyName){ $ret = array(); foreach ($ary['databases'] as $row){ $ret[] = $row['name']; } return $ret; }else{ return $ary; } } /** * 删除数据库 * * @return array */ public function dropDB($dbname){ return $this->_mongo->dropDB($dbname); } /** * 关闭连接 * */ public function close(){ $this->_mongo->close(); } /** * 得到 Mongo 原生对象，进行其它更高级的操作，详细请看PHP手册 * */ public function getMongo(){ return $this->_mongo; } /** * 返回最后的错误信息 * * @return array */ public function getError(){ return $this->_db->lastError(); } /* ======================= 以下为私有方法 ====================== */ // 解析数据组中的'_id'字段(如果有的话) private function _parseId($arr){ if(isset($arr['_id'])){ $arr['_id'] = new MongoId($arr['_id']); } return $arr; } // 得到集合对象 private function _getCol($colName){ return $this->_db->selectCollection($colName); } // 解析数组中的"_id"并且返回 private function _parseArr($arr){ if(!empty($arr)) { $ret = (array)$arr['_id']; $arr['_id'] = $ret['$id']; } return $arr; } }//End Class ?>
其他语言大多都有函数传参解包的功能,php8也加入了这个功能。php version < 8<?php function test(...$arg) { var_dump($arg); } test(); //array(0) { } test(NULL); //array(1) { [0]=> NULL } test("foo", "bar"); //array(2) { [0]=> string(3) "foo" [1]=> string(3) "bar" } test(NULL, array(), "dummy"); //array(3) { [0]=> NULL [1]=>[] [2]=> string(5) "dummy" } function test1($a, $b = NULL, $c = NULL, $d = NULL, $e = NULL) { var_dump($a, $b, $c, $d, $e); } $arg = array( "First arg", NULL, NULL, NULL, "Fifth arg", ); test1(...$arguments); // string(14) "First arg" // NULL // NULL // NULL // string(14) "Fifth arg"php version >=8<?php function dummy($a, $b = NULL, $c = NULL, $d = NULL, $e = NULL) { var_dump($a, $b, $c, $d, $e); } dummy(a:"dummy", e:"foo"); // string(5) "dummy" // NULL // NULL // NULL // string(3) "foo"
php实现图片合并<?php //$qrc 原图 //$bg 背景图 //$new 新图 //$text 文字 //$font 字体 function mergeImages($qrc, $bg, $new, $text, $font) { //创建图片的实例 $dst = imagecreatefromstring(file_get_contents($bg)); $src = imagecreatefromstring(file_get_contents($qrc)); //获取覆盖图图片的宽高 list($src_w, $src_h) = getimagesize($qrc); //将覆盖图复制到目标图片上，最后个参数100是设置透明度（100是不透明），这里实现不透明效果 imagecopymerge($dst, $src, 1200, 210, 0, 0, $src_w, $src_h, 100); //位置可以自己调试 imagepng($dst, $new);//根据需要生成相应的图片 imagedestroy($dst); imagedestroy($src); $image = imagecreatefrompng($new); if ($text && $font) { $color = imagecolorallocate($image, 0, 0, 0); // 文字颜色 imagettftext($image, 40, 0, 250, 600, $color, $font, $text);// 创建文字 } imagepng($image, $new); return $new; }
验证码文字扭曲、旋转，使用不同字体，添加干扰码等<?php /** * 安全验证码 * 安全的验证码要：验证码文字扭曲、旋转，使用不同字体，添加干扰码 */ class Captcha { /** * 验证码的session的下标 * * @var string */ public static $seKey = 'h5.07073.com'; public static $expire = 300; // 验证码过期时间（s） /** * 验证码中使用的字符，01IO容易混淆，建议不用 * * @var string */ public static $codeSet = '2345679ACDEFGHJKLMNPQRTUVWXY'; public static $fontSize = 20; // 验证码字体大小(px) public static $useCurve = true; // 是否画混淆曲线 public static $useNoise = false; // 是否添加杂点 public static $imageH = 0; // 验证码图片宽 public static $imageL = 0; // 验证码图片长 public static $length = 4; // 验证码位数 public static $bg = array(243, 251, 254); // 背景 protected static $_image = null; // 验证码图片实例 protected static $_color = null; // 验证码字体颜色 /** * 输出验证码并把验证码的值保存的session中 * 验证码保存到session的格式为： $_SESSION[self::$seKey] = array('code' => '验证码值', 'time' => '验证码创建时间'); */ public static function entry() { // 图片宽(px) self::$imageL || self::$imageL = self::$length * self::$fontSize * 1.5 + self::$fontSize*1.5; // 图片高(px) self::$imageH || self::$imageH = self::$fontSize * 2; // 建立一幅 self::$imageL x self::$imageH 的图像 self::$_image = imagecreate(self::$imageL, self::$imageH); // 设置背景 imagecolorallocate(self::$_image, self::$bg[0], self::$bg[1], self::$bg[2]); // 验证码字体随机颜色 self::$_color = imagecolorallocate(self::$_image, mt_rand(1,120), mt_rand(1,120), mt_rand(1,120)); // 验证码使用随机字体 $ttf = dirname(__FILE__) . '/ttfs/' . 4 . '.ttf'; //$ttf = dirname(__FILE__) . '/ttfs/' . mt_rand(1, 20) . '.ttf'; if (self::$useNoise) { // 绘杂点 self::_writeNoise(); } if (self::$useCurve) { // 绘干扰线 self::_writeCurve(); } // 绘验证码 $code = array(); // 验证码 $codeNX = 0; // 验证码第N个字符的左边距 for ($i = 0; $i<self::$length; $i++) { $code[$i] = self::$codeSet[mt_rand(0, 27)]; $codeNX += mt_rand(self::$fontSize*1.2, self::$fontSize*1.5); // 写一个验证码字符 imagettftext(self::$_image, self::$fontSize, mt_rand(-10, 70), $codeNX, self::$fontSize*1.5, self::$_color, $ttf, $code[$i]); } // 保存验证码 isset($_SESSION) || session_start(); $_SESSION[self::$seKey]['code'] = join('', $code); // 把校验码保存到session $_SESSION[self::$seKey]['time'] = time(); // 验证码创建时间 header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); header("content-type: image/png"); // 输出图像 imagepng(self::$_image); imagedestroy(self::$_image); } /** * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) * * 高中的数学公式咋都忘了涅，写出来 * 正弦型函数解析式：y=Asin(ωx+φ)+b * 各常数值对函数图像的影响： * A：决定峰值（即纵向拉伸压缩的倍数） * b：表示波形在Y轴的位置关系或纵向移动距离（上加下减） * φ：决定波形与X轴位置关系或横向移动距离（左加右减） * ω：决定周期（最小正周期T=2π/∣ω∣） * */ protected static function _writeCurve() { $A = mt_rand(1, self::$imageH/2); // 振幅 $b = mt_rand(-self::$imageH/4, self::$imageH/4); // Y轴方向偏移量 $f = mt_rand(-self::$imageH/4, self::$imageH/4); // X轴方向偏移量 $T = mt_rand(self::$imageH*1.5, self::$imageL*2); // 周期 $w = (2* M_PI)/$T; $px1 = 0; // 曲线横坐标起始位置 $px2 = mt_rand(self::$imageL/2, self::$imageL * 0.667); // 曲线横坐标结束位置 for ($px=$px1; $px<=$px2; $px=$px+ 0.9) { if ($w!=0) { $py = $A * sin($w*$px + $f)+ $b + self::$imageH/2; // y = Asin(ωx+φ) + b $i = (int) ((self::$fontSize - 6)/4); while ($i > 0) { imagesetpixel(self::$_image, $px + $i, $py + $i, self::$_color); // 这里画像素点比imagettftext和imagestring性能要好很多 $i--; } } } $A = mt_rand(1, self::$imageH/2); // 振幅 $f = mt_rand(-self::$imageH/4, self::$imageH/4); // X轴方向偏移量 $T = mt_rand(self::$imageH*1.5, self::$imageL*2); // 周期 $w = (2* M_PI)/$T; $b = $py - $A * sin($w*$px + $f) - self::$imageH/2; $px1 = $px2; $px2 = self::$imageL; for ($px=$px1; $px<=$px2; $px=$px+ 0.9) { if ($w!=0) { $py = $A * sin($w*$px + $f)+ $b + self::$imageH/2; // y = Asin(ωx+φ) + b $i = (int) ((self::$fontSize - 8)/4); while ($i > 0) { imagesetpixel(self::$_image, $px + $i, $py + $i, self::$_color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出（不用这while循环）性能要好很多 $i--; } } } } /** * 画杂点 * 往图片上写不同颜色的字母或数字 */ protected static function _writeNoise() { for($i = 0; $i < 10; $i++){ //杂点颜色 $noiseColor = imagecolorallocate( self::$_image, mt_rand(150,225), mt_rand(150,225), mt_rand(150,225) ); for($j = 0; $j < 5; $j++) { // 绘杂点 imagestring( self::$_image, 5, mt_rand(-10, self::$imageL), mt_rand(-10, self::$imageH), self::$codeSet[mt_rand(0, 27)], // 杂点文本为随机的字母或数字 $noiseColor ); } } } /** * 验证验证码是否正确 * * @param string $code 用户验证码 * @param boo $del_sess 验证完是否删除session * true:是 * false:否 ajax验证时使用false,提交以后验证，使用true * * @param bool 用户验证码是否正确 */ public static function check($code) { $code = strtoupper($code);//都转为大写 isset($_SESSION) || session_start(); $captcha_arr = array(); if (!isset($_SESSION[self::$seKey]) || empty($_SESSION[self::$seKey])) { return false; } // 清除session,避免sesion重放攻击 $captcha_arr = $_SESSION[self::$seKey]; unset($_SESSION[self::$seKey]); // 验证码不能为空 if(empty($code)) { return false; } // session 过期 if(time() - $captcha_arr['time'] > self::$expire) { return false; } if($code == $captcha_arr['code']) { return true; } return false; } }简单使用# 生成 Captcha::entry(); exit; # 验证 $captcha_res = Captcha::check($_GET['code']); if ($captcha_res == false) { throw new Exception('验证码错误', 102); }
<?php $str = "中文"; $filename = '1.txt'; file_put_contents($filename,$str); echo '测试1-检测本地文件编码：' . detect_encoding($filename) . ' '; $filename = '2.txt'; file_put_contents($filename,mb_convert_encoding($str,'UTF-8',mb_detect_encoding($str))); echo '测试2-检测本地文件编码：' . detect_encoding($filename) . ' '; $fp =fopen($filename,"w+"); $header ="\xef\xbb\xbf"; fwrite($fp,$header); fwrite($fp,$str); fclose($fp); echo '测试3-检测本地文件编码：' . detect_encoding($filename) . ' '; function detect_encoding($file) { $list = ['GBK', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'ISO-8859-1']; $str = file_get_contents($file); foreach ($list as $item) { $tmp = mb_convert_encoding($str, $item, $item); if (md5($tmp) == md5($str)) { return $item; } } return null; }UTF-8编码的TXT文件必须以0xef , 0xbb , 0xbf三个字符开头！