AccountController.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. <?php
  2. namespace App\Http\Controllers\Coach;
  3. use Illuminate\Http\Request;
  4. use App\Traits\ResponseTrait;
  5. use App\Traits\LocationDataTrait;
  6. use Illuminate\Support\Facades\Log;
  7. use App\Http\Controllers\Controller;
  8. use Illuminate\Support\Facades\Auth;
  9. use App\Enums\TechnicianLocationType;
  10. use App\Services\Coach\AccountService;
  11. use App\Http\Requests\Coach\SetLocationRequest;
  12. use App\Http\Requests\Coach\SubmitBaseInfoRequest;
  13. use App\Http\Requests\Coach\SubmitRealNameRequest;
  14. use App\Http\Requests\Coach\SubmitQualificationRequest;
  15. use App\Http\Requests\Coach\UpdateBasicInfoRequest;
  16. /**
  17. * @group 技师端
  18. *
  19. * 技师账户相关的API接口
  20. */
  21. class AccountController extends Controller
  22. {
  23. use ResponseTrait;
  24. use LocationDataTrait;
  25. protected AccountService $service;
  26. public function __construct(AccountService $service)
  27. {
  28. $this->service = $service;
  29. }
  30. /**
  31. * [账户]提交基本信息
  32. *
  33. * @description 提交技师的基本个人信息,包括头像、生活照片、个人资料等
  34. *
  35. * 业务流程:
  36. * 1. 验证提交的数据
  37. * 2. 调用服务层处理业务逻辑
  38. * 3. 返回处理结果
  39. *
  40. * 注意事项:
  41. * - 同一时间只能有一条待审核记录
  42. * - 审核不通过可以重新提交
  43. * - 头像和生活照片支持任意格式的图片数据
  44. * - 除性别和手机号外,其他字段均为可选
  45. * - 生活照片为可选,支持多张
  46. *
  47. * @authenticated 需要技师身份认证
  48. *
  49. * @bodyParam nickname string nullable 昵称(2-20个字符) Example: 张三
  50. * @bodyParam avatar string nullable 头像图片 Example: base64或其他格式的图片数据
  51. * @bodyParam life_photos array nullable 生活照片数组
  52. * @bodyParam life_photos.* string required 生活照片 Example: base64或其他格式的图片数据
  53. * @bodyParam gender string required 性别(1:男 2:女) Example: 1
  54. * @bodyParam mobile string required 手机号 Example: 13800138000
  55. * @bodyParam birthday date nullable 出生日期(年龄需满18岁) Example: 1990-01-01
  56. * @bodyParam work_years integer nullable 工作年限(0-99) Example: 5
  57. * @bodyParam intention_city string nullable 意向城市 Example: 北京
  58. * @bodyParam introduction string nullable 个人简介(10-255个字符) Example: 专业按摩师,从业5年
  59. *
  60. * @response 200 {
  61. * "status": true,
  62. * "message": "基本信息提交成功"
  63. * }
  64. * @response 404 {
  65. * "message": "技师信息不存在"
  66. * }
  67. * @response 422 {
  68. * "message": "已有待审核的基本信息记录"
  69. * }
  70. * @response 422 {
  71. * "message": "验证错误",
  72. * "errors": {
  73. * "nickname": ["昵称不能少于2个字符"],
  74. * "gender": ["性别不能为空"],
  75. * "mobile": ["手机号不能为空"],
  76. * "life_photos.*": ["生活照片格式不正确"]
  77. * }
  78. * }
  79. */
  80. public function submitBaseInfo(SubmitBaseInfoRequest $request)
  81. {
  82. // 获取验证后的数据
  83. $data = $request->validated();
  84. // 调用服务层处理业务逻辑
  85. // 传入当前认证用户和验证后的数据
  86. return $this->success(
  87. $this->service->submitBaseInfo(Auth::user(), $data)
  88. );
  89. }
  90. /**
  91. * [账户]提交资质信息
  92. *
  93. * @description 提交技师的资质认证信息,包括资质照片、营业执照和健康证
  94. *
  95. * @authenticated
  96. *
  97. * @bodyParam qual_type int required 资质类型(1:初级按摩师 2:中级按摩师 3:高级按摩师) Example: 1
  98. * @bodyParam qual_photo string required 资质证书照片 Example: base64或其他格式的图片数据
  99. * @bodyParam business_license string required 营业执照照片 Example: base64或其他格式的图片数据
  100. * @bodyParam health_cert string required 健康证照片 Example: base64或其他格式的图片数据
  101. *
  102. * @response {
  103. * "message": "资质信息提交成功"
  104. * }
  105. */
  106. public function submitQualification(SubmitQualificationRequest $request)
  107. {
  108. $data = $request->validated();
  109. return $this->success($this->service->submitQualification(Auth::user(), $data));
  110. }
  111. /**
  112. * [账户]提交实名认证
  113. *
  114. * @description 提交技师的实名认证信息
  115. *
  116. * @authenticated
  117. *
  118. * @bodyParam real_name string nullable 姓名(2-20个字符) Example: 张三
  119. * @bodyParam id_card string nullable 身份证号(18位) Example: 370602199001011234
  120. * @bodyParam id_card_front_photo string required 身份证正面照片 Example: base64或其他格式的图片数据
  121. * @bodyParam id_card_back_photo string required 身份证反面照片 Example: base64或其他格式的图片数据
  122. * @bodyParam id_card_hand_photo string required 手持身份证照片 Example: base64或其他格式的图片数据
  123. *
  124. * @response {
  125. * "message": "实名认证信息提交成功"
  126. * }
  127. */
  128. public function submitRealName(SubmitRealNameRequest $request)
  129. {
  130. $data = $request->validated();
  131. return $this->success($this->service->submitRealName(Auth::user(), $data));
  132. }
  133. /**
  134. * [账户]获取技师认证信息
  135. *
  136. * @description 获取技师的基本信息、资质信息和实名认证信息
  137. *
  138. * @authenticated
  139. *
  140. * @response {
  141. * "data": {
  142. * "base_info": {
  143. * "nickname": "张三",
  144. * "avatar": "base64或其他格式的图片数据",
  145. * "life_photos": [
  146. * "base64或其他格式的图片数据1",
  147. * "base64或其他格式的图片数据2"
  148. * ],
  149. * "gender": "1",
  150. * "mobile": "138****8000",
  151. * "birthday": "1990-01-01",
  152. * "work_years": 5,
  153. * "intention_city": "北京",
  154. * "introduction": "专业按摩师,从业5年",
  155. * "state": 1,
  156. * "state_text": "已通过",
  157. * "audit_remark": "审核通过"
  158. * },
  159. * "qualification": {
  160. * "qual_type": "高级按摩师",
  161. * "qual_photo": "base64或其他格式的图片数据",
  162. * "business_license": "base64或其他格式的图片数据",
  163. * "health_cert": "base64或其他格式的图片数据",
  164. * "state": 1,
  165. * "state_text": "已通过",
  166. * "audit_remark": "审核通过"
  167. * },
  168. * "real_name": {
  169. * "real_name": "张三",
  170. * "id_card": "370602****1234",
  171. * "id_card_front_photo": "base64或其他格式的图片数据",
  172. * "id_card_back_photo": "base64或其他格式的图片数据",
  173. * "id_card_hand_photo": "base64或其他格式的图片数据",
  174. * "state": 1,
  175. * "state_text": "已通过",
  176. * "audit_remark": "审核通过"
  177. * }
  178. * }
  179. * }
  180. * @response 404 {
  181. * "message": "用户不存在"
  182. * }
  183. * @response 404 {
  184. * "message": "技师信息不存在"
  185. * }
  186. */
  187. public function info()
  188. {
  189. return $this->success($this->service->getCoachInfo(Auth::user()));
  190. }
  191. /**
  192. * [账户]设置技师位置信息
  193. *
  194. * @description 设置技师的当前位置或常用位置
  195. *
  196. * @authenticated
  197. *
  198. * @bodyParam latitude float required 纬度 Example: 39.9042
  199. * @bodyParam longitude float required 经度 Example: 116.4074
  200. * @bodyParam type int nullable 位置类型(1:当前位置 2:常用位置) Example: 2
  201. * @bodyParam province string nullable 省份 Example: 北京市
  202. * @bodyParam city string nullable 城市 Example: 北京市
  203. * @bodyParam district string nullable 区县 Example: 朝阳区
  204. * @bodyParam address string nullable 详细地址 Example: 建国路93号万达广场
  205. * @bodyParam adcode string nullable 行政区划代码 Example: 110105
  206. *
  207. * @response {
  208. * "message": "位置信息设置成功"
  209. * }
  210. */
  211. public function setLocation(SetLocationRequest $request)
  212. {
  213. // 获取验证后的数据
  214. $validated = $request->validated();
  215. // 确保用户和技师存在
  216. $user = Auth::user();
  217. abort_if(!$user->coach, 404, '技师信息不存在');
  218. // 提取位置信息
  219. $locationInfo = $this->extractLocationInfo($validated);
  220. // 传递技师ID给服务层
  221. $this->service->setLocation(
  222. $user->coach->id,
  223. $validated['latitude'],
  224. $validated['longitude'],
  225. $validated['type'] ?? TechnicianLocationType::COMMON->value,
  226. $locationInfo
  227. );
  228. return $this->success(['message' => '位置信息设置成功']);
  229. }
  230. /**
  231. * [账户]获取技师位置信息
  232. *
  233. * @description 获取技师的位置信息
  234. *
  235. * @authenticated
  236. *
  237. * @queryParam type int 位置类型(1:当前位置 2:常用位置) Example: 2
  238. *
  239. * @response {
  240. * "data": {
  241. * "province": "浙江省",
  242. * "city": "杭州市",
  243. * "district": "西湖区",
  244. * "address": "文三路478号",
  245. * "adcode": "330106",
  246. * "longitude": 120.12345,
  247. * "latitude": 30.12345,
  248. * "updated_at": "2024-03-22 10:00:00"
  249. * }
  250. * }
  251. */
  252. public function getLocation(Request $request)
  253. {
  254. $validated = $request->validate([
  255. 'type' => 'required|integer|in:1,2'
  256. ]);
  257. return $this->success(
  258. $this->service->getLocation(Auth::user(), $validated['type'])
  259. );
  260. }
  261. /**
  262. * [账户]设置排班时间
  263. *
  264. * @description 设置技师每天通用的排班时间段
  265. *
  266. * @authenticated
  267. *
  268. * @bodyParam time_ranges array required 时间段数组
  269. * @bodyParam time_ranges[].start_time string required 开始时间(HH:mm格式) Example: "09:00"
  270. * @bodyParam time_ranges[].end_time string required 结束时间(HH:mm格式) Example: "12:00"
  271. *
  272. * @response {
  273. * "status": true,
  274. * "message": "排班设置成功",
  275. * "data": {
  276. * "coach_id": 1,
  277. * "time_ranges": [
  278. * {
  279. * "start_time": "09:00",
  280. * "end_time": "12:00"
  281. * },
  282. * {
  283. * "start_time": "14:00",
  284. * "end_time": "18:00"
  285. * }
  286. * ]
  287. * }
  288. * }
  289. * @response 400 {
  290. * "message": "时间段格式错误"
  291. * }
  292. * @response 400 {
  293. * "message": "时间格式错误,应为HH:mm格式"
  294. * }
  295. * @response 400 {
  296. * "message": "结束时间必须大于开始时间"
  297. * }
  298. * @response 400 {
  299. * "message": "时间段之间不能重叠"
  300. * }
  301. */
  302. public function setSchedule(Request $request)
  303. {
  304. $validated = $request->validate([
  305. 'time_ranges' => 'required|array|min:1',
  306. 'time_ranges.*.start_time' => [
  307. 'required',
  308. 'string',
  309. 'regex:/^([01][0-9]|2[0-3]):[0-5][0-9]$/',
  310. ],
  311. 'time_ranges.*.end_time' => [
  312. 'required',
  313. 'string',
  314. 'regex:/^([01][0-9]|2[0-3]):[0-5][0-9]$/',
  315. ],
  316. ], [
  317. 'time_ranges.required' => '必须设置时间段',
  318. 'time_ranges.array' => '时间段必须是数组格式',
  319. 'time_ranges.min' => '至少设置一个时间段',
  320. 'time_ranges.*.start_time.required' => '开始时间不能为空',
  321. 'time_ranges.*.start_time.regex' => '开始时间格式错误,应为HH:mm格式',
  322. 'time_ranges.*.end_time.required' => '结束时间不能为空',
  323. 'time_ranges.*.end_time.regex' => '结束时间格式错误,应为HH:mm格式',
  324. ]);
  325. return $this->success(
  326. $this->service->setSchedule(Auth::user()->id, $validated['time_ranges'])
  327. );
  328. }
  329. /**
  330. * [账户]更改技师工作状态
  331. *
  332. * @description 更改技师的工作状态(休息中/工作中),工作状态会自动判断为空闲或忙碌
  333. *
  334. * @authenticated
  335. *
  336. * @bodyParam status int required 状态(1:休息中 2:工作中) Example: 2
  337. *
  338. * @response {
  339. * "status": true,
  340. * "message": "状态更新成功",
  341. * "data": {
  342. * "work_status": 2,
  343. * "work_status_text": "空闲中",
  344. * "updated_at": "2024-03-20 10:00:00"
  345. * }
  346. * }
  347. * @response 400 {
  348. * "message": "无效的状态值"
  349. * }
  350. * @response 422 {
  351. * "message": "当前状态不能更改为休息状态"
  352. * }
  353. */
  354. public function updateWorkStatus(Request $request)
  355. {
  356. $validated = $request->validate([
  357. 'status' => 'required|integer|in:1,2',
  358. ], [
  359. 'status.required' => '状态不能为空',
  360. 'status.integer' => '状态必须是整数',
  361. 'status.in' => '无效的状态值',
  362. ]);
  363. return $this->success(
  364. $this->service->updateWorkStatus(Auth::user()->id, $validated['status'])
  365. );
  366. }
  367. /**
  368. * [账户]获取技师工作状态
  369. *
  370. * @description 获取技师当前工作状态
  371. *
  372. * @authenticated
  373. *
  374. * @response {
  375. * "data": {
  376. * "work_status": 2,
  377. * "work_status_text": "空闲中",
  378. * "updated_at": "2024-03-22 10:00:00"
  379. * }
  380. * }
  381. * @response 404 {
  382. * "message": "技师不存在"
  383. * }
  384. */
  385. public function getWorkStatus()
  386. {
  387. return $this->success(
  388. $this->service->getWorkStatus(Auth::user()->coach->id)
  389. );
  390. }
  391. /**
  392. * [账户]获取技师排班信息
  393. *
  394. * @return \Illuminate\Http\JsonResponse
  395. *
  396. * @description 技师获取自己的排班时间段信息
  397. *
  398. * @response {
  399. * "status": true,
  400. * "message": "获取成功",
  401. * "data": {
  402. * "time_ranges": [
  403. * {
  404. * "start_time": "09:00",
  405. * "end_time": "12:00"
  406. * },
  407. * {
  408. * "start_time": "14:00",
  409. * "end_time": "18:00"
  410. * }
  411. * ],
  412. * "updated_at": "2024-03-20 10:00:00"
  413. * }
  414. * }
  415. */
  416. public function getSchedule()
  417. {
  418. $schedule = $this->service->getSchedule(Auth::id());
  419. return $this->success($schedule);
  420. }
  421. /**
  422. * [账户]获取技师详细信息
  423. *
  424. * @description 获取当前登录技师的详细信息,包括基本信息、邀请码和钱包信息
  425. *
  426. * 业务流程:
  427. * 1. 验证用户身份
  428. * 2. 获取技师详细信息
  429. * 3. 返回数据
  430. *
  431. * @authenticated 需要技师身份认证
  432. *
  433. * @response 200 {
  434. * "status": true,
  435. * "message": "获取成功",
  436. * "data": {
  437. * "coach_no": "00000001",
  438. * "mobile": "13800138000",
  439. * "nickname": "张三",
  440. * "avatar": "https://example.com/avatar.jpg",
  441. * "age": 25,
  442. * "gender": 1,
  443. * "work_years": 5,
  444. * "intention_city": "杭州",
  445. * "life_photos": [
  446. * "https://example.com/photo1.jpg",
  447. * "https://example.com/photo2.jpg"
  448. * ],
  449. * "introduction": "专业按摩师,从业5年",
  450. * "state": 1,
  451. * "state_text": "正常",
  452. * "invite_code": "C1",
  453. * "wallet": {
  454. * "balance": 1000,
  455. * "frozen": 200,
  456. * "total_income": 5000,
  457. * "today_income": 300,
  458. * "withdrawable": 800
  459. * }
  460. * }
  461. * }
  462. * @response 404 {
  463. * "message": "技师信息不存在"
  464. * }
  465. */
  466. public function detail()
  467. {
  468. // 获取技师详细信息
  469. $data = $this->service->getCoachDetail();
  470. // 返回成功响应
  471. return $this->success($data, '获取成功');
  472. }
  473. /**
  474. * [账户]更新基础信息
  475. *
  476. * @description 更新技师的基础信息,包括昵称、性别和手机号
  477. *
  478. * @authenticated 需要技师身份认证
  479. *
  480. * @bodyParam nickname string nullable 昵称(2-20个字符) Example: 张三
  481. * @bodyParam gender integer nullable 性别(1:男 2:女) Example: 1
  482. * @bodyParam mobile string nullable 手机号 Example: 13800138000
  483. *
  484. * @response {
  485. * "status": true,
  486. * "message": "基础信息修改申请已提交",
  487. * "data": {
  488. * "record_id": 1,
  489. * "updated_fields": ["nickname", "gender"]
  490. * }
  491. * }
  492. */
  493. public function updateBasicInfo(UpdateBasicInfoRequest $request)
  494. {
  495. return $this->success(
  496. $this->service->updateBasicInfo(Auth::user()->coach, $request->validated())
  497. );
  498. }
  499. }