最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

springboot+websocket實(shí)現(xiàn)并發(fā)搶紅包功能

 更新時(shí)間:2021年12月21日 11:39:19   作者:泰克農(nóng)民工  
本文主要介紹了springboot+websocket實(shí)現(xiàn)并發(fā)搶紅包功能,主要包含了4種步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

概述

搶紅包功能作為幾大高并發(fā)場(chǎng)景中典型,應(yīng)該如何實(shí)現(xiàn)?

源碼地址:https://gitee.com/tech-famer/farmer-redpacket

分析

參考微信搶紅包功能,將搶紅包分成一下幾個(gè)步驟:

  • 發(fā)紅包;主要填寫(xiě)紅包信息,生成紅包記錄
  • 紅包支付回調(diào);用戶(hù)發(fā)紅包支付成功后,收到微信支付付款成功的回調(diào),生成指定數(shù)量的紅包。
  • 搶紅包;用戶(hù)并發(fā)搶紅包。
  • 拆紅包;記錄用戶(hù)搶紅包記錄,轉(zhuǎn)賬搶到的紅包金額。

?效果展示

項(xiàng)目使用sessionId模擬用戶(hù),示例打開(kāi)倆個(gè)瀏覽器窗口模擬兩個(gè)用戶(hù)。

在這里插入圖片描述

設(shè)計(jì)開(kāi)發(fā)

表結(jié)構(gòu)設(shè)計(jì)

紅包記錄在 redpacket 表中,用戶(hù)領(lǐng)取紅包詳情記錄在 redpacket_detail 表中。

CREATE DATABASE  `redpacket`;

use `redpacket`;

CREATE TABLE `redpacket`.`redpacket` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `packet_no` varchar(32) NOT NULL COMMENT '訂單號(hào)',
  `amount` decimal(5,2) NOT NULL COMMENT '紅包金額最高10000.00元',
  `num` int(11) NOT NULL COMMENT '紅包數(shù)量',
  `order_status` int(4) NOT NULL DEFAULT '0' COMMENT '訂單狀態(tài):0初始、1待支付、2支付成功、3取消',
  `pay_seq` varchar(32) DEFAULT NULL COMMENT '支付流水號(hào)',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
  `user_id` varchar(32) NOT NULL COMMENT '用戶(hù)ID',
  `update_time` datetime NOT NULL COMMENT '更新時(shí)間',
  `pay_time` datetime DEFAULT NULL COMMENT '支付時(shí)間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='紅包訂單表';

CREATE TABLE `redpacket`.`redpacket_detail` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `packet_id` bigint(20) NOT NULL COMMENT '紅包ID',
  `amount` decimal(5,2) NOT NULL COMMENT '紅包金額',
  `received` int(1) NOT NULL DEFAULT '0' COMMENT '是否領(lǐng)取0未領(lǐng)取、1已領(lǐng)取',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime NOT NULL COMMENT '更新時(shí)間',
  `user_id` varchar(32) DEFAULT NULL COMMENT '領(lǐng)取用戶(hù)',
  `packet_no` varchar(32) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='紅包詳情表';

發(fā)紅包設(shè)計(jì)

用戶(hù)需要填寫(xiě)紅包金額、紅包數(shù)量、備注信息等,生成紅包記錄,微信收銀臺(tái)下單,返回用戶(hù)支付。

