This commit is contained in:
梁朝伟
2024-11-13 19:23:49 +08:00
commit 7f3db2ed48
461 changed files with 276684 additions and 0 deletions

1
packages/README.md Normal file
View File

@@ -0,0 +1 @@
# some packages

View File

@@ -0,0 +1 @@
# some api service

View File

@@ -0,0 +1,14 @@
#!/bin/bash
sign="xxxxxx"
now=$(date +%s)
curl -X POST "https://dev.energytrust.com.cn/pkg/dataservice/cluepackage?sign=${sign}&method=spi.alipay.data.dataservice.ad.cluepackage.send&charset=UTF-8&version=1.0&utc_timestamp=${now}&sign_type=RSA2" \
--header "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
--data-urlencode "id=123456" \
--data-urlencode "user_name=黄丫" \
--data-urlencode "user_mobile_no=18000000000" \
--data-urlencode "user_province=浙江省" \
--data-urlencode "user_city=杭州市" \
--data-urlencode "clue_time=2024-07-24 11:44:16" \
--data-urlencode "extend_info=行业拓展字段"

View File

@@ -0,0 +1,9 @@
{
"name": "pkg/bydauto",
"type": "metapackage",
"autoload": {
"psr-4": {
"BYDAuto\\": "src/"
}
}
}

View File

@@ -0,0 +1,4 @@
<?php
return [
];

View File

@@ -0,0 +1,39 @@
<?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
{
Schema::create('bydauto_contact', function (Blueprint $table) {
$table->id();
$table->timestamp('date_created');
$table->timestamp('date_updated');
$table->string('status')->nullable();
$table->string('email')->nullable();
$table->string('remark')->nullable();
});
Schema::create('bydauto_testdrive', function (Blueprint $table) {
$table->id();
$table->timestamp('date_created');
$table->timestamp('date_updated');
$table->string('status')->nullable();
$table->string('name')->nullable();
$table->string('mobile')->nullable();
$table->string('platform')->nullable();
$table->string('source')->nullable();
$table->string('request_id')->nullable();
$table->jsonb('meta')->nullable();
$table->jsonb('rawdata')->nullable();
});
}
public function down(): void
{
Schema::dropIfExists('bydauto_testdrive');
}
};

View File

@@ -0,0 +1,14 @@
<?php
use BYDAuto\Controllers\BYDAutoController;
use Illuminate\Support\Facades\Route;
$prefix = '/pkg/bydauto';
Route::group(['prefix' => $prefix, 'middleware' => ['api']], function () {
Route::get('vehicle', [BYDAutoController::class, 'vehicle']);
Route::get('dealer', [BYDAutoController::class, 'dealer']);
Route::get('province', [BYDAutoController::class, 'province']);
Route::get('city', [BYDAutoController::class, 'city']);
Route::post('testdrive', [BYDAutoController::class, 'testdrive']);
});

View File

