JavaWeb项目完整案例实操指南
一、技术选型与环境搭建
(一)技术栈升级
本次项目采用以下最新技术组合:
- 后端:Spring Boot 3.0 + Spring Security 6.0 + Spring Data JPA + WebFlux
- 前端:Vue 3 + Vite + TypeScript + Element Plus
- 数据库:MySQL 8.0 + Redis 7.0
- 容器化:Docker + Kubernetes 基础部署
(二)项目初始化
使用 Spring Initializr 创建项目,依赖选择:
Spring Web
Spring Data JPA
Spring Security
Validation
MySQL Driver
Redis
Springdoc OpenAPI 3
配置 application.yml
:
spring:
datasource:
url: jdbc:mysql://localhost:3306/emp_management?useSSL=false&serverTimezone=UTC
username: root
password: yourpassword
jpa:
hibernate:
ddl-auto: update
show-sql: true
redis:
host: localhost
port: 6379
二、API 设计与实现
(一)RESTful API 规范
采用以下接口设计标准:
GET /api/v1/depts - 获取部门列表
POST /api/v1/depts - 创建部门
GET /api/v1/depts/{id} - 获取单个部门
PUT /api/v1/depts/{id} - 更新部门
DELETE /api/v1/depts/{id} - 删除部门
GET /api/v1/emps - 分页获取员工列表
POST /api/v1/emps - 创建员工
GET /api/v1/emps/{id} - 获取单个员工
PUT /api/v1/emps/{id} - 更新员工
DELETE /api/v1/emps/{id} - 删除员工
(二)部门管理实现
- 实体类:
@Entity
@Table(name = "departments")
@Data
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "部门名称不能为空")
private String name;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@PrePersist
public void prePersist() {
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updateTime = LocalDateTime.now();
}
}
- Repository 层:
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}
- Service 层:
@Service
@Transactional
public class DepartmentServiceImpl implements DepartmentService {
private final DepartmentRepository departmentRepository;
public DepartmentServiceImpl(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
@Override
public List<Department> getAllDepartments() {
return departmentRepository.findAll();
}
@Override
public Department createDepartment(Department department) {
return departmentRepository.save(department);
}
@Override
public Department getDepartmentById(Long id) {
return departmentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Department not found with id: " + id));
}
@Override
public Department updateDepartment(Long id, Department departmentDetails) {
Department department = getDepartmentById(id);
department.setName(departmentDetails.getName());
return departmentRepository.save(department);
}
@Override
public void deleteDepartment(Long id) {
Department department = getDepartmentById(id);
departmentRepository.delete(department);
}
}
- Controller 层:
@RestController
@RequestMapping("/api/v1/depts")
@Api(tags = "部门管理")
public class DepartmentController {
private final DepartmentService departmentService;
public DepartmentController(DepartmentService departmentService) {
this.departmentService = departmentService;
}
@GetMapping
@ApiOperation("获取所有部门")
public ResponseEntity<List<Department>> getAllDepartments() {
List<Department> departments = departmentService.getAllDepartments();
return ResponseEntity.ok(departments);
}
@PostMapping
@ApiOperation("创建部门")
public ResponseEntity<Department> createDepartment(@Valid @RequestBody Department department) {
Department savedDepartment = departmentService.createDepartment(department);
return ResponseEntity.created(URI.create("/api/v1/depts/" + savedDepartment.getId()))
.body(savedDepartment);
}
// 其他接口方法省略...
}
三、安全认证与授权
(一)JWT 认证实现
- 添加依赖:
<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>
- JWT 工具类:
@Component
public class JwtUtils {
private static final String SECRET_KEY = "your-secret-key-here";
private static final long EXPIRATION_TIME = 86400000; // 24小时
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration();
return expiration.before(new Date());
}
}
- Spring Security 配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
四、前端实现
(一)Vue 3 项目初始化
npm init vite@latest emp-management-frontend -- --template vue-ts
cd emp-management-frontend
npm install
npm install element-plus @element-plus/icons-vue axios
(二)API 调用封装
// src/services/axios.ts
import axios from 'axios';
const service = axios.create({
baseURL: 'http://localhost:8080/api/v1',
timeout: 5000
});
// 请求拦截器
service.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${
token}`;
}
return config;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
return response.data;
},
error => {
console.log('err' + error);
return Promise.reject(error);
}
);
export default service;
(三)部门管理页面
<!-- src/views/Department.vue -->
<template>
<div class="department-page">
<el-card>
<template #header>
<div class="header">
<span>部门管理</span>
<el-button type="primary" @click="handleCreate">创建部门</el-button>
</div>
</template>
<el-table :data="departments" stripe>
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="部门名称"></el-table-column>
<el-table-column prop="createTime" label="创建时间"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 30]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</el-card>
<!-- 创建/编辑对话框 -->
<el-dialog :visible.sync="dialogVisible" title="部门管理">
<template #content>
<el-form :model="formData" ref="formRef" label-width="80px">
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称"></el-input>
</el-form-item>
</el-form>
</template>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import axios from '@/services/axios';
// 数据定义
const departments = ref([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const dialogVisible = ref(false);
const formData = reactive({
id: null,
name: ''
});
const formRef = ref(null);
// 获取部门列表
const fetchDepartments = async () => {
try {
const response = await axios.get('/depts', {
params: {
page: currentPage.value - 1,
size: pageSize.value
}
});
departments.value = response.content;
total.value = response.totalElements;
} catch (error) {
ElMessage.error('获取部门列表失败');
}
};
// 创建部门
const handleCreate = () => {
formData.id = null;
formData.name = '';
dialogVisible.value = true;
};
// 编辑部门
const handleEdit = (row) => {
formData.id = row.id;
formData.name = row.name;
dialogVisible.value = true;
};
// 保存部门
const handleSave = async () => {
try {
if (formData.id) {
// 更新部门
await axios.put(`/depts/${formData.id}`, formData);
ElMessage.success('更新部门成功');
} else {
// 创建部门
await axios.post('/depts', formData);
ElMessage.success('创建部门成功');
}
dialogVisible.value = false;
fetchDepartments();
} catch (error) {
ElMessage.error('操作失败');
}
};
// 删除部门
const handleDelete = async (id) => {
try {
await axios.delete(`/depts/${id}`);
ElMessage.success('删除部门成功');
fetchDepartments();
} catch (error) {
ElMessage.error('删除部门失败');
}
};
// 分页相关
const handleSizeChange = (newSize) => {
pageSize.value = newSize;
fetchDepartments();
};
const handleCurrentChange = (newPage) => {
currentPage.value = newPage;
fetchDepartments();
};
onMounted(() => {
fetchDepartments();
});
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
五、Docker 容器化部署
(一)创建 Dockerfile
# 后端服务
FROM openjdk:17-jdk-alpine
VOLUME /tmp
COPY target/emp-management-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 前端服务
FROM node:16-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:1.21.0-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
(二)Nginx 配置
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
(三)Docker Compose 配置
version: '3'
services:
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- db
- redis
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/emp_management?useSSL=false&serverTimezone=UTC
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=yourpassword
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=yourpassword
- MYSQL_DATABASE=emp_management
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7.0
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
mysql-data:
redis-data:
六、测试与监控
(一)单元测试
使用 JUnit 5 和 Mockito 编写测试用例:
@SpringBootTest
@AutoConfigureMockMvc
class DepartmentControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private DepartmentService departmentService;
@Test
void testGetAllDepartments() throws Exception {
List<Department> departments = Arrays.asList(
new Department(1L, "技术部", LocalDateTime.now(), LocalDateTime.now()),
new Department(2L, "市场部", LocalDateTime.now(), LocalDateTime.now())
);
when(departmentService.getAllDepartments()).thenReturn(departments);
mockMvc.perform(get("/api/v1/depts"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()").value(2));
}
}
(二)集成监控
添加 Micrometer 和 Actuator:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置 application.yml
:
management:
endpoints:
web:
exposure:
include: "*"
metrics:
export:
prometheus:
enabled: true
以上就是一个完整的 JavaWeb 项目实操指南,包含了从环境搭建到容器化部署的全过程。项目采用了前后端分离架构,使用了最新的技术栈,实现了部门和员工管理等基本功能,并提供了安全认证、分页查询、异常处理等企业级应用必备的功能。
java Web 项目案例,java 实操指南,Web 项目搭建步骤,Web 项目部署教程,java 项目完整案例,java Web 开发指南,java 项目实操步骤,Web 项目热门关键词,java 案例解析,Web 项目搭建教程,java 部署详细步骤,Web 实操指南,java 热门关键词解析,java 项目搭建部署,Web 完整案例实操
代码获取方式
https://pan.quark.cn/s/14fcf913bae6