El sistema se construye utilizando una arquitectura de separación de capas. En el backend, se emplea el framework SpringBoot para la creación de servicios RESTful, mientras que el frontend se desarrolla con Vue.js para una interfaz interactiva. La aplicación móvil se implementa mediante uniapp, garantizando compatibilidad mutliplataforma. La capa de persistencia de datos se gestiona con MyBatis-Plus, optimizando el acceso a la base de datos MySQL.
Componentes del backend: SpringBoot
SpringBoot simplifica la configuración del servidor al integrar incrustados como Tomcat. Su sistema de autoconfiguración adapta el proyecto según las dependencias incluidas, reduciendo el esfuerzo manual. El framework soporta módulos adicionales como Spring Data para abstracciones de repositorio y Spring Security para control de acceso, facilitando el desarrollo de aplicaciones robustas y extensibles.
Interfaz de usuario: Vue.js
Vue.js utiliza un DOM virtual para optimizar el renderizado, actualizando solo los componentes modificados. La reactividad de los datos sincroniza automáticamente la vista con el modelo subyacente, permitiendo a los desarrolladores concentrarse en la lógica de negocio sin manipular directamente el DOM. Este enfoque modular simplifica el mantenimiento y promueve la reutilización de componentes.
Acceso a datos: MyBatis-Plus
MyBatis-Plus extiende MyBatis con utilidades que minimizan la escritura de SQL manual. Proporciona anotaciones y API para operaciones CRUD comunes, incluyendo paginación dinámica y manejo de bloqueo optimista. Además, ofrece herramientas de generación de código para crear automáticamente entidades y mapeadores, acelerando el desarrollo de la capa de persistencia.
Validación de calidad del sistema
Las pruebas se centran en identificar defectos funcionales mediante técnicas de caja negra. Se diseñan casos de prueba que cubren escenarios típicos y límite, verificando que el sistema cumple con los requisitos especificados. El objetivo es asegurar una experiencia de usuario confiable antes del despliegue.
Pruebas de autenticación
Se evalúa el flujo de inicio de sesión, comprobando las respuestas del sistema ante credenciales válidas e inválidas. Por ejemplo, se verifica que contraseñas incorrectas o captchas erróneos generen mensajes de error apropiados, y que los campos obligatorios no puedan estar vacíos.
Gestión de usuarios
Las operaciones CRUD de usuarios se someten a pruebas exhaustivas. Se valida que la creación de un usuario con nombre duplicado sea rechazada, que la edición persista los cambios y que la eliminación requiera confirmación explícita. Las pruebas aseguran la integridad de los datos y la coherencia de la interfaz.
Fragmentos de código representativos
El siguiente ejemplo muestra un método de autenticación adaptado. Se han renombrado variables y ajustado la estructura para mayor claridad, manteniendo la funcionalidad original de generación de tokens JWT.
@AnotacionIgnorarAuth
@SolicitarMapeo(valor = "/iniciarSesion")
public Respuesta iniciarSesion(String nombreUsuario, String contrasena, String captcha, SolicitudHttp solicitud) {
EntidadUsuario usuario = servicioUsuario.obtenerUno(new EnvolturaEntidad<EntidadUsuario>().igual("nombreUsuario", nombreUsuario));
if(usuario==null || !usuario.getContrasena().equals(contrasena)) {
return Respuesta.error("Credenciales inválidas");
}
String tokenGenerado = servicioToken.crearToken(usuario.getId(), nombreUsuario, "usuarios", usuario.getRol());
return Respuesta.ok().poner("token", tokenGenerado);
}
@Override
public String crearToken(Long idUsuario, String nombreUsuario, String tabla, String rol) {
EntidadToken entidadToken = this.obtenerUno(new EnvolturaEntidad<EntidadToken>().igual("idUsuario", idUsuario).igual("rol", rol));
String valorToken = UtilComun.obtenerCadenaAleatoria(32);
Calendar calendario = Calendar.getInstance();
calendario.setTiempo(new Date());
calendario.agregar(Calendar.HORA_DEL_DIA, 1);
if(entidadToken!=null) {
entidadToken.setToken(valorToken);
entidadToken.setTiempoExpiracion(calendario.getTiempo());
this.actualizarPorId(entidadToken);
} else {
this.insertar(new EntidadToken(idUsuario, nombreUsuario, tabla, rol, valorToken, calendario.getTiempo()));
}
return valorToken;
}
El interceptor de autorización controla el acceso a endpoints protegidos, verificando el token en la cabecera de la solicitud.
@Componente
public class InterceptorAutorizacion implements ManejadorInterceptor {
public static final String CLAVE_TOKEN = "Token";
@Autoinyectado
private ServicioToken servicioToken;
@Override
public boolean preManejar(SolicitudHttp solicitud, RespuestaHttp respuesta, Object manejador) throws Exception {
respuesta.establecerCabecera("Access-Control-Allow-Origin", solicitud.obtenerCabecera("Origin"));
if (solicitud.obtenerMetodo().equals(MetodoSolicitud.OPTIONS.name())) {
respuesta.establecerEstado(HttpStatus.OK.valor());
return false;
}
AnotacionIgnorarAuth anotacion;
if (manejador instanceof MetodoManejador) {
anotacion = ((MetodoManejador) manejador).obtenerAnotacionMetodo(AnotacionIgnorarAuth.class);
} else {
return true;
}
String token = solicitud.obtenerCabecera(CLAVE_TOKEN);
if(anotacion!=null) {
return true;
}
EntidadToken entidadToken = null;
if(StringUtils.isNotBlank(token)) {
entidadToken = servicioToken.obtenerEntidadToken(token);
}
if(entidadToken != null) {
solicitud.obtenerSesion().establecerAtributo("idUsuario", entidadToken.getIdUsuario());
solicitud.obtenerSesion().establecerAtributo("rol", entidadToken.getRol());
solicitud.obtenerSesion().establecerAtributo("tabla", entidadToken.getTabla());
solicitud.obtenerSesion().establecerAtributo("nombreUsuario", entidadToken.getNombreUsuario());
return true;
}
Escritor escritor = null;
respuesta.establecerCodificacionCaracter("UTF-8");
respuesta.establecerTipoContenido("application/json; charset=utf-8");
try {
escritor = respuesta.obtenerEscritor();
escritor.imprimir(JSONObject.toJSONString(Respuesta.error(401, "Autenticación requerida")));
} finally {
if(escritor != null) {
escritor.cerrar();
}
}
return false;
}
}
Estructura de base de datos
La tabla de sesiones almacena tokens de autenticación con tiempo de expiración. El esquema siguiente define la estructura optimizada para consultas frecuentes.
-- Eliminar tabla existente si aplica
TABLA SI EXISTE `sesiones`;
CREAR TABLA `sesiones` (
`id` bigint(20) NO NULO AUTO_INCREMENT COMENTARIO 'Clave primaria',
`idUsuario` bigint(20) NO NULO COMENTARIO 'Identificador de usuario',
`nombreUsuario` varchar(100) NO NULO COMENTARIO 'Nombre de usuario',
`tablaEntidad` varchar(100) NULO POR DEFECTO COMENTARIO 'Tabla asociada',
`rol` varchar(100) NULO POR DEFECTO COMENTARIO 'Rol del usuario',
`token` varchar(200) NO NULO COMENTARIO 'Valor del token',
`fechaCreacion` marca_tiempo NO NULO POR DEFECTO TIMESTAMP_ACTUAL COMENTARIO 'Fecha de creación',
`fechaExpiracion` marca_tiempo NO NULO POR DEFECTO '0000-00-00 00:00:00' COMENTARIO 'Fecha de expiración',
PRIMARIA (`id`) USANDO BTREE
) MOTOR=InnoDB AUTO_INCREMENT=27 CODIFICACION=utf8 FORMATO_FILA=COMPACTO COMENTARIO='Tabla de sesiones';
-- Registros de ejemplo
INSERTAR EN `sesiones` VALORES ('9', '23', 'usuario1', 'clientes', 'cliente', 'al6svx5qkei1wljry5o1npswhdpqcpcg', '2023-02-23 21:46:45', '2023-03-15 14:01:36');
INSERTAR EN `sesiones` VALORES ('10', '11', 'usuario2', 'clientes', 'cliente', 'fahmrd9bkhqy04sq0fzrl4h9m86cu6kx', '2023-02-27 18:33:52', '2023-03-17 18:27:42');
INSERTAR EN `sesiones` VALORES ('11', '17', 'admin01', 'administradores', 'administrador', 'u5km44scxvzuv5yumdah2lhva0gp4393', '2023-02-27 18:46:19', '2023-02-27 19:48:58');
INSERTAR EN `sesiones` VALORES ('12', '1', 'superadmin', 'usuarios', 'administrador', 'h1pqzsb9bldh93m92j9m2sljy9bt1wdh', '2023-02-27 19:37:01', '2023-03-17 18:23:02');