@@ -0,0 +1,23 @@
<?php
namespace BYDAuto\Actions;
use BYDAuto\Models\Testdrive;
use Illuminate\Http\Request;
class TestdriveCreateAction
{
public function execute(Request $request)
{
$data = $request->all();
$data['request_id'] = $request->request_id;
return $this->createTestdrive($data);
}
public function createTestdrive($data)
{
$model = new Testdrive($data);
$model->save();
return $model;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace BYDAuto;
use BYDAuto\Commands\BYDAutoCommand;
use BYDAuto\Commands\BYDAutoNoticeCommand;
use BYDAuto\Commands\CheryTransferCommand;
use BYDAuto\Commands\GeocodingCommand;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;
class BYDAutoServiceProvider extends ServiceProvider
{
public function register()
{
$root = dirname(__DIR__);
// migrate
$this->loadMigrationsFrom($root . '/migrations');
$this->commands([
CheryTransferCommand::class,
BYDAutoCommand::class,
GeocodingCommand::class,
BYDAutoNoticeCommand::class,
]);
$this->mergeConfigFrom($root . '/config/bydauto.php', 'bydauto');
$this->loadRoutesFrom($root . '/routes/api.php');
$this->loadViewsFrom($root . '/views', 'bydauto');
}
public function boot()
{
$this->app->booted(function () {
$schedule = $this->app->make(Schedule::class);
$schedule->command('BYDAuto:notice')->dailyAt('09:00');
});
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace BYDAuto;
use GuzzleHttp\Client;
class CheryClueApi
{
protected $appId;
protected $appSecret;
protected $endpoint;
protected $httpClient;
private static $instance;
private function __construct()
{
$this->appId = config('chery-crm.scrm.app_id');
$this->appSecret = config('chery-crm.scrm.secret');
$this->endpoint = config('chery-crm.scrm.endpoint');
$this->httpClient = new Client(['base_uri' => $this->endpoint, 'timeout' => 10]);
}
public static function getInstance(): self
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 线索推送
*
* @param array $data
* @return CheryResult
*/
public function cluePush($data)
{
$token = $this->getToken();
$response = $this->httpClient->post('/api/app/chery-clue/api/clue/push/channel', ['json' => $data, 'headers' => ['x-csrf-token' => $token]]);
$content = json_decode($response->getBody()->getContents(), true);
$result = new CheryResult($content);
if ($result->code !== '0') {
$message = $result->get('msg', '推送线索失败');
throw new \Exception($message);
}
return $result;
}
public function getToken($force = false)
{
$token = cache('pkg:chery-scrm-token');
if ($force || !$token) {
$response = $this->httpClient->get('/api/app/chery-clue/api/clue/channel/getToken', ['query' => ['appId' => $this->appId, 'appSecret' => $this->appSecret]]);
$json = json_decode($response->getBody()->getContents(), true);
if ($json['code'] === '0') {
$token = $json['data'];
// 有效期4小时提前10分钟刷新
cache(['pkg:chery-scrm-token' => $token], now()->addSeconds(4 * 3600 - 600));
} else {
$message = $json['msg'] ?? 'pkg:chery-scrm-token 获取失败';
throw new \Exception($message);
}
}
return $token;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace BYDAuto;
use BYDAuto\Foundation\DataTransfer;
use Illuminate\Support\Arr;
class CheryResult extends DataTransfer
{
public $code;
public $msg;
public $data;
public $reqId;
public function __construct($attributes)
{
$this->code = Arr::get($attributes, 'code');
$this->msg = Arr::get($attributes, 'msg');
$this->data = Arr::get($attributes, 'data');
$this->reqId = Arr::get($attributes, 'reqId');
$this->attributes = $attributes;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace BYDAuto\Commands;
use BYDAuto\Models\Contact;
use BYDAuto\Models\Testdrive;
use Illuminate\Console\Command;
class BYDAutoCommand extends Command
{
protected $signature = 'bydauto {action?}';
protected $description = 'BYDAuto Command';
public function handle()
{
$action = $this->argument('action');
$method = "action_{$action}";
if (method_exists($this, $method)) {
$this->{$method}();
} else {
$this->error("不支持的操作 {$action}");
}
}
public function action_init()
{
$this->call('db:wipe');
$this->call('migrate');
}
public function action_testdrive()
{
$items = Testdrive::query()->orderBy('id', 'desc')->get();
$this->table(['id', 'date_created', 'status', 'name', 'mobile'], $items->map(function (Testdrive $v) {
return [$v->id, $v->date_created, $v->status, $v->name, $v->mobile];
}));
}
public function action_contact()
{
$items = Contact::all();
$this->table(['id', 'email', 'remark', 'status'], $items->map(function (Contact $v) {
return [$v->id, $v->email, $v->remark, $v->status];
}));
}
public function action_contact_add()
{
$data['email'] = $this->ask('Email');
$data['remark'] = $this->ask('Remark');
$data['status'] = Contact::STATUS_PUBLISHED;
$contact = new Contact($data);
$contact->save();
$this->info('Contact added');
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace BYDAuto\Commands;
use BYDAuto\Models\Testdrive;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
class BYDAutoNoticeCommand extends Command
{
protected $signature = 'bydauto:notice {date?}';
protected $description = '线索日报';
public function handle()
{
$date = $this->argument('date') ?: Carbon::yesterday()->toDateString();
$table = (new Testdrive())->getTable();
$items = DB::table($table)
->selectRaw('source, COUNT(*) AS total')
->groupByRaw('source')
->whereDate('date_created', $date)
->get();
$count = $items->sum('total');
$sourceMap = [
'alipromote:99800024' => '自建站99800024',
'alipromote:100000006' => '自建站100000006',
'alipromote:cluepackage' => '车生活推送',
'alipromote:isaas' => '小程序',
];
$users = config('chery-crm.daily_mail_users');
$users = explode(',', $users);
$html = \View::make('chery-crm::notice.salesleads-day', compact('items', 'date', 'sourceMap', 'count'))->render();
Mail::html($html, function ($message) use ($date, $users) {
$message->subject('线索日报 ' . $date)->to($users);
});
}
public function sourceGroup()
{
$table = (new Testdrive())->getTable();
$items = DB::table($table)
->selectRaw('DATE(date_created) AS day, source, COUNT(*) AS total')
->groupByRaw('DATE(date_created), source')
->orderByRaw('DATE(date_created), source')
->get();
$sourceGroup = $items->groupBy('source');
$html = \View::make('chery-crm::notice.salesleads', compact('sourceGroup'))->render();
Mail::html($html, function ($message) {
$message
->subject('销售线索每日通知')
->to('ifme.in@gmail.com');
});
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace BYDAuto\Commands;
use BYDAuto\Jobs\TestdrivePushJob;
use BYDAuto\Models\Testdrive;
use BYDAuto\Models\Vehicle;
use Illuminate\Console\Command;
class CheryTransferCommand extends Command
{
// php artisan BYDAuto:transfer vehicle_alias_update
// php artisan BYDAuto:transfer dispatch_by_mobile
// php artisan BYDAuto:transfer dispatch_all_draft
protected $signature = 'bydauto:transfer {action}';
protected $description = '奇瑞SCRM数据传输';
public function handle()
{
$action = $this->argument('action');
$this->{'action_' . $action}();
}
public function action_dispatch_all_draft()
{
$items = Testdrive::query()
->where('status', Testdrive::STATUS_DRAFT)
->get();
$this->showTable($items);
$confirm = $this->confirm('是否确认推送?', true);
if (!$confirm) {
return Command::SUCCESS;
}
foreach ($items as $model) {
TestdrivePushJob::dispatch($model);
}
return Command::SUCCESS;
}
public function action_vehicle_alias_update()
{
$items = Vehicle::query()->get();
$items->each(function ($item) {
$cleanName = preg_replace('/\s+/', '', $item->name);
$alias = [$item->name];
if ($item->name !== $cleanName) {
$alias[] = $cleanName;
}
$item->update(['alias' => $alias]);
});
}
public function action_dispatch_by_mobile()
{
$mobiles = $this->ask('请输入手机号码,多个用逗号分隔');
$mobiles = explode(',', $mobiles);
$items = Testdrive::query()
->whereIn('mobile', $mobiles)
->get();
$this->showTable($items);
$confirm = $this->confirm('是否确认推送?', true);
if (!$confirm) {
return Command::SUCCESS;
}
foreach ($items as $model) {
TestdrivePushJob::dispatch($model);
}
return Command::SUCCESS;
}
protected function showTable($items)
{
$columns = ['id', 'status', 'name', 'mobile'];
$this->table($columns, $items->map(fn($item) => $item->only($columns)));
}
}

View File

@@ -0,0 +1,206 @@
<?php
namespace BYDAuto\Commands;
use BYDAuto\Models\Dealer;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class GeocodingCommand extends Command
{
protected $signature = 'bydauto:geocoding {action}';
protected $description = '使用百度地图 API 把地址转换为经纬度';
public function handle()
{
$action = $this->argument('action');
if (method_exists($this, 'action_' . $action)) {
$this->{'action_' . $action}();
} else {
$this->error('Action not found');
}
}
public function action_update()
{
$items = Dealer::query()->orderBy('id')->get();
dump($items[0]);
}
public function xlsx($chunk, $index)
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '门店名称(必填)');
$sheet->setCellValue('B1', '门店编码');
$sheet->setCellValue('C1', '省份名称(必填)');
$sheet->setCellValue('D1', '城市名称(必填)');
$sheet->setCellValue('E1', '描述');
$sheet->setCellValue('F1', '地址(必填)');
$sheet->setCellValue('G1', '经度(必填)');
$sheet->setCellValue('H1', '纬度(必填)');
$sheet->setCellValue('I1', '电话(必填)');
$sheet->setCellValue('J1', '营业时间(必填)');
$sheet->setCellValue('K1', '标签');
$sheet->setCellValue('L1', '业务扩展字段');
$sn = 2;
foreach ($chunk as $dealer) {
$except1 = ['北京', '上海', '天津', '重庆']; // 直辖市
$except2 = ['内蒙古', '内蒙古(蒙东)', '内蒙古(蒙西)']; // 内蒙古自治区
$except3 = ['新疆']; // 新疆维吾尔自治区
$except4 = ['西藏']; // 西藏自治区
$except5 = ['广西']; // 广西壮族自治区
$except6 = ['宁夏']; // 宁夏回族自治区
$except7 = ['香港', '澳门']; // 香港特别行政区、澳门特别行政区
$province = $dealer->province;
if (in_array($province, $except1)) {
$province .= '市';
} elseif (in_array($province, $except2)) {
$province = '内蒙古自治区';
} elseif (in_array($province, $except3)) {
$province = '新疆维吾尔自治区';
} elseif (in_array($province, $except4)) {
$province = '西藏自治区';
} elseif (in_array($province, $except5)) {
$province = '广西壮族自治区';
} elseif (in_array($province, $except6)) {
$province = '宁夏回族自治区';
} elseif (in_array($province, $except7)) {
$province .= '特别行政区';
} else {
$province .= '省';
}
$lng = $dealer->lng ? number_format($dealer->lng, 6) : '';
$lat = $dealer->lat ? number_format($dealer->lat, 6) : '';
$sheet->setCellValue('A' . $sn, $dealer->name);
$sheet->setCellValue('B' . $sn, $dealer->erp);
$sheet->setCellValue('C' . $sn, $province);
$sheet->setCellValue('D' . $sn, $dealer->city);
$sheet->setCellValue('E' . $sn, $dealer->alias);
$sheet->setCellValue('F' . $sn, $dealer->address);
$sheet->setCellValue('G' . $sn, $lng);
$sheet->setCellValue('H' . $sn, $lat);
$sheet->setCellValue('I' . $sn, '13000000000');
$sheet->setCellValue('J' . $sn, '09:00-18:00');
$sheet->setCellValue('K' . $sn, '');
$vehicles = $dealer->vehicles->map(fn($v) => $v->extra_code)->join(',');
$vehicles = trim($vehicles, ',');
$sheet->setCellValue('L' . $sn, $vehicles);
++$sn;
}
$writer = new Xlsx($spreadsheet);
$writer->save(storage_path('app/dealers-' . $index . '.xlsx'));
}
public function csv($chunk, $index)
{
$headers = ['门店名称(必填)', '门店编码', '省份名称(必填)', '城市名称(必填)', '描述', '地址(必填)', '经度(必填)', '纬度(必填)', '电话(必填)', '营业时间(必填)', '标签', '业务扩展字段'];
$file = fopen(storage_path('app/dealers-' . $index . '.csv'), 'w');
fputcsv($file, $headers);
$chunk->each(function (Dealer $dealer) use ($file) {
$except1 = ['北京', '上海', '天津', '重庆']; // 直辖市
$except2 = ['内蒙古', '内蒙古(蒙东)', '内蒙古(蒙西)']; // 内蒙古自治区
$except3 = ['新疆']; // 新疆维吾尔自治区
$except4 = ['西藏']; // 西藏自治区
$except5 = ['广西']; // 广西壮族自治区
$except6 = ['宁夏']; // 宁夏回族自治区
$except7 = ['香港', '澳门']; // 香港特别行政区、澳门特别行政区
$province = $dealer->province;
if (in_array($province, $except1)) {
$province .= '市';
} elseif (in_array($province, $except2)) {
$province = '内蒙古自治区';
} elseif (in_array($province, $except3)) {
$province = '新疆维吾尔自治区';
} elseif (in_array($province, $except4)) {
$province = '西藏自治区';
} elseif (in_array($province, $except5)) {
$province = '广西壮族自治区';
} elseif (in_array($province, $except6)) {
$province = '宁夏回族自治区';
} elseif (in_array($province, $except7)) {
$province .= '特别行政区';
} else {
$province .= '省';
}
$lng = $dealer->lng ? number_format($dealer->lng, 6) : '';
$lat = $dealer->lat ? number_format($dealer->lat, 6) : '';
fputcsv($file, [
$dealer->name,
$dealer->erp,
$province,
$dealer->city,
$dealer->alias,
$dealer->address,
$lng,
$lat,
'13000000000',
'09:00-18:00',
'',
$dealer->region,
]);
});
fclose($file);
}
public function action_csv()
{
$items = Dealer::query()->orderBy('id')->get();
$chunks = $items->chunk(200);
$chunks->each(fn($chunk, $index) => $this->xlsx($chunk, $index));
}
public function action_xlsx()
{
$items = Dealer::query()->orderBy('id')->get();
$chunks = $items->chunk(200);
$chunks->each(fn($chunk, $index) => $this->xlsx($chunk, $index));
}
public function action_dump()
{
$address = $this->ask('请输入地址');
$response = $this->getGeocoding($address);
dump($response);
}
public function action_show()
{
$items = Dealer::query()->take(4)->get();
$this->table(['CODE', '经销商名称', '在售车型'], $items->map(fn(Dealer $d) => [$d->erp, $d->alias, $d->vehicles->map(fn($v) => $v->spec_id)->join(',')]));
}
public function action_fix()
{
$dealers = Dealer::query()->orderBy('id')->whereNull('lat')->get();
$dealers->each(function (Dealer $dealer) {
$endpoint = 'https://api.map.baidu.com/geocoding/v3/';
$address = str_replace("\n", " ", $dealer->address);
$this->info($dealer->id . ':' . $address);
$response = Http::get($endpoint, ['address' => $address, 'output' => 'json', 'ak' => config('services.baidulbs.ak')]);
if ($response->json('status') === 0) {
$location = $response->json('result.location');
$dealer->update(['address' => $address, 'lat' => $location['lat'], 'lng' => $location['lng']]);
}
});
}
public function getGeocoding($address)
{
$endpoint = 'https://api.map.baidu.com/geocoding/v3/';
$response = Http::get($endpoint, ['address' => $address, 'output' => 'json', 'ak' => config('services.baidulbs.ak')]);
return $response->json();
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace BYDAuto\Controllers;
use BYDAuto\Actions\TestdriveCreateAction;
use BYDAuto\Models\Dealer;
use BYDAuto\Models\Vehicle;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class BYDAutoController extends Controller
{
use ValidatesRequests;
public function testdrive(Request $request)
{
$rules['name'] = ['required'];
$rules['mobile'] = ['required'];
$this->validate($request, $rules);
$requestId = Str::uuid();
$request->merge(['request_id' => $requestId]);
(new TestdriveCreateAction())->execute($request);
return response()->json(['code' => 0, 'message' => '提交成功', 'request_id' => $requestId]);
}
public function vehicle(Request $request)
{
$items = Vehicle::cachedItems();
return response()->json($items);
}
public function province(Request $request)
{
$vehicle = Vehicle::query()->firstWhere('name', $request->vehicle);
$dealers = $vehicle->dealers;
$province = $dealers->groupBy('province')->keys();
$data = $province->map(fn($item) => ['name' => $item]);
return response()->json($data);
}
public function city(Request $request)
{
$vehicle = Vehicle::query()->firstWhere('name', $request->vehicle);
if (!$vehicle) {
throw ValidationException::withMessages(['vehicle' => '车型不存在']);
}
$dealers = $vehicle->dealers;
$city = $dealers->where('province', $request->province)->groupBy('city')->keys();
$data = $city->map(fn($item) => ['name' => $item]);
return response()->json($data);
}
public function dealer(Request $request)
{
$vehicle = Vehicle::query()->firstWhere('name', $request->vehicle);
if (!$vehicle) {
throw ValidationException::withMessages(['vehicle' => '车型不存在']);
}
$dealer = Dealer::query()->where('province', $request->province)->where('city', $request->city)->get();
$data = $dealer->map(fn($item) => ['name' => $item->name, 'address' => $item->address, 'erp' => $item->erp]);
return response()->json($data);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace BYDAuto\Foundation;
class AlipayAES
{
/**
* AES加密.
*
* @param $plainText String 明文
* @param $key String 对称密钥
* @return string
* @throws \Exception
*/
public function encrypt($plainText, $key)
{
try {
// AES, 128 模式加密数据 CBC
$screct_key = base64_decode($key);
$str = trim($plainText);
$str = $this->addPKCS7Padding($str);
$iv = str_repeat("\0", 16);
$encrypt_str = openssl_encrypt($str, 'AES-128-CBC', $screct_key, OPENSSL_NO_PADDING, $iv);
return base64_encode($encrypt_str);
} catch (\Exception $e) {
throw new \Exception("AES加密失败plainText=" . $plainText . "keySize=" . strlen($key) . "" . $e->getMessage());
}
}
/**
* AES解密.
*
* @param $cipherText String 密文
* @param $key String 对称密钥
* @return false|string
* @throws \Exception
*/
public function decrypt($cipherText, $key)
{
try {
// AES, 128 模式加密数据 CBC
$str = base64_decode($cipherText);
$screct_key = base64_decode($key);
$iv = str_repeat("\0", 16);
$decrypt_str = openssl_decrypt($str, 'AES-128-CBC', $screct_key, OPENSSL_NO_PADDING, $iv);
return $this->stripPKSC7Padding($decrypt_str);
} catch (\Exception $e) {
throw new \Exception("AES解密失败cipherText=" . $cipherText . "keySize=" . strlen($key) . "" . $e->getMessage());
}
}
/**
* 填充算法.
* @param string $source
* @return string
*/
private function addPKCS7Padding($source)
{
$source = trim($source);
$block = 16;
$pad = $block - (strlen($source) % $block);
if ($pad <= $block) {
$char = chr($pad);
$source .= str_repeat($char, $pad);
}
return $source;
}
/**
* 移去填充算法.
* @param string $source
* @return string
*/
private function stripPKSC7Padding($source)
{
$char = substr($source, -1);
$num = ord($char);
if ($num == 62) {
return $source;
}
return substr($source, 0, -$num);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace BYDAuto\Foundation;
use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory;
class AlipayEasySDKFactory
{
public function __construct()
{
Factory::setOptions($this->getOptions());
}
public function execute($method, $textParams, $bizParams)
{
return Factory::util()->generic()->execute($method, $textParams, $bizParams);
}
protected function getOptions()
{
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = 'RSA2';
$options->appId = config('chery-crm.alipay.app_id');
$merchantPrivateKey = file_get_contents(storage_path(config('chery-crm.alipay.merchant_private_key')));
$options->merchantPrivateKey = $merchantPrivateKey;
$alipayPublicKey = file_get_contents(storage_path(config('chery-crm.alipay.alipay_public_key')));
$options->alipayPublicKey = $alipayPublicKey;
return $options;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace BYDAuto\Foundation;
use Illuminate\Support\Facades\DB;
class DBLog
{
public static function add($message, $content = [], $channel = '')
{
$content = json_encode($content, JSON_UNESCAPED_UNICODE);
DB::table('bydauto_logs')
->insert(['message' => $message, 'content' => $content, 'channel' => $channel, 'created_at' => now(), 'updated_at' => now()]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace BYDAuto\Foundation;
abstract class DataTransfer implements \ArrayAccess, \JsonSerializable
{
protected $attributes;
public function __construct($attributes)
{
$this->attributes = $attributes;
}
public function toArray()
{
return $this->attributes;
}
public function offsetExists($offset): bool
{
return isset($this->attributes[$offset]);
}
public function offsetGet($offset): mixed
{
return $this->attributes[$offset] ?? null;
}
public function offsetSet($offset, $value): void
{
$this->attributes[$offset] = $value;
}
public function offsetUnset($offset): void
{
unset($this->attributes[$offset]);
}
public function toJson($options = 0): string
{
return json_encode($this->jsonSerialize(), $options);
}
public function jsonSerialize(): mixed
{
return $this->toArray();
}
public function get($key, $default = null)
{
return \Arr::get($this->attributes, $key, $default);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace BYDAuto\Foundation;
class Encryption
{
public function __construct(protected $pubkey, protected $privkey)
{
// ...
}
public function encrypt($data)
{
if (openssl_public_encrypt($data, $encrypted, $this->pubkey)) {
$data = base64_encode($encrypted);
} else {
throw new \Exception('Unable to encrypt data. Perhaps it is bigger than the key size?');
}
return $data;
}
public function decrypt($data)
{
if (openssl_private_decrypt(base64_decode($data), $decrypted, $this->privkey)) {
$data = $decrypted;
} else {
$data = '';
}
return $data;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace BYDAuto\Jobs;
use BYDAuto\Models\Contact;
use BYDAuto\Models\Testdrive;
use BYDAuto\Notifications\TestdirveNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\SerializesModels;
class TestdrivePushJob implements ShouldQueue
{
use Queueable;
use SerializesModels;
use Dispatchable;
public function __construct(protected Testdrive $model) {}
public function handle(): void
{
try {
$this->sendNotification();
$this->model->update(['status' => Testdrive::STATUS_PUBLISHED]);
} catch (\Exception $e) {
info('===== TestdrivePushJob::handle exception', ['message' => $e->getMessage()]);
}
}
public function sendNotification()
{
$message = new TestdirveNotification($this->model);
$contacts = Contact::query()->where('status', Contact::STATUS_PUBLISHED)->get();
$contacts->each(fn(Contact $contact) => $contact->notify($message));
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace BYDAuto\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Contact extends Model
{
use Notifiable;
const STATUS_PUBLISHED = 'published';
const STATUS_DRAFT = 'draft';
public const CREATED_AT = 'date_created';
public const UPDATED_AT = 'date_updated';
protected $table = 'bydauto_contact';
protected $fillable = ['remark', 'email', 'status'];
}

View File

@@ -0,0 +1,54 @@
<?php
namespace BYDAuto\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* 经销商门店 class.
*
* @property int $id
* @property string $sn
* @property string $region
* @property string $province
* @property string $city
* @property string $county
* @property string $district
* @property string $type
* @property string $erp
* @property string $name
* @property string $alias
* @property string $group
* @property string $address
*/
class Dealer extends Model
{
public $timestamps = false;
protected $table = 'bydauto_dealers';
protected $fillable = ['sn', 'region', 'province', 'city', 'county', 'district', 'type', 'erp', 'name', 'alias', 'group', 'address', 'lat', 'lng'];
public function vehicles()
{
return $this->belongsToMany(Vehicle::class, 'bydauto_dealer_vehicles', 'dealer_id', 'vehicle_id');
}
public static function cachedItems()
{
return Cache::remember('BYDAuto:dealers', 120, fn() => static::query()->get());
}
public static function findByAlias($alias)
{
$items = self::cachedItems();
return $items->first(fn($d) => $d->alias === $alias);
}
public static function findByName($name)
{
$items = self::cachedItems();
return $items->first(fn($d) => $d->name === $name);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace BYDAuto\Models;
use Carbon\Carbon;
use BYDAuto\Observers\TestdriveObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
/**
* 销售线索.
*
* @property int $id
* @property Carbon $date_created
* @property Carbon $date_updated
*/
#[ObservedBy([TestdriveObserver::class])]
class Testdrive extends Model
{
public const STATUS_UNKNOWN = 'unknown';
public const STATUS_DRAFT = 'draft';
public const STATUS_PUBLISHED = 'published';
public const STATUS_ARCHIVED = 'archived';
public const CREATED_AT = 'date_created';
public const UPDATED_AT = 'date_updated';
protected $table = 'bydauto_testdrive';
protected $fillable = ['status', 'name', 'mobile', 'platform', 'source', 'meta', 'rawdata', 'request_id'];
protected $casts = ['meta' => 'json', 'rawdata' => 'json'];
}

View File

@@ -0,0 +1,44 @@
<?php
namespace BYDAuto\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* Vehicle class.
*
* @property int $id
* @property string $name
* @property array $alias
* @property string $spec_id
*/
class Vehicle extends Model
{
public $timestamps = false;
protected $table = 'bydauto_vehicles';
protected $fillable = ['name', 'alias', 'spec_id'];
protected $casts = ['alias' => 'array'];
public function dealers()
{
return $this->belongsToMany(Dealer::class, 'bydauto_dealer_vehicles', 'vehicle_id', 'dealer_id');
}
public static function cachedItems()
{
return Cache::remember('BYDAuto:vechicle', 3600, function () {
return Vehicle::query()->get()->map(fn($d) => ['id' => $d->id, 'name' => $d->name, 'alias' => $d->alias, 'spec_id' => $d->spec_id]);
});
}
public static function specId($name)
{
$items = self::cachedItems();
$item = $items->first(fn($d) => in_array($name, $d['alias']) || $d['name'] === $name);
return $item ? $item['spec_id'] : null;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace BYDAuto\Notifications;
use BYDAuto\Models\Testdrive;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TestdirveNotification extends Notification
{
use Queueable;
public function __construct(protected Testdrive $model)
{
//
}
public function via(object $notifiable): array
{
return ['mail'];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject("预约试驾线索通知")
->line("收到了一条新的试驾预约信息,请及时处理")
->line("姓名:" . $this->model->name . " \n电话:" . $this->model->mobile)
->line("=====")
->line("祝,工作愉快!");
}
public function toArray(object $notifiable): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace BYDAuto\Observers;
use BYDAuto\Jobs\TestdrivePushJob;
use BYDAuto\Models\Testdrive;
class TestdriveObserver
{
public function creating(Testdrive $model)
{
if ($model->status === null) {
$model->status = Testdrive::STATUS_DRAFT;
}
}
public function created(Testdrive $model)
{
if ($model->status === Testdrive::STATUS_DRAFT) {
TestdrivePushJob::dispatch($model);
} else {
info('===== TestdriveObserver::created not a draft', ['id' => $model->id, 'status' => $model->status]);
}
}
}

View File

@@ -0,0 +1,16 @@
<div>
<h3>{{ $date }}</h3>
@if ($items->isEmpty())
<div>无数据</div>
@else
<ul>
@foreach ($items as $item)
<li>
{{ $sourceMap[$item->source] ?? $item->source }}
<strong style="color: red">{{ $item->total }}</strong>
</li>
@endforeach
</ul>
<p>总数:{{ $count }}</p>
@endif
</div>

View File

@@ -0,0 +1,8 @@
<div>
@foreach ($sourceGroup as $source => $items)
<p>渠道: {{ $source }}</p>
@foreach ($items as $item)
<div>{{ $item->day }} {{ $item->total }}</div>
@endforeach
@endforeach
</div>