初始化项目

This commit is contained in:
2025-05-12 00:19:35 +08:00
parent f6e4638ee6
commit fc327e9f48
27 changed files with 11107 additions and 29 deletions

View File

@@ -1,18 +1,17 @@
APP_NAME=Laravel
APP_NAME=Xiaokang
APP_ENV=local
APP_KEY=
APP_KEY=base64:X9vy9lAitu0bA6J1/vqsiOHdNnc3XOrLihHKRGYHdbY=
APP_DEBUG=true
APP_TIMEZONE=Asia/Shanghai
APP_URL=http://localhost
ECS=dev
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
# 修改后
APP_LOCALE=zh_CN
APP_FALLBACK_LOCALE=zh_CN
APP_FAKER_LOCALE=zh_CN
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
@@ -20,14 +19,15 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
# 新的数据库
DB_CONNECTION=mysql
DB_HOST=mysql8
DB_PORT=3306
DB_DATABASE=cc_admin
DB_USERNAME=root
DB_PASSWORD=root
SESSION_DRIVER=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
@@ -35,25 +35,25 @@ SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
CACHE_STORE=redis
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_HOST=redis
REDIS_PASSWORD=GS2H37yZAxNPBtXA
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
# 邮件发送配置
MAIL_MAILER=smtp
MAIL_HOST=smtp.163.com
MAIL_PORT=465
MAIL_USERNAME=qiqiqiyoucili@163.com
MAIL_PASSWORD=NZbPs32frizAaFM9
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=qiqiqiyoucili@163.com
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
@@ -63,3 +63,20 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
# 消息队列 配置
#QUEUE_CONNECTION=rabbitmq
#RABBITMQ_HOST=172.26.47.10
#RABBITMQ_PORT=15672
#RABBITMQ_VHOST=/
#RABBITMQ_QUEUE=xk-api
#RABBITMQ_USER=xk_api
#RABBITMQ_PASSWORD=qiqi991012
# BaseUrl
#BASE_OLD_API_URL=https://app.xiaokang88.com
BASE_OLD_API_URL=http://www.xk888.com
BASE_OLD_SHOP_API_URL=https://shop.xiaokang88.com
# 密钥
ENCRYPT_KEY=3a8f9b2d7c1e4f8a9b2d7c1e4f8a9b2d7c1e4f8a9b2d7c1e4f8a9b2d7c1e4f8a

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];
}
}

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

@@ -0,0 +1,169 @@
<?php
namespace App\BaseApp;
use App\Service\common\UtilsService;
use Exception;
use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
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 (mb_strlen(strval($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 => '资源被删除',
};
}
}

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,57 @@
<?php
namespace App\Http\Controllers\Api;
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,89 @@
<?php
namespace App\Http\Controllers\Api;
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(),
'获取成功'
);
}
}

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

@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use App\BaseApp\BaseModel;
class UserModel extends BaseModel
{
protected $table = 'cc_admin';
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $guarded = [];
}

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;
}
}

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
use App\Enum\ErrorEnum;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
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__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
@@ -14,5 +20,58 @@ return Application::configure(basePath: dirname(__DIR__))
//
})
->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();

8086
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' => ''
]
],
];

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) : '';
}
}

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));
}
}

191
public/cc_admin.sql Normal file
View File

