通过摸索和折腾,把一件事做成,会有一种小小的成就感。期待发现的问题,可以帮助需要的朋友。
环境说明:
Typecho 1.2.1 版本
handsome主题 9.2.1 版本
篱落VPS空间宝塔8.2.0 版本
下载程序:
php环境基本要求:
PHP >= 7.4
PHP SimpleXML 扩展
PHP fileinfo 扩展
PHP PDO_MYSQL 扩展
微信公众号配置
页面配置服务器URL (微信公众号页面配置服务器URL(不支持二级目录作为接口地址),如果你的博客是https://blog.xxx.cn,那么这个域名可以是https://weixin.xxx.cn。)
Token
安装教程:
- 拷贝项目文件到服务器根目录
- 打开 网址/install.php 安装3
- 在公众号后台服务器配置填写服务器地址为 项目所在网址/server.php 并启用服务器配置
- 公众号发送绑定,点击链接填写相关信息进行绑定
网址:你的博客地址。如:https://blog.xxx.cn
openid:一般会自动获取
cid:时光机/说说的页面cid(博客后台鼠标放在标题上获得链接地址cid=)
cid密钥:自定义参,以防止他人操作发布(1、主题后台设置的身份验证值,2、微信公众号配置Token,3、 网址/install.php安装处Token,4处值保持一致)4
mid:你想要发布文章的所属分类(可在后台获得) - 配置完成,尽情使用吧!使用方法同handsome官方时光机发送公众号
常见问题汇总:
1、微信公众号配置URL提示:系统发生错误,请稍后重试
解决思路:
一、错误反馈未能和服务器地址连接,检查URL地址和格式是否正确
二、访问 网址/server.php 检查程序运行是否正常,参考常见问题汇总3
2、微信公众号配置提示:Token 验证失败
解决思路:公众号Token、install.php安装Token、cid密钥、主题后台设置的身份验证值,四处是否保持一值。
3、访问 网址/server.php 检查程序运行异常提示
红底黑字:error on line 2 at column 1:Extra content at the end of the document
解决方法:在服务端代码 /config.php 内大概13行高德API 暂时删除,保留$amapSecret =
(一切正常后,此代码可以再加上,保证地图功能正常)
4、公众号发送消息异常:身份验证失败
解决方法:检查在服务端代码 /cross.php 内大概42行'time_code' => md5("$timecode"),
md5 值是否存在乱码,进行修改,上面是正确显示。
5、错误提示:Serialization of 'PDO' is not allowed
解决方法:打开根目录 var/Typecho/Db.php 粘贴以下代码进行覆盖,强烈建议备份文件
<?php
namespace Typecho;
use Typecho\Db\Adapter;
use Typecho\Db\Query;
use Typecho\Db\Exception as DbException;
/**
* 包含获取数据支持方法的类.
* 必须定义__TYPECHO_DB_HOST__, __TYPECHO_DB_PORT__, __TYPECHO_DB_NAME__,
* __TYPECHO_DB_USER__, __TYPECHO_DB_PASS__, __TYPECHO_DB_CHAR__
*
* @package Db
*/
class Db
{
/** 读取数据库 */
public const READ = 1;
/** 写入数据库 */
public const WRITE = 2;
/** 升序方式 */
public const SORT_ASC = 'ASC';
/** 降序方式 */
public const SORT_DESC = 'DESC';
/** 表内连接方式 */
public const INNER_JOIN = 'INNER';
/** 表外连接方式 */
public const OUTER_JOIN = 'OUTER';
/** 表左连接方式 */
public const LEFT_JOIN = 'LEFT';
/** 表右连接方式 */
public const RIGHT_JOIN = 'RIGHT';
/** 数据库查询操作 */
public const SELECT = 'SELECT';
/** 数据库更新操作 */
public const UPDATE = 'UPDATE';
/** 数据库插入操作 */
public const INSERT = 'INSERT';
/** 数据库删除操作 */
public const DELETE = 'DELETE';
/**
* 数据库适配器
* @var Adapter
*/
private $adapter;
/**
* 默认配置
*
* @var array
*/
private $config;
/**
* 已经连接
*
* @access private
* @var array
*/
private $connectedPool;
/**
* 前缀
*
* @access private
* @var string
*/
private $prefix;
/**
* 适配器名称
*
* @access private
* @var string
*/
private $adapterName;
/**
* 实例化的数据库对象
* @var Db
*/
private static $instance;
/**
* 数据库类构造函数
*
* @param mixed $adapterName 适配器名称
* @param string $prefix 前缀
*
* @throws DbException
*/
public function __construct($adapterName, string $prefix = 'typecho_')
{
/** 获取适配器名称 */
$adapterName = $adapterName == 'Mysql' ? 'Mysqli' : $adapterName;
$this->adapterName = $adapterName;
/** 数据库适配器 */
$adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $adapterName);
if (!call_user_func([$adapterName, 'isAvailable'])) {
throw new DbException("Adapter {$adapterName} is not available");
}
$this->prefix = $prefix;
/** 初始化内部变量 */
$this->connectedPool = [];
$this->config = [
self::READ => [],
self::WRITE => []
];
//实例化适配器对象
$this->adapter = new $adapterName();
}
/**
* @return Adapter
*/
public function getAdapter(): Adapter
{
return $this->adapter;
}
/**
* 获取适配器名称
*
* @access public
* @return string
*/
public function getAdapterName(): string
{
return $this->adapterName;
}
/**
* 获取表前缀
*
* @access public
* @return string
*/
public function getPrefix(): string
{
return $this->prefix;
}
/**
* @param Config $config
* @param int $op
*/
public function addConfig(Config $config, int $op)
{
if ($op & self::READ) {
$this->config[self::READ][] = $config;
}
if ($op & self::WRITE) {
$this->config[self::WRITE][] = $config;
}
}
/**
* getConfig
*
* @param int $op
*
* @return Config
* @throws DbException
*/
public function getConfig(int $op): Config
{
if (empty($this->config[$op])) {
/** DbException */
throw new DbException('Missing Database Connection');
}
$key = array_rand($this->config[$op]);
return $this->config[$op][$key];
}
/**
* 重置连接池
*
* @return void
*/
public function flushPool()
{
$this->connectedPool = [];
}
/**
* 选择数据库
*
* @param int $op
*
* @return mixed
* @throws DbException
*/
public function selectDb(int $op)
{
if (!isset($this->connectedPool[$op])) {
$selectConnectionConfig = $this->getConfig($op);
$selectConnectionHandle = $this->adapter->connect($selectConnectionConfig);
$this->connectedPool[$op] = $selectConnectionHandle;
}
return $this->connectedPool[$op];
}
/**
* 获取SQL词法构建器实例化对象
*
* @return Query
*/
public function sql(): Query
{
return new Query($this->adapter, $this->prefix);
}
/**
* 为多数据库提供支持
*
* @access public
* @param array $config 数据库实例
* @param integer $op 数据库操作
* @return void
*/
public function addServer(array $config, int $op)
{
$this->addConfig(Config::factory($config), $op);
$this->flushPool();
}
/**
* 获取版本
*
* @param int $op
*
* @return string
* @throws DbException
*/
public function getVersion(int $op = self::READ): string
{
return $this->adapter->getVersion($this->selectDb($op));
}
/**
* 设置默认数据库对象
*
* @access public
* @param Db $db 数据库对象
* @return void
*/
public static function set(Db $db)
{
self::$instance = $db;
}
/**
* 获取数据库实例化对象
* 用静态变量存储实例化的数据库对象,可以保证数据连接仅进行一次
*
* @return Db
* @throws DbException
*/
public static function get(): Db
{
if (empty(self::$instance)) {
/** DbException */
throw new DbException('Missing Database Object');
}
return self::$instance;
}
/**
* 选择查询字段
*
* @param ...$ags
*
* @return Query
* @throws DbException
*/
public function select(...$ags): Query
{
$this->selectDb(self::READ);
$args = func_get_args();
return call_user_func_array([$this->sql(), 'select'], $args ?: ['*']);
}
/**
* 更新记录操作(UPDATE)
*
* @param string $table 需要更新记录的表
*
* @return Query
* @throws DbException
*/
public function update(string $table): Query
{
$this->selectDb(self::WRITE);
return $this->sql()->update($table);
}
/**
* 删除记录操作(DELETE)
*
* @param string $table 需要删除记录的表
*
* @return Query
* @throws DbException
*/
public function delete(string $table): Query
{
$this->selectDb(self::WRITE);
return $this->sql()->delete($table);
}
/**
* 插入记录操作(INSERT)
*
* @param string $table 需要插入记录的表
*
* @return Query
* @throws DbException
*/
public function insert(string $table): Query
{
$this->selectDb(self::WRITE);
return $this->sql()->insert($table);
}
/**
* @param $table
* @throws DbException
*/
public function truncate($table)
{
$table = preg_replace("/^table\./", $this->prefix, $table);
$this->adapter->truncate($table, $this->selectDb(self::WRITE));
}
/**
* 执行查询语句
*
* @param mixed $query 查询语句或者查询对象
* @param int $op 数据库读写状态
* @param string $action 操作动作
*
* @return mixed
* @throws DbException
*/
public function query($query, int $op = self::READ, string $action = self::SELECT)
{
$table = null;
/** 在适配器中执行查询 */
if ($query instanceof Query) {
$action = $query->getAttribute('action');
$table = $query->getAttribute('table');
$op = (self::UPDATE == $action || self::DELETE == $action
|| self::INSERT == $action) ? self::WRITE : self::READ;
} elseif (!is_string($query)) {
/** 如果query不是对象也不是字符串,那么将其判断为查询资源句柄,直接返回 */
return $query;
}
/** 选择连接池 */
$handle = $this->selectDb($op);
/** 提交查询 */
$resource = $this->adapter->query($query instanceof Query ?
$query->prepare($query) : $query, $handle, $op, $action, $table);
if ($action) {
//根据查询动作返回相应资源
switch ($action) {
case self::UPDATE:
case self::DELETE:
return $this->adapter->affectedRows($resource, $handle);
case self::INSERT:
return $this->adapter->lastInsertId($resource, $handle);
case self::SELECT:
default:
return $resource;
}
} else {
//如果直接执行查询语句则返回资源
return $resource;
}
}
/**
* 一次取出所有行
*
* @param mixed $query 查询对象
* @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
*
* @return array
* @throws DbException
*/
public function fetchAll($query, ?callable $filter = null): array
{
//执行查询
$resource = $this->query($query);
$result = $this->adapter->fetchAll($resource);
return $filter ? array_map($filter, $result) : $result;
}
/**
* 一次取出一行
*
* @param mixed $query 查询对象
* @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
* @return array|null
* @throws DbException
*/
public function fetchRow($query, ?callable $filter = null): ?array
{
$resource = $this->query($query);
return ($rows = $this->adapter->fetch($resource)) ?
($filter ? call_user_func($filter, $rows) : $rows) :
null;
}
/**
* 一次取出一个对象
*
* @param mixed $query 查询对象
* @param array|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
* @return object|null
* @throws DbException
*/
public function fetchObject($query, ?array $filter = null): ?object
{
$resource = $this->query($query);
return ($rows = $this->adapter->fetchObject($resource)) ?
($filter ? call_user_func($filter, $rows) : $rows) :
null;
}
/**
* 序列化时保存必要的配置信息,不包含连接池
*
* @return array
*/
public function __serialize(): array
{
return [
'adapterName' => $this->adapterName,
'prefix' => $this->prefix,
'config' => $this->config
];
}
/**
* 反序列化时恢复配置并初始化适配器
*
* @param array $data 序列化数据
* @return void
*/
public function __unserialize(array $data): void
{
$this->adapterName = $data['adapterName'];
$this->prefix = $data['prefix'];
$this->config = $data['config'];
$this->connectedPool = []; // 重置连接池,防止 PDO 被序列化
// 重新实例化适配器
$adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $this->adapterName);
if (!class_exists($adapterName)) {
throw new DbException("Adapter {$adapterName} is not available");
}
$this->adapter = new $adapterName();
}
}
解决方法来自:秃头张
经历过数天不懈努力,将自己公众号搭建完成,中间遇到很多问题,也有一些问题并没写出来,并不是所有问题都可以用同样的方法可以解决。在向各位博主请假和搜索时发现,以上问题是询问最多的一部分,今天整理出来供参考,希望可以帮助更多的博主。
在此感谢
1、空空裤兜、一句话点醒
2、目的地-Destination、远程协助修改,反复解决异常问题
3、白亮吖雅黑丫の小站、解决问题思路解答
4、还有很多博文参考,博主们
5、必应、百度
以上感谢不分前后,每一次的成功离不开所有帮助过的朋友,谢谢你们!!!
5 条评论
这个很厉害!
是支持二级目录的吧 我以前就放在子目录里的OωO
文件可以放在二级目录里,在微信公众号URL设置不支持https://www.xxxx.com/weixin/server.php 二级格式
记录折腾经历很不错,我都是折腾,然后下次不会了再百度,浪费时间。
记录下疼苦点,下次重置好恢复