《苍穹外卖》项目复盘--功能实现(2)

六、基于Redis的店铺营业状态设置

1、Redis简介

          Redis是基于内存的key-value结构数据库 

          因为基于内存存储读写性能更好,适合存储热点数据(热点商品、资讯、新闻)(内存有限,这类数据在某一时间段会被用户大量访问,用Redis来提升读写性能)

        这可能会出现雪崩、击穿、穿透、数据一致性等问题。

补充:MySql是基于磁盘,并通过二维表来存储数据的。

2、Redis常用数据类型和命令

(1)常用数据类型

        key 字符串类型 value中5种常用的数据类型:

  • 字符串 string:最简单的数据类型
  • 哈希 hash:也是散列,有field和value,适合存储对象,类似于HashMap
  • 列表 list:有序,类似于Linkedlist
  • 集合 set:  无序不重复集合。可进行集合运算,类似于HashSet
  • 有序集合sorted set/zset:  集合中会关联一个分数,根据分数进行排序

(2)常用命令

字符串操作命令

set key value  设置指定key的值

get key            获取指定key的值

setex key seconds value 设置指定key的值,并将key的过期时间设为seconds(设置手机验证码)

setnx key value 只有在key不存在时设置key的值(分布式锁)

哈希操作命令

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象

hset key field value   将哈希表key种的字段field的值设为value

hget key field            获取存储在哈希表中指定字段的值

hdel key field             删除

hkeys key                   获取所有的字段(field)

hvals key                    获取所有的值

列表操作命令

列表是简单的字符串列表,按照插入顺序排序,常用命令

lpush key value1 [value2]   将一个或多个值插入到列表头部 队列的顺序是先插入value1, 再插入value2

                                          存储是向上生长的,所以最上面,也就是id=1是最后一个元素,也是最先删除的

lrange key start stop          获取列表指定范围内的元素

rpop key                             移除并获取列表最后一个元素(删除的是最新放入的数据)

llen   key                             获取列表长度

集合操作命令

集合是string类型的无序集合。集合成员是唯一的,集合中不能出现重复的数据

sadd key member1 [member2]  向集合中添加一个或多个成员

smembers key                             返回集合中的所有成员

scard key                                     获取集合的成员数

sinter key1 [key2]                        返回给定所有集合的交集

sunion key1 [key2]                      返沪所有给定集合的并集

srem key member1 [member2]  删除集合中一个或多个成员

有序集合操作命令(排序顺序是升序排序)

有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数

zadd key score1 member1 [score2 member2]    向有序集合添加一个或多个成员

zrem key member [member...]                             移除有序集合中的一个或多个成员

zrange key start stop [WITHSCORES]                  返回指定区间内的成员(由小到大开始排列)

zincrby key increment member                           向有序集合中对指定成员的分数加上增量increment

通用命令

keys pattern 查找所有符合给定模式(pattern)的key

exists key     检查给定key是否存在

type key       返回key所存储的值的类型

del  key        删除key

3、Redis的Java客户端--Spring Data Redis

(1)使用方式:配置坐标、配置数据源、编写配置类,创建RedisTemplate对象,通过该对象操作

                        RedisTemplate提供了五种不同类型的对象

                       opsForValue;opsForHash();opsForList;opsForSet();opsForZset()操作不同数据类型

4、获取店铺营业状态

设置营业状态

(1)  接口设计

            请求参数:使用PUT请求。通过路径传参

                              @PathVariable

            返回值:返回状态码即可。

    (2)代码实现:

            Controller:redisTemplate.opsForValue().set("SHOP_STATUS",status)

获取营业状态

    (1)  接口设计

            请求参数:使用GET请求。

            返回值:返回状态码和店铺状态。

    (2)代码实现:

            Controller:redisTemplate.opsForValue().get("SHOP_STATUS")

     (3)补充:这里admin和user包下都有ShopController类,在容器中bean名称相同,会冲突。                因此通过注解别名的方式区分:

             @RestController("adminShopController")@RestController("userShopController")

七、基于HttpClient微信小程序登录

1、HttpClient

        (1)概述

                 Http协议的客户端编程工具包,就是在Java程序中可以发送Http请求

         (2)发送请求步骤

                创建HttpClient对象

                创建Http请求对象

                调用HttpClient的execute方法发送请求

          (3)代码实现

               发送Get方式的请求,重点在返回数据解析(返回状态码) 

