基于文本的简易k->v数据库改进版

本文介绍了一个基于文本的简易k-v数据库改进版,适用于小规模存储和替代redis、新浪SAEKVDB等。重点包括改进中文乱码问题、数据库结构优化以及API使用指南。
<?php


/**
*       基于文本的简易k->v数据库改进版
*       小规模存储k->v的数据库,数据库结构为JSON,可以在小项目中代替redis,新浪SAE KVDB等
*       @author cemike@126.com 2015/07/04
*       基于以下代码改进,致谢!
*       参考:基于文本方式的KVDB - 开源中国社区 http://my.oschina.net/suconghou/blog/195755
*       修正中文乱码问题:
*       1. 所有页面都用utf-8编码存储(可以用editplus进行可靠的文档编码转换)
*       2. 页面开头加上<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
*       3. 对于所有中文字符(仅仅中文,数字英文不要编码)$v存入JSON数据结构及磁盘文件前都要进行urlencode($v)编码转换,
*          JSON数据结构及磁盘文件中的数据类似于{"init_time":1436070089,"msg.1.keyName1":"%E5%A7%93%E5%90%8D"},
*          否则读取数据并进行JSON解析时$dbarr=json_decode($contents);返回结果null
*       4. 查找出来的数据$v如果在前台页面显示必须相应解码urldecode($v)
*       
*       kvdb API 
* set($key,$value);
*       sets($arr);//一次设置多个
*       get($key);
*       gets($key); 以x开头或者所有
*       del($key);
*       flush();//删除所有
*       like($key);//包含x的
*       增加:
*       function pkrget($key=null, $count) //兼容SAE KVDB,调用like($key)方法,并忽略了$count参数
*       function replace($key,$value)//兼容SAE KVDB,调用set($key,$value)方法
*       function delete($key) //兼容SAE KVDB,调用del($key)方法
*       初始化参数  tmp--存储在系统临时目录(windows通常为c:\windows\tmp\LVDB.tmp)
*                   为空加载默认(apache通常为www\htdocs\kvdb.txt)
*                   其他字符在/www或者项目当前目录生成文件
*       注意:1. kvdb对象销毁时数据会自动存储到文件$dbfile(参考function __destruct())
*             2. 如果要强制中途写入数据到磁盘,可调用function change($file=null)
*             3. 清空数据库function flush(),但不会立即存入磁盘
*             
*/
class kvdb 
{
    private static $dbfile='kvdb.txt';///默认数据库
    private static $dbarr;
 
    function __construct($file=null)
    {
        self::init($file);
    }
 
