Yaf集成think-orm4

目前最新yaf版本3.3.6,可以在PHP8.3上成功安装。

安装步骤
#下载最新代码
wget https://pecl.php.net/get/yaf-3.3.6.tgz
#解压
tar -zxvf yaf-3.3.6.tgz
#安装
cd yaf-3.3.6/
phpize
./configure --with-php-config=/www/server/php/83/bin/php-config
make && make install
#修改php.ini 添加yaf.so
#克隆yaf
git clone https://github.com/laruence/yaf.git
#生成项目skeleton
cd /www/wwwroot/yaf/tools/cg
php yaf_cg -d Sample -n
安装think-orm
sudo -u www composer require topthink/think-orm
#入口引入think-orm
require __DIR__ . '/vendor/autoload.php';
添加数据库配置
[think-orm]
db.type     = mysql
db.hostname = 127.0.0.1
db.username = yaf
db.password = myGEFhY7FKhKME3m
db.database = yaf
db.charset  = utf8mb4
db.prefix   = yaf_
db.debug    = true
初始化数据库配置
<?php
class Bootstrap extends \Yaf\Bootstrap_Abstract {
    public function _initDb(){
        $ini = new \Yaf\Config\Ini(__DIR__."/../conf/db.ini");
        $arr = $ini->toArray();
        $config = ['default'=>'mysql','connections'=>['mysql'=>$arr['think-orm']['db']]];
        (new \think\DbManager())->setConfig($config);
    }
}
配置模型
<?php
use think\Model;
/**
 * @name SampleModel
 * @desc sample模型类 可以使用模型的属性和方法
 * @author root
 */
class SampleModel extends Model{
    protected $table = "yaf_sample";
    public function selectSample() {
        return 'Hello World!';
    }

    public function insertSample($arrInfo) {
        return true;
    }
}
think-orm4新特性Enity引入
<?php
class Bootstrap extends \Yaf\Bootstrap_Abstract {
    public function _initLoader($dispatcher) {
        \Yaf\Loader::getInstance()->registerNamespace('App\Entity', realpath(APPLICATION_PATH . '/application/entity'));
    }
}
Enity sample
<?php
namespace App\Entity;
use think\Entity;
/**
 * @name SampleEntity
 * @desc sample实体类 可以扩展业务逻辑 让模型专注查询、持久化数据
 * @author root
 */
class SampleEntity extends Entity
{
    public function getOptions():array
    {
    	return [
            'modelClass'      => \SampleModel::class,
    	];
    }
}
Enity使用
$entity = new \App\Entity\SampleEntity();
$one = \App\Entity\SampleEntity::find(1);

PHP Empty函数深入理解

empty() 函数用于检查一个变量是否为空,如果变量为空,则返回 true,否则返回 false。对于对象属性,空的定义取决于属性的可见性。

在 PHP 中,对象的属性有公有(public)、私有(private)和受保护(protected)三种可见性。公有属性可以直接从对象外部访问,而私有属性和受保护属性不能直接从对象外部访问。

当使用 empty() 函数检查一个对象的属性时,如果属性是私有的或受保护的,不管在对象内部有无赋值,那么 empty() 函数会返回 true,因为从对象外部无法直接访问到这个属性,也无法确定这个属性是否有值。

class MyObject {
    private $property;
    public function __construct($value = null) {
        $this->property = $value;
    }
    public function __get($key)
    {
        if(isset($this->{$key}))
        {
            return $this->{$key};
        }
        else
        {
            throw new \Exception("Property does not exist: " . get_class($this) . "::" . $key);
        }
    }
}
$object = new MyObject('value');
$property = $object->property; // 从对象外部获取属性值
var_dump(empty($object->property),empty($property));//true,false 因为属性是私有的

通过检查输入的字符串是否是合法的 IP 地址来理解KISS原则

处理字符串是否是ip的三个函数:

