<?php
/**
* Plugin Name: Third Knife - 风格策略输入系统(融合版)
* Plugin URI: https://example.com/third-knife-fengge
* Description: 基于前三代迭代的终极融合版本 —— 支持前端短代码、实时校验、防刷、风控、美观 UI,专为 Third Knife 系统打造。
* Version: 3.0
* Author: Qiu
* License: GPL-2.0+
* Text Domain: third-knife-fengge
*/
if (!defined('ABSPATH')) exit;
// ====================
// 自定义表名(使用 tk_ 前缀)
// ====================
global $wpdb;
define('TK_FENGGE_TABLE', $wpdb->prefix . 'tk_fengge_rules');
define('TK_LOG_TABLE', $wpdb->prefix . 'tk_fengge_logs');
// ====================
// 激活创建主表和日志表
// ====================
register_activation_hook(__FILE__, 'third_knife_fengge_activate');
function third_knife_fengge_activate() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 主表:存储策略规则
$sql1 = "CREATE TABLE IF NOT EXISTS `" . TK_FENGGE_TABLE . "` (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
var VARCHAR(50) NOT NULL,
cangwei DECIMAL(10,4) NOT NULL,
fangxiang INT NOT NULL CHECK (fangxiang IN (1,2)),
effect_time DATETIME NULL,
obv INT DEFAULT 15,
obv2 INT DEFAULT 45,
ma2 INT DEFAULT 10,
order_unit INT DEFAULT 1 CHECK (order_unit BETWEEN 1 AND 500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_var (var(50))
) $charset_collate;";
// 日志表:记录所有操作
$sql2 = "CREATE TABLE IF NOT EXISTS `" . TK_LOG_TABLE . "` (
log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
action VARCHAR(20) NOT NULL,
target_id BIGINT UNSIGNED,
var VARCHAR(50),
old_value JSON,
new_value JSON,
ip_address VARCHAR(45),
user_agent VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta([$sql1, $sql2]);
}
// ====================
// 工具函数:IP 频率限制(每分钟最多3次)
// ====================
function third_knife_is_ip_limited($ip, $max_count = 3, $seconds = 60) {
global $wpdb;
$time_ago = date('Y-m-d H:i:s', time() - $seconds);
$count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*) FROM `" . TK_FENGGE_TABLE . "` WHERE created_at > %s AND INET_ATON(%s) = INET_ATON(ip_address)
", $time_ago, $ip));
return $count >= $max_count;
}
// ====================
// 校验品种是否合法(查询 zhuli 表中的 pinzhong_E)
// ====================
function third_knife_is_valid_symbol($var) {
global $wpdb;
$table = 'zhuli';
$sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE LOWER(pinzhong_E) = LOWER(%s)", $var);
return (int)$wpdb->get_var($sql) > 0;
}
// ====================
// AJAX: 获取所有可用品种(用于自动补全)
// ====================
add_action('wp_ajax_third_knife_get_symbols', 'third_knife_get_symbols');
add_action('wp_ajax_nopriv_third_knife_get_symbols', 'third_knife_get_symbols');
function third_knife_get_symbols() {
global $wpdb;
$symbols = $wpdb->get_col("SELECT DISTINCT pinzhong_E FROM zhuli WHERE pinzhong_E IS NOT NULL AND TRIM(pinzhong_E)!=''");
wp_send_json_success(['symbols' => array_values(array_unique($symbols))]);
}
// ====================
// AJAX: 实时校验品种合法性
// ====================
add_action('wp_ajax_third_knife_validate_symbol', 'third_knife_validate_symbol');
add_action('wp_ajax_nopriv_third_knife_validate_symbol', 'third_knife_validate_symbol');
function third_knife_validate_symbol() {
$var = sanitize_text_field($_GET['var'] ?? '');
if (!$var) wp_send_json(['valid' => false]);
wp_send_json(['valid' => third_knife_is_valid_symbol($var)]);
}
// ====================
// AJAX: 提交或更新规则
// ====================
add_action('wp_ajax_third_knife_submit_rule', 'third_knife_submit_rule');
add_action('wp_ajax_nopriv_third_knife_submit_rule', 'third_knife_submit_rule');
function third_knife_submit_rule() {
$ip = $_SERVER['REMOTE_ADDR'];
// 防刷检测
if (third_knife_is_ip_limited($ip)) {
wp_send_json_error(['message' => '操作太频繁,请稍后再试。']);
}
// 清理输入数据
$id = absint($_POST['id'] ?? 0);
$var = sanitize_text_field($_POST['var'] ?? '');
$cangwei = floatval($_POST['cangwei'] ?? 0);
$fangxiang = in_array((int)$_POST['fangxiang'], [1,2]) ? (int)$_POST['fangxiang'] : 0;
$effect_time = sanitize_text_field($_POST['effect_time'] ?? '');
$obv = max(1, intval($_POST['obv'] ?? 15));
$obv2 = max(1, intval($_POST['obv2'] ?? 45));
$ma2 = max(1, intval($_POST['ma2'] ?? 10));
$order_unit = max(1, min(500, (int)($_POST['order_unit'] ?? 1)));
// 必填项检查
if (!$var || !$fangxiang || !$effect_time || !strtotime($effect_time)) {
wp_send_json_error(['message' => '❌ 请填写所有必填字段!']);
}
if (!third_knife_is_valid_symbol($var)) {
wp_send_json_error(['message' => "❌ 品种 '$var' 不在支持列表中"]);
}
if ($cangwei <= 0) {
wp_send_json_error(['message' => '❌ 仓位必须大于 0!']);
}
// 总仓位 ≤ 100% 控制(排除当前编辑项)
global $wpdb;
$total_sql = "SELECT SUM(cangwei) FROM `" . TK_FENGGE_TABLE . "`";
if ($id) $total_sql .= $wpdb->prepare(" AND id != %d", $id);
$total = (float)$wpdb->get_var($total_sql);
if ($total + $cangwei > 1.0) {
wp_send_json_error([
'message' => sprintf(
'❌ 总仓位超出 100%%!当前合计 %.2f%%,无法添加 %.2f%%。',
$total * 100,
$cangwei * 100
)
]);
}
// 准备写入数据
$data = compact('var', 'cangwei', 'fangxiang', 'effect_time', 'obv', 'obv2', 'ma2', 'order_unit');
$format = ['%s', '%f', '%d', '%s', '%d', '%d', '%d', '%d'];
$result = $id ?
$wpdb->update(TK_FENGGE_TABLE, $data, ['id' => $id], $format, ['%d']) :
$wpdb->insert(TK_FENGGE_TABLE, $data, $format);
if ($result === false) {
wp_send_json_error(['message' => '数据库错误,请重试。']);
}
$rule_id = $id ?: $wpdb->insert_id;
// 记录日志
$new_data = $wpdb->get_row($wpdb->prepare("SELECT * FROM `" . TK_FENGGE_TABLE . "` WHERE id = %d", $rule_id), ARRAY_A);
$old_data = $id ? $wpdb->get_row($wpdb->prepare("SELECT * FROM `" . TK_FENGGE_TABLE . "` WHERE id = %d", $id), ARRAY_A) : null;
$log_data = [
'action' => $id ? 'update' : 'create',
'target_id' => $rule_id,
'var' => $var,
'old_value' => $old_data ? json_encode($old_data, JSON_UNESCAPED_UNICODE) : null,
'new_value' => json_encode($new_data, JSON_UNESCAPED_UNICODE),
'ip_address' => $ip,
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
];
$wpdb->insert(TK_LOG_TABLE, $log_data);
wp_send_json_success(['message' => $id ? '✅ 规则已更新!' : '✅ 规则已创建!']);
}
// ====================
// AJAX: 删除规则(默认允许所有人删除;上线前建议加权限)
// ====================
add_action('wp_ajax_third_knife_delete_rule', 'third_knife_delete_rule');
add_action('wp_ajax_nopriv_third_knife_delete_rule', 'third_knife_delete_rule');
function third_knife_delete_rule() {
// 生产环境应启用权限控制:
// if (!current_user_can('edit_posts')) wp_die('无权限');
$id = absint($_POST['id']);
if (!$id) wp_send_json_error(['message' => '无效 ID']);
global $wpdb;
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `" . TK_FENGGE_TABLE . "` WHERE id = %d", $id), ARRAY_A);
if (!$row) wp_send_json_error(['message' => '记录不存在']);
$result = $wpdb->delete(TK_FENGGE_TABLE, ['id' => $id], ['%d']);
if (!$result) wp_send_json_error(['message' => '删除失败']);
// 写入日志
$wpdb->insert(TK_LOG_TABLE, [
'action' => 'delete',
'target_id' => $id,
'var' => $row['var'],
'old_value' => json_encode($row, JSON_UNESCAPED_UNICODE),
'new_value' => null,
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
]);
wp_send_json_success(['message' => '🗑 规则已删除!']);
}
// ====================
// REST API: 获取所有规则(公开接口)
// ====================
add_action('rest_api_init', function () {
register_rest_route('third-knife/v1', '/rules', [
'methods' => 'GET',
'callback' => function () {
global $wpdb;
$rows = $wpdb->get_results("SELECT *,
CASE WHEN fangxiang = 1 THEN '多' ELSE '空' END AS fangxiang_label
FROM `" . TK_FENGGE_TABLE . "` ORDER BY effect_time DESC", ARRAY_A);
return rest_ensure_response($rows);
},
'permission_callback' => '__return_true'
]);
});
// ====================
// 短代码输出:[third_knife_rules] 或 [tk_rules]
// ====================
add_shortcode('third_knife_rules', 'third_knife_render_frontend_page');
add_shortcode('tk_rules', 'third_knife_render_frontend_page'); // 别名,更短
function third_knife_render_frontend_page() {
ob_start(); ?>
<div class="tk-fengge-form" id="tk-app">
<h3>🔪 Third Knife - 添加/修改策略</h3>
<form id="tk-rule-form" style="max-width:700px;">
<input type="hidden" name="id" value="" />
<label>品种:
<input type="text" name="var" placeholder="请输入英文品种名,如 RU, LC" required list="tk-symbol-suggestions">
<datalist id="tk-symbol-suggestions"></datalist>
<span id="var-help" style="font-size:12px;color:#666;display:block;"></span>
</label>
<label>仓位(支持小数或百分比):
<input type="text" name="cangwei_display" placeholder="例如:30% 或 0.3" required>
<input type="hidden" name="cangwei" value="0">
</label>
<label>方向:
<select name="fangxiang" required>
<option value="">-- 选择 --</option>
<option value="1">多</option>
<option value="2">空</option>
</select>
</label>
<label>生效时间:
<input type="datetime-local" name="effect_time" required>
</label>
<label>OBV 参数 <span class="tooltip">(?)<span class="tooltiptext">能量潮指标周期,默认15</span></span>:
<input type="number" name="obv" min="1" value="15">
</label>
<label>OBV2 参数 <span class="tooltip">(?)<span class="tooltiptext">第二能量潮周期,默认45</span></span>:
<input type="number" name="obv2" min="1" value="45">
</label>
<label>MA 参数2(中线)<span class="tooltip">(?)<span class="tooltiptext">中期均线,默认10周期</span></span>:
<input type="number" name="ma2" min="1" value="10">
</label>
<label>下单单位(每单手数):
<input type="number" name="order_unit" min="1" max="500" value="1" required>
</label>
<button type="submit" style="background:#E91E63;color:white;padding:12px 20px;border:none;margin-top:10px;width:100%;">
🔥 <span class="btn-text">提交规则</span>
</button>
</form>
<hr style="margin:30px 0;">
<h3>📋 当前生效规则</h3>
<div id="tk-rules-list-container">加载中...</div>
</div>
<!-- Tooltip 样式 -->
<style>
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted #666;
cursor: help;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 13px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tk-fengge-form table {
width: 100%; border-collapse: collapse; margin-top: 15px; font-size: 14px;
}
.tk-fengge-form th, .tk-fengge-form td {
border: 1px solid #ddd; padding: 10px; text-align: center;
}
.tk-fengge-form th { background-color: #f5f5f5; color: #333; }
.tk-delete-btn, .tk-edit-btn {
padding: 6px 10px; font-size: 12px; border: none; border-radius: 4px; cursor: pointer;
}
.tk-edit-btn { background: #2196F3; color: white; }
.tk-delete-btn { background: #f44336; color: white; }
</style>
<?php echo third_knife_inline_js(); ?>
<?php
return ob_get_clean();
}
// ====================
// 内联 JavaScript(纯 JS 输出)
// ====================
function third_knife_inline_js() {
ob_start();
?>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
const ajaxurl = '<?php echo admin_url("admin-ajax.php"); ?>';
const restUrl = '<?php echo rest_url('third-knife/v1/rules'); ?>';
// 加载可用品种(来自 zhuli 表)
fetch('<?php echo admin_url('admin-ajax.php'); ?>?action=third_knife_get_symbols')
.then(r => r.json())
.then(data => {
const list = document.getElementById('tk-symbol-suggestions');
(data.symbols || []).forEach(sym => {
const opt = document.createElement('option');
opt.value = sym.trim();
list.appendChild(opt);
});
});
// 实时校验品种
const varInput = document.querySelector('input[name="var"]');
const helpText = document.getElementById('var-help');
varInput.addEventListener('blur', function () {
const val = this.value.trim();
if (!val) return;
fetch('<?php echo admin_url('admin-ajax.php'); ?>?action=third_knife_validate_symbol&var=' + encodeURIComponent(val))
.then(r => r.json())
.then(res => {
if (res.valid) {
helpText.textContent = '✅ 有效品种';
helpText.style.color = 'green';
} else {
helpText.textContent = '❌ 品种未注册,请联系管理员';
helpText.style.color = 'red';
}
});
});
// 仓位格式转换(30% → 0.3)
const displayField = document.querySelector('input[name="cangwei_display"]');
const realField = document.querySelector('input[name="cangwei"]');
displayField.addEventListener('input', function () {
let val = this.value.replace('%', '').trim();
realField.value = parseFloat(val) > 1 ? (parseFloat(val)/100).toFixed(4) : parseFloat(val).toFixed(4);
});
// 默认设置时间为当前
const dtInput = document.querySelector('input[name="effect_time"]');
if (!dtInput.value) {
const now = new Date();
const pad = n => n.toString().padStart(2, '0');
const today = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())}`;
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
dtInput.value = `${today}T${time}`;
}
// 提交表单
document.getElementById('tk-rule-form').addEventListener('submit', function (e) {
e.preventDefault();
const formData = new FormData(this);
const data = {};
for (let [k, v] of formData) data[k] = v;
fetch(ajaxurl, {
method: 'POST',
body: Object.assign(data, { action: 'third_knife_submit_rule' })
})
.then(r => r.json())
.then(res => {
alert(res.message);
if (res.success) {
loadRules();
this.reset();
realField.value = '0';
document.querySelector('.btn-text').textContent = '提交规则';
}
})
.catch(() => alert('网络错误,请重试。'));
});
// 加载规则列表
function loadRules() {
const container = document.getElementById('tk-rules-list-container');
fetch(restUrl)
.then(r => r.json())
.then(rules => {
if (!rules.length) {
container.innerHTML = '<p>暂无生效规则。</p>';
return;
}
let html = `
<table>
<thead>
<tr>
<th>ID</th><th>品种</th><th>仓位</th><th>方向</th>
<th>生效时间</th><th>OBV</th><th>OBV2</th><th>MA2</th>
<th>单位</th><th>操作</th>
</tr>
</thead>
<tbody>`;
rules.forEach(r => {
html += `
<tr>
<td>${r.id}</td>
<td>${r.var}</td>
<td>${(r.cangwei*100).toFixed(2)}%</td>
<td>${r.fangxiang_label}</td>
<td>${r.effect_time}</td>
<td>${r.obv}</td>
<td>${r.obv2}</td>
<td>${r.ma2}</td>
<td>${r.order_unit}</td>
<td>
<button class="tk-edit-btn" data-id="${r.id}" data-item='${JSON.stringify(r)}'>✏️ 编辑</button>
<button class="tk-delete-btn" onclick="deleteRule(${r.id})">🗑 删除</button>
</td>
</tr>`;
});
html += '</tbody></table>';
container.innerHTML = html;
// 绑定编辑按钮事件
document.querySelectorAll('.tk-edit-btn').forEach(btn => {
btn.addEventListener('click', function () {
const item = JSON.parse(this.dataset.item);
document.querySelector('input[name="id"]').value = item.id;
document.querySelector('input[name="var"]').value = item.var;
document.querySelector('input[name="cangwei_display"]').value = (item.cangwei * 100).toFixed(2) + '%';
document.querySelector('input[name="cangwei"]').value = item.cangwei;
document.querySelector('select[name="fangxiang"]').value = item.fangxiang;
document.querySelector('input[name="effect_time"]').value = item.effect_time;
document.querySelector('input[name="obv"]').value = item.obv;
document.querySelector('input[name="obv2"]').value = item.obv2;
document.querySelector('input[name="ma2"]').value = item.ma2;
document.querySelector('input[name="order_unit"]').value = item.order_unit;
document.querySelector('.btn-text').textContent = '更新规则';
window.scrollTo(0, 0);
});
});
})
.catch(() => container.innerHTML = '<p>加载失败,请刷新页面。</p>');
}
// 删除规则
window.deleteRule = function(id) {
if (!confirm('确认删除这条规则?')) return;
fetch(ajaxurl, {
method: 'POST',
body: new URLSearchParams({ action: 'third_knife_delete_rule', id: id })
})
.then(r => r.json())
.then(res => {
alert(res.message);
if (res.success) loadRules();
});
};
// 初始化加载
loadRules();
});
</script>
<?php
return ob_get_clean();
}
这是我最后的代码,按照上面要求,修改后给我最新的代码
最新发布