diff --git a/app/Http/Controllers/Api/PublicController.php b/app/Http/Controllers/Api/PublicController.php index 13a053536..ea19fd2ac 100755 --- a/app/Http/Controllers/Api/PublicController.php +++ b/app/Http/Controllers/Api/PublicController.php @@ -78,17 +78,28 @@ class PublicController extends AbstractController $key = trim(Request::input('key')); $mac = trim(Request::input('mac')); $time = intval(Request::input('time')); + $type = trim(Request::input('type')); // $setting = Base::setting('checkinSetting'); if ($setting['open'] !== 'open') { return 'function off'; } - if (!in_array('auto', $setting['modes'])) { - return 'mode off'; - } - if ($key != $setting['key']) { - return 'key error'; + if ($type === 'face') { + if (!in_array('face', $setting['modes'])) { + return 'mode off'; + } + if ($key != $setting['face_key']) { + return 'key error'; + } + } else { + if (!in_array('auto', $setting['modes'])) { + return 'mode off'; + } + if ($key != $setting['key']) { + return 'key error'; + } } + if ($error = UserBot::checkinBotCheckin($mac, $time)) { return $error; } diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php index 87c16fd2b..6ae7e0d3a 100755 --- a/app/Http/Controllers/Api/SystemController.php +++ b/app/Http/Controllers/Api/SystemController.php @@ -399,6 +399,7 @@ class SystemController extends AbstractController 'remindin', 'remindexceed', 'edit', + 'faceupload', 'modes', 'key', ])) { @@ -407,6 +408,7 @@ class SystemController extends AbstractController } if ($all['open'] === 'close') { $all['key'] = md5(Base::generatePassword(32)); + $all['face_key'] = md5(Base::generatePassword(32)); } else { $botUser = User::botGetOrCreate('check-in'); if (!$botUser) { @@ -414,7 +416,7 @@ class SystemController extends AbstractController } } if ($all['modes']) { - $all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'location']); + $all['modes'] = array_intersect($all['modes'], ['auto', 'manual', 'location', 'face']); } $setting = Base::setting('checkinSetting', Base::newTrim($all)); } else { @@ -425,8 +427,13 @@ class SystemController extends AbstractController $setting['key'] = md5(Base::generatePassword(32)); Base::setting('checkinSetting', $setting); } + if (empty($setting['face_key'])) { + $setting['face_key'] = md5(Base::generatePassword(32)); + Base::setting('checkinSetting', $setting); + } // $setting['open'] = $setting['open'] ?: 'close'; + $setting['faceupload'] = $setting['faceupload'] ?: 'close'; $setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00']; $setting['advance'] = intval($setting['advance']) ?: 120; $setting['delay'] = intval($setting['delay']) ?: 120; diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 8603f38f1..f11278981 100755 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -20,6 +20,7 @@ use App\Models\UmengAlias; use App\Models\UserDelete; use App\Models\UserTransfer; use App\Models\AbstractModel; +use App\Models\UserCheckinFace; use App\Models\UserCheckinMac; use App\Models\UserDepartment; use App\Models\WebSocketDialog; @@ -775,6 +776,14 @@ class UsersController extends AbstractController } return $user; }); + // user_face + $list->transform(function (User $user) use ($getCheckinMac) { + if ($getCheckinMac) { + $checkinFace = UserCheckinFace::query()->whereUserid($user->userid)->first(); + $user->checkin_face = $checkinFace ? Base::fillUrl($checkinFace->faceimg) : ''; + } + return $user; + }); } // return Base::retSuccess('success', $list); @@ -795,6 +804,7 @@ class UsersController extends AbstractController * - settemp 设为临时帐号 * - cleartemp 取消临时身份(取消临时帐号) * - checkin_macs 修改自动签到mac地址(需要参数 checkin_macs) + * - checkin_face 修改签到人脸图片(需要参数 checkin_face) * - department 修改部门(需要参数 department) * - setdisable 设为离职(需要参数 disable_time、transfer_userid) * - cleardisable 取消离职 @@ -805,6 +815,7 @@ class UsersController extends AbstractController * @apiParam {String} [nickname] 昵称 * @apiParam {String} [profession] 职位 * @apiParam {String} [checkin_macs] 自动签到mac地址 + * @apiParam {String} [checkin_face] 人脸图片地址 * @apiParam {String} [department] 部门 * @apiParam {String} [disable_time] 离职时间 * @apiParam {String} [transfer_userid] 离职交接人 @@ -869,6 +880,11 @@ class UsersController extends AbstractController } return UserCheckinMac::saveMac($userInfo->userid, $array); + case 'checkin_face': + $faceimg = $data['checkin_face'] ? $data['checkin_face'] : ''; + + return UserCheckinFace::saveFace($userInfo->userid, $userInfo->nickname, $faceimg, "管理员上传"); + case 'department': if (!is_array($data['department'])) { $data['department'] = []; @@ -1643,8 +1659,14 @@ class UsersController extends AbstractController $user = User::auth(); // $list = UserCheckinMac::whereUserid($user->userid)->orderBy('id')->get(); + $userface = UserCheckinFace::whereUserid($user->userid)->first(); + + $data = [ + 'list' => $list, + 'faceimg' => $userface ? Base::fillUrl($userface->faceimg) : '' + ]; // - return Base::retSuccess('success', $list); + return Base::retSuccess('success', $data); } /** @@ -1669,30 +1691,53 @@ class UsersController extends AbstractController if ($setting['open'] !== 'open') { return Base::retError('此功能未开启,请联系管理员开启'); } - if ($setting['edit'] !== 'open') { + if ($setting['edit'] !== 'open' && $setting['faceupload'] !== 'open') { return Base::retError('未开放修改权限,请联系管理员'); } // $list = Request::input('list'); - $array = []; - if (empty($list) || !is_array($list)) { - return Base::retError('参数错误'); - } - foreach ($list AS $item) { - $item = Base::newTrim($item); - if (Base::isMac($item['mac'])) { - $mac = strtoupper($item['mac']); - $array[$mac] = [ - 'mac' => $mac, - 'remark' => substr($item['remark'], 0, 50), - ]; + $faceimg = Request::input('faceimg'); + // 默认返回值,使用用户传递数据 + $data = [ + 'list' => $list, + 'faceimg' => $faceimg + ]; + // 当mac允许修改 + if ($setting['edit' === 'open']) { + $array = []; + if (empty($list) || !is_array($list)) { + return Base::retError('参数错误'); } + foreach ($list AS $item) { + $item = Base::newTrim($item); + if (Base::isMac($item['mac'])) { + $mac = strtoupper($item['mac']); + $array[$mac] = [ + 'mac' => $mac, + 'remark' => substr($item['remark'], 0, 50), + ]; + } + } + if (count($array) > 3) { + return Base::retError('最多只能添加3个MAC地址'); + } + $saveMacRes = UserCheckinMac::saveMac($user->userid, $array); + $data['list'] = $saveMacRes['data']; + } else { + $list = UserCheckinMac::whereUserid($user->userid)->orderBy('id')->get(); + $data['list'] = $list; } - if (count($array) > 3) { - return Base::retError('最多只能添加3个MAC地址'); + + + // 当图片允许修改 + if ($setting['faceupload'] === 'open') { + UserCheckinFace::saveFace($user->userid, $user->nickname(), $faceimg, "用户上传"); + } else { + $userface = UserCheckinFace::whereUserid($user->userid)->first(); + $data['faceimg'] = $userface; } - // - return UserCheckinMac::saveMac($user->userid, $array); + + return Base::retSuccess('修改成功', $data); } /** diff --git a/app/Models/UserBot.php b/app/Models/UserBot.php index 901d56286..9259845db 100644 --- a/app/Models/UserBot.php +++ b/app/Models/UserBot.php @@ -234,7 +234,20 @@ class UserBot extends AbstractModel 'remark' => '手动签到', ]; } - } + } elseif (Base::leftExists($mac, "checkin-", true)) { + $mac = Base::leftDelete($mac, "checkin-", true); + if ($UserInfo = User::whereUserid($mac)->whereBot(0)->first()) { + $array = [ + 'userid' => $UserInfo->userid, + 'mac' => '00:00:00:00:00:00', + 'date' => $nowDate, + ]; + $checkins[] = [ + 'userid' => $UserInfo->userid, + 'remark' => '考勤机', + ]; + } + } if ($array) { $record = UserCheckinRecord::where($array)->first(); if (empty($record)) { diff --git a/app/Models/UserCheckinFace.php b/app/Models/UserCheckinFace.php new file mode 100644 index 000000000..901398048 --- /dev/null +++ b/app/Models/UserCheckinFace.php @@ -0,0 +1,103 @@ + $nickname, + 'enrollid' => $userid, + 'admin' => 0, + 'backupnum' => 50, + ]; + if ($record != '') { + $data['record'] = $record; + } + + $res = Ihttp::ihttp_post($url, json_encode($data), 15); + if($res['data'] && $data = json_decode($res['data'])){ + if($data->ret != 1 && $data->msg){ + throw new ApiException($data->msg); + } + } + + + return AbstractModel::transaction(function() use ($userid, $faceimg, $remark) { + $checkinFace = self::query()->whereUserid($userid)->first(); + if ($checkinFace) { + self::updateData(['id' => $checkinFace->id], [ + 'faceimg' => $faceimg, + 'status' => 1, + 'remark' => $remark + ]); + } else { + $checkinFace = new UserCheckinFace(); + $checkinFace->faceimg = $faceimg; + $checkinFace->userid = $userid; + $checkinFace->remark = $remark; + $checkinFace->save(); + } + if ($faceimg == '') { + $res = UserCheckinFace::deleteDeviceUser($userid); + if ($res) { + return $res; + } + } + return Base::retSuccess('设置成功'); + }); + } + + public static function deleteDeviceUser($userid) { + $url = 'http://' . env('APP_IPPR') . '.55' . ":7788/user/delete"; + $data = [ + 'enrollid' => $userid, + 'backupnum' => 50, // 13 删除整个用户 50 删除图片 + ]; + + $res = Ihttp::ihttp_post($url, json_encode($data)); + if($res['data'] && $data = json_decode($res['data'])){ + if($data->ret != 1 && $data->msg){ + throw new ApiException($data->msg); + // return Base::retError($data->msg); + } + } + } +} diff --git a/database/migrations/2024_09_27_135805_create_user_checkin_faces_table.php b/database/migrations/2024_09_27_135805_create_user_checkin_faces_table.php new file mode 100644 index 000000000..816349e8d --- /dev/null +++ b/database/migrations/2024_09_27_135805_create_user_checkin_faces_table.php @@ -0,0 +1,38 @@ +bigIncrements('id'); + $table->bigInteger('userid')->nullable()->default(0)->comment('会员id'); + $table->string('faceimg')->nullable()->default('')->comment('人脸图片'); + $table->integer('status')->nullable()->default(0)->comment('状态'); + $table->string('remark',100)->nullable()->default('')->comment('备注'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_checkin_faces'); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 5d2b5ba9e..e7921e715 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -193,6 +193,27 @@ services: depends_on: - mariadb restart: unless-stopped + face: + container_name: "dootask-face-${APP_ID}" + image: "hitosea2020/dooface:0.0.1" + ports: + - "7788:7788" + environment: + TZ: "${TIMEZONE:-PRC}" + STORAGE: mysql + MYSQL_HOST: "${DB_HOST}" + MYSQL_PORT: "${DB_PORT}" + MYSQL_USERNAME: "${DB_USERNAME}" + MYSQL_PASSWORD: "${DB_PASSWORD}" + MYSQL_DB_NAME: "${DB_DATABASE}" + DB_PREFIX: "${DB_PREFIX}" + REPORT_API: "http://${APP_IPPR}.3:80/api/public/checkin/report" + depends_on: + - mariadb + networks: + extnetwork: + ipv4_address: "${APP_IPPR}.55" + restart: unless-stopped networks: extnetwork: diff --git a/resources/assets/js/pages/manage/components/TeamManagement.vue b/resources/assets/js/pages/manage/components/TeamManagement.vue index d2d4080bc..e65433bf8 100644 --- a/resources/assets/js/pages/manage/components/TeamManagement.vue +++ b/resources/assets/js/pages/manage/components/TeamManagement.vue @@ -237,6 +237,34 @@ + + + +
+ {{$L(`正在进行帐号【ID:${checkinFaceEditData.userid},${checkinFaceEditData.nickname}】人脸图片修改。`)}} + + {{$L('人脸图片')}} + + + + + + {{$L('建议尺寸:200x200')}} + + + + + + +
+
+ + +
+
+ import UserSelect from "../../../components/UserSelect.vue"; import UserAvatarTip from "../../../components/UserAvatar/tip.vue"; +import ImgUpload from "../../../components/ImgUpload"; export default { name: "TeamManagement", - components: {UserAvatarTip, UserSelect}, + components: {UserAvatarTip, UserSelect, ImgUpload}, props: { checkinMac: { type: Boolean, @@ -591,6 +620,12 @@ export default { command: 'checkin_mac', }, }, [h('div', this.$L('修改MAC'))])) + + dropdownItems.push(h('EDropdownItem', { + props: { + command: 'checkin_face', + }, + }, [h('div', this.$L('修改人脸图片'))])) } dropdownItems.push(h('EDropdownItem', { @@ -671,6 +706,10 @@ export default { checkinMacEditLoading: 0, checkinMacEditData: {}, + checkinFaceEditShow: false, + checkinFaceEditLoading: 0, + checkinFaceEditData: {}, + departmentEditShow: false, departmentEditLoading: 0, departmentEditData: {}, @@ -753,12 +792,21 @@ export default { created() { if (this.checkinMac) { this.columns.splice(5, 0, { - title: this.$L('MAC地址'), + title: this.$L('设备情况'), key: 'checkin_mac', minWidth: 80, render: (h, {row}) => { let checkin_macs = $A.cloneJSON(row.checkin_macs || []) + let checkin_face = $A.cloneJSON(row.checkin_face || '') + const tmp = [] + const checkin_face_desc = checkin_face ? "已上传(人脸)" : "未上传(人脸)" if (checkin_macs.length === 0) { + if (checkin_face){ + tmp.push(h('AutoTip', checkin_face_desc)) + return h('div', { + class: 'team-table-department-warp' + }, tmp); + } return h('div', '-'); } else { const desc = (item) => { @@ -767,24 +815,28 @@ export default { } return item.mac } - const tmp = [] + const checkin_devices_desc = [] tmp.push(h('AutoTip', desc(checkin_macs[0]))) if (checkin_macs.length > 1) { checkin_macs = checkin_macs.splice(1) + checkin_devices_desc.push(...checkin_macs.map(item => { + return desc(item) + })) + if (checkin_face) { + checkin_devices_desc.push(checkin_face_desc) + } tmp.push(h('ETooltip', [ h('div', { slot: 'content', domProps: { - innerHTML: checkin_macs.map(item => { - return desc(item) - }).join("
") + innerHTML: checkin_devices_desc.join("
") } }), h('div', { class: 'department-tag-num' - }, ` +${checkin_macs.length}`) + }, ` +${checkin_devices_desc.length}`) ])) - } + } return h('div', { class: 'team-table-department-warp' }, tmp); @@ -955,6 +1007,16 @@ export default { } this.checkinMacEditShow = true; break; + case 'checkin_face': + this.checkinFaceEditData = { + type: 'checkin_face', + userid: row.userid, + nickname: row.nickname, + faceimg: row.checkin_face + }; + + this.checkinFaceEditShow = true; + break; case 'department': let departments = [] @@ -1026,6 +1088,14 @@ export default { return new Promise((resolve, reject) => { if (data.type == 'checkin_macs') { this.checkinMacEditLoading++; + } else if (data.type == 'checkin_face') { + this.checkinFaceEditLoading++; + data = { + type: data.type, + userid: data.userid, + nickname: data.nickname, + checkin_face: data.faceimg[0] ? data.faceimg[0].url : '' + } } else if (data.type == 'department') { this.departmentEditLoading++; } else if (data.type == 'setdisable') { @@ -1042,6 +1112,8 @@ export default { resolve() if (data.type == 'checkin_macs') { this.checkinMacEditShow = false; + } else if (data.type == 'checkin_face') { + this.checkinFaceEditShow = false; } else if (data.type == 'department') { this.departmentEditShow = false; } else if (data.type == 'setdisable') { @@ -1056,6 +1128,8 @@ export default { }).finally(_ => { if (data.type == 'checkin_macs') { this.checkinMacEditLoading--; + } else if (data.type == 'checkin_face') { + this.checkinFaceEditLoading--; } else if (data.type == 'department') { this.departmentEditLoading--; } else if (data.type == 'setdisable') { diff --git a/resources/assets/js/pages/manage/setting/checkin.vue b/resources/assets/js/pages/manage/setting/checkin.vue index 9caa09a3e..f09d3c0b3 100644 --- a/resources/assets/js/pages/manage/setting/checkin.vue +++ b/resources/assets/js/pages/manage/setting/checkin.vue @@ -22,25 +22,47 @@ {{$L('设备连接上指定路由器(WiFi)后自动签到。')}}
- - {{$L('设备MAC地址')}} - {{$L('备注')}} - - - - - - - - - -
- + + + + {{$L('设备MAC地址')}} + {{$L('备注')}} + + + + + + + + + + + + +
+ + {{$L('人脸图片')}} + + + + + + + {{$L('建议尺寸:200x200')}} + + +
+
+
+ + + +