chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace addon\appmall\controller;
|
||||
|
||||
use think\facade\Db;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\library\Mailer;
|
||||
use ywxapp\model\BaseModel;
|
||||
use addon\appmall\library\AppmallSchema;
|
||||
|
||||
/**
|
||||
* 开发者中心(独立前台应用,非会员中心、非后台)
|
||||
* 开发者自助注册 / 查询令牌 / 收益查询 / 提现;完全公开(noNeedLogin=*),
|
||||
* 以开发者令牌(token)而非会员登录态认证。
|
||||
* 路由前缀:/appmall/developer/*(由 AppService::loadAddonRoutes 在 boot 阶段包 Route::group('appmall') 注册到全局路由表,不隶属于任何 thinkphp 应用)。
|
||||
* 视图:addon/appmall/view/frontend/developer/。
|
||||
*
|
||||
* 注:AddonFrontend 基类 _initialize 把 view_path 设成【主应用】view/frontend/(与插件目录不符),
|
||||
* 故此处必须在 initialize() 显式把 view_path 锁回插件目录,否则 fetch 找不到模板。
|
||||
*/
|
||||
class Developer extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = ['*'];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 锁定视图路径到插件目录(修复 AddonFrontend 设成主应用目录的坑)
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
// 确保 appmall 运营表存在(开发者中心只读开发者/收益/提现等表,避免全新安装 1146)
|
||||
AppmallSchema::ensure();
|
||||
|
||||
// 仅中心站暴露:客户机(ywxapp.api_url 指向其他服务器,见 is_market_client())作为中心站客户端,
|
||||
// 不持有 appmall_developers / appmall_addon_revenues / appmall_addon_withdrawals 等运营表,
|
||||
// 访问开发者中心直接 404。即便路由层未拦住(如路由缓存陈旧、被直连)也在此兜底。
|
||||
if (is_market_client()) {
|
||||
abort(404, '开发者中心仅在中心站(市场服务端)可用');
|
||||
}
|
||||
|
||||
$this->view->config([
|
||||
'view_path' => ADDON_PATH . 'appmall' . DIRECTORY_SEPARATOR
|
||||
. 'view' . DIRECTORY_SEPARATOR . 'frontend' . DIRECTORY_SEPARATOR,
|
||||
]);
|
||||
// 供前端 <script src=".../wxapp.js" module="{$site.app}"> 使用(前台应用目录名)
|
||||
$this->view->assign('site', ['app' => 'frontend']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册页
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->view->assign('register_open', (bool) config('appmall.allow_register', true));
|
||||
$this->view->assign('need_approval', (bool) config('appmall.need_approval', true));
|
||||
return $this->view->fetch('developer/register');
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交注册(生成令牌,按配置决定是否需要审核)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if (! $this->request->isPost()) {
|
||||
$this->result->error('访问错误');
|
||||
}
|
||||
if (! (bool) config('appmall.allow_register', true)) {
|
||||
$this->result->error('当前未开放自助注册');
|
||||
}
|
||||
|
||||
$email = trim($this->request->param('email', ''));
|
||||
$name = trim($this->request->param('username', ''));
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->result->error('邮箱格式不正确');
|
||||
}
|
||||
if ($name === '') {
|
||||
$this->result->error('请填写开发者名称');
|
||||
}
|
||||
|
||||
if (Db::name('appmall_developers')->where('email', $email)->find()) {
|
||||
$this->result->error('该邮箱已申请过开发者令牌');
|
||||
}
|
||||
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$needApproval = (bool) config('appmall.need_approval', true);
|
||||
$status = $needApproval ? 0 : 1;
|
||||
|
||||
Db::name('appmall_developers')->insert([
|
||||
'username' => $name,
|
||||
'email' => $email,
|
||||
'token' => $token,
|
||||
'status' => $status,
|
||||
'create_at' => time(),
|
||||
'update_at' => time(),
|
||||
]);
|
||||
|
||||
if ($needApproval) {
|
||||
Mailer::send($email, '开发者入驻申请已收到', $this->pendingMail($name));
|
||||
$this->result->success([], '注册成功,等待运营审核,通过后令牌将发送至您的邮箱');
|
||||
}
|
||||
|
||||
Mailer::send($email, '您的开发者令牌', $this->tokenMail($name, $token));
|
||||
$this->result->success(
|
||||
['token' => $token],
|
||||
'注册成功,请将下方令牌配置到开发者控制台的 APPMARKET_DEV_TOKEN'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我的令牌
|
||||
*/
|
||||
public function mine()
|
||||
{
|
||||
return $this->view->fetch('developer/mine');
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据邮箱取回令牌(已激活才返回,并重发邮件)
|
||||
*/
|
||||
public function lookup()
|
||||
{
|
||||
if (! $this->request->isPost()) {
|
||||
$this->result->error('访问错误');
|
||||
}
|
||||
$email = trim($this->request->param('email', ''));
|
||||
$dev = Db::name('appmall_developers')->where('email', $email)->find();
|
||||
if (! $dev) {
|
||||
$this->result->error('未找到该邮箱的开发者记录');
|
||||
}
|
||||
$status = (int) $dev['status'];
|
||||
if ($status === 0) {
|
||||
$this->result->error('您的申请正在审核中,请耐心等待');
|
||||
}
|
||||
if ($status === -1) {
|
||||
$this->result->error('开发者账号已被禁用,请联系运营');
|
||||
}
|
||||
Mailer::send($email, '您的开发者令牌', $this->tokenMail($dev['username'], $dev['token']));
|
||||
$this->result->success(
|
||||
['token' => $dev['token'], 'username' => $dev['username']],
|
||||
'令牌如下(已重新发送至邮箱)'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的收益(令牌认证):返回余额与近期收益账本
|
||||
*/
|
||||
public function earnings()
|
||||
{
|
||||
$this->ensureTables();
|
||||
$token = $this->request->param('token', '');
|
||||
$dev = Db::name('appmall_developers')->where('token', $token)->where('status', 1)->find();
|
||||
if (empty($dev)) {
|
||||
return $this->result->error('令牌无效');
|
||||
}
|
||||
$revenues = Db::name('appmall_addon_revenues')->where('developer_id', $dev['id'])
|
||||
->order('id', 'desc')->limit(20)->select()->toArray();
|
||||
$this->result->success([
|
||||
'balance' => $dev['balance'] ?? 0,
|
||||
'frozen_balance' => $dev['frozen_balance'] ?? 0,
|
||||
'revenues' => $revenues,
|
||||
], '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起提现(令牌认证):可提现余额 → 冻结,生成提现单待运营打款
|
||||
*/
|
||||
public function withdraw()
|
||||
{
|
||||
if (! $this->request->isPost()) {
|
||||
return $this->result->error('访问错误');
|
||||
}
|
||||
$this->ensureTables();
|
||||
$token = $this->request->param('token', '');
|
||||
$amount = (float) $this->request->param('amount', 0);
|
||||
$channel = $this->request->param('channel', 'alipay');
|
||||
$account = trim($this->request->param('account', ''));
|
||||
$dev = Db::name('appmall_developers')->where('token', $token)->where('status', 1)->find();
|
||||
if (empty($dev)) {
|
||||
return $this->result->error('令牌无效');
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
return $this->result->error('提现金额必须大于 0');
|
||||
}
|
||||
if ($amount > (float) ($dev['balance'] ?? 0)) {
|
||||
return $this->result->error('可提现余额不足');
|
||||
}
|
||||
if ($account === '') {
|
||||
return $this->result->error('请填写收款账号');
|
||||
}
|
||||
Db::transaction(function () use ($dev, $amount, $channel, $account) {
|
||||
Db::name('appmall_developers')->where('id', $dev['id'])
|
||||
->dec('balance', $amount)->inc('frozen_balance', $amount)->update();
|
||||
Db::name('appmall_addon_withdrawals')->insert([
|
||||
'developer_id' => $dev['id'],
|
||||
'amount' => $amount,
|
||||
'channel' => $channel,
|
||||
'account' => $account,
|
||||
'status' => 0,
|
||||
'create_at' => time(),
|
||||
'update_at' => time(),
|
||||
]);
|
||||
});
|
||||
return $this->result->success([], '提现申请已提交,等待运营打款');
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发者控制台入口(统一 DZ 风格布局):令牌认证后渲染控制台
|
||||
* 路由:GET /appmall/developer/console?token=xxx
|
||||
*/
|
||||
public function console()
|
||||
{
|
||||
$this->view->assign('token', $this->request->param('token', ''));
|
||||
return $this->view->fetch('developer/console');
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发者控制台(兼容旧入口,重定向到统一控制台)
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
$this->view->assign('token', $this->request->param('token', ''));
|
||||
return $this->view->fetch('developer/console');
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台概览数据(令牌认证):应用数 / 待审数 / 总下载 / 余额 / 累计收益 / 近期提交
|
||||
* 路由:POST /appmall/developer/overview
|
||||
*/
|
||||
public function overview()
|
||||
{
|
||||
$this->ensureTables();
|
||||
$dev = $this->authDev();
|
||||
if (empty($dev)) {
|
||||
return $this->result->error('令牌无效');
|
||||
}
|
||||
$devId = (int) $dev['id'];
|
||||
$appCount = Db::name('appmall_addon_list')->where('developer_id', $devId)->where('status', 1)->count();
|
||||
$pendingCount = Db::name('appmall_addon_submissions')->where('developer_id', $devId)->where('status', 0)->count();
|
||||
$totalDownload = (int) Db::name('appmall_addon_list')->where('developer_id', $devId)->sum('download_count');
|
||||
$totalIncome = (float) Db::name('appmall_addon_revenues')->where('developer_id', $devId)->where('status', 1)->sum('income');
|
||||
$recent = Db::name('appmall_addon_submissions')->where('developer_id', $devId)
|
||||
->field('title,version,status,update_at,type')->order('id', 'desc')->limit(5)->select()->toArray();
|
||||
$this->result->success([
|
||||
'username' => $dev['username'],
|
||||
'email' => $dev['email'],
|
||||
'balance' => $dev['balance'] ?? 0,
|
||||
'frozen_balance' => $dev['frozen_balance'] ?? 0,
|
||||
'app_count' => $appCount,
|
||||
'pending_count' => $pendingCount,
|
||||
'total_downloads' => $totalDownload,
|
||||
'total_income' => $totalIncome,
|
||||
'recent' => $recent,
|
||||
], '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的应用(令牌认证):已上架/下架应用 + 提交记录(待审/驳回)合并返回
|
||||
* 路由:POST /appmall/developer/apps
|
||||
*/
|
||||
public function apps()
|
||||
{
|
||||
$this->ensureTables();
|
||||
$dev = $this->authDev();
|
||||
if (empty($dev)) {
|
||||
return $this->result->error('令牌无效');
|
||||
}
|
||||
$devId = (int) $dev['id'];
|
||||
$listed = Db::name('appmall_addon_list')->where('developer_id', $devId)
|
||||
->field('id,name,title,version,price,logo,category,download_count,status,update_at,type')
|
||||
->order('update_at', 'desc')->select()->toArray();
|
||||
$subs = Db::name('appmall_addon_submissions')->where('developer_id', $devId)
|
||||
->field('id,name,title,version,price,logo,category,status,reject_reason,update_at,type')
|
||||
->order('update_at', 'desc')->select()->toArray();
|
||||
$this->result->success(['listed' => $listed, 'submissions' => $subs], '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改我的应用价格(令牌认证,仅能改自己名下的应用;无需重新打包/重新提审)
|
||||
* 路由:POST /appmall/developer/price {token, name, price}
|
||||
*/
|
||||
public function price()
|
||||
{
|
||||
if (! $this->request->isPost()) {
|
||||
return $this->result->error('访问错误');
|
||||
}
|
||||
$this->ensureTables();
|
||||
$dev = $this->authDev();
|
||||
if (empty($dev)) {
|
||||
return $this->result->error('令牌无效');
|
||||
}
|
||||
$name = trim((string) $this->request->param('name', ''));
|
||||
$price = (float) $this->request->param('price', -1);
|
||||
try {
|
||||
\addon\appmall\service\MarketService::instance()
|
||||
->updatePrice($name, $price, (int) $dev['id']);
|
||||
} catch (\addon\appmall\service\MarketException $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
return $this->result->success([], '价格已更新,市场即时生效');
|
||||
}
|
||||
|
||||
/**
|
||||
* 令牌鉴权:返回 status=1 的开发者记录,否则 null(沿用 earnings/withdraw 的契约)
|
||||
*/
|
||||
private function authDev()
|
||||
{
|
||||
$token = $this->request->param('token', '');
|
||||
return Db::name('appmall_developers')->where('token', $token)->where('status', 1)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时自愈建表(仅当表不存在时创建),保证开发者收益/提现闭环在当前库即可跑通。
|
||||
* 字段与 install.sql、中心站 Market::ensureTables 保持一致。
|
||||
*/
|
||||
private function ensureTables(): void
|
||||
{
|
||||
$prefix = Db::getConfig('prefix') ?: '';
|
||||
$tables = [
|
||||
'appmall_developers' => "CREATE TABLE IF NOT EXISTS `{$prefix}appmall_developers` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(100) NOT NULL COMMENT '开发者名称',
|
||||
`email` varchar(120) NOT NULL COMMENT '邮箱(接收令牌/通知)',
|
||||
`token` varchar(64) NOT NULL COMMENT '开发者令牌(发布插件时校验)',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待审核 1=正常 -1=禁用',
|
||||
`balance` decimal(10,2) DEFAULT '0.00' COMMENT '可提现余额',
|
||||
`frozen_balance` decimal(10,2) DEFAULT '0.00' COMMENT '待结算/冻结金额',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
UNIQUE KEY `uk_token` (`token`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='市场开发者表';",
|
||||
'appmall_addon_revenues' => "CREATE TABLE IF NOT EXISTS `{$prefix}appmall_addon_revenues` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0' COMMENT '受益开发者',
|
||||
`order_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'appmall_addon_orders.id',
|
||||
`aid` int unsigned NOT NULL DEFAULT '0' COMMENT 'appmall_addon_list.id',
|
||||
`uid` int unsigned NOT NULL DEFAULT '0' COMMENT '购买用户',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
|
||||
`commission` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '平台抽成',
|
||||
`income` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '开发者应得',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待结算 1=已结算',
|
||||
`create_at` int DEFAULT 0,
|
||||
`settle_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`),
|
||||
KEY `idx_order` (`order_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件销售收益账本';",
|
||||
'appmall_addon_withdrawals' => "CREATE TABLE IF NOT EXISTS `{$prefix}appmall_addon_withdrawals` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`developer_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '提现金额',
|
||||
`channel` varchar(20) DEFAULT 'alipay' COMMENT '提现渠道',
|
||||
`account` varchar(100) DEFAULT '' COMMENT '收款账号',
|
||||
`status` tinyint DEFAULT 0 COMMENT '0=待处理 1=已打款 -1=驳回',
|
||||
`remark` varchar(255) DEFAULT '',
|
||||
`create_at` int DEFAULT 0,
|
||||
`update_at` int DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_developer` (`developer_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发者提现单';",
|
||||
];
|
||||
foreach ($tables as $t => $sql) {
|
||||
BaseModel::ensureTable($prefix . $t, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发者令牌邮件正文
|
||||
*/
|
||||
private function tokenMail($name, $token): string
|
||||
{
|
||||
return "尊敬的 {$name}:<br>您的开发者令牌已生成:<br><br>"
|
||||
. "<code style=\"background:#f4f4f4;padding:4px 8px;border-radius:4px;\">{$token}</code><br><br>"
|
||||
. '请将其配置到开发者控制台的 <b>APPMARKET_DEV_TOKEN</b> 环境变量后,即可在「发布到官方市场」中使用。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核中邮件正文
|
||||
*/
|
||||
private function pendingMail($name): string
|
||||
{
|
||||
return "尊敬的 {$name}:<br>我们已收到您的开发者入驻申请,将在 1-3 个工作日内完成审核,"
|
||||
. '通过后令牌会发送至本邮箱,请注意查收。';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user