Technical Architecture Overview
The system utilizes a decoupled architecture comprising a Spring Boot backend, a Vue.js-based administrative frontend, and a UniApp-based WeChat Mini Program client. This separation ensures maintainability and scalability across different platforms.
Backend Framework: Spring Boot
Spring Boot serves as the core backend engine, eliminating the need for complex XML configurations through its convention-over-configuration approach. By embedding servers like Tomcat, it simplifies deployment. The framework's auto-configuration mechanism detects project dependencies to set up the environment automatically. Integrating ecosystem tools such as Spring Security for authentication and Spring Data for database interaction accelerates the development of robust, enterprise-grade APIs.
Frontend Framework: Vue.js
Vue.js is employed for the management dashboard, leveraging its Virtual DOM to optimize rendering performance. Its reactive data binding system synchronizes the UI with the underlying data model automatically, reducing boilerplate code for DOM manipulation. The component-based architecture allows for reusable UI elements, making the codebase modular and easier to maintain as the application grows.
Persistence Layer: MyBatis-Plus
MyBatis-Plus enhances the standard MyBatis framework by providing robust CRUD operations without the need for extensive XML mapping files. It supports multiple databases and includes advanced features like pagination, optimistic locking, and code generation. This abstraction layer significantly reduces the development time required for data access objects (DAOs) and ensures consistent SQL handling across the application.
System Testing Strategy
Testing Objectives
Comprehensive testing is critical to ensuring system reliability and user satisfaction. The primary goal is to identify and rectify defects before deployment. By simulating real-world usage scenarios, the testing process validates that the system meets all functional requirements defined in the specification. This phase focuses on verifying logic correctness, data integrity, and the stability of the user interface.
Functional Testing
Black-box testing techniques are applied to verify the system's external behavior without examining the internal code structure. Test cases are designed to cover various inputs, including boundary values and invalid data entries.
Login Module Test Cases:
| Test Scenario | Input Data | Expected Outcome | Actual Result | Status |
|---|---|---|---|---|
| Valid Credentials | User: admin, Pass: correct123, Captcha: Valid |
Redirect to Dashboard | Login Successful | Pass |
| Invalid Password | User: admin, Pass: wrongpass, Captcha: Valid |
Error: Invalid Password | Error Message Displayed | Pass |
| Invalid Captcha | User: admin, Pass: correct123, Captcha: Invalid |
Error: Captcha Mismatch | Error Message Displayed | Pass |
| Empty Username | User: , Pass: correct123, Captcha: Valid |
Error: Username Required | Validation Warning | Pass |
| Empty Password | User: admin, Pass: , Captcha: Valid |
Error: Password Required | Validation Warning | Pass |
User Management Module Test Cases:
| Test Scenario | Input Data | Expected Outcome | Actual Result | Status |
|---|---|---|---|---|
| Create New User | Valid user details | User added to list | Record appears in DB | Pass |
| Update User | Modify existing profile | Changes saved | List reflects updates | Pass |
| Delete User | Select user and confirm | User removed | Record no longer found | Pass |
| Duplicate Username | Existing username | Error: Duplicate Entry | Rejection alert | Pass |
| Missing Required Fields | Blank required fields | Error: Fields Required | Validation fails | Pass |
Code Implementation
Authentication Controller
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
@Autowired
private SecurityService securityService;
@PostMapping("/login")
public ApiResponse<LoginResponse> authenticate(@RequestBody LoginDto credentials) {
AccountEntity account = securityService.findAccountByName(credentials.getUsername());
if (account == null || !securityService.checkPassword(account, credentials.getPassword())) {
return ApiResponse.error("Invalid username or password");
}
String sessionKey = securityService.generateSessionKey(account.getId(), account.getUsername(), account.getRole());
return ApiResponse.success(new LoginResponse(sessionKey));
}
}
Token Generation Service
@Service
public class SecurityService {
@Autowired
private TokenRepository tokenDao;
public String generateSessionKey(Long userId, String username, String roleName) {
// Check for existing session
TokenEntity existingToken = tokenDao.findOneByUserIdAndRole(userId, roleName);
// Generate a random secure key
String newKey = UUID.randomUUID().toString().replace("-", "");
// Set expiration to 1 hour from now
Instant expiry = Instant.now().plus(1, ChronoUnit.HOURS);
if (existingToken != null) {
existingToken.setAccessKey(newKey);
existingToken.setExpiryDate(expiry);
tokenDao.save(existingToken);
} else {
TokenEntity freshToken = new TokenEntity(userId, username, "accounts", roleName, newKey, expiry);
tokenDao.insert(freshToken);
}
return newKey;
}
}
Request Interceptor
@Component
public class SecurityInterceptor implements HandlerInterceptor {
public static final String AUTH_HEADER = "X-Auth-Token";
@Autowired
private SecurityService securityService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Handle CORS preflight
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return false;
}
// Bypass authentication for public endpoints
if (handler instanceof HandlerMethod) {
PublicEndpoint annotation = ((HandlerMethod) handler).getMethodAnnotation(PublicEndpoint.class);
if (annotation != null) {
return true;
}
}
String clientToken = request.getHeader(AUTH_HEADER);
if (StringUtils.isEmpty(clientToken)) {
sendErrorResponse(response, 401, "Authentication token missing");
return false;
}
TokenEntity session = securityService.lookupToken(clientToken);
if (session != null) {
request.setAttribute("currentUserId", session.getUserId());
request.setAttribute("currentUserRole", session.getRole());
return true;
}
sendErrorResponse(response, 401, "Invalid or expired token");
return false;
}
private void sendErrorResponse(HttpServletResponse response, int code, String msg) throws IOException {
response.setContentType("application/;charset=UTF-8");
response.setStatus(code);
PrintWriter out = response.getWriter();
out.print(JSON.toJSONString(Result.fail(code, msg)));
out.flush();
}
}
Database Schema
-- ------------------------------------------------
-- Table Structure for `access_tokens`
-- ------------------------------------------------
DROP TABLE IF EXISTS `access_tokens`;
CREATE TABLE `access_tokens` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`user_id` BIGINT(20) NOT NULL COMMENT 'Associated User ID',
`username` VARCHAR(100) NOT NULL COMMENT 'Account Name',
`table_name` VARCHAR(50) DEFAULT NULL COMMENT 'Target Table',
`role_name` VARCHAR(50) DEFAULT NULL COMMENT 'User Role',
`access_key` VARCHAR(255) NOT NULL COMMENT 'Session Token',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time',
`expires_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Expiration Time',
PRIMARY KEY (`id`),
KEY `idx_user_token` (`user_id`, `access_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='User Session Tokens';
-- ------------------------------------------------
-- Sample Data for `access_tokens`
-- ------------------------------------------------
INSERT INTO `access_tokens` (`id`, `user_id`, `username`, `table_name`, `role_name`, `access_key`, `created_at`, `expires_at`) VALUES
(1, 101, 'system_admin', 'sys_users', 'admin', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', '2023-10-01 10:00:00', '2023-10-01 11:00:00'),
(2, 205, 'student_zhang', 'students', 'student', 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4', '2023-10-02 14:30:00', '2023-10-02 15:30:00');