简单的网站都是一套的逻辑爬虫,大家可以参考一下。有兴趣的话帮忙点个start支持一下
前后端系统以及数据库
https://gitee.com/wonder19991209/mohurd\_search\_sys
爬虫脚本
]]>解压文件
tar -zxvf jdk-8u431-linux-x64.tar.gz -C /home/env/java/
设置环境变量
vim /etc/profile
在文件最末尾加上
export JAVA_HOME=/home/env/java/jdk1.8.0_431
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
使环境变量生效
source /etc/profile
配置软连接
ln -s /home/env/java/jdk1.8.0_431/bin/java /usr/bin/java
检查
java -version
删除软连接并重新配置
确认现有软连接
ls -l /usr/bin/java
删除
rm /usr/bin/java
重新配置
ln -s /env/java/jdk-21.0.3/bin/java /usr/bin/java
]]>起因是一个资源文件在resources目录下面,转换后的地址类型是string类型不是file,于是用类加载器获取文件资源路径
String dbPath = GetIpAddr.class.getClassLoader().getResource("ip2region/ip2region.xdb").getPath();
但是这样在编译部署完毕后会报错
Generating unique operation named: userInfoUsingGET_1
2024-09-26 17:15:40.345 INFO 3992 --- [nio-8081-exec-1] o.a.c.c.C.[.[localhost].[/sys_api] : Initializing Spring DispatcherServlet 'dispatcherServlet'
failed to load vector index from file:/C:/Users/Administrator/Desktop/jar/api.jar!/BOOT-INF/classes!/ip2region/ip2region.xdb: java.io.FileNotFoundException: file:\C:\Users\Administrator\Desktop\jar\api.jar!\BOOT-INF\classes!\ip2region\ip2region.xdb (文件名、目录名或卷标语法不正确。)
2024-09-26 17:15:40.664 ERROR 3992 --- [nio-8081-exec-1] i.d.exception.RenExceptionHandler :
### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'ip_addr' at row 1
### The error may exist in URL [jar:file:/C:/Users/Administrator/Desktop/jar/api.jar!/BOOT-INF/classes!/mapper/MarkVisiter.xml]
### The error may involve io.dellevin.dao.MarkVisiterDao.insertVisitRecord-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO mark_visiter ( visit_url, ip, ip_addr, create_time) VALUES ( ?, ?, ?, NOW() )
### Cause: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'ip_addr' at row 1
; Data truncation: Data too long for column 'ip_addr' at row 1; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'ip_addr' at row 1
org.springframework.dao.DataIntegrityViolationException:
只能用流方式加载文件,于是采用如下方式:可以使用一个临时文件来处理这个问题。首先读取输入流,然后将其写入一个临时文件,最后将该临时文件的路径作为 dbPath
String dbPath = null;
// 1、从 dbPath 中预先加载 VectorIndex 缓存
try (InputStream inputStream = GetIpAddr.class.getClassLoader().getResourceAsStream("ip2region/ip2region.xdb")) {
if (inputStream == null) {
throw new FileNotFoundException("Resource not found: ip2region/ip2region.xdb");
}
// 创建临时文件
File tempFile = Files.createTempFile("ip2region", ".xdb").toFile();
try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
// 获取临时文件路径
dbPath = tempFile.getAbsolutePath();
} catch (IOException e) {
System.out.printf("failed to load vector index: %s\n", e);
return "failed to load vector index: " + e + " ";
}
]]>SELECT gp.*, mu.linkmanName , mu.linkmanPhone, mu.legalPersonName, mu.legalPersonPhone, mu.address, mu.registerArea FROM gp_project gp LEFT JOIN member_user mu ON mu.supplierId = gp.supplier_id WHERE DATE(gp.publicity_time) >= '2024-04-03' AND DATE(gp.publicity_time) <= '2024-04-07' and mu.deleted = 0
针对这样的数据,我的数据表里面数据太多,导致了查询需要512秒的时间,而且这仅仅是两个表的关联,之后还需要关联四五个表组成一条查询语句,如果仅在mysql里面查,未免有些太为难服务器了。因为平常找数据用的sql里面的in语法比较多,所以尝试了一下用关联字段in数据的情况,突然发现,这样还挺快,主要是因为关联字段是索引,所以比较快,通过单表查出数据,然后将关键字段提取出来,in到另一个表里面快速查询,最后拼接字段,来实现数据的查询结果。
另外因为涉及到多个循环,如果仅仅遵循 循环的外小内大原则,这样未免要写多个循环,降低了程序的运行效率多个O(n^2)的程序跑的,这样未免太致命了。改用map的key和value对照关系这样通过一个循环+map的方式降低时间复杂度为O(1) ,这样就会快很多
下面是操作案例
mybaits部分(单个案例)
<?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.xyjq.mapper.db1.LoginMemberUserDao">
<!--根据供应商id判断是否为注册用户 -->
<select id="isRegisterSupplier" resultType="com.xyjq.entity.db1.LoginMemberUserEntity">
SELECT supplierId,createTime FROM `login_member_user` WHERE supplierId in
<foreach collection="supplierIdList" item="supplierId" open="(" separator="," close=")">
#{supplierId}
</foreach>
</select>
</mapper>
业务层部分
List<GpProjectEntity> list = baseDao.queryList(params);
// 提取查询结果里面的supplierID的set集合
Set<Integer> supplierIdList = list.stream()
.map(GpProjectEntity::getSupplierId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyyMMddHHmmss");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 以下遍历中防止嵌套for循环O(n^2),使用map对象存储降低时间复杂度为O(1)
// 优化设置供应商信息
Map<String, MemberUserEntity> memberUserInfoMap = new HashMap<>();
memberUserDao.getMemberUserInfo(supplierIdList).forEach(memberUser -> memberUserInfoMap.put(memberUser.getSupplierId().toString(), memberUser));
// 设置供应商的工商地址信息
Map<String, SupplierBussinessInformationEntity> supplierBusinessInfoMap = new HashMap<>();
supplierBussinessInformationDao.getSupplierBusinessInfo(supplierIdList).forEach(supplierBusinessInfo -> supplierBusinessInfoMap.put(supplierBusinessInfo.getSupplierId().toString(), supplierBusinessInfo));
// 设置是否是注册用户
Map<String, LoginMemberUserEntity> loginMemberUserMap = new HashMap<>();
loginMemberUserDao.isRegisterSupplier(supplierIdList).forEach(loginMemberUser -> loginMemberUserMap.put(loginMemberUser.getSupplierId().toString(), loginMemberUser));
// 设置是否有融资需求
Map<String, String> financingNeedsMap = new HashMap<>();
// 根据搜索结果,如果key一样就拼接value
financingNeedsDao.getCompFinancingNeeds(supplierIdList).forEach(financingNeeds -> {
String key = financingNeeds.getSupplierId();
String value = FinancingStatus.getDescriptionByCode(financingNeeds.getFinancingProgress()) + "(" + financingNeeds.getCreateTime() + ")";
financingNeedsMap.merge(key, value, (oldValue, newValue) -> oldValue + ", " + newValue);
});
for (GpProjectEntity project : list) {
// 设置项目名称
if (project.getAgName() != null && !project.getAgName().isEmpty()) {
project.setProjectNameMergeAgBid(project.getAgName());
} else if (project.getJatTpProName() != null && !project.getJatTpProName().isEmpty()) {
project.setProjectNameMergeAgBid(project.getJatTpProName());
}
// 设置项目金额
if (project.getAgTotalMoney() != null) {
project.setProjectMoneyMergeAgBid(project.getAgTotalMoney());
} else {
project.setProjectMoneyMergeAgBid(project.getWinPriceRevised());
}
// 设置项目的日期
if (project.getAgSignDate() != null) {
Date date = null;
try {
date = inputFormat.parse(project.getAgSignDate());
} catch (ParseException e) {
throw new RuntimeException(e);
}
project.setProjectDateMergeAgBid(dateFormat.format(date));
} else {
project.setProjectDateMergeAgBid( dateFormat.format(project.getJatWinTime()) );
}
String supplierId = project.getSupplierId().toString();
// 设置供应商信息
MemberUserEntity memberUser = memberUserInfoMap.get(supplierId);
if (memberUser != null) {
project.setProjectLinkManName(memberUser.getLinkmanName());
project.setProjectLinkManPhone(memberUser.getLinkmanPhone());
project.setProjectLegalPersonName(memberUser.getLegalPersonName());
project.setProjectLegalPersonPhone(memberUser.getLegaPersonPhone());
project.setProjectAddress(memberUser.getAddress());
project.setProjectRegisterArea(memberUser.getRegisterArea());
}
// 设置工商信息省市区
SupplierBussinessInformationEntity supplierBussinessInformation = supplierBusinessInfoMap.get(supplierId);
if (supplierBussinessInformation != null) {
project.setProjectProvince(supplierBussinessInformation.getProvince());
project.setProjectCity(supplierBussinessInformation.getCity());
project.setProjectDistrict(supplierBussinessInformation.getDistrict());
}
// 设置是否是注册用户
LoginMemberUserEntity loginMemberUser = loginMemberUserMap.get(supplierId);
if (loginMemberUser != null) {
project.setProjectIsRegisterSupplier("已注册(" + dateFormat.format(loginMemberUser.getCreateTime()) + ")");
}
// 设置融资历史记录
String financingNeeds = financingNeedsMap.get(supplierId);
if (financingNeeds != null) {
project.setProjectFinancingNeeds(financingNeeds);
}
}
]]>event1
和 event2
,表示发生在同一天的两个闭区间时间段事件,其中:event1 = [startTime1, endTime1]
event2 = [startTime2, endTime2]
事件的时间为有效的 24 小时制且按 HH:MM
格式给出。
当两个事件存在某个非空的交集时(即,某些时刻是两个事件都包含的),则认为出现 冲突 。
如果两个事件之间存在冲突,返回 true
;否则,返回 false
。
示例 1:
输入:event1 = ["01:15","02:00"], event2 = ["02:00","03:00"]
输出:true
解释:两个事件在 2:00 出现交集。
示例 2:
输入:event1 = ["01:00","02:00"], event2 = ["01:20","03:00"]
输出:true
解释:两个事件的交集从 01:20 开始,到 02:00 结束。
示例 3:
输入:event1 = ["10:00","11:00"], event2 = ["14:00","15:00"]
输出:false
解释:两个事件不存在交集。
提示:
evnet1.length == event2.length == 2.
event1[i].length == event2[i].length == 5
startTime1 <= endTime1
startTime2 <= endTime2
HH:MM
格式给出class Solution {
public boolean haveConflict(String[] event1, String[] event2) {
int event1StartTime = strToIntTime(event1[0]);
int event1EndTime = strToIntTime(event1[1]);
int event2StartTime = strToIntTime(event2[0]);
int event2EndTime = strToIntTime(event2[1]);
if(event1EndTime < event2StartTime || event2EndTime <event1StartTime ){
return false;
}
return true;
}
public static int strToIntTime(String str) {
String[] ss = str.split(":");
int hours = Integer.parseInt(ss[0])*60;
return hours+Integer.parseInt(ss[1]);
}
}
比对时间不在另一个区间内就可以了,但是string不能进行比较,只能使用int类型比较。
]]>arr1
和 arr2
,返回两数相加的结果。数字以 数组形式 给出:数组由若干 0 和 1 组成,按最高有效位到最低有效位的顺序排列。例如,arr = [1,1,0,1]
表示数字 (-2)^3 + (-2)^2 + (-2)^0 = -3
。数组形式 中的数字 arr
也同样不含前导零:即 arr == [0]
或 arr[0] == 1
。
返回相同表示形式的 arr1
和 arr2
相加的结果。两数的表示形式为:不含前导零、由若干 0 和 1 组成的数组。
示例 1:
输入:arr1 = [1,1,1,1,1], arr2 = [1,0,1]
输出:[1,0,0,0,0]
解释:arr1 表示 11,arr2 表示 5,输出表示 16 。
示例 2:
输入:arr1 = [0], arr2 = [0]
输出:[0]
示例 3:
输入:arr1 = [0], arr2 = [1]
输出:[1]
提示:
1 <= arr1.length, arr2.length <= 1000
arr1[i]
和 arr2[i]
都是 0
或 1
arr1
和 arr2
都没有前导0class Solution {
public int[] addNegabinary(int[] arr1, int[] arr2) {
// 定义规则:0+1=1,进位向前边减一,够减则减,不够减则借位
int[] outCome = null;
if (arr1.length > arr2.length) {
for (int i = 1; i <= arr2.length; i++) {
arr1[arr1.length - i] += arr2[arr2.length - i];
}
outCome = arr1;
} else {
for (int i = 1; i <= arr1.length; i++) {
arr2[arr2.length - i] += arr1[arr1.length - i];
}
outCome = arr2;
}
// System.out.println(Arrays.toString(outCome));
int add = 0;
for (int i = outCome.length - 1; i > 0; i--) {
if (outCome[i] + add >= 2) {
add = -1;
outCome[i] -= 2;
} else if (outCome[i] + add < 0) {
outCome[i - 1] += 1;
outCome[i] = 1;
add = 0;
} else {
outCome[i] += add;
add = 0;
}
}
outCome[0] += add;
//System.out.println(Arrays.toString(outCome));
if (outCome[0] >= 2) {
outCome[0] -= 2;
int[] newOutCome = new int[outCome.length + 2];
newOutCome[0] = 1;
newOutCome[1] = 1;
for (int i = 0; i < outCome.length; i++) {
newOutCome[2 + i] = outCome[i];
}
outCome = newOutCome;
}
// System.out.println(Arrays.toString(outCome));
if (outCome[0] == 0) {
int zlength = 0;
while (zlength < outCome.length && outCome[zlength] == 0) {
zlength++;
}
System.out.println(zlength);
if (zlength == outCome.length) {
outCome = new int[1];
outCome[0] = 0;
} else {
int[] newOutCome = new int[outCome.length - zlength];
for (int i = 0; i < newOutCome.length; i++) {
newOutCome[i] = outCome[zlength + i];
}
outCome = newOutCome;
}
}
return outCome;
}
}
这里主要错误的原因是因为忽略了数组之间的长度,因为长度是1000也就是最大的数是-2的1000次幂。远远的超出了bigint,int,以及long类型的计算长度。所以下面的办法是妥妥的不对的
关键字 | 所占位数 | 范围 |
---|---|---|
int | 32 | −231−231 ~ 2 ^{31} - 1 |
long | 64 | −263−263 ~ 2 ^{63} - 1 |
class Solution {
public int[] addNegabinary(int[] arr1, int[] arr2) {
int xx = lowBinaryToInt(arr1) + lowBinaryToInt(arr2);
String ss= intToLowBinaryString(xx);
String[] ssArr = ss.split("");
int[] res = new int[ss.length()];
for (int i = 0; i <= ss.length()-1; i++) {
res[i] = Integer.parseInt(ssArr[i]);
}
return res;
}
public static int lowBinaryToInt(int[] arr) {
double res=0;
int xx = 0;
for (int i = arr.length - 1; i>=0; i--) {
if (arr[i] == 1) {
res = res+Math.pow(-2, xx);
xx++;
}else{
xx++;
}
}
return (int)res;
}
public static String intToLowBinaryString(int N) {
StringBuilder sb = new StringBuilder();
while (N != 0) {
int r = N % -2;
N = N / -2;
if (r < 0) {
r = r + 2;
N = N+1;
}
sb.append(r);
}
return sb.length() > 0 ? sb.reverse().toString() : "0";
}
}
]]>利用office的在线预览,vue的依赖,转换成pdf进行预览。三个办法我都试了。微软官方的office在线预览的话需要一个公网ip,同时有的时候还需要你科学上网,这个直接寄。
word官方预览链接(支持三种格式,部分word带特殊符号或流程图无法显示)
const routeUrl = file.url; // 文件路径
const url = encodeURIComponent(routeUrl);
const officeUrl = "https://view.xdocin.com/view?src=" + url;
office官方预览(pdf不能展示)
let officeUrl = 'http://view.officeapps.live.com/op/view.aspx?src='+url
vue的依赖的话,GitHub上搜索一下会有很多。vue-office这个项目很棒,可惜我引入包和标签的时候会报错,人家用的是js,而我用的是ts,这个也pass了。这个是项目地址,非常棒的一个项目,可以参考学习一下:https://github.com/501351981/vue-office
到最后,还要回到word转换成office的破办法,这是我最难接受的一个,用的人一多,后端压力就大了,万一流量上来了,服务器直接开摆,笑死。但是实在是没办法了,退退退而求其次,只能这样了。主要思路是前端把文件id获取到,去数据库搜索到存放word的url地址,后端通过url地址把doc文档转换成输入流,然后通过输出流存放到一个专属的pdf文件夹,再把这个pdf生成链接给前端,前端显示的话就用这一个链接进行显示。
前端代码
<template>
<el-dialog v-model="visible" :title="$t('文件预览')" :close-on-click-modal="false" :close-on-press-escape="false">
<div v-if="fileMessage.fileType == 'jpg' ||
fileMessage.fileType == 'png' ||
fileMessage.fileType == 'ico' ||
fileMessage.fileType == 'gif' ||
fileMessage.fileType == 'webp'
">
<el-image style="width: 100%" :src="fileMessage.getFilePath"> </el-image>
</div>
<div v-else-if="fileMessage.fileType == 'docx' || fileMessage.fileType == 'doc'">
<embed :src="result.url" style="width: 100%; height: 600px" />
<!-- <h2 style="text-align: center;color: brown; ">暂不支持doc、docx格式!</h2> -->
</div>
<div v-else-if="fileMessage.fileType == 'xlsx' || fileMessage.fileType == 'xls'">
<h2 style="text-align: center;color: brown; ">暂不支持该xlsx、xls格式!</h2>
</div>
<div v-else-if="fileMessage.fileType == 'pdf'">
<embed :src="fileMessage.getFilePath" style="width: 100%; height: 600px" />
</div>
<div v-else>
<h2 style="text-align: center;color: brown; ">暂不支持该文件格式!</h2>
</div>
<template v-slot:footer>
<el-button @click="visible = false">{{ $t("退出预览") }}</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import baseService from "@/service/baseService";
import { ElMessage } from "element-plus";
const visible = ref(false);
const dataFormRef = ref();
const fileMessage = reactive({
getFilePath: "",
fileType: ""
});
const dataForm = reactive({
uploadFileId: "",
uploadCompanyName: "",
uploadFileName: "",
uploadFilePath: "",
uploadUserName: "",
uploadFileTime: "",
});
const init = (uploadFileId?: number) => {
visible.value = true;
dataForm.uploadFileId = "";
//重置表单数据
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
if (uploadFileId) {
getInfo(uploadFileId);
}
};
const result = reactive({
url: "",
msg: ""
});
//var showType: string;
// 获取信息
const getInfo = (uploadFileId: number) => {
baseService.get("/uploadFile/xyjqfileupload/" + uploadFileId).then((res) => {
Object.assign(dataForm, res.data);
if (dataForm.uploadFilePath != "") {
fileMessage.getFilePath = dataForm.uploadFilePath; //获取到图片的路径,写在这里面才管用,写在外面,第一次请求时候没有数据
//console.log(getFilePath.substring(getFilePath.lastIndexOf(".") + 1, getFilePath.length));
//console.log(getFilePath);
fileMessage.fileType = fileMessage.getFilePath.substring(fileMessage.getFilePath.lastIndexOf(".") + 1, fileMessage.getFilePath.length);
//showType = fileType;
console.log("文件类型:" + fileMessage.fileType);
console.log(fileMessage.getFilePath);
if (fileMessage.fileType == "doc" || fileMessage.fileType == "docx") {
baseService
.post(`/uploadFile/xyjqfileupload/pdfUrl/${uploadFileId}`)
.then((res) => {
if (res.code === 0) {
result.url = res.data;
console.log(res.data);
} else {
ElMessage.error(res.msg);
result.msg = res.msg;
}
})
.catch((err) => {
ElMessage.error(err.message);
});
}
} else {
ElMessage.error("链接地址为空,无法打开");
}
});
};
defineExpose({
init
});
</script>
主要核心代码是这里
// 获取信息
const getInfo = (uploadFileId: number) => {
baseService.get("/uploadFile/xyjqfileupload/" + uploadFileId).then((res) => {
Object.assign(dataForm, res.data);
if (dataForm.uploadFilePath != "") {
fileMessage.getFilePath = dataForm.uploadFilePath; //获取到图片的路径,写在这里面才管用,写在外面,第一次请求时候没有数据
//console.log(getFilePath.substring(getFilePath.lastIndexOf(".") + 1, getFilePath.length));
//console.log(getFilePath);
fileMessage.fileType = fileMessage.getFilePath.substring(fileMessage.getFilePath.lastIndexOf(".") + 1, fileMessage.getFilePath.length);
//showType = fileType;
console.log("文件类型:" + fileMessage.fileType);
console.log(fileMessage.getFilePath);
if (fileMessage.fileType == "doc" || fileMessage.fileType == "docx") {
baseService
.post(`/uploadFile/xyjqfileupload/pdfUrl/${uploadFileId}`)
.then((res) => {
if (res.code === 0) {
result.url = res.data;
console.log(res.data);
} else {
ElMessage.error(res.msg);
result.msg = res.msg;
}
})
.catch((err) => {
ElMessage.error(err.message);
});
}
} else {
ElMessage.error("链接地址为空,无法打开");
}
});
};
主要就是获取到文件之后看看文件后缀,欸!是doc或者docx格式的,那就继续post后端,把文件id发过去,这样后端就能通过id在数据库里面找到文件的在线链接。然后进行下载解析word。
后端部分代码
@PostMapping("/pdfUrl/{fileId}")
@ApiOperation("WORD转换PdF")
@LogOperation("WORD转换pdf")
public Result wordToPdf(@PathVariable("fileId") String fileId) throws Exception {
XyjqFileUploadEntity xyjqFileUploadEntity = xyjqFileUploadService.selectById(fileId);
//获取到url地址
String url = xyjqFileUploadEntity.getUploadFilePath();
//给新保存的文件起一个新的名字
String strFileNameAndPoint = url.substring(url.lastIndexOf("/")+1);
String newFileName = strFileNameAndPoint.substring(0,strFileNameAndPoint.lastIndexOf("."))+"_pdf.pdf";
System.out.println(newFileName);
String paths = "D:\\Archive\\Desktop\\uploadFile\\WordToPdf\\"+newFileName;
//先提前构建一个生成的pdf保存路径
File outputFile = new File(paths);
//先把获取到的文件转换成输入流的模式
URL receiveUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection)receiveUrl.openConnection();
//设置超时间为3秒
conn.setConnectTimeout(3*1000);
//将传输过来的doc文件转换成流
InputStream docxInputStream = conn.getInputStream();
try {
OutputStream outputStream = new FileOutputStream(outputFile);
IConverter converter = LocalConverter.builder().build();
converter.
convert(docxInputStream).as(DocumentType.DOCX).//作为doc格式
to(outputStream).as(DocumentType.PDF).
execute();
outputStream.close();
System.out.println("Word To Pdf 转换完成");
} catch (Exception e) {
e.printStackTrace();
}
//将file格式转换成MultipartFile格式
File file = new File(paths);
MultipartFile cMultiFile = getMultipartFile(file);
//获取到新的url返还给前端进行预览
String newUrl = OSSFactory.build().uploadSuffix(cMultiFile.getBytes(), "pdf");
System.out.println(newUrl);
return new Result().ok(newUrl);
}
public static MultipartFile getMultipartFile(File file) {
DiskFileItem item = new DiskFileItem("file"
, MediaType.MULTIPART_FORM_DATA_VALUE
, true
, file.getName()
, (int)file.length()
, file.getParentFile());
try {
OutputStream os = item.getOutputStream();
os.write(FileUtils.readFileToByteArray(file));
} catch (IOException e) {
e.printStackTrace();
}
return new CommonsMultipartFile(item);
}
我开始是想传输url的,但是post之后,发现失败,原来前端不承认这样的链接啊,我也是太天真了,所以只能这么做了。
哦对,还需要引入两个依赖
<!-- 转换成PDF-->
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-local</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-transformer-msoffice-word</artifactId>
<version>1.0.3</version>
</dependency>
基本情况是这个样子,中间还少了一些判断,比如第二次打开的时候,这个链接其实以及生成一个pdf了,再次预览的话,又要生成一个一样的就覆盖了,实在是太占用资源了,还不如去mysql里面做个表去保存这个生成的预览pdf链接,然后直接返还,这样就能再一定程度节省服务器资源了。目前没写主要是太困了,我需要睡觉了。。。想起来再写吧。
]]>想把
[1651114259328487426]
截取成
1651114259328487426
我知道是substring从1开始截取,但是为什么最后是length()-1呢,一开始-2我还很疑惑,看了一些文档才反应过来。
原来是最后的索引是不包括的呀。。。自己真的蠢到家了,自己把自己蠢到了,说到底基础还是不扎实。
Java截取最后一个/后面的所有字符
String imgUrl = "http://127.0.0.1:8080/cms/ReadAddress/1479805098158.jpg";
String image = imgUrl.substring(imgUrl.lastIndexOf("/")+1);
]]>
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-04-03 00:08:57.651 ERROR 15032 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.22.jar:5.3.22]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.22.jar:5.3.22]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.10.jar:2.6.10]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:745) ~[spring-boot-2.6.10.jar:2.6.10]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:420) ~[spring-boot-2.6.10.jar:2.6.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.6.10.jar:2.6.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) ~[spring-boot-2.6.10.jar:2.6.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.6.10.jar:2.6.10]
at com.wonder.UselessToolsApplication.main(UselessToolsApplication.java:14) ~[classes/:na]
Caused by: java.lang.NullPointerException: null
at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:112) ~[springfox-spi-2.9.2.jar:null]
at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:109) ~[springfox-spi-2.9.2.jar:null]
at com.google.common.collect.ComparatorOrdering.compare(ComparatorOrdering.java:37) ~[guava-20.0.jar:na]
at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na]
at java.base/java.util.TimSort.sort(TimSort.java:220) ~[na:na]
at java.base/java.util.Arrays.sort(Arrays.java:1441) ~[na:na]
at com.google.common.collect.Ordering.sortedCopy(Ordering.java:855) ~[guava-20.0.jar:na]
at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:57) ~[springfox-spring-web-2.9.2.jar:null]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:138) ~[springfox-spring-web-2.9.2.jar:null]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:135) ~[springfox-spring-web-2.9.2.jar:null]
at com.google.common.collect.Iterators$7.transform(Iterators.java:750) ~[guava-20.0.jar:na]
at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:52) ~[guava-20.0.jar:na]
at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:50) ~[guava-20.0.jar:na]
at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:249) ~[guava-20.0.jar:na]
at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:209) ~[guava-20.0.jar:na]
at com.google.common.collect.FluentIterable.toList(FluentIterable.java:614) ~[guava-20.0.jar:na]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.defaultContextBuilder(DocumentationPluginsBootstrapper.java:111) ~[springfox-spring-web-2.9.2.jar:null]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.buildContext(DocumentationPluginsBootstrapper.java:96) ~[springfox-spring-web-2.9.2.jar:null]
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167) ~[springfox-spring-web-2.9.2.jar:null]
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.22.jar:5.3.22]
... 14 common frames omitted
因为Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
比如下面版本组合是兼容的
Spring Boot版本 | Swagger 版本 |
---|---|
2.5.6 | 2.9.2 |
比如下面版本组合是兼容的
Spring Boot版本 | Swagger 版本 |
---|---|
2.6.5 | 3.0.0 |
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
我没找到这个java文件。。。。没试过,估计应该可以。。
参考地址:https://github.com/springfox/springfox/issues/3462
spring.mvc.pathmatch.matching-strategy
to ant-path-matcher
hack springfox WebMvcRequestHandlerProvider to filter out actuator controllers which don't respect spring.mvc.pathmatch.matching-strategy
public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext, HandlerMethodResolver methodResolver,
List<RequestMappingInfoHandlerMapping> handlerMappings) {
this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
.apis(RequestHandlerSelectors.basePackage("com.wonder.controller"))
这里填写扫描包,对应类添加@API
package com.wonder.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import javax.servlet.ServletContext;
import java.util.Optional;
@Configuration // 标明是配置类
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2) // DocumentationType.SWAGGER_2 固定的,代表swagger2
// .groupName("分布式任务系统") // 如果配置多个文档的时候,那么需要配置groupName来分组标识
.apiInfo(apiInfo()) // 用于生成API信息
.select() // select()函数返回一个ApiSelectorBuilder实例,用来控制接口被swagger做成文档
.apis(RequestHandlerSelectors.basePackage("com.wonder.controller")) // 用于指定扫描哪个包下的接口
.paths(PathSelectors.any())// 选择所有的API,如果你想只为部分API生成文档,可以配置这里
.build();
}
/**
* 用于定义API主界面的信息,比如可以声明所有的API的总标题、描述、版本
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("项目API") // 可以用来自定义API的主标题
.description("项目SwaggerAPI管理") // 可以用来描述整体的API
//.termsOfServiceUrl("") // 用于定义服务的域名
.version("1.0") // 可以用来定义版本。
.build(); //
}
}
]]> <!--通过ip查询客户端位置-->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Alibaba Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
首先先获取到访问者的ip地址
访问必须是通过公网访问,要是内网互ip访问相测试的话,会返回127.0.0.1,或者访问者的内网ip地址。
通过X-FORWARDED-FOR等信息。跟踪原有的客户端IP地址和原来客户端请求的服务器地址。
package com.wonder.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class getIPutils {
public static String resClientIP(HttpServletRequest request) {
String ip = getClientIpAddress(request);
boolean isInner = isInnerIP(ip);
if (!isInner) {
return ip;
}else {
return "内网ip";
}
}
public static String getClientIpAddress(HttpServletRequest request) {
// 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
String headerName = "x-forwarded-for";
String ip = request.getHeader(headerName);
if (null != ip && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个IP值,第一个IP才是真实IP,它们按照英文逗号','分割
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (checkIp(ip)) {
headerName = "Proxy-Client-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "WL-Proxy-Client-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "HTTP_CLIENT_IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "HTTP_X_FORWARDED_FOR";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "X-Real-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "remote addr";
ip = request.getRemoteAddr();
// 127.0.0.1 ipv4, 0:0:0:0:0:0:0:1 ipv6
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
System.out.println("getClientIp IP is " + ip + ", headerName = " + headerName);
return ip;
}
private static boolean checkIp(String ip) {
if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
return true;
}
return false;
}
/**
* 判断IP是否是内网地址
* @param ipAddress ip地址
* @return 是否是内网地址
*/
public static boolean isInnerIP(String ipAddress) {
boolean isInnerIp;
long ipNum = getIpNum(ipAddress);
/**
私有IP:A类 10.0.0.0-10.255.255.255
B类 172.16.0.0-172.31.255.255
C类 192.168.0.0-192.168.255.255
还有127这个网段是环回地址
**/
long aBegin = getIpNum("10.0.0.0");
long aEnd = getIpNum("10.255.255.255");
long bBegin = getIpNum("172.16.0.0");
long bEnd = getIpNum("172.31.255.255");
long cBegin = getIpNum("192.168.0.0");
long cEnd = getIpNum("192.168.255.255");
isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd)
|| ipAddress.equals("127.0.0.1");
return isInnerIp;
}
private static long getIpNum(String ipAddress) {
String[] ip = ipAddress.split("\\.");
long a = Integer.parseInt(ip[0]);
long b = Integer.parseInt(ip[1]);
long c = Integer.parseInt(ip[2]);
long d = Integer.parseInt(ip[3]);
return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
}
private static boolean isInner(long userIp, long begin, long end) {
return (userIp >= begin) && (userIp <= end);
}
public static String getRealIP(HttpServletRequest request){
// 获取客户端ip地址
String clientIp = request.getHeader("x-forwarded-for");
if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
clientIp = request.getRemoteAddr();
}
String[] clientIps = clientIp.split(",");
if(clientIps.length <= 1) return clientIp.trim();
// 判断是否来自CDN
if(isComefromCDN(request)){
if(clientIps.length>=2) return clientIps[clientIps.length-2].trim();
}
return clientIps[clientIps.length-1].trim();
}
private static boolean isComefromCDN(HttpServletRequest request) {
String host = request.getHeader("host");
return host.contains("www.189.cn") ||host.contains("shouji.189.cn") || host.contains(
"image2.chinatelecom-ec.com") || host.contains(
"image1.chinatelecom-ec.com");
}
}
X-Forwarded-For
记录一个请求从客户端出发到目标服务器过程中经历的代理,或者负载平衡设备的IP。这是由缓存代理软件 Squid 引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。格式为X-Forwarded-For:client1,proxy1,proxy2,一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器的ip。现在大部分的代理都会加上这个请求头。Proxy-Client-IP/WLProxy-Client-IP
这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是他的weblogic插件加上的头。HTTP_CLIENT_IP
有些代理服务器会加上此请求头。X-Real-IP nginx
代理一般会加上此请求头。通过上面返还的ip地址进行解析,具体方式可以参考Ip2region的官方文档。
package com.wonder.utils;
import com.alibaba.fastjson.JSONObject;
import org.lionsoul.ip2region.xdb.Searcher;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class FromIpToCtiy {
//https://github.com/lionsoul2014/ip2region/tree/master/binding/java
public static String resLocation(String ip) {
String dbPath = "src/main/resources/ip2region.xdb";
// 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
byte[] vIndex;
try {
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
return "{\"error\":\"加载索引失败\"}";
}
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher;
try {
searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
} catch (Exception e) {
System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
return "{\"error\":\"创建缓存搜索器失败\"}";
}
// 3、查询
long sTime;
String region = null;
long cost = 0;
try {
sTime= System.nanoTime();
region= searcher.search(ip);
cost= TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
// 4、关闭资源
searcher.close();
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
Map<String,String> resmap = new HashMap<>();
resmap.put("location",region);
resmap.put("ioCount", String.valueOf(searcher.getIOCount()));
resmap.put("cost", cost+"μs");
return JSONObject.toJSONString(resmap);
// 备注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的制度 vIndex 缓存。
}
}
说实在的,其实没啥技术含量,就是互相调用查看的,通过客户端的请求进行解析。然后查询地址的,也就是通过一个人家写的库进行查询。很简单的。欸。。目前没啥好想法了。
]]>