<?php
function isValidIpAddressV1(String $ipAddress){
	if(empty($ipAddress)){
		return false;
	}
	$regex = "/^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$/";
	$ret = preg_match($regex, $ipAddress,$matches);
	return is_bool($ret)?$ret:($ret>0?true:false);
}
function isValidIpAddressV2(String $ipAddress) {
	if(empty($ipAddress)){
		return false;
	}
	$ipUnits = explode('.',$ipAddress); 
	if (count($ipUnits) != 4) { 
		return false; 
	} 
	for ($i = 0; $i < 4; ++$i) {
		$ipUnitIntValue = 0; 
		try { 
			$ipUnitIntValue = intval($ipUnits[$i]); 
		} catch (\Exception $e){ 
			return false; 
		} 
		if ($ipUnitIntValue < 0 || $ipUnitIntValue > 255) { 
			return false; 
		} 
		if ($i == 0 && $ipUnitIntValue == 0) 
		{ 
			return false; 
		} 
	} 
	return true;
}
function isValidIpAddressV3(String $ipAddress){
	$ipChars = str_split($ipAddress);
	$length = count($ipChars);
	$ipUnitIntValue = -1;
	$isFirstUnit = true;
	$unitsCount = 0;
	for ($i=0; $i < $length; $i++) {
		$c = $ipChars[$i];
		if ($c == '.') {
			if ($ipUnitIntValue < 0 || $ipUnitIntValue > 255) return false;
			if ($isFirstUnit && $ipUnitIntValue === 0) return false;
			if ($isFirstUnit) $isFirstUnit = false;
			$ipUnitIntValue = -1; 
			$unitsCount++; 
			continue;
		} 
		if ($c < '0' || $c > '9') {
			return false;    
		}
		if ($ipUnitIntValue == -1) $ipUnitIntValue = 0;
		$ipUnitIntValue = $ipUnitIntValue * 10 + ($c - '0');
	}
	if ($ipUnitIntValue < 0 || $ipUnitIntValue > 255) return false;
	if ($unitsCount != 3) return false;
	return true;
}

第一种实现方式利用的是正则表达式,写出完全没有 bug 的正则表达本身就比较有挑战。所以,从 KISS 原则的设计初衷上来讲,这种实现方式并不符合 KISS 原则。第二种实现方式使用了现成的字符串函数,来处理 IP 地址。第三种实现方式通过逐一处理 IP 地址中的字符,来判断是否合法。从代码行数上来说,这两种方式差不多。但是,第三种要比第二种更加有难度,更容易写出 bug。从可读性上来说,第二种实现方式的代码逻辑更清晰、更好理解。所以,在这两种实现方式中,第二种实现方式更加“简单”,更加符合 KISS 原则。

软著登记二三事

注册网站

https://www.ccopyright.com.cn/

注册可能遇到的问题

1.夜间可能注册不了,跨域问题,猜测网关估计是做了集群,设置了按时间段服务器分流,晚上承载流量的这部分服务没有配置允许跨域请求,建议白天注册。2.登录账号需要实名认证,从账号管理进去实名认证会遇到Network Error;从版权登记软著登记页面进点计算机软件著作权登记申请的立即登记会提示实名,然后可以跳转到邮箱绑定页面,填完邮箱发送邮件按钮还是灰色的浏览器控制台g_emailOk = true,就可以发送了,最后填完所有信息提交还可能遇到Network Error,多提交几次就行了,现在就可以等待审核了。

面向对象接口鉴权简单实现

需要用到的网站

sha加密:https://www.wetools.com/sha
时间戳:https://tool.lu/timestamp/

代码实现

客户端加密:http://domain/?id=id&appid=appid&appsecret=appsecret&ts=ts
请求鉴权:http://domain/?id=id&appid=appid&token=token&ts=ts

