doge vor 6 Stunden
Ursprung
Commit
a2e6e76f55

+ 2 - 2
app/Http/Controllers/admin/User.php

@@ -34,8 +34,8 @@ class User extends Controller
         try {
             $params = request()->validate([
                 'member_id' => ['required', 'string', 'min:1'],
-                'password' => ['nullable'],
-                'payment_password' => ['nullable'],
+                'password' => ['nullable', 'string', 'min:6', 'max:16'],
+                'payment_password' => ['nullable', 'string', 'min:6', 'max:16'],
             ]);
             $user = UserModel::where('member_id', $params['member_id'])->first();
             if (!$user) throw new Exception("用户不存在", HttpStatus::CUSTOM_ERROR);

+ 61 - 0
docs/前端接口/新分享系统/返佣规则-金额千分位显示.md

@@ -0,0 +1,61 @@
+# 会员返佣规则:金额千分位显示
+
+适用接口:`GET /api/share/rates`。本说明更新[返佣比例](返佣比例.md)中的金额显示约定,其余请求参数、认证、响应结构和字段含义继续适用。
+
+## 金额格式
+
+以下金额字段直接返回带英文逗号的展示字符串:
+
+| 响应字段(相对 `data`) | 含义 |
+| --- | --- |
+| `valid_member_min_deposit` | 有效会员累计充值门槛 |
+| `valid_member_min_effective_bet` | 有效会员累计有效投注门槛;未配置仍为 `null` |
+| `data[].tiers[].min_performance` | 各返佣档位的有效业绩门槛 |
+| `data[].direct_performance` | 本人合格直属会员今日在该分类的有效业绩 |
+
+整数金额省略 `.00`,有非零小数时保留两位:
+
+| 金额 | 返回值 |
+| --- | --- |
+| `0.00` | `"0"` |
+| `999.00` | `"999"` |
+| `1000.00` | `"1,000"` |
+| `300000.00` | `"300,000"` |
+| `300000.50` | `"300,000.50"` |
+| `99999999999999.99` | `"99,999,999,999,999.99"` |
+
+前端直接按文本显示这些字段,不再使用 `Number()`、`parseFloat()` 转换或重复添加千分位。它们是展示字符串,不作为计算或提交参数。
+
+## 响应示例
+
+以下片段省略未变化的字段:
+
+```json
+{
+  "code": 1,
+  "show": 0,
+  "msg": "",
+  "data": {
+    "valid_member_min_deposit": "1,000",
+    "valid_member_min_effective_bet": "2,000.50",
+    "data": [
+      {
+        "category": "third:2",
+        "category_name": "电子",
+        "tiers": [
+          {
+            "min_valid_members": 1,
+            "min_performance": "300,000",
+            "rate": "0.2500"
+          }
+        ],
+        "current_rate": "0.2500",
+        "direct_performance": "300,000.50",
+        "valid_direct_members": 1
+      }
+    ]
+  }
+}
+```
+
+人数仍为整数;`rate`、`current_rate` 仍为四位小数的百分数字符串,不加千分位。后台返佣配置接口、会员首页及其他报表接口维持原有金额格式,返佣计算和数据库存储方式不变。

+ 144 - 0
tests/Integration/UserPasswordValidationTest.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace Tests\Integration;
+
+use App\Http\Controllers\admin\User as UserController;
+use Illuminate\Cache\ArrayStore;
+use Illuminate\Cache\Repository as CacheRepository;
+use Illuminate\Config\Repository;
+use Illuminate\Database\Capsule\Manager;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Foundation\Application;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Facade;
+use Illuminate\Translation\ArrayLoader;
+use Illuminate\Translation\Translator;
+use Illuminate\Validation\Factory;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+class UserPasswordValidationTest extends TestCase
+{
+    private Application $app;
+
+    protected function setUp(): void
+    {
+        Facade::clearResolvedInstances();
+        Model::clearBootedModels();
+        require_once dirname(__DIR__, 2) . '/app/Helpers/helpers.php';
+        $this->app = new Application(dirname(__DIR__, 2));
+        $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']]));
+        $this->app->instance('cache', new CacheRepository(new ArrayStore()));
+        $translator = new Translator(new ArrayLoader(), 'zh');
+        $this->app->instance('translator', $translator);
+        $this->app->instance('validator', new Factory($translator, $this->app));
+        $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new \Tests\Support\JsonResponseFactory());
+        Request::macro('validate', function (array $rules) {
+            return app('validator')->make($this->all(), $rules)->validate();
+        });
+        Facade::setFacadeApplication($this->app);
+        $db = new Manager($this->app);
+        $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
+        $db->setAsGlobal();
+        $db->bootEloquent();
+        $this->app->instance('db', $db->getDatabaseManager());
+        $db->schema()->create('users', function (Blueprint $t) {
+            $t->id(); $t->string('member_id'); $t->string('password'); $t->string('payment_password');
+            $t->string('language')->default('zh'); $t->integer('recharge_channel_group_id')->default(1); $t->timestamps();
+        });
+        $db->schema()->create('user_session', function (Blueprint $t) {
+            $t->id(); $t->string('user_id'); $t->string('token'); $t->integer('expire_time');
+        });
+        DB::table('users')->insert([
+            'member_id' => '100001', 'password' => create_password('old-login'),
+            'payment_password' => password_hash('old-funds', PASSWORD_DEFAULT),
+        ]);
+        foreach (['100001' => 'target-token', '100002' => 'other-token'] as $member => $token) {
+            DB::table('user_session')->insert(['user_id' => (string) $member, 'token' => $token, 'expire_time' => time() + 3600]);
+            Cache::put('token_user_' . $token, ['member_id' => (string) $member]);
+        }
+    }
+
+    protected function tearDown(): void
+    {
+        DB::disconnect();
+        Facade::clearResolvedInstances();
+        Model::clearBootedModels();
+        parent::tearDown();
+    }
+
+    private function reset(array $params): array
+    {
+        $this->app->instance('request', Request::create('/admin/user/setPassword', 'POST', $params + ['member_id' => '100001']));
+        return (new UserController())->setPassword()->getData(true);
+    }
+
+    private function credentials(): array
+    {
+        return (array) DB::table('users')->where('member_id', '100001')->first();
+    }
+
+    public function test_invalid_passwords_are_rejected_before_either_credential_or_session_is_changed(): void
+    {
+        $before = $this->credentials();
+        foreach (['password', 'payment_password'] as $field) {
+            foreach (['12345', str_repeat('a', 17), str_repeat('密', 17), 123456, array_fill(0, 6, 'a'), false] as $invalid) {
+                $params = ['password' => 'valid-login', 'payment_password' => 'valid-funds'];
+                $params[$field] = $invalid;
+                $body = $this->reset($params);
+                $this->assertSame(-3, $body['code']);
+                $this->assertSame($before, $this->credentials());
+                $this->assertSame(2, DB::table('user_session')->count());
+                $this->assertTrue(Cache::has('token_user_target-token'));
+            }
+        }
+    }
+
+    public static function validPasswords(): array
+    {
+        return [
+            'minimum' => ['abc123'],
+            'maximum' => [str_repeat('a', 16)],
+            'leading zeroes' => ['000000'],
+            'symbols' => ['abc!@#123'],
+            'unicode character limit' => [str_repeat('密', 16)],
+        ];
+    }
+
+    #[DataProvider('validPasswords')]
+    public function test_valid_boundaries_preserve_hashing_and_login_session_invalidation(string $password): void
+    {
+        $this->assertSame(0, $this->reset(['password' => $password, 'payment_password' => $password])['code']);
+        $row = $this->credentials();
+        $this->assertSame(create_password($password), $row['password']);
+        $this->assertTrue(password_verify($password, $row['payment_password']));
+        $this->assertSame(0, DB::table('user_session')->where('user_id', '100001')->count());
+        $this->assertFalse(Cache::has('token_user_target-token'));
+        $this->assertSame(1, DB::table('user_session')->where('user_id', '100002')->count());
+        $this->assertTrue(Cache::has('token_user_other-token'));
+    }
+
+    public function test_resetting_only_funds_password_preserves_login_password_and_session(): void
+    {
+        $this->assertSame(0, $this->reset(['payment_password' => 'new-funds'])['code']);
+        $row = $this->credentials();
+        $this->assertSame(create_password('old-login'), $row['password']);
+        $this->assertTrue(password_verify('new-funds', $row['payment_password']));
+        $this->assertSame(2, DB::table('user_session')->count());
+        $this->assertTrue(Cache::has('token_user_target-token'));
+    }
+
+    public function test_omitted_or_empty_passwords_remain_unchanged(): void
+    {
+        $before = $this->credentials();
+        foreach ([[], ['password' => null, 'payment_password' => null], ['password' => '', 'payment_password' => '']] as $params) {
+            $this->assertSame(0, $this->reset($params)['code']);
+            $this->assertSame($before, $this->credentials());
+            $this->assertSame(2, DB::table('user_session')->count());
+            $this->assertTrue(Cache::has('token_user_target-token'));
+        }
+    }
+}