public RedPacket generateRedPacket(ReqSendRedPacketsVO data,String userId) {
    final BigDecimal amount = data.getAmount();
    //紅包數(shù)量
    final Integer num = data.getNum();

    //初始化訂單
    final RedPacket redPacket = new RedPacket();
    redPacket.setPacketNo(UUID.randomUUID().toString().replace("-", ""));
    redPacket.setAmount(amount);
    redPacket.setNum(num);
    redPacket.setUserId(userId);
    Date now = new Date();
    redPacket.setCreateTime(now);
    redPacket.setUpdateTime(now);
    int i = redPacketMapper.insertSelective(redPacket);
    if (i != 1) {
        throw new ServiceException("生成紅包出錯(cuò)", ExceptionType.SYS_ERR);
    }

    //模擬收銀臺(tái)下單
    String paySeq = UUID.randomUUID().toString().replace("-", "");

    //拿到收銀臺(tái)下單結(jié)果,更新訂單為待支付狀態(tài)
    redPacket.setOrderStatus(1);//待支付
    redPacket.setPaySeq(paySeq);
    i = redPacketMapper.updateByPrimaryKeySelective(redPacket);
    if (i != 1) {
        throw new ServiceException("生成紅包出錯(cuò)", ExceptionType.SYS_ERR);
    }
    return redPacket;
}

在這里插入圖片描述

紅包支付成功回調(diào)設(shè)計(jì)

用戶(hù)支付成功后,系統(tǒng)接收到微信回調(diào)接口。

更新紅包支付狀態(tài)
二倍均值法生成指定數(shù)量紅包,并批量入庫(kù)。 紅包算法參考:Java實(shí)現(xiàn)4種微信搶紅包算法
紅包總數(shù)入redis,設(shè)置紅包過(guò)期時(shí)間24小時(shí)
websocket通知在線(xiàn)用戶(hù)收到新的紅包

@Transactional(rollbackFor = Exception.class)
public void dealAfterOrderPayCallback(String userId,ReqOrderPayCallbackVO data) {
    RedPacketExample example = new RedPacketExample();
    final String packetNo = data.getPacketNo();
    final String paySeq = data.getPaySeq();
    final Integer payStatus = data.getPayStatus();
    example.createCriteria().andPacketNoEqualTo(packetNo)
            .andPaySeqEqualTo(paySeq)
            .andOrderStatusEqualTo(1);//待支付狀態(tài)
    //更新訂單支付狀態(tài)
    Date now = new Date();
    RedPacket updateRedPacket = new RedPacket();
    updateRedPacket.setOrderStatus(payStatus);
    updateRedPacket.setUpdateTime(now);
    updateRedPacket.setPayTime(now);
    int i = redPacketMapper.updateByExampleSelective(updateRedPacket, example);
    if (i != 1) {
        throw new ServiceException("訂單狀態(tài)更新失敗", ExceptionType.SYS_ERR);
    }

    if (payStatus == 2) {
        RedPacketExample query = new RedPacketExample();
        query.createCriteria().andPacketNoEqualTo(packetNo)
                .andPaySeqEqualTo(paySeq)
                .andOrderStatusEqualTo(2);
        final RedPacket redPacket = redPacketMapper.selectByExample(query).get(0);
        final List<BigDecimal> detailList = getRedPacketDetail(redPacket.getAmount(), redPacket.getNum());
        final int size = detailList.size();
        if (size <= 100) {
            i = detailMapper.batchInsert(detailList, redPacket);
            if (size != i) {
                throw new ServiceException("生成紅包失敗", ExceptionType.SYS_ERR);
            }
        } else {
            int times = size % 100 == 0 ? size / 100 : (size / 100 + 1);
            for (int j = 0; j < times; j++) {
                int fromIndex = 100 * j;
                int toIndex = 100 * (j + 1) - 1;
                if (toIndex > size - 1) {
                    toIndex = size - 1;
                }
                final List<BigDecimal> subList = detailList.subList(fromIndex, toIndex);
                i = detailMapper.batchInsert(subList, redPacket);
                if (subList.size() != i) {
                    throw new ServiceException("生成紅包失敗", ExceptionType.SYS_ERR);
                }
            }
        }

        final String redisKey = REDPACKET_NUM_PREFIX + redPacket.getPacketNo();

        String lua = "local i = redis.call('setnx',KEYS[1],ARGV[1])\r\n" +
                "if i == 1 then \r\n" +
                "   local j = redis.call('expire',KEYS[1],ARGV[2])\r\n" +
                "end \r\n" +
                "return i";
        //優(yōu)化成lua腳本
        final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(redisKey), size, 3600 * 24);
        if (execute != 1L) {
            throw new ServiceException("生成紅包失敗", ExceptionType.SYS_ERR);
        }
        //websocket通知在線(xiàn)用戶(hù)收到新的紅包
        Websocket.sendMessageToUser(userId, JSONObject.toJSONString(redPacket));
    }
}