    ///从文本读入内存
    private static function read()
    {
        $contents=file_get_contents(self::$dbfile);
        //var_dump($contents);
        //$mb=mb_convert_encoding($contents, "GBK", "UTF-8");
//var_dump($mb);
        //$encoded=urlencode($contents);
//var_dump($encoded);
$dbarr=json_decode($contents);

//$dbarr=json_decode(mb_convert_encoding(file_get_contents(self::$dbfile), "GBK", "UTF-8"));
//echo "read: ";
//var_dump(mb_convert_encoding(file_get_contents(self::$dbfile), "GBK", "UTF-8"));
//var_dump($dbarr);
        return $dbarr; 
    }
    //存入文本
    private static function write($dbarr)
    {
//$dbarr=urldecode();
$dbarr=json_encode($dbarr);
//var_dump($dbarr);
        return file_put_contents(self::$dbfile,$dbarr);
    }
    /*
修正创建多个实例时$dnfile路径错误。
self::$dbfile=$_SERVER['DOCUMENT_ROOT'].self::$dbfile;
改为:
self::$dbfile=$_SERVER['DOCUMENT_ROOT'].'kvdb.db';
去掉了中间路径/app/s/
*/
private static function init($file=null)
    {
        if($file==null)//加载默认
        {
            //self::$dbfile=$_SERVER['DOCUMENT_ROOT'].self::$dbfile;
self::$dbfile=$_SERVER['DOCUMENT_ROOT'].'/kvdb.txt';//'/app/s/'.
        }
        else if($file=='tmp')
        {   
            self::$dbfile=sys_get_temp_dir().'/KVDB.tmp';
        }
        else //指定文件名
        {
            self::$dbfile=$_SERVER['DOCUMENT_ROOT'].'/'.$file;//'/app/s/'.
        }
        if(!file_exists(self::$dbfile))
        {
            $init=array('init_time'=>time());
            file_put_contents(self::$dbfile,json_encode($init));
        }/**/
        self::$dbarr=self::read();
    }
    function get($key)
    {
        return isset(self::$dbarr->$key)?self::$dbarr->$key:null;
    }
    function gets($key=null)
    {
        if(empty(self::$dbarr))
        {
            $this->flush();
        }
        foreach (self::$dbarr as $k=> $v)
        {
            if(substr($k,0,strlen($key))==$key)
            {
                $res[$k]=$v;
            }
        }
        return isset($res)?$res:null;
 
    }
    function set($key,$value)
    {
        if(empty(self::$dbarr))
        {
            $this->flush();
        }
        self::$dbarr->$key=$value;
    }
    function replace($key,$value)
    {
if(self::get($key)!=null)self::set($key, $value);
}
    function sets($arr)
    {
        if(empty(self::$dbarr))
        {
            $this->flush();
        }
        foreach ($arr as $key => $value)
        {
            self::$dbarr->$key=$value;
        }
    }
function delete($key)
    {
return self::del($key);
}
    function del($key)
    {
        if(isset(self::$dbarr->$key))
        {
            unset(self::$dbarr->$key);   
        }
        else
        {
            return true;
        }
    }
    function flush()
    {
        self::$dbarr=(object)array('init_time'=>time());
    }
 
    function like($key=null)
    {
        if(empty(self::$dbarr))
        {
            $this->flush();
        }
        foreach (self::$dbarr as $k => $v)
        {
            if (stripos($k,$key)!==false)
            {
                $res[$k]=$v;
            }
        }
        return isset($res)?$res:null;
 
    }
    function pkrget($key=null, $count)
    {
return self::like($key);
}
function change($file=null)
    {
        if($file==null)
        {
            self::$dbfile='kvdb.db';
        }
        self::write(self::$dbarr);
        self::init($file);
 
    }
    function __destruct()
    {
//echo '<br><span>Write kvdb to json file: '.self::$dbfile.'</span><br>';
/*foreach ( self::$dbarr as $key => $value ) {  
self::$dbarr[$key] = urlencode ( $value );  
}*/ 
return self::write(self::$dbarr);
 
    }
 
}
 
