Compare commits

...

10 Commits

Author SHA1 Message Date
6e6eaa14af 222 2025-06-24 09:17:18 +08:00
e61580d330 222 2025-06-23 18:57:22 +08:00
53c10570b6 222 2025-06-23 18:57:07 +08:00
RuoYi
725c7dcea2 添加新群号:191164766 2025-06-20 11:39:20 +08:00
RuoYi
158ccaebe0 优化定时任务包名白名单匹配方式 2025-06-20 11:33:25 +08:00
RuoYi
7b9060af26 优化Excel统计行数值的单元格样式显示 2025-06-19 14:47:49 +08:00
RuoYi
1a2f20e859 升级oshi到最新版本6.8.2 2025-06-18 13:51:41 +08:00
RuoYi
09faecb5d3 升级tomcat到最新版本9.0.106 2025-06-18 13:40:59 +08:00
RuoYi
d46e62a21a 用户头像更换后移除旧头像文件 2025-06-06 14:58:01 +08:00
RuoYi
fa88922637 若依 3.9.0 2025-05-28 09:04:45 +08:00
36 changed files with 3040 additions and 569 deletions

View File

@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.9</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.0</h1>
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.9-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.0-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
@ -92,4 +92,4 @@
## 若依前后端分离交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) 点击按钮入群。
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) 点击按钮入群。

Binary file not shown.

View File

@ -6,14 +6,14 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.8.9</version>
<version>3.9.0</version>
<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依管理系统</description>
<properties>
<ruoyi.version>3.8.9</ruoyi.version>
<ruoyi.version>3.9.0</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
@ -25,13 +25,13 @@
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.7</pagehelper.boot.version>
<fastjson.version>2.0.57</fastjson.version>
<oshi.version>6.8.1</oshi.version>
<oshi.version>6.8.2</oshi.version>
<commons.io.version>2.19.0</commons.io.version>
<poi.version>4.1.2</poi.version>
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<!-- override dependency version -->
<tomcat.version>9.0.105</tomcat.version>
<tomcat.version>9.0.106</tomcat.version>
<logback.version>1.2.13</logback.version>
<spring-security.version>5.7.12</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version>

View File

@ -1,96 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误排除3.0.0中的引用手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 核心模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
<!-- 定时任务-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-quartz</artifactId>
</dependency>
<!-- 代码生成-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-core -->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>1.0.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-extension -->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>1.0.3</version>
</dependency>
<!-- swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误排除3.0.0中的引用手动增加1.6.2版本 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.2</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 核心模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
<!-- 定时任务-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-quartz</artifactId>
</dependency>
<!-- 代码生成-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>

View File

@ -0,0 +1,22 @@
package com.ruoyi.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* 程序注解配置
*
* @author ruoyi
*/
@Configuration
public class BeanRestConfig
{
/**
* 创建RestTemplate Bean
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,17 @@
package com.ruoyi.web.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
/**
* prodController
*
* @author ruoyi
* @date 2025-06-21
*/
@RestController
@RequestMapping("/prod/products")
public class ProductsController extends BaseController
{
}

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
@ -148,7 +147,7 @@ public class CommonController
// 本地资源路径
String localPath = RuoYiConfig.getProfile();
// 数据库资源地址
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
String downloadPath = localPath + FileUtils.stripPrefix(resource);
// 下载名称
String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);

View File

@ -21,6 +21,7 @@ import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
@ -94,7 +95,7 @@ public class SysProfileController extends BaseController
String oldPassword = params.get("oldPassword");
String newPassword = params.get("newPassword");
LoginUser loginUser = getLoginUser();
String userName = loginUser.getUsername();
Long userId = loginUser.getUserId();
String password = loginUser.getPassword();
if (!SecurityUtils.matchesPassword(oldPassword, password))
{
@ -105,7 +106,7 @@ public class SysProfileController extends BaseController
return error("新密码不能与旧密码相同");
}
newPassword = SecurityUtils.encryptPassword(newPassword);
if (userService.resetUserPwd(userName, newPassword) > 0)
if (userService.resetUserPwd(userId, newPassword) > 0)
{
// 更新缓存用户密码&密码最后更新时间
loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate());
@ -126,9 +127,14 @@ public class SysProfileController extends BaseController
if (!file.isEmpty())
{
LoginUser loginUser = getLoginUser();
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true);
if (userService.updateUserAvatar(loginUser.getUserId(), avatar))
{
String oldAvatar = loginUser.getUser().getAvatar();
if (StringUtils.isNotEmpty(oldAvatar))
{
FileUtils.deleteFile(RuoYiConfig.getProfile() + FileUtils.stripPrefix(oldAvatar));
}
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar);
// 更新缓存用户头像

View File

