feat: 项目基础框架+配置+Kernel

This commit is contained in:
testadmin
2026-04-29 05:35:00 +08:00
parent 6ce7adcbfb
commit 9a6151fce3
5082 changed files with 437062 additions and 0 deletions
+474
View File
@@ -0,0 +1,474 @@
<?php
namespace App\DAO;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Carbon;
use GuzzleHttp\Client;
use App\AccountLog;
use App\Currency;
use App\ChainHash;
use App\LbxHash;
use App\UsersWallet;
use App\Jobs\UpdateBalance;
class BlockChain
{
public static function getChainBalance($wallet, $chain_currency = '')
{
try {
throw_unless($wallet, new \Exception('钱包不存在'));
throw_if(empty($wallet->address), new \Exception('钱包地址不存在'));
$currency = Currency::find($wallet->currency);
throw_unless($currency, new \Exception('币种不存在'));
$address = $wallet->address;
$method = 'GET';
$chain_currency == '' && $chain_currency = $currency->type;
switch ($chain_currency) {
case 'eth':
$uri = '/wallet/eth/balance';
$params = [
'query' => [
'address' => $address,
]
];
break;
case 'erc20':
$uri = '/wallet/eth/tokenbalance';
$params = [
'query' => [
'address' => $address,
'tokenaddress' => $currency->contract_address,
]
];
break;
case 'btc':
$uri = '/wallet/btc/balance';
$params = [
'query' => [
'address' => $address,
]
];
break;
case 'usdt':
$uri = '/wallet/usdt/balance';
$params = [
'query' => [
'address' => $address,
]
];
break;
default:
throw new \Exception('不支持的数字货币');
break;
}
$http_client = app('LbxChainServer');
$response = $http_client->request($method, $uri, $params);
$result = $response->getBody()->getContents();
$result = json_decode($result, true);
//echo $uri;
//var_dump($params);
//var_dump($result);
if (!isset($result['code']) || !isset($result['data'])) {
throw new \Exception('请求接口发生错误');
}
if ($result['code'] != 0) {
throw new \Exception($result['msg'] ?? $result['errorinfo']);
}
$balance_data = $result['data'];
$chain_balance = $balance_data['balance'];
$lessen = bc_pow(10, $currency->decimal_scale);
$fact_chain_balance = bc_div($chain_balance, $lessen);
return $fact_chain_balance;
} catch (\Throwable $th) {
throw $th;
}
}
public static function updateWalletBalance($wallet, $no_balance_continue = false)
{
try {
$fact_chain_balance = self::getChainBalance($wallet);
try {
DB::beginTransaction();
$wallet->refresh();
//比较现链上余额是否比原链上余额要大
$compare_result = bc_comp($fact_chain_balance, $wallet->old_balance);
if ($compare_result > 0) {
$diff_balance = bc_sub($fact_chain_balance, $wallet->old_balance);
$wallet->old_balance = $fact_chain_balance; //更新链上余额
$save_result = $wallet->save();
if (!$save_result) {
throw new \Exception('更新链上余额失败');
}
$change_result = change_wallet_balance($wallet, 4, $diff_balance, AccountLog::ETH_EXCHANGE, '链上充币增加');
if ($change_result !== true) {
throw new \Exception($change_result);
}
} elseif ($compare_result == 0) {
// throw new \Exception(
// '用户id:' . $wallet->user_id . ',币种:' . $wallet->currencyCoin->name
// . '(' . $wallet->currencyCoin->type . '):链上余额无增加'
// );
if ($no_balance_continue) {
UpdateBalance::dispatch($wallet, false)
->onQueue('update:block:balance')
->delay(Carbon::now()->addMinutes(5));
}
} else {
if ($no_balance_continue) {
UpdateBalance::dispatch($wallet, false)
->onQueue('update:block:balance')
->delay(Carbon::now()->addMinutes(5));
}
throw new \Exception(
'用户id:' . $wallet->user_id . ',币种:' . $wallet->currencyCoin->name
. '(' . $wallet->currencyCoin->type . '):链上余额小于系统链上余额'
);
}
DB::commit();
} catch (\Exception $ex) {
DB::rollBack();
throw $ex;
}
return true;
} catch (\Throwable $th) {
throw $th;
}
}
/**
* 链上转账
*
* @param string $currency_name 钱包本身对应的币种名称
* @param string $chain_currency 要转的链上币种类型,例如用usdt的钱包地址不仅可以转USDT还可以转BTC,用erc20的钱包地址不仅可以转ERC20代币还可以转ETH
* @param string $to_address 转入地址
* @param float $transfer_qty 转账数量
* @param string $from_address 转出地址
* @param string $from_private_key 转出私钥
* @param integer $type 转账类型 1 归拢,2 打入手续费,3 提币
* @param float $fee 链上手续费
* @param string $verificationcode 验证码
* @return string
* @throws \Exception
*/
public static function transfer($currency_name, $chain_currency, $to_address, $transfer_qty, $from_address, $from_private_key, $type, $fee = 0, $verificationcode = '')
{
try {
$currency = Currency::where('name', $currency_name)->first();
$chain_currency = Currency::where('name', $chain_currency)->first();
if (!$currency) {
throw new \Exception('货币不存在');
}
if (!in_array($currency->type, ['eth', 'erc20', 'usdt', 'btc'])) {
throw new \Exception('货币类型不支持');
}
if (
empty($to_address)
|| empty($transfer_qty)
|| bc_comp($transfer_qty, 0) <= 0
|| empty($from_address)
|| empty($from_private_key)
) {
throw new \Exception('参数不完整或不合法');
}
$origin_transfer_qty = $transfer_qty;
$decimal_scale = $chain_currency->decimal_scale ?? 0; //调整为按链上通道的小数位数,解决代币和主链小数位数不一致的问题
$lessen = bc_pow(10, $decimal_scale);
$transfer_qty = bc_mul($transfer_qty, $lessen, 0); //转账数量转换为区块链上的单位
$fee = bc_mul($currency->chain_fee, $lessen, 0); //手续费转换为区域链上的单位
$http_client = app('LbxChainServer');
$method = 'POST';
$result = [];
$params = [
'multipart' => [
[
'name' => 'type',
'contents' => $type,
],
[
'name' => 'fromaddress',
'contents' => $from_address,
],
[
'name' => 'privkey',
'contents' => $from_private_key,
],
[
'name' => 'toaddress',
'contents' => $to_address,
],
[
'name' => 'amount',
'contents' => $transfer_qty,
],
[
'name' => 'tokenaddress',
'contents' => $currency->contract_address ?? '',
],
[
'name' => 'fee',
'contents' => $fee,
],
[
'name' => 'verificationcode',
'contents' => $verificationcode,
],
]
];
switch ($chain_currency->type) {
case 'erc20':
$uri = '/v3/wallet/eth/tokensendto';
break;
case 'eth':
$uri = '/v3/wallet/eth/sendto';
break;
case 'btc':
$uri = '/v3/wallet/btc/sendto';
break;
case 'usdt':
$uri = '/v3/wallet/usdt/sendto';
break;
default:
throw new \Exception('暂不支持' . $chain_currency->type . '币种');
break;
}
$response = $http_client->request($method, $uri, $params);
$result = json_decode($response->getBody()->getContents(), true);
isset($result['txid']) || $result['txid'] = $result['data']['txHex'] ?? ($result['data']['txid'] ?? '');
if (isset($result['code']) && $result['code'] == 0) {
$chain_hash = [
'code' => strtoupper($currency->type),
'txid' => $result['txid'],
'amount' => $origin_transfer_qty,
'sender' => $from_address,
'recipient' => $to_address,
];
ChainHash::unguarded(function () use ($chain_hash) {
return ChainHash::create($chain_hash);
});
} else {
throw new \Exception($result['msg'] ?? var_export($result, true));
}
//dump($params);
return $result;
} catch (\Exception $e) {
throw $e;
}
}
/**
* 打入手续费
*
* @param UsersWallet $wallet
* @param boolean $refresh_balance
* @return void
*/
public static function transferPoundage(UsersWallet $wallet, $refresh_balance = false)
{
try {
//检测当前是否已有手续费打入的交易hash
//是否先刷新链上余额
if ($refresh_balance) {
$wallet->refresh();
self::updateWalletBalance($wallet);
}
$wallet->refresh();
if (bc_comp($wallet->old_balance, 0) <= 0) {
throw new \Exception('用户链上余额为0,无须打入手续费');
}
$fee_currency = $currency = $wallet->currencyCoin;
$currency_type = $currency->type;
$fee_name = '';
if ($currency_type == 'eth' || $currency_type == 'btc' || $currency_type == 'eos' || $currency_type == 'xrp') {
throw new \Exception($wallet->currencyCoin->name . '币种无需额外打入归拢手续费');
} elseif ($currency_type == 'erc20') {
//从总账号往钱包打入eth
$transfer_qty = $fee_currency->chain_fee;
$from_address = $fee_currency->total_account;
$from_private_key = $fee_currency->origin_key;
$fee_name = 'eth';
} elseif ($currency_type == 'usdt') {
//从总账号往钱包打入btc
$transfer_qty = bc_add($fee_currency->chain_fee, '0.00000546');
$from_address = $fee_currency->total_account;
$from_private_key = $fee_currency->origin_key;
$fee_name = 'btc';
} else {
throw new \Exception('不支持的数字货币');
}
if (empty($from_address) || empty($from_private_key)) {
throw new \Exception($fee_name . '币种总账号信息未设置');
}
$fee_balance = self::getChainBalance($wallet, $fee_name);
//当链上手续费余额大于需要转入的手续费时,提示无须再打入手续费
if (bc_comp($fee_balance, $transfer_qty) >= 0) {
throw new \Exception('钱包内' . $fee_name . '余额充足,无须打入');
} else {
//当有余额时看相差多少,只打入相差的部分
$transfer_qty = bc_sub($transfer_qty, bc_comp($fee_balance, 0) >= 0 ? $fee_balance : 0);
}
$params = [
'currency_type' => $currency_type,
'fee_name' => $fee_name,
'to_address' => $wallet->address,
'transfer_qty' => $transfer_qty,
'from_address' => $from_address,
'from_private_key' => $from_private_key,
'type' => 2,
];
$query_str = md5(http_build_query($params));
if (Cache::has($query_str)) {
throw new \Exception('当前链上已有手续费交易正在确认,请勿重复打入手续费!交易哈希:' . Cache::get($query_str));
}
// 从当日哈希表中检测是否已有未确认的打入手续费的交易
$fee_transaction = LbxHash::where('wallet_id', $wallet->id)
->where('created_at', '>=', Carbon::today())
->where('type', 2)
->where('status', 0)
->first();
if ($fee_transaction) {
throw new \Exception('当前链上已有手续费交易正在确认,请勿重复打入手续费!交易哈希:' . $fee_transaction->txid);
}
DB::beginTransaction();
$result = self::transfer($currency_type, $fee_name, $wallet->address, $transfer_qty, $from_address, $from_private_key, 2);
if ($result['code'] == 0) {
Cache::put($query_str, $result['txid'], 20);
//记录链上哈希信息
$lbx_hash_data = [
'wallet_id' => $wallet->id,
'txid' => $result['txid'],
'type' => 2, //打入手续费
'amount' => $transfer_qty,
'status' => 0,
];
LbxHash::unguarded(function () use ($lbx_hash_data) {
return LbxHash::create($lbx_hash_data);
});
}
DB::commit();
return $result;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* 钱包链上余额归拢到总账号
*
* @param \App\UsersWallet $wallet 要归拢的钱包
* @param bool $refresh_balance 是否从链上刷新余额
* @return string
* @throws \Exception
*/
public static function collect(UsersWallet $wallet, $refresh_balance = false)
{
$currency = $wallet->currencyCoin;
if (!$currency) {
throw new \Exception('对应币种不存在');
}
$from_address = $wallet->address;
$from_private_key = $wallet->private;
//$to_address = $currency->total_account;
$to_address = $currency->collect_account;
$contract_address = $currency->contract_address;
$currency_type = $currency->type;
if (empty($to_address)) {
throw new \Exception('归拢地址未设置');
}
if ($currency_type == 'erc20' && empty($contract_address)) {
throw new \Exception('合约地址未设置');
}
// 根据币种手续费计算
$base_transfer_use_qty = 0; //除手续费消耗主链的数量
switch ($currency_type) {
case 'eth':
$fee_currency_name = 'eth';
$transfer_fee = $currency->chain_fee ?? 0.001;
break;
case 'btc':
$fee_currency_name = 'btc';
$transfer_fee = $currency->chain_fee ?? 0.00006;
break;
case 'erc20':
$fee_currency_name = 'eth';
$transfer_fee = $currency->chain_fee ?? 0.001;
break;
case 'usdt':
$fee_currency_name = 'btc';
$base_transfer_use_qty = 0.00000546;
$transfer_fee = $currency->chain_fee ?? 0.00006;
break;
default:
$fee_currency_name = '';
$transfer_fee = 0;
}
// 查询上次归拢是否完成
$lbx_hash = LbxHash::where('status', 0)
->where('type', 0)
->where('wallet_id', $wallet->id)
->first();
if ($lbx_hash) {
throw new \Exception('当前有归拢操作未完成');
}
// 是否先刷新链上余额
if ($refresh_balance) {
self::updateWalletBalance($wallet);
}
$wallet->refresh();
if ($currency_type == 'erc20' || $currency_type == 'usdt') {
//检测手续费是否充足:erc20扣eth, usdt扣btc
$fee_balance = self::getChainBalance($wallet, $fee_currency_name);
$base_total_use_qty = bc_add($base_transfer_use_qty, $transfer_fee); //手续费+链上交易额外消耗,例如USDT要额外消耗0.00000546BTC
if (bc_comp($fee_balance, $base_total_use_qty) < 0) {
throw new \Exception('钱包内手续费可用余额(' . $fee_balance . ')不足,不能归拢');
}
$transfer_qty = $wallet->old_balance; //代币有多少归多少
} else {
$transfer_qty = bc_sub($wallet->old_balance, $transfer_fee); //主链归拢减去手续费
}
//如果链上余额为空或者只有手续费(ETH、BTC)就没必要做归拢
if (bc_comp($transfer_qty, 0) <= 0) {
throw new \Exception('余额为空或手续费不足,不能归拢');
}
try {
DB::beginTransaction();
$result = self::transfer($currency->name, $currency->type, $to_address, $transfer_qty, $from_address, $from_private_key, 1);
if (!isset($result['code']) || $result['code'] != 0) {
throw new \Exception(var_export($result, true));
}
//记录链上哈希信息
$lbx_hash_data = [
'wallet_id' => $wallet->id,
'txid' => $result['txid'],
'type' => 0,
'amount' => $transfer_qty,
'status' => 0,
];
LbxHash::unguarded(function () use ($lbx_hash_data) {
return LbxHash::create($lbx_hash_data);
});
$wallet->refresh();
$wallet->txid = $result['txid'];
$wallet->gl_time = time();
$wallet->save();
DB::commit();
return $result;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\DAO;
use DB;
use App\Utils\RPC;
use App\Brand;
use Request;
class BrandDAO
{
public static function lists($num = 0) {
if($num == 0) {
$brand = Brand::get();
} else {
$brand = Brand::paginate($num);
}
return $brand;
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace App\DAO;
use App\Activity;
use App\ActivityItem;
use App\Utils\RPC;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Config;
/**
* Created by PhpStorm.
* User: joey
* Date: 2018/1/31
* Time: 15:54
*/
class ExpressADO
{
/**
* 获取物流详情
* @param string $url 请求Url
* @param array $datas 提交的数据
* @return url响应返回的html
*/
public static function getContetn($ShipperCode, $LogisticCode)
{
$kdniao = Config::get("express.kdniao");
if (empty($ShipperCode) || empty($LogisticCode))
return "";
$requestData= "{'OrderCode':'','ShipperCode':'".$ShipperCode."','LogisticCode':'".$LogisticCode."'}";
$datas = array(
'EBusinessID' => $kdniao["EBusinessID"],
'RequestType' => '1002',
'RequestData' => urlencode($requestData) ,
'DataType' => '2',
);
$datas['DataSign'] = ExpressADO::encrypt($requestData, $kdniao["AppKey"]);
$result = ExpressADO::sendPost($kdniao["ReqURL"], $datas);
$result = json_decode($result,true);
return $result["Traces"];
}
/**
* post提交数据
* @param string $url 请求Url
* @param array $datas 提交的数据
* @return url响应返回的html
*/
public static function sendPost($url, $datas)
{
$temps = array();
foreach ($datas as $key => $value) {
$temps[] = sprintf('%s=%s', $key, $value);
}
$post_data = implode('&', $temps);
$url_info = parse_url($url);
if(empty($url_info['port']))
{
$url_info['port']=80;
}
$httpheader = "POST " . $url_info['path'] . " HTTP/1.0\r\n";
$httpheader.= "Host:" . $url_info['host'] . "\r\n";
$httpheader.= "Content-Type:application/x-www-form-urlencoded\r\n";
$httpheader.= "Content-Length:" . strlen($post_data) . "\r\n";
$httpheader.= "Connection:close\r\n\r\n";
$httpheader.= $post_data;
$fd = fsockopen($url_info['host'], $url_info['port']);
fwrite($fd, $httpheader);
$gets = "";
$headerFlag = true;
while (!feof($fd)) {
if (($header = @fgets($fd)) && ($header == "\r\n" || $header == "\n")) {
break;
}
}
while (!feof($fd)) {
$gets.= fread($fd, 128);
}
fclose($fd);
return $gets;
}
/**
* 电商Sign签名生成
* @param data 内容
* @param appkey Appkey
* @return DataSign签名
*/
public static function encrypt($data, $appkey) {
return urlencode(base64_encode(md5($data.$appkey)));
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\DAO;
use App\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Users;
use App\Level;
use App\UserUpgradeLog;
use App\PrizePool;
use App\LeverTransaction;
use App\UsersWallet;
USE App\UsersWalletcopy;
use App\Setting;
use App\AccountLog;
class FactprofitsDAO
{
/**
* 会员历史总盈亏释放
* @param project $user_id 要释放用户的id
*/
public static function Profit_loss_release($user_id)
{
$profit_loss_release=Setting::getValueByKey('profit_loss_release','')/1000;
$sum=LeverTransaction::where("user_id","=",$user_id)->where("status","=",3)->sum("fact_profits");
// var_dump($profit_loss_release);
// var_dump($sum);die;
if($sum<0)
{
$aaaa=UsersWalletcopy::leftjoin("currency","currency.id","=","users_wallet.currency")->where("currency.name","=","USDC")->where("users_wallet.user_id","=",$user_id)->select("users_wallet.id","users_wallet.lever_balance","users_wallet.user_id","currency.id as currency_id")->first();
$user_walllet=UsersWalletcopy::where("user_id","=",$aaaa->user_id)->where("currency","=",$aaaa->currency_id)->first();
$number=-bc_mul($sum,$profit_loss_release,8);
$user_walllet->lever_balance=$user_walllet->lever_balance+$number;
$user_walllet->save();
try {
//增加杠杆币日志记录
$result = change_wallet_balance(
$user_walllet,
3,
+$number,
AccountLog::PROFIT_LOSS_RELEASE,
'历史盈亏释放,增加杠杆币'.$number,
false,
$user_id,
0
);
if ($result !== true) {
throw new \Exception('历史盈亏释放,增加杠杆币:' . $result);
}
DB::commit();
return true;
} catch (\Exception $ex) {
DB::rollBack();
return false;
}
}
}
}
+185
View File
@@ -0,0 +1,185 @@
<?PHP
namespace App\DAO\Lib;
class mail
{
protected $base_url = 'http://api.mysubmail.com/';
protected $mail_configs;
protected $signType = 'normal';
function __construct($mail_config)
{
$this->mail_configs = $mail_config;
if (!empty($mail_config['server'])) {
$this->base_url = $mail_config['server'];
}
}
protected function createSignature($request)
{
$r = "";
switch ($this->signType) {
case 'normal':
$r = $this->mail_configs['appkey'];
break;
case 'md5':
$r = $this->buildSignature($this->argSort($request));
break;
case 'sha1':
$r = $this->buildSignature($this->argSort($request));
break;
}
return $r;
}
protected function buildSignature($request)
{
$arg = "";
$app = $this->mail_configs['appid'];
$appkey = $this->mail_configs['appkey'];
while (list ($key, $val) = each($request)) {
if (strpos($key, "attachments") === false) {
$arg .= $key . "=" . $val . "&";
}
}
$arg = substr($arg, 0, count($arg) - 2);
if (get_magic_quotes_gpc()) {
$arg = stripslashes($arg);
}
if ($this->signType == 'sha1') {
$r = sha1($app . $appkey . $arg . $app . $appkey);
} else {
$r = md5($app . $appkey . $arg . $app . $appkey);
}
return $r;
}
protected function argSort($request)
{
ksort($request);
reset($request);
return $request;
}
public function getTimestamp()
{
$api = $this->base_url . 'service/timestamp.json';
$ch = curl_init($api);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
$output = curl_exec($ch);
$timestamp = json_decode($output, true);
return $timestamp['timestamp'];
}
protected function APIHttpRequestCURL($api, $post_data, $method = 'post')
{
if ($method != 'get') {
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $api,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query($post_data),
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded")
));
} else {
$url = $api . '?' . http_build_query($post_data);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
}
$output = curl_exec($ch);
curl_close($ch);
$output = trim($output, "\xEF\xBB\xBF");
return json_decode($output, true);
}
public function send($request)
{
$api = $this->base_url . 'mail/send.json';
$request['appid'] = $this->mail_configs['appid'];
$request['timestamp'] = $this->getTimestamp();
if (empty($this->mail_configs['sign_type'])
&& $this->mail_configs['sign_type'] == ""
&& $this->mail_configs['sign_type'] != "normal"
&& $this->mail_configs['sign_type'] != "md5"
&& $this->mail_configs['sign_type'] != "sha1"
) {
$this->signType = 'normal';
} else {
$this->signType = $this->mail_configs['sign_type'];
$request['sign_type'] = $this->mail_configs['sign_type'];
}
$request['signature'] = $this->createSignature($request);
$send = $this->APIHttpRequestCURL($api, $request);
return $send;
}
public function xsend($request)
{
$api = $this->base_url . 'mail/xsend.json';
$request['appid'] = $this->mail_configs['appid'];
$request['timestamp'] = $this->getTimestamp();
if (empty($this->mail_configs['sign_type'])
&& $this->mail_configs['sign_type'] == ""
&& $this->mail_configs['sign_type'] != "normal"
&& $this->mail_configs['sign_type'] != "md5"
&& $this->mail_configs['sign_type'] != "sha1"
) {
$this->signType = 'normal';
} else {
$this->signType = $this->mail_configs['sign_type'];
$request['sign_type'] = $this->mail_configs['sign_type'];
}
$request['signature'] = $this->createSignature($request);
$xsend = $this->APIHttpRequestCURL($api, $request);
return $xsend;
}
public function subscribe($request)
{
$api = $this->base_url . 'addressbook/mail/subscribe.json';
$request['appid'] = $this->mail_configs['appid'];
$request['timestamp'] = $this->getTimestamp();
if (empty($this->mail_configs['sign_type'])
&& $this->mail_configs['sign_type'] == ""
&& $this->mail_configs['sign_type'] != "normal"
&& $this->mail_configs['sign_type'] != "md5"
&& $this->mail_configs['sign_type'] != "sha1"
) {
$this->signType = 'normal';
} else {
$this->signType = $this->mail_configs['sign_type'];
$request['sign_type'] = $this->mail_configs['sign_type'];
}
$request['signature'] = $this->createSignature($request);
$subscribe = $this->APIHttpRequestCURL($api, $request);
return $subscribe;
}
public function unsubscribe($request)
{
$api = $this->base_url . 'addressbook/mail/unsubscribe.json';
$request['appid'] = $this->mail_configs['appid'];
$request['timestamp'] = $this->getTimestamp();
if (empty($this->mail_configs['sign_type'])
&& $this->mail_configs['sign_type'] == ""
&& $this->mail_configs['sign_type'] != "normal"
&& $this->mail_configs['sign_type'] != "md5"
&& $this->mail_configs['sign_type'] != "sha1"
) {
$this->signType = 'normal';
} else {
$this->signType = $this->mail_configs['sign_type'];
$request['sign_type'] = $this->mail_configs['sign_type'];
}
$request['signature'] = $this->createSignature($request);
$unsubscribe = $this->APIHttpRequestCURL($api, $request);
return $unsubscribe;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Kewail\Sms\Demo;
require_once "SmsSingleSender.php";
use Kewail\Sms\SmsSingleSender;
try {
// 请根据实际 accesskey 和 secretkey 进行开发,以下只作为演示 sdk 使用
$accesskey = "";
$secretkey = "";
$phoneNumber = "";
$singleSender = new SmsSingleSender($accesskey, $secretkey);
// 普通单发
$result = $singleSender->send(0, "86", $phoneNumber , "【Kewail科技】您注册的验证码:128128有效时间30分钟。", "", "");
$rsp = json_decode($result);
echo $result;
echo "<br>";
} catch (\Exception $e) {
echo var_dump($e);
}
+74
View File
@@ -0,0 +1,74 @@
<?php
// Works well with php5.3 and php5.6.
namespace App\DAO\Moducloud;
class SmsSenderUtil {
function getRandom() {
return rand(100000, 999999);
}
function calculateSig($secretkey, $random, $curTime, $phoneNumbers) {
$phoneNumbersString = $phoneNumbers[0];
for ($i = 1; $i < count($phoneNumbers); $i++) {
$phoneNumbersString .= ("," . $phoneNumbers[$i]);
}
return hash("sha256", "secretkey=".$secretkey."&random=".$random
."&time=".$curTime."&mobile=".$phoneNumbersString);
}
function calculateSigForTemplAndPhoneNumbers($secretkey, $random, $curTime, $phoneNumbers) {
$phoneNumbersString = $phoneNumbers[0];
for ($i = 1; $i < count($phoneNumbers); $i++) {
$phoneNumbersString .= ("," . $phoneNumbers[$i]);
}
return hash("sha256", "secretkey=".$secretkey."&random=".$random
."&time=".$curTime."&mobile=".$phoneNumbersString);
}
function phoneNumbersToArray($nationCode, $phoneNumbers) {
$i = 0;
$tel = array();
do {
$telElement = new \stdClass();
$telElement->nationcode = $nationCode;
$telElement->mobile = $phoneNumbers[$i];
array_push($tel, $telElement);
} while (++$i < count($phoneNumbers));
return $tel;
}
function calculateSigForTempl($secretkey, $random, $curTime, $phoneNumber) {
$phoneNumbers = array($phoneNumber);
return $this->calculateSigForTemplAndPhoneNumbers($secretkey, $random, $curTime, $phoneNumbers);
}
function sendCurlPost($url, $dataObj) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($dataObj));
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Content-Length: ' . strlen(json_encode($dataObj))));
$ret = curl_exec($curl);
if (false == $ret) {
// curl_exec failed
$result = "{ \"result\":" . -2 . ",\"errmsg\":\"" . curl_error($curl) . "\"}";
} else {
$rsp = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (200 != $rsp) {
$result = "{ \"result\":" . -1 . ",\"errmsg\":\"". $rsp . " " . curl_error($curl) ."\"}";
} else {
$result = $ret;
}
}
curl_close($curl);
return $result;
}
}
?>
+302
View File
@@ -0,0 +1,302 @@
<?php
// Works well with php5.3 and php5.6.
namespace App\DAO\Moducloud;
require_once('SmsSenderUtil.php');
class SmsSingleSender {
var $url;
var $accesskey;
var $secretkey;
var $util;
function __construct($accesskey, $secretkey) {
$this->url = "https://live.kewail.com/sms/v1/sendsinglesms";
$this->accesskey = $accesskey;
$this->secretkey = $secretkey;
$this->util = new SmsSenderUtil();
}
/**
* 普通单发,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
* @param int $type 短信类型,0 为普通短信,1 营销短信
* @param string $nationCode 国家码,如 86 为中国
* @param string $phoneNumber 不带国家码的手机号
* @param string $msg 信息内容,必须与申请的模板格式一致,否则将返回错误
* @param string $extend 扩展码,可填空串
* @param string $ext 服务端原样返回的参数,可填空串
* @return string json string { "result": xxxxx, "errmsg": "xxxxxx" ... },被省略的内容参见协议文档
*/
function send($type, $nationCode, $phoneNumber, $msg, $extend = "", $ext = "") {
/*
请求包体
{
"tel": {
"nationcode": "86",
"mobile": "13788888888"
},
"type": 0,
"msg": "你的验证码是1234",
"sig": "fdba654e05bc0d15796713a1a1a2318c",
"time": 1479888540,
"extend": "",
"ext": ""
}
应答包体
{
"result": 0,
"errmsg": "OK",
"ext": "",
"sid": "xxxxxxx",
"fee": 1
}
*/
$random = $this->util->getRandom();
$curTime = time();
$wholeUrl = $this->url . "?accesskey=" . $this->accesskey . "&random=" . $random;
// 按照协议组织 post 包体
$data = new \stdClass();
$tel = new \stdClass();
$tel->nationcode = "".$nationCode;
$tel->mobile = "".$phoneNumber;
$data->tel = $tel;
$data->type = (int)$type;
$data->msg = $msg;
$data->sig = hash("sha256",
"secretkey=".$this->secretkey."&random=".$random."&time=".$curTime."&mobile=".$phoneNumber, FALSE);
$data->time = $curTime;
$data->extend = $extend;
$data->ext = $ext;
return $this->util->sendCurlPost($wholeUrl, $data);
}
/**
* 指定模板单发
* @param string $nationCode 国家码,如 86 为中国
* @param string $phoneNumber 不带国家码的手机号
* @param int $templId 模板 id
* @param array $params 模板参数列表,如模板 {1}...{2}...{3},那么需要带三个参数
* @param string $sign 签名,如果填空串,系统会使用默认签名
* @param string $extend 扩展码,可填空串
* @param string $ext 服务端原样返回的参数,可填空串
* @return string json string { "result": xxxxx, "errmsg": "xxxxxx" ... },被省略的内容参见协议文档
*/
function sendWithParam($nationCode, $phoneNumber, $templId = 0, $params, $sign = "", $extend = "", $ext = "") {
/*
请求包体
{
"tel": {
"nationcode": "86",
"mobile": "13788888888"
},
"sign": "Kewail",
"tpl_id": 19,
"params": [
"验证码",
"1234",
"4"
],
"sig": "fdba654e05bc0d15796713a1a1a2318c",
"time": 1479888540,
"extend": "",
"ext": ""
}
应答包体
{
"result": 0,
"errmsg": "OK",
"ext": "",
"sid": "xxxxxxx",
"fee": 1
}
*/
$random = $this->util->getRandom();
$curTime = time();
$wholeUrl = $this->url . "?sdkaccesskey=" . $this->accesskey . "&random=" . $random;
// 按照协议组织 post 包体
$data = new \stdClass();
$tel = new \stdClass();
$tel->nationcode = "".$nationCode;
$tel->mobile = "".$phoneNumber;
$data->tel = $tel;
$data->sig = $this->util->calculateSigForTempl($this->secretkey, $random, $curTime, $phoneNumber);
$data->tpl_id = $templId;
$data->params = $params;
$data->sign = $sign;
$data->time = $curTime;
$data->extend = $extend;
$data->ext = $ext;
return $this->util->sendCurlPost($wholeUrl, $data);
}
}
class SmsMultiSender {
var $url;
var $accesskey;
var $secretkey;
var $util;
function __construct($accesskey, $secretkey) {
$this->url = "https://live.kewail.com/sms/v1/sendsinglesms";
$this->accesskey = $accesskey;
$this->secretkey = $secretkey;
$this->util = new SmsSenderUtil();
}
/**
* 普通群发,明确指定内容,如果有多个签名,请在内容中以【】的方式添加到信息内容中,否则系统将使用默认签名
* 【注意】海外短信无群发功能
* @param int $type 短信类型,0 为普通短信,1 营销短信
* @param string $nationCode 国家码,如 86 为中国
* @param string $phoneNumbers 不带国家码的手机号列表
* @param string $msg 信息内容,必须与申请的模板格式一致,否则将返回错误
* @param string $extend 扩展码,可填空串
* @param string $ext 服务端原样返回的参数,可填空串
* @return string json string { "result": xxxxx, "errmsg": "xxxxxx" ... },被省略的内容参见协议文档
*/
function send($type, $nationCode, $phoneNumbers, $msg, $extend = "", $ext = "") {
/*
请求包体
{
"tel": [
{
"nationcode": "86",
"mobile": "13788888888"
},
{
"nationcode": "86",
"mobile": "13788888889"
}
],
"type": 0,
"msg": "你的验证码是1234",
"sig": "fdba654e05bc0d15796713a1a1a2318c",
"time": 1479888540,
"extend": "",
"ext": ""
}
应答包体
{
"result": 0,
"errmsg": "OK",
"ext": "",
"detail": [
{
"result": 0,
"errmsg": "OK",
"mobile": "13788888888",
"nationcode": "86",
"sid": "xxxxxxx",
"fee": 1
},
{
"result": 0,
"errmsg": "OK",
"mobile": "13788888889",
"nationcode": "86",
"sid": "xxxxxxx",
"fee": 1
}
]
}
*/
$random = $this->util->getRandom();
$curTime = time();
$wholeUrl = $this->url . "?accesskey=" . $this->accesskey . "&random=" . $random;
$data = new \stdClass();
$data->tel = $this->util->phoneNumbersToArray($nationCode, $phoneNumbers);
$data->type = $type;
$data->msg = $msg;
$data->sig = $this->util->calculateSig($this->secretkey, $random, $curTime, $phoneNumbers);
$data->time = $curTime;
$data->extend = $extend;
$data->ext = $ext;
return $this->util->sendCurlPost($wholeUrl, $data);
}
/**
* 指定模板群发
* 【注意】海外短信无群发功能
* @param string $nationCode 国家码,如 86 为中国
* @param array $phoneNumbers 不带国家码的手机号列表
* @param int $templId 模板 id
* @param array $params 模板参数列表,如模板 {1}...{2}...{3},那么需要带三个参数
* @param string $sign 签名,如果填空串,系统会使用默认签名
* @param string $extend 扩展码,可填空串
* @param string $ext 服务端原样返回的参数,可填空串
* @return string json string { "result": xxxxx, "errmsg": "xxxxxx" ... },被省略的内容参见协议文档
*/
function sendWithParam($nationCode, $phoneNumbers, $templId, $params, $sign = "", $extend ="", $ext = "") {
/*
请求包体
{
"tel": [
{
"nationcode": "86",
"mobile": "13788888888"
},
{
"nationcode": "86",
"mobile": "13788888889"
}
],
"sign": "Kewail",
"tpl_id": 19,
"params": [
"验证码",
"1234",
"4"
],
"sig": "fdba654e05bc0d15796713a1a1a2318c",
"time": 1479888540,
"extend": "",
"ext": ""
}
应答包体
{
"result": 0,
"errmsg": "OK",
"ext": "",
"detail": [
{
"result": 0,
"errmsg": "OK",
"mobile": "13788888888",
"nationcode": "86",
"sid": "xxxxxxx",
"fee": 1
},
{
"result": 0,
"errmsg": "OK",
"mobile": "13788888889",
"nationcode": "86",
"sid": "xxxxxxx",
"fee": 1
}
]
}
*/
$random = $this->util->getRandom();
$curTime = time();
$wholeUrl = $this->url . "?accesskey=" . $this->accesskey . "&random=" . $random;
$data = new \stdClass();
$data->tel = $this->util->phoneNumbersToArray($nationCode, $phoneNumbers);
$data->sign = $sign;
$data->tpl_id = $templId;
$data->params = $params;
$data->sig = $this->util->calculateSigForTemplAndPhoneNumbers(
$this->secretkey, $random, $curTime, $phoneNumbers);
$data->time = $curTime;
$data->extend = $extend;
$data->ext = $ext;
return $this->util->sendCurlPost($wholeUrl, $data);
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\DAO\PrizePool;
use App\PrizePool as PrizePoolModel;
use App\Setting;
class CandyCalculator implements PrizeCalculator
{
protected $reward_type = PrizePoolModel::REWARD_CANDY;
protected $reward_currency = PrizePoolModel::CURRENCY_NONE;
protected $currency_type = PrizePoolModel::CURRENCY_NONE;
public function __construct()
{
}
public function calculate($scene, $reward_qty, $to_user, $from_user = null, $memo = '', $attach_data = [])
{
$fasten_data = [
'reward_type' => $this->reward_type,
'reward_currency' => $this->reward_currency,
'create_time' => time(), //发奖时间
];
$origin_reward_qty = $reward_qty; //原始奖励数量
$candy_tousdt = Setting::getValueByKey('candy_tousdt', 100);
$candy_tousdt = bc_div($candy_tousdt, 100);
$reward_qty = bc_div($reward_qty, $candy_tousdt, 4);
$extra_data = $attach_data['extra_data'];
if (is_string($extra_data) && !empty($extra_data)) {
$extra_data = unserialize($extra_data);
}
$extra_data['origin_reward_qty '] = $origin_reward_qty;
$extra_data['candy_tousdt'] = $candy_tousdt;
$attach_data['extra_data'] = serialize($extra_data);
try {
PrizePoolModel::unguard();
$data = [
'scene' => $scene,
'reward_qty' => $reward_qty,
'to_user_id' => $to_user->id,
'from_user_id' => $from_user ? $from_user->id : $to_user->id,
'memo' => $memo,
];
$data = array_merge($data, $attach_data, $fasten_data);
$prize_pool = PrizePoolModel::create($data);
} catch (\Exception $e) {
return null;
} finally {
PrizePoolModel::reguard();
}
return isset($prize_pool->id) ? $prize_pool : null;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\DAO\PrizePool;
use Illuminate\Support\Facades\DB;
use App\PrizePool;
use App\{Users, AccountLog};
class CandySender implements PrizeSender
{
public function send(\App\PrizePool &$prize) : bool
{
try {
$prize->refresh();
if ($prize->status != 0) {
throw new \Exception('奖励发放异常');
}
if ($prize->reward_type != PrizePool::REWARD_CANDY) {
throw new \Exception('奖励发放类型不匹配');
}
DB::transaction(function () use (&$prize) {
$user = Users::lockForUpdate()->find($prize->to_user_id);
$change_result = change_user_candy($user, $prize->reward_qty, AccountLog::REWARD_CANDY, $prize->memo);
if ($change_result !== true) {
throw new \Exception($change_result);
}
$prize->receive_time = time();
$prize->status = 1;
$result = $prize->save();
if (!$result) {
throw new \Exception('奖励通证发放失败');
}
});
return true;
} catch (\Exception $e) {
$error_info = serialize([
'time' => time(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'message' => $e->getMessage(),
]);
$prize->error_info = $error_info;
$prize->save();
return false;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\DAO\PrizePool;
use App\PrizePool as PrizePoolModel;
class CurrencyCalculator implements PrizeCalculator
{
protected $reward_type = PrizePoolModel::REWARD_CURRENCY;
protected $reward_currency;
protected $currency_type;
public function __construct($reward_currency, $currency_type)
{
$this->reward_currency = $reward_currency;
$this->currency_type = $currency_type;
}
public function calculate($scene, $reward_qty, $to_user, $from_user = null, $memo = '', $attach_data = [])
{
$fasten_data = [
'reward_type' => $this->reward_type,
'reward_currency' => $this->reward_currency,
'create_time' => time(), //发奖时间
];
try {
PrizePoolModel::unguard();
$data = [
'scene' => $scene,
'reward_qty' => $reward_qty,
'to_user_id' => $to_user->id,
'from_user_id' => $from_user ? $from_user->id : $to_user->id,
'memo' => $memo,
];
$data = array_merge($data, $attach_data, $fasten_data);
$prize_pool = PrizePoolModel::create($data);
} catch (\Exception $e) {
return null;
} finally {
PrizePoolModel::reguard();
}
return isset($prize_pool->id) ? $prize_pool : null;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\DAO\PrizePool;
use Illuminate\Support\Facades\DB;
use App\{Users, AccountLog, UsersWallet, PrizePool};
class CurrencySender implements PrizeSender
{
public function send(\App\PrizePool &$prize) : bool
{
try {
$prize->refresh();
if ($prize->status != 0) {
throw new \Exception('奖励发放异常');
}
if ($prize->reward_type != PrizePool::REWARD_CURRENCY) {
throw new \Exception('奖励发放类型不匹配');
}
if (!in_array($prize->currency_type, [1, 2, 3])) {
throw new \Exception('币种类型不正确');
}
DB::transaction(function () use (&$prize) {
$user_wallet = UsersWallet::where('user_id', $prize->to_user_id)
->where('currency', $prize->reward_currency)
->lockForUpdate()
->first();
if (!$user_wallet) {
throw new \Exception('用户钱包不存在');
}
$change_result = change_wallet_balance($user_wallet, $prize->currency_type, $prize->reward_qty, AccountLog::REWARD_CURRENCY, $prize->memo, false, $prize->from_user_id, $prize->sign, $extra_data);
if ($change_result !== true) {
throw new \Exception($change_result);
}
$prize->receive_time = time();
$prize->status = 1;
$result = $prize->save();
if (!$result) {
throw new \Exception('奖励通证发放失败');
}
});
return true;
} catch (\Exception $e) {
$error_info = serialize([
'time' => time(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'message' => $e->getMessage(),
]);
$prize->error_info = $error_info;
$prize->save();
return false;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\DAO\PrizePool;
interface PrizeCalculator
{
/**
* 计算奖励
*
* @param integer $scene 奖励场景
* @param float $reward_qty 奖励数量
* @param \App\Users $to_user 被奖励者
* @param \App\Users $from_user 触发用户
* @param string $memo 备注
* @param array $attach_data 附加数据
* @return \App\PrizePool 返回奖池记录
*/
public function calculate($scene, $reward_qty, $to_user, $from_user = null, $memo = '', $attach_data = []);
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\DAO\PrizePool;
interface PrizeSender
{
/**
* 发放奖励
*
* @param \App\PrizePool $prize
* @return boolean
*/
public function send(\App\PrizePool &$prize) : bool;
}
+557
View File
@@ -0,0 +1,557 @@
<?php
namespace App\DAO;
use Illuminate\Support\Facades\{DB, Log};
use App\{AccountLog, LeverTransaction, PrizePool, Setting, Users};
use App\DAO\PrizePool\{CandySender, CandyCalculator};
class RewardDAO
{
/**
* 向用户直属工作室发放奖励
*
* @return void
*/
public static function rewardLeverFeeToAtelier($lever_trade)
{
$today = strtotime(date('Y-m-d'));
$candy_tousdt = Setting::getValueByKey('candy_tousdt', 100);
$candy_tousdt = bc_div($candy_tousdt, 100);
$atelier_reward_day_must_trade = Setting::getValueByKey('atelier_reward_day_must_trade', 0); //工作室每日最低交易量
$atelier_reward_day_limit = Setting::getValueByKey('atelier_reward_day_limit', 0); //工作室每日奖励上限
$atelier_reward_ratio = Setting::getValueByKey('atelier_reward_ratio', 0); //工作室奖励比例百分比
$atelier_reward_ratio = bc_div($atelier_reward_ratio, 100, 4); //工作室奖励比例转化小数
$can_reward_qty = 0;
$user_id = $lever_trade->user_id;
$trade_id = $lever_trade->trade_id;
$trade_fee = $lever_trade->trade_fee;
$origin_reward_qty = bc_mul($trade_fee, $atelier_reward_ratio, 4); //奖励数量
$convert_reward_qty = bc_div($origin_reward_qty, $candy_tousdt, 4); //换算成与usdt等值糖果
$fact_convert_reward_qty = $convert_reward_qty;
$user = Users::find($user_id);
$parents_path = UserDAO::getParentsPathDesc($user);
$ateliers = UserDAO::getParentsAtelier($user);
if (count($ateliers) <= 0) {
return;
}
$atelier = $ateliers->first();
if (!$atelier) {
return;
}
$key = array_search($atelier->id, $parents_path);
$current_level = $key + 1;
//每日已开仓交易量
$today_has_trades = LeverTransaction::where('user_id', $atelier->user_id)
->where('status', LeverTransaction::TRANSACTION)
->where('create_time', '>=', $today)
->count();
if ($today_has_trades < $atelier_reward_day_must_trade) {
return;
}
//每日已奖励数量
$rewarded_qty = PrizePool::where('scene', PrizePool::LEVER_TRADE_FEE_ATELIER)
->where('sign', 0)
->where('status', 1)
->where('create_time', '>=', $today)
->sum('reward_qty');
//奖励是否已达上限
if (bc_comp($atelier_reward_day_limit, 0) > 0) {
if (bc_comp($rewarded_qty, $atelier_reward_day_limit) >= 0) {
return;
}
//计算还有多少才达到封顶
$can_reward_qty = bc_sub($atelier_reward_day_limit, $rewarded_qty);
//如果即将奖励的值超过封顶,就抹去多余的奖励,以保证奖励不会超过封顶
bc_comp($convert_reward_qty, $can_reward_qty) > 0 && $fact_convert_reward_qty = $can_reward_qty;
}
$fact_reward_qty = bc_mul($fact_convert_reward_qty, $candy_tousdt, 4); //未折合usdt的糖果数量
$prize_calculator = new CandyCalculator();
$prize_sender = new CandySender();
$attach_data = [
'sign' => $current_level,
'extra_data' => serialize([
'trade_id' => $trade_id, //交易id
'level' => $current_level, //用户是第几级
'trade_fee' => $trade_fee, //交易手续费
'atelier_reward_day_limit' => $atelier_reward_day_limit, //工作室日奖励上限
'atelier_reward_ratio' => $atelier_reward_ratio, //奖励比例
'rewarded_qty' => $rewarded_qty, //已奖励数量
'can_reward_qty' => $can_reward_qty, //还能拿的奖励数量
'convert_reward_qty' => $convert_reward_qty,
'fact_convert_reward_qty' => $fact_convert_reward_qty,
'fact_reward_qty' => $fact_reward_qty,
]),
];
try {
//插入奖励记录到奖池
$prize_pool = PrizePool::calculate(
$prize_calculator,
PrizePool::LEVER_TRADE_FEE_ATELIER,
$fact_reward_qty,
$atelier,
$user,
'工作室' . $current_level . '级用户杠杆交易手续费结算',
$attach_data
);
if (!$prize_pool) {
throw new \Exception('交易id:' . $trade_id . ',向第' . $current_level . '级上级(id:' . $atelier->id . ')触发奖励失败');
}
//发放奖励
$receive_result = PrizePool::send($prize_sender, $prize_pool);
if (!$receive_result) {
throw new \Exception('交易id:' . $trade_id . ',向第' . $current_level . '级上级(id:' . $atelier->id . ')发放奖励失败');
}
} catch (\Exception $e) {
$path = base_path() . '/storage/logs/reward/lever_trade/atelier/';
$filename = date('Ymd') . '.log';
file_exists($path) || @mkdir($path);
error_log(
date('Y-m-d H:i:s') . PHP_EOL . $e->getMessage() . PHP_EOL,
3,
$path . $filename
);
}
}
/**
* 奖励杠杆交易手续费
* @param App\LeverTransaction $lever_trade 杠杆交易
* @return void
*/
public static function rewardLeverTransationFee($lever_trade)
{
$lever_fee_options = Setting::getValueByKey('lever_fee_options');
$lever_fee_options = empty($lever_fee_options) ? [] : unserialize($lever_fee_options);
//如果没有取到参数直接返回
if (count($lever_fee_options) <= 0) {
return ;
}
$generations = array_column($lever_fee_options, 'generation');
$reward_ratio = array_column($lever_fee_options, 'reward_ratio');
$need_has_trades_list = array_column($lever_fee_options, 'need_has_trades');
array_multisort($generations, SORT_ASC, SORT_NUMERIC, $lever_fee_options);
$max_generation = max($generations);
$from_user_id = $lever_trade->user_id;
$trade_fee = $lever_trade->trade_fee;
$trade_id = $lever_trade->id;
$from_user = Users::find($from_user_id);
$parents = UserDAO::getParentsPathDesc($from_user, $max_generation);
$prize_calculator = new CandyCalculator();
$prize_sender = new CandySender();
foreach ($parents as $key => $value) {
try {
$current_level = $key + 1; //当前用户是受奖励用户的第几级
$has_trade_num = 0; //当前用户交易笔数
$need_has_trades = 0; //当前用户需要交易的笔数
if (!in_array($current_level, $generations)) {
continue;
}
$v_key = array_search($current_level, $generations); //查询键值
$need_has_trades = $need_has_trades_list[$v_key];
$current_rebate_ratio = $reward_ratio[$v_key];
$current_user = Users::find($value);
if (!$current_user) {
continue;
}
$candy_number = bc_div(bc_mul($trade_fee, $current_rebate_ratio), 100, 4);
$attach_data = [
'sign' => $current_level,
'extra_data' => serialize([
'trade_id' => $trade_id, //交易id
'level' => $current_level, //用户是第几级
'trade_fee' => $trade_fee, //交易手续费
'current_rebate_ratio' => $current_rebate_ratio, //奖励比例
]),
];
//检测当前用户有没有自行体验X笔
$has_trade_num = LeverTransaction::where('user_id', $value)
->whereIn('status', [1, 2, 3])
->count();
if ($has_trade_num < $need_has_trades) {
continue;
}
//插入奖励记录到奖池
$prize_pool = PrizePool::calculate(
$prize_calculator,
PrizePool::LEVER_TRADE_FEE,
$candy_number,
$current_user,
$from_user,
$current_level . '级用户杠杆交易手续费结算',
$attach_data
);
if (!$prize_pool) {
throw new \Exception('交易id:' . $trade_id . ',向第' . $current_level . '级上级(id:' . $current_user->id . ')触发奖励失败');
}
//发放奖励
$receive_result = PrizePool::send($prize_sender, $prize_pool);
if (!$receive_result) {
throw new \Exception('交易id:' . $trade_id . ',向第' . $current_level . '级上级(id:' . $current_user->id . ')发放奖励失败');
}
} catch (\Exception $e) {
$path = base_path() . '/storage/logs/reward/lever_trade/';
$filename = date('Ymd') . '.log';
file_exists($path) || @mkdir($path);
error_log(
date('Y-m-d H:i:s') . PHP_EOL . $e->getMessage() . PHP_EOL,
3,
$path . $filename
);
}
}
}
/**
* 取转账次数对应的积分奖励比例
*
* @param integer $transfer_out_times
* @return float 返回奖励比例(百分比,需要自己除以100)
*/
public static function getRatioByTurnsOutTimes($transfer_out_times)
{
$times_ratio = Setting::getValueByKey('transfer_out_ratio');
empty($times_ratio) || $times_ratio = unserialize($times_ratio);
$fact_ratio = self::getDataByRangeValue($times_ratio, $transfer_out_times);
return $fact_ratio;
}
/**
* 取范围内对应的值
*
* @param array $ratio_array 比例数组
* @param float $compare_value 要比较的值
* @return float
*/
public static function getDataByRangeValue($ratio_array, $compare_value)
{
$fact_data = reset($ratio_array); //先给个默认值
krsort($ratio_array);
foreach ($ratio_array as $key => $value) {
if ($compare_value >= $key) {
$fact_data = $value;
break;
}
}
return $fact_data;
}
/**
* 静态奖
*
* @param App\Users $user 用户模型实例
* @return bool 成功返回真,失败返回假
*/
public static function staticReward($user)
{
$today = strtotime(date('Y-m-d'));
$static_day_release_ratio = Setting::getValueByKey('static_day_release_ratio');
//先通过时间戳判断用户是否领取过
if ($user->static_time > $today) {
return false;
}
//再通过记录判断用户是否领取过
$count = AccountLog::where('type', AccountLog::DAY_STATIC_RELEASE)
->where('created_time', '>=', $today)
->where('user_id', $user->id)
->count();
$count || $count = 0;
if ($count > 0) {
return false;
}
try {
DB::transaction(function () use ($user, $static_day_release_ratio) {
$release_balance = round($user->integral * $static_day_release_ratio / 100, 5);
$result = release_user_integral($user, $release_balance, AccountLog::DAY_STATIC_RELEASE, '每日静态释放奖励');
if (!$result) {
throw new \Exception('释放积分失败');
}
$user->static_time = time();
$user->save(); //更新用户的静态奖领取时间
});
return true;
} catch(\Exception $e) {
return false;
}
}
/**
* 余额转出级差奖
*
* @param app\Users $user 用户模型
* @param float $transfer_out_qty 转出数量
* @return bool
*/
public static function levelDifferenceReward($user, $transfer_out_qty)
{
$parents = UserDAO::getParentsPathDesc($user);
$times_ratio = Setting::getValueByKey('transfer_out_ratio');
$times_ratio = empty($times_ratio) ? [] : unserialize($times_ratio);
krsort($times_ratio);
$max_ratio = reset($times_ratio);
$current_max = self::getRatioByTurnsOutTimes($user->transfer_out_times); //取转账人的比例
rsort($parents);
$param = compact('times_ratio', 'max_ratio', 'current_max');
try {
DB::beginTransaction();
foreach ($parents as $key => $value) {
$parent = Users::find($value);
if (!$parent) {
abort(400, '用户id:' . $user->id . '的上级(id:' . $value . ')不存在');
return false;
}
$transfer_out_times = $parent->transfer_out_times; //转出次数
$current_ratio = self::getRatioByTurnsOutTimes($transfer_out_times); //根据转出次数算出比例
if ($current_max >= $current_ratio) {
continue;
}
$fact_ratio = $current_ratio - $current_max; //比例差
$param = compact('current_max', 'current_ratio', 'transfer_out_times', 'fact_ratio');
$fact_reward_qty = $transfer_out_qty * $fact_ratio / 100; //应奖励的积分
//向当前用户的钱包返比例差的积分
$result = change_user_money($parent, 2, $fact_reward_qty, AccountLog::TRANSFER_OUT_LEVEL_DIFFERENCE_REWARD_INTEGRAL,'转出级差奖励积分');
if ($result !== true) {
throw new \Exception('释放积分到余额失败:' . $result);
}
$current_max = $current_ratio; //改变指针
if ($current_max == $max_ratio) {
break;
}
}
DB::commit();
return true;
} catch (\Exception $e) {
echo '<pre>';
echo '错误:' . $e->getMessage() . PHP_EOL . ',文件:' . $e->getFile() . PHP_EOL . '行号:'. $e->getLine();
DB::rollBack();
return false;
}
}
/**
* 余额兑换积分奖励(加速释放)
*
* @param app\common\model\User $user 用户模型
* @param float $exchange_qty 兑换数量
*/
public static function exchangeReward($user, $exchange_qty)
{
$path = UserDAO::getParentsPathDesc($user, 20);
//array_shift($path); //删除直推上级
if (count($path) < 1) {
return false;
}
DB::beginTransaction();
foreach ($path as $key => $value) {
$fact_ratio = 0;
$current_generations = $key + 1; //因为key从0开始所以加1;
$parent = Users::find($value);
//取当前被触发用户的级别
$parent_level = $parent->level->code;
if ($parent_level <= 1) {
//普通会员不享受动态奖
continue;
}
//根据代数来决定比例
$can_generations = $parent->level->generations['exchange'];
if ($can_generations < $current_generations) {
//当前被触发人级别不能拿超过自己级别对应的代数
continue;
}
if ($parent_level == 2 && $current_generations == 1) {
//直推奖:根据直推人数来决定比例
$children_qty = Users::where('parent_id', $value)->count();
$children_qty || $children_qty = 0;
$exchange_recommend_ratio = Setting::getValueByKey('exchange_recommend_ratio');
$exchange_recommend_ratio = empty($exchange_recommend_ratio) ? [] : unserialize($exchange_recommend_ratio);
$fact_ratio = self::getDataByRangeValue($exchange_recommend_ratio, $children_qty);
} else {
$ratio = Setting::getValueByKey('exchange_generations_ratio'); //取每一代对应的比例
$ratio = empty($ratio) ? [] : unserialize($ratio);
$fact_ratio = self::getDataByRangeValue($ratio, $current_generations);
}
$fact_reward_qty = round($exchange_qty * $fact_ratio / 100, 5);
//奖励不能超过积分
$before_integral = $parent->integral;
$before_integral < $fact_reward_qty && $fact_reward_qty = $parent->integral;
if ($fact_reward_qty == 0) {
continue;
}
try {
$result = release_user_integral($parent, $fact_reward_qty, AccountLog::BALANCE_EXCHANGE_INTEGRAL_REWARD, '余额兑换积分奖励:');
if (!$result) {
throw new \Exception('释放积分到余额失败');
}
} catch (\Exception $e) {
echo '<pre>';
echo '错误:' . $e->getMessage() . PHP_EOL . ',文件:' . $e->getFile() . PHP_EOL . '行号:'. $e->getLine();
DB::rollback();
return false;
}
}
DB::commit();
return true;
}
/**
* 余额增加奖励(转入加速释放)
*
* @param app\common\model\User $user 用户模型
* @param float $add_qty 兑换数量
* @return bool 成功返回真,否则假
*/
public static function balanceAddReward($user, $add_qty)
{
$path = UserDAO::getParentsPathDesc($user, 20);
if (count($path) < 1) {
return false;
}
DB::beginTransaction();
foreach ($path as $key => $value) {
$fact_ratio = 0;
$current_generations = $key + 1; //因为key从0开始所以加1;
$parent = Users::find($value);
//取当前被触发用户的级别
$parent_level = $parent->level->code;
if ($parent_level <= 1) {
//普通会员不享受动态奖
continue;
}
$can_generations = $parent->level->generations['balance_add'];
if ($can_generations < $current_generations) {
//当前被触发人级别不能拿超过自己级别对应的代数
continue;
}
if ($parent_level == 2 && $current_generations == 1) {
//直推奖:根据直推人数来决定比例
$children_qty = Users::where('parent_id', $value)->count();
$children_qty || $children_qty = 0;
$balanceadd_recommend_ratio = Setting::getValueByKey('balanceadd_recommend_ratio');
$balanceadd_recommend_ratio = empty($balanceadd_recommend_ratio) ? [] : unserialize($balanceadd_recommend_ratio);
$fact_ratio = self::getDataByRangeValue($balanceadd_recommend_ratio, $children_qty);
} else {
//根据代数来决定比例
$ratio = Setting::getValueByKey('balanceadd_generations_ratio'); //取每一代对应的比例
$ratio = empty($ratio) ? [] : unserialize($ratio);
$fact_ratio = self::getDataByRangeValue($ratio, $current_generations);
}
$fact_reward_qty = round($add_qty * $fact_ratio / 100, 5);
//奖励不能超过积分
$before_integral = $parent->integral;
$before_integral < $fact_reward_qty && $fact_reward_qty = $parent->integral;
if ($fact_reward_qty == 0) {
continue;
}
try {
$result = release_user_integral($parent, $fact_reward_qty, AccountLog::TRANSFER_IN_PARENTS_REWARD_INTEGRAL, '余额转入奖励:');
if (!$result) {
throw new \Exception('释放积分到余额失败');
}
} catch (\Exception $e) {
echo '<pre>';
echo '错误:' . $e->getMessage() . PHP_EOL . ',文件:' . $e->getFile() . PHP_EOL . '行号:'. $e->getLine();
DB::rollback();
return false;
}
}
DB::commit();
return true;
}
/**
* 余额减少奖励(转出加速释放)
*
* @param app\common\model\User $user 用户模型
* @param float $sub_qty 兑换数量
* @return bool 成功返回真,否则假
*/
public static function balanceSubReward($user, $sub_qty)
{
$parents = UserDAO::getParentsPathDesc($user, 15);//查询用户的指定代数的上级(根据parents_path信息),$qty 要取的上级代数,不传或传null则取全部
if (count($parents) < 1) {
return false;
}
$fact_ratio = Setting::getValueByKey('balance_sub_ratio');
DB::beginTransaction();
foreach ($parents as $key => $value) {
$current_generations = $key + 1; //因为key从0开始所以加1;
$parent = Users::find($value);
//取当前被触发用户的级别
$parent_level = $parent->level->code;
$can_generations = $parent->level->generations['balance_sub'];
if ($parent_level <= 1 || $can_generations < $current_generations) {
//普通会员不享受动态奖
continue;
}
$fact_reward_qty = round($sub_qty * $fact_ratio / 100, 5);
if ($fact_reward_qty == 0) {
continue;
}
try {
$result = change_user_money($parent, 2, $fact_reward_qty, AccountLog::TRANSFER_OUT_PARENTS_REWARD_INTEGRAL, '余额减少上级奖励-增加积分');
if ($result !== true) {
throw new \Exception('奖励积分失败(余额转出上级奖励)'. $result);
}
} catch (\Exception $e) {
DB::rollback();
return false;
}
}
DB::commit();
return true;
}
/**
* 平级余额减少奖励积分(暂且只有转出)
*
* @param app\common\model\User $user 转出用户模型
* @param float $qty 数量
*/
public static function equalLevelReward($user, $qty)
{
$parents = UserDAO::getParentsPathDesc($user);
if (count($parents) < 1) {
return false;
}
$userlevel = $user->level->code;
$fact_ratio = Setting::getValueByKey('equal_level_ratio');
DB::beginTransaction();
foreach ($parents as $key => $value) {
$current_generations = $key + 1;
//取当前被触发用户的级别
$parent = Users::find($value);
$parent_level = $parent->level->code;
$can_generations = $parent->level->generations['equal_level'];
if ($parent_level <= 1 || $can_generations < $current_generations || $userlevel != $parent_level) {
//当前被触发人级别不能拿超过自己级别对应的代数,或者不是平级
continue;
}
$fact_reward_qty = round($qty * $fact_ratio / 100, 4);
try {
$result = change_user_money($parent, 2, $fact_reward_qty, AccountLog::TRANSFER_OUT_EQUALLEVEL_REWARD_INTEGRAL, '余额减少平级奖励-增加积分');
if ($result !== true) {
throw new \Exception('奖励积分失败(余额转出平级奖励):' . $result);
}
} catch (\Exception $e) {
DB::rollback();
return false;
}
}
DB::commit();
return true;
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace App\DAO;
use App\DAO\Lib\mail;
class SubmailMailSend
{
protected $configs;
protected $To = array();
protected $Addressbook = array();
protected $From = '';
protected $From_name = '';
protected $Reply = '';
protected $Cc = array();
protected $Bcc = array();
protected $Subject = '';
protected $Text = '';
protected $Html = '';
protected $Vars = array();
protected $Links = array();
protected $Attachments = array();
protected $Headers = array();
protected $asynchronous = "false";
function __construct($configs)
{
$this->configs = $configs;
}
public function AddTo($address, $name = '')
{
array_push($this->To, array('address' => $address, 'name' => $name));
}
public function AddAddressbook($addressbook)
{
array_push($this->Addressbook, $addressbook);
}
public function SetSender($sender, $name = '')
{
$this->From = $sender;
$this->From_name = $name;
}
public function SetReply($reply)
{
$this->Reply = $reply;
}
public function AddCc($address, $name = '')
{
array_push($this->Cc, array('address' => $address, 'name' => $name));
}
public function AddBcc($address, $name = '')
{
array_push($this->Bcc, array('address' => $address, 'name' => $name));
}
public function SetSubject($subject)
{
$this->Subject = $subject;
}
public function SetText($text)
{
$this->Text = $text;
}
public function SetHtml($html)
{
$this->Html = $html;
}
public function AddVar($key, $val)
{
$this->Vars[$key] = $val;
}
public function AddLink($key, $val)
{
$this->Links[$key] = $val;
}
public function AddAttachment($attachment)
{
array_push($this->Attachments, $attachment);
}
public function AddHeaders($key, $val)
{
$this->Headers[$key] = $val;
}
public function setAsynchronous($asynchronous)
{
if ($asynchronous == true) {
$this->asynchronous = true;
} else {
$this->asynchronous = false;
}
}
protected function buildRequest()
{
$request = array();
if (!empty($this->To)) {
$request['to'] = '';
foreach ($this->To as $tmp) {
$request['to'] .= $tmp['name'] . '<' . $tmp['address'] . '>,';
}
$request['to'] = substr($request['to'], 0, strlen($request['to']) - 1);
//dd($request['to']);
}
if (!empty($this->Addressbook)) {
$request['addressbook'] = '';
foreach ($this->Addressbook as $tmp) {
$request['addressbook'] .= $tmp . ',';
}
$request['addressbook'] = substr($request['addressbook'], 0, strlen($request['addressbook']) - 1);
}
$request['from'] = $this->From;
if ($this->From_name != '') {
$request['from_name'] = $this->From_name;
}
if ($this->Reply != '') {
$request['reply'] = $this->Reply;
}
if (!empty($this->Cc)) {
$request['cc'] = '';
foreach ($this->Cc as $tmp) {
$request['cc'] .= $tmp['name'] . '<' . $tmp['address'] . '>,';
}
$request['cc'] = substr($request['cc'], 0, strlen($request['cc']) - 1);
}
if (!empty($this->Bcc)) {
$request['bcc'] = '';
foreach ($this->Bcc as $tmp) {
$request['bcc'] .= $tmp['name'] . '<' . $tmp['address'] . '>,';
}
$request['bcc'] = substr($request['bcc'], 0, strlen($request['bcc']) - 1);
}
$request['subject'] = $this->Subject;
if ($this->Text != '') {
$request['text'] = $this->Text;
}
if ($this->Html != '') {
$request['html'] = $this->Html;
}
if (!empty($this->Vars)) {
$request['vars'] = json_encode($this->Vars);
}
if (!empty($this->Links)) {
$request['links'] = json_encode($this->Links);
}
if (!empty($this->Attachments)) {
for ($i = 0; $i < count($this->Attachments); $i++) {
//$request['attachments['.$i.']']="@".$this->Attachments[$i];
$request['attachments[' . $i . ']'] = curl_file_create($this->Attachments[$i]);
}
}
if (!empty($this->asynchronous)) {
$request['asynchronous'] = $this->asynchronous;
}
if (!empty($this->Headers)) {
$request['headers'] = json_encode($this->Headers);
}
return $request;
}
public function send()
{
$mail = new mail($this->configs);
return $mail->send($this->buildRequest());
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
namespace App\DAO;
use Qiniu\Storage\UploadManager;
use Qiniu\Auth;
use App\Setting;
class UploaderDAO
{
private static $stateMap = [ //上传状态映射表,国际化用户需考虑此处数据的国际化
"SUCCESS", //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
"文件大小超出 upload_max_filesize 限制",
"文件大小超出 MAX_FILE_SIZE 限制",
"文件未被完整上传",
"没有文件被上传",
"上传文件为空",
"ERROR_TMP_FILE" => "临时文件错误",
"ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
"ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
"ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
"ERROR_CREATE_DIR" => "目录创建失败",
"ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
"ERROR_FILE_MOVE" => "文件保存时出错",
"ERROR_FILE_NOT_FOUND" => "找不到上传文件",
"ERROR_WRITE_CONTENT" => "写入文件内容错误",
"ERROR_UNKNOWN" => "未知错误",
"ERROR_DEAD_LINK" => "链接不可用",
"ERROR_HTTP_LINK" => "链接不是http链接",
"ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确",
"INVALID_URL" => "非法 URL",
"INVALID_IP" => "非法 IP"
];
/**
* 上传错误检查
* @param string $errCode
* @return string
*/
public static function getStateInfo($errCode)
{
return !self::$stateMap[$errCode] ? self::$stateMap["ERROR_UNKNOWN"] : self::$stateMap[$errCode];
}
/**
* 文件上传
*
* @param \Illuminate\Http\UploadedFile $file
* @return array
*/
public static function fileUpload($file, $scene = '')
{
//读取上传参数
$upload_file_size = intval(Setting::getValueByKey('upload_file_size', 0));
$upload_file_ext_list = Setting::getValueByKey('upload_file_ext_list');
$upload_file_ext_list == '' && $upload_file_ext_list = 'png,jpg,jpeg,gif,bmp';
//文件大小判断
$upload_file_size *= 1048576; //文件上传最大
$filesize = $file->getSize();
$filename = $file->getFilename();
$origin = $file->getClientOriginalName();
$ext = $file->guessExtension();
empty($scene) || $scene .= '/';
//文件扩展名判断
$upload_file_ext_list = explode(',', strtolower(str_replace(' ', '', $upload_file_ext_list)));
if (!in_array($ext, $upload_file_ext_list)) {
return [
'state' => self::getStateInfo('ERROR_TYPE_NOT_ALLOWED'),
'url' => '',
'title' => $filename,
'original' => $origin,
'type' => '.' . $ext,
'size' => $filesize,
];
}
//文件大小校验
if ($upload_file_size > 0 && $filesize > $upload_file_size) {
return [
'state' => self::getStateInfo('ERROR_SIZE_EXCEED'),
'url' => '',
'title' => $filename,
'original' => $origin,
'type' => '.' . $ext,
'size' => $filesize,
];
}
//读取存储参数
$use_qiniu_storage = Setting::getValueByKey('use_qiniu_storage', 0);
$qiniu_url = Setting::getValueByKey('qiniu_url', '');
$access_key = Setting::getValueByKey('qiniu_access_key', '');
$secret_key = Setting::getValueByKey('qiniu_secret_key', '');
$bucket_name = Setting::getValueByKey('qiniu_bucket_name', '');
$url = $use_qiniu_storage ? $qiniu_url : url('');
if ($use_qiniu_storage) {
$file_obj = new \SplFileObject($file->getPathname());
$file_content = $file_obj->fread($file->getSize());
$upManager = new UploadManager();
$auth = new Auth($access_key, $secret_key);
$token = $auth->uploadToken($bucket_name);
list($ret, $error) = $upManager->put($token, $filename, $file_content);
if ($error) {
return [
'state' => is_string($error) ? $error : $error->message(),
'url' => '',
'title' => $filename,
'original' => $origin,
'type' => '.' . $ext,
'size' => $filesize,
];
}
$file_url = $url . '/' . $ret['key'];
} else {
$path = '/upload/' . $scene . date('Ymd') . '/';
$full_path = public_path() . $path;
file_exists($path) || @mkdir($full_path, 0777, true);
$file->move($full_path);
if($scene =='admin/'){
$file_url =$path . $filename;
}else{
$file_url = $url . $path . $filename;
}
}
return [
'state' => self::getStateInfo(0),
'url' => $file_url,
'title' => $filename,
'original' => $origin,
'type' => '.' . $ext,
'size' => $filesize,
];
}
}
+424
View File
@@ -0,0 +1,424 @@
<?php
namespace App\DAO;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\{Users, PrizePool, Setting};
use App\DAO\PrizePool\CandySender;
use App\Events\RealNameEvent;
class UserDAO
{
/**
* 检查是否符合升级工作室条件,若符合自动升级
*
* @param App\Users $user
* @return boolean
*/
public static function checkUpgradeAtelierCondition(&$user) : bool
{
$user->refresh();
//检查自身是否已实名或已经是工作室
if ($user->is_realname != 2 || $user->is_atelier == 1) {
return false;
}
$upgrade_atelier_must_has_son = Setting::getValueByKey('upgrade_atelier_must_has_son'); //升级工作室直推实名人数要求
$upgrade_atelier_must_team_son = Setting::getValueByKey('upgrade_atelier_must_team_son'); //升级工作室团队实名人数要求
$upgrade_atelier_must_team_recharge = Setting::getValueByKey('upgrade_atelier_must_team_recharge'); //升级工作室团队充值金额
//检查直推实名人数
if ($user->zhitui_real_number < $upgrade_atelier_must_has_son) {
return false;
}
//检查团队实名人数
if ($user->real_teamnumber < $upgrade_atelier_must_team_son) {
return false;
}
//检查团队充值金额
if ($user->top_upnumber < $upgrade_atelier_must_team_recharge) {
return false;
}
$user->is_atelier = 1;
return $user->save();
}
/**
* 检查用户的上级是否有通证变动
* @param App\Users $user 用户查询的对象
*/
public static function addCandyNumber($user)
{
$parents = self::getParentsPathDesc($user);
foreach ($parents as $key => $user_id) {
$current_user = Users::find($user_id);//检查该上级是否实名认证过
if ($current_user->is_realname == 2) {
//该上级实名认证过
self::checkUserRealNameReward($current_user); //检查是否符合发奖条件,符合就发放奖励
self::checkUpgradeAtelierCondition($current_user); //检查是否符合升级工作室条件
} else {
//该上级还没实名认证
continue;
}
}
}
/**
* 检测用户是否符合实名奖励
*
* @param App\Users $user
* @return bool
*/
public static function checkUserRealNameReward(&$user)
{
if (!$user) {
return false;
}
//获取setting值
$real_name_candy = Setting::getValueByKey('real_name_candy', '');
$zhitui2_number = Setting::getValueByKey('zhitui2_number', '');
$zhitui2_candy = Setting::getValueByKey('zhitui2_candy', '');
$zhitui3_number = Setting::getValueByKey('zhitui3_number', '');
$zhitui3_real_teamnumber = Setting::getValueByKey('zhitui3_real_teamnumber', '');
$zhitui3_top_upnumber = Setting::getValueByKey('zhitui3_top_upnumber', '');
$zhitui3_candy = Setting::getValueByKey('zhitui3_candy', '');
$zhitui4_number = Setting::getValueByKey('zhitui4_number', '');
$zhitui4_real_teamnumber = Setting::getValueByKey('zhitui4_real_teamnumber', '');
$zhitui4_top_upnumber = Setting::getValueByKey('zhitui4_top_upnumber', '');
$zhitui4_candy = Setting::getValueByKey('zhitui4_candy', '');
$zhitui5_number = Setting::getValueByKey('zhitui5_number', '');
$zhitui5_real_teamnumber = Setting::getValueByKey('zhitui5_real_teamnumber', '');
$zhitui5_top_upnumber = Setting::getValueByKey('zhitui5_top_upnumber', '');
$zhitui5_candy = Setting::getValueByKey('zhitui5_candy', '');
$zhitui6_number = Setting::getValueByKey('zhitui6_number', '');
$zhitui6_real_teamnumber = Setting::getValueByKey('zhitui6_real_teamnumber', '');
$zhitui6_top_upnumber = Setting::getValueByKey('zhitui6_top_upnumber', '');
$zhitui6_candy = Setting::getValueByKey('zhitui6_candy', '');
$only = 1;
$user->refresh();
$push_status = $user->push_status;
//实名认证过的有效直推人数
$real_zhitui = Users::where("is_realname", 2)
->where("parent_id", $user->id)
->count();
$user->zhitui_real_number = $real_zhitui ?: 0; //更新直推实名人数
$user->real_teamnumber += 1;
if ($push_status == 1) {
if ($real_zhitui >= $zhitui2_number) {
$user->candy_number += $zhitui2_candy;
$user->push_status = 2;
$log_candy_number = $zhitui2_candy;
$only = 2;
}
} elseif ($push_status == 2) {
if ($real_zhitui >= $zhitui3_number && $user->real_teamnumber >= $zhitui3_real_teamnumber && $user->top_upnumber >= $zhitui3_top_upnumber) {
$user->candy_number += $zhitui3_candy;
$user->push_status = 3;
$log_candy_number = $zhitui3_candy;
$only = 2;
}
} elseif ($push_status == 3) {
if ($real_zhitui >= $zhitui4_number && $user->real_teamnumber >= $zhitui4_real_teamnumber && $user->top_upnumber >= $zhitui4_top_upnumber) {
$user->candy_number += $zhitui4_candy;
$user->push_status = 4;
$log_candy_number = $zhitui4_candy;
$only = 2;
}
} elseif ($push_status == 4) {
if ($real_zhitui >= $zhitui5_number && $user->real_teamnumber >= $zhitui5_real_teamnumber && $user->top_upnumber >= $zhitui5_top_upnumber) {
$user->candy_number += $zhitui5_candy;
$user->push_status = 5;
$log_candy_number = $zhitui5_candy;
$only = 2;
}
} elseif ($push_status == 5) {
if ($real_zhitui >= $zhitui6_number && $user->real_teamnumber >= $zhitui6_real_teamnumber && $user->top_upnumber >= $zhitui6_top_upnumber) {
$user->candy_number += $zhitui6_candy;
$user->push_status = 6;
$log_candy_number = $zhitui6_candy;
$only = 2;
}
}
try {
DB::beginTransaction();
//当天新增实名认证团队人数
$beginToday=mktime(0,0,0,date('m'),date('d'),date('Y'));
if($user->new_isreal_time<$beginToday)
{
$user->today_real_teamnumber=1;
}
else
{
$user->today_real_teamnumber=$user->today_real_teamnumber+1;
}
$user->new_isreal_time=time();
$result = $user->save();
if (!$result) {
throw new \Exception('用户' . $user->account_number . '更新信息失败');
}
//开始记录日志
if ($only == 2) {
$prize_pool = new PrizePool();
$prize_pool->scene = PrizePool::CERTIFICATION;//const CERTIFICATION = 1; //实名认证奖励
$prize_pool->reward_type = PrizePool::REWARD_CANDY;//const REWARD_CANDY = 0; //奖励通证
$prize_pool->reward_qty = $log_candy_number;
$prize_pool->from_user_id = $user->id;
$prize_pool->to_user_id = $user->id;
$prize_pool->status = 1;
$prize_pool->memo = '下级直推' . $real_zhitui . '人,实名认证团队' . $user->real_teamnumber . '人,充值金额' . $user->top_upnumber . '美金,触发通证奖励' . $log_candy_number;
$prize_pool->create_time = time();
$prize_pool->receive_time = time();
$result = $prize_pool->save();
if (!$result) {
throw new \Exception('用户' . $user->account_number . '奖励记录失败');
}
}
DB::commit();
return true;
} catch (\Exception $e) {
DB::rollBack();
return false;
}
}
/**
* 取用户的上级工作室
*
* @param App\Users $user
* @return App\Users||null
*/
public static function getParentsAtelier($user)
{
$parents = UserDAO::getParentsPathDesc($user);
//将所有上级工作室查出来
$parent_atelier = Users::where('is_atelier', 1)
->whereIn('id', $parents)
->get();
if (count($parent_atelier) <= 0) {
return $parent_atelier;
}
$parents_sort = array_flip($parents);
//检测有没有达到封页
$sorted = $parent_atelier->sortBy(function ($item, $key) use ($parents_sort) {
$sort = $parents_sort[$item->id];
return $sort;
});
$sorted = $sorted->values();
return $sorted;
}
//递归查询用户下级所有人数
public function GetTeamMember($members, $mid)
{
$Teams = array();//最终结果
$mids = array($mid);//第一次执行时候的用户id
do {
$othermids = array();
$state = false;
foreach ($mids as $valueone) {
foreach ($members as $key => $valuetwo) {
if ($valuetwo['parent_id'] == $valueone && $valuetwo['is_realname'] == 2) //实名认证通过的团队人数
{
$Teams[] = $valuetwo['id'];//找到我的下级立即添加到最终结果中
$othermids[] = $valuetwo['id'];//将我的下级id保存起来用来下轮循环他的下级
// array_splice($members,$key,1);//从所有会员中删除他
$state = true;
}
}
}
$mids = $othermids;//foreach中找到的我的下级集合,用来下次循环
} while ($state == true);
$Teams = Users::whereIn("id", $Teams)->where("is_realname", "=", 2)->count();
return $Teams;
}
/**
* 更新用户团队充值业绩金额
* @param integer $id 用户的id值,用于查询更新该用户上级的团队充值金额
* @return $number 充值金额值
* * @param integer $qty 要取的上级代数,不传或传null则取全部
*/
public static function updateTopUpnumber($id, $number, $qty = null)
{
$user = Users::find($id);
$parents = self::getParentsPathDesc($user, $qty);
$result = Users::whereIn('id', $parents)->increment('top_upnumber', $number);
//此处应再遍历检查一下$parents是否符合升级条件
foreach ($parents as $key => $current_user_id) {
$current_user = Users::find($current_user_id);
if (!$current_user) {
continue;
}
self::checkUserRealNameReward($current_user);
self::checkUpgradeAtelierCondition($current_user);
}
return $result;
}
/**
* 查询用户的指定代数的上级(根据parents_path信息)
*
* @param App\Users $user 用户模型实例
* @param integer $qty 要取的上级代数,不传或传null则取全部
* @return array 返回包含上级id的数组
*/
public static function getParentsPathDesc($user, $qty = null)
{
$path = $user->parents_path;
if ($path == null || empty($path)) {
return [];
}
$parents = explode(',', $path);
$parents = array_filter($parents);
krsort($parents);
$parents = array_slice($parents, 0, $qty);
return $parents;
}
/**
* 递归查询上级
*
* @param App\Users $user 用户模型实例
* @return array
*/
public static function getRealParents($user)
{
$found_parent_node = [];
$parents = self::findParent($user, $found_parent_node);
return $parents;
}
/**
* 递归查询上级(字符串)
*
* @param App\Users $user 用户模型实例
* @return string 返回逗号间隔的path
*/
public static function getRealParentsPath($user)
{
$parents = self::getRealParents($user);
if (count($parents) > 0) {
return implode(',', $parents);
}
return '';
}
private static function findParent($user, &$found_parent_node)
{
$parent_id = $user->parent_id;
if ($parent_id) {
//检测节点关系是否有死循环
if (in_array($parent_id, $found_parent_node)) {
$context = [
'user_id' => $user->id,
'parent_id' => $parent_id,
'found_parent_node' => $found_parent_node,
];
//记录错误日志
Log::useDailyFiles(base_path('storage/logs/user/'), 7);
Log::critical('id:' . $user->id . '的用户,上级关系存在死循环', $context);
return [];
}
array_unshift($found_parent_node, $parent_id);
$parent = Users::find($parent_id);
$result = self::findParent($parent, $found_parent_node);
unset($parent);
array_push($result, $parent_id);
return $result;
} else {
return [];
}
}
/**
* 检查用户是否符合对应级别的升级,若符合就升级(不降级)
*
* @param App\Users $user 要升级的用户模型实例
* @param App\Users $from_user 触发者用户模型实例
* @return void 无返回值
*/
public static function upgradeCheck($user)
{
$before_level = $user->level_id;
$new_level = 2;
if ($user->is_disable == 1) {
$new_level = 1;
} else if ($user->total_integral >= 1000000) {
$new_level = 5;
} else if ($user->total_topup >= 10000) {
$new_level = 4;
} else if ($user->total_topup >= 300) {
$new_level = 3;
}
//查询等级对应的id
$level = Level::where('code', $new_level)->first();
//不掉级处理
if ($before_level < $new_level) {
try {
DB::transaction(function () use ($user, $level, $before_level, $new_level) {
$user_upgrade_log = new UserUpgradeLog();
$user_upgrade_log->user_id = $user->id;
$user_upgrade_log->from_user_id = $user->id;
$user_upgrade_log->before_level = $before_level;
$user_upgrade_log->after_level = $new_level;
$user_upgrade_log->memo = '用户等级变更:由[' . self::get_level_name($before_level) . ']升级到[' . self::get_level_name($new_level) . ']';
$user_upgrade_log->created_time = time();
$result = $user_upgrade_log->save();
if (!$result) {
throw new \Exception('记录用户升级日志失败');
}
$user->level_id = $level->id;
$result = $user->save();
if (!$result) {
throw new \Exception('变更用户等级失败');
}
});
} catch (\Exception $e) {
echo '<pre>';
echo '错误:' . $e->getMessage() . PHP_EOL . ',文件:' . $e->getFile() . PHP_EOL . '行号:' . $e->getLine();
return;
}
// $parent = Users::find($user->parent_id);
// if ($parent) {
// self::upgradeCheck($parent, $user);
// }
}
}
public static function get_level_name($id = 2)
{
$name = '';
switch ($id) {
case 1:
$name = '限制会员';
break;
case 2:
$name = '临时会员';
break;
case 3:
$name = '正式会员';
break;
case 4:
$name = '五星会员';
break;
case 5:
$name = 'VIP会员';
break;
default:
$name = '临时会员';
}
return $name;
}
}