feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0) - 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、 ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签 (ywxapp.framework_package_verify,默认开启) - JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret() 校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET - /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验 - 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流 - TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive - 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php) - 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis - paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG 工程化 / 交付 - 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本 - 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过 - 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障 - .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess 结构 - appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
This commit is contained in:
1 parent
1d49e6f5ee
commit
18101cf11a
636 files changed
+58517
-9519
No files matched your search
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace app\appmall\controller\backend;
|
||||
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use Exception;
|
||||
use app\appmall\controller\AppmallBackend;
|
||||
// 别名导入:本控制器类名也叫 AddonReview,直接 use 会与自身声明冲突
|
||||
use app\appmall\model\AddonReview as AddonReviewModel;
|
||||
|
||||
/**
|
||||
* 插件审核(运营后台,中心站)
|
||||
*
|
||||
* 审核开发者通过 Market::submit 提交的插件上架申请(appmall_addon_submissions)。
|
||||
* status: 0=待审核 1=已发布 2=已驳回。
|
||||
*/
|
||||
class AddonReview extends AppmallBackend
|
||||
{
|
||||
/**
|
||||
* 审核列表 / 页面
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$status = $this->request->param('status', '');
|
||||
$query = Db::name('appmall_addon_submissions')->order('id', 'desc');
|
||||
if ($status !== '') {
|
||||
$query->where('status', (int) $status);
|
||||
}
|
||||
$list = $query->paginate([
|
||||
'page' => (int) $this->request->param('page', 1),
|
||||
'list_rows' => (int) $this->request->param('limit', 10),
|
||||
]);
|
||||
$this->result->setCount($list->total())->success($list->items(), '获取成功');
|
||||
}
|
||||
return View::fetch('addonreview/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过:标记已发布(status=1)
|
||||
* 注意:正式上架到 appmall_addon_list 由发布流程处理,此处仅切换审核态。
|
||||
*/
|
||||
public function audit()
|
||||
{
|
||||
try {
|
||||
$id = (int) input('id');
|
||||
$row = Db::name('appmall_addon_submissions')->where('id', $id)->find();
|
||||
if (empty($row)) {
|
||||
return $this->result->error('提交记录不存在');
|
||||
}
|
||||
if ((int) $row['status'] === 1) {
|
||||
return $this->result->error('该提交已发布');
|
||||
}
|
||||
Db::name('appmall_addon_submissions')->where('id', $id)
|
||||
->update(['status' => 1, 'reject_reason' => '', 'update_at' => time()]);
|
||||
return $this->result->success([], '已通过审核');
|
||||
} catch (Exception $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回:标记已驳回(status=2)并记录原因
|
||||
*/
|
||||
public function reject()
|
||||
{
|
||||
try {
|
||||
$id = (int) input('id');
|
||||
$reason = trim(input('reason', ''));
|
||||
if ($reason === '') {
|
||||
return $this->result->error('请填写驳回原因');
|
||||
}
|
||||
$row = Db::name('appmall_addon_submissions')->where('id', $id)->find();
|
||||
if (empty($row)) {
|
||||
return $this->result->error('提交记录不存在');
|
||||
}
|
||||
Db::name('appmall_addon_submissions')->where('id', $id)
|
||||
->update(['status' => 2, 'reject_reason' => $reason, 'update_at' => time()]);
|
||||
return $this->result->success([], '已驳回');
|
||||
} catch (Exception $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 用户评价审核(appmall_addon_reviews) ====================
|
||||
|
||||
/**
|
||||
* 评价审核列表 / 页面
|
||||
*
|
||||
* 与上架审核(submissions)分开:这里是用户安装使用后提交的评分与评论,先审后发。
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$status = $this->request->param('status', '');
|
||||
$keyword = trim((string) $this->request->param('keyword', ''));
|
||||
$query = Db::name('appmall_addon_reviews')->alias('r')
|
||||
->leftJoin('appmall_addon_list l', 'r.aid = l.id')
|
||||
->field('r.*, l.title AS addon_title, l.name AS addon_name')
|
||||
->order('r.id', 'desc');
|
||||
if ($status !== '') {
|
||||
$query->where('r.status', (int) $status);
|
||||
}
|
||||
if ($keyword !== '') {
|
||||
$query->whereLike('l.name|l.title|r.content', '%' . $keyword . '%');
|
||||
}
|
||||
$list = $query->paginate([
|
||||
'page' => (int) $this->request->param('page', 1),
|
||||
'list_rows' => (int) $this->request->param('limit', 10),
|
||||
]);
|
||||
$items = $list->items();
|
||||
foreach ($items as &$it) {
|
||||
$it['create_fmt'] = date('Y-m-d H:i', (int) $it['create_at']);
|
||||
$it['content'] = (string) $it['content'];
|
||||
}
|
||||
$this->result->setCount($list->total())->success($items, '获取成功');
|
||||
}
|
||||
// 该模块暂无后台 HTML 视图(应用市场的运营页面已下线),这里作为数据源接口存在:
|
||||
// 后续接 UI 时补 addonreview/comments 视图即可,不再引入半成品页面。
|
||||
return json([
|
||||
'code' => 1,
|
||||
'msg' => '请以 Ajax(X-Requested-With)请求该接口获取评价列表',
|
||||
'data' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 评价通过:置为已发布并重算插件评分
|
||||
*/
|
||||
public function approveComment()
|
||||
{
|
||||
$id = (int) input('id');
|
||||
try {
|
||||
$row = Db::name('appmall_addon_reviews')->where('id', $id)->find();
|
||||
if (empty($row)) {
|
||||
return $this->result->error('评价不存在');
|
||||
}
|
||||
Db::name('appmall_addon_reviews')->where('id', $id)
|
||||
->update(['status' => AddonReviewModel::STATUS_PUBLISHED, 'update_at' => time()]);
|
||||
AddonReviewModel::syncRating((int) $row['aid']);
|
||||
return $this->result->success([], '已通过');
|
||||
} catch (Exception $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 评价驳回:置为已驳回并重算评分(该条不再计入统计)
|
||||
*/
|
||||
public function rejectComment()
|
||||
{
|
||||
$id = (int) input('id');
|
||||
try {
|
||||
$row = Db::name('appmall_addon_reviews')->where('id', $id)->find();
|
||||
if (empty($row)) {
|
||||
return $this->result->error('评价不存在');
|
||||
}
|
||||
Db::name('appmall_addon_reviews')->where('id', $id)
|
||||
->update(['status' => AddonReviewModel::STATUS_REJECTED, 'update_at' => time()]);
|
||||
AddonReviewModel::syncRating((int) $row['aid']);
|
||||
return $this->result->success([], '已驳回');
|
||||
} catch (Exception $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评价(恶意/重复/用户撤回),删除后重算评分
|
||||
*/
|
||||
public function deleteComment()
|
||||
{
|
||||
$id = (int) input('id');
|
||||
try {
|
||||
$row = Db::name('appmall_addon_reviews')->where('id', $id)->find();
|
||||
if (empty($row)) {
|
||||
return $this->result->error('评价不存在');
|
||||
}
|
||||
Db::name('appmall_addon_reviews')->where('id', $id)->delete();
|
||||
AddonReviewModel::syncRating((int) $row['aid']);
|
||||
return $this->result->success([], '已删除');
|
||||
} catch (Exception $e) {
|
||||
return $this->result->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user