//创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");


        //发送请求
        CloseableHttpResponse response = httpClient.execute(httpGet);

        //获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:"+statusCode);

        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println(body);

        //关闭资源
        response.close();
        httpClient.close();

        发送POST请求,重点在封装发送的JSON数据及数据格式

        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");

        JSONObject jsonObject=new JSONObject();
        try {
            jsonObject.put("username","admin");
            jsonObject.put("password","123456");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        StringEntity entity = new StringEntity(jsonObject.toString());

        //指定请求编码方式
        entity.setContentEncoding("utf-8");
        //数据格式
        entity.setContentType("application/json");
        httpPost.setEntity(entity);

        //发送请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        //获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:"+statusCode);

        HttpEntity entity1 = response.getEntity();
        String body = EntityUtils.toString(entity1);
        System.out.println("响应数据为:"+body);

        //关闭资源
        response.close();
        httpClient.close();

        (4)已封装至HttpClientUtil中

2、微信登录代码实现

(1)微信登录流程

小程序端登录获得授权码;发送请求后端服务,后端服务调用接口服务(后端服务发送请求可以使用httpclient)

返回:openid微信用户的唯一标识。

开发者工具保存:openid或者token,返回token

(2)具体实现过程

     ①配置微信小程序、配置JWT相关参数

 

②Controller中生成所需返回的数据(用户id,openid,令牌);封装返回结果

@PostMapping("/login")
    @ApiOperation("用户登录操作")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录:{}",userLoginDTO);

        //登录获取用户;通过授权码保存用户信息,发送请求来获取openid
        User user = userService.login(userLoginDTO);

        //生成令牌
        HashMap<String,Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID,user.getId());
        //生成令牌所需要的信息:密钥,时间,用户id
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);

        //返回用户的信息 id、openid、token
        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);
    }

③Service中获取openid(这部分需要参考微信接口服务文档)

    //微信服务接口地址
    public static final String WX_LOGIN ="https://api.weixin.qq.com/sns/jscode2session";

    @Autowired
    private WeChatProperties weChatProperties;

    @Autowired
    private UserMapper userMapper;

    /**
     * 用户微信登录
     * @param userLoginDTO
     * @return
     */
    public User login(UserLoginDTO userLoginDTO) {
        //获取该用户的openid
        String openid = getOpenid(userLoginDTO.getCode());

        //判断openid是否为空,如果为空表示登陆失败,抛出业务异常
        if(openid==null){
            throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
        }

        //判断该用户是不是为新用户
        User user=userMapper.getUserByOpenid(openid);
        if(user==null){
            user=User.builder()
                    .openid(openid)
                    .createTime(LocalDateTime.now())
                    .build();

            userMapper.insert(user);
        }

        //返回用户对象
        return user;
    }

    private String getOpenid(String code){
        //调用微信接口服务,获得当前微信用户的openid
        Map<String, String> map=new HashMap<>();
        map.put("appid",weChatProperties.getAppid());
        map.put("secret",weChatProperties.getSecret());
        map.put("js_code",code);
        map.put("grant_type","authorization_code");
        String json = HttpClientUtil.doGet(WX_LOGIN, map);

        //通过JSON来进行数据解析
        JSONObject jsonObject = JSON.parseObject(json);
        String openid = jsonObject.getString("openid");

        return openid;
    }

八、基于SpringCache缓存套餐

1、SpringCache

        Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单加一个注解,就能实现缓存功能

       Spring Cache提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCache、Caffeine、Redis(只需要导入不同的jar包,代码不需要额外的修改)

2、常用注解

 @EnableCaching 开启缓存注解功能,通常加在启动类上

 @Cacheable        在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中

@CachePut           将方法的返回值放到缓存中

@CacheEvict         将一条或多条数据从缓存中删除

3、基于SpringCache缓存套餐实现

设计:①在用户端接口SetmealController的list方法上加上@Cacheable注解。(设置了key为套餐id,value是方法的返回结果)②在管理端接口SetmealController的save、delete、update等方法上加上@CacheEvict注解(注意精确删除和全部删除)

