数据库内容多语言方案

场景:很多网站,app,等互联网应用,面向国际业务,需要支持多种语言。

方案一

调用第三方翻译API,如,百度翻译,谷歌翻译,有道翻译等等。在渲染页面或这响应接口之前调用api将内容翻译成目标语言,然后在展示。

缺点:依赖第三方服务,延迟高。

方案二

单表冗余字段设计,比如content字段,增加content_en content_fr …

适应于2个语种,字段少的情况。例如,只有content字段需要多语言,而且只有两种语种。

缺点:

  • 导致单表字段超出数据库设计规范,字段数会翻倍。
  • varchar和char类型 具有 65,535 字节的最大行大小限制,text字段也有容量限制
  • 缺少扩展性,维护的时候还需要增加表字段

方案三

单字段存储json,字段类型设为longText ;

场景:适合短文本少量语种,一旦超出文本限制,就不适用了,例如:长篇富文本多语言支持这类需求。

laravel ORM增加语言支持 扩展包 spatie/laravel-translatable 使用json方式存储

需要对多语言字段内容,增加唯一性验证时,使用 codezero-be/laravel-unique-translation

方案四

全表文本都需要翻译的情况,增加语言类型字段

languagenamecontent
zh-CN名称内容
ennamecontent

全表只有几个文本字段的情况,增加语言翻译扩展表

场景:扩展表设计适合,多语种,长文本存储

主表结构存储,不需要翻译文本的字段,附表结构参考上表格,增加关联id

laravel ORM增加语言支持 扩展包 Astrotomic/laravel-translatable 使用该方式实现 文档地址

参考

如何为 Eloquent 添加多语言支持

网址多语言设计

 MySQL TEXT 字段的限制

数据迁移使用规范

目的,为了使命名更具有语义化和可读性,方便维护

以laravel框架为例,按实际操作行为划分

  • 添加 删除 修改(主要参考迁移功能,提供的api方法) 对应名称create drop modify
  • 2种以上的操作行为,都算修改
  • 给单一操作增加后缀,字段 column,索引 index,视图view, 存储过程 ,函数
  • 命名重复情况增加版本号 v1.0.0 …

操作表

表字段,标注注释,表索引

创建表

create_xxxx_table.php

删除表

drop_xxxx_table.php

修改表

包含添加删除字段或索引或其他 ,2种以上行为

modify_xxxx_table.php

操作字段

字段名称,字段数据类型,字段注释

添加字段

create_xxxx_table_column.php

删除字段

drop_xxxx_table_column.php

修改字段

包含添加和删除和修改字段,2种以上行为

modify_xxxx_table_column.php

操作索引

操作普通索引,全文索引,空间索引等;

索引优化是业务开发中,修改频率很高的行为,所以需要单独列出来,有很多场景需要,单独的变更索引,而不修改字段

添加索引

create_xxxx_table_index.php

删除索引

drop_xxxx_table_index.php

修改索引

包含添加 删除 修改 和自定义 2种以上行为时

modify_xxxx_table_index.php

单一操作行为的规则,参考上文以此类推。

多次重复行为时增加语义化版本后缀

需要改进

比如第一次业务变更 增加了字段 create_users_table_column.php

第二次业务变更,又要对users表增加字段,则命名为 create_users_table_column_v1.0.0.php

laravel8.x以上 可以使用压缩迁移

// 转储当前数据库架构并删除所有现有迁移。。。
php artisan schema:dump --prune

Api设计指南

基本概念

应用程序编程接口

按层级划分

操作系统->编程语言->框架类库->Web API

编程语言对操作系统的API封装,框架类库对编程语言API的封装, Web API是对框架类库的封装

按通讯方式划分

RPCREST APIWebSocket API
消息格式二进制Thrift,Protobuf,GRPC文本XML,JSON,GraphQL 二级制,文本json
通讯协议TCPHTTP,HTTP/2HTTP,websocket
性能一般 一般
接口契约IDLThrift,Protobuf IDLSwaggerSwagger
客户端强类型客户端HTTP客户端websocket客户端
框架Dubbo,GPRC,Thriftweb框架websocket框架
开发者友好一般自动生成存根、客户端、使用友好,二进制消息阅读不友好json可读性高,通用性json可读性高,通用性
应用场景服务间通讯推荐PRC对外暴露接口推荐REST大文件类的流式数据,语言识别服务

