1.6项目功能详细设计与实现
（包括各个功能模块详细设计步骤，通过功能描述、处理逻辑流程图或活动图、核心代码等方式呈现；按功能分小节讲解每个功能是如何是实现的?你的设计思路？贴出该功能相应的代码。）
package com.example.springboot.controller;

import com.example.springboot.entity.User;
import com.example.springboot.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping("/register")
    public String register(@RequestBody User user) {
        return userService.register(user);
    }

    @PostMapping("/login")
    public Map<String, Object> login(@RequestParam String username, @RequestParam String password) {
        Map<String, Object> result = new HashMap<>();
        User user = userService.getByUsername(username);
        if (user != null && userService.verifyPassword(password, user.getPassword())) {
            result.put("code", 200);
            result.put("message", "登录成功");
            result.put("data", Map.of(
                    "id", user.getId(),
                    "name", user.getUsername()
            ));
        } else {
            result.put("code", 401);
            result.put("message", "用户名或密码错误");
        }
        return result;
    }


    @PostMapping("/reset-password")
    public String resetPassword(@RequestParam String username,
                                @RequestParam String phone,
                                @RequestParam String newPassword) {
        return userService.resetPassword(username, phone, newPassword);
    }
    @GetMapping("/info")
    public Map<String, Object> getUserInfo(@RequestParam Integer id) {
        User user = userService.getById(id);
        Map<String, Object> result = new HashMap<>();
        if (user != null) {
            result.put("name", user.getUsername());
        } else {
            result.put("name", "未知用户");
        }
        return result;
    }
}
package com.example.springboot.entity;

public class User {
    private Integer id;
    private String username;
    private String password;
    private String phone;

    // Getters & Setters
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }

    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
}
package com.example.springboot.mapper;

import com.example.springboot.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {

    void insert(User user);

    User findByUsername(String username);

    User findByUsernameAndPhone(String username, String phone);

    void updatePassword(String username, String newPassword);

    User findById(Integer id);

}
package com.example.springboot.service;

