比较提交

...

2 次代码提交

作者 SHA1 备注 提交日期
可乐加冰
c4d7c1fce8 Merge branch 'main' of https://gitcode.gynhc.com/aAHai/xiaozhongchuf 2026-10-26 15:55:55 +08:00
可乐加冰
2456ad6945 ```
feat(deploy): 添加部署路径权限检查功能

Webhook 由 PHP(www) 触发,项目目录必须对当前用户可写。
添加了对 DEPLOY_PATH 的写权限检查,如果当前用户对部署路径没有写权限,
则显示错误信息并退出部署流程。同时提供修复建议:
chown -R www:www ${DEPLOY_PATH} && chmod -R u+rwX ${DEPLOY_PATH}
```
2026-10-26 15:55:43 +08:00
共有 6 个文件被更改,包括 488 次插入 和 0 次删除

查看文件

@@ -0,0 +1,87 @@
<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Db;
/**
* 地图服务(服务端代理高德,避免小程序 request 域名限制)
*/
class Map extends Api
{
protected $noNeedLogin = ['geocode', 'regeo'];
protected $noNeedRight = '*';
protected function amapKey()
{
$raw = Db::name('shopro_config')->where('name', 'amap')->value('value');
if ($raw) {
$cfg = json_decode($raw, true);
if (!empty($cfg['appkey'])) {
return $cfg['appkey'];
}
}
$platform = Db::name('shopro_config')->where('name', 'platform')->value('value');
if ($platform) {
$cfg = json_decode($platform, true);
if (!empty($cfg['amap']['appkey'])) {
return $cfg['amap']['appkey'];
}
}
return '24eb092b2163da8fa5087bf92844a5bd';
}
protected function amapGet($path, $query)
{
$query['key'] = $this->amapKey();
$url = 'https://restapi.amap.com/v3/' . $path . '?' . http_build_query($query);
$ctx = stream_context_create(['http' => ['timeout' => 8]]);
$body = @file_get_contents($url, false, $ctx);
if ($body === false) {
$this->error('地图服务请求失败');
}
$data = json_decode($body, true);
if (!$data || (isset($data['status']) && $data['status'] !== '1')) {
$info = isset($data['info']) ? $data['info'] : '地理编码失败';
$this->error($info);
}
return $data;
}
/**
* 地址 → 坐标
* @ApiParams (name="address", type="string", required=true)
*/
public function geocode()
{
$address = trim($this->request->get('address', ''));
if ($address === '') {
$this->error('地址不能为空');
}
$data = $this->amapGet('geocode/geo', ['address' => $address]);
if (empty($data['geocodes'][0]['location'])) {
$this->error('无法解析该地址,请尽量填写门牌号');
}
list($lng, $lat) = explode(',', $data['geocodes'][0]['location']);
$this->success('ok', [
'latitude' => $lat,
'longitude' => $lng,
'formatted' => $data['geocodes'][0]['formatted_address'] ?? '',
]);
}
/**
* 坐标 → 地址
* @ApiParams (name="location", type="string", required=true, description="lng,lat")
*/
public function regeo()
{
$location = trim($this->request->get('location', ''));
if ($location === '') {
$this->error('坐标不能为空');
}
$data = $this->amapGet('geocode/regeo', ['location' => $location]);
$this->success('ok', $data['regeocode'] ?? []);
}
}

查看文件

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# 将 Shopro 队列 Supervisor 配置安装到服务器
# 用法(root):
# DEPLOY_PATH=/www/wwwroot/chufang.gynhc.com PHP_BIN=/www/server/php/74/bin/php bash deploy/supervisor/install.sh
set -euo pipefail
DEPLOY_PATH="${DEPLOY_PATH:-/www/wwwroot/chufang.gynhc.com}"
PHP_BIN="${PHP_BIN:-$(command -v php || true)}"
CONF_SRC="${DEPLOY_PATH}/deploy/supervisor/shopro-queue.conf"
CONF_DST="${SUPERVISOR_CONF_DIR:-/etc/supervisor/conf.d}/shopro-queue.conf"
if [[ -z "$PHP_BIN" || ! -x "$PHP_BIN" ]]; then
echo "ERROR: 找不到可执行的 PHP,请设置 PHP_BIN,例如:"
echo " PHP_BIN=/www/server/php/74/bin/php"
exit 1
fi
if [[ ! -f "$CONF_SRC" ]]; then
echo "ERROR: 配置文件不存在: $CONF_SRC"
exit 1
fi
if [[ ! -f "${DEPLOY_PATH}/think" ]]; then
echo "ERROR: 项目根目录不正确,未找到 think: ${DEPLOY_PATH}/think"
exit 1
fi
mkdir -p "${DEPLOY_PATH}/runtime/log"
sed \
-e "s|PHP_BIN_PLACEHOLDER|${PHP_BIN}|g" \
-e "s|DEPLOY_PATH_PLACEHOLDER|${DEPLOY_PATH}|g" \
"$CONF_SRC" > "$CONF_DST"
echo "已写入: $CONF_DST"
echo "PHP: $PHP_BIN"
echo "项目: $DEPLOY_PATH"
if command -v supervisorctl >/dev/null 2>&1; then
supervisorctl reread
supervisorctl update
supervisorctl status shopro-queue:*
else
echo "WARN: 未找到 supervisorctl,请手动执行:"
echo " supervisorctl reread && supervisorctl update && supervisorctl status"
fi
echo "完成。"

