菜品相关接口开发
图片上传-阿里云OSS
依赖注入
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun.sdk.oss}</version> </dependency>
配置密钥
sky: alioss: endpoint: ${} access-key-id: ${} access-key-secret: ${} bucket-name: ${}
这里配置的配置信息和关键如密码等信息是分为两个yml来进行配置的,在保护信息、版本控制、配置隔离、团队协作中都起到了一定的作用
配置属性类
@Component @ConfigurationProperties(prefix = "sky.alioss") @Data public class AliOssProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
创建OSS工具类
/** * 文件上传 * * @param bytes * @param objectName * @return */ public String upload(byte[] bytes, String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // 创建PutObject请求。 ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes)); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } //文件访问路径规则 https://BucketName.Endpoint/ObjectName StringBuilder stringBuilder = new StringBuilder("https://"); stringBuilder .append(bucketName) .append(".") .append(endpoint) .append("/") .append(objectName); log.info("文件上传到:{}", stringBuilder.toString()); return stringBuilder.toString(); } }
配置工具类信息,并将工具类交给IOC容器管理
@Configuration @Slf4j public class OssConfiguration { @Bean @ConditionalOnMissingBean public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){ log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties); return new AliOssUtil(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName()); } }
使用工具类实现上传
@RestController @Slf4j @RequestMapping("/admin/common") public class CommonController { @Autowired private AliOssUtil aliOssUtil; @PostMapping("/upload") @ApiOperation("文件上传") public Result<String> upload(MultipartFile file){ log.info("文件上传:{}",file); try { //初始文件名 String originalFilename = file.getOriginalFilename(); //取文件后缀名 String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //生成新的文件名, 防止文件名重复 String obj = UUID.randomUUID().toString() + suffix; //上传文件 String filePath = aliOssUtil.upload(file.getBytes(), obj); return Result.success(filePath); } catch (IOException e) { e.printStackTrace(); } return Result.error("上传文件失败"); } }
菜品分页查询
@Override public PageResult page(DishPageQueryDTO dishPageQueryDTO) { PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize()); Page<DishVO> result = dishMapper.page(dishPageQueryDTO); return new PageResult(result.getTotal(),result.getResult()); } <select id="page" resultType="com.sky.vo.DishVO"> select d.*, c.name as categoryName from dish d left join category c on d.category_id = c.id <where> <if test="categoryId != null"> category_id = #{categoryId} </if> <if test="name != null and name != ''"> and name like concat('%',#{name},'%') </if> <if test="status != null"> and status = #{status} </if> </where> order by update_time desc </select>
接收传送参数时尽量用一个封装好的实体类来传送,尽管可能会多传送几个不需要的参数。这样对后续的维护管理提供了便利,同时在一定程度上也避免了在数据传输的出错。注意多表联查返回参数的选择。
查询菜品及其口味
@Override @Transactional public DishVO getDishById(Long id) { Dish dish = dishMapper.selectByid(id); DishVO dishVO = new DishVO(); BeanUtils.copyProperties(dish, dishVO); List<DishFlavor> flavors = dishFlavorMapper.selectByDishId(id); dishVO.setFlavors(flavors); return dishVO; }
@Transactional 维护代码原子性,中途有问题会进行数据回滚,启动类上要开启注解方式的事务管理@EnableTransactionManagement 。
插入菜品
@Override @Transactional public void save(DishVO dishVO) { Dish dish = new Dish(); BeanUtils.copyProperties(dishVO, dish); dishMapper.save(dish); Long id = dish.getId(); List<DishFlavor> flavors = dishVO.getFlavors(); if(flavors != null && flavors.size() > 0){ flavors.forEach(dishFlavor -> { dishFlavor.setDishId(id); }); //向口味表插入n条数据 dishFlavorMapper.insertBatch(flavors); } }
这里如果只是向dish表中插入数据的话 那么自增的id是拿不到的 Long id = dish.getId(); 将出现异常:Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'dish_id' cannot be null。
插入完成后还需要返回一个id数据,在xml中加入属性:useGeneratedKeys="true" keyProperty="id"。useGeneratedKeys属性:用于告知 MyBatis 是否使用数据库的自动生成主键功能。将其设为 true,MyBatis 会借助 JDBC 的 Statement.getGeneratedKeys()方法来获取数据库自动生成的主键值。keyProperty属性:指定了将自动生成的主键值映射到 Java 对象的哪个属性上。当 useGeneratedKeys为 true 时,MyBatis 会把数据库自动生成的主键值赋给 Java 对象中 keyProperty 指定的属性。
<insert id="save" useGeneratedKeys="true" keyProperty="id"> insert into dish (name, category_id, price, image, description, create_time, update_time, create_user,update_user, status) values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status}) </insert>
删除菜品
@DeleteMapping public Result deleteById(@RequestParam List<Long> ids){ log.info("删除菜品:{}",ids); dishService.deleteById(ids); return Result.success(); }
@RequestParam 注解的作用是从 HTTP 请求的查询字符串中获取 ids 参数的值,并且把它绑定到方法的 ids 参数上。要是请求中没有提供 ids 参数,就会抛出异常。传入什么类型的数据主要看你用什么接收他。因为如果前端以特定格式传参,Spring 会自动进行类型转换,不只针对逗号分隔的字符串这种形式。
@Override @Transactional public void deleteById(List<Long> ids) { // 判断菜品是否能删除 // 是否停售 for(Long id : ids) { Dish dish = dishMapper.selectByid(id); if(dish != null && dish.getStatus() == StatusConstant.ENABLE) throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE); } // 是否关联到其它套餐中了 for(Long id : ids) { Setmeal setmealIdByDishID = setmealDishMapper.getSetmealIdByDishID(id); if(setmealIdByDishID != null) throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL); } for(Long id : ids) { dishMapper.deleteById(id); dishFlavorMapper.deleteByDishId(id); } }
总结:
与前面接口相比学到的有:
1、引用了第三方工具阿里云OSS进行图片文件缓存;
2、同一接口进行多个表查询and维护代码原子性;(数据关性弱时)
3、需要从多个相关表中获取数据,以完整展示业务实体的信息时进行多表联查及返回数据类型选择;(数据相关性强时)
4、useGeneratedKeys、keyProperty 指定是否返回数据And接收参数
5、RequestParam 接收参数
套餐管理
功能实现与菜品类似,不做介绍