@ -0,0 +1,312 @@
package com.ruoyi.web.controller.tool;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.async.DeferredResult;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
* 斑马订单控制器
*
* @author ruoyi
*/
@Api("斑马订单接口")
@RestController
@RequestMapping("/tool/banma")
@Anonymous
public class BanmaOrderController extends BaseController {
private static final String AUTH_TOKEN = "Bearer e5V8Vlaf9xh5i31xaI300wbdXEE3iLtgip+JXfzZsb7GShP2XCGhoVzTEVxyc8LH";
private static final String API_URL = "https://banma365.cn/api/order/list?recipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
private static final int CONNECTION_TIMEOUT = 900000000;
private static final int READ_TIMEOUT = 900000000;
@Autowired
private RestTemplate restTemplate;
public BanmaOrderController() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(CONNECTION_TIMEOUT);
factory.setReadTimeout(READ_TIMEOUT);
restTemplate = new RestTemplate(factory);
}
/**
* 获取斑马订单列表
*/
@ApiOperation("获取斑马订单列表")
@GetMapping("/orders")
public R<List<Map<String, Object>>> getOrders(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", AUTH_TOKEN);
HttpEntity<String> entity = new HttpEntity<>(headers);
String url = String.format(API_URL, page, size, System.currentTimeMillis());
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody == null) {
return R.ok(Collections.emptyList());
}
Map<String, Object> dataMap = (Map<String, Object>) responseBody.get("data");
if (dataMap == null) {
return R.ok(Collections.emptyList());
}
List<Map<String, Object>> orders = (List<Map<String, Object>>) dataMap.get("list");
if (orders == null) {
return R.ok(Collections.emptyList());
}
List<Map<String, Object>> resultList = new ArrayList<>(orders.size());
for (Map<String, Object> order : orders) {
if (order == null) continue;
Map<String, Object> simplifiedOrder = new HashMap<>();
// 国际运单号和国际运费
String trackingNumber = (String) order.get("internationalTrackingNumber");
simplifiedOrder.put("internationalTrackingNumber", trackingNumber);
simplifiedOrder.put("internationalShippingFee", order.get("internationalShippingFee"));
// 获取物流轨迹信息
if (trackingNumber != null && !trackingNumber.isEmpty()) {
simplifiedOrder.put("trackInfo", getTrackingInfo(trackingNumber));
}
// 处理子订单信息
List<Map<String, Object>> subOrders = (List<Map<String, Object>>) order.get("subOrders");
if (subOrders != null && !subOrders.isEmpty()) {
Map<String, Object> firstSubOrder = subOrders.get(0);
if (firstSubOrder != null) {
simplifiedOrder.put("poTrackingNumber", firstSubOrder.get("poTrackingNumber"));
simplifiedOrder.put("orderedAt", firstSubOrder.get("orderedAt"));
simplifiedOrder.put("timeSinceOrder", firstSubOrder.get("timeSinceOrder"));
}
}
resultList.add(simplifiedOrder);
}
return R.ok(resultList);
} catch (Exception e) {
logger.error("获取斑马订单失败", e);
return R.fail("获取订单失败: " + e.getMessage());
}
}
/**
* 获取物流轨迹信息
*/
private String getTrackingInfo(String trackingNumber) {
try {
if (trackingNumber == null || trackingNumber.isEmpty()) return null;
String url = String.format(TRACKING_URL, trackingNumber);
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody == null) return null;
if (Integer.valueOf(0).equals(responseBody.get("code"))) {
Object dataObj = responseBody.get("data");
if (dataObj == null) return null;
List<Map<String, Object>> trackingData = (List<Map<String, Object>>) dataObj;
if (trackingData != null && !trackingData.isEmpty()) {
Map<String, Object> firstTrack = trackingData.get(0);
if (firstTrack != null) {
return (String) firstTrack.get("track");
}
}
}
} catch (Exception e) {
logger.error("获取物流信息失败: {}", trackingNumber);
}
return null;
}
/**
* 获取物流轨迹信息
*/
@ApiOperation("获取物流轨迹信息")
@GetMapping("/tracking/{trackingNumber}")
public R<String> getTracking(@PathVariable("trackingNumber") String trackingNumber) {
try {
String trackInfo = getTrackingInfo(trackingNumber);
return trackInfo != null ? R.ok(trackInfo) : R.fail("未找到物流信息");
} catch (Exception e) {
return R.fail("获取物流信息失败: " + e.getMessage());
}
}
/**
* 获取所有页的斑马订单
*/
@ApiOperation("获取所有页的斑马订单")
@GetMapping("/orders/all")
public DeferredResult<R<Map<String, Object>>> getAllOrders() {
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(1200000L); // 20分钟超时
// 异步处理请求
Thread thread = new Thread(() -> {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", AUTH_TOKEN);
HttpEntity<String> entity = new HttpEntity<>(headers);
int page = 1;
int size = 20;
if (deferredResult.isSetOrExpired()) return;
String url = String.format(API_URL, page, size, System.currentTimeMillis());
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
Map<String, Object> responseBody = response.getBody();
if (responseBody == null || !responseBody.containsKey("data")) {
if (!deferredResult.isSetOrExpired()) {
deferredResult.setResult(R.fail("API返回数据无效"));
}
return;
}
Map<String, Object> dataMap = (Map<String, Object>) responseBody.get("data");
// 获取总记录数
int totalCount = dataMap.containsKey("total") ? ((Number) dataMap.get("total")).intValue() : 0;
List<Map<String, Object>> orders = (List<Map<String, Object>>) dataMap.get("list");
if (orders == null) {
orders = Collections.emptyList();
}
List<Map<String, Object>> allOrders = new ArrayList<>(orders.size());
for (Map<String, Object> order : orders) {
if (order == null) continue;
Map<String, Object> simplifiedOrder = new HashMap<>();
// 国际运单号和国际运费
String trackingNumber = (String) order.get("internationalTrackingNumber");
simplifiedOrder.put("internationalTrackingNumber", trackingNumber);
simplifiedOrder.put("internationalShippingFee", order.get("internationalShippingFee"));
// 获取物流轨迹信息
if (trackingNumber != null && !trackingNumber.isEmpty()) {
simplifiedOrder.put("trackInfo", getTrackingInfo(trackingNumber));
}
// 处理子订单信息
List<Map<String, Object>> subOrders = (List<Map<String, Object>>) order.get("subOrders");
if (subOrders != null && !subOrders.isEmpty()) {
Map<String, Object> firstSubOrder = subOrders.get(0);
if (firstSubOrder != null) {
simplifiedOrder.put("poTrackingNumber", firstSubOrder.get("poTrackingNumber"));
simplifiedOrder.put("orderedAt", firstSubOrder.get("orderedAt"));
simplifiedOrder.put("timeSinceOrder", firstSubOrder.get("timeSinceOrder"));
}
}
allOrders.add(simplifiedOrder);
}
// 计算预计总页数
int totalPages = (int) Math.ceil((double) totalCount / size);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("orders", allOrders);
resultMap.put("total", totalCount);
resultMap.put("totalPages", totalPages);
resultMap.put("hasMore", orders.size() == size);
resultMap.put("nextPage", 2);
// 设置结果前再次检查状态
if (!deferredResult.isSetOrExpired()) {
deferredResult.setResult(R.ok(resultMap));
}
} catch (Exception e) {
deferredResult.setResult(R.fail("获取所有订单失败: " + e.getMessage()));
}
});
thread.setDaemon(true);
thread.start();
return deferredResult;
}
/**
* 获取下一页斑马订单
*/
@ApiOperation("获取下一页斑马订单")
@GetMapping("/orders/next")
public DeferredResult<R<Map<String, Object>>> getNextPageOrders(@RequestParam(value = "page", defaultValue = "1") Integer page) {
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(120000L); // 2分钟超时
Thread thread = new Thread(() -> {
try {
int size = 20;
// 若已超时或已完成则不处理
if (deferredResult.isSetOrExpired()) return;
R<List<Map<String, Object>>> pageResult = getOrders(page, size);
if (pageResult.getCode() != 200) {
if (!deferredResult.isSetOrExpired()) {
deferredResult.setResult(R.fail(pageResult.getMsg()));
}
return;
}
List<Map<String, Object>> pageData = pageResult.getData();
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("orders", pageData);
resultMap.put("hasMore", pageData != null && pageData.size() == size);
resultMap.put("nextPage", page + 1);
// 设置结果前再次检查状态
if (!deferredResult.isSetOrExpired()) {
deferredResult.setResult(R.ok(resultMap));
}
} catch (Exception e) {
logger.error("获取下一页订单失败", e);
if (!deferredResult.isSetOrExpired()) {
deferredResult.setResult(R.fail("获取订单失败: " + e.getMessage()));
}
}
});
thread.setDaemon(true);
thread.start();
return deferredResult;
}
}

