仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ app_plugins/domain_shop/bootstrap.php 12.9 KB · 340 行 原始文件
Z zfhsh first commit 1 天前
1<?php
2/**
3 * domain_shop 插件入口
4 * - 二级域名商品 CRUD(接管原 admin/api/ym.php)
5 * - DNSPod DNS 解析自动创建与管理
6 * - ymgm 支付结算(接管原 MPHX/lib/pay.function.php 的 ymgm 分支)
7 * - host.created 钩子:自动为新主机创建 A 记录
8 */
9if (!defined('IN_CRONLITE')) exit;
10
11// 引入 lib
12require_once __DIR__ . '/lib/domain.php';
13require_once __DIR__ . '/lib/dns.php';
14
15// 已有站点自动升级表结构(补齐 channel / provider_id 字段)
16domain_product_schema_upgrade();
17
18mnbt_plugin_register('domain_shop', ['name' => '域名商品与 DNS 解析']);
19
20/* ============================================================
21 * 1. 后台菜单
22 * ============================================================ */
23mnbt_register_menu('admin', [
24 'title' => '域名服务',
25 'icon' => 'mdi-web',
26 'order' => 30,
27 'children' => [
28 [
29 'title' => '域名商品',
30 'page' => 'products',
31 'icon' => 'mdi-domain',
32 'order' => 10,
33 'multitabs' => true,
34 ],
35 [
36 'title' => 'DNS 服务商',
37 'page' => 'dns_provider',
38 'icon' => 'mdi-dns',
39 'order' => 20,
40 'multitabs' => true,
41 ],
42 [
43 'title' => 'DNS 记录',
44 'page' => 'dns_records',
45 'icon' => 'mdi-format-list-bulleted',
46 'order' => 30,
47 'multitabs' => true,
48 ],
49 ],
50]);
51
52mnbt_register_page('admin', 'products', 'admin/products.php', '域名商品列表');
53mnbt_register_page('admin', 'product_add', 'admin/product_add.php', '添加域名商品');
54mnbt_register_page('admin', 'dns_provider', 'admin/dns_provider.php', 'DNS 服务商配置');
55mnbt_register_page('admin', 'dns_records', 'admin/dns_records.php', 'DNS 记录查看');
56
57/* ============================================================
58 * 2. 用户端菜单(default 主题 sidebar 自动渲染)
59 * ============================================================ */
60mnbt_register_menu('user', [
61 'title' => '域名服务',
62 'icon' => 'mdi-web',
63 'order' => 40,
64 'children' => [
65 [
66 'title' => '域名绑定',
67 'page' => 'bind',
68 'order' => 10,
69 'multitabs' => true,
70 ],
71 [
72 'title' => '我的 DNS 记录',
73 'page' => 'dns_records',
74 'order' => 20,
75 'multitabs' => true,
76 ],
77 ],
78]);
79mnbt_register_page('user', 'dns_records', 'user/dns_records.php', '我的 DNS 记录');
80mnbt_register_page('user', 'bind', 'user/bind.php', '域名绑定');
81
82/* ============================================================
83 * 3. 后台 AJAX:域名商品 CRUD
84 * ============================================================ */
85mnbt_register_ajax('admin', 'p_domain_addym', function () {
86 mnbt_plugin_require_admin();
87 $url = $_POST['url'] ?? '';
88 $bt = $_POST['bt'] ?? '';
89 $jg = $_POST['jg'] ?? '';
90 $js = $_POST['ymjs'] ?? '';
91 $kg = isset($_POST['kg']) ? (bool)$_POST['kg'] : true;
92 $channel = $_POST['channel'] ?? 'pan';
93 $channel = ($channel === 'dnsapi') ? 'dnsapi' : 'pan';
94 $providerId = (int)($_POST['provider_id'] ?? 0);
95 $r = domain_product_add($url, $bt, $jg, $js, $kg, $channel, $providerId);
96 json_exit($r['ok'] ? $r['msg'] : $r['msg']);
97});
98
99mnbt_register_ajax('admin', 'p_domain_xgym', function () {
100 mnbt_plugin_require_admin();
101 $id = (int)($_POST['id'] ?? 0);
102 $js = $_POST['js'] ?? '';
103 $jg = $_POST['jg'] ?? '';
104 $kg = isset($_POST['kg']) ? (bool)$_POST['kg'] : true;
105 $channel = $_POST['channel'] ?? null;
106 if ($channel !== null) {
107 $channel = ($channel === 'dnsapi') ? 'dnsapi' : 'pan';
108 }
109 $providerId = isset($_POST['provider_id']) ? (int)$_POST['provider_id'] : null;
110 $r = domain_product_update($id, $js, $jg, $kg, $channel, $providerId);
111 json_exit($r['ok'] ? $r['msg'] : $r['msg']);
112});
113
114mnbt_register_ajax('admin', 'p_domain_ymsc', function () {
115 mnbt_plugin_require_admin();
116 $id = (int)($_POST['id'] ?? 0);
117 $r = domain_product_delete($id);
118 json_exit($r['ok'] ? $r['msg'] : $r['msg']);
119});
120
121mnbt_register_ajax('admin', 'p_domain_ymscxz', function () {
122 mnbt_plugin_require_admin();
123 $idsz = $_POST['idsz'] ?? [];
124 if (!is_array($idsz)) $idsz = [];
125 $r = domain_product_delete_batch($idsz);
126 json_exit((string)$r['ok'], ['codr' => $r['fail']]);
127});
128
129mnbt_register_ajax('admin', 'p_domain_listym', function () {
130 mnbt_plugin_require_admin();
131 $sorting = strtoupper($_POST['sortOrder'] ?? '') === 'DESC' ? 'DESC' : 'ASC';
132 $paixu = preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['sort'] ?? 'id') ?: 'id';
133 $pagesize = intval($_POST['limit'] ?? 10);
134 $page = intval($_POST['page'] ?? 1);
135 $data = domain_product_list($page, $pagesize, $paixu, $sorting);
136 exit(json_encode(['total' => $data['total'], 'rows' => $data['rows']], 256));
137});
138
139/* ============================================================
140 * 4. 后台 AJAX:DNS 服务商凭证 CRUD
141 * ============================================================ */
142mnbt_register_ajax('admin', 'p_dns_provider_save', function () {
143 mnbt_plugin_require_admin();
144 global $DB, $date;
145 $id = (int)($_POST['id'] ?? 0);
146 $slug = $_POST['slug'] ?? 'dnspod';
147 $name = trim($_POST['name'] ?? '');
148 $apiId = trim($_POST['api_id'] ?? '');
149 $apiSecret = trim($_POST['api_secret'] ?? '');
150 $kg = isset($_POST['kg']) ? (bool)$_POST['kg'] : true;
151
152 if (!in_array($slug, ['dnspod'], true)) {
153 json_exit_error('暂不支持该服务商');
154 }
155 if ($name === '' || $apiId === '' || $apiSecret === '') {
156 json_exit_error('参数不能为空');
157 }
158
159 if ($id > 0) {
160 $ok = $DB->query_prepare(
161 "update `plg_dns_provider` set `slug`=?, `name`=?, `api_id`=?, `api_secret`=?, `qk`=? where `id`=?",
162 [$slug, $name, $apiId, $apiSecret, $kg ? 'true' : 'false', $id]
163 );
164 } else {
165 $ok = $DB->query_prepare(
166 "INSERT INTO `plg_dns_provider` (`slug`, `name`, `api_id`, `api_secret`, `extra`, `qk`, `created_at`) VALUES (?,?,?,?,?,?,?)",
167 [$slug, $name, $apiId, $apiSecret, '{}', $kg ? 'true' : 'false', $date]
168 );
169 }
170 if (!$ok) json_exit_error('保存失败:' . $DB->error());
171 json_exit_success('保存成功');
172});
173
174mnbt_register_ajax('admin', 'p_dns_provider_delete', function () {
175 mnbt_plugin_require_admin();
176 global $DB;
177 $id = (int)($_POST['id'] ?? 0);
178 // 先删关联的本地 DNS 记录(远程记录已无法删除,标记为失效)
179 $DB->query_prepare("DELETE FROM plg_dns_record WHERE provider_id=?", [$id]);
180 $ok = $DB->query_prepare("DELETE FROM plg_dns_provider WHERE id=? limit 1", [$id]);
181 if (!$ok) json_exit_error('删除失败:' . $DB->error());
182 json_exit_success('删除成功');
183});
184
185mnbt_register_ajax('admin', 'p_dns_provider_test', function () {
186 mnbt_plugin_require_admin();
187 $id = (int)($_POST['id'] ?? 0);
188 $provider = dns_provider_get($id);
189 if (!$provider) json_exit_error('服务商不存在');
190 $adapter = dns_provider_adapter($provider);
191 if (!$adapter) json_exit_error('不支持的服务商');
192 $domains = $adapter->listDomains();
193 if (empty($domains)) {
194 json_exit_error('测试失败:' . $adapter->lastError());
195 }
196 json_exit_success('测试成功,共 ' . count($domains) . ' 个域名', ['count' => count($domains)]);
197});
198
199mnbt_register_ajax('admin', 'p_dns_record_delete', function () {
200 mnbt_plugin_require_admin();
201 $id = (int)($_POST['id'] ?? 0);
202 $r = dns_record_delete($id);
203 if (!$r['ok']) json_exit_error($r['msg']);
204 json_exit_success($r['msg']);
205});
206
207/* ============================================================
208 * 5. 用户端 AJAX:DNS 记录管理
209 * ============================================================ */
210mnbt_register_ajax('user', 'p_dns_record_list', function () {
211 mnbt_plugin_require_user();
212 global $yhc;
213 $user = $yhc['user'] ?? '';
214 $records = dns_record_list_by_user($user);
215 json_exit_success('ok', ['rows' => $records]);
216});
217
218mnbt_register_ajax('user', 'p_dns_record_add', function () {
219 mnbt_plugin_require_user();
220 global $yhc, $DB;
221 $user = $yhc['user'] ?? '';
222 $providerId = (int)($_POST['provider_id'] ?? 0);
223 $domain = $_POST['domain'] ?? '';
224 $name = $_POST['name'] ?? '';
225 $type = $_POST['type'] ?? 'A';
226 $value = $_POST['value'] ?? '';
227 $ttl = (int)($_POST['ttl'] ?? 600);
228
229 // 限制:用户只能为已购域名添加记录
230 if (!domain_shop_user_owns_domain($user, $domain)) {
231 json_exit_error('您未购买该域名,无权添加记录');
232 }
233
234 // 限制:仅 DNS API 通道域名允许用户管理 DNS 记录
235 $prod = $DB->get_row_prepare("SELECT * FROM plg_domain_product WHERE url=? limit 1", [$domain]);
236 if (!$prod) json_exit_error('域名商品不存在');
237 if (($prod['channel'] ?? 'pan') !== 'dnsapi') {
238 json_exit_error('该域名为泛解析通道,无需单独管理 DNS 记录');
239 }
240
241 // 服务商以商品配置为准,忽略前端传值
242 $productProviderId = (int)$prod['provider_id'];
243 if ($productProviderId <= 0) {
244 json_exit_error('该域名未关联 DNS 服务商,请联系管理员');
245 }
246
247 $r = dns_record_create($user, $productProviderId, $domain, $name, $type, $value, $ttl, 0);
248 if (!$r['ok']) json_exit_error($r['msg']);
249 json_exit_success($r['msg']);
250});
251
252mnbt_register_ajax('user', 'p_dns_record_delete', function () {
253 mnbt_plugin_require_user();
254 global $yhc;
255 $user = $yhc['user'] ?? '';
256 $id = (int)($_POST['id'] ?? 0);
257 $r = dns_record_delete($id, $user);
258 if (!$r['ok']) json_exit_error($r['msg']);
259 json_exit_success($r['msg']);
260});
261
262/* ============================================================
263 * 6. 用户端 AJAX:域名绑定(迁自 user/api/domain.php)
264 * urllist/erurl 保留原 gn 名(与 cdn.php 不冲突)。
265 * tjurl/scurl/seturl 因与 CDN 产品的 cdn.php 同名冲突,
266 * 改名为 p_domain_tjurl / p_domain_scurl / p_domain_seturl;
267 * CDN 产品(hxc==1)仍走核心 cdn.php 的同名处理器。
268 * ============================================================ */
269require_once __DIR__ . '/user/api/bind.php';
270
271/* ============================================================
272 * 7. 钩子:host.created - 自动创建 A 记录
273 * 仅对 channel='dnsapi' 的域名商品生效;泛解析商品依赖域名整体
274 * 泛 A 记录,无需逐主机建记录(详见 lib/dns.php)。
275 * ============================================================ */
276mnbt_add_action('host.created', function ($host, $ctx = []) {
277 $r = dns_record_auto_create_for_host($host, $ctx);
278 if (!empty($r['ok']) && function_exists('mnbt_log')) {
279 mnbt_log('系统', '插件-domain_shop', '主机开通自动建 DNS:' . $r['msg'], '成功', $GLOBALS['DB']);
280 }
281}, 20);
282
283/* ============================================================
284 * 8. 钩子:order.paid - 处理 ymgm 结算
285 * 迁自 MPHX/lib/pay.function.php 的 ymgm 分支
286 * ============================================================ */
287mnbt_add_action('order.paid', function ($order, $ctx = []) {
288 global $DB;
289 if (!is_array($order)) return;
290 $lx = $order['lx'] ?? '';
291 if ($lx !== 'ymgm') return;
292
293 // ymgm 结算逻辑迁自原核心,已搬到 lib/settle.php
294 require_once __DIR__ . '/lib/settle.php';
295 domain_shop_settle_ymgm($order, $ctx);
296}, 10);
297
298/* ============================================================
299 * 9. 辅助:判断用户是否已购某域名
300 * ============================================================ */
301function domain_shop_user_owns_domain($user, $domain)
302{
303 global $DB;
304 $products = $DB->get_all_prepare("SELECT * FROM plg_domain_product WHERE url=? limit 1", [$domain]);
305 if (!$products) return false;
306 $row = $products[0];
307 $buyers = json_decode($row['json'] ?? '[]', true);
308 if (!is_array($buyers)) return false;
309 return in_array($user, $buyers, true);
310}
311
312/* ============================================================
313 * 10. 路由:/domain/buy - 替代 user/pay.php 的 ymgm 下单分支
314 * ============================================================ */
315mnbt_register_route('POST', '/domain/buy', function ($params, $ctx) {
316 // 兼容 user/pay.php 的 ymgm 下单流程,迁移到此处
317 require_once __DIR__ . '/lib/buy.php';
318 domain_shop_handle_buy();
319});
320
321/* ============================================================
322 * 11. 页面接管:set.php 的 gn=url 分支
323 * 通过 mnbt_register_page_override() 注册 filter,
324 * 当用户访问 set.php?gn=url 且非 CDN 产品时,
325 * 返回 iframe 加载插件 bind 页,替代原主题 set.php 的 url 分支。
326 * 其他 gn(CDN_url/pass/php/ssl 等)返回 null,走原主题逻辑。
327 * 此机制使所有主题(default/layui/bootstrapui/jqueryui)自动适配,
328 * 无需修改各主题的 set.php 文件。
329 * ============================================================ */
330mnbt_register_page_override('user', 'set', function ($vars) {
331 $gn = $_GET['gn'] ?? '';
332 if ($gn !== 'url') return null; // 仅接管 url 分支
333 global $yhc;
334 if (!isset($yhc) || !is_array($yhc)) return null;
335 if (($yhc['hxc'] ?? '') == '1') return null; // CDN 产品走原 CDN_url 分支
336 // 非泛解析产品:返回 iframe 加载插件域名绑定页
337 return '<div style="width:100%;height:calc(100vh - 120px);min-height:500px;padding:0;">'
338 . '<iframe src="plugin.php?p=domain_shop&page=bind" style="width:100%;height:100%;border:0;" frameborder="0" allowtransparency="true"></iframe>'
339 . '</div>';
340});