yinfuyuan

yinfuyuan 查看完整档案

北京编辑辽宁警察学院  |  所学专业 编辑公司  |  开发 编辑 www.timehubs.com 编辑
编辑

🤫嘘!!!

个人动态

yinfuyuan 发布了文章 · 1月13日

非常好用的节假日查询接口

概述

平时在开发的过程中经常会用到查询节假日的功能,但由于节假日是每年由国务院统一公布,我们
无法通过计算获取节假日,所以一般需要我们手动来维护节假日。

网上有很多节假日查询的接口,但查询条件和返回结果并不是太理想,
于是就自己写了个节假日查询接口并提供一个简单的页面。接口提供丰富的查询参数并且响应的字段可以自由定制。

快速开始

节假日查询的 接口地址 为:

https://api.apihubs.cn/holiday/get

直接访问接口地址会得到当前年份当前月份的整月数据,其中包含了详细的节假日信息

节假日查询的 示例页面地址 为:

http://www.apihubs.cn/#/holiday

在示例页面中会根据你选择的条件动态生成接口地址,然后可以直接点击浏览器中打开接口地址预览接口返回信息

参数

节假日查询接口提供了丰富的查询参数,所有参数都为可选参数,可灵活搭配使用,多个条件之间为&&的关系

参数中的日期格式均为PHP中的 日期格式

  • field 用来指定返回结果包含的字段,多个使用英文逗号分隔 枚举
  • year 用来指定要查询的年份,格式为 Y ,多个使用英文逗号分隔
  • month 用来指定要查询的月份,格式为 Ym ,多个使用英文逗号分隔
  • date 用来指定要查询的日期,格式为 Ymd ,多个使用英文逗号分隔
  • yearweek 用来指定要查询一年中的第几周,格式为 oW ,多个使用英文逗号分隔
  • yearday 用来指定要查询一年中的第几天,格式为 z ,多个使用英文逗号分隔
  • holiday 用来指定要查询的节假日,99为全部节假日,多个使用英文逗号分隔 枚举
  • holiday_overtime 用来指定要查询的节假日调休(加班),99为全部调休,多个使用英文逗号分隔 枚举
  • week 用来指定要查询的星期,多个使用英文逗号分隔 枚举
  • workday 用来指定查询是否为工作日(包含调休在内需要上班的日子) 枚举
  • weekend 用来指定查询是否为周末(星期六和星期日) 枚举
  • holiday_today 用来指定查询是否为节日当天 枚举
  • holiday_legal 用来指定查询是否为法定节假日(三倍工资)枚举
  • holiday_recess 用来指定查询是否为假期节假日(节日是否放假)枚举
  • lunar 用来指定年份、月份、日期、天数参数是否查询农历日期
  • cn 用来指定返回结果是否包含中文结果,默认返回的都是数字日期和枚举数字有利于逻辑判断不利于显示
  • page 分页页码
  • size 分页每页数量

响应

节假日查询接口的枚举功能是基于 php-enum 实现的统一格式响应

节假日查询接口响应始终为JSON数据格式,如下

{
    "code": "0",
    "msg": "ok",
    "data": ""
}
  • code 成功时始终为0,失败时为 枚举值 中的key
  • msg 成功时始终为 ok 失败时为 枚举值 中的value
  • data 成功时返回数据,失败时部分返回失败数据,如表单验证失败。
  • data.page 当前页码
  • data.size 当前每页数量
  • data.total 根据查询条件查到的总数量
  • data.list 节假日列表

data.list包含了节假日的详细信息,其中的所有字段都可以通过 field 参数进行按需使用

默认会返回数字日期和枚举码,这非常适合用来做逻辑判断。如下

{
    "year": 2021,
    "month": 202101,
    "date": 20210101,
    "yearweek": 202053,
    "yearday": 1,
    "lunar_year": 2020,
    "lunar_month": 202011,
    "lunar_date": 20201118,
    "lunar_yearday": 343,
    "week": 5,
    "weekend": 2,
    "workday": 2,
    "holiday": 22,
    "holiday_or": 22,
    "holiday_overtime": 10,
    "holiday_today": 1,
    "holiday_legal": 1,
    "holiday_recess": 1
}

当开启了cn查询参数,会将查取的字段名加上 _cn 后缀返回可视化的新,并同原字段一起返回。如下

