当前位置:   article > 正文

springboot + vue项目:大事件文章管理系统(黑马程序员)_黑马程序员大事件项目

黑马程序员大事件项目

 一、项目介绍

首先声明,本项目静态资源和前端部分样式,后端部分接口代码由黑马程序员提供,我根据黑马程序员的基础更改了一部分功能,本项目学习视频:https://www.bilibili.com/video/BV14z4y1N7pg/?spm_id_from=333.337.search-card.all.click&vd_source=3c7831e560a6c8660ad9e4ed74fc8980

 该项目是经典的后台管理系统

前后端分别使用vue和springboot

首先我先进行演示一下

 

 

 

 

 

 

 

二、业务实现逻辑

注册模块:用户通过输入用户名,邮箱,邮箱验证码,密码进行注册,后端接收到用户传入的数据,首先对数据进行校验,验证用户输入的邮箱验证码是否与数据库中的匹配,如果匹配就通过验证,将用户的信息存储到数据库中,其中密码通过md5加密算法进行算法加密,前端返回用户登录成功。

登录模块:用户输入邮箱和密码后进行登录,后端开始对数据进行验证,首先验证邮箱输入是否正确,如果错误返回给用户提示信息,否则登录成功,当用户登录成功后,后端会生成相应的token,并储存在redis中一份,用户在访问其他页面时,必须携带token,否则拦截器进行拦截。

忘记密码模块:用户通过输入邮箱,获取验证码后进行校验比对,如果用户输入的验证码正确,就通过下一步,用户输入新密码,确认新密码后,密码重置成功。

文章管理模块:用户登录成功后,会跳转后文章管理的页面,如果用户在数据库中有数据,则前端向后端发送请求获取文章数据,同时获取用户数据的数量,对其进行分页,用户可以进行条件查询,后端使用动态sql语句进行查询并返回结果,用户点击添加文章,前端通过监听点击事件,弹出抽屉,用户可以输入文章标题,文章分类通过调用后台获取用户的分类数量并显示,用户输入文章封面时,获取图片的url地址,图片会上传到阿里云服务器,数据库存储的仅仅只是数据库的路径,当用户点击编辑按钮时,也会通过监听事件弹出抽屉,并且获取点击按钮的数据并展示,当用户修改了数据,会调用后台的接口进行修改,当用户删除时会进行提示,当用户点击确定后,后台会将该文章从该数据库中删除。

文章分类模块:用户点击分类模块时,前端监听到事件后会调用后台接口进行数据查询,并且返回给前端,前端进行渲染,用户点击添加分类时会弹出表单框,用户输入数据后,前端会把数据返回到后端,后端进行添加,用户点击编辑分类时,通过会弹出表单框,表单框中存有数据,用户可以修改数据然后提交,后端接收到用户修改的数据进行修改,当用户点击删除时,前端监听到事件后会提示用户是否删除,如果用户点击确定,前端调用后端接口进行删除。

基本资料模块:用户点击基本资料,调用后台接口查询数据库中的用户信息,渲染在前端页面上,当用户点击操作按钮时,会弹出一个表单框,用户数据渲染在表单上,用户可以修改用户信息,但是用户名和邮箱是不可修改的,用户无权限修改。

上传头像模块:用户点击选择图片,选择本地图片,然后调用后端接口显示在页面上,此时图片储存到了阿里云服务器上,数据库仅仅存放图片在阿里云服务器url地址,当用户点击上传,后端接口接收到指令后,向阿里云服务器发送请求,获取图片,并且将图片返给前端,前端将图片渲染在页面上。

重置密码模块:用户在表单中输入旧密码,新密码和确认新密码后,点击提交,前端将用户数据发送给后端,后端接收到请求后首先判断用户输入的旧密码是否正确,如果错误,返回给前端错误信息,前端将错误信息渲染后返给用户,如果正确,则验证输入的新密码和确认后的新密码是否一致,如果一致,则调用接口修改数据库中的密码,并将md5加密后的密码存储在数据库中。