import com.example.springboot.entity.User;
import com.example.springboot.mapper.UserMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    public String register(User user) {
        // 禁止注册 admin 用户名
        if ("admin".equalsIgnoreCase(user.getUsername())) {
            return "禁止注册管理员账户";
        }

        if (user.getUsername().length() != 6 || user.getPassword().length() != 8 || user.getPhone().length() != 11) {
            return "输入信息格式不正确";
        }

        user.setPassword(passwordEncoder.encode(user.getPassword()));
        userMapper.insert(user);
        return "注册成功";
    }


    public boolean login(String username, String rawPassword) {
        if ("admin1".equalsIgnoreCase(username)) {
            return "12345678".equals(rawPassword);  // 简单判断
        }

        User dbUser = userMapper.findByUsername(username);
        return dbUser != null && passwordEncoder.matches(rawPassword, dbUser.getPassword());
    }


    public String resetPassword(String username, String phone, String newPassword) {
        if (newPassword.length() != 8) {
            return "密码长度必须为8位";
        }

        User user = userMapper.findByUsernameAndPhone(username, phone);
        if (user == null) {
            return "用户名或手机号不匹配";
        }

        String encodedPassword = passwordEncoder.encode(newPassword);
        userMapper.updatePassword(username, encodedPassword);
        return "密码已重置";
    }

    // 根据id查询员工
    public User getById(Integer id) {
        return userMapper.findById(id);
    }



    public User getByUsername(String username) {
        if ("admin1".equalsIgnoreCase(username)) {
            User admin = new User();
            admin.setId(0); // 不影响数据库主键
            admin.setUsername("admin1");
            admin.setPassword(passwordEncoder.encode("12345678"));
            return admin;
        }
        return userMapper.findByUsername(username);
    }



    // 验证密码
    public boolean verifyPassword(String rawPassword, String encodedPassword) {
        if ("admin1".equals(rawPassword) && passwordEncoder.matches("12345678", encodedPassword)) {
            return true;
        }
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }



}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springboot.mapper.UserMapper">

    <insert id="insert" parameterType="com.example.springboot.entity.User">
        INSERT INTO user (username, password, phone)
        VALUES (#{username}, #{password}, #{phone})
    </insert>

    <select id="findByUsername" resultType="com.example.springboot.entity.User">
        SELECT * FROM user WHERE username = #{username}
    </select>

    <select id="findByUsernameAndPhone" resultType="com.example.springboot.entity.User">
        SELECT * FROM user WHERE username = #{username} AND phone = #{phone}
    </select>

    <update id="updatePassword">
        UPDATE user SET password = #{newPassword} WHERE username = #{username}
    </update>

    <select id="findById" resultType="com.example.springboot.entity.User">
        SELECT * FROM user WHERE id = #{id}
    </select>

</mapper>
<template>
  <div class="login-container">
    <div class="login-form">
      <h2 style="text-align: center;">登录</h2>
      <form @submit.prevent="handleLogin">
        <div class="input-group">
          <label for="username">用户名：</label>
          <input type="text" id="username" v-model="username" required />
        </div>
        <div class="input-group">
          <label for="password">密码：</label>
          <input type="password" id="password" v-model="password" required />
        </div>
        <button type="submit">登录</button>
        <div class="register-link">
          <p>没有账号？<a href="#" @click.prevent="$router.push('/register')">立即注册</a></p>
        </div>
        <div class="forgot-password-link">
          <p><a href="#" @click.prevent="$router.push('/reset-password')">忘记密码？</a></p>
        </div>

      </form>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const username = ref('')
const password = ref('')
const router = useRouter()

const handleLogin = async () => {
  if (username.value.length !== 6 || password.value.length !== 8) {
    alert('请输入6位用户名和8位密码')
    return
  }

  const url = `http://localhost:8080/user/login?username=${username.value}&password=${password.value}`

  try {
    const res = await fetch(url, { method: 'POST' })
    const data = await res.json()  // 解析 JSON 响应

    if (data.code === 200) {
      // 登录成功，保存 userId
      localStorage.setItem('userId', data.data.id)
      alert(data.message)
      // 保存用户名（重点，没有这个admin页面进不去）
      localStorage.setItem('username', username.value)
      // 判断是否为管理员账号
      if (username.value.toLowerCase() === 'admin1') {
        router.push('/admin/AdminFeedback'); // 跳转管理员页面
      } else {
        router.push('/')       // 跳转普通首页
      }
    } else {
      alert(data.message || '登录失败')
    }

  } catch (error) {
    alert('登录请求失败')
    console.error(error)
  }
}
</script>

















<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-image: url('@/assets/login-background.png');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}


.login-form {
  width: 400px;
  padding: 80px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);

}

.input-group {
  margin-bottom: 15px;
}

label {
  display: block;
  font-weight: bold;
  margin-bottom: 5px;
  text-align: left;
}

input[type="text"],
input[type="password"] {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  width: 420px;
  padding: 10px;
  background-color: #559ef1;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 10px;
}

button:hover {
  background-color: #4cb1dc;
}

.register-link {
  margin-top: 15px;
  text-align: center;
  font-size: 14px;
}

.register-link a {
  color: #42b983;
  text-decoration: none;
}

.register-link a:hover {
  text-decoration: underline;
}

.forgot-password-link {
  margin-top: 10px;
  text-align: center;
  font-size: 14px;
}

.forgot-password-link a {
  color: #3498db;
  text-decoration: none;
}

.forgot-password-link a:hover {
  text-decoration: underline;
}

</style>
<template>
  <div class="register-container">
    <div class="register-form">
      <h2 style="text-align: center;">注册</h2>
      <form @submit.prevent="handleRegister">
        <div class="input-group">
          <label for="username">用户名：</label>
          <input type="text" id="username" v-model="form.username" required />
        </div>
        <div class="input-group">
          <label for="phone">电话：</label>
          <input type="text" id="phone" v-model="form.phone" required />

        </div>
        <div class="input-group">
          <label for="password">密码：</label>
          <input type="password" id="password" v-model="form.password" required />
        </div>
        <div class="input-group">
          <label for="confirmPassword">确认密码：</label>
          <input type="password" id="confirmPassword" v-model="form.confirmPassword" required />
        </div>
        <button type="submit">注册</button>
        <div class="login-link">
          <p>已有账号？<a href="#" @click.prevent="$router.push('/login')">立即登录</a></p>
        </div>
      </form>
    </div>
  </div>
