接上篇,公司里测试需要有一些 AI 相关产出和落地情况,然后测试自己开始做工具(造轮子),经过 多个工作日空闲时间的努力,写出了一个基于 pdf、图片的测试用例生成(按概率随机)工具 V1.0。以下是基于后端业务实现,由于已更新到 V1.1,本片代码基于 V1.1 实现。
前端交互优化:
业务接口:
server:
port: 8028
shared:
resource:
capacity: 500
spring:
application:
name: xlxz
web:
resources:
static-locations: classpath:/templates/
add-mappings: true
datasource:
# 添加默认数据源配置
jdbc-url: jdbc:mysql://XXXX:3306/web_database?useSSL=false&serverTimezone=UTC
username: XXXX
password: XXXX
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 20000
# Thymeleaf配置
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
mode: HTML
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
# 日志配置
logging:
level:
com:
community:
sqlapp: DEBUG
org:
springframework:
jdbc:
core: DEBUG
# 千问API配置 (请替换为实际的API密钥)
qianwen:
api:
url: https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
key: (自己申请)
model: qwen-turbo
qianwenvl:
vlapi:
vlurl: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
qianwenlongurl: https://dashscope.aliyuncs.com/compatible-mode/v1/files
qwenlongurl: https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
vlkey: (自己申请)
vlmodel: qwen-vl-plus
# MyBatis配置
mybatis:
mapper-locations: classpath:mapping/*.xml
type-aliases-package: com.community.sqlapp.entity
#腾讯COS对象存储配置
tencent:
cos:
secretid: (自己申请)
secretkey: (自己申请)
region: (自己申请)
bucketname: (自己申请) # 格式:<bucketName>-<appId>
cosurl: https://<bucketName>-<appId>.cos.region.myqcloud.com
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.microDynamic</groupId>
<artifactId>microDynamic</artifactId>
<!--<version>0.0.1-SNAPSHOT</version>-->
<name>microDynamic</name>
<description>microDynamic</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
</dependency>
<!--xmind解析 -->
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version> <!-- 确保版本 >= 2.11.0 -->
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version> <!-- 确保版本 >= 2.11.0 -->
</dependency>
<!--xmind解析poi -->
<!--xpath不加这个依赖会报错-->
<dependency>
<groupId>com.github.eljah</groupId>
<artifactId>xmindjbehaveplugin</artifactId>
<version>0.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.54</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.openai/openai-java -->
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>2.12.0</version>
</dependency>
<!-- JSON解析 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- XML处理 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- ZIP压缩 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--文件操作依赖包-->
<dependency>
<groupId>net.sourceforge.javacsv</groupId>
<artifactId>javacsv</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.8</version> <!-- 你也可以使用 4.5.13 或 4.5.14 等新版本 -->
</dependency>
<!-- 读写Excel文件依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- Spark Core -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_${scala.binary.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<!--工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version> <!-- 请根据实际情况选择合适的版本 -->
</dependency>
</dependencies>
<build>
<finalName>microDynamic</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.txt</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<artifactId> maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.testlink.Excel2XMLUI</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class QwenHttpClientUtil {
// 从配置文件读取key、apiurl等信息,避免硬编码
@Value("${qianwenvl.vlapi.vlkey}")
private String dashscopeApiKey;
@Value("${qianwenvl.vlapi.vlurl}")
private String dashscopeApiUrl;
@Value("${qianwenvl.vlapi.qwenlongurl}")
private String dashscopeqwenlongurl;
/**
* 千问vl大模型
* @param jsonBody
* @return
* @throws IOException
*/
public String sendPostRequest(String jsonBody) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(dashscopeApiUrl);
// 设置请求头
httpPost.setHeader("Authorization", "Bearer " + dashscopeApiKey);
httpPost.setHeader("Content-Type", "application/json");
// 设置请求体
httpPost.setEntity(new StringEntity(jsonBody, "UTF-8"));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
} finally {
httpClient.close();
}
}
/**
* 千问long大模型
* @param jsonBody
* @return
* @throws IOException
*/
public String sendGetLongRequest(String jsonBody) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(dashscopeqwenlongurl);
// 设置请求头
httpPost.setHeader("Authorization", "Bearer " + dashscopeApiKey);
httpPost.setHeader("Content-Type", "application/json");
// 设置请求体
httpPost.setEntity(new StringEntity(jsonBody, "UTF-8"));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
} finally {
httpClient.close();
}
}
}
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
@Configuration
public class TencentCosUtil {
@Value("${tencent.cos.secretid}")
private String secretId;
@Value("${tencent.cos.secretkey}")
private String secretKey;
@Value("${tencent.cos.region}")
private String region;
/**
* 调用静态方法getCosClient()就会获得COSClient实例
* @return
*/
@Bean
public COSClient cosClient() {
// 1 初始化用户身份信息(secretId, secretKey)。
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2.1 设置存储桶的地域(上文获得)
Region regions = new Region(region);
ClientConfig clientConfig = new ClientConfig(regions);
// 2.2 使用https协议传输
clientConfig.setHttpProtocol(HttpProtocol.https);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);
// 返回COS客户端
return cosClient;
}
// 静态上传方法
public static String uploadFile(File file, String cosPath, String bucket, COSClient cosClient) {
PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, cosPath, file);
cosClient.putObject(putObjectRequest);
// 拼接url(假设私有读,实际可用签名url或自定义域名)
String url = String.format("https://%s.cos.%s.myqcloud.com/%s", bucket, cosClient.getClientConfig().getRegion().getRegionName(), cosPath);
return url;
}
}
将文件通过 OpenAI 兼容接口上传到阿里云百炼平台,保存至平台安全存储空间后获取文件 ID,GetFileIdMethodClass.java
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 将文件通过OpenAI兼容接口上传到阿里云百炼平台,保存至平台安全存储空间后获取文件ID
*/
@Component
public class GetFileIdMethodClass {
@Value("${qianwenvl.vlapi.vlkey}")
private String dashscopeApiKey;
@Value("${qianwenvl.vlapi.vlurl}")
private String dashscopeApiUrl;
@Value("${qianwenvl.vlapi.qianwenlongurl}")
private String dashscopeApiLongUrl;
public String getFileId(String tempFilePath) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(dashscopeApiLongUrl);
// 设置请求头
httpPost.setHeader("Authorization", "Bearer " + dashscopeApiKey);
// 构建 form-data 请求体
File file = new File(tempFilePath);
FileBody fileBody = new FileBody(file, ContentType.DEFAULT_BINARY);
StringBody stringBody = new StringBody("file-extract", ContentType.TEXT_PLAIN);
HttpEntity requestEntity = MultipartEntityBuilder.create()
.addPart("file", fileBody) // 文件字段,字段名为 "file"
.addPart("purpose", stringBody) // 字符串字段,字段名为 "stringField"
.build();
httpPost.setEntity(requestEntity);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
} finally {
httpClient.close();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.community.sqlapp.mapper.FileInfoMapper">
<resultMap id="fileInfoResultMap" type="com.community.sqlapp.entity.FileInfoPo">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="fileid" jdbcType="VARCHAR" property="fileId" />
<result column="filename" jdbcType="VARCHAR" property="fileName" />
<result column="fileid_info" jdbcType="VARCHAR" property="fileIdInfo" />
</resultMap>
<!-- 插入文件信息 -->
<insert id="insertFileInfo" parameterType="com.community.sqlapp.entity.FileInfoPo" useGeneratedKeys="true" keyProperty="id">
INSERT INTO web_database.files_info (fileid, filename, fileid_info)
VALUES (#{fileId}, #{fileName}, #{fileIdInfo})
</insert>
<!-- 插入腾讯COS文件信息 -->
<insert id="insertTencentCosFile" parameterType="com.community.sqlapp.entity.TencentCosFile" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tencent_cos_file (file_name, file_url)
VALUES (#{fileName}, #{fileUrl})
</insert>
<!-- 根据文件名查询文件信息 -->
<select id="getFileInfoByFileName" parameterType="String" resultMap="fileInfoResultMap">
SELECT id, fileid, filename, fileid_info
FROM web_database.files_info
WHERE filename = #{fileName}
</select>
<!-- 根据文件名查询最新的文件信息(按上传时间降序排序) -->
<select id="getLatestFileInfoByFileName" parameterType="String" resultMap="fileInfoResultMap">
SELECT id, fileid, filename, fileid_info
FROM web_database.files_info
WHERE filename = #{fileName}
ORDER BY create_time DESC
LIMIT 1
</select>
<!-- 查询所有文件信息 -->
<select id="getAllFileInfo" resultMap="fileInfoResultMap">
SELECT id, fileid, filename, fileid_info
FROM web_database.files_info
ORDER BY id DESC
</select>
</mapper>
FileInfoMapper.java、TencentCosFileMapper.java。
import com.community.sqlapp.entity.FileInfoPo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FileInfoMapper {
/**
* 插入文件信息
* @param fileInfo
* @return
*/
int insertFileInfo(FileInfoPo fileInfo);
/**
* 根据文件名查询文件信息
* @param fileName
* @return
*/
FileInfoPo getFileInfoByFileName(String fileName);
/**
* 根据文件名查询最新的文件信息(按上传时间降序排序)
* @param fileName
* @return
*/
FileInfoPo getLatestFileInfoByFileName(String fileName);
}
import com.community.sqlapp.entity.TencentCosFile;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
@Mapper
public interface TencentCosFileMapper {
@Insert("INSERT INTO tencent_cos_file (file_name, file_url) VALUES (#{fileName}, #{fileUrl})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(TencentCosFile file);
}
import lombok.Data;
@Data
public class FileInfoPo {
private Integer id; // 自增ID
private String fileId;
private String fileName;
private String fileIdInfo;
}
import lombok.Data;
@Data
public class TencentCosFile {
private Integer id;
private String fileName;
private String fileUrl;
}
文件上传,信息入库实现类(FileInfoService.java),千问大模型,生成测试点实现类(QwenVlToXmindService.java),xmind 解析生成 xmind 文件实现类(TestPointConverService.java)。
import com.community.sqlapp.dpsToXmind.GetFileIdMethodClass;
import com.community.sqlapp.entity.FileInfoPo;
import com.community.sqlapp.mapper.FileInfoMapper;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.beans.factory.annotation.Value;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* 文件上传,信息入库实现类
*/
@Service
public class FileInfoService {
@Autowired
private FileInfoMapper fileInfoMapper;
@Autowired
private GetFileIdMethodClass getFileIdMethodClass;
@Autowired
private COSClient cosClient;
@Value("${tencent.cos.bucketname}")
private String bucketName;
@Value("${tencent.cos.cosurl}")
private String cosUrl;
/**
* 上传文件并保存文件信息到数据库
* @param file
* @return
*/
public FileInfoPo uploadFile(MultipartFile file) throws IOException {
// 生成唯一的文件ID
String fileId = UUID.randomUUID().toString();
// 获取原始文件名
String originalFileName = file.getOriginalFilename();
// 创建文件信息对象
FileInfoPo fileInfo = new FileInfoPo();
try {
// 创建临时文件
File tempFile = File.createTempFile("upload_", "_" + originalFileName);
// 将MultipartFile转换为临时文件
file.transferTo(tempFile);
// 使用GetFileIdMethodClass获取千问文件服务器返回的文件信息,传递临时文件的完整绝对路径
String apiFileInfo = getFileIdMethodClass.getFileId(tempFile.getAbsolutePath());
if (apiFileInfo != null && !apiFileInfo.trim().isEmpty()) {
// 解析JSON字符串,提取id字段
try {
JSONObject jsonObject = JSON.parseObject(apiFileInfo);
String extractedFileId = jsonObject.getString("id");
if (extractedFileId != null && !extractedFileId.trim().isEmpty()) {
//从千问文件服务器返回的文件信息中提取fileid
fileId = extractedFileId;
}
} catch (Exception e) {
System.out.println("JSON解析失败: " + e.getMessage());
}
fileInfo.setFileIdInfo(apiFileInfo);
fileInfo.setFileId(fileId);
}
// 删除临时文件
tempFile.delete();
} catch (Exception e) {
// 如果GetFileIdMethodClass失败,继续使用UUID
System.out.println("API调用失败,使用UUID作为文件ID: " + e.getMessage());
}
fileInfo.setFileName(originalFileName);
// 保存到数据库
fileInfoMapper.insertFileInfo(fileInfo);
return fileInfo;
}
/**
* 根据文件名查询文件信息
* @param fileName
* @return
*/
public FileInfoPo getFileInfoByFileName(String fileName) {
return fileInfoMapper.getFileInfoByFileName(fileName);
}
/**
* 图片上传到腾讯COS,获取公网访问域名
* @param file
* @return
* @throws IOException
*/
public String uploadCOSFile(MultipartFile file) throws IOException {
// 生成唯一文件名
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String key = "/images/" + UUID.randomUUID() + suffix;
// 元数据
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());
try (InputStream inputStream = file.getInputStream()) {
PutObjectRequest putObjectRequest =
new PutObjectRequest(bucketName, key, inputStream, metadata);
PutObjectResult result = cosClient.putObject(putObjectRequest);
System.out.println(result.getRequestId());
//cosClient.shutdown();
// 返回可访问地址
return cosUrl + key;
}
}
}
import com.community.sqlapp.dpsToXmind.QwenHttpClientUtil;
import com.community.sqlapp.entity.FileInfoPo;
import com.community.sqlapp.mapper.FileInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* 千问大模型,生成测试点实现类
*/
@Service
public class QwenVlToXmindService {
@Autowired
private QwenHttpClientUtil httpClientUtil;
@Autowired
private FileInfoMapper fileInfoMapper;
/**
* 图片请求qwen-vl-plus大模型
* @param imageUrl
* @param userPrompt
* @return
* @throws IOException
*/
public String parseDocument(String imageUrl, String userPrompt) throws IOException {
String dashscopeModel = "qwen-vl-plus"; // 可从配置读取
// 智能prompt,要求AI只返回JSON结构
String smartPrompt = (userPrompt == null || userPrompt.isEmpty() ? "请根据上传的需求文档,自动提取所有功能点并生成详细的测试点,要求结构化、分层级、覆盖全面。" : userPrompt);
System.out.println("userPrompt: "+userPrompt);
StringBuilder jsonBody = new StringBuilder();
jsonBody.append("{");
jsonBody.append("\"model\": \"").append(dashscopeModel).append("\",");
jsonBody.append("\"messages\": [");
jsonBody.append(" {");
jsonBody.append(" \"role\": \"user\",");
jsonBody.append(" \"content\": [");
// 图片内容
jsonBody.append(" {\"type\": \"image_url\", \"image_url\": {\"url\": \"").append(imageUrl).append("\"}},");
// 文本内容
jsonBody.append(" {\"type\": \"text\", \"text\": \"").append(smartPrompt).append("\"}");
jsonBody.append(" ]");
jsonBody.append(" }");
jsonBody.append("]");
jsonBody.append("}");
System.out.println("请求体: "+jsonBody.toString());
return httpClientUtil.sendPostRequest(jsonBody.toString());
}
/**
* word文档使用qwen-long大模型
* @param fileName 文件名
* @param userPrompt 用户提示
* @return
* @throws IOException
*/
public String parseLongDocument(String fileName, String userPrompt) throws IOException {
String dashscopeModel = "qwen-long"; // 可从配置读取
// 智能prompt,要求AI只返回JSON结构
String smartPrompt = (userPrompt == null || userPrompt.isEmpty() ? "请根据上传的需求文档,自动提取所有功能点并生成详细的测试点,要求结构化、分层级、覆盖全面。" : userPrompt);
// 从数据库获取fileid
FileInfoPo fileInfo = fileInfoMapper.getLatestFileInfoByFileName(fileName);
if (fileInfo == null) {
throw new IOException("未找到文件信息:" + fileName);
}
String fileId = fileInfo.getFileId();
System.out.println("从数据库获取的fileId: "+fileId);
StringBuilder jsonBody = new StringBuilder();
jsonBody.append("{" +
" \"model\": \""+dashscopeModel+"\"," +
" \"input\": {" +
" \"messages\": [" +
" {\"role\": \"system\",\"content\": \"fileid://"+fileId+"\"}," +
" {\"role\": \"user\",\"content\": \""+smartPrompt+"\"}" +
" ]" +
" }," +
" \"parameters\": {" +
" \"result_format\": \"message\"" +
" }" +
"}");
System.out.println("请求体: "+jsonBody.toString());
return httpClientUtil.sendGetLongRequest(jsonBody.toString());
}
}
import com.google.gson.*;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedHashMap;
/**
* xmind解析生成xmind文件实现类
*/
@Service
public class TestPointConverService {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
/**
* 递归将任意JSON结构转为XMind树结构
* 支持 {"测试点": [...] } 这种结构,所有分层、子测试点、测试方法都能还原为XMind节点
*/
public static String jsonToXmindTree(String json) {
JsonElement rootElem = JsonParser.parseString(json);
JsonObject rootObj;
if (rootElem.isJsonObject()) {
rootObj = rootElem.getAsJsonObject();
} else {
throw new IllegalArgumentException("输入必须为JSON对象");
}
// 取第一个key作为根节点
Set<Map.Entry<String, JsonElement>> entries = rootObj.entrySet();
if (entries.isEmpty()) throw new IllegalArgumentException("JSON对象不能为空");
Map.Entry<String, JsonElement> first = entries.iterator().next();
JsonObject xmindRoot = new JsonObject();
xmindRoot.addProperty("topic", first.getKey());
JsonArray children = new JsonArray();
buildXmindChildren(first.getValue(), children);
if (children.size() > 0) xmindRoot.add("children", children);
return gson.toJson(xmindRoot);
}
// 递归构建children,增强适配多层级结构
private static void buildXmindChildren(JsonElement elem, JsonArray children) {
if (elem == null || elem.isJsonNull()) return;
if (elem.isJsonArray()) {
for (JsonElement e : elem.getAsJsonArray()) {
buildXmindChildren(e, children);
}
} else if (elem.isJsonObject()) {
JsonObject obj = elem.getAsJsonObject();
String topic = null;
JsonArray nodeChildren = new JsonArray();
// 优先常规字段
if (obj.has("一级模块")) {
topic = obj.get("一级模块").isJsonPrimitive() ? obj.get("一级模块").getAsString() : "一级模块";
if (obj.has("二级模块")) {
buildXmindChildren(obj.get("二级模块"), nodeChildren);
}
} else if (obj.has("二级模块")) {
if (obj.get("二级模块").isJsonArray()) {
topic = null;
buildXmindChildren(obj.get("二级模块"), nodeChildren);
} else if (obj.get("二级模块").isJsonPrimitive()) {
topic = obj.get("二级模块").getAsString();
if (obj.has("测试点")) {
buildXmindChildren(obj.get("测试点"), nodeChildren);
}
} else if (obj.get("二级模块").isJsonObject()) {
topic = "二级模块";
buildXmindChildren(obj.get("二级模块"), nodeChildren);
}
} else if (obj.has("测试点")) {
JsonElement testPointElem = obj.get("测试点");
if (testPointElem.isJsonArray()) {
topic = "测试点";
buildXmindChildren(testPointElem, nodeChildren);
} else if (testPointElem.isJsonPrimitive()) {
topic = testPointElem.getAsString();
if (obj.has("测试用例名称")) {
buildXmindChildren(obj.get("测试用例名称"), nodeChildren);
}
} else if (testPointElem.isJsonObject()) {
topic = "测试点";
buildXmindChildren(testPointElem, nodeChildren);
}
} else if (obj.has("测试用例名称")) {
topic = obj.get("测试用例名称").isJsonPrimitive() ? obj.get("测试用例名称").getAsString() : "测试用例名称";
} else if (obj.has("名称")) {
topic = obj.get("名称").isJsonPrimitive() ? obj.get("名称").getAsString() : "名称";
} else if (obj.has("topic")) {
topic = obj.get("topic").isJsonPrimitive() ? obj.get("topic").getAsString() : "topic";
} else {
// 新增:如果是普通对象,遍历所有key,每个key作为一个子节点
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
String key = entry.getKey();
JsonElement value = entry.getValue();
JsonObject node = new JsonObject();
node.addProperty("topic", key);
JsonArray childArr = new JsonArray();
if (value.isJsonArray() || value.isJsonObject()) {
buildXmindChildren(value, childArr);
if (childArr.size() > 0) node.add("children", childArr);
} else if (value.isJsonPrimitive()) {
// 叶子节点直接显示key: value
node.addProperty("topic", key + ": " + value.getAsString());
}
children.add(node);
}
return;
}
// 只在有topic时生成节点
if (topic != null) {
JsonObject node = new JsonObject();
node.addProperty("topic", topic);
if (nodeChildren.size() > 0) node.add("children", nodeChildren);
children.add(node);
} else if (nodeChildren.size() > 0) {
for (JsonElement c : nodeChildren) {
children.add(c);
}
}
} else if (elem.isJsonPrimitive()) {
JsonObject node = new JsonObject();
node.addProperty("topic", elem.getAsString());
children.add(node);
}
}
/**
* 将平铺的数组结构自动分层为树状结构,支持一级模块、二级模块、测试点、测试用例名称四级分组
*/
public static String flatJsonToXmindTree(String json) {
JsonElement elem = JsonParser.parseString(json);
if (elem.isJsonArray()) {
JsonArray arr = elem.getAsJsonArray();
// 构建树:Map<一级, Map<二级, Map<测试点, List<测试用例名称>>>>
Map<String, Map<String, Map<String, List<String>>>> tree = new LinkedHashMap<>();
for (JsonElement e : arr) {
JsonObject obj = e.getAsJsonObject();
String lv1 = obj.has("一级模块") ? obj.get("一级模块").getAsString() : "未知一级";
String lv2 = obj.has("二级模块") ? obj.get("二级模块").getAsString() : "未知二级";
// 处理测试点为数组或字符串
List<String> testPoints = new ArrayList<>();
if (obj.has("测试点")) {
JsonElement tpElem = obj.get("测试点");
if (tpElem.isJsonArray()) {
for (JsonElement tp : tpElem.getAsJsonArray()) {
testPoints.add(tp.getAsString());
}
} else if (tpElem.isJsonPrimitive()) {
testPoints.add(tpElem.getAsString());
}
} else {
testPoints.add("未知测试点");
}
// 处理测试用例名称为数组或字符串
List<String> caseNames = new ArrayList<>();
if (obj.has("测试用例名称")) {
JsonElement cnElem = obj.get("测试用例名称");
if (cnElem.isJsonArray()) {
for (JsonElement cn : cnElem.getAsJsonArray()) {
caseNames.add(cn.getAsString());
}
} else if (cnElem.isJsonPrimitive()) {
caseNames.add(cnElem.getAsString());
}
} else {
caseNames.add("用例");
}
// 构建树结构
for (int i = 0; i < Math.max(testPoints.size(), caseNames.size()); i++) {
String lv3 = i < testPoints.size() ? testPoints.get(i) : "未知测试点";
String lv4 = i < caseNames.size() ? caseNames.get(i) : "用例";
tree.computeIfAbsent(lv1, k -> new LinkedHashMap<>())
.computeIfAbsent(lv2, k -> new LinkedHashMap<>())
.computeIfAbsent(lv3, k -> new ArrayList<>())
.add(lv4);
}
}
// 构建XMind JSON
JsonObject root = new JsonObject();
root.addProperty("topic", "一级目录");
JsonArray lv1Arr = new JsonArray();
for (Map.Entry<String, Map<String, Map<String, List<String>>>> e1 : tree.entrySet()) {
JsonObject lv1Node = new JsonObject();
lv1Node.addProperty("topic", e1.getKey());
JsonArray lv2Arr = new JsonArray();
for (Map.Entry<String, Map<String, List<String>>> e2 : e1.getValue().entrySet()) {
JsonObject lv2Node = new JsonObject();
lv2Node.addProperty("topic", e2.getKey());
JsonArray lv3Arr = new JsonArray();
for (Map.Entry<String, List<String>> e3 : e2.getValue().entrySet()) {
JsonObject lv3Node = new JsonObject();
lv3Node.addProperty("topic", e3.getKey());
JsonArray lv4Arr = new JsonArray();
for (String lv4 : e3.getValue()) {
JsonObject lv4Node = new JsonObject();
lv4Node.addProperty("topic", lv4);
lv4Arr.add(lv4Node);
}
if (lv4Arr.size() > 0) lv3Node.add("children", lv4Arr);
lv3Arr.add(lv3Node);
}
if (lv3Arr.size() > 0) lv2Node.add("children", lv3Arr);
lv2Arr.add(lv2Node);
}
if (lv2Arr.size() > 0) lv1Node.add("children", lv2Arr);
lv1Arr.add(lv1Node);
}
if (lv1Arr.size() > 0) root.add("children", lv1Arr);
return gson.toJson(root);
} else if (elem.isJsonObject()) {
// 新增:对象结构直接用递归树方法
return jsonToXmindTree(json);
} else {
throw new IllegalArgumentException("不支持的JSON结构");
}
}
/**
* 生成xmind8文件(包含content.xml和META-INF/manifest.xml,标准结构)
*/
public static void generateXMind(String json, String outputPath) throws Exception {
String contentXml = jsonToXMindContentXml(json);
// 1. 生成content.xml
File tempDir = new File("temp_xmind");
if (!tempDir.exists()) tempDir.mkdirs();
File contentFile = new File(tempDir, "content.xml");
try (FileOutputStream fos = new FileOutputStream(contentFile)) {
fos.write(contentXml.getBytes("UTF-8"));
}
// 2. 生成META-INF/manifest.xml
File metaInfDir = new File(tempDir, "META-INF");
if (!metaInfDir.exists()) metaInfDir.mkdirs();
File manifestFile = new File(metaInfDir, "manifest.xml");
String manifestXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<manifest xmlns=\"urn:xmind:xmap:xmlns:manifest:1.0\">\n" +
" <file-entry full-path=\"content.xml\" media-type=\"text/xml\"/>\n" +
"</manifest>\n";
try (FileOutputStream fos = new FileOutputStream(manifestFile)) {
fos.write(manifestXml.getBytes("UTF-8"));
}
// 3. 打包为zip(xmind)
try (FileOutputStream fos = new FileOutputStream(outputPath);
ZipOutputStream zos = new ZipOutputStream(fos)) {
// content.xml
ZipEntry entry1 = new ZipEntry("content.xml");
zos.putNextEntry(entry1);
byte[] bytes1 = Files.readAllBytes(contentFile.toPath());
zos.write(bytes1, 0, bytes1.length);
zos.closeEntry();
// META-INF/manifest.xml
ZipEntry entry2 = new ZipEntry("META-INF/manifest.xml");
zos.putNextEntry(entry2);
byte[] bytes2 = Files.readAllBytes(manifestFile.toPath());
zos.write(bytes2, 0, bytes2.length);
zos.closeEntry();
}
// 清理临时文件
contentFile.delete();
manifestFile.delete();
metaInfDir.delete();
tempDir.delete();
}
// 将json结构转为xmind8 content.xml内容(极简结构,仅topic树)
private static String jsonToXMindContentXml(String json) {
JsonObject root = gson.fromJson(json, JsonObject.class);
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
sb.append("<xmap-content xmlns=\"urn:xmind:xmap:xmlns:content:2.0\" version=\"2.0\">\n");
sb.append(" <sheet id=\"1\" timestamp=\"" + System.currentTimeMillis() + "\">\n");
sb.append(" <title>").append(root.get("topic").getAsString()).append("</title>\n");
sb.append(jsonTopicToXml(root, 3));
sb.append(" </sheet>\n");
sb.append("</xmap-content>\n");
return sb.toString();
}
private static String jsonTopicToXml(JsonObject node, int indent) {
StringBuilder sb = new StringBuilder();
String topic = node.has("topic") ? node.get("topic").getAsString() : "";
String pad = " ".repeat(indent);
sb.append(pad).append("<topic id=\"t" + System.nanoTime() + "\">\n");
sb.append(pad).append(" <title>").append(topic).append("</title>\n");
if (node.has("children")) {
JsonArray children = node.getAsJsonArray("children");
if (children.size() > 0) {
sb.append(pad).append(" <children>\n");
sb.append(pad).append(" <topics type=\"attached\">\n");
for (int i = 0; i < children.size(); i++) {
JsonObject child = children.get(i).getAsJsonObject();
sb.append(jsonTopicToXml(child, indent + 3));
}
sb.append(pad).append(" </topics>\n");
sb.append(pad).append(" </children>\n");
}
}
sb.append(pad).append("</topic>\n");
return sb.toString();
}
}
import com.community.sqlapp.entity.TencentCosFile;
import com.community.sqlapp.mapper.TencentCosFileMapper;
import com.community.sqlapp.service.FileInfoService;
import com.community.sqlapp.service.QwenVlToXmindService;
import com.community.sqlapp.service.TestPointConverService;
import com.google.gson.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.xmind.core.CoreException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class FileToTestPointController {
@Autowired
private QwenVlToXmindService qwenVlService;
@Autowired
private FileInfoService fileInfoService;
@Autowired
private TencentCosFileMapper tencentCosFileMapper;
/**
* 图片生成xmind测试点
* @param file
* @param promot
* @return
* @throws IOException
* @throws CoreException
*/
@PostMapping("/imageUpload")
public Map<String, Object> imageUploadFile(@RequestParam("file") MultipartFile file, @RequestParam(value = "promot", required = false) String promot) throws IOException, CoreException {
Map<String, Object> response = new HashMap<>();
try {
// 2. 上传到腾讯云COS
String fileUrl = fileInfoService.uploadCOSFile(file);
System.out.println("fileUrl: "+fileUrl);
// 3. 存入数据库
TencentCosFile cosFile = new TencentCosFile();
cosFile.setFileName(file.getOriginalFilename());
cosFile.setFileUrl(fileUrl);
tencentCosFileMapper.insert(cosFile);
// 4. 用fileUrl调用大模型
String promotInfo = "请严格以如下JSON格式返回,不要输出多余的解释或Markdown,返回json内容包含一级模块、二级模块、测试点、测试用例名称";
String userPrompt = (promot != null && !promot.isEmpty() ? promot : "请根据上传的需求文档自动提取所有功能点并生成详细的测试点,要求结构化、分层级、覆盖全面。") + promotInfo;
//调用 qwen-vl-plus 模型解析文档内容(新版API,文档+文本)
String aiJson = qwenVlService.parseDocument(fileUrl, userPrompt);
System.out.println("返回数据: " + aiJson);
String xmindPath = null;
try {
// 1. 提取content字段
JsonObject resp = JsonParser.parseString(aiJson).getAsJsonObject();
if (resp.has("output")) {
resp = resp.getAsJsonObject("output");
}
String content = "";
if (resp.has("choices")) {
JsonArray choices = resp.getAsJsonArray("choices");
if (choices.size() > 0) {
JsonObject msg = choices.get(0).getAsJsonObject();
if (msg.has("message")) {
JsonObject message = msg.getAsJsonObject("message");
if (message.has("content")) {
content = message.get("content").getAsString();
}
}
}
}
if (content.isEmpty()) {
response.put("success", false);
response.put("error", "AI返回内容无content字段,原始返回:" + aiJson);
return response;
}
// 2. 去除markdown代码块包裹
content = content.replaceAll("(?s)```json|```", "").trim();
System.out.println("提取的content内容:\n" + content);
// 3. 平铺数组转树状结构
String xmindJson = TestPointConverService.flatJsonToXmindTree(content);
System.out.println("解析成功!生成的JSON结构:\n" + xmindJson);
// 4. 生成XMind文件
String suffix = file.getOriginalFilename();
int index = suffix.indexOf('.');
if (index != -1) { // 确保找到了点号
suffix = suffix.substring(0, index); // 获取第一个点号之前的部分
}
System.out.println("文件名称"+ suffix);
xmindPath = suffix+".xmind";
TestPointConverService.generateXMind(xmindJson, xmindPath);
System.out.println("XMind文件生成成功:" + xmindPath);
response.put("success", true);
response.put("message", "XMind 文件已生成");
response.put("downloadUrl", "/fileconfig/download?file="+xmindPath);
} catch (Exception e) {
System.err.println("解析失败:" + e.getMessage());
e.printStackTrace();
response.put("success", false);
response.put("error", "解析失败:" + e.getMessage());
}
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
}
return response;
}
/**
* 图片上传到腾讯COS
* @param file
* @return
*/
@PostMapping("/uploadtest")
public ResponseEntity<String> uploadTest(@RequestParam("file") MultipartFile file) {
try {
String url = String.valueOf(fileInfoService.uploadCOSFile(file));
System.out.println("fileUrl: "+url);
return ResponseEntity.ok(url);
} catch (Exception e) {
return ResponseEntity.status(500).body("上传失败:" + e.getMessage());
}
}
/**
* 文件生成测试点,根据文件名从数据库获取fileid
* @param fileName 文件名
* @param promot 提示词
* @return
* @throws IOException
* @throws CoreException
*/
@PostMapping("/wordUpload")
public Map<String, Object> wordUpload(@RequestParam("fileName") String fileName, @RequestParam(value = "promot", required = false) String promot) throws IOException, CoreException {
Map<String, Object> response = new HashMap<>();
try {
String promotInfo="请严格以如下JSON格式返回,不要输出多余的解释或Markdown,返回json内容包含一级模块、二级模块、测试点、测试用例名称";
String userPrompt = promot != null && !promot.isEmpty() ? promot+promotInfo : "请根据上传的需求文档自动提取所有功能点并生成详细的测试点,要求结构化、分层级、覆盖全面。"+promotInfo;
// 调用 Qwen-long 模型解析文档内容(新版API,文档+文本)
String aiJson = qwenVlService.parseLongDocument(fileName, userPrompt);
System.out.println("返回数据: " + aiJson);
String xmindPath = null;
try {
// 1. 提取content字段
JsonObject resp = JsonParser.parseString(aiJson).getAsJsonObject();
if (resp.has("output")) {
resp = resp.getAsJsonObject("output");
}
String content = "";
if (resp.has("choices")) {
JsonArray choices = resp.getAsJsonArray("choices");
if (choices.size() > 0) {
JsonObject msg = choices.get(0).getAsJsonObject();
if (msg.has("message")) {
JsonObject message = msg.getAsJsonObject("message");
if (message.has("content")) {
content = message.get("content").getAsString();
}
}
}
}
if (content.isEmpty()) {
response.put("success", false);
response.put("error", "AI返回内容无content字段,原始返回:" + aiJson);
return response;
}
// 2. 去除markdown代码块包裹
content = content.replaceAll("(?s)```json|```", "").trim();
System.out.println("提取的content内容:\n" + content);
// 3. 平铺数组转树状结构
String xmindJson = TestPointConverService.flatJsonToXmindTree(content);
System.out.println("解析成功!生成的JSON结构:\n" + xmindJson);
// 4. 生成XMind文件
xmindPath = fileName+".xmind";
TestPointConverService.generateXMind(xmindJson, xmindPath);
System.out.println("XMind文件生成成功:" + xmindPath);
response.put("success", true);
response.put("message", "XMind 文件已生成");
response.put("downloadUrl", "/fileconfig/download?file="+xmindPath);
} catch (Exception e) {
System.err.println("解析失败:" + e.getMessage());
e.printStackTrace();
response.put("success", false);
response.put("error", "解析失败:" + e.getMessage());
}
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
}
return response;
}
}
import com.community.sqlapp.service.FileInfoService;
import com.community.sqlapp.entity.FileInfoPo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/fileconfig")
public class FileUploadMethodController {
@Autowired
private FileInfoService fileInfoService;
/**
* 显示文件上传页面
*/
@GetMapping("/upload")
public String showUploadPage() {
return "fileupload";
}
/**
* 文件上传,入库文件ID、文件名称,千问的qwen-long模型存储doc、docx时调这个接口
* @param file
* @return
*/
@PostMapping("/fileupload")
@ResponseBody
public Map<String, Object> qwenLongFileUpload(@RequestParam("file") MultipartFile file) {
Map<String, Object> response = new HashMap<>();
try {
// 调用文件上传服务
FileInfoPo fileInfo = fileInfoService.uploadFile(file);
response.put("success", true);
response.put("message", "文件上传成功");
response.put("fileId", fileInfo.getFileId());
response.put("fileName", fileInfo.getFileName());
response.put("id", fileInfo.getId());
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
}
return response;
}
/**
* 获取文件信息
* @param file
* @return
* @throws Exception
*/
@PostMapping("/fileInfo")
@ResponseBody
public Map<String, Object> parseFileContent(@RequestParam("file") MultipartFile file) throws Exception {
Map<String, Object> response = new HashMap<>();
try {
String fileName = file.getOriginalFilename();
FileInfoPo fileInfo = fileInfoService.getFileInfoByFileName(fileName);
if (fileInfo != null) {
response.put("success", true);
response.put("fileInfo", fileInfo);
} else {
response.put("success", false);
response.put("message", "文件信息未找到");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
}
return response;
}
}
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ConfigurationPropertiesScan
@MapperScan("com.community.sqlapp.mapper")
@ComponentScan(basePackages = {"com.community.sqlapp"})
@EnableConfigurationProperties
public class MicroDynamicApplication {
public static void main(String[] args) {
SpringApplication.run(MicroDynamicApplication.class, args);
}
}
-- 创建files_info表的SQL脚本
-- 数据库:web_database
CREATE TABLE IF NOT EXISTS `web_database`.`files_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`fileid` varchar(255) NOT NULL COMMENT '文件ID',
`filename` varchar(500) NOT NULL COMMENT '文件名',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_fileid` (`fileid`),
KEY `idx_filename` (`filename`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件信息表';
CREATE TABLE tencent_cos_file (
id INT AUTO_INCREMENT PRIMARY KEY,
file_name VARCHAR(255) NOT NULL,
file_url VARCHAR(1024) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
至此 V1.1 版本目前告一段落,完成所有项目文件相关的数据切换到对象存储服务,BUG 和代码健壮性还是很多需要改进的地方,页面还没有完全前后端分离。继续回归业务测试本职工作。下次时间空闲出来了再继续改造。