三、代码实现

由于项目过大,本次我仅仅演示 登录注册模块的前后端交互模块

后端

在后端接口中,我们创建业务的模块分为四部分,controller层,mapper层,service层和pojo层

controller层

  1. @PostMapping("/register")
  2. public Result register(@RequestBody UserRegistrationDTO userRegistrationDTO) {
  3. //获取前端传过来的参数
  4. String username = userRegistrationDTO.getUsername();
  5. String password = userRegistrationDTO.getPassword();
  6. String email = userRegistrationDTO.getEmail();
  7. String captcha = userRegistrationDTO.getCaptcha();
  8. if (username == null && password == null && email == null && captcha == null){
  9. return Result.error("请求参数不能为空!");
  10. }
  11. // 查找用户名是否已被占用
  12. User user = userService.findByUserName(username);
  13. if (user != null) {
  14. return Result.error("用户名已被占用!");
  15. }
  16. //查找邮箱是否被占用
  17. User find_email = userService.findByEmail(email);
  18. if (find_email != null){
  19. captchaMapper.deleteByEmail(email);
  20. return Result.error("邮箱已经被占用!");
  21. }
  22. // 验证邮箱和验证码是否匹配
  23. try {
  24. capthcaService.vaildCaptcha(email, captcha);
  25. } catch (IllegalArgumentException e) {
  26. return Result.error("验证码错误!");
  27. }
  28. // 注册用户
  29. try {
  30. userService.register(username, password, email, captcha);
  31. return Result.success();
  32. } catch (Exception e) {
  33. return Result.error("注册失败:" + e.getMessage());
  34. }
  35. }
  36. @PostMapping("/login")
  37. public Result<String> login(@RequestBody User user){
  38. String email = user.getEmail();
  39. String password = user.getPassword();
  40. // System.out.println(email);
  41. // System.out.println(password);
  42. //查找是否存在用户邮箱
  43. User find_email = userService.findByEmail(email);
  44. if (find_email == null){
  45. return Result.error("邮箱不存在!");
  46. }
  47. //加密后的密码
  48. String passwordByEmail = userService.findPasswordByEmail(email);
  49. // System.out.println("数据库中加密后的密码:"+passwordByEmail);
  50. String inputUserPassword = Md5Util.getMD5String(password);
  51. // System.out.println("用户输入的加密后的密码:"+inputUserPassword);
  52. if (passwordByEmail.equals(inputUserPassword)){
  53. //登录成功
  54. userService.login(email,password);
  55. //获取数据库中的用户名和id
  56. String usernameByEmail = userService.findUsernameByEmail(email);
  57. Integer idByEmail = userService.findIdByEmail(email);
  58. //jwt令牌验证
  59. Map<String,Object> claims = new HashMap<>();
  60. claims.put("id",idByEmail);
  61. claims.put("username",usernameByEmail);
  62. String token = JwtUtil.genToken(claims);
  63. // System.out.println(token);
  64. //把token存储到redis中
  65. ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
  66. //将token设置为键值对,token即为键,又为值,并且设置redis中token过期时间为12天
  67. operations.set(token,token,12, TimeUnit.DAYS);
  68. return Result.success(token);
  69. }else {
  70. return Result.error("密码错误!");
  71. }
  72. }

mapper层

  1. @Mapper
  2. public interface UserMapper {
  3. //根据用户名查询
  4. @Select("select * from user where username = #{username}")
  5. User findByUserName(String username);
  6. @Insert("insert into user(username,password,email,create_time,update_time) " +
  7. "values(#{username},#{encryptedPassword},#{email},now(),now())")
  8. void register(User user);
  9. //查询数据库中的邮箱
  10. @Select("select * from user where email = #{email}")
  11. User findByEmail(String email);
  12. //查询数据库中加密后的密码
  13. @Select("SELECT password FROM user WHERE email =#{email}")
  14. String findPasswordByEmail(String email);
  15. //根据邮箱查询数据库中的用户名
  16. @Select("SELECT username FROM user WHERE email =#{email}")
  17. String findUsernameByEmail(String email);
  18. //根据邮箱查询数据库中的id
  19. @Select("SELECT id FROM user WHERE email =#{email}")
  20. Integer findIdByEmail(String email);
  21. }

