forked from erp-dev/erp
feat: added ApiAuditLog
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import action
|
||||
from api_v1.models import UploadedFile, DataSync, MDYPlateOrderStaging
|
||||
from api_v1.models import UploadedFile, DataSync, MDYPlateOrderStaging, ApiAuditLog
|
||||
from .tasks import backup_database
|
||||
|
||||
|
||||
@@ -41,3 +41,21 @@ class MDYPlateOrderStagingAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
date_hierarchy = 'created_at'
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
@admin.register(ApiAuditLog)
|
||||
class ApiAuditLogAdmin(admin.ModelAdmin):
|
||||
"""API审计日志管理"""
|
||||
list_display = ['id', 'method', 'url', 'username', 'user_id', 'response_status', 'created_at']
|
||||
list_filter = ['method', 'response_status', 'created_at']
|
||||
search_fields = ['url', 'username']
|
||||
readonly_fields = ['url', 'method', 'request_data', 'query_params', 'user_id', 'username', 'response_status', 'created_at']
|
||||
date_hierarchy = 'created_at'
|
||||
ordering = ['-created_at']
|
||||
|
||||
# 禁止添加和修改,只允许查看和删除
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
34
api_v1/migrations/0009_apiauditlog.py
Normal file
34
api_v1/migrations/0009_apiauditlog.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.8 on 2026-01-12 03:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api_v1', '0008_alter_datasync_table_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ApiAuditLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('url', models.CharField(help_text='完整的请求路径', max_length=500, verbose_name='请求URL')),
|
||||
('method', models.CharField(help_text='HTTP方法,如POST', max_length=10, verbose_name='请求方法')),
|
||||
('request_data', models.JSONField(default=dict, help_text='POST请求的body数据(JSON格式)', verbose_name='请求体数据')),
|
||||
('query_params', models.JSONField(default=dict, help_text='URL查询参数', verbose_name='查询参数')),
|
||||
('user_id', models.IntegerField(blank=True, db_index=True, help_text='操作用户的ID', null=True, verbose_name='用户ID')),
|
||||
('username', models.CharField(blank=True, default='', help_text='操作用户的用户名快照', max_length=150, verbose_name='用户名')),
|
||||
('response_status', models.IntegerField(blank=True, help_text='HTTP响应状态码', null=True, verbose_name='响应状态码')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'API审计日志',
|
||||
'verbose_name_plural': 'API审计日志',
|
||||
'db_table': 'api_audit_log',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['user_id', 'created_at'], name='api_audit_l_user_id_b15ad2_idx'), models.Index(fields=['url', 'created_at'], name='api_audit_l_url_338a46_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -113,12 +113,12 @@ class DataSync(ModelBase):
|
||||
|
||||
|
||||
class MDYPlateOrderStaging(ModelBase):
|
||||
"""明道云“开版数据表”同步暂存表(不影响 printing.PlateOrder)。
|
||||
"""明道云"开版数据表"同步暂存表(不影响 printing.PlateOrder)。
|
||||
|
||||
设计目标:
|
||||
- 与现有业务模型完全隔离,避免影响业务数据与流程
|
||||
- 仅保留 rowid 便于检索排查,其余信息全部落在 raw(JSON) 中
|
||||
- 随着同步深入再逐步“属性化”字段(从 raw 实时提取/映射)
|
||||
- 随着同步深入再逐步"属性化"字段(从 raw 实时提取/映射)
|
||||
"""
|
||||
|
||||
mdy_rowid = models.CharField(
|
||||
@@ -161,7 +161,7 @@ class MDYPlateOrderStaging(ModelBase):
|
||||
verbose_name_plural = '明道云开版暂存'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
# 明道云字段:62d52f4b8d2972284492dd0e(开版表“订单id/设计编号”)
|
||||
# 明道云字段:62d52f4b8d2972284492dd0e(开版表"订单id/设计编号")
|
||||
# 用于在暂存表中按该字段快速检索
|
||||
models.Index(
|
||||
KeyTextTransform('62d52f4b8d2972284492dd0e', 'raw'),
|
||||
@@ -171,3 +171,69 @@ class MDYPlateOrderStaging(ModelBase):
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.mdy_rowid}'
|
||||
|
||||
|
||||
class ApiAuditLog(models.Model):
|
||||
"""API审计日志 - 记录创建操作的历史现场
|
||||
|
||||
用于保存特定模块的POST请求,记录"创建"操作的完整请求信息。
|
||||
通过Celery异步写入,避免影响API响应性能。
|
||||
"""
|
||||
url = models.CharField(
|
||||
max_length=500,
|
||||
verbose_name='请求URL',
|
||||
help_text='完整的请求路径'
|
||||
)
|
||||
method = models.CharField(
|
||||
max_length=10,
|
||||
verbose_name='请求方法',
|
||||
help_text='HTTP方法,如POST'
|
||||
)
|
||||
request_data = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name='请求体数据',
|
||||
help_text='POST请求的body数据(JSON格式)'
|
||||
)
|
||||
query_params = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name='查询参数',
|
||||
help_text='URL查询参数'
|
||||
)
|
||||
user_id = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
verbose_name='用户ID',
|
||||
help_text='操作用户的ID'
|
||||
)
|
||||
username = models.CharField(
|
||||
max_length=150,
|
||||
blank=True,
|
||||
default='',
|
||||
verbose_name='用户名',
|
||||
help_text='操作用户的用户名快照'
|
||||
)
|
||||
response_status = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name='响应状态码',
|
||||
help_text='HTTP响应状态码'
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
db_index=True,
|
||||
verbose_name='创建时间'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = 'api_audit_log'
|
||||
verbose_name = 'API审计日志'
|
||||
verbose_name_plural = 'API审计日志'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['user_id', 'created_at']),
|
||||
models.Index(fields=['url', 'created_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.method} {self.url} ({self.username or "anonymous"}) - {self.response_status}'
|
||||
|
||||
@@ -466,3 +466,42 @@ def sync_mdy_plate_orders(
|
||||
)
|
||||
payload["task_id"] = self.request.id
|
||||
return payload
|
||||
|
||||
|
||||
@shared_task
|
||||
def save_api_audit_log(
|
||||
url: str,
|
||||
method: str,
|
||||
request_data: dict,
|
||||
query_params: dict,
|
||||
user_id: int | None,
|
||||
username: str,
|
||||
response_status: int | None,
|
||||
):
|
||||
"""
|
||||
异步保存API审计日志。
|
||||
|
||||
通过Celery队列异步执行,避免阻塞API响应。
|
||||
|
||||
参数:
|
||||
url: 请求URL路径
|
||||
method: HTTP方法(POST等)
|
||||
request_data: 请求体数据(JSON格式)
|
||||
query_params: URL查询参数
|
||||
user_id: 操作用户ID
|
||||
username: 操作用户的用户名
|
||||
response_status: HTTP响应状态码
|
||||
"""
|
||||
try:
|
||||
api_models.ApiAuditLog.objects.create(
|
||||
url=url,
|
||||
method=method,
|
||||
request_data=request_data,
|
||||
query_params=query_params,
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
response_status=response_status,
|
||||
)
|
||||
logger.debug('API审计日志已保存: %s %s (user=%s, status=%s)', method, url, username, response_status)
|
||||
except Exception as e:
|
||||
logger.error('保存API审计日志失败: %s', str(e), exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user