查看文件

@@ -0,0 +1,60 @@
; Shopro 队列守护(Supervisor)
;
; 安装步骤见下方注释,或阅读 deploy/supervisor/install.sh
;
; 队列说明:
; shopro-high 支付后处理(返积分、减库存、拼团、打印小票等,需优先消费)
; shopro 订单自动关闭/确认/评价、消息通知、分销升级、活动等
;
; 使用前请修改:
; 1. PHP_BIN → 服务器 php 绝对路径(宝塔常见:/www/server/php/74/bin/php)
; 2. DEPLOY_PATH → 站点根目录(含 think 文件,非 public)
; ---------------------------------------------------------------------------
; 高优先级队列(积分返还、打印等)
; ---------------------------------------------------------------------------
[program:shopro-queue-high]
process_name=%(program_name)s_%(process_num)02d
command=PHP_BIN_PLACEHOLDER DEPLOY_PATH_PLACEHOLDER/think queue:listen --queue shopro-high --sleep 1 --timeout 120 --memory 256
directory=DEPLOY_PATH_PLACEHOLDER
autostart=true
autorestart=true
startsecs=3
startretries=10
stopasgroup=true
killasgroup=true
stopwaitsecs=120
user=www
numprocs=1
redirect_stderr=true
stdout_logfile=DEPLOY_PATH_PLACEHOLDER/runtime/log/supervisor-shopro-high.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
; ---------------------------------------------------------------------------
; 普通队列(自动关单、通知、分销等)
; ---------------------------------------------------------------------------
[program:shopro-queue]
process_name=%(program_name)s_%(process_num)02d
command=PHP_BIN_PLACEHOLDER DEPLOY_PATH_PLACEHOLDER/think queue:listen --queue shopro --sleep 3 --timeout 120 --memory 256
directory=DEPLOY_PATH_PLACEHOLDER
autostart=true
autorestart=true
startsecs=3
startretries=10
stopasgroup=true
killasgroup=true
stopwaitsecs=120
user=www
numprocs=1
redirect_stderr=true
stdout_logfile=DEPLOY_PATH_PLACEHOLDER/runtime/log/supervisor-shopro.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
; ---------------------------------------------------------------------------
; 分组(可选,便于 supervisorctl 批量管理)
; ---------------------------------------------------------------------------
[group:shopro-queue]
programs=shopro-queue-high,shopro-queue
priority=999

查看文件

@@ -23,6 +23,14 @@ if [[ ! -d .git ]]; then
exit 1 exit 1
fi fi
# Webhook 由 PHP(www) 触发,项目目录必须对当前用户可写
RUN_USER="$(id -un 2>/dev/null || echo unknown)"
if [[ ! -w "$DEPLOY_PATH" ]]; then
log "ERROR: 当前用户 ${RUN_USER} 对 ${DEPLOY_PATH} 无写权限"
log "修复(root): chown -R www:www ${DEPLOY_PATH} && chmod -R u+rwX ${DEPLOY_PATH}"
exit 1
fi
git config --global --add safe.directory "$DEPLOY_PATH" 2>/dev/null || true git config --global --add safe.directory "$DEPLOY_PATH" 2>/dev/null || true
git fetch origin "$DEPLOY_BRANCH" git fetch origin "$DEPLOY_BRANCH"

查看文件

