Files
YwxAppThink/ywxapp/model/BackendPower.php
T
yangwx 18101cf11a 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(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

187 lines
5.4 KiB
PHP

<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
namespace ywxapp\model;
use think\model\Collection;
use think\model\concern\SoftDelete;
use ywxapp\model\BaseModel;
/**
* BackendPower 类
*
* @author ywxapp <admin@ywxapp.cn>
*/
class BackendPower extends BaseModel
{
use SoftDelete;
/** 目录 */
public const TYPE_DIR = 1;
/** 菜单 */
public const TYPE_MENU = 2;
/** 按钮 */
public const TYPE_BUTTON = 3;
/** API */
public const TYPE_API = 4;
/**
* 分组标题(目录内的视觉分组名,不可点击、不参与权限校验)
*
* 用途:一个目录下菜单较多时,用标题把它们分成几组,
* 避免上三级菜单(layui 侧边导航只支持两级展开)。
* 取菜单的地方要显式放行该类型,否则会被 type<3 过滤掉。
*/
public const TYPE_GROUP_TITLE = 5;
protected function getOptions(): array
{
return [
'strict' => false,
'autoWriteTimestamp' => 'int',
'createTime' => 'create_at',
'updateTime' => 'update_at',
'deleteTime' => 'delete_at',
'defaultSoftDelete' => 0,
'append' => ['target'],
'hidden' => ['create_at', 'update_at', 'delete_at'],
'readonly' => ['id'],
];
}
public static function onAfterRead($data)
{
// 这里可以直接使用$this访问当前模型
// if ($data->type == 2) {
// $data->append(['href', 'openType']);
// $data->openType = '_iframe';
// $data->href = $data->route;
// }
}
public static function onAfterDelete($data)
{
BackendRolePower::where('power_id', $data->id)->delete();
}
/**
* 获取器:获取状态文本
*/
public function getTargetAttr($value, $data)
{
return '_self';
}
// 自关联子菜单
public function children()
{
return $this->hasMany(BackendPower::class, 'pid', 'id')->order('sort', 'asc');
}
// 拥有该权限的角色
public function roles()
{
return $this->belongsToMany(BackendRole::class, BackendRolePower::class, 'role_id', 'power_id');
}
// 拥有该权限的用户(通过角色)
public function admins()
{
return $this->belongsToMany(BackendAdmin::class, BackendRoleAccess::class, 'admin_id', 'role_id')->via('roles');
}
/**
* 将扁平菜单数组转为树形结构
* @param array $menus 扁平菜单列表(每个元素是数组)
* @param int $parentId 父ID(默认0表示根)
* @return array 树形结构
*/
public static function buildTree(array $menus, int $parentId = 0): array
{
$branch = [];
foreach ($menus as $menu) {
if ($menu['pid'] == $parentId) {
$children = self::buildTree($menus, $menu['id']);
if (! empty($children)) {
$menu['child'] = $children;
}
$branch[] = $menu;
}
}
return $branch;
}
/**
* 【可选】从 Collection 转为树(如果你用模型查询)
*/
public static function buildTreeFromCollection(Collection $collection, int $parentId = 0): array
{
$menus = $collection->toArray();
return self::buildTree($menus, $parentId);
}
/**
* 获取权限树形结构
* @param int $parentId 父ID(默认0表示根)
* @return array 树形结构
*/
public static function getTree($parentId = 0)
{
$list = self::where('pid', $parentId)
->order('sort', 'asc')
->select()
->toArray();
foreach ($list as &$item) {
$item['children'] = self::getTree($item['id']);
}
return $list;
}
/**
* 无限分类-权限
* @param array $cate 栏目
* @param string $lefthtml 分隔符
* @param int $pid 父ID
* @param int $level 层级
* @return array
*/
public static function cateTree($cate, $name = 'title', $lefthtml = '|— ', $pid = 0, $level = 0)
{
$arr = [];
foreach ($cate as $v) {
if ($v['pid'] == $pid) {
$v['level'] = $level + 1;
$v['lefthtml'] = str_repeat($lefthtml, $level);
$v['l' . $name] = $v['lefthtml'] . lang($v[$name]);
$arr[] = $v;
$arr = array_merge($arr, self::cateTree($cate, $name, $lefthtml, $v['id'], $level + 1));
}
}
return $arr;
}
/**
* 表结构自愈:建表 + 关键列兜底(install.sql 为事实源)。
* 查询/写入前调用,避免老库缺表导致 1146。
*/
public static function ensureSchema(): void
{
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'backend_power');
}
}