This commit is contained in:
15090180611 2024-11-05 18:05:18 +08:00
parent 9ee99202bb
commit e7d732c3d5
11 changed files with 643 additions and 23 deletions

View File

@ -0,0 +1,66 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 第三方登录管理
*
* @icon fa fa-circle-o
*/
class Third extends Backend
{
/**
* Third模型对象
* @var \app\admin\model\Third
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\Third;
}
/**
* 查看
*/
public function index()
{
$this->relationSearch = true;
//设置过滤方法
$this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with(['user'])
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with(['user'])
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $index => $item) {
if ($item->user) {
$item->user->visible(['nickname']);
}
}
$list = collection($list)->toArray();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -0,0 +1,17 @@
<?php
return [
'Id' => 'ID',
'User_id' => '会员ID',
'Platform' => '平台',
'Apptype' => '类型',
'Unionid' => 'UnionID',
'Openid' => 'OpenID',
'Openname' => '第三方会员昵称',
'Access_token' => 'AccessToken',
'Expires_in' => '有效期',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Logintime' => '登录时间',
'Expiretime' => '过期时间'
];

View File

@ -0,0 +1,56 @@
<?php
namespace app\admin\model;
use think\Model;
class Third extends Model
{
// 表名
protected $name = 'third';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
protected $deleteTime = false;
// 追加属性
protected $append = [
'logintime_text',
'expiretime_text'
];
public function getLogintimeTextAttr($value, $data)
{
$value = $value ?: ($data['logintime'] ?? '');
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
}
public function getExpiretimeTextAttr($value, $data)
{
$value = $value ?: ($data['expiretime'] ?? '');
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
}
protected function setLogintimeAttr($value)
{
return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
}
protected function setExpiretimeAttr($value)
{
return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
}
public function user()
{
return $this->belongsTo("User", 'user_id', 'id')->setEagerlyType(0);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace app\admin\validate;
use think\Validate;
class Third extends Validate
{
/**
* 验证规则
*/
protected $rule = [
];
/**
* 提示消息
*/
protected $message = [
];
/**
* 验证场景
*/
protected $scene = [
'add' => [],
'edit' => [],
];
}

View File

@ -0,0 +1,23 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}"><i class="fa fa-refresh"></i> </a>
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('third/del')?'':'hide'}" title="{:__('Delete')}"><i class="fa fa-trash"></i> {:__('Delete')}</a>
</div>
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit=""
data-operate-del="{:$auth->check('third/del')}"
width="100%">
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -20,4 +20,25 @@ class Index extends Api
{
$this->success('请求成功');
}
/**
* @ApiTitle(init接口小程序初始化参数接口)
* @ApiSummary(小程序初始化参数接口)
* @ApiRoute(/api/index/init)
* @ApiMethod(GET)
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {}
*})
*/
public function init() {
$this->success('',[]);
}
}

View File