既然有HTTP协议,为什么还要有RPC

按应用场景划分

业务应用

app,小程序,pc站,手机站 等客户端提供 api

开放平台(Open API)

PaaSSaaS Serverless 平台的服务API

API设计必须要具备的指标

规范和风格

restful api 风格,laravel框架自带

Restful Api 风格规范研究

github路由风格,https://api.github.com/

全部小写,多单词时用短划线分隔

http://xxxx/register-phone

不要用下划线,下划线的域名和路由在一些第三方开放平台无法通过验证,如qq互联申请时

鉴权

OAuth2

Laravel搭建OAuth2.0服务

LaravelSanctum 授权 SPA场景

防止重放攻击

请求限流

浅谈api限流

按时间限流,req/minute

随机拒绝,如秒杀场景

api(路由)版本管理

Laravel路由版本控制的实现。

Guzzle使用经验总结

中文文档 英文文档

调用接口

主要功能,很多sdk都是使用该类库开发

写爬虫抓取页面

Laravel 下使用 Guzzle 编写多线程爬虫实战

项目中应用案例

java的古籍PC网站,该项目无人维护,无法提供书籍数据的接口。分析页面结构和接口使用guzzle库爬取书籍数据,完成数据对接。

在所用请求中共享cookie功能 文档

//创建客户端
$this->client = new Client([
    'base_uri' => $this->config['base_uri'],
    'timeout'  => 20.0,
    'cookies' => true, //共享cookie会话
);
//登录
protected function login()
{
    $response = $this->client->request('POST', 'XXX', [
            'headers' => [
                'Accept' => 'application/json'
            ],
            'form_params' => [
                'loginName' => $this->config['login_name'],
                'loginPassword' => $this->config['login_password']
            ]
        ]);

        $json = json_decode($response->getBody(), true);

        if (isset($json['operateMsg']) && $json['operateMsg'] !== '登录成功!') {
            throw new GujiException('原古籍系统账号故障');
        }
}

//请求接口数据
protected function request(string $pathUrl, array $param)
{
        $this->login(); //首先登录获取Cookies
        $response = $this->client->request('POST', $pathUrl, [
            'headers' => [
                'Accept' => 'application/json'
            ],
            'form_params' => $param
        ]);

        $contents = $response->getBody()->getContents();
        $json = json_decode($contents, true);

        if (json_last_error() === JSON_ERROR_NONE) {
            return $json;
        } elseif (json_last_error() == 10) {
            //解决json_decode错误Single unpaired UTF-16 surrogate in unicode escape
            $contents = \preg_replace('/(?<!\\\)\\\u[a-f0-9]{4}/iu', '', $contents);
            $json = \json_decode($contents, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                $json = $this->customJsonDecode($contents);
            }

            return $json;
        }
        {
            throw new GujiException("请求古籍系统接口失败");
        }
}

//抓取页面数据
protected function capture(string $pathUrl, array $param = [])
{
        $this->login(); //首先登录获取Cookies
        $response = $this->client->request('GET', $pathUrl, $param);

        if ($response->getStatusCode() == 200) {
            //获取页面内容
            return $response->getBody()->getContents();
        } else {
            throw new GujiException("古籍系统故障");
        }
}

跟随重定向

https://docs.guzzlephp.org/en/stable/faq.html#how-can-i-track-redirected-requests

https://docs.guzzlephp.org/en/stable/request-options.html#allow-redirects

调用非知名第三方支付系统,前后端分离架构,前端重定向到接口,接口调用第三方支付接口,成功后跟随响应到成功页面

use Illuminate\Support\Facades\Http;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

//Http::withOptions laravel对guzzle的封装,详情看文档
$response = Http::withOptions([
            'allow_redirects' => [
                'max'             => 1,
                'on_redirect'     => function (
                    RequestInterface $request,
                    ResponseInterface $response,
                    UriInterface $uri
                ) use ($data) {

                    //自动跟随重定向响应
                    header('Location:' . $uri);
                },
            ]
        ])->asForm()->post($this->config['base_uri'] . '/multipay/h5.do', $data);

如何实现给每个注册用户生成固定独立的URL地址

使用二级域名作为独立url, nginx使用通配符配置域名绑定

这种方案浪费域名资源

官方文档 https://nginx.org/en/docs/http/server_names.html

本地测试

host文件增加三个配置

127.0.0.1 abc.pan-domain.stu
127.0.0.1 efg.pan-domain.stu
127.0.0.1 yangliuan.pan-domain.stu

nginx配置

server {
    listen 80;
    #通配符绑定
    server_name *.pan-domain.stu;
    root /home/yangliuan/Code/Study/PHP/pan-domain;
    index index.php;

    location ~ [^/]\.php(/|$) {
        fastcgi_pass unix:/dev/shm/php-cgi.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location ~ ^/(\.user.ini|\.ht|\.git|\.svn|\.project|LICENSE|README.md) {
        deny all;
    }
}

测试结果

对其中一个域名yangliuan.pan-domain.stu 进行优先级测试,增加单独的站点配置和绑定,

server {
    listen 80;
    server_name yangliuan.pan-domain.stu;
    root /home/yangliuan/Code/Study/PHP/test;
    index index.php;

    location ~ [^/]\.php(/|$) {
        #fastcgi_pass remote_php_ip:9000;
        fastcgi_pass unix:/dev/shm/php-cgi.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location ~ ^/(\.user.ini|\.ht|\.git|\.svn|\.project|LICENSE|README.md) {
        deny all;
    }
}

结果如下

说明 server_name指令中 指定名称的优先级大于通配符

yangliuan.pan-domain.stu 的优先级大于 *.pan-domain.stu

如果云平台可以调用平台的域名解析接口,添加子域名解析 或者使用*.xxxx.com 泛域名解析

最简单实用的方案,给每个用户生成一个固定的独立子路由

  1. 使用用户昵称 命名
  1. 用户唯一标识 命名

PHP处理富文本html标签的方法

使用XML 相关扩展操作

https://www.php.net/manual/zh/refs.xml.php

示例代码

以处理图片地址为例子

  $htmlContent = '<p>测试<img src='xxx.jpeg'></p>';
  $htmlDom = new DOMDocument();
  @$htmlDom->loadHTML($htmlContent);
  $images = $htmlDom->getElementsByTagName('img');
  
  //处理富文本中的图片
  foreach ($images as $key => $image)
  {  
      //获取img图片的src属性值
      $src = $image->getAttribute('src');
      //拼接成完整的url
      $image->setAttribute('src', 'http://xxxx.com'.$src);
  }
  
  //获取body标签的内容
  $body = $htmlDom->getElementsByTagName('body')->item(0);
  //转换成html字符串
  $content = $htmlDom->saveHTML($body);
  //替换掉body标签
  $content = str_replace(['<body>', '</body>'], '', $content);

电商商品模块,关于规格问题的研究

基础概念

商品规格 商品属性 SKU

没有规格

最简单的商品规格设计,就是没有规格直接展示SKU

只需要两张表 商品表 (products) 和 商品sku表(product_skus)

product_skus 包含 库存和价格

多规格

有赞的设计方式

实现逻辑

对规格值进行笛卡尔积计算,生成SKU

1.规格不变,规格值发生变化时

添加规格值,重新生成添加值对应的笛卡尔积,删除规格值,删除删除值对应的笛卡尔积

2.规格变动,重新生成笛卡尔积覆盖原有数据

阿里云OSS相关问题

STS授权配置

参考:

https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.6.708.576e6cb8K5XZME

  1. 登陆案例云账号进入《控制台》之后,按照下图指示进行创建RAM用户
  1. 配置用户权限,看下图
  1. 创建AccessKey ID、AccessKey Secret (注意这一步弹框生成的文件一定要保存下来),默认进来之后会有一个AccessKey ID先删除再创建;具体看下图

4、新建自定义权限策略

{    
 "Version": "1",    
 "Statement": [     
   {           
     "Effect": "Allow",           
     "Action": [             
        "oss:*"           
     ],           
     "Resource": [             
       "acs:oss:*:*:Bucket名称",             
       "acs:oss:*:*:Bucket名称/*"           
     ]     
   }    
]}

4、创建RAM账号

5、bucket与RAM绑定

跨域设置,点击上图中的跨域设置进行下图的配置

OSS文件自动下载问题

参考

https://help.aliyun.com/knowledge_detail/39545.html?spm=a2c4g.11186623.2.19.58241a216Srrvd

文件被强制下载可能的原因以下所示:

  • 使用OSS提供的默认域名,且没有经过其他配置。需要绑定自定义域名
  • 对应资源的Content-Type设置错误。
  • 对应资源的Content-Disposition设置错误。
  • CDN缓存了错误的Content-Type或者Content-Disposition。
  • 浏览器不支持该格式资源的展示。

其它常见问题

https://help.aliyun.com/knowledge_detail/142685.html?spm=a2c4g.11186623.6.1859.5af0606caO1MHy

字符编码知识 乱码原因

参考

字符编码基础知识其一

字符编码笔记:ASCII,Unicode 和 UTF-8 ——阮一峰

分享一下我所了解的字符编码知识

字符编码详解及由来(UNICODE,UTF-8,GBK)

字符编码详解(基础) ——PHP鸟哥

文件和字符编码

编码方式之ASCII、ANSI、Unicode概述

字节(Byte或byte):计算机系统中用于计量存储容量的一种计量单位, 1B=8bit

字符(Character)是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等,但是一个字符在计算机中占用多少字节是与编码方式有关的,不同的编码方式占用的内存不一样。例如:标点符号+是一个字符,汉字我们是两个字符,在GBK编码中一个汉字占2个字节,在UTF-8编码中一个汉字占3个字节。

mysql(utf8mb4编码)中varchar(255) 255是字符长度不需要考虑不同字符(中文,英文)字节占用问题这是mysql底层处理的

字节字符有什么联系和区别呢?简单来说字节是计算机存储和操作的最小单位,字符是人们阅读的最小单位;字节是存储(物理)概念,字符是逻辑概念;字节代表数据(内涵和本质),字符代表其含义字符由字节组成
举几个例子说明两者区别:“中国”包含2个字符,GBK编码表示需要4个字节,UTF-8编码需要6个字节;数字“1234567890”,包含10个字符,用int32类型表示只需4个字节

编码规范 随着计算机的普及,人们希望能在计算机中显示字符,但是计算机只能显示0和1这样的二进制数,为了显示字符,国际组织就制定了编码规范,希望使用不同的二进制数来表示代表不同的字符,这样电脑就可以根据二进制数来显示其对应的字符。所谓字符集其实就是一套编码规范中的子概念,所以我们通常就称呼其为XX编码,XX字符集。例如:GBK 编码规范,根据这套编码规范,计算机就可以在中文字符和二进制数之间相互转换。而使用GBK编码就可以使计算机显示中文字符。

字库表 一套编码规范不一定包含世界上所有的字符,每套编码规范都有自己的使用场景,而字库表就存储了某种编码规范中能显示的所有字符,计算机就是根据二进制数字库表中找到与之对应的字符然后显示给用户的字库表相当于一个存储字符的数据库。例如:几乎所有汉字都保存在GBK 编码规范的字库表中。所以可以显示汉字,但法语,俄语并不在其字库表中,所以使用GBK编码的文档不能正常显示法语,俄语等不包含在其字库表中的字符。

编码字符集(字符集)在一个字库表中,每一个字符都有一个对应的二进制地址,而编码字符集就是这些地址的集合。字符集定义了字符和二进制的对应关系,为每个字符分配了唯一的编号。可以将字符集理解成一个很大的表格,它列出了所有字符和二进制的对应关系,计算机显示文字或者存储文字,就是一个查表的过程

字符编码(编码方式 )而字符编码规定了如何将字符的编号存储到计算机中,如果使用了类似 GB2312 和 GBK 的变长存储方案(不同的字符占用的字节数不一样),那么为了区分一个字符到底使用了几个字节,就不能将字符的编号直接存储到计算机中,字符编号在存储之前必须要经过转换,在读取时还要再逆向转换一次,这套转换方案就叫做字符编码。

字符集和字符编码的关系

通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码(例如:ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了对应的字符编码,但Unicode不是,它采用现代的模型)),因此基本上可以将两者视为同义词
字符和字符编码的异同可参见:https://www.cnblogs.com/lanhaicode/p/11214827.html

粗略总结

  • 解码过程:一个较短的二进制数,通过一种编码方式,转换成编码字符集中正常的地址,然后在字库表中找到一个对应的字符,最终显示给用户。  
  • 编码过程:字库表中的一个文字或符号,在字符集中找到对应的二进制串,然后通过一种编码方式,存储到计算机存储设备中

常见的编码规范及其发展过程

单字节

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),是最早产生的编码规范,共128个字符,用7位二进制表示(00000000-01111111即0x00-0x7F),可以看出ASCII码只需要1个字节的存储空间,它没有特定的编码方式,直接使用地址对应的二进制数来表示,非要说那就叫他ASCII 编码方式。可以表示阿拉伯数字和大小写英文字母,以及一些简单的符号。

EASCII(Extended ASCII),256个字符,用8位二进制表示(00000000-11111111即0x00-0xFF)。当计算机传到了欧洲,国际标准化组织在ASCII的基础上进行了扩展,形成了ISO-8859标准,跟EASCII类似,兼容ASCII,在高128个码位上有所区别。ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性

MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。由此可见,ISO-8859-1只占1个字节,且MySQL数据库默认编码就是ISO-8859-1,有时,tomcat服务器默认也是使用ISO-8859-1编码,然而ISO-8859-1是不支持中文的,有时这就是在浏览器上显示乱码的原因。但是由于欧洲的语言环境十分复杂,所以根据各地区的语言又形成了很多子标准,ISO-8859-1、ISO-8859-2、ISO-8859-3、……、ISO-8859-16。 

双字节  

当计算机传到了亚洲,256个码位就不够用了。于是乎继续扩大二维表,单字节改双字节,16位二进制数,65536个码位。在不同国家和地区又出现了很多编码,中国的GB2312港台的BIG5、日本的Shift JIS,韩国的Euc-kr等等。


GBK全称《汉字内码扩展规范》,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字。GBK字符集中所有字符占2个字节,不论中文英文都是2个字节。 没有特殊的编码方式,习惯称呼GBK 编码。

一般在国内,汉字较多时使用。GBK(Chinese Internal Code Specification)是GB2312的扩展,GBK 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1国际标准,是前者向后者过渡过程中的一个承上启下的产物。ISO 10646 是国际化标准组织 ISO 公布的一个编码标准,即 Universal Multilpe-Octet Coded Character Set(简称UCS),与 Unicode 组织的 Unicode 编码完全兼容。
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。 

多字节

当互联网席卷了全球,地域限制被打破了,不同国家和地区的计算机在交换数据的过程中,由于之前出现的各种不同的编码方式,文本就会出现乱码的问题,即对同一组二进制数据,不同的编码会解析出不同的字符。而当某个字符集中没有文本中的字符编码时,就会出现乱码。

通用字符集UCS(Universal Character Set)对应两种编码:对每一个字符采用四个8比特字节编码的称为UCS-4,对每一个字符采用两个8比特字节编码的称为UCS-2。

Unicode字符集的出现就是为了解决这个问题。Unicode 是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母AinU+0041表示英语的大写字母AU+4E25表示汉字。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。2)Unicode 在很长一段时间内无法推广

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。

BOM

BOM(字节顺序标记, byte-order mark)也是我们常见到的名词, 比如我们的代码文件都要求使用UTF-8无BOM形式保存, 不然有可能编译不过, 或者出现一些诡异的事情.
BOM实际上是位于码位U+FEFF的Unicode字符的名称.
对于UTF-16, UCS-2, UTF-32 / UCS-4这类码元不是8位的编码方式来说, 编码后的数据要存储/传输时, 必然会有字节序的问题, BOM出现在字节流的开头, 则用于标识该字节流的字节序. 各编码方案按自己的方式对U+FEFF进行编码, 放在头部即可标志编码该字节流时使用的字节序.
比如, 当我们知道即将读取的字节流以UTF-16编码, 字节序未知, 读到的前两个字节是0xFF, 0xFE, Unicode中U+FFFE则不映射到字符, 而这两个字节必定是编码的U+FEFF, 因此可以判断当前字节流使用小端序, 即UTF-16 LE

对于UTF-8, 由于它使用的是8位的码元, 不存在字节序的问题, 也不建议在头部添加BOM, 因为可能影响到一些工具, 因此使用无BOM的UTF-8成了主流.

ANSI

ANSI全称(American National Standard Institite)美国国家标准学会(美国的一个非营利组织),首先ANSI不是指的一种特定的编码,而是不同地区扩展编码方式的统称,各个国家和地区所独立制定的兼容ASCII,但互相不兼容的字符编码,微软统称为ANSI编码

