Explorar o código

[智价云] 微信扫码登录

tangyuanwang hai 1 mes
pai
achega
d2e4cab1d1

+ 72 - 0
app/Http/Controllers/Manager/Login.php

@@ -8,6 +8,8 @@ use App\Models\Manager\AuthRule;
 use App\Facades\Servers\Encrypts\AccessToken;
 use App\Models\Manager\Personnel\Employee as EmployeeModel;
 use App\Facades\Servers\Sms\VerifyCode as Sms;
+use App\Models\Manager\Personnel\EmployeeOpenid as EmployeeOpenidModel;
+use App\Servers\Wechat\WeChatWebApp;
 
 /**
  * 管理后台登录控制器
@@ -257,4 +259,74 @@ class Login extends Manager
 		// 表单令牌
 		return    	json_send(['code' => 'success', 'msg' => '登录成功', 'data' => $accessToken]);
 	}
+
+
+	/**
+	 * 微信扫码登录			/manager/login/wechat
+	 * @author 唐远望
+	 * @version   1.0
+	 * @date      2026-01-19
+	 * @param string 	open_code		微信扫码登录的code
+	 * 
+	 */
+	public function wechat(Request $Request, AuthRule $AuthRule, EmployeeModel $EmployeeModel, EmployeeOpenidModel $EmployeeOpenidModel)
+	{
+		// 验证规则
+		$Request->scene('wechat')->validate();
+		// 接收数据
+		$open_code    = $Request->input('open_code', '');
+		$wechatApp = new WeChatWebApp();
+		$tokenData = $wechatApp->getAccessTokenByCode($open_code);
+		if (!$tokenData) return json_send(['code' => 'error', 'msg' => '获取微信用户信息失败']);
+		$user_open_data = $EmployeeOpenidModel->where(['openid' => $tokenData['openid']])->first();
+		if (!$user_open_data)   return json_send(['code' => 'error', 'msg' => '未绑定账号,请登录后在绑定']);
+		// 查询用户
+		$admin    	= $EmployeeModel->where('id', $user_open_data->employee_id)->first(['id as uid', 'name as username', 'mobile as phone', 'status', 'password', 'insert_time', 'update_time']);
+		// 用户不存在
+		if (!$admin)	return json_send(['code' => 'error', 'msg' => '账号不存在']);
+		// 用户不存在
+		if ($admin['status']) 				return json_send(['code' => 'error', 'msg' => '该账号已停用']);
+		// 转数组
+		$admin    	= $admin->toArray();
+		// 登录
+		$accessToken 						= $EmployeeModel->Login($admin['uid'], 'manager');
+		// 比对密码
+		if (isset($accessToken['error'])) 	return json_send(['code' => 'error', 'msg' => '登录失败', 'data' => $accessToken['data']]);
+		// 获取权限列表
+		$accessToken['username']			= $admin['username'];
+		// 获取权限列表
+		$accessToken['auth_rules']			= $AuthRule->getAuthList($admin['uid'], 'manager');;
+		// 表单令牌
+		return    	json_send(['code' => 'success', 'msg' => '登录成功', 'data' => $accessToken]);
+	}
+
+	/**
+	 * 微信扫码授权绑定			/manager/login/wechat_bind
+	 * @author 唐远望
+	 * @version   1.0
+	 * @date      2026-01-19
+	 * @param string 	open_code		微信扫码登录的code
+	 * 
+	 */
+	public function wechat_bind(Request $Request,EmployeeOpenidModel $EmployeeOpenidModel)
+	{
+		// 验证规则
+		$Request->scene('wechat_bind')->validate();
+		$uid             = request('access_token.uid', 0);
+		// 接收数据
+		$open_code    = $Request->input('open_code', '');
+		$wechatApp = new WeChatWebApp();
+		$tokenData = $wechatApp->getAccessTokenByCode($open_code);
+		if (!$tokenData) return json_send(['code' => 'error', 'msg' => '获取微信用户信息失败']);
+		$user_open_data = $EmployeeOpenidModel->where(['openid' => $tokenData['openid'],'employee_id'=> $uid])->first();
+		if ($user_open_data)   return json_send(['code' => 'error', 'msg' => '微信已绑定,无需重复绑定']);
+		//新增绑定记录
+		$EmployeeOpenidModel->create([
+			'openid' => $tokenData['openid'],
+			'unionid' => isset($tokenData['unionid']) ? $tokenData['unionid'] : '',
+			'employee_id' => $uid,
+			'insert_time' => time(),
+		]);
+		return json_send(['code' => 'success', 'msg' => '绑定成功','data'=>'']);
+	}
 }

+ 2 - 1
app/Http/Requests/Manager/Login.php

