网站的专题模板制作软件,百度竞价排名点击软件,网站开发主要做哪些,建设一个网站大概多少钱目录 公共字段自动填充——问题分析和实现思路 公共字段自动填充——代码实现(1) 公共字段自动填充——代码实现完善(2)
新增菜品——需求分析与设计
产品原型
编辑 接口设计 编辑 数据库设计
新增菜品——代码开发1(文件上传接口)
配置文件
Controller层代码
前后…目录 公共字段自动填充——问题分析和实现思路 公共字段自动填充——代码实现(1) 公共字段自动填充——代码实现完善(2)
新增菜品——需求分析与设计
产品原型
编辑 接口设计 编辑 数据库设计
新增菜品——代码开发1(文件上传接口)
配置文件
Controller层代码
前后端联调测试
新增菜品——代码开发2(新增菜品接口)
Controller层中
Service层中
Mapper层中
功能测试 菜品分页查询——需求分析与设计
产品原型
接口设计 菜品分页查询——代码开发和功能测试
Controller层 Service层
Mapper层
功能测试 删除菜品——需求分析和设计
产品原型编辑
接口设计 编辑
数据库设计 删除菜品——代码实现
Controller层中
在service层中
Mapper层中
修改菜品——需求分析与设计
产品原型 接口设计 修改菜品——代码开发(1):根据id查询
Controller层
Service层
Mapper层 修改菜品——代码开发(2)修改菜品
Controller中
Service中
Mapper中 公共字段自动填充——问题分析和实现思路 公共字段自动填充——代码实现(1)
自定义一个注解和一个切面类
/*** 自定义注解用于标识某个方法需要进行功能字段自动填充*/
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface AutoFill {//指定数据库操作的类型insert updateOperationType value();}
/*** 自定义切面实现公共字段自定义填充处理逻辑*/
Aspect
Component
Slf4j
public class AutoFillAspect {/*** 切入点:对哪些类的哪些方法进行拦截,这个匹配了所有的类和方法以及所有参数类型以及满足了有AutoFill这个注解*/Pointcut(execution(* com.sky.mapper.*.*(..)) annotation(com.sky.annotation.AutoFill))public void autoFillPointCut(){}/*** 前置通知在通知中进行公共字段的赋值*/Before(autoFillPointCut())public void autoFill(JoinPoint joinPoint){log.info(开始进行公共字段的自动填充...);}
}
在mapper对应insert和update方法上加上对应的注解。 /*** 根据主键动态修改属性* param employee*/AutoFill(value OperationType.UPDATE)void update(Employee employee);Insert(insert into employee (name,username,password,phone,sex,id_number,create_time,update_time,create_user,update_user,status) values (#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status}) )AutoFill(value OperationType.INSERT)void insert(Employee employee);公共字段自动填充——代码实现完善(2)
切面类代码完善如下
/*** 自定义切面实现公共字段自定义填充处理逻辑*/
Aspect
Component
Slf4j
public class AutoFillAspect {/*** 切入点:对哪些类的哪些方法进行拦截,这个匹配了所有的类和方法以及所有参数类型以及满足了有AutoFill这个注解*/Pointcut(execution(* com.sky.mapper.*.*(..)) annotation(com.sky.annotation.AutoFill))public void autoFillPointCut(){}/*** 前置通知在通知中进行公共字段的赋值*/Before(autoFillPointCut())public void autoFill(JoinPoint joinPoint){//获取到当前被拦截的方法上的数据库操作类型MethodSignature signature (MethodSignature) joinPoint.getSignature();//方法签名对象AutoFill autoFill signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象OperationType operationType autoFill.value();//获得数据库操作类型//获取到当前被拦截的方法的参数———实体对象Object[] args joinPoint.getArgs();if(argsnull||args.length0){return;}//约定了实体放在第一个参数Object entityargs[0];//准备赋值的数据LocalDateTime now LocalDateTime.now();Long currentId BaseContext.getCurrentId();//根据当前不同的操作类型为对应的属性通过反射赋值if(operationTypeOperationType.INSERT){//为4个公共字段赋值try {Method setCreateTime entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {throw new RuntimeException(e);}}else if(operationTypeOperationType.UPDATE){//为2个公共字段赋值try {Method setUpdateTime entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {throw new RuntimeException(e);}}}
}
在service层中这些公共属性不再需要一个个set了 没有功能测试
新增菜品——需求分析与设计
产品原型 接口设计 multipart/form-data是通过浏览器进行文件上传固定的请求头。 数据库设计 新增菜品——代码开发1(文件上传接口)
配置文件 这里写了一个配置属性类加上一个ConfigurationProperties使得可以读取配置文件的配置项然后封装成java对象。这里类中的属性使用驼峰命名法配置文件里面使用横线的形式springboot框架可以实现自动转换就算不使用横线也是可以。
最终项目上线了有可能开发环境和生产环境用的不同的账号这里采用引用的方式提供不同环境的配置文件.
再借助一个封装好的aliOSS的工具类
Data
AllArgsConstructor
Slf4j
public class AliOssUtil {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;/*** 文件上传** 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/ObjectNameStringBuilder stringBuilder new StringBuilder(https://);stringBuilder.append(bucketName).append(.).append(endpoint).append(/).append(objectName);log.info(文件上传到:{}, stringBuilder.toString());return stringBuilder.toString();}
} 然后对于这个工具类里面的相关属性还要再定义相关的配置类初始化出来
/*** 配置类用于创建AliOssUtil对象*/
Configuration
Slf4j
public class OssConfiguration {BeanConditionalOnMissingBean//保证整个spring容器只有一个AliOssUtil对象public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){log.info(开始创建阿里文件上传工具类对象{},aliOssProperties);return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());}
}Controller层代码
为防止文件覆盖使用UUID进行命名. /*** 通用接口*/
RestController
RequestMapping(/admin/common)
Api(tags通用接口)
Slf4j
public class CommonController {Autowiredprivate AliOssUtil aliOssUtil;/*** 文件上传* param file* return*/PostMapping(upload)ApiOperation(文件上传)public ResultString upload(MultipartFile file){log.info(文件上传:{},file);try {//原始文件名String originalFilename file.getOriginalFilename();//截取后缀 xxx.jpgString extension originalFilename.substring(originalFilename.lastIndexOf(.));//构造新文件名称String objectName UUID.randomUUID().toString()extension;//文件的请求路径String filePath aliOssUtil.upload(file.getBytes(), objectName);return Result.success(filePath);} catch (IOException e) {log.error(文件上传失败{},e);}return Result.error(MessageConstant.UPLOAD_FAILED);}
}
前后端联调测试
成功回显。 新增菜品——代码开发2(新增菜品接口)
Controller层中
新建一个DishController
/*** 菜品管理*/
RestController
RequestMapping(/admin/dish)
Api(tags菜品相关接口)
Slf4j
public class DishController {Autowiredprivate DishService dishService;/*** 新增菜品* param dishDTO* return*/PostMappingApiOperation(新增菜品)public Result save(RequestBody DishDTO dishDTO){log.info(新增菜品:{},dishDTO);dishService.saveWithFlavor(dishDTO);return Result.success();}
}
Service层中
新建一个DishService接口和DishServiceImpl这个实现类
Service
public class DishServiceImpl implements DishService {Autowiredprivate DishMapper dishMapper;Autowiredprivate DishFlavorMapper dishFlavorMapper;/*** 新增菜品和对应的口味数据* param dishDTO*/OverrideTransactional//涉及到两个表的操作要保证事务的一致性在启动类已经开启了注解方式的事务管理public void saveWithFlavor(DishDTO dishDTO) {Dish dishnew Dish();BeanUtils.copyProperties(dishDTO,dish);//向菜品表插入1条数据dishMapper.insert(dish);//通过主键返回获取insert语句生成的主键值Long dishId dish.getId();//向口味表插入n条数据ListDishFlavor flavors dishDTO.getFlavors();if(flavors!nullflavors.size()0){flavors.forEach(dishFlavor - {dishFlavor.setDishId(dishId);});//向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}
}
Mapper层中
因为涉及到两个表所以新建一个DishMapper和DishFlavorMapper接口还要新建对应的XML文件用于写动态SQl.这里涉及到主键返回和批量插入用到foreach标签和useGeneratedKeys属性
Mapper
public interface DishMapper {/*** 根据分类id查询菜品数量* param categoryId* return*/Select(select count(id) from dish where category_id #{categoryId})Integer countByCategoryId(Long categoryId);/*** 插入菜品数据* param dish*/AutoFill(value OperationType.INSERT)void insert(Dish dish);
}?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.sky.mapper.DishMapperinsert idinsert useGeneratedKeystrue keyPropertyidinsert into dish (name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)values(#{name},#{categoryId} ,#{price},#{imape},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser} ,#{status})/insert
/mapperMapper
public interface DishFlavorMapper {/*** 批量插入口味数据* param flavors*/void insertBatch(ListDishFlavor flavors);
}?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.sky.mapper.DishFlavorMapperinsert idinsertBatchinsert into dish_flavor (dish_id,name,value) VALUESforeach collectionflavors itemdf separator,(#{df.dishId},#{df.name},#{df.value})/foreach/insert
/mapper功能测试
前后端联调测试 数据无误 菜品分页查询——需求分析与设计
产品原型 接口设计 菜品分页查询——代码开发和功能测试
DTO和VO设计 Controller层
在DishController中创建一个新的方法 /*** 菜品分页查询* param dishPageQueryDTO* return*/GetMapping(/page)ApiOperation(菜品分页查询)public ResultPageResult page(DishPageQueryDTO dishPageQueryDTO){log.info(菜品分页查询:{},dishPageQueryDTO);PageResult pageResult dishService.pageQuery(dishPageQueryDTO);return Result.success(pageResult);} Service层 /*** 菜品分页查询* param dishPageQueryDTO* return*/Overridepublic PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());PageDishVO pagedishMapper.pageQuery(dishPageQueryDTO);return new PageResult(page.getTotal(),page.getResult());}
Mapper层 /*** 菜品分页查询* param dishPageQueryDTO* return*/PageDishVO pageQuery(DishPageQueryDTO dishPageQueryDTO);
映射文件中涉及到两个表所以做个连接查询 select idpageQuery resultTypecom.sky.vo.DishVOselect d.* ,c.name as categoryName from dish d left outer join category c on d.category_idc.idwhereif testname ! nulland d.name like concat(%,#{name},%)/ifif testcategoryId ! nulland d.category_id#{categoryId}/ifif teststatus ! nulland d.status #{status}/if/whereorder by d.create_time desc/select
功能测试
接口文档测试 前后端联调测试 删除菜品——需求分析和设计
产品原型 接口设计 数据库设计 删除菜品——代码实现
Controller层中
这里针对url中的字符串使用一个RequestParam注解将其转圜为list /*** 菜品批量删除* 通过RequestParam注解将字符串转换为数组* param ids* return*/DeleteMappingApiOperation(菜品批量删除)public Result delete(RequestParam ListLong ids){log.info(菜品批量删除{},ids);dishService.deleteBatch(ids);return Result.success();}
在service层中
设计到三个表的操作 OverrideTransactionalpublic void deleteBatch(ListLong ids) {//判断当前菜品是否能删除--是否存在起售中的菜品for(Long id :ids){Dish dishdishMapper.getById(id);if(dish.getStatus() StatusConstant.ENABLE){//当前菜品处于起售中不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//判断当前菜品是否能删除——是否被套餐关联ListLong setmealIds setmealDishMapper.getSetmealIdsByDishIds(ids);if(setmealIds!nullsetmealIds.size()0){//当前菜品被套餐关联throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除菜品表中的菜品数据for(Long id:ids){dishMapper.deleteById(id);//删除菜品关联的口味数据dishFlavorMapper.deleteByDishId(id);}}
Mapper层中
DishMapper中 /*** 根据主键查询* param id* return*/Select(select * from dish where id #{id})Dish getById(Long id);/***根据菜品主键删除菜品数据* param id*/Delete(delete from dish where id #{id})void deleteById(Long id);
DishFlavorMapper中 /*** 根据菜品id删除对应的口味数据* param dishId*/Delete(delete from dish_flavor where dish_id#{dish_id})void deleteByDishId(Long dishId); SetmealDishMapper中
Mapper
public interface SetmealDishMapper {/*** 根据菜品id查询套餐id* param dishIds* return*///select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)ListLong getSetmealIdsByDishIds(ListLong dishIds);
}
对应的映射文件 select idgetSetmealIdsByDishIds resultTypejava.lang.Longselect setmeal_id from setmeal_dish where dish_id inforeach collectiondishIds itemdishId separator, open( close)#{dishId}/foreach/select
功能那个测试就算了
修改菜品——需求分析与设计
产品原型 接口设计 修改菜品——代码开发(1):根据id查询
Controller层 /*** 根据id查询菜品* param id* return*/GetMapping(/{id})ApiOperation(根据id查询菜品)public ResultDishVO getById(PathVariable Long id){log.info(根据id查询菜品:{},id);DishVO dishVOdishService.getByIdWithFlavor(id);return Result.success(dishVO);}
Service层 /*** 根据id查询菜平和对应的口味数据* param id* return*/Overridepublic DishVO getByIdWithFlavor(Long id) {//根据id查询菜品数据Dish dish dishMapper.getById(id);//根据菜品id查询口味数据ListDishFlavor dishFlavors dishFlavorMapper.getByDishId(id);//将查询到的数据封装到VODishVO dishVOnew DishVO();BeanUtils.copyProperties(dish,dishVO);dishVO.setFlavors(dishFlavors);return dishVO;}
Mapper层 /*** 根据菜品id查询对应的口味数据* param dishId* return*/Select(select * from dish_flavor where dish_id#{dishId})ListDishFlavor getByDishId(Long dishId); 修改菜品——代码开发(2)修改菜品
Controller中 /*** 根据id修改菜品基本信息和对应口味信息* param dishDTO* return*/PutMappingApiOperation(修改菜品)public Result update(RequestBody DishDTO dishDTO){log.info(修改菜品:{},dishDTO);dishService.updateWithFlavor(dishDTO);return Result.success();}Service中 /*** 根据id修改菜品基本信息和对应口味信息* param dishDTO*/Overridepublic void updateWithFlavor(DishDTO dishDTO) {Dish dishnew Dish();BeanUtils.copyProperties(dishDTO,dish);//修改菜品表基本信息dishMapper.update(dish);//删除原有的口味数据dishFlavorMapper.deleteByDishId(dishDTO.getId());//重新插入口味数据ListDishFlavor flavors dishDTO.getFlavors();if(flavors!nullflavors.size()0){flavors.forEach(dishFlavor - {dishFlavor.setDishId(dishDTO.getId());});//向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}
Mapper中 /***根据id动态修改菜品* param dish*/AutoFill(valueOperationType.UPDATE)void update(Dish dish);
对应的映射文件 update idupdateupdate dishsetif testname ! nullname#{name},/ifif testcategoryId ! nullcategory_id#{categoryId},/ifif testprice ! nullprice#{price},/ifif testimage ! nullimage#{image},/ifif testdescription ! nulldescription#{description},/ifif teststatus ! nullstatus#{status},/ifif testupdateTime ! nullupdate_time#{updateTime},/ifif testupdateUser ! nullupdate_user#{updateUser},/if/setwhere id#{id}/update