src/main/java/com/ruoyi/RuoYiApplication.java
@@ -3,12 +3,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.scheduling.annotation.EnableScheduling; /** * å¯å¨ç¨åº * * @author ruoyi */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) @EnableScheduling public class RuoYiApplication { public static void main(String[] args) src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
@@ -25,6 +25,8 @@ IGNORE_TABLES.add("sys_logininfor"); IGNORE_TABLES.add("sys_post"); IGNORE_TABLES.add("sys_user_post"); IGNORE_TABLES.add("sales_ledger_file"); IGNORE_TABLES.add("temp_file"); } } src/main/java/com/ruoyi/other/controller/TempFileController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package com.ruoyi.other.controller; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.other.service.TempFileService; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/file") @AllArgsConstructor public class TempFileController { private TempFileService tempFileService; @PostMapping("/upload") public AjaxResult uploadFile(MultipartFile file) { try { return AjaxResult.success(tempFileService.uploadFile(file)); }catch (Exception e) { return AjaxResult.error(e.getMessage()); } } } src/main/java/com/ruoyi/other/mapper/TempFileMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,8 @@ package com.ruoyi.other.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.other.pojo.TempFile; public interface TempFileMapper extends BaseMapper<TempFile> { } src/main/java/com/ruoyi/other/pojo/TempFile.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,15 @@ package com.ruoyi.other.pojo; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("temp_file") public class TempFile { private String tempId; // ä¸´æ¶æä»¶IDï¼UUIDï¼ private String originalName; // åå§æä»¶å private String tempPath; // 临æ¶åå¨è·¯å¾ private LocalDateTime expireTime; // è¿ææ¶é´ } src/main/java/com/ruoyi/other/service/TempFileService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,10 @@ package com.ruoyi.other.service; import com.ruoyi.other.pojo.TempFile; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; public interface TempFileService { TempFile uploadFile(MultipartFile file) throws IOException; } src/main/java/com/ruoyi/other/service/impl/TempFileServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,80 @@ package com.ruoyi.other.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.other.mapper.TempFileMapper; import com.ruoyi.other.pojo.TempFile; import com.ruoyi.other.service.TempFileService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @Service @Slf4j public class TempFileServiceImpl extends ServiceImpl<TempFileMapper, TempFile> implements TempFileService { @Autowired private TempFileMapper tempFileMapper; @Value("${file.temp-dir}") private String tempDir; // ä¸ä¼ å°ä¸´æ¶ç®å½ @Override public TempFile uploadFile(MultipartFile file) throws IOException { // 1. çæä¸´æ¶æä»¶IDåè·¯å¾ String tempId = UUID.randomUUID().toString(); Path tempFilePath = Paths.get(tempDir, tempId + "_" + file.getOriginalFilename()); // 2. ç¡®ä¿ç®å½åå¨ Path parentDir = tempFilePath.getParent(); if (parentDir != null) { Files.createDirectories(parentDir); // éå½å建ç®å½ } // 3. ä¿åæä»¶å°ä¸´æ¶ç®å½ file.transferTo(tempFilePath.toFile()); // 4. ä¿åä¸´æ¶æä»¶è®°å½ TempFile tempFileRecord = new TempFile(); tempFileRecord.setTempId(tempId); tempFileRecord.setOriginalName(file.getOriginalFilename()); tempFileRecord.setTempPath(tempFilePath.toString()); tempFileRecord.setExpireTime(LocalDateTime.now().plusHours(2)); // 2å°æ¶åè¿æ tempFileMapper.insert(tempFileRecord); return tempFileRecord; } @Scheduled(cron = "0 0 3 * * ?") // æ¯å¤©åæ¨3ç¹æ§è¡ public void cleanupExpiredTempFiles() { LambdaQueryWrapper<TempFile> wrapper = new LambdaQueryWrapper<>(); wrapper.lt(TempFile::getExpireTime, LocalDateTime.now()); // expireTime < å½åæ¶é´ List<TempFile> expiredFiles = tempFileMapper.selectList(wrapper); for (TempFile file : expiredFiles) { try { // å é¤ç©çæä»¶ Files.deleteIfExists(Paths.get(file.getTempPath())); // å 餿°æ®åºè®°å½ tempFileMapper.deleteById(file); log.info("å·²æ¸ çè¿æä¸´æ¶æä»¶: {}", file.getTempPath()); } catch (IOException e) { log.error("å é¤æä»¶å¤±è´¥: {}", file.getTempPath(), e); // å¯éæ©è®°å½å¤±è´¥æ¥å¿æéè¯ } } log.info("è¿æä¸´æ¶æä»¶æ¸ ç宿ï¼å ±æ¸ ç {} 个æä»¶", expiredFiles.size()); } } src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -1,6 +1,6 @@ package com.ruoyi.sales.controller; import java.util.List; import java.util.*; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.utils.poi.ExcelUtil; @@ -8,12 +8,7 @@ import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.service.ISalesLedgerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import com.ruoyi.framework.aspectj.lang.annotation.Log; import com.ruoyi.framework.aspectj.lang.enums.BusinessType; import com.ruoyi.framework.web.controller.BaseController; @@ -22,14 +17,13 @@ /** * éå®å°è´¦Controller * * * @author ruoyi * @date 2025-05-08 */ @RestController @RequestMapping("/sales/ledger") public class SalesLedgerController extends BaseController { public class SalesLedgerController extends BaseController { @Autowired private ISalesLedgerService salesLedgerService; @@ -37,8 +31,7 @@ * æ¥è¯¢éå®å°è´¦å表 */ @GetMapping("/list") public TableDataInfo list(SalesLedgerDto salesLedgerDto) { public TableDataInfo list(SalesLedgerDto salesLedgerDto) { startPage(); List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedgerDto); return getDataTable(list); @@ -48,8 +41,8 @@ * æ¥è¯¢éå®å°è´¦å产åç¶åå表 */ @GetMapping("/getSalesLedgerWithProducts") public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto){ return salesLedgerService.getSalesLedgerWithProducts(salesLedgerDto); public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto) { return salesLedgerService.getSalesLedgerWithProducts(salesLedgerDto); } /** @@ -57,8 +50,7 @@ */ @Log(title = "éå®å°è´¦", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, SalesLedgerDto salesLedgerDto) { public void export(HttpServletResponse response, SalesLedgerDto salesLedgerDto) { List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedgerDto); ExcelUtil<SalesLedger> util = new ExcelUtil<SalesLedger>(SalesLedger.class); util.exportExcel(response, list, "éå®å°è´¦æ°æ®"); @@ -68,9 +60,8 @@ * æ°å¢ä¿®æ¹éå®å°è´¦ */ @Log(title = "éå®å°è´¦", businessType = BusinessType.INSERT) @PostMapping ("/addOrUpdateSalesLedger") public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto) { @PostMapping("/addOrUpdateSalesLedger") public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto) { return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedgerDto)); } @@ -78,9 +69,8 @@ * å é¤éå®å°è´¦ */ @Log(title = "éå®å°è´¦", businessType = BusinessType.DELETE) @DeleteMapping("/delLedger") public AjaxResult remove(@RequestBody Long[] ids) { @DeleteMapping("/delLedger") public AjaxResult remove(@RequestBody Long[] ids) { if (ids == null || ids.length == 0) { return AjaxResult.error("è¯·ä¼ å ¥è¦å é¤çID"); } src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -23,4 +23,6 @@ private String attachmentMaterials; private Boolean hasChildren = false; private List<SalesLedgerProduct> productData; private List<String> tempFileIds; } src/main/java/com/ruoyi/sales/mapper/SalesLedgerFileMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,8 @@ package com.ruoyi.sales.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.sales.pojo.SalesLedgerFile; public interface SalesLedgerFileMapper extends BaseMapper<SalesLedgerFile> { } src/main/java/com/ruoyi/sales/pojo/SalesLedgerFile.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,31 @@ package com.ruoyi.sales.pojo; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("sales_ledger_file") public class SalesLedgerFile { @TableId(type = IdType.AUTO) private Long id; /** éå®å°è´¦ID */ private Long ledgerId; /** æä»¶åç§° */ private String fileName; /** æä»¶è·¯å¾ */ private String filePath; /** å建æ¶é´ */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** æ´æ°æ¶é´ */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; } src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -8,22 +8,33 @@ import com.ruoyi.basic.pojo.Customer; import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.other.mapper.TempFileMapper; import com.ruoyi.other.pojo.TempFile; import com.ruoyi.sales.dto.SalesLedgerDto; import com.ruoyi.sales.mapper.SalesLedgerFileMapper; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerFile; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.sales.service.ISalesLedgerService; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.nio.file.*; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; @@ -37,14 +48,22 @@ * @date 2025-05-08 */ @Service @AllArgsConstructor @RequiredArgsConstructor @Slf4j public class SalesLedgerServiceImpl extends ServiceImpl<SalesLedgerMapper, SalesLedger> implements ISalesLedgerService { private SalesLedgerMapper salesLedgerMapper; private final SalesLedgerMapper salesLedgerMapper; private CustomerMapper customerMapper; private final CustomerMapper customerMapper; private SalesLedgerProductMapper salesLedgerProductMapper; private final SalesLedgerProductMapper salesLedgerProductMapper; private final SalesLedgerFileMapper salesLedgerFileMapper; private final TempFileMapper tempFileMapper; @Value("${file.upload-dir}") private String uploadDir; private static final String LOCK_PREFIX = "contract_no_lock:"; private static final long LOCK_WAIT_TIMEOUT = 10; // éçå¾ è¶ æ¶æ¶é´ï¼ç§ï¼ @@ -102,46 +121,126 @@ return salesLedgerMapper.deleteBatchIds(idList); } @Override @Transactional(rollbackFor = Exception.class) public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) { // 1. æ ¡éªå®¢æ·ä¿¡æ¯ Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId()); if (customer == null) { throw new BaseException("客æ·ä¸åå¨"); try { // 1. æ ¡éªå®¢æ·ä¿¡æ¯ Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId()); if (customer == null) { throw new BaseException("客æ·ä¸åå¨"); } // 2. DTO转Entity SalesLedger salesLedger = convertToEntity(salesLedgerDto); salesLedger.setCustomerName(customer.getCustomerName()); salesLedger.setTenantId(customer.getTenantId()); // 3. æ°å¢ææ´æ°ä¸»è¡¨ if (salesLedger.getId() == null) { String contractNo = generateSalesContractNo(); salesLedger.setSalesContractNo(contractNo); salesLedgerMapper.insert(salesLedger); } else { salesLedgerMapper.updateById(salesLedger); } // 4. å¤çåè¡¨æ°æ® List<SalesLedgerProduct> productList = salesLedgerDto.getProductData(); if (productList != null && !productList.isEmpty()) { handleSalesLedgerProducts(salesLedger.getId(), productList); updateMainContractAmount( salesLedger.getId(), productList, SalesLedgerProduct::getTaxInclusiveTotalPrice, salesLedgerMapper, SalesLedger.class ); } // 5. è¿ç§»ä¸´æ¶æä»¶å°æ£å¼ç®å½ if (salesLedgerDto.getTempFileIds() != null && !salesLedgerDto.getTempFileIds().isEmpty()) { migrateTempFilesToFormal(salesLedger.getId(), salesLedgerDto.getTempFileIds()); } return 1; } catch (IOException e) { throw new BaseException("æä»¶è¿ç§»å¤±è´¥: " + e.getMessage()); } // 2. DTO转Entity SalesLedger salesLedger = convertToEntity(salesLedgerDto); salesLedger.setCustomerName(customer.getCustomerName()); salesLedger.setTenantId(customer.getTenantId()); // 3. æ°å¢ææ´æ°ä¸»è¡¨ if (salesLedger.getId() == null) { // çæååç¼å· String contractNo = generateSalesContractNo(); salesLedger.setSalesContractNo(contractNo); salesLedgerMapper.insert(salesLedger); } else { salesLedgerMapper.updateById(salesLedger); } // 4. å¤çåè¡¨æ°æ® List<SalesLedgerProduct> productList = salesLedgerDto.getProductData(); if (productList != null && !productList.isEmpty()) { handleSalesLedgerProducts(salesLedger.getId(), productList); // â è°ç¨éç¨æ¹æ³æ´æ°ä¸»è¡¨éé¢ updateMainContractAmount( salesLedger.getId(), productList, SalesLedgerProduct::getTaxInclusiveTotalPrice, salesLedgerMapper, SalesLedger.class ); } return 1; // æä½æåè¿å1 } // æä»¶è¿ç§»æ¹æ³ /** * å°ä¸´æ¶æä»¶è¿ç§»å°æ£å¼ç®å½ * * @param businessId ä¸å¡IDï¼éå®å°è´¦IDï¼ * @param tempFileIds ä¸´æ¶æä»¶IDå表 * @throws IOException æä»¶æä½å¼å¸¸ */ private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException { if (CollectionUtils.isEmpty(tempFileIds)) { return; } // æå»ºæ£å¼ç®å½è·¯å¾ï¼æä¸å¡ç±»å忥æåç»ï¼ String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); Path formalDirPath = Paths.get(formalDir); // ç¡®ä¿æ£å¼ç®å½åå¨ï¼éå½åå»ºï¼ if (!Files.exists(formalDirPath)) { Files.createDirectories(formalDirPath); } for (String tempFileId : tempFileIds) { // æ¥è¯¢ä¸´æ¶æä»¶è®°å½ TempFile tempFile = tempFileMapper.selectById(tempFileId); if (tempFile == null) { throw new FileNotFoundException("ä¸´æ¶æä»¶ä¸åå¨: " + tempFileId); } // æå»ºæ£å¼æä»¶åï¼å å«ä¸å¡IDåæ¶é´æ³ï¼é¿å å²çªï¼ String originalFilename = tempFile.getOriginalName(); String fileExtension = FilenameUtils.getExtension(originalFilename); String formalFilename = businessId + "_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + (StringUtils.hasText(fileExtension) ? "." + fileExtension : ""); Path formalFilePath = formalDirPath.resolve(formalFilename); try { // æ§è¡æä»¶è¿ç§»ï¼ä½¿ç¨ååæä½ç¡®ä¿å®å ¨æ§ï¼ Files.move( Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE ); log.info("æä»¶è¿ç§»æå: {} -> {}", tempFile.getTempPath(), formalFilePath); // æ´æ°æä»¶è®°å½ï¼å ³èå°ä¸å¡IDï¼ SalesLedgerFile fileRecord = new SalesLedgerFile(); fileRecord.setLedgerId(businessId); fileRecord.setFileName(originalFilename); fileRecord.setFilePath(formalFilePath.toString()); fileRecord.setCreateTime(LocalDateTime.now()); salesLedgerFileMapper.insert(fileRecord); // å é¤ä¸´æ¶æä»¶è®°å½ tempFileMapper.deleteById(tempFile); log.info("æä»¶è¿ç§»æå: {} -> {}", tempFile.getTempPath(), formalFilePath); } catch (IOException e) { log.error("æä»¶è¿ç§»å¤±è´¥: {}", tempFile.getTempPath(), e); // å¯éæ©åæ»äºå¡æè®°å½å¤±è´¥æä»¶ throw new IOException("æä»¶è¿ç§»å¼å¸¸", e); } } } private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) { // æIDåç»ï¼åºåæ°å¢åæ´æ°çè®°å½ Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream() src/main/resources/application-druid.yml
@@ -6,7 +6,8 @@ druid: # ä¸»åºæ°æ®æº master: url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 url: jdbc:mysql://114.132.189.42:9004/product-inventory-management?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 # ä»åºæ°æ®æº @@ -59,3 +60,7 @@ wall: config: multi-statement-allow: true file: temp-dir: D:/ruoyi/temp/uploads # 临æ¶ç®å½ upload-dir: D:/ruoyi/prod/uploads # æ£å¼ç®å½