init
This commit is contained in:
77
web10/app/Http/Controllers/ArticleController.php
Normal file
77
web10/app/Http/Controllers/ArticleController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\ContentViewLog;
|
||||
use App\Models\SiteSetting;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ArticleController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$perPage = (int) SiteSetting::value('article_page_size', 10);
|
||||
$tagSlug = $request->query('tag');
|
||||
|
||||
$query = Article::published()->orderByDesc('published_at');
|
||||
if ($tagSlug) {
|
||||
$query->whereHas('tags', function ($tagQuery) use ($tagSlug) {
|
||||
$tagQuery->where('slug', $tagSlug);
|
||||
});
|
||||
}
|
||||
|
||||
$articles = $query->paginate($perPage);
|
||||
|
||||
return view('article.index', [
|
||||
'articles' => $articles,
|
||||
'tagSlug' => $tagSlug,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $slug, Request $request)
|
||||
{
|
||||
$article = Article::with('tags')->where('slug', $slug)->firstOrFail();
|
||||
|
||||
$this->recordView($article, $request);
|
||||
|
||||
$comments = $article->comments()
|
||||
->where('status', 'approved')
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
$relatedArticles = Article::published()
|
||||
->where('id', '!=', $article->id)
|
||||
->whereHas('tags', function ($query) use ($article) {
|
||||
$query->whereIn('tags.id', $article->tags->pluck('id'));
|
||||
})
|
||||
->orderByDesc('published_at')
|
||||
->limit(6)
|
||||
->get();
|
||||
|
||||
return view('article.show', [
|
||||
'article' => $article,
|
||||
'comments' => $comments,
|
||||
'relatedArticles' => $relatedArticles,
|
||||
]);
|
||||
}
|
||||
|
||||
private function recordView(Article $article, Request $request): void
|
||||
{
|
||||
$ip = $request->ip() ?? '0.0.0.0';
|
||||
$userAgent = $request->userAgent() ?? 'unknown';
|
||||
$userAgentHash = sha1($userAgent);
|
||||
|
||||
$log = ContentViewLog::firstOrCreate([
|
||||
'content_type' => 'article',
|
||||
'content_id' => $article->id,
|
||||
'ip' => $ip,
|
||||
'user_agent_hash' => $userAgentHash,
|
||||
'viewed_on' => now()->toDateString(),
|
||||
]);
|
||||
|
||||
if ($log->wasRecentlyCreated) {
|
||||
$article->increment('view_count');
|
||||
}
|
||||
}
|
||||
}
|
||||
55
web10/app/Http/Controllers/CategoryController.php
Normal file
55
web10/app/Http/Controllers/CategoryController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Tag;
|
||||
use App\Models\SiteSetting;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$categories = Category::withCount('products')
|
||||
->whereNull('parent_id')
|
||||
->orderBy('sort')
|
||||
->get();
|
||||
|
||||
return view('category.index', [
|
||||
'categories' => $categories,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $slug)
|
||||
{
|
||||
$category = Category::where('slug', $slug)->firstOrFail();
|
||||
$perPage = (int) SiteSetting::value('list_page_size', 20);
|
||||
$moreThreshold = (int) SiteSetting::value('list_more_threshold', 20);
|
||||
$tagSlug = request()->query('tag');
|
||||
$pricing = request()->query('pricing');
|
||||
$tags = Tag::orderBy('name')->get();
|
||||
|
||||
$products = $category->products()
|
||||
->published()
|
||||
->when($tagSlug, function ($builder) use ($tagSlug) {
|
||||
$builder->whereHas('tags', function ($query) use ($tagSlug) {
|
||||
$query->where('slug', $tagSlug);
|
||||
});
|
||||
})
|
||||
->when($pricing, function ($builder) use ($pricing) {
|
||||
$builder->where('pricing_type', $pricing);
|
||||
})
|
||||
->orderBy('sort')
|
||||
->orderByDesc('hot_score')
|
||||
->paginate($perPage);
|
||||
|
||||
return view('category.show', [
|
||||
'category' => $category,
|
||||
'products' => $products,
|
||||
'moreThreshold' => $moreThreshold,
|
||||
'tags' => $tags,
|
||||
'tagSlug' => $tagSlug,
|
||||
'pricing' => $pricing,
|
||||
]);
|
||||
}
|
||||
}
|
||||
100
web10/app/Http/Controllers/CommentController.php
Normal file
100
web10/app/Http/Controllers/CommentController.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Product;
|
||||
use App\Models\SensitiveWord;
|
||||
use App\Models\SiteSetting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
public function captcha()
|
||||
{
|
||||
$code = (string) random_int(1000, 9999);
|
||||
session(['captcha_code' => $code]);
|
||||
|
||||
$svg = <<<SVG
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="44">
|
||||
<rect width="100%" height="100%" fill="#f3f4f6"/>
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"
|
||||
font-family="Arial, sans-serif" font-size="20" fill="#111827">{$code}</text>
|
||||
</svg>
|
||||
SVG;
|
||||
|
||||
return response($svg, 200)->header('Content-Type', 'image/svg+xml');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!SiteSetting::value('comments_enabled', '1')) {
|
||||
return back()->withErrors(['comments' => '评论功能已关闭。']);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'target_type' => ['required', 'in:product,article'],
|
||||
'target_id' => ['required', 'integer'],
|
||||
'nickname' => ['required', 'string', 'max:50'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'content' => ['required', 'string', 'max:1000'],
|
||||
'captcha' => ['required', 'string', 'max:10'],
|
||||
]);
|
||||
|
||||
$captcha = (string) session('captcha_code');
|
||||
session()->forget('captcha_code');
|
||||
|
||||
if ($captcha === '' || $data['captcha'] !== $captcha) {
|
||||
return back()->withErrors(['captcha' => '验证码错误,请重试。']);
|
||||
}
|
||||
|
||||
$ip = $request->ip() ?? '0.0.0.0';
|
||||
$rateKey = "comment_rate_{$ip}";
|
||||
if (Cache::has($rateKey)) {
|
||||
return back()->withErrors(['comments' => '提交太频繁,请稍后再试。']);
|
||||
}
|
||||
Cache::put($rateKey, true, now()->addMinutes(10));
|
||||
|
||||
$targetExists = $data['target_type'] === 'product'
|
||||
? Product::where('id', $data['target_id'])->exists()
|
||||
: Article::where('id', $data['target_id'])->exists();
|
||||
|
||||
if (!$targetExists) {
|
||||
return back()->withErrors(['comments' => '评论目标不存在。']);
|
||||
}
|
||||
|
||||
$content = $this->filterSensitiveWords($data['content']);
|
||||
|
||||
Comment::create([
|
||||
'target_type' => $data['target_type'],
|
||||
'target_id' => $data['target_id'],
|
||||
'nickname' => $data['nickname'],
|
||||
'email' => $data['email'] ?? null,
|
||||
'content' => $content,
|
||||
'status' => 'pending',
|
||||
'ip' => $ip,
|
||||
'user_agent' => $request->userAgent(),
|
||||
]);
|
||||
|
||||
return back()->with('success', '评论已提交,审核通过后展示。');
|
||||
}
|
||||
|
||||
public function like(Comment $comment)
|
||||
{
|
||||
$comment->increment('like_count');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
private function filterSensitiveWords(string $content): string
|
||||
{
|
||||
$words = SensitiveWord::pluck('word')->filter()->all();
|
||||
foreach ($words as $word) {
|
||||
$content = str_replace($word, str_repeat('*', mb_strlen($word)), $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
27
web10/app/Http/Controllers/ContactController.php
Normal file
27
web10/app/Http/Controllers/ContactController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContactMessage;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function show()
|
||||
{
|
||||
return view('page.contact');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'content' => ['required', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
ContactMessage::create($data);
|
||||
|
||||
return back()->with('success', '提交成功,我们会尽快联系你。');
|
||||
}
|
||||
}
|
||||
12
web10/app/Http/Controllers/Controller.php
Normal file
12
web10/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
||||
49
web10/app/Http/Controllers/HomeController.php
Normal file
49
web10/app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use App\Models\SiteSetting;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$featuredLimit = (int) SiteSetting::value('home_featured_limit', 8);
|
||||
$newLimit = (int) SiteSetting::value('home_new_limit', 8);
|
||||
$categoryLimit = (int) SiteSetting::value('home_category_limit', 20);
|
||||
|
||||
$featuredProducts = Product::published()
|
||||
->featured()
|
||||
->orderBy('sort')
|
||||
->orderByDesc('hot_score')
|
||||
->limit($featuredLimit)
|
||||
->get();
|
||||
|
||||
$newProducts = Product::published()
|
||||
->orderByDesc('sort')
|
||||
->orderByDesc('created_at')
|
||||
->limit($newLimit)
|
||||
->get();
|
||||
|
||||
$categories = Category::with([
|
||||
'children',
|
||||
'products' => function ($query) use ($categoryLimit) {
|
||||
$query->published()
|
||||
->orderBy('sort')
|
||||
->orderByDesc('hot_score')
|
||||
->limit($categoryLimit);
|
||||
},
|
||||
])
|
||||
->whereNull('parent_id')
|
||||
->orderBy('sort')
|
||||
->get();
|
||||
|
||||
return view('home', [
|
||||
'featuredProducts' => $featuredProducts,
|
||||
'newProducts' => $newProducts,
|
||||
'categories' => $categories,
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
web10/app/Http/Controllers/OutboundController.php
Normal file
17
web10/app/Http/Controllers/OutboundController.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
|
||||
class OutboundController extends Controller
|
||||
{
|
||||
public function redirect(string $slug)
|
||||
{
|
||||
$product = Product::where('slug', $slug)->firstOrFail();
|
||||
$product->increment('click_count');
|
||||
$product->refreshHotScore();
|
||||
|
||||
return redirect()->away($product->website_url);
|
||||
}
|
||||
}
|
||||
17
web10/app/Http/Controllers/PageController.php
Normal file
17
web10/app/Http/Controllers/PageController.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SiteSetting;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
public function about()
|
||||
{
|
||||
$content = SiteSetting::value('about_content', "我们专注于收录优质 AI 工具与产品,帮助用户快速找到合适的解决方案。");
|
||||
|
||||
return view('page.about', [
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
}
|
||||
57
web10/app/Http/Controllers/ProductController.php
Normal file
57
web10/app/Http/Controllers/ProductController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ContentViewLog;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function show(string $slug, Request $request)
|
||||
{
|
||||
$product = Product::with('tags', 'category')->where('slug', $slug)->firstOrFail();
|
||||
|
||||
$this->recordView($product, $request);
|
||||
|
||||
$comments = $product->comments()
|
||||
->where('status', 'approved')
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
$relatedProducts = Product::published()
|
||||
->where('id', '!=', $product->id)
|
||||
->whereHas('tags', function ($query) use ($product) {
|
||||
$query->whereIn('tags.id', $product->tags->pluck('id'));
|
||||
})
|
||||
->orderByDesc('hot_score')
|
||||
->limit(8)
|
||||
->get();
|
||||
|
||||
return view('product.show', [
|
||||
'product' => $product,
|
||||
'comments' => $comments,
|
||||
'relatedProducts' => $relatedProducts,
|
||||
]);
|
||||
}
|
||||
|
||||
private function recordView(Product $product, Request $request): void
|
||||
{
|
||||
$ip = $request->ip() ?? '0.0.0.0';
|
||||
$userAgent = $request->userAgent() ?? 'unknown';
|
||||
$userAgentHash = sha1($userAgent);
|
||||
|
||||
$log = ContentViewLog::firstOrCreate([
|
||||
'content_type' => 'product',
|
||||
'content_id' => $product->id,
|
||||
'ip' => $ip,
|
||||
'user_agent_hash' => $userAgentHash,
|
||||
'viewed_on' => now()->toDateString(),
|
||||
]);
|
||||
|
||||
if ($log->wasRecentlyCreated) {
|
||||
$product->increment('view_count');
|
||||
$product->refreshHotScore();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
web10/app/Http/Controllers/SearchController.php
Normal file
29
web10/app/Http/Controllers/SearchController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\SiteSetting;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = trim((string) $request->query('q', ''));
|
||||
$perPage = (int) SiteSetting::value('list_page_size', 20);
|
||||
|
||||
$products = Product::published()
|
||||
->when($query !== '', function ($builder) use ($query) {
|
||||
$builder->where('name', 'like', "%{$query}%");
|
||||
})
|
||||
->orderByDesc('hot_score')
|
||||
->paginate($perPage)
|
||||
->appends(['q' => $query]);
|
||||
|
||||
return view('search.index', [
|
||||
'query' => $query,
|
||||
'products' => $products,
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
web10/app/Http/Controllers/SitemapController.php
Normal file
50
web10/app/Http/Controllers/SitemapController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use App\Models\Tag;
|
||||
|
||||
class SitemapController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$baseUrl = rtrim(config('app.url'), '/');
|
||||
|
||||
$urls = [
|
||||
'/',
|
||||
'/categories',
|
||||
'/tags',
|
||||
'/articles',
|
||||
'/about',
|
||||
'/contact',
|
||||
];
|
||||
|
||||
$categories = Category::all()->map(fn ($c) => "/category/{$c->slug}")->all();
|
||||
$tags = Tag::all()->map(fn ($t) => "/tag/{$t->slug}")->all();
|
||||
$products = Product::published()->get()->map(fn ($p) => "/product/{$p->slug}")->all();
|
||||
$articles = Article::published()->get()->map(fn ($a) => "/article/{$a->slug}")->all();
|
||||
|
||||
$allUrls = array_merge($urls, $categories, $tags, $products, $articles);
|
||||
|
||||
$xml = new \XMLWriter();
|
||||
$xml->openMemory();
|
||||
$xml->startDocument('1.0', 'UTF-8');
|
||||
$xml->startElement('urlset');
|
||||
$xml->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
|
||||
|
||||
foreach ($allUrls as $path) {
|
||||
$xml->startElement('url');
|
||||
$xml->writeElement('loc', $baseUrl . $path);
|
||||
$xml->endElement();
|
||||
}
|
||||
|
||||
$xml->endElement();
|
||||
$xml->endDocument();
|
||||
|
||||
return response($xml->outputMemory(), 200)
|
||||
->header('Content-Type', 'application/xml');
|
||||
}
|
||||
}
|
||||
49
web10/app/Http/Controllers/TagController.php
Normal file
49
web10/app/Http/Controllers/TagController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SiteSetting;
|
||||
use App\Models\Tag;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$query = request()->query('q');
|
||||
$tags = Tag::when($query, function ($builder) use ($query) {
|
||||
$builder->where('name', 'like', "%{$query}%");
|
||||
})
|
||||
->orderByDesc('hot_score')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('tag.index', [
|
||||
'tags' => $tags,
|
||||
'query' => $query,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(string $slug)
|
||||
{
|
||||
$tag = Tag::where('slug', $slug)->firstOrFail();
|
||||
$perPage = (int) SiteSetting::value('list_page_size', 20);
|
||||
$moreThreshold = (int) SiteSetting::value('list_more_threshold', 20);
|
||||
$pricing = request()->query('pricing');
|
||||
|
||||
$products = $tag->products()
|
||||
->published()
|
||||
->when($pricing, function ($builder) use ($pricing) {
|
||||
$builder->where('pricing_type', $pricing);
|
||||
})
|
||||
->orderBy('product_tag.sort')
|
||||
->orderByDesc('hot_score')
|
||||
->paginate($perPage);
|
||||
|
||||
return view('tag.show', [
|
||||
'tag' => $tag,
|
||||
'products' => $products,
|
||||
'moreThreshold' => $moreThreshold,
|
||||
'pricing' => $pricing,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user