样机小程序

This commit is contained in:
2025-12-08 17:37:34 +08:00
commit d19bb9046b
175 changed files with 19427 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace app\common\service;
use app\common\validate\AgreementValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Agreement;
use support\Request;
class AgreementService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Agreement::class;
/**
* 验证器
*/
protected $validator = AgreementValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'show':
if (isset($p['type']) && !empty($p['type'])) {
$config['where']['type'] = $p['type'];
}
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace app\common\service;
use app\common\logic\AuthLogic;
use app\common\model\User;
use app\common\utils\Feishu;
use support\Db;
class AuthService
{
/**
* 飞书授权
*/
public function feishuAuthorize(string $corpAbbr, string $systemCcode)
{
$logic = new AuthLogic();
$certificate = $logic->feishuAppInfo($corpAbbr, $systemCcode);
$feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']);
return ['auth_url' => $feishu->authorize($certificate)];
}
/**
* 登录,支持多类型
*/
public function login(array $data): array
{
$loginType = $data['login_type'] ?? 'upwd';
$logic = new AuthLogic();
return match ($loginType) {
default => $logic->uPwdLogin($data),
};
}
/**
* 用户注册
*/
public function register(array $data)
{
$logic = new AuthLogic();
try {
Db::beginTransaction();
$result = $logic->register($data);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
/**
* 校验token
*/
public function verifyToken(string $token, string $systemCode): array
{
return (new AuthLogic())->verifyToken($token, $systemCode);
}
/**
* 刷新token
* @param string $refreshToken
* @param string|null $systemCode
*/
public function refreshToken(string $refreshToken, string $systemCode): array
{
return (new AuthLogic())->refreshToken($refreshToken, $systemCode);
}
/**
* 登出(失效token)
*/
public function logout(?string $token, string $systemCode): void
{
(new AuthLogic())->logout($token, $systemCode);
}
/**
* 强制拉黑token
*/
public function blockToken(string $token, string $systemCode): void
{
(new AuthLogic())->blockToken($token, $systemCode);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace app\common\service;
use app\common\model\BannerModule;
use app\common\validate\BannerValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Banner;
class BannerService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Banner::class;
/**
* 验证器
*/
protected $validator = BannerValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
public function getBanner($params){
$where = [];
if(isset($params['module'])){
$where['module'] = $params['module'];
}
return Banner::where('status','1')->where($where)->get();
}
public function getBannerModule(){
return BannerModule::where('status','1')->get();
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace app\common\service;
use app\common\model\Corp;
use app\common\model\Systems;
use support\Redis;
use support\exception\BusinessException;
class BasicDataService
{
// 定义Redis键名前缀常量
const LOGIN_TYPE_KEY = 'bsh_info_login_types';
const SYSTEM_CODE_KEY = 'bsh_info_system_codes';
/**
* 检查并缓存独立的基础参数
*/
public function checkAndCacheBaseParams($loginType, $systemCode)
{
if (!$loginType && !$systemCode) {
throw new BusinessException('header缺少必要参数');
}
// 检查并缓存登录类型
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
$this->cacheLoginTypes();
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
throw new BusinessException('无效的登录方式');
}
}
// 检查并缓存系统码
if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) {
$this->cacheSystemCodes();
if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) {
throw new BusinessException('无效的系统码');
}
}
return true;
}
/**
* 检查参数是否在Redis集合中
*/
private function checkParamInCache($cacheKey, $param)
{
return Redis::sIsMember($cacheKey, $param);
}
/**
* 缓存所有登录类型到集合
*/
private function cacheLoginTypes()
{
$cacheKey = self::LOGIN_TYPE_KEY;
// 如果集合已存在,直接返回
// if (Redis::exists($cacheKey)) {
// return;
// }
$loginTypes = $this->getAllLoginTypes();
if (empty($loginTypes)) {
throw new BusinessException('未获取到登录类型数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$loginTypes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 缓存所有系统码到集合
*/
private function cacheSystemCodes()
{
$cacheKey = self::SYSTEM_CODE_KEY;
// 如果集合已存在,直接返回
// if (Redis::exists($cacheKey)) {
// return;
// }
$systemCodes = $this->getAllSystemCodes();
if (empty($systemCodes)) {
throw new BusinessException('未获取到系统码数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$systemCodes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 获取所有有效的登录类型
*/
private function getAllLoginTypes()
{
return ['upwd', 'feishu', 'wechat', 'qrcode']; // 示例数据
}
/**
* 获取所有有效的系统码
*/
private function getAllSystemCodes()
{
return Systems::pluck('code')->toArray();
}
/**
* 获取所有有效的公司标识
*/
private function getAllCorpAbbrs()
{
return Corp::pluck('abbr')->toArray();
}
}
@@ -0,0 +1,92 @@
<?php
namespace app\common\service;
use app\common\validate\MessageNoticeValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\MessageNotice;
use support\Request;
class MessageNoticeService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = MessageNotice::class;
/**
* 验证器
*/
protected $validator = MessageNoticeValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 分页列表 */
public function serviceSelfList(Request $request, string $scene = 'self')
{
$result = $this->cqyProcess(ucfirst($scene), $request);
if ($result === false) {
$config = $this->buildQueryConfig($request, $scene);
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$result = $this->logicList($config, $limit, $page);
}
return $this->postProcess(ucfirst($scene), $result);
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['order']['id'] = 'desc';
break;
case 'self':
$config['where']['receiver_user_id'] = $request->user->uid;
$config['order']['id'] = 'desc';
break;
case 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加后置处理
*/
public function beforeServiceSelf($data, Request $request)
{
MessageNotice::where('receiver_user_id', $request->user->uid)->update(['is_read' => 1]);
return $data;
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace app\common\service;
use OSS\OssClient;
use OSS\Core\OssException;
use support\exception\BusinessException;
class OssService
{
private $accessKeyId;
private $accessKeySecret;
private $bucket;
private $endpoint;
private $domain;
private $ossClient;
public function __construct()
{
$this->accessKeyId = getenv('OSS_ACCESSID');
$this->accessKeySecret = getenv('OSS_ACCESSSECRET');
$this->bucket = getenv('OSS_BUCKET');
$this->endpoint = getenv('OSS_ENDPOINT');
$this->domain = getenv('OSS_DOMAIN');
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
}
/**
* 上传图片到 OSS
* @param string $filePath 本地文件路径
* @param string $objectKey OSS上存储的对象名称
* @return string 图片的 URL
*/
public function uploadImage($filePath, $objectKey)
{
try {
// 上传文件到 OSS
$this->ossClient->uploadFile($this->bucket, $objectKey, $filePath);
// 构造图片的私有 URL
$url = $this->generatePrivateUrl($objectKey);
return $url;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Error: ' . $th->getMessage());
}
}
/**
* 生成图片的私有访问 URL
* @param string $objectKey
* @return string
*/
public function generatePrivateUrl($objectKey, $expireTime = 3600)
{
// 生成 OSS 私有 URL(有效期为 3600 秒)
$originalUrl = $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime);
// // 使用正则表达式替换
$originalUrl = preg_replace(
'#^https?://[^/]+#',
'https://static.hnzhwlkj.cn',
$originalUrl
);
return $originalUrl;
}
/**
* 删除 OSS 上的文件
* @param string $objectKey OSS上存储的对象名称
* @return bool 删除是否成功
*/
public function deleteFile($objectKey)
{
try {
// 删除 OSS 上的文件
$this->ossClient->deleteObject($this->bucket, $objectKey);
return true;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Delete Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Delete Error: ' . $th->getMessage());
}
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\common\service;
use app\common\logic\PermLogic;
use support\Cache;
class PermService
{
protected $logic;
public function __construct()
{
$this->logic = (new PermLogic());
}
// 用户资源缓存key
protected static function cacheKey(int $userId, string $systemCode): string
{
return "user_resources_{$userId}_{$systemCode}";
}
/**
* 获取用户所有资源(合并角色+直接分配),结果缓存
*/
public function getUserResources(int $userId, string $systemCode)
{
$cacheKey = self::cacheKey($userId, $systemCode);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
// 调用 Logic 层获取资源
$resources = $this->logic->getUserResources($userId, $systemCode);
// 缓存结果
Cache::set($cacheKey, $resources, 3600);
return $resources;
}
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public function getMenuTree(int $userId, string $systemCode): array
{
// 调用 Logic 层处理菜单构建
$resources = $this->getUserResources($userId, $systemCode);
return $this->logic->buildTree($resources);
}
/**
* 获取用户所有权限code
*/
public function getPermissionCodes(int $userId, string $systemCode): array
{
return $this->logic->getPermissionCodes($userId, $systemCode);
}
/**
* 清除资源相关缓存
*/
public function clearCache(int $userId, string $systemCode): void
{
Cache::delete(self::cacheKey($userId, $systemCode));
}
}
@@ -0,0 +1,89 @@
<?php
namespace app\common\service;
use app\common\logic\PostCategoryLogic;
use app\common\model\NavItems;
use app\common\validate\PostCategoryValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostCategory;
use support\Request;
class PostCategoryService
{
use CrudServiceTrait;
protected $logic;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostCategory::class;
/**
* 验证器
*/
protected $validator = PostCategoryValidate::class;
public function __construct()
{
$this->logic = (new PostCategoryLogic());
}
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取系统list
*/
public function getList($params)
{
$where = [];
if(isset($params['parent_id']) && $params['parent_id']) {
$where['parent_id'] = $params['parent_id'];
}else{
$where['parent_id'] = 0;
}
$result = $this->logic->getList($where)->toArray();
foreach ($result as $key => $value) {
$where = [];
$where['parent_id'] = $value['id'];
$result[$key]['children'] = $this->logic->getList($where)->toArray();
foreach ( $result[$key]['children'] as $k => $v) {
$where = [];
$where['parent_id'] = $v['id'];
$result[$key]['children'][$k]['children'] = $this->logic->getList($where)->toArray();
}
}
return $result;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$brand_id = $data['parent_id'] ?? 0;
if($brand_id == 0) {
$data['level'] = 1;
$data['path'] = '';
return $data;
}
$parent = PostCategory::where('id', $data['parent_id'])->first();
$parent['path'] = !empty($parent['path'])?:'/';
$data['level'] = $parent['level']+1;
$data['path'] = $parent['path'].$parent['id'].'/';
if($data['level'] > 3) {
throw new \Exception("分类层级不能超过三层");
}
return $data;
}
}
+157
View File
@@ -0,0 +1,157 @@
<?php
namespace app\common\service;
use app\common\model\MessageNotice;
use app\common\model\Post;
use app\common\validate\PostCommentValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostComment;
use support\Redis;
use support\Request;
class PostCommentService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostComment::class;
/**
* 验证器
*/
protected $validator = PostCommentValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
// 帖子
if (isset($p['post_id']) && !empty($p['post_id'])) {
$config['where']['post_id'] = $p['post_id'];
}
// 用户
if (isset($p['user_id']) && !empty($p['user_id'])) {
$config['where']['user_id'] = $p['user_id'];
}
// 只查询根评论
if (isset($p['parent_id']) && $p['parent_id'] == 0) {
$config['where']['parent_id'] = 0;
}
// 根评论ID
if (isset($p['parent_id']) && !empty($p['parent_id'])) {
$config['where']['root_id'] = $p['parent_id'];
}
// 根评论ID
if (isset($p['received_id']) && !empty($p['received_id'])) {
$config['where']['id'] = ['not in', $p['received_id']];
}
// 根评论ID
if (isset($p['status']) && !empty($p['status'])) {
$config['where']['status'] = $p['status'];
}
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
$data['root_id'] = 0;
if(!empty($data['parent_id'])){
$data['root_id'] = PostComment::where('id', $data['parent_id'])->value('root_id');
if($data['root_id'] == 0){
$data['root_id'] = $data['parent_id'];
}
}
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceStore($res)
{
$data = [];
$data['post_id'] = $res['post_id'];
$data['receiver_user_id'] = Post::where(['id' => $data['post_id']])->value('user_id');
$data['sender_user_id'] = $res['user_id'];
$data['comment_id'] = $res['id'];
$data['type'] = 3;
MessageNotice::create($data);
$post = Post::where(['id' => $data['post_id']])->first();
$post['comment'] = $post['comment']+1;
$post->save();
}
/**
* 添加后置处理
*/
public function serviceDelete(Request $request)
{
$param = $request->all();
if (empty($param['id'])) {
throw new \Exception("参数错误");
}
$where = [];
$where['id'] = $param['id'];
$comment = PostComment::where($where)->first();
$post = Post::where(['id' => $comment['post_id']])->first();
$post['comment'] = $post['comment']-1;
$post->save();
$count = $comment->delete();
return $count;
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
namespace app\common\service;
use app\common\model\MessageNotice;
use app\common\model\Post;
use app\common\model\PostLike;
use app\common\validate\PostFavoriteValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostFavorite;
use support\Request;
class PostFavoriteService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostFavorite::class;
/**
* 验证器
*/
protected $validator = PostFavoriteValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 分页列表 */
public function serviceSelfList(Request $request, string $scene = 'self')
{
$result = $this->cqyProcess(ucfirst($scene), $request);
if ($result === false) {
$config = $this->buildQueryConfig($request, $scene);
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$result = $this->logicList($config, $limit, $page);
}
return $this->postProcess(ucfirst($scene), $result);
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['order']['id'] = 'desc';
break;
case 'self':
$config['where']['user_id'] = $request->user->uid;
$config['order']['id'] = 'desc';
break;
case 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceStore($res)
{
$data = [];
$data['post_id'] = $res['post_id'];
$data['receiver_user_id'] = Post::where(['id' => $data['post_id']])->value('user_id');
$data['sender_user_id'] = $res['user_id'];
$data['type'] = 2;
MessageNotice::create($data);
$post = Post::where(['id' => $data['post_id']])->first();
$post['favorite'] = $post['favorite']+1;
$post->save();
}
/**
* 添加后置处理
*/
public function serviceDelete(Request $request)
{
$param = $request->all();
if (empty($param['post_id']) && empty($param['user_id'])) {
throw new \Exception("参数错误");
}
$where = [];
$where['post_id'] = $param['post_id'];
$where['user_id'] = $request->user->uid;
$count = PostFavorite::where($where)->delete();
if ($count) {
$post = Post::where(['id' => $param['post_id']])->first();
$post['favorite'] = $post['favorite']-1;
$post['favorite'] = $post['favorite']<0?0:$post['favorite'];
$post->save();
}
return $count;
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
namespace app\common\service;
use app\common\model\MessageNotice;
use app\common\model\Post;
use app\common\model\PostMedia;
use app\common\validate\PostLikeValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostLike;
use support\Redis;
use support\Request;
class PostLikeService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostLike::class;
/**
* 验证器
*/
protected $validator = PostLikeValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 分页列表 */
public function serviceSelfList(Request $request, string $scene = 'self')
{
$result = $this->cqyProcess(ucfirst($scene), $request);
if ($result === false) {
$config = $this->buildQueryConfig($request, $scene);
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$result = $this->logicList($config, $limit, $page);
}
return $this->postProcess(ucfirst($scene), $result);
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['order']['id'] = 'desc';
break;
case 'self':
$config['where']['user_id'] = $request->user->uid;
$config['order']['id'] = 'desc';
break;
case 'index':
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceStore($res)
{
$data = [];
$data['post_id'] = $res['post_id'];
$data['receiver_user_id'] = Post::where(['id' => $data['post_id']])->value('user_id');
$data['sender_user_id'] = $res['user_id'];
$data['type'] = 1;
MessageNotice::create($data);
$post = Post::where(['id' => $data['post_id']])->first();
$post['like'] = $post['like']+1;
$post->save();
}
/**
* 添加后置处理
*/
public function serviceDelete(Request $request)
{
$param = $request->all();
if (empty($param['post_id']) && empty($param['user_id'])) {
throw new \Exception("参数错误");
}
$where = [];
$where['post_id'] = $param['post_id'];
$where['user_id'] = $request->user->uid;
$count = PostLike::where($where)->delete();
if ($count) {
$post = Post::where(['id' => $param['post_id']])->first();
$post['like'] = $post['like']-1;
$post['like'] = $post['like']<0?0:$post['like'];
$post->save();
}
return $count;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\PostMediaValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostMedia;
class PostMediaService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostMedia::class;
/**
* 验证器
*/
protected $validator = PostMediaValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+200
View File
@@ -0,0 +1,200 @@
<?php
namespace app\common\service;
use app\common\model\PostMedia;
use app\common\model\User;
use app\common\validate\PostValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Post;
use support\exception\BusinessException;
use support\Redis;
use support\Request;
class PostService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Post::class;
/**
* 验证器
*/
protected $validator = PostValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'index':
if (isset($p['category_id']) && $p['category_id']) {
$config['where']['category_id'] = $p['category_id'];
}
if (isset($p['scene']) && $p['scene']=='self') {
if(!empty($request->user) && !empty($request->user->uid)){
$config['where']['user_id'] = $request->user->uid;
}else{
$config['where']['user_id'] = -1;
}
}
if(isset($p['year']) && $p['year']){
$from = strtotime($p['year'].'-01-01');
$to = strtotime($p['year'].'-12-31 23:59:59');
$config['where']['created_at'] = [
'between', [$from, $to]
];
}
if (isset($p['keyword']) && $p['keyword']) {
$config['where']['content'] = ['like', '%'.$p['keyword'].'%'];
}
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
if (isset($p['favorite']) && $p['favorite']) {
$config['order']['favorite'] = 'desc';
} elseif (isset($p['browse']) && $p['browse']) {
$config['order']['browse'] = 'desc';
} else {
$config['order']['id'] = 'desc';
}
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
// $user = User::where('id', $data['user_id'])->first();
// if($user->is_release == 0){
// throw new BusinessException('无发布权限');
// }
$media = serialize($data['media']);
redis::set('PostMedia_'.$data['user_id'],$media,30);
unset($data['media']);
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceStore($res)
{
$media = redis::get('PostMedia_'.$res['user_id']);
$media = unserialize($media);
if(!is_array($media)){
$media = explode(',',$media);
}
foreach ($media as $v) {
PostMedia::create([
'post_id' => $res->id,
'media_type' => $this->getMediaTypeByUrl($v),
'url' => $v,
]);
}
}
/**
* 添加前置处理
*/
public function beforeServiceUpdate($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
$media = serialize($data['media']);
redis::set('PostMedia_'.$data['user_id'],$media,30);
unset($data['media']);
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceUpdate($res)
{
PostMedia::where('post_id', $res->id)->delete();
$media = redis::get('PostMedia_'.$res['user_id']);
$media = unserialize($media);
foreach ($media as $v) {
PostMedia::create([
'post_id' => $res->id,
'media_type' => $this->getMediaTypeByUrl($v['url']),
'url' => $v['url'],
]);
}
}
/**
* 添加前置处理
*/
public function beforeServiceShow($data, Request $request)
{
$post = Post::find($data['id']);
if(!$post){
$post->browse = $post->browse+1;
$post->save();
}
return $data;
}
function getMediaTypeByUrl(string $url): int
{
// 提取后缀
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
// 图片类型
$imageExt = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
// 视频类型
$videoExt = ['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv'];
if (in_array($ext, $imageExt)) {
return 1; // 图片
}
if (in_array($ext, $videoExt)) {
return 2; // 视频
}
return 0; // 未知类型
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace app\common\service;
use app\common\logic\ResourceLogic;
use app\common\validate\ResourceValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Resource;
use app\common\utils\Tree;
use support\Request;
class ResourceService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Resource::class;
/**
* 验证器
*/
protected $validator = ResourceValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuList':
$config['where']['status'] = 1;
$config['where']['type'] = ['!=', 'permission'];
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuTreeList':
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
public function serviceMenuList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuList');
$result = $this->logicListAll($config);
return $this->postProcess('MenuList', $result);
}
public function serviceMenuTreeList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuTreeList');
$result = (new ResourceLogic())->logicListAllResources($request, $config);
return $this->postProcess('MenuTreeList', $result);
}
protected function afterServiceDefault($data)
{
$data = Tree::buildMenuTreeWithReference($data->toArray());
return $data;
}
protected function afterServiceMenuList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
protected function afterServiceMenuTreeList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
public function beforeServiceStore($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
public function beforeServiceUpdate($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\common\service;
use app\common\logic\RoleLogic;
use app\common\validate\RoleValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Role;
use support\Db;
class RoleService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Role::class;
/**
* 验证器
*/
protected $validator = RoleValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取角色资源
*/
public function menuResource($request, $data)
{
return (new RoleLogic)->updateRoleResources($data);
}
public function getList()
{
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
return $this->logicListAll($config);
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\SkuAttributeValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SkuAttribute;
class SkuAttributeService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = SkuAttribute::class;
/**
* 验证器
*/
protected $validator = SkuAttributeValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\SkuAttributeValueValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\SkuAttributeValue;
class SkuAttributeValueService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = SkuAttributeValue::class;
/**
* 验证器
*/
protected $validator = SkuAttributeValueValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\SkuValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Sku;
class SkuService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Sku::class;
/**
* 验证器
*/
protected $validator = SkuValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace app\common\service;
use app\common\logic\SystemsLogic;
class SystemsService
{
protected $logic;
public function __construct()
{
$this->logic = (new SystemsLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace app\common\service;
use support\exception\BusinessException;
use support\Log;
use support\Request;
class UploadService
{
/**
* 从服务器本地路径上传文件到 OSS
*
* @param Request $request
* - path: 本地文件绝对路径
* - scene: 业务分区/场景
* - category: 文件分类(可选)
* - original_name: 原始文件名(可选)
*/
public function uploadLocalFile($param)
{
try {
$filePath = $param['path'];
$scene = $param['scene'];
$category = $param['category']??'general';
$providedOriginalName = $param['original_name']??'';
if (empty($filePath)) {
Log::error('本地文件路径不能为空',$param);
throw new BusinessException('本地文件路径不能为空');
}
if (!file_exists($filePath) || !is_readable($filePath)) {
Log::error('本地文件不存在或不可读',$param);
throw new BusinessException('本地文件不存在或不可读');
}
if (empty($scene)) {
Log::error('上传场景不能为空',$param);
throw new BusinessException('上传场景不能为空');
}
$basename = basename($filePath);
$originalName = $providedOriginalName ?: $basename;
$extensionFromName = pathinfo($originalName, PATHINFO_EXTENSION);
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
$extension = $extensionFromName ?: $extensionFromPath;
$mime = mime_content_type($filePath) ?: 'application/octet-stream';
$size = filesize($filePath) ?: 0;
$sceneUrl = $scene ? ($scene . '/') : '';
$dir = 'bsh_sample/' . $sceneUrl . date('Ymd') . '/';
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
// 若提供的名称没有扩展名或扩展名不一致,则以推断扩展名为准
$safeBaseName = pathinfo($safeBaseName, PATHINFO_FILENAME) . '.' . $extension;
} elseif (!$extension && !pathinfo($safeBaseName, PATHINFO_EXTENSION)) {
// 实在没有扩展名信息时,保持原名
}
// 重名检测:同一目录、同名且同类型则在文件名后追加序号
$uploadedFilesService = new UploadedFilesService();
$candidateName = $safeBaseName;
$nameBody = pathinfo($safeBaseName, PATHINFO_FILENAME);
$nameExt = pathinfo($safeBaseName, PATHINFO_EXTENSION);
$counter = 1;
while (true) {
$candidatePath = $dir . $candidateName;
$existing = $uploadedFilesService->findFile(['file_path' => $candidatePath]);
if (!$existing) {
break;
}
// 若存在同路径同名,且类型也相同,则追加序号
if (isset($existing->file_type) && strtolower($existing->file_type) === strtolower($mime)) {
$suffix = ' (' . $counter . ')';
$candidateName = $nameBody . $suffix . ($nameExt !== '' ? ('.' . $nameExt) : '');
$counter++;
continue;
}
// 类型不同则允许同名,结束循环
break;
}
$objectKey = $dir . $candidateName;
$ossService = new OssService();
$ossService->uploadImage($filePath, $objectKey);
$privateUrl = $ossService->generatePrivateUrl($objectKey);
// 记录文件信息到数据库
$uploadedFilesService = new UploadedFilesService();
$fileInfo = [
'file_name' => basename($objectKey),
'file_path' => $objectKey,
'file_size' => $size,
'file_type' => $mime,
'original_name' => $originalName,
];
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, null, $scene, $category);
$data = [
'original_name' => $originalName,
'file_name' => $objectKey,
'file_url' => $privateUrl,
'file_id' => $recordResult['id'] ?? null,
'partition' => $scene,
'category' => $category,
];
return $data;
} catch (\Throwable $e) {
Log::error($e->getMessage(),$param);
throw new BusinessException($e->getMessage());
}
}
/**
* 删除 OSS 上的文件
*
* @param array $param
* - object_key: OSS 对象键名-oss路径(必需)
* - file_id: 数据库文件记录ID(可选,用于删除数据库记录)
* @return bool 删除是否成功
*
* 示例
* $uploadService = new UploadService();
*
* 只删除 OSS 文件
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/documents/20241201/abc123.pdf'
* ]);
*
* 同时删除 OSS 文件和数据库记录
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/images/20241201/abc123.jpg',
* 'file_id' => 123
* ]);
*/
public function deleteOssFile($objectKey,$fileId = null)
{
try {
if (empty($objectKey)) {
Log::error('OSS对象键名不能为空', $objectKey);
throw new BusinessException('OSS对象键名不能为空');
}
$uploadedFilesService = new UploadedFilesService();
// 如果提供了文件ID,同时删除数据库记录
if ($fileId) {
$uploadedFilesService->deleteFile($fileId);
}else{
$uploadedFilesService = new UploadedFilesService();
if (empty($fileId)) {
$where = ['file_path' => $objectKey];
$file = $uploadedFilesService->getFile($where);
$fileId = $file->id;
}
if(!empty($fileId)){
$uploadedFilesService->deleteFile($fileId);
}
}
// 删除 OSS 上的文件
$ossService = new OssService();
$ossService->deleteFile($objectKey);
Log::info('OSS文件删除成功', ['object_key' => $objectKey, 'file_id' => $fileId]);
return true;
} catch (\Throwable $e) {
Log::error('删除OSS文件失败: ' . $e->getMessage());
throw new BusinessException('删除OSS文件失败: ' . $e->getMessage());
}
}
}
+238
View File
@@ -0,0 +1,238 @@
<?php
namespace app\common\service;
use app\common\validate\UploadedFilesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\UploadedFiles;
use support\Request;
use support\exception\BusinessException;
class UploadedFilesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UploadedFiles::class;
/**
* 文件上传分区配置
*/
protected array $uploadPartitions = [
'image' => [
'path' => 'images',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
],
'document' => [
'path' => 'documents',
'max_size' => 10 * 1024 * 1024, // 10MB
'allowed_types' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
'extensions' => ['pdf', 'doc', 'docx']
],
'audio' => [
'path' => 'audios',
'max_size' => 20 * 1024 * 1024, // 20MB
'allowed_types' => ['audio/mp3', 'audio/wav', 'audio/ogg'],
'extensions' => ['mp3', 'wav', 'ogg']
],
'other' => [
'path' => 'others',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => [],
'extensions' => []
]
];
/**
* 记录文件信息到数据库
*
* @param array $fileInfo 文件信息
* @param Request|null $request 请求对象(可选)
* @param string $partition 分区名称
* @param string $category 文件分类
* @return array
* @throws BusinessException
*/
public function recordFileInfo(array $fileInfo, ?Request $request = null, string $partition = 'other', string $category = 'general'): array
{
// 验证必需的文件信息
$requiredFields = ['file_name', 'file_path', 'file_size', 'file_type'];
foreach ($requiredFields as $field) {
if (!isset($fileInfo[$field])) {
throw new BusinessException("缺少必需的文件信息字段: {$field}");
}
}
// 获取文件扩展名
$fileExtension = pathinfo($fileInfo['file_name'], PATHINFO_EXTENSION);
// 记录到数据库
$uploadIp = $request ? $request->getRealIp() : '0.0.0.0';
$userAgent = $request ? $request->header('User-Agent', '') : '';
$fileData = [
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
'partition' => $partition,
'upload_ip' => $uploadIp,
'user_agent' => $userAgent,
];
$uploadedFile = $this->logicCreate($fileData);
return [
'id' => $uploadedFile->id,
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_url' => getenv('MAIN_DOMAIN') . $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'partition' => $partition,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
];
}
/**
* 获取文件信息
*
* @param int $fileId
* @return array|null
*/
public function getFileInfo(int $fileId): ?array
{
$file = $this->logicShow(['where' => ['id' => $fileId]]);
if (!$file) {
return null;
}
return [
'id' => $file->id,
'file_name' => $file->file_name,
'file_path' => $file->file_path,
'file_url' => getenv('MAIN_DOMAIN') . $file->file_path,
'file_size' => $file->file_size,
'file_type' => $file->file_type,
'category' => $file->category,
'partition' => $file->partition ?? 'other',
'original_name' => $file->original_name ?? '',
'extension' => $file->extension ?? '',
'created_at' => $file->created_at,
];
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function getFile(array $where)
{
$config['model'] = $this->modelClass;
$config['where'] = $where;
$file = $this->logicShow($config);
if (!$file) {
return false;
}
return $file;
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function findFile(array $where)
{
$file = UploadedFiles::where(['file_path' => $where['file_path']]);
if (!$file) {
return false;
}
return $file;
}
/**
* 删除文件
*
* @param int $fileId
* @return bool
*/
public function deleteFile(int $fileId): bool
{
$config['model'] = $this->modelClass;
$config['where'] = ['id' => $fileId];
$file = $this->logicShow($config);
if (!$file) {
return false;
}
// 删除数据库记录
$config =[
'model' => $this->modelClass,
'where' => ['id' => $fileId],
];
return $this->logicDeleteBy($config);
}
/**
* 获取分区配置
*
* @return array
*/
public function getPartitionConfig(): array
{
return $this->uploadPartitions;
}
/**
* 生成文件名
*
* @param $file
* @return string
*/
protected function generateFileName($file): string
{
$extension = $file->getUploadExtension();
$timestamp = time();
$random = substr(md5(uniqid()), 0, 8);
return $timestamp . '_' . $random . '.' . $extension;
}
/**
* 格式化文件大小
*
* @param int $bytes
* @return string
*/
protected function formatFileSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
}
+315
View File
@@ -0,0 +1,315 @@
<?php
namespace app\common\service;
use app\common\logic\AuthLogic;
use app\common\validate\UserValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\User;
use app\common\logic\UserLogic;
use app\common\utils\Jwt;
use support\exception\BusinessException;
use support\Request;
class UserService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
protected $logic;
// 字符集配置
protected static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
public function __construct()
{
$this->logic = (new UserLogic());
}
/**
* 验证器
*/
protected $validator = UserValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取微信二维码
*/
public function makeQrUrl()
{
$wechat = new WechatService();
return $wechat->makeQrUrl();
}
/**
* 获取用户信息
*/
public function userInfo(string $code, string $state)
{
$logic = new UserLogic();
$accessToken = $logic->getAccessToken($code, $state);
// $accessToken['openid'] = 'op9DT6xBzVF05dypmQ2JsM5DP6yw';
// $accessToken['unionid'] = 'o04nT6-St_2MZR6VRduYEwiicmiA';
$userInfo = $logic->checkUser($accessToken['openid'], $accessToken['unionid']);
if ($userInfo === false) {
return [
'is_bind' => false,
'access_token' => $accessToken['access_token'],
'openid' => $accessToken['openid'],
'unionid' => $accessToken['unionid'],
];
}
$jwt = new Jwt();
$token = $jwt->generateToken($logic->buildJwtPayload($userInfo));
$refreshToken = (new AuthLogic())->generateRefreshToken($userInfo->id, 'board');
return array_merge($userInfo->toArray(), ['access_token' => $token, 'is_bind' => true, 'refresh_token'=> $refreshToken, 'expires_in' => config('jwt.jwt_expiration')]);
}
/**
* 获取用户信息(包括菜单和权限)
*/
public function getUserInfo(int $uid)
{
$user = $this->logic->userInfo($uid);
$ossservice = new OssService();
$user['avatar_pull'] = $user->avatar;
if(strpos($user->avatar, 'http') === false && !empty($user->avatar)){
$user['avatar_pull'] = $ossservice->generatePrivateUrl($user->avatar);
// $user['avatar_pull'] = '/proxy/ossurl?file='.$user->avatar;
}
return [
'user' => [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'avatar_pull' => $user['avatar_pull'],
'mobile' => $user->mobile,
'email' => $user->email,
'nickname' => $user->nickname,
'status' => $user->status,
'openid' => $user->openid,
'unionid' => $user->unionid,
],
];
}
/**
* 绑定用户
*
* @param array $data
* @return array
*/
public function bindUser(array $data)
{
$logic = new UserLogic();
$wechat = [
'access_token' => $data['access_token'],
'openid' => $data['openid'],
];
$wechatUserInfo = $logic->getWechatUserInfo($wechat);
$uData = [
'nickname' => $wechatUserInfo['nickname'],
'avatar' => $wechatUserInfo['headimgurl'],
'unionid' => $wechatUserInfo['unionid'],
'openid' => $wechatUserInfo['openid'],
'name' => $data['name'],
'mobile' => $data['mobile'],
'sn' => $data['sn'],
'password' => $data['password'],
];
$userinfo = $logic->bindUser($uData);
if ($userinfo === false) {
throw new BusinessException('未查到用户信息');
}
$jwt = new Jwt();
$refreshToken = (new AuthLogic())->generateRefreshToken($userinfo->id, 'board');
return array_merge($userinfo->toArray(), [
'access_token' => $jwt->generateToken($logic->buildJwtPayload($userinfo)),
'is_bind' => true,
'refresh_token'=> $refreshToken,
'expires_in' => config('jwt.jwt_expiration')
]);
}
/**
* 获取用户列表
*/
public function getList()
{
return $this->logic->getList();
}
/**
* 修改用户密码
*/
public function savePassword(int $uid, array $data)
{
// 对密码进行哈希处理
$password = $data['password'];
$user = User::find($uid);
if (!password_verify($password, $user->password)) {
throw new BusinessException('密码错误');
}
$user = $this->logic->savePassword($uid,$data);
return $user;
}
/**
* 修改用户密码
*/
public function save(int $uid, array $data)
{
// 对密码进行哈希处理
$user = User::find($uid);
$keysToKeep = ['name', 'nickname', 'avatar'];
$data = array_intersect_key($data, array_flip($keysToKeep));
$user = $user->update($data);
return $user;
}
/**
* 获取微信小程序用户信息(登录及获取手机号)
*/
public function wechatLogin(string $loginCode, string $phoneCode)
{
$logic = new UserLogic();
//$accessToken = $logic->getAccessToken($code, $state);
$session = $logic->miniCode2Session($loginCode);
if (!isset($session['openid'])) {
throw new BusinessException('登录失败');
// return json(['code'=>0, 'msg'=>'登录失败', 'data'=>$session]);
}
$openid = $session['openid'];
$tokenData = $logic->getMiniAccessToken();
$access_token = $tokenData['access_token'];
// 换手机号
$phoneData = $logic->getMiniPhoneNumber($access_token, $phoneCode);
if (!isset($phoneData['phone_info']['phoneNumber'])) {
throw new BusinessException('获取手机号失败');
// return json(['code'=>0, 'msg'=>'获取手机号失败', 'data'=>$phoneData]);
}
$phone = $phoneData['phone_info']['phoneNumber'];
$user = User::firstOrCreate(['phone' => $phone], [
'openid' => $openid
]);
// $userInfo = $logic->checkUser($accessToken['openid'], $accessToken['unionid']);
//
// if ($userInfo === false) {
// return [
// 'is_bind' => false,
// 'access_token' => $accessToken['access_token'],
// 'openid' => $accessToken['openid'],
// 'unionid' => $accessToken['unionid'],
// ];
// }
$jwt = new Jwt();
$JwtPayload = $logic->buildJwtPayload($user);
$JwtPayload['exp'] = $JwtPayload['exp'] + (86400 * 30);
$token = $jwt->generateToken($JwtPayload);
$refreshToken = (new AuthLogic())->generateRefreshToken($user->id, 'info');
return array_merge($user->toArray(), ['access_token' => $token, 'is_bind' => true, 'refresh_token'=> $refreshToken, 'expires_in' => config('jwt.jwt_expiration')]);
}
/**
* 小程序登录
*/
public function miniappLogin(array $data)
{
$logic = new UserLogic();
$wechatService = new WechatService();
$wxinfo = $wechatService->xcxOpenid($data['code']);
$name = '微信用户_' . self::generate(8);
// 用户信息
$user = User::firstOrCreate(
['openid' => $wxinfo['openid']],
[
'nickname' => $name,
'unionid' => $wxinfo['unionid'],
'name' => $name,
'avatar' => 'bsh_datax/user/20251021/a4ee815405a1506abeec1af0f738f70f_68f6e76514b66.jpg'
]
);
$jwt = new Jwt();
$JwtPayload = $logic->buildJwtPayload($user);
$JwtPayload['exp'] = $JwtPayload['exp'] + (86400 * 30);
$token = $jwt->generateToken($JwtPayload);
$refreshToken = (new AuthLogic())->generateRefreshToken($user->id, 'info');
return array_merge($user->toArray(), ['token' => $token, 'is_bind' => true, 'refresh_token'=> $refreshToken, 'expires_in' => config('jwt.jwt_expiration')]);
}
/**
* 获取手机号
*/
public function miniappPhoneNumber(array $data,Request $request)
{
// 手机号
$wechatService = new WechatService();
$wxinfo = $wechatService->xcxPhoneNumber($data['phone_code']);
$uinfo = User::where('id', $request->user->uid)->first();
$uinfo->mobile = $wxinfo['phone_info']['phoneNumber'];
$uinfo->save();
return $wxinfo;
}
/**
* 生成随机字符串编码
*
* @param int $length 字符串长度
* @param bool $uppercaseOnly 是否只使用大写字母
* @return string
*/
public static function generate(int $length = 8, bool $uppercaseOnly = false): string
{
$characters = self::$characters;
if ($uppercaseOnly) {
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[random_int(0, $charactersLength - 1)];
}
return $randomString;
}
}
+236
View File
@@ -0,0 +1,236 @@
<?php
namespace app\common\service;
use app\common\model\User;
use EasyWeChat\MiniApp\Application as xcxApplication;
use EasyWeChat\OpenPlatform\Application;
use support\Redis;
use GuzzleHttp\Client;
use support\exception\BusinessException;
class WechatService
{
protected array $web;
protected array $mini;
protected array $login;
protected $app;
public function __construct()
{
$cfg = config('wechat');
$this->web = $cfg['web'];
$this->mini = $cfg['mini'];
$this->login = $cfg['login'];
}
/**
* 获取微信二维码
*/
public function makeQrUrl()
{
try {
$state = bin2hex(random_bytes(16));
$ttl = (int)($this->login['state_ttl'] ?? 300);
// 记录 state
Redis::setEx("wx:login:state:$state", $ttl, json_encode([
'redirect' => $this->web['redirect'],
'ts' => time(),
], JSON_UNESCAPED_UNICODE));
// 组装跳转地址
$qrUrl = sprintf(
'https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect',
$this->web['app_id'],
urlencode($this->web['redirect']),
'code',
'snsapi_login',
$state,
);
$data = [
'state' => $state,
'qr_url' => $qrUrl
];
return $data;
} catch (\Throwable $th) {
// var_dump($th->getFile().'|'.$th->getLine().'|'.$th->getMessage());
throw new BusinessException('获取微信二维码失败');
}
}
/**
* 获取微信access_token
*/
public function wechatWebAccessToken(string $code, string $state): array
{
try {
// 校验 state
$stateJson = Redis::get("wx:login:state:$state");
if (!$stateJson) {
throw new \RuntimeException('state 已过期或无效');
}
// 拼接地址
$tokenUrl = sprintf(
'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code',
$this->web['app_id'],
$this->web['app_secret'],
$code
);
// code 换 token
$token = $this->httpGetJson($tokenUrl);
if (!isset($token['access_token'], $token['openid'])) {
throw new \RuntimeException('换取 access_token 失败:'. json_encode($token, JSON_UNESCAPED_UNICODE));
}
return $token;
} catch (\Throwable $th) {
throw new BusinessException('获取 access_token 失败');
}
}
/**
* 获取微信用户信息
*
* @param array $token
* @return array
*/
public function wechatWebUserInfo(array $accessToken): array
{
try {
// 拉取用户信息
return $this->httpGetJson(sprintf(
'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s',
$accessToken['access_token'],
$accessToken['openid']
));
} catch (\Throwable $th) {
// var_dump($th->getFile().'|'.$th->getLine().'|'.$th->getMessage());
throw new BusinessException('获取用户信息失败');
}
}
/**
* tools - httpGetJson
*/
protected function httpGetJson(string $url): array
{
$client = new Client([
'verify' => false, // 禁用 SSL 验证
'timeout' => 10,
]);
$json = $client->get($url)->getBody()->getContents();
return json_decode($json, true) ?: [];
}
//----------------------------小程序登录----------------------------------------
/**
* 获取 openid / session_key
* @param $code
* @return mixed
*/
public function miniCode2Session($code)
{
$appid = $this->mini['app_id'];
$secret = $this->mini['app_secret'];
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code";
return json_decode(file_get_contents($url), true);
}
/**
* 获取 access_token
* @return mixed
*/
public function getMiniAccessToken()
{
$appid = $this->mini['app_id'];
$secret = $this->mini['app_secret'];
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret";
return json_decode(file_get_contents($url), true);
}
/**
* 调用微信接口取手机号
* @param $access_token
* @param $phone_code
* @return mixed
*/
public function getMiniPhoneNumber($access_token, $phone_code)
{
$url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={$access_token}";
$data = json_encode([
'code' => $phone_code
]);
$options = [
'http' => [
'header' => "Content-type: application/json",
'method' => 'POST',
'content' => $data,
],
];
return json_decode(file_get_contents($url, false, stream_context_create($options)), true);
}
/**
* 小程序获取openid
* @param string $code
* @return array
* @throws BusinessException
*/
public function xcxOpenid(string $code): array
{
$config = [
'app_id' => getenv('WECHAT_MINI_APPID'),
'secret' => getenv('WECHAT_MINI_SECRET'),
'response_type' => 'array',
'log' => [
'level' => 'debug',
'file' => runtime_path().'/mzl/wechat.log',
]
];
// 创建微信小程序对象
$wechat = new xcxApplication($config);
// 获取 session_key 和 openid
return $wechat->getUtils()->codeToSession($code);
}
/**
* 小程序获取手机号
* @param string $phone_code
* @return mixed
* @throws BusinessException
*/
public function xcxPhoneNumber(string $phone_code)
{
$config = [
'app_id' => getenv('WECHAT_MINI_APPID'),
'secret' => getenv('WECHAT_MINI_SECRET'),
'response_type' => 'array',
'log' => [
'level' => 'debug',
'file' => runtime_path().'/mzl/wechat.log',
]
];
// 创建微信小程序对象
$wechat = new xcxApplication($config);
// getPhoneNumber
$data = ['code' => $phone_code];
$result = $wechat->getClient()->postJson('wxa/business/getuserphonenumber', $data);
if (isset($result['phone_info'])) {
// 业务处理 $result['phone_info']['phoneNumber']
return $result;
} else {
throw new BusinessException($result['errmsg']);
}
}
}