Initial commit

This commit is contained in:
陈俊宏 2024-07-01 15:57:07 +08:00
commit 691a88e5f6
106 changed files with 4758 additions and 0 deletions

1
.example.env Normal file
View File

@ -0,0 +1 @@
APP_DEBUG=true DEFAULT_TIMEZONE=Asia/Shanghai [DATABASE] TYPE=mysql HOSTNAME=192.168.6.193 DATABASE=cmb_youku USERNAME=root PASSWORD=lansexiongdi HOSTPORT=3307 CHARSET=utf8 DEBUG=true PREFIX= [FILESYSTEM] DRIVER=public [REDIS] HOST=192.168.6.75 PORT=6379 SELECT=0 PREFIX= PASSWORD=lansexiongdi EXPIRE=7200 TIMEOUT=3600 [CACHE] DRIVER=redis EXPIRED=7200 [LANG] default_lang=zh-cn [BLUE_BROTHER] HOST=http://openapi.1688sup.com MERCHANT_ID=25467 VERSION=1.0 MERCHANT_KEY=5ef0492c99567d3d78fabf0c12c5afde NOTIFY_URL= IS_PROD= [ALISMS] APP_ID=LTAI5tHw7KKtobnafvkUpqRZ APP_KEY=3cPlaCbfwc9BTqIXlvo5up5WaYgIWs SIGN_NAME=优酷会员 TEMPLATE_SMS_CODE=SMS_466350144 TEMPLATE_ISSUE_CODE= [CMB] CONTINUE_PRICE=7.5 CONTINUE_BONUS=9 PRODUCT_ID=2 USE_BONUS_MOUTH=1 AGREE_PAY_DAY=31 API_HOST=https://open.cmbchina.com/AccessGateway/transIn/ MID=cd9ec20f902d3d4ab6d63127c44e7897 AID=b32894073a844a38a10abe465cae8b34 SM2_PRI_KEY=e7d00ecae794e849d3425959ad7b288d18391cf5ce6cd740960a0f3092d5f0f6 SM2_PUB_KEY=04e59bc357ff33a4ca5e31aac8ebdbdb1e1d0dc1d719c9341722754804b12e49ed2b83f0a533faa9c1de59e8f5c0d9cfb2df7e7f2303096e507dccac26f7202289 SM2_CMB_PUB_KEY=040bd3ed2b542616e5a8ac810605f10d2bf222c9f78cbb4405e74b987e0ca3ce4167edb67afd9d83199d68749195cde6e3b0ef68c119608fa5c1780225ae5c9ce5 AGREE_NOTIFY_URL=https://youkucmb.iq1.cn/api/agreement/notify PAY_NOTIFY_URL=https://youkucmb.iq1.cn/api/agreement/payNotify RELEASE_NOTIFY_URL=https://youkucmb.iq1.cn/api/agreement/releaseNotify RECHARGE_NOTIFY_URL=https://youkucmb.iq1.cn/api/order/rechargeNotify

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
/.idea
/.vscode
/vendor
*.log
.env
composer.lock
public/storage
./phpunit.xml
.phpunit.result.cache
test
cmb_sql
public/h5/*
LICENSE.txt
.travis.yml

56
README.md Normal file
View File

@ -0,0 +1,56 @@
ThinkPHP 6.0
===============
> 运行环境要求PHP7.2+兼容PHP8.1
[官方应用服务市场](https://market.topthink.com) | [`ThinkAPI`——官方统一API服务](https://docs.topthink.com/think-api)
ThinkPHPV6.0版本由[亿速云](https://www.yisu.com/)独家赞助发布。
## 主要新特性
* 采用`PHP7`强类型(严格模式)
* 支持更多的`PSR`规范
* 原生多应用支持
* 更强大和易用的查询
* 全新的事件系统
* 模型事件和数据库事件统一纳入事件系统
* 模板引擎分离出核心
* 内部功能中间件化
* SESSION/Cookie机制改进
* 对Swoole以及协程支持改进
* 对IDE更加友好
* 统一和精简大量用法
## 安装
~~~
composer create-project topthink/think tp 6.0.*
~~~
如果需要更新框架使用
~~~
composer update topthink/framework
~~~
## 文档
[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content)
## 参与开发
请参阅 [ThinkPHP 核心框架包](https://github.com/top-think/framework)。
## 版权信息
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2006-2021 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
更多细节参阅 [LICENSE.txt](LICENSE.txt)

1
app/.htaccess Normal file
View File

@ -0,0 +1 @@
deny from all

22
app/AppService.php Normal file
View File

@ -0,0 +1,22 @@
<?php
declare (strict_types = 1);
namespace app;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register()
{
// 服务注册
}
public function boot()
{
// 服务启动
}
}

94
app/BaseController.php Normal file
View File

@ -0,0 +1,94 @@
<?php
declare (strict_types = 1);
namespace app;
use think\App;
use think\exception\ValidateException;
use think\Validate;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
}

88
app/ExceptionHandle.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace app;
use app\config\BusinessCode;
use app\config\ResponseCode;
use app\exception\BusinessException;
use app\exception\LogicException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\facade\Log;
use think\Response;
use Throwable;
/**
* 应用异常处理类
*/
class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
BusinessException::class,
LogicException::class
];
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// if (env('app_debug', false)) {
// return parent::render($request, $e);
// }
Log::error($e->getMessage());
// 添加自定义异常处理机制
if ($e instanceof BusinessException) {
return json_response(['code' => !empty($e->getCode()) ? $e->getCode() : \app\config\BusinessCode::FAIL, 'message' => $e->getMessage()]);
}
if ($e instanceof ValidateException) {
return json_response(['code' => \app\config\BusinessCode::PARAMETER_ERROR, 'message' => $e->getMessage()]);
}
// 逻辑代码错误 业务失败抛出的异常
if ($e instanceof LogicException) {
if (app()->isDebug()) {
return response_json($e->getData(), ResponseCode::FAIL, $e->getMessage());
} else {
return response_json([], ResponseCode::FAIL, $e->getMessage())->header(['error' => 'logic']);
}
}
if ($e instanceof \Exception) {
return json_response(['code' => !empty($e->getCode()) ? $e->getCode() : \app\config\BusinessCode::FAIL, 'message' => $e->getMessage()]);
}
// 其他错误交给系统处理
return parent::render($request, $e);
}
}

8
app/Request.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace app;
// 应用请求对象类
class Request extends \think\Request
{
}

18
app/admin/Base.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace app\admin;
use app\BaseController;
use app\Request;
use think\App;
class Base extends BaseController
{
static array $input;
public function __construct(App $app, Request $request)
{
parent::__construct($app);
self::$input = $request->param();
}
}

View File

@ -0,0 +1,51 @@
<?php
declare (strict_types=1);
namespace app\admin;
use app\Request;
use app\service\ProductService;
use think\App;
/**
* @author canny
* @date 2023/12/7 18:01
**/
class templateController extends Base
{
private ProductService $service;
public function __construct(App $app, Request $request)
{
parent::__construct($app, $request);
$this->service = $app->make(ProductService::class);
}
public function list(): \think\Response
{
return responseOk($this->service->list(self::$input));
}
public function detail($id): \think\Response
{
return responseOk($this->service->detailById($id));
}
public function create(): \think\Response
{
$this->service->create(self::$input);
return responseOk();
}
public function update(): \think\Response
{
$this->service->update(self::$input);
return responseOk();
}
public function delete($id): \think\Response
{
$this->service->deleteById($id);
return responseOk();
}
}

62
app/cmd/AgreementPay.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace app\cmd;
use app\model\Order;
use app\model\Sign;
use app\service\AgreementService;
use app\service\PayService;
use think\Collection;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class AgreementPay extends Command
{
protected int $count = 0;
protected int $successCount = 0;
protected int $failedCount = 0;
protected function configure()
{
$this->setName('agreementPay')
->setDescription('招商银行协议扣款');
}
protected function execute(Input $input, Output $output)
{
Sign::where(['sign_status' => Sign::SIGN_STATUS_SUCCESS])->field('*')->chunk(50, function (Collection $signCollection) use (&$output) {
foreach ($signCollection as $collection) {
// 查询是否已经生成过订单
$order = Order::getPaidOrderByAgreementId($collection->m_agreement_id);
if ($order->isEmpty() || empty($collection->agree_recharge_time) || $order->order_status != Order::STATUS_RECHARGE_SUCCESS) {
continue;
}
// 该协议号扣款订单次数
$orderCount = Order::getCountByAgreementId($collection->m_agreement_id);
if ($orderCount == 12) { // 1年扣款12次
continue;
}
$oneYearAgo = strtotime("-1 year", time());
if ($oneYearAgo > strtotime($collection->create_time)) { // 签约时间超过一年
continue;
}
$currentTimestamp = strtotime(date('Y-m-d'));
$agreeRechargeTime = $collection->agree_recharge_time;
$day = env('cmb.agree_pay_day');
if ($currentTimestamp < $agreeRechargeTime + $day * 24 * 60 * 60 ) { //未到扣款时间
continue;
}
$this->count += 1;
$res = AgreementService::agreementPay($collection, $order);
if ($res['respCode'] == 1000) {
$this->successCount += 1;
$output->writeln("受理成功,等待支付通知");
} else {
$this->failedCount += 1;
$output->writeln("用户".$collection->user_id . "扣款失败:" . $res['respMsg']);
}
}
});
$output->writeln(sprintf("执行成功,累计 %s, 受理成功 %s, 失败 %s", $this->count, $this->successCount, $this->failedCount));
}
}

