期中考试ATM登录功能

release
ldl 5 months ago
commit b18f71df26

27
.gitignore vendored

@ -0,0 +1,27 @@
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
/target/

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

219
Jenkinsfile vendored

@ -0,0 +1,219 @@
pipeline {
agent any
// 全局环境变量配置
environment {
// Java环境
JAVA_HOME = "${tool 'JDK-21'}"
MAVEN_HOME = "${tool 'Maven-3.9'}"
MAVEN_OPTS = '-Xmx1024m'
// 应用信息
APP_NAME = 'cstatm-mte'
APP_VERSION = '1.0.0'
// SonarQube配置
SONAR_SCANNER_HOME = tool 'SonarQubeScanner'
SONARQUBE_SCANNER_PARAMS = '-Dsonar.host.url=http://localhost:9000 -Dsonar.login=$SONAR_TOKEN'
// 头歌仓库凭据
EDU_CRED = credentials('educoder-cred')
}
// 工具配置
tools {
maven 'Maven-3.9'
jdk 'JDK-21'
sonarQubeScanner 'SonarQubeScanner'
}
// 流水线选项
options {
skipDefaultCheckout(true)
timeout(time: 60, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
}
stages {
// 阶段1拉取代码
stage('拉取代码') {
steps {
// 清理工作空间
cleanWs()
// 从Gitea仓库拉取代码
git branch: 'main',
url: 'http://localhost:3000/gitea/cstatm-mte.git',
credentialsId: 'git-credentials'
// 显示提交信息
script {
def commit = sh(returnStdout: true, script: 'git rev-parse HEAD')
def message = sh(returnStdout: true, script: 'git log -1 --pretty=%B')
echo "当前提交: ${commit}"
echo "提交信息: ${message}"
}
}
}
// 阶段2代码质量检测
stage('代码质量检测') {
steps {
// 执行SonarQube扫描
withSonarQubeEnv('SonarQube') {
sh "mvn sonar:sonar ${SONARQUBE_SCANNER_PARAMS}"
}
}
post {
always {
// 等待质量门禁结果
script {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "代码质量检测未通过: ${qg.status}"
} else {
echo "代码质量检测通过: ${qg.status}"
}
}
}
}
}
// 阶段3编译构建
stage('编译构建') {
steps {
// 执行Maven编译
sh "mvn clean compile"
// 检查编译结果
script {
def targetDir = 'target/classes'
if (fileExists(targetDir)) {
echo "编译成功生成class文件"
sh "find ${targetDir} -name '*.class' | wc -l"
} else {
error "编译失败未找到class文件"
}
}
}
}
// 阶段4运行测试
stage('运行测试') {
steps {
// 执行单元测试
sh "mvn test"
// 发布测试报告
junit 'target/surefire-reports/*.xml'
// 发布代码覆盖率报告
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: 'JaCoCo Coverage Report'
])
}
}
// 阶段5打包应用
stage('打包应用') {
steps {
// 执行Maven打包
sh "mvn package -DskipTests"
// 归档构建产物
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
// 显示打包结果
script {
def jarFiles = sh(returnStdout: true, script: 'ls -la target/*.jar || true')
echo "打包结果: ${jarFiles}"
}
}
}
// 阶段6推送源码至头歌release分支
stage('推送源码至头歌release分支') {
steps {
script {
def eduRepo = "https://bdgit.educoder.net/pu6zrsfoy/cstatm-mte.git"
def eduBranch = "release"
// 配置Git用户
sh 'git config --global user.name "Jenkins"'
sh 'git config --global user.email "602924803@qq.com"'
// 关联头歌仓库并推送源码
sh "git remote add educoder ${eduRepo} || true"
sh "git pull educoder ${eduBranch} --rebase" // 拉取最新代码避免冲突
sh "git push https://${EDU_CRED_USR}:${EDU_CRED_PSW}@bdgit.educoder.net/pu6zrsfoy/cstatm-mte.git main:${eduBranch}"
}
}
}
// 阶段7发布制品至头歌
stage('发布制品至头歌') {
steps {
script {
// 复制JAR包并提交
sh 'cp target/*.jar ./'
sh 'git add *.jar'
sh 'git commit -m "发布登录功能制品:$(date +%Y%m%d)"'
sh "git push https://${EDU_CRED_USR}:${EDU_CRED_PSW}@bdgit.educoder.net/pu6zrsfoy/cstatm-mte.git HEAD:release"
}
}
}
}
// 后置操作
post {
// 成功时执行
success {
echo "构建成功"
// 归档构建产物
archiveArtifacts artifacts: 'target/*', fingerprint: true
// 发布HTML报告
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/site',
reportFiles: 'index.html',
reportName: 'Maven Site Report'
])
}
// 失败时执行
failure {
echo "构建失败"
// 发送失败通知
emailext (
subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "构建失败,请查看日志: ${env.BUILD_URL}console",
to: 'dev-team@example.com'
)
}
// 总是执行
always {
// 清理工作空间
cleanWs()
// 显示构建结果
script {
def result = currentBuild.result ?: 'SUCCESS'
def duration = currentBuild.durationString.replaceAll(' and counting', '')
echo "构建结果: ${result}"
echo "构建时长: ${duration}"
}
}
}
}

@ -0,0 +1,5 @@
Manifest-Version: 1.0
Created-By: Maven Archiver 3.6.0
Build-Jdk-Spec: 17
Main-Class: com.atm.view.gui.Gui

@ -0,0 +1,42 @@
# ATM应用程序
这是一个基于Java Swing的ATM模拟应用程序。
## 如何运行
### 方法1使用批处理文件推荐
1. 确保已安装Java 17或更高版本
2. 双击运行 `start-atm.bat` 文件
### 方法2使用命令行
1. 打开命令提示符或PowerShell
2. 导航到项目目录:`cd c:\Users\ldl\Desktop\cstatm-mte`
3. 运行以下命令:
```
java -jar target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar
```
### 方法3使用Maven
1. 确保已安装Maven
2. 在项目目录中运行:
```
mvn clean package -DskipTests
java -jar target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar
```
## 已修复的问题
1. 修复了Customer类中的构造函数问题将`public void Customer()`更改为`public Customer()`
2. 配置了Maven跳过测试以避免数据库连接问题
## 应用程序功能
- 用户登录界面
- 验证用户ID和PIN
- 连接PostgreSQL数据库进行用户验证
## 注意事项
- 应用程序需要连接到PostgreSQL数据库
- 如果没有可用的数据库连接,登录验证将失败
- 测试已配置为跳过,以避免数据库连接问题

@ -0,0 +1,14 @@
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.BorderLayout;
public class TestGui {
public static void main(String[] args) {
JFrame frame = new JFrame("Test GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
JLabel label = new JLabel("GUI is working!", JLabel.CENTER);
frame.getContentPane().add(label, BorderLayout.CENTER);
frame.setVisible(true);
}
}

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 修改cstatm-mte -->
<jnlp codebase="http://116.204.84.48:8080/cstatm-mte/" href="cstatm-mte.jnlp">
<information>
<title>JWS to Run cstatm</title>
<vendor>czldl</vendor>
<description>ATM EAGitOps</description>
<homepage href="http://116.204.84.48:8080/cstatm-mte/index.html"/>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<update check="always" policy="always"/>
<resources>
<j2se href="http://java.sun.com/products/autodl/j2se" version="1.6+" />
<jar href="cstatm-mte.jar" main="true"/>
<jar href="lib/postgresql-42.6.0.jar"/>
</resources>
<application-desc name="ATM EAGitOps" main-class="com.atm.view.gui.Gui"/>
</jnlp>

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>计科21《软件工程基础》期中考试专用</title>
</head>
<body>
学号: 206004 姓名: 刘东良 小组项目cstatm-mte
<hr />
<h1>本地localhost:8080</h1>
<a href="http://localhost:8080/cstatm-mte/cstatm-mte_exe-1.0.exe">下载 cstatm-mte-1.0.exe and 双击</a>
<p />
<a href="http://localhost:8080/cstatm-mte/cstatm-mte_msi-1.0.msi">下载 cstatm-mte-1.0.msi ,安装、执行</a>
<hr />
<h1>华为云ECS 116.204.84.48:8080</h1>
<A href="http://116.204.84.48:8080/cstatm-mte/cstatm-mte.jnlp">下载 jnlp双击cstatm-mte.jnlp</A>
<p />
<a href="http://116.204.84.48:8080/cstatm-mte/cstatm-mte_exe-1.0.exe">下载 cstatm-mte-1.0.exe and 双击</a>
<p />
<a href="http://116.204.84.48:8080/cstatm-mte/cstatm-mte_msi-1.0.msi">下载 cstatm-mte-1.0.msi ,安装、执行</a>
</body>
</html>

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>ATM Application Launcher</title>
</head>
<body>
<h1>ATM Application</h1>
<p>Click the button below to launch the ATM application:</p>
<button onclick="launchApp()">Launch ATM</button>
<script>
function launchApp() {
// This will try to launch the Java application
// Note: This may not work in all browsers due to security restrictions
window.location.href = 'cstatm-mte.jnlp';
}
</script>
</body>
</html>

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/>
</parent>
<groupId>com.atm</groupId>
<artifactId>cstatm-mte</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cstatm-mte</name>
<description>ATM System for Mid-term Exam</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.report.outputEncoding>UTF-8</project.report.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- PostgreSQL Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- SQLite JDBC Driver (for development) -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.41.2.1</version>
</dependency>
<!-- H2 Database for testing -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot DevTools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Security Test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Hamcrest -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- JSON Web Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<skipTests>false</skipTests>
<argLine>-Dfile.encoding=UTF-8 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED</argLine>
<useSystemClassLoader>false</useSystemClassLoader>
<systemPropertyVariables>
<spring.profiles.active>test</spring.profiles.active>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<configuration>
<skip>false</skip>
<destFile>target/coverage-reports/jacoco-unit.exec</destFile>
<dataFile>target/coverage-reports/jacoco-unit.exec</dataFile>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,4 @@
@echo off
echo Starting ATM GUI Application...
java -jar target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar
pause

@ -0,0 +1,2 @@
Write-Host "Starting ATM GUI Application..."
Start-Process -FilePath "java" -ArgumentList "-jar", "target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar" -Wait

@ -0,0 +1,24 @@
#计科21《软件工程基础》期中考试专用
#务必修改为自己学号后6位
sonar.projectKey=cstatm-mte
#务必修改为自己学号后6位
sonar.projectName=cstatm-mte
sonar.projectVersion=1.0
sonar.sourceEndcoding=UTF-8
sonar.language=java
sonar.java.libraries=src/main/resources/lib/*.jar
sonar.sources=src/main/java
sonar.java.binaries=target
sonar.java.test.libraries=src/main/resources/lib/*.jar
sonar.tests=src/test/java
sonar.dynamicAnalysis=reuseReports
sonar.core.codeCoveragePlugin=jacoco
sonar.java.coveragePlugin=jacoco
sonar.jacoco.reportPaths=target/site/jacoco/jacoco.exec
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.junit.reportPaths=target/site/surefire-reports
sonar.host.url=http://localhost:9000
sonar.token=squ_8a16932a4d051ce8924fb7e91d377c86e68ad6e4
#最后一次注释上面两行,前面加上#,去掉下面两行
#sonar.host.url=http://116.204.84.48:9000
#sonar.token=squ_bc35d22b677cf7779337fef614b386d59dc9df50

@ -0,0 +1,17 @@
package com.atm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* ATM
* Spring Boot
*/
@SpringBootApplication
public class AtmApplication {
public static void main(String[] args) {
SpringApplication.run(AtmApplication.class, args);
System.out.println("ATM系统已启动访问地址: http://localhost:8080");
}
}

@ -0,0 +1,24 @@
package com.atm.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT
*
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问受保护的REST资源而不提供任何凭据时调用
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未授权访问");
}
}

@ -0,0 +1,66 @@
package com.atm.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* JWT
* JWT token
*/
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
public JwtRequestFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token格式为 "Bearer token"
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (Exception e) {
logger.warn("无法获取JWT Token中的用户名");
}
}
// 验证token
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 如果token有效配置Spring Security手动设置认证
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}

@ -0,0 +1,105 @@
package com.atm.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* JWT
*/
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* token
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* token
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* token
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* token
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* token
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
/**
* token
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
*
*/
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
}