/*

$db=new kvdb('kvdb.txt');
$db->set('用户名','张三');
$db->set('user_1','111');
$db->set('user_2','2222');
$db->set('user_3','3333');
$db->set('sua_3','wsss');
 
$a=$db->gets('user');
$b=$db->like('a');
var_dump($a);
var_dump($b);
*/
#!/bin/bash #============================================ # YFW跨平台IM插件自动化部署脚本 # 部署路径: /www/wwwroot/yfw_szrengjing_com/wp-content/plugins # 数据库配置: 名称=yfw_szrengjing_c, 用户=yfw_szrengjing_c, 密码=GAm2jPL4Dm #============================================ # 配置参数(使用绝对路径) PLUGIN_DIR="/www/wwwroot/yfw_szrengjing_com/wp-content/plugins/yfw-im" DB_NAME="yfw_szrengjing_c" DB_USER="yfw_szrengjing_c" DB_PASS="GAm2jPL4Dm" WS_PORT=8080 PHP_VERSION_REQUIRED="7.4" RATCHET_VERSION="0.4.4" # 颜色输出函数 info() { echo -e "\033[34m[INFO] $1\033[0m"; } success() { echo -e "\033[32m[SUCCESS] $1\033[0m"; } error() { echo -e "\033[31m[ERROR] $1\033[0m" && exit 1; } #=============================== # 1. 环境检查 #=============================== info "开始环境检查..." # 检查PHP版本 PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;") if (( $(echo "$PHP_VERSION < $PHP_VERSION_REQUIRED" | bc -l) )); then error "需要PHP $PHP_VERSION_REQUIRED及以上版本,当前版本: $PHP_VERSION" fi # 检查Composer if ! command -v composer &> /dev/null; then error "未安装Composer,请先安装Composer" fi # 检查目标目录是否存在 if [ ! -d "/www/wwwroot/yfw_szrengjing_com/wp-content/plugins" ]; then error "目标路径不存在: /www/wwwroot/yfw_szrengjing_com/wp-content/plugins" fi #=============================== # 2. 创建插件目录结构 #=============================== info "创建插件目录结构..." mkdir -p "$PLUGIN_DIR"/{includes,assets/{js,css}} || error "创建目录失败" #=============================== # 3. 生成核心文件 #=============================== info "生成插件核心文件..." # 3.1 插件入口文件 (yfw-im.php) cat > "$PLUGIN_DIR/yfw-im.php" << 'EOF' <?php /** * Plugin Name: YFW 跨平台IM * Description: 基于WebSocket的跨平台即时通讯插件 * Version: 1.0 * Author: Your Name */ if (!defined('ABSPATH')) exit; // 加载核心文件 require_once plugin_dir_path(__FILE__) . 'includes/class-yfw-im.php'; require_once plugin_dir_path(__FILE__) . 'includes/db.php'; require_once plugin_dir_path(__FILE__) . 'config.php'; // 初始化插件 function yfw_im_init() { $yfw_im = new YFW_IM(); $yfw_im->init(); } add_action('plugins_loaded', 'yfw_im_init'); // 激活插件时创建数据库表 register_activation_hook(__FILE__, 'yfw_im_install'); function yfw_im_install() { require_once plugin_dir_path(__FILE__) . 'includes/db.php'; yfw_im_create_tables(); } EOF # 3.2 数据库操作文件 (includes/db.php) cat > "$PLUGIN_DIR/includes/db.php" << EOF <?php global \$wpdb; \$table_prefix = \$wpdb->prefix . 'yfw_im_'; // 数据库配置 define('YFW_IM_DB_NAME', '$DB_NAME'); define('YFW_IM_DB_USER', '$DB_USER'); define('YFW_IM_DB_PASS', '$DB_PASS'); // 创建消息表和会话表 function yfw_im_create_tables() { global \$wpdb, \$table_prefix; \$charset_collate = \$wpdb->get_charset_collate(); // 会话表 \$sql = "CREATE TABLE IF NOT EXISTS {\$table_prefix}conversations ( id INT AUTO_INCREMENT PRIMARY KEY, user1_id BIGINT NOT NULL, user2_id BIGINT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY user_pair (user1_id, user2_id) ) \$charset_collate;"; // 消息表 \$sql .= "CREATE TABLE IF NOT EXISTS {\$table_prefix}messages ( id INT AUTO_INCREMENT PRIMARY KEY, conversation_id INT NOT NULL, sender_id BIGINT NOT NULL, content TEXT NOT NULL, type ENUM('text', 'image', 'file') DEFAULT 'text', status ENUM('sent', 'delivered', 'read') DEFAULT 'sent', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (conversation_id) REFERENCES {\$table_prefix}conversations(id) ON DELETE CASCADE ) \$charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta(\$sql); } // 存储消息 function yfw_im_save_message(\$conversation_id, \$sender_id, \$content, \$type = 'text') { global \$wpdb, \$table_prefix; return \$wpdb->insert( "{\$table_prefix}messages", [ 'conversation_id' => \$conversation_id, 'sender_id' => \$sender_id, 'content' => \$content, 'type' => \$type ] ); } EOF # 3.3 WebSocket服务端 (includes/websocket.php) cat > "$PLUGIN_DIR/includes/websocket.php" << 'EOF' <?php use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; require_once __DIR__ . '/../vendor/autoload.php'; class YFW_IM_Socket implements MessageComponentInterface { protected $clients; protected $userConnections; // 用户ID与连接映射 public function __construct() { $this->clients = new \SplObjectStorage; $this->userConnections = []; } public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); error_log("新连接: {$conn->resourceId}"); } public function onMessage(ConnectionInterface $from, $msg) { $data = json_decode($msg, true); if (!$data) return; if ($data['action'] === 'auth') { $user_id = $data['user_id']; $this->userConnections[$user_id] = $from; $from->send(json_encode(['status' => 'success', 'msg' => '认证成功'])); return; } if ($data['action'] === 'send_message') { $this->handleMessage($data); } } private function handleMessage($data) { $sender_id = $data['sender_id']; $receiver_id = $data['receiver_id']; $content = $data['content']; $conversation_id = $this->getOrCreateConversation($sender_id, $receiver_id); yfw_im_save_message($conversation_id, $sender_id, $content); if (isset($this->userConnections[$receiver_id])) { $receiverConn = $this->userConnections[$receiver_id]; $receiverConn->send(json_encode([ 'action' => 'new_message', 'sender_id' => $sender_id, 'content' => $content, 'time' => date('Y-m-d H:i:s') ])); } } private function getOrCreateConversation($user1, $user2) { global $wpdb, $table_prefix; $min = min($user1, $user2); $max = max($user1, $user2); $conv = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$table_prefix}conversations WHERE user1_id = %d AND user2_id = %d", $min, $max ) ); if ($conv) return $conv->id; $wpdb->insert("{$table_prefix}conversations", ['user1_id' => $min, 'user2_id' => $max]); return $wpdb->insert_id; } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); $this->userConnections = array_filter($this->userConnections, function($c) use ($conn) { return $c !== $conn; }); error_log("连接关闭: {$conn->resourceId}"); } public function onError(ConnectionInterface $conn, \Exception $e) { error_log("错误: " . $e->getMessage()); $conn->close(); } } // CLI模式下启动服务器 if (php_sapi_name() === 'cli') { $server = IoServer::factory( new HttpServer( new WsServer(new YFW_IM_Socket()) ), 8080 ); $server->run(); } EOF # 3.4 核心类文件 (includes/class-yfw-im.php) cat > "$PLUGIN_DIR/includes/class-yfw-im.php" << 'EOF' <?php class YFW_IM { public function init() { add_action('wp_ajax_yfw_im_load_messages', [$this, 'load_messages']); add_action('wp_ajax_nopriv_yfw_im_load_messages', [$this, 'load_messages_nopriv']); } public function load_messages() { check_ajax_referer('yfw_im_nonce', 'nonce'); $current_user = wp_get_current_user(); if (!$current_user->ID) { wp_send_json_error('未登录'); } $receiver_id = intval($_POST['receiver_id']); $messages = $this->get_message_history($current_user->ID, $receiver_id); wp_send_json_success(['messages' => $messages]); } public function load_messages_nopriv() { wp_send_json_error('请先登录'); } private function get_message_history($user1, $user2) { global $wpdb, $table_prefix; $min = min($user1, $user2); $max = max($user1, $user2); $conv = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$table_prefix}conversations WHERE user1_id = %d AND user2_id = %d", $min, $max ) ); if (!$conv) return []; return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table_prefix}messages WHERE conversation_id = %d ORDER BY created_at ASC", $conv->id ), ARRAY_A ); } } EOF # 3.5 前端WebSocket客户端 (assets/js/socket.js) cat > "$PLUGIN_DIR/assets/js/socket.js" << 'EOF' class YFW_IM_Client { constructor() { this.socket = null; this.userId = yfw_im_config.user_id; this.connect(); } connect() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const host = yfw_im_config.ws_host || `${protocol}//${window.location.hostname}:8080`; this.socket = new WebSocket(host); this.socket.onopen = () => { console.log('WebSocket连接成功'); this.auth(); }; this.socket.onmessage = (event) => { const data = JSON.parse(event.data); this.handleMessage(data); }; this.socket.onerror = (error) => { console.error('WebSocket错误:', error); }; this.socket.onclose = () => { console.log('连接关闭,尝试重连...'); setTimeout(() => this.connect(), 3000); }; } auth() { this.send({ action: 'auth', user_id: this.userId }); } sendMessage(receiverId, content) { this.send({ action: 'send_message', sender_id: this.userId, receiver_id: receiverId, content: content }); } send(data) { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(data)); } else { console.error('连接未就绪'); } } handleMessage(data) { if (data.action === 'new_message') { window.dispatchEvent(new CustomEvent('yfw_im_new_message', { detail: data })); } } } EOF # 3.6 前端聊天界面逻辑 (assets/js/frontend.js) cat > "$PLUGIN_DIR/assets/js/frontend.js" << 'EOF' document.addEventListener('DOMContentLoaded', () => { const imClient = new YFW_IM_Client(); const chatWindow = document.getElementById('yfw-im-chat'); const messageInput = document.getElementById('yfw-im-input'); const sendBtn = document.getElementById('yfw-im-send'); let currentReceiver = null; document.querySelectorAll('.yfw-im-contact').forEach(contact => { contact.addEventListener('click', () => { currentReceiver = contact.dataset.userId; chatWindow.innerHTML = ''; loadMessages(currentReceiver); }); }); sendBtn.addEventListener('click', () => { const content = messageInput.value.trim(); if (content && currentReceiver) { imClient.sendMessage(currentReceiver, content); addMessageToUI(content, 'outgoing', yfw_im_config.user_id); messageInput.value = ''; } }); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendBtn.click(); }); window.addEventListener('yfw_im_new_message', (e) => { const data = e.detail; addMessageToUI(data.content, 'incoming', data.sender_id); }); function loadMessages(receiverId) { fetch(yfw_im_config.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'yfw_im_load_messages', receiver_id: receiverId, nonce: yfw_im_config.nonce }) }) .then(res => res.json()) .then(data => { if (data.success) { data.data.messages.forEach(msg => { const type = msg.sender_id == yfw_im_config.user_id ? 'outgoing' : 'incoming'; addMessageToUI(msg.content, type, msg.sender_id); }); } }); } function addMessageToUI(content, type, senderId) { const msgEl = document.createElement('div'); msgEl.className = `yfw-im-message ${type}`; msgEl.innerHTML = `<div class="content">${content}</div>`; chatWindow.appendChild(msgEl); chatWindow.scrollTop = chatWindow.scrollHeight; } }); EOF # 3.7 样式文件 (assets/css/style.css) cat > "$PLUGIN_DIR/assets/css/style.css" << 'EOF' .yfw-im-chat { width: 400px; height: 500px; border: 1px solid #ccc; border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; } .yfw-im-messages { flex: 1; padding: 10px; overflow-y: auto; background-color: #f9f9f9; } .yfw-im-message { margin: 5px 0; max-width: 70%; padding: 8px 12px; border-radius: 18px; clear: both; } .yfw-im-message.incoming { background-color: #e9e9eb; float: left; } .yfw-im-message.outgoing { background-color: #0078d7; color: white; float: right; } .yfw-im-input-area { display: flex; padding: 10px; border-top: 1px solid #ccc; } #yfw-im-input { flex: 1; padding: 8px 12px; border: 1px solid #ccc; border-radius: 20px; outline: none; } #yfw-im-send { margin-left: 10px; padding: 8px 16px; background-color: #0078d7; color: white; border: none; border-radius: 20px; cursor: pointer; } .yfw-im-contacts { padding: 10px; border-bottom: 1px solid #ccc; } .yfw-im-contact { padding: 8px 12px; border-radius: 4px; cursor: pointer; margin-bottom: 5px; } .yfw-im-contact:hover { background-color: #f0f0f0; } EOF # 3.8 配置文件 (config.php) cat > "$PLUGIN_DIR/config.php" << 'EOF' <?php define('YFW_IM_WS_HOST', 'ws://' . $_SERVER['HTTP_HOST'] . ':8080'); define('YFW_IM_ALLOWED_ORIGINS', [ 'https://yfw_szrengjing_com', 'http://yfw_szrengjing_com', 'http://localhost:3000' ]); function yfw_im_enqueue_scripts() { wp_enqueue_script('yfw-im-socket', plugins_url('assets/js/socket.js', __FILE__), [], '1.0', true); wp_enqueue_script('yfw-im-frontend', plugins_url('assets/js/frontend.js', __FILE__), ['yfw-im-socket'], '1.0', true); wp_enqueue_style('yfw-im-style', plugins_url('assets/css/style.css', __FILE__)); wp_localize_script('yfw-im-frontend', 'yfw_im_config', [ 'user_id' => get_current_user_id(), 'ws_host' => YFW_IM_WS_HOST, 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('yfw_im_nonce') ]); } add_action('wp_enqueue_scripts', 'yfw_im_enqueue_scripts'); EOF #=============================== # 4. 安装依赖 #=============================== info "安装WebSocket依赖 (Ratchet v$RATCHET_VERSION)..." cd "$PLUGIN_DIR" || error "无法进入插件目录: $PLUGIN_DIR" composer require cboden/ratchet:$RATCHET_VERSION --no-interaction > /dev/null 2>&1 || error "安装Ratchet失败" #=============================== # 5. 数据库初始化 #=============================== info "初始化数据库..." if command -v wp &> /dev/null; then cd /www/wwwroot/yfw_szrengjing_com || error "无法进入WordPress根目录" wp plugin activate yfw-im --quiet success "数据库表创建成功(通过WP-CLI)" else info "未检测到WP-CLI,请登录WordPress后台手动激活插件以创建数据表" fi #=============================== # 6. 配置WebSocket服务自启动 (systemd) #=============================== info "配置WebSocket服务自启动..." SERVICE_FILE="/etc/systemd/system/yfw-im-websocket.service" CURRENT_USER=$(whoami) if [ -w "/etc/systemd/system/" ]; then cat > "$SERVICE_FILE" << EOF [Unit] Description=YFW IM WebSocket Service After=network.target [Service] User=$CURRENT_USER WorkingDirectory=$PLUGIN_DIR/includes ExecStart=$(which php) websocket.php Restart=always RestartSec=3 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl start yfw-im-websocket systemctl enable yfw-im-websocket >/dev/null 2>&1 success "WebSocket服务已启动并设置为开机自启" else info "无权限写入 /etc/systemd/system/,请使用root权限运行此脚本" info "手动启动命令: php $PLUGIN_DIR/includes/websocket.php" fi #=============================== # 7. 部署完成提示 #=============================== success "✅ 部署完成!" echo "======================================" echo "插件路径: $PLUGIN_DIR" echo "WebSocket端口: $WS_PORT" echo "使用说明:" echo "1. 登录WordPress后台 → 插件 → 激活 'YFW 跨平台IM'" echo "2. 在页面中添加聊天组件(需开发短代码或JS嵌入)" echo "3. 确保防火墙开放 $WS_PORT 端口(TCP)" echo " 示例: firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload" echo "4. 测试WebSocket连接: ws://your-domain:8080" echo "======================================" 配置完整版前后端 完整版im 全部功能 完整版源文件
最新发布
11-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值