{
    "year": 2021,
    "month": 202101,
    "date": 20210101,
    "yearweek": 202053,
    "yearday": 1,
    "lunar_year": 2020,
    "lunar_month": 202011,
    "lunar_date": 20201118,
    "lunar_yearday": 343,
    "week": 5,
    "weekend": 2,
    "workday": 2,
    "holiday": 22,
    "holiday_or": 22,
    "holiday_overtime": 10,
    "holiday_today": 1,
    "holiday_legal": 1,
    "holiday_recess": 1,
    "year_cn": "2021年",
    "month_cn": "2021年01月",
    "date_cn": "2021年01月01日",
    "yearweek_cn": "2020年第53周",
    "yearday_cn": "2021年第1天",
    "lunar_year_cn": "二零二零年",
    "lunar_month_cn": "二零二零年冬月",
    "lunar_date_cn": "二零二零年冬月十八",
    "lunar_yearday_cn": "2020年第343天",
    "week_cn": "星期五",
    "weekend_cn": "非周末",
    "workday_cn": "非工作日",
    "holiday_cn": "元旦",
    "holiday_or_cn": "元旦",
    "holiday_overtime_cn": "非节假日调休",
    "holiday_today_cn": "节日当天",
    "holiday_legal_cn": "法定节假日",
    "holiday_recess_cn": "假期节假日"
}
  • year 公历年份
  • month 公历月份
  • date 公历日期
  • yearweek 公历一年中的第几周,注意这里的年份是ISO-8601周编号年份,始终以周一至周日为一周。如需获取7天为一周直接使用年份中的天数除7即可。
  • yearday 公历一年中的第几天
  • lunar_year 农历年份
  • lunar_month 农历月份
  • lunar_date 农历日期
  • lunar_yearday 农历一年中的第几天
  • week 星期几
  • weekend 是否为周末
  • workday 是否为工作日(包含调休在内需要上班的日子)
  • holiday 节假日,这里使用两位数字枚举表示节假日,其中特殊数字10表示非节假日,特殊数字99表示全部节假日
  • holiday_or 其他节假日,枚举与节假日相同,表示同一天中的另一个节日,如 2020-10-01
  • holiday_overtime 节假日调休,枚举与节假日相同
  • holiday_today 是否为节日当天
  • holiday_legal 是否为法定节假日(三倍工资)
  • holiday_recess 是否为假期节假日(节日是否放假)

节假日查询的功能到这里就介绍完了,大家在使用过程中遇到任何问题都可以 联系我

查看原文

赞 1 收藏 1 评论 0

yinfuyuan 发布了文章 · 1月12日

centos上PHP8安装fileinfo拓展失败

前段时间通过编译安装PHP8的时候因为内存的原因加了--disable-fileinfo禁用了fileinfo拓展
然而最近使用PHP8安装Laravel8的时候却因为部分包依赖fileinfo而无法安装。

想着我也不是头一次给编译后的PHP安装拓展了,装个fileinfo的拓展又有多难呢。
然而现实还是给了我一记响亮亮的耳光,make的时候不仅报错,还找不到解决办法。

网上找了半天也是找了个寂寞,就一篇 相关的文章 在最后告诉我 如无意外,fileinfo是安装不上的。
果然我没有遇到那个"意外"。
他得到的错误是这样的:

