diff --git a/readme/Demo/Config/Config.php b/readme/Demo/Config/Config.php new file mode 100644 index 00000000..88021b6e --- /dev/null +++ b/readme/Demo/Config/Config.php @@ -0,0 +1,7 @@ + '1', + 'KeyId' => 'FAFAB92C-DFDF-1221-DEA2-40A0E915EB10', +]; \ No newline at end of file diff --git a/readme/Demo/Config/Info.php b/readme/Demo/Config/Info.php new file mode 100644 index 00000000..c0431b0f --- /dev/null +++ b/readme/Demo/Config/Info.php @@ -0,0 +1,12 @@ + 'Linker', + Plugin::AUTHOR => 'snltty', + Plugin::WEB_SITE => '#', + Plugin::DESCRIPTION => '下单成功后加密卡密', + Plugin::VERSION => '1.0.0' +]; \ No newline at end of file diff --git a/readme/Demo/Config/Submit.php b/readme/Demo/Config/Submit.php new file mode 100644 index 00000000..aa6946ad --- /dev/null +++ b/readme/Demo/Config/Submit.php @@ -0,0 +1,11 @@ + "Key", + "name" => "KeyId", + "type" => "input", + "placeholder" => "加密秘钥" + ] +]; \ No newline at end of file diff --git a/readme/Demo/Controller/Demo.php b/readme/Demo/Controller/Demo.php new file mode 100644 index 00000000..484e4534 --- /dev/null +++ b/readme/Demo/Controller/Demo.php @@ -0,0 +1,26 @@ +render(title: 'Demo', template: 'Demo.html', controller: true); + } +} \ No newline at end of file diff --git a/readme/Demo/Hook/Demo.php b/readme/Demo/Hook/Demo.php new file mode 100644 index 00000000..efd0bed1 --- /dev/null +++ b/readme/Demo/Hook/Demo.php @@ -0,0 +1,91 @@ +secret,true); + + try{ + $widget = json_decode($order->widget,true); + foreach($widget as $k=>$v){ + $secret["Widget".$k] = $v["value"]; + } + }catch(\Exception $e){ + } + //file_put_contents("order.txt",json_encode($order,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $secret["OrderId"] = "ACG".$order["trade_no"]; + $secret["Contact"] = $order["contact"]; + $secret["CostPrice"] = $order['commodity']["factory_price"]; + $secret["Price"] = $order['commodity']["price"]; + $secret["UserPrice"] = $order['commodity']["user_price"]; + $secret["PayPrice"] = $order["amount"]; + $secret["Count"] = $order["card_num"]; + $order->secret = json_encode($secret,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + + $config = Plugin::getConfig("Demo"); + $aesCrypto = new AesCrypto($config["KeyId"]); + $order->secret = base64_encode($aesCrypto->encode($order->secret)); + $order->save(); + } + +} +class AesCrypto +{ + private $key; + private $iv; + private $password; + + public function __construct($password) + { + $this->password = $password; + $this->initAes(); + } + + private function initAes() + { + $keyAndIV = $this->generateKeyAndIV($this->password); + $this->key = $keyAndIV['key']; + $this->iv = $keyAndIV['iv']; + } + + public function encode($data) + { + return $this->encodeWithOffset($data, 0, strlen($data)); + } + + public function encodeWithOffset($data, $offset, $length) + { + $data = substr($data, $offset, $length); + return openssl_encrypt($data, 'AES-128-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv); + } + + public function decode($data) + { + return $this->decodeWithOffset($data, 0, strlen($data)); + } + + public function decodeWithOffset($data, $offset, $length) + { + $data = substr($data, $offset, $length); + return openssl_decrypt($data, 'AES-128-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv); + } + + private function generateKeyAndIV($password) + { + $hash = hash('sha384', $password, true); + $key = substr($hash, 0, 16); // 取前 16 字节作为密钥 + $iv = substr($hash, 16, 16); // 取接下来的 16 字节作为 IV + return ['key' => $key, 'iv' => $iv]; + } +} diff --git a/readme/Demo/View/Demo.html b/readme/Demo/View/Demo.html new file mode 100644 index 00000000..e653a789 --- /dev/null +++ b/readme/Demo/View/Demo.html @@ -0,0 +1,20 @@ +#{include file=$manage_view_path|cat:"/Header.html"} + +
+ + #{include file=$manage_view_path|cat:"Toolbar.html"} + + +
+ +
+ ok +
+ +
+ +
+ + +#{include file=$manage_view_path|cat:"Footer.html"} \ No newline at end of file diff --git a/readme/OrderService.php b/readme/OrderService.php new file mode 100644 index 00000000..648736d4 --- /dev/null +++ b/readme/OrderService.php @@ -0,0 +1,1060 @@ +user_id, $commodity->id)) { + $premium = (float)$userCommodity->premium; + } + } + + //解析配置文件 + $this->parseConfig($commodity, $group, $owner, 1, $race); + $price = $owner == 0 ? $commodity->price : $commodity->user_price; + + //禁用任何折扣,直接计算 + if ($commodity->level_disable == 1) { + return (int)(string)(($num * ($price + $premium)) * 100) / 100; + } + + $userDefinedConfig = Commodity::parseGroupConfig((string)$commodity->level_price, $group); + + + if ($userDefinedConfig && $userDefinedConfig['amount'] > 0) { + if (!$commodity->race) { + //如果自定义价格成功,那么将覆盖其他价格 + $price = $userDefinedConfig['amount']; + } + } elseif ($group) { + //如果没有对应的会员等级解析,那么就直接采用系统折扣 + $price = $price - ($price * $group->discount); + } + + //判定是race还是普通订单 + if (is_array($commodity->race)) { + if (array_key_exists((string)$race, (array)$commodity->category_wholesale)) { + //判定当前race是否可以折扣 + $list = $commodity->category_wholesale[$race]; + krsort($list); + foreach ($list as $k => $v) { + if ($num >= $k) { + $price = $v; + break; + } + } + } + } else { + //普通订单,直接走批发 + $list = (array)$commodity->wholesale; + krsort($list); + foreach ($list as $k => $v) { + if ($num >= $k) { + $price = $v; + break; + } + } + } + + $price += $premium; //分站加价 + return (int)(string)(($num * $price) * 100) / 100; + } + + + /** + * 解析配置 + * @param Commodity $commodity + * @param UserGroup|null $group + * @param int $owner + * @param int $num + * @param string|null $race + * @return void + * @throws JSONException + */ + public function parseConfig(Commodity &$commodity, ?UserGroup $group, int $owner = 0, int $num = 1, ?string $race = null): void + { + $parseConfig = Ini::toArray((string)$commodity->config); + //用户组解析 + $userDefinedConfig = Commodity::parseGroupConfig($commodity->level_price, $group); + + if ($userDefinedConfig) { + if (key_exists("category", $userDefinedConfig['config'])) { + $parseConfig['category'] = $userDefinedConfig['config']['category']; + } + + if (key_exists("wholesale", $userDefinedConfig['config'])) { + $parseConfig['wholesale'] = $userDefinedConfig['config']['wholesale']; + } + + if (key_exists("category_wholesale", $userDefinedConfig['config'])) { + $parseConfig['category_wholesale'] = $userDefinedConfig['config']['category_wholesale']; + } + } + + if (key_exists("category", $parseConfig)) { + $category = $parseConfig['category']; + //将类别数组存到对象中 + $commodity->race = $category; + //判断是否传了指定的类别 + if ($race) { + if (!key_exists($race, $category)) { + throw new JSONException("商品种类不存在"); + } + $commodity->price = $category[$race]; + $commodity->user_price = $commodity->price; + } else { + $commodity->price = current($category); + $commodity->user_price = $commodity->price; + } + } + + //判定批发配置是否配置,如果配置 + if (key_exists("wholesale", $parseConfig)) { + $wholesale = $parseConfig['wholesale']; + if (!empty($wholesale)) { + //将全局批发配置写入到对象中 + $commodity->wholesale = $wholesale; + } + } + + if (key_exists("category_wholesale", $parseConfig)) { + $categoryWholesale = $parseConfig['category_wholesale']; + if (!empty($categoryWholesale)) { + //将商品种类批发配置写入到对象中 + $commodity->category_wholesale = $categoryWholesale; + } + } + + //成本参数 + if (key_exists("category_factory", $parseConfig)) { + $categoryFactory = $parseConfig['category_factory']; + if (!empty($categoryFactory)) { + $commodity->category_factory = $categoryFactory; + } + } + } + + /** + * @param Commodity $commodity + * @param UserGroup|null $group + * @return array|null + */ + public function userDefinedPrice(Commodity $commodity, ?UserGroup $group): ?array + { + if ($group) { + $levelPrice = (array)json_decode((string)$commodity->level_price, true); + return array_key_exists($group->id, $levelPrice) ? $levelPrice[$group->id] : null; + } + return null; + } + + /** + * @param User|null $user + * @param UserGroup|null $userGroup + * @param array $map + * @return array + * @throws JSONException + */ + public function trade(?User $user, ?UserGroup $userGroup, array $map): array + { + #CFG begin + $commodityId = (int)$map['commodity_id'];//商品ID + $contact = (string)$map['contact'];//联系方式 + $num = (int)$map['num']; //购买数量 + $cardId = (int)$map['card_id'];//预选的卡号ID + $payId = (int)$map['pay_id'];//支付方式id + $device = (int)$map['device'];//设备 + $password = (string)$map['password'];//查单密码 + $coupon = (string)$map['coupon'];//优惠卷 + $from = (int)$map['from'];//推广人ID + $owner = $user == null ? 0 : $user->id; + $race = (string)$map['race']; //2022/01/09 新增,商品种类功能 + $requestNo = (string)$map['request_no']; + #CFG end + + if ($commodityId == 0) { + throw new JSONException("请选择商品在下单"); + } + + if ($num <= 0) { + throw new JSONException("至少购买1个"); + } + + $commodity = Commodity::query()->find($commodityId); + + + if (!$commodity) { + throw new JSONException("商品不存在"); + } + + if ($commodity->status != 1) { + throw new JSONException("当前商品已停售"); + } + + if ($commodity->only_user == 1 || $commodity->purchase_count > 0) { + if ($owner == 0) { + throw new JSONException("请先登录后再购买哦"); + } + } + + + if ($commodity->minimum > 0 && $num < $commodity->minimum) { + throw new JSONException("本商品最少购买{$commodity->minimum}个"); + } + + if ($commodity->maximum > 0 && $num > $commodity->maximum) { + throw new JSONException("本商品单次最多购买{$commodity->maximum}个"); + } + + + $widget = []; + + //widget + if ($commodity->widget) { + $widgetList = (array)json_decode((string)$commodity->widget, true); + foreach ($widgetList as $item) { + if ($item['regex'] != "") { + if (!preg_match("/{$item['regex']}/", (string)$map[$item['name']])) { + throw new JSONException($item['error']); + } + } + $widget[$item['name']] = [ + "value" => $map[$item['name']], + "cn" => $item['cn'] + ]; + } + } + + $widget = json_encode($widget, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + + //预选卡密 + if ($commodity->draft_status == 1 && $cardId != 0) { + $num = 1; + } + + $regx = ['/^1[3456789]\d{9}$/', '/.*(.{2}@.*)$/i', '/[1-9]{1}[0-9]{4,11}/']; + $msg = ['手机', '邮箱', 'QQ号']; + //未登录才检测,登录后无需检测 + if (!$user) { + if (mb_strlen($contact) < 3) { + throw new JSONException("联系方式不能低于3个字符"); + } + //联系方式正则判断 + if ($commodity->contact_type != 0) { + if (!preg_match($regx[$commodity->contact_type - 1], $contact)) { + throw new JSONException("您输入的{$msg[$commodity->contact_type - 1]}格式不正确!"); + } + } + if ($commodity->password_status == 1 && mb_strlen($password) < 6) { + throw new JSONException("您的设置的密码过于简单,不能低于6位哦"); + } + } + + if ($commodity->seckill_status == 1) { + if (time() < strtotime($commodity->seckill_start_time)) { + throw new JSONException("抢购还未开始"); + } + if (time() > strtotime($commodity->seckill_end_time)) { + throw new JSONException("抢购已结束"); + } + } + + //解析配置文件且注入对象 + $commodityClone = clone $commodity; + $this->parseConfig($commodityClone, $userGroup, $owner, $num, $race); + + if ($commodityClone->race && !key_exists($race, $commodityClone->race)) { + throw new JSONException("请选择商品种类"); + } + + //成本价 + if ($commodityClone->race && $race != "") { + //获取种类成本 + $factoryPrice = 0; + if ($commodityClone->category_factory && isset($commodityClone->category_factory[$race])) { + $factoryPrice = (float)$commodityClone->category_factory[$race]; + } + } else { + $factoryPrice = $commodity->factory_price; + } + + //------------- + + $shared = $commodity->shared; //获取商品的共享平台 + + if ($shared) { + if (!$this->shared->inventoryState($shared, $commodity, $cardId, $num, $race)) { + throw new JSONException("库存不足"); + } + } else { + //自动发货,库存检测 + if ($commodity->delivery_way == 0) { + $count = Card::query()->where("commodity_id", $commodityId)->where("status", 0); + if ($race) { + $count = $count->where("race", $race); + } + $count = $count->count(); + + if ($count == 0 || $num > $count) { + throw new JSONException("库存不足"); + } + } + } + + if ($commodity->purchase_count > 0 && $owner > 0) { + $orderCount = \App\Model\Order::query()->where("owner", $owner)->where("commodity_id", $commodity->id)->count(); + if ($orderCount >= $commodity->purchase_count) { + throw new JSONException("该商品每人只能购买{$commodity->purchase_count}件"); + } + } + + + //计算订单基础价格 + $amount = $this->calcAmount($owner, $num, $commodity, $userGroup, $race); + + //判断预选费用 + $pay = Pay::query()->find($payId); + + if (!$pay) { + throw new JSONException("该支付方式不存在"); + } + + if ($pay->commodity != 1) { + throw new JSONException("当前支付方式已停用,请换个支付方式再进行支付"); + } + + //回调地址 + $callbackDomain = trim(Config::get("callback_domain"), "/"); + $clientDomain = Client::getUrl(); + + if (!$callbackDomain) { + $callbackDomain = $clientDomain; + } + + DB::connection()->getPdo()->exec("set session transaction isolation level serializable"); + return Db::transaction(function () use ($requestNo, $user, $userGroup, $num, $contact, $device, $amount, $owner, $commodity, $pay, $cardId, $password, $coupon, $from, $widget, $race, $shared, $callbackDomain, $clientDomain, $factoryPrice) { + //生成联系方式 + if ($user) { + //检测订单频繁 + // + $contact = "-"; + } + + if ($requestNo && \App\Model\Order::query()->where("request_no", $requestNo)->first()) { + throw new JSONException("The request ID already exists"); + } + + + $date = Date::current(); + $order = new \App\Model\Order(); + $order->widget = $widget; + $order->owner = $owner; + $order->trade_no = Str::generateTradeNo(); + $order->amount = $amount; + $order->commodity_id = $commodity->id; + $order->pay_id = $pay->id; + $order->create_time = $date; + $order->create_ip = Client::getAddress(); + $order->create_device = $device; + $order->status = 0; + $order->contact = trim((string)$contact); + $order->delivery_status = 0; + $order->card_num = $num; + $order->user_id = (int)$commodity->owner; + $order->rent = $factoryPrice * $num; //成本价 + + if ($requestNo) { + $order->request_no = $requestNo; + } + + + if ($race) { + $order->race = $race; + } + + if ($from != 0 && $order->user_id != $from && $owner != $from) { + $order->from = $from; + if (($userCommodity = UserCommodity::getCustom($from, $commodity->id)) && Business::get(Client::getDomain())) { + $order->premium = $userCommodity->premium; + } + } + + if ($commodity->draft_status == 1 && $cardId != 0) { + if ($shared) { + //加钱 + $order->amount = $order->amount + $commodity->draft_premium; + $order->card_id = $cardId; + } else { + $card = Card::query(); + if ($race) { + $card = $card->where("race", $race); + } + + $card = $card->find($cardId); + + if (!$card || $card->status != 0) { + throw new JSONException("该卡已被他人抢走啦"); + } + + if ($card->commodity_id != $commodity->id) { + throw new JSONException("该卡密不属于这个商品,无法预选" . $commodity->id); + } + //加钱 + $order->amount = $order->amount + $commodity->draft_premium; + $order->card_id = $cardId; + } + } + + if ($password != "") { + $order->password = $password; + } + + //用户组减免 + /* if ($userGroup) { + $order->amount = $order->amount - ($order->amount * $userGroup->discount); + }*/ + + //优惠卷 + if ($coupon != "") { + $voucher = Coupon::query()->where("code", $coupon)->first(); + + if (!$voucher) { + throw new JSONException("该优惠卷不存在"); + } + + if ($voucher->owner != $commodity->owner) { + throw new JSONException("该优惠卷不存在"); + } + + if ($race && $voucher->commodity_id != 0) { + if ($race != $voucher->race) { + throw new JSONException("该优惠卷不能抵扣当前商品"); + } + } + + if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) { + throw new JSONException("该优惠卷不属于该商品"); + } + + //判断该优惠卷是否有分类设定 + if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) { + throw new JSONException("该优惠卷不能抵扣当前商品"); + } + + if ($voucher->status != 0) { + throw new JSONException("该优惠卷已失效"); + } + + //检测过期时间 + if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) { + throw new JSONException("该优惠卷已过期"); + } + + //检测面额 + if ($voucher->money >= $order->amount) { + throw new JSONException("该优惠卷面额大于订单金额"); + } + + //进行优惠 + $order->amount = $voucher->mode == 0 ? $order->amount - $voucher->money : $order->amount - (($order->amount / $order->card_num) * $voucher->money); + $voucher->service_time = $date; + $voucher->use_life = $voucher->use_life + 1; + $voucher->life = $voucher->life - 1; + + if ($voucher->life <= 0) { + $voucher->status = 1; + } + + $voucher->trade_no = $order->trade_no; + $voucher->save(); + $order->coupon_id = $voucher->id; + } + + $secret = null; + $order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100); + + hook(\App\Consts\Hook::USER_API_ORDER_TRADE_PAY_BEGIN, $commodity, $order, $pay); + + if ($order->amount == 0) { + //免费赠送 + $order->save();//先将订单保存下来 + $secret = $this->orderSuccess($order); //提交订单并且获取到卡密信息 + } else { + if ($pay->handle == "#system") { + //余额购买 + if ($owner == 0) { + throw new JSONException("您未登录,请先登录后再使用余额支付"); + } + $session = User::query()->find($owner); + if (!$session) { + throw new JSONException("用户不存在"); + } + + if ($session->status != 1) { + throw new JSONException("You have been banned"); + } + $parent = $session->parent; + if ($parent && $order->user_id != $from) { + $order->from = $parent->id; + } + //扣钱 + Bill::create($session, $order->amount, Bill::TYPE_SUB, "商品下单[{$order->trade_no}]"); + //发卡 + $order->save();//先将订单保存下来 + $secret = $this->orderSuccess($order); //提交订单并且获取到卡密信息 + } else { + //开始进行远程下单 + $class = "\\App\\Pay\\{$pay->handle}\\Impl\\Pay"; + if (!class_exists($class)) { + throw new JSONException("该支付方式未实现接口,无法使用"); + } + $autoload = BASE_PATH . '/app/Pay/' . $pay->handle . "/Vendor/autoload.php"; + if (file_exists($autoload)) { + require($autoload); + } + //增加接口手续费:0.9.6-beta + $order->pay_cost = $pay->cost_type == 0 ? $pay->cost : $order->amount * $pay->cost; + $order->amount = $order->amount + $order->pay_cost; + $order->amount = (float)sprintf("%.2f", (int)(string)($order->amount * 100) / 100); + + $payObject = new $class; + $payObject->amount = $order->amount; + $payObject->tradeNo = $order->trade_no; + $payObject->config = PayConfig::config($pay->handle); + + $payObject->callbackUrl = $callbackDomain . '/user/api/order/callback.' . $pay->handle; + + //判断如果登录 + if ($owner == 0) { + $payObject->returnUrl = $clientDomain . '/user/index/query?tradeNo=' . $order->trade_no; + } else { + $payObject->returnUrl = $clientDomain . '/user/personal/purchaseRecord?tradeNo=' . $order->trade_no; + } + + $payObject->clientIp = Client::getAddress(); + $payObject->code = $pay->code; + $payObject->handle = $pay->handle; + + $trade = $payObject->trade(); + if ($trade instanceof PayEntity) { + $order->pay_url = $trade->getUrl(); + switch ($trade->getType()) { + case \App\Pay\Pay::TYPE_REDIRECT: + $url = $order->pay_url; + break; + case \App\Pay\Pay::TYPE_LOCAL_RENDER: + $base64 = urlencode(base64_encode('type=1&handle=' . $pay->handle . '&code=' . $pay->code . '&tradeNo=' . $order->trade_no)); + $url = '/user/pay/order.' . $base64; + break; + case \App\Pay\Pay::TYPE_SUBMIT: + $base64 = urlencode(base64_encode('type=2&tradeNo=' . $order->trade_no)); + $url = '/user/pay/order.' . $base64; + break; + } + $order->save(); + $option = $trade->getOption(); + if (!empty($option)) { + OrderOption::create($order->id, $trade->getOption()); + } + } else { + throw new JSONException("支付方式未部署成功"); + } + } + } + + + $order->save(); + + hook(\App\Consts\Hook::USER_API_ORDER_TRADE_AFTER, $commodity, $order, $pay); + return ['url' => $url, 'amount' => $order->amount, 'tradeNo' => $order->trade_no, 'secret' => $order->secret]; + }); + } + + + /** + * 初始化回调 + * @throws JSONException + */ + #[ArrayShape(["trade_no" => "mixed", "amount" => "mixed", "success" => "mixed"])] public function callbackInitialize(string $handle, array $map): array + { + $json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $payInfo = PayConfig::info($handle); + $payConfig = PayConfig::config($handle); + $callback = $payInfo['callback']; + + $autoload = BASE_PATH . '/app/Pay/' . $handle . "/Vendor/autoload.php"; + if (file_exists($autoload)) { + require($autoload); + } + + //检测签名验证是否开启 + if ($callback[\App\Consts\Pay::IS_SIGN]) { + $class = "\\App\\Pay\\{$handle}\\Impl\\Signature"; + if (!class_exists($class)) { + PayConfig::log($handle, "CALLBACK", "插件未实现接口"); + throw new JSONException("signature not implements interface"); + } + $signature = new $class; + Context::set(\App\Consts\Pay::DAFA, $map); + if (!$signature->verification($map, $payConfig)) { + PayConfig::log($handle, "CALLBACK", "签名验证失败,接受数据:" . $json); + throw new JSONException("sign error"); + } + $map = Context::get(\App\Consts\Pay::DAFA); + } + + //验证状态 + if ($callback[\App\Consts\Pay::IS_STATUS]) { + if ($map[$callback[\App\Consts\Pay::FIELD_STATUS_KEY]] != $callback[\App\Consts\Pay::FIELD_STATUS_VALUE]) { + PayConfig::log($handle, "CALLBACK", "状态验证失败,接受数据:" . $json); + throw new JSONException("status error"); + } + } + + //拿到订单号和金额 + return ["trade_no" => $map[$callback[\App\Consts\Pay::FIELD_ORDER_KEY]], "amount" => $map[$callback[\App\Consts\Pay::FIELD_AMOUNT_KEY]], "success" => $callback[\App\Consts\Pay::FIELD_RESPONSE]]; + } + + + /** + * @throws JSONException + */ + public function orderSuccess(\App\Model\Order $order): string + { + $commodity = $order->commodity; + $order->pay_time = Date::current(); + $order->status = 1; + $shared = $commodity->shared; //获取商品的共享平台 + + if ($shared) { + //拉取远程平台的卡密发货 + $order->secret = $this->shared->trade($shared, $commodity, $order->contact, $order->card_num, (int)$order->card_id, $order->create_device, (string)$order->password, (string)$order->race, $order->widget, $order->trade_no); + $order->delivery_status = 1; + } else { + //自动发货 + if ($commodity->delivery_way == 0) { + //拉取本地的卡密发货 + $order->secret = $this->pullCardForLocal($order, $commodity); + $order->delivery_status = 1; + } else { + //手动发货 + $order->secret = ($commodity->delivery_message != null && $commodity->delivery_message != "") ? $commodity->delivery_message : '正在发货中,请耐心等待,如有疑问,请联系客服。'; + } + } + + //佣金 + $merchant = $order->user; + if ($merchant) { + //获取返佣比例 + $businessLevel = $merchant->businessLevel; + if ($businessLevel) { + $order->cost = $order->amount * $businessLevel->cost; //手续费 + $a1 = $order->amount - $order->cost - $order->pay_cost; + if ($a1 > 0) { + Bill::create($merchant, $a1, Bill::TYPE_ADD, "商品出售[$order->trade_no]", 1); + } + } + } + + //真 · 返佣 + $promote_1 = $order->promote; + + if ($promote_1) { + //检测是否分站 + $bus = BusinessLevel::query()->find((int)$promote_1->business_level); + if ($bus) { + //查询该商户的拿货价 + $calcAmount = $this->calcAmount($promote_1->id, $order->card_num, $commodity, UserGroup::get($promote_1->recharge), $order->race, true); + //计算差价 + if ($order->amount > $calcAmount) { + $rebate = $order->amount - $calcAmount; //差价 + $order->premium = $rebate; + $a2 = $rebate - ($order->card_id ? $commodity->draft_premium : 0) - $order->pay_cost; + if ($rebate >= 0.01 && $a2 > 0) { + Bill::create($promote_1, $a2, Bill::TYPE_ADD, "分站返佣", 1); + $order->rebate = $a2; + } + } + //检测到商户等级,进行分站返佣算法 废弃 + // $rebate = ($bus->accrual * ($order->amount - $order->premium)) + $order->premium; //20.00 + } else { + //推广系统 + $promoteRebateV1 = (float)Config::get("promote_rebate_v1"); //3级返佣 0.2 + $rebate1 = $promoteRebateV1 * ($order->amount - $order->pay_cost); //20.00 + if ($rebate1 >= 0.01) { + $promote_2 = $promote_1->parent; //获取上级 + if (!$promote_2) { + //没有上级,直接进行1级返佣 + Bill::create($promote_1, $rebate1, Bill::TYPE_ADD, "推广返佣", 1); //反20.00 + $order->rebate = $rebate1; + } else { + $_rebate = 0; + //出现上级,开始将返佣的钱继续拆分 + $promoteRebateV2 = (float)Config::get("promote_rebate_v2"); // 0.4 + $rebate2 = $promoteRebateV2 * $rebate1; //拿走属于第二级百分比返佣 8.00 + //先给上级返佣,这里拿掉上级的拿一份 + Bill::create($promote_1, $rebate1 - $rebate2, Bill::TYPE_ADD, "推广返佣", 1); // 20-8=12.00 + $_rebate += ($rebate1 - $rebate2); + if ($rebate2 > 0.01) { // 8.00 + $promote_3 = $promote_2->parent; //获取第二级的上级 + if (!$promote_3) { + //没有上级直接进行第二级返佣 + Bill::create($promote_2, $rebate2, Bill::TYPE_ADD, "推广返佣", 1); // 8.00 + $_rebate += $rebate2; + } else { + //出现上级,继续拆分剩下的佣金 + $promoteRebateV3 = (float)Config::get("promote_rebate_v3"); // 0.4 + $rebate3 = $promoteRebateV3 * $rebate2; // 8.00 * 0.4 = 3.2 + //先给上级反 + Bill::create($promote_2, $rebate2 - $rebate3, Bill::TYPE_ADD, "推广返佣", 1); // 8.00 - 3.2 = 4.8 + $_rebate += ($rebate2 - $rebate3); + if ($rebate3 > 0.01) { + Bill::create($promote_3, $rebate3, Bill::TYPE_ADD, "推广返佣", 1); // 3.2 + $_rebate += $rebate3; + //返佣结束 3.2 + 4.8 + 12 = 20.00 + } + } + + + if ($_rebate > 0.01) { + $order->rebate = $_rebate; + } + } + } + } + } + } + + $order->save(); + + hook(\App\Consts\Hook::USER_API_ORDER_PAY_AFTER, $commodity, $order, $order->pay); + + if ($commodity->contact_type == 2 && $commodity->send_email == 1 && $order->owner == 0) { + try { + $this->email->send($order->contact, "【发货提醒】您购买的卡密发货啦", "您购买的卡密如下:" . $order->secret); + } catch (\Exception|\Error $e) { + } + } + + return (string)$order->secret; + } + + /** + * 拉取本地卡密,需要事务环境执行 + * @param \App\Model\Order $order + * @param Commodity $commodity + * @return string + */ + private function pullCardForLocal(\App\Model\Order $order, Commodity $commodity): string + { + $secret = "很抱歉,有人在你付款之前抢走了商品,请联系客服。"; + + $draft = $order->card; + + //指定预选卡密 + if ($draft) { + if ($draft->status == 0) { + $secret = $draft->secret; + $draft->purchase_time = $order->pay_time; + $draft->order_id = $order->id; + $draft->status = 1; + $draft->save(); + } + return $secret; + } + + //取出和订单相同数量的卡密 + $direction = match ($commodity->delivery_auto_mode) { + 0 => "id asc", + 1 => "rand()", + 2 => "id desc" + }; + $cards = Card::query()->where("commodity_id", $order->commodity_id)->orderByRaw($direction)->where("status", 0); + //判断订单是否存在类别 + if ($order->race) { + $cards = $cards->where("race", $order->race); + } + + $cards = $cards->limit($order->card_num)->get(); + + if (count($cards) == $order->card_num) { + $ids = []; + $cardc = ''; + foreach ($cards as $card) { + $ids[] = $card->id; + $cardc .= $card->secret . PHP_EOL; + } + try { + //将全部卡密置已销售状态 + $rows = Card::query()->whereIn("id", $ids)->update(['purchase_time' => $order->pay_time, 'order_id' => $order->id, 'status' => 1]); + if ($rows != 0) { + $secret = trim($cardc, PHP_EOL); + } + } catch (\Exception $e) { + } + } + + return $secret; + } + + + /** + * @param string $handle + * @param array $map + * @return string + * @throws JSONException + */ + public function callback(string $handle, array $map): string + { + $callback = $this->callbackInitialize($handle, $map); + $json = json_encode($map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + DB::connection()->getPdo()->exec("set session transaction isolation level serializable"); + DB::transaction(function () use ($handle, $map, $callback, $json) { + //获取订单 + $order = \App\Model\Order::query()->where("trade_no", $callback['trade_no'])->first(); + if (!$order) { + PayConfig::log($handle, "CALLBACK", "订单不存在,接受数据:" . $json); + throw new JSONException("order not found"); + } + if ($order->status != 0) { + PayConfig::log($handle, "CALLBACK", "重复通知,当前订单已支付"); + throw new JSONException("order status error"); + } + if ($order->amount != $callback['amount']) { + PayConfig::log($handle, "CALLBACK", "订单金额不匹配,接受数据:" . $json); + throw new JSONException("amount error"); + } + //第三方支付订单成功,累计充值 + if ($order->owner != 0 && $owner = User::query()->find($order->owner)) { + //累计充值 + $owner->recharge = $owner->recharge + $order->amount; + $owner->save(); + } + $this->orderSuccess($order); + }); + return $callback['success']; + } + + /** + * @param User|null $user + * @param UserGroup|null $userGroup + * @param int $cardId + * @param int $num + * @param string $coupon + * @param int|Commodity|null $commodityId + * @param string|null $race + * @param bool $disableShared + * @return array + * @throws JSONException + */ + #[ArrayShape(["amount" => "mixed", "price" => "float|int", "couponMoney" => "float|int"])] public function getTradeAmount(?User $user, ?UserGroup $userGroup, int $cardId, int $num, string $coupon, int|Commodity|null $commodityId, ?string $race = null, bool $disableShared = false): array + { + if ($num <= 0) { + throw new JSONException("购买数量不能低于1个"); + } + + if ($commodityId instanceof Commodity) { + $commodity = $commodityId; + } else { + $commodity = Commodity::query()->find($commodityId); + } + + if (!$commodity) { + throw new JSONException("商品不存在"); + } + if ($commodity->status != 1) { + throw new JSONException("当前商品已停售"); + } + + $data = []; + + if ($commodity->delivery_way == 0 && ($commodity->shared_id == null || $commodity->shared_id == 0)) { + if ($race) { + $data['card_count'] = Card::query()->where("commodity_id", $commodity->id)->where("status", 0)->where("race", $race)->count(); + } + } elseif ($commodity->shared_id != 0) { + //查远程平台的库存 + $shared = \App\Model\Shared::query()->find($commodity->shared_id); + if ($shared && !$disableShared) { + $inventory = $this->shared->inventory($shared, $commodity, (string)$race); + $data['card_count'] = $inventory['count']; + } + } + + //检测限购数量 + if ($commodity->minimum != 0 && $num < $commodity->minimum) { + throw new JSONException("本商品单次最少购买{$commodity->minimum}个"); + } + + if ($commodity->maximum != 0 && $num > $commodity->maximum) { + throw new JSONException("本商品单次最多购买{$commodity->maximum}个"); + } + + if ($cardId != 0 && $commodity->draft_status == 1) { + $num = 1; + } + + $ow = 0; + if ($user) { + $ow = $user->id; + } + $amount = $this->calcAmount($ow, $num, $commodity, $userGroup, $race); + if ($cardId != 0 && $commodity->draft_status == 1) { + $amount = $amount + $commodity->draft_premium; + } + + $couponMoney = 0; + //优惠卷 + $price = $amount / $num; + if ($coupon != "") { + $voucher = Coupon::query()->where("code", $coupon)->first(); + + if (!$voucher) { + throw new JSONException("该优惠卷不存在"); + } + + if ($voucher->owner != $commodity->owner) { + throw new JSONException("该优惠卷不存在"); + } + + if ($race && $voucher->commodity_id != 0) { + if ($race != $voucher->race) { + throw new JSONException("该优惠卷不能抵扣当前商品"); + } + } + + if ($voucher->commodity_id != 0 && $voucher->commodity_id != $commodity->id) { + throw new JSONException("该优惠卷不属于该商品"); + } + + //判断该优惠卷是否有分类设定 + if ($voucher->commodity_id == 0 && $voucher->category_id != 0 && $voucher->category_id != $commodity->category_id) { + throw new JSONException("该优惠卷不能抵扣当前商品"); + } + + if ($voucher->status != 0) { + throw new JSONException("该优惠卷已失效"); + } + + //检测过期时间 + if ($voucher->expire_time != null && strtotime($voucher->expire_time) < time()) { + throw new JSONException("该优惠卷已过期"); + } + + //检测面额 + if ($voucher->money >= $amount) { + throw new JSONException("该优惠卷面额大于订单金额"); + } + + $deduction = $voucher->mode == 0 ? $voucher->money : $price * $voucher->money; + $amount = $amount - $deduction; + $couponMoney = $deduction; + } + + + $data ['amount'] = sprintf("%.2f", (int)(string)($amount * 100) / 100); + $data ['price'] = sprintf("%.2f", (int)(string)($price * 100) / 100); + $data ['couponMoney'] = sprintf("%.2f", (int)(string)($couponMoney * 100) / 100); + + return $data; + } + + + /** + * @param Commodity $commodity + * @param string $race + * @param int $num + * @param string $contact + * @param string $password + * @param int|null $cardId + * @param int $userId + * @param string $widget + * @return array + * @throws JSONException + */ + public function giftOrder(Commodity $commodity, string $race = "", int $num = 1, string $contact = "", string $password = "", ?int $cardId = null, int $userId = 0, string $widget = "[]"): array + { + return DB::transaction(function () use ($race, $widget, $contact, $password, $num, $cardId, $commodity, $userId) { + //创建订单 + $date = Date::current(); + $order = new \App\Model\Order(); + $order->owner = $userId; + $order->trade_no = Str::generateTradeNo(); + $order->amount = 0; + $order->commodity_id = $commodity->id; + $order->card_id = $cardId; + $order->card_num = $num; + $order->pay_id = 1; + $order->create_time = $date; + $order->create_ip = Client::getAddress(); + $order->create_device = 0; + $order->status = 0; + $order->password = $password; + $order->contact = trim($contact); + $order->delivery_status = 0; + $order->widget = $widget; + $order->rent = 0; + $order->race = $race; + $order->user_id = $commodity->owner; + $order->save(); + $secret = $this->orderSuccess($order); + return [ + "secret" => $secret, + "tradeNo" => $order->trade_no + ]; + }); + } +} \ No newline at end of file diff --git a/readme/acg-faka-plugin-Demo.zip b/readme/acg-faka-plugin-Demo.zip new file mode 100644 index 00000000..bcb7b0d8 Binary files /dev/null and b/readme/acg-faka-plugin-Demo.zip differ diff --git a/src/linker.libs/Crypto.cs b/src/linker.libs/Crypto.cs index 0b6a706b..8554789c 100644 --- a/src/linker.libs/Crypto.cs +++ b/src/linker.libs/Crypto.cs @@ -11,9 +11,9 @@ namespace linker.libs /// /// /// - public static ISymmetricCrypto CreateSymmetric(string password) + public static ISymmetricCrypto CreateSymmetric(string password, PaddingMode mode = PaddingMode.ANSIX923) { - return new AesCrypto(password); + return new AesCrypto(password, mode); } } @@ -38,11 +38,11 @@ namespace linker.libs public string Password { get; set; } - public AesCrypto(string password) + public AesCrypto(string password, PaddingMode mode = PaddingMode.ANSIX923) { Password = password; using Aes aes = Aes.Create(); - aes.Padding = PaddingMode.ANSIX923; + aes.Padding = mode; (aes.Key, aes.IV) = GenerateKeyAndIV(password); encryptoTransform = aes.CreateEncryptor(aes.Key, aes.IV); diff --git a/src/linker.messenger.relay/client/RelayApiController.cs b/src/linker.messenger.relay/client/RelayApiController.cs index 15950515..bc79e216 100644 --- a/src/linker.messenger.relay/client/RelayApiController.cs +++ b/src/linker.messenger.relay/client/RelayApiController.cs @@ -68,7 +68,7 @@ namespace linker.messenger.relay }); return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); } - + [Access(AccessValue.RelayCdkey)] public async Task AddCdkey(ApiControllerParamsInfo param) { @@ -97,6 +97,7 @@ namespace linker.messenger.relay Payload = serializer.Serialize(new RelayServerCdkeyDelInfo { CdkeyId = long.Parse(param.Content), + UserId = signInClientStore.Server.UserId, SecretKey = relayClientStore.Server.SecretKey }) }); @@ -141,6 +142,39 @@ namespace linker.messenger.relay return new RelayServerCdkeyPageResultInfo(); } + + public async Task TestCdkey(ApiControllerParamsInfo param) + { + RelayServerCdkeyImportInfo info = param.Content.DeJson(); + info.SecretKey = relayClientStore.Server.SecretKey; + info.UserId = signInClientStore.Server.UserId; + var resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)RelayMessengerIds.TestCdkey, + Payload = serializer.Serialize(info) + }); + if (resp.Code == MessageResponeCodes.OK) + { + return serializer.Deserialize(resp.Data.Span); + } + + return new RelayServerCdkeyTestResultInfo(); + } + + public async Task ImportCdkey(ApiControllerParamsInfo param) + { + RelayServerCdkeyImportInfo info = param.Content.DeJson(); + info.SecretKey = relayClientStore.Server.SecretKey; + info.UserId = signInClientStore.Server.UserId; + var resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)RelayMessengerIds.ImportCdkey, + Payload = serializer.Serialize(info) + }); + return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); + } } public sealed class RelayConnectInfo diff --git a/src/linker.messenger.relay/messenger/RelayMessenger.cs b/src/linker.messenger.relay/messenger/RelayMessenger.cs index 326a7abb..f3bf1928 100644 --- a/src/linker.messenger.relay/messenger/RelayMessenger.cs +++ b/src/linker.messenger.relay/messenger/RelayMessenger.cs @@ -177,90 +177,6 @@ namespace linker.messenger.relay.messenger } - /// - /// 检查权限 - /// - /// - /// - [MessengerId((ushort)RelayMessengerIds.AccessCdkey)] - public void AccessCdkey(IConnection connection) - { - string secretKey = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); - connection.Write(relayServerStore.SecretKey == secretKey ? Helper.TrueArray : Helper.FalseArray); - } - /// - /// 添加CDKEY - /// - /// - [MessengerId((ushort)RelayMessengerIds.AddCdkey)] - public async Task AddCdkey(IConnection connection) - { - RelayServerCdkeyAddInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); - if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) - { - connection.Write(Helper.FalseArray); - return; - } - if (relayServerStore.SecretKey != info.SecretKey) - { - connection.Write(Helper.FalseArray); - return; - } - - await relayServerCdkeyStore.Add(info.Data); - connection.Write(Helper.TrueArray); - } - - /// - /// 删除Cdkey - /// - /// - /// - [MessengerId((ushort)RelayMessengerIds.DelCdkey)] - public async Task DelCdkey(IConnection connection) - { - RelayServerCdkeyDelInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); - if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) - { - connection.Write(Helper.FalseArray); - return; - } - if (relayServerStore.SecretKey != info.SecretKey) - { - connection.Write(Helper.FalseArray); - return; - } - - await relayServerCdkeyStore.Del(info.CdkeyId); - connection.Write(Helper.TrueArray); - } - - /// - /// 查询CDKEY - /// - /// - /// - [MessengerId((ushort)RelayMessengerIds.PageCdkey)] - public async Task PageCdkey(IConnection connection) - { - RelayServerCdkeyPageRequestInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); - if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) - { - connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { })); - return; - } - if (relayServerStore.SecretKey != info.SecretKey && string.IsNullOrWhiteSpace(info.UserId)) - { - connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { })); - return; - } - - var page = await relayServerCdkeyStore.Get(info); - - connection.Write(serializer.Serialize(page)); - } - - /// /// 获取缓存 /// @@ -311,5 +227,132 @@ namespace linker.messenger.relay.messenger Dictionary result = await relayServerTransfer.AddTraffic(info); connection.Write(serializer.Serialize(result)); } + + + /// + /// 检查权限 + /// + /// + /// + [MessengerId((ushort)RelayMessengerIds.AccessCdkey)] + public void AccessCdkey(IConnection connection) + { + string secretKey = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + connection.Write(relayServerStore.SecretKey == secretKey ? Helper.TrueArray : Helper.FalseArray); + } + /// + /// 添加CDKEY + /// + /// + [MessengerId((ushort)RelayMessengerIds.AddCdkey)] + public async Task AddCdkey(IConnection connection) + { + RelayServerCdkeyAddInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) + { + connection.Write(Helper.FalseArray); + return; + } + if (relayServerStore.SecretKey != info.SecretKey) + { + connection.Write(Helper.FalseArray); + return; + } + + await relayServerCdkeyStore.Add(info.Data); + connection.Write(Helper.TrueArray); + } + + /// + /// 删除Cdkey + /// + /// + /// + [MessengerId((ushort)RelayMessengerIds.DelCdkey)] + public async Task DelCdkey(IConnection connection) + { + RelayServerCdkeyDelInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) + { + connection.Write(Helper.FalseArray); + return; + } + if (relayServerStore.SecretKey == info.SecretKey) + { + await relayServerCdkeyStore.Del(info.CdkeyId); + } + else + { + await relayServerCdkeyStore.Del(info.CdkeyId,info.UserId); + } + connection.Write(Helper.TrueArray); + } + + /// + /// 查询CDKEY + /// + /// + /// + [MessengerId((ushort)RelayMessengerIds.PageCdkey)] + public async Task PageCdkey(IConnection connection) + { + RelayServerCdkeyPageRequestInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) + { + connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { })); + return; + } + if (relayServerStore.SecretKey != info.SecretKey && string.IsNullOrWhiteSpace(info.UserId)) + { + connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { })); + return; + } + + var page = await relayServerCdkeyStore.Page(info); + + connection.Write(serializer.Serialize(page)); + } + + + /// + /// 测试cdkey + /// + /// + /// + [MessengerId((ushort)RelayMessengerIds.TestCdkey)] + public async Task TestCdkey(IConnection connection) + { + RelayServerCdkeyImportInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) + { + connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { })); + return; + } + if (relayServerStore.SecretKey != info.SecretKey) + { + connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { })); + return; + } + RelayServerCdkeyTestResultInfo test = await relayServerCdkeyStore.Test(info); + connection.Write(serializer.Serialize(test)); + } + + /// + /// 导入cdkey + /// + /// + /// + [MessengerId((ushort)RelayMessengerIds.ImportCdkey)] + public async Task ImportCdkey(IConnection connection) + { + RelayServerCdkeyImportInfo info = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false) + { + connection.Write(Helper.FalseArray); + return; + } + bool result = await relayServerCdkeyStore.Import(info); + connection.Write(result ? Helper.TrueArray : Helper.FalseArray); + } } } diff --git a/src/linker.messenger.relay/messenger/RelayMessengerIds.cs b/src/linker.messenger.relay/messenger/RelayMessengerIds.cs index 1ff00617..7d2b5bef 100644 --- a/src/linker.messenger.relay/messenger/RelayMessengerIds.cs +++ b/src/linker.messenger.relay/messenger/RelayMessengerIds.cs @@ -25,6 +25,9 @@ NodeReport = 2113, TrafficReport = 2114, + TestCdkey = 2115, + ImportCdkey = 2116, + Max = 2199 } } diff --git a/src/linker.messenger.relay/server/IRelayServerCdkeyStore.cs b/src/linker.messenger.relay/server/IRelayServerCdkeyStore.cs index f7df8103..78db33fb 100644 --- a/src/linker.messenger.relay/server/IRelayServerCdkeyStore.cs +++ b/src/linker.messenger.relay/server/IRelayServerCdkeyStore.cs @@ -2,8 +2,38 @@ { public interface IRelayServerCdkeyStore { + /// + /// 添加 + /// + /// + /// public Task Add(RelayServerCdkeyStoreInfo info); + /// + /// 删除 + /// + /// + /// public Task Del(long id); + /// + /// 删除 + /// + /// + /// + /// + public Task Del(long id, string userid); + + /// + /// 测试卡密是否可用 + /// + /// + /// + public Task Test(RelayServerCdkeyImportInfo info); + /// + /// 导入卡密 + /// + /// + /// + public Task Import(RelayServerCdkeyImportInfo info); /// /// 获取有效的CDKEY @@ -11,30 +41,32 @@ /// /// public Task> GetAvailable(string userid); + /// + /// 获取CDKEY列表 + /// + /// + /// public Task> Get(List ids); - + /// + /// 消耗流量 + /// + /// + /// public Task Traffic(Dictionary dic); - public Task Get(RelayServerCdkeyPageRequestInfo relayServerCdkeyPageRequestInfo); + /// + /// 分页 + /// + /// + /// + public Task Page(RelayServerCdkeyPageRequestInfo relayServerCdkeyPageRequestInfo); } public sealed class RelayServerCdkeyConfigInfo { /// - /// 获取可用的CDKEY + /// 加解密密钥 /// - public string CdkeyAvailablePostUrl { get; set; } = string.Empty; - /// - /// 分页获取CDKEY - /// - public string CdkeyPagePostUrl { get; set; } = string.Empty; - /// - /// id列表获取CDKEY - /// - public string CdkeyListPostUrl { get; set; } = string.Empty; - /// - /// 报告流量websocket - /// - public string CdkeyTrafficWsUrl { get; set; } = string.Empty; + public string SecretKey { get; set; } = Guid.NewGuid().ToString().ToUpper(); } public sealed partial class RelayServerCdkeyPageRequestInfo @@ -45,8 +77,23 @@ public string Sort { get; set; } public string UserId { get; set; } public string Remark { get; set; } + public string OrderId { get; set; } + public string Contact { get; set; } public string SecretKey { get; set; } + public RelayServerCdkeyPageRequestFlag Flag { get; set; } } + public enum RelayServerCdkeyPageRequestFlag + { + All = 0, + TimeIn = 1, + TimeOut = 2, + BytesIn = 4, + BytesOut = 8, + UnDeleted = 16, + Deleted = 32, + } + + public sealed partial class RelayServerCdkeyPageResultInfo { public int Page { get; set; } @@ -63,6 +110,7 @@ public sealed partial class RelayServerCdkeyDelInfo { public string SecretKey { get; set; } + public string UserId { get; set; } public long CdkeyId { get; set; } } @@ -77,11 +125,6 @@ /// 用户标识 /// public string UserId { get; set; } - - /// - /// KEY - /// - public string CdKey { get; set; } /// /// 添加时间 /// @@ -98,26 +141,112 @@ /// 最后使用时间 /// public DateTime UseTime { get; set; } - /// - /// 允许节点 - /// - public List Nodes { get; set; } + /// /// 流量 /// public long MaxBytes { get; set; } + /// + /// 成本价 + /// + public double CostPrice { get; set; } /// /// 原价 /// - public double Memory { get; set; } + public double Price { get; set; } + /// + /// 会员价 + /// + public double UserPrice { get; set; } /// /// 支付金额 /// - public double PayMemory { get; set; } + public double PayPrice { get; set; } /// /// 备注 /// public string Remark { get; set; } + + /// + /// 订单号 + /// + public string OrderId { get; set; } + /// + /// 联系方式 + /// + public string Contact { get; set; } + /// + /// 已删除 + /// + public bool Deleted { get; set; } + } + + public sealed partial class RelayServerCdkeyTestResultInfo + { + public RelayServerCdkeyOrderInfo Order { get; set; } + public string Cdkey { get; set; } + public List Field { get; set; } = new List(); + } + /// + /// 导入中继cdkey + /// + public sealed partial class RelayServerCdkeyImportInfo + { + public string SecretKey { get; set; } + public string UserId { get; set; } + public string Base64 { get; set; } + } + /// + /// 导入中继cdkey + /// + public sealed partial class RelayServerCdkeyOrderInfo + { + /// + /// 总流量 + /// + public int GB { get; set; } + /// + /// 带宽 + /// + public int Speed { get; set; } + /// + /// 有效年 + /// + public string Time { get; set; } + + /// + /// 用户标识 + /// + public string WidgetUserId { get; set; } + /// + /// 订单号 + /// + public string OrderId { get; set; } + /// + /// 联系方式 + /// + public string Contact { get; set; } + /// + /// 成本价 + /// + public double CostPrice { get; set; } + /// + /// 原价 + /// + public double Price { get; set; } + /// + /// 会员价 + /// + public double UserPrice { get; set; } + /// + /// 支付金额 + /// + public double PayPrice { get; set; } + /// + /// 数量 + /// + public int Count { get; set; } + } } diff --git a/src/linker.messenger.relay/server/RelayServerNodeTransfer.cs b/src/linker.messenger.relay/server/RelayServerNodeTransfer.cs index be0d78ca..96dc736d 100644 --- a/src/linker.messenger.relay/server/RelayServerNodeTransfer.cs +++ b/src/linker.messenger.relay/server/RelayServerNodeTransfer.cs @@ -183,7 +183,8 @@ namespace linker.messenger.relay.server Interlocked.Add(ref cache.Sendt, length); - if (cache.CurrentCdkey != null) return cache.CurrentCdkey.LastBytes > 0; + var current = cache.CurrentCdkey; + if (current != null) return current.LastBytes > 0; return relayServerNodeStore.Node.MaxGbTotalLastBytes > 0; } @@ -210,6 +211,7 @@ namespace linker.messenger.relay.server return; } + relayCache.CurrentCdkey = null; relayCache.Limit.SetLimit((uint)Math.Ceiling((relayServerNodeStore.Node.MaxBandwidth * 1024 * 1024) / 8.0)); } diff --git a/src/linker.messenger.serializer.memorypack/Entry.cs b/src/linker.messenger.serializer.memorypack/Entry.cs index c83a0c59..4d748e06 100644 --- a/src/linker.messenger.serializer.memorypack/Entry.cs +++ b/src/linker.messenger.serializer.memorypack/Entry.cs @@ -61,8 +61,11 @@ namespace linker.messenger.serializer.memorypack MemoryPackFormatterProvider.Register(new RelayServerCdkeyPageResultInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayServerCdkeyAddInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayServerCdkeyDelInfoFormatter()); - MemoryPackFormatterProvider.Register(new RelayTrafficReportInfoFormatter()); - + MemoryPackFormatterProvider.Register(new RelayTrafficUpdateInfoFormatter()); + MemoryPackFormatterProvider.Register(new RelayServerCdkeyImportInfoFormatter()); + MemoryPackFormatterProvider.Register(new RelayServerCdkeyTestResultInfoFormatter()); + MemoryPackFormatterProvider.Register(new RelayServerCdkeyOrderInfoFormatter()); + MemoryPackFormatterProvider.Register(new AccessUpdateInfoFormatter()); MemoryPackFormatterProvider.Register(new AccessInfoFormatter()); diff --git a/src/linker.messenger.serializer.memorypack/RelaySerializer.cs b/src/linker.messenger.serializer.memorypack/RelaySerializer.cs index 80d41cd4..fb5381a4 100644 --- a/src/linker.messenger.serializer.memorypack/RelaySerializer.cs +++ b/src/linker.messenger.serializer.memorypack/RelaySerializer.cs @@ -510,14 +510,16 @@ namespace linker.messenger.serializer.memorypack [MemoryPackInclude] long CdkeyId => info.CdkeyId; + [MemoryPackInclude] + double Bandwidth => info.Bandwidth; + [MemoryPackInclude] + long LastBytes => info.LastBytes; + [MemoryPackInclude] string Id => info.Id; [MemoryPackInclude] string UserId => info.UserId; - - [MemoryPackInclude] - string CdKey => info.CdKey; [MemoryPackInclude] DateTime AddTime => info.AddTime; [MemoryPackInclude] @@ -526,42 +528,50 @@ namespace linker.messenger.serializer.memorypack DateTime EndTime => info.EndTime; [MemoryPackInclude] DateTime UseTime => info.UseTime; - [MemoryPackInclude] - List Nodes => info.Nodes; - [MemoryPackInclude] - double Bandwidth => info.Bandwidth; + [MemoryPackInclude] long MaxBytes => info.MaxBytes; + [MemoryPackInclude] - long LastBytes => info.LastBytes; + double CostPrice => info.CostPrice; [MemoryPackInclude] - double Memory => info.Memory; + double Price => info.Price; [MemoryPackInclude] - double PayMemory => info.PayMemory; + double UserPrice => info.UserPrice; + [MemoryPackInclude] + double PayPrice => info.PayPrice; [MemoryPackInclude] string Remark => info.Remark; + [MemoryPackInclude] + string OrderId => info.OrderId; + [MemoryPackInclude] + string Contact => info.Contact; + [MemoryPackInclude] + bool Deleted => info.Deleted; [MemoryPackConstructor] - SerializableRelayServerCdkeyStoreInfo(long cdkeyid, string id, string userid, string cdKey, DateTime addTime, DateTime startTime, DateTime endTime, DateTime useTime, - List nodes, double bandwidth, long maxBytes, long lastBytes, double memory, double payMemory, string remark) + SerializableRelayServerCdkeyStoreInfo(long cdkeyid, double bandwidth, long lastBytes, string id, string userid, DateTime addTime, DateTime startTime, DateTime endTime, DateTime useTime, long maxBytes, double costPrice, double price, double userPrice, double payPrice, string remark, string orderId, string contact, bool deleted) { var info = new RelayServerCdkeyStoreInfo { CdkeyId = cdkeyid, + Bandwidth = bandwidth, + LastBytes = lastBytes, Id = id, UserId = userid, - CdKey = cdKey, AddTime = addTime, StartTime = startTime, EndTime = endTime, UseTime = useTime, - Nodes = nodes, - Bandwidth = bandwidth, MaxBytes = maxBytes, - LastBytes = lastBytes, - Memory = memory, - PayMemory = payMemory, - Remark = remark + CostPrice = costPrice, + Price = price, + UserPrice = userPrice, + PayPrice = payPrice, + Remark = remark, + OrderId = orderId, + Contact = contact, + Deleted = deleted }; this.info = info; } @@ -664,14 +674,17 @@ namespace linker.messenger.serializer.memorypack [MemoryPackInclude] string SecretKey => info.SecretKey; [MemoryPackInclude] + string UserId => info.UserId; + [MemoryPackInclude] long CdkeyId => info.CdkeyId; [MemoryPackConstructor] - SerializableRelayServerCdkeyDelInfo(string secretKey, long cdkeyid) + SerializableRelayServerCdkeyDelInfo(string secretKey, string userid, long cdkeyid) { var info = new RelayServerCdkeyDelInfo { SecretKey = secretKey, + UserId = userid, CdkeyId = cdkeyid }; this.info = info; @@ -730,10 +743,16 @@ namespace linker.messenger.serializer.memorypack [MemoryPackInclude] string Remark => info.Remark; [MemoryPackInclude] + string OrderId => info.OrderId; + [MemoryPackInclude] + string Contact => info.Contact; + [MemoryPackInclude] string SecretKey => info.SecretKey; + [MemoryPackInclude] + RelayServerCdkeyPageRequestFlag Flag => info.Flag; [MemoryPackConstructor] - SerializableRelayServerCdkeyPageRequestInfo(int page, int size, string order, string sort, string userid, string remark, string secretKey) + SerializableRelayServerCdkeyPageRequestInfo(int page, int size, string order, string sort, string userid, string remark, string orderid, string contact, string secretKey,RelayServerCdkeyPageRequestFlag flag) { var info = new RelayServerCdkeyPageRequestInfo { @@ -743,7 +762,10 @@ namespace linker.messenger.serializer.memorypack Page = page, UserId = userid, Remark = remark, - SecretKey = secretKey + OrderId = orderid, + Contact = contact, + SecretKey = secretKey, + Flag = flag }; this.info = info; } @@ -845,7 +867,7 @@ namespace linker.messenger.serializer.memorypack [MemoryPackable] - public readonly partial struct SerializableRelayTrafficReportInfo + public readonly partial struct SerializableRelayTrafficUpdateInfo { [MemoryPackIgnore] public readonly RelayTrafficUpdateInfo info; @@ -858,7 +880,7 @@ namespace linker.messenger.serializer.memorypack string SecretKey => info.SecretKey; [MemoryPackConstructor] - SerializableRelayTrafficReportInfo(Dictionary dic, List ids, string secretKey) + SerializableRelayTrafficUpdateInfo(Dictionary dic, List ids, string secretKey) { var info = new RelayTrafficUpdateInfo { @@ -869,12 +891,12 @@ namespace linker.messenger.serializer.memorypack this.info = info; } - public SerializableRelayTrafficReportInfo(RelayTrafficUpdateInfo info) + public SerializableRelayTrafficUpdateInfo(RelayTrafficUpdateInfo info) { this.info = info; } } - public class RelayTrafficReportInfoFormatter : MemoryPackFormatter + public class RelayTrafficUpdateInfoFormatter : MemoryPackFormatter { public override void Serialize(ref MemoryPackWriter writer, scoped ref RelayTrafficUpdateInfo value) { @@ -884,7 +906,7 @@ namespace linker.messenger.serializer.memorypack return; } - writer.WritePackable(new SerializableRelayTrafficReportInfo(value)); + writer.WritePackable(new SerializableRelayTrafficUpdateInfo(value)); } public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayTrafficUpdateInfo value) @@ -896,10 +918,208 @@ namespace linker.messenger.serializer.memorypack return; } - var wrapped = reader.ReadPackable(); + var wrapped = reader.ReadPackable(); value = wrapped.info; } } + + [MemoryPackable] + public readonly partial struct SerializableRelayServerCdkeyImportInfo + { + [MemoryPackIgnore] + public readonly RelayServerCdkeyImportInfo info; + + [MemoryPackInclude] + string SecretKey => info.SecretKey; + [MemoryPackInclude] + string UserId => info.UserId; + [MemoryPackInclude] + string Base64 => info.Base64; + + [MemoryPackConstructor] + SerializableRelayServerCdkeyImportInfo(string secretKey, string userid, string base64) + { + var info = new RelayServerCdkeyImportInfo + { + SecretKey = secretKey, + UserId = userid, + Base64 = base64 + }; + this.info = info; + } + + public SerializableRelayServerCdkeyImportInfo(RelayServerCdkeyImportInfo info) + { + this.info = info; + } + } + public class RelayServerCdkeyImportInfoFormatter : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref RelayServerCdkeyImportInfo value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableRelayServerCdkeyImportInfo(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyImportInfo value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.info; + } + } + + + [MemoryPackable] + public readonly partial struct SerializableRelayServerCdkeyTestResultInfo + { + [MemoryPackIgnore] + public readonly RelayServerCdkeyTestResultInfo info; + + [MemoryPackInclude, MemoryPackAllowSerialize] + RelayServerCdkeyOrderInfo Order => info.Order; + [MemoryPackInclude] + string Cdkey => info.Cdkey; + [MemoryPackInclude] + List Field => info.Field; + + [MemoryPackConstructor] + SerializableRelayServerCdkeyTestResultInfo(RelayServerCdkeyOrderInfo order, string cdkey, List field) + { + var info = new RelayServerCdkeyTestResultInfo + { + Order = order, + Cdkey = cdkey, + Field = field + }; + this.info = info; + } + + public SerializableRelayServerCdkeyTestResultInfo(RelayServerCdkeyTestResultInfo info) + { + this.info = info; + } + } + public class RelayServerCdkeyTestResultInfoFormatter : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref RelayServerCdkeyTestResultInfo value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableRelayServerCdkeyTestResultInfo(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyTestResultInfo value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.info; + } + } + + [MemoryPackable] + public readonly partial struct SerializableRelayServerCdkeyOrderInfo + { + [MemoryPackIgnore] + public readonly RelayServerCdkeyOrderInfo info; + + [MemoryPackInclude] + int GB => info.GB; + [MemoryPackInclude] + int Speed => info.Speed; + [MemoryPackInclude] + string Time => info.Time; + [MemoryPackInclude] + string WidgetUserId => info.WidgetUserId; + [MemoryPackInclude] + string OrderId => info.OrderId; + [MemoryPackInclude] + string Contact => info.Contact; + + [MemoryPackInclude] + double CostPrice => info.CostPrice; + [MemoryPackInclude] + double Price => info.Price; + [MemoryPackInclude] + double UserPrice => info.UserPrice; + [MemoryPackInclude] + double PayPrice => info.PayPrice; + [MemoryPackInclude] + int Count => info.Count; + + + [MemoryPackConstructor] + SerializableRelayServerCdkeyOrderInfo(int gb, int speed, string time, string widgetUserId, string orderId, string contact, double costPrice, double price, double userPrice, double payPrice, int count) + { + var info = new RelayServerCdkeyOrderInfo + { + GB = gb, + Speed = speed, + Time = time, + WidgetUserId = widgetUserId, + OrderId = orderId, + Contact = contact, + CostPrice = costPrice, + Price = price, + UserPrice = userPrice, + PayPrice = payPrice, + Count = count + }; + this.info = info; + } + + public SerializableRelayServerCdkeyOrderInfo(RelayServerCdkeyOrderInfo info) + { + this.info = info; + } + } + public class RelayServerCdkeyOrderInfoFormatter : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref RelayServerCdkeyOrderInfo value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableRelayServerCdkeyOrderInfo(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyOrderInfo value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.info; + } + } } diff --git a/src/linker.messenger.store.file/relay/RelayServerCdkeyStore.cs b/src/linker.messenger.store.file/relay/RelayServerCdkeyStore.cs index 14c5a7b7..53ce4a57 100644 --- a/src/linker.messenger.store.file/relay/RelayServerCdkeyStore.cs +++ b/src/linker.messenger.store.file/relay/RelayServerCdkeyStore.cs @@ -1,17 +1,25 @@ -using linker.messenger.relay.server; +using linker.libs; +using linker.libs.extends; +using linker.messenger.relay.server; using LiteDB; +using System.Text; +using System.Text.RegularExpressions; using Yitter.IdGenerator; namespace linker.messenger.store.file.relay { public sealed class RelayServerCdkeyStore : IRelayServerCdkeyStore { + private string regex = @"([0-9]+)-([0-9]+)-([0-9]+)\s+([0-9]+):([0-9]+):([0-9]+)"; + private readonly Storefactory dBfactory; private readonly ILiteCollection liteCollection; + private readonly ICrypto crypto; public RelayServerCdkeyStore(Storefactory dBfactory, FileConfig fileConfig) { this.dBfactory = dBfactory; liteCollection = dBfactory.GetCollection("relayCdkey"); + this.crypto = CryptoFactory.CreateSymmetric(fileConfig.Data.Server.Relay.Cdkey.SecretKey, System.Security.Cryptography.PaddingMode.PKCS7); } public async Task Add(RelayServerCdkeyStoreInfo info) @@ -19,11 +27,11 @@ namespace linker.messenger.store.file.relay if (string.IsNullOrWhiteSpace(info.Id)) { info.Id = ObjectId.NewObjectId().ToString(); - info.CdKey = Guid.NewGuid().ToString().ToUpper(); info.AddTime = DateTime.Now; info.UseTime = DateTime.Now; info.LastBytes = info.MaxBytes; info.CdkeyId = YitIdHelper.NextId(); + info.OrderId = $"Linker{YitIdHelper.NextId()}"; liteCollection.Insert(info); } else @@ -34,7 +42,101 @@ namespace linker.messenger.store.file.relay } public async Task Del(long id) { - return await Task.FromResult(liteCollection.DeleteMany(c => c.CdkeyId == id) > 0); + return await Task.FromResult(liteCollection.UpdateMany(c => new RelayServerCdkeyStoreInfo { Deleted = true }, c => c.CdkeyId == id) > 0); + } + public async Task Del(long id, string userid) + { + return await Task.FromResult(liteCollection.UpdateMany(c => new RelayServerCdkeyStoreInfo { Deleted = true }, c => c.CdkeyId == id && c.UserId == userid) > 0); + } + + public async Task Test(RelayServerCdkeyImportInfo info) + { + List error = new List(); + RelayServerCdkeyTestResultInfo result = new RelayServerCdkeyTestResultInfo(); + + try + { + result.Cdkey = Encoding.UTF8.GetString(crypto.Decode(Convert.FromBase64String(info.Base64)).Span); + RelayServerCdkeyOrderInfo order = result.Cdkey.DeJson(); + result.Order = order; + + if (order.WidgetUserId != info.UserId) + { + error.Add("UserId"); + } + if (order.Speed <= 0) + { + error.Add("Speed"); + } + if (order.GB <= 0) + { + error.Add("GB"); + } + if (order.Count <= 0) + { + error.Add("Count"); + } + if (Regex.IsMatch(order.Time, regex) == false) + { + error.Add("Time"); + } + if (string.IsNullOrWhiteSpace(order.OrderId)) + { + error.Add("OrderId"); + } + } + catch (Exception ex) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Error(ex); + } + error.Add("Parse"); + } + result.Field = error; + + return await Task.FromResult(result); + } + public async Task Import(RelayServerCdkeyImportInfo info) + { + RelayServerCdkeyTestResultInfo test = await Test(info); + if (test.Field.Count > 0) + { + return false; + } + RelayServerCdkeyOrderInfo order = test.Order; + var time = Regex.Match(order.Time, regex).Groups; + for (int i = 0; i < order.Count; i++) + { + RelayServerCdkeyStoreInfo store = new RelayServerCdkeyStoreInfo + { + UseTime = DateTime.Now, + AddTime = DateTime.Now, + Bandwidth = order.Speed, + CostPrice = order.CostPrice, + EndTime = DateTime.Now + .AddYears(int.Parse(time[1].Value)) + .AddMonths(int.Parse(time[2].Value)) + .AddDays(int.Parse(time[3].Value)) + .AddHours(int.Parse(time[4].Value)) + .AddMinutes(int.Parse(time[5].Value)) + .AddSeconds(int.Parse(time[6].Value)), + LastBytes = order.Speed * 1024 * 1024 * 1024, + MaxBytes = order.Speed * 1024 * 1024 * 1024, + Price = order.Price, + Remark = "order", + StartTime = DateTime.Now, + UserId = order.WidgetUserId, + CdkeyId = YitIdHelper.NextId(), + Contact = order.Contact, + OrderId = order.OrderId, + PayPrice = order.PayPrice, + UserPrice = order.UserPrice, + Id = ObjectId.NewObjectId().ToString() + }; + liteCollection.Insert(store); + } + return await Task.FromResult(true); } public async Task Traffic(Dictionary dic) @@ -53,25 +155,42 @@ namespace linker.messenger.store.file.relay public async Task> GetAvailable(string userid) { - return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime < DateTime.Now).ToList()); + return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime < DateTime.Now && x.Deleted == false).ToList()); } public async Task> Get(List ids) { return await Task.FromResult(liteCollection.Find(x => ids.Contains(x.CdkeyId)).ToList()); } - public async Task Get(RelayServerCdkeyPageRequestInfo info) + public async Task Page(RelayServerCdkeyPageRequestInfo info) { ILiteQueryable query = liteCollection.Query(); - if (string.IsNullOrWhiteSpace(info.Order) == false) + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.TimeIn)) { - query = query.OrderBy(info.Order, info.Sort == "asc" ? Query.Ascending : Query.Descending); + query = query.Where(x => x.EndTime > DateTime.Now); } - else + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.TimeOut)) { - query = query.OrderBy(c => c.CdkeyId, Query.Descending); + query = query.Where(x => x.EndTime < DateTime.Now); } + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.BytesIn)) + { + query = query.Where(x => x.LastBytes > 0); + } + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.BytesOut)) + { + query = query.Where(x => x.LastBytes <= 0); + } + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.Deleted)) + { + query = query.Where(x => x.Deleted == true); + } + if (info.Flag.HasFlag(RelayServerCdkeyPageRequestFlag.UnDeleted)) + { + query = query.Where(x => x.Deleted == false); + } + if (string.IsNullOrWhiteSpace(info.UserId) == false) { query = query.Where(x => x.UserId == info.UserId); @@ -80,6 +199,22 @@ namespace linker.messenger.store.file.relay { query = query.Where(x => x.Remark.Contains(info.Remark)); } + if (string.IsNullOrWhiteSpace(info.OrderId) == false) + { + query = query.Where(x => x.OrderId.Contains(info.OrderId)); + } + if (string.IsNullOrWhiteSpace(info.Contact) == false) + { + query = query.Where(x => x.Contact.Contains(info.Contact)); + } + if (string.IsNullOrWhiteSpace(info.Order) == false) + { + query = query.OrderBy(info.Order, info.Sort == "asc" ? Query.Ascending : Query.Descending); + } + else + { + query = query.OrderBy(c => c.CdkeyId, Query.Descending); + } return await Task.FromResult(new RelayServerCdkeyPageResultInfo { diff --git a/src/linker.web/src/apis/relay.js b/src/linker.web/src/apis/relay.js index 242a1e14..4e41058f 100644 --- a/src/linker.web/src/apis/relay.js +++ b/src/linker.web/src/apis/relay.js @@ -20,4 +20,7 @@ export const relayCdkeyAdd = (data) => { } export const relayCdkeyDel = (data) => { return sendWebsocketMsg('relay/DelCdkey', data); +} +export const relayCdkeyMy = (data) => { + return sendWebsocketMsg('relay/MyCdkey', data); } \ No newline at end of file diff --git a/src/linker.web/src/lang/en-us.js b/src/linker.web/src/lang/en-us.js index 5b399b52..1221fb87 100644 --- a/src/linker.web/src/lang/en-us.js +++ b/src/linker.web/src/lang/en-us.js @@ -130,21 +130,34 @@ export default { 'server.relayUse': 'Use', 'server.relayMyCdkey': 'My CDKEY', 'server.relayCdkey': 'Manager CDKEY', + 'server.relayAddCdkey': 'Add CDKEY', 'server.relayCdkeyUserId': 'UserId', 'server.relayCdkeyBandwidth': 'Bandwidth', - 'server.relayCdkeyMaxBytes': 'Total', - 'server.relayCdkeyLastBytes': 'Surplus', - 'server.relayCdkeyMemory': 'Price', - 'server.relayCdkeyPayMemory': 'Pay', + 'server.relayCdkeyBytes': 'Bytes', + 'server.relayCdkeyPay': 'Pay', + 'server.relayCdkeyOrder': 'Order', + 'server.relayCdkeyPrice': 'Price', + 'server.relayCdkeyPayPrice': 'Pay price', + 'server.relayCdkeyCostPrice': 'Cost price', + 'server.relayCdkeyUserPrice': 'User price', + 'server.relayCdkeyOrderId': 'Order No', + 'server.relayCdkeyContact': 'Email', + 'server.relayCdkeyRemark': 'Remark', + 'server.relayCdkeyAddTime': 'Add', 'server.relayCdkeyStartTime': 'Start', + 'server.relayCdkeyEndTime': 'End Time', + 'server.relayCdkeyUseTime': 'Use Time', + 'server.relayCdkeyYear': 'Year', + 'server.relayCdkeyMonth': 'Month', + 'server.relayCdkeyDay': 'Day', + 'server.relayCdkeyHour': 'Hour', + 'server.relayCdkeyMin': 'Min', + 'server.relayCdkeySec': 'Sec', + 'server.relayCdkeyDuration': 'Duration', - 'server.relayCdkeyStartTimeY': 'Year', - 'server.relayCdkeyStartTimeM': 'Month', - 'server.relayCdkeyEndTime': 'End', - 'server.relayCdkeyUseTime': 'Use', - 'server.relayCdkeyRemark': 'Remark', + 'server.relayCdkeyOper': 'Oper', 'server.relayCdkeyDelConfirm': 'Are you sure to delete?', diff --git a/src/linker.web/src/lang/zh-cn.js b/src/linker.web/src/lang/zh-cn.js index 2b482ce7..6216ba28 100644 --- a/src/linker.web/src/lang/zh-cn.js +++ b/src/linker.web/src/lang/zh-cn.js @@ -133,21 +133,34 @@ export default { 'server.relayUse': '使用', 'server.relayMyCdkey': '我的CDKEY', 'server.relayCdkey': '管理CDKEY', + 'server.relayAddCdkey': '添加CDKEY', 'server.relayCdkeyUserId': '用户标识', - 'server.relayCdkeyBandwidth': '最大带宽', - 'server.relayCdkeyMaxBytes': '总流量', - 'server.relayCdkeyLastBytes': '剩余流量', - 'server.relayCdkeyMemory': '原价', - 'server.relayCdkeyPayMemory': '支付', + 'server.relayCdkeyBandwidth': '带宽', + 'server.relayCdkeyBytes': '流量', + 'server.relayCdkeyPay': '支付', + 'server.relayCdkeyPrice': '原价', + 'server.relayCdkeyPayPrice': '支付', + 'server.relayCdkeyCostPrice': '成本', + 'server.relayCdkeyUserPrice': '会员价', + 'server.relayCdkeyOrder': '订单', + 'server.relayCdkeyOrderId': '订单号', + 'server.relayCdkeyContact': '邮箱', + 'server.relayCdkeyRemark': '备注', + 'server.relayCdkeyAddTime': '添加', 'server.relayCdkeyStartTime': '开始', + 'server.relayCdkeyEndTime': '有效时间', + 'server.relayCdkeyUseTime': '最后使用', + 'server.relayCdkeyYear': '年', + 'server.relayCdkeyMonth': '月', + 'server.relayCdkeyDay': '日', + 'server.relayCdkeyHour': '时', + 'server.relayCdkeyMin': '分', + 'server.relayCdkeySec': '秒', + 'server.relayCdkeyDuration': '持续时间', - 'server.relayCdkeyStartTimeY': '年', - 'server.relayCdkeyStartTimeM': '月', - 'server.relayCdkeyEndTime': '结束', - 'server.relayCdkeyUseTime': '使用', - 'server.relayCdkeyRemark': '备注', + 'server.relayCdkeyOper': '操作', 'server.relayCdkeyDelConfirm': '确认删除吗?', diff --git a/src/linker.web/src/views/full/server/relayCdkey/Index.vue b/src/linker.web/src/views/full/server/relayCdkey/Index.vue index d4e62f42..9aa0e022 100644 --- a/src/linker.web/src/views/full/server/relayCdkey/Index.vue +++ b/src/linker.web/src/views/full/server/relayCdkey/Index.vue @@ -1,7 +1,8 @@ diff --git a/src/linker.web/src/views/full/server/relayCdkey/My.vue b/src/linker.web/src/views/full/server/relayCdkey/My.vue new file mode 100644 index 00000000..77a545de --- /dev/null +++ b/src/linker.web/src/views/full/server/relayCdkey/My.vue @@ -0,0 +1,158 @@ + + + + \ No newline at end of file diff --git a/version.txt b/version.txt index 821f3686..5e43fb9e 100644 --- a/version.txt +++ b/version.txt @@ -1,5 +1,5 @@ v1.6.9 -2025-03-06 21:15:19 +2025-03-08 01:56:41 1. 优化linux下路由跟踪问题 2. 优化linux下获取本机IP问题 3. 增加ICS,让win7+、win server2008+支持NAT