小程序用户改用门店登录

This commit is contained in:
liu
2026-08-20 13:46:04 +08:00
parent 6d9f02d831
commit c1d490134e
251 changed files with 689 additions and 2727 deletions
@@ -7,7 +7,6 @@ use App\Http\Requests\Customer\CustomerLevelFormRequest;
use App\Models\CustomerLevelModel;
use App\Models\NoticeModel;
use App\Models\StoreModel;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -114,18 +113,13 @@ class CustomerLevelController extends BaseController
}
/**
* 上浮比例变更后,给该等级下正常门店绑定的正常用户生成价格变更通知
* 上浮比例变更后,给该等级下全部正常门店生成价格变更通知
*/
private function notifyPriceChange(CustomerLevelModel $level): void
{
$userIds = UserModel::query()
->where('status', UserModel::STATUS_NORMAL)
->whereIn('store_id', function ($q) use ($level) {
$q->select('id')
->from('store')
->where('status', StoreModel::STATUS_NORMAL)
->where('level_id', $level->id);
})
$storeIds = StoreModel::query()
->where('status', StoreModel::STATUS_NORMAL)
->where('level_id', $level->id)
->pluck('id');
$content = mb_substr(
@@ -134,9 +128,9 @@ class CustomerLevelController extends BaseController
500
);
foreach ($userIds as $userId) {
foreach ($storeIds as $storeId) {
NoticeModel::create([
'user_id' => $userId,
'store_id' => $storeId,
'type' => NoticeModel::TYPE_PRICE,
'title' => '商品价格变更',
'content' => $content,
@@ -1,80 +0,0 @@
<?php
namespace App\Http\Controllers\Customer;
use App\Exceptions\RepositoryException;
use App\Http\Requests\Customer\MiniUserBindRequest;
use App\Models\StoreModel;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\Common\Http\Controllers\BaseController;
/**
* 小程序用户管理(用户由小程序登录自动生成,后台只做绑定与状态管理,无增删)
*/
#[RequestAttribute('/customer/miniUser', 'customer.miniUser')]
class MiniUserController extends BaseController
{
protected array $searchField = [
'store_id' => '=',
'status' => '=',
];
protected array $quickSearchField = ['nickname', 'phone'];
/** 小程序用户列表 */
#[GetRoute(authorize: 'query')]
public function query(Request $request): JsonResponse
{
$params = $request->all();
$pageSize = $params['pageSize'] ?? 10;
$data = $this->buildSearch($params, UserModel::query()->with('store:id,name'))
->orderBy('id', 'desc')
->paginate($pageSize)
->toArray();
return $this->success($data);
}
/**
* 绑定门店(一个门店可绑多个账号,一个账号只绑一个门店)
*/
#[PutRoute(route: '/{id}/bind', authorize: 'bind', where: ['id' => '[0-9]+'])]
public function bind(int $id, MiniUserBindRequest $request): JsonResponse
{
$validated = $request->validated();
$user = UserModel::find($id);
if (empty($user)) {
throw new RepositoryException('用户不存在');
}
$store = StoreModel::find((int) $validated['store_id']);
if (empty($store)) {
throw new RepositoryException('门店不存在');
}
$user->store_id = $store->id;
$user->save();
return $this->success();
}
/** 启用/停用 */
#[PutRoute(route: '/{id}/status', authorize: 'update', where: ['id' => '[0-9]+'])]
public function status(int $id, Request $request): JsonResponse
{
$data = $request->validate([
'status' => 'required|integer|in:0,1',
], [
'status.required' => '状态不能为空',
'status.in' => '状态值不正确',
]);
$user = UserModel::find($id);
if (empty($user)) {
throw new RepositoryException('用户不存在');
}
$user->status = (int) $data['status'];
$user->save();
return $this->success();
}
}
@@ -14,7 +14,7 @@ use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\Common\Http\Controllers\BaseController;
/**
* 通知管理(小程序端消息;user_id=0 为全员广播)
* 通知管理(小程序端消息;store_id=0 为全员广播)
*/
#[RequestAttribute('/customer/notice', 'customer.notice')]
class NoticeController extends BaseController
@@ -22,7 +22,7 @@ class NoticeController extends BaseController
protected array $searchField = [
'type' => '=',
'is_read' => '=',
'user_id' => '=',
'store_id' => '=',
];
protected array $quickSearchField = ['title', 'content'];
@@ -40,7 +40,7 @@ class NoticeController extends BaseController
return $this->success($data);
}
/** 发布通知(user_id=0 全员广播) */
/** 发布通知(store_id=0 全员广播) */
#[PostRoute(authorize: 'create')]
public function create(NoticeFormRequest $request): JsonResponse
{
@@ -15,7 +15,7 @@ use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\Common\Http\Controllers\BaseController;
/**
* 门店管理(小程序下单主体,即客户)
* 门店管理(小程序下单主体,即客户;门店账号即小程序登录账号
*/
#[RequestAttribute('/customer/store', 'customer.store')]
class StoreController extends BaseController
@@ -23,13 +23,14 @@ class StoreController extends BaseController
protected array $searchField = [
'name' => 'like',
'code' => 'like',
'username' => 'like',
'level_id' => '=',
'status' => '=',
];
protected array $quickSearchField = ['name', 'code', 'contact', 'phone'];
protected array $quickSearchField = ['name', 'code', 'username', 'contact', 'phone'];
/** 门店列表(含等级名回显) */
/** 门店列表(含等级名回显;密码永不回显 */
#[GetRoute(authorize: 'query')]
public function query(Request $request): JsonResponse
{
@@ -42,17 +43,18 @@ class StoreController extends BaseController
return $this->success($data);
}
/** 创建门店 */
/** 创建门店(同时设置小程序登录账号与初始密码) */
#[PostRoute(authorize: 'create')]
public function create(StoreFormRequest $request): JsonResponse
{
$validated = $request->validated();
$validated['code'] = generate_unique_code(StoreModel::class);
$validated['password'] = password_hash((string) $validated['password'], PASSWORD_DEFAULT);
StoreModel::create($validated);
return $this->success();
}
/** 编辑门店 */
/** 编辑门店(密码留空则不修改) */
#[PutRoute(route: '/{id}', authorize: 'update', where: ['id' => '[0-9]+'])]
public function update(int $id, StoreFormRequest $request): JsonResponse
{
@@ -60,7 +62,13 @@ class StoreController extends BaseController
if (empty($model)) {
throw new RepositoryException('门店不存在');
}
$model->update($request->validated());
$validated = $request->validated();
if (empty($validated['password'])) {
unset($validated['password']);
} else {
$validated['password'] = password_hash((string) $validated['password'], PASSWORD_DEFAULT);
}
$model->update($validated);
return $this->success();
}
@@ -11,7 +11,6 @@ use App\Models\StoreModel;
use App\Models\StoreOrderItemModel;
use App\Models\StoreOrderModel;
use App\Models\SupplierModel;
use App\Models\UserModel;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
@@ -349,9 +348,9 @@ class DashboardController extends BaseController
}
/**
* 基础档案计数:在营门店 / 在售商品 / 合作供应商 / 小程序用户
* 基础档案计数:在营门店 / 在售商品 / 合作供应商
*
* @return array{stores: int, products: int, suppliers: int, users: int}
* @return array{stores: int, products: int, suppliers: int}
*/
private function archives(): array
{
@@ -359,7 +358,6 @@ class DashboardController extends BaseController
'stores' => StoreModel::query()->where('status', StoreModel::STATUS_NORMAL)->count(),
'products' => ProductModel::query()->where('status', ProductModel::STATUS_ON)->count(),
'suppliers' => SupplierModel::query()->where('status', SupplierModel::STATUS_NORMAL)->count(),
'users' => UserModel::query()->where('status', UserModel::STATUS_NORMAL)->count(),
];
}
+1 -39
View File
@@ -2,13 +2,8 @@
namespace App\Http\Controllers;
use App\Http\Requests\UserRegisterRequest;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\Common\Trait\RequestJson;
@@ -17,7 +12,7 @@ class IndexController
{
use RequestJson;
// 权限验证白名单
protected array $noPermission = ['index', 'login', 'register', 'mail'];
protected array $noPermission = ['index'];
/** 获取首页信息 */
#[GetRoute('/index')]
@@ -27,37 +22,4 @@ class IndexController
return $this->success(compact('web_setting'));
}
/** 用户登录 */
#[PostRoute('/login')]
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'username' => 'required|min:4|alphaDash',
'password' => 'required|min:4|alphaDash',
]);
if (Auth::guard('users')->attempt($credentials, true)) {
$data = $request->user('users')
->createToken($credentials['username'])
->toArray();
return $this->success($data, __('user.login_success'));
}
return $this->error(__('user.login_error'));
}
/** 用户注册 */
#[PostRoute('/register')]
public function register(UserRegisterRequest $request): JsonResponse
{
$data = $request->validated();
$model = new UserModel;
$model->username = $data['username'];
$model->password = password_hash($data['password'], PASSWORD_DEFAULT);
$model->email = $data['email'];
if ($model->save()) {
return $this->success();
}
return $this->error('创建用户失败');
}
}
+43 -80
View File
@@ -3,10 +3,7 @@
namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Http\Requests\UserUpdateInfoRequest;
use App\Models\StoreModel;
use App\Models\UserModel;
use App\Services\WechatService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\AnnoRoute\Attribute\GetRoute;
@@ -15,113 +12,79 @@ use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
/**
* 小程序认证
* 小程序认证(门店 账号 + 密码 登录,不再使用微信能力)
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class AuthController extends BaseMiniController
{
/** 小程序登录 */
/** 门店登录(账号 + 密码) */
#[PostRoute('/auth/login', authorize: false)]
public function login(Request $request): JsonResponse
{
$validated = $request->validate([
'code' => 'required|string'
'username' => 'required|string|max:20',
'password' => 'required|string|max:20',
], [
'code.required' => '登录参数格式错误',
'code.string' => '登录参数格式错误',
'username.required' => '请输入登录账号',
'password.required' => '请输入登录密码',
]);
$session = app(WechatService::class)->code2Session($validated['code']);
$store = StoreModel::where('username', $validated['username'])->first();
$user = UserModel::where('openid', $session['openid'])->first();
if ($user === null || $user->status === UserModel::STATUS_DISABLED) {
return $this->error('账号不存在或已被停用');
if ($store === null || ! password_verify($validated['password'], (string) $store->password)) {
return $this->error('账号或密码错误');
}
if ($store->status === StoreModel::STATUS_DISABLED) {
return $this->error('账号已被停用,请联系客服处理');
}
$user->last_login_at = date('Y-m-d H:i:s');
$user->save();
$token = $user->createToken($user->openid)->toArray();
return $this->success([
'token' => $token['plainTextToken'],
'user' => $user->toArray(),
], __('user.login_success'));
$store->last_login_at = date('Y-m-d H:i:s');
$store->save();
}
/** 小程序注册 */
#[PostRoute('/auth/register', authorize: false)]
public function register(Request $request): JsonResponse
{
$validated = $request->validate([
'code' => 'required|string',
'phoneCode' => 'required|string',
'storeCode' => 'required|string'
], [
'code.required' => '注册参数格式错误',
'code.string' => '注册参数格式错误',
'phoneCode.required' => '注册参数格式错误',
'phoneCode.string' => '注册参数格式错误',
'storeCode.required' => '门店编码必须填写',
'storeCode.string' => '注册参数格式错误',
]);
$store = StoreModel::where('code', $validated['storeCode'])->first();
if (!$store) {
return $this->error('门店不存在!');
}
// 通过 code 换取 openid、session_key、unionid
$session = app(WechatService::class)->code2Session($validated['code']);
$user = UserModel::where('openid', $session['openid'])->first();
if ($user) {
return $this->error('你的微信已经注册,请直接登录!');
}
$userData = [
'openid' => $session['openid'],
'unionid' => $session['unionid'] ?? '',
'username' => 'wx_'.uniqid(),
'nickname' => '微信用户',
'store_id' => $store->id,
'avatar' => '',
'password' => '',
'last_login_at' => date('Y-m-d H:i:s'),
];
$phone = app(WechatService::class)->getPhone($validated['phoneCode']);
$userData['phone'] = $phone ?? '';
$user = UserModel::create($userData);
$token = $user->createToken($user->username)->toArray();
$token = $store->createToken($store->username)->toArray();
$store->load('level:id,name');
return $this->success([
'token' => $token['plainTextToken'],
'user' => $user->toArray(),
'user' => $store->toArray(),
], __('user.login_success'));
}
/** 当前用户信息 */
/** 当前门店信息(含客户等级) */
#[GetRoute('/auth/info')]
public function info(Request $request): JsonResponse
{
$user = UserModel::with(['store.level:id,name'])
->find($request->user()->id);
if ($user === null) {
$store = StoreModel::with('level:id,name')->find($request->user()->id);
if ($store === null) {
throw new RepositoryException('账号不存在');
}
return $this->success($user->toArray());
return $this->success($store->toArray());
}
#[PutRoute('auth/info')]
public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse
/** 修改登录密码 */
#[PutRoute('/auth/password')]
public function setPassword(Request $request): JsonResponse
{
UserModel::where('user_id', auth('user')->id())->update($request->validated());
$data = $request->validate([
'oldPassword' => 'required|string|max:20',
'newPassword' => 'required|string|min:6|max:20',
'rePassword' => 'required|same:newPassword',
], [
'oldPassword.required' => '请输入原密码',
'newPassword.required' => '请输入新密码',
'newPassword.min' => '新密码至少 6 位',
'rePassword.same' => '两次输入的密码不一致',
]);
return $this->error('更新成功');
$store = $this->currentStore($request);
if (! password_verify($data['oldPassword'], (string) $store->password)) {
return $this->error('原密码不正确');
}
$store->password = password_hash($data['newPassword'], PASSWORD_DEFAULT);
$store->save();
return $this->success([], '密码修改成功');
}
}
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Models\StoreModel;
use App\Models\UserModel;
use Illuminate\Http\Request;
use Modules\Common\Http\Controllers\BaseController;
use Modules\SystemUser\Models\SysAccessToken;
@@ -13,71 +12,39 @@ use Modules\SystemUser\Models\SysAccessToken;
* 小程序端控制器基类
*
* 无 #[RequestAttribute],不会被 AnnoRoute 注册为路由。
* 提供当前用户获取与门店绑定前置校验。
* 门店即用户:登录主体就是门店(store 表),提供当前门店获取与状态校验。
*/
abstract class BaseMiniController extends BaseController
{
/**
* 当前小程序用户auth:sanctum 注入的 tokenable
* 当前登录门店auth:sanctum 注入的 tokenable
*/
protected function currentUser(Request $request): UserModel
protected function currentStore(Request $request): StoreModel
{
$user = UserModel::find($request->user()->id);
if ($user === null || $user->status === UserModel::STATUS_DISABLED) {
$store = StoreModel::find($request->user()->id);
if ($store === null || $store->status === StoreModel::STATUS_DISABLED) {
throw new RepositoryException('账号不存在或已被停用');
}
return $user;
return $store;
}
/**
* 可选登录场景手动识别当前用户(路由关闭 authorize 时使用)
* 可选登录场景手动识别当前门店(路由关闭 authorize 时使用)
*
* 手动解析 Bearer token;未携带 token、token 无效或非小程序用户时返回 null(不抛错)。
* 手动解析 Bearer token;未携带 token、token 无效或非门店账号时返回 null(不抛错)。
*/
protected function optionalUser(Request $request): ?UserModel
protected function optionalStore(Request $request): ?StoreModel
{
$token = $request->bearerToken();
if (empty($token)) {
return null;
}
$accessToken = SysAccessToken::findToken($token);
if ($accessToken === null || $accessToken->tokenable_type !== UserModel::class) {
if ($accessToken === null || $accessToken->tokenable_type !== StoreModel::class) {
return null;
}
$user = UserModel::find($accessToken->tokenable_id);
if ($user === null || $user->status === UserModel::STATUS_DISABLED) {
return null;
}
return $user;
}
/**
* 门店端前置校验
*/
protected function ensureStoreBound(UserModel $user): StoreModel
{
if ($user->store_id <= 0) {
throw new RepositoryException('尚未绑定门店,请联系客服处理');
}
$store = StoreModel::find($user->store_id);
if ($store === null || $store->status !== StoreModel::STATUS_NORMAL) {
throw new RepositoryException('门店不存在或已停用,请联系客服处理');
}
return $store;
}
/**
* 用户当前绑定的正常门店(未绑定/已停用返回 null,不抛错)
*/
protected function boundStore(UserModel $user): ?StoreModel
{
if ($user->store_id <= 0) {
return null;
}
$store = StoreModel::find($user->store_id);
if ($store === null || $store->status !== StoreModel::STATUS_NORMAL) {
$store = StoreModel::find($accessToken->tokenable_id);
if ($store === null || $store->status === StoreModel::STATUS_DISABLED) {
return null;
}
+3 -6
View File
@@ -43,8 +43,7 @@ class BillController extends BaseMiniController
'pageSize.max' => '每页数量最大 50',
]);
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$query = BillModel::query()
->where('store_id', $store->id)
@@ -100,8 +99,7 @@ class BillController extends BaseMiniController
'category_id' => 'nullable|integer|min:0',
]);
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$ids = array_values(array_unique(array_filter(array_map('intval', explode(',', (string) $data['ids'])))));
if ($ids === [] || count($ids) > 100) {
@@ -131,8 +129,7 @@ class BillController extends BaseMiniController
#[GetRoute('/bill/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$bill = BillModel::with('purchase:id,purchase_no,purchase_date')
->where('store_id', $store->id)
+12 -17
View File
@@ -34,8 +34,7 @@ class CartController extends BaseMiniController
#[PostRoute('/cart')]
public function store(MiniCartRequest $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
if ($store->level_id <= 0 || $store->level === null) {
return $this->error('门店未设置客户等级,无法加购,请联系客服');
}
@@ -48,9 +47,9 @@ class CartController extends BaseMiniController
$quantity = (string) $request->validated('quantity');
$cart = DB::transaction(function () use ($user, $productId, $quantity) {
$cart = DB::transaction(function () use ($store, $productId, $quantity) {
$row = CartModel::query()
->where('user_id', $user->id)
->where('store_id', $store->id)
->where('product_id', $productId)
->lockForUpdate()
->first();
@@ -67,7 +66,7 @@ class CartController extends BaseMiniController
}
return CartModel::create([
'user_id' => $user->id,
'store_id' => $store->id,
'product_id' => $productId,
'quantity' => $quantity,
]);
@@ -85,11 +84,10 @@ class CartController extends BaseMiniController
#[GetRoute('/cart', authorize: true)]
public function index(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$rows = CartModel::query()
->where('user_id', $user->id)
->where('store_id', $store->id)
->orderBy('id', 'desc')
->get();
@@ -174,12 +172,11 @@ class CartController extends BaseMiniController
#[PutRoute('/cart/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function update(int $id, MiniCartRequest $request): JsonResponse
{
$user = $this->currentUser($request);
$this->ensureStoreBound($user);
$store = $this->currentStore($request);
$row = CartModel::query()
->where('id', $id)
->where('user_id', $user->id)
->where('store_id', $store->id)
->first();
if ($row === null) {
throw new RepositoryException('购物车项不存在');
@@ -197,12 +194,11 @@ class CartController extends BaseMiniController
#[DeleteRoute('/cart/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function destroy(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$this->ensureStoreBound($user);
$store = $this->currentStore($request);
$deleted = CartModel::query()
->where('id', $id)
->where('user_id', $user->id)
->where('store_id', $store->id)
->delete();
if ($deleted === 0) {
throw new RepositoryException('购物车项不存在');
@@ -217,10 +213,9 @@ class CartController extends BaseMiniController
#[DeleteRoute('/cart', authorize: true)]
public function clear(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$this->ensureStoreBound($user);
$store = $this->currentStore($request);
CartModel::query()->where('user_id', $user->id)->delete();
CartModel::query()->where('store_id', $store->id)->delete();
return $this->success([], '购物车已清空');
}
+16 -16
View File
@@ -11,34 +11,34 @@ use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
/**
* 小程序通知(本通知 + 全员广播)
* 小程序通知(本通知 + 全员广播)
*
* 广播已读处理:user_id=0 的广播是全局共享记录,直接改 is_read 会影响其他用户
* 因此标记已读时复制一条本专属的已读记录(data.broadcast_from 记来源),
* 广播已读处理:store_id=0 的广播是全局共享记录,直接改 is_read 会影响其他门店
* 因此标记已读时复制一条本专属的已读记录(data.broadcast_from 记来源),
* 列表查询时排除已有已读副本的广播。
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class NoticeController extends BaseMiniController
{
/** 本通知 + 全员广播(user_id in [0, 当前id]),分页 + unread_count */
/** 本通知 + 全员广播(store_id in [0, 当前id]),分页 + unread_count */
#[GetRoute('/notice', authorize: true)]
public function index(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->currentStore($request);
// 本已读过的广播来源ID(已读副本记录)
// 本已读过的广播来源ID(已读副本记录)
$readBroadcastIds = NoticeModel::query()
->where('user_id', $user->id)
->where('store_id', $store->id)
->whereNotNull('data->broadcast_from')
->pluck('data')
->map(static fn (?array $data) => $data['broadcast_from'] ?? null)
->filter()
->values();
$query = NoticeModel::query()->where(function ($q) use ($user, $readBroadcastIds) {
$q->where('user_id', $user->id)
$query = NoticeModel::query()->where(function ($q) use ($store, $readBroadcastIds) {
$q->where('store_id', $store->id)
->orWhere(function ($broadcastQuery) use ($readBroadcastIds) {
$broadcastQuery->where('user_id', NoticeModel::BROADCAST_USER_ID);
$broadcastQuery->where('store_id', NoticeModel::BROADCAST_STORE_ID);
if ($readBroadcastIds->isNotEmpty()) {
$broadcastQuery->whereNotIn('id', $readBroadcastIds->all());
}
@@ -55,24 +55,24 @@ class NoticeController extends BaseMiniController
return $this->success($data);
}
/** 标记已读(广播 → 复制本已读副本;个人通知 → 直接更新) */
/** 标记已读(广播 → 复制本已读副本;定向通知 → 直接更新) */
#[PutRoute('/notice/{id}/read', authorize: true, where: ['id' => '[0-9]+'])]
public function read(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->currentStore($request);
$notice = NoticeModel::whereIn('user_id', [NoticeModel::BROADCAST_USER_ID, $user->id])->find($id);
$notice = NoticeModel::whereIn('store_id', [NoticeModel::BROADCAST_STORE_ID, $store->id])->find($id);
if ($notice === null) {
throw new RepositoryException('通知不存在');
}
if ($notice->user_id === NoticeModel::BROADCAST_USER_ID) {
$exists = NoticeModel::where('user_id', $user->id)
if ($notice->store_id === NoticeModel::BROADCAST_STORE_ID) {
$exists = NoticeModel::where('store_id', $store->id)
->where('data->broadcast_from', $notice->id)
->exists();
if (! $exists) {
NoticeModel::create([
'user_id' => $user->id,
'store_id' => $store->id,
'type' => $notice->type,
'title' => $notice->title,
'content' => $notice->content,
+5 -10
View File
@@ -30,8 +30,7 @@ class OrderController extends BaseMiniController
#[PostRoute('/order', authorize: true)]
public function store(MiniOrderRequest $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$level = $store->level_id > 0 ? $store->level : null;
if ($level === null) {
throw new RepositoryException('门店未设置客户等级,无法下单,请联系客服');
@@ -138,8 +137,7 @@ class OrderController extends BaseMiniController
'pageSize.max' => '每页数量最大 50',
]);
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$query = StoreOrderModel::query()
->where('store_id', $store->id)
@@ -220,8 +218,7 @@ class OrderController extends BaseMiniController
throw new RepositoryException('period 参数只能是 day/week/month');
}
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
// 按数据库方言选择周期分组表达式(生产 MySQL / 测试 SQLite
$driver = DB::connection()->getDriverName();
@@ -259,8 +256,7 @@ class OrderController extends BaseMiniController
#[GetRoute('/order/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$order = StoreOrderModel::with('items')
->where('store_id', $store->id)
@@ -276,8 +272,7 @@ class OrderController extends BaseMiniController
#[PutRoute('/order/{id}/cancel', authorize: true, where: ['id' => '[0-9]+'])]
public function cancel(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$order = StoreOrderModel::where('store_id', $store->id)->find($id);
if ($order === null) {
@@ -48,8 +48,7 @@ class PaymentController extends BaseMiniController
#[GetRoute('/payment', authorize: true)]
public function index(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$query = PaymentModel::query()
->where('store_id', $store->id)
@@ -89,11 +88,10 @@ class PaymentController extends BaseMiniController
'remark.max' => '备注超过最大长度',
]);
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$billIds = array_map('intval', $data['bill_ids']);
$payment = DB::transaction(function () use ($store, $user, $data, $billIds) {
$payment = DB::transaction(function () use ($store, $data, $billIds) {
$bills = BillModel::query()
->where('store_id', $store->id)
->whereIn('id', $billIds)
@@ -119,7 +117,6 @@ class PaymentController extends BaseMiniController
$payment = PaymentModel::create([
'payment_no' => app(BillNumberService::class)->make('ZF'),
'store_id' => $store->id,
'user_id' => $user->id,
'amount' => $amount,
'pay_method' => (int) $data['pay_method'],
'voucher_ids' => array_map('intval', $data['voucher_ids']),
@@ -144,8 +141,7 @@ class PaymentController extends BaseMiniController
#[GetRoute('/payment/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$payment = PaymentModel::query()
->where('store_id', $store->id)
@@ -51,8 +51,7 @@ class ProductController extends BaseMiniController
->paginate($pageSize);
// 当前门店的等级(售价 = 成本价 × (100 + 等级上浮比例) / 100
$user = $this->optionalUser($request);
$store = $user !== null ? $this->boundStore($user) : null;
$store = $this->optionalStore($request);
$level = ($store !== null && $store->level_id > 0) ? $store->level : null;
$paginator->getCollection()->transform(
@@ -70,7 +69,7 @@ class ProductController extends BaseMiniController
}
/**
* 商品详情(免登录浏览;登录门店按等级上浮比例显示换算价,未登录/未绑店/未设等级 price=null
* 商品详情(免登录浏览;登录门店按等级上浮比例显示换算价,未登录/未设等级 price=null
*/
#[GetRoute('/product/{id}', authorize: false, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
@@ -84,8 +83,7 @@ class ProductController extends BaseMiniController
}
$price = null;
$user = $this->optionalUser($request);
$store = $user !== null ? $this->boundStore($user) : null;
$store = $this->optionalStore($request);
$level = ($store !== null && $store->level_id > 0) ? $store->level : null;
if ($level !== null) {
$price = CustomerLevelModel::calcLevelPrice($product->cost_price, $level->percent);
@@ -18,8 +18,7 @@ class StoreController extends BaseMiniController
#[GetRoute('/store/info', authorize: true)]
public function info(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
return $this->success([
'id' => $store->id,
@@ -46,8 +45,7 @@ class StoreController extends BaseMiniController
'address.max' => '地址最长 255 个字符',
]);
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
$store = $this->currentStore($request);
$store->update($data);
@@ -26,9 +26,9 @@ class UploadController extends BaseMiniController
'file.max' => '图片不能超过 5MB',
]);
$user = $this->currentUser($request);
$store = $this->currentStore($request);
// 分组 4=用户上传,渠道 20=APP用户
$result = app(SysFileService::class)->upload($data['file'], 4, 20, $user->id);
$result = app(SysFileService::class)->upload($data['file'], 4, 20, $store->id);
return $this->success([
'id' => $result['id'],
@@ -5,8 +5,8 @@ namespace App\Http\Controllers\Order;
use App\Exceptions\RepositoryException;
use App\Models\BillModel;
use App\Models\NoticeModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
use App\Models\UserModel;
use App\Services\ItemImageResolver;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -219,26 +219,27 @@ class StoreOrderController extends BaseController
}
/**
* 状态流转后通知门店用户
* 状态流转后通知门店
*/
private function notifyStore(StoreOrderModel $order): void
{
$userIds = UserModel::query()
->where('store_id', $order->store_id)
->where('status', UserModel::STATUS_NORMAL)
->pluck('id');
$store = StoreModel::query()
->where('id', $order->store_id)
->where('status', StoreModel::STATUS_NORMAL)
->first();
if ($store === null) {
return;
}
$statusName = StoreOrderModel::STATUS_NAMES[$order->status] ?? (string) $order->status;
foreach ($userIds as $userId) {
NoticeModel::create([
'user_id' => $userId,
'type' => NoticeModel::TYPE_ORDER,
'title' => '订单状态更新',
'content' => mb_substr("您的订单 {$order->order_no} 状态已更新为「{$statusName}", 0, 500),
'data' => ['order_id' => $order->id],
'is_read' => NoticeModel::UNREAD,
]);
}
NoticeModel::create([
'store_id' => $store->id,
'type' => NoticeModel::TYPE_ORDER,
'title' => '订单状态更新',
'content' => mb_substr("您的订单 {$order->order_no} 状态更新为「{$statusName}", 0, 500),
'data' => ['order_id' => $order->id],
'is_read' => NoticeModel::UNREAD,
]);
}
/**
@@ -9,7 +9,6 @@ use App\Models\CustomerLevelModel;
use App\Models\NoticeModel;
use App\Models\ProductModel;
use App\Models\StoreModel;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -188,19 +187,14 @@ class ProductController extends BaseController
->implode('、');
$content = mb_substr('以下商品价格已调整:' . $productNames . ',下次登录小程序后按新价格显示', 0, 500);
// 成本价变更影响所有等级的售价:通知全部正常门店绑定的正常用户
$userIds = UserModel::query()
->where('status', UserModel::STATUS_NORMAL)
->whereIn('store_id', function ($q) {
$q->select('id')
->from('store')
->where('status', StoreModel::STATUS_NORMAL);
})
// 成本价变更影响所有等级的售价:通知全部正常门店
$storeIds = StoreModel::query()
->where('status', StoreModel::STATUS_NORMAL)
->pluck('id');
foreach ($userIds as $userId) {
foreach ($storeIds as $storeId) {
NoticeModel::create([
'user_id' => $userId,
'store_id' => $storeId,
'type' => NoticeModel::TYPE_PRICE,
'title' => '商品价格变更',
'content' => $content,
@@ -35,7 +35,7 @@ class PaymentController extends BaseController
$params = $request->all();
$pageSize = $params['pageSize'] ?? 10;
$data = $this->buildSearch($params, PaymentModel::query()
->with(['store:id,name', 'user:id,nickname', 'auditor:id,nickname'])
->with(['store:id,name', 'auditor:id,nickname'])
->withCount('bills'))
->orderBy('status')
->orderBy('id', 'desc')
@@ -48,7 +48,7 @@ class PaymentController extends BaseController
#[GetRoute(route: '/{id}', authorize: 'query', where: ['id' => '[0-9]+'])]
public function detail(int $id): JsonResponse
{
$payment = PaymentModel::with(['store:id,name,contact,phone', 'user:id,nickname', 'auditor:id,nickname'])->find($id);
$payment = PaymentModel::with(['store:id,name,contact,phone', 'auditor:id,nickname'])->find($id);
if (empty($payment)) {
throw new RepositoryException('支付记录不存在');
}
-68
View File
@@ -1,68 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UserUpdateInfoRequest;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\Common\Trait\RequestJson;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
#[RequestAttribute('/api/user', authGuard: 'users')]
class UserController
{
use RequestJson;
protected array $noPermission = ['refreshToken'];
#[GetRoute]
public function getUserInfo(): JsonResponse
{
$info = auth()->user();
return $this->success(compact('info'));
}
#[PostRoute('/logout')]
public function logout(): JsonResponse
{
$user_id = auth('users')->id();
$model = new UserModel;
if ($model->logout($user_id)) {
return $this->success('退出登录成功');
} else {
return $this->error($model->getErrorMsg());
}
}
#[PutRoute]
public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse
{
UserModel::where('user_id', auth('user')->id())->update($request->validated());
return $this->error('更新成功');
}
#[PostRoute('/setPwd')]
public function setPassword(Request $request): JsonResponse
{
$data = $request->validate([
'oldPassword' => 'required|string|max:20',
'newPassword' => 'required|string|min:6|max:20',
'rePassword' => 'required|same:newPassword',
]);
$user_id = auth('user')->id();
$user = UserModel::query()->find($user_id);
if (! password_verify($data['oldPassword'], $user['password'])) {
return $this->error('旧密码不正确!');
}
$user->password = password_hash($data['newPassword'], PASSWORD_DEFAULT);
if ($user->save()) {
return $this->success('更新成功');
}
return $this->error('更新失败');
}
}