using System.Security.Claims; using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Service.S8; using Microsoft.Extensions.Logging; namespace Admin.NET.Plugin.AiDOP.Controllers.S8; [ApiController] [Route("api/aidop/s8/config/watch-rules")] [NonUnify] public class AdoS8ConfigWatchRulesController : ControllerBase { private const string LegacyHeaderUseInstead = "PUT /api/aidop/s8/config/watch-rules/{id}/params"; private readonly S8WatchRuleService _svc; private readonly ILogger _logger; public AdoS8ConfigWatchRulesController( S8WatchRuleService svc, ILogger logger) { _svc = svc; _logger = logger; } [HttpGet] public async Task ListAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) => Ok(await _svc.ListAsync(tenantId, factoryId)); /// /// Legacy endpoint. Rule configuration UI must use PUT /{id}/params. /// 保留兼容历史脚本/集成调用,但响应 header 与日志会标记为 deprecated。 /// [Obsolete("Legacy full-create endpoint. Use PUT /api/aidop/s8/config/watch-rules/{id}/params for safe params editing.")] [HttpPost] public async Task CreateAsync([FromBody] AdoS8WatchRule body) { MarkLegacyDeprecated("POST /api/aidop/s8/config/watch-rules"); try { return Ok(await _svc.CreateAsync(body)); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } /// /// Legacy endpoint. Rule configuration UI must use PUT /{id}/params. /// 保留兼容历史脚本/集成调用,但响应 header 与日志会标记为 deprecated。 /// [Obsolete("Legacy full-update endpoint. Use PUT /api/aidop/s8/config/watch-rules/{id}/params for safe params editing.")] [HttpPut("{id:long}")] public async Task UpdateAsync(long id, [FromBody] AdoS8WatchRule body) { MarkLegacyDeprecated($"PUT /api/aidop/s8/config/watch-rules/{id}", id); try { return Ok(await _svc.UpdateAsync(id, body)); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } /// /// Legacy endpoint. Rule configuration UI must use PUT /{id}/params. /// 保留兼容历史脚本/集成调用,但响应 header 与日志会标记为 deprecated。 /// [Obsolete("Legacy delete endpoint. Use PUT /api/aidop/s8/config/watch-rules/{id}/params for safe params editing.")] [HttpDelete("{id:long}")] public async Task DeleteAsync(long id) { MarkLegacyDeprecated($"DELETE /api/aidop/s8/config/watch-rules/{id}", id); await _svc.DeleteAsync(id); return Ok(); } /// /// Legacy endpoint. Rule configuration UI must use PUT /{id}/params. /// 保留兼容历史脚本/集成调用,但响应 header 与日志会标记为 deprecated。 /// [Obsolete("Legacy test endpoint. Use PUT /api/aidop/s8/config/watch-rules/{id}/params for safe params editing.")] [HttpPost("{id:long}/test")] public async Task TestAsync(long id) { MarkLegacyDeprecated($"POST /api/aidop/s8/config/watch-rules/{id}/test", id); try { return Ok(await _svc.TestAsync(id)); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } /// /// R4 安全更新:仅修改 params_json 与 enabled,绝不接受 expression / rule_code / data_source_id / /// scene_code / watch_object_type / rule_type / source_object_type 等敏感字段。 /// 服务端按 rule_type 用对应 evaluator 的 Params.Parse 进行 schema 校验。 /// [HttpPut("{id:long}/params")] public async Task UpdateParamsAsync(long id, [FromBody] S8WatchRuleParamsPayload body) { try { return Ok(await _svc.UpdateParamsAsync(id, body)); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } /// /// 旧端点 deprecated 收口:写入响应 header 标记 + 结构化 warning 日志,便于运行期识别 legacy 调用。 /// 不阻断调用,不改返回结构。 /// private void MarkLegacyDeprecated(string endpoint, long? ruleId = null) { // 响应 header — Headers 在响应已开始发送后会抛 InvalidOperationException,此处守卫一下避免污染主调用。 var headers = Response.Headers; if (!headers.ContainsKey("X-AiDOP-Deprecated")) headers["X-AiDOP-Deprecated"] = "true"; if (!headers.ContainsKey("X-AiDOP-Use-Instead")) headers["X-AiDOP-Use-Instead"] = LegacyHeaderUseInstead; var userId = User?.FindFirst("UserId")?.Value ?? User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var tenantId = HttpContext?.Request?.Query["tenantId"].ToString(); var factoryId = HttpContext?.Request?.Query["factoryId"].ToString(); _logger.LogWarning( "legacy_watch_rule_endpoint endpoint={Endpoint} ruleId={RuleId} userId={UserId} tenantId={TenantId} factoryId={FactoryId} useInstead={UseInstead}", endpoint, ruleId, userId, tenantId, factoryId, LegacyHeaderUseInstead); } }