View File

@ -1,5 +1,4 @@
package com.ruoyi.web.controller.tool;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

View File

@ -0,0 +1,407 @@
package com.ruoyi.web.controller.tool;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.downloader.HttpClientDownloader;
import us.codecraft.webmagic.proxy.Proxy;
import us.codecraft.webmagic.proxy.SimpleProxyProvider;
import us.codecraft.webmagic.scheduler.QueueScheduler;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.File;
import java.util.Objects;
/**
* 亚马逊爬虫控制器 - 爬取价格和卖家信息
*
* @author ruoyi
*/
@Api("亚马逊爬虫功能")
@RestController
@RequestMapping("/tool/webmagic")
@Anonymous
public class WebMagicController extends BaseController implements PageProcessor {
private static final Logger logger = LoggerFactory.getLogger(WebMagicController.class);
// 随机数生成器
private final Random random = new Random();
// 站点配置
private final Site site = Site.me()
.setRetryTimes(3)
.setSleepTime(1000 + random.nextInt(2000))
.setTimeOut(15000)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
.addCookie("session-id", "358-1261309-0483141")
.addCookie("session-id-time", "2082787201l")
.addCookie("i18n-prefs", "JPY")
.addCookie("lc-acbjp", "zh_CN")
.addCookie("ubid-acbjp", "357-8224002-9668932");
// 默认代理配置
private static final String DEFAULT_PROXY_HOST = "127.0.0.1";
private static final String DEFAULT_PROXY_PORT = "7890";
private String currentProxyName = "";
private String currentProxyHost = DEFAULT_PROXY_HOST;
private int currentProxyPort = Integer.parseInt(DEFAULT_PROXY_PORT);
// 存储所有代理节点信息
private List<Map<String, Object>> proxyNodes = new ArrayList<>();
// 爬取结果
private Map<String, Object> resultMap = new HashMap<>();
/**
* 初始化加载代理配置
*/
public WebMagicController() {
loadProxyConfig();
}
/**
* 加载代理配置文件
*/
@SuppressWarnings("unchecked")
private void loadProxyConfig() {
try {
File configFile = ResourceUtils.getFile("classpath:test_proxy.yml");
if (configFile.exists()) {
Yaml yaml = new Yaml();
try (InputStream inputStream = new FileInputStream(configFile)) {
Map<String, Object> config = yaml.load(inputStream);
if (config != null && config.containsKey("proxies")) {
List<Map<String, Object>> proxies = (List<Map<String, Object>>) config.get("proxies");
proxyNodes = proxies.stream()
.filter(proxy -> proxy != null && proxy.containsKey("name") && proxy.containsKey("server") && proxy.containsKey("port"))
.collect(Collectors.toList());
}
}
}
} catch (Exception e) {
logger.error("加载代理配置失败", e);
}
}
/**
* 获取所有可用代理节点
*/
@ApiOperation("获取所有可用代理节点")
@GetMapping("/proxies")
public R<List<Map<String, String>>> getProxies() {
List<Map<String, String>> result = proxyNodes.stream()
.map(node -> {
Map<String, String> proxyInfo = new HashMap<>();
proxyInfo.put("name", (String) node.get("name"));
proxyInfo.put("server", (String) node.get("server"));
proxyInfo.put("port", String.valueOf(node.get("port")));
proxyInfo.put("type", (String) node.get("type"));
return proxyInfo;
})
.collect(Collectors.toList());
return R.ok(result);
}
/**
* 设置当前使用的代理节点
*/
@ApiOperation("设置当前使用的代理节点")
@PostMapping("/proxy/set")
public R<Map<String, String>> setCurrentProxy(@RequestParam String proxyName) {
// 查找匹配的代理节点
Map<String, Object> targetProxy = proxyNodes.stream()
.filter(node -> proxyName.equals(node.get("name")))
.findFirst()
.orElse(null);
Map<String, String> result = new HashMap<>();
if (targetProxy != null) {
// 更新当前代理信息
currentProxyName = (String) targetProxy.get("name");
currentProxyHost = (String) targetProxy.get("server");
currentProxyPort = ((Number) targetProxy.get("port")).intValue();
result.put("name", currentProxyName);
result.put("host", currentProxyHost);
result.put("port", String.valueOf(currentProxyPort));
return R.ok(result);
} else {
return R.fail("未找到指定的代理节点: " + proxyName);
}
}
@Override
public void process(Page page) {
try {
Html html = page.getHtml();
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
if (priceSymbol != null && !priceSymbol.isEmpty() && priceWhole != null && !priceWhole.isEmpty()) {
resultMap.put("price", priceSymbol + priceWhole);
}
resultMap.put("seller", html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString());
resultMap.put("asin", html.xpath("//input[@id='ASIN']/@value").toString());
String price = (String) resultMap.get("price");
String seller = (String) resultMap.get("seller");
Object retriesObj = page.getRequest().getExtra("retries");
int retries = retriesObj == null ? 0 : (int) retriesObj;
if ((price == null || price.isEmpty() || seller == null || seller.isEmpty()) && retries < 1) {
String url = page.getUrl().toString();
us.codecraft.webmagic.Request request = new us.codecraft.webmagic.Request(url);
request.putExtra("retries", retries + 1);
page.addTargetRequest(request);
try {
Thread.sleep(1000 + random.nextInt(2000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
logger.error("解析页面失败", e);
}
}
@Override
public Site getSite() {
return site;
}
/**
* 获取配置了代理的下载器
*/
private HttpClientDownloader getProxyDownloader() {
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
clearSystemProxy();
System.setProperty("http.proxyHost", DEFAULT_PROXY_HOST);
System.setProperty("http.proxyPort", DEFAULT_PROXY_PORT);
System.setProperty("https.proxyHost", DEFAULT_PROXY_HOST);
System.setProperty("https.proxyPort", DEFAULT_PROXY_PORT);
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1");
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
new Proxy(DEFAULT_PROXY_HOST, Integer.parseInt(DEFAULT_PROXY_PORT))
));
return httpClientDownloader;
}
private void clearSystemProxy() {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
}
/**
* 构建亚马逊产品URL
*/
private String buildAmazonUrl(String asin) {
return "https://www.amazon.co.jp/dp/" + asin;
}
/**
* 批量爬取亚马逊产品信息
*/
@ApiOperation("批量爬取亚马逊产品信息")
@PostMapping("/batch")
public DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> batchGetAmazonProductInfo(@RequestBody List<String> asinList) {
DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> deferredResult = new DeferredResult<>(500000L);
if (asinList == null || asinList.isEmpty()) {
deferredResult.setResult(ResponseEntity.ok(R.fail("ASIN列表不能为空")));
return deferredResult;
}
CompletableFuture.runAsync(() -> {
List<Map<String, Object>> resultList = new ArrayList<>();
try {
for (String asin : asinList) {
if (asin == null || asin.trim().isEmpty()) {
continue;
}
String url = buildAmazonUrl(asin.trim());
resultMap = new HashMap<>();
resultMap.put("asin", asin);
Spider spider = null;
try {
spider = Spider.create(this)
.addUrl(url)
.setDownloader(getProxyDownloader())
.thread(1);
spider.run();
} finally {
clearSystemProxy();
}
resultList.add(new HashMap<>(resultMap));
}
deferredResult.setResult(ResponseEntity.ok(R.ok(resultList)));
} catch (Exception e) {
clearSystemProxy();
logger.error("批量爬取失败", e);
deferredResult.setResult(ResponseEntity.ok(R.fail("批量爬取失败")));
}
});
return deferredResult;
}
/**
* 测试代理节点延迟
*/
@ApiOperation("测试代理节点延迟")
@PostMapping("/proxy/test")
public DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> testProxyDelay(@RequestBody List<String> proxyNames) {
DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> deferredResult = new DeferredResult<>(600000L);
CompletableFuture.runAsync(() -> {
List<Map<String, Object>> resultList = new ArrayList<>();
try {
for (int i = 0; i < proxyNames.size(); i += 2) {
final int currentIndex = i;
List<CompletableFuture<Map<String, Object>>> futures = new ArrayList<>();
futures.add(CompletableFuture.supplyAsync(() -> testSingleProxy(proxyNames.get(currentIndex))));
if (currentIndex + 1 < proxyNames.size()) {
futures.add(CompletableFuture.supplyAsync(() ->
testSingleProxy(proxyNames.get(currentIndex + 1)))
);
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.get(3, TimeUnit.SECONDS);
} catch (Exception e) {
logger.debug("等待代理测试完成时发生异常", e);
}
for (CompletableFuture<Map<String, Object>> future : futures) {
try {
if (future.isDone() && !future.isCompletedExceptionally()) {
resultList.add(future.join());
} else if (!future.isDone()) {
Map<String, Object> timeoutResult = new HashMap<>();
timeoutResult.put("name", "unknown");
timeoutResult.put("status", "fail");
timeoutResult.put("message", "测试超时");
timeoutResult.put("delay", -1);
resultList.add(timeoutResult);
}
} catch (Exception ignored) {
}
}
}
deferredResult.setResult(ResponseEntity.ok(R.ok(resultList)));
} catch (Exception e) {
logger.error("测试代理延迟失败", e);
deferredResult.setResult(ResponseEntity.ok(R.fail("测试代理延迟失败")));
}
});
return deferredResult;
}
/**
* 测试单个代理节点延迟
*/
private Map<String, Object> testSingleProxy(String proxyName) {
Map<String, Object> result = new HashMap<>();
result.put("name", proxyName);
long startTime = System.currentTimeMillis();
try {
Map<String, Object> targetProxy = proxyNodes.stream()
.filter(node -> proxyName.equals(node.get("name")))
.findFirst()
.orElse(null);
if (targetProxy == null) {
result.put("status", "fail");
result.put("message", "未找到指定的代理节点");
result.put("delay", -1);
return result;
}
String proxyHost = (String) targetProxy.get("server");
int proxyPort = ((Number) targetProxy.get("port")).intValue();
HttpClientDownloader testDownloader = new HttpClientDownloader();
clearSystemProxy();
testDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy(proxyHost, proxyPort)));
Spider.create(new PageProcessor() {
@Override
public void process(Page page) {
}
@Override
public Site getSite() {
return Site.me()
.setRetryTimes(0)
.setSleepTime(100)
.setTimeOut(2000); // 减少超时时间为2秒
}
})
.setDownloader(testDownloader)
.addUrl("http://www.gstatic.com/generate_204")
.thread(1)
.run();
long endTime = System.currentTimeMillis();
result.put("status", "success");
result.put("delay", endTime - startTime);
} catch (Exception e) {
result.put("status", "fail");
result.put("message", "连接超时或失败");
result.put("delay", -1);
} finally {
clearSystemProxy();
}
return result;
}
}

