一、数据库表设计
1.MySQL 数据库脚本
CREATE DATABASE IF NOT EXISTS game2048 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE game2048; CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, highest_score INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, highest_score INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS game_records ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, score INT NOT NULL, played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );
2.数据库设计说明
-
users
表: 存储用户的基本信息,包括用户名、密码(需加密处理)、电子邮件(可选)和用户的最高得分。id
: 主键,用户的唯一标识。username
: 用户名,必须唯一。password
: 用户密码(需要在应用程序层进行加密)。email
: 用户电子邮件,可选项,允许为空。highest_score
: 用户的最高分,用于排行榜展示。created_at
和updated_at
: 用户创建和更新的时间戳。
-
game_records
表: 存储每次游戏的记录,包括用户ID、得分和游戏的时间。id
: 主键,游戏记录的唯一标识。user_id
: 外键,关联到users
表,指向该游戏记录所属的用户。score
: 游戏得分。played_at
: 游戏记录时间戳,默认是记录创建的时间。
3. 数据库索引与优化
- 在
users
表上,我们为username
字段添加了一个唯一索引,以确保用户名的唯一性。 user_id
是game_records
表的外键,它与users
表的id
关联,设置了级联删除,以便在删除用户时自动删除其所有游戏记录。
4. 额外建议
- 密码加密: 在创建用户时,确保在应用程序层对用户的密码进行加密(例如使用 bcrypt),并在数据库中存储加密后的密码。
- 数据备份: 定期备份数据库,确保数据安全。
- 优化查询: 根据应用程序的查询需求,可以考虑添加额外的索引来优化查询性能,例如根据用户ID和得分进行排序的索引。
二、代码实现
1. 项目结构
我们将项目分成前后端两个部分:
- 后端 (Spring Boot): 负责用户管理、游戏记录存储、积分榜和排行榜等功能。
- 前端 (Vue): 实现 2048 游戏逻辑、用户界面、用户登录注册等。
2. 后端 (Spring Boot)
2.1 创建 Spring Boot 项目
使用 Spring Initializr 创建一个 Spring Boot 项目,选择以下依赖:
- Spring Web
- Spring Data JPA
- Spring Security
- MySQL
- Lombok
2.2 创建后端实体类
用户实体
package com.example.game.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private int score;
}
游戏记录实体
package com.example.game.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
private int score;
private LocalDateTime playedAt;
}
2.3 创建 Repository 接口
package com.example.game.repository;
import com.example.game.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
package com.example.game.repository;
import com.example.game.entity.GameRecord;
import com.example.game.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface GameRecordRepository extends JpaRepository<GameRecord, Long> {
List<GameRecord> findByUser(User user);
}
2.4 创建服务层
package com.example.game.service;
import com.example.game.entity.User;
import com.example.game.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User register(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
public User findByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
package com.example.game.service;
import com.example.game.entity.GameRecord;
import com.example.game.entity.User;
import com.example.game.repository.GameRecordRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class GameRecordService {
@Autowired
private GameRecordRepository gameRecordRepository;
public GameRecord saveGameRecord(User user, int score) {
GameRecord gameRecord = new GameRecord();
gameRecord.setUser(user);
gameRecord.setScore(score);
gameRecord.setPlayedAt(LocalDateTime.now());
return gameRecordRepository.save(gameRecord);
}
public List<GameRecord> getGameRecordsByUser(User user) {
return gameRecordRepository.findByUser(user);
}
public List<GameRecord> getAllGameRecords() {
return gameRecordRepository.findAll();
}
}
2.5 创建控制器
package com.example.game.controller;
import com.example.game.entity.User;
import com.example.game.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public User register(@RequestBody User user) {
return userService.register(user);
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
}
package com.example.game.controller;
import com.example.game.entity.GameRecord;
import com.example.game.entity.User;
import com.example.game.service.GameRecordService;
import com.example.game.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/games")
public class GameRecordController {
@Autowired
private GameRecordService gameRecordService;
@Autowired
private UserService userService;
@PostMapping("/save")
public GameRecord saveGameRecord(@RequestParam String username, @RequestParam int score) {
User user = userService.findByUsername(username);
if (user == null) {
throw new RuntimeException("User not found");
}
return gameRecordService.saveGameRecord(user, score);
}
@GetMapping("/records")
public List<GameRecord> getAllGameRecords() {
return gameRecordService.getAllGameRecords();
}
}
2.6 配置安全性 (Spring Security)
创建一个简单的安全配置,允许所有用户注册和登录。
package com.example.game.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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/users/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. 前端 (Vue)
3.1 创建 Vue 项目
使用 Vue CLI 创建一个 Vue 3 项目。
vue create 2048-game
选择 Vue 3 和 TypeScript 支持。
3.2 安装依赖
安装 Vue Router、Axios 和 Element Plus(UI 组件库)。
npm install vue-router@4 axios element-plus
3.3 配置路由
在 src/router/index.ts
中配置路由:
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import Login from '../views/Login.vue';
import Register from '../views/Register.vue';
import Game from '../views/Game.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{ path: '/register', component: Register },
{ path: '/game', component: Game },
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
3.4 创建登录和注册页面
Login.vue
<template>
<div>
<el-form :model="loginForm" @submit.native.prevent="login">
<el-form-item label="用户名">
<el-input v-model="loginForm.username" autocomplete="off" />
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="loginForm.password" autocomplete="off" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import axios from 'axios';
export default defineComponent({
name: 'Login',
setup() {
const loginForm = reactive({
username: '',
password: ''
});
const login = async () => {
try {
const response = await axios.post('/api/users/login', loginForm);
console.log('登录成功', response.data);
} catch (error) {
console.error('登录失败', error);
}
};
return { loginForm, login };
}
});
</script>
Register.vue
<template>
<div>
<el-form :model="registerForm" @submit.native.prevent="register">
<el-form-item label="用户名">
<el-input v-model="registerForm.username" autocomplete="off" />
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="registerForm.password" autocomplete="off" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="register">注册</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import axios from 'axios';
export default defineComponent({
name: 'Register',
setup() {
const registerForm = reactive({
username: '',
password: ''
});
const register = async () => {
try {
const response = await axios.post('/api/users/register', registerForm);
console.log('注册成功', response.data);
} catch (error) {
console.error('注册失败', error);
}
};
return { registerForm, register };
}
});
</script>
3.5 实现 2048 游戏逻辑
Game.vue
<template>
<div>
<h2>2048 游戏</h2>
<el-button @click="startGame">开始游戏</el-button>
<!-- 游戏网格 -->
<div class="grid">
<div class="row" v-for="row in grid" :key="row">
<div class="cell" v-for="cell in row" :key="cell">{{ cell }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
name: 'Game',
setup() {
const grid = reactive([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]);
const startGame = () => {
// 初始化游戏逻辑
console.log('游戏开始');
};
return { grid, startGame };
}
});
</script>
<style scoped>
.grid {
display: grid;
grid-template-columns: repeat(4, 100px);
grid-gap: 10px;
}
.cell {
width: 100px;
height: 100px;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
}
</style>
4. 启动项目
- 后端:使用 Maven 或 Gradle 构建并运行 Spring Boot 项目。
- 前端:在前端项目目录下运行
npm run serve
。