Technology Stack Overview
The smart elderly care platform adopts a separated frontend and backend architecture. The backend leverages Spring Boot as the core framework, which simplifies application configuration through its auto-configuration mechanism. It includes embedded servers like Tomcat, eliminating the need for external server deployment. Spring Boot integrates seamlessly with Spring Data, Spring Security, and Spring Cloud, providing extensibility for enterprise-level applications.
The frontend utilizes Vue.js, which employs a reactive data binding system combined with a virtual DOM. When data changes occur, the view updates automatically, allowing developers to focus on business logic rather than manual DOM manipulation. The component-based architecture promotes code reusability and maintainability.
For data persistence, MyBatis-Plus serves as an enhancement to the traditional MyBatis framework. It provides a code generator for creating entity classes, mapper interfaces, and XML mapping files. Additional features include pagination queries, dynamic query construction, and optimistic locking support.
Authentication and Token Management Implementation
The platform implements a token-based authentication system. When users log in, the system validates credentials and generates a unique token for subsequent requests.
Login Controller
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private SysUserService sysUserService;
@Autowired
private AccessTokenService accessTokenService;
@SkipAuthCheck
@PostMapping("/signin")
public ResponseEntity<ApiResponse> authenticateUser(
@RequestParam String account,
@RequestParam String secret,
@RequestParam(required = false) String verifyCode) {
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.eq("account", account);
SysUser currentUser = sysUserService.getOne(wrapper);
if (currentUser == null || !currentUser.getSecret().equals(secret)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.fail("Invalid credentials"));
}
String accessToken = accessTokenService.createAccessToken(
currentUser.getId(),
account,
"sys_user",
currentUser.getRoleType()
);
return ResponseEntity.ok(ApiResponse.success().data("accessToken", accessToken));
}
}Token Generation Service
@Service
public class AccessTokenService {
@Autowired
private AccessTokenDao accessTokenDao;
private static final int TOKEN_VALIDITY_HOURS = 24;
public String createAccessToken(Long uid, String account, String entityName, String roleType) {
QueryWrapper<AccessToken> query = new QueryWrapper<>();
query.eq("uid", uid).eq("role_type", roleType);
AccessToken existingToken = accessTokenDao.selectOne(query);
String newTokenValue = UUID.randomUUID().toString().replace("-", "");
Calendar expiryTime = Calendar.getInstance();
expiryTime.add(Calendar.HOUR, TOKEN_VALIDITY_HOURS);
if (existingToken != null) {
existingToken.setTokenValue(newTokenValue);
existingToken.setExpireTime(expiryTime.getTime());
accessTokenDao.updateById(existingToken);
} else {
AccessToken freshToken = new AccessToken();
freshToken.setUid(uid);
freshToken.setAccount(account);
freshToken.setEntityName(entityName);
freshToken.setRoleType(roleType);
freshToken.setTokenValue(newTokenValue);
freshToken.setExpireTime(expiryTime.getTime());
accessTokenDao.insert(freshToken);
}
return newTokenValue;
}
public AccessToken fetchTokenEntity(String tokenValue) {
QueryWrapper<AccessToken> query = new QueryWrapper<>();
query.eq("token_value", tokenValue);
AccessToken token = accessTokenDao.selectOne(query);
if (token != null && token.getExpireTime().after(new Date())) {
return token;
}
return null;
}
}Authorization Interceptor
@Component
public class AuthInterceptor implements HandlerInterceptor {
private static final String HEADER_TOKEN_KEY = "X-Access-Token";
@Autowired
private AccessTokenService accessTokenService;
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Headers",
"Content-Type, X-Access-Token, Origin, Accept, Authorization");
res.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
if (req.getMethod().equalsIgnoreCase("OPTIONS")) {
res.setStatus(HttpServletResponse.SC_OK);
return false;
}
SkipAuthCheck skipAnnotation = null;
if (handler instanceof HandlerMethod) {
skipAnnotation = ((HandlerMethod) handler).getMethodAnnotation(SkipAuthCheck.class);
} else {
return true;
}
if (skipAnnotation != null) {
return true;
}
String tokenFromHeader = req.getHeader(HEADER_TOKEN_KEY);
AccessToken tokenEntity = null;
if (StringUtils.hasText(tokenFromHeader)) {
tokenEntity = accessTokenService.fetchTokenEntity(tokenFromHeader);
}
if (tokenEntity != null) {
req.getSession().setAttribute("currentUid", tokenEntity.getUid());
req.getSession().setAttribute("currentRole", tokenEntity.getRoleType());
req.getSession().setAttribute("currentAccount", tokenEntity.getAccount());
return true;
}
res.setContentType("application/json;charset=UTF-8");
res.getWriter().write(JSONObject.toJSONString(ApiResponse.fail(401, "Authentication required")));
return false;
}
}Database Schema Design
The token management table stores session information for authenticated users.
-- Access Token Table Structure
DROP TABLE IF EXISTS `access_token`;
CREATE TABLE `access_token` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`uid` BIGINT(20) NOT NULL COMMENT 'User ID',
`account` VARCHAR(100) NOT NULL COMMENT 'Account Name',
`entity_name` VARCHAR(100) DEFAULT NULL COMMENT 'Entity Table',
`role_type` VARCHAR(100) DEFAULT NULL COMMENT 'User Role',
`token_value` VARCHAR(200) NOT NULL COMMENT 'Token String',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time',
`expire_time` TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:01' COMMENT 'Expiration Time',
PRIMARY KEY (`id`),
INDEX `idx_uid_role` (`uid`, `role_type`),
INDEX `idx_token` (`token_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Access Token Storage';
-- Sample Data
INSERT INTO `access_token` VALUES
(1, 1, 'admin', 'sys_user', 'administrator', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', NOW(), DATE_ADD(NOW(), INTERVAL 24 HOUR)),
(2, 15, 'nurse_wang', 'staff', 'caregiver', 'q1w2e3r4t5y6u7i8o9p0a1s2d3f4g5h6', NOW(), DATE_ADD(NOW(), INTERVAL 24 HOUR)),
(3, 28, 'elder_zhang', 'elderly', 'senior', 'z9x8c7v6b5n4m3l2k1j0h9g8f7d6s5a4', NOW(), DATE_ADD(NOW(), INTERVAL 24 HOUR));System Testing Methodology
The testing process employs black-box testing techniques to validate system functionality from an end-user perspective. Test cases are designed to cover normal operations, boundary conditions, and error handling scenarios.
Login Functionality Test Cases
| Test Input | Expected Outcome | Actual Result | Status |
|---|---|---|---|
| Account: admin, Password: correct_pwd, Captcha: valid | Login successful | Token generated, redirect to dashboard | Pass |
| Account: admin, Password: wrong_pwd, Captcha: valid | Authentication failed | Error: Invalid credentials | Pass |
| Account: admin, Password: correct_pwd, Captcha: invalid | Captcha verification failed | Error: Captcha mismatch | Pass |
| Account: empty, Password: any, Captcha: any | Validation error | Error: Account required | Pass |
| Account: admin, Password: empty, Captcha: any | Validation error | Error: Password required | Pass |
User Management Test Cases
| Operation | Test Data | Expected Result | Status |
|---|---|---|---|
| Create User | Valid user data with all required fields | User created, appears in list | Pass |
| Create User | Missing required field (username) | Error: Username cannot be empty | Pass |
| Create User | Duplicate username | Error: Username already exists | Pass |
| Update User | Modified user details | Changes saved successfully | Pass |
| Delete User | Select user for deletion | Confirmation prompt, user removed after confirm | Pass |
Testing validates that the authentication flow handles various input scenarios correctly, including invalid credentials, missing fields, and expired tokens. The interceptor properly enforces access control for protected endpoints while allowing public routes to function without authentication.