Este artículo detalla la implementación de un sistema de apoyo a la decisión empresarial utilizando las tecnologías Spring Boot para el backend y Vue.js para el frontend. Se abordan las tecnologías clave, el proceso de testing y ejemplos de código y estructura de base de datos.
Tecnologías Utilizadas
Backend: Spring Boot
Spring Boot simplifica el desarrollo de aplicaciones Java al proporcionar configuración automática y embeber servidores como Tomcat o Jetty, eliminando la necesidad de configuraciones externas. Su característica principal es la auto-configuración, que detecta dependencias y configura la aplicación automáticamente. Ofrece un ecosistema robusto con módulos como Spring Data, Spring Security y Spring Cloud, acelerando la construcción y escalabilidad de aplicaciones.
Frontend: Vue.js
Vue.js es un framework progresivo de JavaScript que destaca por su uso de Virtual DOM para optimizar las actualizaciones de la interfaz de usuario. Emplea data binding reactivo y un enfoque basado en componentes, permitiendo a los desarrolladores centrarse en la lógica de datos en lugar de la manipulación manual del DOM. Esto resulta en un desarrollo más ágil, flexible y mantenible.
Capa de Persistencia: MyBatis-Plus
MyBatis-Plus es una extensión de MyBatis diseñada para simplificar el desarrollo ORM en Java. Soporta múltiples bases de datos y proporciona una API rica y anotaciones para reducir la escritura de SQL. Incluye un generador de código que automatiza la creación de entidades, Mappers y archivos de mapeo XML. Además, ofrece funcionalidades avanzadas como paginación, consultas dinámicas y manejo de concurrencia (optimistic locking), mejorando significativamente la eficiencia en el desarrollo de la capa de acceso a datos.
Pruebas del Sistema
Objetivos de las Pruebas
Las pruebas del sistema son cruciales para asegurar la calidad y fiabilidad del producto final. El objetivo principal es identificar y corregir defectos para garantizar que el sistema cumpla con los requisitos del cliente y ofrezca una experiencia de usuario óptima. Se busca validar la funcionalidad, la lógica y la robustez del sistema bajo diversas condiciones simuladas.
Pruebas de Funcionalidad
Se emplean técnicas de caja negra para probar los diferentes módulos del sistema, incluyendo la validación de entradas, casos de borde y campos obligatorios. Se diseñan casos de prueba para verificar funcionalidades específicas.
Plan de Pruebas: Inicio de Sesión
Esta prueba valida el proceso de autenticación del usuario. Se verifica que el sistema maneje correctamente las credenciales válidas e inválidas, así como los códigos de verificación. También se comprueba la restricción de acceso basada en roles.
| Datos de Entrada | Resultado Esperado | Resultado Real | Análisis |
|---|---|---|---|
| Usuario: admin, Contraseña: 123456, Captcha: Correcto | Acceso al sistema | Acceso exitoso | Coincide con la expectativa |
| Usuario: admin, Contraseña: 111111, Captcha: Correcto | Error de contraseña | Contraseña incorrecta, reintente | Coincide con la expectativa |
| Usuario: admin, Contraseña: 123456, Captcha: Incorrecto | Error de Captcha | Captcha inválido | Coincide con la expectativa |
| Usuario: (vacío), Contraseña: 123456, Captcha: Correcto | Campo de usuario obligatorio | Ingrese el nombre de usuario | Coincide con la expectativa |
| Usuario: admin, Contraseña: (vacío), Captcha: Correcto | Campo de contraseña obligatorio | Ingrese la contraseña | Coincide con la expectativa |
Plan de Pruebas: Gestión de Usuarios
Esta prueba cubre las operaciones de añadir, editar, eliminar y buscar usuarios. Se verifica la validación de campos obligatorios, la detección de nombres de usuario duplicados, las confirmaciones de eliminación y la correcta visualización de la información actualizada.
| Datos de Entrada | Resultado Esperado | Resultado Real | Análisis |
|---|---|---|---|
| Información básica de usuario válida | Usuario añadido exitosamente, aparece en la lista | Usuario aparece en la lista | Coincide con la expectativa |
| Modificación de información de usuario | Edición exitosa, información actualizada en la vista | Información del usuario modificada | Coincide con la expectativa |
| Seleccionar usuario para eliminar y confirmar | Confirmación de eliminación, usuario no encontrado tras la acción | Confirmación de eliminación, usuario eliminado | Coincide con la expectativa |
| Intentar añadir usuario sin nombre de usuario | Error: nombre de usuario no puede estar vacío | Error: nombre de usuario no puede estar vacío | Coincide con la expectativa |
| Intentar añadir usuario con nombre de usuario existente | Error: nombre de usuario ya existe | Error: nombre de usuario ya existe | Coincide con la expectativa |
Conclusión de las Pruebas del Sistema
Mediante la aplicación de pruebas de caja negra y la creación de casos de prueba, se ha validado la corrección de los flujos y funcionalidades del sistema. Las pruebas son esenciales para refinar el sistema y mejorar su usabilidad. El objetivo final de las pruebas es asegurar que el sistema satisfaga las necesidades del usuario y cumpla con los objetivos de diseño, tento en funcionalidad como en rendimiento.
Ejemplos de Código
Controlador de Autenticación (Fragmento)
@IgnoreAuth
@PostMapping(value = "/login")
public R authenticateUser(String username, String password, String captcha, HttpServletRequest request) {
// Busca al usuario por nombre de usuario
UsersEntity user = userService.getOne(new QueryWrapper<UsersEntity>().eq("username", username));
// Verifica si el usuario existe y la contraseña es correcta
if (user == null || !user.getPassword().equals(password)) {
return R.error("Nombre de usuario o contraseña incorrectos");
}
// Genera un token de autenticación
String token = tokenService.generateAuthToken(user.getId(), username, "users", user.getRole());
return R.ok().put("token", token);
}
@Override
public String generateAuthToken(Long userId, String username, String tableName, String role) {
TokenEntity existingToken = tokenService.getToken(userId, role);
String newToken = generateRandomString(32); // Función auxiliar para generar cadena aleatoria
// Calcula la fecha de expiración (1 hora desde ahora)
Calendar expiration = Calendar.getInstance();
expiration.add(Calendar.HOUR_OF_DAY, 1);
if (existingToken != null) {
// Actualiza el token existente
existingToken.setToken(newToken);
existingToken.setExpirationTime(expiration.getTime());
tokenService.updateToken(existingToken);
} else {
// Crea un nuevo token
tokenService.saveToken(new TokenEntity(userId, username, tableName, role, newToken, expiration.getTime()));
}
return newToken;
}
Interceptor de Autorización (Fragmento)
@Component
public class AuthInterceptor implements HandlerInterceptor {
private static final String AUTH_TOKEN_HEADER = "Token";
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Configuración de CORS
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,request-source,Token, Origin,imgType, Content-Type, cache-control,postman-token,Cookie, Accept,authorization");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
// Manejo de peticiones OPTIONS para CORS
if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
response.setStatus(HttpStatus.OK.value());
return false;
}
// Ignorar autenticación si el método está anotado con @IgnoreAuth
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (handlerMethod.getMethodAnnotation(IgnoreAuth.class) != null) {
return true;
}
// Obtener token de la cabecera
String tokenValue = request.getHeader(AUTH_TOKEN_HEADER);
TokenEntity tokenDetails = null;
if (StringUtils.isNotBlank(tokenValue)) {
tokenDetails = tokenService.getTokenDetails(tokenValue);
}
// Validar token y establecer atributos de sesión
if (tokenDetails != null) {
request.getSession().setAttribute("userId", tokenDetails.getUserId());
request.getSession().setAttribute("role", tokenDetails.getRole());
request.getSession().setAttribute("tableName", tokenDetails.getTableName());
request.getSession().setAttribute("username", tokenDetails.getUsername());
return true;
}
// Si el token no es válido o no existe, devolver error 401
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(JSONObject.toJSONString(R.error(401, "Por favor, inicie sesión primero")));
if (writer != null) {
writer.close();
}
return false;
}
}
Estructura de la Base de Datos
Tabla: token
Esta tabla almacena la información de los tokens de autenticación para gestionar las sesiones de usuario.
-- Table structure for token
DROP TABLE IF EXISTS `token`;
CREATE TABLE `token` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`user_id` bigint(20) NOT NULL COMMENT 'User ID',
`username` varchar(100) NOT NULL COMMENT 'Username',
`table_name` varchar(100) DEFAULT NULL COMMENT 'Associated Table Name',
`role` varchar(100) DEFAULT NULL COMMENT 'User Role',
`token_value` varchar(200) NOT NULL COMMENT 'Authentication Token',
`creation_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Timestamp',
`expiration_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Expiration Timestamp',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='Token table';
-- Sample Records (truncated for brevity)
INSERT INTO `token` VALUES ('9', '23', 'cd01', 'xuesheng', 'student', 'al6svx5qkei1wljry5o1npswhdpqcpcg', '2023-02-23 21:46:45', '2023-03-15 14:01:36');
INSERT INTO `token` VALUES ('10', '11', 'xh01', 'xuesheng', 'student', 'fahmrd9bkhqy04sq0fzrl4h9m86cu6kx', '2023-02-27 18:33:52', '2023-03-17 18:27:42');
INSERT INTO `token` VALUES ('12', '1', 'admin', 'users', 'administrator', 'h1pqzsb9bldh93m92j9m2sljy9bt1wdh', '2023-02-27 19:37:01', '2023-03-17 18:23:02');
-- ... more records