总结对照

常用的字符编码ASCII,GBK,GB2312,BIG5,UTF-8 英文和中文简繁

Unicode字符集的编码方式有UTF-8,UTF-16,UTF-32

中文乱码产生的原因

PHP获取中文的第一个字符

//多字节字符串 PHP文件编码为UTF-8
$str = '你好PHP';
var_dump($str[0]); //输出结果 b"ä",乱码
var_dump(substr($str, 0, 1));//输出结果 b"ä",乱码
var_dump(substr($str, 0, 3));//输出结果 你 ,utf-8编码中一个汉字是三个字节
var_dump(mb_substr($str, 0, 1));//输出结果 你

PHP处理字符串的方式默认是把字符串作为单字节字符处理的,例如数组方式取字符和普通字符串函数

PHP处理多字节字符串需要用这些扩展 国际化与字符编码支持 常用的有mbstring 扩展和 iconv 函数

mbstring扩展支持的编码 https://www.php.net/manual/zh/mbstring.supported-encodings.php

iconv_get_encoding 获取 iconv 扩展的内部配置变量,

文本文件和二进制的区别

文本文件是二进制文件的一种,底层存储也是0和1;文本文件可读性和移植性好,但表现字符有限;二进制文件数据存储紧凑,无字符编码限制。文本文件基本上只能存放数字、文字、标点等有限字符组成的内容;二进制没有字符约束,可随意存储图像、音视频等数据。

