Compare commits

...

40 Commits

Author SHA1 Message Date
lq
edefaa4379 功能视频测试
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-28 16:51:40 +08:00
lq
2b767fb17b 项目相关功能完善
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-08 14:10:53 +08:00
lq
0d51623bbf 项目相关功能完善 2025-05-08 14:10:45 +08:00
lq
8ea4f2bafd 项目相关优化 2025-05-08 13:28:56 +08:00
lq
e68c2dc663 项目列表
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-07 23:40:40 +08:00
lq
6b4d1e5be3 项目列表 2025-05-07 23:40:31 +08:00
lq
60faecae99 项目列表 2025-05-07 22:44:15 +08:00
lq
61acb0de5a OSS封装 2025-05-07 22:28:56 +08:00
lq
dabef7a5a0 OSS封装 2025-05-07 22:24:01 +08:00
lq
8a99bcb904 部分前台接口 2025-05-07 16:52:49 +08:00
lq
7a08ac219e sql文件 2025-05-07 14:54:54 +08:00
lq
d3bb8380e2 前台部分接口 2025-05-07 14:48:34 +08:00
lq
6e963b23b0 项目相关接口、图片视频上传
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-06 17:00:02 +08:00
lq
cd304275b8 项目相关接口、图片视频上传 2025-05-06 16:18:22 +08:00
lq
cbd0b19b91 OSS封装
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-05 23:48:59 +08:00
lq
36658dc184 注册接口 2025-05-05 23:48:21 +08:00
lq
c61357eca1 注册接口 2025-05-05 20:23:01 +08:00
lq
91a1d06b53 角色相关(列表和下拉框)、用户查询、项目部分API 2025-05-05 17:00:08 +08:00
lq
2609d009ed 角色相关(列表和下拉框)、用户查询、项目部分API 2025-05-05 16:44:23 +08:00
lq
7c8b090940 用户相关api、jwt接入 2025-05-05 14:20:37 +08:00
lq
bd26d52fff 用户相关api、jwt接入 2025-05-05 14:20:17 +08:00
lq
147b2d3fd5 登录注册功能 2025-05-05 09:44:50 +08:00
lq
db54446f8b 路由封装 2025-05-05 08:15:50 +08:00
lq
0bfe7786d3 基类和工具类
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-05-04 22:42:51 +08:00
lq
ba260c2bf4 模型关联数据修正
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-04-28 17:00:09 +08:00
lq
44c1e18745 json返回格式设置 2025-04-28 16:45:47 +08:00
lq
994ecb2488 模型关系设置 2025-04-28 16:45:23 +08:00
lq
87ac792634 模型创建 2025-04-28 16:18:47 +08:00
lq
dfb7acab05 模型创建 2025-04-28 16:15:09 +08:00
lq
02312397b3 删除vite
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
2025-04-25 16:54:50 +08:00
lq
caf3f87482 初始化项目 2025-04-25 16:31:53 +08:00
Ahmed Alaa
f6e4638ee6 Clean up URL formatting in README (#6601) 2025-04-16 06:37:18 -05:00
taylorotwell
126a6638bd Update CHANGELOG 2025-04-15 15:46:02 +00:00
Josh Cirre
e654bf3662 Premium partner changes (#6599) 2025-04-15 10:24:16 -05:00
Mior Muhammad Zaki
43ad1946fc Add composer run test command (#6598)
This prevents the application from running tests with cached config.
2025-04-11 10:21:14 -05:00
taylorotwell
fc42b7132a Update CHANGELOG 2025-04-08 18:50:46 +00:00
Taylor Otwell
7a7015534e use in memory database by default 2025-04-08 13:50:08 -05:00
taylorotwell
14cec1e9a3 Update CHANGELOG 2025-04-08 18:47:30 +00:00
Ahmed Alaa
6087e4fd3b [12.x] Update config/mail.php to match the latest core configuration (#6594)
* Update config/mail.php to match the latest core configuration

* Update mail.php

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
2025-04-02 12:34:19 -05:00
taylorotwell
f13915178d Update CHANGELOG 2025-04-01 14:59:10 +00:00
65 changed files with 13050 additions and 133 deletions

View File

@@ -1,6 +1,24 @@
# Release Notes # Release Notes
## [Unreleased](https://github.com/laravel/laravel/compare/v12.0.3...12.x) ## [Unreleased](https://github.com/laravel/laravel/compare/v12.0.7...12.x)
## [v12.0.7](https://github.com/laravel/laravel/compare/v12.0.6...v12.0.7) - 2025-04-15
* Add `composer run test` command by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/laravel/pull/6598
* Partner Directory Changes in ReadME by [@joshcirre](https://github.com/joshcirre) in https://github.com/laravel/laravel/pull/6599
## [v12.0.6](https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6) - 2025-04-08
**Full Changelog**: https://github.com/laravel/laravel/compare/v12.0.5...v12.0.6
## [v12.0.5](https://github.com/laravel/laravel/compare/v12.0.4...v12.0.5) - 2025-04-02
* [12.x] Update `config/mail.php` to match the latest core configuration by [@AhmedAlaa4611](https://github.com/AhmedAlaa4611) in https://github.com/laravel/laravel/pull/6594
## [v12.0.4](https://github.com/laravel/laravel/compare/v12.0.3...v12.0.4) - 2025-03-31
* Bump vite from 6.0.11 to 6.2.3 - Vulnerability patch by [@abdel-aouby](https://github.com/abdel-aouby) in https://github.com/laravel/laravel/pull/6586
* Bump vite from 6.2.3 to 6.2.4 by [@thinkverse](https://github.com/thinkverse) in https://github.com/laravel/laravel/pull/6590
## [v12.0.3](https://github.com/laravel/laravel/compare/v12.0.2...v12.0.3) - 2025-03-17 ## [v12.0.3](https://github.com/laravel/laravel/compare/v12.0.2...v12.0.3) - 2025-03-17

View File

@@ -1,66 +1,5 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> # SPA 应用API
## 技术栈
<p align="center"> * [laravel(12.x)](https://laravel.com/)
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a> * [mysql(8.4)](https://www.mysql.com/)
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a> * [redis(7.x)](https://redis.io/)
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@@ -0,0 +1,23 @@
<?php
namespace App\BaseApp;
class BaseClient
{
private static mixed $_instance;
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
}

170
app/BaseApp/BaseController.php Executable file
View File

@@ -0,0 +1,170 @@
<?php
namespace App\BaseApp;
use App\Service\common\UtilsService;
use Exception;
use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use function PHPUnit\Framework\isString;
class BaseController
{
protected array $insertField = [];
protected array $notRequest = [];
protected array $updateField = [];
protected $service;
public function __construct()
{
}
/**
* 获取列表
* @Method GET
* @return JsonResponse
* @throws Exception
*/
public function list(): JsonResponse
{
return jok(
$this->service->list(),
'列表获取成功'
);
}
/**
* 获取下拉列表
* @Method GET
* @return mixed
* @throws Exception
*/
public function option(): mixed
{
return jok(
$this->service->option(),
'列表获取成功'
);
}
/**
* 获取详情
*
* @Method GET
* @return JsonResponse
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception
*/
public function detail(): JsonResponse
{
$id = request()->get('id');
if (empty($id)) UtilsService::getInstance()->notFound('参数错误');
return jok(
$this->service->detail($id),
'详情获取成功'
);
}
/**
* 创建
*
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function create(): JsonResponse
{
$params = $this->checkRequiredFields(request()->post());
return jok(
$this->service->create($params),
'创建成功'
);
}
/**
* 更新
*
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function update(): JsonResponse
{
$params = $this->checkRequiredFields(request()->post(), 'update');
$id = $params['id'];
unset($params['id']);
return jok(
$this->service->update($id, $params),
'更新成功'
);
}
/**
* 删除
*
* @Method POST
* @return JsonResponse
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception
*/
public function delete(): JsonResponse
{
$id = request()->post('ids');
if (empty($id)) UtilsService::getInstance()->notFound('参数错误');
return jok(
$this->service->delete($id),
'删除成功'
);
}
/**
* 检查必填字段
*
* @Method NO
* @param array $data
* @param string $checkType
* @param bool $throw
* @return array
* @throws Exception
*/
public function checkRequiredFields(array $data = [], string $checkType = 'create', bool $throw = true): array
{
$checkFields = $checkType == 'create' ? $this->insertField : $this->updateField;
if (!empty($this->notRequest)) {
$checkFields = array_merge($this->notRequest, $checkFields);
}
$requiredFields = [];
$result = [];
foreach ($checkFields as $fieldName) {
$checkValue = $data[$fieldName] ?? '';
if (is_array($checkValue) && !empty($checkValue)) {
$result[$fieldName] = $checkValue;
} else {
if ((empty($checkValue) && $checkValue !== 0) && !in_array($fieldName, $this->notRequest)) {
$requiredFields[] = $fieldName;
} else {
if (!empty($checkValue) || $checkValue === 0) {
$result[$fieldName] = $checkValue;
}
}
}
}
if ($throw && $requiredFields) {
UtilsService::getInstance()->notFound('未填写的字段: ' . implode(',', $requiredFields));
}
if (!empty($requiredFields)) {
return $requiredFields;
}
return $result;
}
}

26
app/BaseApp/BaseModel.php Executable file
View File

@@ -0,0 +1,26 @@
<?php
namespace App\BaseApp;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected $connection = 'mysql';
public $timestamps = false;
public function getCreatedAtAttribute($value): string
{
if (empty($value)) {
return '';
}
return date('Y-m-d H:i:s', $value);
}
// 定义访问器,将 created_at 字段格式化为指定格式
public function getUpdatedAtAttribute($value): string
{
if (empty($value)) {
return '';
}
return date('Y-m-d H:i:s', $value);
}
}

322
app/BaseApp/BaseService.php Executable file
View File

@@ -0,0 +1,322 @@
<?php
namespace App\BaseApp;
use App\Models\UserModel;
use App\Service\common\JWTService;
use App\Service\common\UtilsService;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class BaseService
{
/**
* @var mixed 单例实例,确保该类只有一个全局实例
*/
protected static mixed $_instance;
/**
* @var object 工具类对象,用于提供通用的功能方法
*/
protected object $utils;
/**
* @var int 用户ID默认值为0表示未登录状态
*/
protected int $userId = 0;
/**
* @var array 用户信息
*/
protected array $userInfo = [];
/**
* @var object 数据模型对象,用于数据库操作
*/
protected $model;
/**
* @var bool 是否需要验证用户权限
*/
protected bool $isAuth = true;
/**
* @var array 查询的字段
*/
protected array $selectField = ['*'];
/**
* @var array 查询条件Between
*/
protected array $whereBetween = [];
/**
* @var array 排序条件
*/
protected array $orderBy = [
'name' => 'id',
'sort' => 'desc',
];
/**
* @var array 查询条件
*/
protected array $where = [
['deleted_at', '=', 0]
];
/**
* @var array 查询条件In
*/
protected array $whereIn = [];
/**
* @var array 下拉列表字段
*/
protected array $optionField = ['id', 'name'];
/**
* @var array 下拉列表字段
*/
protected array $queryField = [];
/**
* @var array 关联加载
*/
protected array $with = [];
/**
* @var string 时间字段
*/
protected string $timeField = 'created_at';
public function __construct()
{
if ($this->isAuth) {
$this->userInfo = JWTService::getInstance()->getToken()->getUserInfo();
$this->userId = $this->userInfo['id'] ?? 0;
} else {
try {
$this->userInfo = JWTService::getInstance()->getToken()->getUserInfo();
$this->userId = $this->userInfo['id'] ?? 0;
} catch (Exception $e) {
$this->userId = 0;
}
}
$this->utils = UtilsService::getInstance();
}
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 获取列表数据
*
* 本函数负责根据设定的条件,从数据库中查询并返回分页后的数据列表
* 它会根据空值条件动态地修改查询,以实现灵活的查询需求
*
* @return array 返回包含分页信息和数据列表的数组
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function getPageList($isArr = true, $isGetWhere = true): array
{
// 初始化查询条件
if ($isGetWhere === true) $this->getWhere();
// 获取范围查询条件
$searchTime = request()->get('search_time');
if (!empty($searchTime)) {
$this->getWhereBetween($searchTime);
}
// 执行数据库查询,根据条件动态构建查询语句
$result = $this->model::when(!empty($this->with), function ($query) {
// 如果关联加载条件存在,则添加关联加载
$query->with($this->with);
})
->when(!empty($this->where), function ($query) {
// 如果查询条件存在,则添加查询条件
$query->where($this->where);
})
->when(!empty($this->whereIn), function ($query) {
// 如果查询条件存在,则添加查询条件
$query->whereIn($this->whereIn[0], $this->whereIn[1]);
})
->when(!empty($this->whereBetween), function ($query) {
// 如果范围查询条件存在,则添加范围查询
$query->whereBetween($this->whereBetween[0], $this->whereBetween[1]);
})
->when(!empty($this->orderBy), function ($query) {
// 如果排序条件存在,则添加排序条件
$query->orderBy($this->orderBy['name'], $this->orderBy['sort']);
})
->select($this->selectField)
// 执行分页查询每页返回10条记录
->paginate(request()->get('pageSize', 20));
if ($isArr === true) {
$result = $result->toArray() ?? [];
// 返回分页信息和数据列表
return [
'page' => $result['current_page'],
'size' => $result['per_page'],
'page_count' => $result['last_page'],
'total' => $result['total'],
'items' => $result['data'],
];
}
// 返回分页信息和数据列表
return [
'page' => $result->currentPage(),
'size' => $result->perPage(),
'page_count' => $result->lastPage(),
'total' => $result->total(),
'items' => $result->items(),
];
}
/**
* 获取下拉列表数据
* @return mixed
*/
public function getOption(): mixed
{
$this->getWhere();
return $this->model::when(!empty($this->where), function ($query) {
$query->where($this->where);
})->when(!empty($this->whereIn), function ($query) {
$query->where($this->whereIn[0], $this->whereIn[1]);
})->orderBy($this->orderBy['name'], $this->orderBy['sort'])->get($this->optionField);
}
/**
* 详情
*
* @param $id
* @return mixed
* @throws Exception
*/
public function getDetail($id): mixed
{
$info = $this->model::with($this->with)->select($this->selectField)->find($id);
if (empty($info)) {
return $this->utils->notFound('数据不存在');
}
return $info;
}
/**
* 新增
* @param $params
* @return mixed
*/
public function insert($params): mixed
{
if (!array_key_exists('created_at', $params)) {
$params['created_at'] = time();
}
// return $this->model::insertGetId($this->utils->truncatedOss($params));
return $this->model::insertGetId($params);
}
/**
* 编辑
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function save($id, $params): mixed
{
if (empty(trim($id))) {
$this->utils->errorThrow('参数错误');
}
$this->where[] = ['id', '=', $id];
// 软删除
$info = $this->model::where($this->where)->first();
if (empty($info)) {
return $this->utils->notFound('数据不存在');
}
if (!array_key_exists('updated_at', $params)) {
$params['updated_at'] = time();
}
// return $this->model::where('id', $id)->update($this->utils->truncatedOss($params));
return $this->model::where('id', $id)->update($params);
}
/**
* 删除
*
* @param $id
* @return mixed|true
* @throws Exception
*/
public function del($id): mixed
{
// 软删除
$info = $this->model::whereIn('id', $id)->get(['id']);
if (empty($info)) {
return $this->utils->notFound('数据不存在');
}
if ($this->model::whereIn('id', $id)->update([
'deleted_at' => time(),
'updated_at' => time()
])) {
return true;
} else {
$this->utils->errorThrow('删除失败');
}
}
/**
* 获取查询条件
* @return void
*/
protected function getWhere(): void
{
$query = request()->query();
if (!empty($this->queryField)) {
foreach ($this->queryField as $key => $value) {
if (in_array($key, array_keys($query))) {
if (!empty($query[$key] ?? '') || $query[$key] == '0') {
switch ($value) {
case '=':
$this->where[] = [$key, $value, $query[$key]];
break;
case 'like':
$this->where[] = [$key, $value, '%' . $query[$key] . '%'];
break;
default:
break;
}
}
}
}
}
}
/**
* 获取时间范围查询条件
* @param $searchTime
* @return void
*/
protected function getWhereBetween($searchTime): void
{
$this->whereBetween = [$this->timeField, [strtotime($searchTime[0] ?? date('Y-m-01 00:00:00')), strtotime(date('Y-m-d 23:59:59', strtotime($searchTime[1])) ?? date('Y-m-d 23:59:59'))]];
}
}

55
app/Enum/ErrorEnum.php Executable file
View File

@@ -0,0 +1,55 @@
<?php
namespace App\Enum;
/**
* 状态码定义
*/
enum ErrorEnum: int
{
/**
* 成功
*/
case SUCCESS = 0;
/**
* 登录过期
*/
case NOT_AUTH = 401;
/**
* 权限不足
*/
case NOT_PERMISSION = 403;
/**
* 未找到资源的错误
*/
case NOT_FOUND = 404;
/**
* 失败状态的错误
*/
case FAIL = 500;
/**
* 验证失败的错误
*/
case NOT_VALID = 2001;
/**
* 不存在的错误
*/
case NOT_EXIST = 2002;
/**
* 不存在或已删除的错误
*/
case NOT_EXIST_OR_DELETED = 2003;
public function description(): string
{
return match ($this) {
self::SUCCESS => '请求成功',
self::FAIL => '服务器异常',
self::NOT_FOUND => '未找到资源',
self::NOT_AUTH => '未授权',
self::NOT_PERMISSION => '您没有权限',
self::NOT_VALID => '字段验证失败',
self::NOT_EXIST => '资源不存在',
self::NOT_EXIST_OR_DELETED => '资源被删除',
};
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Enum;
/**
* 状态码定义
*/
enum ProjectFieldTypeEnum: int
{
case SOLD = 1;
case SELECTED = 2;
case CAROUSEL = 3;
public function description(): string
{
return match ($this) {
self::SELECTED => 'is_selected',
self::CAROUSEL => 'is_carousel',
self::SOLD => 'is_sold',
};
}
}

25
app/Enum/ProjectStatusEnum.php Executable file
View File

@@ -0,0 +1,25 @@
<?php
namespace App\Enum;
/**
* 状态码定义
*/
enum ProjectStatusEnum: int
{
// '状态 0草稿 1上架 2下架 3重构中'
case DRAFT = 0;
case ON_SHELF = 1;
case OFF_SHELF = 2;
case REBUILD = 3;
public function description(): string
{
return match ($this) {
self::DRAFT => '草稿',
self::ON_SHELF => '上架',
self::OFF_SHELF => '下架',
self::REBUILD => '重构中',
default => '未知状态',
};
}
}

20
app/Enum/StatusEnum.php Executable file
View File

@@ -0,0 +1,20 @@
<?php
namespace App\Enum;
/**
* 状态码定义
*/
enum StatusEnum: int
{
// 状态 0正常 1禁用
case NORMAL = 0;
case DISABLE = 1;
public function description(): string
{
return match ($this) {
self::NORMAL => '正常',
self::DISABLE => '禁用',
};
}
}

23
app/Enum/UserStatusEnum.php Executable file
View File

@@ -0,0 +1,23 @@
<?php
namespace App\Enum;
/**
* 状态码定义
*/
enum UserStatusEnum: int
{
// 状态 0正常 1冻结 2封号 3注销
case NORMAL = 0;
case DISABLE = 1;
case DELETED = 2;
public function description(): string
{
return match ($this) {
self::NORMAL => '正常',
self::DISABLE => '冻结',
self::DELETED => '注销',
default => '未知',
};
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\CategoryService;
use App\Service\LabelService;
use App\Service\ProjectService;
use App\Service\RoleService;
class CategoryController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = CategoryService::getInstance();
$this->insertField = ['name', 'pid', 'value'];
$this->updateField = ['id', 'name', 'pid', 'value'];
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\CollectionService;
use Illuminate\Http\JsonResponse;
class CollectionController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = CollectionService::getInstance();
$this->insertField = ['project_id'];
}
/**
* 取消收藏
* @return JsonResponse
*/
public function unCollection(): JsonResponse
{
$projectId = request()->post('project_id');
if (empty($projectId)) {
return jerr('参数错误');
}
return jok(
$this->service->unCollection($projectId),
'取消成功'
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\FileService;
use App\Service\ProjectService;
use App\Service\RoleService;
class FileController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = FileService::getInstance();
$this->insertField = ['url', 's'];
$this->updateField = ['id', 'url', 's'];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\HomeService;
use App\Service\ProjectService;
use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class HomeController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = HomeService::getInstance();
}
/**
* 轮播图列表
* @Method GET
* @return JsonResponse
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function carousel(): JsonResponse
{
return jok(
$this->service->carousel(),
'轮播图列表'
);
}
/**
* 项目列表
* @Method GET
* @return JsonResponse
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function projectList(): JsonResponse
{
return jok(
$this->service->projectList(),
'项目列表'
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\LabelService;
use App\Service\ProjectService;
use App\Service\RoleService;
class LabelController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = LabelService::getInstance();
$this->insertField = ['name', 'pid', 'value'];
$this->updateField = ['id', 'name', 'pid', 'value'];
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\LoginService;
use Illuminate\Http\JsonResponse;
class LoginController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = LoginService::getInstance();
}
/**
* 登录
* @Method POST
* @return JsonResponse
* @throws \Exception
*/
public function login()
{
$this->insertField = [
'account', 'password', 'remember'
];
$param = $this->checkRequiredFields(request()->post());
return jok(
$this->service->login($param['account'], $param['password']),
'登录成功'
);
}
/**
* 注册
* @Method POST
* @return JsonResponse
* @throws \Exception
*/
public function register()
{
$this->insertField = [
'account', 'password', 'email', 'code'
];
$param = $this->checkRequiredFields(request()->post());
return jok(
$this->service->register($param['account'], $param['password'], $param['email'], $param['code']),
'登录成功'
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\ProjectService;
use Exception;
use Illuminate\Http\JsonResponse;
class ProjectController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = ProjectService::getInstance();
// $this->insertField = [ 'title', 'features', 'cover', 'labels', 'description', 'category_id', 'video_url', 'price', 'front', 'service', 'deploy', 'status', 'module_image', 'flowchart'];
// $this->updateField = [ 'id', 'title', 'features', 'cover', 'labels', 'description', 'category_id', 'video_url', 'price', 'front', 'service', 'deploy', 'status', 'module_image', 'flowchart'];
$this->insertField = [ 'title', 'features', 'cover', 'labels', 'description', 'category_id', 'price', 'front', 'service', 'deploy', 'status', 'module_image', 'flowchart'];
$this->updateField = [ 'id', 'title', 'features', 'cover', 'labels', 'description', 'category_id', 'price', 'front', 'service', 'deploy', 'status', 'module_image', 'flowchart'];
$this->notRequest = ['screenshots', 'preferential_price', 'content', 'video_url'];
}
/**
* 修改项目内容
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function changeContent(): JsonResponse
{
$id = request()->post('id');
$content = request()->post('content');
if (empty($id) || empty($content)) {
return jerr('参数错误');
}
return jok(
$this->service->changeContent($id, $content),
'修改成功'
);
}
/**
* 修改项目内容
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function changeType(): JsonResponse
{
$id = request()->post('id');
$type = request()->post('type');
if (empty($id) || empty($type)) {
return jerr('参数错误');
}
return jok(
$this->service->changeType($id, $type),
'修改成功'
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\ProjectService;
use App\Service\RoleService;
class RoleController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = RoleService::getInstance();
$this->insertField = ['name', 'pid', 'value'];
$this->updateField = ['id', 'name', 'pid', 'value'];
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\common\UploadService;
use App\Service\ProjectService;
use App\Service\RoleService;
use Illuminate\Http\JsonResponse;
class UploadController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = UploadService::getInstance();
}
/**
* 图片上传
* @Method POST
* @return JsonResponse
*/
public function image()
{
$file = request()->file('file');
return jok(
$this->service->uploadImage($file),
'上传成功'
);
}
/**
* 视频上传
* @Method POST
* @return JsonResponse
*/
public function video()
{
$file = request()->file('file');
return jok(
$this->service->uploadVideo($file),
'上传成功'
);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers;
use App\BaseApp\BaseController;
use App\Service\UserService;
use Exception;
use Illuminate\Http\JsonResponse;
class UserController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->service = UserService::getInstance();
$this->insertField = [ 'account', 'nick_name', 'password', 'role_id' ];
$this->updateField = [ 'id', 'account', 'nick_name', 'role_id' ];
$this->notRequest = ['qr_code', 'avatar', 'email', 'status'];
}
/**
* 修改密码
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function changePassword(): JsonResponse
{
$this->insertField = ['old_password', 'new_password', 'confirm_password'];
$params = $this->checkRequiredFields(request()->post());
if ($params['old_password'] === $params['new_password']) {
return jerr('新密码不能与旧密码相同');
} elseif ($params['new_password'] !== $params['confirm_password']) {
return jerr('新密码与确认密码不一致');
}
return jok(
$this->service->changePassword($params['old_password'], $params['new_password']),
'重置密码成功'
);
}
/**
* 重置密码
* @Method POST
* @return JsonResponse
* @throws Exception
*/
public function resetPassword(): JsonResponse
{
$this->insertField = ['id', 'password', 'new_password'];
$params = $this->checkRequiredFields(request()->post());
return jok(
$this->service->resetPassword($params['id'], $params['password'], $params['new_password']),
'重置密码成功'
);
}
/**
* 获取当前用户信息
* @Method GET
* @return JsonResponse
* @throws Exception
*/
public function myInfo(): JsonResponse
{
return jok(
$this->service->myInfo(),
'获取成功'
);
}
/**
* 获取当前用户信息
* @Method GET
* @return JsonResponse
* @throws Exception
*/
public function userStats(): JsonResponse
{
return jok(
$this->service->userStats(),
'获取成功'
);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* 分类表
*/
class CategoryModel extends BaseModel
{
//
protected $table = 'categories';
protected $guarded = [];
/**
* 项目
* @return HasMany
*/
public function projects(): HasMany
{
return $this->hasMany(ProjectModel::class, 'category_id', 'id');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 用户收藏表
*/
class CollectionModel extends BaseModel
{
//
protected $table = 'collection';
protected $guarded = [];
/**
* 用户关联
* @return HasOne
*/
public function user(): HasOne
{
return $this->hasOne(UserModel::class, 'id', 'user_id');
}
/**
* 项目关联
* @return HasOne
*/
public function project(): HasOne
{
return $this->hasOne(ProjectModel::class, 'id', 'project_id');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 核心功能表
*/
class FeatureModel extends BaseModel
{
//
protected $table = 'features';
protected $guarded = [];
}

27
app/Models/FileModel.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 用户表
*/
class FileModel extends BaseModel
{
//
protected $table = 'file';
protected $guarded = [];
/**
* 角色关联
* @return HasOne
*/
public function user(): HasOne
{
return $this->hasOne(UserModel::class, 'id', 'user_id');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* 框架表
*/
class FrameworkModel extends BaseModel
{
//
protected $table = 'frameworks';
protected $guarded = [];
/**
* 项目关联
* @return BelongsToMany
*/
public function projects()
{
return $this->belongsToMany(ProjectModel::class, 'project_frameworks_relations', 'framework_id', 'project_id');
}
}

26
app/Models/LabelModel.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* 标签表
*/
class LabelModel extends BaseModel
{
//
protected $table = 'labels';
protected $guarded = [];
/**
* 项目关联
* @return BelongsToMany
*/
public function projects(): BelongsToMany
{
return $this->belongsToMany(ProjectModel::class, 'project_labels_relations', 'label_id', 'project_id');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 订单详情表
*/
class OrderItemModel extends BaseModel
{
//
protected $table = 'order_items';
protected $guarded = [];
}

36
app/Models/OrderModel.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 订单表
*/
class OrderModel extends BaseModel
{
//
protected $table = 'orders';
protected $guarded = [];
/**
* 用户关联
* @return HasOne
*/
public function user()
{
return $this->hasOne(UserModel::class, 'id', 'user_id');
}
/**
* 订单详情
* @return HasMany
*/
public function items(): HasMany
{
return $this->hasMany(OrderItemModel::class, 'order_id', 'id');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 合作伙伴表
*/
class PartnerModel extends BaseModel
{
//
protected $table = 'partners';
protected $guarded = [];
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 项目框架关联表
*/
class ProjectFrameworksRelationModel extends BaseModel
{
//
protected $table = 'project_frameworks_relations';
protected $guarded = [];
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 项目标签关联表
*/
class ProjectLabelRelationModel extends BaseModel
{
//
protected $table = 'project_label_relations';
protected $guarded = [];
/**
* 项目
* @return HasOne
*/
public function project(): HasOne
{
return $this->hasOne(ProjectModel::class, 'id', 'project_id');
}
/**
* 标签
* @return HasOne
*/
public function label(): HasOne
{
return $this->hasOne(LabelModel::class, 'id', 'label_id');
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 项目表
*/
class ProjectModel extends BaseModel
{
//
protected $table = 'projects';
protected $guarded = [];
/**
* 用户关联
* @return HasOne
*/
public function user(): HasOne
{
return $this->hasOne(UserModel::class, 'id', 'user_id');
}
/**
* 作者关联
* @return HasOne
*/
public function author(): HasOne
{
return $this->hasOne(UserModel::class, 'id', 'author_id');
}
/**
* 标签关联
* @return BelongsToMany
*/
public function labels(): BelongsToMany
{
return $this->belongsToMany(LabelModel::class, 'project_label_relations', 'project_id', 'label_id');
}
/**
* 截图关联
* @return HasMany
*/
public function screenshots(): HasMany
{
return $this->hasMany(ScreenshotModel::class, 'project_id', 'id');
}
/**
* 框架关联
* @return BelongsToMany
*/
public function frameworks(): BelongsToMany
{
return $this->belongsToMany(FrameworkModel::class, 'project_frameworks_relations', 'project_id', 'framework_id');
}
/**
* 功能关联
* @return HasMany
*/
public function features(): HasMany
{
return $this->hasMany(FeatureModel::class, 'project_id', 'id');
}
/**
* 分类关联
* @return HasOne
*/
public function category(): HasOne
{
return $this->hasOne(CategoryModel::class, 'id', 'category_id');
}
}

16
app/Models/RoleModel.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 角色表
*/
class RoleModel extends BaseModel
{
//
protected $table = 'roles';
protected $guarded = [];
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
/**
* 截图表
*/
class ScreenshotModel extends BaseModel
{
//
protected $table = 'screenshots';
protected $guarded = [];
}

View File

@@ -1,48 +0,0 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}

45
app/Models/UserModel.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 用户表
*/
class UserModel extends BaseModel
{
//
protected $table = 'users';
protected $guarded = [];
/**
* 项目关联 - 上传视角
* @return HasMany
*/
public function projects(): HasMany
{
return $this->hasMany(ProjectModel::class, 'user_id', 'id');
}
/**
* 项目关联 - 作者视角
* @return HasMany
*/
public function projectsAuthor(): HasMany
{
return $this->hasMany(ProjectModel::class, 'author_id', 'id');
}
/**
* 角色关联
* @return HasOne
*/
public function roles(): HasOne
{
return $this->hasOne(RoleModel::class, 'id', 'role_id');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* 用户收藏表
*/
class UserPayModel extends BaseModel
{
//
protected $table = 'user_pay';
protected $guarded = [];
/**
* 用户关联
* @return HasOne
*/
public function user(): HasOne
{
return $this->hasOne(UserModel::class, 'id', 'user_id');
}
/**
* 项目关联
* @return HasOne
*/
public function project(): HasOne
{
return $this->hasOne(ProjectModel::class, 'id', 'project_id');
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\CategoryModel;
use App\Models\LabelModel;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class CategoryService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = ['id', 'name', 'created_at', 'updated_at', 'deleted_at'];
$this->model = CategoryModel::class;
}
/**
* 获取项目列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
return $this->getPageList();
}
public function detail($id)
{
return $this->getDetail($id);
}
/**
* 项目下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'name'];
return $this->getOption();
}
/**
* 创建项目
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
return $this->insert($params);
}
/**
* 编辑项目
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除项目
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\CollectionModel;
use App\Models\FileModel;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class CollectionService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = ['id', 'user_id', 'project_id', 'created_at', 'updated_at', 'deleted_at'];
$this->model = CollectionModel::class;
$this->where[] = ['user_id', '=', $this->userId];
}
/**
* 获取用户收藏列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
$this->with = [
'project:id,title,cover,category_id,description,price,preferential_price,front,service,deploy,status,created_at,updated_at',
'project.category:id,name',
'project.labels:id,name',
];
return $this->getPageList();
}
public function detail($id)
{
return $this->getDetail($id);
}
/**
* 用户收藏下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'url'];
return $this->getOption();
}
/**
* 创建用户收藏
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
$myCollectionModel = $this->model::where('user_id', $this->userId)->where('project_id', $params['project_id'])->first();
if ($myCollectionModel) {
if (!empty($myCollectionModel['deleted_at'])) {
return $this->model::where('id', $myCollectionModel['id'])->update([
'deleted_at' => 0
]);
}
$this->utils->errorThrow('您已收藏过该项目');
}
$params['user_id'] = $this->userId;
return $this->insert($params);
}
/**
* 取消收藏
* @param $projectId
* @return mixed
*/
public function unCollection($projectId)
{
return $this->model::where('user_id', $this->userId)->where('project_id', $projectId)->update([
'deleted_at' => get_time()
]);
}
/**
* 编辑用户收藏
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除用户收藏
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\FileModel;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class FileService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = ['id', 'user_id', 'url', 'type', 'created_at', 'updated_at', 'deleted_at'];
$this->model = FileModel::class;
}
/**
* 获取文件列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
return $this->getPageList();
}
public function detail($id)
{
return $this->getDetail($id);
}
/**
* 文件下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'url'];
return $this->getOption();
}
/**
* 创建文件
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
return $this->insert($params);
}
/**
* 编辑文件
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除文件
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
}

103
app/Service/HomeService.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Enum\ProjectStatusEnum;
use App\Enum\StatusEnum;
use App\Models\CollectionModel;
use App\Models\FeatureModel;
use App\Models\ProjectLabelRelationModel;
use App\Models\ProjectModel;
use App\Models\ScreenshotModel;
use App\Models\UserModel;
use Exception;
use Illuminate\Support\Facades\DB;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class HomeService extends BaseService
{
public function __construct()
{
$this->isAuth = false;
parent::__construct();
$this->selectField = [ 'id', 'user_id', 'title', 'cover', 'category_id', 'is_sold', 'description', 'module_image', 'flowchart', 'content', 'video_url', 'author_id', 'price', 'preferential_price', 'front', 'service', 'deploy', 'status', 'created_at', 'updated_at', 'deleted_at', ];
$this->model = ProjectModel::class;
}
/**
* 获取精选项目
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
$this->where[] = [ 'is_selected', '=', 1 ];
$this->where[] = [ 'status', '=', 1 ];
$this->with = [
'frameworks:id,name',
'labels:id,name',
'category:id,name',
];
return $this->getPageList();
}
/**
* 获取项目列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function projectList(): array
{
$this->queryField = [
'title' => 'like'
];
$this->where[] = [ 'status', '=', 1 ];
$this->with = [
'frameworks:id,name',
'labels:id,name',
'category:id,name',
];
return $this->getPageList();
}
/**
* 获取轮播图
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function carousel(): array
{
$this->where[] = [ 'is_carousel', '=', 1 ];
$this->where[] = [ 'status', '=', 1 ];
$this->with = [
'frameworks:id,name',
'labels:id,name',
'category:id,name',
];
return $this->getPageList();
}
public function detail($id)
{
$this->with = [
'frameworks:id,name',
'labels:id,name',
'category:id,name',
'screenshots:id,url,project_id',
'features:id,project_id,name',
'author:id,nick_name,avatar,email'
];
$result = $this->getDetail($id);
if (!empty($this->userId)) {
$result['is_collection'] = CollectionModel::where('project_id', $id)->where('user_id', $this->userId)->exists();
} else {
$result['is_collection'] = false;
}
return $result;
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\LabelModel;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class LabelService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = ['id', 'name', 'created_at', 'updated_at', 'deleted_at'];
$this->model = LabelModel::class;
}
/**
* 获取项目列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
return $this->getPageList();
}
public function detail($id)
{
return $this->getDetail($id);
}
/**
* 项目下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'name'];
return $this->getOption();
}
/**
* 创建项目
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
return $this->insert($params);
}
/**
* 编辑项目
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除项目
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Service;
use App\Models\UserModel;
use App\Service\common\JWTService;
use App\Service\common\UtilsService;
use Exception;
use Illuminate\Support\Str;
class LoginService
{
/**
* @var mixed 单例实例,确保该类只有一个全局实例
*/
private static mixed $_instance;
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 登录
* @param $account
* @param $password
* @return array
* @throws Exception
*/
public function login($account, $password): array
{
$userModel = UserModel::with(['roles:id,name,value'])->where('account', $account)->first();
if (empty($userModel)) {
UtilsService::getInstance()->errorThrow('用户或密码错误!');
// return $this->register($account, $password);
}
if (!password_verify($password, $userModel->password)) {
UtilsService::getInstance()->errorThrow('用户或密码错误!');
}
if ($userModel->ip !== get_ip()) {
$ipTable = json_decode($userModel->ip_table, true);
if (!in_array(get_ip(), $ipTable)) {
$ipTable[] = get_ip();
}
$update = UserModel::where('id', $userModel->id)->update([
'ip' => get_ip(),
'ip_table' => json_encode($ipTable),
'updated_at' => get_time()
]);
if (!$update) {
UtilsService::getInstance()->errorThrow('更新失败!');
}
$userModel = UserModel::where('id', $userModel->id)->first();
}
$result = [
'id' => $userModel->id,
'account' => $userModel->account,
'nick_name' => $userModel->nick_name,
'avatar' => $userModel->avatar,
'email' => $userModel->email,
'role_name' => $userModel->roles->name,
'role_value' => $userModel->roles->value,
'ip' => $userModel->ip,
'ip_table' => $userModel->ip_table,
];
$token = JWTService::getInstance()->generateToken($result);
$result['token'] = $token;
return $result;
}
/**
* 注册
* @param $account
* @param $password
* @param $email
* @param $code
* @return array
* @throws Exception
*/
public function register($account, $password, $email, $code): array
{
$userModel = UserModel::where('account', $account)->first();
if ($userModel) {
UtilsService::getInstance()->errorThrow('账号已被占用!');
}
$createModel = UserModel::create([
'account' => $account,
'email' => $email,
'password' => password_hash($password, PASSWORD_DEFAULT),
'nick_name' => '新用户'. Str::random(),
'avatar' => 'https://pic.rmb.bdstatic.com/bjh/80852bfe7c321988191838517ba64e309354.jpeg@h_1280',
'role_id' => 2,
'ip' => get_ip(),
'ip_table' => json_encode([get_ip()]),
'created_at' => get_time()
]);
if (!$createModel) {
UtilsService::getInstance()->errorThrow('注册失败!');
}
$userInfo = UserModel::with(['roles:id,name,value'])->where('id', $createModel->id)->first();
$result = [
'id' => $userInfo->id,
'account' => $userInfo->account,
'nick_name' => $userInfo->nick_name,
'avatar' => $userInfo->avatar,
'email' => $userInfo->email?? '',
'role_name' => $userInfo->roles->name,
'role_value' => $userInfo->roles->value,
'ip' => $userInfo->ip,
'ip_table' => $userInfo->ip_table,
];
$token = JWTService::getInstance()->generateToken($result);
$result['token'] = $token;
return $result;
}
}

View File

@@ -0,0 +1,288 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Enum\ProjectFieldTypeEnum;
use App\Enum\ProjectStatusEnum;
use App\Enum\StatusEnum;
use App\Models\CollectionModel;
use App\Models\FeatureModel;
use App\Models\ProjectLabelRelationModel;
use App\Models\ProjectModel;
use App\Models\ScreenshotModel;
use App\Models\UserModel;
use Exception;
use Illuminate\Support\Facades\DB;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class ProjectService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = [ 'id', 'user_id', 'title', 'is_selected', 'is_carousel', 'module_image', 'is_sold', 'flowchart', 'content', 'cover', 'category_id', 'description', 'video_url', 'author_id', 'price', 'preferential_price', 'front', 'service', 'deploy', 'status', 'created_at', 'updated_at', 'deleted_at', ];
$this->model = ProjectModel::class;
}
/**
* 获取项目列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
$this->with = [
'frameworks:id,name',
'features:id,project_id,name',
'labels:id,name',
'category:id,name',
'screenshots:id,url,project_id',
// 'features:id,project_id,name',
'author:id,nick_name,avatar'
];
$result = $this->getPageList();
foreach ($result['items'] as &$item) {
$item['status_text'] = ProjectStatusEnum::from($item['status'])->description();
$item['features_names'] = array_column($item['features'], 'name');
$item['label_ids'] = array_column($item['labels'], 'id');
$item['screenshots'] = array_column($item['screenshots'], 'url');
}
return $result;
}
public function detail($id)
{
$this->with = [
'frameworks:id,name',
'labels:id,name',
'category:id,name',
'screenshots:id,url,project_id',
'features:id,project_id,name',
'author:id,nick_name,avatar'
];
return $this->getDetail($id);
}
/**
* 项目下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'title'];
return $this->getOption();
}
/**
* 修改项目详细介绍
* @param $id
* @param $content
* @return mixed
* @throws Exception
*/
public function changeContent($id, $content)
{
return $this->save($id, ['content' => $content]);
}
/**
* 创建项目
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
$params['user_id'] = $this->userId;
$params['author_id'] = $params['author_id']?? $this->userId;
$screenshots = $params['screenshots'];
$labels = $params['labels'];
$features = $params['features'];
if (empty($params['preferential_price'])) {
$params['preferential_price'] = $params['price'];
}
if (empty($params['content'])) {
$params['content'] = '';
}
unset($params['screenshots'], $params['labels'], $params['features']);
DB::beginTransaction();
try {
$insertProjectModel = $this->insert($params);
$labelInsertData = [];
foreach ($labels as $label) {
if (!empty($label)) {
$labelInsertData[] = [
'project_id' => $insertProjectModel,
'label_id' => $label,
'created_at' => get_time()
];
}
}
$insertLabelModel = ProjectLabelRelationModel::insert($labelInsertData);
if (!$insertLabelModel) {
$this->utils->errorThrow('标签添加失败');
}
$featureInsertData = [];
foreach ($features as $feature) {
if (!empty($feature)) {
$featureInsertData[] = [
'project_id' => $insertProjectModel,
'name' => $feature,
'created_at' => get_time()
];
}
}
$insertFeatureModel = FeatureModel::insert($featureInsertData);
if (!$insertFeatureModel) {
$this->utils->errorThrow('项目功能添加失败');
}
$screenInsertData = [];
foreach ($screenshots as $screenshot) {
if (!empty($screenshot)) {
$screenInsertData[] = [
'project_id' => $insertProjectModel,
'url' => $screenshot,
'created_at' => get_time()
];
}
}
$insertScreenModel = ScreenshotModel::insert($screenInsertData);
if (!$insertScreenModel) {
$this->utils->errorThrow('项目截图添加失败');
}
DB::commit();
} catch (Exception $e) {
DB::rollBack();
$this->utils->errorThrow($e->getMessage());
}
return $insertProjectModel;
}
/**
* 编辑项目
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
$params['user_id'] = $this->userId;
$params['author_id'] = $params['author_id']?? $this->userId;
$screenshots = $params['screenshots']?? [];
$labels = $params['labels']?? [];
$features = $params['features']?? [];
unset($params['screenshots'], $params['labels'], $params['features']);
if (empty($params['content'])) {
$params['content'] = '';
}
DB::beginTransaction();
try {
// 更新项目基本信息
$updateProjectModel = $this->save($id, $params);
// 先删除旧的标签关联数据
ProjectLabelRelationModel::where('project_id', $id)->delete();
$labelInsertData = [];
foreach ($labels as $label) {
$labelInsertData[] = [
'project_id' => $id,
'label_id' => $label,
'created_at' => get_time()
];
}
if (!empty($labelInsertData)) {
$insertLabelModel = ProjectLabelRelationModel::insert($labelInsertData);
if (!$insertLabelModel) {
$this->utils->errorThrow('标签更新失败');
}
}
// 先删除旧的功能数据
FeatureModel::where('project_id', $id)->delete();
$featureInsertData = [];
foreach ($features as $feature) {
$featureInsertData[] = [
'project_id' => $id,
'name' => $feature,
'created_at' => get_time()
];
}
if (!empty($featureInsertData)) {
$insertFeatureModel = FeatureModel::insert($featureInsertData);
if (!$insertFeatureModel) {
$this->utils->errorThrow('项目功能更新失败');
}
}
// 先删除旧的截图数据
ScreenshotModel::where('project_id', $id)->delete();
$screenInsertData = [];
foreach ($screenshots as $screenshot) {
$screenInsertData[] = [
'project_id' => $id,
'url' => $screenshot,
'created_at' => get_time()
];
}
if (!empty($screenInsertData)) {
$insertScreenModel = ScreenshotModel::insert($screenInsertData);
if (!$insertScreenModel) {
$this->utils->errorThrow('项目截图更新失败');
}
}
DB::commit();
} catch (Exception $e) {
DB::rollBack();
$this->utils->errorThrow($e->getMessage());
}
return $updateProjectModel;
}
/**
* 删除项目
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
/**
* 修改项目(是否精选、是否出售、是否为轮播图)
* @param $id
* @param $type
* @return mixed
* @throws Exception
*/
public function changeType($id, $type): mixed
{
$field = ProjectFieldTypeEnum::from($type)->description();
$this->selectField = [$field];
$detail = $this->detail($id);
if (empty($detail)) {
$this->utils->errorThrow('项目不存在');
}
$update = $this->save($id, [$field => $detail[$field] === 0? 1 : 0]);
if (!$update) {
$this->utils->errorThrow('项目状改失败');
}
return $update;
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class RoleService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = ['id', 'name', 'value', 'pid', 'desc', 'created_at', 'updated_at', 'deleted_at'];
$this->model = RoleModel::class;
}
/**
* 获取项目列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
return $this->getPageList();
}
public function detail($id)
{
return $this->getDetail($id);
}
/**
* 项目下拉列表
* @return mixed
*/
public function option()
{
$this->optionField = ['id', 'name'];
return $this->getOption();
}
/**
* 创建项目
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
return $this->insert($params);
}
/**
* 编辑项目
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除项目
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
}

210
app/Service/UserService.php Normal file
View File

@@ -0,0 +1,210 @@
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Enum\UserStatusEnum;
use App\Models\CollectionModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use App\Models\UserPayModel;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class UserService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->selectField = [ 'id', 'account', 'nick_name', 'avatar', 'email', 'balance', 'is_sys_notifications', 'is_collection_notifications', 'is_marketing_notifications', 'qr_code', 'role_id', 'ip', 'ip_table', 'status', 'last_reset_password_at', 'created_at', 'updated_at', 'deleted_at', ];
$this->model = UserModel::class;
$this->queryField = [
'account' => 'like',
'nick_name' => 'like',
'email' => 'like',
'role_id' => '=',
'status' => '=',
];
}
/**
* 获取用户列表
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function list(): array
{
$this->with = [
'roles'
];
$result = $this->getPageList();
foreach ($result['items'] as &$v) {
$v['ip_table'] = json_decode($v['ip_table'], true);
$v['status_text'] = UserStatusEnum::from($v['status'])->description();
$v['last_reset_password_at'] = format_time($v['last_reset_password_at']);
}
return $result;
}
public function detail($id)
{
$result = $this->getDetail($id);
$result['ip_table'] = json_decode($result['ip_table'], true);
$result['status_text'] = UserStatusEnum::from($result['status'])->description();
return $result;
}
public function option()
{
$this->optionField = ['id', 'nick_name'];
return $this->getOption();
}
/**
* 创建用户
* @param $params
* @return mixed
* @throws Exception
*/
public function create($params): mixed
{
$params['ip_table'] = json_encode([]);
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$existsUser = $this->model::where('account', $params['account'])->whereOr('email', $params['email'])->exists();
if ($existsUser) {
$this->utils->errorThrow('账号或邮箱已存在');
}
return $this->insert($params);
}
/**
* 编辑用户
* @param $id
* @param $params
* @return mixed
* @throws Exception
*/
public function update($id, $params): mixed
{
return $this->save($id, $params);
}
/**
* 删除用户
* @param $ids
* @return mixed|true
* @throws Exception
*/
public function delete($ids): mixed
{
return $this->del($ids);
}
/**
* 修改密码
* @param $password
* @param $newPassword
* @return mixed
* @throws Exception
*/
public function changePassword($password, $newPassword): mixed
{
$info = $this->model::find($this->userId);
if (empty($info)) {
return $this->utils->notFound('数据不存在');
}
if (!password_verify($password, $info['password'])) {
return $this->utils->errorThrow('密码错误');
}
if (password_verify($newPassword, $info['password'])) {
return $this->utils->errorThrow('新密码不能与旧密码相同');
}
$result = $this->model::where('id', $this->userId)->update([
'password' => password_hash($newPassword, PASSWORD_DEFAULT),
'last_reset_password_at' => get_time()
]);
if (!$result) {
return $this->utils->errorThrow('重置密码失败');
}
return $result;
}
/**
* 重置密码
* @param $id
* @param $password
* @param $newPassword
* @return mixed
* @throws Exception
*/
public function resetPassword($id, $password, $newPassword): mixed
{
if ($password !== $newPassword) {
return $this->utils->errorThrow('两次密码不一致');
}
$info = $this->model::find($id);
if (empty($info)) {
return $this->utils->notFound('数据不存在');
}
if (password_verify($newPassword, $info['password'])) {
return $this->utils->errorThrow('新密码不能与旧密码相同');
}
$result = $this->model::where('id', $id)->update([
'password' => password_hash($newPassword, PASSWORD_DEFAULT),
'last_reset_password_at' => get_time()
]);
if (!$result) {
return $this->utils->errorThrow('重置密码失败');
}
return $result;
}
/**
* 获取用户信息
* @return mixed
* @throws Exception
*/
public function myInfo(): mixed
{
$this->with = [
'roles'
];
$result = $this->getDetail($this->userId);
$result['created_day_at'] = format_time(strtotime($result['created_at']), 'Y-m-d');
$result['account'] = desensitization($result['account']);
return $result;
}
/**
* 用户统计
* @return array[]
*/
public function userStats()
{
$payProject = UserPayModel::where('user_id', $this->userId)->count();
$collection = CollectionModel::where('user_id', $this->userId)->count();
$role = $this->userInfo['role_name'];
return [
'pay_project' => [
'label' => '已购项目',
'value' => $payProject,
'icon' => 'shopping-bag'
],
'collection' => [
'label' => '我的收藏',
'value' => $collection,
'icon' => 'heart'
],
'role' => [
'label' => '用户类型',
'value' => $role,
'icon' => 'user'
],
];
}
}

112
app/Service/common/JWTService.php Executable file
View File

@@ -0,0 +1,112 @@
<?php
namespace App\Service\common;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Exception;
class JWTService
{
private static mixed $_instance;
private string $secretKey = 'cc_spa_jwt_secret_key'; // 请替换为你的密钥
private string $token;
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 获取 Token
* @return $this|null
* @throws Exception
*/
public function getToken(): null|static
{
$token = request()->bearerToken();
if (empty($token)) return UtilsService::getInstance()->notAuth('请先登录');
$this->token = $token;
return $this;
}
/**
* 生成 JWT Token
* @param array $data 要嵌入到 token 中的数据
* @return string
*/
public function generateToken(array $data): string
{
$issuedAt = time();
$expirationTime = $issuedAt + config('cc.redis.jwt_ttl', 600000);
$payload = [
'iat' => $issuedAt,
'exp' => $expirationTime,
'data' => $data
];
RedisService::getInstance()->init(config('cc.redis.jwt'))->set($data['id'], json_encode($data));
return JWT::encode($payload, $this->secretKey, 'HS256');
}
/**
* 解析 JWT Token
* @return object|null 返回解码后的 payload 或者 null 如果验证失败
* @throws Exception
*/
public function parseToken(): ?object
{
try {
if (empty($this->token)) return UtilsService::getInstance()->notAuth('请先登录');
return JWT::decode($this->token, new Key($this->secretKey, 'HS256'));
} catch (Exception $e) {
return UtilsService::getInstance()->notAuth('【1】Token解析失败请重新登录'. $e->getMessage());
}
}
/**
* 解析 JWT Token
* @return array|null 返回解码后的 payload 或者 null 如果验证失败
* @throws Exception
*/
public function getUserInfo(): ?array
{
try {
$jwt = $this->parseToken();
$user = RedisService::getInstance()->init(config('cc.redis.jwt'))->get($jwt->data->id);
if (empty($user)) return UtilsService::getInstance()->notAuth('登录状态过期');
return json_decode($user, true);
} catch (Exception $e) {
return UtilsService::getInstance()->notAuth('【2】Token解析失败请重新登录'. $e->getMessage());
}
}
/**
* 续签 JWT Token
* @return string|null 新的 JWT 字符串或者 null 如果原 token 已过期或无效
* @throws Exception
*/
public function refreshToken(): ?string
{
$decoded = $this->parseToken();
if ($decoded === null || !property_exists($decoded, 'data')) {
return null;
}
return $this->generateToken((array)$decoded->data);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Service\common;
use Illuminate\Support\Facades\Redis;
class RedisService
{
private static mixed $_instance;
private $prefix = 'default';
private $redis;
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 初始化
* @param string $prefix
* @return RedisService
*/
public function init(string $prefix = 'default'): static
{
$this->redis = Redis::class;
$this->prefix = $prefix;
return $this;
}
/**
* 设置缓存
* @param $key
* @param $value
* @param int $expire
* @return true
*/
public function set($key, $value, int $expire = 0): true
{
$this->redis::set($this->prefix . $key, $value);
if ($expire > 0) {
$this->redis::expire($this->prefix . $key, $expire);
}
return true;
}
/**
* 累加
* @param $key
* @param int $value
* @param int $expire
* @return true
*/
public function incr($key, int $value = 1, int $expire = 0): true
{
$this->redis::incr($this->prefix . $key, $value);
if ($expire > 0) {
$this->redis::expire($this->prefix . $key, $expire);
}
return true;
}
/**
* 获取缓存
*
* @param $key
* @return mixed
*/
public function get($key): mixed
{
return $this->redis::get($this->prefix . $key);
}
/**
* 删除缓存
* @param $key
* @return bool
*/
public function del($key): bool
{
return $this->redis::del($this->prefix . $key);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Service\common;
use App\BaseApp\BaseService;
use App\Models\ProjectModel;
use App\Models\RoleModel;
use App\Models\UserModel;
use App\Service\common\upload\LocalhostStorageService;
use App\Service\common\upload\QiniuStorageService;
use App\Service\FileService;
use Exception;
use Illuminate\Support\Str;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class UploadService extends BaseService
{
private $uploadService;
public function __construct()
{
parent::__construct();
$this->model = RoleModel::class;
switch (env('ECS')) {
case 'aliyun':
// $this->uploadService = AliyunStorageService::getInstance();
break;
case 'qcloud':
// $this->uploadService = QcloudStorageService::getInstance();
break;
case 'qiniu':
$this->uploadService = QiniuStorageService::getInstance();
break;
default:
$this->uploadService = LocalhostStorageService::getInstance();
break;
}
}
/**
* 上传OSS图片
* @param $file
* @return array|bool
* @throws Exception
*/
public function uploadImage($file): array|bool
{
// 获取文件后缀
$ext = $file->getClientOriginalExtension();
$key = 'spa/image/'. date('Ymd') . '/' . 'cc_upload_'. Str::random(). uniqid() . '.' . $ext;
$result = $this->uploadService->uploadVideo($file, $key);
// $result = [
// 'key' => $key,
// 'url' => 'https://img2.baidu.com/it/u=1629222614,1358629025&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500&a='. rand(1111111, 9999999)
// ];
FileService::getInstance()->create([
'user_id' => $this->userId,
'url' => $result['url'],
]);
return $result;
}
/**
* 上传OSS视频
* @param $file
* @return array|bool
* @throws Exception
*/
public function uploadVideo($file): array|bool
{
// 获取文件后缀
$ext = $file->getClientOriginalExtension();
$key = 'spa/video/'. date('Ymd') . '/' . 'cc_upload_'. Str::random(). uniqid() . '.' . $ext;
$result = $this->uploadService->uploadVideo($file, $key);
// $result = [
// 'key' => $key,
// 'url' => 'http://d-jy.nailaoyun.cn//storage/video//20250327/2cc1abf9bd69410ce7c69660daf54260.mp4'
// ];
FileService::getInstance()->create([
'user_id' => $this->userId,
'url' => $result['url'],
]);
return $result;
}
}

View File

@@ -0,0 +1,247 @@
<?php
namespace App\Service\common;
use App\Enum\ErrorEnum;
use DateTime;
use Exception;
use Illuminate\Support\Facades\Route;
use Random\RandomException;
use ReflectionClass;
class UtilsService
{
private static mixed $_instance;
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 抛出异常
*
* @param string $msg
* @param ErrorEnum $code
* @return mixed
* @throws Exception
*/
public function errorThrow(string $msg = '请求出了问题!', ErrorEnum $code = ErrorEnum::FAIL): mixed
{
throw new Exception($msg, $code->value);
}
/**
* 抛出未找到异常 2000
*
* @param $msg
* @return mixed
* @throws Exception
*/
public function notFound($msg): mixed
{
return self::errorThrow($msg, ErrorEnum::NOT_FOUND);
}
/**
* 未授权 401
*
* @param $msg
* @return mixed
* @throws Exception
*/
public function notAuth($msg): mixed
{
return self::errorThrow($msg, ErrorEnum::NOT_AUTH);
}
/**
* 生成密码
* @param $password
* @return string
*/
public function genPassword($password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* 密码验证
*
* @param $password
* @param $passwordAse
* @return bool
*/
public function checkPassword($password, $passwordAse): bool
{
// 盐
return password_verify($password, $passwordAse);
}
/**
* 生成OpenId
* @return string
* @throws RandomException
*/
public function genOpenId(): string
{
// 获取当前时间戳(精确到秒)
$timestamp = dechex(time());
// 生成16字节的随机数据并转换为32个十六进制字符组成的字符串
$randomString = bin2hex(random_bytes(16));
// 结合时间和随机字符串确保总长度为28个字符
return substr($timestamp . $randomString, 0, 28);
}
/**
* 递归生成树形结构
* @param $data
* @param int $pid
* @return array
*/
public function tree($data, int $pid = 0): array
{
$tree = [];
foreach ($data as $v) {
if ($v['pid'] == $pid) {
$v['children'] = $this->tree($data, $v['id']);
if (empty($v['children'])) {
unset($v['children']);
}
$tree[] = $v;
}
}
return $tree;
}
/**
* 检查手机号格式
*
* @param string $phone
* @return bool
* @throws Exception
*/
public function checkPhone(string $phone): bool
{
$phone = trim($phone);
if (preg_match('/^1[3456789]\d{9}$/', $phone)) {
return true;
} else {
self::errorThrow('手机号格式不正确');
}
}
/**
* 生成订单号
* @param int $type
* @return string
*/
public function genOrderNo(int $type = 0): string
{
$len = 9999999999;
switch ($type) {
case 0:
return 'DK' . date('Ymd') . str_pad(mt_rand(1, $len), 11, '0', STR_PAD_LEFT);
case 1:
return 'DD' . date('Ymd') . str_pad(mt_rand(1, $len), 11, '0', STR_PAD_LEFT);
default:
return '';
}
}
/**
* 是否是开发环境
* @return bool
*/
public static function isDev(): bool
{
return env('ECS') === 'localhost';
}
/**
* 根据身份证号计算年龄
* @param string $idno 身份证号15位或18位
* @return int|false 成功返回年龄失败返回false
*/
public function getAgeFromIdNo($idno = '')
{
// 校验身份证号长度
$len = strlen($idno);
if (!in_array($len, [15, 18])) {
return false;
}
// 提取出生日期部分
if ($len === 18) {
$datePart = substr($idno, 6, 8);
} else {
// 15位身份证补全年份为19XX
$datePart = '19' . substr($idno, 6, 6);
}
// 分解年月日
$year = substr($datePart, 0, 4);
$month = substr($datePart, 4, 2);
$day = substr($datePart, 6, 2);
// 校验年月日是否为数字
if (!ctype_digit($year) || !ctype_digit($month) || !ctype_digit($day)) {
return false;
}
// 尝试创建日期对象
try {
$birthDate = new DateTime("$year-$month-$day");
} catch (Exception $e) {
return false;
}
$currentDate = new DateTime();
// 检查出生日期是否在当前日期之前
if ($birthDate > $currentDate) {
return false;
}
// 计算年龄差
$ageInterval = $currentDate->diff($birthDate);
return $ageInterval->y;
}
public function autoRouteRegister($class): void
{
foreach ($class as $key => $value) {
// 获取控制器$value的所有方法
$methods = (new ReflectionClass($value))->getMethods();
// 注册路由
foreach ($methods as $method) {
// 获取方法注释 @Method
$docComment = $method->getDocComment();
if ($method->name === '__construct' || preg_match('/@Method\s+(NO)\b/', $docComment, $matches)) {
continue;
}
$httpMethod = 'any';
// 查询 @Method GET 或者 @Method POST
if (preg_match('/@Method\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|ANY)\b/', $docComment, $matches)) {
$httpMethod = $matches[1];
}
Route::$httpMethod($key . '/' . cc_camel_case_to_dash($method->getName()), [$value, conjunction_symbol_processing($method->name)]);
}
}
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Service\common\upload;
use App\BaseApp\BaseService;
use Illuminate\Support\Facades\Storage;
class LocalhostStorageService extends BaseService
{
/**
* @var mixed 单例实例,确保该类只有一个全局实例
*/
protected static mixed $_instance;
public function __construct()
{
parent::__construct();
}
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 上传图片
*
* @param string $filePath 文件本地路径
* @param string $key 上传到本地存储的文件名
* @return array|bool
*/
public function uploadImage($filePath, $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
/**
* 上传视频
*
* @param string $filePath 文件本地路径
* @param string $key 上传到本地存储的文件名
* @return array|bool
*/
public function uploadVideo($filePath, $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
/**
* 删除图片
*
* @param string $key 本地存储的文件名
* @return bool
*/
public function deleteImage($key): bool
{
return $this->deleteFile($key);
}
/**
* 删除视频
*
* @param string $key 本地存储的文件名
* @return bool
*/
public function deleteVideo($key): bool
{
return $this->deleteFile($key);
}
/**
* 上传文件
*
* @param string $filePath 文件本地路径
* @param string $key 上传到本地存储的文件名
* @return array|bool
*/
private function uploadFile(string $filePath, string $key): bool|array
{
if (Storage::put($key, file_get_contents($filePath))) {
return [
'key' => $key,
'url' => asset('/storage/' . $key)
];
}
return false;
}
/**
* 删除文件
*
* @param string $key 本地存储的文件名
* @return bool
*/
private function deleteFile(string $key): bool
{
return Storage::delete($key);
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace App\Service\common\upload;
use App\BaseApp\BaseService;
use Exception;
use Qiniu\Auth;
use Qiniu\Storage\BucketManager;
use Qiniu\Storage\UploadManager;
class QiniuStorageService extends BaseService
{
/**
* @var mixed 单例实例,确保该类只有一个全局实例
*/
protected static mixed $_instance;
private $accessKey;
private $secretKey;
private $bucket;
private $domain;
public function __construct()
{
parent::__construct();
$this->accessKey = config('cc.oss.qiniu.access_key');
$this->secretKey = config('cc.oss.qiniu.secret_key');
$this->bucket = config('cc.oss.qiniu.bucket');
$this->domain = config('cc.oss.qiniu.domain');
}
/**
* 获取实例
* @return null|static
*/
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 上传图片
*
* @param string $filePath 文件本地路径
* @param string $key 上传到七牛云的文件名
* @return array|bool
* @throws Exception
*/
public function uploadImage($filePath, $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
/**
* 上传视频
*
* @param string $filePath 文件本地路径
* @param string $key 上传到七牛云的文件名
* @return array|bool
*/
public function uploadVideo($filePath, $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
/**
* 删除图片
*
* @param string $key 七牛云存储的文件名
* @return bool
*/
public function deleteImage($key): bool
{
return $this->deleteFile($key);
}
/**
* 删除视频
*
* @param string $key 七牛云存储的文件名
* @return bool
*/
public function deleteVideo($key): bool
{
return $this->deleteFile($key);
}
/**
* 上传文件
*
* @param string $filePath 文件本地路径
* @param string $key 上传到七牛云的文件名
* @return array|bool
* @throws Exception
*/
private function uploadFile(string $filePath, string $key): bool|array
{
$auth = new Auth($this->accessKey, $this->secretKey);
$token = $auth->uploadToken($this->bucket);
$uploadMgr = new UploadManager();
list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
if ($err !== null) {
return false;
} else {
return [
'key' => $ret['key'],
'url' => $this->domain . '/' . $ret['key']
];
}
}
/**
* 删除文件
*
* @param string $key 七牛云存储的文件名
* @return bool
*/
private function deleteFile(string $key): bool
{
$auth = new Auth($this->accessKey, $this->secretKey);
$bucketMgr = new BucketManager($auth);
$err = $bucketMgr->delete($this->bucket, $key);
return $err === null;
}
}

View File

@@ -1,12 +1,18 @@
<?php <?php
use App\Enum\ErrorEnum;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
health: '/up', health: '/up',
) )
@@ -14,5 +20,58 @@ return Application::configure(basePath: dirname(__DIR__))
// //
}) })
->withExceptions(function (Exceptions $exceptions) { ->withExceptions(function (Exceptions $exceptions) {
// $exceptions->render(function (Throwable $e) {
// 判断异常是否为Exception类型
if ($e instanceof \Exception) {
// \App\Models\new\log\ErrorLogModel::create([
// 'type' => 0,
// 'content' => json_encode([
// 'message' => $e->getMessage(),
// 'line' => $e->getLine(),
// 'file' => $e->getFile(),
// 'trace' => $e->getTraceAsString(),
// ]),
// 'created_at' => time()
// ]);
// 使用switch语句判断异常类型
switch (true) { // 注意这里使用了 'true' 因为 PHP 不允许在 case 中使用表达式
// 如果异常是NotFoundHttpException类型
case $e instanceof NotFoundHttpException:
// 返回一个自定义的JSON响应状态码为404错误信息为"路由未找到"
return jInstanceof(ErrorEnum::NOT_FOUND, '路由未找到', $e->getMessage());
// 如果异常是MethodNotAllowedHttpException类型
case $e instanceof MethodNotAllowedHttpException:
Log::error($e->getMessage());
// 返回一个自定义的JSON响应状态码为405错误信息为"请求方式错误"
return jInstanceof(405, '请求方式错误', $e->getMessage());
// 如果异常是UnprocessableEntityHttpException类型
case $e instanceof UnprocessableEntityHttpException:
Log::warning($e->getMessage());
// 返回一个自定义的JSON响应状态码为422错误信息为"服务器处理不过来啦"
return jInstanceof(422, '服务器处理不过来啦', $e->getMessage());
// 如果异常是HttpException类型
case $e instanceof HttpException:
Log::error($e->getMessage());
// 返回一个自定义的JSON响应状态码为异常的状态码错误信息为"服务器出错啦"
return jInstanceof($e->getCode(), ErrorEnum::FAIL->description(), 'ERROR'. $e->getMessage());
// 对于所有其他类型的异常默认返回500内部服务器错误
default:
Log::critical($e->getMessage()); // 记录更严重的日志级别
// 返回一个自定义的JSON响应状态码为500错误信息为"内部服务器错误"
return jInstanceof($e->getCode(), $e->getMessage(), $e->getCode());
}
}
return jInstanceof(ErrorEnum::FAIL, '服务器出错啦~', [
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTrace(),
]);
});
})->create(); })->create();

View File

@@ -7,8 +7,10 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"firebase/php-jwt": "^6.11",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1" "laravel/tinker": "^2.10.1",
"qiniu/php-sdk": "^7.14"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
@@ -50,6 +52,10 @@
"dev": [ "dev": [
"Composer\\Config::disableProcessTimeout", "Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
] ]
}, },
"extra": { "extra": {

8272
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

135
config/cc.php Executable file
View File

@@ -0,0 +1,135 @@
<?php
return [
/*
* 过期时间配置
*/
'time_out' => [
// 订单
'product_order' => [
'over_time' => 24*3600,//秒,自动取消时间
// 'over_time' => 3,//秒,自动取消时间
'received_time' => 7*24*3600,//自动确认收货时间
'forbidden_refund_time' => 30*24*3600, //不允许退款时间
'settlement_time' => 0//7*24*3600//订单发货 分账自动结算时间
],
// 处方
'prescription' => [
'over_time' => 24*3600 //处方审核自动失效时间
// 'over_time' => 0 //处方审核自动失效时间
],
],
/*
* redis配置
*/
'redis' => [
'jwt' => 'xk_login_',
'jwt_ttl' => 360000,
// 'jwt_ttl' => 360,
'email' => 'xk_email_',
'phone' => 'xk_phone_',
'login_out_key' => 'xk_login_out_',
'menu_key' => 'xk_menu_',
],
/*
* api白名单
*/
'api' => [
'white_list' => [
'/api/admin/login',
'/api/admin/register',
]
],
/*
* 阿里云oss配置
*/
'oss' => [
'redis_key' => 'xk_oss_url_',
'ali' => [
'accessKeyId' => env('OSS_ACCESS_KEY_ID'),
'accessKeySecret' => env('OSS_ACCESS_KEY_SECRET'),
'endpoint' => env('OSS_ENDPOINT'),
'bucket' => env('OSS_BUCKET'),
'domain' => env('OSS_DOMAIN'),
],
'qiniu' => [
'access_key' => 'RPQlZUd2yrL3kR8CC4BpTGV6FhGr11npBzf6IuCT',
'secret_key' => 'b-796uVY1InOKSVecpY8VBpO7Cdubo1Tv4RcOfMY',
'bucket' => 'ccspa',
'domain' => 'http://o-spa.nailaoyun.cn',
]
],
/*
* 短信配置
*/
'sms' => [
'ali' => [
'status' => '1',
'access_key_id' => env('ALIYUN_SMS_ACCESS_KEY_ID'),
'access_key_secret' => env('ALIYUN_SMS_ACCESS_KEY_SECRET'),
'sign_name' => env('ALIYUN_SMS_SIGN_NAME'),
'captcha' => [
"template_id" => "SMS_275060336", // 模板id
"template_variable" => "code", //模板变量
],
"doctor" => [
"register" => [ //挂号订单提醒
"template_id" => "SMS_462205458",
"template_variable" => ""
],
"prescription_pass" => [
"template_id" => "SMS_462260412",
"template_variable" => "order"
],
"prescription_refuse" => [
"template_id" => "SMS_462230433",
"template_variable" => [
"order",
"cause"
]
]
],
"pharmacist" => [
"wait_approval" => [
"template_id" => "SMS_462255406",
"template_variable" => ""
]
]
]
],
/*
* 旧接口配置
*/
'base_url' => [
'old_api' => env('BASE_OLD_API_URL', ''),
'old_shop_api' => env('BASE_OLD_SHOP_API_URL', ''),
// 微信接口
'we_chat_api' => env('BASE_WE_CHAT_API_URL', ''),
],
/*
* 日志配置
*/
'log' => [
// 不添加返回记录的api接口
'no_insert' => [
'api/admin/auth/login',
'api/admin/auth/send-verification-code',
'api/admin/auth/my-info',
'api/admin/auth/codes',
'api/admin/auth/menu',
'api/admin/log/api-list',
'api/admin/log/ledger-list',
'api/admin/log/op-list',
'api/admin/log/prescription-list',
]
],
'app' => [
'we_chat' => [
'app_id' => '',
'app_secret' => '',
'merchant_id' => '',
'merchant_key' => '',
'apiclient_cert' => '',
'apiclient_key' => ''
]
],
];

View File

@@ -32,7 +32,7 @@ return [
'local' => [ 'local' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app/private'), 'root' => storage_path('app/public'),
'serve' => true, 'serve' => true,
'throw' => false, 'throw' => false,
'report' => false, 'report' => false,

504
config/helpers.php Executable file
View File

@@ -0,0 +1,504 @@
<?php
/*
* 辅助函数封装
*/
/**
* 路由注册封装
* 使用 cc_route_register 函数,路由名需要用连词符 "-" 来表示,并且控制器要使用小驼峰的命名规范
* @param $router
* @param $class
* @return void
*/
if ( !function_exists('cc_route_register') ) {
function cc_route_register ($router,$class): void
{
foreach ( $router as [ $method, $router_name ] ) {
// ds(Illuminate\Support\Facades\Route::$method( $router_name, [$class, conjunction_symbol_processing($router_name)] ));
Illuminate\Support\Facades\Route::$method( $router_name, [$class, conjunction_symbol_processing($router_name)] );
}
}
}
/**
* 自动路由注册封装
* 使用 cc_auto_route_register 函数,路由名需要用连词符 "-" 来表示,并且控制器要使用小驼峰的命名规范
* @param $class
* @return void
*/
if ( !function_exists('cc_auto_route_register') ) {
function cc_auto_route_register ($class): void
{
foreach ($class as $key => $value) {
// 获取控制器$value的所有方法
$methods = (new ReflectionClass($value))->getMethods();
// 注册路由
foreach ($methods as $method) {
// 获取方法注释 @Method
$docComment = $method->getDocComment();
if ($method->name === '__construct' || preg_match('/@Method\s+(NO)\b/', $docComment, $matches)) {
continue;
}
$httpMethod = 'any';
// 查询 @Method GET 或者 @Method POST
if (preg_match('/@Method\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|ANY)\b/', $docComment, $matches)) {
$httpMethod = $matches[1];
}
Illuminate\Support\Facades\Route::$httpMethod( $key. '/'. cc_camel_case_to_dash($method->getName()), [$value, conjunction_symbol_processing($method->name)] );
}
}
}
}
/**
* 无限极分类
* @param $data
* @param $pid
* @return mixed
*/
if ( !function_exists('tree') ) {
function tree($data, $pid = 0, $idField = 'id', $pidField = 'pid'): array
{
$tree = array();
foreach ($data as &$v)
{
if ($v[$pidField] == $pid)
{
$v['children'] = tree($data, $v[$idField], $idField, $pidField);
if (empty($v['children'])) unset($v['children']);
$tree[] = $v;
}
}
return $tree;
}
}
/**
* 设置不关闭
* @return true
*/
if ( !function_exists('cc_set_time_limit') ) {
function cc_set_time_limit(): bool {
//让程序一直运行
set_time_limit(0);
//设置程序运行内存
ini_set('memory_limit', '1024M');
return true;
}
}
/**
* 获取ip所属地
*
* @return true
*/
if ( !function_exists('cc_get_ip_lookup') ) {
function cc_get_ip_lookup($ip = ''): array {
$result = (new GuzzleHttp\Client())->get('https://api.vore.top/api/Weather?ip='. $ip);
$weather = json_decode($result->getBody()->getContents(), true);
if ( empty($weather) ) {
$data = [
'weather' => '未知',
'temperature' => '未知',
'winddirection' => '未知',
'reporttime' => '未知',
'area' => '未知',
'info' => '未知',
];
} else {
$data = [
'area' => $weather['data']['ipdata']['area'],
'info' => $weather['data']['ipdata']['info'],
];
}
return $data;
}
}
/**
* 获取ip
*/
if (!function_exists('get_ip')) {
function get_ip() {
return $_SERVER['HTTP_X_FORWARDED_FOR']?? request()->getClientIp();
}
}
/**
* 获取操作系统
* @return string
*/
if ( !function_exists('cc_get_os') ) {
function cc_get_os(): string {
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
$os = $_SERVER['HTTP_USER_AGENT'];
return match (true) {
str_contains($os, 'Windows') => 'Windows',
str_contains($os, 'Macintosh') => 'Mac',
str_contains($os, 'Linux') => 'Linux',
str_contains($os, 'Android') => 'Android',
str_contains($os, 'iOS') => 'iOS',
str_contains($os, 'Api') => 'Api Post',
default => 'Unknown',
};
} else {
return "获取访客操作系统信息失败!";
}
}
}
/**
* 获取浏览器
* @return string
*/
if ( !function_exists('cc_get_browser') ) {
function cc_get_browser(): string {
if (empty($_SERVER['HTTP_USER_AGENT'])) {
return "获取浏览器信息失败!";
}
$userAgent = $_SERVER['HTTP_USER_AGENT'];
foreach ([
'MSIE' => 'MSIE',
'Edg' => 'Microsoft Edge',
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Api' => 'Api Post',
] as $key => $browser) {
if (str_contains($userAgent, $key)) {
return $browser;
}
}
return 'Other';
}
}
/**
* 在二维数组其中某个值相同时,提取指定的字段
* @param $arr // 数组
* @param $extract // 提取字段
* @param $repeat // 重复字段
* @param $isUnset // 是否删除未重复的值
* @return array
*/
if ( !function_exists('cc_get_repeat_values') ) {
function cc_get_repeat_values($arr, $extract, $repeat , $isUnset = false): array {
$key = [];
$result = [];
$len = count($arr);
for ( $i=0; $i<$len; $i++ ) {
if ( !in_array($arr[$i][$repeat], $key) ) {
if ( !array_key_exists($arr[$i][$repeat], $result) ) $result[$arr[$i][$repeat]] = [];
$result[$arr[$i][$repeat]][] = $arr[$i][$extract];
continue;
}
$key[] = $arr[$i][$repeat];
}
if ( $isUnset === true ) {
// 删除未重复的下标
foreach ( $result as $k => &$v ) {
if ( count($v) <= 1 ) {
unset($result[$k]);
}
}
}
return $result;
}
}
/**
* 数据脱敏
* @param string $str
* @param string $type
* @return string
*/
if ( !function_exists('desensitization') ) {
function desensitization(string $str, string $type = 'phone'): string
{
if (empty($str)) return '';
if ($type === 'phone') {
if (strlen($str) !== 11) {
throw new Exception('手机号格式错误');
}
return substr($str, 0, 3) . '****' . substr($str, 7);
} elseif ($type === 'id_card') {
if (strlen($str) !== 18) {
throw new Exception('身份证号格式错误');
}
return substr($str, 0, 6) . '********' . substr($str, 14);
} else {
throw new Exception('类型错误');
}
}
}
/**
* 连赐福转换小驼峰
* @param $string
* @return string
*/
if(!function_exists('conjunction_symbol_processing')) {
function conjunction_symbol_processing($string): string
{
$string = str_replace('-', ' ', $string); // 将连字符替换为空格
$string = ucwords($string); // 将每个单词的首字母大写
$string = str_replace(' ', '', $string); // 空格删除
$string[0] = lcfirst($string)[0]; // 首字母小写
return $string;
}
}
// 小驼峰转换-
if ( !function_exists('cc_camel_case_to_dash') ) {
function cc_camel_case_to_dash($str): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1-$2', $str));
}
}
/**
* 将驼峰字符串转化为下划线区分
* @param $str
* @return string
*/
if ( !function_exists('cc_camel_case_to_underscore') ) {
function cc_camel_case_to_underscore($str): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $str));
}
}
/**
* 身份证脱敏
* @return true
*/
if ( !function_exists('cc_id_card_text') ) {
function cc_id_card_text($idCard): string {
return substr_replace($idCard, '********', 6, 8 );
}
}
/**
* 手机号脱敏
* @return true
*/
if ( !function_exists('cc_phone_text') ) {
function cc_phone_text($phone): string {
return substr_replace($phone, '******', 3, 6 );
}
}
/**
* 身份证号验证
* @param $id
* @return bool
*/
if ( !function_exists('cc_is_id_card') ) {
function cc_is_id_card($id): bool {
$id = strtoupper($id);
$regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/";
$arr_split = array();
if (!preg_match($regx, $id)) {
return FALSE;
}
if (15 == strlen($id)) //检查15位
{
$regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/";
@preg_match($regx, $id, $arr_split);
//检查生日日期是否正确
$dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
if (!strtotime($dtm_birth)) {
return FALSE;
} else {
return TRUE;
}
} else { //检查18位
$regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
@preg_match($regx, $id, $arr_split);
$dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
if (!strtotime($dtm_birth)) //检查生日日期是否正确
{
return FALSE;
} else {
//检验18位身份证的校验码是否正确。
//校验位按照ISO 7064:1983.MOD 11-2的规定生成X可以认为是数字10。
$arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
$arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
$sign = 0;
for ($i = 0; $i < 17; $i++) {
$b = (int) $id[$i];
$w = $arr_int[$i];
$sign += $b * $w;
}
$n = $sign % 11;
$val_num = $arr_ch[$n];
if ($val_num != substr($id, 17, 1)) {
return FALSE;
} else {
return TRUE;
}
}
}
}
}
/**
* 分割数字
*/
if (!function_exists('money_format')) {
function money_format($number) {
return preg_replace("/(?=\B(\d{3})+$)/", ',', $number);
}
}
/**
* 密码加密处理
* @param $password
* @return string
*/
if (!function_exists('ase_password')) {
function ase_password($password): string
{
return sha1($password. config('cc.configs.ase.slate'));
}
}
// 获取当前项目启动的地址
if (!function_exists('cc_get_project_url')) {
function cc_get_project_url(): string
{
$url = config('cc.configs.project_url');
if (empty($url)) {
$url = request()->root(true);
}
return $url;
}
}
/**
* 加密
* @param $str
* @return mixed|string
*/
if(!function_exists('ase_encode')) {
function ase_encode($str)
{
if (empty($str)) return $str;
return base64_encode(\Illuminate\Support\Str::random(config('ase.len')). base64_encode($str));
}
}
/**
* 解密
* @param $str
* @return false|string
*/
if (!function_exists('ase_decode')) {
function ase_decode($str): bool|string
{
if (empty($str)) return $str?? '';
$result = base64_decode($str);
$result = base64_decode(substr($result, config('ase.len')));
$pattern = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\x{E000}-\x{F8FF}]/u';
if (preg_match($pattern, $result)) return $str;
return $result;
}
}
/**
* 获取选中的时间段有几天
*
* @param $startTime
* @param $endTime
* @return array
*/
if (!function_exists('get_day_list')) {
function get_day_list($startTime, $endTime): array
{
$day = [];
$currentTime = $startTime;
while ($currentTime <= $endTime) {
$day[] = date('m-d', $currentTime);
$currentTime = strtotime('+1 day', $currentTime);
}
return $day;
}
}
/**
* 获取选中的时间段有几个月
*
* @param $startTime
* @param $endTime
* @return array
*/
if (!function_exists('get_month_list')) {
function get_month_list($startTime, $endTime): array
{
$monthList = [];
$currentTime = $startTime;
while ($currentTime <= $endTime) {
$monthList[] = date('Y-m', $currentTime);
$currentTime = strtotime('+1 month', $currentTime);
}
return $monthList;
}
}
/**
* 生成32位的uuid
*
* @return array
*/
if (!function_exists('gen_uuid')) {
function gen_uuid(): string
{
$chart = md5(uniqid(rand(), true));
$hyphen = chr(45);// "-"
return substr($chart, 0, 8) . $hyphen
. substr($chart, 8, 4) . $hyphen
. substr($chart, 12, 4) . $hyphen
. substr($chart, 16, 4) . $hyphen
. substr($chart, 20, 12);
}
}
/**
* 获取时间
*
* @return array
*/
if (!function_exists('get_time')) {
function get_time($isString = false, $format = 'Y-m-d H:i:s'): string
{
return $isString === true ? date($format) : time();
}
}
/**
* 格式化
*
* @return array
*/
if (!function_exists('format_time')) {
function format_time($time, $format = 'Y-m-d H:i:s'): string
{
return !empty($time)? date($format, $time) : '';
}
}

View File

@@ -85,6 +85,7 @@ return [
'smtp', 'smtp',
'log', 'log',
], ],
'retry_after' => 60,
], ],
'roundrobin' => [ 'roundrobin' => [
@@ -93,6 +94,7 @@ return [
'ses', 'ses',
'postmark', 'postmark',
], ],
'retry_after' => 60,
], ],
], ],

112
config/response.php Executable file
View File

@@ -0,0 +1,112 @@
<?php
use Illuminate\Http\JsonResponse;
use JetBrains\PhpStorm\NoReturn;
# 返回封装
/**
* 请求成功返回
* @param $data
* @param $msg
* @param $code
* @return JsonResponse
*/
if ( !function_exists('jok') ) {
function jok($data = [], $msg = '请求成功'): JsonResponse
{
return response()->json([
'code' => 0,
'message' => $msg,
'result' => $data,
'type' => 'success'
]);
}
}
/**
* 请求失败返回
* @param $data
* @param $msg
* @param $code
* @return JsonResponse
*/
if ( !function_exists('jerr') ) {
#[NoReturn]
function jerr($msg = '', $data = [], $code = 500): JsonResponse
{
exit(json_encode([
'code' => $code,
'message' => $msg,
'result' => $data,
'type' => 'error'
], JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR));
}
}
/**
* 异常返回(他会阻止后面所有代码的运行)
* @param $data
* @param $msg
* @param $code
* @return JsonResponse
*/
if ( !function_exists('jInstanceof') ) {
function jInstanceof($code = 500, $msg = '错误返回', $data = []): JsonResponse
{
// 转换成秒
$timeDifference = microtime(true) - LARAVEL_START;
return response()->json([
'code' => $code,
'message' => $msg,
'result' => $data,
'type' => 'error',
'interface_info' => [
'result_time' => $timeDifference < 1? round($timeDifference * 1000) . ' ms': number_format($timeDifference, 1) . ' s',
'ecs' => env('ECS')
]
]);
}
}
/**
* 测试 (他会阻止后面所有代码的运行)
* @param $data
* @param $msg
* @param $code
* @return void
*/
if ( !function_exists('ds') ) {
#[NoReturn]
function ds($data = [], $msg = '测试返回'): void
{
exit(json_encode([
'code' => 19522,
'message' => $msg,
'result' => $data,
'type' => 'success'
],JSON_UNESCAPED_SLASHES,JSON_UNESCAPED_UNICODE));
}
}
/**
* 身份过期返回(他会阻止后面所有代码的运行)
* @param $data
* @param $msg
* @param $code
* @return void
*/
if ( !function_exists('jexpire') ) {
#[NoReturn]
function jexpire($msg = '身份过期', $data = []): void
{
exit(json_encode([
'code' => 401,
'message' => $msg,
'result' => $data,
'type' => 'jexpire'
],JSON_UNESCAPED_SLASHES,JSON_UNESCAPED_UNICODE));
}
}

View File

@@ -22,8 +22,8 @@
<env name="APP_MAINTENANCE_DRIVER" value="file"/> <env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/> <env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> --> <env name="DB_CONNECTION" value="sqlite"/>
<!-- <env name="DB_DATABASE" value=":memory:"/> --> <env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/> <env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/> <env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>

357
public/cc_spa_init.sql Normal file
View File

@@ -0,0 +1,357 @@
/*
Navicat Premium Dump SQL
Source Server : 测试库
Source Server Type : MySQL
Source Server Version : 80404 (8.4.4)
Source Host : 101.43.12.11:3306
Source Schema : cc_spa
Target Server Type : MySQL
Target Server Version : 80404 (8.4.4)
File Encoding : 65001
Date: 07/05/2025 14:54:10
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for categories
-- ----------------------------
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类名称',
`pid` int NOT NULL DEFAULT 0 COMMENT '父级分类',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '更新时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品分类' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of categories
-- ----------------------------
INSERT INTO `categories` VALUES (1, 'Web 应用', 0, 0, 0, 0);
INSERT INTO `categories` VALUES (2, '移动端', 0, 0, 0, 0);
INSERT INTO `categories` VALUES (3, '微信小程序', 0, 0, 0, 0);
-- ----------------------------
-- Table structure for collection
-- ----------------------------
DROP TABLE IF EXISTS `collection`;
CREATE TABLE `collection` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`user_id` int NOT NULL DEFAULT 0 COMMENT '用户ID',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户收藏表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of collection
-- ----------------------------
-- ----------------------------
-- Table structure for features
-- ----------------------------
DROP TABLE IF EXISTS `features`;
CREATE TABLE `features` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '说明',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '核心功能表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of features
-- ----------------------------
-- ----------------------------
-- Table structure for file
-- ----------------------------
DROP TABLE IF EXISTS `file`;
CREATE TABLE `file` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int NOT NULL DEFAULT 0 COMMENT '上传者ID',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目截图地址',
`type` int NOT NULL DEFAULT 0 COMMENT '类型 0图片 1视频 2压缩包 3excel 4pdf',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文件表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of file
-- ----------------------------
-- ----------------------------
-- Table structure for frameworks
-- ----------------------------
DROP TABLE IF EXISTS `frameworks`;
CREATE TABLE `frameworks` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '框架名称',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '更新时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '框架表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of frameworks
-- ----------------------------
INSERT INTO `frameworks` VALUES (1, 'Laravel', 1745829979, 0, 0);
-- ----------------------------
-- Table structure for labels
-- ----------------------------
DROP TABLE IF EXISTS `labels`;
CREATE TABLE `labels` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标签名称',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '更新时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '标签表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of labels
-- ----------------------------
INSERT INTO `labels` VALUES (1, 'PHP', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (2, 'VUE', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (3, 'Golang', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (4, 'JAVA', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (5, 'HTML', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (6, 'Docker', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (7, 'Mysql', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (8, 'Redis', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (9, 'RabbitMQ', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (10, 'MongoDB', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (11, 'Javascript', 1746424986, 0, 0);
INSERT INTO `labels` VALUES (12, 'Type Javascript', 1746424986, 0, 0);
-- ----------------------------
-- Table structure for order_items
-- ----------------------------
DROP TABLE IF EXISTS `order_items`;
CREATE TABLE `order_items` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_id` int NOT NULL DEFAULT 0 COMMENT '订单ID',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`price` decimal(50, 3) NOT NULL DEFAULT 0.000 COMMENT '价格',
`preferential_price` decimal(50, 2) NOT NULL DEFAULT 0.00 COMMENT '优惠价格',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单详情表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of order_items
-- ----------------------------
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '订单号',
`user_id` int NOT NULL DEFAULT 0 COMMENT '用户ID',
`total_price` decimal(50, 3) NOT NULL DEFAULT 0.000 COMMENT '总价',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态 0待支付 1已支付 2已交付 3已完成 4已取消',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of orders
-- ----------------------------
-- ----------------------------
-- Table structure for partners
-- ----------------------------
DROP TABLE IF EXISTS `partners`;
CREATE TABLE `partners` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '合作伙伴名称',
`logo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'LOGO',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '更新时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '合作伙伴表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of partners
-- ----------------------------
-- ----------------------------
-- Table structure for project_frameworks_relations
-- ----------------------------
DROP TABLE IF EXISTS `project_frameworks_relations`;
CREATE TABLE `project_frameworks_relations` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`framework_id` int NOT NULL COMMENT '框架ID',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '项目框架关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of project_frameworks_relations
-- ----------------------------
-- ----------------------------
-- Table structure for project_label_relations
-- ----------------------------
DROP TABLE IF EXISTS `project_label_relations`;
CREATE TABLE `project_label_relations` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`label_id` int NOT NULL COMMENT '标签ID',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 33 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '标签项目关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of project_label_relations
-- ----------------------------
-- ----------------------------
-- Table structure for projects
-- ----------------------------
DROP TABLE IF EXISTS `projects`;
CREATE TABLE `projects` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int NOT NULL DEFAULT 0 COMMENT '用户ID',
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '项目标题',
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '项目封面',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目简介',
`video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '视频解说地址',
`author_id` int NOT NULL DEFAULT 0 COMMENT '开发者',
`category_id` int NOT NULL DEFAULT 0 COMMENT '项目分类',
`price` decimal(50, 3) NOT NULL DEFAULT 0.000 COMMENT '价格',
`preferential_price` decimal(50, 2) NOT NULL DEFAULT 0.00 COMMENT '优惠价格',
`front` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '前端技术栈',
`service` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '服务端',
`deploy` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '0' COMMENT '部署',
`is_selected` tinyint(1) NOT NULL DEFAULT 0 COMMENT '精选 0否 1',
`is_carousel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '轮播图 0否 1',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态 0草稿 1上架 2下架 3重构中',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '项目表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of projects
-- ----------------------------
-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称',
`value` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色值',
`pid` int NOT NULL DEFAULT 0 COMMENT '上级角色',
`desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色说明',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '更新时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of roles
-- ----------------------------
INSERT INTO `roles` VALUES (1, '超级管理员', 'admin', 0, '站长', 1745829979, 0, 0);
INSERT INTO `roles` VALUES (2, '用户', 'user', 0, '普通用户', 1745829980, 0, 0);
-- ----------------------------
-- Table structure for screenshots
-- ----------------------------
DROP TABLE IF EXISTS `screenshots`;
CREATE TABLE `screenshots` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目截图地址',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '项目截图表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of screenshots
-- ----------------------------
-- ----------------------------
-- Table structure for user_pay
-- ----------------------------
DROP TABLE IF EXISTS `user_pay`;
CREATE TABLE `user_pay` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`project_id` int NOT NULL DEFAULT 0 COMMENT '项目ID',
`user_id` int NOT NULL DEFAULT 0 COMMENT '用户ID',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户购买表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_pay
-- ----------------------------
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '账号',
`nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵称',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '头像',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '邮箱',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '登录密码',
`balance` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '余额',
`qr_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '微信二维码地址',
`role_id` int NOT NULL DEFAULT 0 COMMENT '角色',
`is_sys_notifications` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否接受邮件系统通知 0不接受 1接受',
`is_collection_notifications` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否接受收藏的项目更新信息 0不接受 1接受',
`is_marketing_notifications` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否接受营销信息 0不接受 1接受',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '本次登录ip',
`ip_table` json NOT NULL COMMENT '常用登录IP地址列表',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态 0正常 1冻结 2封号 3注销',
`last_reset_password_at` int NOT NULL DEFAULT 0 COMMENT '最后一次修改密码的时间',
`created_at` int NOT NULL DEFAULT 0 COMMENT '注册时间',
`updated_at` int NOT NULL DEFAULT 0 COMMENT '编辑时间',
`deleted_at` int NOT NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of users
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;

28
routes/api.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
use App\Service\common\UtilsService;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
/**
* 自动注册路由
*/
UtilsService::class::getInstance()->autoRouteRegister([
'' => \App\Http\Controllers\LoginController::class, // 登录控制器
'home' => \App\Http\Controllers\HomeController::class, // 首页控制器
]);
Route::group([], function () {
UtilsService::class::getInstance()->autoRouteRegister([
'user' => \App\Http\Controllers\UserController::class, // 用户控制器
'project' => \App\Http\Controllers\ProjectController::class, // 项目控制器
'role' => \App\Http\Controllers\RoleController::class, // 角色控制器
'upload' => \App\Http\Controllers\UploadController::class, // 上传控制器
'label' => \App\Http\Controllers\LabelController::class, // 标签控制器
'category' => \App\Http\Controllers\CategoryController::class, // 分类控制器
'collection' => \App\Http\Controllers\CollectionController::class, // 收藏控制器
]);
});

View File

@@ -1,13 +0,0 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
tailwindcss(),
],
});