/root/oneinstack/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c: In function ‘file_checkfmt’:
/root/oneinstack/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c:97:2: error: ‘for’ loop initial declarations are only allowed in C99 mode
for (const char *p = fmt; *p; p++) {
^
/root/oneinstack/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c:97:2: note: use option -std=c99 or -std=gnu99 to compile your code
make: *** [libmagic/funcs.lo] Error 1

而我得到的错误是中文版本:

/usr/local/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c: 在函数‘file_checkfmt’中:
/usr/local/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c:97:2: 错误:只允许在 C99 模式下使用‘for’循环初始化声明
for (const char *p = fmt; *p; p++) {
^
/usr/local/src/php-8.0.0/ext/fileinfo/libmagic/funcs.c:97:2: 附注:使用 -std=c99 或 -std=gnu99 来编译您的代码
make: *** [libmagic/funcs.lo] 错误 1

既然网上已经找不到解决办法了,但问题还要解决呀,就详细看了这个错误信息。
这里的附注让使用 -std=c99 或 -std=gnu99 来编译代码,看的我也是一脸懵,这我要加在哪里呀?
于是我就加在了make后面

make -std=c99

然后我得到了更多错误...

这个时候去查了下c99,了解到的这是一个cc选项。
我们知道在使用phpize的时候是根据系统环境生成configure文件
而在执行configure会生成Makefile文件(我猜的)
make命令依赖Makefile进行构建,于是去查了下Makefile文件, 找到了下面的关键信息

20    libdir = ${exec_prefix}/lib
21    prefix = /usr/local/php8
22    phplibdir = /usr/local/src/php-8.0.0/ext/fileinfo/modules
23    phpincludedir = /usr/local/php8/include/php
24    CC = cc
25    CFLAGS = -g -O2
26    CFLAGS_CLEAN = $(CFLAGS)
27    CPP = cc -E
28    CPPFLAGS = -DHAVE_CONFIG_H
29    CXX =
30    CXXFLAGS =

于是尝试在第25行(不同环境的行数可能不一样,注意区分)上添加-std=c99,也就是

20    libdir = ${exec_prefix}/lib
21    prefix = /usr/local/php8
22    phplibdir = /usr/local/src/php-8.0.0/ext/fileinfo/modules
23    phpincludedir = /usr/local/php8/include/php
24    CC = cc
25    CFLAGS = -std=c99 -g -O2
26    CFLAGS_CLEAN = $(CFLAGS)
27    CPP = cc -E
28    CPPFLAGS = -DHAVE_CONFIG_H
29    CXX =
30    CXXFLAGS =

重新make,这个时候之前的报错不见了,又有了新的报错(这不重要),然后又改了点别的(这也不重要)
发现还是那个错误,想着是不是有缓存呀,恍惚记得有个命令是这样的: make clean

然后重新make,这时"意外"居然发生了,第二个错误不见了(所以说它不重要)直接编译通过。然后再执行make install安装成功
后续使用也没有遇到其他问题。

叨叨了这么多,总结一下就是遇到这个问题,这样解决

  • 先执行 make clean 清除缓存
  • 然后修改 Makefile 中的 CFLAGS 添加 -std=c99

然后重新make && make install 就可以了

查看原文

赞 0 收藏 0 评论 0

yinfuyuan 发布了文章 · 2020-12-26

PHP中如何实现JAVA枚举功能

概述

在迭代了N个版本后,终于在目前最新的版本中实现了和Java枚举定义使用都非常相似的PHP枚举库php-enum

这里有必要提一下为什么非要实现Java枚举功能。我是在Java中认识枚举的,在此之前,我使用PHP很长时间也没有听说的过枚举,但在Java的项目中,枚举随处可见,尤其是在API返回统一状态码的场景中,它已经快成为了规范,所以我并没有办法忽略它,于是也学着使用它,等我再使用PHP的时候发现已经不太习惯没有枚举,于是感觉去搜索PHP的枚举

众所周知,在PHP中要使用枚举有两个选择,一个是官方在SPL中提供的枚举库,看到这里,是不是特别开心。别急,阅读文档后你会发现,你不仅要以拓展的方式安装它,它提供的方法也非常有限。所以通常我们会选择第二种方式,也就是使用第三方的枚举库。而通过阅读第三方的枚举的源码你也会发现,它们或多或少都有Java枚举的影子。但如果它们实现了Java枚举的功能也就不会有今天的库和文章了。

我查了很多枚举,发现它们都缺少Java枚举中的核心功能,也就是自定义属性值(在Java枚举中,枚举并不是简单的定义常量名称和常量值,你可以定义属性来承载枚举中的元素)同时也发现在php中实现这个功能并不容易,于是我在项目中写了个抽象类来实现固定两个属性的功能,因为要在多个项目中使用,就将它放到了github上,经过多次重构,一次次推翻自己的想法,最终终于接近了Java枚举的实现

安装

composer require phpenum/phpenum

快速开始

PHPEnum用起来和Java枚举很像,比如定义一个表示性别的枚举

在Java中:

public enum GenderEnum {
    MALE(1, "male"),
    FEMALE(2, "female");

    private Integer id;
    private String name;

    GenderEnum(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

使用PhpEnum:

class GenderEnum extends \PhpEnum\Enum
{
    const MALE = [1, 'male'];
    const FEMALE = [2, 'female'];

    private $id;
    private $name;

    protected function construct($id, $name)
    {
        $this->id = $id;
        $this->name->$name;
    }

    public function getId()
    {
        return $this->id;
    }
    
    public function getName()
    {
        return $this->name;
    }
}

你会发现它们的用法也非常相似

在Java中:

GenderEnum.values(); // enum instance array
GenderEnum.valueOf("FEMALE"); // enum instance
GenderEnum.MALE.equals(GenderEnum.valueOf("MALE")); // true
GenderEnum.MALE.name(); // MALE
GenderEnum.MALE.ordinal(); // 0
GenderEnum.MALE.toString(); // MALE
GenderEnum.MALE.getId(); // 1
GenderEnum.MALE.getName(); // male

使用PhpEnum:

GenderEnum::values(); // enum instance array
GenderEnum::valueOf('FEMALE'); // enum instance
GenderEnum::MALE()->equals(GenderEnum::valueOf('MALE')); // true
GenderEnum::MALE()->name(); // MALE
GenderEnum::MALE()->ordinal(); // 0
(string)GenderEnum::MALE(); // MALE
GenderEnum::MALE()->getId(); // 1
GenderEnum::MALE()->getName(); // male

不仅如此,PhpEnum还在子类中提供了高级功能

GenderEnum::MALE()->idEquals(1); // true
GenderEnum::MALE()->NameEquals('male'); // true
GenderEnum::containsId(1); // 1
GenderEnum::containsName('male'); // 1
GenderEnum::ofId(1); // enum instance
GenderEnum::ofName('male'); // enum instance
查看原文

赞 0 收藏 0 评论 0

yinfuyuan 赞了回答 · 2020-12-25

PHP 中自定义Exception的必要性

不是不够漂亮。
举个简单的例子

假设我做个读取微信用户信息写入本地数据库的应用。
如果出错就会抛出异常。
如果不定义异常类。 只知道跑错了,究竟是微信报错还是数据库报错只能判断报错的文案。
如果自定义异常类就可以自定义WechatExceptionDatabaseException了。
catch的时候

try{
}catch(WechatException $e) {
 echo "微信错误:".$e->message;
}catch(DatabaseException $e) {
    echo "数据库错误";
}

关注 6 回答 5

yinfuyuan 发布了文章 · 2020-06-29

laravel基于FormRequest实现场景验证规则

概述

在开发的过程中,经常会遇到表单验证的需求,Laravel为我们提供了功能强大且可拓展的验证器使我们可以快速实现此类需求,但是由于Laravel并没有为我们提供类似于 yii2的场景 的概念,这使得我们要为每一个
带有验证规则的请求都创建一个请求类文件,或将验证规则直接写在控制器中。这可能听起来没什么问题,但在实际开发中我们遇到非常复杂的业务场景,需要提供成百上千个接口,也就是说要创建成百上千个请求类文件或把成百上千个验证规则都写在控制器中。

为了解决这个问题,参考了 yii2的场景 ,尝试在Laravel中实现场景的概念,但是Laravel有很大的不同,在Laravel中自动验证的Requst类是注入进来的,这样想实现自动验证的时候就已经使用了正确的场景除非是通过表单提交的时候携带场景参数或者尝试使用Middleware进行处理,但这两种方式都不是太理想,经过一段时间的尝试和思考,想到可以利用变量名来传递场景信息。

于是开始实现,本想着很好解决,通过内置函数或者魔术函数或者反射类能轻松在类内部获取到这个类被实例化的名称,结果在这里浪费了很长很长时间,尝试了很多方式,但是依然没有办法获取实例名称。最后已经快放弃的时候。试到了可以通过Laravel路由来获取要到达的控制器类,再通过反射类来取到变量名称。

安装

composer require laravel-form-request/laravel-form-request

文档

表单请求基类 源码测试用例

表单请求基类是抽象类,不能直接实例化使用,所有需要用到场景的请求类都需要继承该类。

表单请求基类继承自Laravel的 表单请求类 其中下面方法使用final关键字修饰,不能进行重写,需要使用场景名称+方法名称驼峰写法进行重写以实现不同场景使用不同的验证规则。

  • authorize()
  • rules()
  • messages()
  • attributes()

表单请求基类为上述方法使用默认场景提供了下面的重写方法,需要继承后重写自己的规则。

  • defaultAuthorize()
  • defaultRules()
  • defaultMessages()
  • defaultAttributes()

表单请求基类默认的场景是default,可以在子类中使用 const SCENARIO_DEFAULT = 'login' 修改默认场景名称,表单请求基类提供了以下方法获取和重新设置场景

  • getScenario()
  • setScenario(string $scenario)

表单请求基类重写了Laravel表单请求类的prepareForValidation方法以实现截取请求类的变量名称用作场景,prepareForValidation方法也使用final关键字修饰,不能进行重写。其中的主要功能都通过Laravel的路由来实现,所以如果请求不是通过路由构建的,无法使用此功能。
表单请求基类默认使用Request对变量进行截取,如$defaultRequest截取的场景名称为default,可以在子类中使用 protected $delimiter = '_request'; 修改默认分隔符。

下面以用户的表单请求类为例,介绍场景的使用,如下定义中定义了三个场景,分别是default index closure

use LaravelFormRequest\FormRequest;
class UserFormRequest extends FormRequest
{
    public function defaultRules()
    {
        return [];
    }
    public function defaultMessages()
    {
        return [];
    }
    public function indexRules()
    {
        return [
            'name' => 'required',
        ];
    }
    public function indexMessages()
    {
        return [
            'name.required' => 'The name field is required in index scenario.',
        ];
    }
    public function closureRules()
    {
        return [
            'name' => 'required',
        ];
    }
    public function closureMessages()
    {
        return [
            'name.required' => 'The name field is required in closure scenario.',
        ];
    }
}

然后在控制器中用同一个请求类进行定义,在请求到达控制器之前会根据变量名称转化成不同的场景规则进行验证

use Illuminate\Routing\Controller as BaseController;
class UserController extends BaseController
{
    public function index(UserFormRequest $request)
    {
        echo $request->getScenario(); // default
    }
    public function index(UserFormRequest $indexRequest)
    {
        echo $indexRequest->getScenario(); // index
    }
}

也可以不使用控制器,直接使用路由闭包的形式进行验证

Route::get('users', function (UserFormRequest $closureRequest) {
    echo $closureRequest->getScenario(); // closure
});
查看原文

赞 0 收藏 0 评论 0

yinfuyuan 发布了文章 · 2020-05-22

PHP枚举类ENUM的实现

概述

阅读此文档时,假设您已经了解枚举的基本概念。否则你可能会对文档中的部分内容感到困惑。
枚举在每个项目中都有可能会被用到,在PHP中通常使用一组常量或一组静态变量来代替枚举。但在属性之间存在着组合或关联关系的复杂情景中,使用枚举会大大降低开发和维护成本。
PHP官方通过SPL类库已经为我们提供了枚举类SplEnum ,但首先你需要以扩展的方式安装它,其次它通常需要再次封装才能很好的使用。
此枚举类通过参考JAVA枚举 ,实现了一个简单易用但功能强大的枚举类库。

安装

composer require phpenum/phpenum

文档

在定义枚举时只有通过public或protected修饰的const关键字定义的常量才会被枚举类识别,其中public可以省略。
所有枚举类都是抽象类,枚举类构造方式是受保护的,无法进行实例化,只能通过枚举类提供的方法获取枚举单例。
受PHP的浮点类型精度影响,当枚举的属性包含浮点类型值时,不应该信任任何基于该浮点类型比对或查寻的结果。详见 浮点精度描述
在同一个枚举对象中包含相同的枚举常量值时,应该在定义和使用的过程中都使用前缀进行分组使用,否则你不应该信任使用具有相同枚举常量值对比或查找的结果。

基础枚举为单值类型枚举,枚举类没有对枚举常量值进行强制类型约束,但你应该使每组枚举常量值的类型始终相同。
基础枚举中所有的getter方法都省略了get,这其实是为了其子类实现getter做了预留。

基础枚举提供了以下方法

name                : 获取枚举的名称
value               : 获取枚举的值
equals              : 判断当前枚举与给定的枚举是否相等
nameEquals          : 判断枚举的名称与给定的名称是否相等
valueEquals         : 判断枚举的值与给定的值是否相等
names               : 【静态方法】获取枚举所有的名称,以数据的形式返回,如果指定了名称前缀,只返回指定名称前缀的部分
values              : 【静态方法】获取枚举所有的值,以数据的形式返回,数组的键为枚举名称,如果指定了名称前缀,只返回指定名称前缀的部分
enums               : 【静态方法】获取枚举所有的实例,以数据的形式返回,数组的键为枚举名称,如果指定了名称前缀,只返回指定名称前缀的部分
hasName             : 【静态方法】判断枚举名称是否存在,如果指定了名称前缀,只判断指定名称前缀的部分
hasValue            : 【静态方法】判断枚举常量值是否存在,如果指定了名称前缀,只判断指定名称前缀的部分
byName              : 【静态方法】根据枚举名称获取枚举实例,如果指定了名称前缀,只判断指定名称前缀的部分
byValue             : 【静态方法】根据枚举常量值获取枚举实例,如果指定了名称前缀,只判断指定名称前缀的部分
count               : 【静态方法】返回枚举属性的数量,如果指定了名称前缀,只判断指定名称前缀的部分

下面以定义用户枚举为例,介绍基础枚举的用法,如下用户枚举中包含了用户的性别和状态两组枚举常量值,分别使用SEX和STATUS前缀。

/**
 * @method static self SEX_MAN
 * @method static self SEX_WOMAN
 * @method static self STATUS_NORMAL
 * @method static self STATUS_INVALID
 */
class UserEnum extends \PhpEnum\Enum
{
    const SEX_MAN = 1; // 1表示性别男
    const SEX_WOMAN = 2; // 2表示性别女
    const STATUS_NORMAL = 1; // 1表示正常状态
    const STATUS_INVALID = 9; // 9表示无效状态
}

通过下面的方法可以获取到枚举对象,枚举对象首次创建后会被缓存,再次通过任意方法获取到枚举对象始终为同一个,第一种方式效率最高,推荐使用。

UserEnum::SEX_MAN(); // 如果实例未创建,只会创建并返回当前枚举常量值的实例,推荐使用
UserEnum::byName('SEX_MAN'); // 如果有未创建的实例,会创建所有未创建的枚举常量值的实例,然后返回当前实例
UserEnum::byValue(1,'SEX'); // 如果当前组有未创建的实例,会创建当前组所有未创建的枚举常量值的实例,然后返回当前实例
UserEnum::enums('SEX')['SEX_MAN']; // 如果当前组有未创建的实例,会创建当前组所有未创建的枚举常量值的实例,然后返回当前实例

列表枚举继承于基础枚举,为多值类型枚举,其中的多值是通过数组元素来实现的,所以枚举常量值的类型必须定义为数组,且数组的长度也要通过列表枚举类的 length 方法指定。
列表枚举需要为枚举常量中每一个数组元素定义一个私有属性作为载体,然后通过重写列表枚举类的 ListEnum 方法将接受到的数据元素分配到固定属性,通常使用 list 来进行分配。
列表枚举需要为每个载体属性提供getter方法,但不应该提供setter方法防止枚举结构被破坏。
列表枚举继承了基础枚举的所有方法,但由于列表枚举常量值的类型为数组,valueEqualsvalueshasValuebyValue 这些使用枚举常量值的方法通常没有太大意义。

列表枚举提供了以下方法

ListEnum            : 列表枚举类同名方法,接受当前枚举常量值,需要进行重写
length              :【静态方法】返回列表枚举常量值元素的个数,需要进行重写

下面以城市枚举为例,介绍列表枚举的用法,如下城市枚举中的枚举常量长度为3,包含了省份编码,城市编码,城市名称

/**
 * @method static self PROVINCE_LIAONING
 * @method static self CITY_BEIJING
 * @method static self CITY_SHENYANG
 * @method static self CITY_DALIAN
 */
class CityEnum extends \PhpEnum\ListEnum
{
    const PROVINCE_LIAONING = ['0', '22000', 'Liaoning'];
    const CITY_BEIJING = ['110000', '110000', 'Beijing'];
    const CITY_SHENYANG = ['22000', '210100', 'Shengyang'];
    const CITY_DALIAN = ['22000', '210200', 'Dalian'];
    private $province;
    private $city;
    private $name;
    protected final function ListEnum($list)
    {
        list($this->province, $this->city, $this->name) = $list;
    }
    public final static function length()
    {
        return 3;
    }
    public function getProvince()
    {
        return $this->province;
    }
    public function getCity()
    {
        return $this->city;
    }
    public function getName()
    {
        return $this->name;
    }
}

获取城市枚举属性值

CityEnum::PROVINCE_LIAONING()->getProvince(); // string(1) "0"
CityEnum::PROVINCE_LIAONING()->getCity(); // string(5) "22000"
CityEnum::PROVINCE_LIAONING()->getName(); // string(8) "Liaoning"

分别获取城市枚举所有省份和城市


CityEnum::enums('PROVINCE'); // 获取所有省份枚举
CityEnum::enums('province', false); // 获取所有省份枚举

CityEnum::enums('CITY'); // 获取所有城市枚举
CityEnum::enums('city', false); // 获取所有城市枚举

数组枚举继承于列表枚举,为双值类型枚举,数组枚举常量值始终有两个元素,第一个元素作为key,第二个元素作为value。
数组枚举中value含义将不再是枚举常量值,而是常量值中的第二个元素, valueEqualshasValuebyValue 这些继承与基础枚举的方法都已被重写并使用常量值中的第二个元素作为value。

数组枚举提供了以下方法

getKey              : 获取数据枚举常量值的第一个元素
getValue            : 获取数据枚举常量值的第二个元素
keyEquals           : 判断数据枚举常量值的第一个元素与给定的值是否相等
valueEquals         : 判断数据枚举常量值的第二个元素与给定的值是否相等
getKeys             : 【静态方法】获取数据枚举常量值第一个元素的集合,以数据的形式返回,如果指定了名称前缀,只返回指定名称前缀的部分
getValues           : 【静态方法】获取数据枚举常量值第一个元素的集合,以数据的形式返回,如果指定了名称前缀,只返回指定名称前缀的部分
getEnums            : 【静态方法】获取数组枚举所有的实例,以数据的形式返回,如果指定了名称前缀,只返回指定名称前缀的部分
hasKey              : 【静态方法】判断数据枚举常量值第一个元素是否存在,如果指定了名称前缀,只判断指定名称前缀的部分
hasValue            : 【静态方法】判断数据枚举常量值第二个元素是否存在,如果指定了名称前缀,只判断指定名称前缀的部分
byKey               : 【静态方法】根据数据枚举常量值第一个元素获取枚举实例,如果指定了名称前缀,只判断指定名称前缀的部分
byValue             : 【静态方法】根据数据枚举常量值第二个元素获取枚举实例,如果指定了名称前缀,只判断指定名称前缀的部分

下面以统一格式错误码为例,介绍数组枚举的用法,如下错误码枚举中key为错误码,value为错误描述

/**
 * @method static self OK
 * @method static self UNKNOWN_ERROR
 * @method static self ERROR_DATA_VALIDATION
 * @method static self ERROR_USER_INVALID
 * @method static self ERROR_CONFIG_ERROR
 */
class ErrorCodeEnum extends \PhpEnum\ArrayEnum
{
    const OK = ['0', 'ok'];
    const UNKNOWN_ERROR = ['99999', 'Unknown error'];
    const ERROR_DATA_VALIDATION = ['10000', 'The given data was invalid'];
    const ERROR_USER_INVALID = ['20000', 'User credentials was invalid'];
    const ERROR_CONFIG_ERROR = ['30000', 'Config info is error'];
}
    

通过下面的方式可以获取错误码和错误描述信息

ErrorCodeEnum::OK()->getKey(); // string(1) "0"
ErrorCodeEnum::OK()->getValue(); // string(2) "ok"

要实现返回统一格式错误码,还需要自定义异常类

class ApiException extends Exception
{
    private $data;
    public function __construct(ErrorCodeEnum $enum, $data = '')
    {
        parent::__construct($enum->getValue(), $enum->getKey());
        $this->data = $data;
    }
    public function toArray()
    {
        return [
            'code' => $this->getCode(),
            'msg' => $this->getMessage(),
            'data' => $this->data
        ];
    }
}

返回失败结果时,抛出自定义异常并指定错误码枚举

throw new ApiException(ErrorCodeEnum::ERROR_DATA_VALIDATION());
// {"code":10000,"msg":"The given data was invalid","data":""}

throw new ApiException(ErrorCodeEnum::ERROR_USER_INVALID(),'This is data');
// {"code":20000,"msg":"User credentials was invalid","data":"This is data"}

返回成功的结果应该单独定义

return [
    'code' => ErrorCodeEnum::OK()->getKey(),
    'msg' => ErrorCodeEnum::OK()->getValue(),
    'data' => '',
];
// {"code":"0","msg":"ok","data":""}
查看原文

赞 1 收藏 1 评论 0

yinfuyuan 发布了文章 · 2020-01-02

Laravel Passport Auth::login/Auth::logout does not exist

最近升级laravel到最新版本6.9.0
之前写的接口登录可以登录上,结果注销的时候突然报错了,刚开始以为是升级框架配置错误的原因影响的小问题。等有时间来解决这个问题的时候这个问题却困扰了我很长时间。

部分代码如下:
config/auth.php
`
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],],
`
AuthService.php
`
public function logout(Request $request)
{
Auth::logout();
$request->user()->token()->revoke();
}
`
结果一直得到如下错误(5.5版本是可以注销的)
Method Illuminate\Auth\RequestGuard::logout does not exist.

Auth使用门面模式指向AuthManager,auth的注视中也看到了
@method static void logout()
的注释,通过阅读文档和代码发现登录的时候走的是默认的guard 也就是web,然后logout时候因为加了auth:api走的是api的guard。而guards的对应关系如下:

session=>IlluminateAuthSessionGuard
token=>IlluminateAuthTokenGuard
passport=>IlluminateAuthRequestGuard

而login logout方法只有SessionGuard也就是web的guard才会有。
个人理解laravel是想只有web的session方式有登录和注销,而token和passport都是api通过token认证的方式,只校验token,所以没有login和logout。
所以如果使用了auth:api的路由如果还想使用web session的login和logout可以这样用:
Auth::guard('web')->login($user);
Auth::guard('web')->logout();
但是这样做的意义就不大了,因为通过校验后的token直接使用Auth::user()或者$request->user()是使用默认的api的guard是可以直接获取登录用户的。

查看原文

赞 0 收藏 0 评论 0

yinfuyuan 发布了文章 · 2019-10-28

laravel返回统一格式错误码

背景

最近在学习开发一个安卓项目,后端接口项目开始用PHP的Yii2.0框架新启了个项目,后换成laravel5.5,最近看到laravel升级了新版本,于是又将项目更新到laravel6.4
在使用yii和laravel的过程中,两个框架对web-api都非常友好,也都对restful做了不同程度的支持,但是还是遇到了一些问题,下面以laravel6.4为例,简单描述下我遇到的问题。

问题一:访问接口返回页面代码

最典型的就是laravel new 一个项目后,在浏览器直接访问localhost会进入laravel框架模版的默认欢迎页,这个没有太大的问题,问题就是你用postman把这个地址当接口
调用,返回的就是页面的代码,你在安卓端调用返回的还是页面的代码,其实实际使用不会去调用/跟接口,但是调用接口的时候一些其他的错误比如4xx,5xx都会返回html代码。
安卓端只能通过判断状态码来判断请求的成功失败,而且极难拿到错误信息。其实这里可以在安卓端统一加header,但是...... 于是网上查了下怎么处理
第一种办法解决postman调试的是可以在postman的请求中设置headers X-Requested-With:XMLHttpRequest来模拟ajax请求
第二种办法使项目仅返回JSON格式的需要新建一个Middleware

namespace App\Http\Middleware;
use Closure;
class JsonApplication
{
    public function handle($request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');
        return $next($request);
    }
}

然后在Kernel中全局注册Middleware并应用所有的api请求(这里因为项目是web-api项目,所以将routes/api.php的namespace去掉了,所以$middlewareGroups中的key是api)

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'api' => [
            ......
            'json_application',
        ],
    ];
    protected $routeMiddleware = [
        ......
        'json_application' => \App\Http\Middleware\JsonApplication::class,
    ];
}

这样配置好后就再也不用担心调用接口,给你返回的是页面代码。

问题二: 接口返回统一的JSON格式

通过上面的配置接口返回数据都是JSON的格式了,但是继续开发会发现,还是需要通过HTTP状态码来判断是否成功,然后返回的JSON里面的key不同的接口差异特别大,即使同一个接口在成功和出错的时候也会返回不同的KEY。
这个问题多采用返回同一格式的问题,由于之前给vue写过很多接口,所以还是沿用之前的key的模式

{
    "code": "0",
    "msg": "ok",
    "data": ""
}

但是在laravel中怎么返回这个格式成了一个问题,网上查了好几次,都没有太好的解决办法,多是覆盖的情况不全,再有就是错误码错误信息都写在逻辑层,新加的完全不知道有没有冲突。
后来又在BD和GG搜索好久,自己也尝试用laravel自带的异常机制和Middleware处理,始终不是太满意。
用过JAVA的都知道,在java中处理错误码很方便,直接定义一个枚举把所有的错误代码都写在里面,抛出异常的时候枚举当做参数传递进去。类似于这样
枚举

package *.*.*
public enum ErrorCode {
    OK("ok", 0),
    PARAM_ERROR("param error", 88888),
    UNKNOWN_ERROR("unknown error", 99999);
    ErrorCode(String value, Integer key) {
        this.value = value;
        this.key = key;
    }
    private String value;
    private Integer key;
    public String getValue() {
        return value;
    }
    public Integer getKey() {
        return key;
    }
}

异常类

package *.*.*;
import *.*.*.ErrorCode;
public class ApiException extends Exception {
    public int code = 0;
    public ApiException(ErrorCode errorCode) {
        super(errorCode.getValue());
        this.code = errorCode.getKey();
    }
    ......
}

使用

throw new ApiException(ErrorCode.UNKNOWN_ERROR);

于是查了下PHP的枚举,还真支持,但仔细一研究才发现,PHP的枚举不仅要安装开启SPL,然而提供的方法也并没有什么卵用
于是仿照JAVA写了一个
基类

namespace App\Enums;
abstract class Enum
{
    public static function __callStatic($name, $arguments)
    {
        return new static(constant('static::' . $name));
    }
}

错误码 这里因为用到了魔术方法,所以要在注视中标注

namespace App\Enums;
/**
 * @method static CodeEnum OK
 * @method static CodeEnum ERROR
 */
class CodeEnum extends Enum
{
    public const OK = ['0', 'ok'];
    public const ERROR = ['99999', 'fail'];
    private $code;
    private $msg;
    public function __construct($param)
    {
        $this->code = reset($param);
        $this->msg = end($param);
    }
    public function getCode()
    {
        return $this->code;
    }
    public function getMsg()
    {
        return $this->msg;
    }
}

自定义异常类

namespace App\Exceptions;
use App\Enums\CodeEnum;
use Exception;
use Illuminate\Support\Facades\Log;
class ApiException extends Exception
{
    public function __construct(CodeEnum $enum)
    {
        parent::__construct($enum->getMsg(), $enum->getCode());
    }
    public function render($request)
    {
        return response([
            'code' => $this->getCode(),
            'msg' => $this->getMessage(),
            'data' => ''
        ]);
    }
}

调用

throw new ApiException(new CodeEnum(CodeEnum::ERROR)); // 这样调总感觉不太好看
throw new ApiException(CodeEnum::OK()); // 这样调用和java的调用方式就很像了

当然可能有人觉得你写的是php,为什么总也和java比呢,这里没有比的意思,本人由于工作需要在平时工作中java和php都写,会结合另一种语言好的用法来使用。php中好的用法也会想着如何用java来实现。

上面的实现已经将源码放到github上了

https://github.com/yinfuyuan/php-enum

详细的使用方法也可以参考另一篇文章

https://segmentfault.com/a/1190000022711880

在文末介绍了枚举结合laravel返回统一格式错误码的用法

查看原文

赞 0 收藏 0 评论 0

yinfuyuan 发布了文章 · 2019-04-19

时间以半点为单位相连的算法

背景

在做广点通信息流数据获取的时候有这么一个字段:time_series 投放时间段,格式为 48 * 7 位字符串,且都为 0 和 1,以半个小时为最小粒度,从周一零点开始至周日 24 点结束。0 为不投放,1 为投放,全传 1 视为全时段投放,不允许全部传 0。朋友圈广告的投放时间需大于等于 6 小时,小于等于 30 个自然日,且每天投放的时段需保持一致。在实际的数据获取返回结果为:

000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111000000000000001111111111111000000011111111111111

需求方提出需要将此字段进行格式化,格式化后的字段格式为:

[1, 2, 3, 4, 5, 6, 7]7:00-13:30,17:00-24:00

方法

由于时间比较紧促,简单写了个方法记录下,后面再回来

    private String formatTimeSeries(String timeSeries)
    {
        Map<Integer, List<String>> weekTimeMap = new LinkedHashMap<>();
        if(!timeSeries.matches("[0-1]{336}")) {
            return "";
        }
        for (int i = 0; i < timeSeries.length(); i++) {
            if(!String.valueOf(timeSeries.charAt(i)).equals("1")) {
                continue;
            }
            int day = i/48 + 1;
            List<String> list = weekTimeMap.get(day);
            if(null == list) {
                list = new ArrayList<>();
            }
            list.add((i%48)/2+(i%48%2==1?":30":":00"));
            weekTimeMap.put(day,list);
        }
        Map<String, List<Integer>> timeWeekMap = new HashMap<>();
        for(Integer i : weekTimeMap.keySet()){
            List<String> list = weekTimeMap.get(i);
            String formatTimeSeries = "";
            while (list.size() > 0) {
                String startTime = list.get(0);
                list.remove(0);
                String startHour = startTime.split(":")[0];
                String startMinute = startTime.split(":")[1];
                String endTime = startMinute.equals("30") ? (Integer.valueOf(startHour) + 1) + ":00" : startHour + ":30";
                while (list.contains(endTime)) {
                    list.remove(endTime);
                    String endHour = endTime.split(":")[0];
                    String endMinute = endTime.split(":")[1];
                    endTime = endMinute.equals("30") ? (Integer.valueOf(endHour) + 1) + ":00" : endHour + ":30";
                }
                formatTimeSeries += (formatTimeSeries.length() == 0 ? "" : ",") + startTime + "-" + endTime;
            }
            List<Integer> timeList = timeWeekMap.get(formatTimeSeries);
            if(null == timeList) {
                timeList = new ArrayList<>();
            }
            timeList.add(i);
            timeWeekMap.put(formatTimeSeries, timeList);
        }
        String formatTimeSeries = "";
        for(String time : timeWeekMap.keySet()){
            formatTimeSeries += (formatTimeSeries.length() == 0 ? "" : ";") + timeWeekMap.get(time).toString() + time;
        }
        return formatTimeSeries;
    }
查看原文

赞 0 收藏 0 评论 0

yinfuyuan 发布了文章 · 2019-04-10

clickhouse两个表关联后出现几十位的小数

问题

在一次clickhouse的数据查询的时候,QA反馈说列表中某些指标数据出现了几十位的小数。开始以为是DataFormat时候bug
导致的。于是从接口入手开始跟数据,一直跟到数据源,发现几十位的小数点一直都在。最后打印了执行sql在DataGrip中执行。
发现sql产生的数据就带有几十位的小数点,详见图例。之前在clickhouse聚合查询的时候会出现小数后面数字浮动的情况,但
这种明显又是另一类的问题。

解决

于是开始分析这个几千行的sql,精简后其实主要是两个表的单独分组聚合然后再进行关联。单独执行子查询数据是正常的。
经过反复尝试后,**发现是 子查询 子查询 子查询 中使用了 select * 导致的,将这里换成具体查询的字段数据就正常了**。
这里使用*是因为报表是需要根据用户所选择的字段动态展示。

SQL

SELECT
    *
FROM
    (
        SELECT
            fieldsa,
            fieldsb,
            fields1c
        FROM
            table1
        WHERE
            condition1
        ORDER BY
            fields1c DESC
    )
ANY LEFT JOIN
    (
        SELECT
            * //将这里换成具体要查询的字段就可以了
        FROM
            (
                SELECT
                    fieldsa,
                    fieldsb,
                    round(sum(fields2c) / 1, 2) AS fields2c,
                    round(sum(fields2d) / 1, 2) AS fields2d
                FROM
                    table2
                WHERE
                    condition2
                GROUP BY
                    fieldsa,fieldsb
            )
        ALL FULL JOIN
            (
                SELECT
                    fieldsa,
                    fieldsb,
                    round(sum(fields3c) / 1, 2) AS fields3c,
                    round(sum(fields3d) / 1, 2) AS fields3d
                FROM
                    table3
                WHERE
                    condition3
                GROUP BY
                    fieldsa,fieldsb
            ) 
        USING 
            fieldsa,fieldsb
        WHERE
            condition4
    )
USING 
    fieldsa,fieldsb

图例

图片描述

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 5 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2019-04-04
个人主页被 740 人浏览