/**
 * 紅包隨機(jī)算法
 *
 * @param amount 紅包金額
 * @param num    紅包數(shù)量
 * @return 隨機(jī)紅包集合
 */
private List<BigDecimal> getRedPacketDetail(BigDecimal amount, Integer num) {
    List<BigDecimal> redPacketsList = new ArrayList<>(num);
    //最小紅包金額
    final BigDecimal min = new BigDecimal("0.01");
    //最少需要紅包金額
    final BigDecimal bigNum = new BigDecimal(num);
    final BigDecimal atLastAmount = min.multiply(bigNum);
    //出去最少紅包金額后剩余金額
    BigDecimal remain = amount.subtract(atLastAmount);
    if (remain.compareTo(BigDecimal.ZERO) == 0) {
        for (int i = 0; i < num; i++) {
            redPacketsList.add(min);
        }
        return redPacketsList;
    }

    final Random random = new Random();
    final BigDecimal hundred = new BigDecimal("100");
    final BigDecimal two = new BigDecimal("2");
    BigDecimal redPacket;
    for (int i = 0; i < num; i++) {
        if (i == num - 1) {
            redPacket = remain;
        } else {
            //100內(nèi)隨機(jī)獲得的整數(shù)
            final int rand = random.nextInt(100);
            redPacket = new BigDecimal(rand).multiply(remain.multiply(two).divide(bigNum.subtract(new BigDecimal(i)), 2, RoundingMode.CEILING)).divide(hundred, 2, RoundingMode.FLOOR);
        }
        if (remain.compareTo(redPacket) > 0) {
            remain = remain.subtract(redPacket);
        } else {
            remain = BigDecimal.ZERO;
        }
        redPacketsList.add(min.add(redPacket));
    }

    return redPacketsList;
}

頁(yè)面加載成功后初始化websocket,監(jiān)聽(tīng)后端新紅包生成成功,動(dòng)態(tài)添加紅包到聊天窗口。

$(function (){
    var websocket;
    if('WebSocket' in window) {
        console.log("此瀏覽器支持websocket");
        websocket = new WebSocket("ws://127.0.0.1:8082/websocket/${session.id}");
    } else if('MozWebSocket' in window) {
        alert("此瀏覽器只支持MozWebSocket");
    } else {
        alert("此瀏覽器只支持SockJS");
    }
    websocket.onopen = function(evnt) {
        console.log("鏈接服務(wù)器成功!")
    };
    websocket.onmessage = function(evnt) {
        console.log(evnt.data);
        var json = eval('('+evnt.data+ ')');
        obj.addPacket(json.id,json.packetNo,json.userId)

    };
    websocket.onerror = function(evnt) {};
    websocket.onclose = function(evnt) {
        console.log("與服務(wù)器斷開(kāi)了鏈接!")
    }
});

搶紅包設(shè)計(jì)

搶紅包設(shè)計(jì)高并發(fā),本地單機(jī)項(xiàng)目,通過(guò)原子Integer控制搶紅包接口并發(fā)限制為20,

private AtomicInteger receiveCount = new AtomicInteger(0);

@PostMapping("/receive")
public CommonJsonResponse receiveOne(@Validated @RequestBody CommonJsonRequest<ReqReceiveRedPacketVO> vo) {
    Integer num = null;
    try {
        //控制并發(fā)不要超過(guò)20
        if (receiveCount.get() > 20) {
            return new CommonJsonResponse("9999", "太快了");
        }
        num = receiveCount.incrementAndGet();
        final String s = orderService.receiveOne(vo.getData());
        return StringUtils.isEmpty(s) ? CommonJsonResponse.ok() : new CommonJsonResponse("9999", s);
    } finally {
        if (num != null) {
            receiveCount.decrementAndGet();
        }
    }
}

