commit
b18f71df26
@ -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"
|
||||
}
|
||||
@ -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,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,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,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,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,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,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,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);
|
||||
|
||||
/**
|
||||
* 根据客户ID和PIN码查找客户
|
||||
*/
|
||||
Optional<Customer> findByCidAndCpin(Long cid, String cpin);
|
||||
|
||||
/**
|
||||
* 检查客户ID是否存在
|
||||
*/
|
||||
boolean existsByCid(Long cid);
|
||||
|
||||
/**
|
||||
* 根据客户状态查找客户
|
||||
*/
|
||||
java.util.List<Customer> findByCstatus(String cstatus);
|
||||
}
|
||||
@ -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,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,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
|
||||
Loading…
Reference in new issue