实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

1、项目介绍:

(1)功能介绍:

主要业务:为公司活动(如年会等)提供在线抽奖功能,满足奖品、抽奖人员的管理,及抽奖活动的需要。

  • 用户注册
  • 用户登录、会话管理
  • 抽奖设置:奖品管理,抽奖人员管理
  • 人员抽奖

(2)开发环境与技术栈:

  • windows
  • Maven
  • Lombok
  • Spring、SpringMVC、SpringBoot
  • MySQL、Mybatis、Druid

(3)项目演示:

用户登录:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
用户注册:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
奖项设置:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
抽奖人员设置:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
抽奖:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

2、项目准备:

(1)代码框架:

实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

全部代码源码:

https://github.com/JACK-QBS/Project

(2)数据库设计:

数据库表关系图:
实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
(业务上一对一)

为什么要有设置表?

  • 1对1关联的表,其实可以只使用1张表保存所有字段;但是在一些可能出现的业务扩展,方便系统扩展使用,所以设计表时,考虑1对1设计。
  • 拓展业务 ,一个用户进来设置,当多个用户进来设置的时候(多个用户属于同一公司),如果是只有用户表的话,及无法支撑业务,用户表关联公司,设置表在关联公司

3、后端对前端接口的实现:

要实现功能,需要先明确前后端约定好的接口。需要说明的是,接口的定义一般是前后端约定好的,所以也和前端代码息息相关,前端需要什么数据,需要什么格式的数据,也会在接口中体现。