對(duì)于沒(méi)有領(lǐng)取過(guò)該紅包的用戶(hù),在紅包沒(méi)有過(guò)期且紅包還有剩余的情況下,搶紅包成功,記錄成功標(biāo)識(shí)入redis,設(shè)置標(biāo)識(shí)過(guò)期時(shí)間為5秒。

public String receiveOne(ReqReceiveRedPacketVO data) {
    final Long redPacketId = data.getPacketId();
    final String redPacketNo = data.getPacketNo();
    final String redisKey = REDPACKET_NUM_PREFIX + redPacketNo;
    if (!redisTemplate.hasKey(redisKey)) {
        return "紅包已經(jīng)過(guò)期";
    }
    final Integer num = (Integer) redisTemplate.opsForValue().get(redisKey);
    if (num <= 0) {
        return "紅包已搶完";
    }
    RedPacketDetailExample example = new RedPacketDetailExample();
    example.createCriteria().andPacketIdEqualTo(redPacketId)
            .andReceivedEqualTo(1)
            .andUserIdEqualTo(data.getUserId());
    final List<RedPacketDetail> details = detailMapper.selectByExample(example);
    if (!details.isEmpty()) {
        return "該紅包已經(jīng)領(lǐng)取過(guò)了";
    }
    final String receiveKey = REDPACKET_RECEIVE_PREFIX + redPacketNo + ":" + data.getUserId();

    //優(yōu)化成lua腳本
    String lua = "local i = redis.call('setnx',KEYS[1],ARGV[1])\r\n" +
            "if i == 1 then \r\n" +
            "   local j = redis.call('expire',KEYS[1],ARGV[2])\r\n" +
            "end \r\n" +
            "return i";
    //優(yōu)化成lua腳本
    final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(receiveKey), 1, 5);
    if (execute != 1L) {
        return "太快了";
    }
    return "";
}

拆紅包設(shè)計(jì)

在用戶(hù)搶紅包成功標(biāo)識(shí)未過(guò)期的狀態(tài)下,且紅包未過(guò)期紅包未領(lǐng)完時(shí),從數(shù)據(jù)庫(kù)中領(lǐng)取一個(gè)紅包,領(lǐng)取成功將領(lǐng)取記錄寫(xiě)入redis以供查詢(xún)過(guò)期時(shí)間為48小時(shí)。

@Transactional(rollbackFor = Exception.class)
public String openRedPacket(ReqReceiveRedPacketVO data) {
    final Long packetId = data.getPacketId();
    final String packetNo = data.getPacketNo();
    final String userId = data.getUserId();
    final String redisKey = REDPACKET_NUM_PREFIX + packetNo;
    Long num = null;
    try {
        final String receiveKey = REDPACKET_RECEIVE_PREFIX + packetNo + ":" + userId;
        if (!redisTemplate.hasKey(receiveKey)) {
            log.info("未獲取到紅包資格,packet:{},user:{}", packetNo, userId);
            throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
        }
        redisTemplate.delete(receiveKey);
        if (!redisTemplate.hasKey(redisKey)) {
            log.info("紅包過(guò)期了,packet:{}", packetNo);
            throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
        }
        num = redisTemplate.opsForValue().increment(redisKey, -1);
        if (num < 0L) {
            log.info("紅包領(lǐng)完了,packet:{}", packetNo);
            throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
        }
        final int i = detailMapper.receiveOne(packetId, packetNo, userId);
        if (i != 1) {
            log.info("紅包真的領(lǐng)完了,packet:{}", packetNo);
            throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
        }
        RedPacketDetailExample example = new RedPacketDetailExample();
        example.createCriteria().andPacketIdEqualTo(packetId)
                .andReceivedEqualTo(1)
                .andUserIdEqualTo(userId);
        final List<RedPacketDetail> details = detailMapper.selectByExample(example);
        if (details.size() != 1) {
            log.info("已經(jīng)領(lǐng)取過(guò)了,packet:{},user:{}", packetNo, userId);
            throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
        }
        //處理加款
        log.info("搶到紅包金額{},packet:{},user:{}", details.get(0).getAmount(), packetNo, userId);
        final String listKey = REDPACKET_LIST_PREFIX + packetNo;
        redisTemplate.opsForList().leftPush(listKey,details.get(0));
        redisTemplate.expire(redisKey, 48, TimeUnit.HOURS);
        return "" + details.get(0).getAmount();
    } catch (Exception e) {
        if (num != null) {
            redisTemplate.opsForValue().increment(redisKey, 1L);
        }
        log.warn("打開(kāi)紅包異常", e);
        throw new ServiceException("紅包飛走了", ExceptionType.SYS_ERR);
    }
}

其中 detailMapper.receiveOne(packetId, packetNo, userId); sql如下,將指定紅包記錄下未領(lǐng)取的紅包更新一條未當(dāng)前用戶(hù)已經(jīng)領(lǐng)取,若成功更新一條則表示領(lǐng)取成功,否則領(lǐng)取失敗。

update redpacket_detail d
set received = 1,update_time = now(),user_id = #{userId,jdbcType=VARCHAR}
where received = 0
and packet_id = #{packetId,jdbcType=BIGINT}
and packet_no = #{packetNo,jdbcType=VARCHAR}
and user_id is null
limit 1

獲取紅包領(lǐng)取記錄設(shè)計(jì)

直接充redis中獲取用戶(hù)領(lǐng)取記錄,沒(méi)有則直接獲取數(shù)據(jù)庫(kù)并同步至redis。

public RespReceiveListVO receiveList(ReqReceiveListVO data) {
    //紅包記錄redisKey
    final String packetNo = data.getPacketNo();
    final String redisKey = REDPACKET_LIST_PREFIX + packetNo;
    if (!redisTemplate.hasKey(redisKey)) {
        RedPacketDetailExample example = new RedPacketDetailExample();
        example.createCriteria().andPacketNoEqualTo(packetNo)
                .andReceivedEqualTo(1);
        final List<RedPacketDetail> list = detailMapper.selectByExample(example);
        redisTemplate.opsForList().leftPushAll(redisKey, list);
        redisTemplate.expire(redisKey, 24, TimeUnit.HOURS);
    }
    List retList = redisTemplate.opsForList().range(redisKey, 0, -1);
    final Object collect = retList.stream().map(item -> {
        final JSONObject packetDetail = (JSONObject) item;
        return ReceiveRecordVO.builder()
                .amount(packetDetail.getBigDecimal("amount"))
                .receiveTime(packetDetail.getDate("updateTime"))
                .userId(packetDetail.getString("userId"))
                .packetId(packetDetail.getLong("redpacketId"))
                .packetNo(packetDetail.getString("redpacketNo"))
                .build();
    }).collect(Collectors.toList());
    return RespReceiveListVO.builder().list((List) collect).build();
}

jmeter并發(fā)測(cè)試搶紅包、查紅包接口

設(shè)置jmeter參數(shù)1秒中并發(fā)請(qǐng)求50個(gè)搶11個(gè)紅包,可以看到,前面的請(qǐng)求都是成功的,中間并發(fā)量上來(lái)后有部分達(dá)到并發(fā)上限被攔截,后面紅包搶完請(qǐng)求全部失敗。

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

到此這篇關(guān)于springboot+websocket實(shí)現(xiàn)并發(fā)搶紅包功能的文章就介紹到這了,更多相關(guān)springboot+websocket并發(fā)搶紅包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Security無(wú)法調(diào)用接口錯(cuò)誤的問(wèn)題解決

    Spring Security無(wú)法調(diào)用接口錯(cuò)誤的問(wèn)題解決

    記錄一下之前在寫(xiě)程序的時(shí)候遇到的問(wèn)題,Spring Security無(wú)法調(diào)用接口錯(cuò)誤的問(wèn)題,本文就來(lái)介紹一下解決方法,感興趣的可以了解一下
    2023-08-08
  • 詳解Mybatis的二級(jí)緩存配置

    詳解Mybatis的二級(jí)緩存配置

    這篇文章主要介紹了Mybatis的二級(jí)緩存配置的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • 解決spring cloud zuul與nginx的域名轉(zhuǎn)發(fā)問(wèn)題

    解決spring cloud zuul與nginx的域名轉(zhuǎn)發(fā)問(wèn)題

    這篇文章主要介紹了spring cloud zuul與nginx的域名轉(zhuǎn)發(fā)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Springboot集成規(guī)則引擎Drools方式

    Springboot集成規(guī)則引擎Drools方式

    這篇文章主要介紹了Springboot集成規(guī)則引擎Drools方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • java如何將可運(yùn)行jar打包成exe可執(zhí)行文件

    java如何將可運(yùn)行jar打包成exe可執(zhí)行文件

    Java程序完成以后,對(duì)于Windows操作系統(tǒng)習(xí)慣總是想雙擊某個(gè)exe文件就可以直接運(yùn)行程序,這篇文章主要給大家介紹了關(guān)于java如何將可運(yùn)行jar打包成exe可執(zhí)行文件的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • Java之Rsync并發(fā)遷移數(shù)據(jù)并校驗(yàn)詳解

    Java之Rsync并發(fā)遷移數(shù)據(jù)并校驗(yàn)詳解

    這篇文章主要介紹了Java之Rsync并發(fā)遷移數(shù)據(jù)并校驗(yàn)詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java @GlobalLock注解詳細(xì)分析講解

    Java @GlobalLock注解詳細(xì)分析講解

    這篇文章主要介紹了Java @GlobalLock注解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-11-11
  • java實(shí)現(xiàn)ATM機(jī)系統(tǒng)(2.0版)

    java實(shí)現(xiàn)ATM機(jī)系統(tǒng)(2.0版)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)ATM機(jī)系統(tǒng)2.0版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-03-03
  • Java使用Math.random()結(jié)合蒙特卡洛方法計(jì)算pi值示例

    Java使用Math.random()結(jié)合蒙特卡洛方法計(jì)算pi值示例

    這篇文章主要介紹了Java使用Math.random()結(jié)合蒙特卡洛方法計(jì)算pi值的方法,簡(jiǎn)單說(shuō)明了結(jié)合具體實(shí)例蒙特卡洛方法的原理,并結(jié)合具體實(shí)例形式分析了java使用蒙特卡洛方法計(jì)算PI值的操作技巧,需要的朋友可以參考下
    2017-09-09
  • Java實(shí)現(xiàn)將CSV轉(zhuǎn)為Excel的示例代碼

    Java實(shí)現(xiàn)將CSV轉(zhuǎn)為Excel的示例代碼

    CSV(Comma?Separated?Values)文件是一種純文本文件,包含用逗號(hào)分隔的數(shù)據(jù),常用于將數(shù)據(jù)從一個(gè)應(yīng)用程序?qū)牖驅(qū)С龅搅硪粋€(gè)應(yīng)用程序。本文將利用Java實(shí)現(xiàn)CSV轉(zhuǎn)為Excel,感興趣的可以了解一下
    2022-03-03

最新評(píng)論

吉木乃县| 汕尾市| 隆子县| 防城港市| 京山县| 漳浦县| 安阳县| 桓仁| 连云港市| 镇江市| 浙江省| 容城县| 库车县| 历史| 星子县| 泽库县| 博客| 施秉县| 长汀县| 鄂托克前旗| 南开区| 大埔县| 儋州市| 土默特左旗| 上高县| 辽源市| 梓潼县| 盖州市| 克拉玛依市| 木里| 织金县| 建昌县| 湄潭县| 阳泉市| 来宾市| 广昌县| 若尔盖县| 饶河县| 丰城市| 云安县| 眉山市|