</template>

<script setup>
import { reactive } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const form = reactive({
  username: '',
  phone: '',
  password: '',
  confirmPassword: ''
})

const handleRegister = async () => {
  const usernameLower = form.username.toLowerCase()

  // 禁止包含 admin 相关用户名
  if (usernameLower === 'admin' || /^admin\d*$/.test(usernameLower)) {
    alert('禁止注册管理员相关用户名')
    return
  }
  // 基本表单校验
  if (form.username.length !== 6) {
    alert('用户名必须是6位')
    return
  }
  if (form.phone.length !== 11 || !/^\d{11}$/.test(form.phone)) {
    alert('请输入11位手机号')
    return
  }
  if (form.password.length !== 8) {
    alert('密码必须是8位')
    return
  }
  if (form.password !== form.confirmPassword) {
    alert('两次输入的密码不一致')
    return
  }

  // 发起注册请求
  try {
    const res = await fetch('http://localhost:8080/user/register', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        username: form.username,
        password: form.password,
        phone: form.phone
      })
    })

    const text = await res.text()
    alert(text)

    if (text === '注册成功') {
      router.push('/login')
    }
  } catch (error) {
    console.error('注册失败:', error)
    alert('注册请求失败，请稍后再试')
  }
}
</script>




<style scoped>
.register-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-image: url('@/assets/login-background.png');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.register-form {
  width: 400px;
  padding: 80px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}

.input-group {
  margin-bottom: 15px;
}

label {
  display: block;
  font-weight: bold;
  margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"] {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  width: 420px;
  padding: 10px;
  background-color: #559ef1;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 10px;
}

button:hover {
  background-color: #4cb1dc;
}

.login-link {
  margin-top: 15px;
  text-align: center;
  font-size: 14px;
}

.login-link a {
  color: #42b983;
  text-decoration: none;
}

.login-link a:hover {
  text-decoration: underline;
}
</style>
<template>
  <div class="reset-password-container">
    <div class="reset-password-form">
      <h2 style="text-align: center;">重置密码</h2>
      <form @submit.prevent="handleResetPassword">
        <div class="input-group">
          <label for="username">用户名：</label>
          <input type="text" id="username" v-model="username" required />
        </div>
        <div class="input-group">
          <label for="phone">电话：</label>
          <input type="tel" id="phone" v-model="phone" required />
        </div>
        <button type="submit">重置密码</button>
        <div class="login-link">
          <p>已有账号？<a href="#" @click.prevent="$router.push('/login')">立即登录</a></p>
        </div>
      </form>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const username = ref('')
const phone = ref('')
const router = useRouter()

const handleResetPassword = async () => {
  const newPassword = prompt('请输入新的8位密码：')
  if (!newPassword || newPassword.length !== 8) {
    alert('密码长度必须为8位')
    return
  }

  const url = `http://localhost:8080/user/reset-password?username=${username.value}&phone=${phone.value}&newPassword=${newPassword}`

  try {
    const res = await fetch(url, { method: 'POST' })
    const text = await res.text()
    alert(text)
    if (text === '密码已重置') {
      router.push('/login')
    }
  } catch (error) {
    alert('重置失败')
    console.error(error)
  }
}
</script>



<style scoped>
.reset-password-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-image: url('@/assets/login-background.png');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.reset-password-form {
  width: 400px;
  padding: 80px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}

h2 {
  text-align: center;
  margin-bottom: 30px;
  font-size: 24px;
  color: #333;
}

.input-group {
  margin-bottom: 20px;
}

label {
  display: block;
  font-weight: 600;
  margin-bottom: 8px;
  color: #444;
}

input[type="text"],
input[type="tel"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font-size: 14px;
  transition: border 0.2s;
}

input:focus {
  border-color: #559ef1;
  outline: none;
}

button {
  width: 420px;
  padding: 12px;
  background-color: #559ef1;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #3d8fe3;
}

