feat: 公共表更名 common/member 前缀 + 插件命令自动加载 + 版本 1.1.1
- 18 张公共表更名(addon/attachment/configure/links/spider_log/spider_stat/sms/notice/ad/task/prop/medal/help/card -> common_*,member_wallets->member_wallet,score_rule/score_log -> member_*,addon_config->common_addonconf),模型全部对齐新表名,Db 直引用清零 - Attachment 模型补 表名绑定,修复富文本上传查 wxapp_attachment 1146 隐患 - install.sql + 迁移 SQL:backend_admin/backend_role delete_at 默认 0,修复软删除(NULL != 0)误过滤导致后台菜单为空 - AppService::boot() 支持插件 info.php 声明 commands 自动注册插件命令(psr-4 自动加载,坏类名自动跳过) - 各插件(haonav/mqttbroker/wxchat/articles/blog/forum 等)字段与配置同步调整 - 框架版本 1.1.0 -> 1.1.1
This commit is contained in:
@@ -126,8 +126,7 @@ abstract class AddonBase
|
||||
*/
|
||||
public static function readConfig(string $name): array
|
||||
{
|
||||
$addonPath = (defined('ADDON_PATH') ? ADDON_PATH : app()->getRootPath() . 'addon' . DIRECTORY_SEPARATOR)
|
||||
. $name . DIRECTORY_SEPARATOR;
|
||||
$addonPath = (defined('ADDON_PATH') ? ADDON_PATH : app()->getRootPath() . 'addon' . DIRECTORY_SEPARATOR) . $name . DIRECTORY_SEPARATOR;
|
||||
$config = [];
|
||||
// ① 静态代码级配置:addon/<name>/config/<name>.php(随插件分发、支持 env() 覆盖)。
|
||||
// 与表单 config.php 分工:此处放「运行期/部署相关、需在代码中用 env() 取默认」的项
|
||||
|
||||
@@ -372,15 +372,21 @@ class AddonHealth extends Command
|
||||
|
||||
Cache::delete($testKey);
|
||||
|
||||
// 插件缓存命中情况(仅警告,不影响健康)
|
||||
$addon = Config::get('addon', []);
|
||||
foreach ($addon as $addon) {
|
||||
if (!isset($addon['name']) || !($addon['state'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
if (Cache::get('addon_config_' . $addon['name']) === false) {
|
||||
$result['warnings'][] = "插件 {$addon['name']} 缓存未命中,可能影响性能";
|
||||
// 插件缓存命中情况(仅警告,不影响健康)。
|
||||
// 注意:Config::get('addon') 是框架 addon 组件配置(multi_app/priority 等),非插件列表;
|
||||
// 直接用 AddonModel 的插件名列表,避免遍历配置时访问 true['name']。
|
||||
try {
|
||||
$addons = \ywxapp\model\AddonModel::field('name,state')->select()->toArray();
|
||||
foreach ($addons as $addon) {
|
||||
if (!isset($addon['name']) || !($addon['state'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
if (Cache::get('addon_config_' . $addon['name']) === false) {
|
||||
$result['warnings'][] = "插件 {$addon['name']} 缓存未命中,可能影响性能";
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 数据表可能尚未创建,忽略
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$result['healthy'] = false;
|
||||
|
||||
@@ -53,7 +53,7 @@ class AddonMake extends Make
|
||||
$this->setName('addon:make')
|
||||
->addArgument('addon', Argument::OPTIONAL, 'addon name, e.g. demo (required for build & generators)')
|
||||
->addOption('action', 'a', Option::VALUE_REQUIRED, 'build|clear|controller|model|middleware|validate|event|listener|service|subscribe')
|
||||
->addOption('name', 'n', Option::VALUE_OPTIONAL, 'class name for generator actions, e.g. User')
|
||||
->addOption('name', null, Option::VALUE_OPTIONAL, 'class name for generator actions, e.g. User')
|
||||
->addOption('api', null, Option::VALUE_NONE, 'controller: generate an api controller class.')
|
||||
->addOption('plain', null, Option::VALUE_NONE, 'controller: generate an empty controller class.')
|
||||
->addOption('cache', 'c', Option::VALUE_NONE, 'clear: clear cache file')
|
||||
@@ -62,7 +62,7 @@ class AddonMake extends Make
|
||||
->setDescription('Unified addon scaffold & class generator (build / clear / controller / model / middleware / validate / event / listener / service / subscribe)');
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function execute(Input $input, Output $output): int
|
||||
{
|
||||
$addon = trim((string) $input->getArgument('addon'));
|
||||
@@ -227,8 +227,12 @@ class AddonMake extends Make
|
||||
mkdir($appPath . 'controller');
|
||||
mkdir($appPath . 'controller/backend');
|
||||
mkdir($appPath . 'controller/member');
|
||||
mkdir($appPath . 'controller/api');
|
||||
}
|
||||
$this->buildIndexController($app, $namespace);
|
||||
$this->buildIndexController($app, $namespace); // 前台 FrontendBase
|
||||
$this->buildIndexController($app, $namespace, 'backend'); // BackendBase
|
||||
$this->buildIndexController($app, $namespace, 'member'); // MemberBase
|
||||
$this->buildIndexController($app, $namespace, 'api'); // ApiBase
|
||||
if (! is_dir($appPath . 'lang')) {
|
||||
mkdir($appPath . 'lang');
|
||||
}
|
||||
@@ -342,14 +346,25 @@ class AddonMake extends Make
|
||||
* @param string $namespace 类库命名空间
|
||||
* @return void
|
||||
*/
|
||||
public function buildIndexController(string $app, string $namespace): void
|
||||
public function buildIndexController(string $app, string $namespace, string $area = ''): void
|
||||
{
|
||||
$suffix = $this->app->config->get('route.controller_suffix') ? 'Controller' : '';
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
|
||||
|
||||
$dir = 'controller' . ($area !== '' ? DIRECTORY_SEPARATOR . $area : '');
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . $dir . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
|
||||
if (! is_file($filename)) {
|
||||
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'controller.stub');
|
||||
$content = str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], ['Index', $suffix, $namespace . '\\controller', $namespace], $content);
|
||||
$base_class = $area == 'backend' ? 'BackendBase' : ($area == 'member' ? 'MemberBase' : ($area == 'api' ? 'ApiBase' : 'FrontendBase'));
|
||||
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'controller.stub');
|
||||
$content = str_replace(
|
||||
[
|
||||
'{%className%}',
|
||||
'{%actionSuffix%}',
|
||||
'{%namespace%}',
|
||||
'{%app_namespace%}',
|
||||
'{%baseClass%}'
|
||||
],
|
||||
['Index', $suffix, $namespace . ($area !== '' ? '\\controller\\' . $area : '\\controller'), $namespace, $base_class],
|
||||
$content
|
||||
);
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
@@ -366,8 +381,16 @@ class AddonMake extends Make
|
||||
{
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR . 'app.php';
|
||||
if (! is_file($filename)) {
|
||||
$content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . 'route.stub');
|
||||
$content = str_replace(
|
||||
[
|
||||
'{%app%}'
|
||||
],
|
||||
[$app],
|
||||
$content
|
||||
);
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, "<?php" . PHP_EOL . "use think\\facade\\Route;" . PHP_EOL . PHP_EOL);
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use think\File;
|
||||
use ywxapp\exception\AddonException;
|
||||
use ywxapp\service\AddonService;
|
||||
use ywxapp\service\AddonHotReload;
|
||||
use think\facade\Env;
|
||||
|
||||
/**
|
||||
* 插件统一管理命令(合并 develop / install / reload)
|
||||
@@ -94,8 +95,14 @@ class AddonManage extends Command
|
||||
} catch (AddonException $e) {
|
||||
$output->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
return 1;
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// \Throwable 兜底:\Error(如类型错误)不是 \Exception 子类,必须单独捕获,
|
||||
// 否则会漏到框架层导致无友好提示。
|
||||
$output->writeln('<error>开发模式安装失败:' . $e->getMessage() . '</error>');
|
||||
if (Env::get('app_debug')) {
|
||||
$output->writeln('<error>FILE: ' . $e->getFile() . ':' . $e->getLine() . '</error>');
|
||||
$output->writeln('<error>TRACE: ' . $e->getTraceAsString() . '</error>');
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -26,7 +26,7 @@ use ywxapp\model\BaseModel;
|
||||
* 整合以下自愈能力,使一个出问题的插件恢复到「菜单正确 + 表结构完整 + 配置齐全」的状态:
|
||||
* 1. 重载菜单:refreshMenu()(清旧 admin_power/user_rule + 重建,顺带修复菜单路由前缀)
|
||||
* 2. 数据库自愈:解析 install.sql 中该插件全部 CREATE TABLE,缺表建表、缺列补列
|
||||
* 3. 配置行补全:确保 addon_config 表存在该插件配置行(无则按默认 info 写入)
|
||||
* 3. 配置行补全:确保 common_addonconf 表存在该插件配置行(无则按默认 info 写入)
|
||||
*
|
||||
* 用法:
|
||||
* php think addon:repair <插件名> # 修复指定插件(菜单 + 表 + 配置)
|
||||
@@ -245,24 +245,25 @@ class AddonRepair extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置行补全:确保 addon_config 表有该插件配置行。
|
||||
* 配置行补全:确保 common_addonconf 表有该插件配置行。
|
||||
*/
|
||||
private function repairConfig(AddonService $svc, string $name, Output $output): bool
|
||||
{
|
||||
$default = Config::get('database.default');
|
||||
$prefix = Config::get("database.connections.{$default}.prefix") ?: '';
|
||||
$configTable = $prefix . 'addon_config';
|
||||
$configTable = $prefix . 'common_addonconf';
|
||||
try {
|
||||
if (!BaseModel::tableExists($configTable)) {
|
||||
$output->writeln('<comment>[配置] addon_config 表不存在,跳过</comment>');
|
||||
$output->writeln('<comment>[配置] common_addonconf 表不存在,跳过</comment>');
|
||||
return true;
|
||||
}
|
||||
$row = Db::table($configTable)->where('name', $name)->find();
|
||||
if (!empty($row)) {
|
||||
// common_addonconf 设计:每行一个配置项(addon + name 唯一),故按 addon 判断是否存在
|
||||
$existing = Db::table($configTable)->where('addon', $name)->count();
|
||||
if ($existing > 0) {
|
||||
$output->writeln('<comment>[配置] 配置行已存在,跳过</comment>');
|
||||
return true;
|
||||
}
|
||||
// 读取插件默认配置(config.php 返回数组)
|
||||
// 读取插件默认配置(config.php 返回关联数组 name=>value)
|
||||
$configFile = $this->addonDir($name) . 'config.php';
|
||||
$value = [];
|
||||
if (is_file($configFile)) {
|
||||
@@ -271,10 +272,13 @@ class AddonRepair extends Command
|
||||
$value = $cfg;
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'value' => json_encode($value, JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
if (empty($value)) {
|
||||
$output->writeln('<comment>[配置] 无默认配置,跳过</comment>');
|
||||
return true;
|
||||
}
|
||||
// 逐配置项写入独立行(与 AddonService::config/$changed 的存储格式一致)
|
||||
\ywxapp\service\AddonService::config($name, $value);
|
||||
$data = []; // 已通过 AddonService::config 写入,无需再 insert
|
||||
// 兼容不同时间戳字段名(createtime/updatetime 或 create_time/update_time)
|
||||
$cols = Db::query("SHOW COLUMNS FROM `{$configTable}`");
|
||||
$colNames = array_column($cols, 'Field');
|
||||
|
||||
+70
-253
@@ -17,6 +17,8 @@ use think\console\Output;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
|
||||
use think\console\command\Make;
|
||||
|
||||
/**
|
||||
* AppCommand 应用命令统一入口
|
||||
*
|
||||
@@ -44,25 +46,8 @@ use think\console\input\Option;
|
||||
* php think app clear --cache # 清理缓存
|
||||
* php think app build myapp # 构建应用目录
|
||||
*/
|
||||
class AppCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Make 型子命令配置
|
||||
* type : 类型标签(用于成功/存在提示)
|
||||
* stub : stub 文件名(不含 .stub 扩展)
|
||||
* ns : 命名空间末尾段(如 controller/model/event ...)
|
||||
* suffix : 类名后缀(仅 controller 在开启 controller_suffix 时追加 Controller)
|
||||
*/
|
||||
private const MAKE_TYPES = [
|
||||
'controller' => ['type' => 'Controller', 'stub' => 'controller', 'ns' => 'controller', 'suffix' => 'Controller'],
|
||||
'model' => ['type' => 'Model', 'stub' => 'model', 'ns' => 'model', 'suffix' => ''],
|
||||
'event' => ['type' => 'Event', 'stub' => 'event', 'ns' => 'event', 'suffix' => ''],
|
||||
'listener' => ['type' => 'Listener', 'stub' => 'listener', 'ns' => 'listener', 'suffix' => ''],
|
||||
'middleware' => ['type' => 'Middleware', 'stub' => 'middleware', 'ns' => 'middleware', 'suffix' => ''],
|
||||
'service' => ['type' => 'Service', 'stub' => 'service', 'ns' => 'service', 'suffix' => ''],
|
||||
'subscribe' => ['type' => 'Subscribe', 'stub' => 'subscribe', 'ns' => 'subscribe', 'suffix' => ''],
|
||||
'validate' => ['type' => 'Validate', 'stub' => 'validate', 'ns' => 'validate', 'suffix' => ''],
|
||||
];
|
||||
class AppCommand extends Make
|
||||
{
|
||||
|
||||
/**
|
||||
* 应用基础目录(Build 使用)
|
||||
@@ -70,12 +55,43 @@ class AppCommand extends Command
|
||||
*/
|
||||
private $basePath = '';
|
||||
|
||||
/**
|
||||
* stub 模板目录(已从 ywxapp/command/app/stubs 上移至此)
|
||||
*/
|
||||
private function stubDir(): string
|
||||
private $subCommon = '';
|
||||
|
||||
protected function getStub(): string
|
||||
{
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR;
|
||||
if ($this->input->getOption('api')) {
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.api.stub';
|
||||
}
|
||||
if ($this->input->getOption('plain')) {
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.plain.stub';
|
||||
}
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'appstubs' . DIRECTORY_SEPARATOR . $this->subCommon . '.stub';
|
||||
}
|
||||
|
||||
protected function getNamespace(string $app): string
|
||||
{
|
||||
if ($this->subCommon === 'model') {
|
||||
return 'ywxapp\\model';
|
||||
}
|
||||
return parent::getNamespace($app) . '\\' . $this->subCommon;
|
||||
}
|
||||
|
||||
protected function getClassName(string $name): string
|
||||
{
|
||||
if ($this->subCommon == 'controller') {
|
||||
return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
|
||||
}
|
||||
return parent::getClassName($name);
|
||||
}
|
||||
|
||||
protected function getPathName(string $name): string
|
||||
{
|
||||
if ($this->subCommon == 'model') {
|
||||
$name = substr($name, 6);
|
||||
return $this->app->getRootPath() . 'ywxapp' . '/' . ltrim(str_replace('\\', '/', $name), '/') . '.php';
|
||||
}
|
||||
$name = substr($name, 4);
|
||||
return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,171 +114,42 @@ class AppCommand extends Command
|
||||
/**
|
||||
* 执行命令
|
||||
*/
|
||||
protected function execute(Input $input, Output $output): int
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if ($input->getOption('list') || empty($input->getArgument('subcommand'))) {
|
||||
$this->showList($output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$sub = $input->getArgument('subcommand');
|
||||
$name = (string) $input->getArgument('name');
|
||||
|
||||
if (isset(self::MAKE_TYPES[$sub])) {
|
||||
return $this->runMake($sub, $name, $input, $output);
|
||||
}
|
||||
if ($sub === 'build') {
|
||||
$this->subCommon = $input->getArgument('subcommand');
|
||||
$name = trim($input->getArgument('name'));
|
||||
if ($this->subCommon === 'build') {
|
||||
return $this->runBuild($name, $output);
|
||||
}
|
||||
if ($sub === 'clear') {
|
||||
if ($this->subCommon === 'clear') {
|
||||
return $this->runClear($name, $input, $output);
|
||||
}
|
||||
|
||||
$output->writeln('<error>未知子命令: ' . $sub . '</error>');
|
||||
$this->showList($output);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有可用子命令
|
||||
*/
|
||||
private function showList(Output $output): void
|
||||
{
|
||||
$output->writeln('<info>YwxApp 应用命令(`php think app <子命令> [参数]`):</info>');
|
||||
$output->writeln('');
|
||||
$width = max(array_map('strlen', array_keys(self::MAKE_TYPES + ['build' => 1, 'clear' => 1])));
|
||||
foreach (array_keys(['build' => 1, 'clear' => 1] + self::MAKE_TYPES) as $name) {
|
||||
$desc = [
|
||||
'build' => '构建应用目录',
|
||||
'clear' => '清理运行时文件',
|
||||
'controller' => '生成控制器(--api/--plain)',
|
||||
'model' => '生成模型',
|
||||
'event' => '生成事件',
|
||||
'listener' => '生成监听器',
|
||||
'middleware' => '生成中间件',
|
||||
'service' => '生成服务',
|
||||
'subscribe' => '生成订阅器',
|
||||
'validate' => '生成验证器',
|
||||
][$name];
|
||||
$output->writeln(' ' . str_pad($name, $width) . ' ' . $desc);
|
||||
}
|
||||
$output->writeln('');
|
||||
$output->writeln('示例:php think app controller app\\index');
|
||||
$output->writeln(' php think app clear --cache');
|
||||
}
|
||||
|
||||
/* ===================== Make 型生成器 ===================== */
|
||||
|
||||
/**
|
||||
* 运行 Make 型子命令
|
||||
*/
|
||||
private function runMake(string $type, string $name, Input $input, Output $output): int
|
||||
{
|
||||
if ($name === '') {
|
||||
$output->writeln('<error>缺少类名参数,用法:php think app ' . $type . ' <类名></error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$cfg = self::MAKE_TYPES[$type];
|
||||
$classname = $this->makeClassName($type, $name);
|
||||
$pathname = $this->makePathName($classname);
|
||||
|
||||
$classname = $this->getClassName($name);
|
||||
$pathname = $this->getPathName($classname);
|
||||
if (is_file($pathname)) {
|
||||
$output->writeln('<error>' . $cfg['type'] . ':' . $classname . ' already exists!</error>');
|
||||
return 1;
|
||||
$output->writeln('<error>' . $this->type . ':' . $classname . ' already exists!</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir(dirname($pathname))) {
|
||||
mkdir(dirname($pathname), 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($pathname, $this->buildClass($classname, $type, $input));
|
||||
|
||||
$output->writeln('<info>' . $cfg['type'] . ':' . $classname . ' created successfully.</info>');
|
||||
return 0;
|
||||
file_put_contents($pathname, $this->buildClass($classname));
|
||||
$output->writeln('<info>' . $this->type . ':' . $classname . ' created successfully.</info>');
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 stub 路径(controller 支持 --api/--plain)
|
||||
*/
|
||||
private function makeStub(string $type, Input $input): string
|
||||
protected function buildClass(string $name)
|
||||
{
|
||||
if ($type === 'controller') {
|
||||
if ($input->getOption('api')) {
|
||||
return $this->stubDir() . 'controller.api.stub';
|
||||
}
|
||||
if ($input->getOption('plain')) {
|
||||
return $this->stubDir() . 'controller.plain.stub';
|
||||
}
|
||||
return $this->stubDir() . 'controller.stub';
|
||||
}
|
||||
|
||||
return $this->stubDir() . self::MAKE_TYPES[$type]['stub'] . '.stub';
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析命名空间
|
||||
*/
|
||||
private function makeNamespace(string $type, string $app): string
|
||||
{
|
||||
return 'app' . ($app ? '\\' . $app : '') . '\\' . self::MAKE_TYPES[$type]['ns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析完整类名(等价于 think\console\command\Make::getClassName + Controller 后缀)
|
||||
*/
|
||||
private function makeClassName(string $type, string $name): string
|
||||
{
|
||||
if (str_contains($name, '\\')) {
|
||||
$class = $name;
|
||||
} else {
|
||||
$app = '';
|
||||
if (str_contains($name, '@')) {
|
||||
[$app, $name] = explode('@', $name);
|
||||
}
|
||||
if (str_contains($name, '/')) {
|
||||
$name = str_replace('/', '\\', $name);
|
||||
}
|
||||
$class = $this->makeNamespace($type, $app) . '\\' . $name;
|
||||
}
|
||||
|
||||
$suffix = self::MAKE_TYPES[$type]['suffix'];
|
||||
if ($suffix !== '' && $this->app->config->get('route.controller_suffix')) {
|
||||
$class .= $suffix;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析文件路径(等价于各 Make 命令重写的 getPathName)
|
||||
*/
|
||||
private function makePathName(string $name): string
|
||||
{
|
||||
return $this->app->getRootPath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 stub 渲染类文件(等价于 think\console\command\Make::buildClass)
|
||||
*/
|
||||
private function buildClass(string $name, string $type, Input $input): string
|
||||
{
|
||||
$stub = file_get_contents($this->makeStub($type, $input));
|
||||
|
||||
$stub = file_get_contents($this->getStub());
|
||||
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
|
||||
$class = str_replace($namespace . '\\', '', $name);
|
||||
|
||||
return str_replace(
|
||||
['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'],
|
||||
[
|
||||
$class,
|
||||
$this->app->config->get('route.action_suffix'),
|
||||
$namespace,
|
||||
$this->app->getNamespace(),
|
||||
],
|
||||
$stub
|
||||
);
|
||||
$class = str_replace($namespace . '\\', '', $name);
|
||||
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
|
||||
$class,
|
||||
$this->app->config->get('route.action_suffix'),
|
||||
$namespace,
|
||||
$this->app->getNamespace(),
|
||||
], $stub);
|
||||
}
|
||||
|
||||
|
||||
/* ===================== Build 命令 ===================== */
|
||||
|
||||
@@ -273,18 +160,20 @@ class AppCommand extends Command
|
||||
{
|
||||
$this->basePath = $this->app->getRootPath() . 'app' . DIRECTORY_SEPARATOR;
|
||||
$app = $app ?: '';
|
||||
|
||||
if (is_file($this->basePath . 'build.php')) {
|
||||
$list = include $this->basePath . 'build.php';
|
||||
} else {
|
||||
$list = [
|
||||
'__dir__' => ['controller', 'model', 'view'],
|
||||
'__file__' => ['common.php', 'event.php', 'middleware.php', 'service.php'],
|
||||
'__dir__' => ['controller', 'model', 'view', 'route'],
|
||||
'controller' => ['Index'],
|
||||
'model' => [],
|
||||
'view' => ['index/index'],
|
||||
'route' => ['app'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->buildApp($app, $list);
|
||||
$output->writeln('<info>Successed</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -296,15 +185,8 @@ class AppCommand extends Command
|
||||
if (!is_dir($this->basePath . $app)) {
|
||||
mkdir($this->basePath . $app);
|
||||
}
|
||||
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
$namespace = 'app' . ($app ? '\\' . $app : '');
|
||||
|
||||
$this->buildCommon($app);
|
||||
$this->buildHello($app, $namespace);
|
||||
$this->buildapp1($app, $namespace);
|
||||
$this->buildInfo($app);
|
||||
|
||||
foreach ($list as $path => $file) {
|
||||
if ('__dir__' == $path) {
|
||||
foreach ($file as $dir) {
|
||||
@@ -330,6 +212,9 @@ class AppCommand extends Command
|
||||
}
|
||||
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'class ' . $class . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
|
||||
break;
|
||||
case 'route':
|
||||
$content = "<?php" . PHP_EOL . PHP_EOL . "use think\\facade\Route;";
|
||||
break;
|
||||
case 'model':
|
||||
$content = '<?php' . PHP_EOL . 'namespace ' . $space . ';' . PHP_EOL . PHP_EOL . 'use think\Model;' . PHP_EOL . PHP_EOL . 'class ' . $class . ' extends Model' . PHP_EOL . '{' . PHP_EOL . PHP_EOL . '}';
|
||||
break;
|
||||
@@ -350,70 +235,7 @@ class AppCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建应用的欢迎页面
|
||||
*/
|
||||
private function buildHello(string $app, string $namespace): void
|
||||
{
|
||||
$suffix = $this->app->config->get('route.controller_suffix') ? 'Controller' : '';
|
||||
$filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
|
||||
|
||||
if (!is_file($filename)) {
|
||||
$content = file_get_contents($this->stubDir() . 'controller.stub');
|
||||
$content = str_replace(
|
||||
['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'],
|
||||
['Index', $suffix, $namespace . '\\controller', $this->app->getNamespace()],
|
||||
$content
|
||||
);
|
||||
$this->checkDirBuild(dirname($filename));
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildapp1(string $app, string $namespace): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
if (!is_file($appPath . 'app.php')) {
|
||||
$content = file_get_contents($this->stubDir() . 'app.stub');
|
||||
$content = str_replace(['{%namespace%}', '{%app%}', '{%className%}'], [$namespace, $app, 'app'], $content);
|
||||
file_put_contents($appPath . 'app.php', $content);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildInfo(string $app): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
if (!is_file($appPath . 'info.php')) {
|
||||
$array = [
|
||||
'name' => '',
|
||||
'title' => '',
|
||||
'intro' => '',
|
||||
'author' => '',
|
||||
'website' => '',
|
||||
'version' => '1.0.0',
|
||||
'state' => 1,
|
||||
'url' => '',
|
||||
'license' => '',
|
||||
'licenseto' => 0,
|
||||
];
|
||||
$array = array_merge($array, ['name' => $app, 'url' => '/' . $app]);
|
||||
$output = "<?php\n return " . var_export($array, true) . ";\n?>";
|
||||
file_put_contents($appPath . 'info.php', $output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建应用的公共文件
|
||||
*/
|
||||
private function buildCommon(string $app): void
|
||||
{
|
||||
$appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
foreach (['event', 'middleware', 'common', 'config'] as $nm) {
|
||||
if (!is_file($appPath . $nm . '.php')) {
|
||||
file_put_contents($appPath . $nm . '.php', '<?php' . PHP_EOL . '// 这是系统自动生成的' . $nm . '定义文件' . PHP_EOL . 'return [' . PHP_EOL . PHP_EOL . '];' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录
|
||||
@@ -433,7 +255,6 @@ class AppCommand extends Command
|
||||
private function runClear(string $app, Input $input, Output $output): int
|
||||
{
|
||||
$runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
|
||||
|
||||
if ($input->getOption('cache')) {
|
||||
$path = $runtimePath . 'cache';
|
||||
} elseif ($input->getOption('log')) {
|
||||
@@ -441,12 +262,9 @@ class AppCommand extends Command
|
||||
} else {
|
||||
$path = $runtimePath;
|
||||
}
|
||||
|
||||
$rmdir = $input->getOption('dir') ? true : false;
|
||||
$this->clearRuntime(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
|
||||
|
||||
$output->writeln('<info>Clear Successed</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -456,7 +274,6 @@ class AppCommand extends Command
|
||||
private function clearRuntime(string $path, bool $rmdir): void
|
||||
{
|
||||
$files = is_dir($path) ? scandir($path) : [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ('.' != $file && '..' != $file && is_dir($path . $file)) {
|
||||
array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*'));
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace {%namespace%};
|
||||
|
||||
use think\Request;
|
||||
|
||||
class {%className%} extends \ywxapp\controller\AddonFrontend
|
||||
class {%className%} extends \ywxapp\controller\{%baseClass%}
|
||||
{
|
||||
/**
|
||||
* Summary of needLogin
|
||||
@@ -33,7 +33,7 @@ class {%className%} extends \ywxapp\controller\AddonFrontend
|
||||
*/
|
||||
public function index{%actionSuffix%}()
|
||||
{
|
||||
return "这是一个{%app_namespace%} 插件应用";
|
||||
return "这是一个{%app_namespace%} 插件应用控制器{%namespace%}";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,7 @@ class {%className%} extends \ywxapp\controller\AddonFrontend
|
||||
* @param int $id
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function edit{%actionSuffix%}($id)
|
||||
public function edit{%actionSuffix%}($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
|
||||
// 公共前缀 /{%app%},同时设置默认命名空间为 addon\{%app%}\controller
|
||||
|
||||
// 后台 → addon\{%app%}\controller\backend\Index
|
||||
Route::group('backend', function () {
|
||||
Route::get('index/index', 'index/index')->name('{%app%}_backend_index');
|
||||
})->namespace('addon\{%app%}\controller\backend');
|
||||
|
||||
// 用户中心 → addon\{%app%}\controller\member\Index
|
||||
Route::group('member', function () {
|
||||
Route::get('index/index', 'index/index')->name('{%app%}_member_index');
|
||||
})->namespace('addon\{%app%}\controller\member');
|
||||
|
||||
// API → apaddonp\{%app%}\controller\api\Index
|
||||
Route::group('api', function () {
|
||||
Route::get('index/index', 'index/index')->name('{%app%}_api_index');
|
||||
})->namespace('addon\{%app%}\controller\api');
|
||||
|
||||
// 前台 → addon\{%app%}\controller\Index(不加子分组)
|
||||
Route::get('index/index', 'index/index')->name('{%app%}_index');
|
||||
@@ -35,7 +35,7 @@ class Addon
|
||||
if (!isset($info['name'])) {
|
||||
continue;
|
||||
}
|
||||
$installedInfo = Db::name('addon')->where('name', $name)->find();
|
||||
$installedInfo = Db::name('common_addon')->where('name', $name)->find();
|
||||
$list[] = [
|
||||
'name' => $name,
|
||||
'title' => $info['title'] ?? $name,
|
||||
@@ -125,7 +125,7 @@ class Addon
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 404, 'msg' => '插件不存在']);
|
||||
}
|
||||
$installed = Db::name('addon')->where('name', $name)->find();
|
||||
$installed = Db::name('common_addon')->where('name', $name)->find();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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 ywxapp\controller;
|
||||
|
||||
use think\App;
|
||||
|
||||
/**
|
||||
* API 控制器基础类(App / 小程序 / 第三方客户端通用)
|
||||
*
|
||||
* 统一提供:JWT 登录态注入、未登录 401 拦截、标准响应封装。
|
||||
* 业务控制器继承本类即可复用登录态(\ywxapp\model\MemberUser)与响应(\ywxapp\library\Result)。
|
||||
*
|
||||
* 鉴权约定:
|
||||
* - 子类通过 $noNeedLogin 声明“免登录即可访问”的 action,['*'] 表示全部放行。
|
||||
* - 其余 action 在构造阶段统一校验登录态,未登录直接返回 HTTP 401。
|
||||
* - 统一响应:成功 $this->result->success($data),失败 $this->result->error($msg, $code)。
|
||||
*
|
||||
* @package ywxapp\controller
|
||||
*/
|
||||
class ApiBase extends BaseController
|
||||
{
|
||||
/**
|
||||
* 无需登录即可访问的 action 列表,['*'] 表示全部放行。
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 无需额外校验的 action 列表(兼容旧写法,语义同 $noNeedLogin)。
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $noNeedVerify = [];
|
||||
|
||||
/**
|
||||
* 构造方法。
|
||||
*
|
||||
* 完成父类初始化后,依次调用 initialize()(子类自定义初始化)与
|
||||
* checkLogin()(统一登录拦截)。
|
||||
*
|
||||
* @param App $app 应用实例
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->initialize();
|
||||
$this->checkLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制器初始化钩子,子类可重写以执行构造后的自定义初始化逻辑。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize() {}
|
||||
|
||||
/**
|
||||
* 统一登录拦截。
|
||||
*
|
||||
* 当当前 action 不在 $noNeedLogin / $noNeedVerify 白名单,且会员未登录时,
|
||||
* 直接返回 HTTP 401,阻止后续业务执行。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkLogin(): void
|
||||
{
|
||||
$action = $this->request->action();
|
||||
$white = array_merge($this->noNeedLogin, $this->noNeedVerify);
|
||||
if (in_array('*', $white, true) || in_array($action, $white, true)) {
|
||||
return;
|
||||
}
|
||||
if (!$this->auth || !$this->auth->isLogin) {
|
||||
$this->result->apiError('请先登录', 401, null, 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录会员模型(\ywxapp\model\MemberUser)。
|
||||
*
|
||||
* 未登录访问已被 checkLogin() 拦截,此处仅作兜底返回。
|
||||
*
|
||||
* @return \ywxapp\model\MemberUser 当前登录会员模型
|
||||
*/
|
||||
protected function user()
|
||||
{
|
||||
if ($this->auth && $this->auth->isLogin) {
|
||||
return $this->auth->info;
|
||||
}
|
||||
$this->result->apiError('请先登录', 401, null, 401);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录会员的主键 ID(uid)。
|
||||
*
|
||||
* @return int 会员 uid,未登录时为 0
|
||||
*/
|
||||
protected function uid(): int
|
||||
{
|
||||
return (int) ($this->auth->model->uid ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 成功响应(强制 JSON,统一契约).
|
||||
*
|
||||
* 透传至 \ywxapp\library\Result::apiSuccess():固定 JSON、字段
|
||||
* code/msg/data/timestamp,自动携带 token 与分页 count。
|
||||
*
|
||||
* @param mixed $data 业务数据
|
||||
* @param string $message 成功提示语
|
||||
* @param int $count 分页总数(可选)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function apiSuccess($data = null, string $message = 'success', int $count = 0): void
|
||||
{
|
||||
$this->result->apiSuccess($data, $message, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 失败响应(强制 JSON,业务 code 与 HTTP 状态解耦).
|
||||
*
|
||||
* 透传至 \ywxapp\library\Result::apiError():固定 JSON、字段
|
||||
* code/msg/data/timestamp;业务 code 原样透传,HTTP 状态由 $httpStatus 控制。
|
||||
*
|
||||
* @param string $message 失败提示语
|
||||
* @param int $code 业务状态码(默认 1,鉴权失败建议 401)
|
||||
* @param mixed $data 附加数据(可选)
|
||||
* @param int $httpStatus HTTP 状态码(默认 200)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function apiError(string $message = 'Error', int $code = 1, $data = null, int $httpStatus = 200): void
|
||||
{
|
||||
$this->result->apiError($message, $code, $data, $httpStatus);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?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 ywxapp\controller;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
*/
|
||||
class ApiController extends BaseController
|
||||
{
|
||||
// use \ywxapp\traits\Backend;
|
||||
|
||||
/**
|
||||
* 控制器初始化 _initialize
|
||||
* @return void
|
||||
*/
|
||||
public function _initialize()
|
||||
{
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/** 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize() {}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
* \ywxapp\model\Member
|
||||
*/
|
||||
protected function user()
|
||||
{
|
||||
if ($this->auth && $this->auth->isLogin) {
|
||||
return $this->auth->info;
|
||||
}
|
||||
$this->result->error('获取用户信息失败!');
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ declare(strict_types=1);
|
||||
namespace ywxapp\controller;
|
||||
|
||||
use think\facade\View;
|
||||
use ywxapp\model\Configure as ConfModel;
|
||||
use ywxapp\model\Links;
|
||||
use ywxapp\model\Notice;
|
||||
use ywxapp\model\Ad;
|
||||
use ywxapp\model\CommonConfigure as ConfModel;
|
||||
use ywxapp\model\CommonLinks;
|
||||
use ywxapp\model\CommonNotice;
|
||||
use ywxapp\model\CommonAd;
|
||||
use ywxapp\library\SkinOverlay;
|
||||
use ywxapp\library\SkinVariables;
|
||||
use ywxapp\library\TemplateManager;
|
||||
@@ -77,21 +77,21 @@ abstract class FrontendBase extends BaseController
|
||||
}
|
||||
$siteConf = ConfModel::cache(true)->column('value', 'name');
|
||||
$this->view->assign('siteConf', $siteConf);
|
||||
$links = Links::cache(true)->where('status', 1)->order('sort desc')->select();
|
||||
$links = CommonLinks::cache(true)->where('status', 1)->order('sort desc')->select();
|
||||
$this->view->assign('links', $links);
|
||||
// 全站置顶公告条(方案 A):仅取当前生效 + 置顶 + 启用的公告
|
||||
$typeList = Notice::typeList();
|
||||
$typeList = CommonNotice::typeList();
|
||||
$topNotices = array_map(function ($n) use ($typeList) {
|
||||
$n['type_text'] = $typeList[$n['type']] ?? '公告';
|
||||
return $n;
|
||||
}, Notice::getActiveTopNotices());
|
||||
}, CommonNotice::getActiveTopNotices());
|
||||
$this->view->assign('topNotices', $topNotices);
|
||||
// 全站广告位:按 position 分组的启用广告,模板可随时调用 {notempty name="adSlots.home_top"}
|
||||
$this->view->assign('adSlots', Ad::getSlots());
|
||||
$this->view->assign('adSlots', CommonAd::getSlots());
|
||||
// 注入站点信息(route_base / site.module / site.controller 等),供前端 route.js 使用
|
||||
$this->assignSite();
|
||||
// 前台主导航菜单(后台可配置,两级下拉),供 header.html 渲染
|
||||
$this->view->assign('navMenus', \app\backend\model\NavMenu::getNavTree());
|
||||
$this->view->assign('navMenus', \ywxapp\model\CommonNavMenu::getNavTree());
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -81,9 +81,9 @@ class MemberBase extends BaseController
|
||||
if ($this->auth && $this->auth->isLogin) {
|
||||
$this->view->assign('member', $this->auth->info);
|
||||
}
|
||||
$siteConf = \ywxapp\model\Configure::cache(true)->column('value', 'name');
|
||||
$siteConf = \ywxapp\model\CommonConfigure::cache(true)->column('value', 'name');
|
||||
$this->view->assign('siteConf', $siteConf);
|
||||
$links = \ywxapp\model\Links::cache(true)->where('status', 1)->order('sort desc')->select();
|
||||
$links = \ywxapp\model\CommonLinks::cache(true)->where('status', 1)->order('sort desc')->select();
|
||||
$this->view->assign('links', $links);
|
||||
// 注入站点信息(route_base / site.module / site.controller 等),供前端 route.js 使用
|
||||
$this->assignSite();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// +----------------------------------------------------------------------
|
||||
namespace ywxapp\controller;
|
||||
|
||||
use ywxapp\model\Attachment;
|
||||
use ywxapp\model\CommonAttachment;
|
||||
use ywxapp\service\FileStorageService;
|
||||
|
||||
/**
|
||||
@@ -141,7 +141,7 @@ class UeditorPlus
|
||||
{
|
||||
try {
|
||||
$user = app()->auth->info;
|
||||
$datas = Attachment::where('uid', $user->uid)->where('mime', 'like', 'image%')->field('name,url')->select();
|
||||
$datas = CommonAttachment::where('uid', $user->uid)->where('mime', 'like', 'image%')->field('name,url')->select();
|
||||
return json([
|
||||
"state" => "SUCCESS",
|
||||
"list" => $datas,
|
||||
@@ -171,7 +171,7 @@ class UeditorPlus
|
||||
try {
|
||||
$user = app()->auth->info;
|
||||
$data['uid'] = $user->uid; // 根据你的登录逻辑获取用户ID
|
||||
$info = Attachment::create($data);
|
||||
$info = CommonAttachment::create($data);
|
||||
return $info;
|
||||
} catch (\Exception $e) {
|
||||
// 记录日志,但不要因为入库失败而中断上传流程(或者根据需求决定)
|
||||
|
||||
@@ -121,3 +121,49 @@ if (!function_exists('is_market_client')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('cross_url')) {
|
||||
function cross_url(string $appName, string $url = '', array $vars = [], string $suffix = ''): string
|
||||
{
|
||||
$map = config('app.domain_bind', []);
|
||||
$mainDomain = config('app.url_main_domain');
|
||||
|
||||
$domainKey = '';
|
||||
foreach ($map as $key => $mappedApp) {
|
||||
if ($mappedApp === $appName) {
|
||||
$domainKey = $key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 host 和 port
|
||||
$host = '';
|
||||
$port = '';
|
||||
|
||||
if ($domainKey === '') {
|
||||
$host = true; // 用当前域名
|
||||
} elseif (str_contains($domainKey, ':')) {
|
||||
// 带端口:api.example.com:8080
|
||||
[$host, $port] = explode(':', $domainKey, 2);
|
||||
$port = ':' . $port;
|
||||
} elseif (str_contains($domainKey, '.')) {
|
||||
// 完整域名
|
||||
$host = $domainKey;
|
||||
} else {
|
||||
// 子域名
|
||||
$host = "{$domainKey}.{$mainDomain}";
|
||||
}
|
||||
|
||||
return (string) \think\facade\Url::build($url)
|
||||
->vars($vars)
|
||||
->suffix($suffix)
|
||||
->domain($host);
|
||||
}
|
||||
}
|
||||
if (!function_exists('L')) {
|
||||
function L($value)
|
||||
{
|
||||
return \think\facade\Lang::get($value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace ywxapp\library;
|
||||
|
||||
use think\facade\Event;
|
||||
use ywxapp\library\Result;
|
||||
use ywxapp\model\Email as EmailModel;
|
||||
use ywxapp\model\CommonEmail as EmailModel;
|
||||
use ywxapp\model\MemberUser as UsersModel;
|
||||
|
||||
/**
|
||||
|
||||
@@ -134,13 +134,18 @@ class Result
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回封装后的 API 数据到客户端.
|
||||
* 返回封装后的 API 数据到客户端(已弃用,字段契约与 success/error 不一致).
|
||||
*
|
||||
* 请改用 apiSuccess() / apiError():统一 JSON、统一字段(code/msg/data/timestamp),
|
||||
* 业务 code 与 HTTP 状态码解耦,不再受后台 HTML/401 跳转逻辑影响。
|
||||
*
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param int $code 返回的 code
|
||||
* @param mixed $msg 提示信息
|
||||
* @param string $type 返回数据格式
|
||||
* @param array $header 发送的 Header 信息
|
||||
*
|
||||
* @deprecated 使用 apiSuccess() / apiError() 替代
|
||||
*/
|
||||
protected function result($data, $code = 0, $msg = '', $type = '', array $header = [])
|
||||
{
|
||||
@@ -156,6 +161,82 @@ class Result
|
||||
throw new HttpResponseException($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 成功响应(强制 JSON,契约稳定).
|
||||
*
|
||||
* 专供 App / 小程序 / 第三方客户端调用,与后台 HTML 响应完全解耦:
|
||||
* - 无论请求是否 ajax,固定返回 JSON;
|
||||
* - 统一字段:code(0) / msg / data / timestamp;
|
||||
* - 若本实例持有 token,自动附加 access_token / refresh_token 等字段;
|
||||
* - 若设置了 count,自动附加 count(分页场景)。
|
||||
*
|
||||
* @param mixed $data 业务数据,可为数组 / 模型 / null
|
||||
* @param string $message 成功提示语
|
||||
* @param int $count 分页总数(可选,>0 时附加 count 字段)
|
||||
*
|
||||
* @return void 通过 Response 直接输出并终止
|
||||
*/
|
||||
public function apiSuccess($data = null, string $message = 'success', int $count = 0): void
|
||||
{
|
||||
$result = ['code' => 0, 'msg' => $message, 'timestamp' => time()];
|
||||
if ($data !== null) {
|
||||
$result['data'] = $data;
|
||||
}
|
||||
if ($count > 0) {
|
||||
$result['count'] = $count;
|
||||
}
|
||||
if ($this->AccessToken !== null) {
|
||||
$result['access_token'] = $this->AccessToken;
|
||||
$result['access_expired'] = $this->AccessExpired;
|
||||
}
|
||||
if ($this->RefreshToken !== null) {
|
||||
$result['refresh_token'] = $this->RefreshToken;
|
||||
$result['refresh_expired'] = $this->RefreshExpired;
|
||||
}
|
||||
$response = Response::create($result, 'json', $this->StatusCode)->header($this->Headers);
|
||||
$this->applyTokenCookies($response);
|
||||
$response->send();
|
||||
app()->http->end($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 失败响应(强制 JSON,业务 code 与 HTTP 状态解耦).
|
||||
*
|
||||
* 与 apiSuccess 配套,专供客户端调用:
|
||||
* - 固定返回 JSON;
|
||||
* - 统一字段:code / msg / data(可选) / timestamp;
|
||||
* - 业务 code 原样透传(如 401 失败返回 code=401,而非被覆盖);
|
||||
* - HTTP 状态码由 $httpStatus 控制(默认 200,鉴权失败传 401)。
|
||||
*
|
||||
* @param string $message 失败提示语
|
||||
* @param int $code 业务状态码(默认 1,鉴权失败建议 401)
|
||||
* @param mixed $data 附加数据(可选)
|
||||
* @param int $httpStatus HTTP 状态码(默认 200)
|
||||
*
|
||||
* @return void 通过 Response 直接输出并终止
|
||||
*/
|
||||
public function apiError(string $message = 'Error', int $code = 1, $data = null, int $httpStatus = 200): void
|
||||
{
|
||||
$result = ['code' => $code, 'msg' => $message, 'timestamp' => time()];
|
||||
if ($data !== null) {
|
||||
$result['data'] = $data;
|
||||
}
|
||||
if ($this->AccessToken !== null) {
|
||||
$result['access_token'] = $this->AccessToken;
|
||||
$result['access_expired'] = $this->AccessExpired;
|
||||
}
|
||||
if ($this->RefreshToken !== null) {
|
||||
$result['refresh_token'] = $this->RefreshToken;
|
||||
$result['refresh_expired'] = $this->RefreshExpired;
|
||||
}
|
||||
$response = Response::create($result, 'json', $httpStatus)->header($this->Headers);
|
||||
$this->applyTokenCookies($response);
|
||||
$response->send();
|
||||
app()->http->end($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 重定向.
|
||||
*
|
||||
|
||||
+14
-4
@@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-06 22:05:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-19 22:12:47
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\library\Sms.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
@@ -9,13 +18,13 @@
|
||||
namespace ywxapp\library;
|
||||
|
||||
use ywxapp\utils\Random;
|
||||
use think\facade\Event;
|
||||
use ywxapp\model\Sms as SmsModel;
|
||||
use think\facade\Event;
|
||||
use ywxapp\model\CommonSms as SmsModel;
|
||||
|
||||
/**
|
||||
* 短信验证码类
|
||||
*/
|
||||
class Sms
|
||||
class Sms
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -53,7 +62,8 @@ class Sms
|
||||
* @return boolean
|
||||
*/
|
||||
public static function send($mobile, $code = null, $event = 'default')
|
||||
{
|
||||
{
|
||||
|
||||
$code = is_null($code) ? Random::numeric(6) : $code;
|
||||
$time = time();
|
||||
$ip = request()->ip();
|
||||
|
||||
@@ -51,8 +51,8 @@ class SpiderStat
|
||||
protected function record($request, $response, string $spider, string $ua): void
|
||||
{
|
||||
$prefix = config('database.connections.mysql.prefix', 'wxapp_');
|
||||
$logTable = $prefix . 'spider_log';
|
||||
$statTable = $prefix . 'spider_stat';
|
||||
$logTable = $prefix . 'common_spider_log';
|
||||
$statTable = $prefix . 'common_spider_stat';
|
||||
|
||||
$data = [
|
||||
'spider' => $spider,
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?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 ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class AddonModel extends BaseModel
|
||||
{
|
||||
// -- 插件主表
|
||||
// CREATE TABLE `addon` (
|
||||
// `id` int unsigned AUTO_INCREMENT PRIMARY KEY,
|
||||
// `name` varchar(50) NOT NULL COMMENT '标识符',
|
||||
// `title` varchar(100) NOT NULL COMMENT '名称',
|
||||
// `version` varchar(20) NOT NULL COMMENT '版本号',
|
||||
// `price` decimal(10,2) NOT NULL COMMENT '价格',
|
||||
// `file_path` varchar(255) NOT NULL COMMENT '存储路径(非公开)',
|
||||
// `file_hash` varchar(64) NOT NULL COMMENT 'SHA256校验值',
|
||||
// `status` tinyint DEFAULT 1 COMMENT '1上架 0下架'
|
||||
// );
|
||||
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'name' => 'addon',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'schema' => [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'title' => 'string',
|
||||
'version' => 'string',
|
||||
'price' => 'float',
|
||||
'description' => 'string',
|
||||
'author' => 'string',
|
||||
'status' => 'tinyint',
|
||||
'config' => 'text',
|
||||
'file_path' => 'string',
|
||||
'file_hash' => 'string',
|
||||
'sort' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int',
|
||||
]
|
||||
];
|
||||
}
|
||||
// // 获取已安装插件列表
|
||||
// public function getInstalledaddon(): array
|
||||
// {
|
||||
// return $this->where('status', '<>', self::STATUS_DISABLE)->order('create_at', 'desc')->select()->toArray();
|
||||
// }
|
||||
|
||||
// // 检查插件是否已安装
|
||||
// public function isInstalled(string $name): bool
|
||||
// {
|
||||
// return $this->where('name', $name)->count() > 0;
|
||||
// }
|
||||
}
|
||||
@@ -24,7 +24,6 @@ class BackendAdmin extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'backend_admin',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-06 22:05:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-19 20:51:06
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\BackendLog.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
@@ -15,8 +24,7 @@ use ywxapp\model\BaseModel;
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class BackendLog extends BaseModel{
|
||||
protected $name = 'backend_log';
|
||||
class BackendLog extends BaseModel{
|
||||
protected $updateTime = false;
|
||||
/**
|
||||
* 获取用户的角色
|
||||
@@ -31,7 +39,7 @@ class BackendLog extends BaseModel{
|
||||
|
||||
public function roles()
|
||||
{
|
||||
return $this->belongsToMany(Role::class, 'access', 'rid','aid');
|
||||
return $this->belongsToMany(BackendRole::class, 'access', 'rid','aid');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ class BackendPower extends BaseModel
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'backend_power',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace ywxapp\model;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
/**
|
||||
* BackendRole 类
|
||||
*
|
||||
@@ -19,24 +20,22 @@ use ywxapp\model\BaseModel;
|
||||
class BackendRole extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
// 设置表名(think-orm 4.0:name 不带前缀,自动拼 wxapp_)
|
||||
protected $name = 'backend_role';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
// 自动时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
protected $deleteTime = 'delete_at';
|
||||
// 隐藏字段
|
||||
protected $hidden = ['password', 'delete_at'];
|
||||
// 只读字段
|
||||
protected $readonly = ['id'];
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'append' => ['target'],
|
||||
'hidden' => ['password', 'delete_at'],
|
||||
'readonly' => ['id'],
|
||||
];
|
||||
}
|
||||
|
||||
// 角色拥有的用户
|
||||
|
||||
public function admins()
|
||||
{
|
||||
return $this->belongsToMany(BackendAdmin::class, BackendRoleAccess::class, 'admin_id', 'role_id');
|
||||
@@ -52,7 +51,7 @@ class BackendRole extends BaseModel
|
||||
}
|
||||
|
||||
// 【推荐】角色 → 菜单模型(通过中间表 role_permissions 关联 menus)
|
||||
|
||||
|
||||
public function menus()
|
||||
{
|
||||
return $this->belongsToMany(BackendPower::class, BackendRolePower::class, 'power_id', 'role_id', 'permission');
|
||||
|
||||
@@ -4,34 +4,35 @@
|
||||
* @Date: 2026-08-06 22:05:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-10 09:44:50
|
||||
* @Description:
|
||||
* @Description: ywxapp/model/BackendPower.php
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\BackendRoleAccess.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\Pivot;
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
/**
|
||||
* BackendRoleAccess 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class BackendRoleAccess extends Pivot{
|
||||
protected $name = 'backend_role_access';
|
||||
|
||||
// 自动时间戳 (create_at 已统一为 int 时间戳)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = false;
|
||||
class BackendRoleAccess extends Pivot
|
||||
{
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'append' => ['target'],
|
||||
'hidden' => ['create_at'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 表结构自愈:建表 + 关键列兜底(install.sql 为事实源)。
|
||||
@@ -41,4 +42,4 @@ class BackendRoleAccess extends Pivot{
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'backend_role_access');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,19 @@ use think\model\Pivot;
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class BackendRolePower extends Pivot
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'backend_role_power';
|
||||
protected $pk = ['role_id', 'power_id'];
|
||||
// 自动时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = false;
|
||||
{
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => false,
|
||||
'defaultSoftDelete' => 0,
|
||||
'append' => ['target'],
|
||||
'hidden' => ['create_at', 'update_at', 'delete_at'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ use think\Model;
|
||||
*/
|
||||
class BaseModel extends Model
|
||||
{
|
||||
|
||||
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 聊天
|
||||
*/
|
||||
class Chat extends BaseModel
|
||||
{
|
||||
|
||||
// 设置表名(think-orm 4.0:name 不带前缀,自动拼 wxapp_)
|
||||
protected $name = 'chat';
|
||||
|
||||
protected $schema = [
|
||||
'id' => 'int',
|
||||
'from_uid' => 'int',
|
||||
'to_uid' => 'int',
|
||||
'type' => 'int',
|
||||
'content' => 'string',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// CREATE TABLE `chat_users` (
|
||||
// `id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
// `username` varchar(50) NOT NULL,
|
||||
// `password` varchar(255) NOT NULL,
|
||||
// `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
// PRIMARY KEY (`id`)
|
||||
// );
|
||||
|
||||
// CREATE TABLE `chat_messages` (
|
||||
// `id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
// `from_user_id` int(11) NOT NULL,
|
||||
// `to_user_id` int(11) NOT NULL,
|
||||
// `message` text NOT NULL,
|
||||
// `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
// PRIMARY KEY (`id`)
|
||||
// );
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 聊天消息
|
||||
*/
|
||||
class ChatMessage extends \ywxapp\BaseController
|
||||
{
|
||||
|
||||
}
|
||||
@@ -4,9 +4,8 @@ namespace ywxapp\model;
|
||||
use think\facade\Db;
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
class Ad extends BaseModel
|
||||
{
|
||||
protected $name = 'ad';
|
||||
class CommonAd extends BaseModel
|
||||
{
|
||||
|
||||
// 广告类型
|
||||
const TYPE_IMAGE = 1; // 图片
|
||||
@@ -29,7 +28,6 @@ class Ad extends BaseModel
|
||||
public function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'ad',
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => true,
|
||||
'createTime' => 'create_at',
|
||||
@@ -82,9 +80,9 @@ class Ad extends BaseModel
|
||||
try {
|
||||
$p = self::currentPrefix();
|
||||
$prefix = $p;
|
||||
$table = $prefix . 'ad';
|
||||
// 缺表建表(install.sql L638 同款 DDL,运行时前缀兜底)
|
||||
BaseModel::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$p}ad` (
|
||||
$table = $prefix . 'common_ad';
|
||||
// 缺表建表(install.sql 同款 DDL,运行时前缀兜底)
|
||||
BaseModel::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$p}common_ad` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '广告标题',
|
||||
`type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型 1图片2文字3代码',
|
||||
@@ -116,7 +114,7 @@ class Ad extends BaseModel
|
||||
public static function getByPosition(string $position): array
|
||||
{
|
||||
self::ensureSchema();
|
||||
$rows = Db::name('ad')
|
||||
$rows = Db::name('common_ad')
|
||||
->where('status', 1)
|
||||
->where('position', $position)
|
||||
->where('delete_at', 0)
|
||||
@@ -140,7 +138,7 @@ class Ad extends BaseModel
|
||||
self::ensureSchema();
|
||||
$positions = array_keys(self::positionList());
|
||||
$slots = array_fill_keys($positions, []);
|
||||
$rows = Db::name('ad')
|
||||
$rows = Db::name('common_ad')
|
||||
->where('status', 1)
|
||||
->where('position', 'in', $positions)
|
||||
->where('delete_at', 0)
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-06 22:05:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-19 20:51:29
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\AddonModel.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class CommonAddon extends BaseModel
|
||||
{
|
||||
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'schema' => [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'title' => 'string',
|
||||
'version' => 'string',
|
||||
'price' => 'float',
|
||||
'description' => 'string',
|
||||
'author' => 'string',
|
||||
'status' => 'tinyint',
|
||||
'config' => 'text',
|
||||
'file_path' => 'string',
|
||||
'file_hash' => 'string',
|
||||
'sort' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int',
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,9 @@ namespace ywxapp\model;
|
||||
use think\Model;
|
||||
|
||||
|
||||
class Attachment extends BaseModel
|
||||
{
|
||||
class CommonAttachment extends BaseModel
|
||||
{
|
||||
|
||||
// 设置字段信息
|
||||
protected $schema = [
|
||||
//文件ID
|
||||
@@ -90,7 +91,7 @@ class Attachment extends BaseModel
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'attachment');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'common_attachment');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,15 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-07 10:22:47
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-20 00:36:36
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\CommonCard.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
@@ -10,18 +20,23 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 充值卡密
|
||||
*/
|
||||
class Card extends BaseModel
|
||||
class CommonCard extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'card';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
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'],
|
||||
];
|
||||
}
|
||||
|
||||
// 卡密状态
|
||||
const STATUS_UNSOLD = 0; // 未售
|
||||
@@ -33,7 +48,7 @@ class Card extends BaseModel
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'card');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'common_card');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,9 +103,9 @@ class Card extends BaseModel
|
||||
$card->save();
|
||||
|
||||
// 2) 会员钱包加余额 + 累计充值(无钱包则自动创建)
|
||||
$wallet = Db::name('member_wallets')->where('uid', $uid)->find();
|
||||
$wallet = Db::name('member_wallet')->where('uid', $uid)->find();
|
||||
if (empty($wallet)) {
|
||||
Db::name('member_wallets')->insert([
|
||||
Db::name('member_wallet')->insert([
|
||||
'uid' => $uid,
|
||||
'balance' => $amount,
|
||||
'total_recharge' => $amount,
|
||||
@@ -98,7 +113,7 @@ class Card extends BaseModel
|
||||
'update_at' => time(),
|
||||
]);
|
||||
} else {
|
||||
Db::name('member_wallets')
|
||||
Db::name('member_wallet')
|
||||
->where('uid', $uid)
|
||||
->inc('balance', $amount)
|
||||
->inc('total_recharge', $amount)
|
||||
@@ -14,15 +14,27 @@ namespace ywxapp\model;
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Configure extends BaseModel
|
||||
class CommonConfigure extends BaseModel
|
||||
{
|
||||
|
||||
|
||||
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 function getTitleAttr($value)
|
||||
{
|
||||
return lang($value);
|
||||
}
|
||||
|
||||
|
||||
public function getGroupAttr($value)
|
||||
{
|
||||
return lang($value);
|
||||
@@ -33,7 +45,7 @@ class Configure extends BaseModel
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'configure');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'common_configure');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,8 +14,21 @@ namespace ywxapp\model;
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Email extends BaseModel{
|
||||
class CommonEmail extends BaseModel{
|
||||
// 设置表名(think-orm 4.0:name 不带前缀,自动拼 wxapp_)
|
||||
protected $name = 'email';
|
||||
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'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-07 10:22:47
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-19 21:08:06
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\Help.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
@@ -9,25 +20,29 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 站点帮助
|
||||
*/
|
||||
class Help extends BaseModel
|
||||
class CommonHelp extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'help';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'readonly' => ['id'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 运行时自愈:确保 help 主表存在(install.sql 已含 category/view_count 等列,为事实源)。
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'help');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'common_help');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,4 +54,4 @@ class Help extends BaseModel
|
||||
parent::initialize();
|
||||
self::ensureSchema();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ use think\model\concern\SoftDelete;
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class Links extends BaseModel
|
||||
class CommonLinks extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
@@ -29,7 +29,6 @@ class Links extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'name' => 'links',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
@@ -41,7 +40,7 @@ class Links extends BaseModel
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'links');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'common_links');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
@@ -10,18 +12,24 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 勋章中心
|
||||
*/
|
||||
class Medal extends BaseModel
|
||||
class CommonMedal extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'medal';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
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'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 控制器初始化(模型实例方法,非静态)
|
||||
@@ -74,7 +82,7 @@ class Medal extends BaseModel
|
||||
}
|
||||
return Db::name('member_medal')
|
||||
->alias('um')
|
||||
->join('medal m', 'm.id = um.medal_id')
|
||||
->join('common_medal m', 'm.id = um.medal_id')
|
||||
->where('um.uid', $uid)
|
||||
->where('m.delete_at', 0)
|
||||
->field('m.id,m.title,m.image,m.description,um.create_at')
|
||||
@@ -89,7 +97,7 @@ class Medal extends BaseModel
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
BaseModel::ensureTableFromInstall($prefix, 'medal');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'common_medal');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'member_medal');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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 ywxapp\model;
|
||||
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 前台主导航菜单模型(支持两级下拉).
|
||||
*/
|
||||
class CommonNavMenu extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $deleteTime = 'delete_at';
|
||||
|
||||
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'],
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 运行时自愈:确保 common_nav_menu 主表存在(install.sql 为事实源),
|
||||
* 老库补充 app 字段(跨应用链接,见 ensureColumn).
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
BaseModel::ensureTableFromInstall($prefix, 'common_nav_menu');
|
||||
BaseModel::ensureColumn(
|
||||
$prefix . 'common_nav_menu',
|
||||
'app',
|
||||
"varchar(30) NOT NULL DEFAULT '' COMMENT '所属应用(跨应用链接用,空=当前应用)' AFTER `url`"
|
||||
);
|
||||
}
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
self::ensureSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的导航树(两级:父 + 子)。
|
||||
* @return array [{id,title,url,icon,child:[...]}]
|
||||
*/
|
||||
public static function getNavTree(): array
|
||||
{
|
||||
$list = self::where('status', 1)
|
||||
->where('delete_at', 0)
|
||||
->order('sort', 'asc')
|
||||
->order('id', 'asc')
|
||||
->field('id,parent_id,title,url,app,icon,sort')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$parents = [];
|
||||
$childrenMap = [];
|
||||
foreach ($list as $item) {
|
||||
$item['ctrl'] = strtolower(strtok($item['url'], '/') ?: '');
|
||||
if ((int) $item['parent_id'] === 0) {
|
||||
$item['child'] = [];
|
||||
$parents[] = $item;
|
||||
} else {
|
||||
$childrenMap[$item['parent_id']][] = $item;
|
||||
}
|
||||
}
|
||||
foreach ($parents as &$p) {
|
||||
if (isset($childrenMap[$p['id']])) {
|
||||
$p['child'] = $childrenMap[$p['id']];
|
||||
}
|
||||
}
|
||||
return $parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台列表:返回两级扁平树(含隐藏项).
|
||||
*
|
||||
* @param string $title 可选:按菜单名称模糊筛选(命中子项时自动带上其父级)
|
||||
*/
|
||||
public static function getAdminTree(string $title = ''): array
|
||||
{
|
||||
$query = self::where('delete_at', 0);
|
||||
if ($title !== '') {
|
||||
$matchIds = self::where('delete_at', 0)->whereLike('title', "%{$title}%")->column('id');
|
||||
if (empty($matchIds)) {
|
||||
return [];
|
||||
}
|
||||
$parentIds = self::where('delete_at', 0)->whereIn('parent_id', $matchIds)->column('parent_id');
|
||||
$ids = array_values(array_unique(array_merge($matchIds, $parentIds)));
|
||||
$query->whereIn('id', $ids);
|
||||
}
|
||||
$list = $query->order('sort', 'asc')
|
||||
->order('id', 'asc')
|
||||
->field('id,parent_id,title,url,app,icon,sort,status')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$parents = [];
|
||||
$childrenMap = [];
|
||||
foreach ($list as $item) {
|
||||
$item['ctrl'] = strtolower(strtok($item['url'], '/') ?: '');
|
||||
if ((int) $item['parent_id'] === 0) {
|
||||
$item['child'] = [];
|
||||
$parents[] = $item;
|
||||
} else {
|
||||
$childrenMap[$item['parent_id']][] = $item;
|
||||
}
|
||||
}
|
||||
foreach ($parents as &$p) {
|
||||
if (isset($childrenMap[$p['id']])) {
|
||||
$p['child'] = $childrenMap[$p['id']];
|
||||
}
|
||||
}
|
||||
return $parents;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
@@ -8,13 +10,10 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 站点公告
|
||||
*/
|
||||
class Notice extends BaseModel
|
||||
class CommonNotice extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 类型:1系统维护 2活动公告 3版本更新 4其他
|
||||
public const TYPE_MAINTAIN = 1;
|
||||
public const TYPE_ACTIVITY = 2;
|
||||
@@ -35,9 +34,6 @@ class Notice extends BaseModel
|
||||
'delete_at' => 'integer',
|
||||
];
|
||||
|
||||
// 只读字段
|
||||
protected $readonly = ['view_count'];
|
||||
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
@@ -45,10 +41,10 @@ class Notice extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'name' => 'notice',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
|
||||
@@ -121,9 +117,9 @@ class Notice extends BaseModel
|
||||
try {
|
||||
$p = self::currentPrefix();
|
||||
$prefix = $p;
|
||||
$table = $prefix . 'notice';
|
||||
// 缺表建表(install.sql L618 同款 DDL,运行时前缀兜底)
|
||||
self::ensureTable($table, "CREATE TABLE IF NOT EXISTS `{$p}notice` (
|
||||
$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 '公告内容',
|
||||
@@ -152,4 +148,4 @@ class Notice extends BaseModel
|
||||
// 忽略自愈失败,由业务暴露
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class CommonProp extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,25 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 电子商务
|
||||
*/
|
||||
class Shop extends BaseModel
|
||||
class CommonShop extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'shop';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 运行时自愈:确保 shop 主表存在(install.sql 为事实源)。
|
||||
*/
|
||||
@@ -8,18 +8,25 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 短信服务
|
||||
*/
|
||||
class Sms extends BaseModel
|
||||
class CommonSms extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'sms';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时自愈:确保 sms 主表存在(install.sql 为事实源),并幂等补 delete_at 软删除列。
|
||||
@@ -27,8 +34,8 @@ class Sms extends BaseModel
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
BaseModel::ensureTableFromInstall($prefix, 'sms');
|
||||
self::ensureColumn($prefix . 'sms', 'delete_at', "int NOT NULL DEFAULT 0 COMMENT '删除时间(软删除)'");
|
||||
BaseModel::ensureTableFromInstall($prefix, 'common_sms');
|
||||
self::ensureColumn($prefix . 'common_sms', 'delete_at', "int NOT NULL DEFAULT 0 COMMENT '删除时间(软删除)'");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class CommonSmsTemplate extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'name' => 'sms_template',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\model\Medal;
|
||||
use ywxapp\model\CommonMedal;
|
||||
|
||||
use think\facade\Db;
|
||||
use think\model\concern\SoftDelete;
|
||||
@@ -11,18 +11,25 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 站点任务
|
||||
*/
|
||||
class Task extends BaseModel
|
||||
class CommonTask extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'task';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
|
||||
// 奖励类型
|
||||
const REWARD_SCORE = 1; // 积分
|
||||
@@ -100,19 +107,19 @@ class Task extends BaseModel
|
||||
// 发放奖励
|
||||
switch ((int) $task['reward_type']) {
|
||||
case self::REWARD_SCORE:
|
||||
[$ok, $msg] = \ywxapp\model\Score::change($uid, \ywxapp\model\Score::TYPE_GAIN, $num, '完成任务:' . $task['title']);
|
||||
[$ok, $msg] = \ywxapp\model\MebmberScore::change($uid, \ywxapp\model\MebmberScore::TYPE_GAIN, $num, '完成任务:' . $task['title']);
|
||||
if (! $ok) {
|
||||
throw new \Exception($msg);
|
||||
}
|
||||
break;
|
||||
case self::REWARD_COIN:
|
||||
Db::name('member_wallets')->where('uid', $uid)->inc('coins', $num)->update();
|
||||
Db::name('member_wallet')->where('uid', $uid)->inc('coins', $num)->update();
|
||||
Db::name('member_bill')->insert([
|
||||
'uid' => $uid,
|
||||
'currency' => 2, // 2=金币
|
||||
'type' => 1, // 1=收入
|
||||
'amount' => $num,
|
||||
'balance' => Db::name('member_wallets')->where('uid', $uid)->value('coins') ?: $num,
|
||||
'balance' => Db::name('member_wallet')->where('uid', $uid)->value('coins') ?: $num,
|
||||
'remark' => '完成任务:' . $task['title'],
|
||||
'create_at' => time(),
|
||||
]);
|
||||
@@ -127,7 +134,7 @@ class Task extends BaseModel
|
||||
break;
|
||||
case self::REWARD_MEDAL:
|
||||
// reward_num 存勋章ID
|
||||
$grant = Medal::grant($uid, $num);
|
||||
$grant = CommonMedal::grant($uid, $num);
|
||||
if (!$grant[0]) {
|
||||
throw new \Exception($grant[1]);
|
||||
}
|
||||
@@ -159,10 +166,10 @@ class Task extends BaseModel
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
BaseModel::ensureTableFromInstall($prefix, 'task');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'common_task');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'member_prop');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'member_task');
|
||||
BaseModel::ensureColumn("{$prefix}member", 'score', "int unsigned NOT NULL DEFAULT 0 COMMENT '积分(做任务/互动获得)'");
|
||||
BaseModel::ensureColumn("{$prefix}task", 'prop_num', "int NOT NULL DEFAULT 1 COMMENT '道具数量(奖励类型为道具时生效)'");
|
||||
BaseModel::ensureColumn("{$prefix}member_user", 'score', "int unsigned NOT NULL DEFAULT 0 COMMENT '积分(做任务/互动获得)'");
|
||||
BaseModel::ensureColumn("{$prefix}common_task", 'prop_num', "int NOT NULL DEFAULT 1 COMMENT '道具数量(奖励类型为道具时生效)'");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/*
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-07 22:53:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-19 21:12:33
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\model\Score.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
namespace ywxapp\model;
|
||||
|
||||
@@ -10,17 +19,23 @@ use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* 积分规则 & 积分流水
|
||||
*/
|
||||
class Score extends BaseModel
|
||||
class MebmberScore extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'score_rule';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
|
||||
// 规则类型
|
||||
const TYPE_GAIN = 1; // 获取
|
||||
@@ -40,9 +55,9 @@ class Score extends BaseModel
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
BaseModel::ensureTableFromInstall($prefix, 'score_rule');
|
||||
BaseModel::ensureTableFromInstall($prefix, 'score_log');
|
||||
BaseModel::ensureColumn("{$prefix}member", 'score', "int unsigned NOT NULL DEFAULT 0 COMMENT '积分(做任务/互动获得)'");
|
||||
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 '积分(做任务/互动获得)'");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +81,7 @@ class Score extends BaseModel
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$user = Db::name('member')->where('uid', $uid)->lock(true)->find();
|
||||
$user = Db::name('member_user')->where('uid', $uid)->lock(true)->find();
|
||||
if (!$user) {
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
@@ -77,9 +92,9 @@ class Score extends BaseModel
|
||||
? $user['score'] - $value
|
||||
: $user['score'] + $value;
|
||||
|
||||
Db::name('member')->where('uid', $uid)->update(['score' => $balance]);
|
||||
Db::name('member_user')->where('uid', $uid)->update(['score' => $balance]);
|
||||
|
||||
Db::name('score_log')->insert([
|
||||
Db::name('member_score_log')->insert([
|
||||
'uid' => $uid,
|
||||
'rule_id' => $ruleId,
|
||||
'type' => $type,
|
||||
@@ -103,7 +118,7 @@ class Score extends BaseModel
|
||||
public static function logList(int $uid = 0, int $page = 1, int $limit = 15): array
|
||||
{
|
||||
self::ensureSchema();
|
||||
$query = Db::name('score_log');
|
||||
$query = Db::name('member_score_log');
|
||||
if ($uid > 0) {
|
||||
$query->where('uid', $uid);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class MemberBill extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,7 @@ class MemberGroup extends BaseModel
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_group',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
|
||||
@@ -22,8 +22,7 @@ class MemberGroupAccess extends Pivot
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_group_access',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int', // create_at 已统一为 int 时间戳
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => false,
|
||||
|
||||
@@ -20,14 +20,10 @@ class MemberGroupRule extends Model
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_group_rule',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int', // create_at 已统一为 int 时间戳
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => false,
|
||||
// 'deleteTime' => 'delete_at',
|
||||
// 'defaultSoftDelete' => 0,
|
||||
// 'dateFormat' => 'Y-m-d H:i:s',
|
||||
'updateTime' => false,
|
||||
'append' => ['is_parent'],
|
||||
'hidden' => ['password', 'create_at', 'update_at', 'delete_at'],
|
||||
'readonly' => ['id'],
|
||||
|
||||
@@ -19,14 +19,10 @@ use ywxapp\model\BaseModel;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_log',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => false,
|
||||
// 'deleteTime' => 'delete_at',
|
||||
// 'defaultSoftDelete' => 0,
|
||||
// 'dateFormat' => 'Y-m-d H:i:s',
|
||||
'updateTime' => false,
|
||||
'append' => ['is_parent'],
|
||||
'hidden' => ['password', 'create_at', 'update_at', 'delete_at'],
|
||||
'readonly' => ['id'],
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class MemberMedal extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,9 @@ class MemberProfile extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_profile',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
// 'deleteTime' => 'delete_at',
|
||||
// 'defaultSoftDelete' => 0,
|
||||
// 'dateFormat' => 'Y-m-d H:i:s',
|
||||
'append' => ['is_parent', 'age', 'is_online'],
|
||||
'hidden' => ['create_at', 'update_at', 'delete_at'],
|
||||
'readonly' => ['uid'],
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 用户持有道具(member_prop)
|
||||
*/
|
||||
class MemberProp extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
/**
|
||||
* 模型配置(BaseModel::applyOptions 约定)
|
||||
*/
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'readonly' => ['view_count']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时自愈:确保 member_prop 表存在(install.sql 为事实源)。
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'member_prop');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化:自愈建表,避免远程库缺失 member_prop 导致 1146。
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
self::ensureSchema();
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,11 @@ class MemberRule extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'member_rule',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
'deleteTime' => 'delete_at',
|
||||
'defaultSoftDelete' => 0,
|
||||
// 'dateFormat' => 'Y-m-d H:i:s',
|
||||
'append' => ['is_parent' ],
|
||||
'hidden' => ['password', 'create_at', 'update_at', 'delete_at'],
|
||||
'readonly' => ['id'],
|
||||
|
||||
@@ -18,25 +18,21 @@ use ywxapp\model\BaseModel;
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class ScoreLog extends BaseModel
|
||||
class MemberScoreLog extends BaseModel
|
||||
{
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'score_log');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'member_score_log');
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => false,
|
||||
'name' => 'score_log',
|
||||
'strict' => false,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
// 'deleteTime' => 'delete_at',
|
||||
// 'defaultSoftDelete' => 0,
|
||||
// 'dateFormat' => 'Y-m-d H:i:s',
|
||||
'updateTime' => 'update_at',
|
||||
'append' => ['is_parent'],
|
||||
'hidden' => ['password', 'create_at', 'update_at', 'delete_at'],
|
||||
'readonly' => ['id'],
|
||||
@@ -49,6 +45,6 @@ class ScoreLog extends BaseModel
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'score_log');
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'member_score_log');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class MemberTask extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+10
-21
@@ -26,7 +26,6 @@ class MemberUser extends BaseModel
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'name' => 'member_user',
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
@@ -82,31 +81,21 @@ class MemberUser extends BaseModel
|
||||
->column('rid');
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
|
||||
// 密码加密:bcrypt 自带盐,无需独立 salt 列
|
||||
// 兼容旧账户(若库里是 password_hash(value.salt) 的老哈希,
|
||||
// 这里统一改为 password_hash(value),老账户需要先重置密码)。
|
||||
public function setPasswordAttr($value)
|
||||
{
|
||||
$this->salt = $this->generateSalt();
|
||||
return password_hash($value . $this->salt, PASSWORD_DEFAULT);
|
||||
return password_hash($value, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
// 生成随机盐
|
||||
|
||||
protected function generateSalt($length = 6)
|
||||
{
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_[]{}<>~`+=,.;:/?|';
|
||||
$salt = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$salt .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
return $salt;
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
|
||||
// 验证密码:bcrypt 哈希内已含盐,直接验证即可
|
||||
public function checkPassword($password)
|
||||
{
|
||||
return password_verify($password . $this->salt, $this->password);
|
||||
if (empty($this->password)) {
|
||||
return false;
|
||||
}
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
@@ -194,7 +183,7 @@ class MemberUser extends BaseModel
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
$prefix = BaseModel::currentPrefix();
|
||||
foreach (['member', 'member_bill', 'member_group', 'member_group_access', 'member_group_rule', 'member_log', 'member_profile', 'member_rule', 'member_wallets'] as $t) {
|
||||
foreach (['member_user', 'member_bill', 'member_group', 'member_group_access', 'member_group_rule', 'member_log', 'member_profile', 'member_rule', 'member_wallet'] as $t) {
|
||||
BaseModel::ensureTableFromInstall($prefix, $t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
// use think\model\concern\SoftDelete;
|
||||
/**
|
||||
* @mixin \think\Model
|
||||
*/
|
||||
class MemberWallet extends BaseModel
|
||||
{
|
||||
//use SoftDelete;
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'strict' => true,
|
||||
'autoWriteTimestamp' => 'int',
|
||||
'readonly' => ['uid'],
|
||||
'createTime' => 'create_at',
|
||||
'updateTime' => 'update_at',
|
||||
//'deleteTime' => 'delete_at',
|
||||
//'hidden' => ['password', 'delete_at'],
|
||||
//'append' => ['status_text', 'last_login'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace ywxapp\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
use think\model\concern\SoftDelete;
|
||||
|
||||
/**
|
||||
* 道具中心
|
||||
*/
|
||||
class Prop extends BaseModel
|
||||
{
|
||||
use SoftDelete;
|
||||
|
||||
protected $name = 'prop';
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $defaultSoftDelete = 0;
|
||||
|
||||
// 时间戳自动写入
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
/**
|
||||
* 运行时自愈:确保 prop 主表存在(install.sql 为事实源)。
|
||||
*/
|
||||
public static function ensureSchema(): void
|
||||
{
|
||||
BaseModel::ensureTableFromInstall(BaseModel::currentPrefix(), 'prop');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化:自愈建表,避免远程库缺失 wxapp_prop 导致 1146。
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
self::ensureSchema();
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,12 @@ class AddonDevService
|
||||
$data[] = $item;
|
||||
}
|
||||
$this->writeReturnArray($this->addonDir . 'config.php', $data);
|
||||
|
||||
// 设计器改了 config.php 静态默认值后,清运行时配置缓存与插件加载缓存,
|
||||
// 使下次 readConfig() 回源重新合并(文件默认值 + 数据库已保存值),
|
||||
// 否则仅改了文件、DB/缓存未动,运行时 config('插件名') 仍读旧值。
|
||||
\ywxapp\service\AddonService::clearConfigCache($this->addon);
|
||||
\ywxapp\service\AppService::clearAddonCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1334,7 +1334,7 @@ class AddonService
|
||||
if ($config === null || !empty($changed)) {
|
||||
self::ensureConfigTable();
|
||||
// 从统一配置表读取「独立项」(addon + name 唯一,每行一个配置项)
|
||||
$rows = Db::name('addon_config')->where('addon', $name)->column('value', 'name');
|
||||
$rows = Db::name('common_addonconf')->where('addon', $name)->column('value', 'name');
|
||||
$config = $rows ?: [];
|
||||
// 平滑迁移:若该插件此前用 .addonrc 文件存过配置,则一次性导入数据库
|
||||
if (empty($config)) {
|
||||
@@ -1372,14 +1372,14 @@ class AddonService
|
||||
|
||||
/**
|
||||
* 自愈建表:确保统一插件配置表存在(CREATE TABLE IF NOT EXISTS)
|
||||
* 表结构:wxapp_addon_config,每个配置项独立成行(addon + name 唯一)
|
||||
* 表结构:{prefix}common_addonconf,每个配置项独立成行(addon + name 唯一)
|
||||
*/
|
||||
private static function ensureConfigTable(): void
|
||||
{
|
||||
try {
|
||||
$defaultDb = Config::get('database.default');
|
||||
$prefix = Config::get("database.connections.{$defaultDb}.prefix");
|
||||
$table = $prefix . 'addon_config';
|
||||
$table = $prefix . 'common_addonconf';
|
||||
$exists = Db::query("SHOW TABLES LIKE '{$table}'");
|
||||
if (empty($exists)) {
|
||||
Db::execute("CREATE TABLE IF NOT EXISTS `{$table}` (
|
||||
@@ -1407,7 +1407,7 @@ class AddonService
|
||||
{
|
||||
$defaultDb = Config::get('database.default');
|
||||
$prefix = Config::get("database.connections.{$defaultDb}.prefix");
|
||||
$table = $prefix . 'addon_config';
|
||||
$table = $prefix . 'common_addonconf';
|
||||
$time = time();
|
||||
foreach ($values as $key => $val) {
|
||||
$val = is_array($val) ? json_encode($val, JSON_UNESCAPED_UNICODE) : (string) $val;
|
||||
@@ -1508,7 +1508,7 @@ class AddonService
|
||||
public function upgrade(string $currentVersion = ''): bool
|
||||
{
|
||||
if ($currentVersion === '') {
|
||||
$installed = \ywxapp\model\AddonModel::where('name', $this->addon)->value('version');
|
||||
$installed = \ywxapp\model\CommonAddon::where('name', $this->addon)->value('version');
|
||||
$currentVersion = $installed ?: '';
|
||||
}
|
||||
$this->callAddonHook('upgrade', [$currentVersion]);
|
||||
@@ -1951,6 +1951,13 @@ class AddonService
|
||||
} catch (\PDOException $e) {
|
||||
Log::error("Addon {$operation} error (Database): " . $e->getMessage());
|
||||
throw new Exception(lang('数据库操作失败'));
|
||||
} catch (\Error $e) {
|
||||
Log::error("Addon {$operation} error (Error): " . $e->getMessage());
|
||||
// 开发环境透传原始错误信息便于定位;生产环境统一返回通用提示,避免泄露路径等内部细节
|
||||
if (Env::get('app_debug')) {
|
||||
throw new Exception('插件操作失败:' . $e->getMessage());
|
||||
}
|
||||
throw new Exception('插件操作失败,请稍后重试或联系管理员');
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Addon {$operation} error: " . $e->getMessage());
|
||||
// 开发环境透传原始异常信息(如 Addon already exists / version incorrect)便于定位;
|
||||
@@ -1958,7 +1965,7 @@ class AddonService
|
||||
if (Env::get('app_debug')) {
|
||||
throw new Exception('插件操作失败:' . $e->getMessage());
|
||||
}
|
||||
throw new Exception('插件操作失败,请稍后重试或联系管理员' . $e->getMessage());
|
||||
throw new Exception('插件操作失败,请稍后重试或联系管理员');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2565,8 +2572,9 @@ class AddonService
|
||||
// 不存在(即未指定可用顶级菜单)则回退到「以插件名为标题的顶级菜单」。
|
||||
// 2) menu.json 未声明 superior:直接生成一个以插件名为标题的顶级菜单(pid=0),
|
||||
// 所有菜单都注入到这个插件名顶级菜单下。
|
||||
$superior = $menu['superior'] ?? '';
|
||||
if ($superior !== '') {
|
||||
$superior = $menu['parent'] ?? 'app';
|
||||
$isRoot = $menu['is_root'] ?? false;
|
||||
if ($isRoot== false) {
|
||||
$appPid = $this->getOrCreateAppMenuId($superior);
|
||||
if ($appPid > 0) {
|
||||
// 指定顶级菜单存在:在其下挂「插件名分组」,子项再挂该分组下
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @Author: YwxApp <ywx@ywxapp.cn>
|
||||
* @Date: 2026-08-06 22:05:48
|
||||
* @LastEditors: YwxApp <ywx@ywxapp.cn>
|
||||
* @LastEditTime: 2026-08-13 16:46:11
|
||||
* @LastEditTime: 2026-08-20 11:19:15
|
||||
* @Description:
|
||||
* @FilePath: \ywxapp_dev\ywxapp\service\AppService.php
|
||||
* @CustomString: Copyright (c) 2026 YwxApp
|
||||
@@ -74,7 +74,7 @@ class AppService extends Service
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
\think\facade\Config::set(\ywxapp\addon::readConfig($name), $name);
|
||||
\think\facade\Config::set(\ywxapp\AddonBase::readConfig($name), $name);
|
||||
} catch (\Throwable $e) {
|
||||
// 单个插件预热失败不影响主流程
|
||||
}
|
||||
@@ -82,23 +82,29 @@ class AppService extends Service
|
||||
});
|
||||
// 默认短信发送监听:开发环境不实际下发(仅记录验证码到日志并回传前端用于调试),
|
||||
// 生产环境返回 null,交由短信插件(监听 SmsSend 事件)实际发送。
|
||||
\think\facade\Event::listen('SmsSend', function ($sms) {
|
||||
if (Env::get('app_debug')) {
|
||||
\think\facade\Log::info('[DEV] 短信验证码(未实际下发): ' . ($sms['code'] ?? '') . ' -> ' . ($sms['mobile'] ?? ''));
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
// \think\facade\Event::listen('SmsSend', function ($sms) {
|
||||
// if (Env::get('app_debug')) {
|
||||
// \think\facade\Log::info('[DEV] 短信验证码(未实际下发): ' . ($sms['code'] ?? '') . ' -> ' . ($sms['mobile'] ?? ''));
|
||||
// return true;
|
||||
// }
|
||||
// return null;
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$this->app->middleware->import([MultiApp::class], 'global');
|
||||
|
||||
|
||||
// 2. 替换 url() 使用的 UrlBuild 类
|
||||
$this->app->bind(\think\route\Url::class, UrlBuild::class);
|
||||
$this->commands([
|
||||
// 插件命令自动注册约定:
|
||||
// 插件在 info.php 声明 'commands' => ['插件名:动作' => \addon\<插件名>\command\Xxx::class]
|
||||
// 命令类 extends think\console\Command,psr-4 (addon\ => addon) 自动加载。
|
||||
// 执行链:Console 构造 → app->initialize() → BootService 触发本 boot() →
|
||||
// $this->commands() 挂 Console::starting 回调 → Console::run() 统一 addCommands。
|
||||
// 坏类名/非 Command 子类会被 addCommands 跳过,单个插件声明错误不影响整体。
|
||||
$commands = [
|
||||
'app' => \ywxapp\command\AppCommand::class,
|
||||
'addon:manage' => \ywxapp\command\AddonManage::class,
|
||||
'addon:make' => \ywxapp\command\AddonMake::class,
|
||||
@@ -106,7 +112,14 @@ class AppService extends Service
|
||||
'addon:license-check' => \ywxapp\command\AddonLicenseCheck::class,
|
||||
'addon:repair' => \ywxapp\command\AddonRepair::class,
|
||||
'ywxapp:upgrade' => \ywxapp\command\Upgrade::class,
|
||||
]);
|
||||
];
|
||||
foreach ($this->getEnabledaddon() as $info) {
|
||||
$addonCmds = $info['commands'] ?? [];
|
||||
if (is_array($addonCmds) && $addonCmds) {
|
||||
$commands = array_merge($commands, $addonCmds);
|
||||
}
|
||||
}
|
||||
$this->commands($commands);
|
||||
$this->loadAddonRelevant();
|
||||
// $this->loadAddonRoutes();
|
||||
Route::rule('ueditor', function () {
|
||||
@@ -252,10 +265,18 @@ class AppService extends Service
|
||||
public static function clearAddonCache(): void
|
||||
{
|
||||
\think\facade\Cache::delete('addon_loaded_config');
|
||||
// 同时清除配置缓存
|
||||
$addon = \think\facade\Config::get('addon', []);
|
||||
foreach ($addon as $addon) {
|
||||
\think\facade\Cache::delete('addon_config_' . $addon['name']);
|
||||
// 同时清除各插件的配置缓存。
|
||||
// 注意:Config::get('addon') 返回的是框架 addon 组件自身配置(含 multi_app/priority 等),
|
||||
// 并非插件列表,切勿直接 foreach 它(遍历到 multi_app=true 会触发 "array offset on true")。
|
||||
// 正确的插件名来源是 addon 主表的 name 列。
|
||||
try {
|
||||
$names = \ywxapp\model\CommonAddon::column('name');
|
||||
foreach ($names as $name) {
|
||||
\think\facade\Cache::delete('addon_config_' . $name);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 数据表可能尚未创建,忽略
|
||||
\think\facade\Log::warning('clearAddonCache 获取插件列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +291,7 @@ class AppService extends Service
|
||||
return true; // 无市场插件 → 无授权域,放行
|
||||
}
|
||||
try {
|
||||
$addonId = \ywxapp\model\AddonModel::where('name', $addon)->value('id');
|
||||
$addonId = \ywxapp\model\CommonAddon::where('name', $addon)->value('id');
|
||||
if (!$addonId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ class WalletService
|
||||
*/
|
||||
public static function ensure(int $uid): void
|
||||
{
|
||||
$exist = Db::name('member_wallets')->where('uid', $uid)->value('id');
|
||||
$exist = Db::name('member_wallet')->where('uid', $uid)->value('id');
|
||||
if (! $exist) {
|
||||
Db::name('member_wallets')->insert([
|
||||
Db::name('member_wallet')->insert([
|
||||
'uid' => $uid,
|
||||
'balance' => 0,
|
||||
'coins' => 0,
|
||||
@@ -43,7 +43,7 @@ class WalletService
|
||||
public static function getCoins(int $uid): int
|
||||
{
|
||||
self::ensure($uid);
|
||||
return (int) Db::name('member_wallets')->where('uid', $uid)->value('coins');
|
||||
return (int) Db::name('member_wallet')->where('uid', $uid)->value('coins');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ class WalletService
|
||||
self::ensure($uid);
|
||||
Db::startTrans();
|
||||
try {
|
||||
Db::name('member_wallets')->where('uid', $uid)->inc('coins', $num)->update(['update_at' => time()]);
|
||||
Db::name('member_wallet')->where('uid', $uid)->inc('coins', $num)->update(['update_at' => time()]);
|
||||
Db::name('member_bill')->insert([
|
||||
'uid' => $uid,
|
||||
'type' => 1,
|
||||
@@ -85,13 +85,13 @@ class WalletService
|
||||
return true;
|
||||
}
|
||||
self::ensure($uid);
|
||||
$cur = Db::name('member_wallets')->where('uid', $uid)->lock(true)->value('coins');
|
||||
$cur = Db::name('member_wallet')->where('uid', $uid)->lock(true)->value('coins');
|
||||
if ($cur < $num) {
|
||||
throw new \Exception('金币不足');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
Db::name('member_wallets')->where('uid', $uid)
|
||||
Db::name('member_wallet')->where('uid', $uid)
|
||||
->dec('coins', $num)
|
||||
->inc('total_consume', $num)
|
||||
->update(['update_at' => time()]);
|
||||
|
||||
Reference in New Issue
Block a user