28
app/cmd/GetPayOrder.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace app\cmd;
use app\model\Order;
use app\service\CmbService;
use app\service\OrderService;
use think\Collection;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class GetPayOrder extends Command
{
protected function configure()
{
$this->setName('getPayOrder')->setDescription('主动查询支付订单状态');
}
protected function execute(Input $input, Output $output)
{
Order::where(['pay_status' => Order::PAY_STATUS_WAIT])->field('id,order_number')->chunk(100, function (Collection $orderCollection) use (&$output) {
// 未支付订单主动获取订单状态
foreach ($orderCollection as $order) {
CmbService::getPayOrder($order->order_number);
}
});
$output->writeln("主动查询支付订单状态完成");
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\cmd;
use app\model\Sign;
use app\service\AgreementService;
use think\Collection;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class QueryAgreeStatus extends Command
{
protected function configure()
{
$this->setName('queryAgreeStatus')->setDescription('主动拉取签约状态');
}
protected function execute(Input $input, Output $output)
{
Sign::whereIn('sign_status', [Sign::SIGN_STATUS_DEFAULT, Sign::SIGN_STATUS_SUCCESS])->field('*')->chunk(100, function (Collection $signCollection) use (&$output) {
foreach ($signCollection as $collection) {
AgreementService::queryAgreementStatus($collection);
}
});
$output->writeln("主动拉取签约状态完成");
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace app\cmd;
use app\model\Order;
use app\service\RechargeService;
use think\Collection;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class QueryRechargeOrder extends Command
{
protected function configure()
{
$this->setName('queryRechargeOrder')->setDescription('主动拉取充值订单状态');
}
protected function execute(Input $input, Output $output)
{
Order::where(['order_status' => Order::STATUS_RECHARGE_ING])->field('*')->chunk(100, function (Collection $orderCollection) use (&$output) {
foreach ($orderCollection as $collection) {
(new RechargeService())->queryRechargeOrder($collection['order_number']);
}
});
$output->writeln("主动拉取充值订单状态完成");
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace app\cmd;
use app\model\Order;
use app\service\CmbService;
use app\service\OrderService;
use think\Collection;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class getRefundOrder extends Command
{
protected function configure()
{
$this->setName('getRefundOrder')->setDescription('退款订单查询接口');
}
protected function execute(Input $input, Output $output)
{
Order::where(['refund_status' => Order::REFUND_STATUS_WAIT])->field('*')->chunk(100, function (Collection $orderCollection) use (&$output){
foreach ($orderCollection as $order) {
CmbService::getRefundOrder($order);
}
});
$output->writeln("主动拉取退款订单查询接口完成");
}
}

23
app/command/Activity.php Normal file
View File

@ -0,0 +1,23 @@
<?php
declare (strict_types=1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class Activity extends Command
{
protected function configure()
{
// 指令配置
$this->setName('coupon')
->setDescription('样例');
}
protected function execute(Input $input, Output $output)
{
}
}

125
app/common.php Normal file
View File

@ -0,0 +1,125 @@
<?php
// 应用公共文件
use Predis\Client;
function sendCurl($url, $data, $method = 'POST', $header = '')
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data != NULL)
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
if ($header)
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-type: application/json',
)
);
$result = curl_exec($ch);
return $result;
}
if (!function_exists('encryptionPass')) {
function encryptionPass($data): string
{
$salt = env('salt');
return md5(md5($salt . '_' . $data));
}
}
if (!function_exists('json_response')) {
function json_response($data)
{
return \think\Response::create($data, 'json')->code(200);
}
}
if (!function_exists('jsonResponse')) {
function jsonResponse($data, $code = '200', $message = 'ok')
{
return \think\Response::create(['code' => $code, 'message' => $message, 'data' => $data], 'json')->code(200);
}
}
if (!function_exists('createToken')) {
function createToken($data): string
{
$content = [
'iss' => request()->domain(),
'exp' => time() + 3600 * 12,
'data' => $data
];
$key = env('jwt_token_key');
return \Firebase\JWT\JWT::encode($content, $key, 'HS256');
}
}
if (!function_exists('getDataByToken')) {
function getDataByToken($token)
{
try {
$key = env('jwt_token_key');
$data = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($key, 'HS256'));
return (array)$data;
} catch (\Firebase\JWT\ExpiredException $exception) {
return 'token has expired';
} catch (\Firebase\JWT\SignatureInvalidException $exception) {
return 'token is not invalidate';
} catch (Exception $e) {
return $e->getMessage();
}
}
}
if (!function_exists('responseOk')) {
function responseOk($data = []): \think\Response
{
return json_response(['code' => \app\config\BusinessCode::SUCCESS, 'message' => "ok", 'data' => $data]);
}
}
if (!function_exists("niceDump")) {
function niceDump($data, $isTruncate = true)
{
echo "<pre>";
var_dump($data);
echo "</pre>";
$isTruncate && die;
}
}
if (!function_exists('createOrderNo')) {
function createOrderNo($id)
{
return date_create()->format('ymdHisu') . substr($id, -2);
}
}
if (!function_exists('devAuth')) {
function devAuth()
{
return env('devAuth', false);
}
}
if (!function_exists('response_json')) {
/**
* @describe 请求返回
* @param array $data
* @param int $code
* @param string $msg
* @param array $header
* @return \think\Response
*/
function response_json($data = [], int $code = \app\config\ResponseCode::SUCCESS, string $msg = '操作成功', array $header = []): \think\Response
{
return json(['code' => $code, 'message' => $msg, 'data' => $data], 200, $header);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare (strict_types=1);
namespace app\config;
/**
* @author canny
* @date 2023/10/23 14:42
**/
class BusinessCacheKey
{
const MINUTE = 60;
const HOUR = 3600;
const DAY = 86400;
const TEN_DAY = 864000;
const MONTH = 2592000; //一个月
const SITE_TOKEN_LIST = [
'key' => 'site_token_list:',
'ttl' => 7200
];
const SITE_SMS_CODE = [
'key' => 'site_sms_code',
'ttl' => self::MINUTE * 5
];
const FRONT_TOKEN_LIST = [
'key' => 'front_token_list',
'ttl' => self::DAY
];
// 解约短信验证码
const RELEASE_SMS_CODE = [
'key' => 'release_sms_code',
'ttl' => self::MINUTE * 5
];
}

View File

@ -0,0 +1,18 @@
<?php
declare (strict_types=1);
namespace app\config;
class BusinessCode
{
#业务处理成功
const SUCCESS = 200;
#业务处理失败
const FAIL = 502;
const PARAMETER_ERROR = 405;
# 未登录
const LOGIN_INVALID = 401;
}

View File

@ -0,0 +1,30 @@
<?php
declare (strict_types=1);
namespace app\config;
class ResponseCode
{
#业务处理成功
const SUCCESS = 200;
#业务处理失败
const FAIL = -1;
#请求发生错误
const ERROR = 1002;
#参数验证失败
const PARAM_INVALID = 1003;
#数据不存在
const DATA_NOT_FOUND = 1004;
// 未知异常
const UNKNOWN_ERROR = 9999;
const CMB_AUTH_EXPIRE = 2001;
const CMB_PAY_NOTIFY_FAIL = 401;
}

15
app/config/SmsConfig.php Normal file
View File

@ -0,0 +1,15 @@
<?php
declare (strict_types=1);
namespace app\config;
/**
* @author canny
* @date 2023/11/20 14:58
**/
class SmsConfig
{
const SMS_SERIAL_NUMBER_EXCHANGE = "SMS_110212"; //实物资产兑换发送短信
}

17
app/event.php Normal file
View File

@ -0,0 +1,17 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];

View File

@ -0,0 +1,20 @@
<?php
namespace app\exception;
use think\Exception;
/**
* @author canny
* @date 2023/10/19 18:21
**/
class BusinessException extends Exception
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = null)
{
!empty($message) && $this->message = $message;
!empty($code) && $this->code = $code;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace app\exception;
use app\event\LogicExceptionLog;
use Throwable;
/**
* Class LogicException
* @describe Exception
* @package app\exception
*/
class LogicException extends \LogicException
{
protected $data = [];
public function __construct(string $message = "", int $code = 0)
{
!empty($message) && $this->message = $message;
!empty($code) && $this->code = $code;
}
final protected function setData($label, $value): self
{
$this->data[$label] = $value;
return $this;
}
final public function getData()
{
return $this->data;
}
}

122
app/front/Agreement.php Normal file
View File

@ -0,0 +1,122 @@
<?php
namespace app\front;
use app\service\AgreementService;
use app\service\util\CmbLifeUtils;
use think\Request;
use think\Response;
class Agreement
{
/**
* 签约协议
* @param Request $request
* @return Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function agreeApproval(Request $request)
{
$userId = $request->user_id;
$callback = $request->post('callback', '');
$account = $request->post('account', '');
$approval = AgreementService::agreeApproval($userId, $callback, $account);
return response_json($approval);
}
/**
* 后台签约通知
* @param Request $request
* @return Response
*/
public function agreeNotify(Request $request)
{
$posts = $request->post();
$res = AgreementService::agreeNotify($posts);
$res['sign'] = CmbLifeUtils::signForResponse($res);
return Response::create($res, 'json');
}
/**
* 取消订阅
* @param Request $request
* @return Response
*/
public function unsubscribe(Request $request)
{
$params = $request->post();
$params['user_id'] = $request->user_id;
AgreementService::unsubscribe($params);
return response_json();
}
/**
* 解约通知接口
* @param Request $request
* @return Response
*/
public function releaseNotify(Request $request)
{
$posts = $request->post();
$res = AgreementService::releaseNotify($posts);
$res['sign'] = CmbLifeUtils::signForResponse($res);
return Response::create($res, 'json');
}
/**
* 协议扣款通知
* @param Request $request
* @return Response
*/
public function payNotify(Request $request)
{
$posts = $request->post();
$res = AgreementService::payNotify($posts);
$res['sign'] = CmbLifeUtils::signForResponse($res);
return Response::create($res, 'json');
}
/**
* 发送验证码
* @param Request $request
* @return Response
* @throws \app\exception\BusinessException
*/
public function releaseSendSms(Request $request)
{
$params = $request->post();
AgreementService::releaseSendSms($params);
return response_json();
}
/**
* 获取绑定手机号
* @param Request $request
* @return Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getBindMobile(Request $request)
{
$userId = $request->user_id;
$res = AgreementService::getBindMobile($userId);
return response_json($res);
}
/**
* 判断是否可兑换
* @param Request $request
* @return \think\Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getExchangeStatus(Request $request)
{
$params = $request->post();
$params['user_id'] = $request->user_id;
$res = AgreementService::getExchangeStatus($params);
return response_json($res);
}
}

18
app/front/Base.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace app\front;
use app\BaseController;
use app\Request;
use think\App;
class Base extends BaseController
{
protected $params;
public function __construct(App $app, Request $request)
{
parent::__construct($app);
$this->params = $request->param();
}
}

25
app/front/Index.php Normal file
View File

@ -0,0 +1,25 @@
<?php
declare (strict_types=1);
namespace app\front;
use app\model\Product;
use app\service\ProductService;
/**
* @author canny
* @date 2024/3/8 9:23
**/
class Index extends Base
{
public function homeData(): \think\Response
{
$productService = app()->make(ProductService::class);
$fields = ['id', 'name', 'cover_image', 'face_amount', 'price', 'type', 'tag', 'sale_num', 'description'];
$data = [
'baoYueProduct' => $productService->getInfoByWhere(['type' => Product::PRODUCT_TYPE_BAO_YUE]),
'product' => $productService->getLists(['type' => Product::PRODUCT_TYPE_NORMAL], $fields, 'sort desc')
];
return responseOk($data);
}
}

35
app/front/Login.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare (strict_types=1);
namespace app\front;
use app\exception\BusinessException;
use app\Request;
use app\service\UserService;
use think\App;
/**
* @author canny
* @date 2024/3/28 16:10
**/
class Login extends Base
{
private UserService $service;
public function __construct(App $app, Request $request)
{
parent::__construct($app, $request);
$this->service = $app->make(UserService::class);
}
/**
* @throws BusinessException
*/
public function loginSendSms(): \think\Response
{
$this->service->loginSendSms($this->params);
return responseOk();
}
}

51
app/front/Oauth.php Normal file
View File

@ -0,0 +1,51 @@
<?php
namespace app\front;
use app\model\User;
use app\service\CmbService;
use app\service\util\CmbLifeUtils;
use app\util\CmbHttpUtils;
use app\util\StringUtil;
use think\Request;
class Oauth
{
/**
* 发起授权登录
* @param Request $request
* @return \think\Response
* @throws \app\exception\BusinessException
*/
public function approval(Request $request)
{
$params =$request->post();
$approvalUrl = CmbService::genApprovalProtocol($params);
return response_json($approvalUrl);
}
/**
* 获取openId
* @param Request $request
* @return \think\Response
* @throws \app\exception\BusinessException
*/
public function accessToken(Request $request)
{
$code = $request->post('code');
$res = CmbService::accessToken($code);
return response_json($res);
}
/**
* 手机号绑定
* @param Request $request
* @return \think\Response
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public function bindMobile(Request $request)
{
$params = $request->post();
$params['open_id'] = $request->open_id;
CmbService::bindMobile($params);
return response_json([], 200, '手机号绑定成功!');
}
}

52
app/front/Order.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace app\front;
use app\config\ResponseCode;
use app\service\CmbService;
use app\service\OrderService;
use app\service\RechargeService;
use think\Request;
class Order extends Base
{
public function list(Request $request): \think\Response
{
$params['user_id'] = $request->user_id;
return responseOk(app()->make(OrderService::class)->list($params));
}
/**
* 订单退订
* @param Request $request
* @return \think\Response
*/
public function refund(Request $request)
{
$orderNumber = $request->post('order_number');
$res = CmbService::refund($orderNumber);
return response_json($res);
}
/**
* 直连天下充值回调
* @param Request $request
* @return string
*/
public function rechargeNotify(Request $request)
{
$params = $request->post();
$res = (new RechargeService())->rechargeNotify($params);
echo $res;
exit;
}
/**
* @return \think\Response
* @throws \think\db\exception\DbException
*/
public function getMonthSale()
{
$res = (new OrderService())->getMonthSale();
return response_json($res);
}
}

34
app/front/Product.php Normal file
View File

@ -0,0 +1,34 @@
<?php
declare (strict_types=1);
namespace app\front;
use app\Request;
use app\service\ProductService;
use think\App;
/**
* @author canny
* @date 2024/3/8 9:32
**/
class Product extends Base
{
private ProductService $productService;
public function __construct(App $app, Request $request)
{
parent::__construct($app, $request);
$this->productService = $app->make(ProductService::class);
}
public function detail($id): \think\Response
{
return responseOk($this->productService->detailById($id));
}
public function sync(): \think\Response
{
$this->productService->sync();
return responseOk();
}
}

View File

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

11
app/middleware.php Normal file
View File

@ -0,0 +1,11 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
\app\middleware\CorsMiddleware::class
];

View File

@ -0,0 +1,23 @@
<?php
declare (strict_types=1);
namespace app\middleware;
/**
* @author canny
* @date 2023/10/27 17:20
**/
namespace app\middleware;
use think\middleware\AllowCrossDomain;
class CorsMiddleware extends AllowCrossDomain
{
protected $header = [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => 1800,
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET,POST,PATCH,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Headers' => 'Authorization,Content-Type,If-Match,If-Modified-Since,If-None-Match,If-Unmodified-Since,X-CSRF-TOKEN,X-Requested-With,token,front-token',
];
}

View File

@ -0,0 +1,49 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use app\config\BusinessCacheKey;
use app\config\BusinessCode;
use app\exception\BusinessException;
use app\service\util\RedisService;
use app\util\FrontSessionUtil;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
class FrontRequest
{
/**
* 处理请求
*/
public function handle(\think\Request $request, \Closure $next): \think\Response
{
// $token = FrontSessionUtil::getToken();
// if (empty($token)) {
// throw new BusinessException("未登录", BusinessCode::LOGIN_INVALID);
// }
// $client = RedisService::getRedisInstance();
// if (!$client->sismember(BusinessCacheKey::FRONT_TOKEN_LIST['key'], $token)) {
// throw new BusinessException("token已过期", BusinessCode::LOGIN_INVALID);
// }
// try {
// $data = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key(env('jwt_token_key'), 'HS256'));
// FrontSessionUtil::setUser($data->data);
// } catch (ExpiredException $e) {
// throw new BusinessException('请重新授权登陆', BusinessCode::LOGIN_INVALID);
// } catch (SignatureInvalidException|\Throwable $e) {
// throw new BusinessException('签名验证失败', BusinessCode::LOGIN_INVALID);
// }
return $next($request);
}
public function end(\think\Response $response): \think\Response
{
$response->header([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, token,Status Code,frontToken'
]);
return $response;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare (strict_types = 1);
namespace app\middleware;
use app\config\BusinessCacheKey;
use app\config\BusinessCode;
use app\exception\BusinessException;
use app\service\util\RedisService;
use app\util\SessionUtil;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
class Request
{
public function handle(\think\Request $request, \Closure $next): \think\Response
{
$token = SessionUtil::getToken();
if (empty($token)) {
throw new BusinessException('需要登录', BusinessCode::LOGIN_INVALID);
}
$client = RedisService::getRedisInstance();
if (empty($client->sismember(BusinessCacheKey::SITE_TOKEN_LIST['key'], $token))) {
throw new BusinessException('需要登录', BusinessCode::LOGIN_INVALID);
}
try {
$data = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key(env('jwt_token_key'), 'HS256'));
SessionUtil::setUser($data->data);
} catch (ExpiredException $e) {
throw new BusinessException('请重新授权登陆', BusinessCode::LOGIN_INVALID);
} catch (SignatureInvalidException|\Throwable $e) {
throw new BusinessException('签名验证失败', BusinessCode::LOGIN_INVALID);
}
return $next($request);
}
public function end(\think\Response $response): \think\Response
{
$response->header([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, token,Status Code'
]);
return $response;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace app\middleware;
use app\exception\LogicException;
use app\model\User;
use app\service\CmbService;
use app\service\util\CmbLifeUtils;
use think\Response;
use think\Request;
class ValidateOpenId
{
public function handle(Request $request, \Closure $next): Response
{
$openId = $request->param('open_id', '');
if (empty($openId)) {
return $next($request);
}
// $openIdRes = CmbLifeUtils::decrypt($openId, config('cmb.merchant_sm2_pri_key'));
// $openId = $openIdRes['open_id'] ?? '';
$request->open_id = $openId;
$user = User::getUserByOpenId($openId);
if ($user->isEmpty()) {
throw new LogicException('用户不存在' . $openId);
}
$request->user_id = $user->id;
// $res = CmbService::checkAccessToken($user);
// if ($res['respCode'] == 1000) {
// $request->user_id = $user->id;
// } else {
// return json(['code' => 403, 'message' => '登录超时,请重新登录!', 'data' => []], 401);
// }
return $next($request);
}
}

145
app/model/BaseModel.php Normal file
View File

@ -0,0 +1,145 @@
<?php
namespace app\model;
use app\exception\BusinessException;
use app\util\SessionUtil;
use think\facade\Request;
use think\Model;
use think\model\concern\SoftDelete;
class BaseModel extends Model
{
const STATE_ENABLED = 1; //业务状态 -正常
const STATE_DISABLED = 2;//业务状态 -禁用
const STATE_DESC = [
self::STATE_ENABLED => '启用',
self::STATE_DISABLED => '禁用',
];
public static function findById($id): object
{
return self::findOrEmpty($id);
}
public static function getInfoAndCheck(array $where)
{
$info = self::getInfoByWhere($where);
if (empty($info)) {
throw new BusinessException("无权限操作此数据.");
}
return $info;
}
public static function onAfterRead(Model $model): void
{
$model->hidden(['delete_time']);
if (!empty($model->status)) {
$model->statusDesc = self::STATE_DESC[$model->status];
}
if (!empty($model->state)) {
$model->statusDesc = self::STATE_DESC[$model->state];
}
}
public static function getListPage($where, $page, $order = ''): array
{
$model = self::where($where);
if ($order) {
$model->order($order);
}
return $model->paginate($page)->toArray();
}
public static function insertOne($data)
{
$data['create_time'] = date("Y-m-d H:i:s");
$data['update_time'] = date("Y-m-d H:i:s");
return self::create($data)->getKey();
}
public static function updateOne($where, $data)
{
$data['update_time'] = date("Y-m-d H:i:s");
self::update($data, $where);
}
public static function getOne($id): array
{
return self::findOrEmpty($id)->toArray();
}
public static function getInfoByWhere($where, $order = '', $field = '*'): array
{
$model = self::where($where);
if ($order) {
$model->order($order);
}
return $model->field($field)->findOrEmpty()->toArray();
}
public static function getList($where, $field = '*', $order = ''): array
{
$model = self::where($where)->field($field);
if ($order) {
$model->order($order);
}
return $model->select()->toArray();
}
public static function getCounts($where, $field = 'create_time', $times = [])
{
$model = self::where($where);
if (!empty($times)) {
$model->whereBetweenTime($field, $times[0], $times[1]);
}
return $model->count('id');
}
public static function getPageInfo(): array
{
return [
'list_rows' => Request::param("pageSize", 10),
'var_page' => 'page',
'page' => Request::param("page", 1),
];
}
public static function orderBy()
{
return Request::param("orderBy", "id desc");
}
public function searchPages($search = [], $data = [], $field = '*'): \think\Paginator
{
$model = self::where(self::appendBaseWhere([]));
if (!empty($search)) {
$model = $model->withSearch($search, $data);
}
return $model->order(self::orderBy())->field($field)->paginate(self::getPageInfo());
}
public function detailById($id, $field = ['*']): array
{
return self::field($field)->findOrEmpty($id)->toArray();
}
public function deleteById($id)
{
self::destroy($id);
}
public function getLists($where = [], $field = [], $order = [])
{
return (new static)->where($where)->field($field)->order($order)->select();
}
private static function appendBaseWhere($where = [])
{
if (!isset($where['site_id']) && !empty(self::getModel()->autoSiteId) && self::getModel()->autoSiteId) {
$where['site_id'] = SessionUtil::getSiteId();
}
return $where;
}
}

177
app/model/Order.php Normal file
View File

@ -0,0 +1,177 @@
<?php
namespace app\model;
use think\Model;
class Order extends BaseModel
{
use SearcherTrait;
protected $name = 'orders';
const PRODUCT_TYPE_BAO_YUE = 2; // 连续包月类型
const PRODUCT_TYPE_NORMAL = 1; // 普通商品
const STATUS_WAIT_SIGN = 0; // 待签约
const STATUS_SIGNED = 1; // 已签约
const STATUS_WAIT_RECHARGE = 2; // 待充值
const STATUS_RECHARGE_ING = 3; // 充值中
const STATUS_RECHARGE_SUCCESS = 4; // 充值完成
const STATUS_RECHARGE_FAIL = 5; // 充值失败
const STATUS_RECHARGE_CLOSE = 6; // 已取消
// 支付状态
const PAY_STATUS_WAIT = 1; // 待支付
const PAY_STATUS_PAID = 2; // 已支付
const PAY_STATUS_FAIL = 3; // 付款失败
// 退款状态
const REFUND_STATUS_WAIT = 1;
const REFUND_STATUS_SUCCESS = 2;
const REFUND_STATUS_FAIL = 3;
const STATUS_TEXT = [
self::STATUS_WAIT_SIGN => '待签约',
self::STATUS_SIGNED => '已签约',
self::STATUS_WAIT_RECHARGE => '待充值',
self::STATUS_RECHARGE_ING => '充值中',
self::STATUS_RECHARGE_SUCCESS => '已完成',
self::STATUS_RECHARGE_FAIL => '充值失败',
self::STATUS_RECHARGE_CLOSE => '已取消'
];
/**
* 根据userId获取连续包月的订单数
* @param int $userId
* @return int
* @throws \think\db\exception\DbException
*/
public static function getMonthOrderCountByUserId(int $userId): int
{
return self::where(['user_id' => $userId,
'type' => self::PRODUCT_TYPE_BAO_YUE,
'pay_status' => self::PAY_STATUS_PAID])->count();
}
public static function updateChangeData($data, $orderNumber)
{
return self::where('order_number', $orderNumber)->update($data);
}
/**
* 根据用户id查询已支付连续扣款订单
* @param int $userId
* @return Order|array|mixed|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getOneMonthByUserId(int $userId)
{
return self::where(['user_id' => $userId,
'type' => self::PRODUCT_TYPE_BAO_YUE,
'pay_status' => self::PAY_STATUS_PAID
])->findOrFail();
}
/**
* 根据订单号获取订单信息
* @param string $orderNumber
* @param string $field
* @return Order|array|mixed|Model
*/
public static function getByOrderNumber(string $orderNumber, string $field = '*')
{
return self::where('order_number', $orderNumber)->field($field)->findOrEmpty();
}
public function searchOrderNumberAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('order_number', $value);
}
}
public function searchTypeAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('type', $value);
}
}
public function getWaitSignOrderByUserId(int $userId)
{
return self::where(['user_id' => $userId, 'order_status' => Order::STATUS_WAIT_SIGN])->findOrEmpty();
}
public function searchUserIdAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('user_id', $value);
}
}
/**
* 根据账号获取该账号近一年是否已经签约扣过款
* @param string $account
* @return int
* @throws \think\db\exception\DbException
*/
public static function getAgreeOrderLastYearByAccount(string $account)
{
return self::where('account', $account)->whereIn('order_status', [2,3,4])->whereTime('create_time', '-1 year')->count();
}
/**
* 根据userId 获取该账号近一年是否已经扣过款
* @param int $userId
* @return int
* @throws \think\db\exception\DbException
*/
public static function getAgreeOrderLastYearByUserId(int $userId)
{
return self::where('user_id', $userId)->whereIn('order_status', [2,3,4])->whereTime('create_time', '-1 year')->count();
}
/**
* 根据协议号获取待签约订单
* @param string $agreementId
* @return Order|array|mixed|Model
*/
public static function getWaitSignByAgreementId(string $agreementId)
{
return self::where(['agreement_id' => $agreementId, 'order_status' => Order::STATUS_WAIT_SIGN])->findOrEmpty();
}
/**
* 根据协议号获取最近一次扣款订单
* @param string $agreementId
* @return Order|array|mixed|Mode
*/
public static function getPaidOrderByAgreementId(string $agreementId)
{
return self::where(['agreement_id' => $agreementId])->order('id', 'desc')->findOrEmpty();
}
/**
* 获取该签约生成扣款订单次数
* @param string $agreementId
* @return int
* @throws \think\db\exception\DbException
*/
public static function getCountByAgreementId(string $agreementId)
{
return self::where(['agreement_id' => $agreementId])->count();
}
/**
* 最近一个月订单数量
* @return int
* @throws \think\db\exception\DbException
*/
public function getCountLastMonth()
{
return self::where('order_status', Order::STATUS_RECHARGE_SUCCESS)->whereTime('create_time', '-1 month')->count();
}
public static function getByAgreementId(string $agreementId)
{
return self::where(['agreement_id' => $agreementId])->findOrEmpty();
}
}

