From 4d2e73552e8ea0ad2aef1f2019de129efcaf2fec Mon Sep 17 00:00:00 2001 From: weizong song Date: Fri, 5 Jun 2026 22:41:49 +0800 Subject: [PATCH] up --- .env.example | 26 +-- .snapshots/config.json | 151 ++++++++++++++++++ .snapshots/readme.md | 11 ++ .snapshots/sponsors.md | 44 +++++ .../Controllers/Api/AuthSmsController.php | 11 +- .../Controllers/ChannelEntryController.php | 8 - app/Http/Middleware/TrustProxies.php | 2 +- app/Providers/AppServiceProvider.php | 5 +- config/channel_entry.php | 3 - config/sms.php | 34 ++-- .../Feature/ChannelSignupIntegrationTest.php | 17 +- 11 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 .snapshots/config.json create mode 100644 .snapshots/readme.md create mode 100644 .snapshots/sponsors.md diff --git a/.env.example b/.env.example index a485075..61287d3 100644 --- a/.env.example +++ b/.env.example @@ -28,24 +28,30 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 -SMS_GATEWAY=http://www.btom.cn:8080/simpleinter/sendSMS -SMS_APPID= -SMS_SECRETKEY= -SMS_SIGN=【创青春新消费】 +# SMS / Tencent Cloud +SMS_DRIVER=tencentcloud +SMS_ENABLED=false SMS_HTTP_TIMEOUT=5 -# 为 true 时不调短信网关,仅写缓存(联调用) -SMS_MOCK=false -# 为 true 时不请求短信网关,接口直接返回 debug_code(默认 local 环境为 true,见 config/sms.php) -# SMS_SKIP_GATEWAY=true -# 同一手机号再次发送验证码最短间隔(秒) SMS_RESEND_INTERVAL_SECONDS=60 +SMS_CODE_TTL_SECONDS=300 + +TENCENT_SMS_ENDPOINT=sms.tencentcloudapi.com +TENCENT_SMS_REGION=ap-guangzhou +TENCENT_SMS_SDK_APP_ID= +TENCENT_SMS_APP_KEY= +TENCENT_SMS_SIGN_NAME=元禾控股 + +# 当前模板变量数量与正式模板保持一致,后续正式切换只需要替换 TENCENT_SMS_TEMPLATE_ID。 +TENCENT_SMS_TEMPLATE_ID=2175650 +TENCENT_SMS_TEMPLATE_NAME=邮箱登陆验证码 +TENCENT_SMS_TEMPLATE_CONTENT=您正在尝试登录元禾控股邮件系统,验证码{1},有效期{2}分钟,如非本人操作,请尽快反馈给元禾沙湖信息技术部。 +TENCENT_SMS_TEMPLATE_PARAM_COUNT=2 # 报名表未在 JSON 中写 company_name.required_when 时:企业名称在何「参赛组别」取值下必填(须与下拉 value 一致,自定义 value 如 entry_group2 时填 entry_group2) # ENTRY_GROUP_COMPANY_REQUIRED_VALUE=entry_group2 # 渠道入口安全配置 CHANNEL_ENTRY_TIMESTAMP_WINDOW_SECONDS=300 -CHANNEL_ENTRY_REQUIRE_HTTPS=true MAIL_MAILER=smtp MAIL_HOST=mailpit diff --git a/.snapshots/config.json b/.snapshots/config.json new file mode 100644 index 0000000..dfadca2 --- /dev/null +++ b/.snapshots/config.json @@ -0,0 +1,151 @@ +{ + "excluded_patterns": [ + ".git", + ".gitignore", + "gradle", + "gradlew", + "gradlew.*", + "node_modules", + ".snapshots", + ".idea", + ".vscode", + "*.log", + "*.tmp", + "target", + "dist", + "build", + ".DS_Store", + "*.bak", + "*.swp", + "*.swo", + "*.lock", + "*.iml", + "coverage", + "*.min.js", + "*.min.css", + "__pycache__", + ".marketing", + ".env", + ".env.*", + "*.jpg", + "*.jpeg", + "*.png", + "*.gif", + "*.bmp", + "*.tiff", + "*.ico", + "*.svg", + "*.webp", + "*.psd", + "*.ai", + "*.eps", + "*.indd", + "*.raw", + "*.cr2", + "*.nef", + "*.mp4", + "*.mov", + "*.avi", + "*.wmv", + "*.flv", + "*.mkv", + "*.webm", + "*.m4v", + "*.wfp", + "*.prproj", + "*.aep", + "*.psb", + "*.xcf", + "*.sketch", + "*.fig", + "*.xd", + "*.db", + "*.sqlite", + "*.sqlite3", + "*.mdb", + "*.accdb", + "*.frm", + "*.myd", + "*.myi", + "*.ibd", + "*.dbf", + "*.rdb", + "*.aof", + "*.pdb", + "*.sdb", + "*.s3db", + "*.ddb", + "*.db-shm", + "*.db-wal", + "*.sqlitedb", + "*.sql.gz", + "*.bak.sql", + "dump.sql", + "dump.rdb", + "*.vsix", + "*.jar", + "*.war", + "*.ear", + "*.zip", + "*.tar", + "*.tar.gz", + "*.tgz", + "*.rar", + "*.7z", + "*.exe", + "*.dll", + "*.so", + "*.dylib", + "*.app", + "*.dmg", + "*.iso", + "*.msi", + "*.deb", + "*.rpm", + "*.apk", + "*.aab", + "*.ipa", + "*.pkg", + "*.nupkg", + "*.snap", + "*.whl", + "*.gem", + "*.pyc", + "*.pyo", + "*.pyd", + "*.class", + "*.o", + "*.obj", + "*.lib", + "*.a", + "*.map", + ".npmrc" + ], + "default": { + "default_prompt": "Enter your prompt here", + "default_include_all_files": false, + "default_include_entire_project_structure": true + }, + "included_patterns": [ + "build.gradle", + "settings.gradle", + "gradle.properties", + "pom.xml", + "Makefile", + "CMakeLists.txt", + "package.json", + "requirements.txt", + "Pipfile", + "Gemfile", + "composer.json", + ".editorconfig", + ".eslintrc.json", + ".eslintrc.js", + ".prettierrc", + ".babelrc", + ".dockerignore", + ".gitattributes", + ".stylelintrc", + ".npmrc" + ] +} \ No newline at end of file diff --git a/.snapshots/readme.md b/.snapshots/readme.md new file mode 100644 index 0000000..21fa917 --- /dev/null +++ b/.snapshots/readme.md @@ -0,0 +1,11 @@ +# Snapshots Directory + +This directory contains snapshots of your code for AI interactions. Each snapshot is a markdown file that includes relevant code context and project structure information. + +## What's included in snapshots? +- Selected code files and their contents +- Project structure (if enabled) +- Your prompt/question for the AI + +## Configuration +You can customize snapshot behavior in `config.json`. diff --git a/.snapshots/sponsors.md b/.snapshots/sponsors.md new file mode 100644 index 0000000..2df337f --- /dev/null +++ b/.snapshots/sponsors.md @@ -0,0 +1,44 @@ +# Thank you for using Snapshots for AI + +Thanks for using Snapshots for AI. We hope this tool has helped you solve a problem or two. + +If you would like to support our work, please help us by considering the following offers and requests: + +## Ways to Support + +### Join the GBTI Network!!! 🙏🙏🙏 +The GBTI Network is a community of developers who are passionate about open source and community-driven development. Members enjoy access to exclussive tools, resources, a private MineCraft server, a listing in our members directory, co-op opportunities and more. + +- Support our work by becoming a [GBTI Network member](https://gbti.network/membership/). + +### Try out BugHerd 🐛 +BugHerd is a visual feedback and bug-tracking tool designed to streamline website development by enabling users to pin feedback directly onto web pages. This approach facilitates clear communication among clients, designers, developers, and project managers. + +- Start your free trial with [BugHerd](https://partners.bugherd.com/55z6c8az8rvr) today. + +### Hire Developers from Codeable 👥 +Codeable connects you with top-tier professionals skilled in frameworks and technologies such as Laravel, React, Django, Node, Vue.js, Angular, Ruby on Rails, and Node.js. Don't let the WordPress focus discourage you. Codeable experts do it all. + +- Visit [Codeable](https://www.codeable.io/developers/?ref=z8h3e) to hire your next team member. + +### Lead positive reviews on our marketplace listing ⭐⭐⭐⭐⭐ +- Rate us on [VSCode marketplace](https://marketplace.visualstudio.com/items?itemName=GBTI.snapshots-for-ai) +- Review us on [Cursor marketplace](https://open-vsx.org/extension/GBTI/snapshots-for-ai) + +### Star Our GitHub Repository ⭐ +- Star and watch our [repository](https://github.com/gbti-network/vscode-snapshots-for-ai) + +### 📡 Stay Connected +Follow us on your favorite platforms for updates, news, and community discussions: +- **[Twitter/X](https://twitter.com/gbti_network)** +- **[GitHub](https://github.com/gbti-network)** +- **[YouTube](https://www.youtube.com/channel/UCh4FjB6r4oWQW-QFiwqv-UA)** +- **[Dev.to](https://dev.to/gbti)** +- **[Daily.dev](https://dly.to/zfCriM6JfRF)** +- **[Hashnode](https://gbti.hashnode.dev/)** +- **[Discord Community](https://gbti.network)** +- **[Reddit Community](https://www.reddit.com/r/GBTI_network)** + +--- + +Thank you for supporting open source software! 🙏 diff --git a/app/Http/Controllers/Api/AuthSmsController.php b/app/Http/Controllers/Api/AuthSmsController.php index 0591fd1..cdc32e2 100644 --- a/app/Http/Controllers/Api/AuthSmsController.php +++ b/app/Http/Controllers/Api/AuthSmsController.php @@ -63,20 +63,17 @@ class AuthSmsController extends Controller $smsSign = (string) config('sms.sign'); $content = "{$smsSign}您的验证码是:{$code},验证码五分钟内有效,如非本人操作,请忽略。"; - $skipGateway = config('sms.skip_gateway') - || (config('app.debug') && empty(config('sms.app_id')) && empty(config('sms.secret'))); + $smsEnabled = (bool) config('sms.enabled'); Log::info('sms.send branch', [ 'mobile_mask' => $this->maskMobile($mobile), - 'sms_skip_gateway' => (bool) config('sms.skip_gateway'), - 'app_debug' => (bool) config('app.debug'), - 'has_sms_credentials' => config('sms.app_id') !== '' && config('sms.secret') !== '', + 'sms_enabled' => (bool) config('sms.enabled'), ]); - if ($skipGateway) { + if (! $smsEnabled) { Cache::put($key, ['code' => $code, 'time' => time()], 300); - Log::info('sms.send skip_gateway cache_set', [ + Log::info('sms.send disabled cache_set', [ 'mobile_mask' => $this->maskMobile($mobile), 'ttl_sec' => 300, ]); diff --git a/app/Http/Controllers/ChannelEntryController.php b/app/Http/Controllers/ChannelEntryController.php index 82b309c..9f9d79c 100644 --- a/app/Http/Controllers/ChannelEntryController.php +++ b/app/Http/Controllers/ChannelEntryController.php @@ -23,7 +23,6 @@ class ChannelEntryController extends Controller $context = $this->logContext($request, $parameters); try { - $this->assertHttps($request); $this->assertRequiredParameters($parameters); $channel = $this->resolveChannel($parameters['channel_code']); $context['channel_code'] = $channel->channel_code; @@ -112,13 +111,6 @@ class ChannelEntryController extends Controller } } - private function assertHttps(Request $request): void - { - if (config('channel_entry.require_https', true) && ! $request->isSecure()) { - throw new ChannelEntryException('HTTPS_REQUIRED', '请使用安全链接重新进入报名系统。'); - } - } - /** * @return array{channel_code: string, timestamp: string, name: string, mobile: string, company_name: string, state: string, hash: string} */ diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 3391630..559dd2f 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -12,7 +12,7 @@ class TrustProxies extends Middleware * * @var array|string|null */ - protected $proxies; + protected $proxies = '*'; /** * The headers that should be used to detect proxies. diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..6a56508 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -19,6 +20,8 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - // + if (app()->environment('production') || str_starts_with((string) config('app.url'), 'https://')) { + URL::forceScheme('https'); + } } } diff --git a/config/channel_entry.php b/config/channel_entry.php index 8afe00d..a768b11 100644 --- a/config/channel_entry.php +++ b/config/channel_entry.php @@ -3,7 +3,4 @@ return [ /** 渠道入口链接允许的时间偏差,单位:秒。 */ 'timestamp_window_seconds' => (int) env('CHANNEL_ENTRY_TIMESTAMP_WINDOW_SECONDS', 300), - - /** 渠道入口必须通过 HTTPS 访问。 */ - 'require_https' => (bool) env('CHANNEL_ENTRY_REQUIRE_HTTPS', true), ]; diff --git a/config/sms.php b/config/sms.php index 39adfd3..4f1ddf4 100644 --- a/config/sms.php +++ b/config/sms.php @@ -4,32 +4,40 @@ return [ /* |-------------------------------------------------------------------------- - | Btom 短信(与 wx.sstbc.com ymSms 一致) + | Tencent Cloud SMS |-------------------------------------------------------------------------- */ - 'gateway' => env('SMS_GATEWAY', 'http://www.btom.cn:8080/simpleinter/sendSMS'), - - 'app_id' => env('SMS_APPID'), - - 'secret' => env('SMS_SECRETKEY'), - - /** 短信正文前缀,如:【苏州科技商学院】 */ - 'sign' => env('SMS_SIGN', '【创青春新消费】'), + 'driver' => env('SMS_DRIVER', 'tencentcloud'), /** HTTP 超时(秒) */ 'timeout' => (int) env('SMS_HTTP_TIMEOUT', 5), /** - * 为 true 时不调用短信网关,仅写入缓存(本地调试用)。 - * 默认:APP_ENV=local 时为 true;生产必须保持 false 或未配置。 + * 是否真实调用短信供应商。 + * 默认:local 环境为 false;非 local 环境为 true。 */ - 'skip_gateway' => filter_var( - env('SMS_SKIP_GATEWAY', env('APP_ENV') === 'local'), + 'enabled' => filter_var( + env('SMS_ENABLED', env('APP_ENV') !== 'local'), FILTER_VALIDATE_BOOLEAN ), /** 同一手机号两次发送最短间隔(秒),防刷 */ 'resend_interval_seconds' => max(1, (int) env('SMS_RESEND_INTERVAL_SECONDS', 60)), + /** 验证码有效期(秒) */ + 'code_ttl_seconds' => max(60, (int) env('SMS_CODE_TTL_SECONDS', 300)), + + 'tencentcloud' => [ + 'endpoint' => env('TENCENT_SMS_ENDPOINT', 'sms.tencentcloudapi.com'), + 'region' => env('TENCENT_SMS_REGION', 'ap-guangzhou'), + 'sdk_app_id' => env('TENCENT_SMS_SDK_APP_ID'), + 'app_key' => env('TENCENT_SMS_APP_KEY'), + 'sign_name' => env('TENCENT_SMS_SIGN_NAME', '元禾控股'), + 'template_id' => env('TENCENT_SMS_TEMPLATE_ID'), + 'template_name' => env('TENCENT_SMS_TEMPLATE_NAME', '邮箱登陆验证码'), + 'template_content' => env('TENCENT_SMS_TEMPLATE_CONTENT', '您正在尝试登录元禾控股邮件系统,验证码{1},有效期{2}分钟,如非本人操作,请尽快反馈给元禾沙湖信息技术部。'), + 'template_param_count' => (int) env('TENCENT_SMS_TEMPLATE_PARAM_COUNT', 2), + ], + ]; diff --git a/tests/Feature/ChannelSignupIntegrationTest.php b/tests/Feature/ChannelSignupIntegrationTest.php index 9b489dd..06d36ba 100644 --- a/tests/Feature/ChannelSignupIntegrationTest.php +++ b/tests/Feature/ChannelSignupIntegrationTest.php @@ -40,7 +40,7 @@ class ChannelSignupIntegrationTest extends TestCase ], 'logging.default' => 'single', 'logging.channels.single.path' => $this->logPath, - 'channel_entry.require_https' => false, + 'app.url' => 'https://localhost', ]); DB::purge(); @@ -140,15 +140,18 @@ class ChannelSignupIntegrationTest extends TestCase $channelA = $this->channel($competition, 'CHANNEL-A', ['shared_secret' => 'secret-A']); $channelB = $this->channel($competition, 'CHANNEL-B', ['shared_secret' => 'secret-B']); - config(['channel_entry.require_https' => true]); - $this->assertChannelEntryError($this->signed($channelA), 'HTTPS_REQUIRED'); - config(['channel_entry.require_https' => false]); - - $response = $this->get('/channel-entry?'.http_build_query($this->signed($channelA))); + $response = $this + ->withHeader('X-Forwarded-Proto', 'https') + ->withHeader('X-Forwarded-Port', '443') + ->get('/channel-entry?'.http_build_query($this->signed($channelA))); $response->assertOk() ->assertHeader('Referrer-Policy', 'no-referrer') ->assertSee("localStorage.setItem('cxxfds_token'", false) - ->assertSee('/admin/c/main-event/apply', false); + ->assertSee('/admin/c/main-event/apply', false) + ->assertSee('href="https://', false) + ->assertDontSee('href="http://', false) + ->assertSee("const redirectUrl = 'https:\\/\\/", false) + ->assertDontSee("const redirectUrl = 'http:\\/\\/", false); $cacheControl = (string) $response->headers->get('Cache-Control'); foreach (['private', 'no-store', 'no-cache', 'must-revalidate'] as $directive) { $this->assertStringContainsString($directive, $cacheControl);