提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、项目介绍
这是一个酒店订购服务的一个项目,后端主要有酒店模块,搜索模块,个人中心。
前端主要技术:
后端主要技术:
组织结构:
系统架构:
开发环境:
二、数据库的设计
1.注意事项
遵循阿里规范,注意字段类型
三、搜索模块
1.枚举
代码如下:
@RestController
@Api(tags = "后台枚举CRUD", value = "EnumController")
@RequestMapping("/admin/hotel/search")
public class EnumController {
@Resource
private IEnumService enumService;
/**
* 获取全部枚举
*
* @return 全部枚举
*/
@ApiOperation(value = "获取全部枚举")
@GetMapping(value = "/enum-list")
public CommonResult<List<EnumType>> allEnum() {
List<EnumType> enumTypeList = enumService.getDictItem();
return CommonResult.success(enumTypeList);
}
/**
* 新增枚举
*
* @param enumType 枚举类型
* @return 返回添加结果
*/
@ApiOperation(value = "添加枚举")
@PostMapping(value = "/enum-add")
public CommonResult<Boolean> saveEnum(@RequestBody EnumType enumType) {
enumService.saveEnum(enumType);
return CommonResult.success(true, "添加成功");
}
/**
* 修改枚举
*
* @param enumType 枚举类型
* @return 返回修改结果
*/
@ApiOperation(value = "修改枚举")
@PostMapping(value = "/enum-update")
public CommonResult<Boolean> updateEnum(@RequestBody EnumType enumType) {
enumService.updateEnum(enumType);
return CommonResult.success(true, "修改成功");
}
/**
* 删除枚举
*
* @param id 枚举id
* @return 返回删除结果
*/
@ApiOperation(value = "删除枚举")
@GetMapping(value = "/enum-delete")
public CommonResult<Boolean> deleteEnum(@RequestParam Long id) {
enumService.deleteEnum(id);
return CommonResult.success(true, "删除成功");
}
}
@Service
public class EnumServiceImpl extends ServiceImpl<EnumMapper, EnumType> implements IEnumService {
/**
* 查询所有枚举
*
* @return 枚举
*/
@Override
public List<EnumType> getDictItem() {
List<EnumType> allEnm = this.list();
List<EnumType> parentList = allEnm.stream().filter(v -> v.getParentId().equals(0L)).collect(Collectors.toList());
parentList.forEach(parentEnum -> {
List<EnumType> children = allEnm.stream().filter(v -> v.getParentId().equals(parentEnum.getId())).collect(Collectors.toList());
parentEnum.setChildren(children);
});
return parentList;
}
/**
* 添加枚举
*
* @param enumType 枚举对象
*/
@Override
public void saveEnum(EnumType enumType) {
boolean flag = this.save(enumType);
if (!flag) {
Asserts.fail("新增失败");
}
}
/**
* 修改枚举
*
* @param enumType 枚举对象
*/
@Override
public void updateEnum(EnumType enumType) {
boolean flag = this.updateById(enumType);
if (!flag) {
Asserts.fail("更新失败");
}
}
/**
* @param id 枚举id
*/
@Override
public void deleteEnum(Long id) {
boolean flag = this.removeById(id);
if (!flag) {
Asserts.fail("删除失败");
}
}
}
public interface IEnumService extends IService<EnumType> {
/**
* 查询所有枚举
*
* @return 枚举
*/
List<EnumType> getDictItem();
/**
* 添加枚举
*
* @param enumType
*/
void saveEnum(EnumType enumType);
/**
* 更新枚举
*
* @param enumType
*/
void updateEnum(EnumType enumType);
/**
* 删除枚举
*
* @param id
*/
void deleteEnum(Long id);
}
2.Elasticsearch
代码如下:
@Api(tags = "搜索酒店", value = "EsHotelController")
@Validated
@RestController
@RequestMapping("/hotel/search")
public class EsHotelController {
@Resource
private EsHotelService esHotelService;
@Resource
private IEnumService enumService;
@ApiOperation(value = "综合搜索、筛选、排序")
@PostMapping(value = "/list")
public CommonResult<CommonPage<EsHotel>> search(HotelDTO hotelDTO) {
Page<EsHotel> esHotelPage = esHotelService.search(hotelDTO);
CommonPage<EsHotel> hotelCommonPage = CommonPage.restPage(esHotelPage);
return CommonResult.success(hotelCommonPage);
}
/**
* @return 返回查询结果
*/
@ApiOperation(value = "获取全部枚举列表")
@GetMapping(value = "/enum-list")
public CommonResult<List<EnumType>> allEnum() {
List<EnumType> enumTypeList = enumService.getDictItem();
return CommonResult.success(enumTypeList);
}
}
@Service
@Slf4j
public class EsHotelServiceImpl implements EsHotelService {
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* @return 酒店列表
*/
@Override
public Page<EsHotel> search(HotelDTO hotelDTO) {
//创建分页
Pageable pageable = PageRequest.of(hotelDTO.getPageNum(), hotelDTO.getPageSize());
//创建查询条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//分页
nativeSearchQueryBuilder.withPageable(pageable);
//过滤
filter(hotelDTO, nativeSearchQueryBuilder);
//搜索
searchKeyword(hotelDTO, nativeSearchQueryBuilder);
//排序
sort(hotelDTO, nativeSearchQueryBuilder);
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
//打印查询DSL语句
log.info("DSL:{}", searchQuery.getQuery());
SearchHits<EsHotel> searchHits = elasticsearchRestTemplate.search(searchQuery, EsHotel.class);
//查询无结果返回空集合
if (searchHits.getTotalHits() <= 0) {
return new PageImpl<>(new ArrayList<>(), pageable, 0);
}
//转换成酒店集合
List<EsHotel> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
return new PageImpl<>(searchProductList, pageable, searchHits.getTotalHits());
}
private void filter(HotelDTO hotelDTO, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//查询附近地标
if (ObjectUtil.isNotNull(hotelDTO.getLandmarkNearby())) {
boolQueryBuilder.must(QueryBuilders.matchQuery(SearchCode.LANDMARK_NEARBY, hotelDTO.getLandmarkNearby()));
}
//查询酒店星级
if (ObjectUtil.isNotNull(hotelDTO.getHotelStars())) {
boolQueryBuilder.must(QueryBuilders.termsQuery(SearchCode.GRADE, (Object[]) hotelDTO.getHotelStars()));
}
//查询方圆几千米酒店
if (ObjectUtil.isNotNull(hotelDTO.getLongitude()) && ObjectUtil.isNotNull(hotelDTO.getLatitude()) && ObjectUtil.isNotNull(hotelDTO.getRange())) {
boolQueryBuilder.must(QueryBuilders.geoDistanceQuery(SearchCode.LOCATION).point(hotelDTO.getLatitude(), hotelDTO.getLongitude())
.distance(hotelDTO.getRange(), DistanceUnit.KILOMETERS).geoDistance(GeoDistance.ARC));
}
//查询价格区间
if (ObjectUtil.isNotNull(hotelDTO.getMinPrice()) && ObjectUtil.isNotNull(hotelDTO.getMaxPrice())) {
boolQueryBuilder.must(QueryBuilders.rangeQuery(SearchCode.FLOOR_SPECIAL_PRICE).gte(hotelDTO.getMinPrice()).lte(hotelDTO.getMaxPrice()));
}
nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
}
private void searchKeyword(HotelDTO hotelDTO, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
if (CharSequenceUtil.isEmpty(hotelDTO.getQueryText())) {
//关键字为空则全查
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
} else {
//设置权重
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(SearchCode.ADDRESS_DETAIL, hotelDTO.getQueryText()),
ScoreFunctionBuilders.weightFactorFunction(10)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(SearchCode.CITY_NAME, hotelDTO.getQueryText()),
ScoreFunctionBuilders.weightFactorFunction(8)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(SearchCode.REGION, hotelDTO.getQueryText()),
ScoreFunctionBuilders.weightFactorFunction(6)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(SearchCode.BRAND, hotelDTO.getQueryText()),
ScoreFunctionBuilders.weightFactorFunction(4)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(SearchCode.NAME, hotelDTO.getQueryText()),
ScoreFunctionBuilders.weightFactorFunction(2)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
}
}
private void sort(HotelDTO hotelDTO, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
//设置排序默认值
if (ObjectUtil.isNull(hotelDTO.getSort())) {
hotelDTO.setSort(0);
}
switch (hotelDTO.getSort()) {
//直线距离排序
case SearchCode.DISTANCE_ASC:
//经纬度为null无法排序
if (ObjectUtil.isNotNull(hotelDTO.getLongitude()) && ObjectUtil.isNotNull(hotelDTO.getLatitude())) {
nativeSearchQueryBuilder.withSort(SortBuilders.geoDistanceSort(SearchCode.LOCATION, hotelDTO.getLatitude(), hotelDTO.getLongitude())
.unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC));
}
break;
//销量高低排序
case SearchCode.SALE_COUNT_DESC:
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(SearchCode.SALE_COUNT).order(SortOrder.DESC));
break;
//价格低高排序
case SearchCode.PRICE_ASC:
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(SearchCode.FLOOR_SPECIAL_PRICE).order(SortOrder.ASC));
break;
//价格高低排序
case SearchCode.PRICE_DESC:
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(SearchCode.FLOOR_SPECIAL_PRICE).order(SortOrder.DESC));
break;
//评分高低排序
case SearchCode.SCORE_DESC:
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(SearchCode.EVALUATE_SCORE).order(SortOrder.DESC));
break;
//点评数高低排序
case SearchCode.COMMENT_DESC:
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(SearchCode.EVALUATE_COUNT).order(SortOrder.DESC));
break;
//综合排序
default:
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
break;
}
}
}
public interface EsHotelService {
/**
* 搜索酒店
*
* @param hotelDTO 酒店传参
* @return 酒店列表
*/
Page<EsHotel> search(HotelDTO hotelDTO);
}
四、API 文档规范
- 请求方式 GET/POST
- 请求路径按照任务分配表上的写
- GET 请求的参数在 Query 里面填写,POST 在 Body 里写。
- 不要使用 RESTFul 的路径传参
- 参数名采用驼峰命名
- 数据库中存在的字段的参数,命名要保持一致
- 如果参数中用多个 id 参数,必须区分命名,且要与数据库一致
- 传参的实例值尽量真实一点
- 类型要选择正确,类型的选项很多自己看看。
- 响应数据示例的格式必须的实际的格式
- 响应数据的类型和描述和请求参数的要求一致
- 金额数据:后端使用整数存,前端部分要转化为小数
五、接口设计
酒店搜索接口
获取枚举列表接口
添加枚举
修改枚举
删除枚举
查询枚举
总结
经历了一个多月吧,项目也接近尾声。通过这次项目,让我们这些新手熟悉到开发的流程。总而言之,这次的项目学到了很多对工作有益的事。我也顺利的找到了实习,加油吧!