1、common下模块 缺少.iml文件
或者
mvn idea:module
2、common下子模块未生成jar包
3、common下子工程,类需要spring管理
spring.factories中添加spring管理类的路径
4、富文本加密上传
问题:前台vue editor组件上传内容,图片地址会莫名丢失,有偶发性bug
解决:上传前,encode或encodeURIComponent编码,展示前 解码
//编码
this.form.particulars = encodeURI(this.form.particulars)
//解码
this.form.particulars = decodeURI(this.form.particulars)
encodeURI是对url中的查询字符串部分进行转义
encodeURIComponent对整个url进行转义,包括空格、英文冒号、斜杠等
至于decodeURI和decodeURIComponent,只要知道decodeURI和encodeURI是互逆操作,decodeURIComponent和encodeURIComponent是互逆操作
5、富文本上传,标签被xss过滤
nacos 网关 yml 中添加
6、system模块中集成websocket
pom.xml中添加
<!-- websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
package com.quan.system.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created with IntelliJ IDEA.
* @ Description:
* @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@Component
@Slf4j
@Service
@ServerEndpoint(value = "/imserver/{sid}")
public class WebSocketServer {
//当前在线连接数
private static int onlineCount = 0;
//存放每个客户端对应的MyWebSocket对象
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
private Session session;
//接收sid
private String sid = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
this.sid = sid;
addOnlineCount(); //在线数加1
try {
sendMessage("conn_success");
log.info("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());
} catch (IOException e) {
log.error("websocket IO Exception");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("释放的sid为:"+sid);
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @ Param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口" + sid + "的信息:" + message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @ Param session
* @ Param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
log.info("推送消息到窗口" + sid + ",推送内容:" + message);
for (WebSocketServer item : webSocketSet) {
try {
//为null则全部推送
if (sid == null) {
// item.sendMessage(message);
} else if (item.sid.equals(sid)) {
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
package com.quan.system.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 注入一个ServerEndpointExporter,该bean会自动舌侧使用 @ServerEndpoint注解声明websocket endpoind
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
WebMvcConfig中增加 不拦截地址
nocas网关中增加白名单
前台websocket通信地址为
'ws://localhost:8080/system/imserver/'
7、vue中配置排序参数
后台默认集成了PageHelper,可在前端查询参数中配置 orderByColumn排序的列,isAsc排序方向即可
8、修改菜单栏宽度
9、main.js 判断开发环境,生成环境
10、新增数据返回自增主键
<insert id="insertNotice" parameterType="SysNotice" keyColumn="notice_id" keyProperty="noticeId" useGeneratedKeys="true">
11、command line is too long
12、[loadNacosData,104] - parse data from Nacos error
配置文件中有中文,而nacos读取配置文件时默认编码为utf-8,而通过cmd启动项目默认使用gbk。
java -jar -Dfile.encoding=utf-8 包名.jar
13、打包成运行jar,jar包和class文件分离
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!-- 是否要把第三方jar加入到类构建路径 -->
<addClasspath>true</addClasspath>
<!-- 外部依赖jar包的最终位置 -->
<classpathPrefix>lib/</classpathPrefix>
<!--指定jar程序入口-->
<mainClass>com.quan.system.QuanSystemApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!--拷贝依赖到jar外面的lib目录-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 拷贝项目依赖包到lib/目录下 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
14、自定义过滤器filter
gateway-dev.yml 中给需要配置过滤器的子模块增加配置,如下给system模块增加CacheRequestFilter 和 ApiFilter
网关模块中增加ApiFilter过滤器
ApiFilter.java
package com.cazql.gateway.filter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.cazql.common.core.utils.ServletUtils;
import com.cazql.common.core.utils.StringUtils;
import com.cazql.gateway.config.properties.CaptchaProperties;
import com.cazql.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
/**
* 外部接口过滤器
*
* @author ruoyi
*/
@Component
public class ApiFilter extends AbstractGatewayFilterFactory<Object>
{
private final static String[] VALIDATE_URL = new String[] { "/system/api" };
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非VALIDATE_URL 中请求,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL))
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSON.parseObject(rspStr);
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
15、SM2加解密
package com.cazql.gateway.filter;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.cazql.common.core.utils.uuid.UUID;
import java.io.*;
import java.security.KeyPair;
public class t {
public static void main(String[] args) throws Exception {
System.out.println(UUID.randomUUID().toString().replace("-","").length());
String text = "我是一段测试aaaa";
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
System.out.println(HexUtil.encodeHexStr(privateKey));
System.out.println(HexUtil.encodeHexStr(publicKey));
outPk(privateKey,"aaa.privateKey");
outPk(publicKey,"aaa.publicKey");
privateKey = reakPk("aaa.privateKey");
publicKey = reakPk("aaa.publicKey");
//公钥加密
SM2 sm2 = SmUtil.sm2(null, publicKey);
String encryptStr = sm2.encryptBcd(text, KeyType.PublicKey);
// 私钥解密
sm2 = SmUtil.sm2(privateKey, null);
String decryptStr = StrUtil.utf8Str(sm2.decryptFromBcd(encryptStr, KeyType.PrivateKey));
System.out.println(decryptStr);
}
public static void outPk(byte[] pk,String fileName) throws Exception {
File file = new File("C:\\Users\\Lenovo\\Desktop\\"+fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(pk);
fos.flush();
fos.close();
}
public static byte[] reakPk(String fileName) throws Exception{
File file = new File("C:\\Users\\Lenovo\\Desktop\\"+fileName);
int _bufferSize = (int) file.length();
//定义buffer缓冲区大小
byte[] buffer = new byte[_bufferSize];
FileInputStream fos = new FileInputStream(file);
fos.read(buffer);
fos.close();
return buffer;
}
}
16、SM4加解密
//随机生成sm4加密key
String sm4Key = "GJwsXX_BzW=gJWJW";
System.out.println("sm4Key:"+sm4Key);
//sm4加密业务报文
SM4 sm4 = new SM4(Mode.ECB, Padding.PKCS5Padding,sm4Key.getBytes());
String sm4Encrypt = sm4.encryptBase64("12345");
System.out.println("sm4加密报文:"+sm4Encrypt);
String sm4Decrypt = sm4.decryptStr(sm4Encrypt);
System.out.println("sm4解密报文:"+sm4Decrypt);
System.out.println("=============================");
17、vue 前台sm4加解密
npm install gm-crypt
utils下建sm4.js
const SM4 = require('gm-crypt').sm4
let sm4Config = {
key: 'GJwsXX_BzW=gJWJW',
mode: 'ecb', // 加密的方式有两种,ecb和cbc两种,也是看后端如何定义的,不过要是cbc的话下面还要加一个iv的参数,ecb不用
// optional: this is the cipher data's type; Can be 'base64' or 'text'
cipherType: 'base64' // default is base64
}
let sm4 = new SM4(sm4Config)
export default sm4
main.js
import SM4 from '@/utils/sm4'
Vue.prototype.SM4 = SM4
vue页面中使用
let Account = this.SM4.encrypt("12345"); //账号加密
let Account1 = this.SM4.decrypt(Account); //账号解密
18、nginx Request entity too large
client_max_body_size 100m;
19、富文本增加功能按钮提示
新建quill-title.js
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-video': '视频',
'ql-clean': '清除字体样式'
}
export function addQuillTitle () {
const oToolBar = document.querySelector('.ql-toolbar'),
aButton = oToolBar.querySelectorAll('button'),
aSelect = oToolBar.querySelectorAll('select')
aButton.forEach(function (item) {
if (item.className === 'ql-script') {
item.value === 'sub' ? item.title = '下标' : item.title = '上标'
} else if (item.className === 'ql-indent') {
item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进'
} else {
item.title = titleConfig[item.classList[0]]
}
})
aSelect.forEach(function (item) {
item.parentNode.title = titleConfig[item.classList[0]]
})
}
页面引入
import { addQuillTitle } from './quill-title.js'
mounted() {
addQuillTitle();
},
20、取消分页合理化
21、高并发下防止余额为负
1.查询时加悲观锁
2.service方法中增加事务注解
3.controller业务处理层增加事务注解
业务处理层最终执行完 update 后 锁解除,否则排序等待锁解除,这样就不会发生超扣
利用的是行级锁,上锁->更新->解锁
22、nginx配置ssl
阿里云下载nginx证书
上传到cert下
server配置
server {
listen 443 default_server ssl;
server_name www.域名;
ssl_certificate cert/9057355_域名.pem; #存放.pem证书的路径
ssl_certificate_key cert/9057355_域名.key; #存放.key证书的路径
server_tokens off;
......
}
重启nginx即可
23、jar包启动配置ssl
下载tomcat类型证书
pfx证书放到resources下
重启jar即可
24、仅本人数据权限配置
@DataScope(userAlias = "u")
<!-- 数据范围过滤 -->
${params.dataScope}
25、本地jar包 打包后没在MANIFEST.MF中导致启动报错类找不到
本地jar包放到 src下
<!-- 引入本地lib包 -->
<!-- 天翼云对象存储oss start -->
<dependency>
<groupId>cn.ctyun</groupId>
<artifactId>oos-sdk</artifactId>
<scope>system</scope>
<version>6.5.0</version>
<systemPath>${project.basedir}/src/lib/oos-sdk-6.5.0.jar</systemPath>
</dependency>
添加本地jar包
<manifestEntries>
<Class-Path>.</Class-Path>
<Class-Path>lib/oos-sdk-6.5.0.jar</Class-Path>
</manifestEntries>
26、支持多字段排序
PageDomain.java 中增加以下代码
//支持多字段排序 start
String orderByStr = "";
String[] columns = orderByColumn.split(",");
String[] isAscs = isAsc.split(",");
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
String order = isAscs[i];
if("ascending".equals(order)){//多字段,不被格式化,特殊处理
order = "asc";
}
if("descending".equals(order)){
order = "desc";
}
orderByStr += "," + StringUtils.toUnderScoreCase(column) + " " + order;
}
orderByStr = orderByStr.substring(1, orderByStr.length());
//支持多字段排序 end
return orderByStr;
请求参数如下:
27、增加滑块验证码
安装插件
npm install --save vue-monoplasty-slide-verify
main.js中
//滑块验证码
import SlideVerify from 'vue-monoplasty-slide-verify';
Vue.use(SlideVerify);
//全局组件挂载
Vue.component('SliderCode',SliderCode)
//滑块验证码
import SliderCode from '@/components/SliderCode'
添加子组件SliderCode
<!--滑块验证码
使用说明:
1.父组件调用open方法打开滑块验证码
2.验证通过后调用父组件 verifySuccess 方法
示例:<slider-code ref="sliderCodeRef" @verifySuccess="verifySuccess" />
-->
<template>
<div>
<el-dialog title="滑块验证码" :visible.sync="VerifyShow" width="370px" :close-on-click-modal="false">
<div class="page-slidecode">
<!-- l:滑块的边长
r:滑块突出圆的半径
w:canvas画布的宽
h:canvas画布的高
imgs:背景图数组(放到public目录下)
sliderText:滑块底纹文字
accuracy:滑动验证的误差范围,默认值为5
show:是否显示刷新按钮
success:验证码匹配成功的回调
fail:验证码未匹配的回调
refresh:点击刷新按钮后的回调函数
fulfilled:刷新成功之后的回调函数-->
<slide-verify
ref="slideblock"
:l="42"
:r="10"
:w="330"
:h="165"
:imgs="imgs"
:accuracy="accuracy"
:slider-text="text"
@again="onAgain"
@fulfilled="onFulfilled"
@success="onSuccess"
@fail="onFail"
@refresh="onRefresh"
/>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// 滑块验证
VerifyShow: false,
accuracy: 5,
imgs: ["/src/001.png"],
text: "向右滑动~"
};
},
methods: {
open() {
this.VerifyShow = true;
this.onAgain();
},
onFail() {
this.$message({
message: "验证未通过",
type: "error"
});
},
onSuccess() {
console.log("验证通过");
this.VerifyShow = false;
this.$emit("verifySuccess");
},
onRefresh() {
// console.log('点击了刷新小图标');
},
onFulfilled() {
// console.log('刷新成功啦!');
},
onAgain() {
// console.log('检测到非人为操作的哦!');
// 刷新
this.$refs.slideblock.reset();
}
}
};
</script>
<style scoped>
.el-dialog__header {
padding: unset;
}
</style>
使用
<slider-code ref="sliderCodeRef" @verifySuccess="verifySuccess" />
//打开滑块验证码的校验
this.$refs.sliderCodeRef.open();
//验证通过执行业务逻辑
verifySuccess() {
alert("验证通过");
},
27、禁用Actuator端点(Actuator未授权访问漏洞)
yml中增加
# 禁用Actuator端点
management:
server:
port: -1
28、禁用swagger
29、调用存储过程传参 报错
### Error querying database. Cause: java.lang.ClassCastException: net.sf.jsqlparser.statement.execute.Execute cannot be cast to net.sf.jsqlparser.statement.select.Select
### Cause: java.lang.ClassCastException: net.sf.jsqlparser.statement.execute.Execute cannot be cast to net.sf.jsqlparser.statement.select.Select
原来的写法
<select id="listNew" parameterType="java.util.Map" >
CALL update_dy_stock_new(
#{receiveSite},
#{coalName},
#{planCalorificValue},
#{planSulfurContent},
#{isSpecialUnitAccounting},
#{specialAccountingId},
#{financeAuditStatus}
)
</select>
改后的写法
<update id="listNew" parameterType="java.util.Map" >
CALL update_dy_stock_new(
#{receiveSite},
#{coalName},
#{planCalorificValue},
#{planSulfurContent},
#{isSpecialUnitAccounting},
#{specialAccountingId},
#{financeAuditStatus}
)
</update>
原因:用select 标签,会有一步count统计,导致出错,换其他标签即可
30、vue 遍历移除list中item
//删除请求参数
delParams(item) {
let targetIndex = this.form.requestParams.findIndex(
itemTemp => itemTemp === item
);
if (targetIndex !== -1) {
this.form.requestParams.splice(targetIndex, 1);
}
},
31、swagger配置
请求地址:http://localhost:8080/swagger-ui/index.html
SwaggerConfig.java 中配置
配置请求头header
.globalRequestParameters(Collections.singletonList(new RequestParameterBuilder()
.name("authorizationTags")
.description(" 授权标识 ")
.in(ParameterType.HEADER)
.required(true)
.build()))
@ApiOperation("获取共享数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "databaseName", value = "库名称", required = true, dataType = "string", paramType = "path"),
@ApiImplicitParam(name = "tableName", value = "表名称", required = true, dataType = "string", paramType = "path"),
@ApiImplicitParam(name = "authorizationTags", value = "授权标识", required = true, dataType = "string", paramType = "header"),
})
@PostMapping("/share/{databaseName}/{tableName}")
public AjaxResult share(@RequestHeader("authorizationTags") String authorizationTags, @RequestBody String body, @PathVariable("databaseName") String databaseName,
@PathVariable("tableName") String tableName) {
32、mybatis返回map 保留空值
<setting name="callSettersOnNulls" value="true"/>
33、根据模板导出word文件
pom.xml
<!-- 根据模板导出excel/word -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
controller
@GetMapping("/downloadTemplate/{id}")
@Operation(summary = "下载模板")
public void downloadTemplate(HttpServletResponse response,@PathVariable("id") Long id) throws IOException {
PersonTemplateRespVO personTemplate = personTemplateService.getPersonTemplate(templateId);
String templateType = personTemplate.getTemplateType();
/*文本*/
Map<String, Object> map = new HashMap<>();
map.put("name", "Sayi");
map.put("IDNumber", "612255");
// url资源转化为file流
File file = FileUtils.urlToFile(personTemplate.getFile());
// 获取文件绝对路径
String absolutePath = file.getAbsolutePath();
ServletOutputStream outputStream = response.getOutputStream();
if("Word".equals(templateType)){
// 模板注意 用{{}} 来表示你要用的变量
XWPFTemplate template = XWPFTemplate.compile(absolutePath).render(map);
file.delete();
template.write(outputStream);
outputStream.close();
template.close();
}else if("Excel".equals(templateType)){
// 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
String fileName = absolutePath + "simpleFill" + System.currentTimeMillis() + ".xlsx";
// 这里 会填充到第一个sheet, 然后文件流会自动关闭
EasyExcel.write(fileName).withTemplate(absolutePath).sheet().doFill(map);
file.delete();
//以流的形式写出
//设置响应头
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", 999 + ".xlsx"));
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", -1);
response.setCharacterEncoding("UTF-8");
//将文件流写入响应流
FileInputStream fis = new FileInputStream(fileName);
IOUtils.copy(fis, outputStream);
//关闭文件流和响应输出流
fis.close();
FileUtils.deleteFileByPath(fileName);
outputStream.flush();
outputStream.close();
}
}
urlToFile
/**
* url资源转化为file流
* @param url2
* @return
*/
public static File urlToFile(String url2) throws MalformedURLException {
URL url = new URL(url2);
InputStream is = null;
File file = null;
FileOutputStream fos = null;
try {
file = File.createTempFile("tmp", url2.substring(url2.lastIndexOf(".")));
URLConnection urlConn = null;
urlConn = url.openConnection();
is = urlConn.getInputStream();
fos = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int length;
while ((length = is.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
return file;
} catch (IOException e) {
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}
}
js
// 下载模板
export function downloadTemplate(id) {
return request({
url: '/system/person-template/downloadTemplate/'+id,
method: 'get',
responseType: 'blob'
})
}
vue
//下载
handleDownload(row) {
downloadTemplate(row.id).then(response => {
if (row.templateType == "Word") {
this.$download.word(response, row.name + ".docx");
} else if (row.templateType == "Excel") {
this.$download.excel(response, row.name + ".xls");
}
});
},
34、nginx开启gzip
gzip on; # 是否开启gzip
gzip_buffers 32 4K; # 缓冲(压缩在内存中缓冲几块? 每块多大?)
gzip_comp_level 6; # 推荐6压缩级别(级别越高,压的越小,越浪费CPU计算资源)
gzip_min_length 1k; # 开始压缩的最小长度
gzip_types text/plain application/javascript text/css application/xml text/xml; # 对哪些类型的文件用压缩 如txt,xml,html ,css
gzip_disable "MSIE [1-6]\."; # 配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_http_version 1.0; # 开始压缩的http协议版本开始压缩的http协议版本
gzip_vary on; # 是否传输gzip压缩标志
35、double运算精度缺失
//传入字符串形式的值作为参数,可以确保运算精度
//创建
BigDecimal a = BigDecimal.valueOf(10);
BigDecimal b = new BigDecimal(10);
BigDecimal c = new BigDecimal("0.1111111");
System.out.println("加法:"+a.add(b));
System.out.println("减法:"+a.subtract(b));
System.out.println("乘法:"+a.multiply(b));
//10:小数点后的位数 RoundingMode.HALF_DOWN:取舍模式
System.out.println("除法:"+a.divide(b,10, RoundingMode.HALF_DOWN));
System.out.println("设置小数点后两位:"+c.setScale(2,BigDecimal.ROUND_HALF_UP));
NumberUtil.round(taxAmount, 2).doubleValue()
36、vue 强制更新表格list对象值
getServiceListPersonPageTips(this.queryParams).then(response => {
var tagJson = response.data;
var listNew = [...this.list];
for (let i = 0; i < listNew.length; i++) {
var item = listNew[i];
let employeeId = item.employeeId;
let tags = tagJson[employeeId + ""];
if (tags == undefined) {
continue;
}
this.$set(this.list[i], "tags", tags);
}
});
37、el-checkbox 多选框的change 事件自定义传参
<el-checkbox
v-for="(item, index) in item2.children"
:checked="checked(item.id)"
@change="checked => checkChange(checked, item)"
>{{ formatLabel(item)}}</el-checkbox>
38、jar包启动内存不够:Native memory allocation (mmap) failed to map 354418688 bytes for committing reserved memory.
# 查看内存占用情况汇总
free -m
free -g
# 查看内存占用情况明细
top
M
# 查看指定进程的详细信息
ps -p pid -o pid,ppid,cmd,%cpu,%mem
# jar包启动限制内存占用率,启动内存为512M,最大运行内存为1024M
nohup java -jar -Xms512m -Xmx1024m /home/module-infra-biz.jar
39、el-input 只允许输入数值,最多两位小数
<el-input v-model="form.socialSecurityPayBase" placeholder="请输入社保统一缴纳基数" oninput="value=value.replace(/^\D*([0-9]\d*\.?\d{0,2})?.*$/,'$1')"></el-input>
40、封装子组件,数值组件,允许为负数
//父组件中
<NumberInput v-model.value="item.value" />
//子组件
<template>
<el-input-number
v-model="value"
:step="0.01"
class="num_input"
@input.native="changeInputPt2($event)"
style="width: 100%;"
></el-input-number>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: undefined
}
},
data() {
return {
};
},
watch: {
value(newVal) {
this.$emit('input', newVal);
}
},
methods: {
changeInputPt2(e) {
if (e.target.value.indexOf(".") >= 0) {
e.target.value = e.target.value.substring(
0,
e.target.value.indexOf(".") + 3
); // 这里采用截取 既可以限制第三位小数输入 也解决了数字输入框默认四舍五入问题
}
}
}
};
</script>
<style scoped>
.num_input /deep/.el-input-number__decrease {
display: none !important;
}
.num_input /deep/.el-input-number__increase {
display: none !important;
}
.num_input /deep/.el-input__inner {
padding: 0 !important;
text-align: left;
padding-left: 5px !important;
}
</style>
41、动态下拉框某项禁止选择
<el-select
:class="styleCss"
:popper-append-to-body="false"
multiple
style="width: 100%;"
v-model="item.invoiceDetailType"
placeholder="请选择发票明细类别"
@visible-change="filterInvoiceDetailTypes(form)"
>
<el-option
v-for="dict in invoiceDetailTypes"
:key="dict.value"
:label="dict.label"
:value="dict.value"
:disabled="dict.disabled"
/>
</el-select>
filterInvoiceDetailTypes(form) {
var useInvoiceDetailTypeList = [];
for (let i = 0; i < form.invoiceDetails.length; i++) {
const element = form.invoiceDetails[i];
useInvoiceDetailTypeList = useInvoiceDetailTypeList.concat(
element.invoiceDetailType
);
}
for (let j = 0; j < this.invoiceDetailTypes.length; j++) {
const element = this.invoiceDetailTypes[j];
if (useInvoiceDetailTypeList.indexOf(element.value) > -1) {
this.$set(this.invoiceDetailTypes[j], "disabled", true);
} else {
this.$set(this.invoiceDetailTypes[j], "disabled", false);
}
}
this.$forceUpdate();
},
// 初始化
this.invoiceDetailTypes = this.getDictDatas("invoice_detail_type");
for (let i = 0; i < this.invoiceDetailTypes.length; i++) {
const element = this.invoiceDetailTypes[i];
element.disabled = false;
}
42、点击一级菜单自动打开第一个子集有效菜单(顶部为一级菜单模式)
/src/components/TopNav/index.vue中
<!-- el-menu添加点击事件 -->
<el-menu
:default-active="activeMenu"
mode="horizontal"
@select="handleSelect"
class="topnav"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
>
// 菜单选择事件
handleSelect(key, keyPath) {
this.currentIndex = key;
const route = this.routers.find(item => item.path === key);
if (this.ishttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank");
} else if (!route || !route.children) {
// 没有子路由路径内部打开
this.$router.push({ path: key });
this.$store.dispatch("app/toggleSideBarHide", true);
} else {
// 显示左侧联动菜单
this.activeRoutes(key);
if (!this.$route.meta.link) {
this.$store.dispatch("app/toggleSideBarHide", false);
}
//判断如果有子集路由,自动打开子集第一个菜单
if (this.routesSun && this.routesSun.length > 0) {
let toPath = this.routesSun[0].path;
if (this.routesSun[0].children && this.routesSun[0].children.length > 0) {
toPath += "/" + this.routesSun[0].children[0].path;
}
this.$router.push({
path: toPath
});
}
}
},
// 当前激活的路由
activeRoutes(key) {
const routes = [];
if (this.childrenMenus && this.childrenMenus.length > 0) {
this.childrenMenus.map(item => {
if (
key === item.parentPath ||
(key === "index" && "" === item.path)
) {
routes.push(item);
}
});
}
if (routes.length > 0) {
this.$store.commit("SET_SIDEBAR_ROUTERS", routes);
} else {
this.$store.dispatch("app/toggleSideBarHide", true);
}
this.routesSun = routes;
},
43、vue 添加echarts地图
①下载地图json echarts图表集
<div id="echartMap" style="width: 100%; height: 100%;"></div>
import aaa from "./610000.json";
//初始化
mounted() {
echarts.registerMap("jingzhouMap", aaa);
},
var myChart = echarts.init(document.getElementById("echartMap"));
// 设置初始化的地图样式
// 在mounted函数中书写
const option = {
series: [
{
type: "map", // 设置图表类型为地图
map: "jingzhouMap", // 地图要加载的JSON文件
zoom: 0.9, // 地图缩放大小,这个根据自己需求,默认是1,不缩放
layoutCenter: ["45%", "50%"], // 定义地图中心在屏幕中的位置
// 如果宽高比大于 1 则宽度为 100,如果小于 1 则高度为 100,保证了不超过 100x100 的区域
layoutSize: 650,
aspectScale: 1, // 长宽比
roam: false,
itemStyle: {
// 这里就是设置item 的样式,也就是地图中每个区块的样式(区块就是JSON文件中包含经纬度信息的对象,获取的地图JSON文件中应该有一个数组,其中的每一个对象就对应一个区块)
normal: {
// 区块的基本样式
areaColor: "#2C49BD",
borderColor: "#41b8ff", // 区块的边界(边框)颜色
borderWidth: 1 // 区块的边界(边框)宽度
},
emphasis: {
label: {
show: true,
textStyle: {
color: '#ffffff' // 鼠标移入时文字颜色
}
},
// 区块高亮时的样式(鼠标移入的时候可以选中进行高亮显示)
areaColor: "#2C49BD",
borderColor: "#ffffff",
shadowBlur: 10, // 模糊度
shadowColor: "rgba(0,255,244,0.6)", // 模糊阴影的颜色
borderWidth: 2,
color: "#2C49BD" // 鼠标选择区域颜色
}
},
label: {
show: true, // 是否显示label
fontSize: 14, // 字体大小
color: "#fff" // 字体颜色
}
}
]
};
// 4. 显示地图
myChart.setOption(option); // 用 option 和 option2 效果一样
44、chrome 浏览器奔溃
重命名Chrome应用程序
-
打开文件资源管理器
-
打开 Chrome 应用程序文件夹,该文件夹应位于
C:\Program Files (x86)\Google\Chrome\Application下 -
右键单击 Chrome 应用程序并将其重命名为newbrowserapp
-
再次启动 Chrome 就可以了。
45、动态切换数据源
package com.sxhfhr.module.system.tenantFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
//web相关
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private TenantDsInterceptor tenantDsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册租户切换数据源拦截器
registry.addInterceptor(this.tenantDsInterceptor);
}
}
package com.sxhfhr.module.system.tenantFilter;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.sxhfhr.module.system.service.tenant.TenantService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
/**
* @author :扫地僧 租户切换拦截器
* @date :2022-12-28 上午 10:40:36
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
* @description:
**/
@Slf4j
@Component
public class TenantDsInterceptor implements HandlerInterceptor {
@Autowired
private TenantService tenantService;
@Autowired
private DataSource dataSource;
/**
* 在请求处理前调用
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
//只有登录的时候,切换一次数据源,之后都用切换后的数据源,避免每次接口调用都需要切换数据源
//log.info("经过多数据源Interceptor,当前路径是{}", requestURI);
String tenantId = request.getHeader("Tenant-Id");
//如果tenantId为空,则使用默认数据源
if (StringUtils.isNotEmpty(tenantId) && !"1".equals(tenantId)){
log.info("拿到的租户编码{}", tenantId);
tenantService.changeDsByTenantId(Long.parseLong(tenantId));
}
return true;
}
/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空当前线程数据源
//DynamicDataSourceContextHolder.clear();
}
}
@Resource
private DataSourceMapper dataSourceMapper;
@Autowired
private DataSource dataSource;
@Autowired
private DefaultDataSourceCreator dataSourceCreator;
/**
* 动态添加数据源 【注册mybatis动态数据源】
*
*/
@Override
public void changeDsByTenantId(Long tenantId) {
TenantDO tenant = this.getTenant(tenantId);
String dsName = tenantId + "";
DataSource dataSource1 = ((DynamicRoutingDataSource) dataSource).getDataSources().get(dsName);
if(dataSource1 != null){
DynamicDataSourceContextHolder.push(dsName);//动态切换数据源
return;
}
Long dataSourceId = tenant.getDataSourceId();
DataSourceRespVO dataSourceRespVO = dataSourceMapper.selectById(dataSourceId);
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty.setUrl(dataSourceRespVO.getConnectMsg());
dataSourceProperty.setPassword(dataSourceRespVO.getPsd());
dataSourceProperty.setDriverClassName("com.mysql.jdbc.Driver");
dataSourceProperty.setUsername(dataSourceRespVO.getUserName());
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
ds.addDataSource(dsName, dataSource);
DynamicDataSourceContextHolder.push(dsName);//动态切换数据源
}
46、vue2防止表单重复提交
utils下创建directive.js
// 阻止按钮重复提交
import Vue from 'vue'
// 阻止按钮重复提交
const preventRepeatClick = Vue.directive('preventRepeatClick', {
inserted (el, binding) {
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true
setTimeout(() => {
el.disabled = false
}, binding.value || 3000)
}
})
}
})
export { preventRepeatClick }
main.js引入
import { preventRepeatClick } from './utils/directive.js';
vue页面使用
<el-button type="primary" v-prevent-repeat-click @click="submitForm">确 定</el-button>