准备操作:导入maven坐标、在启动类上加入@EnableCaching注解

    @Cacheable(cacheNames = "setmealCache",key="#categoryId")
    @GetMapping("/list")
    @ApiOperation("根据分类id查询套餐")
    public Result<List<Setmeal>> list(Long categoryId) {
        Setmeal setmeal = new Setmeal();
        setmeal.setCategoryId(categoryId);
        setmeal.setStatus(StatusConstant.ENABLE);

        List<Setmeal> list = setmealService.list(setmeal);
        return Result.success(list);
    }
    /**
     * 删除套餐
     * @param ids
     * @return
     */
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    @DeleteMapping()
    @ApiOperation("删除套餐")
    public Result delete(@RequestParam List<Long> ids){
        log.info("删除套餐:{}",ids);
        setmealService.deleteByIds(ids);
        return Result.success();
    }
    /**
     * 新增套餐
     * @param setmealDTO
     * @return
     */
    @ApiOperation("新增套餐接口")
    @PostMapping
    @CacheEvict(cacheNames = "setmealCache",key="#setmealDTO.categoryId")
    public Result save(@RequestBody SetmealDTO setmealDTO){

        log.info("新增套餐接口:{}",setmealDTO);
        setmealService.saveWithDishs(setmealDTO);
        return Result.success();
    }

九、基于SpringTask的订单状态定时处理

1、Spring Task

         Spring Task是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑

         在线cron表达式生成器 可以生成 Spring Task中cron语句需要的时间节点

2、Spring Task使用步骤

        (1)导入maven坐标spring-context

        (2)启动类添加注解@EnableScheduling开启任务调度类

        (3)自定义定时任务类(该类也要加上Component注解,需要被实例化)

@Component   //实例化,自动生成bean交给容器管理
@Slf4j
public class MyTask {

    /**
     * 定时任务 每隔5秒触发一次
     */
    @Scheduled(cron="0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务开始执行:{}",new Date());
    }
}
3、基于SpringTask的订单状态定时处理

步骤同上,自定义定时任务类OrderTask:

① 每分钟查询未支付超时订单,设为取消②每天凌晨一点将派送中订单设置为已完成

@Component
@Slf4j
public class OrderTask {

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 处理超时订单的方法
     */
    @Scheduled(cron="0 * * * * ? ") //每分钟触发一次
    public void processTimeoutOrder(){
        log.info("定时处理超时订单:{}", LocalDateTime.now());

        LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(-15);

        /**
         * 查询待支付的超时订单
         */
        List<Orders> ordersUnpaiedTimeOut = orderMapper.getByStatusAndOrderTimeLt(Orders.PENDING_PAYMENT, localDateTime);
        if(ordersUnpaiedTimeOut!=null&&ordersUnpaiedTimeOut.size()>0){
            for (Orders orders : ordersUnpaiedTimeOut) {
                orders.setStatus(Orders.CANCELLED);
                orders.setCancelReason("订单未支付已超时");
                orders.setCancelTime(LocalDateTime.now());
                orderMapper.update(orders);
            }
        }
        }

    /**
     * 处理一直处于派送中状态的订单
     */
    @Scheduled(cron="0 0 1 * * ?") //每天的凌晨一点触发一次
    public void processDeliveryOrder(){
        log.info("定时处理处于派送中的订单:{}",LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);

        List<Orders> ordersProcessDelivery = orderMapper.getByStatusAndOrderTimeLt(Orders.DELIVERY_IN_PROGRESS,time);
        if(ordersProcessDelivery!=null&&ordersProcessDelivery.size()>0){
            for (Orders orders : ordersProcessDelivery) {
                orders.setStatus(Orders.COMPLETED);
                orderMapper.update(orders);
            }
        }

    }

}

十、基于WebSocket的来单提醒和用户催单功能实现

1、WebSocket

  (1)简介: WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信--浏览器和服务器只需要一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

  (2)HTTP协议和WebSocket协议对比

        ①HTTP是短连接,WebSocket是长连接。

        ②HTTP通信是单向的,基于请求响应模式,WebSocket支持双向通信

        ③HTTP和WebSocket底层都是TCP连接

        HTTP协议是请求响应模式,只有浏览器请求服务器,服务器才会发数据给浏览器进行聊天。

 (3)WebSocket应用场景

        ①视频弹幕

        ②网页聊天(服务器主动将消息推送到网页上)

        ③体育实况更新

        ④股票基金报价实时更新

(4)实现步骤

        ①直接使用websocket.html页面作为WebSocket客户端

        ②导入WebSocket的maven坐标

        ③导入WebSocket服务端组件WebSocketServer,用于和客户端通信(有点类似于Controller,此处的请求路径用的是ws协议)

        ④导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

        ⑤导入定时任务类WebSocketTask,定时向客户端推送数据

补充:WebSocketServer会使用一个Map<String,Session>存放会话对象:会话是指一个终端用户与交互系统进行通讯的过程,比如从输入账户密码进入操作系统到退出操作系统就是一个会话过程。会话较多用于网络上,TCP的三次握手就创建了一个会话,TCP关闭连接就是关闭会话。

2、基于WebSocket的来单提醒

(1)设计

        ①通过WebSocket实现管理端页面和服务端保持长连接状态

        ②当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息

        ③客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报。

        ④约定服务端发送给客户端浏览器的数据格式是JSON,字段包括:type(消息类型)、orderId、content(消息内容)

(2)后端的具体实现

       ①导入WebSocket的maven坐标

       ②导入WebSocket服务端组件WebSocketServer,用于和客户端通信(有点类似于Controller,此处的请求路径用的是ws协议)

        ③在支付成功后,通过websocket向客户端浏览器推送消息

        //通过webSocket向客户端浏览器推送消息 type orderId content
        Map map=new HashMap();
        map.put("type",1);
        map.put("orderId",this.orders.getId());
        map.put("content","订单号:"+this.orders.getNumber());

        String json= JSON.toJSONString(map);
        //群发给所有的会话
        webSocketServer.sendToAllClient(json);
   3、客户催单的具体实现   

    (1)接口设计 

            请求参数:使用GET请求。通过路径传参 id

                              @PathVariable Integer Id

            返回值:返回状态码即可。

      (2)  Controller接收数据,调用Service方法;

           Service中方法的具体实现:查询当前数据是否存在,存在封装JSON数据,通过webSocketServer推送到前端页面。

    (3)Service中客户催单代码实现

   public void reminder(Long id){
        //通过webSocket向客户端浏览器推送消息 type orderId content
        Orders order = orderMapper.getById(id);
        Map map=new HashMap();
        map.put("type",2);   //1表示来单提醒,2表示催单
        map.put("orderId",order.getId());
        map.put("content","订单号:"+order.getNumber());

        String json= JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);
    }

十一、数据统计        

1、Apache ECharts

        是一款基于Javascript的数据可视化图表,提供直观、生动、可交互、可个性化定制的数据可视化图标研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。

        我们需要做的就是要返回可视化图表所需要的数据(横坐标、纵坐标等一系列数据)

2、以订单量统计为例

        (1)产品原型和接口设计

                

                请求方式GET,地址栏传参:开始日期begin和结束日期end

                返回数据:用OrderReportVO封装

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderReportVO implements Serializable {

    //日期,以逗号分隔,例如:2022-10-01,2022-10-02,2022-10-03
    private String dateList;

    //每日订单数,以逗号分隔,例如:260,210,215
    private String orderCountList;

    //每日有效订单数,以逗号分隔,例如:20,21,10
    private String validOrderCountList;

    //订单总数
    private Integer totalOrderCount;

    //有效订单数
    private Integer validOrderCount;

    //订单完成率
    private Double orderCompletionRate;

}

        (2)Service中方法的具体代码实现(注意这里日期集合与每一天最大最小时间计算方式)

 public OrderReportVO getOrderStatiscs(LocalDate begin, LocalDate end){
        //计算日期的集合
        List<LocalDate> localDates = new ArrayList<>();
        localDates.add(begin);
        while(!begin.equals(end)){
            begin=begin.plusDays(1);
            localDates.add(begin);
        }
        String dateList= StringUtils.join(localDates,",");
        //计算每一天的订单量
        ArrayList<Integer> orderStatiscsList = new ArrayList<Integer>();
        ArrayList<Integer> validOrderStatiscsList = new ArrayList<Integer>();
        Integer totalOrderCount=0;
        Integer validOrderCount=0;
        for (LocalDate date: localDates) {
            //获取每一天的最大时间和最小时间
            LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(date,LocalTime.MAX);

            Map map = new HashMap();
            map.put("begin",beginTime);
            map.put("end",endTime);
            Integer orderCount=orderMapper.countOrderByMap(map);
            map.put("status", Orders.COMPLETED);
            Integer validOrderCountPerDay = orderMapper.countOrderByMap(map);


            totalOrderCount=totalOrderCount+(orderCount==null?0:orderCount);
            validOrderCount=validOrderCount+(validOrderCountPerDay==null?0:validOrderCountPerDay);

            orderStatiscsList.add(orderCount);
            validOrderStatiscsList.add(validOrderCountPerDay);


        }
        String orderCountList= StringUtils.join(orderStatiscsList,",");
        String validOrderCountList=StringUtils.join(validOrderStatiscsList,",");

        Double orderCompletionRate=0.0;
        if(totalOrderCount!=0){
            orderCompletionRate=validOrderCount.doubleValue()/totalOrderCount;
        }

        //封装返回结果
        return OrderReportVO
                .builder()
                .dateList(dateList)
                .orderCountList(orderCountList)
                .validOrderCountList(validOrderCountList)
                .totalOrderCount(totalOrderCount)
                .validOrderCount(validOrderCount)
                .orderCompletionRate(orderCompletionRate)
                .build();
    }