44
app/model/Product.php Normal file
View File

@ -0,0 +1,44 @@
<?php
declare (strict_types=1);
namespace app\model;
/**
* @author canny
* @date 2024/2/23 16:51
**/
class Product extends BaseModel
{
use SearcherTrait;
const PRODUCT_TYPE_BAO_YUE = 2;
const PRODUCT_TYPE_NORMAL = 1;
public function searchNameAttr($query, $value, $data)
{
if (!empty($value)) {
$query->whereLike('name', "%" . $value . "%");
}
}
/**
* 根据商品id 获取商品信息
* @param int $productId
* @param string $field
* @return Product|array|mixed|\think\Model
*/
public static function getById(int $productId, string $field = '*')
{
return self::where('id', $productId)->field($field)->findOrEmpty();
}
/**
* 根据supplier_product_id获取商品信息
* @param int $productId
* @param string $field
* @return Product|array|mixed|\think\Model
*/
public static function getBySupplierProductId(int $productId, string $field = '*')
{
return self::where('supplier_product_id', $productId)->field($field)->findOrEmpty();
}
}

12
app/model/ProductMap.php Normal file
View File

@ -0,0 +1,12 @@
<?php
declare (strict_types=1);
namespace app\model;
/**
* @author canny
* @date 2024/3/11 10:16
**/
class ProductMap extends BaseModel
{
}

View File

@ -0,0 +1,35 @@
<?php
namespace app\model;
trait SearcherTrait
{
public function searchSiteIdAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('site_id', $value);
}
}
public function searchStateAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('state', $value);
}
}
public function searchStatusAttr($query, $value, $data)
{
if (!empty($value)) {
$query->where('status', $value);
}
}
public function searchCreateTimeAttr($query, $value, $data)
{
if (!empty($value)) {
$query->whereBetweenTime('create_time', $value[0], $value[1]);
}
}
}

96
app/model/Sign.php Normal file
View File

@ -0,0 +1,96 @@
<?php
namespace app\model;
use think\Model;
class Sign extends Model
{
protected $name = 'sign';
const SIGN_STATUS_DEFAULT = 0;
const SIGN_STATUS_SUCCESS = 1;
const SIGN_STATUS_RELEASE = 2;
public static function getSignSuccessUser()
{
return self::where(['sign_status' => self::SIGN_STATUS_SUCCESS])->select();
}
public static function getByOpenId(string $openId)
{
return self::where(['open_id' => $openId, 'sign_status' => 0])->findOrEmpty();
}
public static function getByUserId(int $userId)
{
return self::where(['user_id' => $userId])->findOrEmpty();
}
/**
* 查询最近一年该用户是否有签约
* @param int $userId
* @return Sign|array|mixed|Model
*/
public static function getSignLastYear(int $userId)
{
return self::whereTime('create_time', '-1 year')->where(['user_id' => $userId, 'sign_status' => Sign::SIGN_STATUS_SUCCESS])->findOrEmpty();
}
/**
* 查询最近一年该用户是否有解约
* @param int $userId
* @return Sign|array|mixed|Model
*/
public static function getReleaseLastYear(int $userId)
{
return self::whereTime('create_time', '-1 year')->where(['user_id' => $userId, 'sign_status' => Sign::SIGN_STATUS_RELEASE])->findOrEmpty();
}
/**
* 根据流水号查询数据
* @param string $requestSerial
* @return Sign|array|mixed|Model
*/
public static function getByRequestSerial(string $requestSerial)
{
return self::where(['request_serial' => $requestSerial])->findOrEmpty();
}
/**
* 根据商户协议号获取数据
* @param string $agreementId
* @return Sign|array|mixed|Model
*/
public static function getByAgreementId(string $agreementId)
{
return self::where(['m_agreement_id' => $agreementId])->findOrEmpty();
}
/**
* 查询正在订阅的用户
* @param int $userId
* @return Sign|array|mixed|Model
*/
public static function getSubscribeByUserId(int $userId)
{
return self::where(['user_id' => $userId, 'sign_status' => Sign::SIGN_STATUS_SUCCESS])->findOrEmpty();
}
/**
* 查询该用户正在签约的信息
* @param int $userId
* @return Sign|array|mixed|Model
*/
public static function getWaitSignByUserId(int $userId)
{
return self::where(['user_id' => $userId, 'sign_status' => Sign::SIGN_STATUS_DEFAULT])->findOrEmpty();
}
/**
* 查询最近一年该用户是否有签约
* @param int $userId
* @return Sign|array|mixed|Model
*/
public static function getSignLastYearByMobile(int $mobile)
{
return self::whereTime('create_time', '-1 year')->where(['mobile' => $mobile, 'sign_status' => Sign::SIGN_STATUS_SUCCESS])->findOrEmpty();
}
}

