Files
docs/thinkphp/app/service/MssqlQuery.php
cenzuhai 3a04eede84 feat: 初始化进销存数据开放平台项目
初始化完整的前后端项目结构,包含ThinkPHP后端API服务和Next.js前端管理系统,添加基础配置、中间件、模型、路由等核心代码,以及项目文档和静态资源。
2026-07-08 17:48:00 +08:00

429 lines
17 KiB
PHP

<?php
namespace app\service;
use think\facade\Db;
use think\facade\Log;
class MssqlQuery
{
private static $healthCache = [
'available' => null,
'checked_at' => 0
];
private static $healthCheckInterval = 60;
private static function isMssqlAvailable()
{
$now = time();
if ($now - self::$healthCache['checked_at'] < self::$healthCheckInterval) {
return self::$healthCache['available'];
}
try {
Db::connect('mssql')->query('SELECT 1');
self::$healthCache['available'] = true;
self::$healthCache['checked_at'] = $now;
Log::info('SQL Server 连接恢复正常');
} catch (\Exception $e) {
self::$healthCache['available'] = false;
self::$healthCache['checked_at'] = $now;
Log::warning("SQL Server 不可用(下次探测 " . self::$healthCheckInterval . "s 后): " . $e->getMessage());
}
return self::$healthCache['available'];
}
public static function queryChuku($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams)
{
if (!self::isMssqlAvailable()) {
throw new \Exception('SQL Server 连接不可用');
}
return self::queryChukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams);
}
private static function queryChukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams)
{
$dateCondition = '';
$dateParams = [];
if ($startDate) {
$dateCondition .= " AND x.[日期] >= :start_date";
$dateParams['start_date'] = $startDate;
}
if ($endDate) {
$dateCondition .= " AND x.[日期] <= :end_date";
$dateParams['end_date'] = $endDate;
}
$startRow = ($page - 1) * $limit + 1;
$endRow = $page * $limit;
$countParams = array_merge($dateParams, $isolationParams);
$dataParams = array_merge($countParams, [
'start_row' => $startRow,
'end_row' => $endRow
]);
$sqlResult = self::buildLogSql(self::getChukuBaseSql(), $dateCondition, $isolationSql, $dataParams);
$dataSql = $sqlResult['dataSql'];
$logSql = $sqlResult['logSql'];
$countSql = self::getChukuCountSql();
$countSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $countSql);
Log::debug("出库查询 - 统计SQL: " . $countSql);
Log::debug("出库查询 - 统计参数: " . json_encode($countParams));
Log::debug("出库查询 - 统计参数数量: " . count($countParams));
try {
$total = Db::connect('mssql')->query($countSql, $countParams);
$total = isset($total[0]['total']) ? (int)$total[0]['total'] : 0;
Log::debug("出库查询 - 统计成功,总数: " . $total);
} catch (\Exception $e) {
Log::error("出库查询 - 统计失败: " . $e->getMessage());
Log::error("出库查询 - 统计SQL(原始): " . $countSql);
Log::error("出库查询 - 统计参数(原始): " . json_encode($countParams));
throw $e;
}
Log::debug("出库查询 - 数据SQL: " . $dataSql);
Log::debug("出库查询 - 数据参数: " . json_encode($dataParams));
Log::debug("出库查询 - 数据参数数量: " . count($dataParams));
try {
$rows = Db::connect('mssql')->query($dataSql, $dataParams);
Log::debug("出库查询 - 数据查询成功,行数: " . count($rows));
} catch (\Exception $e) {
Log::error("出库查询 - 数据查询失败: " . $e->getMessage());
Log::error("出库查询 - 数据SQL(原始): " . $dataSql);
Log::error("出库查询 - 数据参数(原始): " . json_encode($dataParams));
throw $e;
}
$records = [];
foreach ($rows as $row) {
$records[] = self::convertChukuRow($row);
}
return [
'records' => $records,
'total' => $total,
'sql' => $logSql
];
}
public static function queryRuku($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams)
{
if (!self::isMssqlAvailable()) {
throw new \Exception('SQL Server 连接不可用');
}
return self::queryRukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams);
}
private static function queryRukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams)
{
$dateCondition = '';
$dateParams = [];
if ($startDate) {
$dateCondition .= " AND x.[日期] >= :start_date";
$dateParams['start_date'] = $startDate;
}
if ($endDate) {
$dateCondition .= " AND x.[日期] <= :end_date";
$dateParams['end_date'] = $endDate;
}
$startRow = ($page - 1) * $limit + 1;
$endRow = $page * $limit;
$countParams = array_merge($dateParams, $isolationParams);
$dataParams = array_merge($countParams, [
'start_row' => $startRow,
'end_row' => $endRow
]);
$sqlResult = self::buildLogSql(self::getRukuBaseSql(), $dateCondition, $isolationSql, $dataParams);
$dataSql = $sqlResult['dataSql'];
$logSql = $sqlResult['logSql'];
$countSql = self::getRukuCountSql();
$countSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $countSql);
Log::debug("入库查询 - 统计SQL: " . $countSql);
Log::debug("入库查询 - 统计参数: " . json_encode($countParams));
$total = Db::connect('mssql')->query($countSql, $countParams);
$total = isset($total[0]['total']) ? (int)$total[0]['total'] : 0;
Log::debug("入库查询 - 数据SQL: " . $dataSql);
Log::debug("入库查询 - 数据参数: " . json_encode($dataParams));
$rows = Db::connect('mssql')->query($dataSql, $dataParams);
$records = [];
foreach ($rows as $row) {
$records[] = self::convertRukuRow($row);
}
return [
'records' => $records,
'total' => $total,
'sql' => $logSql
];
}
private static function buildLogSql($sqlTemplate, $dateCondition, $isolationSql, $allParams)
{
$dataSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $sqlTemplate);
$logSql = $dataSql;
foreach ($allParams as $key => $value) {
$placeholder = ':' . $key;
if (is_string($value)) {
$logSql = str_replace($placeholder, "'" . $value . "'", $logSql);
} else {
$logSql = str_replace($placeholder, (string)$value, $logSql);
}
}
return ['dataSql' => $dataSql, 'logSql' => $logSql];
}
private static function convertChukuRow($row)
{
return [
'chuku_id' => $row['chuku_id'] ?? '',
'ruku_id' => $row['ruku_id'] ?? '',
'ticket_no' => $row['ticket_no'] ?? '',
'date' => self::convertDate($row['date'] ?? null),
'warehouse' => $row['warehouse'] ?? '',
'batch_no' => $row['batch_no'] ?? '',
'expire_date' => self::convertDate($row['expire_date'] ?? null),
'quantity' => self::convertFloat($row['quantity'] ?? null),
'unit_price' => self::convertFloat($row['unit_price'] ?? null),
'amount' => self::convertFloat($row['amount'] ?? null),
'purchase_price' => self::convertFloat($row['purchase_price'] ?? null),
'profit' => self::convertFloat($row['profit'] ?? null),
'operator' => $row['operator'] ?? '',
'salesman' => $row['salesman'] ?? '',
'receiver' => $row['receiver'] ?? '',
'summary' => $row['summary'] ?? '',
'supplier_id' => $row['supplier_id'] ?? '',
'supplier_name' => $row['supplier_name'] ?? '',
'supplier_short_name' => $row['supplier_short_name'] ?? '',
'customer_id' => $row['customer_id'] ?? '',
'customer_name' => $row['customer_name'] ?? '',
'customer_short_name' => $row['customer_short_name'] ?? '',
'customer_region' => $row['customer_region'] ?? '',
'product_id' => $row['product_id'] ?? '',
'product_name' => $row['product_name'] ?? '',
'product_spec' => $row['product_spec'] ?? '',
'product_origin' => $row['product_origin'] ?? '',
'product_unit' => $row['product_unit'] ?? '',
'manufacturer' => $row['manufacturer'] ?? '',
];
}
private static function convertRukuRow($row)
{
return [
'ruku_id' => $row['ruku_id'] ?? '',
'ticket_no' => $row['ticket_no'] ?? '',
'date' => self::convertDate($row['date'] ?? null),
'warehouse' => $row['warehouse'] ?? '',
'batch_no' => $row['batch_no'] ?? '',
'expire_date' => self::convertDate($row['expire_date'] ?? null),
'quantity' => self::convertFloat($row['quantity'] ?? null),
'unit_price' => self::convertFloat($row['unit_price'] ?? null),
'amount' => self::convertFloat($row['amount'] ?? null),
'purchase_price' => self::convertFloat($row['purchase_price'] ?? null),
'warehouse_qty' => self::convertFloat($row['warehouse_qty'] ?? null),
'operator' => $row['operator'] ?? '',
'purchaser' => $row['purchaser'] ?? '',
'responsible_person' => $row['responsible_person'] ?? '',
'arrival_no' => $row['arrival_no'] ?? '',
'invoice_no' => $row['invoice_no'] ?? '',
'summary' => $row['summary'] ?? '',
'supplier_id' => $row['supplier_id'] ?? '',
'supplier_name' => $row['supplier_name'] ?? '',
'supplier_short_name' => $row['supplier_short_name'] ?? '',
'product_id' => $row['product_id'] ?? '',
'product_name' => $row['product_name'] ?? '',
'product_spec' => $row['product_spec'] ?? '',
'product_origin' => $row['product_origin'] ?? '',
'product_unit' => $row['product_unit'] ?? '',
'manufacturer' => $row['manufacturer'] ?? '',
];
}
private static function convertDate($value)
{
if ($value === null) {
return null;
}
if (is_numeric($value)) {
try {
$base = new \DateTime('1899-12-30');
$base->add(new \DateInterval('P' . (int)$value . 'D'));
return $base->format('Y-m-d');
} catch (\Exception $e) {
return (string)$value;
}
}
if ($value instanceof \DateTime) {
return $value->format('Y-m-d');
}
return (string)$value;
}
private static function convertFloat($value)
{
if ($value === null) {
return null;
}
return is_numeric($value) ? (float)$value : null;
}
private static function getChukuBaseSql()
{
return "
WITH PageData AS (
SELECT
ROW_NUMBER() OVER (ORDER BY x.[日期] DESC) AS RowID,
x.[出库序号] AS chuku_id,
x.[入库序号] AS ruku_id,
x.[票号] AS ticket_no,
x.[日期] AS date,
x.[库房] AS warehouse,
x.[批号] AS batch_no,
x.[效期] AS expire_date,
x.[数量] AS quantity,
x.[单价] AS unit_price,
x.[金额] AS amount,
x.[进价] AS purchase_price,
x.[毛利] AS profit,
x.[操作员] AS operator,
x.[业务员] AS salesman,
x.[提货人] AS receiver,
x.[摘要] AS summary,
x.[供方序号] AS supplier_id,
k.[供方全称] AS supplier_name,
k.[简称] AS supplier_short_name,
x.[销方序号] AS customer_id,
f.[销方全称] AS customer_name,
f.[简称] AS customer_short_name,
f.[地区] AS customer_region,
x.[产品序号] AS product_id,
p.[产品名称] AS product_name,
p.[规格] AS product_spec,
p.[产地] AS product_origin,
p.[单位] AS product_unit,
p.[生产厂家] AS manufacturer
FROM [ywsj].[dbo].[vckmx] x
JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号]
JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号]
JOIN [ywsj].[dbo].[bxfkh] f ON x.[销方序号] = f.[销方序号]
WHERE 1=1
{date_condition}
{isolation_condition}
)
SELECT
chuku_id, ruku_id, ticket_no, date, warehouse, batch_no, expire_date,
quantity, unit_price, amount, purchase_price, profit,
operator, salesman, receiver, summary,
supplier_id, supplier_name, supplier_short_name,
customer_id, customer_name, customer_short_name, customer_region,
product_id, product_name, product_spec, product_origin,
product_unit, manufacturer
FROM PageData
WHERE RowID BETWEEN :start_row AND :end_row
ORDER BY RowID
";
}
private static function getChukuCountSql()
{
return "
SELECT COUNT(*) AS total
FROM [ywsj].[dbo].[vckmx] x
JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号]
JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号]
JOIN [ywsj].[dbo].[bxfkh] f ON x.[销方序号] = f.[销方序号]
WHERE 1=1
{date_condition}
{isolation_condition}
";
}
private static function getRukuBaseSql()
{
return "
WITH PageData AS (
SELECT
ROW_NUMBER() OVER (ORDER BY x.[日期] DESC) AS RowID,
x.[入库序号] AS ruku_id,
x.[票号] AS ticket_no,
x.[日期] AS date,
x.[库房] AS warehouse,
x.[批号] AS batch_no,
x.[效期] AS expire_date,
x.[数量] AS quantity,
x.[单价] AS unit_price,
x.[金额] AS amount,
x.[进价] AS purchase_price,
x.[库房数量] AS warehouse_qty,
x.[操作员] AS operator,
x.[采购员] AS purchaser,
x.[负责人] AS responsible_person,
x.[来货单号] AS arrival_no,
x.[发票单号] AS invoice_no,
x.[摘要] AS summary,
x.[供方序号] AS supplier_id,
k.[供方全称] AS supplier_name,
k.[简称] AS supplier_short_name,
x.[产品序号] AS product_id,
p.[产品名称] AS product_name,
p.[规格] AS product_spec,
p.[产地] AS product_origin,
p.[单位] AS product_unit,
p.[生产厂家] AS manufacturer
FROM [ywsj].[dbo].[vrkmx] x
JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号]
JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号]
WHERE 1=1
{date_condition}
{isolation_condition}
)
SELECT
ruku_id, ticket_no, date, warehouse, batch_no, expire_date,
quantity, unit_price, amount, purchase_price, warehouse_qty,
operator, purchaser, responsible_person, arrival_no, invoice_no,
summary, supplier_id, supplier_name, supplier_short_name,
product_id, product_name, product_spec, product_origin,
product_unit, manufacturer
FROM PageData
WHERE RowID BETWEEN :start_row AND :end_row
ORDER BY RowID
";
}
private static function getRukuCountSql()
{
return "
SELECT COUNT(*) AS total
FROM [ywsj].[dbo].[vrkmx] x
JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号]
JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号]
WHERE 1=1
{date_condition}
{isolation_condition}
";
}
}