首页
关于小站
朋友
壁纸
留言
时光之书
笔顺字帖
LayUI手册
Search
1
【PHP】PHPoffice/PHPSpreadsheet读取和写入Excel
1,684 阅读
2
【Layui】控制页面元素展示隐藏
1,531 阅读
3
【Git】No tracked branch configured for branch master or the branch doesn't exist.
1,469 阅读
4
【PHP】PHP实现JWT生成和验证
1,383 阅读
5
精准检测,助力社交管理 —— 微信好友检测服务来袭!
1,290 阅读
默认分类
PHP
ThinkPHP
Laravel
面向对象
设计模式
算法
基础
网络安全
Web
HTML
CSS
JavaScript
jQuery
Layui
VUE
uni-app
Database
MySQL
Redis
RabbitMQ
Nginx
Git
Linux
Soft Ware
Windows
网赚
Go
Docker
登录
Search
标签搜索
PHP
函数
方法
类
MySQL
ThinkPHP
JavaScript
OOP
Layui
Web
Server
Docker
Linux
PHPSpreadsheet
PHPoffice
Array
设计模式
Nginx
Git
排序算法
小破孩
累计撰写
251
篇文章
累计收到
13
条评论
首页
栏目
默认分类
PHP
ThinkPHP
Laravel
面向对象
设计模式
算法
基础
网络安全
Web
HTML
CSS
JavaScript
jQuery
Layui
VUE
uni-app
Database
MySQL
Redis
RabbitMQ
Nginx
Git
Linux
Soft Ware
Windows
网赚
Go
Docker
页面
关于小站
朋友
壁纸
留言
时光之书
笔顺字帖
LayUI手册
搜索到
247
篇与
的结果
2025-04-18
【Docker】实践环节 编写dockerfile, 创建镜像,启动容器的这个过程
下面通过一个简单的 Python Flask 应用示例,详细介绍编写 Dockerfile、创建镜像以及启动容器的完整过程。步骤 1:准备 Flask 应用代码首先,创建一个简单的 Flask 应用。在项目根目录下创建 app.py 文件,内容如下:from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, Docker!' if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) 接着,创建 requirements.txt 文件,列出应用所需的依赖:flask步骤 2:编写 Dockerfile在项目根目录下创建 Dockerfile 文件,其内容如下:# 使用 Python 3.9 作为基础镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制 requirements.txt 文件到工作目录 COPY requirements.txt . # 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 复制当前目录下的所有文件到工作目录 COPY . . # 暴露端口 EXPOSE 5000 # 容器启动时执行的命令 CMD ["python", "app.py"] 步骤 3:构建 Docker 镜像打开终端,进入项目根目录,执行以下命令来构建 Docker 镜像:docker build -t my-flask-app:1.0 .这里的 -t 参数用于为镜像指定标签,my-flask-app:1.0 是镜像的名称和版本号,. 表示使用当前目录下的 Dockerfile 进行构建。构建过程中,Docker 会按照 Dockerfile 中的指令依次执行,最终创建出一个包含 Flask 应用的镜像。步骤 4:查看构建好的镜像构建完成后,可使用以下命令查看本地已有的镜像:docker images在输出列表中,你应该能看到刚刚构建的 my-flask-app:1.0 镜像。步骤 5:启动 Docker 容器使用以下命令启动基于该镜像的容器:docker run -d -p 5000:5000 my-flask-app:1.0参数解释:-d:表示以守护进程模式运行容器,即容器在后台运行。-p 5000:5000:将容器内部的 5000 端口映射到宿主机的 5000 端口,这样就可以通过宿主机的 5000 端口访问容器内的 Flask 应用。my-flask-app:1.0:指定要使用的镜像名称和版本号。步骤 6:验证应用是否正常运行打开浏览器,访问 http://localhost:5000,如果看到 Hello, Docker! 的输出,就说明容器内的 Flask 应用已成功运行。步骤 7:停止和删除容器若要停止运行中的容器,可使用以下命令:docker stop <容器 ID>其中 <容器 ID> 可通过 docker ps 命令查看。停止容器后,若要删除容器,可执行:docker rm <容器 ID>通过以上步骤,你就完成了编写 Dockerfile、创建镜像以及启动容器的整个过程。
2025年04月18日
11 阅读
0 评论
0 点赞
2025-04-18
【Docker】容器化和Dockerfile
容器化概述容器化是一种将应用程序及其依赖项打包成独立容器的技术,这些容器可以在不同的环境中一致地运行。容器化技术的核心目标是实现应用的隔离性、可移植性和资源的高效利用。隔离性:容器使用操作系统的内核特性(如命名空间和控制组)来隔离应用程序的运行环境,使得每个容器中的应用程序相互独立,不会相互干扰。可移植性:容器将应用程序及其所有依赖项打包在一起,形成一个独立的运行单元。这意味着容器可以在任何支持容器化技术的环境中运行,无需担心环境差异导致的兼容性问题。资源高效利用:相比于传统的虚拟机技术,容器不需要运行完整的操作系统,因此占用的资源更少,启动速度更快,可以在同一台物理服务器上运行更多的容器。Dockerfile 概述Dockerfile 是一个文本文件,用于定义 Docker 镜像的构建过程。通过编写 Dockerfile,你可以自动化地创建自定义的 Docker 镜像。基本结构与常用指令基础镜像指定(FROM):指定构建镜像所基于的基础镜像。例如:FROM ubuntu:20.04这行代码指定使用 Ubuntu 20.04 作为基础镜像。维护者信息(MAINTAINER 或 LABEL):用于注明镜像的维护者信息。示例:LABEL maintainer="your_email@example.com"运行命令(RUN):在构建镜像的过程中执行命令。例如,安装软件包:RUN apt-get update && apt-get install -y python3这行代码会在基础镜像中更新软件包列表并安装 Python 3。复制文件(COPY 或 ADD):将本地文件复制到镜像中。例如:COPY app.py /app/这会将本地的 app.py 文件复制到镜像的 /app/ 目录下。工作目录设置(WORKDIR):指定后续命令的工作目录。例如:WORKDIR /app后续的命令都会在 /app 目录下执行。环境变量设置(ENV):设置环境变量。例如:ENV PORT 8080这会在镜像中设置 PORT 环境变量为 8080。容器启动命令(CMD 或 ENTRYPOINT):指定容器启动时执行的命令。例如:CMD ["python3", "app.py"]这表示容器启动时会运行 python3 app.py 命令。Dockerfile 构建镜像示例以下是一个简单的 Dockerfile 示例,用于构建一个运行 Python Flask 应用的镜像:# 使用 Python 3.9 作为基础镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制当前目录下的所有文件到工作目录 COPY . /app # 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 设置环境变量 ENV FLASK_APP=app.py ENV FLASK_RUN_HOST=0.0.0.0 # 暴露端口 EXPOSE 5000 # 容器启动时执行的命令 CMD ["flask", "run"] 使用以下命令可以基于这个 Dockerfile 构建镜像:docker build -t my-flask-app .其中,-t 用于指定镜像的标签,. 表示使用当前目录下的 Dockerfile 进行构建。综上所述,容器化是一种先进的应用部署技术,而 Dockerfile 是实现容器化过程中用于构建自定义镜像的重要工具。
2025年04月18日
17 阅读
0 评论
0 点赞
2025-04-18
【Docker】Docker的安装
以下为你介绍在不同操作系统上安装 Docker 的方法:在 Ubuntu 系统安装 Docker步骤 1:更新系统软件包列表sudo apt-get update步骤 2:安装必要的依赖包sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-release步骤 3:添加 Docker 的官方 GPG 密钥sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg步骤 4:设置 Docker 软件源echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null步骤 5:再次更新软件包列表sudo apt-get update步骤 6:安装 Docker 引擎sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin步骤 7:验证 Docker 是否安装成功sudo docker run hello-world在 CentOS 系统安装 Docker步骤 1:卸载旧版本(如果有)sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine步骤 2:安装必要的依赖包sudo yum install -y yum-utils步骤 3:设置 Docker 软件源sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo步骤 4:安装 Docker 引擎sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin步骤 5:启动 Docker 服务并设置开机自启sudo systemctl start docker sudo systemctl enable docker步骤 6:验证 Docker 是否安装成功sudo docker run hello-world在 Windows 系统安装 Docker步骤 1:确保系统满足要求Windows 10 64 位:专业版、企业版或教育版(1607 版本及以上),或者 Windows 11。开启 Hyper - V 和容器功能。步骤 2:下载 Docker Desktop 安装程序访问 Docker 官方网站(https://www.docker.com/products/docker-desktop/),下载适用于 Windows 的 Docker Desktop 安装程序。步骤 3:运行安装程序双击下载的安装程序,按照提示完成安装。安装完成后,启动 Docker Desktop。步骤 4:验证安装打开命令提示符或 PowerShell,运行以下命令:docker run hello-world在 macOS 系统安装 Docker步骤 1:确保系统满足要求macOS Catalina 10.15 及以上版本。步骤 2:下载 Docker Desktop 安装程序访问 Docker 官方网站(https://www.docker.com/products/docker-desktop/),下载适用于 macOS 的 Docker Desktop 安装程序。步骤 3:运行安装程序双击下载的 .dmg 文件,将 Docker 图标拖到“应用程序”文件夹中。在“应用程序”文件夹中找到 Docker 并打开,按照提示完成设置。步骤 4:验证安装打开终端,运行以下命令:docker run hello-world通过上述步骤,你就可以在不同操作系统上完成 Docker 的安装,并通过运行 hello - world 镜像验证安装是否成功。
2025年04月18日
20 阅读
0 评论
0 点赞
2025-04-18
【Docker】Docker的基本原理和概念
基本概念1. 镜像(Image)镜像是一个只读的模板,它包含了运行应用程序所需的所有文件系统、代码、依赖库、环境变量和配置文件等。可以把镜像看作是一个软件的“安装包”,它是静态的,不包含任何动态数据。例如,你可以有一个基于Ubuntu系统的Python镜像,这个镜像中已经安装好了Python环境以及相关的依赖库。2. 容器(Container)容器是镜像的运行实例。当你启动一个镜像时,就会创建一个容器。容器是一个独立的运行环境,它可以被启动、停止、删除等操作。容器之间相互隔离,每个容器都有自己独立的文件系统、进程空间和网络环境。例如,基于前面提到的Python镜像启动的容器,就可以在其中运行Python程序。3. 仓库(Registry)仓库是用于存储和分发镜像的地方。类似于代码仓库,镜像仓库可以包含多个镜像,每个镜像又可以有不同的版本。Docker官方提供了公共的镜像仓库Docker Hub,其中包含了大量的开源镜像,你也可以搭建自己的私有镜像仓库。4. DockerfileDockerfile是一个文本文件,它包含了一系列的指令,用于构建Docker镜像。通过编写Dockerfile,你可以定义镜像的构建步骤,包括基础镜像的选择、软件的安装、环境变量的设置等。例如,你可以在Dockerfile中指定从Ubuntu镜像开始,然后安装Python和相关的依赖库,最后将你的应用程序代码复制到镜像中。基本原理1. 容器化技术基础Docker主要基于Linux内核的两个特性来实现容器化:命名空间(Namespaces):命名空间提供了一种隔离机制,它可以将系统资源(如进程、网络、文件系统等)隔离开来,使得不同的命名空间中的进程看起来好像拥有自己独立的系统资源。例如,PID命名空间可以让每个容器都有自己独立的进程ID,NET命名空间可以让每个容器都有自己独立的网络栈。控制组(Control Groups,简称cgroups):控制组用于限制和监控容器对系统资源(如CPU、内存、磁盘I/O等)的使用。通过cgroups,可以为每个容器分配一定的资源配额,防止某个容器占用过多的系统资源而影响其他容器的正常运行。2. 镜像构建原理当你使用docker build命令根据Dockerfile构建镜像时,Docker会按照Dockerfile中的指令依次执行每个步骤。每执行一个指令,就会创建一个新的镜像层(Layer),这些镜像层是只读的,并且可以被多个镜像共享。最终的镜像就是由这些只读的镜像层叠加而成的。例如,当你在Dockerfile中使用RUN指令安装一个软件时,就会创建一个新的镜像层,该层包含了安装好的软件。3. 容器运行原理当你使用docker run命令启动一个容器时,Docker会在镜像的基础上创建一个可写的容器层(Container Layer)。容器层位于镜像层之上,所有对容器内文件系统的写操作都会发生在这个可写层中。当容器被删除时,容器层也会被删除,但镜像层不会受到影响。例如,当你在容器中创建一个新文件时,这个文件会被存储在容器层中。4. 网络通信原理Docker提供了多种网络模式,如bridge、host、none等。默认情况下,Docker使用bridge网络模式,它会在宿主机上创建一个虚拟网桥(docker0),每个容器都会连接到这个网桥上。容器之间可以通过IP地址进行通信,同时也可以通过端口映射将容器内的端口映射到宿主机上,使得外部网络可以访问容器内的服务。
2025年04月18日
16 阅读
0 评论
0 点赞
2025-04-18
【Docker】Docker和虚拟机的区别
Docker和虚拟机在实现原理、资源占用、性能、隔离性等方面存在明显差异,以下是详细对比:实现原理Docker:基于容器化技术,利用Linux内核的特性(如命名空间和控制组)来实现进程的隔离。容器共享宿主机的操作系统内核,只需打包应用程序及其依赖项,就能在不同环境中运行。虚拟机:通过虚拟机管理程序(Hypervisor)模拟出硬件环境,在这个虚拟的硬件上安装完整的操作系统,每个虚拟机都有独立的操作系统实例。资源占用Docker:容器共享内核,不需要额外的操作系统开销,因此资源占用少,启动速度快,通常只需几秒钟。虚拟机:每个虚拟机都包含一个完整的操作系统,需要分配独立的CPU、内存、存储等资源,资源占用大,启动时间长,可能需要几分钟。性能Docker:由于直接使用宿主机的内核,容器的性能损耗小,接近原生应用程序的性能。虚拟机:因为需要模拟硬件层,并且运行独立的操作系统,存在一定的性能开销,性能相对较低。隔离性Docker:隔离性相对较弱,容器之间共享内核,一个容器的崩溃可能会影响其他容器,但通过合理的配置和管理可以降低这种风险。虚拟机:提供了更强的隔离性,每个虚拟机都有独立的操作系统和硬件环境,一个虚拟机的故障通常不会影响其他虚拟机。便携性Docker:容器镜像是轻量级的,易于打包、分发和部署,可以在不同的Docker环境中快速迁移。虚拟机:虚拟机镜像通常较大,包含完整的操作系统,迁移和部署相对复杂。应用场景Docker:适用于微服务架构、持续集成/持续部署(CI/CD)、开发和测试环境等场景,能够快速部署和扩展应用程序。虚拟机:适合需要完全隔离的环境,如运行不同操作系统的应用程序、安全要求较高的场景等。以下表格对上述区别进行了总结:对比维度Docker虚拟机实现原理基于容器化技术,共享宿主机内核通过Hypervisor模拟硬件,运行独立操作系统资源占用少,启动快大,启动慢性能接近原生,损耗小有性能开销,相对较低隔离性相对较弱强便携性轻量级,易迁移镜像大,迁移复杂应用场景微服务、CI/CD、开发测试需完全隔离、多操作系统、高安全场景
2025年04月18日
17 阅读
0 评论
0 点赞
2025-04-18
【Docker】为什么要使用Docker
Docker是一款流行的容器化平台,使用Docker主要有以下几个原因:环境一致性:Docker容器可以确保应用程序及其所有依赖项在任何环境中都能以相同的方式运行。无论是开发环境、测试环境还是生产环境,只要安装了Docker,容器内的应用程序就会运行在相同的环境中,避免了因环境差异导致的“在我机器上能运行,在其他地方不行”的问题。轻量级和高效性:与传统的虚拟机相比,Docker容器不需要包含完整的操作系统,它们共享宿主机的操作系统内核,因此启动速度快、占用资源少。这使得在同一台物理服务器上可以同时运行多个容器,提高了服务器的资源利用率。易于部署和扩展:使用Docker,可以将应用程序及其依赖打包成一个容器镜像,然后轻松地在不同的服务器上部署。当应用程序的流量增加时,可以快速启动多个容器副本进行水平扩展,以满足业务需求。便于团队协作:开发人员可以在自己的本地环境中使用Docker容器进行开发和测试,然后将容器镜像分享给其他团队成员或部署到生产环境。这使得团队成员之间的环境更加一致,减少了因环境配置不同而导致的问题,提高了协作效率。隔离性:Docker容器提供了良好的隔离性,每个容器都有自己独立的文件系统、进程空间和网络环境。这意味着一个容器内的应用程序出现问题不会影响到其他容器,提高了系统的稳定性和可靠性。版本控制和可重复性:可以对Docker容器镜像进行版本控制,就像对代码进行版本控制一样。这使得在需要时可以轻松回滚到之前的版本,并且能够确保每次部署都是可重复的,提高了系统的可维护性。多语言和多框架支持:Docker可以用于各种不同的编程语言和框架。无论是Python、Java、Node.js还是其他语言,都可以将其应用程序及其依赖项打包到Docker容器中,实现跨语言和跨框架的统一部署和管理。
2025年04月18日
16 阅读
0 评论
0 点赞
2025-03-27
【JavaScript】网站底部版权年份自动更换
/** * 将当前年份赋值给指定 id 的元素 * @param {string} elementId - 要赋值的元素的 id * @returns {boolean} - 如果元素存在并成功赋值,返回 true;否则返回 false */ function setCurrentYear(elementId) { // 获取当前年份 const currentYear = new Date().getFullYear(); // 获取指定 id 的元素 const element = document.getElementById(elementId); // 检查元素是否存在 if (element) { // 更新元素内容 element.textContent = currentYear; return true; } else { console.error(`元素 id "${elementId}" 不存在`); return false; } } 实例 <!DOCTYPE html> <html> <head> <title>显示当前年份</title> </head> <body> <span id="currentYear"></span> <script> // 调用封装好的方法 setCurrentYear('currentYear'); </script> </body> </html> 方法2 /** * 将当前年份嵌入到指定元素的内容中 * @param {string} elementId - 要赋值的元素的 id * @param {string} prefix - 年份前的文本 * @param {string} suffix - 年份后的文本 * @returns {boolean} - 如果元素存在并成功赋值,返回 true;否则返回 false */ function setCurrentYearWithText(elementId, prefix = '', suffix = '') { const currentYear = new Date().getFullYear(); const element = document.getElementById(elementId); if (element) { element.textContent = `${prefix}${currentYear}${suffix}`; return true; } else { console.error(`元素 id "${elementId}" 不存在`); return false; } } // 输出 "Copyright © 2025" setCurrentYearWithText('currentYear', 'Copyright © ', ''); // <span id="currentYear">Copyright © </span> // 获取当前日期 const currentDate = new Date(); // 获取当前年份 const currentYear = currentDate.getFullYear(); //赋值 document.getElementById('currentYear').textContent = `Copyright © ${currentYear}`;
2025年03月27日
18 阅读
0 评论
0 点赞
2025-03-25
【MySQL】批量清空MySQL数据表,主键自增从1开始
public function truncateTables() { // 要排除的表 $exclude_tables = [ 'web_admin_func',// 权限表 'web_admin_role',// 角色表 'web_admin_user',// 用户表 'web_china_city',// 中国行政区划表 'web_china_city_area',// 四级省市区镇地区表 'web_china_city_backup',// 中国行政区划表初始备份 ]; try { // 开启事务 Db::startTrans(); // 获取所有表名 $tables = Db::query('SHOW TABLES'); foreach ($tables as $table) { $table_name = current($table); if (!in_array($table_name, $exclude_tables)) { // 清空表并重置索引 Db::execute("TRUNCATE TABLE {$table_name}"); echo "表 {$table_name} 已清空<br>"; } } // 提交事务 Db::commit(); } catch (\Exception $e) { // 回滚事务 Db::rollback(); echo "发生错误: " . $e->getMessage(); } }
2025年03月25日
25 阅读
0 评论
0 点赞
2025-03-13
【JavaScript】网页实现打印
<html> <title>山东尼惜亚食品有限公司(尼惜亚冻品工厂仓:https://www.nixiyadp.com)</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head> <style> .content { border-collapse: collapse; width: 100%; } .content th, .content td { border: 1px solid black; padding: 8px; text-align: center; } h2{ text-align: center; } .order-info{ display: flex; align-items: center; justify-content: space-between; font-size: 16px; margin-bottom: 6px; } .boutton{ margin: 0 auto; width: 200px; height: 60px; text-align: center; line-height: 60px; color: #fff; background-color: #009688; margin-top: 100px; border-radius: 8px; } </style> </head> <body> <!--startprint--><!--注意要加上html里star和end的这两个标记--> <br> <h2>要货申请单</h2> <table border="0" style="width: 100%; border-collapse: collapse; text-align: left;"> <tr> <!-- <td>订单编号:{$v.o_uuid}</td>--> <!-- --> <!-- <td>山货仓库:{$v.warehouse_name}</td>--> <td>商品总数:{$num}件</td> <td>商品总价:{$total}元</td> <tr> <!-- <tr>--> <!-- <td></td>--> <!-- <td></td>--> <!-- <td></td>--> <!-- </tr>--> <tr> <!-- <td>中请时间:{$v.o_create_time}</td>--> <!-- <td>门店名称:{$v.withorderinfo.u_shop_user_name}</td>--> <tr> <!-- <tr>--> <!-- <td></td>--> <!-- <td></td>--> <!-- <td></td>--> <!-- </tr>--> <tr> <!-- <td>审核时间:{$v.o_pay_receipt_allow_time}</td>--> <!-- <td>联系方式:{$v.o_address_tel}</td>--> <tr> </table> <br> <table class="content"> <tr> <th width="32px">序号</th> <th width="32px">类型</th> <th>商品名称</th> <th width="32px">数量</th> <th width="62px">单价</th> <th width="62px">总价</th> <!-- <th>备注</th>--> </tr> {volist name="$list" id="v"} <tr> <td>{$i}</td> <td>{empty name="$v.oi_issendgoods"} 商品{else /} 赠品{/empty}</td> <td>{$v['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name']}({$v['oi_sku_info']['sgcs_name']})</td> <td>{$v.total_num}</td> <td>{$v.unit_price}</td> <td>{$v.total_price}</td> <!-- <td>{$v.o_reamrk}</td>--> </tr> {/volist} </table> <br><br> <table border="0" style="width: 100%; border-collapse: collapse; text-align: left;"> <tr> <!-- <td>收 货 人:{$v.o_address_name}</td>--> <td>打印时间:{php}echo date('Y-m-d H:i:s');{/php}</td> <tr> <!-- <tr>--> <!-- <td>联系方式:{$v.o_address_tel}</td>--> <!-- <tr>--> <!-- <tr>--> <!-- <td>收货地址:{$v.o_address_info}</td>--> <!-- <tr>--> <!-- <tr>--> <!-- <td>备 注:{$v.o_reamrk}</td>--> <!-- <tr>--> </table> <!--endprint--> <div class="boutton" onclick="doPrint()"> 打 印 </div> </body> <script type="text/javascript"> function doPrint() { bdhtml=window.document.body.innerHTML; sprnstr="<!--startprint-->"; eprnstr="<!--endprint-->"; prnhtml=bdhtml.substr(bdhtml.indexOf(sprnstr)+17); prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr)); window.document.body.innerHTML=prnhtml; window.print(); location.reload(); } </script> </html>
2025年03月13日
56 阅读
0 评论
1 点赞
2025-03-13
【PHP】通联支付 通企付 生产签名 PHP版本
/** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2025/2/17 15:25 * @param $array * @return string * @Description:数组以key=value&key=value 返回字符串 */ public function arrayKeyValueToString($array) { $result = ''; foreach ($array as $key => $value) { $result.= $key. '='. $value. '&'; } // 去除末尾多余的 & 符号 return rtrim($result, '&'); } /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2025/2/14 15:32 * @return string * @Description:生产签名 */ public function setSign() { $data = [ 'mchNo' => $this->payConfig['tppay_mchid'], // 商户号 'appId' => $this->payConfig['tppay_appid'], // appid 'reqTime' => $this->currentTimestamp, // 13位时间戳 'version' => "1.0", // 固定值 'signType' => 'RSA', // 验签方式 'mchOrderNo' => $this->orderNo, // 订单号 'amount' => (string)$this->amount, // 金额 单位 分 'body' => $this->body, // 商品描述 'notifyUrl' => $this->getNotifyUrl(), // 回调通知地址 'expiredTime' => '1800', // 订单超时支付时间 单位 秒 'channelExtra' => $this->channelExtra, 'payTypeInfo' => (string)$this->payTypeInfo(), // 收银台展示的付款方式 // 'directPayType'=> (string)$this->getPayType(), // 直接支付的支付方式 ]; ksort($data); // Log::write("发起签名的参数:".var_export($data,true),"tppay"); $instanceArr = new \app\common\lib\data\Arr(); $encodedParams = $instanceArr->arrayKeyValueToString($data); Log::write("处理后的签名字符串:".PHP_EOL.var_export($encodedParams,true),"tppay"); $privateKey = "-----BEGIN PRIVATE KEY-----\n" . $this->payConfig['tppay_rsa_private_key'] . "\n-----END PRIVATE KEY-----"; $publicKey = "-----BEGIN PUBLIC KEY-----\n" . $this->payConfig['tppay_rsa_public_key'] . "\n-----END PUBLIC KEY-----"; // Log::write("发起签名的私钥:".var_export($privateKey,true),"tppay"); $instanceRsa = new \app\common\lib\pay\tppay\Rsa(null, null, $privateKey, $publicKey); $encryptedWithPrivate = $instanceRsa->sign($encodedParams); //签名使用SHA1withRSA // Log::write("签名的结果:".var_export($encryptedWithPrivate,true),"tppay"); return $encryptedWithPrivate; }
2025年03月13日
63 阅读
0 评论
0 点赞
2025-03-13
【PHP】ThinkPHP6.1 参数验证中间件
public function handle($request, \Closure $next) { try { // 获取并清理参数 $params = array_filter(array_map(function ($value) { return is_string($value) ? trim($value) : $value; }, $request->param()), function ($value) { return is_numeric($value) || !empty($value); }); unset($params['controller'], $params['function']); if (empty($params)) return $next($request); // 设置请求属性,方便后续使用 $request->checkParam = $params; // 获取应用名、控制器和操作名 $appName = app('http')->getName(); $controller = Request::instance()->controller(true); $action = Request::instance()->action(true); // 动态构建验证器路径 $controllerParts = explode('.', $controller); $validatePathParts = array_merge([$appName, 'validate'], $controllerParts); $lastKey = array_key_last($validatePathParts); $validatePathParts[$lastKey] = ucfirst((string) $validatePathParts[$lastKey]); // $validatePath = implode('\\', array_map('ucfirst', $validatePathParts)); $validatePath = 'app\\'.implode('\\', $validatePathParts); // 检查验证器是否存在及场景是否定义 if (!class_exists($validatePath) || !$this->sceneExists($validatePath, $action)) { return $next($request); } // 验证数据 $validateInstance = new $validatePath; if (!$validateInstance->scene($action)->check($params)) { throw new Exception($validateInstance->getError()); } } catch (Exception $e) { return show(100, $e->getMessage()); } return $next($request); } /** * 检查指定验证场景是否存在 * * @param string $validateClass 验证类名 * @param string $scene 场景名 * @return bool */ protected function sceneExists(string $validateClass, string $scene): bool { return (new $validateClass)->hasScene($scene); }
2025年03月13日
128 阅读
0 评论
0 点赞
2025-03-13
【PHP】发送腾讯云短信
优化空间很大,先用着,能用<?php namespace app\common\lib\sms\tencent; //缓存 use think\facade\Cache; use TencentCloud\Common\Credential; use TencentCloud\Common\Profile\ClientProfile; use TencentCloud\Common\Profile\HttpProfile; use TencentCloud\Common\Exception\TencentCloudSDKException; use TencentCloud\Sms\V20210111\SmsClient; use TencentCloud\Sms\V20210111\Models\SendSmsRequest; class Sms{ public $SecretID = "......................"; public $SecretKey = "......................."; public $SmsSdkAppId = "..........."; public $TemplateId = "........."; public $SignName = "............"; public $code; public $phone; public function __construct($phone = '', $code = '', $tempID = '') { $this->phone = $phone; $this->code = $code; if(!empty($tempID)){ $this->TemplateId = $tempID; } } public function send(){ try { //控制台 >API密钥管理页面获取 SecretID 和 SecretKey $cred = new Credential($this->SecretID, $this->SecretKey); //实例化一个http选项 [可选] $httpProfile = new HttpProfile(); $httpProfile->setEndpoint("sms.tencentcloudapi.com"); //实例化一个client选项 [可选] $clientProfile = new ClientProfile(); $clientProfile->setHttpProfile($httpProfile); /** * 实例化以sms为例的client对象, [第三个参数 可选] * * 第二个参数是地域信息,可以直接填 ap-guangzhou */ $client = new SmsClient($cred, "ap-beijing", $clientProfile); // 实例化一个sms发送短信请求对象,每个接口都会对应一个request对象。 $req = new SendSmsRequest(); //生成随机验证码 // $code = rand(11111, 99999); // $params = array( // //接收方手机号,带上+86 示例:+8613711112222 // "PhoneNumberSet" => array((string)$this->phone), // //短信应用ID:在 [短信控制台] 添加应用后生成的实际SdkAppId // "SmsSdkAppId" => (string)$this->SmsSdkAppId, // //短信签名内容:[不理解可以看文章里的截图] // "SignName" => (string)$this->SignName, // //模板ID:必须填写已审核通过的模板 // "TemplateId" => (string)$this->TemplateId, // //我的模板中有两个参数 第一个是验证码参数 第二个是有效时间 若无模板参数,则设置为空 // "TemplateParamSet" => array((string)$this->code, '10'), // //SecretID // // "SenderId" => (string)$this->SecretID // ); $params = array( "PhoneNumberSet" => array( (string)$this->phone ), "SmsSdkAppId" => (string)$this->SmsSdkAppId, "SignName" => (string)$this->SignName, "TemplateId" => (string)$this->TemplateId, "TemplateParamSet" => array( (string)$this->code), // "SenderId" => (string)$this->SecretID ); $req->fromJsonString(json_encode($params)); //发出请求,返回一个实例 $resp = $client->SendSms($req); // print_r($resp);die; //如果成功,把验证码存入缓存 //成功实例中的Code值为 Ok if ($resp->SendStatusSet[0]->Code === "Ok") { return true; // Cache::set('name', $code, 600); // return json(['msg' => "发送成功", 'code' => 200]); } } catch (TencentCloudSDKException $e) { echo $e; } } }
2025年03月13日
56 阅读
0 评论
0 点赞
2025-03-13
【PHP】打印猿&蜂打打 开放平台 完整对接
基础类<?php namespace app\common\lib\printman; use think\facade\Log; class Basic { #APPID public $AppId; #密钥 public $AppSecret; #API地址 public $ApiUrl; #打印机ID public $PrinterId; public function __construct($AppId, $AppSecret, $PrinterId) { $this->AppId = $AppId; $this->AppSecret = $AppSecret; $this->ApiUrl = "https://iot-app-prod.fengdada.cn/mk/api"; $this->PrinterId = $PrinterId; } public function encode($BizData, $nonce) { // global $AppSecret; $jsonBytes = mb_convert_encoding($BizData , 'utf-8'); $bizData = strval($jsonBytes); $sign_ori = $bizData . $nonce . $this->AppSecret; $md5_hash = md5($sign_ori, true); $sign = base64_encode($md5_hash); return $sign; } public function generate_verification_code() { $verification_code = ""; for ($i = 0; $i < 6; $i++) { $verification_code .= strval(rand(0, 9)); } return $verification_code; } public function requests_post($url, $data, $headers) { $ch = curl_init(); curl_setopt($ch, CURLOPT_CAINFO, "cacert-2023-01-10.pem"); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); return $response; } public function DoMyPost($URL, $json_BizData) { $ts = round(microtime(true) * 1000); $nonce = $this->generate_verification_code(); $sign = $this->encode($json_BizData, $nonce); $data = array( "bizData" => $json_BizData, "nonce" => $nonce, "appId" => $this->AppId, "timestamp" => strval($ts) ); $headers = array( 'Content-Type: application/json', 'sign:'.$sign ); // $response = $this->requests_post($URL, json_encode($data), $headers); $response = $this->curlRequest($URL, "POST",json_encode($data),true,false, $headers); Log::write("打印机日志:".print_r($response,true),'printman'); if(empty($response)) { echo "Error: no response received"; }else{ return $response; } } // 云打印验证码 public function PrintCaptcha() { $URL = $this->ApiUrl."/print/captcha"; $BizData = array('printerId' => $this->PrinterId); $json_BizData = json_encode($BizData); return $this->DoMyPost($URL, $json_BizData); } // 云打印机绑定 public function PrintBind($VerificationCode) { $URL = $this->ApiUrl."/printer/bind"; $BizData = array('printerId' => $this->PrinterId, 'captcha' => $VerificationCode); $json_BizData = json_encode($BizData); return $this->DoMyPost($URL, $json_BizData); } // 云打印 public function CloudPrint( $ShareCode, $PrintDataList) { $URL = $this->ApiUrl."/print"; $BizData = array('printerId' => $this->PrinterId, 'shareCode' => $ShareCode, 'printData' => $PrintDataList); $json_BizData = json_encode($BizData); return $this->DoMyPost($URL, $json_BizData); } // 云打印状态查询 public function QueryPrintStatus($ShareCode) { $URL = $this->ApiUrl."/printer/status/query"; $BizData = array('printerId' => $this->PrinterId, 'shareCode' => $ShareCode); $json_BizData = json_encode($BizData); return $this->DoMyPost($URL, $json_BizData); } //云打印解绑//0标识解绑失败,1标识解绑成功 public function unbind($ShareCode){ $URL = $this->ApiUrl."/printer/unbind"; $BizData = array('printerId' => $this->PrinterId, 'shareCode' => $ShareCode); $json_BizData = json_encode($BizData); return $this->DoMyPost($URL, $json_BizData); } /** * @Author: 小破孩嫩 * @Email: 3584685883@qq.com * @Time: 2021/4/1 10:39 * @param string $url url地址 * @param string $method 请求方法,默认为 'GET',可选值为 'GET' 或 'POST' * @param mixed $data 要发送的数据,如果是 POST 请求则为数据内容,否则为 null * @param array $headers 自定义请求头信息 * @param int $timeout 超时时间,默认为 30 秒 * @param bool $verifySSL 是否验证 SSL 证书,默认为 true * @param bool $flbg 返回值是否转成数组,默认不转 * @param bool $headercontent 是否获取请求的header值内容,默认不获取 * @return array|bool|mixed|string * @Description:curl请求 */ protected function curlRequest($url, $method = 'GET', $data = null, $flbg = false, $verifySSL = true, $headers = [], $headerContent = false, $timeout = 30) { // 初始化 cURL 会话 $ch = curl_init(); // 设置要请求的 URL curl_setopt($ch, CURLOPT_URL, $url); // 设置获取的信息以字符串形式返回,而不是直接输出 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 设置超时时间 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置请求方法 if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } // 设置请求头 if (!empty($headers)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } // 设置是否验证 SSL 证书 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySSL); // 执行 cURL 会话并获取响应 $response = curl_exec($ch); // 获取 HTTP 响应码 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 如果 cURL 执行出错 if (curl_errno($ch)) { // 输出错误信息 echo 'Curl error: ' . curl_error($ch); // 关闭 cURL 会话并返回 false curl_close($ch); return false; } // 如果 HTTP 响应码大于等于 400(表示错误) elseif ($httpCode >= 400) { // 输出错误信息 echo "HTTP error: $httpCode"; // 关闭 cURL 会话并返回 false curl_close($ch); return false; } // 处理是否获取请求头内容 if ($headerContent && $httpCode == 200) { $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $headerSize); $body = substr($response, $headerSize); curl_close($ch); return [$headers, $body]; } // 关闭 cURL 会话 curl_close($ch); // 处理是否将响应转换为数组 if ($flbg) { $response = json_decode($response, true); } // 返回响应内容 return $response; } /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2025/1/10 14:02 * @param $code * @return string * @Description:打印机错误码 */ public function errorCode($code){ $errorCodes = [ 200 => "success 成功", 500 => "sys fail 系统异常", 2001 => "sign check fail 签名失败", 2002 => "not find partner 查无合作伙伴", 2003 => "illegal access 非法访问", 3001 => "param check error 参数错误", 3002 => "please input params 请输入参数", 40001 => "please input APPID 请输入appid", 40002 => "biz exception 业务异常", 40003 => "printer xxx is offline 打印机离线", 40004 => "printer xxx is not auth 打印机未授权", 40005 => "shareCode is error 分享码错误", 40006 => "printer xxx after 5 minutes reprinting 请5分钟后重试", 40007 => "printer xxx captcha error 验证码错误", 40008 => "printer xxx captcha expired 验证码过期", 40009 => "printer xxx bind fail 绑定失败", 40023 => "lip not close 盖子未闭合", 40023 => "sticker 粘纸", 40023 => "sticker and lip not close 粘纸并且盖子未闭合", 40023 => "no page 缺纸", 40023 => "no page and lip not close 缺纸并且盖子未闭合", 40023 => "temperature too high 温度过高", 40023 => "temperature too high and lip not close 温度过高且盖子未闭合", 40023 => "temperature too high and sticker 温度过高且粘纸", 40023 => "temperature too high and lip not close and sticker 温度过高且粘纸,盖子未闭合", 40023 => "command error 指令错误" ]; return $errorCodes[$code]; } }模板<?php namespace app\common\lib\printman\template; use app\common\lib\printman\Template; class Temp1 implements Template { protected $id; public function __construct($id = "") { $this->id = $id; } /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2025/1/10 10:51 * @return string * @Description:打印模板1 */ public function temp($order = ""){ if(empty($order)){ return "没有订单信息"; } $height = ceil(self::getTempHight($order)/10+50); $i = 40; $template = ""; $template .= "SIZE 72 mm, ".$height." mm\r\n"; $template .= "CODEPAGE 437\r\n"; $template .= "DENSITY 8\r\n"; $template .= "CLS \r\n"; $template .= "CODEPAGE 936\r\n"; $template .= "DIRECTION 0\r\n"; $template .= "TEXT 220,0,\"4\",0,1,1,\""."订单详情"."\"\r\n"; //小票标题 $template .= "TEXT 220,".$i.",\"4\",0,1,1,\"".""."\"\r\n"; //换行 $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"订单编号:".$order['o_uuid']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"打印时间:".date("Y-m-d H:i:s")."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"申 请 人:".$order['o_address_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"联系方式:".$order['o_address_tel_default']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"收货地址:".mb_substr($order['o_address_info'],0,16)."\"\r\n"; if(mb_strlen($order['o_address_info']) > 16){ $template .= "TEXT 40,"; $template .= $i+=30; $template .=",\"0\",0,1,1,\" ".mb_substr($order['o_address_info'],16)."\"\r\n"; } $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"申请时间:".$order['o_create_time']."\"\r\n"; // $template .= "TEXT 40,"; // $template .= $i+=30; // $template .= ",\"0\",0,1,1,\"审核时间:".date("Y-m-d H:i:s",$order['o_pay_receipt_allow_time'])."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"配 送 员:".$order['salesmaninfo']['sm_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"配送电话:".$order['salesmaninfo']['sm_phone']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"出货仓库:".$order['warehouse_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"商品总数:".$order['goods_total_num']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"商品总价:".$order['o_real_price']."\"\r\n"; $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; $template .= "TEXT 40,"; $template .= $i+=16; $template .= ",\"0\",0,1,1,\"商品 数量 单价 金额\"\r\n"; $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; foreach ($order['order_list'] as $kk => $vv){ if(!empty($vv['oi_issendgoods'])){ $firstNamaText = "赠品:"; }else{ $firstNamaText = "商品:"; } $knum = $kk+=1; $template .= "TEXT 30,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"$knum.". mb_substr($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')',0,26)."\"\r\n"; if(mb_strlen($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')') > 26){ $template .= "TEXT 30,"; $template .= $i+=30; $template .=",\"0\",0,1,1,\" ".mb_substr($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')',26)."\"\r\n"; } $template .= "TEXT 65,"; $template .= $i+=30; if(!empty($vv['oi_issendgoods'])){ $template .=",\"0\",0,1,1,\"".$vv['oi_sku_info']['num'].'件'.' '."0.00".' '."0.00"."\"\r\n"; }else{ $template .=",\"0\",0,1,1,\"".$vv['oi_sku_info']['num'].'件'.' '.sprintf("%.2f",$vv['oi_sku_info']['sgcs_price']/$vv['oi_sku_info']['num']).' '.$vv['oi_real_price']."\"\r\n"; } } $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"数量总计:".$order['goods_total_num'].'件'."\"\r\n"; $template .= "PRINT 1,1"; $instacneStr = new \app\common\lib\data\Str(); $data = [ 'waybillPrinterData' => $instacneStr->gzipAndBase64Encode($template), 'printType' => 'tspl', 'id' => $this->id ]; return [$data]; } protected function getTempHight($order){ $i = 50; $template = ""; $template .= "SIZE 72 mm, 90 mm\r\n"; $template .= "CODEPAGE 437\r\n"; $template .= "DENSITY 8\r\n"; $template .= "CLS \r\n"; $template .= "CODEPAGE 936\r\n"; $template .= "DIRECTION 0\r\n"; $template .= "TEXT 220,0,\"4\",0,1,1,\""."订单详情"."\"\r\n"; //小票标题 $template .= "TEXT 220,".$i.",\"4\",0,1,1,\"".""."\"\r\n"; //换行 $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"订单编号:".$order['o_uuid']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"打印时间:".date("Y-m-d H:i:s")."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"申 请 人:".$order['o_address_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"联系方式:".$order['o_address_tel_default']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"收货地址:".mb_substr($order['o_address_info'],0,16)."\"\r\n"; if(mb_strlen($order['o_address_info']) > 16){ $template .= "TEXT 40,"; $template .= $i+=30; $template .=",\"0\",0,1,1,\" ".mb_substr($order['o_address_info'],16)."\"\r\n"; } $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"申请时间:".$order['o_create_time']."\"\r\n"; // if(empty($order['o_help'])){ // $template .= "TEXT 40,"; // $template .= $i+=30; // $template .= ",\"0\",0,1,1,\"审核时间:".date("Y-m-d H:i:s",$order['o_pay_receipt_allow_time'])."\"\r\n"; // } $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"配送员姓名:".$order['salesmaninfo']['sm_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"配送员电话:".$order['salesmaninfo']['sm_phone']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"出货仓库:".$order['warehouse_name']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"商品总数:".$order['goods_total_num']."\"\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"商品总价:".$order['o_real_price']."\"\r\n"; $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; $template .= "TEXT 40,"; $template .= $i+=16; $template .= ",\"0\",0,1,1,\"商品 数量 单价 金额\"\r\n"; $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; // foreach ($order as $key => $val){ foreach ($order['order_list'] as $kk => $vv){ if(!empty($vv['oi_issendgoods'])){ $firstNamaText = "赠品:"; }else{ $firstNamaText = "商品:"; } $knum = $kk+=1; $template .= "TEXT 30,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"$knum.". mb_substr($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')',0,26)."\"\r\n"; if(mb_strlen($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')') > 26){ $template .= "TEXT 30,"; $template .= $i+=30; $template .=",\"0\",0,1,1,\" ".mb_substr($firstNamaText.$vv['oi_sku_info']['goods_info']['withgoodsinfoinfo']['sg_name'].'('.$vv['oi_sku_info']['sgcs_name'].')',26)."\"\r\n"; } $template .= "TEXT 65,"; $template .= $i+=30; $template .=",\"0\",0,1,1,\"".$vv['oi_sku_info']['num'].'件'.' '.$vv['oi_sku_info']['sgcs_price'].' '.$vv['oi_sku_info']['sgcs_price']*$vv['oi_sku_info']['num']."\"\r\n"; } $template .= "BAR 20,"; $template .= $i+=28; $template .= ",720,2\r\n"; $template .= "TEXT 40,"; $template .= $i+=30; $template .= ",\"0\",0,1,1,\"数量总计:".$order['goods_total_num']."\"\r\n"; // } $template .= "PRINT 1,1"; return $i; } }
2025年03月13日
130 阅读
0 评论
1 点赞
2025-03-13
【PHP】给富文本内容的图片,视频,文件 拼接当前网址域名
/** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/11/18 15:20 * @param $text * @param $domain * @return string|string[]|null * @Description:给服务文本拼接当前网址域名 */ public function addDomainToPaths($text, $domain){ // 匹配图片路径 $text = preg_replace('/<img.*?src="([^"]+)"/i', '<img src="' . $domain . '$1"', $text); // 匹配视频路径 $text = preg_replace('/<video.*?src="([^"]+)"/i', '<video src="' . $domain . '$1"', $text); // 匹配文件路径(可根据具体文件类型的链接特征进行修改) $text = preg_replace('/<a.*?href="([^"]+)"/i', '<a href="' . $domain . '$1"', $text); return $text; }
2025年03月13日
109 阅读
0 评论
0 点赞
2025-03-13
【PHP】过滤富文本内容
封装了一个类class TextFilter { // 定义要过滤的 SQL 关键字模式 const SQL_PATTERNS = [ '/\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|AND|OR|JOIN|DROP|CREATE|ALTER|TRUNCATE|GRANT|REVOKE|SET)\b/i', '/\b(AS|LIKE|NOT|IN|BETWEEN|IS|NULL|COUNT|SUM|AVG|MIN|MAX)\b/i', '/\b(UNION|ALL|ANY|EXISTS)\b/i', '/\b(ORDER\s+BY|LIMIT)\b/i' ]; // 定义要过滤的常见函数模式 const FUNCTION_PATTERNS = [ '/\b(function\s+\w+\s*\([^)]*\))\b/i', '/\b(eval|exec|system|passthru|shell_exec|assert)\b/i' ]; // 定义要过滤的特殊字符和表达式模式 const SPECIAL_PATTERNS = [ '/\$\{.*?\}/', // 过滤类似 ${expression} 的表达式 '/@.*?;/', // 过滤以 @ 开头并以 ; 结尾的表达式 '/\b(phpinfo|var_dump)\b/i', // 过滤特定的 PHP 函数 '/<\s*(script|iframe|object|embed|applet)[^>]*>/i' // 过滤危险的脚本标签 ]; // 定义要过滤的危险属性模式 const DANGEROUS_ATTRIBUTES_PATTERNS = [ '/on\w+\s*=/i', // 过滤以 "on" 开头的事件属性 '/javascript:[^"]*"/i' // 过滤 JavaScript 协议的链接 ]; /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/10/24 13:50 * @param $text * @return string|string[]|null * @Description:过滤富文本 */ public static function filterRichText($text) { // 合并所有要过滤的模式 $allPatterns = array_merge( self::SQL_PATTERNS, self::FUNCTION_PATTERNS, self::SPECIAL_PATTERNS, self::DANGEROUS_ATTRIBUTES_PATTERNS ); // 先过滤所有匹配的模式 $filteredText = preg_replace($allPatterns, '', $text); // 保留 <img> 标签,但需要确保 src 属性是安全的 $filteredText = preg_replace_callback('/<img[^>]+>/i', [__CLASS__, 'filterImgTag'], $filteredText); // 允许表情符号和其他图标 $filteredText = preg_replace('/[\x{1F600}-\x{1F64F}]|\x{1F300}-\x{1F5FF}|\x{1F680}-\x{1F6FF}|\x{2600}-\x{26FF}|\x{2700}-\x{27BF}/u', '$0', $filteredText); // 处理可能出现的连续空格 $filteredText = preg_replace('/\s+/', ' ', $filteredText); // 去除前后的空格 $filteredText = trim($filteredText); // 转换 HTML 实体 $filteredText = htmlentities($filteredText, ENT_QUOTES, 'UTF-8'); return $filteredText; } private static function filterImgTag($matches) { $imgTag = $matches[0]; if (preg_match('/src=["\'](?<src>[^"\']+)["\']/i', $imgTag, $srcMatch)) { $src = $srcMatch['src']; // 这里可以进一步验证 src 是否是允许的 URL 或本地路径 if (filter_var($src, FILTER_VALIDATE_URL) || strpos($src, '/') === 0) { return $imgTag; } } return ''; } } // 示例调用 $text = '<script>alert("XSS")</script><img src="https://example.com/image.jpg">'; $filteredText = TextFilter::filterRichText($text); echo $filteredText; 函数 方法 /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/10/24 13:50 * @param $text * @return string|string[]|null * @Description:过滤富文本 */ public static function filterRichText($text){ // 定义要过滤的 SQL 关键字模式 $sqlPatterns = [ '/\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|AND|OR|JOIN|DROP|CREATE|ALTER|TRUNCATE|GRANT|REVOKE|SET)\b/i', '/\b(AS|LIKE|NOT|IN|BETWEEN|IS|NULL|COUNT|SUM|AVG|MIN|MAX)\b/i', '/\b(UNION|ALL|ANY|EXISTS)\b/i', '/\b(ORDER\s+BY|LIMIT)\b/i' ]; // 定义要过滤的常见函数模式 $functionPatterns = [ '/\b(function\s+\w+\s*\([^)]*\))\b/i', '/\b(eval|exec|system|passthru|shell_exec|assert)\b/i' ]; // 定义要过滤的特殊字符和表达式模式 $specialPatterns = [ '/\$\{.*?\}/', // 过滤类似 ${expression} 的表达式 '/@.*?;/', // 过滤以 @ 开头并以 ; 结尾的表达式 '/\b(phpinfo|var_dump)\b/i', // 过滤特定的 PHP 函数 '/<\s*(script|iframe|object|embed|applet)[^>]*>/i' // 过滤危险的脚本标签 ]; // 定义要过滤的危险属性模式 $dangerousAttributesPatterns = [ '/on\w+\s*=/i', // 过滤以 "on" 开头的事件属性 '/javascript:[^"]*"/i' // 过滤 JavaScript 协议的链接 ]; // 先过滤 SQL 关键字 $filteredText = preg_replace($sqlPatterns, '', $text); // 再过滤函数 $filteredText = preg_replace($functionPatterns, '', $filteredText); // 然后过滤特殊字符和表达式 $filteredText = preg_replace($specialPatterns, '', $filteredText); // 接着过滤危险的属性 $filteredText = preg_replace($dangerousAttributesPatterns, '', $filteredText); // 允许表情符号和其他图标 $filteredText = preg_replace('/[\x{1F600}-\x{1F64F}]|\x{1F300}-\x{1F5FF}|\x{1F680}-\x{1F6FF}|\x{2600}-\x{26FF}|\x{2700}-\x{27BF}/u', '$0', $filteredText); // 处理可能出现的连续空格 $filteredText = preg_replace('/\s+/', ' ', $filteredText); // 去除前后的空格 $filteredText = trim($filteredText); // 转换 HTML 实体 $filteredText = htmlentities($filteredText, ENT_QUOTES, 'UTF-8'); return $filteredText; } /** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/10/24 13:50 * @param $text * @return string|string[]|null * @Description:过滤富文本 */ function filterRichText($text) { // 合并所有要过滤的模式 $patterns = [ '/\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|AND|OR|JOIN|DROP|CREATE|ALTER|TRUNCATE|GRANT|REVOKE|SET)\b/i', '/\b(AS|LIKE|NOT|IN|BETWEEN|IS|NULL|COUNT|SUM|AVG|MIN|MAX)\b/i', '/\b(UNION|ALL|ANY|EXISTS)\b/i', '/\b(ORDER\s+BY|LIMIT)\b/i', '/\b(function\s+\w+\s*\([^)]*\))\b/i', '/\b(eval|exec|system|passthru|shell_exec|assert)\b/i', '/\$\{.*?\}/', '/@.*?;/', '/\b(phpinfo|var_dump)\b/i', '/<\s*(script|iframe|object|embed|applet)[^>]*>/i', '/on\w+\s*=/i', '/javascript:[^"]*"/i' ]; // 先过滤所有匹配的模式 $filteredText = preg_replace($patterns, '', $text); // 允许表情符号和其他图标 $filteredText = preg_replace('/[\x{1F600}-\x{1F64F}]|\x{1F300}-\x{1F5FF}|\x{1F680}-\x{1F6FF}|\x{2600}-\x{26FF}|\x{2700}-\x{27BF}/u', '$0', $filteredText); // 处理可能出现的连续空格 $filteredText = preg_replace('/\s+/', ' ', $filteredText); // 去除前后的空格 $filteredText = trim($filteredText); // 转换 HTML 实体 $filteredText = htmlentities($filteredText, ENT_QUOTES, 'UTF-8'); return $filteredText; } // 示例调用 $text = '<script>alert("XSS")</script><img src="https://example.com/image.jpg">'; $filteredText = filterRichText($text); echo $filteredText;
2025年03月13日
90 阅读
0 评论
0 点赞
2025-03-13
【PHP】获取二维数组里面最小的值
/** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/12/5 17:16 * @param $array * @return array * @Description:获取一个二维数组,数据最小的,并返回对应的key和value */ public function getMinValueKey($array) { $minValue = PHP_INT_MAX; $desiredKey = null; foreach ($array as $key => $subArray) { foreach ($subArray as $subKey => $value) { if ($value < $minValue) { $minValue = $value; $desiredKey = $subKey; } } } return [$desiredKey, $minValue]; } //使用场景 $inatanceMap = new \app\common\lib\map\baidu\Lnglat($this->param['ac_address']); $lnglat = $inatanceMap->addressToLngLat(); $this->param['u_lng'] = $lnglat['lng'];//经度 $this->param['u_lat'] = $lnglat['lat'];//纬度 $companyList = M("AdminCompany")::getCompanyListUseSelect(); $instanceDis = new \app\common\lib\map\Distance(); foreach ($companyList as $key => $val){ $arrAddress[$key][$val['ac_uuid']] = $instanceDis->getdistance($val['ac_lng'],$val['ac_lat'],$lnglat['lng'],$lnglat['lat']); } $instanceArr = new \app\common\lib\data\Arr(); list($minKey, $minValue) = $instanceArr->getMinValueKey($arrAddress); $this->param['u_company_uuid'] = $minKey; $this->param['u_address'] = $this->param['ac_address'];
2025年03月13日
92 阅读
0 评论
0 点赞
2025-03-13
【PHP】按照个商品金额,等比例分配优惠劵
/** * @Author:小破孩 * @Email:3584685883@qq.com * @Time:2024/11/23 11:41 * @param $products ['id' => 'price','id' => price] * @param $totalCouponAmount 优惠劵优惠金额 * @return array * @Description:按照商品比例拆分优惠劵,分配给对应的商品 */ public function getSplitCoupon($products, $totalCouponAmount) { $totalAmount = array_sum($products); $discounts = []; $allocatedDiscount = 0; foreach ($products as $id => $amount) { $ratio = $amount / $totalAmount; $discount = $ratio * $totalCouponAmount; $roundedDiscount = round($discount, 2); $discounts[$id] = $roundedDiscount; $allocatedDiscount += $roundedDiscount; } // 调整以使总和为指定的优惠券总额 $diff = $totalCouponAmount - $allocatedDiscount; if ($diff!= 0) { $sortedDiscounts = $discounts; arsort($sortedDiscounts); $i = 0; foreach ($sortedDiscounts as $id => $discount) { if ($i < abs($diff)) { $discounts[$id] += ($diff > 0)? 0.01 : -0.01; } $i++; } } return $discounts; }
2025年03月13日
91 阅读
0 评论
0 点赞
2024-10-26
【ThinkPHP】最新版本上传文件的类
<?php namespace app\common\lib\file; use think\Exception; use think\exception\ValidateException; class Uploads { private $domain; protected $name; protected $type; protected $module; protected $image; public function __construct($name = '',$image = []) { $this->name = $name; $this->module = app('http')->getName(); $this->image = $image; $this->domain = Request()->domain(); } protected $config = [ 'image' => [ 'validate' => [ 'size' => 10*1024*1024, 'ext' => 'jpg,png,gif,jpeg', ], 'path' => '/images', ], 'audio' => [ 'validate' => [ 'size' => 100*1024*1024, 'ext' => 'mp3,wav,cd,ogg,wma,asf,rm,real,ape,midi', ], 'path' => '/audios', ], 'video' => [ 'validate' => [ 'size' => 100*1024*1024, 'ext' => 'mp4,avi,rmvb,rm,mpg,mpeg,wmv,mkv,flv', ], 'path' => '/videos', ], 'file' => [ 'validate' => [ 'size' => 5*1024*1024, 'ext' => 'doc,docx,xls,xlsx,pdf,ppt,pptx,txt,rar,zip,pem,p12', ], 'path' => '/files', ], ]; private function determineFileType($file) { $mime = $file->getMime(); if (strpos($mime, 'image/') === 0) { $this->type = 'image'; } elseif (strpos($mime, 'video/') === 0) { $this->type = 'video'; } elseif (strpos($mime, 'audio/') === 0) { $this->type = 'audio'; } else { $this->type = 'file'; } if (!in_array($this->type, array_keys($this->config))) { throw new ValidateException("the file type does not exist"); } validate(['file' => self::validateFile()])->check(['file' => $file]); } public function upfile($infoSwitch = false,$savelocal = 'local'){ try{ $file = request()->file($this->name); //检测文件 if($file == null) throw new ValidateException("the file cannot be empty"); //验证文件 $this->determineFileType($file); //上传文件 switch ($savelocal){ case 'aliyun': $savename = \think\facade\Filesystem::disk('aliyun')->putFile( $this->module.$this->config[$this->type]['path'], $file); break; case 'qiniu': $savename = \think\facade\Filesystem::disk('qiniu')->putFile( $this->module.$this->config[$this->type]['path'], $file); break; case 'qcloud': $savename = \think\facade\Filesystem::disk('qcloud')->putFile( $this->module.$this->config[$this->type]['path'], $file); break; case 'local': $savename = \think\facade\Filesystem::disk('public')->putFile( $this->module.$this->config[$this->type]['path'], $file); break; default : $savename = \think\facade\Filesystem::disk('public')->putFile( $this->module.$this->config[$this->type]['path'], $file); break; } // $savename = \think\facade\Filesystem::disk('public')->putFile( $this->module.$this->config[$this->type]['path'], $file); // $savename = \think\facade\Filesystem::disk('aliyun')->putFile( 'topic', $file); //返回文件详情和文件地址 if($infoSwitch){ return self::getFileInfo($file,$savename); } return $this->domain.config('filesystem.disks.public.url').'/'.str_replace('\\','/',$savename); }catch (\think\exception\ValidateException $e){ return show(100,self::languageChange($e->getMessage())); } } private function validateFile(){ if(empty($this->image)){ $validataType = [ 'fileSize' => $this->config[$this->type]['validate']['size'], 'fileExt' => $this->config[$this->type]['validate']['ext'], ]; }else{ if(is_array($this->image)) throw new ValidateException(""); $validataType = [ 'fileSize' => $this->config[$this->type]['validate']['size'], 'fileExt' => $this->config[$this->type]['validate']['ext'], 'image' => $this->image //示例值 [200,200] ]; } return $validataType; } private function languageChange($msg){ $data = [ 'the file type does not exist' => '文件类型不存在!', 'the file cannot be empty' => '文件不能为空!', 'unknown upload error' => '未知上传错误!', 'file write error' => '文件写入失败!', 'upload temp dir not found' => '找不到临时文件夹!', 'no file to uploaded' => '没有文件被上传!', 'only the portion of file is uploaded' => '文件只有部分被上传!', 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', 'upload write error' => '文件上传保存错误!', ]; return $data[$msg] ?? $msg; } private function getFileInfo($file,$savename){ $info = [ 'path' => config('filesystem.disks.public.url').'/'.str_replace('\\','/',$savename), 'url' => $this->domain.config('filesystem.disks.public.url').'/'.str_replace('\\','/',$savename), 'size' => $file->getSize(), 'name' => $file->getOriginalName(), 'mime' => $file->getMime(), 'ext' => $file->extension(), 'type' => $this->type ]; return $info; } public function __clone() { throw new Exception("Cloning operation is not allowed"); // TODO: Implement __clone() method. } // 使用方法 : // $instanceUpload = new Uploads($fileName); // $info = $instanceUpload->upfile(true); } 如果要用建议封装一下,使用interface定义个规范,这里更像一个方法函数
2024年10月26日
160 阅读
0 评论
0 点赞
2024-09-30
【PHP】PHP函数详解:call_user_func()使用方法
PHP函数详解:call_user_func()使用方法 call_user_func函数类似于一种特别的调用函数的方法,使用方法如下: <?php function nowamagic($a,$b) { echo $a; echo $b; } call_user_func('nowamagic', "111","222"); call_user_func('nowamagic', "333","444"); //显示 111 222 333 444 ?> 调用类内部的方法比较奇怪,居然用的是array,不知道开发者是如何考虑的,当然省去了new,也挺有新意的: <?php class a { function b($c) { echo $c; } } call_user_func(array("a", "b"),"111"); //实例化a类并调用b方法 //显示 111 ?> call_user_func_array函数和call_user_func很相似,只不过是换了一种方式传递了参数,让参数的结构更清晰: <?php function a($b, $c) { echo $b; echo $c; } call_user_func_array('a', array("111", "222")); //显示 111 222 ?> call_user_func_array函数也可以调用类内部的方法的 <?php Class ClassA { function bc($b, $c) { $bc = $b + $c; echo $bc; } } call_user_func_array(array('ClassA','bc'), array("111", "222")); //显示 333 ?> call_user_func函数和call_user_func_array函数都支持引用,这让他们和普通的函数调用更趋于功能一致: <?php function a($b) { $b++; } $c = 0; call_user_func('a', $c); echo $c;//显示 1 call_user_func_array('a', array($c)); echo $c;//显示 2 ?> 另外,call_user_func函数和call_user_func_array函数都支持引用。 <?php function increment(&$var) { $var++; } $a = 0; call_user_func('increment', $a); echo $a; // 0 call_user_func_array('increment', array(&$a)); // You can use this instead echo $a; // 1 ?>
2024年09月30日
174 阅读
0 评论
0 点赞
2024-06-23
【PHP】H5微信网页自定义分享功能实现
<?php namespace app\index\lib\wechat; header("Access-Control-Allow-Origin:*"); class share { public $appid; public $secret; // 步骤1.appid和secret //header("Access-Control-Allow-Origin:*"); //$appid = "appid"; //$secret = "secret"; public function __construct($appid,$secret) { $this->appid = $appid; $this->secret = $secret; } // 步骤2.生成签名的随机串 public function nonceStr($length){ $str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK1NGJBQRSTUVWXYZ';//随即串,62个字符 $strlen = 62; while($length > $strlen){ $str .= $str; $strlen += 62; } $str = str_shuffle($str); return substr($str,0,$length); } // 步骤3.获取access_token public function http_get($url){ $oCurl = curl_init(); if(stripos($url,"https://")!==FALSE){ curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1 } curl_setopt($oCurl, CURLOPT_URL, $url); curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 ); $sContent = curl_exec($oCurl); $aStatus = curl_getinfo($oCurl); curl_close($oCurl); if(intval($aStatus["http_code"])==200){ return $sContent; }else{ return false; } } // 步骤4.获取ticket public function getTicket(){ $result = $this->http_get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->secret); $json = json_decode($result,true); $access_token = $json['access_token']; $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$access_token"; $res = json_decode ( $this->http_get ( $url ) ); return $res->ticket; } // 步骤5.生成wx.config需要的参数 //$surl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; //$ws = getWxConfig( $ticket,$surl,time(),nonceStr(16) ); // public function getWxConfig($jsapiTicket,$url,$timestamp,$nonceStr) { public function getWxConfig() { $jsapiTicket=$this->getTicket(); $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; $timestamp = time(); $nonceStr = $this->nonceStr(rand(8,15)); $string = "jsapi_ticket=".$jsapiTicket."&noncestr=".$nonceStr."×tamp=".$timestamp."&url=".$url; $signature = sha1 ($string); $WxConfig["appId"] = $this->appid; $WxConfig["nonceStr"] = $nonceStr; $WxConfig["timestamp"] = $timestamp; $WxConfig["url"] = $url; $WxConfig["signature"] = $signature; $WxConfig["rawString"] = $string; return $WxConfig; } } public function getWxShareConfig(){ $instanceWxShare = new \app\index\lib\wechat\share('appid','secret'); return $instanceWxShare->getWxConfig(); } <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script> // console.log(timestamp); wx.config({ debug: false, appId: '{$wxsc.appId}', timestamp: '{$wxsc.timestamp}', nonceStr: '{$wxsc.nonceStr}', signature: '{$wxsc.signature}', jsApiList: ['updateAppMessageShareData','updateTimelineShareData'] }); wx.ready(function () { //需在用户可能点击分享按钮前就先调用 wx.updateAppMessageShareData({ title: '医博:', // 分享标题 desc: '专业肛肠、胃肠、中医交流平台,为业界名专家们搭建教学、学术平台,为专业医生提供手术直播、科普交流基地,为学者提供学习、沟通、上升平台。', // 分享描述 link: 'https://wx.kmyebo.com', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: 'https://wx.kmyebo.com/yb_share_icon.jpg', // 分享图标 success: function () { // 设置成功 } }) }); wx.ready(function () { //需在用户可能点击分享按钮前就先调用 wx.updateTimelineShareData({ title: '医博:', // 分享标题 link: 'https://wx.kmyebo.com', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: 'https://wx.kmyebo.com', // 分享图标 success: function () { // 设置成功 } }) }); </script>http://www.xmyfw.com.cn/pc/show.php?id=55
2024年06月23日
202 阅读
0 评论
0 点赞
1
2
3
...
13