安全(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(市场 / 开发者中心 / 审核 / 分润)
153 lines
5.5 KiB
PHP
153 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ywxapp\model;
|
|
|
|
use ywxapp\model\BaseModel;
|
|
use think\model\concern\SoftDelete;
|
|
|
|
/**
|
|
* 站点公告
|
|
*/
|
|
class CommonNotice extends BaseModel
|
|
{
|
|
use SoftDelete;
|
|
|
|
// 类型:1系统维护 2活动公告 3版本更新 4其他
|
|
public const TYPE_MAINTAIN = 1;
|
|
public const TYPE_ACTIVITY = 2;
|
|
public const TYPE_UPDATE = 3;
|
|
public const TYPE_OTHER = 4;
|
|
|
|
// 字段类型转换
|
|
protected $type = [
|
|
'type' => 'integer',
|
|
'is_top' => 'integer',
|
|
'start_time' => 'integer',
|
|
'end_time' => 'integer',
|
|
'view_count' => 'integer',
|
|
'sort' => 'integer',
|
|
'status' => 'integer',
|
|
'create_at' => 'integer',
|
|
'update_at' => 'integer',
|
|
'delete_at' => 'integer',
|
|
];
|
|
|
|
/**
|
|
* 模型配置(BaseModel::applyOptions 约定)
|
|
*/
|
|
protected function getOptions(): array
|
|
{
|
|
return [
|
|
'strict' => true,
|
|
'autoWriteTimestamp' => 'int',
|
|
'createTime' => 'create_at',
|
|
'updateTime' => 'update_at',
|
|
'deleteTime' => 'delete_at',
|
|
'readonly' => ['view_count']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 公告类型映射
|
|
*/
|
|
public static function typeList(): array
|
|
{
|
|
return [
|
|
self::TYPE_MAINTAIN => '系统维护',
|
|
self::TYPE_ACTIVITY => '活动公告',
|
|
self::TYPE_UPDATE => '版本更新',
|
|
self::TYPE_OTHER => '其他',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 取全站生效的置顶公告(用于前台公告条)。
|
|
* 过滤:status=1 + 在有效期内(start_time<=now<=end_time 或 0=长期) + is_top=1
|
|
*/
|
|
public static function getActiveTopNotices(): array
|
|
{
|
|
self::ensureSchema();
|
|
$now = time();
|
|
return (new self())
|
|
->where('status', 1)
|
|
->where('is_top', 1)
|
|
->where(function ($q) use ($now) {
|
|
$q->where('start_time', 0)->whereOr('start_time', '<=', $now);
|
|
})
|
|
->where(function ($q) use ($now) {
|
|
$q->where('end_time', 0)->whereOr('end_time', '>=', $now);
|
|
})
|
|
->order('sort', 'desc')
|
|
->order('id', 'desc')
|
|
->limit(5)
|
|
->select()
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* 取前台公告列表(分页),按置顶优先 + 排序 + 时间倒序
|
|
*/
|
|
public static function getList(int $page = 1, int $limit = 10, int $type = 0): \think\Paginator
|
|
{
|
|
self::ensureSchema();
|
|
$now = time();
|
|
$query = (new self())
|
|
->where('status', 1)
|
|
->where(function ($q) use ($now) {
|
|
$q->where('start_time', 0)->whereOr('start_time', '<=', $now);
|
|
})
|
|
->where(function ($q) use ($now) {
|
|
$q->where('end_time', 0)->whereOr('end_time', '>=', $now);
|
|
});
|
|
if ($type > 0) {
|
|
$query->where('type', $type);
|
|
}
|
|
return $query->order('is_top', 'desc')
|
|
->order('sort', 'desc')
|
|
->order('id', 'desc')
|
|
->paginate(['page' => $page, 'list_rows' => $limit]);
|
|
}
|
|
|
|
/**
|
|
* 运行时结构自愈:老库缺新列时自动补齐(install.sql 为唯一事实源,此处兜底)
|
|
*/
|
|
public static function ensureSchema(): void
|
|
{
|
|
try {
|
|
$p = self::currentPrefix();
|
|
$prefix = $p;
|
|
$table = $prefix . 'common_notice';
|
|
// 缺表建表(install.sql 同款 DDL,运行时前缀兜底)
|
|
self::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$p}common_notice` (
|
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '公告标题',
|
|
`content` text COMMENT '公告内容',
|
|
`author` varchar(50) NOT NULL DEFAULT '' COMMENT '发布人',
|
|
`type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型 1系统维护 2活动公告 3版本更新 4其他',
|
|
`is_top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶 0否 1是',
|
|
`start_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示开始时间 0=长期',
|
|
`end_time` int(11) NOT NULL DEFAULT 0 COMMENT '展示结束时间 0=长期',
|
|
`view_count` int(11) NOT NULL DEFAULT 0 COMMENT '浏览量',
|
|
`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
|
|
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态 0禁用 1启用',
|
|
`create_at` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
|
|
`update_at` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
|
|
`delete_at` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间',
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_type` (`type`),
|
|
KEY `idx_is_top` (`is_top`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站点公告'");
|
|
// 老库扩展列兜底(幂等)
|
|
self::ensureColumn($table, 'type', "tinyint(1) NOT NULL DEFAULT 1 COMMENT '公告类型 1系统维护 2活动公告 3版本更新 4其他'");
|
|
self::ensureColumn($table, 'is_top', "tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶 0否 1是'");
|
|
self::ensureColumn($table, 'start_time', "int(11) NOT NULL DEFAULT 0 COMMENT '展示开始时间 0=长期'");
|
|
self::ensureColumn($table, 'end_time', "int(11) NOT NULL DEFAULT 0 COMMENT '展示结束时间 0=长期'");
|
|
self::ensureColumn($table, 'view_count', "int(11) NOT NULL DEFAULT 0 COMMENT '浏览量'");
|
|
} catch (\Throwable $e) {
|
|
// 忽略自愈失败,由业务暴露
|
|
}
|
|
}
|
|
}
|