一、项目简介
竞赛信息管理系统,针对竞赛信息管理的需求,面向对竞赛管理的管理员用户,基于B/S体系架构,后端采用SpringBoot技术,前端使用BootStrap框架,实现对竞赛信息进行增删改查功能,管理员也具有注册、登录以及注销功能。
二、前置配置
1、创建数据库
在数据库中设计两张表,分别表示用户表以及管理员表。
-- 创建competition数据库 drop database if exists competition; create database competition character set 'utf8mb4'; use competition; -- 创建竞赛信息表 create table competition_info ( competition_id int primary key auto_increment, competition_name varchar(100) unique , competition_description varchar(100) not null, publication_date datetime not null, submission_deadline datetime not null, sponsor varchar(100) not null, venue varchar(100) not null )default charset='utf8mb4'; -- 创建管理员表 create table user ( uid int primary key auto_increment, username varchar(50) unique , password varchar(65) not null )default charset='utf8mb4';
2、编写application.yml文件
# 配置数据库的连接字符串 spring: datasource: url: jdbc:mysql://127.0.0.1/competition?characterEncoding=utf8 username: root password: gsy20021014 driver-class-name: com.mysql.cj.jdbc.Driver # 设置 Mybatis 的 xml 保存路径 mybatis: mapper-locations: classpath:mapper/**Mapper.xml configuration: # 配置打印 MyBatis 执行的 SQL log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis 执行的 SQL logging: level: com: demo: usermanager: debug
三、公共基础类
1、自定义登录拦截器类
根据session会话信息来判断用户是否登录,用户访问页面时如果不是登录状态,就会将用户跳转到登录页面,让用户登录之后在进行操作。
@Component public class LoginIntercept implements HandlerInterceptor { /* true:已将登录状态 false:未登录状态 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if(session != null && session.getAttribute(ConstVariable.USER_SESSION_KEY) != null){ //表示已经在登录状态 return true; } //未在登录状态,跳转到登录页面 response.sendRedirect("/login.html"); return false; } }
2、自定义拦截规则
除了与登录有关的接口活或页面以及注册相关的不被拦截之外,别的都要被拦截。
@Configuration public class AppConfig implements WebMvcConfigurer { @Autowired private LoginIntercept loginIntercept; @Override public void addInterceptors(InterceptorRegistry registry) { System.out.println(registry.addInterceptor(loginIntercept). addPathPatterns("/**"). excludePathPatterns("/css/**"). excludePathPatterns("/js/**"). excludePathPatterns("/images/**"). excludePathPatterns("/login"). excludePathPatterns("/adduser"). excludePathPatterns("/**/login.html") .excludePathPatterns("/**/addUser.html") ); } }
3、统一数据返回类
需要实现ResponseBodyAdvice接口,supports方法表示是否支持使用下面自定义的数据返回,beforeBodyWrite方法来自定义数据返回的格式。
public class MyResponseAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HashMap<String,Object> res = new HashMap<>(); res.put("state",1); res.put("msg",""); res.put("data",body); return res; } }
4、统一异常处理类
对异常返回的格式进行了统一处理。
@RestControllerAdvice public class MyExceptionAdvice { @ExceptionHandler(Exception.class) public Object exceptionAdvice(Exception e){ Map<String,Object> res = new HashMap<>(); res.put("state:",-1); res.put("errorMessage",e.getMessage()); res.put("data",""); return res; } }
5、工具类
a、密码工具类
对用户的密码使用md5和加盐的形式进行加密。
md5:是将任意长度的输入通过一个算法然后生成一个128位的输出,通常情况下是用32位的16进制来表示,其加密是不可逆的即加密之后不能通过加密的数据来推测出未加密的密码。
加盐:由于md5对于同一个密码加密的结果是固定的,风险较大,就需要使用到加盐,也就是给原密码加上一个随机数,之间再加上#是为了方便解密。
public class PasswordUtil { /*加密操作*/ public static String encryption(String password){ String salt = IdUtil.simpleUUID();//生成随机的32位盐值 String midpwd = SecureUtil.md5(salt+password); return salt+"#"+midpwd;//方便解密 } /*解密:判断密码是否相同,并不能得到解密后的密码*/ public static boolean decrypt(String password,String truePassword){ if(StringUtils.hasLength(password) && StringUtils.hasLength(truePassword)){ if(truePassword.length() == 65 && truePassword.contains("#")){ String[] pwd = truePassword.split("#"); String salt = pwd[0];//得到盐值 String midPassword = pwd[1];//得到盐值+密码使用md5加密后的密码 password = SecureUtil.md5(salt+password); if(password.equals(midPassword)){ return true; } } } return false; }
b、时间工具类
由于竞赛信息需要用到发布时间以及截止时间,就使用SimpleDateFormat对时间格式进行统一处理。
public class DateUtil { public static boolean isValid(String dateStr) { DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setLenient(false); try { sdf.parse(dateStr); } catch (ParseException e) { return false; } return true; } }
6、全局变量
存放session用户的id。
public class ConstVariable { //表示session用户的id public static final String USER_SESSION_KEY = "user_session_key"; }
四、用户模块
1、定义用户实体类
@Data public class User { private int uid; private String username; private String password; }
2、编写UserMapper接口
@Mapper public interface UserMapper { //根据用户名获取用户信息 User getUserByName(@Param("username") String username); //添加用户,返回受影响的函数 int addUser(User user); //删除用户,返回受影响的行数 int deleteUser(@Param("uid") Integer uid); }
3、编写UserMapper.xml文件
编写对用户表进行操作的sql语句。
<?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.competition.mapper.UserMapper"> <insert id="addUser"> insert into user(username,password) values(#{username},#{password}); </insert> <delete id="deleteUser"> delete from user where uid=#{uid} </delete> <select id="getUserByName" resultType="com.example.competition.bean.User"> select * from user where username=#{username} </select> </mapper>
4、定义UserService类
由于接口层一次性只处理一个sql操作,当某一个功能想要完成两个甚至以上的操作的时候就需要使用service层将这些sql操作给封装起来然后再供控制层调用。
@Service public class UserService { @Autowired private UserMapper userMapper; public User getUserByName(String name) { return userMapper.getUserByName(name); } public int addUser(User user){ return userMapper.addUser(user); } public int deleteUser(Integer uid){ return userMapper.deleteUser(uid); } }
5、UserController处理请求
首先将UserService注入进来。
@Autowired private UserService userService;
a、用户登录
对接口传入的密码和用户名进行判空操作,然后根据用户名得到用户信息,得到数据库中的用户密码与传入的密码进行解密对比,如果一致则登录成功,存储用户的session会话信息,否则登录失败。
//用户登录方法 @RequestMapping("/login") public boolean login(HttpServletRequest request, String username, String password){ if(StringUtils.hasLength(username) && StringUtils.hasLength(password)){ User user = userService.getUserByName(username); //对数据库中的密码和传入的密码进行解密对比 if(PasswordUtil.decrypt(password,user.getPassword())){ if(user != null && user.getUid() > 0){ //存储session会话信息 HttpSession session = request.getSession(true); session.setAttribute(ConstVariable.USER_SESSION_KEY,user); return true; } } } return false; }
前端代码实现:
<script> function login(){ //对登录名和密码进行非空校验 var username = jQuery("#username"); var password = jQuery("#password"); //对首部去空格后进行非空校验 if(jQuery.trim(username.val()) === ""){ alert("请输入登录名"); //清除原有数据,将光标定位到输入框起始位置 loginname.focus(); } if(jQuery.trim(password.val()) === ""){ alert("请输入密码"); password.focus(); } jQuery.ajax({ url:"login", type:"GET", data:{"username":username.val(),"password":password.val()}, success:function (result){ if(result != null && result.data === true){ location.href = "list.html"; }else{ alert("用户名或密码输入错误"); } } }); } </script>
b、用户注册
首先对传入的用户进行判空操作,接着对用户名做唯一性校验,最后对用户密码进行加密之后再存入数据库中。
//添加用户,返回受影响的行数 @RequestMapping("/adduser") public int addUser(User user){ int res = 0; if(user == null){ return res; } //对用户名做唯一性校验 if(userService.getUserByName(user.getUsername()) != null){ return res; } //对添加的用户的密码进行加密 user.setPassword(PasswordUtil.encryption(user.getPassword())); res = userService.addUser(user); return res; }
前端代码实现:
<script> //进行注册操作 function reg(){ var username = jQuery("#username"); var password = jQuery("#password"); var password2 = jQuery("#password2"); //非空校验 if(jQuery.trim(username.val()) === ""){ alert("请先输入用户名"); username.focus(); return false; } if(jQuery.trim(password.val()) === ""){ alert("请先输入密码"); password.focus(); return false; } if(jQuery.trim(password2.val()) === ""){ alert("请先输入确认密码"); password2.focus(); return false; } if(password.val() !== password2.val()){ alert("两次密码输入不一致,请重新输入"); password.focus(); password2.focus(); return false; } jQuery.ajax({ url:"adduser", type:"POST", data:{ "username":username.val(), "password":password.val(), }, success:function(result){ if(result.data > 0){ alert("添加成功!"); location.href = "login.html"; } } }); } </script>
c、用户注销
获取当前用户的会话信息,拿到用户的userid,将用户从数据库中进行删除。
//删除用户,返回受影响的行数 @RequestMapping("/deleteuser") public int deleteUser(HttpServletRequest request){ HttpSession session = request.getSession(false); User user = (User)session.getAttribute(ConstVariable.USER_SESSION_KEY); if(user != null){ return userService.deleteUser(user.getUid()); } return 0; }
前端代码实现:
function logout(){ if(confirm("确认注销当前用户吗")){ jQuery.ajax({ url:"deleteuser", type:"POST", success : function(result) { if (result != null && result.data > 0) { alert("注销用户成功"); location.href = "login.html"; }else{ alert("注销用户失败"); } } }) } }
竞赛信息管理系统——SSM2:https://developer.aliyun.com/article/1521818