添加用户登录,购物车管理

This commit is contained in:
lewis 2025-07-22 17:50:26 +08:00
parent 25e3b1adbd
commit 4e17620cbe
21 changed files with 1719 additions and 15 deletions

View File

@ -2,7 +2,7 @@
namespace app;
use hg\apidoc\exception\ErrorException;
use app\common\exception\UnauthorizedException;
use Next\VarDumper\Dumper;
use Next\VarDumper\DumperHandler;
use support\exception\BusinessException;
@ -20,11 +20,11 @@ class ExceptionHandler extends Handler
{
if ($exception instanceof Dumper) {
return \response(self::convertToHtml($exception));
}elseif ($exception instanceof BusinessException) {
if ($request->expectsJson()) {
return json(['code' => 0, 'msg' => $exception->getMessage(),'show'=>1]);
}
return response($exception->getMessage());
} elseif ($exception instanceof UnauthorizedException) {
$json = json_encode(['code' => 401, 'msg' => $exception->getMessage()], JSON_UNESCAPED_UNICODE);
return new Response(401, ['Content-Type' => 'application/json'], $json);
} elseif ($exception instanceof BusinessException) {
return json(['code' => 0, 'msg' => $exception->getMessage(), 'show' => 1]);
} elseif ($exception instanceof \Exception) {
$isDebug = config('app.debug');
$error = [
@ -36,9 +36,10 @@ class ExceptionHandler extends Handler
$error['file'] = $exception->getFile();
$error['line'] = $exception->getLine();
}
return response(json_encode($error, JSON_UNESCAPED_UNICODE));
$json = json_encode($error, JSON_UNESCAPED_UNICODE);
return new Response(200, ['Content-Type' => 'application/json'], $json);
}
// 非json请求则返回一个页面
return new Response(200, [], 'msg:'.$exception->getMessage().'。line:'.$exception->getLine().'。file:'.$exception->getFile());
return new Response(200, [], 'msg:' . $exception->getMessage() . '。line:' . $exception->getLine() . '。file:' . $exception->getFile());
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace app\api\controller;
use app\api\logic\UserLogic;
use app\common\controller\BaseLikeAdminController;
use support\Request;
class AuthController extends BaseLikeAdminController
{
public $optional = ['login'];
public function login(Request $request, UserLogic $userLogic)
{
$type = $request->post('type', 'wechat');
$code = $request->post('code', '', 1);
$token = $userLogic->login($type, $code);
return $this->data($token);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace app\api\controller;
use app\api\logic\CartLogic;
use app\api\validate\CartValidate;
use app\common\controller\BaseLikeAdminController;
class CartController extends BaseLikeAdminController
{
public function list(CartLogic $cartLogic)
{
$params['keyword'] = $this->request->get('keyword');
$params['uid'] = $this->request->user->id;
$params['page'] = $this->request->page();
$params['limit'] = $this->request->limit();
$data = $cartLogic->list($params);
return $this->data($data);
}
public function add(CartValidate $cartValidate, CartLogic $cartLogic)
{
$params = $this->request->post();
$params['uid'] = $this->request->user->id;
$params = $cartValidate->goCheck('add', $params);
$cartLogic->add($params);
return $this->success('添加成功', [], 1, 1);
}
public function delete(CartValidate $cartValidate, CartLogic $cartLogic)
{
$params = $cartValidate->post()->goCheck('delete');
$params['uid'] = $this->request->user->id;
if ($cartLogic->delete($params)) {
return $this->success('删除成功', [], 1, 1);
}
return $this->fail('删除失败');
}
public function count(CartLogic $cartLogic)
{
$params['uid'] = $this->request->user->id;
$data = $cartLogic->count($params);
return $this->data(['count' => $data]);
}
public function change()
{
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace app\api\controller;
use app\common\controller\BaseLikeAdminController;
use app\common\model\Dishes;
use app\common\model\DishesCategory;
class DishesController extends BaseLikeAdminController
{
public function category()
{
$category = DishesCategory::field('id,name,pid')->select()->toArray();
$category = linear_to_tree($category, 'children');
return $this->data($category);
}
public function dishes()
{
$categoryId = $this->request->get('category_id');
$keyword = $this->request->get('keyword');
$query = Dishes::field('id,dishes_category_id,name,image,intro')->where('status', 1);
if (!empty($categoryId)) {
$query->where('dishes_category_id', $categoryId);
}
if (!empty($keyword)) {
$query->whereLike('name', "%$keyword%");
}
$data = $query->select()->toArray();
return $this->data($data);
}
public function detail()
{
$id = $this->request->get('id');
$data = Dishes::with(['dishesProduct' => function ($query) {
$query->with(['product', 'unit']);
}])->where('id', $id)->where('status', 1)->find();
if (empty($data)) {
return $this->fail('菜品不存在');
}
$data = $data->toArray();
return $this->data($data);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace app\api\controller;
use app\common\controller\BaseLikeAdminController;
class OrderController extends BaseLikeAdminController
{
}

View File

@ -0,0 +1,81 @@
<?php
namespace app\api\logic;
use app\common\logic\BaseLogic;
use app\common\model\Cart;
use app\common\model\CartProduct;
use app\common\model\Dishes;
use app\common\model\DishesProduct;
use think\facade\Db;
class CartLogic extends BaseLogic
{
public function list($params)
{
$query = Cart::with(['cartDishes', 'cartProduct' => function ($query) {
$query->with(['product', 'unit']);
}])->where('uid', $params['uid'])->where('paid', 0)->where('buy_now', 0);
if (!empty($params['keyword'])) {
$dishesIds = Dishes::whereLike('name', "%{$params['keyword']}%")->column('id');
$query->whereIn('dishes_id', $dishesIds);
}
$data = $query->page($params['page'], $params['limit'])->select()->toArray();
return $data;
}
public function count($params)
{
return Cart::where('uid', $params['uid'])->where('paid', 0)->where('buy_now', 0)->count();
}
public function delete($params)
{
Db::startTrans();
try {
CartProduct::destroy(['cart_id' => $params['id']]);
Cart::destroy(['id' => $params['id'], 'uid' => $params['uid']]);
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
return false;
}
}
public function add($params)
{
Db::startTrans();
try {
$cart = new Cart();
if (!$cart->save($params)) {
throw new \Exception('添加购物车失败');
}
$products = DishesProduct::where('dishes_id', $params['dishes_id'])->select()->toArray();
$cartProductData = [];
$datetime = date('Y-m-d H:i:s');
foreach ($products as $product) {
$cartProductData[] = [
'uid' => $params['uid'],
'dishes_id' => $params['dishes_id'],
'product_id' => $product['product_id'],
'cart_id' => $cart->id,
'nums' => $product['nums'],
'unit_id' => $product['unit_id'],
'create_time' => $datetime,
'update_time' => $datetime,
];
}
if (!CartProduct::insertAll($cartProductData)) {
throw new \Exception('添加购物车商品失败');
}
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace app\api\logic;
use app\common\logic\BaseLogic;
use app\common\model\user\User;
use Tinywan\Jwt\JwtToken;
class UserLogic extends BaseLogic
{
public function login($code, $type)
{
if (env('environment') == 'local') {
$user = User::find(1);
$data = [
'id' => $user->id,
'nickname' => $user->nickname,
'avatar' => $user->avatar,
'code' => $user->code,
];
$token = JwtToken::generateToken(['id' => $user->id]);
$token['expires_in'] = $token['expires_in'] + time();
$token['refresh_token_expire'] = time() + config('plugin.tinywan.jwt.app.jwt.refresh_exp');
$data['token'] = $token;
} else {
$wechatUserLogic = new WechatUserLogic();
$data = $wechatUserLogic->findOrCreate($code);
}
return $data;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace app\api\logic;
use app\common\logic\BaseLogic;
use app\common\model\user\User;
use app\common\model\user\WechatUser;
use support\auth\Weixin;
use support\exception\BusinessException;
use think\facade\Db;
use Tinywan\Jwt\JwtToken;
class WechatUserLogic extends BaseLogic
{
public function findOrCreate($code)
{
$handler = new Weixin();
$appId = $handler->appId();
$res = $handler->login($code);
Db::startTrans();
try {
$wechatUser = WechatUser::withTrashed()->where('app_id', $appId)->where('routine_openid', $res['openid'])->find();
if (empty($wechatUser)) {
$wechatUser = new WechatUser();
$wechatUser->app_id = $appId;
$wechatUser->unionid = $res['union_id'] ?? $res['unionid'] ?? '';
$wechatUser->routine_openid = $res['openid'];
$wechatUser->nickname = !empty($this->nickname) ? $this->nickname : $handler->nickname();
$wechatUser->headimgurl = $handler->avatar();
if (!$wechatUser->save()) {
throw new \Exception('三方登录信息保存出错', 500);
}
}
$user = User::withTrashed()->where('wechat_user_id', $wechatUser->id)->field('id,nickname,avatar')->find();
if (!empty($user['delete_time'])) {
throw new BusinessException('账号已注销', 500);
}
if (empty($user)) {
$user = new User();
$user->wechat_user_id = $wechatUser->id;
$user->real_name = '';
$user->nickname = $wechatUser->nickname;
$user->avatar = $wechatUser->headimgurl;
}
$user->last_time = date('Y-m-d H:i:s');
if (!$user->save()) {
throw new \Exception('用户信息保存出错', 500);
}
Db::commit();
} catch (\Exception $exception) {
Db::rollback();
throw new \Exception($exception->getMessage(), $exception->getCode());
}
$tokenParam = [
'id' => $user->id,
'nickname' => $user->nickname,
'avatar' => $user->avatar,
'code' => $user->code,
];
$token = JwtToken::generateToken(['id' => $user->id]);
$token['expires_in'] = $token['expires_in'] + time();
$token['refresh_token_expire'] = time() + config('plugin.tinywan.jwt.app.jwt.refresh_exp');
$tokenParam['token'] = $token;
return $tokenParam;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace app\api\middleware;
use app\common\exception\UnauthorizedException;
use app\common\model\user\User;
use Tinywan\Jwt\JwtToken;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
class AuthMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
try {
$payload = JwtToken::verify();
if (!$payload) {
throw new UnauthorizedException('用户不存在', 401);
}
$request->user = User::withTrashed()->find($payload['extend']['id']);
if (empty($request->user)) {
throw new UnauthorizedException('用户不存在', 401);
}
} catch (\Throwable $e) {
$controller = new $request->controller;
if ((!isset($controller->optional) || !in_array($request->action, $controller->optional))) {
throw new UnauthorizedException('请登录', 401);
}
}
/** @var Response $response */
$response = $handler($request);
return $response;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace app\api\validate;
use app\common\validate\BaseValidate;
class CartValidate extends BaseValidate
{
/**
* 设置校验规则
* @var string[]
*/
protected $rule = [
'id' => 'require',
'uid' => 'require',
'dishes_id' => 'require',
// 'project_id' => 'require',
// 'cart_num' => 'require',
// 'paid' => 'require',
'buy_now' => 'require',
];
/**
* 参数描述
* @var string[]
*/
protected $field = [
'id' => 'id',
'uid' => '用户id',
'dishes_id' => '菜品id',
'project_id' => '项目id',
'cart_num' => '购买数量',
'paid' => '支付状态',
'buy_now' => '立即购买',
];
/**
* @notes 添加场景
* @return CartValidate
*/
public function sceneAdd()
{
return $this->remove('id', true);
}
/**
* @notes 编辑场景
* @return CartValidate
*/
public function sceneEdit()
{
return $this->only(['id']);
}
/**
* @notes 删除场景
* @return CartValidate
*/
public function sceneDelete()
{
return $this->only(['id']);
}
/**
* @notes 详情场景
* @return CartValidate
*/
public function sceneDetail()
{
return $this->only(['id']);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace app\common\exception;
use Throwable;
class UnauthorizedException extends \RuntimeException
{
protected $response = null;
public function __construct($message = "", $code = 0, $header=[],Throwable $previous = null)
{
$this->response = json(json_decode($message,true));
parent::__construct($message, $code, $previous);
}
public function getResponse(){
return $this->response;
}
}

23
app/common/model/Cart.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace app\common\model;
use think\model\concern\SoftDelete;
class Cart extends BaseModel
{
use SoftDelete;
protected $name = 'cart';
protected $deleteTime = 'delete_time';
public function cartDishes()
{
return $this->hasOne(Dishes::class, 'id', 'dishes_id')->bind(['dishes_name' => 'name', 'image']);
}
public function cartProduct()
{
return $this->hasMany(CartProduct::class, 'cart_id', 'id');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace app\common\model;
use think\model\concern\SoftDelete;
class CartProduct extends BaseModel
{
use SoftDelete;
protected $name = 'cart_product';
protected $deleteTime = 'delete_time';
public function product()
{
return $this->hasOne(Product::class, 'id', 'product_id')->bind(['product_name' => 'name', 'image', 'product_type']);
}
public function unit()
{
return $this->hasOne(ProductUnit::class, 'id', 'unit_id')->bind(['unit_name' => 'name']);
}
}

View File

@ -23,4 +23,9 @@ class Dishes extends BaseModel
return $this->hasOne(DishesCategory::class, 'id', 'dishes_category_id');
}
public function dishesProduct()
{
return $this->hasMany(DishesProduct::class, 'dishes_id', 'id');
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace app\common\model\user;
use app\common\model\BaseModel;
/**
* Class WechatUser
* @package app\common\model
*/
class WechatUser extends BaseModel
{
}

View File

@ -44,7 +44,9 @@
"doctrine/annotations": "^1.14",
"illuminate/redis": "^10.22",
"symfony/cache": "^5.4",
"next/var-dumper": "^0.1.0"
"next/var-dumper": "^0.1.0",
"tinywan/jwt": "^1.11",
"w7corp/easywechat": "^6.17"
},
"suggest": {
"ext-event": "For better performance. "

1032
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -22,5 +22,6 @@ return [
app\admin\middleware\LoginMiddleware::class,
// 权限认证
app\admin\middleware\AuthMiddleware::class,
]
];
],
'api' => [\app\api\middleware\AuthMiddleware::class],
];

View File

@ -0,0 +1,83 @@
<?php
return [
'enable' => true,
'jwt' => [
/** 算法类型 HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512 */
'algorithms' => 'HS256',
/** access令牌秘钥 */
'access_secret_key' => '2022d3d3LmJq',
/** access令牌过期时间单位秒。默认 2 小时 */
'access_exp' => 86400 * 3,
/** refresh令牌秘钥 */
'refresh_secret_key' => '2022KTxigxc9o50c',
/** refresh令牌过期时间单位秒。默认 7 天 */
'refresh_exp' => 604800,
/** refresh 令牌是否禁用,默认不禁用 false */
'refresh_disable' => false,
/** 令牌签发者 */
'iss' => 'webman.tinywan.cn',
/** 某个时间点后才能访问单位秒。30 表示当前时间30秒后才能使用 */
'nbf' => 0,
/** 时钟偏差冗余时间,单位秒。建议这个余地应该不大于几分钟 */
'leeway' => 60,
/** 是否允许单设备登录,默认不允许 false */
'is_single_device' => false,
/** 缓存令牌时间,单位:秒。默认 7 天 */
'cache_token_ttl' => 604800,
/** 缓存令牌前缀,默认 JWT:TOKEN: */
'cache_token_pre' => 'JWT:TOKEN:',
/** 缓存刷新令牌前缀,默认 JWT:REFRESH_TOKEN: */
'cache_refresh_token_pre' => 'JWT:REFRESH_TOKEN:',
/** 用户信息模型 */
'user_model' => function ($uid) {
return [];
},
/** 是否支持 get 请求获取令牌 */
'is_support_get_token' => false,
/** GET 请求获取令牌请求key */
'is_support_get_token_key' => 'authorization',
/** access令牌私钥 */
'access_private_key' => <<<EOD
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
EOD,
/** access令牌公钥 */
'access_public_key' => <<<EOD
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
EOD,
/** refresh令牌私钥 */
'refresh_private_key' => <<<EOD
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
EOD,
/** refresh令牌公钥 */
'refresh_public_key' => <<<EOD
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
EOD,
],
];

View File

@ -21,4 +21,16 @@ namespace support;
class Request extends \Webman\Http\Request
{
}
public function page()
{
$page = $this->get('page', 1);
return intval($page);
}
public function limit()
{
$limit = $this->get('limit', 10);
return intval(min($limit, 50));
}
}

63
support/auth/Weixin.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace support\auth;
use EasyWeChat\MiniApp\Application;
use support\exception\BusinessException;
class Weixin
{
public $app;
public $config;
public function __construct()
{
$this->config = [
'app_id' => '',
'secret' => '',
'response_type' => 'array',
'log' => [
'level' => 'debug',
'file' => __DIR__ . '/wechat.log',
],
];
$this->app = new Application($this->config);
}
public function appId()
{
return $this->config['app_id'];
}
public function nickname()
{
return '微信用户' . time() . rand(1, 100);
}
public function avatar()
{
return '/image/avatar.jpg';
}
public function login($code)
{
$result = $this->app->getUtils()->codeToSession($code);
if (isset($result['openid'])) {
return $result;
}
throw new BusinessException('登录失败', 500);
}
public function getPhoneNumber($code, $iv, $encryptedData)
{
$utils = $this->app->getUtils();
$response = $utils->codeToSession($code);
$session = $utils->decryptSession($response['session_key'], $iv, $encryptedData);
if (empty($session['phoneNumber'])) {
throw new BusinessException('获取手机号失败', 500);
}
return $session['phoneNumber'];
}
}