@@ -0,0 +1,272 @@
<?php
/**
* 方案 A:清除小程序「直播」相关后台数据(链接、装修组件、宫格入口等)
*
* 用法(在项目根目录 chufang.gynhc.com_rFTnT 下):
* php scripts/remove-live-for-audit.php # 仅预览,不写库
* php scripts/remove-live-for-audit.php --apply # 执行清理
*
* 说明:
* - 依赖 application/database.php / .env 数据库配置
* - 清理后请到后台「装修」各页面保存一次并发布;小程序需重新编译(已去掉 pages/live 路由)
*/
define('APP_PATH', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'application' . DIRECTORY_SEPARATOR);
if (!is_file(APP_PATH . 'admin/command/Install/install.lock')) {
fwrite(STDERR, "未检测到 install.lock,请确认在 Shopro 后端项目根目录运行。\n");
exit(1);
}
require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'thinkphp' . DIRECTORY_SEPARATOR . 'base.php';
\think\App::initCommon();
use think\Db;
$apply = in_array('--apply', $argv ?? [], true);
/** 是否直播相关路径/文案 */
function is_live_related_string($str)
{
if (!is_string($str) || $str === '') {
return false;
}
$lower = strtolower($str);
if (strpos($lower, 'live') !== false) {
return true;
}
if (strpos($str, '直播') !== false) {
return true;
}
if (strpos($str, 'live-player-plugin') !== false) {
return true;
}
return false;
}
/** 递归清理 JSON 结构中的直播链接与菜单项 */
function scrub_live_from_value(&$value)
{
$changed = false;
if (is_array($value)) {
// 带 path / path_name / name 的菜单项:整项删除
if (isset($value['path']) || isset($value['path_name']) || isset($value['name'])) {
$path = isset($value['path']) ? $value['path'] : '';
$pathName = isset($value['path_name']) ? $value['path_name'] : (isset($value['pathName']) ? $value['pathName'] : '');
$name = isset($value['name']) ? $value['name'] : '';
if (is_live_related_string($path) || is_live_related_string($pathName) || is_live_related_string($name)) {
return ['__delete__' => true, 'changed' => true];
}
}
$isList = array_keys($value) === range(0, count($value) - 1);
if ($isList) {
$newList = [];
foreach ($value as $item) {
$res = scrub_live_from_value($item);
if (is_array($res) && !empty($res['__delete__'])) {
$changed = true;
continue;
}
if (is_array($res) && isset($res['value'])) {
$newList[] = $res['value'];
if (!empty($res['changed'])) {
$changed = true;
}
} else {
$newList[] = $item;
}
}
$value = $newList;
return ['value' => $value, 'changed' => $changed];
}
foreach ($value as $k => &$child) {
if ($k === 'path' || $k === 'path_name' || $k === 'pathName' || $k === 'page') {
if (is_string($child) && is_live_related_string($child)) {
$child = '';
$changed = true;
}
}
$res = scrub_live_from_value($child);
if (is_array($res) && !empty($res['__delete__'])) {
unset($value[$k]);
$changed = true;
continue;
}
if (is_array($res) && array_key_exists('value', $res)) {
$value[$k] = $res['value'];
if (!empty($res['changed'])) {
$changed = true;
}
}
}
unset($child);
}
return ['value' => $value, 'changed' => $changed];
}
function log_line($msg)
{
echo $msg . PHP_EOL;
}
try {
Db::query('SELECT 1');
} catch (\Throwable $e) {
fwrite(STDERR, "数据库连接失败,请在已安装 Shopro 的服务器上运行(读取 .env / application/database.php)。\n");
fwrite(STDERR, $e->getMessage() . "\n");
exit(1);
}
$prefix = config('database.prefix') ?: 'fa_';
$linkTable = $prefix . 'shopro_link';
$contentTable = $prefix . 'shopro_decorate_content';
$configTable = $prefix . 'shopro_config';
$decorateTable = $prefix . 'shopro_decorate';
log_line('=== 清除直播入口(方案 A)===');
log_line('模式: ' . ($apply ? '【执行 --apply】' : '【预览,加 --apply 才写库】'));
log_line('表前缀: ' . $prefix);
log_line('');
// ---------- 1. shopro_link ----------
$links = Db::table($linkTable)
->whereNull('deletetime')
->where(function ($q) {
$q->where('path', 'like', '%live%')
->whereOr('path', 'like', '%直播%')
->whereOr('name', 'like', '%直播%');
})
->select();
log_line('[1] 系统链接 fa_shopro_link 命中 ' . count($links) . ' 条');
foreach ($links as $row) {
log_line(' - id=' . $row['id'] . ' group=' . $row['group'] . ' name=' . $row['name'] . ' path=' . $row['path']);
}
if ($apply && count($links) > 0) {
$ids = array_column($links, 'id');
Db::table($linkTable)->where('id', 'in', $ids)->update(['deletetime' => time()]);
log_line(' => 已软删除');
}
// 名称/路径含 live 的宽松匹配(path 字段)
$links2 = Db::table($linkTable)
->whereNull('deletetime')
->where('path', 'like', '%/pages/live%')
->select();
if (count($links2) > 0) {
log_line('[1b] 路径含 /pages/live 再命中 ' . count($links2) . ' 条');
foreach ($links2 as $row) {
log_line(' - id=' . $row['id'] . ' name=' . $row['name'] . ' path=' . $row['path']);
}
if ($apply) {
Db::table($linkTable)->where('id', 'in', array_column($links2, 'id'))->update(['deletetime' => time()]);
}
}
log_line('');
// ---------- 2. shopro_decorate_content ----------
$rows = Db::table($contentTable)->select();
$deletedComponents = 0;
$updatedComponents = 0;
log_line('[2] 装修组件 fa_shopro_decorate_content 共 ' . count($rows) . ' 条');
foreach ($rows as $row) {
$type = $row['type'] ?? '';
$id = $row['id'];
$decorateId = $row['decorate_id'] ?? '';
$name = $row['name'] ?? '';
if ($type === 'live') {
log_line(" - 删除直播组件 id={$id} decorate_id={$decorateId} name={$name}");
if ($apply) {
Db::table($contentTable)->where('id', $id)->delete();
}
$deletedComponents++;
continue;
}
$contentRaw = $row['content'] ?? '';
if ($contentRaw === '' || $contentRaw === null) {
continue;
}
$content = json_decode($contentRaw, true);
if (!is_array($content)) {
continue;
}
$res = scrub_live_from_value($content);
if (empty($res['changed'])) {
continue;
}
log_line(" - 清理组件 id={$id} type={$type} decorate_id={$decorateId} name={$name}");
$updatedComponents++;
if ($apply) {
Db::table($contentTable)->where('id', $id)->update([
'content' => json_encode($res['value'], JSON_UNESCAPED_UNICODE),
]);
}
}
log_line(" 小结: 删除直播组件 {$deletedComponents} 个,清理其它组件 {$updatedComponents} 个");
log_line('');
// ---------- 3. shopro_config(JSON 配置里可能藏宫格/菜单) ----------
$configRows = Db::table($configTable)->where(function ($q) {
$q->where('value', 'like', '%live%')->whereOr('value', 'like', '%直播%');
})->select();
log_line('[3] 配置 fa_shopro_config 命中 ' . count($configRows) . ' 条(value 含 live/直播)');
$configUpdated = 0;
foreach ($configRows as $row) {
$val = $row['value'];
$decoded = json_decode($val, true);
if (!is_array($decoded)) {
if (is_live_related_string($val)) {
log_line(' - 跳过非 JSON 配置 name=' . $row['name'] . '(请人工检查)');
}
continue;
}
$res = scrub_live_from_value($decoded);
if (empty($res['changed'])) {
continue;
}
log_line(' - 清理配置 name=' . $row['name']);
$configUpdated++;
if ($apply) {
Db::table($configTable)->where('id', $row['id'])->update([
'value' => json_encode($res['value'], JSON_UNESCAPED_UNICODE),
]);
}
}
log_line(" 小结: 更新配置 {$configUpdated} 条");
log_line('');
// ---------- 4. 列出仍使用中的装修模板(便于人工核对) ----------
try {
$decorates = Db::table($decorateTable)->field('id,name,status,type,platform')->select();
log_line('[4] 当前装修模板(清理后请到后台确认「使用中」的页面已发布)');
foreach ($decorates as $d) {
$status = isset($d['status']) ? $d['status'] : '';
log_line(' - id=' . $d['id'] . ' name=' . $d['name'] . ' status=' . $status . ' platform=' . ($d['platform'] ?? ''));
}
} catch (\Exception $e) {
log_line('[4] 读取装修模板列表跳过: ' . $e->getMessage());
}
log_line('');
log_line('=== 完成 ===');
if (!$apply) {
log_line('以上为预览。确认无误后执行: php scripts/remove-live-for-audit.php --apply');
}
log_line('小程序端请重新编译上传(pages.json 已去掉直播页路由)。');

查看文件

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# 在服务器后端项目根目录执行(与 public、application 同级)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
echo "项目目录: $ROOT"
php scripts/remove-live-for-audit.php "$@"
if [[ "${1:-}" == "--apply" ]]; then
echo ""
echo "下一步:"
echo " 1. 后台 Shopro → 装修 → 打开「使用中」的模板,随便保存并发布"
echo " 2. 小程序 chufang-goods 重新编译上传(pages.json 已去掉直播页)"
fi