@@ -0,0 +1,191 @@
/*
Navicat Premium Dump SQL
Source Server : 本地
Source Server Type : MySQL
Source Server Version : 80405 (8.4.5)
Source Host : localhost:3306
Source Schema : cc_admin
Target Server Type : MySQL
Target Server Version : 80405 (8.4.5)
File Encoding : 65001
Date: 12/05/2025 00:18:57
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for cc_admin
-- ----------------------------
DROP TABLE IF EXISTS `cc_admin`;
CREATE TABLE `cc_admin` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`open_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OpenID后期整合萧康服务中心会用到',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像',
`nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`phone` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户手机',
`email` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱',
`code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '业务员推广码',
`role_id` int NOT NULL DEFAULT 0 COMMENT '角色',
`province_id` int NOT NULL DEFAULT 0 COMMENT '',
`city_id` int NOT NULL DEFAULT 0 COMMENT '',
`reg_ip` bigint NOT NULL DEFAULT 0 COMMENT '注册IP',
`last_login_time` int NOT NULL DEFAULT 0 COMMENT '最后登录时间',
`last_login_ip` bigint NOT NULL DEFAULT 0 COMMENT '最后登录IP',
`operation_password` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '操作密码',
`desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '用户状态 0正常 1禁用',
`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 = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '管理员表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cc_admin
-- ----------------------------
-- ----------------------------
-- Table structure for xk_admin_notice
-- ----------------------------
DROP TABLE IF EXISTS `xk_admin_notice`;
CREATE TABLE `xk_admin_notice` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '消息标题',
`detail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '简介',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '消息内容',
`type` int NOT NULL DEFAULT 0 COMMENT '消息类型',
`user_id` int NOT NULL DEFAULT 0 COMMENT '关联用户ID',
`status` int NOT NULL DEFAULT 0 COMMENT '状态 0未读 1已读',
`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 = 84 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '管理员消息列表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_admin_notice
-- ----------------------------
-- ----------------------------
-- Table structure for xk_api_op_log
-- ----------------------------
DROP TABLE IF EXISTS `xk_api_op_log`;
CREATE TABLE `xk_api_op_log` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int NOT NULL COMMENT '操作用户ID',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '操作路由',
`method` tinyint(1) NOT NULL DEFAULT 0 COMMENT '请求方式 0未知 1GET 2POST',
`controller` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '操作的控制器',
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '操作者IP地址',
`param` json NOT NULL COMMENT '请求携带参数',
`result` json NOT NULL COMMENT '返回json',
`type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '操作状态 0成功 1失败',
`result_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '返回状态码',
`platform_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '平台类型 0总后台 1门店后台 2小程序',
`belong_id` int NOT NULL DEFAULT 0 COMMENT '所属平台ID',
`user_type` int NOT NULL DEFAULT 0 COMMENT '用户类型',
`equipment` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '操作系统',
`browser` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '浏览器',
`created_at` int NOT NULL DEFAULT 0 COMMENT '操作时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16860 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '新的后台API访问日志记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_api_op_log
-- ----------------------------
-- ----------------------------
-- Table structure for xk_file
-- ----------------------------
DROP TABLE IF EXISTS `xk_file`;
CREATE TABLE `xk_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 DEFAULT '' COMMENT '文件地址',
`type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '文件类型 0图片 1视频 2音频 3excel 4压缩包 ',
`source` tinyint(1) NOT NULL COMMENT '来源 0后台 1用户端 2医生端小程序 3医生PC端 4旧的后台',
`created_at` int NOT NULL 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 = 84 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '文件表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_file
-- ----------------------------
-- ----------------------------
-- Table structure for xk_menu
-- ----------------------------
DROP TABLE IF EXISTS `xk_menu`;
CREATE TABLE `xk_menu` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '菜单标题',
`icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '菜单图标',
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '页面Name全局唯一',
`path` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '访问路由',
`component` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '组件路径需要去掉 views/ 和 .vue',
`redirect` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '父级菜单重定向的子集菜单',
`keep_alive` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否开启页面缓存 0开启 1关闭',
`hide_in_menu` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否将页面展示在菜单栏 0展示 1隐藏',
`affix_tab` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否为置顶页 0是 1',
`badge` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '菜单的徽标',
`badge_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '用于配置页面的徽标类型0dot 小红点 1normal 文本',
`badge_variants` tinyint(1) NOT NULL DEFAULT 0 COMMENT '用于配置页面的徽标颜色 \r\n0default 1destructive 2primary 3success 4 warning',
`iframe_src` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '内嵌的页面路径',
`pid` int NOT NULL DEFAULT 0 COMMENT '父级菜单id',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序',
`query` 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 = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_menu
-- ----------------------------
-- ----------------------------
-- Table structure for xk_role
-- ----------------------------
DROP TABLE IF EXISTS `xk_role`;
CREATE TABLE `xk_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '用户类型 1诊所 2平台 3供应商',
`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 = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_role
-- ----------------------------
-- ----------------------------
-- Table structure for xk_role_menu_relations
-- ----------------------------
DROP TABLE IF EXISTS `xk_role_menu_relations`;
CREATE TABLE `xk_role_menu_relations` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`role_id` int NOT NULL DEFAULT 0 COMMENT '角色ID',
`menu_id` int NOT NULL DEFAULT 0 COMMENT '菜单ID',
`created_at` int NOT NULL DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 65 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色权限绑定表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of xk_role_menu_relations
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;

21
routes/api.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
use App\Service\common\UtilsService;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
/**
* 自动注册路由
*/
UtilsService::class::getInstance()->autoRouteRegister([
'' => \App\Http\Controllers\Api\LoginController::class, // 登录控制器
]);
Route::group([], function () {
UtilsService::class::getInstance()->autoRouteRegister([
'user' => \App\Http\Controllers\Api\UserController::class, // 用户控制器
]);
});