@@ -34,7 +34,8 @@ class Login extends BaseRequest
         'mobile'                 => ['phone', 'password'],
         'send_code'              => ['phone'],
         'email'                  => ['email', 'password'],
-        'mobile_code'            => ['phone', 'code']
+        'mobile_code'            => ['phone', 'code'],
+        'wechat'                 => ['open_code'],
     ];
 
     /**

+ 151 - 0
app/Models/Manager/Personnel/EmployeeOpenid.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace App\Models\Manager\Personnel;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 员工openid模型
+ * @author 唐远望
+ * @version 1.0
+ * @date 2026-01-19
+ */
+class EmployeeOpenid extends Model
+{
+    use HasFactory;
+    // 与模型关联的表名
+    protected $table = 'personnel_employee_openid';
+    // 是否主动维护时间戳
+    public $timestamps = false;
+    // 定义时间戳字段名
+    // const CREATED_AT = 'insert_time';
+    // const UPDATED_AT = 'update_time';
+
+
+    /**
+     * 添加
+     * @author 唐远望
+     * @version 1.0
+     * @date 2025-12-04
+     */
+    public function addEmployeeOpenid_content($data)
+    {
+        $insert_data = [
+            'employee_id' => $data['employee_id'],
+            'unionid'    => $data['unionid'],
+            'openid' => $data['openid'],
+            'type'  => isset($data['type']) ? $data['type'] : '3',
+            'insert_time' => time(),
+        ];
+        $EmployeeOpenid_id = $this->insertGetId($insert_data);
+        return $EmployeeOpenid_id;
+    }
+
+
+
+    /**
+     * 写入数据
+     * @author 唐远望
+     * @version 1.0
+     * @date 2025-12-04
+     * @param $data
+     * @return bool
+     */
+    public function addEmployeeOpenid($data)
+    {
+        DB::beginTransaction();
+        try {
+            $insert_data = [
+                'employee_id' => $data['employee_id'],
+                'unionid'    => $data['unionid'],
+                'openid' => $data['openid'],
+                'type'  => isset($data['type']) ? $data['type'] : '3',
+                'insert_time' => time(),
+            ];
+            $EmployeeOpenid_id = $this->insertGetId($insert_data);
+            DB::commit();
+            return $EmployeeOpenid_id;
+            // 成功处理...
+        } catch (\Exception $e) {
+            DB::rollBack();
+            // 错误处理...
+            return false;
+        }
+    }
+
+
+    /**
+     * 编辑内容
+     * @author 唐远望
+     * @version 1.0
+     * @date 2025-12-04
+     * @param $data
+     * @return bool
+     */
+    public function editEmployeeOpenid_content($where, $data)
+    {
+        $EmployeeOpenid = $this->where($where)->first();
+        if (!$EmployeeOpenid) {
+            return false;
+        }
+        $EmployeeOpenid->employee_id = $data['employee_id'];
+        $EmployeeOpenid->unionid = $data['unionid'];
+        $EmployeeOpenid->openid = $data['openid'];
+        $EmployeeOpenid->type = isset($data['type']) ? $data['type'] : '3';
+        $EmployeeOpenid->update_time = time();
+        $EmployeeOpenid->save();
+        return true;
+    }
+
+
+
+    /**
+     * 更新数据
+     * @author 唐远望
+     * @version 1.0
+     * @date 2025-12-04
+     * @param $data
+     * @return bool
+     */
+    public function updateEmployeeOpenid($EmployeeOpenid, $data)
+    {
+        DB::beginTransaction();
+        try {
+            $EmployeeOpenid->employee_id = $data['employee_id'];
+            $EmployeeOpenid->unionid = $data['unionid'];
+            $EmployeeOpenid->openid = $data['openid'];
+            $EmployeeOpenid->type = isset($data['type']) ? $data['type'] : '3';
+            $EmployeeOpenid->update_time = time();
+            $EmployeeOpenid->save();
+
+            DB::commit();
+            return true;
+            // 成功处理...
+        } catch (\Exception $e) {
+            DB::rollBack();
+            print_r($e->getMessage());
+            exit;
+            // 错误处理...
+            return false;
+        }
+    }
+    /**
+     * 删除数据
+     * @author 唐远望
+     * @version 1.0
+     * @date 2025-12-04
+     * @param $id
+     * @return bool
+     */
+    public function deleteEmployeeOpenid($where)
+    {
+        $EmployeeOpenid = $this->where($where)->first();
+        if (!$EmployeeOpenid) {
+            return false;
+        }
+        $EmployeeOpenid->delete();
+        return true;
+    }
+}

+ 304 - 0
app/Servers/Wechat/WeChatWebApp.php

