这是星际大战系列的第三篇送福利文章,感谢一路以来支持和关注这个项目的每一位朋友!
文章力求严谨,但难免有疏漏之处,欢迎各位朋友指出,让我们一起在交流中进步。
项目代码、文档和相关资源都可以免费获取,希望能帮助到更多对游戏开发感兴趣的朋友。
如果您有任何想法、建议或疑问,都欢迎在评论区留言或通过私信与我交流。您的每一个反馈都是项目进步的动力!
繁星点点,太空浩瀚。让我们再次启程,在代码中探索无尽的可能。
大家好,我是程序员果冻~。首先要特别感谢之前纯前端版本的广大读者们的支持和鼓励!你们的点赞、收藏、评论和私信建议给了我极大的动力。应大家的要求,这次我为大家带来了全新升级的前后端完整版本,适合正在学习JAVA的新手朋友学习练习。
源码附文章末尾。
前后端进阶版飞机大战
如果上面的视频无法观看,可能是因为还在审核中,可以先看下面的视频。
前后端进阶版飞机大战
本次升级版本在保留了原有炫酷游戏玩法的基础上,新增了用户系统、排行榜、游戏记录等功能,让游戏体验更加完整和社交化。项目采用前后端分离架构,是一个非常适合学习全栈开发的示例项目。
完整的用户体验
安全性保障
数据统计分析
优秀的代码结构
本项目使用原生Canvas API构建了一个轻量级的2D游戏引擎,主要包括以下核心组件:
每个组件都采用面向对象设计,具有高内聚低耦合的特点,便于扩展和维护。
排行榜管理器设计采用了单例模式,集中处理排行榜相关的所有逻辑:
// 排行榜管理器
const LeaderboardManager = {
currentPage: 0,
pageSize: 10,
currentDifficulty: 'EASY', // 默认简单模式
currentType: 'score', // 默认得分排行
init() {
// 初始化排行榜UI和事件监听
this.addLeaderboardButton();
this.setupEventListeners();
// 设置难度选择器
const difficultySelect = document.getElementById('difficultySelect');
if (difficultySelect) {
difficultySelect.innerHTML = `
`;
difficultySelect.value = this.currentDifficulty;
}
},
fetchLeaderboard() {
// 构建API请求URL,包含分页、难度和类型参数
const url = `${API_BASE_URL}/api/records/leaderboard?difficulty=${this.currentDifficulty}&type=${this.currentType}&page=${this.currentPage}&size=${this.pageSize}`;
// 发送请求获取排行榜数据
fetch(url)
.then(response => response.json())
.then(data => {
this.renderLeaderboard(data);
this.updatePagination(data);
})
.catch(error => console.error('获取排行榜失败:', error));
}
}
排行榜渲染采用了模板化设计,支持不同类型数据的高亮显示:
renderLeaderboard(data) {
const tbody = document.getElementById('leaderboardList');
tbody.innerHTML = '';
if (data.content.length === 0) {
tbody.innerHTML = '暂无数据 ';
return;
}
// 根据排行类型决定高亮显示的列
const highlightColumn = this.getHighlightColumn();
data.content.forEach((record, index) => {
const row = document.createElement('tr');
// 为前三名添加特殊样式
if (index < 3) {
row.classList.add(`rank-${index + 1}`);
}
// 检测是否为当前用户,添加高亮
if (record.user.username === currentUser) {
row.classList.add('current-user');
}
// 构建行数据
row.innerHTML = `
${this.currentPage * this.pageSize + index + 1}
${record.user.username}
${highlightColumn === 'score' ? 'highlight' : ''}">${record.score}
${highlightColumn === 'kills' ? 'highlight' : ''}">${record.enemiesKilled}
${highlightColumn === 'items' ? 'highlight' : ''}">${record.itemsCollected}
${highlightColumn === 'time' ? 'highlight' : ''}">${formatTime(record.playTime)}
${formatDate(record.createdAt)}
`;
tbody.appendChild(row);
});
}
分页控件实现,支持首页、上一页、下一页、末页导航:
updatePagination(data) {
const paginationDiv = document.getElementById('leaderboardPagination');
const totalPages = data.totalPages;
paginationDiv.innerHTML = `
第 ${this.currentPage + 1}/${totalPages} 页
`;
}
难度和排行类型切换的实现:
setupEventListeners() {
// 难度选择器事件监听
const difficultySelect = document.getElementById('difficultySelect');
if (difficultySelect) {
difficultySelect.addEventListener('change', () => {
this.currentDifficulty = difficultySelect.value;
this.currentPage = 0; // 切换难度时重置页码
this.fetchLeaderboard();
});
}
// 排行类型选择器事件监听
const typeSelect = document.getElementById('typeSelect');
if (typeSelect) {
typeSelect.addEventListener('change', () => {
this.currentType = typeSelect.value;
this.currentPage = 0; // 切换类型时重置页码
this.fetchLeaderboard();
});
}
}
requestAnimationFrame
实现平滑动画游戏核心逻辑被拆分为多个独立模块:
game.js
: 游戏主循环和状态管理player.js
: 玩家控制和属性enemy.js
: 敌人生成和AIbullet.js
: 子弹系统item.js
: 道具系统background.js
: 背景渲染audioManager.js
: 音频管理main.js
: 应用入口和UI控制不依赖第三方游戏引擎,从零构建了轻量级2D游戏引擎,包括:
后端采用经典的多层架构设计,确保代码的可维护性和可扩展性:
系统采用Spring Security + JWT的安全架构:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationFilter jwtAuthFilter;
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
系统使用Spring Cache实现多级缓存策略,提高查询性能:
不过本文为了能够减少外部服务依赖,并没有引入其他第三方缓存中间件。
直接存在JVM内存中
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<>();
caches.add(new ConcurrentMapCache("leaderboard"));
caches.add(new ConcurrentMapCache("userRecords"));
cacheManager.setCaches(caches);
return cacheManager;
}
}
用户认证流程采用JWT令牌机制,确保API安全:
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest request) {
return ResponseEntity.ok(authService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.login(request));
}
}
JWT过滤器实现:
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
final String jwt = authHeader.substring(7);
final String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
游戏记录服务实现了高效的数据存储和查询功能,并集成了缓存优化:
@Service
@RequiredArgsConstructor
public class GameRecordService {
private final GameRecordRepository gameRecordRepository;
private final UserRepository userRepository;
@CacheEvict(value = "leaderboard", allEntries = true)
public GameRecord saveRecord(String username, GameRecordRequest request) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
GameRecord record = convertToEntity(request, user);
return gameRecordRepository.save(record);
}
@Cacheable(value = "leaderboard", key = "'leaderboard_' + #difficulty + '_' + #type + '_page_' + #page + '_size_' + #size")
public Page<GameRecord> getLeaderboard(String difficulty, String type, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
switch (type.toLowerCase()) {
case "score":
return gameRecordRepository.findTopScoresByDifficulty(difficulty, pageable);
case "kills":
return gameRecordRepository.findTopKillsByDifficulty(difficulty, pageable);
case "items":
return gameRecordRepository.findTopItemsByDifficulty(difficulty, pageable);
case "time":
return gameRecordRepository.findTopPlayTimeByDifficulty(difficulty, pageable);
case "escapes":
return gameRecordRepository.findTopEscapesByDifficulty(difficulty, pageable);
default:
return gameRecordRepository.findTopScoresByDifficulty(difficulty, pageable);
}
}
}
排行榜查询使用了高效的SQL查询,通过窗口函数实现复杂的排名逻辑:
@Repository
public interface GameRecordRepository extends JpaRepository<GameRecord, Long> {
@Query(nativeQuery = true, value =
"WITH RankedScores AS ( " +
" SELECT *, " +
" ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY score DESC, id DESC) as rn " +
" FROM game_records " +
" WHERE difficulty = :difficulty " +
") " +
"SELECT * FROM RankedScores WHERE rn = 1 ORDER BY score DESC")
Page<GameRecord> findTopScoresByDifficulty(String difficulty, Pageable pageable);
@Query(nativeQuery = true, value =
"WITH RankedKills AS ( " +
" SELECT *, " +
" ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY enemies_killed DESC, score DESC, id DESC) as rn " +
" FROM game_records " +
" WHERE difficulty = :difficulty " +
") " +
"SELECT * FROM RankedKills WHERE rn = 1 ORDER BY enemies_killed DESC, score DESC")
Page<GameRecord> findTopKillsByDifficulty(String difficulty, Pageable pageable);
}
P6Spy配置示例:
# 指定日志输出模块
modulelist=com.p6spy.engine.logging.P6LogFactory
# 自定义日志格式
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=执行SQL:%(sqlSingleLine) | 耗时:%(executionTime)ms
# 设置P6Spy输出到控制台
appender=com.p6spy.engine.spy.appender.StdoutLogger
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准,单位秒
outagedetectioninterval=2
使用BCrypt加密算法保护用户密码:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
系统按功能划分为多个独立模块,便于扩展:
采用RESTful API设计原则,便于集成和扩展:
关键配置通过配置文件外部化,便于环境切换:
spring:
datasource:
url: jdbc:p6spy:mysql://localhost:3306/aircraftwar?useSSL=false&serverTimezone=UTC
username: root
password: ******
jpa:
hibernate:
ddl-auto: update
jwt:
secret: 5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437
expiration: 86400000 # 24小时
mysql版本需要8.0以上版本
前端需要安装好nodejs
# 安装依赖
npm install
# 启动服务
npm start
# 导入数据库脚本
source init.sql
# 修改数据库配置
vim application.yml
# 启动Spring Boot应用
mvn spring-boot:run
https://download.csdn.net/download/Pte_moon/90491980?spm=1001.2014.3001.5503
再次感谢所有支持和关注本项目的朋友们!你们的支持是我持续更新的动力。也欢迎大家在评论区留言,分享你的想法和建议。让我们一起把这个项目做得更好!
精心打造,只为分享。若这个全栈项目对你有所启发或帮助,请赐予一个赞与收藏⭐。每一次支持都是对创作者最好的鼓励,也是促使我不断优化迭代的关键动力。
也欢迎大家关注我,后续会持续分享更多有趣的项目!