用存储数字的例子可以形象的看出文本文件和二进制文件存储内容上的差异。例如要存储数字1234567890,文本文件要存储0-9这十个数字的ASCII码,对应的十六进制表示为:31 32 33 34 35 36 37 38 39 30,占用10个字节;1234567890对应的二进制为“‭0100 1001 1001 0110 0000 0010 1101 0010‬”,占用4个字节(二进制表示32位,一个字节8位),存储到文件的16进制表示为(大端):49 96 02 D2。

文本文件按字符存放内容,二进制按字节存放,这是两种文件最本质的区别。根据这个特性,可以推断出一些常见结论:二进制文件常常比文本文件紧凑,占用空间少;文本文件更友好易用,能用所见即所得的方式编辑;二进制文件常常需要专用程序打开,等等。

回过头看文本编辑器打开二进制文件常常是乱码的现象。例如一个二进制文件存放了一个整数1234(四个字节),用16进制表示为:00 00 04 D2。文本编辑器打开后逐个字符解释,会发现这几个字节拼不出可显示的字符,只好乱码相待。乱码的原因是文本编辑器不能正确解析字节流,这也是二进制文件需要用专用软件打开的原因。例如jpg文件要用看图软件打开,如果用音乐播放器打开,完蛋!视频文件要用播放器打开,用压缩软件打开,歇菜!有的专用软件会做处理打开不支持的格式后不做反应,有的会报错。