.login-link {
  margin-top: 20px;
  text-align: center;
  font-size: 14px;
  color: #666;
}

.login-link a {
  color: #42b983;
  text-decoration: none;
}

.login-link a:hover {
  text-decoration: underline;
}
</style>


<template>
  <div class="admin-page">
    <!-- 头部开始 -->
    <header class="admin-header">
      <!-- 左侧 logo + 标题 -->
      <div class="header-left">
        <img src="@/assets/logo.png" class="logo" />
        <span class="title">背多分管理员后台</span>
      </div>
      <!-- 右侧 用户名 + 退出按钮 -->
      <div class="header-right">
        <span class="username">欢迎，{{ username }}</span>
        <el-button @click="logout" type="info" size="small" plain>登录/退出</el-button>
      </div>
    </header>
    <!-- 头部结束 -->

    <!-- 页面主体开始 -->
    <div class="admin-main">
      <!-- 左侧导航 -->
      <aside class="admin-sidebar">
        <el-menu
            router
            :default-active="$route.path"
            class="sidebar-menu"
        >

          <el-menu-item index="/admin/AdminNotices">
            <el-icon><Bell /></el-icon>
            <span>公告管理</span>
          </el-menu-item>

          <el-menu-item index="/admin/word">
            <el-icon><HomeFilled /></el-icon>
            <span>词库管理</span>
          </el-menu-item>

          <el-menu-item index="/admin/AdminFeedback">
            <el-icon><ChatDotRound /></el-icon>
            <span>用户反馈</span>
          </el-menu-item>

          <el-menu-item index="/admin/AdminForum">
            <el-icon><ChatDotRound /></el-icon>
            <span>论坛管理</span>
          </el-menu-item>



        </el-menu>
      </aside>
      <!-- 主体内容 -->
      <main class="admin-content">
        <RouterView />
      </main>
    </div>
    <!-- 页面主体结束 -->
  </div>
</template>

<script setup>
// 引入图标
import { HomeFilled, Document } from '@element-plus/icons-vue'
// 引入路由
import { useRouter } from 'vue-router'
// 响应式变量和生命周期
import { ref, onMounted } from 'vue'
// axios 请求
import axios from 'axios'

const username = ref('加载中...')
const router = useRouter()
const userId = localStorage.getItem('userId')

const logout = () => {
  localStorage.removeItem('userId');
  localStorage.removeItem('username');
  localStorage.clear()
  router.push('/login');
}


const fetchUserInfo = async () => {
  if (!userId || isNaN(userId)) {
    username.value = '未登录'
    return
  }

  try {
    const response = await axios.get('http://localhost:8080/user/info', {
      params: { id: userId }
    })
    if (response.data && response.data.name) {
      username.value = response.data.name
    } else {
      console.warn('响应中未包含用户名:', response.data)
      username.value = '未知用户'
    }
  } catch (error) {
    console.error('获取用户信息失败:', error)
    username.value = '未知用户'
  }
}

onMounted(() => {
  username.value = '管理员'
})


</script>

<style scoped>
.admin-header {
  height: 60px;
  background-color: #6596ff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  color: white;
}

.header-left {
  display: flex;
  align-items: center;
}

.logo {
  width: 40px;
  height: 40px;
  margin-right: 10px;
}

.title {
  font-size: 24px;
  font-weight: bold;
}

.header-right {
  display: flex;
  align-items: center;
}

.username {
  margin-right: 20px;
}

.admin-main {
  display: flex;
  min-height: calc(100vh - 60px);
}

.admin-sidebar {
  width: 200px;
  border-right: 1px solid #ddd;
}

.sidebar-menu {
  border: none;
}

.admin-content {
  flex: 1;
  background-color: #f8f8ff;
  padding: 20px;
}
</style>
<template>
  <div>
