主要业务:为公司活动(如年会等)提供在线抽奖功能,满足奖品、抽奖人员的管理,及抽奖活动的需
要。
drop database if exists lucky_draw;
create database lucky_draw character set utf8mb4;
use lucky_draw;
drop table if exists user;
create table user(
id int primary key auto_increment,username varchar(20) not null unique comment '用户账号',
password varchar(20) not null comment '密码',
nickname varchar(20) comment '用户昵称',
email varchar(50) comment '邮箱',
age int comment '年龄',
head varchar(255) comment '头像url',
create_time timestamp default NOW() comment '创建时间'
) comment '用户表';
drop table if exists setting;
create table setting(
id int primary key auto_increment,
user_id int not null comment '用户id',
batch_number int not null comment '每次抽奖人数',
create_time timestamp default NOW() comment '创建时间',
foreign key (user_id) references user(id)
) comment '抽奖设置';
drop table if exists award;
create table award(
id int primary key auto_increment,
name varchar(20) not null comment '奖项名称',
count int not null comment '奖项人数',
award varchar(20) not null comment '奖品',
setting_id int not null comment '抽奖设置id',
create_time timestamp default NOW() comment '创建时间',
foreign key (setting_id) references setting(id)
) comment '奖项';
drop table if exists member;
create table member(
id int primary key auto_increment,
name varchar(20) not null comment '姓名',
no varchar(20) not null comment '工号',
user_id int not null comment '用户id',
create_time timestamp default NOW() comment '创建时间',
foreign key (user_id) references user(id)
) comment '抽奖人员';
drop table if exists record;
create table record(
id int primary key auto_increment,
member_id int not null,
award_id int not null,
create_time timestamp default NOW() comment '创建时间',
foreign key (member_id) references member(id),
foreign key (award_id) references award(id)
) comment '学生表';
insert into user(id, username, password, nickname, email, age, head) values (1,
'bit', '123', '小比特', '[email protected]', 18, 'img/test-head.jpg');
\## 数据字典:学生毕业年份
insert into setting(id, user_id, batch_number) values (1, 1, 8);insert into award(name, count, award, setting_id) values ('特靠谱欢乐奖', 1, '深圳湾
一号', 1);
insert into award(name, count, award, setting_id) values ('特靠谱娱乐奖', 5, 'BMW
X5', 1);
insert into award(name, count, award, setting_id) values ('特靠谱励志奖', 20, '办公
室一日游', 1);
\## 数据字典:学生专业
insert into member(name, no, user_id) values ('李寻欢', '水果刀', 1);
insert into member(name, no, user_id) values ('郭靖', '降猪十八掌', 1);
insert into member(name, no, user_id) values ('韦小宝', '抓?龙爪手', 1);
insert into member(name, no, user_id) values ('风清扬', '孤独九贱', 1);
insert into member(name, no, user_id) values ('哪吒', '喷气式电单车', 1);
insert into member(name, no, user_id) values ('渠昊空', 'no2', 1);
insert into member(name, no, user_id) values ('闵觅珍', 'no2', 1);
insert into member(name, no, user_id) values ('慈新之', 'no3', 1);
insert into member(name, no, user_id) values ('户柔绚', 'no4', 1);
insert into member(name, no, user_id) values ('柯雅容', 'no5', 1);
insert into member(name, no, user_id) values ('邰虹彩', 'no6', 1);
insert into member(name, no, user_id) values ('延易蓉', 'no7', 1);
insert into member(name, no, user_id) values ('吉娇然', 'no8', 1);
insert into member(name, no, user_id) values ('百里惜蕊', 'no9', 1);
insert into member(name, no, user_id) values ('云寻双', 'no10', 1);
insert into member(name, no, user_id) values ('衅嘉颖', 'no11', 1);
insert into member(name, no, user_id) values ('银以晴', 'no12', 1);
insert into member(name, no, user_id) values ('保颐和', 'no13', 1);
insert into member(name, no, user_id) values ('饶燕婉', 'no14', 1);
insert into member(name, no, user_id) values ('单阳平', 'no15', 1);
insert into member(name, no, user_id) values ('墨碧春', 'no16', 1);
insert into member(name, no, user_id) values ('侨诗柳', 'no17', 1);
insert into member(name, no, user_id) values ('羿灵珊', 'no18', 1);
insert into member(name, no, user_id) values ('甘凌波', 'no19', 1);
insert into member(name, no, user_id) values ('希忻然', 'no20', 1);
insert into member(name, no, user_id) values ('虎晴画', 'no21', 1);
insert into member(name, no, user_id) values ('闪雅洁', 'no22', 1);
insert into member(name, no, user_id) values ('风易云', 'no23', 1);
insert into member(name, no, user_id) values ('泷运盛', 'no24', 1);
insert into member(name, no, user_id) values ('沐长菁', 'no25', 1);
insert into member(name, no, user_id) values ('栗芃芃', 'no26', 1);
insert into member(name, no, user_id) values ('义涵蕾', 'no27', 1);
insert into member(name, no, user_id) values ('泥清妙', 'no28', 1);
insert into member(name, no, user_id) values ('亓官清宁', 'no29', 1);
insert into member(name, no, user_id) values ('侯曜曦', 'no30', 1);
insert into member(name, no, user_id) values ('齐淑雅', 'no31', 1);
insert into member(name, no, user_id) values ('邸平松', 'no32', 1);
insert into member(name, no, user_id) values ('泉千易', 'no33', 1);
insert into member(name, no, user_id) values ('段彩静', 'no34', 1);
insert into member(name, no, user_id) values ('伦晓凡', 'no35', 1);
insert into member(name, no, user_id) values ('余莎莎', 'no36', 1);
insert into member(name, no, user_id) values ('贵念梦', 'no37', 1);
insert into member(name, no, user_id) values ('接骊文', 'no38', 1);
insert into member(name, no, user_id) values ('龚芷蝶', 'no39', 1);
insert into member(name, no, user_id) values ('丙冷霜', 'no40', 1);
insert into member(name, no, user_id) values ('卫诗蕊', 'no41', 1);
insert into member(name, no, user_id) values ('濯雅懿', 'no42', 1);
insert into member(name, no, user_id) values ('蓝亦竹', 'no43', 1);
insert into member(name, no, user_id) values ('雷书君', 'no44', 1);
insert into member(name, no, user_id) values ('刚孤风', 'no45', 1);insert into member(name, no, user_id) values ('帛晨蓓', 'no46', 1);
insert into member(name, no, user_id) values ('雀凝梦', 'no47', 1);
insert into member(name, no, user_id) values ('於良工', 'no48', 1);
insert into member(name, no, user_id) values ('从翠阳', 'no49', 1);
insert into member(name, no, user_id) values ('宫咸英', 'no50', 1);
insert into member(name, no, user_id) values ('项英光', 'no51', 1);
insert into member(name, no, user_id) values ('胥友菱', 'no52', 1);
insert into member(name, no, user_id) values ('慎初翠', 'no53', 1);
insert into member(name, no, user_id) values ('锺映寒', 'no54', 1);
insert into member(name, no, user_id) values ('貊飞翔', 'no55', 1);
insert into member(name, no, user_id) values ('葛秀妮', 'no56', 1);
insert into member(name, no, user_id) values ('劳令梅', 'no57', 1);
insert into member(name, no, user_id) values ('昝欣怿', 'no58', 1);
insert into member(name, no, user_id) values ('党忆柏', 'no59', 1);
insert into member(name, no, user_id) values ('福月华', 'no60', 1);
insert into member(name, no, user_id) values ('睢巧春', 'no61', 1);
insert into member(name, no, user_id) values ('修听枫', 'no62', 1);
insert into member(name, no, user_id) values ('孔梦竹', 'no63', 1);
insert into member(name, no, user_id) values ('子车悦欣', 'no64', 1);
insert into member(name, no, user_id) values ('赵飞宇', 'no65', 1);
insert into member(name, no, user_id) values ('宁天睿', 'no66', 1);
insert into member(name, no, user_id) values ('申文心', 'no67', 1);
insert into member(name, no, user_id) values ('冀轩昂', 'no68', 1);
insert into member(name, no, user_id) values ('邬代灵', 'no69', 1);
insert into member(name, no, user_id) values ('佟嘉德', 'no70', 1);
insert into member(name, no, user_id) values ('溥绿兰', 'no71', 1);
insert into member(name, no, user_id) values ('改昊昊', 'no72', 1);
insert into member(name, no, user_id) values ('捷梦影', 'no73', 1);
insert into member(name, no, user_id) values ('孛书语', 'no74', 1);
insert into member(name, no, user_id) values ('粟芮优', 'no75', 1);
insert into member(name, no, user_id) values ('东门虹英', 'no76', 1);
insert into member(name, no, user_id) values ('漆梓玥', 'no77', 1);
insert into member(name, no, user_id) values ('尔幻玉', 'no78', 1);
insert into member(name, no, user_id) values ('丁秋玉', 'no79', 1);
insert into member(name, no, user_id) values ('平晨旭', 'no80', 1);
insert into member(name, no, user_id) values ('遇沙羽', 'no81', 1);
insert into member(name, no, user_id) values ('国琳溪', 'no82', 1);
insert into member(name, no, user_id) values ('仪谷枫', 'no83', 1);
insert into member(name, no, user_id) values ('钭尔琴', 'no84', 1);
insert into member(name, no, user_id) values ('澄慧丽', 'no85', 1);
insert into member(name, no, user_id) values ('佼清秋', 'no86', 1);
insert into member(name, no, user_id) values ('缪荌荌', 'no87', 1);
insert into member(name, no, user_id) values ('闻人幼丝', 'no88', 1);
insert into member(name, no, user_id) values ('绍美曼', 'no89', 1);
insert into member(name, no, user_id) values ('回访波', 'no90', 1);
要实现功能,需要先明确前后端约定好的接口。
需要说明的是,接口的定义一般是前后端约定好的,所以也和前端代码息息相关,前端需要什么数据,
需要什么格式的数据,也会在接口中体现。
接口主要体现在
请求
POST api/user/login
Content-Type: application/json
{username: "bit", password: "123"}
响应
{
"success" : true
}
请求
POST api/user/register
Content-Type: multipart/form-data; boundary=----
WebKitFormBoundarypOUwkGIMUyL0aOZT
username: haha
password: 111
nickname: 牛牛牛
email: 666@163.com
age: 66
headFile: (binary)
注意:以上请求数据是解析过的,http原生发送的数据还包含其他很多内容,比较多,可以动手抓包看
看。其中boundary后边的是随机生成的,请求数据中会使用该信息。
响应
{
"success" : true
}
请求
GET api/setting/query
响应
{
"success" : true,
"data" : {
"id" : 1,
"userId" : 1,
"batchNumber" : 8,"createTime" : "2020-08-14 08:16:31",
"user" : {
"id" : 1,
"username" : "bit",
"password" : "123",
"nickname" : "小比特",
"email" : "[email protected]",
"age" : 18,
"head" : "img/test-head.jpg",
"createTime" : "2020-08-14 08:16:31",
"settingId" : 1
},
"awards" : [ {
"id" : 1,
"name" : "特靠谱欢乐奖",
"count" : 1,
"award" : "深圳湾一号",
"settingId" : 1,
"createTime" : "2020-08-14 08:16:31",
"luckyMemberIds" : [ 5 ]
}, {
"id" : 2,
"name" : "特靠谱娱乐奖",
"count" : 5,
"award" : "BMW X5",
"settingId" : 1,
"createTime" : "2020-08-14 08:16:31",
"luckyMemberIds" : [ 56, 40, 32, 65, 81 ]
}, {
"id" : 3,
"name" : "特靠谱励志奖",
"count" : 20,
"award" : "办公室一日游",
"settingId" : 1,
"createTime" : "2020-08-14 08:16:31",
"luckyMemberIds" : [ 48, 68, 43, 73, 13, 83, 63, 25 ]
} ],
"members" : [ {
"id" : 1,
"name" : "李寻欢",
"no" : "水果刀",
"userId" : 1,
"createTime" : "2020-08-14 08:16:31"
}, {
"id" : 2,
"name" : "郭靖",
"no" : "降猪十八掌",
"userId" : 1,
"createTime" : "2020-08-14 08:16:31"
}, {
"id" : 3,
"name" : "韦小宝",
"no" : "抓?龙爪手",
"userId" : 1,
"createTime" : "2020-08-14 08:16:31"
} ]
}
}
请求
GET api/setting/update?batchNumber=5
接口对应抽奖设置页面中,点每次抽奖人数下拉菜单切换时修改
响应
{
"success" : true
}
请求
POST api/award/add
Content-Type: application/json
{
name: "牛哄哄", count: 3, award: "华为手机"}
响应
{
"success" : true
}
请求
POST api/award/update
Content-Type: application/json
{
name: "牛哄哄", count: 3, award: "小米手机", id: 4}
响应
{
"success" : true
}
请求
GET api/award/delete/4响应
最后的数字4,对应奖项的id
响应
{
"success" : true
}
请求
POST api/member/add
Content-Type: application/json
{
name: "羞羞的粉拳", no: "007"}
响应
{
"success" : true
}
请求
POST api/member/update
Content-Type: application/json
{
name: "泰山", no: "000", id: 96}
响应
{
"success" : true
}
请求
GET api/member/delete/97
注意最后的数字为抽奖人员的id
响应
{
"success" : true
}
请求
POST api/record/add/3
Content-Type: application/json
[92, 22, 43, 76]
以上路径中最后的数字代表奖项id,请求数据为抽奖人员id组成的数组
响应
{
"success" : true
}
请求
GET api/record/delete/member?id=22
根据人员id删除对应的获奖记录
响应
{
"success" : true
}
请求
GET api/record/delete/award?id=3
根据奖项id删除对应所有获奖人员记录
响应
{
"success" : true
}
Maven在IDEA中的配置,主要是全局配置文件和本地仓库路径的配置
数据库面板中配置MySQL数据库连接
Lombok插件的安装
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.4.RELEASEversion>
parent>
<groupId>frankgroupId>
<artifactId>lucky-drawartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.5version>
dependency>
<dependency><groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.21version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<finalName>lucky-drawfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
debug=true
# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
#扫描的包:druid.sql.Statement类和sun包
logging.level.druid.sql.Statement=ERROR
logging.level.sun=DEBUG
# 美化JSON数据格式
spring.jackson.serialization.indent-output=true
# 设置JSON数据的日期格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# JSON数据属性为null时不返回
spring.jackson.default-property-inclusion=non_null
# 找不到资源404时抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
# 禁用静态资源的自动映射,如不禁用,不存在的url将被映射到/**,servlet不有机会抛出异常
#spring.resources.add-mappings=false
# get请求参数及表单提交数据的日期格式
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
# 应用/项目的部署路径,默认为/
server.servlet.context-path=/lucky-draw
#server.port=8081
# SpringMVC中,DispatcherServlet的映射路径,默认为/**
#spring.mvc.servlet.path=/**
# 自定义属性:用户头像本地保存根路径
user.head.local-path=H:/Typora图片
user.head.remote-path=http://localhost:8080${server.servlet.context-path}
user.head.filename=head.jpg
# 静态资源映射:将路径映射为/,即/static/xxx,映射为/xxx,支持多个字符串,逗号间隔
# 默认为/META-INF/resources/, /resources/, /static/, /public/
# 指定外部web资源文件夹:访问的路径为/
spring.resources.static-locations=classpath:/static/,classpath:/public/,file:${user.head.local-path}
#druid数据库连接池配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/lucky_draw?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
spring.datasource.druid.test-on-borrow=true
#Mybatis配置
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
#mybatis.type-aliases-package=sun.mapper
mybatis.configuration.map-underscore-to-camel-case=true
#mybatis.config-location=classpath:mybatis/mybatis-config.xml
#mapper
#mappers 多个接口时逗号隔开
#mapper.mappers=tk.mybatis.mapper.common.Mapper,tk.mybatis.mapper.common.MySqlMapper,tk.mybatis.mapper.common.IdsMapper
#mapper.notEmpty=true
#mapper.identity=MYSQL
#pagehelper
#数据库方言:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
pagehelper.helperDialect=mysql
#默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
#pagehelper.offset-as-page-num=falses
#默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
pagehelper.row-bounds-with-count=true
#默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
#pagehelper.page-size-zero=false
#分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
pagehelper.reasonable=true
#为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
pagehelper.params=pageNum=pageNumber;pageSize=pageSize;count=countSql;reasonable=reasonable;
#支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。
#pagehelper.supportMethodsArguments=true
#用于控制默认不带 count 查询的方法中,是否执行 count 查询,默认 true 会执行 count 查询,这是一个全局生效的参数,多数据源时也是统一的行为。
pagehelper.default-count=false
@SpringBootApplication
@MapperScan(修改为Mybatis要扫描Mapper的包)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
主要针对HTTP请求数据的统一字段设计(本项目没有用到这些字段,可以先保留,为以后扩展设计预
留)
package sun.base;
public class BaseEntity {
}
设计统一响应类
主要为返回数据的统一字段设计
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ResponseResult {
private boolean success;
private String code;
private String message;
private Object data;
private ResponseResult(){
}
public static ResponseResult ok(Object data){
ResponseResult result = new ResponseResult();
result.setSuccess(true);
result.setData(data);
return result;
}
public static ResponseResult error(){
return error("ERR000", "未知错误");
}
public static ResponseResult error(String code, String message){
ResponseResult result = new ResponseResult();
result.setCode(code);
result.setMessage(message);
return result;
}
}
主要针对不同的场景,需要抛异常来处理时,能定位业务含义
主要分为
单实现,不考虑具体字段的报错)
(BUG)
都可以抛(一般是系统发生错误,如网络断了,数据库挂了等等)
自定义异常前端需要显示错误码和错误消息(一般是自己抛的中文异常描述),用户可以根据提示
信息判断原因。
非自定义异常,异常信息一般是框架或JDK抛出的英文,是给开发人员描述错误的,无法给用户提示,所
以错误信息提示为未知异常。
先定义异常的基类:抛异常时,有时候是自己抛,有时候是捕获到异常,再往外抛,所以提供两个构造
方法
@Getter
@Setter
public class BaseException extends RuntimeException {
protected String code;
public BaseException(String code, String message) {
this(code, message, null);
}
public BaseException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
再完成异常的子类:提供不同的错误码前缀,以便于前端提示时,知道是哪的问题
public class BusinessException extends BaseException {
public BusinessException(String code) {
this(code, null);
}
public BusinessException(String code, String message) {
this(code, message, null);
}
public BusinessException(String code, String message, Throwable cause) {
super("BUS" + code, message, cause);
}
}
public ClientException(String code) {
this(code, null);
}
public ClientException(String code, String message) {
this(code, message, null);
}
public ClientException(String code, String message, Throwable cause) {
super("CLI" + code, message, cause);
}
}
public class SystemException extends BaseException {
public SystemException(String code) {
this(code, null);
}
public SystemException(String code, String message) {
this(code, message, null);
}
public SystemException(String code, String message, Throwable cause) {
super("SYS" + code, message, cause);
}
}
这里不光是Controller抛异常,只要Controller代码执行,调用其他方法产生的异常,都会退出 Controller方法,即HTTP请求方法结束,由拦截器统一处理。
@Slf4j
@ControllerAdvice
public class ExceptionAdvisor {
/**
\* 请求数据错误:包括类型转换错误,校验失败
\* @param e
*/
@ExceptionHandler({
BindException.class//使用@Valid 验证路径中请求实体校验失败后抛出的异常
, ConstraintViolationException.class//处理请求参数格式错误 @RequestParam
上validate失败后抛出的异常
, MethodArgumentNotValidException.class//处理请求参数格式错误
@RequestBody上validate失败后抛出的异常
, MethodArgumentTypeMismatchException.class//请求参数类型转换错误
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleMethodArgumentTypeMismatchException(Throwable e){
log.debug("================================");
log.debug("Controller方法参数类型转换错误", e);
}
@ExceptionHandler({
MethodNotAllowedException.class
, HttpRequestMethodNotSupportedException.class
})
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public void handleMethodNotAllowedException(Throwable e){
log.debug("================================");
log.debug("Controller提供的http方法不支持", e);
}
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleNoHandlerFoundException(Throwable e){
log.debug("================================");
log.debug("找不到http请求处理器", e);
}
@ExceptionHandler(BaseException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handleBaseException(BaseException e){
log.debug("================================");
log.debug("自定义异常", e);
return ResponseResult.error(e.getCode(), e.getMessage(), e);
}
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handleException(Throwable e){
log.error("================================");
log.error("未知异常:"+e.getClass().getName()+",请联系管理员", e);
return ResponseResult.error(e);
}
}
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
//登录校验
if(session != null && session.getAttribute("user") != null){
return true;
}
response.setStatus(401);
return false;
}
}
设计Web统一处理的配置
会话管理的拦截器要引入配置:只拦截后端服务路径,并排除登录、注册、注销接口
后端服务器路径都有/api的前缀,可以加上统一的路径映射
统一的响应数据格式封装:这里不使用 @ControllerAdvice 和 ResponseBodyAdvice 进行拦截,
原因是,返回值为null,会出现无法统一包装,响应体为空
import frank.config.interceptor.LoginInterceptor;
import frank.config.web.RequestResponseBodyMethodProcessorWrapper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapt
er;
import
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodP
rocessor;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class SysConfig implements WebMvcConfigurer, InitializingBean{
@Resource
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() {
List<HandlerMethodReturnValueHandler> returnValueHandlers =
adapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> handlers = new
ArrayList(returnValueHandlers);
for(int i=0; i<handlers.size(); i++){
HandlerMethodReturnValueHandler handler = handlers.get(i);
if(handler instanceof RequestResponseBodyMethodProcessor){
handlers.set(i, new
RequestResponseBodyMethodProcessorWrapper(handler));
}
}
adapter.setReturnValueHandlers(handlers);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/user/login")
.excludePathPatterns("/api/user/register")
.excludePathPatterns("/api/user/logout");
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
提供统一响应体封装处理类:
import frank.base.ResponseResult;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
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 {
if(!(returnValue instanceof ResponseResult)){
returnValue = ResponseResult.ok(returnValue);
}
delegate.handleReturnValue(returnValue, returnType, mavContainer,
webRequest);
}
}
使用Mybatis的接口方法,所有接口方法都是类似,只是传入参数和返回值不同,可以考虑设计统一的
基类,以泛型的方式定义出不同的参数类型、返回类型
public interface BaseMapper<T extends BaseEntity>{
T selectByPrimaryKey(Integer id);
int insert(T record);
int insertSelective(T record);
int updateByPrimaryKeySelective(T record);
int updateByPrimaryKey(T record);
int deleteByPrimaryKey(Integer id);
T selectOne(T record);
List<T> selectAll();
List<T> selectByCondition(T record);
int deleteByIds(List<Integer> ids);
}