@ -7,7 +7,10 @@ use app\common\controller\Api;
use app\common\library\Ems;
use app\common\library\Sms;
use fast\Random;
use think\Cache;
use think\Config;
use think\Db;
use think\Log;
use think\Validate;
use app\admin\library\Wechat;
@ -16,7 +19,7 @@ use app\admin\library\Wechat;
*/
class User extends Api
{
protected $noNeedLogin = ['login', 'mobilelogin', 'register', 'resetpwd', 'changeemail', 'changemobile', 'third'];
protected $noNeedLogin = ["registerLogin",'getOpenid','decodeData','login', 'mobilelogin', 'register', 'resetpwd', 'changeemail', 'changemobile', 'third'];
protected $noNeedRight = '*';
protected $miniConfig;
@ -25,6 +28,9 @@ class User extends Api
{
$this->miniConfig = (new Wechat)->getMiniConfig();
parent::_initialize();
if (!Config::get('fastadmin.usercenter')) {
$this->error(__('User center already closed'));
}
}
@ -33,7 +39,7 @@ class User extends Api
/**
* @ApiTitle(获取小程序openid信息)
* @ApiSummary(根据前端code换取openid信息)
* @ApiRoute(/getOpenid)
* @ApiRoute(/api/user/getOpenid)
* @ApiMethod(GET)
* @ApiParams(name="code",type="string",required=true,description="前端code值")
* @ApiHeaders(name = "brand-key", type = "string",require = true, description = "应用key")
@ -51,8 +57,6 @@ class User extends Api
public function getOpenid(){
$code = $this->request->param('code/s');
try {
$result = LoginService::getInstance(['mini_config' => $this->miniConfig])->getOpenid($code);
} catch (\WeChat\Exceptions\LocalCacheException $e){
@ -60,13 +64,14 @@ class User extends Api
} catch (\Exception $e){
$this->error($e->getMessage());
}
Cache::set('wechat_mini_code'.$code.$result['openid'],"1",60);
$this->success('',$result);
}
/**
* @ApiTitle(加密信息解密)
* @ApiSummary(解密微信信息)
* @ApiRoute(/decodeData)
* @ApiRoute(/api/user/decodeData)
* @ApiMethod(POST)
* @ApiParams(name = "iv", type = "string",required=true)
* @ApiParams(name = "encryptedData", type = "string",required=true)
@ -92,24 +97,71 @@ class User extends Api
public function _initialize()
{
parent::_initialize();
if (!Config::get('fastadmin.usercenter')) {
$this->error(__('User center already closed'));
/**
* @ApiTitle(微信小程序授权登录注册通用[支持静默登录])
* @ApiSummary(登录注册通用-支持静默登录)
* @ApiRoute(/api/user/registerLogin)
* @ApiMethod(POST)
* @ApiParams(name = "mobile", type = "string",required=false,description = "mobile 静默非必传,手机授权必传")
* @ApiParams(name = "unionid", type = "string",required=false, description = "unionid 非必传")
* @ApiParams(name = "apptype", type = "string",required=false, description = "应用类型默认miniapp")
* @ApiParams(name = "platform", type = "string",require=false, description = "平台标识默认wechat")
* @ApiParams(name = "openid", type = "string", require=true, description = "授权的openid")
* @ApiParams(name = "keeptime", type = "string", require=false, description = "token保持时间默认0永久")
* @ApiParams(name = "code", type = "string", require=true, description = "授权的code")
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {tokenxxxxx,'errcode':0(如果为30001则是需要重新授权)}
*})
*/
public function registerLogin(){
$extend= $params = [];
$extend['mobile'] = $this->request->param('mobile/s','');
$params['unionid'] = $this->request->param('unionid/s','');
$params['apptype'] = $this->request->param('apptype/s','miniapp');
$params['openid'] = $this->request->param('openid/s','');
$code = $this->request->param('code/s','');
if(empty($code)){
$this->error(__('缺少code'));
}
$wechat_mini_code = Cache::get('wechat_mini_code'.$code.$params['openid']);
if(!$wechat_mini_code)$this->error("授权code已过期或已使用请重新发起授权",['errcode'=>30002]);
$platform = $this->request->param('platform/s','wechat');
//推荐人:逻辑未实现
$rec_user_id = $this->request->param('rec_user_id',0,'xilufitness_get_id_value');
$keeptime = $this->request->param('keeptime/d',0);
//access_token
try {
\addons\third\library\Service::loginAndRegisterByMobile($platform, $params, $extend, $keeptime);
} catch (\Exception $e){
Log::log($e->getMessage());
$this->error($e->getMessage(),['errcode'=>$e->getCode()]);
}
Cache::rm('wechat_mini_code'.$code.$params['openid']);
$this->success('获取成功', ['token' => $this->auth->getToken()]);
}
/**
* 会员中心
*/
public function index()
{
$this->success('', ['welcome' => $this->auth->nickname]);
$data = [
'welcome' => $this->auth->nickname,
'user_info'=>$this->auth->getUserinfo()
];
$data['user_info']["avatar"] = $data['user_info']["avatar"]? cdnurl($data['user_info']["avatar"],true):$data['user_info']["avatar"];
$this->success('调用成功',$data);
}
/**
@ -230,34 +282,56 @@ class User extends Api
* 修改会员个人信息
*
* @ApiMethod (POST)
* @ApiParams (name="update_fields", type="json", required=true, description="本次需要更新的用户字段json格式更新谁传谁的字段名比如只更新头像和昵称 则 json=['avatar','nickname']")
* @ApiParams (name="avatar", type="string", required=true, description="头像地址")
* @ApiParams (name="username", type="string", required=true, description="用户名")
* @ApiParams (name="nickname", type="string", required=true, description="昵称")
* @ApiParams (name="realname", type="string", required=true, description="姓名")
* @ApiParams (name="gender", type="string", required=true, description="性别0女 1男")
* @ApiParams (name="birthday", type="string", required=true, description="出生日期:日期字符串 比如”1995-9-20")
* @ApiParams (name="work", type="string", required=true, description="职业")
* @ApiParams (name="bio", type="string", required=true, description="个人简介")
*/
public function profile()
{
$user = $this->auth->getUser();
$username = $this->request->post('username');
$nickname = $this->request->post('nickname');
$bio = $this->request->post('bio');
$avatar = $this->request->post('avatar', '', 'trim,strip_tags,htmlspecialchars');
if ($username) {
$update_fields = $this->request->post('update_fields/a',[]);
// var_dump($update_fields);die;
if(!$update_fields)$this->error(__('请指定要更新的字段!'));
$username = $this->request->post('username/s');
$nickname = $this->request->post('nickname/s');
$realname = $this->request->post('realname/s');
$gender = $this->request->post('gender/d');
$birthday = $this->request->post('birthday/s');
$work = $this->request->post('work/s');
$bio = $this->request->post('bio/s',null);
$avatar = $this->request->post('avatar', null, 'trim,strip_tags,htmlspecialchars');
if ($username && in_array('username', $update_fields)) {
$exists = \app\common\model\User::where('username', $username)->where('id', '<>', $this->auth->id)->find();
if ($exists) {
$this->error(__('Username already exists'));
}
$user->username = $username;
}
if ($nickname) {
if ($nickname && in_array('nickname', $update_fields)) {
$exists = \app\common\model\User::where('nickname', $nickname)->where('id', '<>', $this->auth->id)->find();
if ($exists) {
$this->error(__('Nickname already exists'));
}
$user->nickname = $nickname;
}
$user->bio = $bio;
$user->avatar = $avatar;
if(in_array('bio', $update_fields))$user->bio = $bio;
if($avatar!==null && in_array('avatar', $update_fields))$user->avatar = $avatar;
if(in_array('realname', $update_fields))$user->realname = $realname;
if(in_array('gender', $update_fields)){
if(!in_array($gender, [1,2]))$this->error(__('请输入正确的性别!'));
$user->gender = $gender;
}
if(in_array('birthday', $update_fields))$user->birthday = $birthday;
if(in_array('work', $update_fields))$user->work = $work;
$user->save();
$this->success();
}

View File

@ -26,7 +26,7 @@ class Auth
//默认配置
protected $config = [];
protected $options = [];
protected $allowFields = ['id', 'username', 'nickname', 'mobile', 'avatar', 'score'];
protected $allowFields = ['id', 'username', 'nickname', 'mobile', 'avatar', 'score', 'realname', 'work', 'gender', 'birthday', 'bio'];
public function __construct($options = [])
{

View File

@ -0,0 +1,200 @@
<?php
namespace app\index\controller;
use addons\third\library\Application;
use addons\third\library\Service;
use app\common\controller\Frontend;
use app\common\library\Sms;
use fast\Random;
use think\Cookie;
use think\Hook;
use think\Lang;
use think\Session;
/**
* 第三方登录控制器
*/
class Third extends Frontend
{
protected $noNeedLogin = ['prepare'];
protected $noNeedRight = ['*'];
protected $app = null;
protected $options = [];
protected $layout = 'default';
public function _initialize()
{
parent::_initialize();
$config = get_addon_config('third');
$this->app = new Application($config);
$auth = $this->auth;
//监听注册登录事件
Hook::add('user_login_successed', function ($user) use ($auth) {
$expire = input('post.keeplogin') ? 30 * 86400 : 0;
Cookie::set('uid', $user->id, $expire);
Cookie::set('token', $auth->getToken(), $expire);
});
Hook::add('user_register_successed', function ($user) use ($auth) {
Cookie::set('uid', $user->id);
Cookie::set('token', $auth->getToken());
});
}
/**
* 准备绑定
*/
public function prepare()
{
$platform = $this->request->request('platform', '');
if (!in_array($platform, ['wechat', 'weibo', 'qq'])) {
$this->error("未找到指定平台");
}
$url = $this->request->get('url', '/', 'trim,xss_clean');
// 授权成功后的回调
$thirdinfo = Session::get("third-{$platform}");
if (!$thirdinfo) {
$this->error("操作失败,请返回重试");
}
$apptype = Service::getApptype();
$openid = $thirdinfo['openid'] ?? '';
$unionid = $thirdinfo['unionid'] ?? '';
// 如果是登录状态,直接跳到绑定
if ($this->auth->id) {
$isBind = Service::isBindThird($platform, $openid, '', $unionid);
if ($isBind) {
$this->error("已经绑定其它账号,无法进行绑定");
}
$this->redirect(url("index/third/bind") . "?" . http_build_query(['platform' => $platform, 'url' => $url]));
}
if ($this->request->isPost()) {
$mobile = $this->request->post("mobile");
$platform = $this->request->post("platform");
$captcha = $this->request->post("captcha");
$nickname = $thirdinfo['userinfo']['nickname'] ?? '';
if (!Sms::check($mobile, $captcha, 'bind')) {
$this->error(__('验证码不正确'));
}
$user = \app\common\model\User::where('mobile', $mobile)->find();
if ($user) {
$isBind = \addons\third\model\Third::where('platform', $platform)->where('user_id', $user['id'])->find();
if ($isBind) {
$this->error('该手机号已经占用');
}
$result = $this->auth->direct($user->id);
} else {
$extend = array_filter(['nickname' => $nickname]);
$result = $this->auth->register($mobile, Random::alnum(), '', $mobile, $extend);
}
// 账号创建成功则添加绑定第三方绑定
if ($result) {
\addons\third\model\Third::create(['user_id' => $this->auth->id, 'platform' => $platform, 'apptype' => $apptype, 'openid' => $openid, 'unionid' => $unionid, 'openname' => $nickname], true);
}
//删除临时Session
Session::delete("third-{$platform}");
//绑定成功,跳转到之前页面
$this->success("绑定成功", $url);
}
$this->view->assign('userinfo', $thirdinfo['userinfo']);
$this->view->assign('platform', $platform);
$this->view->assign('url', $url);
$this->view->assign('bindurl', url("index/third/bind") . '?' . http_build_query(['platform' => $platform, 'url' => $url]));
$this->view->assign('captchaType', config('fastadmin.user_register_captcha'));
$this->view->assign('title', "账号绑定");
return $this->view->fetch();
}
/**
* 绑定账号
*/
public function bind()
{
$platform = $this->request->request('platform', '');
if (!in_array($platform, ['wechat', 'weibo', 'qq'])) {
$this->error("未找到指定平台");
}
$url = $this->request->get('url', $this->request->server('HTTP_REFERER', '', 'trim'), 'trim');
if (!$platform) {
$this->error("参数不正确");
}
$apptype = $platform == 'wechat' ? Service::getApptype() : '';
// 授权成功后的回调
$thirdinfo = Session::get("third-{$platform}");
if (!$thirdinfo) {
$this->redirect(addon_url('third/index/connect', [':platform' => $platform]) . '?url=' . urlencode($url));
}
$third = \addons\third\model\Third::where('user_id', $this->auth->id)->where('platform', $platform)->find();
if ($third) {
$this->error("已绑定账号,请勿重复绑定");
}
$time = time();
$values = [
'platform' => $platform,
'apptype' => $apptype,
'user_id' => $this->auth->id,
'unionid' => $thirdinfo['unionid'] ?? '',
'openid' => $thirdinfo['openid'],
'openname' => $thirdinfo['userinfo']['nickname'] ?? '',
'access_token' => $thirdinfo['access_token'],
'refresh_token' => $thirdinfo['refresh_token'],
'expires_in' => $thirdinfo['expires_in'],
'logintime' => $time,
'expiretime' => $time + $thirdinfo['expires_in'],
];
$isBind = Service::isBindThird($platform, $values['openid'], '', $values['unionid']);
if ($isBind) {
$this->error("已经绑定其它账号,无法进行绑定");
}
$third = \addons\third\model\Third::create($values);
if ($third) {
//删除临时Session
Session::delete("third-{$platform}");
$this->success("账号绑定成功", $url);
} else {
$this->error("账号绑定失败,请重试", $url);
}
}
/**
* 解绑账号
*/
public function unbind()
{
$platform = $this->request->request('platform', '');
if (!in_array($platform, ['wechat', 'weibo', 'qq'])) {
$this->error("未找到指定平台");
}
$apptype = $platform == 'wechat' ? Service::getApptype() : '';
$third = \addons\third\model\Third::where('user_id', $this->auth->id)
->where('platform', $platform)
->where(function ($query) use ($platform, $apptype) {
if ($platform == 'wechat') {
$query->where('apptype', $apptype);
}
})
->find();
if (!$third) {
$this->error("未找到指定的账号绑定信息");
}
Session::delete("third-{$platform}");
$third->delete();
$this->success("账号解绑成功");
}
}

View File

@ -0,0 +1,76 @@
<style>
.login-section .control-label {
font-weight: normal;
}
</style>
<div id="content-container" class="container">
{if isset($userinfo['avatar'])}
<div class="text-center">
<img src="{$userinfo.avatar}" class="img-circle" width="80" height="80" alt=""/>
<div style="margin-top:15px;">{$userinfo.nickname|default=''|htmlentities}</div>
</div>
{/if}
<div class="user-section login-section" style="margin-top:20px;">
<div class="bind-main login-main">
<h3 class="text-center mt-0 mb-4">绑定账号</h3>
<form name="form" id="bind-form" class="form-vertical" method="POST" action="">
{:token()}
<input type="hidden" name="platform" value="{$platform|htmlentities}"/>
<input type="hidden" name="url" value="{$url|htmlentities}"/>
<div class="form-group">
<label class="control-label">手机号</label>
<div class="controls">
<input type="text" id="mobile" name="mobile" data-rule="required" class="form-control input-lg">
<p class="help-block"></p>
</div>
</div>
<div class="form-group">
<label class="control-label">验证码</label>
<div class="controls">
<div class="input-group">
<input type="text" name="captcha" class="form-control input-lg" data-rule="required;length({$Think.config.captcha.length});digits;remote({:url('api/validate/check_sms_correct')}, event=bind, mobile:#mobile)" />
<span class="input-group-btn" style="padding:0;border:none;">
<a href="javascript:;" class="btn btn-info btn-captcha btn-lg" data-url="{:url('api/sms/send')}" data-type="mobile" data-event="bind">发送验证码</a>
</span>
</div>
<p class="help-block"></p>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block btn-lg">{:__('确认绑定')}</button>
</div>
</form>
</div>
</div>
</div>
<script>
require.callback = function () {
define('frontend/third', ['jquery', 'bootstrap', 'frontend', 'template', 'form'], function ($, undefined, Frontend, Template, Form) {
var Controller = {
prepare: function () {
var validatoroptions = {
invalid: function (form, errors) {
$.each(errors, function (i, j) {
Layer.msg(j);
});
}
};
//本地验证未通过时提示
$("#register-form").data("validator-options", validatoroptions);
//为表单绑定事件
Form.api.bindevent($("#bind-form"), function (data, ret) {
location.href = ret.url;
return false;
}, function (data, ret) {
});
}
};
return Controller;
});
}
</script>

View File

@ -0,0 +1,61 @@
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'third/index' + location.search,
add_url: 'third/add',
edit_url: 'third/edit',
del_url: 'third/del',
multi_url: 'third/multi',
table: 'third',
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
fixedColumns: true,
fixedRightNumber: 1,
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'user_id', title: __('User_id'), formatter: Table.api.formatter.search},
{field: 'user.nickname', title: __('Nickname')},
{field: 'platform', title: __('Platform'), formatter: Table.api.formatter.flag, searchList: {'wechat': '微信', 'qq': 'QQ', 'weibo': '微博'}, custom: {'wechat': 'success', 'qq': 'info', 'weibo': 'danger'}},
{field: 'apptype', title: __('Apptype'), formatter: Table.api.formatter.normal, searchList: {'mp': '公众号', 'wxapp': '小程序', 'web': 'PC端'}},
{field: 'openid', title: __('Openid')},
{field: 'unionid', title: __('Unionid')},
{field: 'openname', title: __('Openname'), formatter: Table.api.formatter.search},
{field: 'createtime', title: __('Createtime'), sortable: true, operate: 'RANGE', addclass: 'datetimerange', formatter: Table.api.formatter.datetime},
{field: 'updatetime', title: __('Updatetime'), visible: false, sortable: true, operate: 'RANGE', addclass: 'datetimerange', formatter: Table.api.formatter.datetime},
{field: 'logintime', title: __('Logintime'), operate: 'RANGE', addclass: 'datetimerange', formatter: Table.api.formatter.datetime},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
},
add: function () {
Controller.api.bindevent();
},
edit: function () {
Controller.api.bindevent();
},
api: {
bindevent: function () {
Form.api.bindevent($("form[role=form]"));
}
}
};
return Controller;
});