diff --git a/ruoyi-fastapi-backend/common/mixin.py b/ruoyi-fastapi-backend/common/mixin.py new file mode 100644 index 000000000..1a3120e24 --- /dev/null +++ b/ruoyi-fastapi-backend/common/mixin.py @@ -0,0 +1,48 @@ +from datetime import datetime + +from sqlalchemy import Column, DateTime +from sqlalchemy.orm import Mapped, declared_attr + + +class CreateTimeMixin: + """ + 创建时间字段Mixin + """ + + __create_time_nullable__ = True + __create_time_comment__ = '创建时间' + + @declared_attr + def create_time(cls) -> Mapped[datetime]: # noqa: N805 + return Column( + DateTime, + nullable=cls.__create_time_nullable__, + default=datetime.now, + comment=cls.__create_time_comment__, + ) + + +class UpdateTimeMixin: + """ + 更新时间字段Mixin + """ + + __update_time_nullable__ = True + __update_time_insert_default__ = True + __update_time_comment__ = '更新时间' + + @declared_attr + def update_time(cls) -> Mapped[datetime]: # noqa: N805 + return Column( + DateTime, + nullable=cls.__update_time_nullable__, + default=datetime.now if cls.__update_time_insert_default__ else None, + onupdate=datetime.now, + comment=cls.__update_time_comment__, + ) + + +class AuditTimeMixin(CreateTimeMixin, UpdateTimeMixin): + """ + 创建时间和更新时间字段Mixin + """ diff --git a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py index 29eb73f64..bd4eb0316 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/config_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/config_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -65,9 +64,7 @@ async def add_system_config( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_config.create_by = current_user.user.user_name - add_config.create_time = datetime.now() add_config.update_by = current_user.user.user_name - add_config.update_time = datetime.now() add_config_result = await ConfigService.add_config_services(request, query_db, add_config) logger.info(add_config_result.message) @@ -91,7 +88,6 @@ async def edit_system_config( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_config.update_by = current_user.user.user_name - edit_config.update_time = datetime.now() edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config) logger.info(edit_config_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py index 6f00b9a8f..bfb55f0b4 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dept_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Path, Query, Request, Response @@ -86,9 +85,7 @@ async def add_system_dept( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_dept.create_by = current_user.user.user_name - add_dept.create_time = datetime.now() add_dept.update_by = current_user.user.user_name - add_dept.update_time = datetime.now() add_dept_result = await DeptService.add_dept_services(query_db, add_dept) logger.info(add_dept_result.message) @@ -115,7 +112,6 @@ async def edit_system_dept( if not current_user.user.admin: await DeptService.check_dept_data_scope_services(query_db, edit_dept.dept_id, data_scope_sql) edit_dept.update_by = current_user.user.user_name - edit_dept.update_time = datetime.now() edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept) logger.info(edit_dept_result.message) @@ -171,7 +167,6 @@ async def delete_system_dept( await DeptService.check_dept_data_scope_services(query_db, int(dept_id), data_scope_sql) delete_dept = DeleteDeptModel(deptIds=dept_ids) delete_dept.update_by = current_user.user.user_name - delete_dept.update_time = datetime.now() delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept) logger.info(delete_dept_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py index 66d04fcf3..aa097e34e 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/dict_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -74,9 +73,7 @@ async def add_system_dict_type( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_dict_type.create_by = current_user.user.user_name - add_dict_type.create_time = datetime.now() add_dict_type.update_by = current_user.user.user_name - add_dict_type.update_time = datetime.now() add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) logger.info(add_dict_type_result.message) @@ -100,7 +97,6 @@ async def edit_system_dict_type( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_dict_type.update_by = current_user.user.user_name - edit_dict_type.update_time = datetime.now() edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type) logger.info(edit_dict_type_result.message) @@ -272,9 +268,7 @@ async def add_system_dict_data( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_dict_data.create_by = current_user.user.user_name - add_dict_data.create_time = datetime.now() add_dict_data.update_by = current_user.user.user_name - add_dict_data.update_time = datetime.now() add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) logger.info(add_dict_data_result.message) @@ -298,7 +292,6 @@ async def edit_system_dict_data( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_dict_data.update_by = current_user.user.user_name - edit_dict_data.update_time = datetime.now() edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data) logger.info(edit_dict_data_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py index 67d60b82f..80d46c3c4 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/job_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/job_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -74,9 +73,7 @@ async def add_system_job( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_job.create_by = current_user.user.user_name - add_job.create_time = datetime.now() add_job.update_by = current_user.user.user_name - add_job.update_time = datetime.now() add_job_result = await JobService.add_job_services(query_db, add_job) logger.info(add_job_result.message) @@ -100,7 +97,6 @@ async def edit_system_job( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_job.update_by = current_user.user.user_name - edit_job.update_time = datetime.now() edit_job_result = await JobService.edit_job_services(query_db, edit_job) logger.info(edit_job_result.message) @@ -126,7 +122,6 @@ async def change_system_job_status( jobId=change_job.job_id, status=change_job.status, updateBy=current_user.user.user_name, - updateTime=datetime.now(), type='status', ) edit_job_result = await JobService.edit_job_services(query_db, edit_job) diff --git a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py index a8fb8c7cc..23e3ca047 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/menu_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Path, Query, Request, Response @@ -100,9 +99,7 @@ async def add_system_menu( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_menu.create_by = current_user.user.user_name - add_menu.create_time = datetime.now() add_menu.update_by = current_user.user.user_name - add_menu.update_time = datetime.now() add_menu_result = await MenuService.add_menu_services(query_db, add_menu) logger.info(add_menu_result.message) @@ -126,7 +123,6 @@ async def edit_system_menu( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_menu.update_by = current_user.user.user_name - edit_menu.update_time = datetime.now() edit_menu_result = await MenuService.edit_menu_services(query_db, edit_menu) logger.info(edit_menu_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py index 667421f90..69c18c19a 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/notice_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Path, Query, Request, Response @@ -143,9 +142,7 @@ async def add_system_notice( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_notice.create_by = current_user.user.user_name - add_notice.create_time = datetime.now() add_notice.update_by = current_user.user.user_name - add_notice.update_time = datetime.now() add_notice_result = await NoticeService.add_notice_services(query_db, add_notice) logger.info(add_notice_result.message) @@ -169,7 +166,6 @@ async def edit_system_notice( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_notice.update_by = current_user.user.user_name - edit_notice.update_time = datetime.now() edit_notice_result = await NoticeService.edit_notice_services(query_db, edit_notice) logger.info(edit_notice_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/post_controller.py b/ruoyi-fastapi-backend/module_admin/controller/post_controller.py index 30d78b43d..5195b9246 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/post_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/post_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -65,9 +64,7 @@ async def add_system_post( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_post.create_by = current_user.user.user_name - add_post.create_time = datetime.now() add_post.update_by = current_user.user.user_name - add_post.update_time = datetime.now() add_post_result = await PostService.add_post_services(query_db, add_post) logger.info(add_post_result.message) @@ -91,7 +88,6 @@ async def edit_system_post( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_post.update_by = current_user.user.user_name - edit_post.update_time = datetime.now() edit_post_result = await PostService.edit_post_services(query_db, edit_post) logger.info(edit_post_result.message) diff --git a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py index 120aab4d5..c902e4aad 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/role_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/role_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -102,9 +101,7 @@ async def add_system_role( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: add_role.create_by = current_user.user.user_name - add_role.create_time = datetime.now() add_role.update_by = current_user.user.user_name - add_role.update_time = datetime.now() add_role_result = await RoleService.add_role_services(query_db, add_role) logger.info(add_role_result.message) @@ -132,7 +129,6 @@ async def edit_system_role( if not current_user.user.admin: await RoleService.check_role_data_scope_services(query_db, str(edit_role.role_id), data_scope_sql) edit_role.update_by = current_user.user.user_name - edit_role.update_time = datetime.now() edit_role_result = await RoleService.edit_role_services(query_db, edit_role) logger.info(edit_role_result.message) @@ -164,7 +160,6 @@ async def edit_system_role_datascope( deptIds=role_data_scope.dept_ids, deptCheckStrictly=role_data_scope.dept_check_strictly, updateBy=current_user.user.user_name, - updateTime=datetime.now(), ) role_data_scope_result = await RoleService.role_datascope_services(query_db, edit_role) logger.info(role_data_scope_result.message) @@ -194,7 +189,7 @@ async def delete_system_role( await RoleService.check_role_allowed_services(RoleModel(roleId=int(role_id))) if not current_user.user.admin: await RoleService.check_role_data_scope_services(query_db, role_id, data_scope_sql) - delete_role = DeleteRoleModel(roleIds=role_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_role = DeleteRoleModel(roleIds=role_ids, updateBy=current_user.user.user_name) delete_role_result = await RoleService.delete_role_services(query_db, delete_role) logger.info(delete_role_result.message) @@ -280,7 +275,6 @@ async def reset_system_role_status( roleId=change_role.role_id, status=change_role.status, updateBy=current_user.user.user_name, - updateTime=datetime.now(), type='status', ) edit_role_result = await RoleService.edit_role_services(query_db, edit_role) diff --git a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py index 068ce9f24..7c3de7c36 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/user_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/user_controller.py @@ -124,9 +124,7 @@ async def add_system_user( await UserService.validate_password_services(request.app.state.redis, add_user.password) add_user.password = PwdUtil.get_password_hash(add_user.password) add_user.create_by = current_user.user.user_name - add_user.create_time = datetime.now() add_user.update_by = current_user.user.user_name - add_user.update_time = datetime.now() add_user_result = await UserService.add_user_services(query_db, add_user) logger.info(add_user_result.message) @@ -160,7 +158,6 @@ async def edit_system_user( query_db, ','.join([str(item) for item in edit_user.role_ids]), role_data_scope_sql ) edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now() edit_user_result = await UserService.edit_user_services(query_db, edit_user) logger.info(edit_user_result.message) @@ -193,7 +190,7 @@ async def delete_system_user( await UserService.check_user_allowed_services(UserModel(userId=int(user_id))) if not current_user.user.admin: await UserService.check_user_data_scope_services(query_db, int(user_id), data_scope_sql) - delete_user = DeleteUserModel(userIds=user_ids, updateBy=current_user.user.user_name, updateTime=datetime.now()) + delete_user = DeleteUserModel(userIds=user_ids, updateBy=current_user.user.user_name) delete_user_result = await UserService.delete_user_services(query_db, delete_user) logger.info(delete_user_result.message) @@ -227,7 +224,6 @@ async def reset_system_user_pwd( password=PwdUtil.get_password_hash(reset_user.password), pwdUpdateDate=datetime.now(), updateBy=current_user.user.user_name, - updateTime=datetime.now(), type='pwd', ) edit_user_result = await UserService.edit_user_services(query_db, edit_user) @@ -259,7 +255,6 @@ async def change_system_user_status( userId=change_user.user_id, status=change_user.status, updateBy=current_user.user.user_name, - updateTime=datetime.now(), type='status', ) edit_user_result = await UserService.edit_user_services(query_db, edit_user) @@ -348,7 +343,6 @@ async def change_system_user_profile_avatar( userId=current_user.user.user_id, avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}', updateBy=current_user.user.user_name, - updateTime=datetime.now(), type='avatar', ) edit_user_result = await UserService.edit_user_services(query_db, edit_user) @@ -381,7 +375,6 @@ async def change_system_user_profile_info( userId=current_user.user.user_id, userName=current_user.user.user_name, updateBy=current_user.user.user_name, - updateTime=datetime.now(), roleIds=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], postIds=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], role=current_user.user.role, @@ -412,7 +405,6 @@ async def reset_system_user_password( password=reset_password.new_password, pwdUpdateDate=datetime.now(), updateBy=current_user.user.user_name, - updateTime=datetime.now(), ) await UserService.validate_password_services(request.app.state.redis, reset_user.password) reset_user_result = await UserService.reset_user_services(query_db, reset_user) diff --git a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py index d130d58a8..f3396cf8f 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/config_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/config_dao.py @@ -95,7 +95,7 @@ async def add_config_dao(cls, db: AsyncSession, config: ConfigModel) -> SysConfi :param config: 参数配置对象 :return: """ - db_config = SysConfig(**config.model_dump()) + db_config = SysConfig(**config.model_dump(exclude={'create_time', 'update_time'})) db.add(db_config) await db.flush() @@ -110,7 +110,10 @@ async def edit_config_dao(cls, db: AsyncSession, config: dict) -> None: :param config: 需要更新的参数配置字典 :return: """ - await db.execute(update(SysConfig), [config]) + await db.execute( + update(SysConfig), + [{key: value for key, value in config.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel) -> None: diff --git a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py index acea7725c..b70695107 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dept_dao.py @@ -193,7 +193,7 @@ async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> SysDept: :param dept: 部门对象 :return: 新增校验结果 """ - db_dept = SysDept(**dept.model_dump()) + db_dept = SysDept(**dept.model_dump(exclude={'create_time', 'update_time'})) db.add(db_dept) await db.flush() @@ -208,7 +208,10 @@ async def edit_dept_dao(cls, db: AsyncSession, dept: dict) -> None: :param dept: 需要更新的部门字典 :return: 编辑校验结果 """ - await db.execute(update(SysDept), [dept]) + await db.execute( + update(SysDept), + [{key: value for key, value in dept.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def update_dept_children_dao(cls, db: AsyncSession, update_dept: list) -> None: @@ -269,9 +272,7 @@ async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel) -> None: :return: """ await db.execute( - update(SysDept) - .where(SysDept.dept_id == dept.dept_id) - .values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time) + update(SysDept).where(SysDept.dept_id == dept.dept_id).values(del_flag='2', update_by=dept.update_by) ) @classmethod diff --git a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py index e7b5530df..adb89c447 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/dict_dao.py @@ -109,7 +109,7 @@ async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> :param dict_type: 字典类型对象 :return: """ - db_dict_type = SysDictType(**dict_type.model_dump()) + db_dict_type = SysDictType(**dict_type.model_dump(exclude={'create_time', 'update_time'})) db.add(db_dict_type) await db.flush() @@ -124,7 +124,10 @@ async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict) -> None: :param dict_type: 需要更新的字典类型字典 :return: """ - await db.execute(update(SysDictType), [dict_type]) + await db.execute( + update(SysDictType), + [{key: value for key, value in dict_type.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel) -> None: @@ -250,7 +253,7 @@ async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> :param dict_data: 字典数据对象 :return: """ - db_data_type = SysDictData(**dict_data.model_dump()) + db_data_type = SysDictData(**dict_data.model_dump(exclude={'create_time', 'update_time'})) db.add(db_data_type) await db.flush() @@ -265,7 +268,10 @@ async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict) -> None: :param dict_data: 需要更新的字典数据字典 :return: """ - await db.execute(update(SysDictData), [dict_data]) + await db.execute( + update(SysDictData), + [{key: value for key, value in dict_data.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel) -> None: diff --git a/ruoyi-fastapi-backend/module_admin/dao/file_business_dao.py b/ruoyi-fastapi-backend/module_admin/dao/file_business_dao.py index 188e09598..d84eae945 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/file_business_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/file_business_dao.py @@ -309,7 +309,9 @@ async def edit_file_retention_policy( :return: None """ await db.execute( - update(SysFileRetentionPolicy).where(SysFileRetentionPolicy.business_type == business_type).values(**policy) + update(SysFileRetentionPolicy) + .where(SysFileRetentionPolicy.business_type == business_type) + .values(**{key: value for key, value in policy.items() if key not in {'create_time', 'update_time'}}) ) @classmethod diff --git a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py index 52df8e372..71b04ea2e 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/job_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/job_dao.py @@ -118,7 +118,7 @@ async def add_job_dao(cls, db: AsyncSession, job: JobModel) -> SysJob: :param job: 定时任务对象 :return: """ - db_job = SysJob(**job.model_dump()) + db_job = SysJob(**job.model_dump(exclude={'create_time', 'update_time'})) db.add(db_job) await db.flush() @@ -141,7 +141,7 @@ async def edit_job_dao(cls, db: AsyncSession, job: dict, old_job: JobModel) -> N SysJob.job_name == old_job.job_name, SysJob.job_group == old_job.job_group, ) - .values(**job) + .values(**{key: value for key, value in job.items() if key not in {'create_time', 'update_time'}}) ) @classmethod diff --git a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py index d9190eabe..b357bcc46 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/menu_dao.py @@ -199,7 +199,7 @@ async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel) -> SysMenu: :param menu: 菜单对象 :return: """ - db_menu = SysMenu(**menu.model_dump()) + db_menu = SysMenu(**menu.model_dump(exclude={'create_time', 'update_time'})) db.add(db_menu) await db.flush() @@ -214,7 +214,10 @@ async def edit_menu_dao(cls, db: AsyncSession, menu: dict) -> None: :param menu: 需要更新的菜单字典 :return: """ - await db.execute(update(SysMenu), [menu]) + await db.execute( + update(SysMenu), + [{key: value for key, value in menu.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def update_menu_sort_dao(cls, db: AsyncSession, menu_sort_list: list[dict[str, int]]) -> None: diff --git a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py index 74c259886..180cd4ce7 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/notice_dao.py @@ -257,7 +257,7 @@ async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> SysNotic :param notice: 通知公告对象 :return: """ - db_notice = SysNotice(**notice.model_dump()) + db_notice = SysNotice(**notice.model_dump(exclude={'create_time', 'update_time'})) db.add(db_notice) await db.flush() @@ -272,7 +272,10 @@ async def edit_notice_dao(cls, db: AsyncSession, notice: dict) -> None: :param notice: 需要更新的通知公告字典 :return: """ - await db.execute(update(SysNotice), [notice]) + await db.execute( + update(SysNotice), + [{key: value for key, value in notice.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel) -> None: diff --git a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py index da671239f..644ab21ec 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/post_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/post_dao.py @@ -107,7 +107,7 @@ async def add_post_dao(cls, db: AsyncSession, post: PostModel) -> SysPost: :param post: 岗位对象 :return: """ - db_post = SysPost(**post.model_dump()) + db_post = SysPost(**post.model_dump(exclude={'create_time', 'update_time'})) db.add(db_post) await db.flush() @@ -122,7 +122,10 @@ async def edit_post_dao(cls, db: AsyncSession, post: dict) -> None: :param post: 需要更新的岗位字典 :return: """ - await db.execute(update(SysPost), [post]) + await db.execute( + update(SysPost), + [{key: value for key, value in post.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_post_dao(cls, db: AsyncSession, post: PostModel) -> None: diff --git a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py index c4e295fc7..3df42b181 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/role_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/role_dao.py @@ -179,7 +179,7 @@ async def add_role_dao(cls, db: AsyncSession, role: RoleModel) -> SysRole: :param role: 角色对象 :return: """ - db_role = SysRole(**role.model_dump(exclude={'admin'})) + db_role = SysRole(**role.model_dump(exclude={'admin', 'create_time', 'update_time'})) db.add(db_role) await db.flush() @@ -194,7 +194,10 @@ async def edit_role_dao(cls, db: AsyncSession, role: dict) -> None: :param role: 需要更新的角色字典 :return: """ - await db.execute(update(SysRole), [role]) + await db.execute( + update(SysRole), + [{key: value for key, value in role.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_role_dao(cls, db: AsyncSession, role: RoleModel) -> None: @@ -206,9 +209,7 @@ async def delete_role_dao(cls, db: AsyncSession, role: RoleModel) -> None: :return: """ await db.execute( - update(SysRole) - .where(SysRole.role_id == role.role_id) - .values(del_flag='2', update_by=role.update_by, update_time=role.update_time) + update(SysRole).where(SysRole.role_id == role.role_id).values(del_flag='2', update_by=role.update_by) ) @classmethod diff --git a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py index 71b7c4bf2..1a1f8f24d 100644 --- a/ruoyi-fastapi-backend/module_admin/dao/user_dao.py +++ b/ruoyi-fastapi-backend/module_admin/dao/user_dao.py @@ -341,7 +341,7 @@ async def add_user_dao(cls, db: AsyncSession, user: UserModel) -> SysUser: :param user: 用户对象 :return: 新增校验结果 """ - db_user = SysUser(**user.model_dump(exclude={'admin'})) + db_user = SysUser(**user.model_dump(exclude={'admin', 'create_time', 'update_time'})) db.add(db_user) await db.flush() @@ -356,7 +356,10 @@ async def edit_user_dao(cls, db: AsyncSession, user: dict) -> None: :param user: 需要更新的用户字典 :return: 编辑校验结果 """ - await db.execute(update(SysUser), [user]) + await db.execute( + update(SysUser), + [{key: value for key, value in user.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_user_dao(cls, db: AsyncSession, user: UserModel) -> None: @@ -368,9 +371,7 @@ async def delete_user_dao(cls, db: AsyncSession, user: UserModel) -> None: :return: """ await db.execute( - update(SysUser) - .where(SysUser.user_id == user.user_id) - .values(del_flag='2', update_by=user.update_by, update_time=user.update_time) + update(SysUser).where(SysUser.user_id == user.user_id).values(del_flag='2', update_by=user.update_by) ) @classmethod diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py index b10c7e0d0..d657074c7 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/config_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, Column, DateTime, Integer, String +from sqlalchemy import CHAR, Column, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysConfig(Base): +class SysConfig(AuditTimeMixin, Base): """ 参数配置表 """ @@ -21,9 +20,7 @@ class SysConfig(Base): config_value = Column(String(500), nullable=True, server_default="''", comment='参数键值') config_type = Column(CHAR(1), nullable=True, server_default='N', comment='系统内置(Y是 N否)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py index 92d65de97..0a90e7461 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dept_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysDept(Base): +class SysDept(AuditTimeMixin, Base): """ 部门表 """ @@ -41,6 +40,4 @@ class SysDept(Base): status = Column(CHAR(1), nullable=True, server_default='0', comment='部门状态(0正常 1停用)') del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py index 0a84b8f14..4d9bb77b8 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/dict_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysDictType(Base): +class SysDictType(AuditTimeMixin, Base): """ 字典类型表 """ @@ -20,9 +19,7 @@ class SysDictType(Base): dict_type = Column(String(100), unique=True, nullable=True, server_default="''", comment='字典类型') status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, @@ -31,7 +28,7 @@ class SysDictType(Base): ) -class SysDictData(Base): +class SysDictData(AuditTimeMixin, Base): """ 字典数据表 """ @@ -59,9 +56,7 @@ class SysDictData(Base): is_default = Column(CHAR(1), nullable=True, server_default='N', comment='是否默认(Y是 N否)') status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1停用)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/file_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/file_do.py index 9a903410a..899d28b98 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/file_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/file_do.py @@ -2,15 +2,18 @@ from sqlalchemy import CHAR, BigInteger, Column, DateTime, Index, Integer, String, Text, UniqueConstraint +from common.mixin import AuditTimeMixin, CreateTimeMixin from config.database import Base -class SysFileInfo(Base): +class SysFileInfo(AuditTimeMixin, Base): """ 文件信息表 """ __tablename__ = 'sys_file_info' + __create_time_nullable__ = False + __update_time_nullable__ = False __table_args__ = ( UniqueConstraint( 'storage_type', @@ -49,20 +52,19 @@ class SysFileInfo(Base): file_hash = Column(String(64), nullable=False, comment='文件SHA-256') status = Column(String(20), nullable=False, server_default='active', comment='文件状态') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=False, default=datetime.now, comment='更新时间') expire_time = Column(DateTime, nullable=True, comment='过期时间') deleted_time = Column(DateTime, nullable=True, comment='移入回收站时间') del_flag = Column(CHAR(1), nullable=False, server_default='0', comment='删除标志') -class SysFileReference(Base): +class SysFileReference(CreateTimeMixin, Base): """ 文件业务引用表 """ __tablename__ = 'sys_file_reference' + __create_time_nullable__ = False __table_args__ = ( UniqueConstraint( 'file_id', @@ -82,15 +84,16 @@ class SysFileReference(Base): business_name = Column(String(255), nullable=True, comment='业务名称') retention_expire_time = Column(DateTime, nullable=True, comment='保留期限到期时间') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间') -class SysFileRetentionPolicy(Base): +class SysFileRetentionPolicy(AuditTimeMixin, Base): """ 文件业务保留策略表 """ __tablename__ = 'sys_file_retention_policy' + __create_time_nullable__ = False + __update_time_nullable__ = False __table_args__ = {'comment': '文件业务保留策略表'} business_type = Column(String(50), primary_key=True, nullable=False, comment='业务类型') @@ -98,17 +101,16 @@ class SysFileRetentionPolicy(Base): status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0启用 1停用)') remark = Column(String(500), nullable=True, comment='备注') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=False, default=datetime.now, comment='更新时间') -class SysFileRetentionNotice(Base): +class SysFileRetentionNotice(CreateTimeMixin, Base): """ 文件保留期限提醒表 """ __tablename__ = 'sys_file_retention_notice' + __create_time_nullable__ = False __table_args__ = ( UniqueConstraint( 'file_id', @@ -126,17 +128,17 @@ class SysFileRetentionNotice(Base): notice_type = Column(String(20), nullable=False, comment='提醒类型') expire_time = Column(DateTime, nullable=False, comment='文件过期时间') status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0未读 1已读 2已失效)') - create_time = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间') read_by = Column(String(64), nullable=True, server_default="''", comment='读取者') read_time = Column(DateTime, nullable=True, comment='读取时间') -class SysFileAcl(Base): +class SysFileAcl(CreateTimeMixin, Base): """ 文件访问控制表 """ __tablename__ = 'sys_file_acl' + __create_time_nullable__ = False __table_args__ = ( UniqueConstraint( 'file_id', @@ -159,7 +161,6 @@ class SysFileAcl(Base): include_children = Column(CHAR(1), nullable=False, server_default='0', comment='部门是否包含下级') expire_time = Column(DateTime, nullable=True, comment='授权过期时间') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间') del_flag = Column(CHAR(1), nullable=False, server_default='0', comment='删除标志') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py index 9ee6dd97e..66c5bf49c 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/job_do.py @@ -1,15 +1,14 @@ -from datetime import datetime - from sqlalchemy import CHAR, BigInteger, Column, DateTime, String from sqlalchemy.dialects.mysql import DATETIME from sqlalchemy.dialects.postgresql import TIMESTAMP +from common.mixin import AuditTimeMixin, CreateTimeMixin from config.database import Base JOB_LOG_TIME_TYPE = DateTime().with_variant(DATETIME(fsp=3), 'mysql').with_variant(TIMESTAMP(precision=3), 'postgresql') -class SysJob(Base): +class SysJob(AuditTimeMixin, Base): """ 定时任务调度表 """ @@ -34,13 +33,11 @@ class SysJob(Base): concurrent = Column(CHAR(1), nullable=True, server_default='1', comment='是否并发执行(0允许 1禁止)') status = Column(CHAR(1), nullable=True, server_default='0', comment='状态(0正常 1暂停)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column(String(500), nullable=True, server_default="''", comment='备注信息') -class SysJobLog(Base): +class SysJobLog(CreateTimeMixin, Base): """ 定时任务调度日志表 """ @@ -61,4 +58,3 @@ class SysJobLog(Base): exception_info = Column(String(2000), nullable=True, server_default="''", comment='异常信息') start_time = Column(JOB_LOG_TIME_TYPE, nullable=True, comment='执行开始时间') end_time = Column(JOB_LOG_TIME_TYPE, nullable=True, comment='执行结束时间') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py index 298921c27..e9bcb53d0 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/log_do.py @@ -21,7 +21,7 @@ class SysLogininfor(Base): os = Column(String(50), nullable=True, server_default="''", comment='操作系统') status = Column(CHAR(1), nullable=True, server_default='0', comment='登录状态(0成功 1失败)') msg = Column(String(255), nullable=True, server_default="''", comment='提示消息') - login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') + login_time = Column(DateTime, nullable=True, default=datetime.now, comment='访问时间') idx_sys_logininfor_s = Index('idx_sys_logininfor_s', status) idx_sys_logininfor_lt = Index('idx_sys_logininfor_lt', login_time) @@ -52,7 +52,7 @@ class SysOperLog(Base): json_result = Column(String(2000), nullable=True, server_default="''", comment='返回参数') status = Column(Integer, nullable=True, server_default='0', comment='操作状态(0正常 1异常)') error_msg = Column(String(2000), nullable=True, server_default="''", comment='错误消息') - oper_time = Column(DateTime, nullable=True, default=datetime.now(), comment='操作时间') + oper_time = Column(DateTime, nullable=True, default=datetime.now, comment='操作时间') cost_time = Column(BigInteger, nullable=True, server_default='0', comment='消耗时间') idx_sys_oper_log_bt = Index('idx_sys_oper_log_bt', business_type) diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py index 94d31e15f..4ce682a24 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/menu_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysMenu(Base): +class SysMenu(AuditTimeMixin, Base): """ 菜单权限表 """ @@ -46,7 +45,5 @@ class SysMenu(Base): ) icon = Column(String(100), nullable=True, server_default='#', comment='菜单图标') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column(String(500), nullable=True, server_default="''", comment='备注') diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py index 749004e79..9565ad354 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/notice_do.py @@ -3,12 +3,13 @@ from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, LargeBinary, String, UniqueConstraint from sqlalchemy.dialects import mysql +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysNotice(Base): +class SysNotice(AuditTimeMixin, Base): """ 通知公告表 """ @@ -27,9 +28,7 @@ class SysNotice(Base): ) status = Column(CHAR(1), nullable=True, server_default='0', comment='公告状态(0正常 1关闭)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now()) update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now()) remark = Column( String(255), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py index c42d05783..6791533ed 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/post_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysPost(Base): +class SysPost(AuditTimeMixin, Base): """ 岗位信息表 """ @@ -21,9 +20,7 @@ class SysPost(Base): post_sort = Column(Integer, nullable=False, comment='显示顺序') status = Column(CHAR(1), nullable=False, comment='状态(0正常 1停用)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py index 0d630cd8b..f1353c73d 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/role_do.py @@ -1,14 +1,13 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, SmallInteger, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, SmallInteger, String from sqlalchemy.dialects import mysql +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysRole(Base): +class SysRole(AuditTimeMixin, Base): """ 角色信息表 """ @@ -41,9 +40,7 @@ class SysRole(Base): status = Column(CHAR(1), nullable=False, comment='角色状态(0正常 1停用)') del_flag = Column(CHAR(1), nullable=True, server_default='0', comment='删除标志(0代表存在 2代表删除)') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py index 500e0b7db..4a7fb904a 100644 --- a/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py +++ b/ruoyi-fastapi-backend/module_admin/entity/do/user_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - from sqlalchemy import CHAR, BigInteger, Column, DateTime, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysUser(Base): +class SysUser(AuditTimeMixin, Base): """ 用户信息表 """ @@ -36,9 +35,7 @@ class SysUser(Base): login_date = Column(DateTime, nullable=True, comment='最后登录时间') pwd_update_date = Column(DateTime, nullable=True, comment='密码最后更新时间') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, comment='创建时间', default=datetime.now()) update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, comment='更新时间', default=datetime.now()) remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/module_admin/service/config_service.py b/ruoyi-fastapi-backend/module_admin/service/config_service.py index 6dd3fbbae..374a3fe99 100644 --- a/ruoyi-fastapi-backend/module_admin/service/config_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/config_service.py @@ -121,7 +121,7 @@ async def edit_config_services( :param page_object: 编辑参数配置对象 :return: 编辑参数配置校验结果 """ - edit_config = page_object.model_dump(exclude_unset=True) + edit_config = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) config_info = await cls.config_detail_services(query_db, page_object.config_id) if config_info.config_id: if not await cls.check_config_key_unique_services(query_db, page_object): diff --git a/ruoyi-fastapi-backend/module_admin/service/dept_service.py b/ruoyi-fastapi-backend/module_admin/service/dept_service.py index d3bb94029..17a20387e 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dept_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dept_service.py @@ -151,7 +151,7 @@ async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel old_ancestors = old_dept.ancestors page_object.ancestors = new_ancestors await cls.update_dept_children(query_db, page_object.dept_id, new_ancestors, old_ancestors) - edit_dept = page_object.model_dump(exclude_unset=True) + edit_dept = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) await DeptDao.edit_dept_dao(query_db, edit_dept) if ( page_object.status == CommonConstant.DEPT_NORMAL diff --git a/ruoyi-fastapi-backend/module_admin/service/dict_service.py b/ruoyi-fastapi-backend/module_admin/service/dict_service.py index afc83cecb..64dd0eda7 100644 --- a/ruoyi-fastapi-backend/module_admin/service/dict_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/dict_service.py @@ -99,7 +99,7 @@ async def edit_dict_type_services( :param page_object: 编辑字典类型对象 :return: 编辑字典类型校验结果 """ - edit_dict_type = page_object.model_dump(exclude_unset=True) + edit_dict_type = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) dict_type_info = await cls.dict_type_detail_services(query_db, page_object.dict_id) if dict_type_info.dict_id: if not await cls.check_dict_type_unique_services(query_db, page_object): @@ -114,7 +114,7 @@ async def edit_dict_type_services( dictType=page_object.dict_type, updateBy=page_object.update_by, updateTime=page_object.update_time, - ).model_dump(exclude_unset=True) + ).model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) await query_db.commit() @@ -354,7 +354,7 @@ async def edit_dict_data_services( :param page_object: 编辑字典数据对象 :return: 编辑字典数据校验结果 """ - edit_data_type = page_object.model_dump(exclude_unset=True) + edit_data_type = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) dict_data_info = await cls.dict_data_detail_services(query_db, page_object.dict_code) if dict_data_info.dict_code: if not await cls.check_dict_data_unique_services(query_db, page_object): diff --git a/ruoyi-fastapi-backend/module_admin/service/file_business_service.py b/ruoyi-fastapi-backend/module_admin/service/file_business_service.py index 6c7b0c70c..b17aeb662 100644 --- a/ruoyi-fastapi-backend/module_admin/service/file_business_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/file_business_service.py @@ -275,7 +275,6 @@ async def add_file_retention_policy_services( ) if exists_policy: raise ServiceException(message=f'业务类型{policy.business_type}的保留策略已存在') - current_time = datetime.now() db_policy = SysFileRetentionPolicy( **policy.model_dump( exclude={ @@ -286,9 +285,7 @@ async def add_file_retention_policy_services( } ), create_by=operator_name, - create_time=current_time, update_by=operator_name, - update_time=current_time, ) try: await FileRetentionPolicyDao.add_file_retention_policy(query_db, db_policy) @@ -329,7 +326,7 @@ async def edit_file_retention_policy_services( 'update_time', } ) - policy_data.update(update_by=operator_name, update_time=datetime.now()) + policy_data.update(update_by=operator_name) try: await FileRetentionPolicyDao.edit_file_retention_policy( query_db, diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 5e7313586..b0fba2280 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -111,7 +111,7 @@ async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobMod :param page_object: 编辑定时任务对象 :return: 编辑定时任务校验结果 """ - edit_job = page_object.model_dump(exclude_unset=True) + edit_job = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) cls._deal_edit_job(page_object, edit_job) job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: diff --git a/ruoyi-fastapi-backend/module_admin/service/menu_service.py b/ruoyi-fastapi-backend/module_admin/service/menu_service.py index e610503c6..6909a894c 100644 --- a/ruoyi-fastapi-backend/module_admin/service/menu_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/menu_service.py @@ -166,7 +166,7 @@ async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel :param page_object: 编辑部门对象 :return: 编辑菜单校验结果 """ - edit_menu = page_object.model_dump(exclude_unset=True) + edit_menu = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) menu_info = await cls.menu_detail_services(query_db, page_object.menu_id) if menu_info.menu_id: if not await cls.check_menu_name_unique_services(query_db, page_object): diff --git a/ruoyi-fastapi-backend/module_admin/service/notice_service.py b/ruoyi-fastapi-backend/module_admin/service/notice_service.py index d153cab75..0f09a92a4 100644 --- a/ruoyi-fastapi-backend/module_admin/service/notice_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/notice_service.py @@ -147,7 +147,7 @@ async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeM :param page_object: 编辑通知公告对象 :return: 编辑通知公告校验结果 """ - edit_notice = page_object.model_dump(exclude_unset=True) + edit_notice = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) notice_info = await cls.notice_detail_services(query_db, page_object.notice_id) if notice_info.notice_id: if not await cls.check_notice_unique_services(query_db, page_object): diff --git a/ruoyi-fastapi-backend/module_admin/service/post_service.py b/ruoyi-fastapi-backend/module_admin/service/post_service.py index 9a11f23c2..46c020d71 100644 --- a/ruoyi-fastapi-backend/module_admin/service/post_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/post_service.py @@ -92,7 +92,7 @@ async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel :param page_object: 编辑岗位对象 :return: 编辑岗位校验结果 """ - edit_post = page_object.model_dump(exclude_unset=True) + edit_post = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) post_info = await cls.post_detail_services(query_db, page_object.post_id) if post_info.post_id: if not await cls.check_post_name_unique_services(query_db, page_object): diff --git a/ruoyi-fastapi-backend/module_admin/service/role_service.py b/ruoyi-fastapi-backend/module_admin/service/role_service.py index b7cd0a208..4db12a93a 100644 --- a/ruoyi-fastapi-backend/module_admin/service/role_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/role_service.py @@ -149,7 +149,7 @@ async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleMod :param page_object: 新增角色对象 :return: 新增角色校验结果 """ - add_role = RoleModel(**page_object.model_dump(by_alias=True)) + add_role = RoleModel(**page_object.model_dump(by_alias=True, exclude={'create_time', 'update_time'})) if not await cls.check_role_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在') if not await cls.check_role_key_unique_services(query_db, page_object): @@ -175,7 +175,10 @@ async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleMo :param page_object: 编辑角色对象 :return: 编辑角色校验结果 """ - edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + edit_role = page_object.model_dump( + exclude_unset=True, + exclude={'admin', 'create_time', 'update_time'}, + ) if page_object.type != 'status': del edit_role['menu_ids'] if page_object.type == 'status': @@ -213,7 +216,10 @@ async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddR :param page_object: 角色数据权限对象 :return: 分配角色数据权限结果 """ - edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin', 'dept_ids'}) + edit_role = page_object.model_dump( + exclude_unset=True, + exclude={'admin', 'dept_ids', 'create_time', 'update_time'}, + ) role_info = await cls.role_detail_services(query_db, page_object.role_id) if role_info.role_id: try: diff --git a/ruoyi-fastapi-backend/module_admin/service/user_service.py b/ruoyi-fastapi-backend/module_admin/service/user_service.py index 8c6834ecc..66565b21e 100644 --- a/ruoyi-fastapi-backend/module_admin/service/user_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/user_service.py @@ -1,6 +1,5 @@ import io import re -from datetime import datetime from typing import Any import pandas as pd @@ -198,7 +197,7 @@ async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserMod :param page_object: 新增用户对象 :return: 新增用户校验结果 """ - add_user = UserModel(**page_object.model_dump(by_alias=True)) + add_user = UserModel(**page_object.model_dump(by_alias=True, exclude={'create_time', 'update_time'})) if not await cls.check_user_name_unique_services(query_db, page_object): raise ServiceException(message=f'新增用户{page_object.user_name}失败,登录账号已存在') if page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): @@ -245,7 +244,10 @@ async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserM :param page_object: 编辑用户对象 :return: 编辑用户校验结果 """ - edit_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + edit_user = page_object.model_dump( + exclude_unset=True, + exclude={'admin', 'create_time', 'update_time'}, + ) cls._deal_edit_user(page_object, edit_user) user_info = await cls.user_detail_services(query_db, edit_user.get('user_id')) if user_info.data and user_info.data.user_id: @@ -378,7 +380,10 @@ async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUse :param page_object: 重置用户对象 :return: 重置用户校验结果 """ - reset_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + reset_user = page_object.model_dump( + exclude_unset=True, + exclude={'admin', 'create_time', 'update_time'}, + ) if page_object.old_password: user = (await UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id)).get('user_basic_info') if not PwdUtil.verify_password(page_object.old_password, user.password): @@ -483,9 +488,7 @@ async def batch_import_user_services( sex=row['sex'], status=row['status'], createBy=current_user.user.user_name, - createTime=datetime.now(), updateBy=current_user.user.user_name, - updateTime=datetime.now(), ) user_info = await UserDao.get_user_by_info(query_db, UserModel(userName=row['user_name'])) if user_info: @@ -500,7 +503,6 @@ async def batch_import_user_services( sex=row['sex'], status=row['status'], updateBy=current_user.user.user_name, - updateTime=datetime.now(), ) edit_user_model.validate_fields() await cls.check_user_allowed_services(edit_user_model) @@ -511,7 +513,10 @@ async def batch_import_user_services( await DeptService.check_dept_data_scope_services( query_db, edit_user_model.dept_id, dept_data_scope_sql ) - edit_user = edit_user_model.model_dump(exclude_unset=True) + edit_user = edit_user_model.model_dump( + exclude_unset=True, + exclude={'create_time', 'update_time'}, + ) await UserDao.edit_user_dao(query_db, edit_user) else: add_error_result.append(f'{count}.用户账号{row["user_name"]}已存在') diff --git a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py index e4e45ed4b..08a40806d 100644 --- a/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py +++ b/ruoyi-fastapi-backend/module_generator/controller/gen_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Path, Query, Request, Response @@ -133,7 +132,6 @@ async def edit_gen_table( current_user: Annotated[CurrentUserModel, CurrentUserDependency()], ) -> Response: edit_gen_table.update_by = current_user.user.user_name - edit_gen_table.update_time = datetime.now() await GenTableService.validate_edit(edit_gen_table) edit_gen_result = await GenTableService.edit_gen_table_services(query_db, edit_gen_table) logger.info(edit_gen_result.message) diff --git a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py index 7c0dd70a4..44ffed84a 100644 --- a/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py +++ b/ruoyi-fastapi-backend/module_generator/dao/gen_dao.py @@ -347,7 +347,11 @@ async def add_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel) -> :param gen_table: 业务表对象 :return: """ - db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump()) + db_gen_table = GenTable( + **GenTableBaseModel( + **gen_table.model_dump(by_alias=True, exclude={'create_time', 'update_time'}) + ).model_dump(exclude={'create_time', 'update_time'}) + ) db.add(db_gen_table) await db.flush() @@ -362,7 +366,10 @@ async def edit_gen_table_dao(cls, db: AsyncSession, gen_table: dict) -> None: :param gen_table: 需要更新的业务表字典 :return: """ - await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()]) + await db.execute( + update(GenTable), + [GenTableBaseModel(**gen_table).model_dump(exclude={'create_time', 'update_time'})], + ) @classmethod async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel) -> None: @@ -431,7 +438,9 @@ async def add_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: GenT :return: """ db_gen_table_column = GenTableColumn( - **GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump() + **GenTableColumnBaseModel( + **gen_table_column.model_dump(by_alias=True, exclude={'create_time', 'update_time'}) + ).model_dump(exclude={'create_time', 'update_time'}) ) db.add(db_gen_table_column) await db.flush() @@ -447,7 +456,10 @@ async def edit_gen_table_column_dao(cls, db: AsyncSession, gen_table_column: dic :param gen_table_column: 需要更新的业务表字段字典 :return: """ - await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()]) + await db.execute( + update(GenTableColumn), + [GenTableColumnBaseModel(**gen_table_column).model_dump(exclude={'create_time', 'update_time'})], + ) @classmethod async def delete_gen_table_column_by_table_id_dao( diff --git a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py index a5b386bbf..5b16e000a 100644 --- a/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py +++ b/ruoyi-fastapi-backend/module_generator/entity/do/gen_do.py @@ -1,14 +1,13 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Integer, String from sqlalchemy.orm import foreign, relationship +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class GenTable(Base): +class GenTable(AuditTimeMixin, Base): """ 代码生成业务表 """ @@ -49,9 +48,7 @@ class GenTable(Base): gen_path = Column(String(200), nullable=True, server_default='/', comment='生成路径(不填默认项目路径)') options = Column(String(1000), nullable=True, comment='其它生成选项') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, @@ -67,7 +64,7 @@ class GenTable(Base): ) -class GenTableColumn(Base): +class GenTableColumn(AuditTimeMixin, Base): """ 代码生成业务表字段 """ @@ -99,9 +96,7 @@ class GenTableColumn(Base): dict_type = Column(String(200), nullable=True, server_default="''", comment='字典类型') sort = Column(Integer, nullable=True, comment='排序') create_by = Column(String(64), server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') tables = relationship( 'GenTable', primaryjoin=lambda: foreign(GenTableColumn.table_id) == GenTable.table_id, back_populates='columns' diff --git a/ruoyi-fastapi-backend/module_generator/service/gen_service.py b/ruoyi-fastapi-backend/module_generator/service/gen_service.py index f9436cfad..4e9e80253 100644 --- a/ruoyi-fastapi-backend/module_generator/service/gen_service.py +++ b/ruoyi-fastapi-backend/module_generator/service/gen_service.py @@ -2,7 +2,6 @@ import json import os import zipfile -from datetime import datetime from typing import Any import aiofiles @@ -174,7 +173,11 @@ async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: Edit :param page_object: 编辑业务表对象 :return: 编辑业务表校验结果 """ - edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True) + edit_gen_table = page_object.model_dump( + exclude_unset=True, + by_alias=True, + exclude={'create_time', 'update_time'}, + ) gen_table_info = await cls.get_gen_table_by_id_services(query_db, page_object.table_id) if gen_table_info.table_id: try: @@ -184,9 +187,12 @@ async def edit_gen_table_services(cls, query_db: AsyncSession, page_object: Edit await GenTableDao.edit_gen_table_dao(query_db, edit_gen_table) for gen_table_column in page_object.columns: gen_table_column.update_by = page_object.update_by - gen_table_column.update_time = datetime.now() await GenTableColumnDao.edit_gen_table_column_dao( - query_db, gen_table_column.model_dump(by_alias=True) + query_db, + gen_table_column.model_dump( + by_alias=True, + exclude={'create_time', 'update_time'}, + ), ) await query_db.commit() return CrudResponseModel(is_success=True, message='更新成功') diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 index 11aec6738..ab0ebaf68 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/controller.py.jinja2 @@ -2,15 +2,6 @@ {% set pk_field = pkColumn.python_field | camel_to_snake %} {% set pkParentheseIndex = pkColumn.column_comment.find("(") %} {% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} -{% set need_import_datetime = namespace(has_datetime=False) %} -{% for column in columns %} -{% if column.python_field in ["createTime", "updateTime"] %} -{% set need_import_datetime.has_datetime = True %} -{% endif %} -{% endfor %} -{% if need_import_datetime.has_datetime %} -from datetime import datetime -{% endif %} from typing import Annotated from fastapi import Form, Path, Query, Request, Response @@ -82,12 +73,8 @@ async def add_{{ moduleName }}_{{ businessName }}( {% for column in columns %} {% if column.python_field == "createBy" %} add_{{ businessName }}.create_by = current_user.user.user_name - {% elif column.python_field == "createTime" %} - add_{{ businessName }}.create_time = datetime.now() {% elif column.python_field == "updateBy" %} add_{{ businessName }}.update_by = current_user.user.user_name - {% elif column.python_field == "updateTime" %} - add_{{ businessName }}.update_time = datetime.now() {% endif %} {% endfor %} add_{{ businessName }}_result = await {{ BusinessName }}Service.add_{{ businessName }}_services(query_db, add_{{ businessName }}) @@ -114,8 +101,6 @@ async def edit_{{ moduleName }}_{{ businessName }}( {% for column in columns %} {% if column.python_field == "updateBy" %} edit_{{ businessName }}.update_by = current_user.user.user_name - {% elif column.python_field == "updateTime" %} - edit_{{ businessName }}.update_time = datetime.now() {% endif %} {% endfor %} edit_{{ businessName }}_result = await {{ BusinessName }}Service.edit_{{ businessName }}_services(query_db, edit_{{ businessName }}) diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 index 3337e3723..3a5e74bcb 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/dao.py.jinja2 @@ -2,8 +2,41 @@ {% set pk_field = pkColumn.python_field | camel_to_snake %} {% set pkParentheseIndex = pkColumn.column_comment.find("(") %} {% set pk_field_comment = pkColumn.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pkColumn.column_comment %} +{% set add_exclude_fields = namespace(fields=[]) %} +{% if table.sub %} +{% set add_exclude_fields.fields = add_exclude_fields.fields + [subclassName ~ '_list'] %} +{% endif %} +{% for column in columns %} +{% if column.column_name in ["create_time", "update_time"] %} +{% set add_exclude_fields.fields = add_exclude_fields.fields + [column.column_name] %} +{% elif not column.insert and column.column_name not in column_not_add_show + column_not_edit_show %} +{% set add_exclude_fields.fields = add_exclude_fields.fields + [column.python_field | camel_to_snake] %} +{% endif %} +{% endfor %} +{% set edit_audit_fields = namespace(fields=[]) %} +{% for column in columns %} +{% if column.column_name in ["create_time", "update_time"] %} +{% set edit_audit_fields.fields = edit_audit_fields.fields + [column.column_name] %} +{% endif %} +{% endfor %} +{% set edit_audit_values = namespace(value='') %} +{% for field in edit_audit_fields.fields %} +{% set edit_audit_values.value = edit_audit_values.value ~ "'" ~ field ~ "'" ~ (", " if not loop.last else "") %} +{% endfor %} +{% set sub_edit_audit_fields = namespace(fields=[]) %} +{% if table.sub %} +{% for column in subTable.columns %} +{% if column.column_name in ["create_time", "update_time"] %} +{% set sub_edit_audit_fields.fields = sub_edit_audit_fields.fields + [column.column_name] %} +{% endif %} +{% endfor %} +{% endif %} +{% set sub_edit_audit_values = namespace(value='') %} +{% for field in sub_edit_audit_fields.fields %} +{% set sub_edit_audit_values.value = sub_edit_audit_values.value ~ "'" ~ field ~ "'" ~ (", " if not loop.last else "") %} +{% endfor %} {% for column in columns %} -{% if column.query and column.query_type == 'BETWEEN' and column.python_field == "createTime" %} +{% if column.query and column.query_type == 'BETWEEN' and column.column_name == "create_time" %} from datetime import datetime, time {% endif %} {% endfor %} @@ -153,7 +186,7 @@ class {{ BusinessName }}Dao: :param {{ businessName }}: {{ functionName }}对象 :return: """ - db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% if table.sub %}'{{ subclassName }}_list', {% endif %}{% for column in columns %}{% if not column.insert and column.column_name not in column_not_add_show + column_not_edit_show %}'{{ column.python_field | camel_to_snake }}'{% if not loop.last %}, {% endif %}{% endif %}{% endfor %}{% raw %}}{% endraw %})) + db_{{ businessName }} = {{ ClassName }}(**{{ businessName }}.model_dump(exclude={% raw %}{{% endraw %}{% for field in add_exclude_fields.fields %}'{{ field }}'{% if not loop.last %}, {% endif %}{% endfor %}{% raw %}}{% endraw %})) db.add(db_{{ businessName }}) await db.flush() @@ -168,7 +201,11 @@ class {{ BusinessName }}Dao: :param {{ businessName }}: 需要更新的{{ functionName }}字典 :return: """ - await db.execute(update({{ ClassName }}), [{{ businessName }}]) + safe_{{ businessName }} = { + key: value + for key, value in {{ businessName }}.items() + if key not in {% if edit_audit_values.value %}{{ "{" }}{{ edit_audit_values.value }}{{ "}" }}{% else %}set(){% endif %}{{ "\n" }} } + await db.execute(update({{ ClassName }}), [safe_{{ businessName }}]) @classmethod async def delete_{{ businessName }}_dao(cls, db: AsyncSession, {{ businessName }}: {{ BusinessName }}Model) -> None: @@ -191,7 +228,7 @@ class {{ BusinessName }}Dao: :param {{ subTable.business_name }}: {{ subTable.function_name }}对象 :return: """ - db_{{ subTable.business_name }} = {{ subClassName }}(**{{ subTable.business_name }}.model_dump()) + db_{{ subTable.business_name }} = {{ subClassName }}(**{{ subTable.business_name }}.model_dump(exclude={% raw %}{{% endraw %}{% for field in sub_edit_audit_fields.fields %}'{{ field }}'{% if not loop.last %}, {% endif %}{% endfor %}{% raw %}}{% endraw %})) db.add(db_{{ subTable.business_name }}) await db.flush() @@ -206,7 +243,11 @@ class {{ BusinessName }}Dao: :param {{ subTable.business_name }}: 需要更新的{{ subTable.function_name }}字典 :return: """ - await db.execute(update({{ subClassName }}), [{{ subTable.business_name }}]) + safe_{{ subTable.business_name }} = { + key: value + for key, value in {{ subTable.business_name }}.items() + if key not in {% if sub_edit_audit_values.value %}{{ "{" }}{{ sub_edit_audit_values.value }}{{ "}" }}{% else %}set(){% endif %}{{ "\n" }} } + await db.execute(update({{ subClassName }}), [safe_{{ subTable.business_name }}]) @classmethod async def delete_{{ subTable.business_name }}_dao(cls, db: AsyncSession, {{ subTable.business_name }}: {{ subTable.business_name | capitalize }}Model) -> None: diff --git a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 index 13e6a7a3c..c81f3f55f 100644 --- a/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 +++ b/ruoyi-fastapi-backend/module_generator/templates/python/do.py.jinja2 @@ -1,3 +1,31 @@ +{% set main_time = namespace(create=None, update=None) %} +{% for column in columns %} +{% if column.column_name == "create_time" and column.python_type == "datetime" %} +{% set main_time.create = column %} +{% elif column.column_name == "update_time" and column.python_type == "datetime" %} +{% set main_time.update = column %} +{% endif %} +{% endfor %} +{% set sub_time = namespace(create=None, update=None) %} +{% if table.sub %} +{% for column in subTable.columns %} +{% if column.column_name == "create_time" and column.python_type == "datetime" %} +{% set sub_time.create = column %} +{% elif column.column_name == "update_time" and column.python_type == "datetime" %} +{% set sub_time.update = column %} +{% endif %} +{% endfor %} +{% endif %} +{% set time_mixin_imports = namespace(names=[]) %} +{% if (main_time.create and main_time.update) or (sub_time.create and sub_time.update) %} +{% set time_mixin_imports.names = time_mixin_imports.names + ['AuditTimeMixin'] %} +{% endif %} +{% if (main_time.create and not main_time.update) or (sub_time.create and not sub_time.update) %} +{% set time_mixin_imports.names = time_mixin_imports.names + ['CreateTimeMixin'] %} +{% endif %} +{% if (main_time.update and not main_time.create) or (sub_time.update and not sub_time.create) %} +{% set time_mixin_imports.names = time_mixin_imports.names + ['UpdateTimeMixin'] %} +{% endif %} {% for do_import in doImportList %} {{ do_import }} {% endfor %} @@ -5,6 +33,9 @@ from sqlalchemy.orm import relationship {% endif %} +{% if time_mixin_imports.names %} +from common.mixin import {{ time_mixin_imports.names | join(', ') }} +{% endif %} {% if dataSourceName == defaultDataSourceName %} from config.database import Base {% else %} @@ -15,16 +46,30 @@ DataSourceBase = get_data_source_base('{{ dataSourceName }}') {% endif %} -class {{ ClassName }}({% if dataSourceName == defaultDataSourceName %}Base{% else %}DataSourceBase{% endif %}): +class {{ ClassName }}({% if main_time.create and main_time.update %}AuditTimeMixin, {% elif main_time.create %}CreateTimeMixin, {% elif main_time.update %}UpdateTimeMixin, {% endif %}{% if dataSourceName == defaultDataSourceName %}Base{% else %}DataSourceBase{% endif %}): """ {{ functionName }}表 """ __tablename__ = '{{ tableName }}' __table_args__ = {'comment': '{{ tableComment }}'} + {% if main_time.create and (main_time.create.required or main_time.create.pk) %} + __create_time_nullable__ = False + {% endif %} + {% if main_time.create and main_time.create.column_comment != '创建时间' %} + __create_time_comment__ = '{{ main_time.create.column_comment }}' + {% endif %} + {% if main_time.update and (main_time.update.required or main_time.update.pk) %} + __update_time_nullable__ = False + {% endif %} + {% if main_time.update and main_time.update.column_comment != '更新时间' %} + __update_time_comment__ = '{{ main_time.update.column_comment }}' + {% endif %} {% for column in columns %} + {% if not (column.column_name in ["create_time", "update_time"] and column.python_type == "datetime") %} {{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type(dataSourceName) }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}') + {% endif %} {% endfor %} {% if table.sub %} @@ -33,15 +78,29 @@ class {{ ClassName }}({% if dataSourceName == defaultDataSourceName %}Base{% els {% if table.sub %} -class {{ subClassName }}({% if dataSourceName == defaultDataSourceName %}Base{% else %}DataSourceBase{% endif %}): +class {{ subClassName }}({% if sub_time.create and sub_time.update %}AuditTimeMixin, {% elif sub_time.create %}CreateTimeMixin, {% elif sub_time.update %}UpdateTimeMixin, {% endif %}{% if dataSourceName == defaultDataSourceName %}Base{% else %}DataSourceBase{% endif %}): """ {{ subTable.function_name }}表 """ __tablename__ = '{{ subTableName }}' + {% if sub_time.create and (sub_time.create.required or sub_time.create.pk) %} + __create_time_nullable__ = False + {% endif %} + {% if sub_time.create and sub_time.create.column_comment != '创建时间' %} + __create_time_comment__ = '{{ sub_time.create.column_comment }}' + {% endif %} + {% if sub_time.update and (sub_time.update.required or sub_time.update.pk) %} + __update_time_nullable__ = False + {% endif %} + {% if sub_time.update and sub_time.update.column_comment != '更新时间' %} + __update_time_comment__ = '{{ sub_time.update.column_comment }}' + {% endif %} {% for column in subTable.columns %} + {% if not (column.column_name in ["create_time", "update_time"] and column.python_type == "datetime") %} {{ column.column_name }} = Column({{ column.column_type | get_sqlalchemy_type(dataSourceName) }}, {% if column.column_name == subTableFkName %}ForeignKey('{{ tableName }}.{{ subTableFkName }}'), {% endif %}{% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}') + {% endif %} {% endfor %} {% if table.sub %} diff --git a/ruoyi-fastapi-backend/plugins/ai/controller/ai_model_controller.py b/ruoyi-fastapi-backend/plugins/ai/controller/ai_model_controller.py index d7d493598..9c1effdd2 100644 --- a/ruoyi-fastapi-backend/plugins/ai/controller/ai_model_controller.py +++ b/ruoyi-fastapi-backend/plugins/ai/controller/ai_model_controller.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Annotated from fastapi import Path, Query, Request, Response @@ -100,9 +99,7 @@ async def add_ai_model( add_ai_model.user_id = current_user.user.user_id add_ai_model.dept_id = current_user.user.dept_id add_ai_model.create_by = current_user.user.user_name - add_ai_model.create_time = datetime.now() add_ai_model.update_by = current_user.user.user_name - add_ai_model.update_time = datetime.now() add_ai_model_result = await AiModelService.add_ai_model_services(query_db, add_ai_model) logger.info(add_ai_model_result.message) @@ -134,7 +131,6 @@ async def edit_ai_model( if not current_user.user.admin: await AiModelService.check_ai_model_data_scope_services(query_db, edit_ai_model.model_id, data_scope_sql) edit_ai_model.update_by = current_user.user.user_name - edit_ai_model.update_time = datetime.now() edit_ai_model_result = await AiModelService.edit_ai_model_services(query_db, edit_ai_model) logger.info(edit_ai_model_result.message) diff --git a/ruoyi-fastapi-backend/plugins/ai/dao/ai_chat_dao.py b/ruoyi-fastapi-backend/plugins/ai/dao/ai_chat_dao.py index 5e3bae74b..eacbf34bf 100644 --- a/ruoyi-fastapi-backend/plugins/ai/dao/ai_chat_dao.py +++ b/ruoyi-fastapi-backend/plugins/ai/dao/ai_chat_dao.py @@ -34,7 +34,9 @@ async def add_chat_config_dao(cls, db: AsyncSession, chat_config: AiChatConfigMo :param chat_config: 对话配置对象 :return: 配置对象 """ - db_chat_config = AiChatConfig(**chat_config.model_dump(exclude_unset=True)) + db_chat_config = AiChatConfig( + **chat_config.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) + ) db.add(db_chat_config) await db.flush() @@ -49,4 +51,7 @@ async def edit_chat_config_dao(cls, db: AsyncSession, chat_config: dict) -> None :param chat_config: 需要更新的对话配置字典 :return: """ - await db.execute(update(AiChatConfig), [chat_config]) + await db.execute( + update(AiChatConfig), + [{key: value for key, value in chat_config.items() if key not in {'create_time', 'update_time'}}], + ) diff --git a/ruoyi-fastapi-backend/plugins/ai/dao/ai_model_dao.py b/ruoyi-fastapi-backend/plugins/ai/dao/ai_model_dao.py index c4c0f1c66..99e296208 100644 --- a/ruoyi-fastapi-backend/plugins/ai/dao/ai_model_dao.py +++ b/ruoyi-fastapi-backend/plugins/ai/dao/ai_model_dao.py @@ -67,7 +67,7 @@ async def add_ai_model_dao(cls, db: AsyncSession, ai_model: AiModelModel) -> AiM :param ai_model: AI模型对象 :return: AI模型信息对象 """ - db_model = AiModels(**ai_model.model_dump(exclude_unset=True)) + db_model = AiModels(**ai_model.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'})) db.add(db_model) await db.flush() @@ -82,7 +82,10 @@ async def edit_ai_model_dao(cls, db: AsyncSession, ai_model: dict) -> None: :param ai_model: 需要更新的AI模型字典 :return: """ - await db.execute(update(AiModels), [ai_model]) + await db.execute( + update(AiModels), + [{key: value for key, value in ai_model.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_ai_model_dao(cls, db: AsyncSession, ai_model: AiModelModel) -> None: diff --git a/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_chat_do.py b/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_chat_do.py index a07584aa1..084e2daa1 100644 --- a/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_chat_do.py +++ b/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_chat_do.py @@ -1,11 +1,10 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Float, Integer, Text +from sqlalchemy import CHAR, BigInteger, Column, Float, Integer, Text +from common.mixin import AuditTimeMixin from config.database import Base -class AiChatConfig(Base): +class AiChatConfig(AuditTimeMixin, Base): """ AI对话配置表 """ @@ -22,5 +21,3 @@ class AiChatConfig(Base): metrics_default_visible = Column(CHAR(1), server_default='0', comment='默认显示指标(0是, 1否)') vision_enabled = Column(CHAR(1), server_default='1', comment='是否开启视觉(0是, 1否)') image_max_size_mb = Column(Integer, nullable=True, comment='图片最大大小(MB)') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') - update_time = Column(DateTime, nullable=True, default=datetime.now, comment='更新时间') diff --git a/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_model_do.py b/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_model_do.py index 6928d9918..87bf34221 100644 --- a/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_model_do.py +++ b/ruoyi-fastapi-backend/plugins/ai/entity/do/ai_model_do.py @@ -1,13 +1,12 @@ -from datetime import datetime - -from sqlalchemy import CHAR, BigInteger, Column, DateTime, Float, Integer, String +from sqlalchemy import CHAR, BigInteger, Column, Float, Integer, String +from common.mixin import AuditTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class AiModels(Base): +class AiModels(AuditTimeMixin, Base): """ AI模型表 """ @@ -51,9 +50,7 @@ class AiModels(Base): user_id = Column(BigInteger, nullable=True, comment='用户ID') dept_id = Column(BigInteger, nullable=True, comment='部门ID') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/plugins/ai/service/ai_chat_service.py b/ruoyi-fastapi-backend/plugins/ai/service/ai_chat_service.py index bd05061fa..d4bfbf6a2 100644 --- a/ruoyi-fastapi-backend/plugins/ai/service/ai_chat_service.py +++ b/ruoyi-fastapi-backend/plugins/ai/service/ai_chat_service.py @@ -359,11 +359,12 @@ async def save_ai_chat_config_services( if chat_config: if chat_config.chat_config_id != page_object.chat_config_id: raise ServiceException(message='只允许修改当前用户的配置') - page_object.update_time = datetime.now() - edit_ai_chat_config = page_object.model_dump(exclude_unset=True) + edit_ai_chat_config = page_object.model_dump( + exclude_unset=True, + exclude={'create_time', 'update_time'}, + ) await AiChatConfigDao.edit_chat_config_dao(query_db, edit_ai_chat_config) else: - page_object.create_time = datetime.now() await AiChatConfigDao.add_chat_config_dao(query_db, page_object) await query_db.commit() diff --git a/ruoyi-fastapi-backend/plugins/ai/service/ai_model_service.py b/ruoyi-fastapi-backend/plugins/ai/service/ai_model_service.py index 48b32c735..a9b99fe9b 100644 --- a/ruoyi-fastapi-backend/plugins/ai/service/ai_model_service.py +++ b/ruoyi-fastapi-backend/plugins/ai/service/ai_model_service.py @@ -93,7 +93,7 @@ async def edit_ai_model_services(cls, query_db: AsyncSession, page_object: AiMod :param page_object: 编辑AI模型对象 :return: 编辑AI模型校验结果 """ - edit_ai_model = page_object.model_dump(exclude_unset=True) + edit_ai_model = page_object.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) if page_object.api_key: if page_object.api_key == '********' * 3: if 'api_key' in edit_ai_model: diff --git a/ruoyi-fastapi-backend/plugins/core/lifecycle/jobs.py b/ruoyi-fastapi-backend/plugins/core/lifecycle/jobs.py index 188d3dbf4..f2b336c47 100644 --- a/ruoyi-fastapi-backend/plugins/core/lifecycle/jobs.py +++ b/ruoyi-fastapi-backend/plugins/core/lifecycle/jobs.py @@ -1,5 +1,4 @@ import json -from datetime import datetime from sqlalchemy import delete, func, select, update from sqlalchemy.ext.asyncio import AsyncSession @@ -253,7 +252,6 @@ async def upsert_plugin_job(self, job_model: JobModel) -> SysJob: :return: 写入后的系统任务对象 """ existing_job = await self.repository.get_job_detail_by_name_group(job_model.job_name, job_model.job_group) - now = datetime.now() if existing_job: ownership_prefix = f'{PluginJobModelBuilder.REMARK_PREFIX} {job_model.job_name}' if not str(existing_job.remark or '').startswith(ownership_prefix): @@ -275,14 +273,11 @@ async def upsert_plugin_job(self, job_model: JobModel) -> SysJob: concurrent=job_model.concurrent, status=job_model.status, update_by=job_model.update_by, - update_time=now, remark=job_model.remark, ) ) return existing_job - job_model.create_time = now - job_model.update_time = now return await JobDao.add_job_dao(self.query_db, job_model) async def pause_plugin_jobs(self, plugin_id: str) -> None: diff --git a/ruoyi-fastapi-backend/plugins/core/management/dao/dao.py b/ruoyi-fastapi-backend/plugins/core/management/dao/dao.py index 65fd40f56..208924fb5 100644 --- a/ruoyi-fastapi-backend/plugins/core/management/dao/dao.py +++ b/ruoyi-fastapi-backend/plugins/core/management/dao/dao.py @@ -131,7 +131,10 @@ def dump_plugin_persistence_payload(plugin: PluginModel) -> dict[str, Any]: :param plugin: 插件信息对象 :return: 可写入 sys_plugin 的字段字典 """ - return plugin.model_dump(exclude_unset=True, exclude=PLUGIN_MODEL_RUNTIME_FIELDS) + return plugin.model_dump( + exclude_unset=True, + exclude=PLUGIN_MODEL_RUNTIME_FIELDS | {'create_time', 'update_time'}, + ) @classmethod async def update_plugin(cls, db: AsyncSession, plugin: dict) -> None: @@ -142,7 +145,10 @@ async def update_plugin(cls, db: AsyncSession, plugin: dict) -> None: :param plugin: 插件更新字典 :return: None """ - await db.execute(update(SysPlugin), [plugin]) + await db.execute( + update(SysPlugin), + [{key: value for key, value in plugin.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def delete_plugin(cls, db: AsyncSession, plugin_id: str) -> None: @@ -221,7 +227,7 @@ async def add_plugin_menu(cls, db: AsyncSession, plugin_menu: PluginMenuModel) - :param plugin_menu: 插件菜单关联对象 :return: 新增后的插件菜单关联对象 """ - db_plugin_menu = SysPluginMenu(**plugin_menu.model_dump(exclude_unset=True)) + db_plugin_menu = SysPluginMenu(**plugin_menu.model_dump(exclude_unset=True, exclude={'create_time'})) db.add(db_plugin_menu) await db.flush() @@ -422,7 +428,7 @@ async def update_plugin_migration_status( SysPluginMigration.plugin_id == plugin_id, SysPluginMigration.migration_path == migration_path, ) - .values(status=status, error_message=error_message, finished_time=now, update_time=now) + .values(status=status, error_message=error_message, finished_time=now) ) await db.flush() @@ -485,7 +491,12 @@ def _apply_migration_observability_payload( :return: None """ status = payload.get('status', 'success') - payload.setdefault('update_time', now) + if existing_plugin_migration is None: + # migration records have no INSERT default; keep one snapshot on first write. + payload['update_time'] = now + else: + # UPDATE statements use the mapped onupdate callback. Ignore client/audit input. + payload.pop('update_time', None) if status == 'running': payload.setdefault('started_time', now) payload.setdefault('finished_time', None) @@ -592,7 +603,9 @@ async def add_plugin_config(cls, db: AsyncSession, plugin_config: PluginConfigMo :param plugin_config: 插件配置对象 :return: 新增后的插件配置对象 """ - db_plugin_config = SysPluginConfig(**plugin_config.model_dump(exclude_unset=True)) + db_plugin_config = SysPluginConfig( + **plugin_config.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'}) + ) db.add(db_plugin_config) await db.flush() @@ -607,7 +620,10 @@ async def update_plugin_config(cls, db: AsyncSession, plugin_config: dict) -> No :param plugin_config: 插件配置更新字典 :return: None """ - await db.execute(update(SysPluginConfig), [plugin_config]) + await db.execute( + update(SysPluginConfig), + [{key: value for key, value in plugin_config.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def add_plugin_operation_log( @@ -622,7 +638,9 @@ async def add_plugin_operation_log( :param operation_log: 插件批量操作审计日志对象 :return: 新增后的插件批量操作审计日志对象 """ - db_operation_log = SysPluginOperationLog(**operation_log.model_dump(exclude_unset=True)) + db_operation_log = SysPluginOperationLog( + **operation_log.model_dump(exclude_unset=True, exclude={'create_time'}) + ) db.add(db_operation_log) await db.flush() @@ -915,7 +933,7 @@ async def add_sys_menu(cls, db: AsyncSession, menu: MenuModel) -> SysMenu: :param menu: 菜单对象 :return: 新增后的系统菜单对象 """ - db_menu = SysMenu(**menu.model_dump(exclude_unset=True)) + db_menu = SysMenu(**menu.model_dump(exclude_unset=True, exclude={'create_time', 'update_time'})) db.add(db_menu) await db.flush() @@ -930,7 +948,10 @@ async def update_sys_menu(cls, db: AsyncSession, menu: dict) -> None: :param menu: 菜单更新字典 :return: None """ - await db.execute(update(SysMenu), [menu]) + await db.execute( + update(SysMenu), + [{key: value for key, value in menu.items() if key not in {'create_time', 'update_time'}}], + ) @classmethod async def update_sys_menu_status_by_ids(cls, db: AsyncSession, menu_ids: list[int], status: str) -> None: diff --git a/ruoyi-fastapi-backend/plugins/core/management/entity/do/models.py b/ruoyi-fastapi-backend/plugins/core/management/entity/do/models.py index b0f204698..f60e7d5a9 100644 --- a/ruoyi-fastapi-backend/plugins/core/management/entity/do/models.py +++ b/ruoyi-fastapi-backend/plugins/core/management/entity/do/models.py @@ -1,13 +1,12 @@ -from datetime import datetime - from sqlalchemy import CHAR, BigInteger, CheckConstraint, Column, DateTime, Integer, String, Text, UniqueConstraint +from common.mixin import AuditTimeMixin, CreateTimeMixin from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil -class SysPlugin(Base): +class SysPlugin(AuditTimeMixin, Base): """ 插件信息表。 @@ -36,15 +35,7 @@ class SysPlugin(Base): last_error = Column(String(1000), nullable=True, comment='最近一次错误信息') description = Column(String(500), nullable=True, comment='插件说明') create_by = Column(String(64), nullable=True, server_default="''", comment='创建者') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default="''", comment='更新者') - update_time = Column( - DateTime, - nullable=True, - default=datetime.now, - onupdate=datetime.now, - comment='更新时间', - ) remark = Column( String(500), nullable=True, @@ -53,7 +44,7 @@ class SysPlugin(Base): ) -class SysPluginMenu(Base): +class SysPluginMenu(CreateTimeMixin, Base): """ 插件和菜单关联表。 @@ -69,10 +60,9 @@ class SysPluginMenu(Base): plugin_id = Column(String(64), primary_key=True, nullable=False, comment='插件ID') menu_id = Column(BigInteger, primary_key=True, nullable=False, comment='菜单ID') menu_key = Column(String(255), nullable=False, comment='插件内菜单自然键') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') -class SysPluginMigration(Base): +class SysPluginMigration(AuditTimeMixin, Base): """ 插件 migration 执行历史表。 @@ -80,6 +70,8 @@ class SysPluginMigration(Base): """ __tablename__ = 'sys_plugin_migration' + __create_time_comment__ = '执行时间' + __update_time_insert_default__ = False __table_args__ = {'comment': '插件 migration 执行历史表'} plugin_id = Column(String(64), primary_key=True, nullable=False, comment='插件ID') @@ -92,11 +84,9 @@ class SysPluginMigration(Base): attempt_count = Column(Integer, nullable=False, default=0, comment='尝试次数') started_time = Column(DateTime, nullable=True, comment='最近开始时间') finished_time = Column(DateTime, nullable=True, comment='最近结束时间') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='执行时间') - update_time = Column(DateTime, nullable=True, onupdate=datetime.now, comment='更新时间') -class SysPluginConfig(Base): +class SysPluginConfig(AuditTimeMixin, Base): """ 插件配置表。 @@ -116,17 +106,9 @@ class SysPluginConfig(Base): secret = Column(CHAR(1), nullable=False, server_default='1', comment='是否敏感(0是 1否)') options = Column(Text, nullable=True, comment='配置选项JSON') description = Column(String(500), nullable=True, comment='配置说明') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') - update_time = Column( - DateTime, - nullable=True, - default=datetime.now, - onupdate=datetime.now, - comment='更新时间', - ) -class SysPluginOperationLog(Base): +class SysPluginOperationLog(CreateTimeMixin, Base): """ 插件批量操作审计日志表。 @@ -150,7 +132,6 @@ class SysPluginOperationLog(Base): status = Column(String(32), nullable=False, comment='执行状态') summary = Column(Text, nullable=True, comment='执行汇总JSON') result = Column(Text, nullable=True, comment='完整执行结果JSON') - create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') remark = Column( String(500), nullable=True, diff --git a/ruoyi-fastapi-backend/plugins/core/management/service/menus.py b/ruoyi-fastapi-backend/plugins/core/management/service/menus.py index aeb3cf5ef..c74b64fc9 100644 --- a/ruoyi-fastapi-backend/plugins/core/management/service/menus.py +++ b/ruoyi-fastapi-backend/plugins/core/management/service/menus.py @@ -1,5 +1,3 @@ -from datetime import datetime - from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.menu_do import SysMenu @@ -349,7 +347,6 @@ def _build_menu_model( status=menu.status, perms=menu.perms or None, icon=menu.icon, - updateTime=datetime.now(), remark=menu_remark, ) diff --git a/ruoyi-fastapi-backend/plugins/core/management/service/service.py b/ruoyi-fastapi-backend/plugins/core/management/service/service.py index e44f6cf92..ed6541b6c 100644 --- a/ruoyi-fastapi-backend/plugins/core/management/service/service.py +++ b/ruoyi-fastapi-backend/plugins/core/management/service/service.py @@ -193,7 +193,6 @@ async def update_plugin_enabled_services( 'plugin_id': plugin_id, 'enabled': enabled_value, 'status': status, - 'update_time': datetime.now(), } if enabled: update_payload['last_error'] = None @@ -230,9 +229,12 @@ async def mark_plugin_installed_services( 'installed_version': manifest.version, 'status': status, 'last_error': None, - 'update_time': datetime.now(), } await PluginDao.update_plugin(query_db, update_payload) + if plugin is not None: + await query_db.refresh(plugin) + else: + plugin = await PluginDao.get_plugin_by_id(query_db, manifest.id) return PluginModel( pluginId=manifest.id, @@ -244,7 +246,7 @@ async def mark_plugin_installed_services( source='local', backendPath=str(discovered_plugin.backend_path), description=manifest.description, - updateTime=update_payload['update_time'], + updateTime=getattr(plugin, 'update_time', None), ) @classmethod @@ -270,7 +272,6 @@ async def mark_plugin_uninstalled_services(cls, query_db: AsyncSession, plugin_i 'enabled': '1', 'status': 'discovered', 'last_error': None, - 'update_time': datetime.now(), } await PluginDao.update_plugin(query_db, update_payload) await PluginDao.delete_plugin_menus(query_db, plugin_id) @@ -335,7 +336,6 @@ async def install_plugin_default_config_services( 'secret': config_model.secret, 'options': config_model.options, 'description': config_model.description, - 'update_time': datetime.now(), }, ) else: @@ -433,7 +433,6 @@ async def update_plugin_config_services( 'plugin_id': discovered_plugin.manifest.id, 'config_key': key, 'config_value': PluginConfigManager.serialize_config_value(value, secret=item.secret), - 'update_time': datetime.now(), }, ) @@ -646,7 +645,6 @@ async def mark_plugin_error_services( 'status': PluginStateTransitionTable.resolve_target(getattr(plugin, 'status', None), 'mark_error') or 'error', 'last_error': error_message[:1000], - 'update_time': datetime.now(), }, ) await PluginMenuInstaller(query_db).set_plugin_menu_status(plugin_id, '1') @@ -703,7 +701,6 @@ async def recover_plugin_dependency_error_services( 'plugin_id': plugin_id, 'status': target_status, 'last_error': None, - 'update_time': datetime.now(), }, ) diff --git a/ruoyi-fastapi-backend/tests/module_generator/dao/test_gen_dao.py b/ruoyi-fastapi-backend/tests/module_generator/dao/test_gen_dao.py index f21c29b6c..1c001a8e4 100644 --- a/ruoyi-fastapi-backend/tests/module_generator/dao/test_gen_dao.py +++ b/ruoyi-fastapi-backend/tests/module_generator/dao/test_gen_dao.py @@ -1,12 +1,17 @@ +from datetime import datetime from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock, patch import pytest -from module_generator.dao.gen_dao import GenTableDao +from module_generator.dao.gen_dao import GenTableColumnDao, GenTableDao from module_generator.entity.vo.gen_vo import GenTablePageQueryModel from utils.page_util import PageUtil +TABLE_ID = 7 +COLUMN_ID = 9 +CLIENT_AUDIT_TIME = datetime(2000, 1, 1) + @pytest.mark.asyncio async def test_get_gen_table_by_name_is_scoped_to_data_source() -> None: @@ -85,3 +90,37 @@ async def test_get_gen_db_table_list_uses_target_source_config() -> None: query = paginate.await_args.args[1] assert 'list_table' in str(query.compile()) assert query.compile().params['excluded_table_names'] == ('sys_user',) + + +@pytest.mark.asyncio +async def test_edit_gen_table_keeps_alias_fields_while_excluding_audit_times() -> None: + db = MagicMock() + db.execute = AsyncMock() + + await GenTableDao.edit_gen_table_dao( + db, + {'tableId': TABLE_ID, 'tableName': 'orders', 'updateTime': CLIENT_AUDIT_TIME}, + ) + + params = db.execute.await_args.args[1][0] + assert params['table_id'] == TABLE_ID + assert params['table_name'] == 'orders' + assert 'create_time' not in params + assert 'update_time' not in params + + +@pytest.mark.asyncio +async def test_edit_gen_table_column_keeps_alias_fields_while_excluding_audit_times() -> None: + db = MagicMock() + db.execute = AsyncMock() + + await GenTableColumnDao.edit_gen_table_column_dao( + db, + {'columnId': COLUMN_ID, 'columnName': 'order_id', 'updateTime': CLIENT_AUDIT_TIME}, + ) + + params = db.execute.await_args.args[1][0] + assert params['column_id'] == COLUMN_ID + assert params['column_name'] == 'order_id' + assert 'create_time' not in params + assert 'update_time' not in params diff --git a/ruoyi-fastapi-backend/tests/module_generator/test_gen_service.py b/ruoyi-fastapi-backend/tests/module_generator/test_gen_service.py index b7914b497..ea1c55ecc 100644 --- a/ruoyi-fastapi-backend/tests/module_generator/test_gen_service.py +++ b/ruoyi-fastapi-backend/tests/module_generator/test_gen_service.py @@ -7,6 +7,7 @@ from sqlalchemy import create_engine, text from exceptions.exception import ServiceException +from module_generator.entity.vo.gen_vo import EditGenTableModel, GenTableColumnModel from module_generator.service.gen_service import GenTableService @@ -76,3 +77,45 @@ async def test_batch_gen_code_services_rejects_tables_from_other_sources() -> No assert exc_info.value.message == '业务表不存在或不属于数据源 reporting:report_order' init_jinja2.assert_not_called() + + +@pytest.mark.asyncio +async def test_edit_gen_table_services_preserves_alias_fields_for_both_daos() -> None: + table_id = 7 + column_id = 9 + page_object = EditGenTableModel( + tableId=table_id, + tableName='orders', + updateBy='admin', + columns=[GenTableColumnModel(columnId=column_id, columnName='order_id')], + ) + query_db = SimpleNamespace(commit=AsyncMock(), rollback=AsyncMock()) + table_info = SimpleNamespace(table_id=table_id, data_source_name='primary') + + with ( + patch.object( + GenTableService, + 'get_gen_table_by_id_services', + new=AsyncMock(return_value=table_info), + ), + patch( + 'module_generator.service.gen_service.GenTableDao.edit_gen_table_dao', + new=AsyncMock(), + ) as edit_table, + patch( + 'module_generator.service.gen_service.GenTableColumnDao.edit_gen_table_column_dao', + new=AsyncMock(), + ) as edit_column, + ): + await GenTableService.edit_gen_table_services(query_db, page_object) + + table_payload = edit_table.await_args.args[1] + column_payload = edit_column.await_args.args[1] + assert table_payload['tableId'] == table_id + assert table_payload['tableName'] == 'orders' + assert column_payload['columnId'] == column_id + assert column_payload['columnName'] == 'order_id' + assert 'createTime' not in table_payload + assert 'updateTime' not in table_payload + assert 'createTime' not in column_payload + assert 'updateTime' not in column_payload diff --git a/ruoyi-fastapi-backend/tests/plugins/core/management/test_plugin_state.py b/ruoyi-fastapi-backend/tests/plugins/core/management/test_plugin_state.py index 887c6534e..938297cdc 100644 --- a/ruoyi-fastapi-backend/tests/plugins/core/management/test_plugin_state.py +++ b/ruoyi-fastapi-backend/tests/plugins/core/management/test_plugin_state.py @@ -484,9 +484,11 @@ async def test_mark_plugin_installed_updates_installed_version(tmp_path: Path) - assert plugin.installed_version == '1.0.0' assert plugin.status == 'installed' + assert plugin.update_time is not None assert db_plugin is not None assert db_plugin.installed_version == '1.0.0' assert db_plugin.status == 'installed' + assert plugin.update_time == db_plugin.update_time finally: await engine.dispose() diff --git a/ruoyi-fastapi-backend/tests/test_audit_time_delegation.py b/ruoyi-fastapi-backend/tests/test_audit_time_delegation.py new file mode 100644 index 000000000..a1b8c1712 --- /dev/null +++ b/ruoyi-fastapi-backend/tests/test_audit_time_delegation.py @@ -0,0 +1,35 @@ +from datetime import datetime + +from plugins.core.management.dao.dao import PluginDao +from plugins.core.management.entity.do.models import SysPluginMigration +from plugins.core.management.entity.vo.schemas import PluginMigrationModel, PluginModel + + +def test_plugin_persistence_payload_ignores_client_audit_times() -> None: + now = datetime(2026, 8, 21, 12, 0, 0) + payload = PluginDao.dump_plugin_persistence_payload( + PluginModel( + pluginId='demo', + pluginName='Demo', + version='1.0.0', + createTime=now, + updateTime=now, + ) + ) + + assert 'create_time' not in payload + assert 'update_time' not in payload + + +def test_existing_migration_update_uses_onupdate_not_external_time() -> None: + now = datetime(2026, 8, 21, 12, 0, 0) + payload = PluginMigrationModel( + pluginId='demo', + migrationPath='migrations/001.sql', + migrationChecksum='checksum', + updateTime=datetime(2000, 1, 1), + ).model_dump(exclude_unset=True) + + PluginDao._apply_migration_observability_payload(payload, SysPluginMigration(), now) + + assert 'update_time' not in payload diff --git a/ruoyi-fastapi-backend/tests/test_entity_time_columns.py b/ruoyi-fastapi-backend/tests/test_entity_time_columns.py new file mode 100644 index 000000000..5cbefd0ea --- /dev/null +++ b/ruoyi-fastapi-backend/tests/test_entity_time_columns.py @@ -0,0 +1,228 @@ +"""契约测试:实体 DO 的时间列必须使用 SQLAlchemy callable defaults。""" + +import asyncio + +import pytest +from sqlalchemy import DateTime, update +from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine + +from common.mixin import AuditTimeMixin, CreateTimeMixin +from config.database import Base +from module_admin.entity.do.config_do import SysConfig +from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.dict_do import SysDictData, SysDictType +from module_admin.entity.do.file_do import ( + SysFileAccessLog, + SysFileAcl, + SysFileInfo, + SysFileReconcileIssue, + SysFileReconcileRun, + SysFileReference, + SysFileRetentionNotice, + SysFileRetentionPolicy, +) +from module_admin.entity.do.job_do import SysJob, SysJobLog +from module_admin.entity.do.log_do import SysLogininfor, SysOperLog +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.do.notice_do import SysNotice, SysNoticeRead +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu +from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole +from module_generator.entity.do.gen_do import GenTable, GenTableColumn +from plugins.ai.entity.do.ai_chat_do import AiChatConfig +from plugins.ai.entity.do.ai_model_do import AiModels +from plugins.core.management.entity.do.models import ( + SysPlugin, + SysPluginConfig, + SysPluginMenu, + SysPluginMigration, + SysPluginOperationLog, +) + +EXPECTED_ENTITY_MODEL_COUNT = 35 +EXPECTED_UPDATE_TIME_MODEL_COUNT = 19 +EXPECTED_CREATE_TIME_ONLY_MODEL_COUNT = 6 + +# Keep both inventories explicit: there are 35 DO classes and 19 update_time +# columns. Adding/removing one should be an intentional model change. +ENTITY_MODELS = ( + SysConfig, + SysDept, + SysDictType, + SysDictData, + SysFileInfo, + SysFileReference, + SysFileRetentionPolicy, + SysFileRetentionNotice, + SysFileAcl, + SysFileAccessLog, + SysFileReconcileRun, + SysFileReconcileIssue, + SysJob, + SysJobLog, + SysLogininfor, + SysOperLog, + SysMenu, + SysNotice, + SysNoticeRead, + SysPost, + SysRole, + SysRoleDept, + SysRoleMenu, + SysUser, + SysUserRole, + SysUserPost, + GenTable, + GenTableColumn, + AiChatConfig, + AiModels, + SysPlugin, + SysPluginMenu, + SysPluginMigration, + SysPluginConfig, + SysPluginOperationLog, +) + +UPDATE_TIME_MODELS = ( + SysConfig, + SysDept, + SysDictType, + SysDictData, + SysFileInfo, + SysFileRetentionPolicy, + SysJob, + SysMenu, + SysNotice, + SysPost, + SysRole, + SysUser, + GenTable, + GenTableColumn, + AiChatConfig, + AiModels, + SysPlugin, + SysPluginMigration, + SysPluginConfig, +) + +CREATE_TIME_ONLY_MODELS = ( + SysFileReference, + SysFileRetentionNotice, + SysFileAcl, + SysJobLog, + SysPluginMenu, + SysPluginOperationLog, +) + +REQUIRED_CREATE_TIME_MODELS = { + SysFileInfo, + SysFileReference, + SysFileRetentionPolicy, + SysFileRetentionNotice, + SysFileAcl, +} + +REQUIRED_UPDATE_TIME_MODELS = { + SysFileInfo, + SysFileRetentionPolicy, +} + + +def _column_default(model: type, field_name: str, attribute: str) -> object | None: + column = model.__table__.c[field_name] + value = getattr(column, attribute) + return None if value is None else value.arg + + +def test_audit_time_columns_are_provided_by_mixins_without_changing_contracts() -> None: + """审计字段统一由Mixin声明,并保留非空、注释及特殊默认值语义。""" + assert len(UPDATE_TIME_MODELS) == EXPECTED_UPDATE_TIME_MODEL_COUNT + assert len(CREATE_TIME_ONLY_MODELS) == EXPECTED_CREATE_TIME_ONLY_MODEL_COUNT + + for model in UPDATE_TIME_MODELS: + assert issubclass(model, AuditTimeMixin) + assert model.__table__.c.create_time.nullable is (model not in REQUIRED_CREATE_TIME_MODELS) + assert model.__table__.c.update_time.nullable is (model not in REQUIRED_UPDATE_TIME_MODELS) + assert model.__table__.c.create_time.server_default is None + assert model.__table__.c.update_time.server_default is None + + for model in CREATE_TIME_ONLY_MODELS: + assert issubclass(model, CreateTimeMixin) + assert model.__table__.c.create_time.nullable is (model not in REQUIRED_CREATE_TIME_MODELS) + assert model.__table__.c.create_time.server_default is None + + assert SysPluginMigration.__table__.c.create_time.comment == '执行时间' + + +def test_all_update_time_columns_use_callable_defaults_and_onupdate() -> None: + """每个 update_time 都在每次插入/更新时求值,而非导入时冻结时间。""" + assert len(UPDATE_TIME_MODELS) == EXPECTED_UPDATE_TIME_MODEL_COUNT + + for model in UPDATE_TIME_MODELS: + onupdate = _column_default(model, 'update_time', 'onupdate') + assert callable(onupdate) + assert getattr(onupdate, '__name__', None) == 'now' + + if model is SysPluginMigration: + # Migration history rows are created with an explicit timestamp; + # preserve that special semantic while still tracking updates. + assert _column_default(model, 'update_time', 'default') is None + else: + default = _column_default(model, 'update_time', 'default') + assert callable(default) + assert getattr(default, '__name__', None) == 'now' + + +def test_all_create_time_defaults_are_callable() -> None: + """所有使用 datetime.now 默认值的 DateTime 列都必须传入 callable。""" + assert len(ENTITY_MODELS) == EXPECTED_ENTITY_MODEL_COUNT + + for model in ENTITY_MODELS: + for column in model.__table__.columns: + if isinstance(column.type, DateTime) and column.default is not None: + default = column.default.arg + assert callable(default), f'{model.__name__}.{column.name}' + assert getattr(default, '__name__', None) == 'now', f'{model.__name__}.{column.name}' + + +@pytest.mark.asyncio +async def test_core_model_time_defaults_are_evaluated_per_insert_and_core_update() -> None: + """SQLite 行为契约:INSERT 默认值和 Core UPDATE onupdate 都重新求值。""" + engine = create_async_engine('sqlite+aiosqlite:///:memory:') + session_maker = async_sessionmaker(bind=engine, expire_on_commit=False) + + try: + async with engine.begin() as connection: + await connection.run_sync(Base.metadata.create_all, tables=[SysPlugin.__table__]) + + async with session_maker() as session: + first = SysPlugin(plugin_id='time-first', plugin_name='First', version='1.0.0') + second = SysPlugin(plugin_id='time-second', plugin_name='Second', version='1.0.0') + session.add(first) + await session.flush() + first_create_time = first.create_time + first_update_time = first.update_time + + await asyncio.sleep(0.001) + session.add(second) + await session.flush() + assert second.create_time != first_create_time + assert second.update_time != first_update_time + second_update_time = second.update_time + + await asyncio.sleep(0.001) + await session.execute( + update(SysPlugin).where(SysPlugin.plugin_id == first.plugin_id).values(version='1.1.0') + ) + await session.refresh(first) + assert first.update_time > first_update_time + + await asyncio.sleep(0.001) + await session.execute( + update(SysPlugin), + [{'plugin_id': second.plugin_id, 'version': '1.1.0'}], + ) + await session.refresh(second) + assert second.update_time > second_update_time + finally: + await engine.dispose() diff --git a/ruoyi-fastapi-backend/tests/utils/test_template_util.py b/ruoyi-fastapi-backend/tests/utils/test_template_util.py index 0126b25c0..87cac7937 100644 --- a/ruoyi-fastapi-backend/tests/utils/test_template_util.py +++ b/ruoyi-fastapi-backend/tests/utils/test_template_util.py @@ -8,6 +8,8 @@ from module_generator.service.gen_service import GenTableService from utils.template_util import TemplateInitializer, TemplateUtils +MAX_GENERATED_LINE_LENGTH = 120 + def _gen_table( *, @@ -176,7 +178,172 @@ def test_secondary_source_templates_use_named_dependency_and_isolated_metadata( assert "DataSourceBase = get_data_source_base('reporting')" in entity assert 'class GenItem(DataSourceBase):' in entity assert 'from config.database import Base' not in entity + assert 'from datetime import datetime' not in entity + assert 'from common.mixin import' not in entity compile(entity, '', 'exec') + dao = env.get_template('python/dao.py.jinja2').render(**context) + assert 'if key not in set()' in dao + compile(dao, '', 'exec') + compile(controller, '', 'exec') + + +def test_python_templates_handle_audit_timestamps_by_exact_column_name() -> None: + columns = [ + GenTableColumnModel( + columnName='item_id', + columnComment='编号', + columnType='bigint', + pythonType='int', + pythonField='itemId', + isPk='1', + isInsert='1', + ), + GenTableColumnModel( + columnName='create_by', + columnComment='创建者', + columnType='varchar(64)', + pythonType='str', + pythonField='createBy', + isInsert='1', + isEdit='0', + ), + GenTableColumnModel( + columnName='update_by', + columnComment='更新者', + columnType='varchar(64)', + pythonType='str', + pythonField='updateBy', + isInsert='1', + isEdit='1', + ), + GenTableColumnModel( + columnName='create_time', + columnComment='创建时间', + columnType='datetime', + pythonType='datetime', + pythonField='createTime', + isInsert='1', + isEdit='0', + ), + GenTableColumnModel( + columnName='update_time', + columnComment='更新时间', + columnType='datetime', + pythonType='datetime', + pythonField='updateTime', + isInsert='1', + isEdit='1', + ), + GenTableColumnModel( + columnName='created_time', + columnComment='其他时间', + columnType='datetime', + pythonType='datetime', + pythonField='createdTime', + isInsert='1', + isEdit='1', + ), + ] + gen_table = _gen_table(gen_view=False) + gen_table.columns = columns + gen_table.pk_column = columns[0] + + context = TemplateUtils.prepare_context(gen_table) + env = TemplateInitializer.init_jinja2() + entity = env.get_template('python/do.py.jinja2').render(**context) + dao = env.get_template('python/dao.py.jinja2').render(**context) + controller = env.get_template('python/controller.py.jinja2').render(**context) + + assert 'from datetime import datetime' not in entity + assert 'from common.mixin import AuditTimeMixin' in entity + assert 'class GenItem(AuditTimeMixin, Base):' in entity + assert 'create_time = Column(' not in entity + assert 'update_time = Column(' not in entity + assert 'created_time = Column(DateTime, nullable=True, comment=' in entity + assert "model_dump(exclude={'create_time', 'update_time'})" in dao + assert "if key not in {'create_time', 'update_time'}" in dao + assert 'create_time = datetime.now()' not in controller + assert 'update_time = datetime.now()' not in controller + assert 'create_by = current_user.user.user_name' in controller + assert 'update_by = current_user.user.user_name' in controller + compile(entity, '', 'exec') + compile(dao, '', 'exec') + compile(controller, '', 'exec') + assert entity.index('from sqlalchemy import') < entity.index('from common.mixin import AuditTimeMixin') + assert all(len(line) <= MAX_GENERATED_LINE_LENGTH for line in entity.splitlines()) + assert all(len(line) <= MAX_GENERATED_LINE_LENGTH for line in dao.splitlines()) + assert all(len(line) <= MAX_GENERATED_LINE_LENGTH for line in controller.splitlines()) + + +def test_dao_audit_filter_uses_a_set_for_a_single_timestamp() -> None: + columns = [ + GenTableColumnModel( + columnName='item_id', + columnComment='编号', + columnType='bigint', + pythonType='int', + pythonField='itemId', + isPk='1', + isInsert='1', + ), + GenTableColumnModel( + columnName='create_time', + columnComment='创建时间', + columnType='datetime', + pythonType='datetime', + pythonField='createTime', + isInsert='1', + ), + ] + gen_table = _gen_table(gen_view=False) + gen_table.columns = columns + gen_table.pk_column = columns[0] + + context = TemplateUtils.prepare_context(gen_table) + env = TemplateInitializer.init_jinja2() + entity = env.get_template('python/do.py.jinja2').render(**context) + dao = env.get_template('python/dao.py.jinja2').render(**context) + + assert 'from common.mixin import CreateTimeMixin' in entity + assert 'class GenItem(CreateTimeMixin, Base):' in entity + assert 'create_time = Column(' not in entity + assert "if key not in {'create_time'}" in dao + compile(entity, '', 'exec') + compile(dao, '', 'exec') + + +def test_do_template_supports_required_update_time_only_mixin() -> None: + columns = [ + GenTableColumnModel( + columnName='item_id', + columnComment='编号', + columnType='bigint', + pythonType='int', + pythonField='itemId', + isPk='1', + ), + GenTableColumnModel( + columnName='update_time', + columnComment='最后修改时间', + columnType='datetime', + pythonType='datetime', + pythonField='updateTime', + isRequired='1', + ), + ] + gen_table = _gen_table(gen_view=False) + gen_table.columns = columns + gen_table.pk_column = columns[0] + + context = TemplateUtils.prepare_context(gen_table) + entity = TemplateInitializer.init_jinja2().get_template('python/do.py.jinja2').render(**context) + + assert 'from common.mixin import UpdateTimeMixin' in entity + assert 'class GenItem(UpdateTimeMixin, Base):' in entity + assert '__update_time_nullable__ = False' in entity + assert "__update_time_comment__ = '最后修改时间'" in entity + assert 'DateTime' not in entity + compile(entity, '', 'exec') def test_sub_table_entity_template_uses_required_nullable_semantics() -> None: @@ -204,14 +371,43 @@ def test_sub_table_entity_template_uses_required_nullable_semantics() -> None: pythonField='optionalValue', isRequired='0', ), + GenTableColumnModel( + columnName='create_time', + columnComment='创建时间', + columnType='datetime', + pythonType='datetime', + pythonField='createTime', + isRequired='1', + ), + GenTableColumnModel( + columnName='update_time', + columnComment='更新时间', + columnType='datetime', + pythonType='datetime', + pythonField='updateTime', + ), ], + businessName='itemDetail', + functionName='生成测试明细', ) + gen_table.sub_table.pk_column = gen_table.sub_table.columns[0] context = TemplateUtils.prepare_context(gen_table) - entity = TemplateInitializer.init_jinja2().get_template('python/do.py.jinja2').render(**context) + env = TemplateInitializer.init_jinja2() + entity = env.get_template('python/do.py.jinja2').render(**context) + dao = env.get_template('python/dao.py.jinja2').render(**context) assert "required_value = Column(String(100), nullable=False, comment='必填值')" in entity assert "optional_value = Column(String(100), nullable=True, comment='可选值')" in entity + assert 'from common.mixin import AuditTimeMixin' in entity + assert 'class GenItemDetail(AuditTimeMixin, Base):' in entity + assert '__create_time_nullable__ = False' in entity + assert 'create_time = Column(' not in entity + assert 'update_time = Column(' not in entity + assert "model_dump(exclude={'create_time', 'update_time'})" in dao + assert "if key not in {'create_time', 'update_time'}" in dao + compile(entity, '', 'exec') + compile(dao, '', 'exec') def test_named_data_source_bases_are_cached_and_isolated(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/ruoyi-fastapi-backend/utils/gen_util.py b/ruoyi-fastapi-backend/utils/gen_util.py index 33a4824c2..d37a42ec5 100644 --- a/ruoyi-fastapi-backend/utils/gen_util.py +++ b/ruoyi-fastapi-backend/utils/gen_util.py @@ -1,5 +1,4 @@ import re -from datetime import datetime from common.constant import GenConstant from config.env import DataBaseConfig, GenConfig @@ -28,9 +27,7 @@ def init_table(cls, gen_table: GenTableModel, oper_name: str) -> None: gen_table.function_name = cls.replace_text(gen_table.table_comment) gen_table.function_author = GenConfig.author gen_table.create_by = oper_name - gen_table.create_time = datetime.now() gen_table.update_by = oper_name - gen_table.update_time = datetime.now() @classmethod def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel) -> None: @@ -105,9 +102,7 @@ def init_column_field(cls, column: GenTableColumnModel, table: GenTableModel) -> column.html_type = GenConstant.HTML_EDITOR column.create_by = table.create_by - column.create_time = datetime.now() column.update_by = table.update_by - column.update_time = datetime.now() @classmethod def arrays_contains(cls, arr: list[str], target_value: str) -> bool: diff --git a/ruoyi-fastapi-backend/utils/template_util.py b/ruoyi-fastapi-backend/utils/template_util.py index 21f56536a..b439b6584 100644 --- a/ruoyi-fastapi-backend/utils/template_util.py +++ b/ruoyi-fastapi-backend/utils/template_util.py @@ -299,6 +299,8 @@ def get_do_import_list(cls, gen_table: GenTableModel) -> list[str]: import_list.add('from sqlalchemy import Column') for column in columns: sqlalchemy_type = cls.get_sqlalchemy_type(column.column_type, gen_table.data_source_name) + if column.column_name in {'create_time', 'update_time'} and sqlalchemy_type == 'DateTime': + continue if sqlalchemy_type == 'Geometry': import_list.add('from geoalchemy2 import Geometry') else: @@ -308,6 +310,8 @@ def get_do_import_list(cls, gen_table: GenTableModel) -> list[str]: sub_columns = gen_table.sub_table.columns or [] for sub_column in sub_columns: sqlalchemy_type = cls.get_sqlalchemy_type(sub_column.column_type, gen_table.data_source_name) + if sub_column.column_name in {'create_time', 'update_time'} and sqlalchemy_type == 'DateTime': + continue if sqlalchemy_type == 'Geometry': import_list.add('from geoalchemy2 import Geometry') else: