2
0

feat: 账单和发票补充

This commit is contained in:
caiyuchao
2025-06-11 17:45:52 +08:00
parent 560162c27d
commit 32fc1d98ac
12 changed files with 454 additions and 14 deletions

View File

@@ -0,0 +1,8 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE `wfc_user_db`.`u_bill`
ADD COLUMN `invoice_number` varchar(64) NULL COMMENT '发票编号' AFTER `status`,
ADD COLUMN `invoice_file` varchar(255) NULL COMMENT '发票文件' AFTER `invoice_number`
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -108,6 +108,13 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>6.2.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,26 +1,34 @@
package org.wfc.user.controller; package org.wfc.user.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.itextpdf.html2pdf.HtmlConverter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.TemplateEngine; import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context; import org.thymeleaf.context.Context;
import org.wfc.common.core.constant.Constants; import org.wfc.common.core.constant.Constants;
import org.wfc.common.core.constant.GlobalConstants; import org.wfc.common.core.constant.GlobalConstants;
import org.wfc.common.core.domain.R; import org.wfc.common.core.domain.R;
import org.wfc.common.core.utils.MessageUtils; import org.wfc.common.core.utils.MessageUtils;
import org.wfc.common.core.utils.file.MultipartFileUtil;
import org.wfc.common.core.web.controller.BaseController; import org.wfc.common.core.web.controller.BaseController;
import org.wfc.common.mail.config.properties.MailProperties; import org.wfc.common.mail.config.properties.MailProperties;
import org.wfc.common.mail.utils.MailUtils;
import org.wfc.common.redis.service.RedisService; import org.wfc.common.redis.service.RedisService;
import org.wfc.system.api.RemoteFileService;
import org.wfc.system.api.domain.SysFile;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@@ -38,6 +46,7 @@ public class UEmailController extends BaseController {
private final MailProperties mailProperties; private final MailProperties mailProperties;
private final RedisService redisService; private final RedisService redisService;
private final TemplateEngine templateEngine; private final TemplateEngine templateEngine;
private final RemoteFileService remoteFileService;
/** /**
* 邮箱验证码 * 邮箱验证码
@@ -56,12 +65,46 @@ public class UEmailController extends BaseController {
Context context = new Context(); Context context = new Context();
context.setVariable("verificationCode", code); context.setVariable("verificationCode", code);
context.setVariable("expirationTime", Constants.MAIL_CAPTCHA_EXPIRATION); context.setVariable("expirationTime", Constants.MAIL_CAPTCHA_EXPIRATION);
String htmlStr = templateEngine.process("mail", context); context.setVariable("username", "chason");
context.setVariable("email", "707821112@qq.com");
context.setVariable("invoiceNumber", "INV-2025-05-001");
context.setVariable("invoiceDate", "2025-05-28");
context.setVariable("hasDetails", "false");
context.setVariable("itemName", "Network Traffic Fee");
context.setVariable("qty", "1");
context.setVariable("unitPrice", "$50");
context.setVariable("total", "$50");
context.setVariable("currency", "USD");
context.setVariable("traffic", "200GB");
context.setVariable("speedCap", "Upload 30 Mbps / Download 100 Mbps");
context.setVariable("clientsNumber", "5");
context.setVariable("billingCycle", "Monthly");
String htmlStr = templateEngine.process("invoice", context);
// String basePath = System.getProperty("user.dir") + File.separator + "invoice.pdf";
String basePath = "D:\\documents\\projects\\wanfi\\invoice";
FileUtil.mkdir(basePath);
basePath = basePath + File.separator + "invoice.pdf";
File uploadFile = new File(basePath);
// HtmlConverter.convertToPdf(htmlStr, outputStream);
HtmlConverter.convertToPdf(htmlStr, Files.newOutputStream(Paths.get(basePath)));
// HtmlConverter.convertToPdf(htmlStr, Files.newOutputStream(Paths.get("D:\\projects\\pro\\be.wfc\\invoice.pdf")));
MultipartFile multipartFile = MultipartFileUtil.getMultipartFile(uploadFile);
R<SysFile> fileResult = remoteFileService.upload(multipartFile);
String url = fileResult.getData().getUrl();
log.info("qr code file: {}", url);
String subject = mailProperties.getSubject(); String subject = mailProperties.getSubject();
if (StrUtil.isBlank(subject)) { if (StrUtil.isBlank(subject)) {
subject = "Your WANFI Verification Code"; subject = "Your WANFI Verification Code";
} }
MailUtils.sendHtml(email, subject, htmlStr); // MailUtils.sendHtml(email, subject, htmlStr, uploadFile);
// FileUtil.del(uploadFile);
} catch (Exception e) { } catch (Exception e) {
log.error("email verification code send failed => {}", e.getMessage()); log.error("email verification code send failed => {}", e.getMessage());
return R.fail(e.getMessage()); return R.fail(e.getMessage());

View File

@@ -1,12 +1,12 @@
package org.wfc.user.domain; package org.wfc.user.domain;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import org.wfc.common.mybatis.domain.BaseData;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.wfc.common.mybatis.domain.BaseData;
import java.math.BigDecimal;
/** /**
* <p> * <p>
@@ -38,4 +38,10 @@ public class UBill extends BaseData {
@Schema(description = "状态") @Schema(description = "状态")
private Integer status; private Integer status;
@Schema(description = "发票编号")
private String invoiceNumber;
@Schema(description = "发票文件")
private String invoiceFile;
} }

View File

@@ -0,0 +1,40 @@
package org.wfc.user.domain.bo;
import lombok.Data;
/**
* @description: 发票模板bo
* @author: cyc
* @since: 2025-06-06
*/
@Data
public class UInvoiceTemplateBo {
private String username;
private String email;
private String invoiceNumber;
private String invoiceDate;
private String itemName;
private String qty;
private String unitPrice;
private String total;
private String currency;
private boolean hasDetails;
private String traffic;
private String speedCap;
private String clientsNumber;
private String billingCycle;
}

View File

@@ -12,8 +12,8 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum OrderTypeEnum { public enum OrderTypeEnum {
PACKAGE(0, "套餐"), PACKAGE(0, "Package"),
RECHARGE(1, "充值"); RECHARGE(1, "Recharge");
private final Integer code; private final Integer code;
private final String desc; private final String desc;

View File

@@ -12,10 +12,10 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum PeriodTypeEnum { public enum PeriodTypeEnum {
HOUR(0, "小时"), HOUR(0, "Hour"),
DAY(1, ""), DAY(1, "Day"),
MONTH(2, ""), MONTH(2, "Month"),
YEAR(3, "") YEAR(3, "Year")
; ;
private final Integer code; private final Integer code;

View File

@@ -2,7 +2,10 @@ package org.wfc.user.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import org.wfc.user.domain.UBill; import org.wfc.user.domain.UBill;
import org.wfc.user.domain.UOrder;
import org.wfc.user.domain.vo.UBillUserVo; import org.wfc.user.domain.vo.UBillUserVo;
import org.wfc.user.domain.vo.UInvoiceBillVo;
import org.wfc.user.domain.vo.UInvoiceGenVo;
import java.util.List; import java.util.List;
@@ -20,4 +23,5 @@ public interface IUBillService extends IService<UBill> {
List<UInvoiceBillVo> getInvoiceByUser(); List<UInvoiceBillVo> getInvoiceByUser();
UInvoiceGenVo genInvoice(UOrder order);
} }

View File

@@ -1,15 +1,62 @@
package org.wfc.user.service.impl; package org.wfc.user.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.wfc.common.core.constant.CacheConstants;
import org.wfc.common.core.domain.LoginUser; import org.wfc.common.core.domain.LoginUser;
import org.wfc.common.core.domain.R;
import org.wfc.common.core.exception.ServiceException;
import org.wfc.common.core.utils.file.MultipartFileUtil;
import org.wfc.common.mail.config.properties.MailProperties;
import org.wfc.common.mail.utils.MailUtils;
import org.wfc.common.mybatis.domain.BaseData;
import org.wfc.common.redis.service.RedisService;
import org.wfc.common.security.utils.SecurityUtils; import org.wfc.common.security.utils.SecurityUtils;
import org.wfc.system.api.RemoteFileService;
import org.wfc.system.api.domain.SysFile;
import org.wfc.user.api.domain.UUser;
import org.wfc.user.domain.InvoiceBean;
import org.wfc.user.domain.UBill; import org.wfc.user.domain.UBill;
import org.wfc.user.domain.UOrder;
import org.wfc.user.domain.UPackage;
import org.wfc.user.domain.URateLimit;
import org.wfc.user.domain.bo.UInvoiceTemplateBo;
import org.wfc.user.domain.constant.OrderTypeEnum;
import org.wfc.user.domain.constant.PeriodTypeEnum;
import org.wfc.user.domain.vo.UBillUserVo; import org.wfc.user.domain.vo.UBillUserVo;
import org.wfc.user.domain.vo.UInvoiceBillVo;
import org.wfc.user.domain.vo.UInvoiceGenVo;
import org.wfc.user.mapper.UBillMapper; import org.wfc.user.mapper.UBillMapper;
import org.wfc.user.mapper.UPackageMapper;
import org.wfc.user.mapper.URateLimitMapper;
import org.wfc.user.mapper.UUserMapper;
import org.wfc.user.service.IUBillService; import org.wfc.user.service.IUBillService;
import org.wfc.user.util.TrafficConverter;
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@@ -19,12 +66,186 @@ import java.util.List;
* @author sys * @author sys
* @since 2025-01-06 * @since 2025-01-06
*/ */
@Slf4j
@Service @Service
@RequiredArgsConstructor
public class UBillServiceImpl extends ServiceImpl<UBillMapper, UBill> implements IUBillService { public class UBillServiceImpl extends ServiceImpl<UBillMapper, UBill> implements IUBillService {
private final MailProperties mailProperties;
private final TemplateEngine templateEngine;
private final RemoteFileService remoteFileService;
private final UUserMapper userMapper;
private final RedisService redisService;
private final UPackageMapper packageMapper;
private final URateLimitMapper urateLimitMapper;
private final InvoiceBean invoiceBean;
public static final String PRE_INVOICE_NUMBER = "INV-";
public static final String LAST_INVOICE_NUMBER = "-001";
private static final String DEFAULT_SYS_PAY_CURRENCY_VALUE = "USD";
private static final String DEFAULT_SYS_PAY_CURRENCY_SYMBOL_VALUE = "$";
@Override @Override
public List<UBillUserVo> getBillByUser() { public List<UBillUserVo> getBillByUser() {
LoginUser<Object> loginUser = SecurityUtils.getLoginUser(); LoginUser<Object> loginUser = SecurityUtils.getLoginUser();
return this.baseMapper.getBillByUser(loginUser.getUserid()); return this.baseMapper.getBillByUser(loginUser.getUserid());
} }
@Override
public List<UInvoiceBillVo> getInvoiceByUser() {
LoginUser<Object> loginUser = SecurityUtils.getLoginUser();
return this.baseMapper.getInvoiceByUser(loginUser.getUserid());
}
@Override
public UInvoiceGenVo genInvoice(UOrder order) {
String url;
UInvoiceTemplateBo invoiceBo = new UInvoiceTemplateBo();
if (order.getUserId() != null) {
UUser user = userMapper.selectUserById(order.getUserId());
invoiceBo.setUsername(user.getUserName());
invoiceBo.setEmail(user.getEmail());
}
UBill bill = this.baseMapper.selectOne(Wrappers.<UBill>lambdaQuery()
.isNotNull(UBill::getInvoiceNumber)
.orderByDesc(BaseData::getCreateTime).last("limit 1"), false);
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateStr = dateTime.format(formatter);
String oldInvoiceNumber = StrUtil.EMPTY;
if (bill != null) {
oldInvoiceNumber = bill.getInvoiceNumber();
}
invoiceBo.setInvoiceNumber(genInvoiceNumber(dateStr, oldInvoiceNumber));
invoiceBo.setInvoiceDate(dateStr);
Map<String, Object> cacheMap = redisService.getCacheMap(CacheConstants.SYS_PAY_CONFIG_KEY);
String currencySymbol = DEFAULT_SYS_PAY_CURRENCY_SYMBOL_VALUE;
String currency = DEFAULT_SYS_PAY_CURRENCY_VALUE;
if (CollUtil.isNotEmpty(cacheMap)) {
Object currencyObj = cacheMap.get(CacheConstants.SYS_PAY_CURRENCY_KEY);
if (currencyObj != null) {
currency = currencyObj.toString();
}
invoiceBo.setCurrency(currency);
Object currencySymbolObj = cacheMap.get(CacheConstants.SYS_PAY_CURRENCY_SYMBOL_KEY);
if (currencySymbolObj != null) {
currencySymbol = currencySymbolObj.toString();
}
}
if (OrderTypeEnum.PACKAGE.getCode().equals(order.getType())) {
// 套餐
invoiceBo.setHasDetails(true);
UPackage uPackage = packageMapper.selectById(order.getPackageId());
invoiceBo.setItemName(uPackage.getPackageName());
invoiceBo.setTraffic(uPackage.getTrafficEnable() ? TrafficConverter.formatBytes(uPackage.getTraffic()) : "unlimited");
invoiceBo.setClientsNumber(uPackage.getClientNumEnable() ? uPackage.getClientNum() + "" : "unlimited");
PeriodTypeEnum periodTypeEnum = PeriodTypeEnum.getByCode(uPackage.getPeriodType());
invoiceBo.setBillingCycle(uPackage.getPeriodNum() + " " + (periodTypeEnum == null ? "" : periodTypeEnum.getDesc()));
Boolean rateLimitEnable = uPackage.getRateLimitEnable();
rateLimitEnable = ObjectUtil.isNotNull(uPackage.getRateLimitId()) ? rateLimitEnable : false;
String speedCap = "";
if (rateLimitEnable) {
URateLimit rateLimit = urateLimitMapper.selectById(uPackage.getRateLimitId());
if (rateLimit != null) {
String up = rateLimit.getUpLimitEnable() ? TrafficConverter.formatKbps(rateLimit.getUpLimit()) : "unlimited";
String down = rateLimit.getDownLimitEnable() ? TrafficConverter.formatKbps(rateLimit.getDownLimit()) : "unlimited";
speedCap = "Upload " + up + " / Download " + down;
}
} else {
speedCap = "unlimited";
}
invoiceBo.setSpeedCap(speedCap);
} else if (OrderTypeEnum.RECHARGE.getCode().equals(order.getType())) {
// 充值
invoiceBo.setHasDetails(false);
invoiceBo.setItemName(OrderTypeEnum.RECHARGE.getDesc());
}
invoiceBo.setUnitPrice(currencySymbol + order.getOrderAmount().setScale(2, RoundingMode.HALF_UP));
invoiceBo.setTotal(currencySymbol + order.getOrderAmount().setScale(2, RoundingMode.HALF_UP));
// 填充发票模版
Context context = getContext(invoiceBo);
String htmlStr = templateEngine.process("invoice", context);
String prefix = invoiceBean.getPath();
String basePath = prefix + File.separator + "invoice";
String fontsDir = prefix + File.separator + "fonts";
FileUtil.mkdir(basePath);
basePath = basePath + File.separator + "invoice.pdf";
File uploadFile = new File(basePath);
FontProvider fontProvider = new FontProvider();
fontProvider.addDirectory(fontsDir);
try {
HtmlConverter.convertToPdf(htmlStr, Files.newOutputStream(Paths.get(basePath)), new ConverterProperties().setFontProvider(fontProvider));
} catch (IOException e) {
log.error("convert pdf failed {}", e.getMessage());
throw new ServiceException("convert pdf failed");
}
MultipartFile multipartFile = MultipartFileUtil.getMultipartFile(uploadFile);
R<SysFile> fileResult = remoteFileService.upload(multipartFile);
url = fileResult.getData().getUrl();
log.info("invoice file addr: {}", url);
try {
String subject = mailProperties.getSubject();
if (StrUtil.isBlank(subject)) {
subject = "Your WANFI Verification Code";
}
MailUtils.sendHtml(invoiceBo.getEmail(), subject, htmlStr, uploadFile);
FileUtil.del(uploadFile);
} catch (Exception e) {
log.error("email invoice send failed => {}", e.getMessage());
}
UInvoiceGenVo invoiceGenVo = new UInvoiceGenVo();
invoiceGenVo.setInvoiceNumber(invoiceBo.getInvoiceNumber());
invoiceGenVo.setInvoiceFile(url);
return invoiceGenVo;
}
private static Context getContext(UInvoiceTemplateBo invoiceBo) {
Context context = new Context();
context.setVariable("username", invoiceBo.getUsername());
context.setVariable("email", invoiceBo.getEmail());
context.setVariable("invoiceNumber", invoiceBo.getInvoiceNumber());
context.setVariable("invoiceDate", invoiceBo.getInvoiceDate());
context.setVariable("hasDetails", invoiceBo.isHasDetails() + "");
context.setVariable("itemName", invoiceBo.getItemName());
context.setVariable("qty", "1");
context.setVariable("unitPrice", invoiceBo.getUnitPrice());
context.setVariable("total", invoiceBo.getTotal());
context.setVariable("currency", invoiceBo.getCurrency());
context.setVariable("traffic", invoiceBo.getTraffic());
context.setVariable("speedCap", invoiceBo.getSpeedCap());
context.setVariable("clientsNumber", invoiceBo.getClientsNumber());
context.setVariable("billingCycle", invoiceBo.getBillingCycle());
return context;
}
private String genInvoiceNumber(String dateStr, String oldInvoiceNumber) {
String dateSubStr = dateStr.substring(0, 7);
String invoiceNumber = PRE_INVOICE_NUMBER + dateSubStr + LAST_INVOICE_NUMBER;
if (oldInvoiceNumber.contains(dateSubStr)) {
List<String> invStr = StrUtil.split(oldInvoiceNumber, "-");
if (CollUtil.isNotEmpty(invStr)) {
String no = invStr.get(invStr.size() - 1);
int integer = NumberUtils.toInt(no);
String noStr = String.format("%0" + 3 + "d", integer + 1);
invoiceNumber = PRE_INVOICE_NUMBER + dateSubStr + "-" + noStr;
}
}
return invoiceNumber;
}
} }

View File

@@ -33,6 +33,7 @@ import org.wfc.user.mapper.UBillMapper;
import org.wfc.user.mapper.UBillRuleMapper; import org.wfc.user.mapper.UBillRuleMapper;
import org.wfc.user.mapper.UOrderMapper; import org.wfc.user.mapper.UOrderMapper;
import org.wfc.user.service.IUAccountService; import org.wfc.user.service.IUAccountService;
import org.wfc.user.service.IUBillService;
import org.wfc.user.service.IUClientService; import org.wfc.user.service.IUClientService;
import org.wfc.user.service.IUOrderService; import org.wfc.user.service.IUOrderService;
import org.wfc.user.service.IUPackageService; import org.wfc.user.service.IUPackageService;
@@ -76,6 +77,9 @@ public class UOrderServiceImpl extends ServiceImpl<UOrderMapper, UOrder> impleme
@Autowired @Autowired
private UAccountPackageMapper accountPackageMapper; private UAccountPackageMapper accountPackageMapper;
@Autowired
private IUBillService billService;
@Autowired @Autowired
private IWifiApi wifiApi; private IWifiApi wifiApi;
@@ -108,6 +112,7 @@ public class UOrderServiceImpl extends ServiceImpl<UOrderMapper, UOrder> impleme
billMapper.insert(bill); billMapper.insert(bill);
// 保存或更新账户信息 // 保存或更新账户信息
UAccount account = accountService.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, order.getUserId()), false); UAccount account = accountService.getOne(Wrappers.<UAccount>lambdaQuery().eq(UAccount::getUserId, order.getUserId()), false);
Long accountId = null; Long accountId = null;

View File

@@ -35,8 +35,8 @@ public class AccountUtil {
public static boolean isPackageValid(UAccount account, Date current) { public static boolean isPackageValid(UAccount account, Date current) {
// 套餐是否有效 // 套餐是否有效
return (DateUtil.compare(account.getStartTime(), current) <= 0 && DateUtil.compare(account.getEndTime(), current) > 0) return (DateUtil.compare(account.getStartTime(), current) <= 0 && DateUtil.compare(account.getEndTime(), current) > 0)
&& (!account.getTrafficEnable() || account.getTrafficUsed() <= account.getTraffic()) && (!account.getTrafficEnable() || account.getTrafficUsed() < account.getTraffic())
&& (!account.getDurationEnable() || account.getDurationUsed() <= account.getDuration()); && (!account.getDurationEnable() || account.getDurationUsed() < account.getDuration());
} }
public static boolean isBalanceValid(UAccount account) { public static boolean isBalanceValid(UAccount account) {

View File

@@ -0,0 +1,106 @@
package org.wfc.user.util;
import cn.hutool.core.convert.Convert;
public class TrafficConverter {
// 定义字节单位
private static final String[] BYTE_UNITS = {"B", "KB", "MB", "GB", "TB"};
private static final String[] BPS_UNITS = {"Kbps", "Mbps", "Gbps"};
/**
* 将字节数转换为带单位的字符串
*
* @param bytes 字节数
* @return 带单位的字符串
*/
public static String formatBytes(long bytes) {
if (bytes <= 0) {
return "0B";
}
int unitIndex = 0;
double value = bytes;
while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) {
value /= 1024;
unitIndex++;
}
return String.format("%.0f%s", value, BYTE_UNITS[unitIndex]);
}
/**
* 将kbps数转换为带单位的字符串
*
* @param Kbps Kbps
* @return 带单位的字符串
*/
public static String formatKbps(long Kbps) {
if (Kbps <= 0) {
return "0Kbps";
}
int unitIndex = 0;
double value = Kbps;
while (value >= 1024 && unitIndex < BPS_UNITS.length - 1) {
value /= 1024;
unitIndex++;
}
return String.format("%.0f%s", value, BPS_UNITS[unitIndex]);
}
/**
* 将带单位的字符串转换为字节数
*
* @param byteStr 带单位的字符串
* @return 字节数
*/
public static long parseBytes(String byteStr) {
if (byteStr == null || byteStr.isEmpty()) {
throw new IllegalArgumentException("Byte string cannot be null or empty");
}
// 提取数字和单位
String[] parts = byteStr.trim().split("\\s*");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid byte string format: " + byteStr);
}
long value = Convert.toLong(parts[0]);
String unit = parts[1].toUpperCase();
// 找到单位对应的指数
int unitIndex = -1;
for (int i = 0; i < BYTE_UNITS.length; i++) {
if (BYTE_UNITS[i].equals(unit)) {
unitIndex = i;
break;
}
}
if (unitIndex == -1) {
throw new IllegalArgumentException("Unknown byte unit: " + unit);
}
// 计算字节数
long result = value;
for (int i = 0; i < unitIndex; i++) {
result *= 1024;
}
return result;
}
public static void main(String[] args) {
// 测试字节单位转换
long bytes = 123456789L;
// long bytes = 1073741824L;
String formatted = formatBytes(bytes);
System.out.println(formatted); // 输出: 117.74MB
long kbps = 204800L;
formatted = formatKbps(kbps);
System.out.println(formatted);
// long parsed = parseBytes("117.74MB");
// System.out.println(parsed); // 输出: 123456789
}
}