本章是完成乘车人的增删改查功能
乘车人表的设计
在实际的购票流程中,用户不仅能够登录自己的账号为自己购买票务,还可以帮助他人购票。因此,在设计数据库中的乘客表时,引入了两个关键字段:id
和 member_id
。这里,id
代表为哪些人购票的唯一标识符,由雪花算法生成,而 member_id
则与登录账户(即会员表中的 id
)相对应,用于记录是哪个会员进行了购票操作。这里member_id
被设置为普通索引而非唯一索引,这是为了支持一个会员为多个人购票的需求,即支持一对多的关系映射,并方便通过 member_id
查询该会员为哪些人购买了什么票。如果将member_id设置为主键,由于member_id为多人买票时,member_id是相同的,不满足主键对应的每一条购票记录都需要有一个独一无二的标识符。
旅客类型|枚举[PassengerTypeEnum]是为了后面的代码生成器,由于旅客类型是一个枚举型,总体表示type
字段的值应从 PassengerTypeEnum
枚举中选择数据。
其他可以优化的点:分区表:如果数据量非常大,可以根据 member_id
或者时间等维度对表进行分区,这样可以加快特定条件下的查询速度。
drop table if exists member;
create table member(
id bigint not null comment 'id',
mobile varchar(11) comment '手机号',
primary key (id),
unique key mobile_unique(mobile)
)engine=InnoDB default charset=utf8mb4 comment='会员';
DROP TABLE IF EXISTS passenger;
CREATE TABLE passenger (
id BIGINT NOT NULL COMMENT 'id',
member_id BIGINT NOT NULL COMMENT '会员id',
name VARCHAR(20) NOT NULL COMMENT '姓名',
id_card VARCHAR(18) NOT NULL COMMENT '身份证',
type CHAR(1) NOT NULL COMMENT '旅客类型|枚举[PassengerTypeEnum]',
create_time DATETIME(3) COMMENT '新增时间',
update_time DATETIME(3) COMMENT '修改时间',
PRIMARY KEY (id),
INDEX member_id_index (member_id)
) ENGINE = INNODB DEFAULT CHARSET=utf8mb4 COMMENT='乘车人';
为了旅客类型的枚举,可以在member模块增加一个enums包,增加一个PassengerTypeEnum类,和上面的表进行对应,字母是为了编写代码,第一个字符串放到数据库表中,第二个字段是为了显示到页面,为了购票人进行理解。最后调用mybatis的代码生成器完成Mapper层的编写。
ADULT("1", "成人"),
CHILD("2", "儿童"),
STUDENT("3", "学生");
乘车表新增用户
对于Mapper(数据持久层)来说,完成对于数据库的CRUD操作,这里已经使用了Mybatis代码生成器完成了这部分的代码。对于Service层来说,需要根据具体业务需求准备相应的输入数据。由于一个乘客对象包含多个属性,在进行诸如插入等操作时,如果逐个传递这些属性会显得繁琐且不易维护。因此可以将这些信息封装到一个特定的请求类(req
类)中。为什么不直接使用乘客类?由于对数据库进行不同操作的时候,所需要传入的参数是不同的,如果都是由乘客类,那么乘客类又承担了其他的业务操作,就感觉代码不那么的纯净。对于新增用户来说,这里设计的是传入的姓名,身份证和旅客类型,因此在Serivice层中还需要添加id,member_id和新增时间以及修改时间。将准备好的完整用户对象传递给Mapper层,通过MyBatis映射机制将其转换为SQL语句并执行插入操作。
@Resource
private PassengerMapper passengerMapper;
public void save(PassengerSaveReq req) {
DateTime now = DateTime.now();
Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
passenger.setMemberId(LoginMemberContext.getId());
passenger.setId(SnowUtil.getSnowflakeNextId());
passenger.setCreateTime(now);
passenger.setUpdateTime(now);
passengerMapper.insert(passenger);
}
Controller层主要负责处理外部请求并将它们转发给相应的Service层进行业务逻辑处理。处理完成后,Controller层会根据Service层的执行结果构造适当的响应返回给客户端。同理,由于不同方法返回值类型的不同,因此也可以将返回值封装成一个类,这里是Resp。第一次封装。在构建后端接口时,若针对同一数据模型的不同操作返回不同类型的结果,这会增加前端处理的复杂度。因此可以对所有后端接口的响应进行标准化封装,确保所有的接口返回统一的数据结构。这种可以简化了前端工作量,还提升了系统的可维护性和用户体验。第二次封装。换句话说,第一层是业务特定响应类。第二层是通用响应包装类
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody PassengerSaveReq req) {
passengerService.save(req);
return new CommonResp<>();
}
@RequestBody
注解用于将HTTP请求的正文部分(通常是JSON或XML格式的数据)转换为一个Java对象。否则后端这里就会拿不到数据。同样的,为了增加用户输入的值是否为空,可以在请求类中增加@NotBlank(message = "【XXX】不能为空")。
乘车人查询用户
由于乘车人是根据自己的id,即表中的member_id来查询为那些人购买了车票,因此在对对应的req类中只需要保留memberId即可。
public class PassengerQueryReq extends PageReq {
private Long memberId;
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("PassengerQueryReq{");
sb.append("memberId=").append(memberId);
sb.append('}');
return sb.toString();
}
}
对于乘车人来说,只能够查询到自己id购买的火车票,但是对于控制台来说,可以查询所有乘车人员的车票,由于查询功能类似,不过只是传入的参数不同而已,因此可以将两种查询合并成一中查询方法,由于查询返回的可能不止一个,为了增加通用性,返回值是一个List<PassengerQueryResp>。由于是查询,是将req转化为list,最后一行做的就是这个,可以称为拷贝。
// 查询列表
public List<PassengerQueryResp> queryList(PassengerQueryReq req) {
PassengerExample passengerExample = new PassengerExample();
PassengerExample.Criteria criteria = passengerExample.createCriteria();
if(ObjectUtil.isNotNull(req.getMemberId())){
criteria.andMemberIdEqualTo(req.getMemberId());
}
List<Passenger> passengerList= passengerMapper.selectByExample(passengerExample);
return BeanUtil.copyToList(passengerList, PassengerQueryResp.class);
}
由于是会员端,是根据自己的id去查询乘车人的信息,因此可以从登录界面去获取到id,根据自己登录的id进行查询,当然,他这里不能写道service层,如果写道service层,那么service层就是根据 id查询,但是我们为了更加的通用,将传入id写道controller层
@GetMapping("/query-list")
public CommonResp<List<PassengerQueryResp>> queryList(@Valid PassengerQueryReq req) {
req.setMemberId(LoginMemberContext.getId());
List<PassengerQueryResp> list = passengerService.queryList(req);
return new CommonResp<>(list);
}
乘车人编辑用户
前面提到,编辑可以和新增写道一起,因此只需要将save方法的代码进行修改,只需要判断参数是否为空即可。其他的不需要进行修改。
public void save(PassengerSaveReq req) {
DateTime now = DateTime.now();
Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
if(ObjectUtil.isNull(passenger.getId())) {
passenger.setMemberId(LoginMemberContext.getId());
passenger.setId(SnowUtil.getSnowflakeNextId());
passenger.setCreateTime(now);
passenger.setUpdateTime(now);
passengerMapper.insert(passenger);
}else {
passenger.setUpdateTime(now);
passengerMapper.updateByPrimaryKey(passenger);
}
}
乘车人删除用户
删除就更加的简单,直接根据数据库中的id进行删除即可,不过多的解释。代码如下:
public void delete(Long id) {
passengerMapper.deleteByPrimaryKey(id);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
passengerService.delete(id);
return new CommonResp<>();
}