@@ -0,0 +1,304 @@
+<?php
+
+namespace App\Servers\Wechat;
+
+/**
+ * 微信网站应用
+ * @author 唐远望
+ * @version 1.0
+ * @date 2026-01-19
+ */
+class WeChatWebApp
+{
+    private $appId;
+    private $appSecret;
+    private $redirectUri;
+
+    /**
+     * 构造函数
+     * 
+     * @param string $appId 应用唯一标识
+     * @param string $appSecret 应用密钥
+     * @param string $redirectUri 授权回调地址
+     */
+    public function __construct()
+    {
+        $this->appId = config('wechat.openplat.app_id',[]);
+        $this->appSecret = config('wechat.openplat.secret',[]);
+        $this->redirectUri = urlencode(config('wechat.openplat.release_host_url',[]));
+    }
+
+    /**
+     * 第一步:生成授权URL,引导用户跳转到微信授权页面
+     * 
+     * @param string $scope 应用授权作用域
+     *        snsapi_base - 静默授权,不弹出授权页面,只能获取openid
+     *        snsapi_userinfo - 弹出授权页面,可获取用户信息
+     * @param string $state 重定向后会带上state参数,开发者可以填写任意参数值
+     * @return string 授权URL
+     */
+    public function getAuthorizeUrl($scope = 'snsapi_base', $state = 'STATE')
+    {
+        $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appId}&redirect_uri={$this->redirectUri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect";
+        return $url;
+    }
+
+    /**
+     * 第二步:通过code获取access_token和openid
+     * 
+     * @param string $code 授权code
+     * @return array|false 成功返回数组,失败返回false
+     */
+    public function getAccessTokenByCode($code)
+    {
+        $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appId}&secret={$this->appSecret}&code={$code}&grant_type=authorization_code";
+
+        $result = $this->httpGet($url);
+
+        if ($result) {
+            $data = json_decode($result, true);
+
+            if (!isset($data['errcode'])) {
+                return $data; // 成功返回
+            } else {
+                $this->logError("获取access_token失败", $data);
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 刷新access_token
+     * 
+     * @param string $refreshToken 刷新token
+     * @return array|false 成功返回数组,失败返回false
+     */
+    public function refreshAccessToken($refreshToken)
+    {
+        $url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={$this->appId}&grant_type=refresh_token&refresh_token={$refreshToken}";
+
+        $result = $this->httpGet($url);
+
+        if ($result) {
+            $data = json_decode($result, true);
+
+            if (!isset($data['errcode'])) {
+                return $data; // 成功返回
+            } else {
+                $this->logError("刷新access_token失败", $data);
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取用户信息(需要scope为snsapi_userinfo)
+     * 
+     * @param string $accessToken 接口调用凭证
+     * @param string $openid 用户唯一标识
+     * @return array|false 成功返回数组,失败返回false
+     */
+    public function getUserInfo($accessToken, $openid)
+    {
+        $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$accessToken}&openid={$openid}&lang=zh_CN";
+
+        $result = $this->httpGet($url);
+
+        if ($result) {
+            $data = json_decode($result, true);
+
+            if (!isset($data['errcode'])) {
+                return $data; // 成功返回
+            } else {
+                $this->logError("获取用户信息失败", $data);
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证access_token是否有效
+     * 
+     * @param string $accessToken 接口调用凭证
+     * @param string $openid 用户唯一标识
+     * @return bool 是否有效
+     */
+    public function checkAccessToken($accessToken, $openid)
+    {
+        $url = "https://api.weixin.qq.com/sns/auth?access_token={$accessToken}&openid={$openid}";
+
+        $result = $this->httpGet($url);
+
+        if ($result) {
+            $data = json_decode($result, true);
+
+            if (isset($data['errcode']) && $data['errcode'] == 0) {
+                return true; // 有效
+            }
+        }
+
+        return false; // 无效
+    }
+
+    /**
+     * 完整的授权流程处理
+     * 
+     * @return array|false 成功返回用户信息数组,失败返回false
+     */
+    public function handleAuthorization()
+    {
+        // 检查是否有授权code
+        if (!isset($_GET['code'])) {
+            // 没有code,跳转到授权页面
+            $url = $this->getAuthorizeUrl('snsapi_userinfo', 'authorize');
+            header("Location: {$url}");
+            exit;
+        }
+
+        // 获取授权code
+        $code = $_GET['code'];
+
+        // 通过code获取access_token
+        $tokenData = $this->getAccessTokenByCode($code);
+
+        if (!$tokenData) {
+            return false;
+        }
+
+        // 存储token信息到session
+        session_start();
+        $_SESSION['wechat_access_token'] = $tokenData['access_token'];
+        $_SESSION['wechat_refresh_token'] = $tokenData['refresh_token'];
+        $_SESSION['wechat_openid'] = $tokenData['openid'];
+        $_SESSION['wechat_token_expire'] = time() + $tokenData['expires_in'];
+
+        return $tokenData;
+    }
+
+    /**
+     * 获取当前有效的access_token(自动刷新)
+     * 
+     * @return string|false 有效的access_token
+     */
+    public function getValidAccessToken()
+    {
+        session_start();
+
+        // 检查session中是否有token信息
+        if (!isset($_SESSION['wechat_access_token'])) {
+            return false;
+        }
+
+        $accessToken = $_SESSION['wechat_access_token'];
+        $refreshToken = $_SESSION['wechat_refresh_token'];
+        $openid = $_SESSION['wechat_openid'];
+        $expireTime = $_SESSION['wechat_token_expire'];
+
+        // 检查token是否即将过期(提前5分钟刷新)
+        if (time() > $expireTime - 300) {
+            // 刷新token
+            $newTokenData = $this->refreshAccessToken($refreshToken);
+
+            if ($newTokenData) {
+                // 更新session中的token信息
+                $_SESSION['wechat_access_token'] = $newTokenData['access_token'];
+                $_SESSION['wechat_refresh_token'] = $newTokenData['refresh_token'];
+                $_SESSION['wechat_token_expire'] = time() + $newTokenData['expires_in'];
+
+                $accessToken = $newTokenData['access_token'];
+            } else {
+                // 刷新失败,需要重新授权
+                return false;
+            }
+        }
+
+        return $accessToken;
+    }
+
+    /**
+     * HTTP GET 请求
+     * 
+     * @param string $url 请求URL
+     * @return string|false 响应内容
+     */
+    private function httpGet($url)
+    {
+        if (function_exists('curl_init')) {
+            $ch = curl_init();
+            curl_setopt($ch, CURLOPT_URL, $url);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+
+            $response = curl_exec($ch);
+
+            if (curl_errno($ch)) {
+                $this->logError("CURL错误", curl_error($ch));
+                curl_close($ch);
+                return false;
+            }
+
+            curl_close($ch);
+            return $response;
+        } else {
+            // 备用方法:使用file_get_contents
+            $context = stream_context_create([
+                'ssl' => [
+                    'verify_peer' => false,
+                    'verify_peer_name' => false,
+                ]
+            ]);
+
+            return file_get_contents($url, false, $context);
+        }
+    }
+
+    /**
+     * 错误日志记录
+     * 
+     * @param string $message 错误信息
+     * @param mixed $data 错误数据
+     */
+    private function logError($message, $data = null)
+    {
+        $log = date('Y-m-d H:i:s') . " - {$message}";
+
+        if ($data !== null) {
+            $log .= " - " . (is_array($data) ? json_encode($data) : $data);
+        }
+
+        error_log($log . PHP_EOL, 3, 'wechat_oauth_error.log');
+    }
+
+    /**
+     * 获取当前openid
+     * 
+     * @return string|false openid
+     */
+    public function getOpenId()
+    {
+        session_start();
+        return isset($_SESSION['wechat_openid']) ? $_SESSION['wechat_openid'] : false;
+    }
+
+    /**
+     * 清除session中的授权信息
+     */
+    public function clearSession()
+    {
+        session_start();
+        unset(
+            $_SESSION['wechat_access_token'],
+            $_SESSION['wechat_refresh_token'],
+            $_SESSION['wechat_openid'],
+            $_SESSION['wechat_token_expire']
+        );
+    }
+}

+ 24 - 0
config/wechat.php

@@ -0,0 +1,24 @@
+<?php
+
+return [
+    'mini' => [
+        'app_id' => env('WECHAT_APP_ID', 'wx246605ec671bf08d'),
+        'secret' => env('WECHAT_SECRET', '5dfa6b60f10347ffa7b959658b3f2496'),
+        // 下面为可选项
+        // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
+        'response_type' => 'array',
+        'log' => [
+            'level' => 'debug',
+            'file' => storage_path('logs') . '/wechat.log',
+        ],
+    ],
+    'openplat' => [
+        'app_id'               => env('WECHAT_OPENPLAT_APP_ID', 'wx2386988b9b706ff5'),
+        'secret'               => env('WECHAT_OPENPLAT_SECRET', '3b487dd62a738939bb833f928d4fa4bd'),
+        'token'                => env('WECHAT_OPENPLAT_TOKEN', 'Afd61RPH3GzEwfEATRwdJVKhy'),
+        'aes_key'              => env('WECHAT_OPENPLAT_AES_KEY', 'FL1brptJzQeDMwp5nvuojb8YEDx9GXy9kxaQ8JQl2Zi'),
+        'host_url'             => env('WECHAT_OPENPLAT_HOST_URL', 'https://retrieveapi.dfwy.tech/'),
+        'release_host_url'     => env('WECHAT_OPENPLAT_RELEASE_HOST_URL', 'https://retrieveapi.findit.ltd/'),
+    ],
+
+];