Commit 0e62dbdbee3d68f508140ae0b1fe85b185429e2d
1 parent
56bd3ec0
Hito: Consolidación Multi-Tenant (Texto Puro y Blindaje SQL)
Showing
4 changed files
with
31 additions
and
17 deletions
VERSION.txt
| ... | ... | @@ -12,3 +12,4 @@ PROYECTO GIS-GEOSERVER - 2026.04.07.08.18.07 ID DOCKER: d983a409769d. Observacià |
| 12 | 12 | PROYECTO GIS-GEOSERVER - 2026.04.07.20.37.47 ID DOCKER: d983a409769d. Observación: Modernización definitiva del Dashboard completada: Replicación de bloque de bienvenida legado, integración de mapa embebido en modo limpio y reestructuración de la fila inferior de gestión al 100% de ancho. Versión estable validada.Version SIG (Abril 2026) - 2026.04.07.22.30.00 ID DOCKER: d983a409769d. Observación: Éxito en implementación de Login Dinámico (SaaS), integración de logos binarios, eslóganes y responsables desde el servidor .254. |
| 13 | 13 | Version SIG (Abril 2026) - 2026.04.07.23.35.00 ID DOCKER: cb7329596324. Observación: Éxito en la implementación de EstadÃsticas Reales (formato vertical fdw_X.estadisticas_datos) y actualización forzada de FDW desde el menú Administración. |
| 14 | 14 | Version SIG (Abril 2026) - 2026.04.08.06.45.00 ID DOCKER: cb7329596324. Observación: Inicio de migración a Nomenclatura Alfanumérica Multi-Tenant (e0505_lotes). |
| 15 | +Version SIG (Abril 2026) - 2026.04.08.08.15.00 ID DOCKER: cb7329596324. Observación: Consolidación de Arquitectura Multi-Tenant basada en Texto Puro (FDW-TRIM). Blindaje de consultas SQL con filtro de entidad y estandarización alfanumérica de tablas (eXXX_lotes) y vistas (vw_..._XXX). Conexión JOIN optimizada por CCC. | ... | ... |
src/main/java/com/sigem/gis/security/AuthController.java
| ... | ... | @@ -45,12 +45,15 @@ public class AuthController { |
| 45 | 45 | @PostMapping("/login") |
| 46 | 46 | public ResponseEntity<?> login(@RequestBody AuthRequest request) { |
| 47 | 47 | try { |
| 48 | + // Normalización: Únicamente TRIM para respetar el valor de texto original | |
| 49 | + String entidadIdDropdown = request.getEntidad() != null ? request.getEntidad().trim() : ""; | |
| 50 | + | |
| 48 | 51 | // 1. Validar existencia de entidad en directorio maestro (en el .254) |
| 49 | 52 | String sqlEntidades = "SELECT sigem_site, sigem_dbname, lat, lng, zoom, minzoom, maxzoom, mapa_base, boundno, boundse, nombre, eslogan, entidad_logo, responsable FROM public.entidades WHERE activo= TRUE AND entidad = ?"; |
| 50 | - List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(request.getEntidad())); | |
| 53 | + List<Map<String, Object>> entidades = masterJdbcTemplate.queryForList(sqlEntidades, Integer.parseInt(entidadIdDropdown)); | |
| 51 | 54 | |
| 52 | 55 | if (entidades.isEmpty()) { |
| 53 | - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "La MUNICIPALIDAD con código " + request.getEntidad() + " no existe o está inactiva.")); | |
| 56 | + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new AuthResponse(null, null, "La MUNICIPALIDAD con código " + entidadIdDropdown + " no existe o está inactiva.")); | |
| 54 | 57 | } |
| 55 | 58 | |
| 56 | 59 | Map<String, Object> entidadData = entidades.get(0); |
| ... | ... | @@ -63,11 +66,10 @@ public class AuthController { |
| 63 | 66 | |
| 64 | 67 | String responsable = convertObjectToString(entidadData.get("responsable")); |
| 65 | 68 | |
| 66 | - // 2. Asegurar Infraestructura FDW (Regla 16: Solo crea si no existe) | |
| 67 | - fdwService.setupFdw(request.getEntidad(), false); | |
| 68 | - String schemaName = "fdw_" + request.getEntidad(); | |
| 69 | + // 2. Se asume infraestructura FDW básica (Regla 16) | |
| 70 | + String schemaName = "fdw_" + entidadIdDropdown; | |
| 69 | 71 | |
| 70 | - System.out.println("Validando usuario vía FDW local para entidad: " + request.getEntidad()); | |
| 72 | + System.out.println("Validando usuario vía FDW local para entidad: " + entidadIdDropdown); | |
| 71 | 73 | |
| 72 | 74 | // 3. Buscar Usuario en el esquema FDW local (en el .123) |
| 73 | 75 | String sqlUser = "SELECT usu_nom, usu_ape, activo, entidad, " + |
| ... | ... | @@ -84,15 +86,22 @@ public class AuthController { |
| 84 | 86 | |
| 85 | 87 | // 4. Validación Final y Generación de Token |
| 86 | 88 | if (isActivo && request.getPassword().equals(claveDesencriptada)) { |
| 87 | - // Extraer la Entidad oficial desde el FDW ( character varying 8 ) | |
| 89 | + // Extraer la Entidad oficial desde el FDW respetando el texto original - Aplicar TRIM() | |
| 88 | 90 | String entidadOficial = convertObjectToString(userData.get("entidad")); |
| 89 | - if (entidadOficial == null || entidadOficial.trim().isEmpty()) { | |
| 90 | - entidadOficial = request.getEntidad(); // Fallback si el campo está vacío, aunque no debería | |
| 91 | + if (entidadOficial != null) { | |
| 92 | + entidadOficial = entidadOficial.trim(); | |
| 93 | + } | |
| 94 | + | |
| 95 | + if (entidadOficial == null || entidadOficial.isEmpty()) { | |
| 96 | + entidadOficial = entidadIdDropdown; | |
| 91 | 97 | } |
| 92 | 98 | |
| 93 | 99 | String token = jwtUtil.generateToken(request.getUsername(), entidadOficial); |
| 94 | 100 | String nombreCompleto = convertObjectToString(userData.get("usu_nom")) + " " + convertObjectToString(userData.get("usu_ape")); |
| 95 | 101 | |
| 102 | + // 3. ASEGURAR INFRAESTRUCTURA CON IDENTIDAD OFICIAL (Alfanumérica) | |
| 103 | + fdwService.setupFdw(entidadOficial, false); | |
| 104 | + | |
| 96 | 105 | // Metadatos georreferenciados de la entidad |
| 97 | 106 | Double lat = parseDouble(entidadData.get("lat"), -25.456443); |
| 98 | 107 | Double lng = parseDouble(entidadData.get("lng"), -56.446949); | ... | ... |
src/main/java/com/sigem/gis/security/AuthResponse.java
| ... | ... | @@ -17,6 +17,7 @@ public class AuthResponse { |
| 17 | 17 | private String eslogan; |
| 18 | 18 | private String entidadLogo; |
| 19 | 19 | private String responsable; |
| 20 | + private String entidad; | |
| 20 | 21 | |
| 21 | 22 | public AuthResponse(String token, String nombre, String message) { |
| 22 | 23 | this.token = token; |
| ... | ... | @@ -37,12 +38,13 @@ public class AuthResponse { |
| 37 | 38 | this.bounds = bounds; |
| 38 | 39 | } |
| 39 | 40 | |
| 40 | - public AuthResponse(String token, String nombre, String message, Double lat, Double lng, Integer zoom, Integer minZoom, Integer maxZoom, String mapaBase, String bounds, String entidadNombre, String eslogan, String entidadLogo, String responsable) { | |
| 41 | + public AuthResponse(String token, String nombre, String message, Double lat, Double lng, Integer zoom, Integer minZoom, Integer maxZoom, String mapaBase, String bounds, String entidadNombre, String eslogan, String entidadLogo, String responsable, String entidad) { | |
| 41 | 42 | this(token, nombre, message, lat, lng, zoom, minZoom, maxZoom, mapaBase, bounds); |
| 42 | 43 | this.entidadNombre = entidadNombre; |
| 43 | 44 | this.eslogan = eslogan; |
| 44 | 45 | this.entidadLogo = entidadLogo; |
| 45 | 46 | this.responsable = responsable; |
| 47 | + this.entidad = entidad; | |
| 46 | 48 | } |
| 47 | 49 | |
| 48 | 50 | // Getters y Setters |
| ... | ... | @@ -60,4 +62,5 @@ public class AuthResponse { |
| 60 | 62 | public String getEslogan() { return eslogan; } |
| 61 | 63 | public String getEntidadLogo() { return entidadLogo; } |
| 62 | 64 | public String getResponsable() { return responsable; } |
| 65 | + public String getEntidad() { return entidad; } | |
| 63 | 66 | } | ... | ... |
src/main/java/com/sigem/gis/service/FdwService.java
| ... | ... | @@ -57,15 +57,16 @@ public class FdwService { |
| 57 | 57 | // ... |
| 58 | 58 | // Regla Multi-Tenant: Verificar presencia de las 5 tablas críticas |
| 59 | 59 | String checkSql = "SELECT count(*) FROM information_schema.tables " + |
| 60 | - "WHERE table_schema = ? AND table_name IN " + | |
| 61 | - "('usuarios', 'estadisticas_datos', 'v_liq_entidad_percentiles', 'v_liq_entidad_totalxobjeto', 'ventanas_usuario')"; | |
| 62 | - | |
| 60 | + "WHERE table_schema = ? AND table_name IN " + | |
| 61 | + "('usuarios', 'estadisticas_datos', 'v_liq_entidad_percentiles', 'v_liq_entidad_totalxobjeto', 'ventanas_usuario')"; | |
| 62 | + | |
| 63 | 63 | Integer count = (forceUpdate) ? 0 : gisJdbcTemplate.queryForObject(checkSql, Integer.class, schemaName); |
| 64 | - | |
| 64 | + | |
| 65 | 65 | // Si falta alguna de las 5 tablas o se solicita actualización forzada |
| 66 | 66 | if (forceUpdate || count == null || count < 5) { |
| 67 | 67 | if (count != null && count > 0 && count < 5) { |
| 68 | - System.out.println("Infraestructura incompleta para " + entidadId + " (" + count + "/5 tablas). Forzando recreación..."); | |
| 68 | + System.out.println("Infraestructura incompleta para " + entidadId + " (" + count | |
| 69 | + + "/5 tablas). Forzando recreación..."); | |
| 69 | 70 | } |
| 70 | 71 | // (creación de server, user mapping y esquema igual) |
| 71 | 72 | gisJdbcTemplate.execute("DROP SERVER IF EXISTS " + serverName + " CASCADE"); |
| ... | ... | @@ -90,7 +91,7 @@ public class FdwService { |
| 90 | 91 | "CREATE OR REPLACE VIEW public.%s AS " + |
| 91 | 92 | "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + |
| 92 | 93 | "FROM %s l " + |
| 93 | - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral AND l.entidad::text = m.entidad::text", | |
| 94 | + "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral", | |
| 94 | 95 | viewLotesName, tableLotes, schemaName)); |
| 95 | 96 | |
| 96 | 97 | // Vista PNG FULL (WMS) - SIN LIMIT |
| ... | ... | @@ -99,7 +100,7 @@ public class FdwService { |
| 99 | 100 | "CREATE OR REPLACE VIEW public.%s AS " + |
| 100 | 101 | "SELECT l.*, m.inm_ficha, m.inm_ctacatastral, m.trb_total_deuda, m.trb_total_pago, m.ultimo_pago " + |
| 101 | 102 | "FROM %s l " + |
| 102 | - "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral AND l.entidad::text = m.entidad::text", | |
| 103 | + "LEFT JOIN %s.v_liq_entidad_totalxobjeto m ON l.ccc = m.inm_ctacatastral", | |
| 103 | 104 | viewWmsName, tableLotes, schemaName)); |
| 104 | 105 | |
| 105 | 106 | // 4. Sincronización con GeoServer | ... | ... |