@ -0,0 +1,65 @@
package com.atm.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* Spring Security
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtRequestFilter jwtRequestFilter) {
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/authenticate", "/api/register", "/h2-console/**").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

@ -0,0 +1,258 @@
package com.atm.controller;
import com.atm.model.Account;
import com.atm.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
*/
@RestController
@RequestMapping("/api/accounts")
@CrossOrigin(origins = "*")
public class AccountController {
private final AccountService accountService;
@Autowired
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
/**
*
*/
@GetMapping("/{aid}")
public ResponseEntity<Map<String, Object>> getAccount(@PathVariable Long aid) {
try {
Optional<Account> accountOpt = accountService.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("account", account);
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "账户不存在");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取账户失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/customer/{cid}")
public ResponseEntity<Map<String, Object>> getAccountsByCustomer(@PathVariable Long cid) {
try {
List<Account> accounts = accountService.findByCustomerId(cid);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("accounts", accounts);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取账户列表失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping
public ResponseEntity<Map<String, Object>> createAccount(@RequestBody Account account) {
try {
Account newAccount = accountService.createAccount(account);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "账户创建成功");
response.put("account", newAccount);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "账户创建失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping("/{aid}/deposit")
public ResponseEntity<Map<String, Object>> deposit(@PathVariable Long aid, @RequestBody Map<String, Object> request) {
try {
Double amount = Double.parseDouble(request.get("amount").toString());
boolean success = accountService.deposit(aid, amount);
Map<String, Object> response = new HashMap<>();
if (success) {
Optional<Account> accountOpt = accountService.findByAid(aid);
response.put("success", true);
response.put("message", "存款成功");
accountOpt.ifPresent(account -> response.put("account", account));
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "存款失败");
return ResponseEntity.badRequest().body(response);
}
} catch (NumberFormatException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无效的金额格式");
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "存款失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping("/{aid}/withdraw")
public ResponseEntity<Map<String, Object>> withdraw(@PathVariable Long aid, @RequestBody Map<String, Object> request) {
try {
Double amount = Double.parseDouble(request.get("amount").toString());
boolean success = accountService.withdraw(aid, amount);
Map<String, Object> response = new HashMap<>();
if (success) {
Optional<Account> accountOpt = accountService.findByAid(aid);
response.put("success", true);
response.put("message", "取款成功");
accountOpt.ifPresent(account -> response.put("account", account));
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "取款失败,余额不足");
return ResponseEntity.badRequest().body(response);
}
} catch (NumberFormatException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无效的金额格式");
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "取款失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping("/{fromAid}/transfer/{toAid}")
public ResponseEntity<Map<String, Object>> transfer(@PathVariable Long fromAid, @PathVariable Long toAid, @RequestBody Map<String, Object> request) {
try {
Double amount = Double.parseDouble(request.get("amount").toString());
boolean success = accountService.transfer(fromAid, toAid, amount);
Map<String, Object> response = new HashMap<>();
if (success) {
Optional<Account> fromAccountOpt = accountService.findByAid(fromAid);
Optional<Account> toAccountOpt = accountService.findByAid(toAid);
response.put("success", true);
response.put("message", "转账成功");
fromAccountOpt.ifPresent(account -> response.put("fromAccount", account));
toAccountOpt.ifPresent(account -> response.put("toAccount", account));
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "转账失败,余额不足或账户不存在");
return ResponseEntity.badRequest().body(response);
}
} catch (NumberFormatException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无效的金额格式");
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "转账失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/{aid}/balance")
public ResponseEntity<Map<String, Object>> getBalance(@PathVariable Long aid) {
try {
Optional<Account> accountOpt = accountService.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("balance", account.getAbalance());
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "账户不存在");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取余额失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* /
*/
@PostMapping("/{aid}/status")
public ResponseEntity<Map<String, Object>> updateAccountStatus(@PathVariable Long aid, @RequestBody Map<String, String> request) {
try {
String status = request.get("status");
boolean success = accountService.updateAccountStatus(aid, status);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "账户状态更新成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "账户状态更新失败");
return ResponseEntity.badRequest().body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "账户状态更新失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

@ -0,0 +1,188 @@
package com.atm.controller;
import com.atm.model.Customer;
import com.atm.service.AuthenticationService;
import com.atm.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
*/
@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins = "*")
public class AuthController {
private final AuthenticationService authenticationService;
private final CustomerService customerService;
@Autowired
public AuthController(AuthenticationService authenticationService, CustomerService customerService) {
this.authenticationService = authenticationService;
this.customerService = customerService;
}
/**
*
*/
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody Map<String, String> loginRequest) {
try {
Long cid = Long.parseLong(loginRequest.get("cid"));
String cpin = loginRequest.get("cpin");
Optional<String> tokenOpt = authenticationService.authenticateAndGenerateToken(cid, cpin);
if (tokenOpt.isPresent()) {
Optional<Customer> customerOpt = customerService.findByCid(cid);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "登录成功");
response.put("token", tokenOpt.get());
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
response.put("user", Map.of(
"cid", customer.getCid(),
"cname", customer.getCname()
));
}
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户ID或PIN码错误");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
} catch (NumberFormatException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无效的客户ID格式");
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "登录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping("/validate")
public ResponseEntity<Map<String, Object>> validateToken(@RequestBody Map<String, String> tokenRequest) {
String token = tokenRequest.get("token");
boolean isValid = authenticationService.validateToken(token);
Map<String, Object> response = new HashMap<>();
response.put("valid", isValid);
if (isValid) {
String username = authenticationService.getUsernameFromToken(token);
response.put("username", username);
}
return ResponseEntity.ok(response);
}
/**
*
*/
@PostMapping("/refresh")
public ResponseEntity<Map<String, Object>> refreshToken(@RequestBody Map<String, String> tokenRequest) {
String token = tokenRequest.get("token");
Optional<String> newTokenOpt = authenticationService.refreshToken(token);
if (newTokenOpt.isPresent()) {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("token", newTokenOpt.get());
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "令牌刷新失败");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
}
/**
*
*/
@PostMapping("/register")
public ResponseEntity<Map<String, Object>> register(@RequestBody Customer customer) {
try {
// 检查客户ID是否已存在
if (customerService.existsByCid(customer.getCid())) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户ID已存在");
return ResponseEntity.badRequest().body(response);
}
// 创建新客户
Customer newCustomer = customerService.createCustomer(customer);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "注册成功");
response.put("customer", Map.of(
"cid", newCustomer.getCid(),
"cname", newCustomer.getCname()
));
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "注册失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* PIN
*/
@PostMapping("/change-pin")
public ResponseEntity<Map<String, Object>> changePin(@RequestBody Map<String, String> pinRequest) {
try {
Long cid = Long.parseLong(pinRequest.get("cid"));
String oldPin = pinRequest.get("oldPin");
String newPin = pinRequest.get("newPin");
boolean success = customerService.updatePin(cid, oldPin, newPin);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "PIN码修改成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "PIN码修改失败请检查旧PIN码是否正确");
return ResponseEntity.badRequest().body(response);
}
} catch (NumberFormatException e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无效的客户ID格式");
return ResponseEntity.badRequest().body(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "PIN码修改失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

@ -0,0 +1,285 @@
package com.atm.controller;
import com.atm.model.Customer;
import com.atm.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
*/
@RestController
@RequestMapping("/api/customers")
@CrossOrigin(origins = "*")
public class CustomerController {
private final CustomerService customerService;
@Autowired
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
/**
*
*/
@GetMapping("/{cid}")
public ResponseEntity<Map<String, Object>> getCustomer(@PathVariable Long cid) {
try {
Optional<Customer> customerOpt = customerService.findByCid(cid);
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("customer", customer);
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户不存在");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取客户信息失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping
public ResponseEntity<Map<String, Object>> getAllCustomers() {
try {
List<Customer> customers = customerService.findAll();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("customers", customers);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取客户列表失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping
public ResponseEntity<Map<String, Object>> createCustomer(@RequestBody Customer customer) {
try {
// 检查客户ID是否已存在
if (customerService.existsByCid(customer.getCid())) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户ID已存在");
return ResponseEntity.badRequest().body(response);
}
Customer newCustomer = customerService.createCustomer(customer);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "客户创建成功");
response.put("customer", newCustomer);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户创建失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PutMapping("/{cid}")
public ResponseEntity<Map<String, Object>> updateCustomer(@PathVariable Long cid, @RequestBody Customer customer) {
try {
customer.setCid(cid); // 确保ID一致
Customer updatedCustomer = customerService.updateCustomer(customer);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "客户信息更新成功");
response.put("customer", updatedCustomer);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户信息更新失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@DeleteMapping("/{cid}")
public ResponseEntity<Map<String, Object>> deleteCustomer(@PathVariable Long cid) {
try {
boolean success = customerService.deleteCustomer(cid);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "客户删除成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "客户不存在或删除失败");
return ResponseEntity.badRequest().body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户删除失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* PIN
*/
@PostMapping("/{cid}/change-pin")
public ResponseEntity<Map<String, Object>> changePin(
@PathVariable Long cid,
@RequestBody Map<String, String> pinRequest) {
try {
String oldPin = pinRequest.get("oldPin");
String newPin = pinRequest.get("newPin");
boolean success = customerService.updatePin(cid, oldPin, newPin);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "PIN码修改成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "PIN码修改失败请检查旧PIN码是否正确");
return ResponseEntity.badRequest().body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "PIN码修改失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* PIN
*/
@PostMapping("/{cid}/reset-pin")
public ResponseEntity<Map<String, Object>> resetPin(
@PathVariable Long cid,
@RequestBody Map<String, String> pinRequest) {
try {
String newPin = pinRequest.get("newPin");
boolean success = customerService.resetPin(cid, newPin);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "PIN码重置成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "PIN码重置失败客户不存在");
return ResponseEntity.badRequest().body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "PIN码重置失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@PostMapping("/{cid}/status")
public ResponseEntity<Map<String, Object>> updateCustomerStatus(
@PathVariable Long cid,
@RequestBody Map<String, String> statusRequest) {
try {
String status = statusRequest.get("status");
boolean success = customerService.updateCustomerStatus(cid, status);
Map<String, Object> response = new HashMap<>();
if (success) {
response.put("success", true);
response.put("message", "客户状态更新成功");
return ResponseEntity.ok(response);
} else {
response.put("success", false);
response.put("message", "客户状态更新失败");
return ResponseEntity.badRequest().body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "客户状态更新失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/status/{status}")
public ResponseEntity<Map<String, Object>> getCustomersByStatus(@PathVariable String status) {
try {
List<Customer> customers = customerService.findByStatus(status);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("customers", customers);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取客户列表失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/{cid}/exists")
public ResponseEntity<Map<String, Object>> checkCustomerExists(@PathVariable Long cid) {
try {
boolean exists = customerService.existsByCid(cid);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("exists", exists);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "检查客户存在性失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

@ -0,0 +1,251 @@
package com.atm.controller;
import com.atm.model.Transaction;
import com.atm.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
*/
@RestController
@RequestMapping("/api/transactions")
@CrossOrigin(origins = "*")
public class TransactionController {
private final TransactionService transactionService;
@Autowired
public TransactionController(TransactionService transactionService) {
this.transactionService = transactionService;
}
/**
*
*/
@GetMapping("/{tid}")
public ResponseEntity<Map<String, Object>> getTransaction(@PathVariable Long tid) {
try {
Optional<Transaction> transactionOpt = transactionService.findByTid(tid);
if (transactionOpt.isPresent()) {
Transaction transaction = transactionOpt.get();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transaction", transaction);
return ResponseEntity.ok(response);
} else {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "交易记录不存在");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/account/{aid}")
public ResponseEntity<Map<String, Object>> getTransactionsByAccount(@PathVariable Long aid) {
try {
List<Transaction> transactions = transactionService.findByAccountId(aid);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/customer/{cid}")
public ResponseEntity<Map<String, Object>> getTransactionsByCustomer(@PathVariable Long cid) {
try {
List<Transaction> transactions = transactionService.findByCustomerId(cid);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/date-range")
public ResponseEntity<Map<String, Object>> getTransactionsByDateRange(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
try {
List<Transaction> transactions = transactionService.findByDateRange(startDate, endDate);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/account/{aid}/date-range")
public ResponseEntity<Map<String, Object>> getTransactionsByAccountAndDateRange(
@PathVariable Long aid,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
try {
List<Transaction> transactions = transactionService.findByAccountAndDateRange(aid, startDate, endDate);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/customer/{cid}/date-range")
public ResponseEntity<Map<String, Object>> getTransactionsByCustomerAndDateRange(
@PathVariable Long cid,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
try {
List<Transaction> transactions = transactionService.findByCustomerAndDateRange(cid, startDate, endDate);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* N
*/
@GetMapping("/account/{aid}/recent")
public ResponseEntity<Map<String, Object>> getRecentTransactionsByAccount(
@PathVariable Long aid,
@RequestParam(defaultValue = "10") int limit) {
try {
List<Transaction> transactions = transactionService.findRecentTransactionsByAccount(aid, limit);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
* N
*/
@GetMapping("/customer/{cid}/recent")
public ResponseEntity<Map<String, Object>> getRecentTransactionsByCustomer(
@PathVariable Long cid,
@RequestParam(defaultValue = "10") int limit) {
try {
List<Transaction> transactions = transactionService.findRecentTransactionsByCustomer(cid, limit);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/amount-above")
public ResponseEntity<Map<String, Object>> getTransactionsByAmountAbove(@RequestParam Double amount) {
try {
List<Transaction> transactions = transactionService.findByAmountAbove(amount);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
/**
*
*/
@GetMapping("/amount-below")
public ResponseEntity<Map<String, Object>> getTransactionsByAmountBelow(@RequestParam Double amount) {
try {
List<Transaction> transactions = transactionService.findByAmountBelow(amount);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("transactions", transactions);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "获取交易记录失败: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

@ -0,0 +1,23 @@
package com.atm.controller;
public class Validate {
private static final int len = 6;
public static boolean isNumeric(String str) {
boolean flag = str != null && str.matches("\\d{" + len + "}");
if (!flag) {
System.out.println(str + " must is " + len + "Numeric!");
return false;
} else
return true;
}
public static boolean lengthValidate(String lenStr) {
if (lenStr.length() != len) {
System.out.println("Length must is " + len + " Charactors");
return false;
} else
return true;
}
}// end Validate

@ -0,0 +1,80 @@
package com.atm.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DbUtil {
protected static PreparedStatement ps = null;
protected static ResultSet rs = null;
protected static Connection conn = null;
public static synchronized Connection getConn() {
try {
Class.forName("org.postgresql.Driver");
conn = DriverManager.getConnection("jdbc:postgresql://116.204.84.48:5432/seb", "postgres", "gitops123");
} catch (Exception e) {
System.err.println("Load org.postgresql.Driver FailedCheck pgJDBC jar is not Exist!");
return null;
}
return conn;
}
public static void close() {
try {
if (rs != null)
rs.close();
if (ps != null)
ps.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
System.err.println("Close DB link Failed!");
return;
}
}
public static PreparedStatement executePreparedStatement(String sql) {
try {
ps = getConn().prepareStatement(sql);
} catch (Exception e) {
System.err.println("Create PreparedStatement Failed!");
return null;
}
return ps;
}
public static ResultSet executeQuery(String sql) {
try {
ps = executePreparedStatement(sql);
rs = ps.executeQuery();
} catch (SQLException e) {
System.err.println("Execute Query Failed!");
return null;
}
return rs;
}
public static void executeUpdate(String sql) {
try {
ps = executePreparedStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
System.err.println("Execute Update Failed!");
} finally {
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
System.err.println("Close Resources Failed!");
return;
}
}
}
}// end DbUtil

@ -0,0 +1,29 @@
package com.atm.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.atm.model.Customer;
public class Login {
/**
*
* @param c
*/
public boolean login(Customer c) {
try {
ResultSet rs = DbUtil.executeQuery("select * from customer");
while (rs.next())
if (rs.getString("cid").equals(c.getCid()) && rs.getString("cpin").equals(c.getCpin())) {
System.err.println("Hi," + rs.getString("cname"));
return true;
}
} catch (SQLException e) {
System.err.println("Fetch ResultSet Failed!");
return false;
}
System.out.println("No Customer!");
return false;
}
}// end Login

@ -0,0 +1,125 @@
package com.atm.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
*
*/
@Entity
@Table(name = "account")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "aid")
private Long aid;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cid", nullable = false)
private Customer customer;
@Column(name = "atype", nullable = false)
private String atype;
@Column(name = "abalance", precision = 15, scale = 2)
private BigDecimal abalance;
@Column(name = "astatus")
private String astatus;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 默认构造函数
public Account() {
this.abalance = BigDecimal.ZERO;
this.astatus = "active";
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// 带参构造函数
public Account(Customer customer, String atype) {
this();
this.customer = customer;
this.atype = atype;
}
// Getter和Setter方法
public Long getAid() {
return aid;
}
public void setAid(Long aid) {
this.aid = aid;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public String getAtype() {
return atype;
}
public void setAtype(String atype) {
this.atype = atype;
}
public BigDecimal getAbalance() {
return abalance;
}
public void setAbalance(BigDecimal abalance) {
this.abalance = abalance;
}
public String getAstatus() {
return astatus;
}
public void setAstatus(String astatus) {
this.astatus = astatus;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
@Override
public String toString() {
return "Account{" +
"aid=" + aid +
", customer=" + (customer != null ? customer.getCid() : null) +
", atype='" + atype + '\'' +
", abalance=" + abalance +
", astatus='" + astatus + '\'' +
'}';
}
}

@ -0,0 +1,161 @@
package com.atm.model;
import jakarta.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
/**
*
* UserDetailsSpring Security
*/
@Entity
@Table(name = "customer")
public class Customer implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cid")
private Long cid;
@Column(name = "cname", nullable = false)
private String cname;
@Column(name = "cpin", nullable = false)
private String cpin;
@Column(name = "cbalance", precision = 15, scale = 2)
private BigDecimal cbalance;
@Column(name = "cstatus")
private String cstatus;
@Column(name = "ctype")
private String ctype;
// 默认构造函数
public Customer() {
this.cbalance = BigDecimal.ZERO;
this.cstatus = "active";
this.ctype = "normal";
}
// 带参构造函数
public Customer(Long cid, String cname, String cpin) {
this();
this.cid = cid;
this.cname = cname;
this.cpin = cpin;
}
// 带参构造函数用于GUI
public Customer(String cid, String cpin) {
this();
try {
this.cid = Long.parseLong(cid);
} catch (NumberFormatException e) {
this.cid = null;
}
this.cpin = cpin;
}
// Getter和Setter方法
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getCpin() {
return cpin;
}
public void setCpin(String cpin) {
this.cpin = cpin;
}
public BigDecimal getCbalance() {
return cbalance;
}
public void setCbalance(BigDecimal cbalance) {
this.cbalance = cbalance;
}
public String getCstatus() {
return cstatus;
}
public void setCstatus(String cstatus) {
this.cstatus = cstatus;
}
public String getCtype() {
return ctype;
}
public void setCtype(String ctype) {
this.ctype = ctype;
}
// UserDetails接口实现方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 简单实现所有用户都有ROLE_USER权限
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return cpin;
}
@Override
public String getUsername() {
return cid.toString();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !"locked".equals(cstatus);
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return "active".equals(cstatus);
}
@Override
public String toString() {
return "Customer{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", cbalance=" + cbalance +
", cstatus='" + cstatus + '\'' +
'}';
}
}

@ -0,0 +1,134 @@
package com.atm.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
*
*/
@Entity
@Table(name = "transaction")
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "tid")
private Long tid;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_account_id")
private Account fromAccount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_account_id")
private Account toAccount;
@Column(name = "ttype", nullable = false)
private String ttype;
@Column(name = "tamount", precision = 15, scale = 2)
private BigDecimal tamount;
@Column(name = "tdescription")
private String tdescription;
@Column(name = "tstatus")
private String tstatus;
@Column(name = "created_at")
private LocalDateTime createdAt;
// 默认构造函数
public Transaction() {
this.tstatus = "completed";
this.createdAt = LocalDateTime.now();
}
// 带参构造函数
public Transaction(Account fromAccount, Account toAccount, String ttype, BigDecimal tamount) {
this();
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.ttype = ttype;
this.tamount = tamount;
}
// Getter和Setter方法
public Long getTid() {
return tid;
}
public void setTid(Long tid) {
this.tid = tid;
}
public Account getFromAccount() {
return fromAccount;
}
public void setFromAccount(Account fromAccount) {
this.fromAccount = fromAccount;
}
public Account getToAccount() {
return toAccount;
}
public void setToAccount(Account toAccount) {
this.toAccount = toAccount;
}
public String getTtype() {
return ttype;
}
public void setTtype(String ttype) {
this.ttype = ttype;
}
public BigDecimal getTamount() {
return tamount;
}
public void setTamount(BigDecimal tamount) {
this.tamount = tamount;
}
public String getTdescription() {
return tdescription;
}
public void setTdescription(String tdescription) {
this.tdescription = tdescription;
}
public String getTstatus() {
return tstatus;
}
public void setTstatus(String tstatus) {
this.tstatus = tstatus;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public String toString() {
return "Transaction{" +
"tid=" + tid +
", fromAccount=" + (fromAccount != null ? fromAccount.getAid() : null) +
", toAccount=" + (toAccount != null ? toAccount.getAid() : null) +
", ttype='" + ttype + '\'' +
", tamount=" + tamount +
", tstatus='" + tstatus + '\'' +
", createdAt=" + createdAt +
'}';
}
}

@ -0,0 +1,60 @@
package com.atm.repository;
import com.atm.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
/**
* 访
*/
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
/**
* ID
*/
Optional<Account> findByAid(Long aid);
/**
* ID
*/
List<Account> findByCustomerCid(Long cid);
/**
* ID
*/
Optional<Account> findByCustomerCidAndAtype(Long cid, String atype);
/**
* ID
*/
List<Account> findByCustomerCidAndAstatus(Long cid, String astatus);
/**
*
*/
List<Account> findByAtype(String atype);
/**
*
*/
boolean existsByAid(Long aid);
/**
*
*/
@Query("SELECT a FROM Account a WHERE a.abalance > :amount")
List<Account> findAccountsWithBalanceGreaterThan(@Param("amount") BigDecimal amount);
/**
*
*/
@Query("SELECT a FROM Account a WHERE a.abalance < :amount")
List<Account> findAccountsWithBalanceLessThan(@Param("amount") BigDecimal amount);
}

@ -0,0 +1,34 @@
package com.atm.repository;
import com.atm.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 访
*/
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
/**
* ID
*/
Optional<Customer> findByCid(Long cid);
/**
* IDPIN
*/
Optional<Customer> findByCidAndCpin(Long cid, String cpin);
/**
* ID
*/
boolean existsByCid(Long cid);
/**
*
*/
java.util.List<Customer> findByCstatus(String cstatus);
}

@ -0,0 +1,104 @@
package com.atm.repository;
import com.atm.model.Transaction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 访
*/
@Repository
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
/**
* ID
*/
java.util.Optional<Transaction> findByTid(Long tid);
/**
*
*/
List<Transaction> findByFromAccountAid(Long fromAccountId);
/**
*
*/
List<Transaction> findByToAccountAid(Long toAccountId);
/**
* ID
*/
@Query("SELECT t FROM Transaction t WHERE t.fromAccount.aid = :accountId OR t.toAccount.aid = :accountId ORDER BY t.createdAt DESC")
List<Transaction> findByAccountId(@Param("accountId") Long accountId);
/**
*
*/
List<Transaction> findByTtype(String ttype);
/**
*
*/
List<Transaction> findByTstatus(String tstatus);
/**
* ID
*/
@Query("SELECT t FROM Transaction t WHERE t.fromAccount.customer.cid = :cid OR t.toAccount.customer.cid = :cid ORDER BY t.createdAt DESC")
List<Transaction> findByCustomerId(@Param("cid") Long cid);
/**
*
*/
@Query("SELECT t FROM Transaction t WHERE t.createdAt BETWEEN :startTime AND :endTime ORDER BY t.createdAt DESC")
List<Transaction> findByDateRange(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
/**
* ID
*/
@Query("SELECT t FROM Transaction t WHERE (t.fromAccount.customer.cid = :cid OR t.toAccount.customer.cid = :cid) " +
"AND t.createdAt BETWEEN :startTime AND :endTime ORDER BY t.createdAt DESC")
List<Transaction> findByCustomerIdAndDateRange(@Param("cid") Long cid,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
/**
*
*/
@Query("SELECT t FROM Transaction t WHERE t.tamount > :amount ORDER BY t.tamount DESC")
List<Transaction> findTransactionsWithAmountGreaterThan(@Param("amount") BigDecimal amount);
/**
* N
*/
@Query("SELECT t FROM Transaction t WHERE t.fromAccount.aid = :accountId OR t.toAccount.aid = :accountId ORDER BY t.createdAt DESC")
List<Transaction> findRecentTransactionsByAccountId(@Param("accountId") Long accountId, org.springframework.data.domain.Pageable pageable);
/**
* N
*/
@Query("SELECT t FROM Transaction t WHERE t.fromAccount.customer.cid = :customerId OR t.toAccount.customer.cid = :customerId ORDER BY t.createdAt DESC")
List<Transaction> findRecentTransactionsByCustomerId(@Param("customerId") Long customerId, org.springframework.data.domain.Pageable pageable);
/**
* ID
*/
@Query("SELECT t FROM Transaction t WHERE (t.fromAccount.aid = :accountId OR t.toAccount.aid = :accountId) " +
"AND t.createdAt BETWEEN :startTime AND :endTime ORDER BY t.createdAt DESC")
List<Transaction> findByAccountIdAndDateRange(@Param("accountId") Long accountId,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
/**
*
*/
@Query("SELECT t FROM Transaction t WHERE t.tamount < :amount ORDER BY t.tamount ASC")
List<Transaction> findTransactionsWithAmountLessThan(@Param("amount") BigDecimal amount);
}

@ -0,0 +1,241 @@
package com.atm.service;
import com.atm.model.Account;
import com.atm.model.Customer;
import com.atm.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
/**
*
*/
@Service
@Transactional
public class AccountService {
private final AccountRepository accountRepository;
@Autowired
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
/**
* ID
*/
public Optional<Account> findByAid(Long aid) {
return accountRepository.findByAid(aid);
}
/**
* ID
*/
public List<Account> findByCustomerId(Long cid) {
return accountRepository.findByCustomerCid(cid);
}
/**
* ID
*/
public Optional<Account> findByCustomerIdAndType(Long cid, String atype) {
return accountRepository.findByCustomerCidAndAtype(cid, atype);
}
/**
*
*/
public Account createAccount(Account account) {
return accountRepository.save(account);
}
/**
*
*/
public Account createAccountForCustomer(Customer customer, String accountType, BigDecimal initialBalance) {
Account account = new Account();
account.setCustomer(customer);
account.setAtype(accountType);
account.setAbalance(initialBalance);
return accountRepository.save(account);
}
/**
*
*/
public boolean updateBalance(Long aid, BigDecimal newBalance) {
Optional<Account> accountOpt = accountRepository.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
account.setAbalance(newBalance);
accountRepository.save(account);
return true;
}
return false;
}
/**
*
*/
public boolean deposit(Long aid, BigDecimal amount) {
Optional<Account> accountOpt = accountRepository.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
if ("active".equals(account.getAstatus())) {
account.setAbalance(account.getAbalance().add(amount));
accountRepository.save(account);
return true;
}
}
return false;
}
/**
*
*/
public boolean deposit(Long aid, Double amount) {
if (amount == null || amount <= 0) {
return false;
}
return deposit(aid, BigDecimal.valueOf(amount));
}
/**
*
*/
public boolean withdraw(Long aid, BigDecimal amount) {
Optional<Account> accountOpt = accountRepository.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
if ("active".equals(account.getAstatus()) &&
account.getAbalance().compareTo(amount) >= 0) {
account.setAbalance(account.getAbalance().subtract(amount));
accountRepository.save(account);
return true;
}
}
return false;
}
/**
*
*/
public boolean withdraw(Long aid, Double amount) {
if (amount == null || amount <= 0) {
return false;
}
return withdraw(aid, BigDecimal.valueOf(amount));
}
/**
*
*/
public boolean hasSufficientBalance(Long aid, BigDecimal amount) {
Optional<Account> accountOpt = accountRepository.findByAid(aid);
return accountOpt.map(account ->
"active".equals(account.getAstatus()) &&
account.getAbalance().compareTo(amount) >= 0
).orElse(false);
}
/**
*
*/
@Transactional
public boolean transfer(Long fromAid, Long toAid, BigDecimal amount) {
Optional<Account> fromAccountOpt = accountRepository.findByAid(fromAid);
Optional<Account> toAccountOpt = accountRepository.findByAid(toAid);
if (fromAccountOpt.isPresent() && toAccountOpt.isPresent()) {
Account fromAccount = fromAccountOpt.get();
Account toAccount = toAccountOpt.get();
// 检查转出账户状态和余额
if ("active".equals(fromAccount.getAstatus()) &&
"active".equals(toAccount.getAstatus()) &&
fromAccount.getAbalance().compareTo(amount) >= 0) {
// 执行转账
fromAccount.setAbalance(fromAccount.getAbalance().subtract(amount));
toAccount.setAbalance(toAccount.getAbalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
return true;
}
}
return false;
}
/**
*
*/
@Transactional
public boolean transfer(Long fromAid, Long toAid, Double amount) {
if (amount == null || amount <= 0) {
return false;
}
return transfer(fromAid, toAid, BigDecimal.valueOf(amount));
}
/**
*
*/
public boolean updateAccountStatus(Long aid, String newStatus) {
Optional<Account> accountOpt = accountRepository.findByAid(aid);
if (accountOpt.isPresent()) {
Account account = accountOpt.get();
account.setAstatus(newStatus);
accountRepository.save(account);
return true;
}
return false;
}
/**
*
*/
public List<Account> findByAccountType(String accountType) {
return accountRepository.findByAtype(accountType);
}
/**
*
*/
public List<Account> findAccountsWithBalanceGreaterThan(BigDecimal amount) {
return accountRepository.findAccountsWithBalanceGreaterThan(amount);
}
/**
*
*/
public List<Account> findAccountsWithBalanceLessThan(BigDecimal amount) {
return accountRepository.findAccountsWithBalanceLessThan(amount);
}
/**
*
*/
public boolean existsByAid(Long aid) {
return accountRepository.existsByAid(aid);
}
/**
*
*/
public List<Account> findAll() {
return accountRepository.findAll();
}
/**
* ID
*/
public void deleteById(Long aid) {
accountRepository.deleteById(aid);
}
}

@ -0,0 +1,105 @@
package com.atm.service;
import com.atm.config.JwtTokenUtil;
import com.atm.model.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
*
*/
@Service
public class AuthenticationService {
private final AuthenticationManager authenticationManager;
private final JwtTokenUtil jwtTokenUtil;
private final CustomerService customerService;
@Autowired
public AuthenticationService(AuthenticationManager authenticationManager,
JwtTokenUtil jwtTokenUtil,
CustomerService customerService) {
this.authenticationManager = authenticationManager;
this.jwtTokenUtil = jwtTokenUtil;
this.customerService = customerService;
}
/**
* JWT
*/
public Optional<String> authenticateAndGenerateToken(Long cid, String cpin) {
try {
// 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(cid.toString(), cpin);
// 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 认证成功生成JWT令牌
if (authentication.isAuthenticated()) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String token = jwtTokenUtil.generateToken(userDetails);
return Optional.of(token);
}
} catch (BadCredentialsException e) {
// 认证失败
return Optional.empty();
} catch (Exception e) {
// 其他异常
return Optional.empty();
}
return Optional.empty();
}
/**
* JWT
*/
public boolean validateToken(String token) {
try {
String username = jwtTokenUtil.getUsernameFromToken(token);
UserDetails userDetails = customerService.loadUserByUsername(username);
return jwtTokenUtil.validateToken(token, userDetails);
} catch (Exception e) {
return false;
}
}
/**
*
*/
public String getUsernameFromToken(String token) {
try {
return jwtTokenUtil.getUsernameFromToken(token);
} catch (Exception e) {
return null;
}
}
/**
*
*/
public Optional<String> refreshToken(String token) {
try {
String username = jwtTokenUtil.getUsernameFromToken(token);
UserDetails userDetails = customerService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
String newToken = jwtTokenUtil.generateToken(userDetails);
return Optional.of(newToken);
}
} catch (Exception e) {
return Optional.empty();
}
return Optional.empty();
}
}

@ -0,0 +1,171 @@
package com.atm.service;
import com.atm.model.Customer;
import com.atm.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
*
* UserDetailsServiceSpring Security
*/
@Service
@Transactional
public class CustomerService implements UserDetailsService {
private final CustomerRepository customerRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public CustomerService(CustomerRepository customerRepository, PasswordEncoder passwordEncoder) {
this.customerRepository = customerRepository;
this.passwordEncoder = passwordEncoder;
}
/**
* ID
*/
public Optional<Customer> findByCid(Long cid) {
return customerRepository.findByCid(cid);
}
/**
* IDPIN
*/
public Optional<Customer> authenticate(Long cid, String cpin) {
Optional<Customer> customerOpt = customerRepository.findByCid(cid);
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
// 使用密码编码器验证PIN码
if (passwordEncoder.matches(cpin, customer.getCpin())) {
return Optional.of(customer);
}
}
return Optional.empty();
}
/**
*
*/
public Customer createCustomer(Customer customer) {
// 加密PIN码
customer.setCpin(passwordEncoder.encode(customer.getCpin()));
return customerRepository.save(customer);
}
/**
*
*/
public Customer updateCustomer(Customer customer) {
return customerRepository.save(customer);
}
/**
* PIN
*/
public boolean updatePin(Long cid, String oldPin, String newPin) {
Optional<Customer> customerOpt = customerRepository.findByCid(cid);
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
// 验证旧PIN码
if (passwordEncoder.matches(oldPin, customer.getCpin())) {
customer.setCpin(passwordEncoder.encode(newPin));
customerRepository.save(customer);
return true;
}
}
return false;
}
/**
*
*/
public List<Customer> findByStatus(String status) {
return customerRepository.findByCstatus(status);
}
/**
* ID
*/
public boolean existsByCid(Long cid) {
return customerRepository.existsByCid(cid);
}
/**
*
*/
public List<Customer> findAll() {
return customerRepository.findAll();
}
/**
* ID
*/
public void deleteById(Long cid) {
customerRepository.deleteById(cid);
}
/**
*
*/
public boolean deleteCustomer(Long cid) {
if (customerRepository.existsByCid(cid)) {
customerRepository.deleteById(cid);
return true;
}
return false;
}
/**
* PIN
*/
public boolean resetPin(Long cid, String newPin) {
Optional<Customer> customerOpt = customerRepository.findByCid(cid);
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
customer.setCpin(passwordEncoder.encode(newPin));
customerRepository.save(customer);
return true;
}
return false;
}
/**
*
*/
public boolean updateCustomerStatus(Long cid, String status) {
Optional<Customer> customerOpt = customerRepository.findByCid(cid);
if (customerOpt.isPresent()) {
Customer customer = customerOpt.get();
customer.setCstatus(status);
customerRepository.save(customer);
return true;
}
return false;
}
/**
* UserDetailsService
* Spring Security
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Long cid;
try {
cid = Long.parseLong(username);
} catch (NumberFormatException e) {
throw new UsernameNotFoundException("无效的客户ID: " + username);
}
return customerRepository.findByCid(cid)
.orElseThrow(() -> new UsernameNotFoundException("未找到客户ID: " + username));
}
}

@ -0,0 +1,248 @@
package com.atm.service;
import com.atm.model.Account;
import com.atm.model.Transaction;
import com.atm.repository.TransactionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
*
*/
@Service
@Transactional
public class TransactionService {
private final TransactionRepository transactionRepository;
private final AccountService accountService;
@Autowired
public TransactionService(TransactionRepository transactionRepository, AccountService accountService) {
this.transactionRepository = transactionRepository;
this.accountService = accountService;
}
/**
* ID
*/
public Optional<Transaction> findByTid(Long tid) {
return transactionRepository.findByTid(tid);
}
/**
*
*/
public Transaction createDepositTransaction(Long accountId, BigDecimal amount, String description) {
Optional<Account> accountOpt = accountService.findByAid(accountId);
if (accountOpt.isPresent() && amount.compareTo(BigDecimal.ZERO) > 0) {
Account account = accountOpt.get();
// 创建交易记录
Transaction transaction = new Transaction();
transaction.setFromAccount(account);
transaction.setToAccount(account); // 存款是同一账户
transaction.setTtype("deposit");
transaction.setTamount(amount);
transaction.setTdescription(description != null ? description : "存款");
transaction.setTstatus("completed");
return transactionRepository.save(transaction);
}
return null;
}
/**
*
*/
public Transaction createWithdrawalTransaction(Long accountId, BigDecimal amount, String description) {
Optional<Account> accountOpt = accountService.findByAid(accountId);
if (accountOpt.isPresent() && amount.compareTo(BigDecimal.ZERO) > 0) {
Account account = accountOpt.get();
// 创建交易记录
Transaction transaction = new Transaction();
transaction.setFromAccount(account);
transaction.setToAccount(account); // 取款是同一账户
transaction.setTtype("withdrawal");
transaction.setTamount(amount);
transaction.setTdescription(description != null ? description : "取款");
transaction.setTstatus("completed");
return transactionRepository.save(transaction);
}
return null;
}
/**
*
*/
public Transaction createTransferTransaction(Long fromAccountId, Long toAccountId, BigDecimal amount, String description) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
return null;
}
Optional<Account> fromAccountOpt = accountService.findByAid(fromAccountId);
Optional<Account> toAccountOpt = accountService.findByAid(toAccountId);
if (fromAccountOpt.isPresent() && toAccountOpt.isPresent()) {
Account fromAccount = fromAccountOpt.get();
Account toAccount = toAccountOpt.get();
// 创建交易记录
Transaction transaction = new Transaction();
transaction.setFromAccount(fromAccount);
transaction.setToAccount(toAccount);
transaction.setTtype("transfer");
transaction.setTamount(amount);
transaction.setTdescription(description != null ? description : "转账");
transaction.setTstatus("completed");
return transactionRepository.save(transaction);
}
return null;
}
/**
* ID
*/
public List<Transaction> findByAid(Long accountId) {
return transactionRepository.findByAccountId(accountId);
}
/**
* ID
*/
public List<Transaction> findByCid(Long cid) {
return transactionRepository.findByCustomerId(cid);
}
/**
*
*/
public List<Transaction> findByTtype(String transactionType) {
return transactionRepository.findByTtype(transactionType);
}
/**
*
*/
public List<Transaction> findByTransactionStatus(String transactionStatus) {
return transactionRepository.findByTstatus(transactionStatus);
}
/**
*
*/
public List<Transaction> findByDateRange(LocalDateTime startTime, LocalDateTime endTime) {
return transactionRepository.findByDateRange(startTime, endTime);
}
/**
* ID
*/
public List<Transaction> findByCustomerIdAndDateRange(Long cid, LocalDateTime startTime, LocalDateTime endTime) {
return transactionRepository.findByCustomerIdAndDateRange(cid, startTime, endTime);
}
/**
*
*/
public List<Transaction> findTransactionsWithAmountGreaterThan(BigDecimal amount) {
return transactionRepository.findTransactionsWithAmountGreaterThan(amount);
}
/**
* N
*/
public List<Transaction> findRecentTransactionsByAccountId(Long accountId, int limit) {
Pageable pageable = PageRequest.of(0, limit);
return transactionRepository.findRecentTransactionsByAccountId(accountId, pageable);
}
/**
*
*/
public Transaction createTransaction(Transaction transaction) {
return transactionRepository.save(transaction);
}
/**
*
*/
public boolean updateTransactionStatus(Long tid, String newStatus) {
Optional<Transaction> transactionOpt = transactionRepository.findByTid(tid);
if (transactionOpt.isPresent()) {
Transaction transaction = transactionOpt.get();
transaction.setTstatus(newStatus);
transactionRepository.save(transaction);
return true;
}
return false;
}
/**
*
*/
public List<Transaction> findAll() {
return transactionRepository.findAll();
}
/**
* ID
*/
public void deleteById(Long tid) {
transactionRepository.deleteById(tid);
}
/**
*
*/
public List<Transaction> findByAccountAndDateRange(Long accountId, LocalDateTime startTime, LocalDateTime endTime) {
return transactionRepository.findByAccountIdAndDateRange(accountId, startTime, endTime);
}
/**
*
*/
public List<Transaction> findByCustomerAndDateRange(Long customerId, LocalDateTime startTime, LocalDateTime endTime) {
return transactionRepository.findByCustomerIdAndDateRange(customerId, startTime, endTime);
}
/**
* N
*/
public List<Transaction> findRecentTransactionsByAccount(Long accountId, int limit) {
Pageable pageable = PageRequest.of(0, limit);
return transactionRepository.findRecentTransactionsByAccountId(accountId, pageable);
}
/**
* N
*/
public List<Transaction> findRecentTransactionsByCustomer(Long customerId, int limit) {
Pageable pageable = PageRequest.of(0, limit);
return transactionRepository.findRecentTransactionsByCustomerId(customerId, pageable);
}
/**
*
*/
public List<Transaction> findByAmountAbove(Double amount) {
return transactionRepository.findTransactionsWithAmountGreaterThan(BigDecimal.valueOf(amount));
}
/**
*
*/
public List<Transaction> findByAmountBelow(Double amount) {
return transactionRepository.findTransactionsWithAmountLessThan(BigDecimal.valueOf(amount));
}
}

@ -0,0 +1,94 @@
package com.atm.view.gui;
import com.atm.model.Customer;
import com.atm.dao.Login;
import javax.swing.JOptionPane;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import com.atm.controller.Validate;
public class Gui extends JFrame {
private JButton jButtonLogin;
private JLabel jLabelCid;
private JLabel jLabelCpin;
private JPanel jPanel;
private JPanel jPanelBtn;
private JPanel jPanelCid;
private JPanel jPanelCpin;
private JTextField jTextFieldCid;
private JPasswordField jTextFieldCpin;
public Gui() {
this.setTitle("GUI ATM");
this.setSize(300, 200);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
jPanel = new JPanel(new GridLayout(3, 1));
jPanelCid = new JPanel();
jLabelCid = new JLabel("CID");
jLabelCid.setForeground(Color.RED);
jLabelCid.setFont(new Font("", Font.BOLD, 15));
jTextFieldCid = new JTextField(15);
jPanelCid.add(jLabelCid);
jPanelCid.add(jTextFieldCid);
jPanelCpin = new JPanel();
jLabelCpin = new JLabel("CPIN");
jLabelCpin.setForeground(Color.RED);
jLabelCpin.setFont(new Font("", Font.BOLD, 15));
jTextFieldCpin = new JPasswordField(15);
jPanelCpin.add(jLabelCpin);
jPanelCpin.add(jTextFieldCpin);
jPanelBtn = new JPanel();
jButtonLogin = new JButton("LOGIN");
jPanelBtn.add(jButtonLogin);
jPanel.add(jPanelCid);
jPanel.add(jPanelCpin);
jPanel.add(jPanelBtn);
this.add(jPanel);
this.setVisible(true);
jButtonLogin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String cid = new String(jTextFieldCid.getText());
if (cid.length() <= 0) {
JOptionPane.showMessageDialog(null, "CID NULL");
System.out.println("CID NULL");
} else {
String cpin = new String(jTextFieldCpin.getPassword());
Customer c = new Customer(cid, cpin);
boolean isLogin = false;
if (Validate.lengthValidate(cid) && Validate.lengthValidate(cpin)
&& Validate.isNumeric(cid) && Validate.isNumeric(cpin))
isLogin = new Login().login(c);
if (isLogin) {
JOptionPane.showMessageDialog(null, "LOGIN SUCCEEDED!", "PROMPT",
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "ID OR PIN ERROR!");
}
}
}
});
}
public static void main(String[] args) {
new Gui();
}
}// end Gui

@ -0,0 +1,77 @@
package com.atm.view.gui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestGuiFrame extends JFrame {
private JButton jButtonLogin;
private JLabel jLabelCid;
private JLabel jLabelCpin;
private JPanel jPanel;
private JPanel jPanelBtn;
private JPanel jPanelCid;
private JPanel jPanelCpin;
private JTextField jTextFieldCid;
private JPasswordField jTextFieldCpin;
public TestGuiFrame() {
this.setTitle("Test ATM GUI");
this.setSize(300, 200);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
jPanel = new JPanel(new GridLayout(3, 1));
jPanelCid = new JPanel();
jLabelCid = new JLabel("CID");
jLabelCid.setForeground(Color.RED);
jLabelCid.setFont(new Font("", Font.BOLD, 15));
jTextFieldCid = new JTextField(15);
jPanelCid.add(jLabelCid);
jPanelCid.add(jTextFieldCid);
jPanelCpin = new JPanel();
jLabelCpin = new JLabel("CPIN");
jLabelCpin.setForeground(Color.RED);
jLabelCpin.setFont(new Font("", Font.BOLD, 15));
jTextFieldCpin = new JPasswordField(15);
jPanelCpin.add(jLabelCpin);
jPanelCpin.add(jTextFieldCpin);
jPanelBtn = new JPanel();
jButtonLogin = new JButton("LOGIN");
jPanelBtn.add(jButtonLogin);
jPanel.add(jPanelCid);
jPanel.add(jPanelCpin);
jPanel.add(jPanelBtn);
this.add(jPanel);
jButtonLogin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String cid = new String(jTextFieldCid.getText());
if (cid.length() <= 0) {
JOptionPane.showMessageDialog(null, "CID NULL");
System.out.println("CID NULL");
} else {
String cpin = new String(jTextFieldCpin.getPassword());
// For testing, just show the input
JOptionPane.showMessageDialog(null, "CID: " + cid + "\nCPIN: " + cpin, "Test Input", JOptionPane.INFORMATION_MESSAGE);
}
}
});
this.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestGuiFrame();
}
});
}
}

@ -0,0 +1,21 @@
# 开发环境配置
spring:
# 开发环境使用SQLite数据库
datasource:
url: jdbc:sqlite:atm_dev.db
driver-class-name: org.sqlite.JDBC
# JPA配置
jpa:
database-platform: org.hibernate.community.dialect.SQLiteDialect
hibernate:
ddl-auto: update
show-sql: true
# 日志配置
logging:
level:
com.atm: DEBUG
org.springframework.security: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE

@ -0,0 +1,22 @@
# 生产环境配置
spring:
# 生产环境使用PostgreSQL数据库
datasource:
url: jdbc:postgresql://116.204.84.48:5432/seb
username: postgres
password: gitops123
driver-class-name: org.postgresql.Driver
# JPA配置
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: validate
show-sql: false
# 日志配置
logging:
level:
com.atm: INFO
org.springframework.security: WARN
org.hibernate.SQL: WARN

@ -0,0 +1,52 @@
# Spring Boot应用配置
server:
port: 8080
servlet:
context-path: /api
spring:
profiles:
active: dev
application:
name: atm-system
# 数据源配置
datasource:
url: jdbc:postgresql://116.204.84.48:5432/seb
username: postgres
password: gitops123
driver-class-name: org.postgresql.Driver
hikari:
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 10
minimum-idle: 5
# JPA配置
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: none
show-sql: false
properties:
hibernate:
format_sql: true
# JSON配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# 日志配置
logging:
level:
com.atm: DEBUG
org.springframework.security: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# JWT配置
jwt:
secret: atmSecretKeyForJWTTokenGeneration
expiration: 86400000 # 24小时

@ -0,0 +1,300 @@
package com.atm.controller;
import com.atm.model.Account;
import com.atm.model.Customer;
import com.atm.service.AccountService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(AccountController.class)
public class AccountControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AccountService accountService;
@Autowired
private ObjectMapper objectMapper;
private Customer testCustomer;
private Account testAccount;
private Account testAccount2;
@BeforeEach
public void setUp() {
testCustomer = new Customer();
testCustomer.setCid(12345L);
testCustomer.setCname("张三");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("1000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
testAccount = new Account();
testAccount.setAid(1L);
testAccount.setCustomer(testCustomer);
testAccount.setAtype("savings");
testAccount.setAbalance(new BigDecimal("5000.00"));
testAccount.setAstatus("active");
testAccount2 = new Account();
testAccount2.setAid(2L);
testAccount.setCustomer(testCustomer);
testAccount2.setAtype("checking");
testAccount2.setAbalance(new BigDecimal("2000.00"));
testAccount2.setAstatus("active");
}
@Test
public void whenGetAccountById_thenReturnAccount() throws Exception {
// given
when(accountService.findByAid(1L)).thenReturn(Optional.of(testAccount));
// when & then
mockMvc.perform(get("/api/accounts/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.aid").value(1))
.andExpect(jsonPath("$.atype").value("savings"))
.andExpect(jsonPath("$.abalance").value(5000.00));
}
@Test
public void whenGetNonExistingAccountById_thenReturnNotFound() throws Exception {
// given
when(accountService.findByAid(999L)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(get("/api/accounts/999"))
.andExpect(status().isNotFound());
}
@Test
public void whenGetAccountsByCustomerId_thenReturnAccounts() throws Exception {
// given
List<Account> accounts = Arrays.asList(testAccount, testAccount2);
when(accountService.findByCid(12345L)).thenReturn(accounts);
// when & then
mockMvc.perform(get("/api/accounts/customer/12345"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$.length()").value(2));
}
@Test
public void whenCreateAccount_thenReturnCreatedAccount() throws Exception {
// given
Account newAccount = new Account();
newAccount.setCustomer(testCustomer);
newAccount.setAtype("investment");
newAccount.setAbalance(new BigDecimal("10000.00"));
newAccount.setAstatus("active");
when(accountService.createAccount(any(Account.class))).thenReturn(newAccount);
// when & then
mockMvc.perform(post("/api/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(newAccount)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.atype").value("investment"))
.andExpect(jsonPath("$.abalance").value(10000.00));
}
@Test
public void whenDeposit_thenReturnUpdatedAccount() throws Exception {
// given
BigDecimal depositAmount = new BigDecimal("1000.00");
Account updatedAccount = new Account();
updatedAccount.setAid(1L);
updatedAccount.setCustomer(testCustomer);
updatedAccount.setAtype("savings");
updatedAccount.setAbalance(new BigDecimal("6000.00"));
updatedAccount.setAstatus("active");
when(accountService.deposit(1L, depositAmount)).thenReturn(Optional.of(updatedAccount));
// when & then
mockMvc.perform(post("/api/accounts/1/deposit")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"amount\":1000.00}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.aid").value(1))
.andExpect(jsonPath("$.abalance").value(6000.00));
}
@Test
public void whenDepositToNonExistingAccount_thenReturnNotFound() throws Exception {
// given
BigDecimal depositAmount = new BigDecimal("1000.00");
when(accountService.deposit(999L, depositAmount)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(post("/api/accounts/999/deposit")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"amount\":1000.00}"))
.andExpect(status().isNotFound());
}
@Test
public void whenWithdraw_thenReturnUpdatedAccount() throws Exception {
// given
BigDecimal withdrawAmount = new BigDecimal("1000.00");
Account updatedAccount = new Account();
updatedAccount.setAid(1L);
updatedAccount.setCustomer(testCustomer);
updatedAccount.setAtype("savings");
updatedAccount.setAbalance(new BigDecimal("4000.00"));
updatedAccount.setAstatus("active");
when(accountService.withdraw(1L, withdrawAmount)).thenReturn(Optional.of(updatedAccount));
// when & then
mockMvc.perform(post("/api/accounts/1/withdraw")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"amount\":1000.00}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.aid").value(1))
.andExpect(jsonPath("$.abalance").value(4000.00));
}
@Test
public void whenWithdrawFromNonExistingAccount_thenReturnNotFound() throws Exception {
// given
BigDecimal withdrawAmount = new BigDecimal("1000.00");
when(accountService.withdraw(999L, withdrawAmount)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(post("/api/accounts/999/withdraw")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"amount\":1000.00}"))
.andExpect(status().isNotFound());
}
@Test
public void whenWithdrawWithInsufficientBalance_thenReturnBadRequest() throws Exception {
// given
BigDecimal withdrawAmount = new BigDecimal("10000.00");
when(accountService.withdraw(1L, withdrawAmount)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(post("/api/accounts/1/withdraw")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"amount\":10000.00}"))
.andExpect(status().isBadRequest());
}
@Test
public void whenTransfer_thenReturnSuccess() throws Exception {
// given
BigDecimal transferAmount = new BigDecimal("1000.00");
when(accountService.transfer(1L, 2L, transferAmount)).thenReturn(true);
// when & then
mockMvc.perform(post("/api/accounts/1/transfer")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"toAccountId\":2,\"amount\":1000.00}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("转账成功"));
}
@Test
public void whenTransferWithInsufficientBalance_thenReturnBadRequest() throws Exception {
// given
BigDecimal transferAmount = new BigDecimal("10000.00");
when(accountService.transfer(1L, 2L, transferAmount)).thenReturn(false);
// when & then
mockMvc.perform(post("/api/accounts/1/transfer")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"toAccountId\":2,\"amount\":10000.00}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.message").value("转账失败,余额不足"));
}
@Test
public void whenTransferToNonExistingAccount_thenReturnBadRequest() throws Exception {
// given
BigDecimal transferAmount = new BigDecimal("1000.00");
when(accountService.transfer(1L, 999L, transferAmount)).thenReturn(false);
// when & then
mockMvc.perform(post("/api/accounts/1/transfer")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"toAccountId\":999,\"amount\":1000.00}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.message").value("转账失败,余额不足"));
}
@Test
public void whenGetBalance_thenReturnBalance() throws Exception {
// given
when(accountService.findByAid(1L)).thenReturn(Optional.of(testAccount));
// when & then
mockMvc.perform(get("/api/accounts/1/balance"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.aid").value(1))
.andExpect(jsonPath("$.balance").value(5000.00));
}
@Test
public void whenGetBalanceForNonExistingAccount_thenReturnNotFound() throws Exception {
// given
when(accountService.findByAid(999L)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(get("/api/accounts/999/balance"))
.andExpect(status().isNotFound());
}
@Test
public void whenUpdateAccountStatus_thenReturnSuccess() throws Exception {
// given
when(accountService.updateAccountStatus(1L, "inactive")).thenReturn(true);
// when & then
mockMvc.perform(put("/api/accounts/1/status")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"status\":\"inactive\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("账户状态更新成功"));
}
@Test
public void whenUpdateStatusForNonExistingAccount_thenReturnNotFound() throws Exception {
// given
when(accountService.updateAccountStatus(999L, "inactive")).thenReturn(false);
// when & then
mockMvc.perform(put("/api/accounts/999/status")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"status\":\"inactive\"}"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.message").value("账户不存在"));
}
}

@ -0,0 +1,217 @@
package com.atm.controller;
import com.atm.model.Customer;
import com.atm.service.AuthenticationService;
import com.atm.service.CustomerService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.test.web.servlet.MockMvc;
import java.math.BigDecimal;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(AuthController.class)
public class AuthControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AuthenticationService authenticationService;
@MockBean
private CustomerService customerService;
@MockBean
private AuthenticationManager authenticationManager;
@Autowired
private ObjectMapper objectMapper;
private Customer testCustomer;
@BeforeEach
public void setUp() {
testCustomer = new Customer();
testCustomer.setCid(12345L);
testCustomer.setCname("张三");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("1000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
}
@Test
public void whenLogin_thenReturnJwtToken() throws Exception {
// given
String mockToken = "mock.jwt.token";
when(authenticationService.authenticate(12345L, "123456")).thenReturn(mockToken);
// when & then
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"cid\":12345,\"cpin\":\"123456\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").value(mockToken))
.andExpect(jsonPath("$.type").value("Bearer"))
.andExpect(jsonPath("$.cid").value(12345));
}
@Test
public void whenValidateToken_thenReturnTrue() throws Exception {
// given
String token = "valid.jwt.token";
when(authenticationService.validateToken(token)).thenReturn(true);
when(authenticationService.getUsernameFromToken(token)).thenReturn("12345");
// when & then
mockMvc.perform(post("/api/auth/validate")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"token\":\"" + token + "\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.valid").value(true))
.andExpect(jsonPath("$.username").value("12345"));
}
@Test
public void whenValidateInvalidToken_thenReturnFalse() throws Exception {
// given
String token = "invalid.jwt.token";
when(authenticationService.validateToken(token)).thenReturn(false);
// when & then
mockMvc.perform(post("/api/auth/validate")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"token\":\"" + token + "\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.valid").value(false));
}
@Test
public void whenRefreshToken_thenReturnNewToken() throws Exception {
// given
String oldToken = "old.jwt.token";
String newToken = "new.jwt.token";
when(authenticationService.refreshToken(oldToken)).thenReturn(newToken);
// when & then
mockMvc.perform(post("/api/auth/refresh")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"token\":\"" + oldToken + "\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").value(newToken))
.andExpect(jsonPath("$.type").value("Bearer"));
}
@Test
public void whenRegister_thenReturnCreatedCustomer() throws Exception {
// given
Customer newCustomer = new Customer();
newCustomer.setCid(67890L);
newCustomer.setCname("李四");
newCustomer.setCpin("654321");
newCustomer.setCbalance(new BigDecimal("500.00"));
newCustomer.setCstatus("active");
newCustomer.setCtype("regular");
when(customerService.createCustomer(any(Customer.class))).thenReturn(newCustomer);
// when & then
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(newCustomer)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.cid").value(67890))
.andExpect(jsonPath("$.cname").value("李四"));
}
@Test
public void whenRegisterWithInvalidData_thenReturnBadRequest() throws Exception {
// given
Customer invalidCustomer = new Customer();
invalidCustomer.setCname(""); // Invalid: empty name
// when & then
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(invalidCustomer)))
.andExpect(status().isBadRequest());
}
@Test
public void whenChangePinWithValidData_thenReturnSuccess() throws Exception {
// given
when(customerService.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
when(customerService.updatePin(12345L, "123456", "654321")).thenReturn(true);
// when & then
mockMvc.perform(put("/api/auth/change-pin")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"cid\":12345,\"oldPin\":\"123456\",\"newPin\":\"654321\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("PIN码修改成功"));
}
@Test
public void whenChangePinWithInvalidOldPin_thenReturnError() throws Exception {
// given
when(customerService.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
when(customerService.updatePin(12345L, "wrongpin", "654321")).thenReturn(false);
// when & then
mockMvc.perform(put("/api/auth/change-pin")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"cid\":12345,\"oldPin\":\"wrongpin\",\"newPin\":\"654321\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.message").value("原PIN码不正确"));
}
@Test
public void whenChangePinForNonExistingCustomer_thenReturnError() throws Exception {
// given
when(customerService.findByCid(99999L)).thenReturn(Optional.empty());
// when & then
mockMvc.perform(put("/api/auth/change-pin")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"cid\":99999,\"oldPin\":\"123456\",\"newPin\":\"654321\"}"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.message").value("客户不存在"));
}
@Test
public void whenLogout_thenReturnSuccess() throws Exception {
// given
String token = "mock.jwt.token";
when(authenticationService.getUsernameFromToken(token)).thenReturn("12345");
// when & then
mockMvc.perform(post("/api/auth/logout")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("登出成功"));
}
@Test
public void whenLogoutWithoutToken_thenReturnUnauthorized() throws Exception {
// when & then
mockMvc.perform(post("/api/auth/logout"))
.andExpect(status().isUnauthorized());
}
}

@ -0,0 +1,55 @@
package com.atm.controller;
import org.junit.Assert;
import com.atm.controller.Validate;
public class ValidateTest extends junit.framework.TestCase {
/**
*
* @param arg0
*/
public ValidateTest(String arg0) {
super(arg0);
}
/**
*
* @param args
*/
public static void main(String[] args) {
}
/**
*
* @exception Exception
*/
protected void setUp()
throws Exception {
super.setUp();
}
/**
*
* @exception Exception
*/
protected void tearDown()
throws Exception {
super.tearDown();
}
public final void testIsNumeric() {
Assert.assertTrue(Validate.isNumeric("123456"));
}
public final void testIsNumeric_Failed() {
Assert.assertFalse(Validate.isNumeric("12345d"));
}
public final void testLengthValidate() {
Assert.assertTrue(Validate.lengthValidate("123456"));
}
public final void testLengthValidate_Failed() {
Assert.assertFalse(Validate.lengthValidate("1234567"));
}
}// end ValidateTest

@ -0,0 +1,16 @@
package com.atm.dao;
import org.junit.runners.Suite;
import org.junit.runner.RunWith;
import junit.framework.TestSuite;
import junit.framework.Test;
@RunWith(Suite.class)
@Suite.SuiteClasses({ com.atm.dao.LoginTest.class, com.atm.controller.ValidateTest.class })
public class LoginIntegratedTest {
//public static Test suit() {
// TestSuite suite = new TestSuite();
// return suite;
//}
}// end LoginITest

@ -0,0 +1,49 @@
package com.atm.dao;
import org.junit.Assert;
import com.atm.dao.Login;
import com.atm.model.Customer;
public class LoginTest extends junit.framework.TestCase {
/**
*
* @param arg0
*/
public LoginTest(String arg0) {
super(arg0);
}
/**
*
* @param args
*/
public static void main(String[] args) {
}
/**
*
* @exception Exception
*/
protected void setUp()
throws Exception {
super.setUp();
}
/**
*
* @exception Exception
*/
protected void tearDown()
throws Exception {
super.tearDown();
}
public final void testLogin() {
Assert.assertTrue(new Login().login(new Customer("123456", "123456")));
}
public final void testLogin_failed() {
Assert.assertFalse(new Login().login(new Customer("123457", "123458")));
}
}// end LoginTest

@ -0,0 +1,235 @@
package com.atm.integration;
import com.atm.model.Account;
import com.atm.model.Customer;
import com.atm.model.Transaction;
import com.atm.repository.AccountRepository;
import com.atm.repository.CustomerRepository;
import com.atm.repository.TransactionRepository;
import com.atm.service.AccountService;
import com.atm.service.CustomerService;
import com.atm.service.TransactionService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
@Transactional
public class ATMSystemIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransactionRepository transactionRepository;
@Autowired
private CustomerService customerService;
@Autowired
private AccountService accountService;
@Autowired
private TransactionService transactionService;
private Customer testCustomer;
private Account testAccount1;
private Account testAccount2;
@BeforeEach
public void setUp() {
// 创建测试客户
testCustomer = new Customer();
testCustomer.setCid(System.currentTimeMillis()); // 使用时间戳确保唯一性
testCustomer.setCname("测试用户");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("10000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
testCustomer = customerRepository.save(testCustomer);
// 创建测试账户1
testAccount1 = new Account();
testAccount1.setCustomer(testCustomer);
testAccount1.setAtype("savings");
testAccount1.setAbalance(new BigDecimal("5000.00"));
testAccount1.setAstatus("active");
testAccount1 = accountRepository.save(testAccount1);
// 创建测试账户2
testAccount2 = new Account();
testAccount2.setCustomer(testCustomer);
testAccount2.setAtype("checking");
testAccount2.setAbalance(new BigDecimal("3000.00"));
testAccount2.setAstatus("active");
testAccount2 = accountRepository.save(testAccount2);
}
@Test
public void testCustomerAccountTransactionFlow() {
// 1. 验证客户创建成功
Optional<Customer> foundCustomer = customerService.findByCid(testCustomer.getCid());
assertTrue(foundCustomer.isPresent());
assertEquals("测试用户", foundCustomer.get().getCname());
// 2. 验证账户创建成功
List<Account> accounts = accountService.findByCid(testCustomer.getCid());
assertEquals(2, accounts.size());
// 3. 执行存款操作
BigDecimal depositAmount = new BigDecimal("1000.00");
Optional<Account> updatedAccount1 = accountService.deposit(testAccount1.getAid(), depositAmount);
assertTrue(updatedAccount1.isPresent());
assertEquals(new BigDecimal("6000.00"), updatedAccount1.get().getAbalance());
// 4. 验证存款交易记录
Transaction depositTransaction = transactionService.createDepositTransaction(
testAccount1.getAid(), depositAmount, "测试存款");
assertNotNull(depositTransaction);
assertEquals("deposit", depositTransaction.getTtype());
assertEquals(depositAmount, depositTransaction.getTamount());
// 5. 执行取款操作
BigDecimal withdrawAmount = new BigDecimal("500.00");
Optional<Account> updatedAccount2 = accountService.withdraw(testAccount2.getAid(), withdrawAmount);
assertTrue(updatedAccount2.isPresent());
assertEquals(new BigDecimal("2500.00"), updatedAccount2.get().getAbalance());
// 6. 验证取款交易记录
Transaction withdrawTransaction = transactionService.createWithdrawalTransaction(
testAccount2.getAid(), withdrawAmount, "测试取款");
assertNotNull(withdrawTransaction);
assertEquals("withdrawal", withdrawTransaction.getTtype());
assertEquals(withdrawAmount, withdrawTransaction.getTamount());
// 7. 执行转账操作
BigDecimal transferAmount = new BigDecimal("1000.00");
boolean transferResult = accountService.transfer(testAccount1.getAid(), testAccount2.getAid(), transferAmount);
assertTrue(transferResult);
// 验证转账后账户余额
Optional<Account> fromAccount = accountService.findByAid(testAccount1.getAid());
Optional<Account> toAccount = accountService.findByAid(testAccount2.getAid());
assertTrue(fromAccount.isPresent());
assertTrue(toAccount.isPresent());
assertEquals(new BigDecimal("5000.00"), fromAccount.get().getAbalance());
assertEquals(new BigDecimal("3500.00"), toAccount.get().getAbalance());
// 8. 验证转账交易记录
Transaction transferTransaction = transactionService.createTransferTransaction(
testAccount1.getAid(), testAccount2.getAid(), transferAmount, "测试转账");
assertNotNull(transferTransaction);
assertEquals("transfer", transferTransaction.getTtype());
assertEquals(transferAmount, transferTransaction.getTamount());
// 9. 查询客户的所有交易记录
List<Transaction> customerTransactions = transactionService.findByCid(testCustomer.getCid());
assertEquals(3, customerTransactions.size());
// 10. 查询账户的所有交易记录
List<Transaction> account1Transactions = transactionService.findByAid(testAccount1.getAid());
List<Transaction> account2Transactions = transactionService.findByAid(testAccount2.getAid());
assertEquals(2, account1Transactions.size()); // 存款 + 转账
assertEquals(2, account2Transactions.size()); // 取款 + 转账
}
@Test
public void testAccountStatusUpdate() {
// 更新账户状态为inactive
boolean updateResult = accountService.updateAccountStatus(testAccount1.getAid(), "inactive");
assertTrue(updateResult);
// 验证状态更新
Optional<Account> updatedAccount = accountService.findByAid(testAccount1.getAid());
assertTrue(updatedAccount.isPresent());
assertEquals("inactive", updatedAccount.get().getAstatus());
// 验证无法对inactive账户进行操作
BigDecimal depositAmount = new BigDecimal("100.00");
Optional<Account> depositResult = accountService.deposit(testAccount1.getAid(), depositAmount);
assertFalse(depositResult.isPresent());
}
@Test
public void testCustomerPinUpdate() {
// 更新客户PIN码
boolean updateResult = customerService.updatePin(testCustomer.getCid(), "123456", "654321");
assertTrue(updateResult);
// 验证PIN码更新
Optional<Customer> updatedCustomer = customerService.findByCid(testCustomer.getCid());
assertTrue(updatedCustomer.isPresent());
// 注意由于PIN码是加密存储的我们无法直接比较但可以通过认证来验证
// 在实际应用中,应该使用专门的认证方法来验证
}
@Test
public void testTransactionHistoryQuery() {
// 创建一些交易记录
transactionService.createDepositTransaction(testAccount1.getAid(), new BigDecimal("100.00"), "测试存款1");
transactionService.createDepositTransaction(testAccount1.getAid(), new BigDecimal("200.00"), "测试存款2");
transactionService.createWithdrawalTransaction(testAccount2.getAid(), new BigDecimal("50.00"), "测试取款1");
// 查询账户1的交易记录
List<Transaction> account1Transactions = transactionService.findByAid(testAccount1.getAid());
assertEquals(2, account1Transactions.size());
// 查询账户2的交易记录
List<Transaction> account2Transactions = transactionService.findByAid(testAccount2.getAid());
assertEquals(1, account2Transactions.size());
// 查询客户的交易记录
List<Transaction> customerTransactions = transactionService.findByCid(testCustomer.getCid());
assertEquals(3, customerTransactions.size());
// 按交易类型查询
List<Transaction> depositTransactions = transactionService.findByTtype("deposit");
assertEquals(2, depositTransactions.size());
List<Transaction> withdrawalTransactions = transactionService.findByTtype("withdrawal");
assertEquals(1, withdrawalTransactions.size());
}
@Test
public void testAccountBalanceQueries() {
// 查询余额大于指定金额的账户
List<Account> highBalanceAccounts = accountService.findByAbalanceGreaterThan(new BigDecimal("4000.00"));
assertEquals(1, highBalanceAccounts.size());
assertEquals(testAccount1.getAid(), highBalanceAccounts.get(0).getAid());
// 查询余额小于指定金额的账户
List<Account> lowBalanceAccounts = accountService.findByAbalanceLessThan(new BigDecimal("4000.00"));
assertEquals(1, lowBalanceAccounts.size());
assertEquals(testAccount2.getAid(), lowBalanceAccounts.get(0).getAid());
}
@Test
public void testTransactionDateRangeQuery() {
// 创建一些交易记录
transactionService.createDepositTransaction(testAccount1.getAid(), new BigDecimal("100.00"), "测试存款1");
transactionService.createWithdrawalTransaction(testAccount2.getAid(), new BigDecimal("50.00"), "测试取款1");
// 查询日期范围内的交易记录
LocalDateTime startDate = LocalDateTime.now().minusDays(1);
LocalDateTime endDate = LocalDateTime.now().plusDays(1);
List<Transaction> dateRangeTransactions = transactionService.findByDateRange(startDate, endDate);
assertEquals(2, dateRangeTransactions.size());
}
}

@ -0,0 +1,209 @@
package com.atm.repository;
import com.atm.model.Account;
import com.atm.model.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
@ActiveProfiles("test")
public class AccountRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private AccountRepository accountRepository;
private Customer createTestCustomer(Long cid, String name) {
Customer customer = new Customer();
customer.setCid(cid);
customer.setCname(name);
customer.setCpin("123456");
customer.setCbalance(new BigDecimal("1000.00"));
customer.setCstatus("active");
customer.setCtype("regular");
entityManager.persist(customer);
return customer;
}
private Account createTestAccount(Long aid, Customer customer, String type, BigDecimal balance) {
Account account = new Account();
// 不再设置aid让数据库自动生成
account.setCustomer(customer);
account.setAtype(type);
account.setAbalance(balance);
account.setAstatus("active");
entityManager.persist(account);
entityManager.flush(); // 确保账户被持久化并生成ID
return account;
}
@Test
public void whenFindByAid_thenReturnAccount() {
// given
Customer customer = createTestCustomer(12345L, "张三");
Account account = createTestAccount(null, customer, "checking", new BigDecimal("1000.00"));
entityManager.flush();
entityManager.clear(); // 清除持久化上下文,确保从数据库重新加载
// when
Optional<Account> found = accountRepository.findByAid(account.getAid());
// then
assertTrue(found.isPresent());
assertEquals(account.getAid(), found.get().getAid());
assertEquals("checking", found.get().getAtype());
assertEquals(new BigDecimal("1000.00"), found.get().getAbalance());
}
@Test
public void whenFindByCustomer_thenReturnAccounts() {
// given
Customer customer1 = createTestCustomer(12345L, "张三");
Customer customer2 = createTestCustomer(67890L, "李四");
Account account1 = createTestAccount(null, customer1, "checking", new BigDecimal("1000.00"));
Account account2 = createTestAccount(null, customer1, "savings", new BigDecimal("2000.00"));
Account account3 = createTestAccount(null, customer2, "checking", new BigDecimal("1500.00"));
entityManager.flush();
// when
List<Account> customer1Accounts = accountRepository.findByCustomerCid(customer1.getCid());
List<Account> customer2Accounts = accountRepository.findByCustomerCid(customer2.getCid());
// then
assertEquals(2, customer1Accounts.size());
assertEquals(1, customer2Accounts.size());
}
@Test
public void whenFindByCid_thenReturnAccounts() {
// given
Customer customer1 = createTestCustomer(12345L, "张三");
Customer customer2 = createTestCustomer(67890L, "李四");
createTestAccount(1001L, customer1, "checking", new BigDecimal("1000.00"));
createTestAccount(1002L, customer1, "savings", new BigDecimal("2000.00"));
createTestAccount(1003L, customer2, "checking", new BigDecimal("1500.00"));
entityManager.flush();
// when
List<Account> customer1Accounts = accountRepository.findByCustomerCid(12345L);
List<Account> customer2Accounts = accountRepository.findByCustomerCid(67890L);
// then
assertEquals(2, customer1Accounts.size());
assertEquals(1, customer2Accounts.size());
}
@Test
public void whenFindByAtype_thenReturnAccounts() {
// given
Customer customer1 = createTestCustomer(12345L, "张三");
Customer customer2 = createTestCustomer(67890L, "李四");
createTestAccount(1001L, customer1, "checking", new BigDecimal("1000.00"));
createTestAccount(1002L, customer1, "savings", new BigDecimal("2000.00"));
createTestAccount(1003L, customer2, "checking", new BigDecimal("1500.00"));
entityManager.flush();
// when
List<Account> checkingAccounts = accountRepository.findByAtype("checking");
List<Account> savingsAccounts = accountRepository.findByAtype("savings");
// then
assertEquals(2, checkingAccounts.size());
assertEquals(1, savingsAccounts.size());
}
@Test
public void whenFindByAstatus_thenReturnAccounts() {
// given
Customer customer1 = createTestCustomer(12345L, "张三");
Customer customer2 = createTestCustomer(67890L, "李四");
createTestAccount(1001L, customer1, "checking", new BigDecimal("1000.00"));
Account inactiveAccount = createTestAccount(1002L, customer1, "savings", new BigDecimal("2000.00"));
inactiveAccount.setAstatus("inactive");
entityManager.persist(inactiveAccount);
entityManager.flush();
// when
List<Account> activeAccounts = accountRepository.findByCustomerCidAndAstatus(12345L, "active");
List<Account> inactiveAccounts = accountRepository.findByCustomerCidAndAstatus(12345L, "inactive");
// then
assertEquals(1, activeAccounts.size());
assertEquals(1, inactiveAccounts.size());
}
@Test
public void whenExistsByAid_thenReturnTrue() {
// given
Customer customer = createTestCustomer(12345L, "张三");
createTestAccount(1001L, customer, "checking", new BigDecimal("1000.00"));
entityManager.flush();
// when
boolean exists = accountRepository.existsByAid(1001L);
// then
assertTrue(exists);
}
@Test
public void whenNotExistsByAid_thenReturnFalse() {
// when
boolean exists = accountRepository.existsByAid(9999L);
// then
assertFalse(exists);
}
@Test
public void whenFindByAbalanceGreaterThan_thenReturnAccounts() {
// given
Customer customer1 = createTestCustomer(12345L, "张三");
Customer customer2 = createTestCustomer(67890L, "李四");
Account account1 = createTestAccount(null, customer1, "checking", new BigDecimal("1000.00"));
Account account2 = createTestAccount(null, customer1, "savings", new BigDecimal("2000.00"));
Account account3 = createTestAccount(null, customer2, "checking", new BigDecimal("1500.00"));
entityManager.flush();
// when
List<Account> highBalanceAccounts = accountRepository.findAccountsWithBalanceGreaterThan(new BigDecimal("1500.00"));
// then
assertEquals(1, highBalanceAccounts.size());
assertEquals(account2.getAid(), highBalanceAccounts.get(0).getAid());
}
@Test
public void whenFindByAbalanceLessThan_thenReturnAccounts() {
// given
Customer customer = createTestCustomer(12345L, "张三");
Account account1 = createTestAccount(null, customer, "checking", new BigDecimal("500.00"));
Account account2 = createTestAccount(null, customer, "savings", new BigDecimal("1500.00"));
Account account3 = createTestAccount(null, customer, "investment", new BigDecimal("2000.00"));
entityManager.flush();
// when
List<Account> accounts = accountRepository.findAccountsWithBalanceLessThan(new BigDecimal("1000.00"));
// then
assertEquals(1, accounts.size());
assertTrue(accounts.stream().anyMatch(a -> a.getAid().equals(account1.getAid())));
}
}

@ -0,0 +1,134 @@
package com.atm.repository;
import com.atm.model.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import java.math.BigDecimal;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
@ActiveProfiles("test")
public class CustomerRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private CustomerRepository customerRepository;
@Test
public void whenFindByCid_thenReturnCustomer() {
// given
Customer customer = new Customer();
customer.setCid(12345L);
customer.setCname("张三");
customer.setCpin("123456");
customer.setCbalance(new BigDecimal("1000.00"));
customer.setCstatus("active");
customer.setCtype("regular");
entityManager.persist(customer);
entityManager.flush();
// when
Optional<Customer> found = customerRepository.findByCid(12345L);
// then
assertTrue(found.isPresent());
assertEquals(12345L, found.get().getCid());
assertEquals("张三", found.get().getCname());
}
@Test
public void whenFindByCidAndCpin_thenReturnCustomer() {
// given
Customer customer = new Customer();
customer.setCid(12345L);
customer.setCname("张三");
customer.setCpin("123456");
customer.setCbalance(new BigDecimal("1000.00"));
customer.setCstatus("active");
customer.setCtype("regular");
entityManager.persist(customer);
entityManager.flush();
// when
Optional<Customer> found = customerRepository.findByCidAndCpin(12345L, "123456");
// then
assertTrue(found.isPresent());
assertEquals(12345L, found.get().getCid());
assertEquals("123456", found.get().getCpin());
}
@Test
public void whenExistsByCid_thenReturnTrue() {
// given
Customer customer = new Customer();
customer.setCid(12345L);
customer.setCname("张三");
customer.setCpin("123456");
customer.setCbalance(new BigDecimal("1000.00"));
customer.setCstatus("active");
customer.setCtype("regular");
entityManager.persist(customer);
entityManager.flush();
// when
boolean exists = customerRepository.existsByCid(12345L);
// then
assertTrue(exists);
}
@Test
public void whenNotExistsByCid_thenReturnFalse() {
// when
boolean exists = customerRepository.existsByCid(99999L);
// then
assertFalse(exists);
}
@Test
public void whenFindByCstatus_thenReturnCustomers() {
// given
Customer customer1 = new Customer();
customer1.setCid(12345L);
customer1.setCname("张三");
customer1.setCpin("123456");
customer1.setCbalance(new BigDecimal("1000.00"));
customer1.setCstatus("active");
customer1.setCtype("regular");
Customer customer2 = new Customer();
customer2.setCid(67890L);
customer2.setCname("李四");
customer2.setCpin("654321");
customer2.setCbalance(new BigDecimal("2000.00"));
customer2.setCstatus("inactive");
customer2.setCtype("premium");
entityManager.persist(customer1);
entityManager.persist(customer2);
entityManager.flush();
// when
var activeCustomers = customerRepository.findByCstatus("active");
var inactiveCustomers = customerRepository.findByCstatus("inactive");
// then
assertEquals(1, activeCustomers.size());
assertEquals(1, inactiveCustomers.size());
assertEquals("张三", activeCustomers.get(0).getCname());
assertEquals("李四", inactiveCustomers.get(0).getCname());
}
}

@ -0,0 +1,348 @@
package com.atm.service;
import com.atm.model.Account;
import com.atm.model.Customer;
import com.atm.repository.AccountRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class AccountServiceTest {
@Mock
private AccountRepository accountRepository;
@InjectMocks
private AccountService accountService;
private Customer testCustomer;
private Account testAccount;
@BeforeEach
public void setUp() {
testCustomer = new Customer();
testCustomer.setCid(12345L);
testCustomer.setCname("张三");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("1000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
testAccount = new Account();
testAccount.setAid(1L);
testAccount.setCustomer(testCustomer);
testAccount.setAtype("savings");
testAccount.setAbalance(new BigDecimal("5000.00"));
testAccount.setAstatus("active");
}
@Test
public void whenFindByAid_thenReturnAccount() {
// given
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
// when
Optional<Account> result = accountService.findByAid(1L);
// then
assertTrue(result.isPresent());
assertEquals(1L, result.get().getAid());
assertEquals("savings", result.get().getAtype());
}
@Test
public void whenFindByCustomer_thenReturnAccounts() {
// given
when(accountRepository.findByCustomerCid(testCustomer.getCid())).thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findByCustomerId(testCustomer.getCid());
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
@Test
public void whenFindByCid_thenReturnAccounts() {
// given
when(accountRepository.findByCustomerCid(12345L)).thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findByCustomerId(12345L);
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
@Test
public void whenFindByAtype_thenReturnAccounts() {
// given
when(accountRepository.findByAtype("savings")).thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findByAtype("savings");
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
@Test
public void whenFindByAstatus_thenReturnAccounts() {
// given
when(accountRepository.findByCustomerCidAndAstatus(testCustomer.getCid(), "active")).thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findByCustomerIdAndStatus(testCustomer.getCid(), "active");
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
@Test
public void whenCreateAccount_thenReturnSavedAccount() {
// given
Account newAccount = new Account();
newAccount.setCustomer(testCustomer);
newAccount.setAtype("checking");
newAccount.setAbalance(new BigDecimal("1000.00"));
newAccount.setAstatus("active");
when(accountRepository.save(any(Account.class))).thenReturn(newAccount);
// when
Account result = accountService.createAccount(newAccount);
// then
assertNotNull(result);
assertEquals("checking", result.getAtype());
verify(accountRepository, times(1)).save(any(Account.class));
}
@Test
public void whenUpdateAccount_thenReturnUpdatedAccount() {
// given
Account updatedAccount = new Account();
updatedAccount.setAid(1L);
updatedAccount.setCustomer(testCustomer);
updatedAccount.setAtype("savings");
updatedAccount.setAbalance(new BigDecimal("6000.00"));
updatedAccount.setAstatus("active");
when(accountRepository.save(any(Account.class))).thenReturn(updatedAccount);
// when
Account result = accountService.updateAccount(updatedAccount);
// then
assertNotNull(result);
assertEquals(new BigDecimal("6000.00"), result.getAbalance());
verify(accountRepository, times(1)).save(any(Account.class));
}
@Test
public void whenDeleteAccount_thenReturnTrue() {
// given
when(accountRepository.existsByAid(1L)).thenReturn(true);
doNothing().when(accountRepository).deleteById(1L);
// when
boolean result = accountService.deleteAccount(1L);
// then
assertTrue(result);
verify(accountRepository, times(1)).deleteById(1L);
}
@Test
public void whenDeleteNonExistingAccount_thenReturnFalse() {
// given
when(accountRepository.existsByAid(999L)).thenReturn(false);
// when
boolean result = accountService.deleteAccount(999L);
// then
assertFalse(result);
verify(accountRepository, never()).deleteById(any());
}
@Test
public void whenDeposit_thenReturnUpdatedAccount() {
// given
BigDecimal depositAmount = new BigDecimal("500.00");
BigDecimal expectedBalance = new BigDecimal("5500.00");
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
// when
boolean result = accountService.deposit(1L, depositAmount);
// then
assertTrue(result);
verify(accountRepository, times(1)).save(any(Account.class));
}
@Test
public void whenDepositToNonExistingAccount_thenReturnFalse() {
// given
when(accountRepository.findByAid(999L)).thenReturn(Optional.empty());
// when
boolean result = accountService.deposit(999L, new BigDecimal("500.00"));
// then
assertFalse(result);
verify(accountRepository, never()).save(any(Account.class));
}
@Test
public void whenWithdrawWithSufficientBalance_thenReturnTrue() {
// given
BigDecimal withdrawAmount = new BigDecimal("1000.00");
BigDecimal expectedBalance = new BigDecimal("4000.00");
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
// when
boolean result = accountService.withdraw(1L, withdrawAmount);
// then
assertTrue(result);
verify(accountRepository, times(1)).save(any(Account.class));
}
@Test
public void whenWithdrawWithInsufficientBalance_thenReturnFalse() {
// given
BigDecimal withdrawAmount = new BigDecimal("10000.00");
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
// when
boolean result = accountService.withdraw(1L, withdrawAmount);
// then
assertFalse(result);
verify(accountRepository, never()).save(any(Account.class));
}
@Test
public void whenTransferWithSufficientBalance_thenReturnTrue() {
// given
Account toAccount = new Account();
toAccount.setAid(2L);
toAccount.setCustomer(testCustomer);
toAccount.setAtype("checking");
toAccount.setAbalance(new BigDecimal("2000.00"));
toAccount.setAstatus("active");
BigDecimal transferAmount = new BigDecimal("1000.00");
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
when(accountRepository.findByAid(2L)).thenReturn(Optional.of(toAccount));
when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
// when
boolean result = accountService.transfer(1L, 2L, transferAmount);
// then
assertTrue(result);
verify(accountRepository, times(2)).save(any(Account.class));
}
@Test
public void whenTransferWithInsufficientBalance_thenReturnFalse() {
// given
Account toAccount = new Account();
toAccount.setAid(2L);
toAccount.setCustomer(testCustomer);
toAccount.setAtype("checking");
toAccount.setAbalance(new BigDecimal("2000.00"));
toAccount.setAstatus("active");
BigDecimal transferAmount = new BigDecimal("10000.00");
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
when(accountRepository.findByAid(2L)).thenReturn(Optional.of(toAccount));
// when
boolean result = accountService.transfer(1L, 2L, transferAmount);
// then
assertFalse(result);
verify(accountRepository, never()).save(any(Account.class));
}
@Test
public void whenUpdateAccountStatus_thenReturnTrue() {
// given
when(accountRepository.findByAid(1L)).thenReturn(Optional.of(testAccount));
when(accountRepository.save(any(Account.class))).thenReturn(testAccount);
// when
boolean result = accountService.updateAccountStatus(1L, "inactive");
// then
assertTrue(result);
verify(accountRepository, times(1)).save(any(Account.class));
}
@Test
public void whenExistsByAid_thenReturnTrue() {
// given
when(accountRepository.existsByAid(1L)).thenReturn(true);
// when
boolean result = accountService.existsByAid(1L);
// then
assertTrue(result);
}
@Test
public void whenFindByAbalanceGreaterThan_thenReturnAccounts() {
// given
when(accountRepository.findAccountsWithBalanceGreaterThan(new BigDecimal("3000.00")))
.thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findAccountsWithBalanceGreaterThan(new BigDecimal("3000.00"));
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
@Test
public void whenFindByAbalanceLessThan_thenReturnAccounts() {
// given
when(accountRepository.findAccountsWithBalanceLessThan(new BigDecimal("6000.00")))
.thenReturn(Arrays.asList(testAccount));
// when
List<Account> result = accountService.findAccountsWithBalanceLessThan(new BigDecimal("6000.00"));
// then
assertEquals(1, result.size());
assertEquals(testAccount.getAid(), result.get(0).getAid());
}
}

@ -0,0 +1,257 @@
package com.atm.service;
import com.atm.model.Customer;
import com.atm.repository.CustomerRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.math.BigDecimal;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class CustomerServiceTest {
@Mock
private CustomerRepository customerRepository;
@InjectMocks
private CustomerService customerService;
private Customer testCustomer;
@BeforeEach
public void setUp() {
testCustomer = new Customer();
testCustomer.setCid(12345L);
testCustomer.setCname("张三");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("1000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
}
@Test
public void whenLoadUserByUsername_thenReturnUserDetails() {
// given
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
// when
UserDetails userDetails = customerService.loadUserByUsername("12345");
// then
assertNotNull(userDetails);
assertEquals("12345", userDetails.getUsername());
assertTrue(userDetails.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER")));
}
@Test
public void whenLoadUserByUsernameNotFound_thenThrowException() {
// given
when(customerRepository.findByCid(99999L)).thenReturn(Optional.empty());
// when & then
assertThrows(UsernameNotFoundException.class, () -> {
customerService.loadUserByUsername("99999");
});
}
@Test
public void whenFindByCid_thenReturnCustomer() {
// given
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
// when
Optional<Customer> result = customerService.findByCid(12345L);
// then
assertTrue(result.isPresent());
assertEquals(12345L, result.get().getCid());
assertEquals("张三", result.get().getCname());
}
@Test
public void whenFindByCidAndCpin_thenReturnCustomer() {
// given
when(customerRepository.findByCidAndCpin(12345L, "123456")).thenReturn(Optional.of(testCustomer));
// when
Optional<Customer> result = customerService.findByCidAndCpin(12345L, "123456");
// then
assertTrue(result.isPresent());
assertEquals(12345L, result.get().getCid());
assertEquals("123456", result.get().getCpin());
}
@Test
public void whenCreateCustomer_thenReturnSavedCustomer() {
// given
Customer newCustomer = new Customer();
newCustomer.setCid(67890L);
newCustomer.setCname("李四");
newCustomer.setCpin("654321");
newCustomer.setCbalance(new BigDecimal("2000.00"));
newCustomer.setCstatus("active");
newCustomer.setCtype("premium");
when(customerRepository.save(any(Customer.class))).thenReturn(newCustomer);
// when
Customer result = customerService.createCustomer(newCustomer);
// then
assertNotNull(result);
assertEquals(67890L, result.getCid());
assertEquals("李四", result.getCname());
verify(customerRepository, times(1)).save(any(Customer.class));
}
@Test
public void whenUpdateCustomer_thenReturnUpdatedCustomer() {
// given
Customer updatedCustomer = new Customer();
updatedCustomer.setCid(12345L);
updatedCustomer.setCname("张三更新");
updatedCustomer.setCpin("123456");
updatedCustomer.setCbalance(new BigDecimal("1500.00"));
updatedCustomer.setCstatus("active");
updatedCustomer.setCtype("premium");
when(customerRepository.save(any(Customer.class))).thenReturn(updatedCustomer);
// when
Customer result = customerService.updateCustomer(updatedCustomer);
// then
assertNotNull(result);
assertEquals("张三更新", result.getCname());
assertEquals(new BigDecimal("1500.00"), result.getCbalance());
verify(customerRepository, times(1)).save(any(Customer.class));
}
@Test
public void whenDeleteCustomer_thenReturnTrue() {
// given
when(customerRepository.existsByCid(12345L)).thenReturn(true);
doNothing().when(customerRepository).deleteById(12345L);
// when
boolean result = customerService.deleteCustomer(12345L);
// then
assertTrue(result);
verify(customerRepository, times(1)).deleteById(12345L);
}
@Test
public void whenDeleteNonExistingCustomer_thenReturnFalse() {
// given
when(customerRepository.existsByCid(99999L)).thenReturn(false);
// when
boolean result = customerService.deleteCustomer(99999L);
// then
assertFalse(result);
verify(customerRepository, never()).deleteById(any());
}
@Test
public void whenUpdatePinWithCorrectOldPin_thenReturnTrue() {
// given
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPin = passwordEncoder.encode("123456");
testCustomer.setCpin(encodedPin);
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
when(customerRepository.save(any(Customer.class))).thenReturn(testCustomer);
// when
boolean result = customerService.updatePin(12345L, "123456", "654321");
// then
assertTrue(result);
verify(customerRepository, times(1)).save(any(Customer.class));
}
@Test
public void whenUpdatePinWithIncorrectOldPin_thenReturnFalse() {
// given
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPin = passwordEncoder.encode("123456");
testCustomer.setCpin(encodedPin);
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
// when
boolean result = customerService.updatePin(12345L, "wrongpin", "654321");
// then
assertFalse(result);
verify(customerRepository, never()).save(any(Customer.class));
}
@Test
public void whenResetPin_thenReturnTrue() {
// given
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
when(customerRepository.save(any(Customer.class))).thenReturn(testCustomer);
// when
boolean result = customerService.resetPin(12345L, "newpin123");
// then
assertTrue(result);
verify(customerRepository, times(1)).save(any(Customer.class));
}
@Test
public void whenUpdateCustomerStatus_thenReturnTrue() {
// given
when(customerRepository.findByCid(12345L)).thenReturn(Optional.of(testCustomer));
when(customerRepository.save(any(Customer.class))).thenReturn(testCustomer);
// when
boolean result = customerService.updateCustomerStatus(12345L, "inactive");
// then
assertTrue(result);
verify(customerRepository, times(1)).save(any(Customer.class));
}
@Test
public void whenExistsByCid_thenReturnTrue() {
// given
when(customerRepository.existsByCid(12345L)).thenReturn(true);
// when
boolean result = customerService.existsByCid(12345L);
// then
assertTrue(result);
}
@Test
public void whenFindByStatus_thenReturnCustomers() {
// given
when(customerRepository.findByCstatus("active")).thenReturn(java.util.Arrays.asList(testCustomer));
// when
var result = customerService.findByStatus("active");
// then
assertEquals(1, result.size());
assertEquals(testCustomer.getCid(), result.get(0).getCid());
}
}

@ -0,0 +1,324 @@
package com.atm.service;
import com.atm.model.Account;
import com.atm.model.Customer;
import com.atm.model.Transaction;
import com.atm.repository.TransactionRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class TransactionServiceTest {
@Mock
private TransactionRepository transactionRepository;
@Mock
private AccountService accountService;
@InjectMocks
private TransactionService transactionService;
private Customer testCustomer;
private Account testAccount;
private Account testAccount2;
private Transaction testTransaction;
@BeforeEach
public void setUp() {
testCustomer = new Customer();
testCustomer.setCid(12345L);
testCustomer.setCname("张三");
testCustomer.setCpin("123456");
testCustomer.setCbalance(new BigDecimal("1000.00"));
testCustomer.setCstatus("active");
testCustomer.setCtype("regular");
testAccount = new Account();
testAccount.setAid(1L);
testAccount.setCustomer(testCustomer);
testAccount.setAtype("savings");
testAccount.setAbalance(new BigDecimal("5000.00"));
testAccount.setAstatus("active");
testAccount2 = new Account();
testAccount2.setAid(2L);
testAccount2.setCustomer(testCustomer);
testAccount2.setAtype("checking");
testAccount2.setAbalance(new BigDecimal("2000.00"));
testAccount2.setAstatus("active");
testTransaction = new Transaction();
testTransaction.setTid(1001L);
testTransaction.setFromAccount(testAccount);
testTransaction.setToAccount(testAccount2);
testTransaction.setTtype("transfer");
testTransaction.setTamount(new BigDecimal("500.00"));
testTransaction.setTdescription("Test transfer");
testTransaction.setTstatus("completed");
testTransaction.setCreatedAt(LocalDateTime.now());
}
@Test
public void whenFindByTid_thenReturnTransaction() {
// given
when(transactionRepository.findByTid(1001L)).thenReturn(Optional.of(testTransaction));
// when
Optional<Transaction> result = transactionService.findByTid(1001L);
// then
assertTrue(result.isPresent());
assertEquals(1001L, result.get().getTid());
assertEquals("transfer", result.get().getTtype());
}
@Test
public void whenFindByFromAccount_thenReturnTransactions() {
// given
when(transactionRepository.findByFromAccount(testAccount)).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByFromAccount(testAccount);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByToAccount_thenReturnTransactions() {
// given
when(transactionRepository.findByToAccount(testAccount2)).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByToAccount(testAccount2);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByTtype_thenReturnTransactions() {
// given
when(transactionRepository.findByTtype("transfer")).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByTtype("transfer");
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByTstatus_thenReturnTransactions() {
// given
when(transactionRepository.findByTstatus("completed")).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByTstatus("completed");
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenCreateDepositTransaction_thenReturnTransaction() {
// given
BigDecimal amount = new BigDecimal("1000.00");
String description = "Deposit test";
when(transactionRepository.save(any(Transaction.class))).thenReturn(testTransaction);
// when
Transaction result = transactionService.createDepositTransaction(1L, amount, description);
// then
assertNotNull(result);
assertEquals("deposit", result.getTtype());
assertEquals(amount, result.getTamount());
assertEquals(description, result.getTdescription());
verify(transactionRepository, times(1)).save(any(Transaction.class));
}
@Test
public void whenCreateDepositTransactionWithFailedDeposit_thenReturnNull() {
// given
BigDecimal amount = new BigDecimal("1000.00");
String description = "Deposit test";
when(accountService.deposit(1L, amount)).thenReturn(Optional.empty());
// when
Transaction result = transactionService.createDepositTransaction(1L, amount, description);
// then
assertNull(result);
verify(transactionRepository, never()).save(any(Transaction.class));
}
@Test
public void whenCreateWithdrawalTransaction_thenReturnTransaction() {
// given
BigDecimal amount = new BigDecimal("500.00");
String description = "Withdrawal test";
when(transactionRepository.save(any(Transaction.class))).thenReturn(testTransaction);
// when
Transaction result = transactionService.createWithdrawalTransaction(1L, amount, description);
// then
assertNotNull(result);
assertEquals("withdrawal", result.getTtype());
assertEquals(amount, result.getTamount());
assertEquals(description, result.getTdescription());
verify(transactionRepository, times(1)).save(any(Transaction.class));
}
@Test
public void whenCreateWithdrawalTransactionWithFailedWithdrawal_thenReturnNull() {
// given
BigDecimal amount = new BigDecimal("500.00");
String description = "Withdrawal test";
when(accountService.withdraw(1L, amount)).thenReturn(Optional.empty());
// when
Transaction result = transactionService.createWithdrawalTransaction(1L, amount, description);
// then
assertNull(result);
verify(transactionRepository, never()).save(any(Transaction.class));
}
@Test
public void whenCreateTransferTransaction_thenReturnTransaction() {
// given
BigDecimal amount = new BigDecimal("500.00");
String description = "Transfer test";
when(transactionRepository.save(any(Transaction.class))).thenReturn(testTransaction);
// when
Transaction result = transactionService.createTransferTransaction(1L, 2L, amount, description);
// then
assertNotNull(result);
assertEquals("transfer", result.getTtype());
assertEquals(amount, result.getTamount());
assertEquals(description, result.getTdescription());
verify(transactionRepository, times(1)).save(any(Transaction.class));
}
@Test
public void whenCreateTransferTransactionWithFailedTransfer_thenReturnNull() {
// given
Long fromAid = 1L;
Long toAid = 2L;
BigDecimal amount = new BigDecimal("300.00");
String description = "Transfer test";
when(transactionRepository.save(any(Transaction.class))).thenReturn(testTransaction);
// when
Transaction result = transactionService.createTransferTransaction(fromAid, toAid, amount, description);
// then
assertNotNull(result);
assertEquals("transfer", result.getTtype());
assertEquals(amount, result.getTamount());
assertEquals(description, result.getTdescription());
verify(transactionRepository, times(1)).save(any(Transaction.class));
}
@Test
public void whenFindByAid_thenReturnTransactions() {
// given
when(transactionRepository.findByAccountId(1L)).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByAid(1L);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByCid_thenReturnTransactions() {
// given
when(transactionRepository.findByCustomerId(12345L)).thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByCid(12345L);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByDateRange_thenReturnTransactions() {
// given
LocalDateTime startDate = LocalDateTime.now().minusDays(7);
LocalDateTime endDate = LocalDateTime.now();
when(transactionRepository.findByDateRange(startDate, endDate))
.thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByDateRange(startDate, endDate);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindByAmountGreaterThan_thenReturnTransactions() {
// given
BigDecimal amount = new BigDecimal("400.00");
when(transactionRepository.findTransactionsWithAmountGreaterThan(amount))
.thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findByAmountAbove(amount);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
@Test
public void whenFindRecentTransactionsByAid_thenReturnTransactions() {
// given
when(transactionRepository.findRecentTransactionsByAccountId(1L, org.springframework.data.domain.PageRequest.of(0, 10)))
.thenReturn(Arrays.asList(testTransaction));
// when
List<Transaction> result = transactionService.findRecentTransactionsByAccount(1L, 10);
// then
assertEquals(1, result.size());
assertEquals(testTransaction.getTid(), result.get(0).getTid());
}
}

@ -0,0 +1,13 @@
# 测试配置文件
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA配置
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# 日志配置
logging.level.com.atm=DEBUG

@ -0,0 +1,32 @@
@echo off
echo ========================================
echo ATM Application Launcher
echo ========================================
echo.
echo Starting ATM Application...
echo.
REM Check if Java is installed
java -version >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Java is not installed or not in PATH
echo Please install Java and try again
pause
exit /b 1
)
REM Check if the JAR file exists
if not exist "target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar" (
echo ERROR: JAR file not found
echo Please run 'mvn clean package' first
pause
exit /b 1
)
REM Run the application
echo Launching GUI...
java -jar target\cstatm-mte-0.0.1-SNAPSHOT-jar-with-dependencies.jar
echo.
echo Application closed.
pause

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save