qinzexin 6b8be76b29 多样青春活动版:
开发了抖音端授权登录
修改原有活动管理,增加渠道设置,添加和查询时可单独只展示抖音端或者微信端任意一端,而其他端不展示
核销码这块原来是小程序生成,但前端生成无法调成ui需要的样式,改为了后端生成
2025-05-07 18:48:18 +08:00

691 lines
25 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\api\controller;
use addons\xilufitness\services\login\LoginService;
use app\common\controller\Api;
use app\common\library\Ems;
use app\common\library\Sms;
use app\common\model\dyqc\ManystoreShop;
use app\common\model\manystore\UserAuth;
use app\common\model\school\activity\Activity;
use app\common\model\school\activity\order\SettleLog;
use fast\Random;
use think\Cache;
use think\Config;
use think\Db;
use think\Log;
use think\Validate;
use app\admin\library\Wechat;
/**
* 会员接口
*/
class User extends Api
{
protected $noNeedLogin = ["registerLogin","getTtOpenid","decodeTtData",'getOpenid','decodeData','login', 'mobilelogin', 'register', 'resetpwd', 'changeemail', 'changemobile', 'third'];
protected $noNeedRight = '*';
protected $miniConfig;
public function _initialize()
{
$this->miniConfig = (new Wechat)->getMiniConfig();
parent::_initialize();
// if (!Config::get('fastadmin.usercenter')) {
// $this->error(__('User center already closed'));
// }
}
/**
* @ApiTitle(获取抖音小程序openid信息)
* @ApiSummary(根据前端code换取openid信息)
* @ApiRoute(/api/user/getTtOpenid)
* @ApiMethod(GET)
* @ApiParams(name="code",type="string",required=true,description="前端code值")
* @ApiReturnParams(name="code", type="integer",required=true, sample="0")
* @ApiReturnParams(name="msg", type="string",required=true, sample="获取成功")
* @ApiReturnParams(name="data", type="bject",required=true, description= "扩展数据")
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {}
*})
*/
public function getTtOpenid(){
$code = $this->request->param('code/s');
//获取配置
$appId = config("site.tt_app_key"); // 替换为你的抖音小程序 AppID
$appSecret = config("site.tt_app_secret"); // 替换为你的抖音小程序 AppSecret
try {
$url = "https://developer.toutiao.com/api/apps/jscode2session?appid={$appId}&secret={$appSecret}&code={$code}";
$response = file_get_contents($url);
$data = json_decode($response, true);
if (isset($data['openid'])) {
$result = [
'openid' => $data['openid'],
'unionid' => $data['unionid'] ?? null
];
} else {
$result = $data;
}
} catch (\WeChat\Exceptions\LocalCacheException $e){
$this->error($e->getMessage());
} catch (\Exception $e){
$this->error($e->getMessage());
}
if(empty($result['openid'])){
$this->error(__('获取openid失败'),$result);
}
Cache::set('tt_miniapp_code'.$code.$result['openid'],"1",60);
$this->success('',$result);
}
/**
* @ApiTitle(获取微信小程序openid信息)
* @ApiSummary(根据前端code换取openid信息)
* @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")
* @ApiHeaders(name = "token", type = "string", require = true, description = "Token")
* @ApiReturnParams(name="code", type="integer",required=true, sample="0")
* @ApiReturnParams(name="msg", type="string",required=true, sample="获取成功")
* @ApiReturnParams(name="data", type="bject",required=true, description= "扩展数据")
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {}
*})
*/
public function getOpenid(){
$code = $this->request->param('code/s');
try {
$result = LoginService::getInstance(['mini_config' => $this->miniConfig])->getOpenid($code);
} catch (\WeChat\Exceptions\LocalCacheException $e){
$this->error($e->getMessage());
} catch (\Exception $e){
$this->error($e->getMessage());
}
if(empty($result['openid'])){
$this->error(__('获取openid失败'),$result);
}
Cache::set('wechat_miniapp_code'.$code.$result['openid'],"1",60);
$this->success('',$result);
}
/**
* @ApiTitle(微信加密信息解密)
* @ApiSummary(微信解密微信信息)
* @ApiRoute(/api/user/decodeData)
* @ApiMethod(POST)
* @ApiParams(name = "iv", type = "string",required=true)
* @ApiParams(name = "encryptedData", type = "string",required=true)
* @ApiParams(name = "sessionKey", type = "string",required=true)
* @ApiHeaders(name = "brand-key", type = "string",require = true, description = "应用key")
* @ApiHeaders(name = "token", type = "string", require = true, description = "Token")
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {}
*})
*/
public function decodeData() {
$iv = $this->request->param('iv/s');
$encryptedData = $this->request->param('encryptedData/s');
$sessionKey = $this->request->param('sessionKey/s');
if(empty($iv) || empty($encryptedData) || empty($sessionKey)){
$this->error(__('Params error'));
}
$result = LoginService::getInstance(['mini_config' => $this->miniConfig])->decodeData($iv,$sessionKey,$encryptedData);
$info = empty($result["phoneNumber"]) ? ($result["purePhoneNumber"] ?? '' ) : $result["phoneNumber"];
Cache::set('wechat_miniapp_core'.$info,"1",60);
$this->success('',$result);
}
/**
* @ApiTitle(抖音小程序加密信息解密)
* @ApiSummary(解密抖音小程序返回的加密数据)
* @ApiRoute(/api/user/decodeTtData)
* @ApiMethod(POST)
* @ApiParams(name = "iv", type = "string", required=true, description="初始向量")
* @ApiParams(name = "encryptedData", type = "string", required=true, description="加密数据")
* @ApiParams(name = "sessionKey", type = "string", required=true, description="会话密钥")
* @ApiReturn({
"code" => 1,
"msg" => "获取成功",
"data" => {}
*})
*/
public function decodeTtData()
{
$iv = $this->request->param('iv/s');
$encryptedData = $this->request->param('encryptedData/s');
$sessionKey = $this->request->param('sessionKey/s');
if (empty($iv) || empty($encryptedData) || empty($sessionKey)) {
$this->error(__('参数缺失'));
}
try {
// 调用服务层进行解密
$result = $this->decryptTtData($encryptedData, $sessionKey, $iv);
// 缓存手机号(如存在)
$phoneNumber = $result['phoneNumber'] ?? ($result['purePhoneNumber'] ?? '');
if ($phoneNumber) {
Cache::set("tt_miniapp_core{$phoneNumber}", "1", 60);
}
$this->success('解密成功', $result);
} catch (\Exception $e) {
$this->error("解密失败:" . $e->getMessage());
}
}
/**
* 抖音小程序数据解密
*
* @param string $encryptedData 加密数据
* @param string $sessionKey 会话密钥
* @param string $iv 初始向量
* @return array 解密后的用户数据
* @throws Exception
*/
protected function decryptTtData(string $encryptedData, string $sessionKey, string $iv): array
{
// base64 decode
$sessionKey = base64_decode($sessionKey);
$iv = base64_decode($iv);
$encrypted = base64_decode($encryptedData);
// 解密
$decrypted = openssl_decrypt($encrypted, 'AES-128-CBC', $sessionKey, OPENSSL_RAW_DATA, $iv);
if (!$decrypted) {
throw new Exception('解密失败openssl_decrypt 错误');
}
// 去除 PKCS7 填充
$pad = ord($decrypted[strlen($decrypted) - 1]);
$decrypted = substr($decrypted, 0, strlen($decrypted) - $pad);
// 提取 JSON 数据
$data = json_decode($decrypted, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('JSON 解析失败');
}
return $data;
}
/**
* @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微信 tt抖音")
* @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','');
$platform = $this->request->param('platform/s','wechat');
$code = $this->request->param('code/s','');
if(empty($code)){
$this->error(__('缺少code'));
}
//手机号变必填
// if(empty($extend['mobile'])){
// $this->error(__('未传手机号'));
// }
$wechat_mini_code = Cache::get("{$platform}_{$params['apptype']}_code".$code.$params['openid']);
if(!$wechat_mini_code)$this->error("授权code已过期或已使用请重新发起授权",['errcode'=>30002]);
if($extend['mobile']){
$wechat_mini_mobile = Cache::get("{$platform}_{$params['apptype']}_core".$extend['mobile']);
if(!$wechat_mini_mobile)$this->error("授权手机号已过期或已使用,请重新发起授权!",['errcode'=>30002]);
}
//推荐人:逻辑未实现
$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("{$platform}_{$params['apptype']}_code".$code.$params['openid']);
if($extend['mobile']){
Cache::rm("{$platform}_{$params['apptype']}_core".$extend['mobile']);
}
$this->success('获取成功', ['token' => $this->auth->getToken()]);
}
/**
* 会员中心
*/
public function index()
{
$data = [
'welcome' => $this->auth->nickname,
'user_info'=>$this->auth->getUserinfo()
];
//是否有核销按钮展示权
// $data['have_auth'] = \app\common\model\User::verificationAuth($this->auth->id);
// $data['have_teacher'] = \app\common\model\User::teacherAuth($this->auth->id);
//机构认证信息
$data['shop_auth_info'] = ManystoreShop::getAuthInfo($this->auth->id);
$data['user_info']["avatar"] = $data['user_info']["avatar"]? cdnurl($data['user_info']["avatar"],true):$data['user_info']["avatar"];
//结算钱包信息
$data["settle_info"] = SettleLog::getUserSettleInfo($this->auth->id);
//活动信息
$data["activity_info"] = Activity::getActivityInfo($this->auth->id);
$this->success('调用成功',$data);
}
/**
* 会员登录
*
* @ApiMethod (POST)
* @ApiParams (name="account", type="string", required=true, description="账号")
* @ApiParams (name="password", type="string", required=true, description="密码")
*/
public function login()
{
$account = $this->request->post('account');
$password = $this->request->post('password');
if (!$account || !$password) {
$this->error(__('Invalid parameters'));
}
$ret = $this->auth->login($account, $password);
if ($ret) {
$data = ['userinfo' => $this->auth->getUserinfo()];
$this->success(__('Logged in successful'), $data);
} else {
$this->error($this->auth->getError());
}
}
/**
* 手机验证码登录
*
* @ApiMethod (POST)
* @ApiParams (name="mobile", type="string", required=true, description="手机号")
* @ApiParams (name="captcha", type="string", required=true, description="验证码")
*/
public function mobilelogin()
{
$mobile = $this->request->post('mobile');
$captcha = $this->request->post('captcha');
if (!$mobile || !$captcha) {
$this->error(__('Invalid parameters'));
}
if (!Validate::regex($mobile, "^1\d{10}$")) {
$this->error(__('Mobile is incorrect'));
}
if (!Sms::check($mobile, $captcha, 'mobilelogin')) {
$this->error(__('Captcha is incorrect'));
}
$user = \app\common\model\User::getByMobile($mobile);
if ($user) {
if ($user->status != 'normal') {
$this->error(__('Account is locked'));
}
//如果已经有账号则直接登录
$ret = $this->auth->direct($user->id);
} else {
$ret = $this->auth->register($mobile, Random::alnum(), '', $mobile, []);
}
if ($ret) {
Sms::flush($mobile, 'mobilelogin');
$data = ['userinfo' => $this->auth->getUserinfo()];
$this->success(__('Logged in successful'), $data);
} else {
$this->error($this->auth->getError());
}
}
/**
* 注册会员
*
* @ApiMethod (POST)
* @ApiParams (name="username", type="string", required=true, description="用户名")
* @ApiParams (name="password", type="string", required=true, description="密码")
* @ApiParams (name="email", type="string", required=true, description="邮箱")
* @ApiParams (name="mobile", type="string", required=true, description="手机号")
* @ApiParams (name="code", type="string", required=true, description="验证码")
*/
public function register()
{
$username = $this->request->post('username');
$password = $this->request->post('password');
$email = $this->request->post('email');
$mobile = $this->request->post('mobile');
$code = $this->request->post('code');
if (!$username || !$password) {
$this->error(__('Invalid parameters'));
}
if ($email && !Validate::is($email, "email")) {
$this->error(__('Email is incorrect'));
}
if ($mobile && !Validate::regex($mobile, "^1\d{10}$")) {
$this->error(__('Mobile is incorrect'));
}
$ret = Sms::check($mobile, $code, 'register');
if (!$ret) {
$this->error(__('Captcha is incorrect'));
}
$ret = $this->auth->register($username, $password, $email, $mobile, []);
if ($ret) {
$data = ['userinfo' => $this->auth->getUserinfo()];
$this->success(__('Sign up successful'), $data);
} else {
$this->error($this->auth->getError());
}
}
/**
* 退出登录
* @ApiMethod (POST)
*/
public function logout()
{
if (!$this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$this->auth->logout();
$this->success(__('Logout successful'));
}
/**
* 修改会员个人信息
*
* @ApiMethod (POST)
* @ApiParams (name="update_fields", type="string", 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="int", 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();
$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 && 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;
}
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,0]))$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();
//调用事件
$datas = ['user' => $user];
\think\Hook::listen('user_update_after', $datas);
$this->success();
}
/**
* 修改邮箱
*
* @ApiMethod (POST)
* @ApiParams (name="email", type="string", required=true, description="邮箱")
* @ApiParams (name="captcha", type="string", required=true, description="验证码")
*/
public function changeemail()
{
$user = $this->auth->getUser();
$email = $this->request->post('email');
$captcha = $this->request->post('captcha');
if (!$email || !$captcha) {
$this->error(__('Invalid parameters'));
}
if (!Validate::is($email, "email")) {
$this->error(__('Email is incorrect'));
}
if (\app\common\model\User::where('email', $email)->where('id', '<>', $user->id)->find()) {
$this->error(__('Email already exists'));
}
$result = Ems::check($email, $captcha, 'changeemail');
if (!$result) {
$this->error(__('Captcha is incorrect'));
}
$verification = $user->verification;
$verification->email = 1;
$user->verification = $verification;
$user->email = $email;
$user->save();
Ems::flush($email, 'changeemail');
$this->success();
}
/**
* 修改手机号
*
* @ApiMethod (POST)
* @ApiParams (name="mobile", type="string", required=true, description="手机号")
* @ApiParams (name="captcha", type="string", required=true, description="验证码")
*/
public function changemobile()
{
$user = $this->auth->getUser();
$mobile = $this->request->post('mobile');
$captcha = $this->request->post('captcha');
if (!$mobile || !$captcha) {
$this->error(__('Invalid parameters'));
}
if (!Validate::regex($mobile, "^1\d{10}$")) {
$this->error(__('Mobile is incorrect'));
}
if (\app\common\model\User::where('mobile', $mobile)->where('id', '<>', $user->id)->find()) {
$this->error(__('Mobile already exists'));
}
$result = Sms::check($mobile, $captcha, 'changemobile');
if (!$result) {
$this->error(__('Captcha is incorrect'));
}
$verification = $user->verification;
$verification->mobile = 1;
$user->verification = $verification;
$user->mobile = $mobile;
$user->save();
Sms::flush($mobile, 'changemobile');
$this->success();
}
/**
* 第三方登录
*
* @ApiMethod (POST)
* @ApiParams (name="platform", type="string", required=true, description="平台名称")
* @ApiParams (name="code", type="string", required=true, description="Code码")
*/
public function third()
{
$url = url('user/index');
$platform = $this->request->post("platform");
$code = $this->request->post("code");
$config = get_addon_config('third');
if (!$config || !isset($config[$platform])) {
$this->error(__('Invalid parameters'));
}
$app = new \addons\third\library\Application($config);
//通过code换access_token和绑定会员
$result = $app->{$platform}->getUserInfo(['code' => $code]);
if ($result) {
$loginret = \addons\third\library\Service::connect($platform, $result);
if ($loginret) {
$data = [
'userinfo' => $this->auth->getUserinfo(),
'thirdinfo' => $result
];
$this->success(__('Logged in successful'), $data);
}
}
$this->error(__('Operation failed'), $url);
}
/**
* 重置密码
*
* @ApiMethod (POST)
* @ApiParams (name="mobile", type="string", required=true, description="手机号")
* @ApiParams (name="newpassword", type="string", required=true, description="新密码")
* @ApiParams (name="captcha", type="string", required=true, description="验证码")
*/
public function resetpwd()
{
$type = $this->request->post("type", "mobile");
$mobile = $this->request->post("mobile");
$email = $this->request->post("email");
$newpassword = $this->request->post("newpassword");
$captcha = $this->request->post("captcha");
if (!$newpassword || !$captcha) {
$this->error(__('Invalid parameters'));
}
//验证Token
if (!Validate::make()->check(['newpassword' => $newpassword], ['newpassword' => 'require|regex:\S{6,30}'])) {
$this->error(__('Password must be 6 to 30 characters'));
}
if ($type == 'mobile') {
if (!Validate::regex($mobile, "^1\d{10}$")) {
$this->error(__('Mobile is incorrect'));
}
$user = \app\common\model\User::getByMobile($mobile);
if (!$user) {
$this->error(__('User not found'));
}
$ret = Sms::check($mobile, $captcha, 'resetpwd');
if (!$ret) {
$this->error(__('Captcha is incorrect'));
}
Sms::flush($mobile, 'resetpwd');
} else {
if (!Validate::is($email, "email")) {
$this->error(__('Email is incorrect'));
}
$user = \app\common\model\User::getByEmail($email);
if (!$user) {
$this->error(__('User not found'));
}
$ret = Ems::check($email, $captcha, 'resetpwd');
if (!$ret) {
$this->error(__('Captcha is incorrect'));
}
Ems::flush($email, 'resetpwd');
}
//模拟一次登录
$this->auth->direct($user->id);
$ret = $this->auth->changepwd($newpassword, '', true);
if ($ret) {
$this->success(__('Reset password successful'));
} else {
$this->error($this->auth->getError());
}
}
}