<?
interface CredentialStorage{
	public function getAppsecretByAppid(String $appid);
}
class IniCredentialStorage implements CredentialStorage{
	public function getAppsecretByAppid(String $appid){
		$ini_path = str_replace("\\",'/',__DIR__)."/config.ini";
		if(!is_file($ini_path)){
			throw new \Exception("没有找到配置文件",-1);
		}else{
			$ini_data = parse_ini_file($ini_path,true);
			if(isset($ini_data['auth'][$appid])){
				return $ini_data['auth'][$appid];
			}else{
				throw new \Exception("没有找到匹配的appid",-2);
			}
		}
	}
}
class AuthToken{
	const DEFAULT_EXPIRED_TIME_INTERVAL = 1*60;//有效期60s
	private $token;
	private $createTime;
	private $expiredTimeInterval = self::DEFAULT_EXPIRED_TIME_INTERVAL;
	public function __construct($token, $createTime){
		$this->token = $token;
		$this->createTime = $createTime;
	}
	public function generate($originalUrl, $appId, $password, $timestamp){
		$token = sha1($originalUrl.'&appid='.$appId.'&appsecret='.$password.'&ts='.$timestamp);
		$this->token = $token;
	}
	public function getToken(){
		return $this->token;
	}
	public function isExpired(){
		if($this->createTime + self::DEFAULT_EXPIRED_TIME_INTERVAL < time()){
			return true;
		}
		return false;
	}
	public function match($token){
		if($token === $this->token){
			return true;
		}
		return false;
	}
}
class ApiRequest{
	private static $baseUrl;
	private static $token;
	private static $appid;
	private static $timestamp;
	public function __construct(){

	}
	public static function createFromUrl($url){
		preg_match('/appid=([\w]+)&token=([\w]+)&ts=([\d]+)/', $url, $matches);
		self::$appid = $matches[1];
		self::$token = $matches[2];
		self::$timestamp = $matches[3];
		self::$baseUrl = str_replace("&","",substr($url,0,strlen($url)-strlen($matches[0])));
		return new self;
	}
	public function getBaseUrl(){
		return self::$baseUrl;
	}
	public function getToken(){
		return self::$token;
	}
	public function getAppid(){
		return self::$appid;
	}
	public function getTimestamp(){
		return self::$timestamp;
	}
}
interface ApiAuthenticator {
  	public function auth(String $url);
  	public function auth_url(ApiRequest $apiRequest);
}
class DefaultApiAuthenticatorImpl implements ApiAuthenticator {
  	private $credentialStorage;
  	public function __construct(CredentialStorage $credentialStorage) {
    	$this->credentialStorage = $credentialStorage;
  	}
  	public function auth(String $url) {
	    $apiRequest = ApiRequest::createFromUrl($url);
	    return self::auth_url($apiRequest);
	}
  	public function auth_url(ApiRequest $apiRequest) {
    	$appId = $apiRequest->getAppId();
    	$token = $apiRequest->getToken();
    	$timestamp = $apiRequest->getTimestamp();
    	$originalUrl = $apiRequest->getBaseUrl();
	    $clientAuthToken = new AuthToken($token, $timestamp);
	    if ($clientAuthToken->isExpired()) {
	    	throw new \Exception("Token is expired.",-3);
	    }
	    $password = $this->credentialStorage->getAppsecretByAppid($appId);
	    $clientAuthToken->generate($originalUrl, $appId, $password, $timestamp);
	    if (!$clientAuthToken->match($token)) {
	      	throw new \Exception("Token verfication failed.",-4);
	    }
	    return true;
  	}
}
$domain = $_SERVER['REQUEST_SCHEME']."://".$_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
$api_auth_impl = new DefaultApiAuthenticatorImpl(new IniCredentialStorage());
try {
	$ret = $api_auth_impl->auth($domain.$uri);
} catch (Exception $e) {
	echo $e->getMessage()." ".$e->getCode();
}
鉴权的其它实现

OAuth 2.0 + 网关(如Zuul)+ 认证中心 + AOP实现。还可以通过业务模型规避风险: 1. 充值类业务,就算对方篡改接口,最终结果可以通调用证金融机构的接口验证是否有效,不会给公司带来损失。 2. 如果安全等级非常高,比如提现、转账可以通过发送手机短信,确保是本人操作。 3. 如果是商品信息查询类接口,防止第三方爬取数据,可以在调用一定次数后加入人机验证(输入图片识别码、拼图)。 4. 根据IP限制访问次数。 5. 服务器间调用可以绑定mac地址、IP。 6. 服务器、客户端通过架设私有VPN进行通信,将安全问题转移到VPN上,降低业务复杂度的同时还可以避免加解密带来的性能损耗,提升性能。 7. 调用接口时通过付费方式(如实名认证、银行四要素验证这些调用一次都是要收费的),防止恶意调用。 8. 通过独立加密硬件(如U盾)+ 独立密码验证器(Google验证器)+ 语音识别 + 面部识别(刷脸支付) + 指纹 + 多人同时输入动态秘钥(核打击时发射程序)。 9. 安全性会降低系统性能适可而止。 

面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?

  • 面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
  • 设计原则是指导我们代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。
  • 设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。
  • 编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。
  • 重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。

实际上,面向对象、设计原则、设计模式、编程规范、代码重构,这五者都是保持或者提高代码质量的方法论,本质上都是服务于编写高质量代码这一件事的。当我们追本逐源,看清这个本质之后,很多事情怎么做就清楚了,很多选择怎么选也清楚了。比如,在某个场景下,该不该用这个设计模式,那就看能不能提高代码的可扩展性;要不要重构,那就看重代码是否存在可读、可维护问题等。

WordPress

插件

  1. 安装Markup Markdown编辑器插件,编辑器的ToolBar不显示。
  2. 安装文派 ICP 许可证备案插件,通过【外观】=【小工具】=添加【文本小工具】,插入到文本小工具里。没找到文本小工具。
    解决:1.通过【外观】=【小工具】=添加【嵌入】添加链接到底部内容1 2.主题代码直接加a标签。
  3. 主题文件编辑器修改css可以修改主图的样式
  4. 引入Enlighter插件代码高亮,效果尚可。