接口主要体现在:

  • 请求需要的用户登录: 实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    前端请求:

    POST api/user/login (请求路径)
    Content-Type: application/json
    {username: “qbs”, password: “123”}

    响应:

    { “success” : true }

    后端实现接口:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    用户注册:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    前端请求:

    POST api/user/register (请求路径)
    Content-Type: multipart/form-data; boundary=----
    WebKitFormBoundarypOUwkGIMUyL0aOZT

    username: qbs
    password: 123
    nickname: 帅哥
    email: 666@163.com
    age: 18
    headFile: (binary)

    响应:

    { “success” : true }

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    用户注销:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    前端请求:

    POST api/user/login (请求路径)

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    (2)查询奖项设置、修改抽奖人数:

    查询奖项设置:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
    前端请求:

    GET api/setting/query(请求路径)

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    修改抽奖人数:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    前端请求:

    GET api/setting/update?batchNumber=5(请求路径)
    (接口对应抽奖设置页面中,点每次抽奖人数下拉菜单切换时修改)

    后端实现:
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    (3)新增、修改、删除奖项:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    新增奖项:

    前端请求:

    POST api/award/add (请求路径)
    Content-Type: application/json

    {name: “特等奖”, count: 1, award: “全球旅行7日游”}

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    修改奖项:

    前端请求:

    POST api/award/update (请求路径)
    Content-Type: application/json

    后端响应:
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    删除奖项:

    前端请求:

    GET api/award/delete/4(请求路径)

    最后的数字4,对应奖项的id

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    (4)新增、修改、删除抽奖人员:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    新增抽奖人员:

    前端请求:

    POST api/member/add (请求路径)
    Content-Type: application/json

    后端实现:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    修改抽奖人员

    前端请求:

    POST api/member/update
    Content-Type: application/json

    后端实现:
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    删除抽奖人员

    前端请求:

    GET api/member/delete/97
    (最后的数字为抽奖人员的id)

    后端实现:
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    (5)抽奖、删除获奖人员:

    抽奖:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
    前端请求:

    POST api/record/add/3
    Content-Type: application/json
    (以上路径中最后的数字代表奖项id,请求数据为抽奖人员id组成的数组)

    后端实现:

    抽奖后端只是插入记录(人员id、奖项id),具体的抽奖是前端实现的,而且也是简单的实现方式,没有任何算法。(只是在当前奖项剩余名额中,每次抽奖人数,在所有未中奖的人员列表中,随机抽取)
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    删除获奖人员:

    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
    前端请求:

    GET api/record/delete/member?id=22
    (根据 人员id 删除对应的获奖记录)

    GET api/record/delete/award?id=3
    (根据 奖项id 删除对应所有获奖人员记录)

    后端实现:
    实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)

    4、代码设计:

    (1)设计数据库的实体类:

    通过 mybatis 生成工具(tool包):生成 mapper、数据库表实体类(model包)、xml 文件

    (2)设计统一响应类:

    主要是为了返回数据的统一字段设计

    /**  * 统一响应的数据格式  */ public class JSONResponse {     private boolean success;     private String code;     private String message;     private Object data; } 
    /**  * 统一数据封装  */ public class RequestResponseBodyMethodProcessorWrapper implements HandlerMethodReturnValueHandler {      private final HandlerMethodReturnValueHandler delegate;      public RequestResponseBodyMethodProcessorWrapper(HandlerMethodReturnValueHandler delegate) {         this.delegate = delegate;     }      @Override     public boolean supportsReturnType(MethodParameter returnType) {         return delegate.supportsReturnType(returnType);     }      @Override     public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {         //returnValue是Controller请求方法执行完,返回值         if(!(returnValue instanceof JSONResponse)){//返回值本身就是需要的类型,不进行处理             JSONResponse json = new JSONResponse();             json.setSuccess(true);             json.setData(returnValue);             returnValue = json;         }         delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);     } } 

    (3)设计自定义异常:

    主要针对不同的场景,需要抛异常来处理时,能定位业务含义。

    主要分为:

    • 1、 客户端请求错误时的异常:需要给定错误码,方便前端提示用户,如用户名存在不允许注册
    • 2、 业务发生错误时的异常:需要给定错误码,方便后端定位问题,一般如程序上的业务错误都可以抛(BUG)
    • 3、 系统发生错误时的异常:需要给定错误码,方便后端定位问题,程序出错,如数据库连接获取失败都可以抛(一般是系统发生错误,如网络断了,数据库挂了等等)自定义异常前端需要显示错误码和错误消息,用户可以根据提示信息判断原因。
    • 4、非自定义异常,异常信息一般是框架或JDK抛出的英文,是给开发人员描述错误的,无法给用户提示,所以错误信息提示为未知异常。
    /**  * 自定义异常:保存错误码和错误消息  */ @Getter @Setter public class AppException extends RuntimeException {      private String code;      public AppException( String code, String message) {         super(message);         this.code = code;     }      public AppException( String code, String message, Throwable cause) {         super(message, cause);         this.code = code;     } } 
    //统一异常处理 @ControllerAdvice @Slf4j//使用lombok日志日志注解,之后使用log属性来完成日志打印 public class ExceptionAdvice {      //自定义异常报错错误码和错误消息     @ExceptionHandler(AppException.class)     @ResponseBody     public Object handle1(AppException e){         JSONResponse json = new JSONResponse();         json.setCode(e.getCode());         json.setMessage(e.getMessage());         log.debug("自定义异常", e);         return json;     }      //非自定义异常(英文错误信息,堆栈信息,不能给用户看):     // 指定一个错误码,错误消息(未知错误,请联系管理员)     @ExceptionHandler(Exception.class)     @ResponseBody     public Object handle2(Exception e){         JSONResponse json = new JSONResponse();         json.setCode("ERR000");         json.setMessage("未知错误,请联系管理员");         log.error("未知错误", e);         return json;     } } 

    (4)设计统一会话管理的拦截器

    public class LoginInterceptor implements HandlerInterceptor {      private ObjectMapper objectMapper;      public LoginInterceptor(ObjectMapper objectMapper) {         this.objectMapper = objectMapper;     }      @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         HttpSession session = request.getSession(false);         if(session != null){//获取登录时设置的用户信息             User user = (User) session.getAttribute("user");             if(user != null){//登录了,允许访问                 return true;             }         }         //登录失败,不允许访问的业务:区分前后端         //TODO:前端跳转登录页面,后端返回json //        new ObjectMapper().writeValueAsString(object);//序列化对象为json字符串         //请求的服务路径         String servletPath = request.getServletPath();//   /apiXXX.html         if(servletPath.startsWith("/api/")){//后端逻辑:返回json             response.setCharacterEncoding("UTF-8");             response.setContentType(MediaType.APPLICATION_JSON_VALUE);             JSONResponse json = new JSONResponse();             json.setCode("USR000");             json.setMessage("用户没有登录,不允许访问");             String s = objectMapper.writeValueAsString(json);             response.setStatus(HttpStatus.UNAUTHORIZED.value());             PrintWriter pw = response.getWriter();             pw.println(s);             pw.flush();         }else{//前端逻辑:跳转到登录页面 /views/index.html             //相对路径的写法,一定是请求路径作为相对位置的参照点             //使用绝对路径来重定向,不建议使用相对路径和转发             String schema = request.getScheme();//http             String host = request.getServerName();//ip             int port = request.getServerPort();//port             String contextPath = request.getContextPath();//application Context path应用上下文路径             String basePath = schema+"://"+host+":"+port+contextPath;             //重定向到登录页面             response.sendRedirect(basePath+"/index.html");         }         return false;     } } 

    (5)设计Mybatis中Mapper的基类:

    使用Mybatis的接口方法,所有接口方法都是类似,只是传入参数和返回值不同,可以考虑设计统一的基类,以泛型的方式定义出不同的参数类型、返回类型

    /**  * 所有 mapper 父接口  */ public interface BaseMapper<T> {      int deleteByPrimaryKey(Integer id);      int insert(T record);      int insertSelective(T record);      T selectByPrimaryKey(Integer id);//通过主键查询      int updateByPrimaryKeySelective(T record);//根据主键修改其他非主键字段      int updateByPrimaryKey(T record); }