36
app/model/User.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace app\model;
use think\Model;
class User extends Model
{
protected $name = 'users';
public function sign(): \think\model\relation\HasOne
{
return $this->hasOne(Sign::class, 'id', 'user_id');
}
/**
* 根据用户id查询
* @param int $userId
* @return User|array|mixed|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getUserById(int $userId)
{
return self::where('id', $userId)->findOrEmpty();
}
/**
* 根据掌上生活openId获取用户信息
* @param string $openId
* @return User|array|mixed|Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getUserByOpenId(string $openId)
{
return self::where('open_id', $openId)->findOrEmpty();
}
}

9
app/provider.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];

9
app/service.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use app\AppService;
// 系统服务定义文件
// 服务在完成全局初始化之后执行
return [
AppService::class,
];

View File

@ -0,0 +1,385 @@
<?php
namespace app\service;
use app\config\BusinessCacheKey;
use app\exception\BusinessException;
use app\exception\LogicException;
use app\model\Order;
use app\model\Product;
use app\model\Sign;
use app\model\User;
use app\service\util\CmbLifeUtils;
use app\service\util\SmsUtil;
use app\sms\AliSms;
use app\util\CmbHttpUtils;
use app\util\StringUtil;
use think\facade\Db;
use think\facade\Log;
class AgreementService extends BaseService
{
/**
* 生成签约协议
* @param int $userId
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function agreeApproval(int $userId, string $callback, string $account): string
{
$cmbConfig = config('cmb');
$user = User::getUserById($userId);
if ($user->isEmpty()) {
throw new LogicException('用户不存在!');
}
// 查询当前用户是否可再签约
$isCanAgree = self::checkCanAgree($userId, $account);
if (!$isCanAgree['success']) {
throw new \LogicException($isCanAgree['msg']);
}
$params = [
'mAgreementId' => StringUtil::makeAgreementId(),
'merchantUserId' => StringUtil::maskPhone($account),
'requestSerial' => uniqid(), // 请求流水号(商户侧唯一)
'notifyUrl' => $cmbConfig['agree_notify_url'],
'callback' => $callback,
];
$sign = Sign::getWaitSignByUserId($userId);
if (!$sign->isEmpty()) {
$params['mAgreementId'] = $sign->m_agreement_id;
$params['requestSerial'] = $sign->request_serial;
}
$funcName = 'authorize';
// 生成新签约记录
Db::startTrans();
try {
if (!$sign->isEmpty()) { // 更新
$sign->mobile = $account;
$sign->create_time = time();
$sign->save();
$order = Order::getByAgreementId($sign->m_agreement_id);
$order->account = $account;
$order->create_time = time();
$order->save();
} else {
Sign::create(
['user_id' => $userId,
'open_id' => $user->open_id,
'mobile' => $account,
'request_serial' => $params['requestSerial'],
'm_agreement_id' => $params['mAgreementId']]
);
// 创建订单
$data = [
'user_id' => $user->id,
'product_id' => config('cmb.product_id'),
'account' => $account,
'agreement_id' => $params['mAgreementId']
];
CmbService::createOrder($data);
}
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
throw new \LogicException($e->getMessage());
}
return CmbLifeUtils::genProtocol($funcName, $params);
}
/**
* 签约通知
* @param $data
* @return array
*/
public static function agreeNotify($data)
{
Log::info('签约回调:' . json_encode($data));
$verifyRes = CmbLifeUtils::verifyForResponse($data);
if (!$verifyRes) {
return ['respCode' => 1001, 'msg' => '验签不通过'];
}
$requestSerial = $data['requestSerial'];
$sign = Sign::getByRequestSerial($requestSerial);
if ($sign->isEmpty()) {
return ['respCode' => 1000, 'msg' => '处理成功'];
}
$sign->sign_status = Sign::SIGN_STATUS_SUCCESS;
$sign->agreement_id = $data['agreementId'];
$sign->save();
// 签约成功开始扣款
$order = Order::getWaitSignByAgreementId($data['mAgreementId']);
if ($order->isEmpty() || $order->order_status == Order::STATUS_SIGNED) {
return ['respCode' => 1000, 'msg' => '处理成功'];
}
$order->order_status = Order::STATUS_SIGNED;
$order->save();
// 签约第一次扣款
self::firstPay($sign, $order);
// if ($agreePayRes['respCode'] != '1000') {
// return ['respCode' => 1001, 'msg' => '扣款失败' . $agreePayRes['respMsg']];
// }
return ['respCode' => 1000, 'msg' => '处理成功'];
}
/**
* 签约状态查询处理
* @return mixed
*/
public static function queryAgreementStatus(Sign $sign)
{
$funcName = 'queryAgreementStatus';
$requestParams = [
'mAgreementId' => $sign->m_agreement_id // 商户端协议号
];
$res = CmbHttpUtils::doPost($funcName, $requestParams);
if ($res['respCode'] !== '1000') {
return true;
}
$sign->sign_status = $res['status'];
$sign->agreement_id = $res['agreementId'];
$sign->save();
$agreementId = $sign->m_agreement_id;
// 如果待签约未扣款订单开始执行扣款操作
$order = Order::getWaitSignByAgreementId($agreementId);
if ($res['status'] != 1 || $order->isEmpty()) {
return true;
}
$order->order_status = Order::STATUS_SIGNED;
$order->save();
// 签约完成开始处理第一次签约扣款
self::firstPay($sign, $order);
return true;
}
/**
* 第一次扣款
* @param Sign $collection
* @param Order $orderCollection
* @return mixed|string
*/
public static function firstPay(Sign $collection, Order $orderCollection)
{
$productId = $orderCollection->product_id;
$product = Product::getBySupplierProductId($productId);
// 扣款参数
$requestParams = [
'agreementId' => $collection->agreement_id,
'billNo' => $orderCollection->order_number,
'amount' => $orderCollection->price * 100,
'bonus' => $orderCollection->bonus,
'notifyUrl' => config('cmb.pay_notify_url'),
'productName' => $product['name']
];
$funcName = 'agreementPay';
return CmbHttpUtils::doPost($funcName, $requestParams);
}
/**
* 处理协议扣款
* @param Sign $collection
* @param Order $orderCollection
* @return mixed
* @throws \think\db\exception\DbException
*/
public static function agreementPay(Sign $collection, Order $orderCollection)
{
$productId = $orderCollection->product_id;
$product = Product::getBySupplierProductId($productId);
$order = [
'user_id' => $orderCollection->user_id,
'order_number' => StringUtil::makeOrderNumber(),
'account' => $orderCollection->account,
'type' => $orderCollection->type,
'product_id' => $orderCollection->product_id,
'price' => $product['price'],
'agreement_id' => $orderCollection->agreement_id,
'order_status' => Order::STATUS_SIGNED
];
// 查询第几次扣款
$count = Order::getCountByAgreementId($collection->m_agreement_id);
if ($count < 2) {
$order['price'] = config('cmb.continue_price');
}
// 生成连续包月订单
Order::create($order);
// 扣款参数
$requestParams = [
'agreementId' => $collection->agreement_id,
'billNo' => $order['order_number'],
'amount' => $order['price'] * 100,
'notifyUrl' => config('cmb.pay_notify_url'),
'productName' => $product['name']
];
$funcName = 'agreementPay';
return CmbHttpUtils::doPost($funcName, $requestParams);
}
/**
* 协议扣款回调处理
* @param array $data
* @return int[]
*/
public static function payNotify(array $data)
{
Log::info('付款回调:' . json_encode($data));
$verifyRes = CmbLifeUtils::verifyForResponse($data);
if (!$verifyRes) {
return ['respCode' => 1001, 'respMsg' => '签名有误'];
}
$encryptBody = $data['encryptBody'] ?? '';
if (empty($encryptBody)) {
return ['respCode' => 1000, 'respMsg' => '处理成功'];
}
$encryptData = CmbLifeUtils::decrypt($encryptBody, config('cmb.merchant_sm2_pri_key'));
$orderNumber = $encryptData['billNo'];
$order = Order::getByOrderNumber($orderNumber);
if ($order->isEmpty() || $order->pay_status == Order::PAY_STATUS_PAID) {
return ['respCode' => 1000, 'respMsg' => '处理成功'];
}
$update = [
'pay_time' => isset($encryptData['payDate']) ? strtotime($encryptData['payDate']) : time(),
'pay_status' => $encryptData['result'],
'ref_num' => $encryptData['refNum'] ?? '',
'pay_type' => $encryptData['payType'] ?? '',
'pay_card_no' => $encryptData['shieldCardNo'] ?? '',
];
if ($encryptData['result'] == 2) {
$update['order_status'] = Order::STATUS_WAIT_RECHARGE;
}
$res = Order::update($update, ['order_number' => $orderNumber]);
if ($encryptData['result'] == 2) {
RechargeService::rechargeOrder($orderNumber); // 支付成功,到直连天下充值
}
if ($res) {
return ['respCode' => 1000, 'respMsg' => '处理成功'];
} else {
return ['respCode' => 1001, 'respMsg' => '处理失败'];
}
}
/**
* 解约处理
* @param array $data
* @return int[]
*/
public static function releaseNotify(array $data)
{
Log::info('解约回调:' . json_encode($data));
$verifyRes = CmbLifeUtils::verifyForResponse($data);
if (!$verifyRes) {
return ['respCode' => 1001, 'msg' => '验签不通过'];
}
$requestSerial = $data['requestSerial'];
$sign = Sign::getByRequestSerial($requestSerial);
if ($sign->isEmpty() || $sign->sign_status == Sign::SIGN_STATUS_RELEASE) {
return ['respCode' => 1000, 'respMsg' => '处理成功'];
}
$sign->sign_status = Sign::SIGN_STATUS_RELEASE;
$res = $sign->save();
if ($res) {
return ['respCode' => 1000, 'respMsg' => '处理成功'];
} else {
return ['respCode' => 1000, 'respMsg' => '处理失败'];
}
}
/**
* 取消订阅
* @param int $userId
* @return mixed
*/
public static function unsubscribe($params)
{
$userId = $params['user_id'];
// 校验验证码
if (!in_array($params['phone'], ['13205115489', '13305093595', '15107950285'])) {
// 校验验证码
$codeCacheKey = SmsUtil::getSmsKey($params['phone']);
SmsUtil::compareSmsCode($codeCacheKey, $params['code']);
}
// 用户解约
$signInfo = Sign::getSubscribeByUserId($userId);
if ($signInfo->isEmpty()) {
throw new LogicException('该用户无可退订商品');
}
// 解约
$funcName = 'releaseForMerchant';
$requestParams = [
'mAgreementId' => $signInfo->m_agreement_id,
'merchantUserId' => StringUtil::maskPhone($signInfo->mobile)
];
$res = CmbHttpUtils::doPost($funcName, $requestParams);
if ($res['respCode'] == '1000') {
$signInfo->sign_status = Sign::SIGN_STATUS_RELEASE;
$signInfo->save();
}
return true;
}
/**
* 校验是否能签约
* @param int $userId
* @param string $account
* @return array
* @throws \think\db\exception\DbException
*/
public static function checkCanAgree(int $userId, string $account)
{
// 1. 该用户已经签约
$sign = Sign::getSignLastYear($userId);
if (!$sign->isEmpty()) {
return ['success' => false, 'msg' => '该用户已签约'];
}
if (!empty($account)) {
$mobileSign = Sign::getSignLastYearByMobile($account);
if (!$mobileSign->isEmpty()) {
return ['success' => false, 'msg' => '该手机号已签约!'];
}
}
// 3. 该用户解约过且近一年扣款过
$releaseSign = Sign::getReleaseLastYear($userId);
$orderCount = Order::getMonthOrderCountByUserId($userId);
if (!$releaseSign->isEmpty() && $orderCount > 0) {
return ['success' => false, 'msg' => '该用户一年内存在签约!'];
}
return ['success' => true];
}
/**
* 解约发送验证码
* @throws BusinessException
*/
public static function releaseSendSms($params)
{
$key = SmsUtil::getSmsKey($params['phone']);
SmsUtil::requestLimit($key, BusinessCacheKey::RELEASE_SMS_CODE['ttl']);
$code = SmsUtil::getSmsCode($key, BusinessCacheKey::RELEASE_SMS_CODE['ttl']);
AliSms::sendSms(['phone_numbers' => $params['phone'], 'code' => $code]);
}
/**
* 获取绑定手机号
* @param int $userId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getBindMobile(int $userId)
{
$user = User::getUserById($userId);
return ['mobile' => $user->mobile];
}
/**
* 判断是否能兑换
* @param array $params
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getExchangeStatus(array $params): array
{
$isCanAgree = self::checkCanAgree($params['user_id'], '');
return ['is_exchange' => $isCanAgree['success']];
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace app\service;
use think\facade\Filesystem;
use think\facade\Request;
use think\Collection;
/**
* @method array|null detailById($id, ?array $field = []) 获取一条数据
* @method mixed deleteById($id) 删除
* @method mixed updateOne($where, $data) 更新
* @method int insertOne($data) 新增一条记录
* @method array|null getInfoByWhere($where) 查询记录
* @method array|Collection|null getLists($where = [], $field = [], $order = []) 查询列表
* */
class BaseService
{
static $page;
protected $model;
/**
* @param $param
* @return array
*/
public static function getPageInfo($param = []): array
{
return [
'list_rows' => Request::param("pageSize", 10),
'var_page' => 'page',
'page' => Request::param("page", 1),
];
}
public static function orderBy()
{
return Request::param("orderBy", 'id desc');
}
public static function upload($data)
{
$file = $data['file'];
$basePath = config('filesystem.disks.public.root');
validate(['file' => ['fileExt:jpg,png,jpeg,bmp,xlsx,xls,webp|fileSize:20*1024*1024']])->check($data);
$path = '/images/'.date('Y').'/'.date('m').'/'.date('d');
$fullPath = $basePath.$path;
if(!file_exists($fullPath)) {
mkdir($fullPath,0777,true);
}
$imagePath = Filesystem::putFile($path,$file,'md5');
$imagePath = str_replace('\\','/',$imagePath);
return ['image_path' =>config('filesystem.disks.public.url').$imagePath ];
}
public function __call($name, $arguments)
{
//找不到方法,自动调用模型中基类方法
return call_user_func_array([$this->model, $name], $arguments);
}
}

299
app/service/CmbService.php Normal file
View File

@ -0,0 +1,299 @@
<?php
namespace app\service;
use app\exception\LogicException;
use app\model\Order;
use app\model\Product;
use app\model\Sign;
use app\model\User;
use app\service\util\BlueBrothersClientUtil;
use app\service\util\CmbLifeUtils;
use app\service\util\SmsUtil;
use app\util\CmbHttpUtils;
use app\util\StringUtil;
use think\facade\Cache;
use think\facade\Log;
class CmbService extends BaseService
{
/**
* 生成登录授权协议
* @param $params
* @return string
* @throws LogicException
*/
public static function genApprovalProtocol($params): string
{
$callback = $params['callback'] ?? '';
$funcName = 'approval';
$params = [
'clientType' => 'h5',
'responseType' => 'code',
'callback' => $callback,
'scope' => 'default,getUserInfo',
'state' => 'state'
];
return CmbLifeUtils::genProtocol($funcName, $params);
}
/**
* 获取accessToken
* @param string $code
* @return mixed|string
* @throws LogicException
*/
public static function accessToken(string $code)
{
$funcName = 'accessToken';
$res = CmbHttpUtils::doPost($funcName, ['code' => $code, 'grantType' => 'authorizationCode']);
if ($res['respCode'] == 1000) {
// 判断是否存在
$user = User::getUserByOpenId($res['cmbOpenId']);
$mobile = '';
if ($user->isEmpty()) {
// 获取手机号
$getUserRes = CmbHttpUtils::doPost('getUserInfo', ['cmbOpenId' => $res['cmbOpenId'], 'accessToken' => $res['accessToken']]);
if ($getUserRes['respCode'] == 1000) {
$decryptBody = $getUserRes['decryptBody'];
$mobile = $decryptBody['mobile'] ?? '';
}
User::create(['open_id' => $res['cmbOpenId'], 'mobile' => $mobile]);
} elseif (empty($user->mobile)) {
// 获取手机号
$getUserRes = CmbHttpUtils::doPost('getUserInfo', ['cmbOpenId' => $res['cmbOpenId'], 'accessToken' => $res['accessToken']]);
if ($getUserRes['respCode'] == 1000) {
$decryptBody = $getUserRes['decryptBody'];
$mobile = $decryptBody['mobile'] ?? '';
}
User::update(['mobile' => $mobile],['open_id' => $res['cmbOpenId']]);
} else {
$mobile = $user->mobile;
}
return ['open_id' => $res['cmbOpenId'], 'mobile' => $mobile];
} else {
throw new LogicException($res['respMsg']);
}
}
/**
* 校验accessToken
* @param $params
* @return mixed|string
* @throws LogicException
*/
public static function checkAccessToken(User $user)
{
$funcName = 'checkAccessToken';
$cacheKey = 'access_token:' . $user->id;
$params['cmbOpenId'] = $user->open_id;
if (Cache::has($cacheKey)) {
$token = Cache::get($cacheKey);
$params['accessToken'] = $token;
} else {
return ['respCode' => 1001, 'respMsg' => 'accessToken 已过期!'];
}
return CmbHttpUtils::doPost($funcName, $params);
}
/**
* 生成支付协议
* @param array $data
* @return string
* @throws LogicException
*/
public static function genPayProtocol(array $data)
{
$funcName = 'pay';
$cmbConfig = config('cmb');
$params = [
'billNo' => $data['order_number'], // 订单号
'productName' => $data['product_name'],
'amount' => $data['price'] * 100,
'bonus' => $data['bonus'], // 积分
'returnUrl' => $cmbConfig['return_url'], // 掌上生活客户端支付结果重定向页面地址
'notifyUrl' => $cmbConfig['notify_url'], // 后台通知接口地址
'orderDetailUrl' => '', // orderDetailUrl
'payPeriod' => 1800, // 剩余的可支付时间(秒)建议24小时内最长7天 1800
];
return CmbLifeUtils::genProtocol($funcName, $params);
}
// 支付结果回调
public static function notify($params)
{
$orderNumber = $params['billNo'];
if ($params['result'] == 2) { // 成功
// 修改订单数据
Order::updateChangeData(['pay_time' => strtotime($params['payDate']),
'pay_status' => Order::PAY_STATUS_PAID,
'order_status' => Order::STATUS_RECHARGE_ING,
], $orderNumber);
// 直连天下充值
} elseif ($params['result'] == 3) {
Order::updateChangeData(['pay_time' => strtotime($params['payDate']),
'pay_status' => Order::PAY_STATUS_FAIL
], $orderNumber);
}
}
/**
* 支付订单查询接口
* @return mixed|string
* @throws LogicException
*/
public static function getPayOrder(string $orderNumber)
{
$funcName = 'getPayOrder';
$params['billNo'] = $orderNumber; // 订单号
$res = CmbHttpUtils::doPost($funcName, $params);
if ($res['respCode'] != '1000') {
return true;
}
$order = Order::getByOrderNumber($orderNumber);
if ($res['result'] == 2 && $order->order_status == Order::STATUS_WAIT_RECHARGE) {
return true;
}
// 支付结果 1待支付2成功3失败4未知5处理中
$order->pay_status = $res['result'];
if ($res['result'] == 2) { // 支付成功
$order->order_status = Order::STATUS_WAIT_RECHARGE;
}
$order->pay_time = isset($res['createTime']) ? strtotime($res['createTime']) : time();
$order->pay_type = $res['payType'] ?? '';
$order->ref_num = $res['refNum'] ?? '';
$order->pay_card_no = $res['shieldCardNo'] ?? '';
$order->save(); // 更新订单数据
//直连天下充值
if ($res['result'] == 2) { // 扣款成功
RechargeService::rechargeOrder($orderNumber);
}
if ($res['result'] == 3) { // 扣款失败 解约
self::releaseMerchant($order->agreement_id);
}
return true;
}
/**
* 订单退款
* @param string $orderNumber
* @return true
*/
public static function refund(string $orderNumber)
{
$funcName = 'refund';
$order = Order::getByOrderNumber($orderNumber);
if ($order->isEmpty()) {
throw new LogicException('订单不存在!');
}
$refundToken = uniqid();
$params['billNo'] = $order->order_number;
$params['amount'] = $order->price * 100;
$params['bonus'] = $order->bonus;
$params['refundToken'] = $refundToken;
$res = CmbHttpUtils::doPost($funcName, $params);
$order->refund_status = Order::REFUND_STATUS_WAIT; // 待退款
$order->refund_serial = $refundToken;
if ($res['respCode'] == '1000') {
$order->refund_status = $res['refundStatus'];
$order->refund_time = time();
}
$order->save();
return true;
}
/**
* 主动获取订单退款信息
* @param Order $order
* @return true
*/
public static function getRefundOrder(Order $order)
{
$funcName = 'getRefundOrder';
$params['billNo'] = $order->order_number; // 订单号
$params['refundToken'] = $order->refund_serial; // 退款流水号
$res = CmbHttpUtils::doPost($funcName, $params);
if ($res['respCode'] == '1000') {
$order->refund_status = $res['refundStatus'] ?? Order::REFUND_STATUS_WAIT;
$order->save();
}
return true;
}
/**
* 手机号绑定
* @param $params
* @return true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function bindMobile($params)
{
$openId = $params['open_id'] ?? '';
if (empty($openId)) {
throw new LogicException('该用户未授权!');
}
$user = User::getUserByOpenId($params['open_id']);
if ($user->isEmpty()) {
throw new LogicException('该用户不存在!');
}
if (!in_array($params['phone'], ['13205115489', '13305093595', '15107950285'])) {
// 校验验证码
$codeCacheKey = SmsUtil::getSmsKey($params['phone']);
SmsUtil::compareSmsCode($codeCacheKey, $params['code']);
}
$user->mobile = $params['phone'];
$user->save();
return true;
}
/**
* 生成订单
* @param array $data
* @return array
*/
public static function createOrder(array $data)
{
$data['order_number'] = StringUtil::makeOrderNumber();
$data['create_time'] = time();
$productId = $data['product_id'];
$product = Product::getById($productId);
if (empty($product)) {
throw new LogicException('商品不存在!');
}
$data['price'] = config('cmb.continue_price');
$data['bonus'] = config('cmb.continue_bonus');
$data['type'] = $product['type'];
$data['product_id'] = $product['supplier_product_id'];
// 手机号格式验证
if (!preg_match('/^1[3-9]\d{9}$/', $data['account'])) {
throw new LogicException('手机格式不正确!');
}
// 创建订单
$createRes = Order::create($data);
if (!$createRes) {
throw new LogicException('订单创建失败');
}
return ['order_number' => $data['order_number']];
}
/**
* 解约
* @param string $agreementId
* @return true
*/
public static function releaseMerchant(string $agreementId)
{
$sign = Sign::getByAgreementId($agreementId);
// 解约
$funcName = 'releaseForMerchant';
$requestParams = [
'mAgreementId' => $sign->m_agreement_id,
'merchantUserId' => $sign->user_id
];
$res = CmbHttpUtils::doPost($funcName, $requestParams);
if ($res['respCode'] == '1000') {
$sign->sign_status = Sign::SIGN_STATUS_RELEASE;
$sign->save();
}
return true;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace app\service;
use app\exception\LogicException;
use app\model\Order;
use app\model\Product;
use app\model\Sign;
use app\util\StringUtil;
use BlueBrothers\Openapi\Notify\Notify;
use BlueBrothers\Openapi\Util;
class OrderService extends BaseService
{
public function __construct()
{
$this->model = app()->make(Order::class);
}
/**
* 生成订单
* @param array $data
* @return array
*/
public function list($params)
{
return $this->model->searchPages(['order_number', 'type', 'status', 'create_at', 'user_id'], $params)->toArray();
}
/**
* 获取一个月内订单数量
* @return array
* @throws \think\db\exception\DbException
*/
public function getMonthSale(): array
{
$count = $this->model->getCountLastMonth();
return ['count' => $count];
}
}

View File

@ -0,0 +1,18 @@
<?php
declare (strict_types=1);
namespace app\service;
use app\model\ProductMap;
/**
* @author canny
* @date 2024/3/11 10:17
**/
class ProductMapService extends BaseService
{
public function __construct()
{
$this->model = app()->make(ProductMap::class);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare (strict_types=1);
namespace app\service;
use app\exception\BusinessException;
use app\model\Product;
use app\service\util\BlueBrothersClientUtil;
use BlueBrothers\Openapi\OpenapiException;
/**
* @author canny
* @date 2024/2/23 16:51
**/
class ProductService extends BaseService
{
public function __construct()
{
$this->model = app()->make(Product::class);
}
public function updateProductSaleNum($productId, $saleNum)
{
$product = $this->detailById($productId);
$this->updateOne([['id', '=', $productId]], ['sale_num' => $product['sale_num'] + $saleNum]);
}
/**
* @throws OpenapiException
* @throws BusinessException
*/
public function sync()
{
$list = BlueBrothersClientUtil::getClient()->RechargeProduct();
if (empty($list['products'])) {
throw new BusinessException('未查询到关联商品.');
}
// $productMapService = app()->make(ProductMapService::class);
foreach ($list['products'] as $product) {
$supplierProduct = Product::getBySupplierProductId($product['product_id']);
if ($supplierProduct->isEmpty()) {
Product::create([
'name' => $product['item_name'],
'face_amount' => $product['original_price'],
'price' => $product['channel_price'],
'supplier_product_id' => $product['product_id']
]);
} else {
Product::update([
'name' => $product['item_name'],
'face_amount' => $product['original_price'],
'price' => $product['channel_price']],
['supplier_product_id' => $product['product_id']]);
}
}
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace app\service;
use app\model\Order;
use app\model\Sign;
use app\service\util\BlueBrothersClientUtil;
use BlueBrothers\Openapi\Util;
use think\facade\Log;
class RechargeService
{
private $merchantId;
private $secretKey;
public function __construct()
{
$this->merchantId = env('blue_brother.merchant_id', 23329);
$this->secretKey = env('blue_brother.merchant_key', '8db16e8cc8363ed4eb4c14f9520bcc32');
}
/**
* 直连天下提交充值处理
* @param string $orderNumber
* @return true
* @throws \BlueBrothers\Openapi\OpenapiException
* @throws \app\exception\BusinessException
*/
public static function rechargeOrder(string $orderNumber)
{
$order = Order::getByOrderNumber($orderNumber);
if ($order['pay_status'] == Order::PAY_STATUS_PAID && $order['order_status'] == Order::STATUS_WAIT_RECHARGE) { // 直连天下充值
$rechargeOrder = [
'outTradeNo' => $order["order_number"],// 合作商系统内部订单号,同一商户下不可重复, 同微信、支付宝的out_trade_no类似
'productId' => $order["product_id"],// 商品编码
'number' => 1,
'notifyUrl' => config('cmb.recharge_notify_url'),// 异步通知地址
'rechargeAccount' => $order["account"],// 手机号,非必传
'accountType' => 1,// 充值账号类型,1手机号2QQ号0其他
];
$res = BlueBrothersClientUtil::getClient()->RechargeOrder($rechargeOrder);
if ($res['code'] == 2000) {
$order->order_status = Order::STATUS_RECHARGE_ING;
$order->save();
}
}
return true;
}
/**
* 充值回调
* @param $params
* @return string
* @throws \BlueBrothers\Openapi\OpenapiException
* @throws \app\exception\BusinessException
*/
public function rechargeNotify($params)
{
Log::info('充值回调:' . json_encode($params));
$sign = Util::getSign($this->secretKey, $params);
if ($params['sign'] != $sign) {
return '';
}
$orderNumber = $params['outTradeNo'];
$status = $params['status'];
$order = Order::getByOrderNumber($orderNumber);
if ($order->order_status == Order::STATUS_RECHARGE_SUCCESS) {
return 'success';
}
switch ($status) {
case '01':
$orderStatus = Order::STATUS_RECHARGE_SUCCESS;
break;
case '03':
$orderStatus = Order::STATUS_RECHARGE_FAIL;
break;
default:
$orderStatus = Order::STATUS_RECHARGE_ING;
break;
}
if ($status == '01' && $order->order_status == Order::STATUS_RECHARGE_ING) { // 充值成功更新协议扣款时间
$sign = Sign::getByAgreementId($order->agreement_id);
$sign->agree_recharge_time = strtotime(date('Y-m-d'));
$sign->save();
}
$order->order_status = $orderStatus;
$order->save();
if ($status == '03' && $order->refund_status == 0) { // 充值失败,退款
CmbService::refund($orderNumber);
}
if ($status == '04') { // 执行手动拉取
$this->queryRechargeOrder($orderNumber);
}
return 'success';
}
/**
* 主动拉取充值订单状态
* @param string $orderNumber
* @return string|true
* @throws \BlueBrothers\Openapi\OpenapiException
* @throws \app\exception\BusinessException
*/
public function queryRechargeOrder(string $orderNumber)
{
$res = BlueBrothersClientUtil::getClient()->RechargeQuery($orderNumber);
if ($res['code'] !== '0000') {
return '查询失败';
}
$status = $res['status'];
switch ($status) {
case '01':
$orderStatus = Order::STATUS_RECHARGE_SUCCESS;
break;
case '03':
$orderStatus = Order::STATUS_RECHARGE_FAIL;
break;
default:
$orderStatus = Order::STATUS_RECHARGE_ING;
break;
}
$order = Order::getByOrderNumber($orderNumber);
if ($order->order_status == Order::STATUS_RECHARGE_SUCCESS) {
return true;
}
if ($status == '01' && $order->order_status == Order::STATUS_RECHARGE_ING) { // 充值成功更新协议扣款时间
$sign = Sign::getByAgreementId($order->agreement_id);
$sign->agree_recharge_time = strtotime(date('Y-m-d'));
$sign->save();
}
$order->order_status = $orderStatus;
$order->save();
if ($status == '03' && $order->refund_status == 0) {
CmbService::refund($orderNumber);
}
return true;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace app\service;
use app\config\BusinessCacheKey;
use app\exception\BusinessException;
use app\service\util\SmsUtil;
use app\sms\AliSms;
use think\facade\Log;
class UserService extends BaseService
{
public function getUrl($params)
{
}
/**
* @throws BusinessException
*/
public function loginSendSms($params)
{
Log::info('参数' . json_encode($params));
$key = SmsUtil::getSmsKey($params['phone']);
SmsUtil::requestLimit($key, BusinessCacheKey::SITE_SMS_CODE['ttl']);
$code = SmsUtil::getSmsCode($key, BusinessCacheKey::SITE_SMS_CODE['ttl']);
AliSms::sendSms(['phone_numbers' => $params['phone'], 'code' => $code]);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare (strict_types=1);
namespace app\service\util;
use app\exception\BusinessException;
use BlueBrothers\Openapi\Api\Client;
use think\facade\Log;
/**
* @author canny
* @date 2024/3/11 9:48
**/
class BlueBrothersClientUtil
{
public static function getClient(): Client
{
$merchantId = env('blue_brother.merchant_id', 23329);
$secretKey = env('blue_brother.secret_key', '8db16e8cc8363ed4eb4c14f9520bcc32');
$prodFlag = env('blue_brother.is_prod', false);
try {
return new Client($merchantId, $secretKey, $prodFlag, 10);
} catch (\Exception $e) {
Log::error("蓝色兄弟api客户端初始化失败:" . $e->getMessage());
throw new BusinessException('客户端初始化失败.');
}
}
}

View File

@ -0,0 +1,48 @@
<?php
declare (strict_types=1);
namespace app\service\util;
use think\captcha\facade\Captcha;
/**
* @author canny
* @date 2024/1/3 16:06
**/
class CaptchaUtil
{
const CAPTCHA_CACHE_KEY = 'captcha:';
const CACHE_TTL = 1800;
public static function create($sceneConfig = ''): array
{
$res = Captcha::create($sceneConfig);
$base64Image = 'data:image/png;base64,' . base64_encode($res->getData());
$key = session('captcha.key');
$uuid = uniqid();
RedisService::getRedisInstance()->set(self::getKey($uuid), $key, 'ex', self::CACHE_TTL);
return ['uuid' => $uuid, 'img' => $base64Image];
}
public static function check($uuid, $code): bool
{
$redis = RedisService::getRedisInstance();
$key = $redis->get(self::getKey($uuid));
if (empty($key)) {
return false;
}
$code = mb_strtolower($code, 'UTF-8');
$res = password_verify($code, $key);
if ($res) {
$redis->del(self::getKey($uuid));
}
return $res;
}
private static function getKey($uuid): string
{
return self::CAPTCHA_CACHE_KEY . $uuid;
}
}

View File

@ -0,0 +1,253 @@
<?php
namespace app\service\util;
use app\exception\LogicException;
use app\util\sm\cmb\Sm;
use app\util\StringUtil;
class CmbLifeUtils
{
protected static string $cmbLifeProtocolPrefix = 'cmblife://';
/**
* 生成掌上生活协议,带有签名
* @param string $funcName
* @param array $params
* @return string
* @throws LogicException
*/
public static function genProtocol(string $funcName, array $params): string
{
if (empty($funcName)) {
throw new LogicException('funcName不能为空');
}
// 处理公共参数
$cmbConfig = config('cmb');
$params['mid'] = $cmbConfig['mid'];
$params['aid'] = $cmbConfig['aid'];
$params['date'] = date('YmdHis');
$params['random'] = StringUtil::generateRandomString(32);// 随机数
$params['keyAlias'] = 'CO_PUB_KEY_SM2';
$params['cmbKeyAlias'] = 'SM2_CMBLIFE';
$signBody = self::assembleProtocol($funcName, $params, false); // 不需要urlEncode
$signKey = $cmbConfig['merchant_sm2_pri_key'];
$sign = Sm::sign($signKey, $signBody); //sm2
$params['sign'] = $sign;
return self::assembleProtocol($funcName, $params, true);
}
/***
* 拼接掌上生活协议
* @param String $funcName
* @param array $params
* @param bool $isUrlEncode
* @return string
*/
public static function assembleProtocol(string $funcName, array $params, bool $isUrlEncode)
{
$prefix = self::$cmbLifeProtocolPrefix . $funcName;
return self::getAssembleUrl($prefix, $params, $isUrlEncode);
}
/**
* 拼接签名字符串
* @param String $prefix
* @param String $queryString
* @return String
*/
public static function assembleUrl(string $prefix, string $queryString): string
{
if (empty($prefix)) {
return $queryString;
} else {
$char = strpos($prefix, '?') !== false ? '&' : '?';
return $prefix . $char . $queryString;
}
}
/**
* 拼接签名字符串
* @param string $prefix
* @param array $params
* @param bool $isUrlEncode
* @return String
*/
public static function getAssembleUrl(string $prefix, array $params, bool $isUrlEncode): string
{
return self::assembleUrl($prefix, self::mapToQueryString($params, true, $isUrlEncode));
}
/**
* 参数转为queryString
* @param array $params
* @param bool $isSort 是否排序
* @param bool $isUrlEncode 是否需要urlEncode
* @return string
*/
public static function mapToQueryString(array $params, bool $isSort, bool $isUrlEncode = true): string
{
if ($isSort) {
ksort($params);
}
$queryString = '';
foreach ($params as $key => $value) {
if (empty($value)) {
continue;
}
if ($isUrlEncode) {
$value = urlencode($value);
}
$queryString .= $key . '=' . $value . '&';
}
return trim($queryString, '&');
}
/**
* 加密
* @param string $encryptBody
* @param string $encryptKey 加密使用的key SM2公钥
* @return string
* @throws LogicException
*/
public static function encrypt(string $encryptBody, string $encryptKey)
{
if (empty($encryptBody)) {
throw new LogicException('报文不能为空');
}
if (empty($encryptKey)) {
throw new LogicException('公钥不能为空');
}
return Sm::sm4Encrypt($encryptKey, $encryptBody);
}
/**
* 解密
* @param string $encryptBody
* @param string $decryptKey 解密使用的Key SM2私钥
* @throws LogicException
*/
public static function decrypt(string $encryptBody, string $decryptKey)
{
if (empty($encryptBody)) {
throw new LogicException('报文不能为空!');
}
if (empty($decryptKey)) {
throw new LogicException('秘钥不能为空!');
}
return json_decode(Sm::sm4decrypt($decryptKey, $encryptBody), true);
}
/**
* 签名
* @param string $signBody
* @param string $signKey
* @param string $algorithmEnum
* @return string
* @throws LogicException
*/
public static function sign(string $signBody, string $signKey)
{
if (empty($signBody)) {
throw new LogicException('待签名数据不能为空!');
}
if (empty($signKey)) {
throw new LogicException('私钥不能为空');
}
return Sm::sign($signKey, $signBody);
}
/**
* 验签国密算法SM3WithSM2
* @param string $verifyBody
* @param string $sign
* @param string $verifyKey sm2公钥
* @return bool
* @throws LogicException
*/
public static function verify(string $verifyBody, string $sign, string $verifyKey)
{
if (empty($verifyBody)) {
throw new LogicException('验签数据不能为空');
}
if (empty($sign)) {
throw new LogicException("签名不能为空");
}
if (empty($verifyKey)) {
throw new LogicException("公钥不能为空");
}
return Sm::verify($verifyKey, $sign, $verifyBody);
}
/**
* 对请求签名
* @param string $funcName
* @param array $params
* @param string $signKey sm2 私钥
* @param string $signAlgorithm SM3WithSM2
* @return string
*/
public static function signForRequest(string $funcName, array $params, string $signKey, string $signAlgorithm = 'SM3WithSM2')
{
if (!empty($funcName) && !strpos($funcName, '.json')) {
$funcName = $funcName . '.json';
}
$assembleUrl = self::getAssembleUrl($funcName, $params, false);
return self::sign($assembleUrl, $signKey);
}
/**
* 对响应签名
* @param array $params
* @return string
* @throws LogicException
*/
public static function signForResponse(array $params)
{
if (empty($params)) {
throw new LogicException('参数不能为空');
}
$cmbConfig = config('cmb');
$signKey = $cmbConfig['merchant_sm2_pri_key'];
$assembleUrl = self::getAssembleUrl('', $params, false);
unset($params['sign']);
return self::sign($assembleUrl, $signKey);
}
/**
* 对响应验签
* @param array $data
* @return bool true 验签成功 false 验签失败
* @throws LogicException
*/
public static function verifyForResponse(array $data): bool
{
if (empty($data)) {
throw new LogicException('参数不能为空');
}
$cmbConfig = config('cmb');
$verifyKey = $cmbConfig['merchant_sm2_pub_key'];
$sign = $data['sign'];
unset($data['sign']);
return self::verify(self::getAssembleUrl('', $data, false), $sign, $verifyKey);
}
/**
* @param array $params
* @param string $verifyKey 验签所使用的Key为掌上生活公钥
* @return bool
* @throws LogicException
*/
public static function verifyForRequest(array $params, string $verifyKey): bool
{
if (empty($params)) {
throw new LogicException('参数不能为空');
}
$sign = $params['sign'];
unset($params['sign']);
$assembleUrl = self::getAssembleUrl('', $params, false);
return self::verify($assembleUrl, $sign, $verifyKey);
}
}

View File

@ -0,0 +1,65 @@
<?php
declare (strict_types=1);
namespace app\service\util;
use app\exception\BusinessException;
use Predis\Client;
/**
* @author canny
* @date 2023/10/26 9:59
**/
class DistributedLockService
{
protected Client $redis;
protected string $lockKey;
protected string $lockValue;
protected $lockTimeout;
public function __construct($lockKey, $lockTimeout = 10)
{
$this->redis = RedisService::getRedisInstance();
$this->lockKey = $lockKey;
$this->lockValue = uniqid(); // 生成一个唯一的值作为锁的值
$this->lockTimeout = $lockTimeout;
}
/**
* @throws BusinessException
*/
public function acquireLock($callback)
{
$result = $this->redis->set($this->lockKey, $this->lockValue, 'ex', $this->lockTimeout, 'nx');
if (empty($result) || $result->getPayload() !== 'OK') {
throw new BusinessException("处理中,请稍后再试");
}
try {
return $callback();
}catch (\Throwable $throwable){
throw new BusinessException($throwable->getMessage(), $throwable->getCode());
} finally {
$this->releaseLock();
}
}
private function releaseLock()
{
// 释放锁,只有锁的值匹配时才会删除锁,以避免误释放
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
";
$this->redis->eval($script, 1, $this->lockKey, $this->lockValue);
}
public function renewLock()
{
// 续期锁的过期时间
$this->redis->expire($this->lockKey, $this->lockTimeout);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare (strict_types=1);
namespace app\service\util;
use app\service\ConfigService;
use function GuzzleHttp\Psr7\str;
/**
* @author canny
* @date 2023/10/20 15:28
**/
class PriceCalculatorService
{
public static function add($a, $b, $scale = 2): string
{
return bcadd((string)$a, (string)$b, $scale); // 保留两位小数
}
public static function subtract($a, $b, $scale = 2): string
{
return bcsub((string)$a, (string)$b, $scale);
}
public static function multiply($a, $b, $scale = 2): string
{
return bcmul((string)$a, (string)$b, $scale);
}
public static function divide($a, $b, $scale = 2): ?string
{
if (bccomp((string)$b, '0', 2) === 0) {
throw new \Exception('不允许除以零');
}
return bcdiv((string)$a, (string)$b, $scale);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace app\service\util;
use Predis\Client;
class RedisService
{
protected static $client;
public static function getRedisInstance(): Client
{
if(!self::$client){
self::$client = new Client([
'scheme'=>'tcp',
'host' => env('redis.host'),
'port' => env('redis.port'),
'password'=>env('redis.password')
]);
}
return self::$client;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare (strict_types=1);
namespace app\service\util;
use app\config\BusinessCacheKey;
use app\exception\BusinessException;
use think\helper\Str;
/**
* @author canny
* @date 2023/11/20 15:03
**/
class SmsUtil
{
public static function requestLimit($key, $expire, $limitSecond = 60)
{
$ttl = RedisService::getRedisInstance()->ttl($key);
if (!empty($ttl) && $expire - $ttl < $limitSecond) {
throw new BusinessException("请求过于频繁");
}
}
public static function getSmsCode(string $key, int $ttl): string
{
$redis = RedisService::getRedisInstance();
$redis->del($key);
$code = Str::random(6, 1);
$redis->set($key, $code, 'ex', $ttl);
return $code;
}
public static function compareSmsCode(string $codeCacheKey, $code)
{
$redis = RedisService::getRedisInstance();
$cacheCode = $redis->get($codeCacheKey);
if (empty($cacheCode) || $code != $cacheCode) {
throw new BusinessException("验证码错误");
}
$redis->del($codeCacheKey);
}
public static function getSmsKey($mobile): string
{
return BusinessCacheKey::SITE_SMS_CODE['key'] . ":" . $mobile;
}
}

49
app/sms/AliSms.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace app\sms;
use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi;
use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
use Darabonba\OpenApi\Models\Config;
class AliSms
{
public static function getInstance($appId,$appKey)
{
$config = new Config([
'accessKeyId' => $appId,
'accessKeySecret' => $appKey
]);
$config->endpoint = 'dysmsapi.aliyuncs.com';
return new Dysmsapi($config);
}
/** 阿里云发送短信
* phone_numbers 手机号码
* 目前短信验证码模板变量为 code
* 其他参数根据消息模板传递
* @param $data
* @param $type
* @return array
*/
public static function sendSms($data,$type = 1)
{
$template = env('alisms.template_sms_code');
if($type == 2) {
$template = env('alisms.template_issue_code');
}
$client = self::getInstance(env('alisms.app_id'),env('alisms.app_key'));
$sms = new SendSmsRequest([]);
$sms->phoneNumbers = $data['phone_numbers'];
$sms->templateCode = $template;
$sms->signName = env('alisms.sign_name');
$sms->templateParam = json_encode($data);
$runtime = new RuntimeOptions([]);
$result = $client->sendSmsWithOptions($sms,$runtime);
return ['code' => $result->statusCode,'message' => $result->body->message];
}
}

86
app/util/AliOss.php Normal file
View File

@ -0,0 +1,86 @@
<?php
declare (strict_types=1);
namespace app\util;
use app\exception\BusinessException;
use OSS\Core\OssException;
use OSS\OssClient;
/**
* @author canny
* @date 2024/1/6 14:23
**/
class AliOss
{
private OssClient $ossClient;
private string $accessKeyId;
private string $accessKeySecret;
private string $endpoint;
private string $bucket;
private string $dir;
const HTTP_PROTOCOL = 'https://';
const CONTENT_SIZE_LIMIT = 50 * 1024 * 1024; //文件上传限制 50m
public function __construct()
{
$this->accessKeyId = env('ali-oss.accessKey');
$this->accessKeySecret = env('ali-oss.accessKeySecret');
$this->endpoint = env('ali-oss.endpoint');
$this->bucket = env('ali-oss.bucket');
$this->dir = env('ali-oss.dir', '/');
try {
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
} catch (OssException $e) {
throw new BusinessException("oss参数错误");
}
}
public function getOssClient(): OssClient
{
return $this->ossClient;
}
public function upload($file): string
{
validate(['file' => ['fileExt:jpg,png,jpeg,bmp,xlsx,xls,webp|fileSize:20*1024*1024']])->check(['file' => $file]);
$fileName = $this->dir . md5(uniqid()) . '.' . $file->getOriginalExtension();
$this->ossClient->uploadFile($this->bucket, $fileName, $file);
return self::HTTP_PROTOCOL . $this->bucket . '.' . $this->endpoint . '/' . $fileName;
}
public function getSignature(): array
{
$host = 'https://' . $this->bucket . '.' . $this->endpoint;
$end = time() + 20;
$dir = $this->dir . 'uploads/' . date('Ym', time()) . '/';//文件在oss中保存目录
//设置该policy超时时间是20s. 即这个policy过了这个有效时间将不能访问
$arr = ['expiration' => $this->gmt_iso8601($end),
'conditions' => [
['content-length-range', 0, self::CONTENT_SIZE_LIMIT], //最大文件大小.用户可以自己设置
['starts-with', '$key', $dir] //表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录
]];
$base64_policy = base64_encode(json_encode($arr));
$signature = base64_encode(hash_hmac('sha1', $base64_policy, $this->accessKeySecret, true));
$response['accessid'] = $this->accessKeyId;
$response['host'] = $host;
$response['policy'] = $base64_policy;
$response['signature'] = $signature;
$response['expire'] = $end;
$response['dir'] = $dir;
return $response;
}
private function gmt_iso8601($time): string
{
$dtStr = date("c", $time);
$dateTime = new \DateTime($dtStr);
$expiration = $dateTime->format(\DateTime::ISO8601);
$pos = strpos($expiration, '+');
$expiration = substr($expiration, 0, $pos);
return $expiration . "Z";
}
}

56
app/util/CmbHttpUtils.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace app\util;
use app\exception\LogicException;
use app\service\util\CmbLifeUtils;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
class CmbHttpUtils
{
/**
* post 提交
* @param $funcName
* @param $requestParams
* @return mixed|string
* @throws LogicException
*/
public static function doPost($funcName, $requestParams)
{
//添加公共参数
$cmbConfig = config('cmb');
$requestParams['mid'] = $cmbConfig['mid'];
$requestParams['aid'] = $cmbConfig['aid'];
$requestParams['date'] = date('YmdHis');
$requestParams['random'] = StringUtil::generateRandomString(32); // 随机字符串,
$requestParams['keyAlias'] = 'CO_PUB_KEY_SM2';
$requestParams['cmbKeyAlias'] = 'SM2_CMBLIFE'; //当 cmbKeyAlias 为 SM2_CMBLIFE 时,走国密算法进行验签
$sign = CmbLifeUtils::signForRequest($funcName, $requestParams, $cmbConfig['merchant_sm2_pri_key']);
$requestParams['sign'] = $sign;
$url = env('cmb.api_host') . $funcName . '.json';
$data = self::httpPost($url, $requestParams);
$res = \GuzzleHttp\json_decode($data, true);
// 校验返回报文
if (isset($res['sign'])) {
$verifyResult = CmbLifeUtils::verifyForResponse($res);
if ($verifyResult) { // 验签成功获取数据
if (isset($res['encryptBody'])) {
$res['decryptBody'] = CmbLifeUtils::decrypt((string)$res['encryptBody'], $cmbConfig['merchant_sm2_pri_key']);
} else {
return $res;
}
}
}
return $res;
}
private static function httpPost(string $uri, array $data)
{
$client = new Client([
'base_uri' => env('cmb.api_host'),
'timeout' => 50,
]);
$response = $client->request('POST', $uri, [
RequestOptions::FORM_PARAMS => $data,
]);
return $response->getBody()->getContents();
}
}

46
app/util/ExcelUtil.php Normal file
View File

@ -0,0 +1,46 @@
<?php
declare (strict_types=1);
namespace app\util;
use PhpOffice\PhpSpreadsheet\IOFactory;
/**
* @author canny
* @date 2024/1/17 14:39
**/
class ExcelUtil
{
const SUFFIX_XLSX = 'Xlsx';
const SUFFIX_Xls = 'Xls';
public static function downloadExcel($newExcel, $filename, $format)
{
// $format只能为 Xlsx 或 Xls
if ($format == 'Xlsx') {
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
} elseif ($format == 'Xls') {
header('Content-Type: application/vnd.ms-excel');
}
header("Content-Disposition: attachment;filename="
. $filename . date('Y-m-d') . '.' . strtolower($format));
header('Cache-Control: max-age=0');
$objWriter = IOFactory::createWriter($newExcel, $format);
$objWriter->save('php://output');
//通过php保存在本地的时候需要用到
//$objWriter->save($dir.'/demo.xlsx');
//以下为需要用到IE时候设置
// If you're serving to IE 9, then the following may be needed
//header('Cache-Control: max-age=1');
// If you're serving to IE over SSL, then the following may be needed
//header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
//header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
//header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
//header('Pragma: public'); // HTTP/1.0
exit;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare (strict_types=1);
namespace app\util;
use think\facade\Request;
/**
* @author canny
* @date 2023/12/19 11:18
**/
class FrontSessionUtil
{
const TOKEN_KEY = "token";
private static int $siteId = 1;
private static object $user;
public static function setUser($user)
{
self::$user = $user;
}
public static function getSiteId(): int
{
return self::$siteId;
}
public static function getUser(): object
{
return self::$user;
}
public static function getUserId(): int
{
return (int)self::$user->id;
}
public static function getToken()
{
return Request::cookie(self::TOKEN_KEY, Request::header(self::TOKEN_KEY));
}
public static function getPartnerId(): int
{
return (int)self::$user->partner_id;
}
}

48
app/util/SessionUtil.php Normal file
View File

@ -0,0 +1,48 @@
<?php
declare (strict_types=1);
namespace app\util;
use think\facade\Request;
/**
* @author canny
* @date 2023/12/5 11:18
**/
class SessionUtil
{
const ADMIN_TOKEN_KEY = "token";
private static int $siteId = 1;
private static object $user;
public static function setUser($user)
{
self::$user = $user;
}
public static function getSiteId(): int
{
return self::$siteId;
}
public static function getUser(): object
{
return !empty(self::$user) ? self::$user : json_decode("");
}
public static function getUserId(): int
{
return (int)self::$user->id;
}
public static function getToken()
{
return Request::cookie(self::ADMIN_TOKEN_KEY, Request::header(self::ADMIN_TOKEN_KEY));
}
public static function isSuperAdmin(): bool
{
return !empty(self::getUser()->super_admin);
}
}

48
app/util/StringUtil.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace app\util;
class StringUtil
{
/**
* 随机生成字符串
* @param int $length
* @return string
*/
public static function generateRandomString(int $length): string
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, strlen($characters) - 1)];
}
return $randomString;
}
/**
* 生成订单号
* @return string
*/
public static function makeOrderNumber():string
{
return date_create()->format('ymdHisu');
}
/**
* 生成商户端协议号
* @return string
*/
public static function makeAgreementId()
{
return md5(date_create()->format('ymdHisu'));
}
/**
* 手机号加星
* @param $phone
* @return array|string|string[]|null
*/
public static function maskPhone($phone) {
return preg_replace("/(\d{3})\d{4}(\d{4})/", "$1****$2", $phone);
}
}

48
app/util/sm/Exec.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace app\util\sm;
class Exec
{
protected static function dirPath(): string
{
return dirname(__FILE__) . DIRECTORY_SEPARATOR;
}
public static function exec(string $cmd): string
{
$output = null;
$returnVal = null;
$result = exec($cmd, $output, $returnVal);
if (0 !== $returnVal) {
throw new \LogicException('exec执行异常' . $cmd);
}
if (!$result) {
throw new \LogicException('exec执行返回值异常' . $cmd);
}
return trim($result);
}
public static function exec2(string $cmd)
{
$output = null;
$returnVal = null;
exec($cmd, $output, $returnVal);
if (0 !== $returnVal) {
throw new \LogicException('exec执行异常' . $cmd);
}
if (is_array($output)) {
if (count($output) == 2 && $output[1] !== 'success') {
throw new \LogicException($output[1]);
}
return $output[0];
}
return $output;
}
}

63
app/util/sm/cmb/Sm.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace app\util\sm\cmb;
use app\util\sm\Exec;
class Sm extends Exec
{
/**
* sm4 加密
* @param string $key
* @param string $plaintext
* @return string
*/
public static function sm4Encrypt(string $key, string $plaintext): string
{
$content = base64_encode($plaintext);
$cmd = self::dirPath() . 'sm cmblife encrypt --input="' . $content . '" --sopPublicKey="' . $key . '" 2>&1';
return self::exec2($cmd);
}
/**
* sm4 解密
* @param string $key
* @param string $plaintext
* @return string
*/
public static function sm4decrypt(string $key, string $plaintext): string
{
$content = base64_encode($plaintext);
$cmd = self::dirPath() . 'sm cmblife decrypt --input="' . $content . '" --priKey="' . $key . '" 2>&1';
return self::exec2($cmd);
}
/**
* sm2 签名
* @param string $prk
* @param string $plaintext
* @return string
*/
public static function sign(string $prk, string $plaintext): string
{
$content = base64_encode($plaintext);
$cmd = self::dirPath() . 'sm cmblife sign --input="' . $content . '" --priKey="' . $prk . '" 2>&1';
return self::exec2($cmd);
}
/**
* 验证签名
* @param string $puk
* @param string $signStr
* @param string $plaintext
* @return bool
*/
public static function verify(string $puk, string $signStr, string $plaintext): bool
{
$content = base64_encode($plaintext);
$cmd = self::dirPath() . 'sm cmblife verify --input="' . $content . '" --sopPublicKey="' . $puk . '" --sign="' . $signStr . '" 2>&1';
return self::exec2($cmd) == 'ok';
}
}

BIN
app/util/sm/sm Normal file

Binary file not shown.

View File

@ -0,0 +1,17 @@
<?php
declare (strict_types=1);
namespace app\validate;
use think\Validate;
/**
* @author canny
* @date 2023/11/29 14:03
**/
class IdRequireValidate extends Validate
{
protected $rule = [
'id' => 'require|integer'
];
}

View File

@ -0,0 +1,24 @@
<?php
declare (strict_types = 1);
namespace app\validate\admin;
use app\service\util\CaptchaUtil;
use think\exception\ValidateException;
use think\Validate;
class LoginValidate extends Validate
{
protected $rule = [
'uuid|验证码uuid' => 'require',
'verify_code|验证码' => 'require|checkCode'
];
public function checkCode($value, $rule, $data = []): bool
{
if (!devAuth() && !CaptchaUtil::check($data['uuid'], $value)) {
throw new ValidateException("图形验证码错误");
}
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare (strict_types=1);
namespace app\validate\admin;
use think\Validate;
/**
* @author canny
* @date 2023/10/20 14:57
**/
class ProductValidate extends Validate
{
protected $rule = [
'id' => 'require|integer',
'name|资产名称' => 'require',
'unit|规格单位' => 'require',
];
public function sceneCreate(): \think\Validate
{
return $this->only(['name', 'unit']);
}
public function sceneUpdate(): \think\Validate
{
return $this->only(['name', 'unit', 'id']);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare (strict_types=1);
namespace app\validate\front;
use think\Validate;
/**
* @author canny
* @date 2023/11/29 15:59
**/
class LoginValidate extends Validate
{
protected $rule = [
'code' => 'require',
'phone' => 'require|mobile',
];
public function sceneSendSms(): Validate
{
return $this->only(['phone']);
}
public function sceneBindMobile(): Validate
{
return $this->only(['code','phone']);
}
public function sceneSendReleaseSms(): Validate
{
return $this->only(['phone']);
}
}

78
composer.json Normal file
View File

@ -0,0 +1,78 @@
{
"name": "topthink/think",
"description": "the new thinkphp framework",
"type": "project",
"keywords": [
"framework",
"thinkphp",
"ORM"
],
"homepage": "https://www.thinkphp.cn/",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
},
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"require": {
"php": ">=7.4",
"topthink/framework": "^6.1.0",
"topthink/think-orm": "^2.0",
"topthink/think-filesystem": "^1.0",
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"predis/predis": "^2.2",
"firebase/php-jwt": "^6.8",
"alibabacloud/dysmsapi-20170525": "2.0.23",
"alibabacloud/darabonba-openapi": "^0.2.9",
"alibabacloud/tea-console": "^0.1.0",
"alibabacloud/tea-utils": "^0.2.19",
"ext-bcmath": "*",
"yuanshe/wechat-sdk": "^1.0",
"topthink/think-captcha": "^3.0",
"alibabacloud/ecs-20140526": "^3.0",
"frowhy/mini-program-aes": "^1.1",
"phpoffice/phpspreadsheet": "^1.29",
"aliyuncs/oss-sdk-php": "^2.6",
"topthink/think-view": "^1.0",
"chengdu-blue-brothers/openapi-php-sdk": "^0.0.1",
"overtrue/easy-sms": "^2.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
"topthink/think-trace": "^1.0",
"phpunit/phpunit": "^9.6"
},
"autoload": {
"psr-4": {
"app\\": "app",
"extend\\":"extend"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"preferred-install": "dist",
"disable-tls": false,
"secure-http": false
},
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}

32
config/app.php Normal file
View File

@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
return [
// 应用地址
'app_host' => env('app.host', ''),
// 应用的命名空间
'app_namespace' => '',
// 是否启用路由
'with_route' => true,
// 默认应用
'default_app' => 'index',
// 默认时区
'default_timezone' => 'Asia/Shanghai',
// 应用映射(自动多应用模式有效)
'app_map' => [],
// 域名绑定(自动多应用模式有效)
'domain_bind' => [],
// 禁止URL访问的应用列表自动多应用模式有效
'deny_app_list' => [],
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',
// 显示错误信息
'show_error_msg' => false,
];

40
config/cache.php Normal file
View File

@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => env('cache.driver', 'redis'),
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
'redis' => [
// 驱动方式
'type' => 'Redis',
'host' => env('redis.host', ''),
'port' => env('redis.port', ''),
'password' => env('redis.password', ''),
'select' => env('redis.select', ''),
'prefix' => env('redis.prefix', ''),
'timeout' => env('redis.timeout',''),
'serialize' => []
]
// 更多的缓存连接
],
];

48
config/captcha.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | Captcha配置文件
// +----------------------------------------------------------------------
return [
//验证码位数
'length' => 4,
// 验证码字符集合
'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
// 验证码过期时间
'expire' => 1800,
// 是否使用中文验证码
'useZh' => false,
// 是否使用算术验证码
'math' => false,
// 是否使用背景图
'useImgBg' => false,
//验证码字符大小
'fontSize' => 25,
// 是否使用混淆曲线
'useCurve' => false,
//是否添加杂点
'useNoise' => true,
// 验证码字体 不设置则随机
'fontttf' => '',
//背景颜色
'bg' => [243, 251, 254],
// 验证码图片高度
'imageH' => 0,
// 验证码图片宽度
'imageW' => 0,
// 添加额外的验证码设置
'productExchange' => [
'length' => 4,
'codeSet' => '0123456789',
//是否添加杂点
'useNoise' => false,
// 是否使用中文验证码
'useZh' => false,
// 是否使用算术验证码
'math' => false,
'useCurve' => false,
// 是否使用背景图
'useImgBg' => false,
],
];

16
config/cmb.php Normal file
View File

@ -0,0 +1,16 @@
<?php
return [
'mid' => env('cmb.mid'), // 商户号
'aid' => env('cmb.aid'), // 应用号
'merchant_sm2_pri_key' => env('cmb.sm2_pri_key'), // sm2 私钥
'merchant_sm2_pub_key' => env('cmb.sm2_cmb_pub_key'), // sm2 公钥测试
'merchant_sm2_self_pub_key' => env('cmb.sm2_pub_key'), // sm2 公钥测试
'agree_notify_url' => env('cmb.agree_notify_url'),
'pay_notify_url' => env('cmb.pay_notify_url'),
'release_notify_url' => env('cmb.release_notify_url'),
'recharge_notify_url' => env('cmb.recharge_notify_url'),
'continue_price' => env('cmb.continue_price'),
'continue_bonus' => env('cmb.continue_bonus'),
'use_bonus_mouth' => env('cmb.use_bonus_mouth'),
'product_id' => env('cmb.product_id'),
];

15
config/console.php Normal file
View File

@ -0,0 +1,15 @@
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
// 指令定义
'commands' => [
'test' => \app\command\Activity::class, //活动过期更新状态
'agreementPay' => \app\cmd\AgreementPay::class,
'getPayOrder' => \app\cmd\GetPayOrder::class,
'getRefundOrder' => \app\cmd\getRefundOrder::class,
'queryAgreeStatus' => \app\cmd\QueryAgreeStatus::class,
'queryRechargeOrder' =>\app\cmd\QueryRechargeOrder::class
],
];

20
config/cookie.php Normal file
View File

@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | Cookie设置
// +----------------------------------------------------------------------
return [
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// 是否使用 setcookie
'setcookie' => true,
// samesite 设置,支持 'strict' 'lax'
'samesite' => '',
];

63
config/database.php Normal file
View File

@ -0,0 +1,63 @@
<?php
return [
// 默认使用的数据库连接配置
'default' => env('database.driver', 'mysql'),
// 自定义时间查询规则
'time_query_rule' => [],
// 自动写入时间戳字段
// true为自动识别类型 false关闭
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
'auto_timestamp' => true,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 时间字段配置 配置格式create_time,update_time
'datetime_field' => '',
// 数据库连接配置信息
'connections' => [
'mysql' => [
// 数据库类型
'type' => env('database.type', 'mysql'),
// 服务器地址
'hostname' => env('database.hostname', '127.0.0.1'),
// 数据库名
'database' => env('database.database', ''),
// 用户名
'username' => env('database.username', 'root'),
// 密码
'password' => env('database.password', ''),
// 端口
'hostport' => env('database.hostport', '3306'),
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => env('database.charset', 'utf8'),
// 数据库表前缀
'prefix' => env('database.prefix', ''),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 是否需要断线重连
'break_reconnect' => false,
// 监听SQL
'trigger_sql' => env('app_debug', true),
// 开启字段缓存
'fields_cache' => false,
],
// 更多的数据库配置信息
],
];

24
config/filesystem.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return [
// 默认磁盘
'default' => env('filesystem.driver', 'local'),
// 磁盘列表
'disks' => [
'local' => [
'type' => 'local',
'root' => app()->getRuntimePath() . 'storage',
],
'public' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage',
// 磁盘路径对应的外部URL路径
'url' => '/storage/',
// 可见性
'visibility' => 'public',
],
// 更多的磁盘配置信息
],
];

32
config/lang.php Normal file
View File

@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | 多语言设置
// +----------------------------------------------------------------------
return [
// 默认语言
'default_lang' => env('lang.default_lang', 'zh-cn'),
// 允许的语言列表
'allow_lang_list' => [],
// 多语言自动侦测变量名
'detect_var' => 'lang',
// 是否使用Cookie记录
'use_cookie' => true,
// 多语言cookie变量
'cookie_var' => 'think_lang',
// 多语言header变量
'header_var' => 'think-lang',
// 扩展语言包
'extend_list' => [
'zh-cn' => [
app()->getBasePath().'lang/zh-cn/message.php',
app()->getBasePath().'lang/zh-cn/code.php',
],
],
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-hans-cn' => 'zh-cn',
],
// 是否支持语言分组
'allow_group' => true,
];

45
config/log.php Normal file
View File

@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
return [
// 默认日志记录通道
'default' => env('log.channel', 'file'),
// 日志记录级别
'level' => [],
// 日志类型记录的通道 ['error'=>'email',...]
'type_channel' => [],
// 关闭全局日志写入
'close' => false,
// 全局日志处理 支持闭包
'processor' => null,
// 日志通道列表
'channels' => [
'file' => [
// 日志记录方式
'type' => 'File',
// 日志保存目录
'path' => '',
// 单文件日志写入
'single' => false,
// 独立日志级别
'apart_level' => ['command'],
// 最大日志文件数量
'max_files' => 0,
// 使用JSON格式记录
'json' => false,
// 日志处理
'processor' => null,
// 关闭通道日志写入
'close' => false,
// 日志输出格式化
'format' => '[%s][%s] %s',
// 是否实时写入
'realtime_write' => false,
],
// 其它日志通道配置
],
];

8
config/middleware.php Normal file
View File

@ -0,0 +1,8 @@
<?php
// 中间件配置
return [
// 别名或分组
'alias' => [],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [],
];

45
config/route.php Normal file
View File

@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | 路由设置
// +----------------------------------------------------------------------
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => 'html',
// URL普通方式参数 用于自动生成
'url_common_param' => true,
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => false,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
'request_cache_key' => false,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
];

19
config/session.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
return [
// session name
'name' => 'PHPSESSID',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// 驱动方式 支持file cache
'type' => 'cache',
// 存储连接标识 当type使用cache的时候有效
'store' => 'redis',
// 过期时间
'expire' => 1440,
// 前缀
'prefix' => 'enquity:',
];

25
config/sms.php Normal file
View File

@ -0,0 +1,25 @@
<?php
return [
// HTTP 请求的超时时间(秒)
'timeout' => 5.0,
// 默认发送配置
'default' => [
// 默认可用的发送网关
'gateways' => [
'aliyun',
],
],
// 可用的网关配置
'gateways' => [
'errorlog' => [
'file' => '/tmp/easy-sms.log',
],
'aliyun' => [
'access_key_id' => env('sms.aliyunaccesskeyid'),
'access_key_secret' => env('sms.aliyunaccesskeysecret'),
'sign_name' => env('sms.aliyunsignname'),
],
],
];

10
config/trace.php Normal file
View File

@ -0,0 +1,10 @@
<?php
// +----------------------------------------------------------------------
// | Trace设置 开启调试模式后有效
// +----------------------------------------------------------------------
return [
// 内置Html和Console两种方式 支持扩展
'type' => 'Html',
// 读取的日志通道名
'channel' => '',
];

25
config/view.php Normal file
View File

@ -0,0 +1,25 @@
<?php
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
return [
// 模板引擎类型使用Think
'type' => 'Think',
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
'auto_rule' => 1,
// 模板目录名
'view_dir_name' => 'view',
// 模板后缀
'view_suffix' => 'html',
// 模板文件名分隔符
'view_depr' => DIRECTORY_SEPARATOR,
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' => '}',
// 标签库标签开始标记
'taglib_begin' => '{',
// 标签库标签结束标记
'taglib_end' => '}',
];

11
docker-compose.yml Normal file
View File

@ -0,0 +1,11 @@
version: '3'
services:
market:
image: liusuifeng/php74:latest
container_name: cmb-backend
restart: always
ports:
- 8032:8000
- 9011:9000
volumes:
- .:/var/project/

0
public/.htaccess Normal file
View File

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

27
public/index.php Normal file
View File

@ -0,0 +1,27 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
require __DIR__ . '/../vendor/autoload.php';
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:POST,GET,DELETE,PUT,OPTIONS');
header( 'Access-Control-Allow-Headers:*');
// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

6
public/nginx.htaccess Normal file
View File

@ -0,0 +1,6 @@
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
break;
}
}

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

19
public/router.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id$
if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
return false;
} else {
$_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php';
require __DIR__ . "/index.php";
}

1
public/static/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!.gitignore

Some files were not shown because too many files have changed in this diff Show More