service层

  1. @Override
  2. public void register(String username, String password, String email, String captcha) {
  3. // 创建用户对象
  4. User user = new User();
  5. user.setUsername(username);
  6. String encryptedPassword = Md5Util.getMD5String(password);
  7. user.setEncryptedPassword(encryptedPassword);
  8. user.setEmail(email);
  9. // 将用户信息添加到数据库
  10. userMapper.register(user);
  11. }
  12. @Override
  13. public void login(String email, String password) {
  14. // 根据用户名查询用户信息
  15. User email_login = userMapper.findByEmail(email);
  16. if (email_login == null) {
  17. throw new IllegalArgumentException("邮箱不存在!");
  18. }
  19. // 验证密码是否匹配
  20. String encryptedPassword = Md5Util.getMD5String(password);
  21. if (!email_login.getPassword().equals(encryptedPassword)) {
  22. throw new IllegalArgumentException("密码已经错误!");
  23. }
  24. }

pojo层

  1. //lombok 在编译阶段自动生成getter setter toString等方法
  2. @Data
  3. public class User {
  4. private Integer id;//主键ID
  5. private String username;//用户名
  6. // @JsonIgnore //让springmvc把当前对象转换成json格式的时候忽略
  7. private String password;//密码
  8. private String encryptedPassword;//加密后密码
  9. private String nickname;//昵称
  10. private String email;//邮箱
  11. private String userPic;//用户头像地址
  12. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  13. private LocalDateTime createTime;//创建时间
  14. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  15. private LocalDateTime updateTime;//更新时间
  16. private String age; //年龄
  17. private String phoneNumber; //手机号
  18. private String gender; //性别
  19. private String work; //职业
  20. private String hobby; //兴趣爱好
  21. private String area; //地区
  22. private String personSignature; //个性签名
  23. }

前端

