feat: move approved face image job
This commit is contained in:
parent
61f7e26092
commit
8c3875c299
@ -19,6 +19,7 @@ dependencies = [
|
|||||||
"pydantic>=2.9.2",
|
"pydantic>=2.9.2",
|
||||||
"python-dotenv>=1.0.1",
|
"python-dotenv>=1.0.1",
|
||||||
"python-multipart>=0.0.17",
|
"python-multipart>=0.0.17",
|
||||||
|
"pytz>=2024.2",
|
||||||
"sqlmodel>=0.0.22",
|
"sqlmodel>=0.0.22",
|
||||||
"uvicorn>=0.32.0",
|
"uvicorn>=0.32.0",
|
||||||
]
|
]
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pytz
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from persica.factory.component import AsyncInitializingComponent
|
from persica.factory.component import AsyncInitializingComponent
|
||||||
|
|
||||||
@ -11,3 +15,15 @@ class TimeScheduler(AsyncInitializingComponent):
|
|||||||
|
|
||||||
async def shutdown(self):
|
async def shutdown(self):
|
||||||
self.scheduler.shutdown()
|
self.scheduler.shutdown()
|
||||||
|
|
||||||
|
def run_job(self, name: str, func, args: List, seconds: int = 5):
|
||||||
|
self.scheduler.add_job(
|
||||||
|
func,
|
||||||
|
trigger="date",
|
||||||
|
args=args,
|
||||||
|
id=name,
|
||||||
|
name=name,
|
||||||
|
run_date=datetime.datetime.now(pytz.timezone("Asia/Shanghai"))
|
||||||
|
+ datetime.timedelta(seconds=seconds),
|
||||||
|
replace_existing=True,
|
||||||
|
)
|
||||||
|
@ -183,14 +183,14 @@
|
|||||||
this.get(selection[0]);
|
this.get(selection[0]);
|
||||||
},
|
},
|
||||||
// 批准
|
// 批准
|
||||||
approveByIdsF: function (ids, is_approved) {
|
approveByIdsF: function (ids, uids, is_approved) {
|
||||||
sa.ajax("/face/admin/image/approve_by_ids", {ids: ids, is_approved: is_approved}, function(res) {
|
sa.ajax("/face/admin/image/approve_by_ids", {ids: ids, uids: uids, is_approved: is_approved}, function(res) {
|
||||||
const msg = is_approved ? "批准成功" : "撤销批准成功"
|
const msg = is_approved ? "批准成功" : "撤销批准成功"
|
||||||
sa.alert(msg, this.f5);
|
sa.alert(msg, this.f5);
|
||||||
}.bind(this), {});
|
}.bind(this), {});
|
||||||
},
|
},
|
||||||
approve: function (data, is_approved) {
|
approve: function (data, is_approved) {
|
||||||
this.approveByIdsF([data.id], is_approved);
|
this.approveByIdsF([data.id], [data.user_id], is_approved);
|
||||||
},
|
},
|
||||||
approveByIds: function (is_approved) {
|
approveByIds: function (is_approved) {
|
||||||
var selection = this.$refs['data-table'].selection;
|
var selection = this.$refs['data-table'].selection;
|
||||||
@ -198,10 +198,12 @@
|
|||||||
return sa.msg('请选择一条数据')
|
return sa.msg('请选择一条数据')
|
||||||
}
|
}
|
||||||
const ids = [];
|
const ids = [];
|
||||||
|
const uids = [];
|
||||||
selection.forEach(function (i) {
|
selection.forEach(function (i) {
|
||||||
ids.push(i.id);
|
ids.push(i.id);
|
||||||
|
uids.push(i.user_id);
|
||||||
});
|
});
|
||||||
this.approveByIdsF(ids, is_approved);
|
this.approveByIdsF(ids, uids, is_approved);
|
||||||
},
|
},
|
||||||
// 删除
|
// 删除
|
||||||
del: function(data) {
|
del: function(data) {
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
|
import asyncio
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from fastapi_amis_admin.crud import BaseApiOut
|
from fastapi_amis_admin.crud import BaseApiOut
|
||||||
from starlette import status
|
from starlette import status
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from src.plugin import handler
|
from src.core.scheduler import TimeScheduler
|
||||||
|
from src.plugin import handler, job
|
||||||
from src.plugin.plugin import Plugin
|
from src.plugin.plugin import Plugin
|
||||||
from src.services.face_image.schemas import FaceList, FaceApproveList
|
from src.services.face_image.schemas import FaceList, FaceApproveList
|
||||||
from src.services.face_image.services import FaceImageServices
|
from src.services.face_image.services import FaceImageServices
|
||||||
from src.services.users.services import UserRoleServices
|
from src.services.users.services import UserRoleServices
|
||||||
|
from src.utils.move_files import move_files, move_files_by_uid
|
||||||
|
|
||||||
|
|
||||||
class FaceImageAdminRoutes(Plugin):
|
class FaceImageAdminRoutes(Plugin):
|
||||||
@ -16,9 +21,11 @@ class FaceImageAdminRoutes(Plugin):
|
|||||||
self,
|
self,
|
||||||
face_image_services: FaceImageServices,
|
face_image_services: FaceImageServices,
|
||||||
user_role_services: UserRoleServices,
|
user_role_services: UserRoleServices,
|
||||||
|
sche: TimeScheduler,
|
||||||
):
|
):
|
||||||
self.face_image_services = face_image_services
|
self.face_image_services = face_image_services
|
||||||
self.user_role_services = user_role_services
|
self.user_role_services = user_role_services
|
||||||
|
self.sche = sche
|
||||||
self.avatar_path = "/face/view/"
|
self.avatar_path = "/face/view/"
|
||||||
|
|
||||||
@handler.post("/list")
|
@handler.post("/list")
|
||||||
@ -53,7 +60,7 @@ class FaceImageAdminRoutes(Plugin):
|
|||||||
|
|
||||||
@handler.post("/approve_by_ids")
|
@handler.post("/approve_by_ids")
|
||||||
async def approve_by_ids(self, data: FaceApproveList):
|
async def approve_by_ids(self, data: FaceApproveList):
|
||||||
ids, is_approved = data.ids, data.is_approved
|
ids, uids, is_approved = data.ids, data.uids, data.is_approved
|
||||||
if not ids:
|
if not ids:
|
||||||
return BaseApiOut(code=500, msg="指定的人脸 id 不存在")
|
return BaseApiOut(code=500, msg="指定的人脸 id 不存在")
|
||||||
try:
|
try:
|
||||||
@ -61,6 +68,8 @@ class FaceImageAdminRoutes(Plugin):
|
|||||||
await self.face_image_services.approve_image(ids[0], is_approved)
|
await self.face_image_services.approve_image(ids[0], is_approved)
|
||||||
else:
|
else:
|
||||||
await self.face_image_services.approve_images(ids, is_approved)
|
await self.face_image_services.approve_images(ids, is_approved)
|
||||||
|
for uid in set(uids):
|
||||||
|
self.add_move_face_image_by_uid_job(uid)
|
||||||
return BaseApiOut(code=0, msg="请求成功")
|
return BaseApiOut(code=0, msg="请求成功")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return BaseApiOut(code=500, msg="指定的人脸 id 不存在")
|
return BaseApiOut(code=500, msg="指定的人脸 id 不存在")
|
||||||
@ -71,3 +80,35 @@ class FaceImageAdminRoutes(Plugin):
|
|||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
detail=f"Error Execute SQL:{e}",
|
detail=f"Error Execute SQL:{e}",
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
def add_move_face_image_by_uid_job(self, uid: int):
|
||||||
|
self.sche.run_job(
|
||||||
|
f"move_face_image_by_uid_job_{uid}",
|
||||||
|
self.move_face_image_by_uid_job,
|
||||||
|
[uid],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def move_face_image_by_uid_job(self, uid: int):
|
||||||
|
print("Move face image job start uid:", uid)
|
||||||
|
data = await self.face_image_services.get_all(
|
||||||
|
user_id=uid, approved=True, ignore_deleted=True
|
||||||
|
)
|
||||||
|
paths = [i.image for i in data if i.image]
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
with ThreadPoolExecutor() as executor:
|
||||||
|
await loop.run_in_executor(executor, move_files_by_uid, [paths, uid])
|
||||||
|
print("Move face image job done uid:", uid)
|
||||||
|
|
||||||
|
@job.cron(name="move_face_image_job", hour=3, minute=0, second=0)
|
||||||
|
async def move_face_image_job(self):
|
||||||
|
print("Move face image job start")
|
||||||
|
data = await self.face_image_services.get_all(
|
||||||
|
approved=True, ignore_deleted=True
|
||||||
|
)
|
||||||
|
paths = [i.image for i in data if i.image]
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
with ThreadPoolExecutor() as executor:
|
||||||
|
await loop.run_in_executor(executor, move_files, paths)
|
||||||
|
print("Move face image job done")
|
||||||
|
@ -63,4 +63,5 @@ class FaceList(BaseModel):
|
|||||||
|
|
||||||
class FaceApproveList(BaseModel):
|
class FaceApproveList(BaseModel):
|
||||||
ids: List[int]
|
ids: List[int]
|
||||||
|
uids: List[int]
|
||||||
is_approved: bool
|
is_approved: bool
|
||||||
|
@ -25,10 +25,15 @@ class FaceImageServices(AsyncInitializingComponent):
|
|||||||
return await self.repo.get_all(user_id=user_id, ignore_deleted=ignore_deleted)
|
return await self.repo.get_all(user_id=user_id, ignore_deleted=ignore_deleted)
|
||||||
|
|
||||||
async def get_all(
|
async def get_all(
|
||||||
self, approved: Optional[bool] = None, ignore_deleted: bool = True
|
self,
|
||||||
|
user_id: Optional[int] = None,
|
||||||
|
approved: Optional[bool] = None,
|
||||||
|
ignore_deleted: bool = True,
|
||||||
):
|
):
|
||||||
return await self.repo.get_all(
|
return await self.repo.get_all(
|
||||||
is_approved=approved, ignore_deleted=ignore_deleted
|
user_id=user_id,
|
||||||
|
is_approved=approved,
|
||||||
|
ignore_deleted=ignore_deleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def upload_by_user_id(self, user_id: int, image: str) -> FaceImageModel:
|
async def upload_by_user_id(self, user_id: int, image: str) -> FaceImageModel:
|
||||||
|
@ -12,3 +12,6 @@ AVATAR_DATA_PATH.mkdir(exist_ok=True)
|
|||||||
|
|
||||||
FACE_IMAGE_DATA_PATH = DATA_PATH / "face_image"
|
FACE_IMAGE_DATA_PATH = DATA_PATH / "face_image"
|
||||||
FACE_IMAGE_DATA_PATH.mkdir(exist_ok=True)
|
FACE_IMAGE_DATA_PATH.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
FACE_IMAGE_DATABASE_PATH = DATA_PATH / "face_image_database"
|
||||||
|
FACE_IMAGE_DATABASE_PATH.mkdir(exist_ok=True)
|
||||||
|
36
src/utils/move_files.py
Normal file
36
src/utils/move_files.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import contextlib
|
||||||
|
import shutil
|
||||||
|
from threading import Lock
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from ._path import FACE_IMAGE_DATA_PATH, FACE_IMAGE_DATABASE_PATH
|
||||||
|
|
||||||
|
LOCK = Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def move_files(paths: List[str]):
|
||||||
|
with LOCK:
|
||||||
|
shutil.rmtree(FACE_IMAGE_DATABASE_PATH, ignore_errors=True)
|
||||||
|
FACE_IMAGE_DATABASE_PATH.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
p = FACE_IMAGE_DATA_PATH / path
|
||||||
|
d = FACE_IMAGE_DATABASE_PATH / path
|
||||||
|
d.parent.mkdir(exist_ok=True)
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
shutil.copy(p, d)
|
||||||
|
|
||||||
|
|
||||||
|
def move_files_by_uid(data: Tuple[List[str], int]):
|
||||||
|
paths, uid = data
|
||||||
|
with LOCK:
|
||||||
|
shutil.rmtree(FACE_IMAGE_DATABASE_PATH / str(uid), ignore_errors=True)
|
||||||
|
if not paths:
|
||||||
|
return
|
||||||
|
(FACE_IMAGE_DATABASE_PATH / str(uid)).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
p = FACE_IMAGE_DATA_PATH / path
|
||||||
|
d = FACE_IMAGE_DATABASE_PATH / path
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
shutil.copy(p, d)
|
Loading…
Reference in New Issue
Block a user