View File

@ -6,9 +6,9 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.1.89:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
password: 123123
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@ -1,129 +1,129 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.8.9
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: localhost
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.9.0
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置
logging:
level:
com.ruoyi: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: localhost
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
# Swagger配置
swagger:
# 是否开启swagger
enabled: true
# 请求前缀
pathMapping: /dev-api
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice,/tool/webmagic
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*

Binary file not shown.

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -13,11 +13,12 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.common.utils.uuid.Seq;
/**
* 文件上传工具类
*
*
* @author ruoyi
*/
public class FileUploadUtils
@ -102,15 +103,35 @@ public class FileUploadUtils
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
return upload(baseDir, file, allowedExtension, false);
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param useCustomNaming 系统自定义文件名
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = extractFilename(file);
String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
@ -118,12 +139,19 @@ public class FileUploadUtils
}
/**
* 编码文件名
* 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀)
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
/**
* 编编码文件名(日期格式目录 + UUID + 后缀)
*/
public static final String uuidFilename(MultipartFile file)
{
return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
@ -216,7 +244,7 @@ public class FileUploadUtils
/**
* 获取文件名的后缀
*
*
* @param file 表单文件
* @return 后缀名
*/

View File

@ -11,13 +11,14 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.apache.commons.io.FilenameUtils;
/**
* 文件处理工具类
@ -103,6 +104,17 @@ public class FileUtils
return FileUploadUtils.getPathFileName(uploadDir, pathName);
}
/**
* 移除路径中的请求前缀片段
*
* @param filePath 文件路径
* @return 移除后的文件路径
*/
public static String stripPrefix(String filePath)
{
return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX);
}
/**
* 删除文件
*

View File

@ -186,11 +186,6 @@ public class ExcelUtil<T>
*/
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
/**
* 数字格式
*/
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
/**
* 实体对象
*/
@ -861,6 +856,7 @@ public class ExcelUtil<T>
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setDataFormat(dataFormat.getFormat("######0.00"));
Font totalFont = wb.createFont();
totalFont.setFontName("Arial");
totalFont.setFontHeightInPoints((short) 10);
@ -1442,7 +1438,7 @@ public class ExcelUtil<T>
{
cell = row.createCell(key);
cell.setCellStyle(styles.get("total"));
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
cell.setCellValue(statistics.get(key));
}
statistics.clear();
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -131,11 +131,11 @@ public class ScheduleUtils
int count = StringUtils.countMatches(packageName, ".");
if (count > 1)
{
return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR);
}
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
String beanPackageName = obj.getClass().getPackage().getName();
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR)
&& !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR);
}
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.9</version>
<version>3.9.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -70,20 +70,20 @@ public interface SysUserMapper
/**
* 修改用户头像
*
* @param userName 用户名
* @param userId 用户ID
* @param avatar 头像地址
* @return 结果
*/
public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar);
public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar);
/**
* 重置用户密码
*
* @param userName 用户名
* @param userId 用户ID
* @param password 密码
* @return 结果
*/
public int resetUserPwd(@Param("userName") String userName, @Param("password") String password);
public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password);
/**
* 通过用户ID删除用户

View File

@ -155,11 +155,11 @@ public interface ISysUserService
/**
* 修改用户头像
*
* @param userName 用户名
* @param userId 用户ID
* @param avatar 头像地址
* @return 结果
*/
public boolean updateUserAvatar(String userName, String avatar);
public boolean updateUserAvatar(Long userId, String avatar);
/**
* 重置用户密码
@ -172,11 +172,11 @@ public interface ISysUserService
/**
* 重置用户密码
*
* @param userName 用户名
* @param userId 用户ID
* @param password 密码
* @return 结果
*/
public int resetUserPwd(String userName, String password);
public int resetUserPwd(Long userId, String password);
/**
* 通过用户ID删除用户

View File

@ -344,14 +344,14 @@ public class SysUserServiceImpl implements ISysUserService
/**
* 修改用户头像
*
* @param userName 用户名
* @param userId 用户ID
* @param avatar 头像地址
* @return 结果
*/
@Override
public boolean updateUserAvatar(String userName, String avatar)
public boolean updateUserAvatar(Long userId, String avatar)
{
return userMapper.updateUserAvatar(userName, avatar) > 0;
return userMapper.updateUserAvatar(userId, avatar) > 0;
}
/**
@ -369,14 +369,14 @@ public class SysUserServiceImpl implements ISysUserService
/**
* 重置用户密码
*
* @param userName 用户名
* @param userId 用户ID
* @param password 密码
* @return 结果
*/
@Override
public int resetUserPwd(String userName, String password)
public int resetUserPwd(Long userId, String password)
{
return userMapper.resetUserPwd(userName, password);
return userMapper.resetUserPwd(userId, password);
}
/**

View File

@ -19,13 +19,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<result property="pwdUpdateDate" column="pwd_update_date" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<association property="dept" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
</resultMap>
<resultMap id="deptResult" type="SysDept">
@ -202,11 +202,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update>
<update id="updateUserAvatar" parameterType="SysUser">
update sys_user set avatar = #{avatar} where user_name = #{userName}
update sys_user set avatar = #{avatar} where user_id = #{userId}
</update>
<update id="resetUserPwd" parameterType="SysUser">
update sys_user set pwd_update_date = sysdate(), password = #{password} where user_name = #{userName}
update sys_user set pwd_update_date = sysdate(), password = #{password} where user_id = #{userId}
</update>
<delete id="deleteUserById" parameterType="Long">

View File

@ -1,72 +1,73 @@
{
"name": "ruoyi",
"version": "3.8.9",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"repository": {
"type": "git",
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
},
"dependencies": {
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.28.1",
"clipboard": "2.0.8",
"core-js": "3.37.1",
"echarts": "5.4.0",
"element-ui": "2.15.14",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"quill": "2.0.2",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"splitpanes": "2.4.1",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "4.1.0",
"compression-webpack-plugin": "6.1.2",
"connect": "3.6.6",
"sass": "1.32.13",
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-template-compiler": "2.6.12"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}
{
"name": "ruoyi",
"version": "3.9.0",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"repository": {
"type": "git",
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
},
"dependencies": {
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.28.1",
"clipboard": "2.0.8",
"core-js": "^3.43.0",
"echarts": "5.4.0",
"element-ui": "2.15.14",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"quill": "2.0.2",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"splitpanes": "2.4.1",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "4.1.0",
"compression-webpack-plugin": "6.1.2",
"connect": "3.6.6",
"sass": "1.32.13",
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-template-compiler": "2.6.12"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

View File

@ -1,208 +1,208 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #7171C6;
z-index: 1000;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
-ms-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #7171C6;
z-index: 1000;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
-ms-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询prod列表
export function listProducts(query) {
return request({
url: '/prod/products/list',
method: 'get',
params: query
})
}
// 查询prod详细
export function getProducts(productId) {
return request({
url: '/prod/products/' + productId,
method: 'get'
})
}
// 新增prod
export function addProducts(data) {
return request({
url: '/prod/products',
method: 'post',
data: data
})
}
// 修改prod
export function updateProducts(data) {
return request({
url: '/prod/products',
method: 'put',
data: data
})
}
// 删除prod
export function delProducts(productId) {
return request({
url: '/prod/products/' + productId,
method: 'delete'
})
}

View File

@ -87,6 +87,22 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
{
path: '/banma',
component: Layout,
hidden: false,
redirect: 'noredirect',
name: 'Banma',
meta: { title: '斑马订单', icon: 'shopping' },
children: [
{
path: 'orders',
component: () => import('@/views/banma/orders/index'),
name: 'BanmaOrders',
meta: { title: '订单管理', icon: 'list' }
}
]
}
]

100
ruoyi-ui/src/utils/excel.js Normal file
View File

@ -0,0 +1,100 @@
/**
* Excel工具类 - 用于导入导出Excel文件
*/
import axios from 'axios'
import { saveAs } from 'file-saver'
import * as XLSX from 'xlsx'
import { getToken } from '@/utils/auth'
/**
* 解析Excel文件
* @param {String} url - Excel文件URL
* @returns {Promise<Array>} - 解析后的数据数组
*/
export function parseExcel(url) {
return new Promise((resolve, reject) => {
axios({
method: 'get',
url: process.env.VUE_APP_BASE_API + url,
responseType: 'arraybuffer',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then(response => {
try {
// 使用xlsx库解析Excel数据
const data = new Uint8Array(response.data)
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
resolve(jsonData)
} catch (error) {
reject('解析Excel失败: ' + error.message)
}
}).catch(error => {
reject('下载Excel失败: ' + error.message)
})
})
}
/**
* 从本地文件直接读取Excel数据
* @param {File} file - 上传的Excel文件对象
* @returns {Promise<Array>} - 解析后的数据数组
*/
export function parseExcelFile(file) {
return new Promise((resolve, reject) => {
try {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
resolve(jsonData);
} catch (error) {
reject('解析Excel文件失败: ' + error.message);
}
};
reader.onerror = (error) => reject('读取文件失败: ' + error);
reader.readAsArrayBuffer(file);
} catch (error) {
reject('处理文件失败: ' + error.message);
}
});
}
/**
* 导出数据到Excel文件
* @param {Array} data - 要导出的数据数组
* @param {String} filename - 文件名(不含扩展名)
* @param {String} ext - 文件扩展名默认为.xlsx
*/
export function exportExcel(data, filename, ext = '.xlsx') {
const worksheet = XLSX.utils.aoa_to_sheet(data)
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
// 确定文件类型
let bookType = 'xlsx';
let mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
// 根据扩展名确定文件类型
if (ext === '.xls') {
bookType = 'xls';
mimeType = 'application/vnd.ms-excel';
} else if (ext === '.et') {
// WPS表格格式实际上仍使用xlsx格式但改变扩展名
bookType = 'xlsx';
mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
// 生成Excel文件并下载
const excelBuffer = XLSX.write(workbook, { bookType: bookType, type: 'array' })
const blob = new Blob([excelBuffer], { type: mimeType })
// 确保扩展名正确
const fileExt = ext.startsWith('.') ? ext : `.${ext}`;
saveAs(blob, filename + fileExt)
}

View File

@ -0,0 +1,390 @@
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="clearfix">
<span class="card-title">斑马订单数据</span>
</div>
</template>
<el-row :gutter="20" class="mb8">
<el-col :span="4">
<el-button
type="primary"
plain
icon="el-icon-data-analysis"
size="mini"
@click="loadData"
:loading="loading"
class="action-button"
>获取数据</el-button>
</el-col>
<el-col :span="4" v-if="orderData.length > 0">
<el-button
type="success"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
class="action-button"
>导出数据</el-button>
</el-col>
<el-col :span="16">
<div class="notice-box" v-if="orderData.length > 0">
已加载 <span class="count-number">{{orderData.length}}</span> 条订单数据
<span v-if="loading" class="loading-text">加载中...</span>
<span v-else-if="!hasMore" class="complete-text">已加载完成</span>
</div>
</el-col>
</el-row>
<!-- 加载进度条 -->
<el-row v-if="loading">
<el-col :span="24">
<div class="progress-container">
<el-progress
:percentage="loadProgress.percentage"
:format="progressFormat"
status="primary"
:stroke-width="12"
class="load-progress"
></el-progress>
</div>
</el-col>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="orderData" border style="width: 100%">
<el-table-column label="采购单号" align="center" prop="poTrackingNumber" min-width="15%" header-align="center" />
<el-table-column label="客户下单时间" align="center" prop="orderedAt" min-width="15%" header-align="center" />
<el-table-column label="下单距今时间" align="center" prop="timeSinceOrder" min-width="10%" header-align="center" />
<el-table-column label="国际运单号" align="center" prop="internationalTrackingNumber" min-width="15%" header-align="center" />
<el-table-column label="国际运费" align="center" prop="internationalShippingFee" min-width="10%" header-align="center" />
<el-table-column label="物流状态" align="center" prop="trackInfo" min-width="20%" header-align="center">
<template #default="scope">
<el-tag v-if="scope.row.trackInfo" type="success">{{ scope.row.trackInfo }}</el-tag>
<span v-else>暂无物流信息</span>
</template>
</el-table-column>
</el-table>
<!-- 无更多数据提示 -->
<div v-if="orderData.length > 0 && !hasMore && !loading" class="no-more-data">
已加载全部数据
</div>
</el-card>
</div>
</template>
<script>
import request from '@/utils/request'
import { parseTime } from '@/utils/ruoyi'
import { saveAs } from 'file-saver'
import * as XLSX from 'xlsx'
export default {
name: "BanmaOrders",
data() {
return {
//
loading: false,
//
orderData: [],
//
hasMore: false,
//
nextPage: 1,
//
totalPages: 1,
//
currentPage: 0,
//
totalRecords: 0,
//
loadProgress: {
current: 0,
total: 100,
percentage: 0
}
};
},
created() {
//
this.loadFromCache();
},
methods: {
/** 从本地缓存加载数据 */
loadFromCache() {
try {
const cachedData = localStorage.getItem('banma_orders_data');
if (cachedData) {
const parsedData = JSON.parse(cachedData);
this.orderData = parsedData.orderData || [];
this.totalRecords = parsedData.totalRecords || 0;
this.totalPages = parsedData.totalPages || 1;
this.hasMore = parsedData.hasMore || false;
this.nextPage = parsedData.nextPage || 1;
this.currentPage = parsedData.currentPage || 0;
}
} catch (error) {
console.error('加载缓存数据失败:', error);
}
},
/** 保存数据到本地缓存 */
saveToCache() {
try {
if (this.orderData.length > 0) {
const cacheData = {
orderData: this.orderData,
totalRecords: this.totalRecords,
totalPages: this.totalPages,
hasMore: this.hasMore,
nextPage: this.nextPage,
currentPage: this.currentPage,
timestamp: new Date().getTime()
};
localStorage.setItem('banma_orders_data', JSON.stringify(cacheData));
}
} catch (error) {
this.$message.warning('保存数据到本地缓存失败');
}
},
/** 清空缓存数据 */
clearCache() {
localStorage.removeItem('banma_orders_data');
console.log('已清空本地缓存数据');
},
/** 获取订单列表 */
loadData() {
this.loading = true;
this.orderData = []; //
this.nextPage = 1;
this.currentPage = 0;
this.totalPages = 1;
this.totalRecords = 0;
this.loadProgress = {
current: 0,
total: 100,
percentage: 0
};
//
this.clearCache();
//
this.loadFirstPage();
},
/** 加载第一页数据 */
loadFirstPage() {
request({
url: '/tool/banma/orders/all',
method: 'get',
timeout: 920000 // 2
}).then(response => {
if (response.code === 200) {
this.orderData = response.data.orders;
this.hasMore = response.data.hasMore;
this.nextPage = response.data.nextPage;
this.currentPage = 1;
this.totalRecords = response.data.total || 0;
this.totalPages = response.data.totalPages || 1;
this.updateProgress();
this.saveToCache();
//
if (this.hasMore) {
this.autoLoadNextPage();
} else {
this.loading = false;
}
} else {
this.loading = false;
this.$message.error(response.msg || "获取订单失败");
}
}).catch(error => {
this.loading = false;
this.$message.error("获取订单失败: " + (error.message || error));
});
},
/** 自动加载下一页 */
autoLoadNextPage() {
if (!this.hasMore || !this.loading) return;
//
setTimeout(() => {
request({
url: '/tool/banma/orders/next',
method: 'get',
params: { page: this.nextPage },
timeout: 920000 // 2
}).then(response => {
if (response.code === 200) {
//
this.orderData = [...this.orderData, ...response.data.orders];
this.hasMore = response.data.hasMore;
this.nextPage = response.data.nextPage;
this.currentPage++;
//
this.updateProgress();
//
this.saveToCache();
//
const progress = Math.round((this.orderData.length / this.totalRecords) * 100);
if (progress % 25 === 0 || progress > 90) { // 25%
this.$message.info(`已加载 ${progress}%,共${this.orderData.length}/${this.totalRecords}`);
}
if (this.hasMore) {
this.autoLoadNextPage();
} else {
this.loading = false;
this.$message.success("所有数据加载完成");
}
} else {
this.loading = false;
this.$message.error(response.msg || "获取更多订单失败");
}
}).catch(error => {
if (error.message && error.message.includes('timeout')) {
this.$message.warning(`${this.nextPage}页请求超时,将尝试继续下一页`);
this.nextPage++; //
this.currentPage++;
this.updateProgress();
this.autoLoadNextPage(); //
} else {
this.loading = false;
this.$message.error("获取更多订单失败: " + (error.message || error));
}
});
}, 300); //
},
/** 更新进度条 */
updateProgress() {
let percentage;
if (this.totalRecords > 0) {
percentage = Math.min(Math.round((this.orderData.length / this.totalRecords) * 100), 99);
} else {
percentage = Math.min(Math.round((this.currentPage / this.totalPages) * 100), 99);
}
this.loadProgress = {
current: this.currentPage,
total: this.totalPages,
percentage: percentage
};
},
/** 进度条格式化 */
progressFormat(percentage) {
return `已加载 ${this.currentPage}/${this.totalPages} 页,${this.orderData.length}/${this.totalRecords} 条数据`;
},
/** 导出Excel */
handleExport() {
if (!this.orderData.length) {
this.$message.warning("没有数据可导出");
return;
}
//
const mainOrderData = [
['采购单号', '客户下单时间', '下单距今时间', '国际运单号', '国际运费', '物流状态']
];
//
this.orderData.forEach(item => {
mainOrderData.push([
item.poTrackingNumber || '',
item.orderedAt || '',
item.timeSinceOrder || '',
item.internationalTrackingNumber || '',
item.internationalShippingFee || '',
item.trackInfo || '暂无物流信息'
]);
});
// 簿
const wb = XLSX.utils.book_new();
const wsMain = XLSX.utils.aoa_to_sheet(mainOrderData);
XLSX.utils.book_append_sheet(wb, wsMain, '订单数据');
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
saveAs(blob, `斑马订单数据_${parseTime(new Date())}.xlsx`);
}
}
};
</script>
<style scoped>
.mb8 {
margin-bottom: 8px;
}
.progress-container {
margin: 10px 0;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.action-button {
width: 100%;
margin-bottom: 5px;
}
.load-progress {
margin-top: 5px;
}
.el-table {
margin-top: 15px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
.el-table :deep(th) {
background-color: #f5f7fa !important;
color: #303133;
font-weight: bold;
padding: 10px 0;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.box-card {
margin-bottom: 20px;
border-radius: 6px;
}
.notice-box {
margin-bottom: 10px;
font-size: 14px;
color: #606266;
}
.count-number {
font-weight: bold;
color: #409EFF;
}
.loading-text {
color: #E6A23C;
font-style: italic;
}
.complete-text {
color: #67C23A;
font-style: italic;
}
.no-more-data {
text-align: center;
color: #909399;
margin: 20px 0;
font-size: 14px;
}
</style>

View File

@ -87,7 +87,7 @@
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s>
<s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s>
<s> 满287842588 </s> <s> 满187944233 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329" target="_blank">228578329</a>
<s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766" target="_blank">191164766</a>
</p>
<p>
<i class="el-icon-chat-dot-round"></i> 微信<a
@ -111,6 +111,42 @@
<span>更新日志</span>
</div>
<el-collapse accordion>
<el-collapse-item title="v3.9.0 - 2025-05-28">
<ol>
<li>优化菜单搜索查询页</li>
<li>导航栏显示昵称&设置</li>
<li>菜单管理新增路由名称</li>
<li>添加底部版权信息&开关</li>
<li>分配角色禁用不允许勾选</li>
<li>Excel导入导出支持多图片</li>
<li>添加页签图标显示开关功能</li>
<li>上传组件新增拖动排序属性</li>
<li>显隐列组件支持全选/全不选</li>
<li>初始密码支持自定义修改策略</li>
<li>账号密码支持自定义更新周期</li>
<li>代码生成列表支持按时间排序</li>
<li>支持富文本复制粘贴图片上传至url</li>
<li>支持文件&图片组件自定义地址&参数</li>
<li>升级tomcat到最新版本9.0.105</li>
<li>升级oshi到最新版本6.8.1</li>
<li>升级fastjson到最新版2.0.57</li>
<li>升级commons.io到最新版本2.19.0</li>
<li>package.json移除runjs依赖</li>
<li>package.json移除eslint依赖</li>
<li>package.json移除vue-meta依赖</li>
<li>修复代码生成主子表校验必填失效问题</li>
<li>优化前端树结构性能问题</li>
<li>优化前端处理路由函数代码</li>
<li>优化文件上传组件新增类型</li>
<li>优化顶部菜单搜索栏为多层级显示</li>
<li>优化文件&图片上传组件新增disabled属性</li>
<li>优化空指针异常时无法获取错误信息问题</li>
<li>优化定时任务字符包含多个括号导致数据错误</li>
<li>优化登录&注册页表头使用VUE_APP_TITLE配置值</li>
<li>优化导出Excel日期格式双击离开后与设定的格式不一致问题</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.8.9 - 2024-12-30">
<ol>
<li>用户管理支持分栏拖动</li>
@ -1023,7 +1059,7 @@ export default {
data() {
return {
//
version: "3.8.9"
version: "3.9.0"
}
},
methods: {

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
const baseUrl = 'http://localhost:8080' // 后端接口
const baseUrl = 'http://192.168.1.89:8080' // 后端接口
const port = process.env.port || process.env.npm_config_port || 80 // 端口