feat: 账单和发票补充
This commit is contained in:
8
sql/upgrade/1.0.17/upgrade.sql
Normal file
8
sql/upgrade/1.0.17/upgrade.sql
Normal 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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user