本文目录导读:

实现一个Java发票管理案例,通常需要涵盖前端界面、后端逻辑和数据库存储三个层面,由于你问的是“案例”,我会提供一个基于Spring Boot + Vue(或Thymeleaf) 的完整思路和核心代码片段,并针对后端逻辑给出详细说明。
一个典型的发票管理系统功能包括:发票录入、发票查询、发票状态更新(作废、红冲)、税额计算、报表统计等。
下面我以后端核心功能为主,提供完整的实现方案。
技术栈选型(案例标准)
| 层级 | 技术选项 | 说明 |
|---|---|---|
| 后端 | Spring Boot 2.x / 3.x | 提供RESTful API |
| MyBatis-Plus | 数据库ORM,简化CRUD | |
| MySQL / PostgreSQL | 存储发票数据 | |
| 前端 | Vue 3 + Element Plus | 现代化管理界面 |
| 工具 | Lombok | 简化实体类代码 |
| Hutool | 提供日期、金额处理工具 |
数据库表设计(核心)
一个基本的发票表 invoice 设计如下:
CREATE TABLE `invoice` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`invoice_code` VARCHAR(50) NOT NULL COMMENT '发票代码',
`invoice_number` VARCHAR(20) NOT NULL COMMENT '发票号码',
`invoice_type` TINYINT NOT NULL COMMENT '发票类型(1:增值税专用发票,2:普通发票)',
`issue_date` DATE NOT NULL COMMENT '开票日期',
`seller_name` VARCHAR(200) NOT NULL COMMENT '销售方名称',
`buyer_name` VARCHAR(200) NOT NULL COMMENT '购买方名称',
`amount_total` DECIMAL(12,2) NOT NULL COMMENT '含税总金额',
`tax_amount` DECIMAL(12,2) NOT NULL COMMENT '税额',
`amount_without_tax` DECIMAL(12,2) NOT NULL COMMENT '不含税金额',
`status` TINYINT DEFAULT 0 COMMENT '状态(0:正常,1:作废,2:红冲)',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_invoice` (`invoice_code`, `invoice_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发票信息表';
后端核心实现
实体类(Entity)
使用 Lombok 简化 getter/setter。
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("invoice")
public class Invoice {
@TableId(type = IdType.AUTO)
private Long id;
private String invoiceCode;
private String invoiceNumber;
private Integer invoiceType; // 1:专票 2:普票
private LocalDate issueDate;
private String sellerName;
private String buyerName;
private BigDecimal amountTotal; // 含税总金额
private BigDecimal taxAmount; // 税额
private BigDecimal amountWithoutTax; // 不含税金额
private Integer status; // 0:正常 1:作废 2:红冲
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Mapper 层(MyBatis-Plus)
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface InvoiceMapper extends BaseMapper<Invoice> {
// 可自定义复杂查询SQL
}
Service 层(核心业务逻辑)
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
public class InvoiceService extends ServiceImpl<InvoiceMapper, Invoice> {
/**
* 分页查询发票列表(支持模糊查询)
*/
public Page<Invoice> queryPage(int current, int size, String keyword) {
Page<Invoice> page = new Page<>(current, size);
// 使用LambdaQueryWrapper构建动态条件
lambdaQuery()
.like(keyword != null && !keyword.isEmpty(), Invoice::getInvoiceCode, keyword)
.or()
.like(keyword != null && !keyword.isEmpty(), Invoice::getBuyerName, keyword)
.orderByDesc(Invoice::getCreateTime)
.page(page);
return page;
}
/**
* 添加发票(含自动计算税额逻辑)
* 规则:假设一般纳税人增值税率为13%
*/
@Transactional(rollbackFor = Exception.class)
public boolean addInvoice(Invoice invoice) {
// 1. 校验发票唯一性(代码+号码)
long count = lambdaQuery()
.eq(Invoice::getInvoiceCode, invoice.getInvoiceCode())
.eq(Invoice::getInvoiceNumber, invoice.getInvoiceNumber())
.count();
if (count > 0) {
throw new RuntimeException("该发票已存在,不可重复录入");
}
// 2. 根据类型计算税额(仅示例,实际规则复杂)
if (invoice.getTaxAmount() == null && invoice.getAmountWithoutTax() != null) {
// 不含税金额 * 税率 = 税额(这里假设13%)
BigDecimal tax = invoice.getAmountWithoutTax()
.multiply(new BigDecimal("0.13"))
.setScale(2, RoundingMode.HALF_UP);
invoice.setTaxAmount(tax);
// 含税金额 = 不含税金额 + 税额
invoice.setAmountTotal(invoice.getAmountWithoutTax().add(tax));
}
// 3. 默认状态正常
invoice.setStatus(0);
return save(invoice);
}
/**
* 作废发票(状态变为1)
* 必须校验发票状态是否允许作废(只有正常状态才能作废)
*/
@Transactional(rollbackFor = Exception.class)
public boolean voidInvoice(Long id) {
Invoice invoice = getById(id);
if (invoice == null) {
throw new RuntimeException("发票不存在");
}
if (invoice.getStatus() != 0) {
throw new RuntimeException("仅正常状态的发票可以作废");
}
return lambdaUpdate()
.eq(Invoice::getId, id)
.set(Invoice::getStatus, 1)
.update();
}
}
Controller 层(RESTful API)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
@Autowired
private InvoiceService invoiceService;
@GetMapping("/page")
public Result<Page<Invoice>> page(
@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
Page<Invoice> page = invoiceService.queryPage(current, size, keyword);
return Result.success(page);
}
@PostMapping
public Result<String> add(@RequestBody Invoice invoice) {
invoiceService.addInvoice(invoice);
return Result.success("添加成功");
}
@PutMapping("/void/{id}")
public Result<String> voidInvoice(@PathVariable Long id) {
invoiceService.voidInvoice(id);
return Result.success("作废成功");
}
}
前端(Vue 3 + Element Plus)核心片段
以下是一个 发票列表 + 新增对话框 的示例:
<template>
<div>
<el-table :data="tableData" stripe border>
<el-table-column prop="invoiceCode" label="发票代码" width="180" />
<el-table-column prop="invoiceNumber" label="发票号码" width="180" />
<el-table-column prop="amountTotal" label="含税金额" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 0 ? 'success' : 'danger'">
{{ row.status === 0 ? '正常' : '作废' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button
size="small"
type="danger"
@click="handleVoid(row.id)"
:disabled="row.status !== 0">
作废
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增发票对话框 -->
<el-dialog v-model="dialogVisible" title="新增发票">
<el-form :model="form">
<el-form-item label="发票代码">
<el-input v-model="form.invoiceCode" />
</el-form-item>
<el-form-item label="含税金额">
<el-input-number v-model="form.amountTotal" />
</el-form-item>
<!-- 其他字段类似 -->
</el-form>
<template #footer>
<el-button @click="submitForm">提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
const tableData = ref([])
const dialogVisible = ref(false)
const form = ref({})
const fetchData = async () => {
const res = await axios.get('/api/invoices/page')
tableData.value = res.data.data.records
}
const handleVoid = async (id) => {
await axios.put(`/api/invoices/void/${id}`)
ElMessage.success('作废成功')
fetchData()
}
onMounted(fetchData)
</script>
关键扩展点(可自行添加)
- 红冲发票:生成一张负数金额的新发票,关联原发票ID。
- 批量导入/导出:使用 EasyExcel 解析 Excel / CSV 文件。
- 金额自动校验:校验
amountTotal = amountWithoutTax + taxAmount。 - 税率配置化:从数据库读取税率,而非硬编码 13%。
- 日志审计:记录每次作废、红冲的操作人、时间。
代码实现了一个最小可用的发票管理后端,涵盖了:
- 基础CRUD
- 发票状态流转控制
- 金额自动计算
- 分页与模糊搜索
- 前端交互示例
你可以将此作为骨架,根据实际业务(如不同税率的发票、多用户权限、开票限额控制等)进行扩展,如果更关注PDF/OFD电子发票解析、税控盘对接等高级功能,则需要引入专用SDK,但核心架构保持不变。