十二、基于Apache POI运营数据报表导出

1、Apache POI简介

        Apache POI是一个处理微软office各种文件格式的开源项目。在Java程序中对各种文件进行读写操作。

       应用:①银行网银系统导出交易明细②各种业务系统导出Excel报表③批量导入业务数据

2、基于POI操作Excel文件示例

       (1) 实现步骤

           ①在内存中创建一个excel文件②在excel文件中创建sheet页③在Sheet页中创建一个行对象

(rowNumber是从0开始的)④创建列索引创建单元格,并设置其中的值。⑤通过输出的输出流将

内存中的excel写到磁盘⑥关闭资源

        (2)代码实现

public static void write() throws Exception{
        XSSFWorkbook excel = new XSSFWorkbook();
        XSSFSheet sheet = excel.createSheet();
        XSSFRow row = sheet.createRow(1);
        row.createCell(1).setCellValue("BEIJING");

        FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\info.xlsx"));
        excel.write(fileOutputStream);

        excel.close();
        fileOutputStream.close();
    }
    public static void read() throws Exception{
        FileInputStream in = new FileInputStream(new File("D://info.xlsx"));
        XSSFWorkbook excel = new XSSFWorkbook(in);
        XSSFSheet sheetAt = excel.getSheetAt(0);

        int lastRowNum = sheetAt.getLastRowNum();

        for (int i = 1; i <= lastRowNum ; i++) {
            XSSFRow row = sheetAt.getRow(i);
            String stringCellValue = row.getCell(1).getStringCellValue();
            System.out.println(stringCellValue);
        }
            excel.close();
            in.close();

    }
3、导出运营数据

        注意:这里通过读取模板类,设置excel格式,并填充数据。

public void exportBusinessData(HttpServletResponse reponse){
        //1.查询数据库,获取营业数据
        LocalDate beginDate = LocalDate.now().minusDays(30);
        LocalDate endDate = LocalDate.now().minusDays(1);
        LocalDateTime beginDateTime = LocalDateTime.of(beginDate, LocalTime.MIN);
        LocalDateTime endDateTime = LocalDateTime.of(endDate, LocalTime.MAX);

        //查询概览数据
        BusinessDataVO businessDataVO = workspaceService.getBusinessData(beginDateTime, endDateTime);
        //2.通过POI将数据写入到excel文件当中
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/template.xlsx");
        try {
            //将模板文件提取出来进行内容的填充
            XSSFWorkbook excel = new XSSFWorkbook(in);

            XSSFSheet sheetAt = excel.getSheetAt(0);

            //填充数据
            sheetAt.getRow(1).getCell(1).setCellValue("时间"+beginDate+"至"+endDate);

            //获取第4行
            XSSFRow row = sheetAt.getRow(3);
            row.getCell(2).setCellValue(businessDataVO.getTurnover());
            row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());
            row.getCell(6).setCellValue(businessDataVO.getNewUsers());

            //获取第5行
            row=sheetAt.getRow(4);
            row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());
            row.getCell(4).setCellValue(businessDataVO.getUnitPrice());

            LocalDate Date = LocalDate.now();
            //填充明细数据
            for (int i = 0; i < 30 ; i++) {
                Date = Date.minusDays(1);
                BusinessDataVO businessDataVOPerDay = workspaceService.getBusinessData(LocalDateTime.of(Date,LocalTime.MIN), LocalDateTime.of(Date,LocalTime.MAX));

                row  = sheetAt.getRow(7 + i);
                row.getCell(1).setCellValue(Date.toString());
                row.getCell(2).setCellValue(businessDataVOPerDay.getTurnover());
                row.getCell(3).setCellValue(businessDataVOPerDay.getValidOrderCount());
                row.getCell(4).setCellValue(businessDataVOPerDay.getOrderCompletionRate());
                row.getCell(5).setCellValue(businessDataVOPerDay.getUnitPrice());
                row.getCell(6).setCellValue(businessDataVOPerDay.getNewUsers());

            }
            //3.通过输出流将excel文件下载到客户端浏览器
            ServletOutputStream out = reponse.getOutputStream();
            excel.write(out);

            //关闭资源
            in.close();
            out.close();
            excel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  • 41
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值