在大学计算机相关专业的课程设计或毕业设计中,开发一个完整的应用系统是检验学生综合能力的重要方式。个人财务管理系统作为贴近日常生活的实用型项目,既能体现技术深度又具备现实意义。这类系统通常需要满足以下几个核心需求:
SpringBoot作为当前Java领域最流行的后端框架,其"约定优于配置"的理念特别适合课程设计这类需要快速实现完整功能的场景。它内置Tomcat服务器、自动配置等特性,可以让开发者更专注于业务逻辑的实现而非框架整合。
提示:选择个人财务系统作为课程设计课题的优势在于,需求明确且贴近生活,容易获得真实用户反馈,同时技术栈符合企业主流开发规范。
基于项目需求和教学目的,推荐采用以下技术组合:
后端技术栈:
前端技术栈:
数据库:
开发工具:
采用经典的三层架构模式:
code复制表现层(Web) → 业务逻辑层(Service) → 数据访问层(DAO)
模块划分建议:
注意:课程设计项目应适当控制模块粒度,建议聚焦核心记账功能,确保在规定时间内完成可演示的完整系统。
用户表(t_user)
sql复制CREATE TABLE `t_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT '加密密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '显示名称',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
`status` tinyint DEFAULT '1' COMMENT '状态(0-禁用 1-正常)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
账户表(t_account)
sql复制CREATE TABLE `t_account` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL COMMENT '所属用户',
`account_name` varchar(50) NOT NULL COMMENT '账户名称',
`account_type` tinyint NOT NULL COMMENT '账户类型(1-现金 2-银行卡...)',
`balance` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '当前余额',
`remark` varchar(255) DEFAULT NULL COMMENT '备注说明',
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
收支记录表(t_transaction)
sql复制CREATE TABLE `t_transaction` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`account_id` int NOT NULL COMMENT '关联账户',
`category_id` int NOT NULL COMMENT '分类ID',
`amount` decimal(12,2) NOT NULL COMMENT '金额',
`transaction_type` tinyint NOT NULL COMMENT '类型(1-收入 2-支出)',
`transaction_time` datetime NOT NULL COMMENT '交易时间',
`description` varchar(255) DEFAULT NULL COMMENT '交易说明',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_time` (`user_id`,`transaction_time`),
KEY `idx_account` (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
索引设计:
事务处理:
数据安全:
密码加密配置:
java复制@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/register", "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutSuccessUrl("/login");
}
}
用户注册逻辑:
java复制@PostMapping("/register")
public String register(@Valid UserForm form, BindingResult result) {
if (result.hasErrors()) {
return "register";
}
if (userService.existsByUsername(form.getUsername())) {
result.rejectValue("username", "duplicate", "用户名已存在");
return "register";
}
User user = new User();
user.setUsername(form.getUsername());
user.setPassword(passwordEncoder.encode(form.getPassword()));
user.setNickname(form.getNickname());
userService.save(user);
return "redirect:/login";
}
交易记录新增服务:
java复制@Service
@Transactional
public class TransactionService {
@Autowired
private TransactionMapper transactionMapper;
@Autowired
private AccountMapper accountMapper;
public void addTransaction(TransactionDTO dto) {
// 验证账户归属
Account account = accountMapper.selectById(dto.getAccountId());
if (account == null || !account.getUserId().equals(dto.getUserId())) {
throw new BusinessException("账户不存在或无权操作");
}
// 保存交易记录
Transaction transaction = new Transaction();
BeanUtils.copyProperties(dto, transaction);
transactionMapper.insert(transaction);
// 更新账户余额
BigDecimal amount = dto.getTransactionType() == 1 ?
dto.getAmount() : dto.getAmount().negate();
accountMapper.updateBalance(dto.getAccountId(), amount);
}
}
MyBatis-Plus余额更新操作:
java复制@Mapper
public interface AccountMapper extends BaseMapper<Account> {
@Update("UPDATE t_account SET balance = balance + #{amount} WHERE id = #{accountId}")
int updateBalance(@Param("accountId") Integer accountId,
@Param("amount") BigDecimal amount);
}
月度消费统计SQL:
sql复制SELECT
DATE_FORMAT(transaction_time, '%Y-%m') AS month,
SUM(CASE WHEN transaction_type = 2 THEN amount ELSE 0 END) AS expense,
SUM(CASE WHEN transaction_type = 1 THEN amount ELSE 0 END) AS income
FROM t_transaction
WHERE user_id = #{userId}
GROUP BY DATE_FORMAT(transaction_time, '%Y-%m')
ORDER BY month DESC
LIMIT 12;
ECharts柱状图配置:
javascript复制function initTrendChart(data) {
const chart = echarts.init(document.getElementById('trend-chart'));
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['支出', '收入'] },
xAxis: { type: 'category', data: data.months },
yAxis: { type: 'value' },
series: [
{ name: '支出', type: 'bar', data: data.expenses },
{ name: '收入', type: 'bar', data: data.incomes }
]
};
chart.setOption(option);
window.addEventListener('resize', chart.resize);
}
分类统计查询:
java复制public List<CategoryStatVO> getCategoryStats(Integer userId, LocalDate start, LocalDate end) {
return transactionMapper.selectCategoryStats(userId,
start.atStartOfDay(),
end.plusDays(1).atStartOfDay());
}
对应Mapper接口:
java复制@Select("SELECT c.name AS category, SUM(t.amount) AS total " +
"FROM t_transaction t JOIN t_category c ON t.category_id = c.id " +
"WHERE t.user_id = #{userId} AND t.transaction_time BETWEEN #{start} AND #{end} " +
"AND t.transaction_type = 2 " +
"GROUP BY t.category_id")
List<CategoryStatVO> selectCategoryStats(@Param("userId") Integer userId,
@Param("start") Date start,
@Param("end") Date end);
数据库初始化:
CREATE DATABASE finance DEFAULT CHARSET utf8mb4应用配置修改:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/finance?useSSL=false
username: root
password: 123456
redis:
host: localhost
port: 6379
启动应用:
FinanceApplicationmvn spring-boot:runDocker部署方案:
dockerfile复制# Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/finance-system-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
构建与运行命令:
bash复制mvn clean package
docker build -t finance-system .
docker run -d -p 8080:8080 --name finance finance-system
问题1:MyBatis-Plus查询返回空字段
yaml复制mybatis-plus:
configuration:
map-underscore-to-camel-case: true
问题2:Thymeleaf模板无法解析
resources/templates目录xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
问题3:并发修改账户余额不一致
sql复制UPDATE t_account
SET balance = balance + #{amount}, version = version + 1
WHERE id = #{accountId} AND version = #{version}
问题4:分页查询性能低下
java复制Page<Transaction> page = new Page<>(current, size);
page.setOptimizeCountSql(true);
return transactionMapper.selectPage(page, queryWrapper);
对于希望进一步提升项目质量的同学,可以考虑以下扩展方向:
多端适配:
智能分析:
数据安全:
系统集成:
性能优化:
在开发过程中,我特别建议重视单元测试的编写。使用SpringBootTest可以快速构建测试环境,以下是一个典型的Service层测试示例:
java复制@SpringBootTest
class TransactionServiceTest {
@Autowired
private TransactionService service;
@Test
@Transactional
@Rollback
void testAddTransaction() {
TransactionDTO dto = new TransactionDTO();
dto.setUserId(1);
dto.setAccountId(1);
dto.setCategoryId(1);
dto.setAmount(new BigDecimal("100.00"));
dto.setTransactionType(2);
dto.setTransactionTime(LocalDateTime.now());
assertDoesNotThrow(() -> service.addTransaction(dto));
Account account = accountMapper.selectById(1);
assertEquals(new BigDecimal("900.00"), account.getBalance());
}
}