RESAZIP-PC\resaz

update

......@@ -89,4 +89,63 @@ sudo systemctl restart php-fpm || sudo systemctl restart httpd
extension=gmp
extension=bcmath
# 3) Restart web server / PHP-FPM service
```
\ No newline at end of file
```
## QueryLog (Laravel shared package)
### 1) Publish config in child project
```bash
php artisan vendor:publish --tag=query-log-config
```
This creates `config/query_log.php` so each project can tune its own settings.
### 2) Publish migration in child project
```bash
php artisan vendor:publish --tag=query-log-migrations
php artisan migrate
```
This publishes a production-oriented migration for `log_queries` with key indexes:
- `query_at`
- `query_type`
- `connection`
- `user_id`
- composite indexes for common filter windows
### 3) Minimal table fields
Use your own migration and ensure these columns exist in the configured table (`query_log.table`):
- `action` (string)
- `query` (longText/text)
- `query_type` (string, example: `insert`, `update`, `delete`, `replace`)
- `query_time` (float/double)
- `query_at` (datetime)
- `query_order` (int)
- `connection` (string)
- `ip` (nullable string)
- `user_id` (nullable string/int)
- `is_screen` (tinyint/bool)
### 4) Important config for production
- `enable`: enable/disable query log
- `min_time`: skip very fast queries (ms)
- `sample_rate`: sampling percent (`0-100`) to reduce high-traffic load
- `chunk`: batch size per queue job
- `max_queries_per_request`: hard limit per request
- `skip_route_patterns`: wildcard route/url patterns to skip
- `skip_command_patterns`: wildcard console command patterns to skip
- `mask_sensitive_bindings`: mask sensitive values in SQL bindings
- `sensitive_keywords`: keyword list used for masking
- `masked_value`: replacement text for sensitive bindings
### 5) Behavior highlights
- Query buffer is separated by DB connection.
- Buffer is flushed when transaction commits (outermost level).
- Buffer is cleared on rollback (rolled-back queries are not logged).
- Write-query detection supports CTE (`WITH ... UPDATE/INSERT/...`) and skips read queries.
- `query_type` stores action text directly (`insert`, `update`, `delete`, `replace`, `upsert`).
\ No newline at end of file
......
......@@ -10,10 +10,18 @@
"require": {
"brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.13",
"php": "^7.3 || ^8.0",
"laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
"laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"milon/barcode": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
},
"suggest": {
"ext-gmp": "Faster big-number calculations",
"ext-bcmath": "Faster decimal calculations when GMP is unavailable"
},
"extra": {
"laravel": {
"providers": [
"Daito\\Lib\\QueryLog\\Providers\\QueryLogProvider"
]
}
}
}
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0c23b9705a5fe293b678d1bbc5020207",
"content-hash": "94cb3df5cd945ab2de60f59263dbd8d6",
"packages": [
{
"name": "brick/math",
......@@ -1144,6 +1144,81 @@
"time": "2024-09-21T08:32:55+00:00"
},
{
"name": "milon/barcode",
"version": "v12.1.0",
"source": {
"type": "git",
"url": "https://github.com/milon/barcode.git",
"reference": "052e601665cfb99e119a630b6116fab0eb30183e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/milon/barcode/zipball/052e601665cfb99e119a630b6116fab0eb30183e",
"reference": "052e601665cfb99e119a630b6116fab0eb30183e",
"shasum": ""
},
"require": {
"ext-gd": "*",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0 | ^11.0 | ^12.0",
"php": "^7.3 | ^8.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"DNS1D": "Milon\\Barcode\\Facades\\DNS1DFacade",
"DNS2D": "Milon\\Barcode\\Facades\\DNS2DFacade"
},
"providers": [
"Milon\\Barcode\\BarcodeServiceProvider"
]
}
},
"autoload": {
"psr-0": {
"Milon\\Barcode": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Nuruzzaman Milon",
"email": "contact@milon.im"
}
],
"description": "Barcode generator like Qr Code, PDF417, C39, C39+, C39E, C39E+, C93, S25, S25+, I25, I25+, C128, C128A, C128B, C128C, 2-Digits UPC-Based Extention, 5-Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI (Variation of Plessey code)",
"keywords": [
"CODABAR",
"CODE 128",
"CODE 39",
"barcode",
"datamatrix",
"ean",
"laravel",
"pdf417",
"qr code",
"qrcode"
],
"support": {
"issues": "https://github.com/milon/barcode/issues",
"source": "https://github.com/milon/barcode/tree/v12.1.0"
},
"funding": [
{
"url": "https://paypal.me/nuruzzamanmilon",
"type": "custom"
},
{
"url": "https://github.com/milon",
"type": "github"
}
],
"time": "2026-02-07T00:42:12+00:00"
},
{
"name": "monolog/monolog",
"version": "2.11.0",
"source": {
......
<?php
namespace Daito\Lib;
use Milon\Barcode\DNS1D;
use Milon\Barcode\DNS2D;
class DaitoBarcode {
public static function getJan13Number($jan12)
{
// Kiểm tra định dạng
if (!preg_match('/^\d{12}$/', $jan12)) {
return $jan12;
}
$sum = 0;
for ($i = 0; $i < 12; $i++) {
$digit = (int) $jan12[$i];
$sum += $i % 2 === 0 ? $digit : $digit * 3;
}
$checkDigit = (10 - ($sum % 10)) % 10;
return $jan12 . $checkDigit;
}
public static function isJanEAN13($jan)
{
if (!preg_match('/^\d{13}$/', $jan)) {
return false;
}
$jan12 = substr($jan, 0, 12);
$ean13Jan = self::getJan13Number($jan12);
return $ean13Jan === $jan;
}
public static function getJan8Number($jan7)
{
if (!preg_match('/^\d{7}$/', $jan7)) {
return $jan7;
}
$sum = 0;
for ($i = 0; $i < 7; $i++) {
$digit = (int) $jan7[$i];
$sum += $i % 2 === 0 ? $digit * 3 : $digit;
}
$checkDigit = (10 - ($sum % 10)) % 10;
return $jan7 . $checkDigit;
}
public static function isJanEAN8($jan)
{
if (!preg_match('/^\d{8}$/', $jan)) {
return false;
}
$jan7 = substr($jan, 0, 7);
$ean8Jan = self::getJan8Number($jan7);
return $ean8Jan === $jan;
}
public static function getBarcodeTypeJan($jan)
{
if (self::isJanEAN8($jan)) {
return 'EAN8';
}
if (self::isJanEAN13($jan)) {
return 'EAN13';
}
return 'C128';
}
public static function generateBarcodeJan($jan)
{
$typeJan = self::getBarcodeTypeJan($jan);
if ($typeJan === 'EAN13') {
$jan = substr($jan, 0, 12);
} elseif ($typeJan === 'EAN8') {
$jan = substr($jan, 0, 7);
}
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($jan, $typeJan);
}
public static function generateBarcodeC128($text)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($text, 'C128');
}
public static function generateBarcodeC39($text)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($text, 'C39');
}
public static function generateBarcodeEAN13($ean13)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($ean13, 'EAN13');
}
public static function generateBarcodeEAN8($ean8)
{
$dns1d = new DNS1D();
return $dns1d->getBarcodePNG($ean8, 'EAN8');
}
public static function generateBarcodeQrCode($text)
{
$dns2d = new DNS2D();
return $dns2d->getBarcodePNG($text, 'QRCODE');
}
}
\ No newline at end of file
<?php
namespace Daito\Lib;
use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
use RuntimeException;
class DaitoNumber
{
const DEFAULT_MAX_DECIMALS = 12;
/**
* Format number with configurable separators, prefix/suffix, and decimal behavior.
*
* Example:
* DaitoNumber::format('1234567.00') => '1,234,567'
* DaitoNumber::format('1234567.5', array('decimals' => 2)) => '1,234,567.50'
*/
public static function format($number, array $arrOptions = array())
{
$arrConfig = array_merge(
array(
'thousands_separator' => ',',
'decimal_separator' => '.',
'prefix' => '',
'suffix' => '',
'decimals' => null,
'trim_trailing_zeros' => true,
'max_decimals' => self::DEFAULT_MAX_DECIMALS,
),
$arrOptions
);
$numberString = self::toCanonicalString($number);
$isNegative = strpos($numberString, '-') === 0;
$unsigned = $isNegative ? substr($numberString, 1) : $numberString;
if ($arrConfig['decimals'] !== null) {
$decimals = (int) $arrConfig['decimals'];
if ($decimals < 0) {
throw new RuntimeException('decimals must be greater than or equal to 0.');
}
$unsigned = (string) BigDecimal::of($unsigned)->toScale($decimals, RoundingMode::HALF_UP);
} else {
$maxDecimals = (int) $arrConfig['max_decimals'];
if ($maxDecimals < 0) {
throw new RuntimeException('max_decimals must be greater than or equal to 0.');
}
$unsigned = (string) BigDecimal::of($unsigned)->toScale($maxDecimals, RoundingMode::HALF_UP);
if ($arrConfig['trim_trailing_zeros']) {
$unsigned = self::trimTrailingZeros($unsigned);
}
}
$arrParts = explode('.', $unsigned, 2);
$integerPart = self::addThousandsSeparator($arrParts[0], (string) $arrConfig['thousands_separator']);
$decimalPart = isset($arrParts[1]) ? $arrParts[1] : '';
$formattedNumber = $integerPart;
if ($decimalPart !== '') {
$formattedNumber .= (string) $arrConfig['decimal_separator'] . $decimalPart;
}
$formatted = (string) $arrConfig['prefix'] . $formattedNumber . (string) $arrConfig['suffix'];
return $isNegative ? '-' . $formatted : $formatted;
}
/**
* Format currency-friendly output (default 2 decimals, keep trailing zeros).
*
* Example:
* DaitoNumber::formatCurrency('1234.5') => '1,234.50'
* DaitoNumber::formatCurrency('1234.5', array('prefix' => '$')) => '$1,234.50'
*/
public static function formatCurrency($number, array $arrOptions = array())
{
$arrCurrencyOptions = array_merge(
array(
'decimals' => 2,
'trim_trailing_zeros' => false,
'prefix' => '',
'suffix' => '',
),
$arrOptions
);
return self::format($number, $arrCurrencyOptions);
}
/**
* Format percentage value.
*
* Example:
* DaitoNumber::formatPercent('12.3456') => '12.35%'
* DaitoNumber::formatPercent('0.1234', array('input_ratio' => true)) => '12.34%'
*/
public static function formatPercent($number, array $arrOptions = array())
{
$arrPercentOptions = array_merge(
array(
'decimals' => 2,
'trim_trailing_zeros' => true,
'suffix' => '%',
'input_ratio' => false,
),
$arrOptions
);
$numberValue = $number;
if ($arrPercentOptions['input_ratio']) {
$numberValue = (string) BigDecimal::of(self::toCanonicalString($number))->multipliedBy('100');
}
unset($arrPercentOptions['input_ratio']);
return self::format($numberValue, $arrPercentOptions);
}
private static function toCanonicalString($number)
{
if (is_int($number) || is_string($number)) {
$numberString = trim((string) $number);
} elseif (is_float($number)) {
$numberString = self::trimTrailingZeros(sprintf('%.14F', $number));
} else {
throw new RuntimeException('DaitoNumber only supports int, float, or numeric string.');
}
if ($numberString === '' || !preg_match('/^[+-]?\d+(\.\d+)?$/', $numberString)) {
throw new RuntimeException('Invalid number format.');
}
return (string) BigDecimal::of($numberString);
}
private static function addThousandsSeparator($integerPart, $thousandsSeparator)
{
return preg_replace('/\B(?=(\d{3})+(?!\d))/', $thousandsSeparator, $integerPart);
}
private static function trimTrailingZeros($numberString)
{
if (strpos($numberString, '.') === false) {
return $numberString;
}
$numberString = rtrim($numberString, '0');
$numberString = rtrim($numberString, '.');
return $numberString === '' ? '0' : $numberString;
}
}
<?php
namespace Daito\Lib;
class DaitoString {
public static function toUpper($text){
return strtoupper($text);
use RuntimeException;
class DaitoString
{
const UTF8 = 'UTF-8';
const NKF_SOURCE_ENCODINGS = 'SJIS-win,CP932,Shift_JIS,EUC-JP,UTF-8';
/**
* Collapse multiple spaces/tabs/newlines into a single ASCII space.
*
* Example:
* DaitoString::collapseSpaces("a b\t\tc") => "a b c"
*/
public static function collapseSpaces($input)
{
return preg_replace('/\s+/u', ' ', (string) $input);
}
public static function toLower($text){
return strtolower($text);
/**
* Convert full-width Japanese spaces to ASCII spaces.
*
* Example:
* DaitoString::normalizeFullWidthSpace("A B") => "A B"
*/
public static function normalizeFullWidthSpace($input)
{
return str_replace(' ', ' ', (string) $input);
}
/**
* Convert half-width kana to full-width kana.
*
* Example:
* DaitoString::toFullWidthKana("カタカナ") => "カタカナ"
*/
public static function toFullWidthKana($text)
{
return mb_convert_kana((string) $text, 'KV', self::UTF8);
}
/**
* Convert full-width kana to half-width kana.
*
* Example:
* DaitoString::toHalfWidthKana("カタカナ") => "カタカナ"
*/
public static function toHalfWidthKana($text)
{
return mb_convert_kana((string) $text, 'kV', self::UTF8);
}
/**
* Split text by normalized spaces (full-width/extra spaces are handled).
*
* Example:
* DaitoString::splitBySpace("A  B C") => array("A", "B", "C")
*/
public static function splitBySpace($string)
{
$normalized = self::normalizeFullWidthSpace((string) $string);
$normalized = trim(self::collapseSpaces($normalized));
if ($normalized === '') {
return array();
}
return explode(' ', $normalized);
}
/**
* Convert Hiragana to Katakana.
*
* Example:
* DaitoString::toKatakana("ひらがな") => "ヒラガナ"
*/
public static function toKatakana($input)
{
return mb_convert_kana((string) $input, 'C', self::UTF8);
}
/**
* Check whether the input contains Japanese characters.
*
* Example:
* DaitoString::isJapanese("abc日本語") => true
* DaitoString::isJapanese("abcdef") => false
*/
public static function isJapanese($input)
{
return preg_match('/[\x{3040}-\x{30FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{FF66}-\x{FF9D}]/u', (string) $input) === 1;
}
/**
* Convert Japanese-encoded text file to UTF-8.
* Prefer nkf when available, fallback to mb_convert_encoding.
*
* Example:
* DaitoString::convertToUtf8('/tmp/source.csv');
* DaitoString::convertToUtf8('/tmp/source.csv', '/tmp/out', 'source_utf8.csv', 1);
*/
public static function convertToUtf8($sourceFile, $destDir = '', $fileName = '', $isBk = 0)
{
$sourceFilePath = (string) $sourceFile;
if (!is_file($sourceFilePath)) {
throw new RuntimeException('Source file does not exist: ' . $sourceFilePath);
}
$targetDirectory = $destDir ? (string) $destDir : dirname($sourceFilePath);
if (!is_dir($targetDirectory)) {
throw new RuntimeException('Destination directory does not exist: ' . $targetDirectory);
}
$targetFileName = $fileName ? (string) $fileName : basename($sourceFilePath);
$destFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName;
if ((string) realpath($sourceFilePath) === (string) realpath($destFile)) {
$destFile .= '_' . time();
}
if ($isBk) {
$backupFile = $targetDirectory . DIRECTORY_SEPARATOR . $targetFileName . '.bak';
if (!copy($sourceFilePath, $backupFile)) {
throw new RuntimeException('Can not create backup file: ' . $backupFile);
}
}
$utf8Content = self::convertJapaneseTextToUtf8($sourceFilePath);
if (file_put_contents($destFile, $utf8Content) === false) {
throw new RuntimeException('Can not write destination file: ' . $destFile);
}
return $destFile;
}
private static function convertJapaneseTextToUtf8($sourceFilePath)
{
if (self::canUseNkf()) {
$nkfContent = self::convertFileByNkf($sourceFilePath);
if ($nkfContent !== null) {
return $nkfContent;
}
}
return self::convertFileToUtf8ByMbstring($sourceFilePath);
}
private static function canUseNkf()
{
if (!function_exists('shell_exec')) {
return false;
}
$disabledFunctions = (string) ini_get('disable_functions');
if ($disabledFunctions !== '' && strpos($disabledFunctions, 'shell_exec') !== false) {
return false;
}
$output = @shell_exec('nkf --version');
return is_string($output) && $output !== '';
}
private static function convertFileByNkf($sourceFilePath)
{
$command = 'nkf -w -- ' . escapeshellarg($sourceFilePath);
$output = @shell_exec($command);
return is_string($output) ? $output : null;
}
/**
* Convert a file content to UTF-8 using mbstring.
*
* Example:
* DaitoString::convertFileToUtf8ByMbstring('/tmp/source_sjis.txt');
*/
public static function convertFileToUtf8ByMbstring($sourceFilePath)
{
$content = file_get_contents($sourceFilePath);
if ($content === false) {
throw new RuntimeException('Can not read source file: ' . $sourceFilePath);
}
return self::convertTextToUtf8ByMbstring($content);
}
/**
* Convert raw text to UTF-8 using mbstring.
*
* Example:
* DaitoString::convertTextToUtf8ByMbstring($rawText);
*/
public static function convertTextToUtf8ByMbstring($text)
{
$utf8Content = mb_convert_encoding((string) $text, self::UTF8, self::NKF_SOURCE_ENCODINGS);
if ($utf8Content === false) {
throw new RuntimeException('Can not convert text to UTF-8.');
}
return $utf8Content;
}
}
\ No newline at end of file
......
<?php
namespace Daito\Lib\QueryLog\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
class SaveQueryLogJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $arrQueries;
public function __construct(array $arrQueries)
{
$this->arrQueries = $arrQueries;
}
public function handle(): void
{
if ($this->arrQueries === array()) {
return;
}
DB::connection(config('query_log.connection', 'query_log'))
->table(config('query_log.table', 'log_queries'))
->insert($this->arrQueries);
}
}
<?php
namespace Daito\Lib\QueryLog\Models;
use Illuminate\Database\Eloquent\Model;
class QueryLog extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'log_queries';
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'action',
'query',
'query_type',
'query_time',
'query_at',
'query_order',
'connection',
'ip',
'user_id',
'is_screen',
];
protected $connection = 'query_log';
public function __construct(array $arrAttributes = array())
{
parent::__construct($arrAttributes);
$this->setConnection(config('query_log.connection', 'query_log'));
$this->setTable(config('query_log.table', 'log_queries'));
}
}
<?php
return [
'enable' => env('ENABLE_QUERY_LOG', false),
'log_on_console' => env('QUERY_LOG_ON_CONSOLE', false),
'sample_rate' => env('QUERY_LOG_SAMPLE_RATE', 100), // 0-100 (%)
'chunk' => env('QUERY_LOG_CHUNK', 200),
'max_queries_per_request' => env('QUERY_LOG_MAX_PER_REQUEST', 1000),
'min_time' => env('QUERY_LOG_MIN_TIME', 0),
'connection' => env('QUERY_LOG_CONNECTION', 'query_log'),
'table' => env('QUERY_LOG_TABLE', 'log_queries'),
'queue_connection' => env('QUERY_LOG_QUEUE_CONNECTION', null),
'queue_name' => env('QUERY_LOG_QUEUE_NAME', null),
'max_sql_length' => env('QUERY_LOG_MAX_SQL_LENGTH', 4000),
'mask_sensitive_bindings' => env('QUERY_LOG_MASK_SENSITIVE_BINDINGS', true),
'sensitive_keywords' => array(
'password',
'passwd',
'pwd',
'token',
'secret',
'api_key',
'apikey',
'authorization',
'cookie',
'credit_card',
'card_number',
'cvv',
'pin',
),
'masked_value' => '***',
'skip_route_patterns' => array(
'horizon*',
'telescope*',
'_debugbar*',
),
'skip_command_patterns' => array(
'queue:*',
'horizon*',
'schedule:run',
),
'ignore_tables' => array(
'log_queries',
'jobs',
'failed_jobs',
'migrations',
'mst_batch',
),
];
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
$connection = config('query_log.connection', 'query_log');
$table = config('query_log.table', 'log_queries');
Schema::connection($connection)->create($table, function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('action', 512)->nullable();
$table->longText('query');
$table->string('query_type', 32);
$table->decimal('query_time', 10, 3)->default(0);
$table->dateTime('query_at');
$table->unsignedInteger('query_order')->default(0);
$table->string('connection', 64)->nullable();
$table->string('ip', 64)->nullable();
$table->string('user_id', 64)->nullable();
$table->boolean('is_screen')->default(false);
$table->index('query_at', 'idx_log_queries_query_at');
$table->index('query_type', 'idx_log_queries_query_type');
$table->index('connection', 'idx_log_queries_connection');
$table->index('user_id', 'idx_log_queries_user_id');
$table->index(array('query_at', 'query_type'), 'idx_log_queries_at_type');
$table->index(array('connection', 'query_at'), 'idx_log_queries_connection_at');
});
}
public function down(): void
{
$connection = config('query_log.connection', 'query_log');
$table = config('query_log.table', 'log_queries');
Schema::connection($connection)->dropIfExists($table);
}
};
<?php
require __DIR__ . '/vendor/autoload.php';
$container = new Illuminate\Container\Container();
Illuminate\Container\Container::setInstance($container);
$container->instance('config', new Illuminate\Config\Repository(array(
'barcode' => array(
'store_path' => sys_get_temp_dir(),
),
)));
$base64 = Daito\Lib\DaitoBarcode::generateBarcodeQrCode('HELLO-QR');
echo substr($base64, 0, 80) . PHP_EOL;
echo 'length: ' . strlen($base64) . PHP_EOL;
echo Daito\Lib\DaitoMath::div(1, 2) . PHP_EOL;
\ No newline at end of file