feat(fsgx): 完成全部24项开发任务 Phase1-7

Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
This commit is contained in:
apple
2026-03-23 22:32:19 +08:00
parent 788ee0c0c0
commit 434aa8c69d
13098 changed files with 2008990 additions and 961 deletions

View File

@@ -0,0 +1,3 @@
composer.lock
/.idea
/vendor

View File

@@ -0,0 +1,5 @@
# think-filesystem for ThinkPHP6.1
## 安装
> composer require topthink/think-filesystem

View File

@@ -0,0 +1,33 @@
{
"name": "topthink/think-filesystem",
"description": "The ThinkPHP6.1 Filesystem Package",
"type": "library",
"license": "Apache-2.0",
"authors": [
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"autoload": {
"psr-4": {
"think\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"think\\tests\\": "tests/"
}
},
"require": {
"topthink/framework": "^6.1|^8.0",
"league/flysystem": "^2.0"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^8.0"
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="ThinkPHP Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/think</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,89 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use InvalidArgumentException;
use think\filesystem\Driver;
use think\filesystem\driver\Local;
use think\helper\Arr;
/**
* Class Filesystem
* @package think
* @mixin Driver
* @mixin Local
*/
class Filesystem extends Manager
{
protected $namespace = '\\think\\filesystem\\driver\\';
/**
* @param null|string $name
* @return Driver
*/
public function disk(string $name = null): Driver
{
return $this->driver($name);
}
protected function resolveType(string $name)
{
return $this->getDiskConfig($name, 'type', 'local');
}
protected function resolveConfig(string $name)
{
return $this->getDiskConfig($name);
}
/**
* 获取缓存配置
* @access public
* @param null|string $name 名称
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = null, $default = null)
{
if (!is_null($name)) {
return $this->app->config->get('filesystem.' . $name, $default);
}
return $this->app->config->get('filesystem');
}
/**
* 获取磁盘配置
* @param string $disk
* @param null $name
* @param null $default
* @return array
*/
public function getDiskConfig($disk, $name = null, $default = null)
{
if ($config = $this->getConfig("disks.{$disk}")) {
return Arr::get($config, $name, $default);
}
throw new InvalidArgumentException("Disk [$disk] not found.");
}
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver()
{
return $this->getConfig('default');
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\facade;
use think\Facade;
use think\filesystem\Driver;
/**
* Class Filesystem
* @package think\facade
* @mixin \think\Filesystem
* @method static Driver disk(string $name = null) , null|string
* @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
* @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置
* @method static string|null getDefaultDriver() 默认驱动
*/
class Filesystem extends Facade
{
protected static function getFacadeClass()
{
return \think\Filesystem::class;
}
}

View File

@@ -0,0 +1,130 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\filesystem;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use RuntimeException;
use think\Cache;
use think\File;
/**
* Class Driver
* @package think\filesystem
* @mixin Filesystem
*/
abstract class Driver
{
/** @var Cache */
protected $cache;
/** @var Filesystem */
protected $filesystem;
/**
* 配置参数
* @var array
*/
protected $config = [];
public function __construct(Cache $cache, array $config)
{
$this->cache = $cache;
$this->config = array_merge($this->config, $config);
$adapter = $this->createAdapter();
$this->filesystem = $this->createFilesystem($adapter);
}
abstract protected function createAdapter(): FilesystemAdapter;
protected function createFilesystem(FilesystemAdapter $adapter): Filesystem
{
$config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url']));
return new Filesystem($adapter, $config);
}
/**
* 获取文件完整路径
* @param string $path
* @return string
*/
public function path(string $path): string
{
return $path;
}
protected function concatPathToUrl($url, $path)
{
return rtrim($url, '/') . '/' . ltrim($path, '/');
}
public function url(string $path): string
{
throw new RuntimeException('This driver does not support retrieving URLs.');
}
/**
* 保存文件
* @param string $path 路径
* @param File $file 文件
* @param null|string|\Closure $rule 文件名规则
* @param array $options 参数
* @return bool|string
*/
public function putFile(string $path, File $file, $rule = null, array $options = [])
{
return $this->putFileAs($path, $file, $file->hashName($rule), $options);
}
/**
* 指定文件名保存文件
* @param string $path 路径
* @param File $file 文件
* @param string $name 文件名
* @param array $options 参数
* @return bool|string
*/
public function putFileAs(string $path, File $file, string $name, array $options = [])
{
$stream = fopen($file->getRealPath(), 'r');
$path = trim($path . '/' . $name, '/');
$result = $this->put($path, $stream, $options);
if (is_resource($stream)) {
fclose($stream);
}
return $result ? $path : false;
}
protected function put(string $path, $contents, array $options = [])
{
try {
$this->writeStream($path, $contents, $options);
} catch (UnableToWriteFile|UnableToSetVisibility $e) {
return false;
}
return true;
}
public function __call($method, $parameters)
{
return $this->filesystem->$method(...$parameters);
}
}

View File

@@ -0,0 +1,98 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\filesystem\driver;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\Local\LocalFilesystemAdapter;
use League\Flysystem\PathNormalizer;
use League\Flysystem\PathPrefixer;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\Visibility;
use League\Flysystem\WhitespacePathNormalizer;
use think\filesystem\Driver;
class Local extends Driver
{
/**
* 配置参数
* @var array
*/
protected $config = [
'root' => '',
];
/**
* @var PathPrefixer
*/
protected $prefixer;
/**
* @var PathNormalizer
*/
protected $normalizer;
protected function createAdapter(): FilesystemAdapter
{
$visibility = PortableVisibilityConverter::fromArray(
$this->config['permissions'] ?? [],
$this->config['visibility'] ?? Visibility::PRIVATE
);
$links = ($this->config['links'] ?? null) === 'skip'
? LocalFilesystemAdapter::SKIP_LINKS
: LocalFilesystemAdapter::DISALLOW_LINKS;
return new LocalFilesystemAdapter(
$this->config['root'],
$visibility,
$this->config['lock'] ?? LOCK_EX,
$links
);
}
protected function prefixer()
{
if (!$this->prefixer) {
$this->prefixer = new PathPrefixer($this->config['root'], DIRECTORY_SEPARATOR);
}
return $this->prefixer;
}
protected function normalizer()
{
if (!$this->normalizer) {
$this->normalizer = new WhitespacePathNormalizer();
}
return $this->normalizer;
}
/**
* 获取文件访问地址
* @param string $path 文件路径
* @return string
*/
public function url(string $path): string
{
$path = $this->normalizer()->normalizePath($path);
if (isset($this->config['url'])) {
return $this->concatPathToUrl($this->config['url'], $path);
}
return parent::url($path);
}
public function path(string $path): string
{
return $this->prefixer()->prefixPath($path);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace think\tests;
use Mockery as m;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PHPUnit\Framework\TestCase;
use think\App;
use think\Config;
use think\Container;
use think\Filesystem;
use think\filesystem\driver\Local;
class FilesystemTest extends TestCase
{
/** @var App|MockInterface */
protected $app;
/** @var Filesystem */
protected $filesystem;
/** @var Config|MockInterface */
protected $config;
/** @var vfsStreamDirectory */
protected $root;
protected function setUp(): void
{
$this->app = m::mock(App::class)->makePartial();
Container::setInstance($this->app);
$this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
$this->config = m::mock(Config::class);
$this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local');
$this->app->shouldReceive('get')->with('config')->andReturn($this->config);
$this->filesystem = new Filesystem($this->app);
$this->root = vfsStream::setup('rootDir');
}
protected function tearDown(): void
{
m::close();
}
public function testDisk()
{
$this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
'type' => 'local',
'root' => $this->root->url(),
]);
$this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([
'type' => 'local',
'root' => $this->root->url(),
]);
$this->assertInstanceOf(Local::class, $this->filesystem->disk());
$this->assertInstanceOf(Local::class, $this->filesystem->disk('foo'));
}
}

View File

@@ -0,0 +1,2 @@
<?php