<!-- 头部开始 -->
<div style="height: 60px; background-color: #6596ff; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; box-sizing: border-box;">
  <!-- 左侧 logo + 标题 -->
  <div style="display: flex; align-items: center; color: white;">
    <img src="@/assets/logo.png" style="width: 40px; height: 40px; margin-right: 10px;" />
    <!-- 网站标题 -->
    <span style="font-size: 30px; font-weight: bold;">单词背多分</span>
  </div>
  <!-- 右侧 用户名 + 退出按钮 -->
  <div style="display: flex; align-items: center; color: white;">
    <!-- 用户名 -->
    <span style="margin-right: 20px;">欢迎，{{ username }}</span>

    <!-- 退出按钮 -->
    <el-button @click="logout" type="info" size="small" plain>登录/退出</el-button>
  </div>
</div>
<!-- 头部结束 -->

    <div>
      <!-- 下面部分开始 -->
      <div style="display: flex">
        <!-- 左侧导航菜单开始 -->
        <div style="width: 200px; border-right: 1px solid #ddd; min-height: calc(100vh - 60px)">
         <el-menu
          router
          :default-active="$route.path"
          style="border: 0">

           <el-menu-item index="/father/UserNotices">
             <el-icon><Bell /></el-icon>
             <span>系统公告</span>
           </el-menu-item>

           <el-menu-item index="/father/vocabulary">
             <el-icon><Document /></el-icon>
             <span>词典</span>
           </el-menu-item>

            <el-menu-item index="/">
              <el-icon><HomeFilled /></el-icon>
              <span>背单词</span>
            </el-menu-item>

           <el-sub-menu index="/father/UserForum">
             <template #title>
               <el-icon><ChatDotRound /></el-icon>
               <span>经验论坛</span>
             </template>
             <el-menu-item index="/father/Forumindex">论坛首页</el-menu-item>
             <el-menu-item index="/father/UserForum/postPublish">发布帖子</el-menu-item>
             <el-menu-item index="/father/UserForum/MyForum">我的论坛</el-menu-item>
           </el-sub-menu>

           <el-menu-item index="/father/userFeedback">
              <el-icon><Message /></el-icon>
              <span>用户反馈</span>
            </el-menu-item>




        </el-menu>

        </div>
        <!-- 左侧导航菜单结束 -->
        <!-- 右侧主体区域开始 -->
      <div style="flex: 1; width: 0; background-color: #f8f8ff; padding: 10px">
          <RouterView/>
        </div>
        <!-- 右侧主体区域结束 -->
      </div>
      <!-- 下面部分结束 -->
    </div>
  </div>
</template>


<script setup>
// 引入图标
import { HomeFilled, Document,ChatDotRound } from '@element-plus/icons-vue'
// 引入路由
import { useRouter } from 'vue-router'
// 引入响应式数据和生命周期
import { ref, onMounted } from 'vue'
// 引入 axios
import axios from 'axios'


// 响应式用户名变量
const username = ref('加载中...')

// 路由实例
const router = useRouter()

// 退出登录函数
const logout = () => {
  localStorage.removeItem('userId') // 清除用户信息
  localStorage.removeItem('username');//  清除用户名
  localStorage.clear()// 清除所有本地存储
  router.push('/login') // 跳转到登录页
}

// 获取本地存储的用户 ID
const userId = localStorage.getItem('userId')

// 获取用户信息函数
const fetchUserInfo = async () => {
  if (!userId || isNaN(userId)) {
    username.value = '未登录'
    return
  }

  try {
    const response = await axios.get('http://localhost:8080/user/info', {
      params: { id: userId }
    })

    // 确保返回结构是正确的
    if (response.data && response.data.name) {
      username.value = response.data.name
    } else {
      console.warn('响应中未包含用户名:', response.data)
      username.value = '未知用户'
    }

    } catch (error) {
      console.error('获取用户信息失败:', error)
      username.value = '未知用户'
    }
  }

// 挂载后执行
onMounted(() => {
  fetchUserInfo()
})
</script>

router.beforeEach((to, from, next) => {
    const title = to.meta.title;
    if (title) {
        document.title = title;
    }

    const username = localStorage.getItem('username');
    console.log('路由守卫触发，username:', username);

    // 管理员权限控制
    if (to.path.startsWith('/admin')) {
        if (!username || !/^admin\d*$/.test(username.toLowerCase())) {
            alert('您无权访问该页面');
            return next('/login');
        }
    }

    next();
});