实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)
Posted 小乔不掉发
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)相关的知识,希望对你有一定的参考价值。
在线抽奖系统:
1、项目介绍:
(1)功能介绍:
主要业务:为公司活动(如年会等)提供在线抽奖功能,满足奖品、抽奖人员的管理,及抽奖活动的需要。
- 用户注册
- 用户登录、会话管理
- 抽奖设置:奖品管理,抽奖人员管理
- 人员抽奖
(2)开发环境与技术栈:
- windows
- Maven
- Lombok
- Spring、SpringMVC、SpringBoot
- mysql、Mybatis、Druid
(3)项目演示:
用户登录:
用户注册:
奖项设置:
抽奖人员设置:
抽奖:
2、项目准备:
(1)代码框架:(源码)
全部代码源码 Github链接:
https://github.com/JACK-QBS/Project
(2)数据库设计:
数据库表关系图:
(业务上一对一)
为什么要有设置表?
- 1对1关联的表,其实可以只使用1张表保存所有字段;但是在一些可能出现的业务扩展,方便系统扩展使用,所以设计表时,考虑1对1设计。
- 拓展业务 ,一个用户进来设置,当多个用户进来设置的时候(多个用户属于同一公司),如果是只有用户表的话,及无法支撑业务,用户表关联公司,设置表在关联公司
3、后端对前端接口的实现:
要实现功能,需要先明确前后端约定好的接口。需要说明的是,接口的定义一般是前后端约定好的,所以也和前端代码息息相关,前端需要什么数据,需要什么格式的数据,也会在接口中体现。
接口主要体现在:
- 请求需要的信息:请求方法,请求路径,请求数据
- 响应数据
(1)用户的登录、注册、注销
用户登录:
前端请求:
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);
}
以上是关于实现一个在线抽奖系统,就算是个小白看了也能做出来(附源码)的主要内容,如果未能解决你的问题,请参考以下文章
学Python可少不了项目练手,这8个小项目有趣又实用,小白也能做出来!