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
@@ -29,10 +29,12 @@ class MebmberScore extends BaseModel
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'member_score_rule',
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
@@ -51,6 +53,10 @@ class MebmberScore extends BaseModel
|
||||
|
||||
/**
|
||||
* 确保 score_rule / score_log 关联表已就绪(静态入口可能未实例化模型)
|
||||
*
|
||||
* 老库自愈:install.sql 建表后仍可能缺列(如规则引擎新增的 addon/limit_* 与
|
||||
* 流水的 action/source/source_id),这里按 install.sql 的定义补齐,
|
||||
* 并把历史遗留的 name 列改名为 title。
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
@@ -58,6 +64,93 @@ class MebmberScore extends BaseModel
|
||||
BaseModel::ensureTableFromInstall($prefix, 'member_score_rule');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'member_score_log');
|
||||
BaseModel::ensureColumn("{$prefix}member_user", 'score', "int unsigned NOT NULL DEFAULT 0 COMMENT '积分(做任务/互动获得)'");
|
||||
|
||||
$ruleTable = $prefix . 'member_score_rule';
|
||||
$logTable = $prefix . 'member_score_log';
|
||||
|
||||
// 历史遗留字段改名:name -> title(仅当 name 存在且 title 不存在)
|
||||
self::renameColumnIfNeeded(
|
||||
$ruleTable,
|
||||
'name',
|
||||
'title',
|
||||
"varchar(100) NOT NULL DEFAULT '' COMMENT '规则名称'"
|
||||
);
|
||||
|
||||
// 规则表:规则引擎新增列
|
||||
foreach ([
|
||||
'addon' => "varchar(50) NOT NULL DEFAULT '' COMMENT '归属插件标识(空=核心)'",
|
||||
'limit_type' => "tinyint(4) NOT NULL DEFAULT 0 COMMENT '限次周期 0不限1每日2每周3每月4累计'",
|
||||
'limit_times' => "int(11) NOT NULL DEFAULT 0 COMMENT '周期内最多触发次数(0=不限)'",
|
||||
'unique_type' => "tinyint(4) NOT NULL DEFAULT 0 COMMENT '去重 0不去重1按来源业务ID去重'",
|
||||
'remark' => "varchar(255) NOT NULL DEFAULT '' COMMENT '流水备注模板({title}等占位)'",
|
||||
] as $column => $def) {
|
||||
BaseModel::ensureColumn($ruleTable, $column, $def);
|
||||
}
|
||||
|
||||
// 流水表:溯源与去重列
|
||||
foreach ([
|
||||
'action' => "varchar(50) NOT NULL DEFAULT '' COMMENT '触发动作标识'",
|
||||
'source' => "varchar(50) NOT NULL DEFAULT '' COMMENT '来源模块/插件'",
|
||||
'source_id' => "varchar(64) NOT NULL DEFAULT '' COMMENT '来源业务ID(去重与追踪)'",
|
||||
] as $column => $def) {
|
||||
BaseModel::ensureColumn($logTable, $column, $def);
|
||||
}
|
||||
|
||||
self::ensureIndex($ruleTable, 'idx_addon', '`addon`');
|
||||
self::ensureIndex($logTable, 'idx_rule', '`rule_id`');
|
||||
self::ensureIndex($logTable, 'idx_dedup', '`action`,`uid`,`source_id`');
|
||||
}
|
||||
|
||||
/**
|
||||
* 列改名(幂等):仅当旧列存在且新列不存在时执行 CHANGE
|
||||
*/
|
||||
protected static function renameColumnIfNeeded(string $table, string $from, string $to, string $def): void
|
||||
{
|
||||
try {
|
||||
$hasFrom = Db::query(
|
||||
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1",
|
||||
[$table, $from]
|
||||
);
|
||||
if (empty($hasFrom)) {
|
||||
return;
|
||||
}
|
||||
$hasTo = Db::query(
|
||||
"SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1",
|
||||
[$table, $to]
|
||||
);
|
||||
if (! empty($hasTo)) {
|
||||
return;
|
||||
}
|
||||
Db::execute("ALTER TABLE `{$table}` CHANGE COLUMN `{$from}` `{$to}` {$def}");
|
||||
} catch (\Throwable $e) {
|
||||
try {
|
||||
\think\facade\Log::error("[MebmberScore] rename {$table}.{$from} -> {$to} failed: " . $e->getMessage());
|
||||
} catch (\Throwable $e2) {
|
||||
// 日志失败则忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 索引不存在则追加(幂等)
|
||||
*/
|
||||
protected static function ensureIndex(string $table, string $index, string $columns): void
|
||||
{
|
||||
try {
|
||||
$exists = Db::query(
|
||||
"SELECT 1 FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ? LIMIT 1",
|
||||
[$table, $index]
|
||||
);
|
||||
if (empty($exists)) {
|
||||
Db::execute("ALTER TABLE `{$table}` ADD INDEX `{$index}` ({$columns})");
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
try {
|
||||
\think\facade\Log::error("[MebmberScore] ensureIndex {$table}.{$index} failed: " . $e->getMessage());
|
||||
} catch (\Throwable $e2) {
|
||||
// 日志失败则忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +160,10 @@ class MebmberScore extends BaseModel
|
||||
* @param int $value 变动值(正数)
|
||||
* @param string $remark 备注
|
||||
* @param int $ruleId 关联规则ID(默认0)
|
||||
* @param array $extra 溯源信息:action(动作标识)/ source(来源模块或插件)/ source_id(来源业务ID)
|
||||
* @return array [bool, string]
|
||||
*/
|
||||
public static function change(int $uid, int $type, int $value, string $remark = '', int $ruleId = 0): array
|
||||
public static function change(int $uid, int $type, int $value, string $remark = '', int $ruleId = 0, array $extra = []): array
|
||||
{
|
||||
self::ensureSchema();
|
||||
$uid = (int) $uid;
|
||||
@@ -97,9 +191,12 @@ class MebmberScore extends BaseModel
|
||||
Db::name('member_score_log')->insert([
|
||||
'uid' => $uid,
|
||||
'rule_id' => $ruleId,
|
||||
'action' => (string) ($extra['action'] ?? ''),
|
||||
'type' => $type,
|
||||
'value' => $value,
|
||||
'balance' => $balance,
|
||||
'source' => (string) ($extra['source'] ?? ''),
|
||||
'source_id' => (string) ($extra['source_id'] ?? ''),
|
||||
'remark' => $remark,
|
||||
'create_at' => time(),
|
||||
]);
|
||||
@@ -115,13 +212,22 @@ class MebmberScore extends BaseModel
|
||||
/**
|
||||
* 积分流水列表(后台查看)
|
||||
*/
|
||||
public static function logList(int $uid = 0, int $page = 1, int $limit = 15): array
|
||||
public static function logList(int $uid = 0, int $page = 1, int $limit = 15, array $filter = []): array
|
||||
{
|
||||
self::ensureSchema();
|
||||
$query = Db::name('member_score_log');
|
||||
if ($uid > 0) {
|
||||
$query->where('uid', $uid);
|
||||
}
|
||||
if (! empty($filter['action'])) {
|
||||
$query->where('action', (string) $filter['action']);
|
||||
}
|
||||
if (! empty($filter['source'])) {
|
||||
$query->where('source', (string) $filter['source']);
|
||||
}
|
||||
if (! empty($filter['rule_id'])) {
|
||||
$query->where('rule_id', (int) $filter['rule_id']);
|
||||
}
|
||||
$count = $query->count();
|
||||
$list = $query->order('id', 'desc')
|
||||
->page($page, $limit)
|
||||
|
||||
Reference in new issue
Block a user