• 苍穹外卖笔记


    目录

    关于Swagger使用

    添加新员工

    员工分页查询

    启用禁用员工

    编辑员工信息

    分类接口

    注解AOP自动填充创建修改信息

    阿里云上传文件

    新增菜品

    菜品分页展示

    删除菜品

    修改菜品

    店铺营业状态开发

    微信小程序登录

    用户端浏览菜品

    使用spring_cache管理套餐缓存

    购物车业务

    订单提交

    订单支付

    使用Spring Task定时处理异常订单

    使用websocket实现订单提醒

    Apache Echarts制表

    管理端数据分析功能

    订单管理


    关于Swagger使用

    Swagger能生成接口文档,方便后端调试

    使用方式

    导入maven坐标

    1. <dependency>
    2. <groupId>com.github.xiaoymingroupId>
    3. <artifactId>knife4j-spring-boot-starterartifactId>
    4. <version>3.0.2version>
    5. dependency>

    创建配置类

    1. @Bean
    2. public Docket docket() {
    3. ApiInfo apiInfo = new ApiInfoBuilder()
    4. .title("苍穹外卖项目接口文档")
    5. .version("2.0")
    6. .description("苍穹外卖项目接口文档")
    7. .build();
    8. Docket docket = new Docket(DocumentationType.SWAGGER_2)
    9. .apiInfo(apiInfo)
    10. .select()
    11. .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
    12. .paths(PathSelectors.any())
    13. .build();
    14. return docket;
    15. }

    图中扫描了controller包,之后会自动生成接口文档

    添加静态资源映射

    1. protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    2. registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
    3. registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    4. }

    完成后访问页面即可,按图中操作既是访问/doc.html

    注解

    注解的作用是在swagger生成的接口文档中添加相应的说明,随手添加养成好习惯

    添加新员工

    创建类往线程中添加键值对存储当前操作者的id,前端的每一次请求都会开启一个线程

    1. public class BaseContext {
    2. public static ThreadLocal threadLocal = new ThreadLocal<>();
    3. public static void setCurrentId(Long id) {
    4. threadLocal.set(id);
    5. }
    6. public static Long getCurrentId() {
    7. return threadLocal.get();
    8. }
    9. public static void removeCurrentId() {
    10. threadLocal.remove();
    11. }
    12. }

    在员工类中对dto和其他属性进行封装,BeanUtils.copyProperties(a,b)能将a对象中成员值传递到b对象的同名成员中

    1. public static Employee create(EmployeeDTO employeeDTO,Long creator)
    2. {
    3. Employee employee=new Employee();
    4. BeanUtils.copyProperties(employeeDTO,employee);
    5. employee.setStatus(StatusConstant.ENABLE);
    6. employee.setCreateTime(LocalDateTime.now());
    7. employee.setUpdateTime(LocalDateTime.now());
    8. employee.setCreateUser(creator);
    9. employee.setUpdateUser(creator);
    10. employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
    11. return employee;
    12. }

    用户名在数据库里设置为unique,当重复时抛出SQLIntegrityConstraintViolationException异常,对前端返回

    1. @ExceptionHandler
    2. public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
    3. String message=ex.getMessage();
    4. if(message.contains("Duplicate entry")) return Result.error("用户名已存在");
    5. return Result.error("未知错误");
    6. }

    员工分页查询

    在service层使用PageHelper插件,该插件类似于拦截器,在sql语句之后自动添加limit,故不要加分号,同时,在此之前会进行对数据库的一次count()询问,得到total

    PageHelper依赖

    1. com.github.pagehelper
    2. pagehelper-spring-boot-starter

    此时,返回的值中日期时间会以数组形式返回给前端

    在WebMvcConfiguration中重写实现json转换器对传回前端的日期等数据进行格式转化

    1. protected void extendMessageConverters(List> converters){
    2. MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
    3. converter.setObjectMapper(new JacksonObjectMapper());
    4. converters.add(0,converter);//往转换器集合中添加自己的转换器
    5. }

    重启服务器后得到正确格式

    启用禁用员工

    为员工设置状态,限制其登录,本质上就是修改操作update

    将update使用标签,传递一个实体类,若不为空就修改,这样使得sql语句可以复用,以后就不用写update了

    1. "update">
    2. update sky_take_out.employee
    3. <if test="name!=null and name!='' ">name=#{name},if>
    4. <if test="username!=null and username!='' ">username=#{username},if>
    5. <if test="password!=null and password!='' ">passord=#{password},if>
    6. <if test="phone!=null and phone!='' ">phone=#{phone},if>
    7. <if test="sex!=null and sex!='' ">sex=#{sex},if>
    8. <if test="idNumber!=null and idNumber!='' ">id_number=#{idNumber},if>
    9. <if test="status!=null">status=#{status},if>
    10. <if test="createTime!=null">create_time=#{createTime},if>
    11. <if test="updateTime!=null">update_time=#{updateTime},if>
    12. <if test="createUser!=null">create_user=#{createUser},if>
    13. <if test="updateUser!=null">update_user=#{updateUser},if>
    14. where id=#{id}

    编辑员工信息

    前端需要数据回显,先进行id查询员工的操作,然后在修改

    我们利用上面提到的update更新即可,记得更新修改者和修改时间,同上单线程内的当前id已经放入context了,直接取出来就行了

    1. @Override
    2. public void updateEmployee(EmployeeDTO employeeDTO) {
    3. Employee employee=new Employee();
    4. BeanUtils.copyProperties(employeeDTO,employee);
    5. employee.setUpdateUser(BaseContext.getCurrentId());
    6. employee.setUpdateTime(LocalDateTime.now());
    7. employeeMapper.update(employee);
    8. }

    分类接口

    分类接口和员工接口操作类似,不再赘述

    注解AOP自动填充创建修改信息

    发现随着项目的复杂化,在创建修改数据是会产生大量冗余的代码来设置创建修改的信息,所以考虑采用AOP和注解的方式实现自动填充信息

    自定义注解AutoFill

    1. @Target(ElementType.METHOD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface AutoFill {
    4. //设置操作类型,update 和 insert
    5. OperationType value();
    6. }

    这里我们在需要填充信息的方法上添加注解,在切面设置Before方法时通过下面的代码就能获得添加的注解的value从而执行不同的填充操作,然后利用反射对mapper的参数填充信息

    1. //获得加了注解的方法对象
    2. MethodSignature signature=(MethodSignature) joinPoint.getSignature();
    3. //通过方法对象获得该方法上的注解对象
    4. AutoFill autoFill=signature.getMethod().getAnnotation(AutoFill.class);
    5. //获得注解的枚举值
    6. OperationType operationType=autoFill.value();

    阿里云上传文件

    我们可以把项目中所上传保存的图片上传到阿里云保存,先开通阿里云的对象存储oss服务

    创建bucket并获取自己的AccessKey的id和secret,在meaven中导入依赖,创建上传工具类后即可,创建controller以接受前端传来的请求

    安装方式参考官方文档

    工具类代码如下

    1. @Data
    2. @AllArgsConstructor
    3. @Slf4j
    4. public class AliOssUtil {
    5. private String endpoint;
    6. private String accessKeyId;
    7. private String accessKeySecret;
    8. private String bucketName;
    9. /**
    10. * 文件上传
    11. *
    12. * @param bytes
    13. * @param objectName
    14. * @return
    15. */
    16. public String upload(byte[] bytes, String objectName) {
    17. // 创建OSSClient实例。
    18. OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    19. try {
    20. // 创建PutObject请求。
    21. ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
    22. } catch (OSSException oe) {
    23. System.out.println("Caught an OSSException, which means your request made it to OSS, "
    24. + "but was rejected with an error response for some reason.");
    25. System.out.println("Error Message:" + oe.getErrorMessage());
    26. System.out.println("Error Code:" + oe.getErrorCode());
    27. System.out.println("Request ID:" + oe.getRequestId());
    28. System.out.println("Host ID:" + oe.getHostId());
    29. } catch (ClientException ce) {
    30. System.out.println("Caught an ClientException, which means the client encountered "
    31. + "a serious internal problem while trying to communicate with OSS, "
    32. + "such as not being able to access the network.");
    33. System.out.println("Error Message:" + ce.getMessage());
    34. } finally {
    35. if (ossClient != null) {
    36. ossClient.shutdown();
    37. }
    38. }
    39. //文件访问路径规则 https://BucketName.Endpoint/ObjectName
    40. StringBuilder stringBuilder = new StringBuilder("https://");
    41. stringBuilder
    42. .append(bucketName)
    43. .append(".")
    44. .append(endpoint)
    45. .append("/")
    46. .append(objectName);
    47. log.info("文件上传到:{}", stringBuilder.toString());
    48. return stringBuilder.toString();
    49. }
    50. }

    新增菜品

    菜品除了自身单一的属性之外,还有和口味的一对多的关系,这里将口味抽离出来作为单独的对象,自建一个数据库表,所以在添加菜品时应先将口味除外的属性添加到Dish表,并拿到回显的主键,然后将设置好对应关系的口味插入DishFlavor表中

    1. @Override
    2. public Result save(DishDTO dishDTO) {
    3. Dish dish=new Dish();
    4. BeanUtils.copyProperties(dishDTO,dish);
    5. dish.setStatus(StatusConstant.ENABLE);
    6. List flavors=dishDTO.getFlavors();
    7. if(dishMapper.check(dish.getName())!=0)
    8. {return Result.error("菜品已存在");}
    9. dishMapper.save(dish);
    10. Long id=dish.getId();
    11. if(flavors!=null && !flavors.isEmpty()){
    12. flavors.forEach(dishFlavor -> {
    13. dishFlavor.setDishId(id);
    14. });
    15. }
    16. dishFlavorMapper.saveBatch(flavors);
    17. return Result.success();
    18. }

    菜品分页展示

    dish表中只含有category_id,没有菜品分类的名字,这里使用多表查询,为c的项目起别名来映射到对象

    删除菜品

    删除单个和多个菜品共用一个方法即可,删除菜品时得删两个表,菜品表和口味表,在售的菜品不能删,还包含在套餐里的菜品也不能删

    1. @Override
    2. public Result deleteBatch(List ids) {
    3. //在售不能删
    4. Long countSell=dishMapper.numberOfSell(ids);
    5. //套餐关联不能删
    6. Long countSetMeal=setMealDishMapper.numberOfDishId(ids);
    7. if(countSell!=0||countSetMeal!=0)
    8. {
    9. return Result.error("有菜品在售或被套餐绑定!");
    10. }
    11. dishMapper.deleteBatch(ids);
    12. dishFlavorMapper.deleteByDishId(ids);
    13. return Result.success("删除成功");
    14. }

    修改菜品

    修改菜品时口味表也许会变,得先删除原先的口味表,然后新设置口味表插入

    1. @Override
    2. @Transactional
    3. public void update(DishDTO dishDTO) {
    4. Dish dish=new Dish();
    5. BeanUtils.copyProperties(dishDTO,dish);
    6. List dishId=new ArrayList<>();//复用方法删除口味表
    7. dishId.add(dishDTO.getId());
    8. dishFlavorMapper.deleteByDishId(dishId);
    9. List flavors=dishDTO.getFlavors();//为dish设置口味表
    10. Long id=dishDTO.getId();
    11. if(flavors!=null && !flavors.isEmpty()){
    12. flavors.forEach(dishFlavor -> {
    13. dishFlavor.setDishId(id);
    14. });
    15. }
    16. dishFlavorMapper.saveBatch(flavors);
    17. System.out.println("OK");
    18. dishMapper.update(dish);
    19. }

    店铺营业状态开发

    店铺状态查询需要频繁查询,内容又简单,适合使用redis来实现

    Redis在spring-boot中的使用方法

    1.导入maven坐标

    1. org.springframework.boot
    2. spring-boot-starter-data-redis

    2.在配置文件中配置redis

    1. redis:
    2. password: 123456 //密码
    3. host: localhost //地址
    4. port: 6379 //端口
    5. database: 0 //使用几号库

    3.添加配置类,导入序列化器

    1. @Configuration
    2. @Slf4j
    3. public class RedisConfiguration {
    4. @Bean
    5. public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
    6. log.info("创建redis模板对象");
    7. RedisTemplate redisTemplate=new RedisTemplate();
    8. redisTemplate.setConnectionFactory(redisConnectionFactory);
    9. redisTemplate.setKeySerializer(new StringRedisSerializer());
    10. return redisTemplate;
    11. }
    12. }

    然后在代码里通过redisTemplate对象得到的如ValueOperations的方法就可使用redis

    营业端代码

    1. private final String KEY="SHOP_STATUS";
    2. @PutMapping("/{status}")
    3. @ApiOperation("设置营业状态")
    4. public Result setShopStatus(@PathVariable Integer status)
    5. {
    6. log.info("设置营业状态:{}",status==1?"营业中":"打样中");
    7. ValueOperations valueOperations=redisTemplate.opsForValue();
    8. valueOperations.set(KEY,status);
    9. return Result.success();
    10. }
    11. @GetMapping("/status")
    12. @ApiOperation("查询营业状态")
    13. public Result getShopStatus()
    14. {
    15. ValueOperations valueOperations= redisTemplate.opsForValue();
    16. Integer status= (Integer) valueOperations.get(KEY);
    17. log.info("营业状态:{}",status==1?"营业中":"打样中");
    18. return Result.success(status);
    19. }

    微信小程序登录

    微信小程序登录的官方流程图如下

    小程序先发出携带js_code的登录请求,服务端接收后将小程序的id密钥连同用户的js_code发送给微信接口服务,微信接口服务返回用户的openid给服务端,服务端此时可以通过此id来注册登录用户

    向微信接口服务发送请求时可以使用Httpclient,此处用工具类包装

    1. private String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
    2. private String getOpenid(String code){
    3. Map mp=new HashMap<>();
    4. mp.put("appid", weChatProperties.getAppid());
    5. mp.put("secret",weChatProperties.getSecret());
    6. mp.put("js_code",code);
    7. mp.put("grant_type","authorization_code");
    8. String json=HttpClientUtil.doGet(WX_LOGIN,mp);
    9. JSONObject jsonObject= JSON.parseObject(json);
    10. return jsonObject.getString("openid");
    11. }

    同时注意要再设置一个拦截器拦截来自用户端的请求

    用户端浏览菜品

    后端代码与管理端大同小异,不再赘述,根据需求文档编写代码

    但是,在用户端访问时,可能出现频繁请求访问的情况,使用redis缓存来减轻数据库压力,再通过分类查询菜品后,将该分类id作为key,将得到的数据放入redis缓存中,下次用户查询若redis中已经存在该数据,就不用查询数据库了

    1. String key="dish_"+categoryId;
    2. List list = (List) redisTemplate.opsForValue().get(key);
    3. if(list!=null && !list.isEmpty())
    4. {
    5. return Result.success(list);
    6. }

    为了保持数据的一致性,管理端在更新修改菜品时,应该清除redis中相关的缓存

    在修改操作返回前执行清除缓存操作

    1. private void cleanCache(String pattern)
    2. {
    3. Set keys=redisTemplate.keys(pattern);
    4. redisTemplate.delete(keys);
    5. }

    使用spring_cache管理套餐缓存

    补全套餐接口,注意没写根据id获得套餐信息(数据回显)之前无法正常测试修改套餐接口,(明明前端已经有套餐id了还要使用回显的id),代码重复度高,略

    spring_cache使用方法

    导入坐标

    1. org.springframework.boot
    2. spring-boot-starter-cache

    在启动类上加上@EnableCaching注解开启缓存代理,会自动判断你使用的缓存来进行管理

    在需要管理的方法上加上对应注解即可

    @Cacheable(cacheNames="xxx",key="#yyy")

    将在缓存中添加key为xxx::yyy,value为方法返回值的键值对(#代表取值而不是字符串)

    @CacheEvict(cacheNames="xxx",allentries=true)

    删除所有xxx::的缓存,也可将后面替换为key来精确删除

    购物车业务

    单独开表实现购物车功能,购物车表里每一项都代表着某人的购物车里的某个菜,在添加时先查询是否存在,如果存在就修改数量,不存在就从其他表里拿出必要信息补全并添加,删除同理,删除单个时要先判断菜品的数量来决定修改还是删除

    添加代码如下

    1. @Override
    2. public void add(ShoppingCartDTO shoppingCartDTO) {
    3. ShoppingCart shoppingCart=new ShoppingCart();
    4. BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    5. shoppingCart.setUserId(BaseContext.getCurrentId());
    6. List list=shoppingCartMapper.list(shoppingCart);
    7. if(list!=null && !list.isEmpty())
    8. {
    9. ShoppingCart res=list.get(0);
    10. res.setNumber(res.getNumber()+1);
    11. shoppingCartMapper.update(res);
    12. return ;
    13. }
    14. if(shoppingCart.getDishId()!=null)
    15. {
    16. Long dishId=shoppingCart.getDishId();
    17. Dish dish= new Dish();
    18. dish.setId(dishId);
    19. List res=dishMapper.list(dish);
    20. dish=res.get(0);
    21. shoppingCart.setImage(dish.getImage());
    22. shoppingCart.setName(dish.getName());
    23. shoppingCart.setAmount(dish.getPrice());
    24. }
    25. else
    26. {
    27. Long setmealId=shoppingCart.getSetmealId();
    28. Setmeal setmeal=setmealMapper.selectById(setmealId);
    29. shoppingCart.setImage(setmeal.getImage());
    30. shoppingCart.setName(setmeal.getName());
    31. shoppingCart.setAmount(setmeal.getPrice());
    32. }
    33. shoppingCart.setNumber(1);
    34. shoppingCart.setCreateTime(LocalDateTime.now());
    35. shoppingCartMapper.add(shoppingCart);
    36. }

    删除代码如下

    1. @Override
    2. public void delete(ShoppingCartDTO shoppingCartDTO) {
    3. ShoppingCart shoppingCart=new ShoppingCart();
    4. BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    5. shoppingCart.setUserId(BaseContext.getCurrentId());
    6. List list=shoppingCartMapper.list(shoppingCart);
    7. ShoppingCart res=list.get(0);
    8. if(res.getNumber()>1)
    9. {
    10. res.setNumber(res.getNumber()-1);
    11. shoppingCartMapper.update(res);
    12. }
    13. else
    14. {
    15. shoppingCartMapper.delete(shoppingCart);
    16. }
    17. }

    订单提交

    订单提交到后端时,先处理下数据,处理没有地址,购物车为空的情况(前端已经完成过,以防万一),然后将前端传过来的数据补全,添加到订单表(order)和订单详情表(order_detail),最后注意清空用户的购物车表(shoppingcart)

    订单支付

    个人认证的小程序没法使用支付功能,这里跳过支付,只要用户点击支付就算支付成功,代码如下

    1. @Override
    2. public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) {
    3. Long userId=BaseContext.getCurrentId();
    4. User user=userMapper.getByid(userId);
    5. JSONObject jsonObject = new JSONObject();
    6. jsonObject.put("code", "ORDERPAID");
    7. OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
    8. vo.setPackageStr(jsonObject.getString("package"));//返回前端的对象
    9. Orders orders=new Orders();
    10. orders.setNumber(ordersPaymentDTO.getOrderNumber());
    11. orders.setUserId(userId);
    12. Orders ordersDB = orderMapper.select(orders);//根据userid和订单号查询指定订单
    13. orders.setId(ordersDB.getId());
    14. orders.setStatus(Orders.TO_BE_CONFIRMED);
    15. orders.setPayStatus(Orders.PAID);
    16. orders.setPayMethod(ordersPaymentDTO.getPayMethod());
    17. orders.setCheckoutTime(LocalDateTime.now());
    18. orderMapper.update(orders);//更新为支付状态
    19. return vo;
    20. }

    微信小程序支付官方流程图

    使用Spring Task定时处理异常订单

    使用方法

    导入坐标(在整合包中已经包含)

    在启动类上添加@EnableScheduling开启定时任务

    创建定时任务类,在方法上添加@Scheduled(cron = "参数")注解

    在项目启动后,就会按照cron所表达的内容定时执行该方法

    1. @Scheduled(cron = "0 * * * * ?")
    2. public void timeOutOrder()
    3. {
    4. log.info("处理超时未支付订单:{}",LocalDateTime.now());
    5. LocalDateTime time=LocalDateTime.now().plusMinutes(-15);
    6. List list=orderMapper.wrongOrder(Orders.PENDING_PAYMENT,time);
    7. if(list==null||list.isEmpty()) return;
    8. for(Orders orders:list)
    9. {
    10. orders.setStatus(Orders.CANCELLED);
    11. orders.setCancelReason("支付超时");
    12. orders.setCancelTime(LocalDateTime.now());
    13. orderMapper.update(orders);
    14. }
    15. }
    16. @Select("select * from orders where status=#{status} and order_time<#{orderTime}")
    17. List wrongOrder(Integer status, LocalDateTime orderTime);

    使用websocket实现订单提醒

    与http短连接协议不同,ws协议是持续链接协议,建立了全双工通道,可以实时相互发送消息,项目里包装使用,代码如下

    1. @Component
    2. @ServerEndpoint("/ws/{sid}")
    3. public class WebSocketServer {
    4. //存放会话对象
    5. private static Map sessionMap = new HashMap();
    6. /**
    7. * 连接建立成功调用的方法
    8. */
    9. @OnOpen
    10. public void onOpen(Session session, @PathParam("sid") String sid) {
    11. System.out.println("客户端:" + sid + "建立连接");
    12. sessionMap.put(sid, session);
    13. }
    14. /**
    15. * 收到客户端消息后调用的方法
    16. *
    17. * @param message 客户端发送过来的消息
    18. */
    19. @OnMessage
    20. public void onMessage(String message, @PathParam("sid") String sid) {
    21. System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    22. }
    23. /**
    24. * 连接关闭调用的方法
    25. *
    26. * @param sid
    27. */
    28. @OnClose
    29. public void onClose(@PathParam("sid") String sid) {
    30. System.out.println("连接断开:" + sid);
    31. sessionMap.remove(sid);
    32. }
    33. /**
    34. * 群发
    35. *
    36. * @param message
    37. */
    38. public void sendToAllClient(String message) {
    39. Collection sessions = sessionMap.values();
    40. for (Session session : sessions) {
    41. try {
    42. //服务器向客户端发送消息
    43. session.getBasicRemote().sendText(message);
    44. } catch (Exception e) {
    45. e.printStackTrace();
    46. }
    47. }
    48. }
    49. }

    由于无法实现支付,新订单提醒放在了暂定的实现方法后面,正常应放在支付成功,微信服务端返回调用的函数中

    催单提醒时,先判断订单号和订单状态,存在相应的订单才向客户端发送催单

    Apache Echarts制表

    此为前端技术,引入js文件后,按照指定格式填写数据就能简单地在页面生成一个表格

    Apache Echarts官方手册

    因此,后端的任务根据接口文档就知道返回数据列表即可

    先将前端转过来的日期范围落实成一个日期的列表,然后根据列表的内容去查询每天的营业额,再把每天的营业额放入另一个列表,最后将两个列表封装返回给前端

    1. @Override
    2. public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
    3. List datelist=new ArrayList<>();
    4. datelist.add(begin);
    5. while(!begin.equals(end))
    6. {
    7. begin=begin.plusDays(1);
    8. datelist.add(begin);
    9. }
    10. List incomelist=new ArrayList<>();
    11. for(LocalDate date:datelist)
    12. {
    13. LocalDateTime beginTime=LocalDateTime.of(date, LocalTime.MIN);
    14. LocalDateTime endTime=LocalDateTime.of(date, LocalTime.MAX);
    15. Map mp=new HashMap<>();
    16. mp.put("begin",beginTime);
    17. mp.put("end",endTime);
    18. mp.put("status",Orders.COMPLETED);
    19. Double income=orderMapper.sumByMap(mp);
    20. incomelist.add(income==null?0.0:income);
    21. }
    22. return TurnoverReportVO.builder()
    23. .dateList(org.apache.commons.lang.StringUtils.join(datelist,","))
    24. .turnoverList(StringUtils.join(incomelist,","))
    25. .build();
    26. }

    管理端数据分析功能

    通过上述制表技术,前端要求给出日期始末,返回相应的数据

    将拿到的日期先得到一个日期的List,再根据每一个日期在数据库中相应的数据

    将得到的数据List转化为字符串(此处可用stream流),封装之后返回给前端

    订单管理

    按着计划写下来最后就差订单管理板块,根据接口文档写即可,注意在put的请求时,这里接收参数都是用dto或path,单独接收id会出错(传过来的就是对象)

    至此项目基本完成

    gitee链接

  • 相关阅读:
    【故障补牢】贪吃的 Bing 爬虫,限量供应的应对措施
    Xilinx microblaze axi can 使用说明
    Java程序设计——枚举(Java高级应用)
    【2023研电赛】安谋科技企业命题特别奖:面向独居老人的智能居家监护系统
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    小程序容器如何确保小程序安全?
    使用并查集生成一个迷宫
    智能电子界桩自然保护区远程监控解决方案
    图片懒加载的原理
    MySQL - 初识MySQL
  • 原文地址:https://blog.csdn.net/xzwysElysia/article/details/138795270