文件格式

Windows按文件拓展名识别文件格式,并调用对应的程序打开文件;

(类)Unix系统,拓展名可有可无,有file命令,这个命令可以告诉我们文件到底是什么格式文件拓展名不是文件格式的本质区别,内容才是。把a.zip改成a.txt/a.jgp/a.mp3,无论什么文件名,file都让其原形毕露:Zip archive data, at least v1.0 to extract。file命令的工作原理可这篇文章

PHP读取txt文本文件获取第一个字符乱码

参考

PHP检测文件BOM头

ANSCII编码对照表

有一个ASCII的编码的文本文件,在linux上打开会显示乱码,因此使用windows记事本打开转成了UTF-8编码,然后用php读取该文本文件第一个字符时出现乱码,原因是windows记事本保存文件时会给文本文件增加bom头。

php检测处理bom头的原理,就是用ord函数检测前三个字符在ASCII编码中的数字是否为239,187,191

if (ord($contents[0]) === 239 && ord($contents[1]) === 187 && ord($contents[2]) == 191)
{
   $contents = substr($contents, 3);
   var_dump($contents[0]);
}

对应的字符如下

用户的操作系统类型是不确定,因此文本文件的字符编码也无法确定 ,需要对用户上传的文本文件转换字符编码

$fileContents = file_get_contents($path);//读取文件字符串,此处可以用框架方法替代
$encoding = mb_detect_encoding($fileContent, ['ASCII', 'GBK', 'GB2312', 'BIG5', 'UTF-8']);//获取文件内容编码
$fileContent = mb_convert_encoding($fileContent, 'UTF-8', $encoding);//转码
//$contents = iconv($encoding, 'UTF-8', $contents);//iconv也可以