登录注册在一个vue文件中,两个表单通过数据模型的变化而变化

  1. <script setup>
  2. import {User, Lock, CreditCard, Right} from '@element-plus/icons-vue'
  3. import {ref} from 'vue'
  4. // 导入 axios
  5. import axios from 'axios';
  6. import {getCaptcha,startCountdown} from './api/register.js'
  7. import {ElMessage} from "element-plus";
  8. //控制注册与登录表单得显示, 默认显示注册
  9. const isRegister = ref(false)
  10. //定义用于注册的数据模型
  11. const registerData = ref({
  12. username:'',
  13. email:'',
  14. captcha:'',
  15. password:'',
  16. repassword:''
  17. });
  18. const loginData = ref({
  19. email:'',
  20. password:''
  21. })
  22. //自定义确认密码的校验函数
  23. const rePasswordValid = (rule, value, callback) => {
  24. if (value == null || value === '') {
  25. return callback(new Error('请再次确认密码'))
  26. }else if(value !== registerData.value.password) {
  27. return callback(new Error('两次输入密码不一致'))
  28. }else {
  29. return callback()
  30. }
  31. }
  32. //定义表单校验规则
  33. const rules = {
  34. username:[
  35. {required:true,message:"请输入用户名",trigger:"blur"},
  36. {max:16,min:5,message: "长度为5到16位非空字符",trigger: "blur"}
  37. ],
  38. email:[
  39. {required:true,message:"请输入邮箱",trigger:"blur"}
  40. ],
  41. captcha:[
  42. {required:true,message:"请输入验证码",trigger:"blur"}
  43. ],
  44. password:[
  45. {required:true,message:"请输入密码",trigger:"blur"},
  46. {max:16,min:5,message: "长度为5到16位非空字符",trigger: "blur"}
  47. ],
  48. repassword:[
  49. {validator:rePasswordValid,trigger:"blur"}
  50. ]
  51. }
  52. const email = ref('');
  53. const disabled = ref(false);
  54. const countdown = ref(60);
  55. const buttonText = ref('获取验证码');
  56. function handleGetVerificationCode() {
  57. // 检查邮箱是否为空
  58. if (!registerData.value.email) {
  59. console.error("邮箱不能为空!");
  60. return;
  61. }
  62. // 发送请求给后端获取验证码
  63. getCaptcha(registerData.value.email, axios)
  64. .then(response => {
  65. ElMessage.success("验证码发送成功!")
  66. // 验证码发送成功,开始倒计时
  67. disabled.value = true;
  68. startCountdown({ disabled, countdown, buttonText });
  69. })
  70. .catch(error => {
  71. console.error(error);
  72. // 处理错误情况
  73. });
  74. }
  75. // 发送注册请求给后端
  76. async function register(){
  77. // 从 registerData 中获取用户输入的数据
  78. const { username, email, captcha, password } = registerData.value;
  79. // 发送注册请求给后端
  80. try {
  81. const response = await axios.post('/api/user/register', {
  82. username,
  83. password,
  84. email,
  85. captcha
  86. }, {
  87. headers: {
  88. 'Content-Type': 'application/json'
  89. }
  90. });
  91. // 根据后端返回的响应进行处理
  92. if (response.data.code === 0) {
  93. ElMessage.success("注册成功!")
  94. } else {
  95. console.error('注册失败:', response.data.message);
  96. // 在界面上显示错误提示信息或者执行其他必要的操作
  97. ElMessage.error(response.data.message?response.data.message:"注册失败!")
  98. }
  99. } catch (error) {
  100. // 处理注册失败的情况
  101. console.error('验证注册失败:', error.message);
  102. }
  103. }
  104. import {useTokenStore} from "@/stores/token.js";
  105. import {useRouter} from 'vue-router'
  106. const router = useRouter()
  107. const tokenStore = useTokenStore()
  108. //发送登录请求给后端
  109. async function login(){
  110. // 从 loginData 中获取用户输入的数据
  111. const {email,password} =loginData.value;
  112. //给后端发送登录请求
  113. try {
  114. const response = await axios.post('/api/user/login', {
  115. email,
  116. password
  117. }, {
  118. headers: {
  119. 'Content-Type': 'application/json',
  120. }
  121. });
  122. if (response.data.code === 0){
  123. console.log('登录成功:', response.data);
  124. ElMessage.success("登录成功!")
  125. //将得到的token放入pinia中
  126. tokenStore.setToken(response.data.data,12 * 24 * 60 * 60)//设置12天有效期
  127. //登录成功后跳转到主页面
  128. router.push('/layoutMain')
  129. }else {
  130. // 在界面上显示错误提示信息或者执行其他必要的操作
  131. ElMessage.error(response.data.message?response.data.message:"登录失败!")
  132. }
  133. }catch (error){
  134. console.log("登录失败:"+error.message)
  135. }
  136. }
  137. //忘记密码跳转页面
  138. const forgetPwd = ()=>{
  139. router.push('/forgetPwd')
  140. }
  141. </script>
  142. <template>
  143. <el-row class="login-page">
  144. <el-col :span="12" class="bg"></el-col>
  145. <el-col :span="6" :offset="3" class="form">
  146. <!-- 注册表单 -->
  147. <template v-if="isRegister">
  148. <el-form ref="form" size="large" autocomplete="off" :rules="rules" :model="registerData">
  149. <el-form-item>
  150. <h1>注册</h1>
  151. </el-form-item>
  152. <el-form-item prop="username">
  153. <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
  154. </el-form-item>
  155. <el-form-item prop="email">
  156. <el-input :prefix-icon="CreditCard" placeholder="请输入邮箱" v-model="registerData.email"></el-input>
  157. <el-button type="primary" :disabled="disabled" @click="handleGetVerificationCode">{{buttonText}}</el-button>
  158. </el-form-item>
  159. <el-form-item prop="captcha">
  160. <el-input :prefix-icon="Right" placeholder="请输入验证码" v-model="registerData.captcha"></el-input>
  161. </el-form-item>
  162. <el-form-item prop="password">
  163. <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
  164. </el-form-item>
  165. <el-form-item prop="repassword">
  166. <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.repassword"></el-input>
  167. </el-form-item>
  168. <!-- 注册按钮 -->
  169. <el-form-item>
  170. <el-button class="button" type="primary" auto-insert-space @click="register">
  171. 注册
  172. </el-button>
  173. </el-form-item>
  174. <el-form-item class="flex">
  175. <el-link type="info" :underline="false" @click="isRegister = false">
  176. ← 返回
  177. </el-link>
  178. </el-form-item>
  179. </el-form>
  180. </template>
  181. <!-- 登录表单 -->
  182. <template v-else>
  183. <el-form ref="form" size="large" autocomplete="off" :rules="rules" :model="loginData">
  184. <el-form-item>
  185. <h1>登录</h1>
  186. </el-form-item>
  187. <el-form-item prop="email">
  188. <el-input :prefix-icon="User" placeholder="请输入邮箱" v-model="loginData.email"></el-input>
  189. </el-form-item>
  190. <el-form-item prop="password">
  191. <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="loginData.password"></el-input>
  192. </el-form-item>
  193. <el-form-item class="flex">
  194. <div class="flex">
  195. <el-checkbox>记住我</el-checkbox>
  196. <el-link type="primary" :underline="false" @click="forgetPwd">忘记密码?</el-link>
  197. </div>
  198. </el-form-item>
  199. <!-- 登录按钮 -->
  200. <el-form-item>
  201. <el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
  202. </el-form-item>
  203. <el-form-item class="flex">
  204. <el-link type="info" :underline="false" @click="isRegister = true">
  205. 注册 →
  206. </el-link>
  207. </el-form-item>
  208. </el-form>
  209. </template>
  210. </el-col>
  211. </el-row>
  212. </template>
  213. <style lang="scss" scoped>
  214. /* 样式 */
  215. .login-page {
  216. height: 100vh;
  217. background-color: #fff;
  218. .bg {
  219. background: url('@/assets/logo.png') no-repeat 60% center / 240px auto,
  220. url('@/assets/login_bg.jpg') no-repeat center / cover;
  221. border-radius: 0 20px 20px 0;
  222. }
  223. .form {
  224. display: flex;
  225. flex-direction: column;
  226. justify-content: center;
  227. user-select: none;
  228. .title {
  229. margin: 0 auto;
  230. }
  231. .button {
  232. width: 100%;
  233. }
  234. .flex {
  235. width: 100%;
  236. display: flex;
  237. justify-content: space-between;
  238. }
  239. /* 将输入框和按钮的宽度设为自适应 */
  240. .el-input {
  241. flex: 2; /* 较长 */
  242. }
  243. .el-button {
  244. flex: 1; /* 较短 */
  245. }
  246. /* 调整输入框和按钮之间的间距 */
  247. .el-input + .el-button {
  248. margin-left: 10px;
  249. }
  250. }
  251. }
  252. </style>

浏览器跨域问题

在前后端分离的项目中还存在浏览器跨域问题,我们通过配置代理的方法,让前端向后端发送请求,不直接通过浏览器发送

  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. // https://vitejs.dev/config/
  5. export default defineConfig({
  6. plugins: [
  7. vue(),
  8. ],
  9. resolve: {
  10. alias: {
  11. '@': fileURLToPath(new URL('./src', import.meta.url))
  12. }
  13. },
  14. //为后端服务器配置代理
  15. server:{
  16. proxy:{
  17. '/api':{
  18. target:'http://localhost:8080', //后端服务器地址
  19. changeOrigin:true,
  20. rewrite:(path)=>path.replace(/^\/api/,'') //api替换为控制符串
  21. }
  22. }
  23. }
  24. })

本项目的源码后续更新......尽请期待.......

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/1016980
推荐阅读
相关标签
  

闽ICP备14008679号