开发实战总结
总结了一些实际开发过程中需要注意的一些细节。(持续更新…)
MySQL
使用
st_distance_sphere
函数获取指定地点周边一定范围内的所有地点。例如:district表中保存了各个城市(city_code)的经纬度信息(longitude、latitude),使用以下sql查询上海市(121.797447, 31.166809)周边300公里内的城市
1
2
3select distinct city_code, st_distance_sphere(Point(longitude, latitude), Point(121.797447, 31.166809)) as distance
from district
where st_distance_sphere(Point(longitude, latitude), Point(121.797447, 31.166809)) < 300000;SQL join语法速记:
A inner join B on ...
取A和B的交集A left join B on ...
取A全部,B没有的对应的字段值为nullA right join B on ...
取B全部,A没有的对应的字段值为nullA full outer join B on ...
取并集,彼此没有的对应的字段值为null
SQL的where语句中,使用
=
(或!=
)去匹配给定的字段值时,匹配的结果中不包含表中该字段为null的数据。例如:当tt_data表中存在一条数据的字段data_type为null时,使用以下sql都无法查询到该条数据:
1
2select * from tt_data where data_type = 'A';
select * from tt_data where data_type != 'A';使用多线程分页同步数据时,会使用到常用的分页查询sql:
select * from table order by id asc limit n, m
。在数据量较大时,该sql在执行到最后几页的时候耗时会明显增加,原因是limit
语句会从表的第1行扫描到第n行,n越大,扫描的时间越长。下面是针对这种场景的一种优化方式:- 假设主键ID是自增的,查出带同步数据的最小主键ID(minId)和最大主键ID(maxId)
- 通过sql:
select * from table where id >= startId and id < endId order by id asc
查询得到每页需要同步的数据。其中startId目标数据页的起始行ID,endId为目标数据页的结束行ID,并且endId = startId + m
。特殊的,第一页的startId=minId,最后一页的endId=maxId。
在执行数据清理任务是,如果MySQL单表需要清除的数据超过全表数据的50%时,可采用以下方案:
- 创建临时表,表结构与原表结构相同
- 拷贝需要保留的数据到临时表
- 重命名临时表为原表名
- 删除原表
具体sql为:
1
2
3
4
5
6
7
8-- 创建临时表
CREATE TABLE `temp_table` LIKE `ori_table`;
-- 拷贝原表数据到临时表
INSERT INTO `temp_table` SELECT * FROM `ori_table` WHERE ...;
-- 重命名表
RENAME TABLE `ori_table` TO `ori_table_bak`, `temp_table` TO `ori_table`;
-- 移除原表
DROP TABLE `ori_table_bak`;配置MySQL连接参数
rewriteBatchedStatements=true
可启用批量插入优化,将多条数据插入语句批量提交只执行一次,可在一定程度上优化写入性能降低TPS。MySQL随机数sql:
1
2-- 获取500~1500之间的随机数
select round(rand() * 1000 + 500);在查询sql中使用
force index(idx)
强制此次查询使用指定索引,例如:1
select * from tt_data force index(idx) where ...;
MySQL执行sql报错:
You can't specify target table 'tt_data' for update in FROM clause
,改写sql之后解决。但是改写在后的sql中,delete from tt_data where id in (ids)
语句并没有并没有走主键索引进行查找删除,换成join临时表的方式使id in (ids)
正常走主键索引查询删除,效率提升明显。具体语句如下:1
2
3
4
5
6-- 原sql,会报错
delete from tt_data where id in (select id from tt_data where ...);
-- 改写后的sql
delete from tt_data where id in (select id from (select id from tt_data where ...));
-- 优化后的sql
delete a from tt_data a inner join (select id from tt_data where ...) b on a.id = b.id;
Java
RandomUtils.nextInt()
生成随机整数,RandomStringUtils
生成随机字符串。实现随机抽取集合里面的部分元素
Collections.shuffle(list)
将 list 元素循序打乱list.subList(0, loreResource.getQuesNum()); // subList(fromIndex, toIndex)的实际范围是[fromIndex, toIndex)
获取指定数量的元素。ListUtils.select()
方法,类似于 JQuery 数组的 filter 方法。System.out.println(Arrays.toString(someList.toArray()));
方法可以方便地打印List内容。Arrays.asList(T... a)
无法将基本类型转换为 List,原因是asList()
方法接收的是泛型的可变长参数,而基本类型(如int,char等)是无法泛型化的。使用asList()
对基本数据类型进行操作时需要使用基本数据类型的包装类。asList()
返回的 ArrayList 类型是Arrays
的一个内部类,没有实现add()
、remove()
等用于操作 ArrayList 的方法,当我们需要对asList()
返回的列表进行常用操作时需要对其进行转换,List list = new ArrayList(Arrays.asList(testArray));
Java类启动顺序,
static
静态代码先于构造方法。ThreadLocal变量一般使用
static
修饰。使用时,为了避免内存泄漏,在当前线程执行完之后需要调用ThreadLocal.remove()
方法清除线程关联对象。
SpringBoot
如果项目启用了事务管理,Service 的实现类在增删改数据时需要加上
@Transactional
注解标明该类或方法加入了事务管理。使用 shiro 权限框架时,需要检查需要权限控制的 Controller 类或方法是否加上了
@RequiresRoles
或@RequiresPermissions
注解。Controller 的方法传入
@RequestBody
参数时,method 只能是 POST 或 PUT。Controller 通过 Model 对象实例通过 setAttribute 方法传值到前端 jsp 页面或是其他模板页面时,如果是在 js 里获取的话需要通过 var param = ‘${param}’ 方式获取,用’’引起来可以避免当 ${param} 为空时 js 代码报错的问题,但同时也将参数类型强制转换为 string 类型了;当参数类型是 List 集合时,需要使用 eval(‘${param}’) 方法。
后台在增删改数据时,记得更新和该数据相关的缓存;特别是在更改记录时要更新更改之前和之后会包含该记录的缓存。
跨域访问时,可以在全局 Interceptor 中使用
httpServletResponse.setHeader("Access-Control-Allow-Origin", ACCESS_CONTROL_ALLOW_ORIGIN)
限制可跨域访问的域名,类似的,使用httpServletResponse.setHeader("Access-Control-Allow-Methods", ACCESS_CONTROL_ALLOW_METHODS)
限制请求方法,httpServletResponse.setHeader("Access-Control-Max-Age", ACCESS_CONTROL_MAX_AGE)
设置可跨域访问的时限,httpServletResponse.setHeader("Access-Control-Allow-Headers", ACCESS_CONTROL_ALLOW_HEADERS)
限制请求头参数。SpringBoot 中可以使用
@EnableScheduling
注解启用定时任务功能,在方法上使用@Scheduled
注解设置任务启动的时间。访问日志功能的实现:定义一个 LogInterceptor ,使用
logger.info()
等方法记录下 httpServletRequest 中相关属性。shiro 的 reaml 中保存的 Principal 为 User 对象时,页面上想要获取 User 对象的 username 属性可以使用
<shiro:principal property="username"/>
标签,注意这里的 property 即是对应的属性名。Spring 框架自带的
BeanUtils.copyProperties(Object source, Object target, String... ignoreProperties)
方法可以方便的做类属性的拷贝。SpringBoot security 在使用
.antMatchers("/management/**").hasAnyRole("SuperAdmin", "admin")
要注意数据库里保存的角色名称必须要以'ROLE_'
开头,这里的角色在数据库的名称应为"ROLE_SuperAdmin"
和"ROLE_admin"
。SpringBoot security 的注解
@PreAuthorize
可以用在类或方法上来进行权限控制,其中hasRole()
和hasAuthority()
的区别在于前者可以忽略角色信息的前缀(默认是 “ROLE_”),而后者则必须要包含前缀。例如当保存的角色信息为”ROLE_admin”时,hasRole('admin')
和hasAuthority('ROLE_admin')
是等效的。SpringBoot 在整合 shiro 时无法在使用 rememberMeCookie 实现”记住我”(即 rememberMe)功能的同时实现使用 sessionIdCookie 单独管理用户 session 信息。而使用 sessionIdCookie 可以解决出现404之后刷新页面直接跳转到登录页面的问题(问题详情:http://jinnianshilongnian.iteye.com/blog/1999182)。
SpringBoot JPA 可以很简单的集成分页和排序功能,支持的 request 参数如下:
1
2
3page,第几页,从 0 开始,默认为第 0 页;
size,每一页的数量,默认为 10;
sort,排序相关的信息,以 `property,direction(,ASC|DESC)` 的方式组织,例如 `sort=firstname&sort=lastname,desc` 表示在按 firstname 增序排列的基础上按 lastname 的降序排列。例如请求链接为:
http://host:port/blogs?page=0&size=3&sort=category,asc&sort=description,desc
1
2
3
4
5
6
7
8
9
10
11
12
13
public Page<Blog> findAll( { Pageable pageable)
long startTime = System.currentTimeMillis();
Page<Blog> blogPage = (Page<Blog>) redisTemplate.opsForValue().get(blogCachePrefix + JSON.toJSONString(pageable));
if (blogPage == null) {
blogPage = blogRepository.findAll(pageable);
redisTemplate.opsForValue().set(blogCachePrefix + JSON.toJSONString(pageable), blogPage, 3, TimeUnit.MINUTES);
}
long endTime = System.currentTimeMillis();
logger.info("获取分页博客博客,pageable :{}, 耗时:{}ms", JSON.toJSONString(pageable), (endTime - startTime));
return blogPage;
}使用 jackson 将实体对象转成 JSON ,在属性上加上 @JsonInclude(JsonInclude.Include.NON_NULL) 注解,当该属性NULL或者为空时将不参加序列化。JsonInclude 可选项有:
1
2
3
4JsonInclude.Include.ALWAYS //默认总是参加序列化
JsonInclude.Include.NON_DEFAULT //属性为默认值不序列化
JsonInclude.Include.NON_EMPTY //属性为空或NULL都不序列化
JsonInclude.Include.NON_NULL //属性为NULL不序列化实体类里的Enum类型的属性映射数据库字段的时候可以使用
@Convert(converter = Status.class, attributeName = "status")
注解;jackson 在序列化Enum类型的时候可以在Enum类对应的getter方法使用@JsonValue
注解来定义向前端输出的内容,而前端传过来的值同样可以通过@JsonCreator
注解反序列化得到对应的Enum类型对象。1.5.6.RELEASE 版本的SpringBoot在使用
ElasticsearchRespository
类时会产生以下异常:1
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON document: (was java.lang.NullPointerException) (through reference chain: org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])
推荐使用 1.4.7.RELEASE 版本。
Json 序列化的时候会将 Long 等类型强转成类似于 Javascript 中的 number 类型(内部实现可能是转成 Double 类型),这样会导致大数值丢失精度的问题。jackson 可以使用
@JsonSerialize(using = ToStringSerializer.class)
注解将 Long 类型转成String 再进行序列化,但这样会导致前端接收到的数据类型变成了 String 而不是 number。在进行批量更新操作时,需要将待更新的数据进行排序之后再进行批量操作,这样可以避免并发场景下的死锁问题。
@RequestBody注解的实体对象如果需要将前端传过来的 array 转成实体中相应的 String 类型的属性时,可以在该属性的 setter 方法进行转换操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WelcomeMessage implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String articles;
public void setArticles(List<WxMpXmlOutNewsMessage.Item> articles) {
this.articles = JSON.toJSONString(articles);
}
public List<WxMpXmlOutNewsMessage.Item> getArticles() {
return JSON.parseArray(this.articles, WxMpXmlOutNewsMessage.Item.class);
}
}相应的,如果需要将数据库中存储的 String 转成 List 传回前端或者应用于后台逻辑,可以在字段对应属性的 getter 方法进行转换。
这里使用的是 fastjson 的 JSON 类。过长的 Long 类型主键转 Json 传回前端会使用js处理会丢失精度,可以在实体的 @Id 上配置转成 Json 的序列化及反序列化处理类,在将 id 传回前端或者接收 前端传过来的 id 时使用 String 类型来进行转换保证精度,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13/**
* 解决Long类型的主键转json传回前端丢失精度的问题,将Long转成String
* @author yupaits
* @date 2017/12/18
*/
public class LongJsonSerializer extends JsonSerializer<Long> {
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
if (value != null) {
jsonGenerator.writeString(String.valueOf(value));
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* 将前端String类型的主键转成Long类型
* @author yupaits
* @date 2017/12/18
*/
public class LongJsonDeserializer extends JsonDeserializer<Long> {
public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getText();
try {
return value == null ? null : Long.parseLong(value);
} catch (NumberFormatException e) {
return null;
}
}
}1
2
3
4
private Long id;SpringAop的Order顺序遵循先进后出的原则。如Aop1(order=1),Aop2(order=2),则切面的执行顺序为
Aop1.doAround.beforeProceed
->Aop1.doBefore
->Aop2.doAround.beforeProcced
->Aop2.doBefore
->Aop2.doAround.afterProcced
->Aop2.doAfter
->Aop2.doAfterReturning
->Aop1.doAround.afterProcced
->Aop1.doAfter
->Aop1.doAfterReturning
。Spring Boot启动时指定环境。
- IDEA中进入
Run/Debug Configurations
设置Environment->VM options
为-Dspring.profiles.active=test
,或者在Environment variables
中添加参数spring.profiles.active
并指定参数的值为test
。 - 命令行使用
java -jar *.jar
启动时,在命令的后面增加--spring.profiles.active=test
。 - 在
application.yml
中配置spring.profiles.active
的值为test
。
- IDEA中进入
通过zuul上传包含中文名的文件时,需要在上传文件url的path加上
/zuul
前缀即可。例如:当上传url为
http://localhost:10060/upload/image
,上传名为头像.jpg
的文件,会出现报错java.io.FileNotFoundException: E:\upload\image\2018-5\??.jpg (文件名、目录名或卷标语法不正确。)
。如果将url直接改为http://localhost:10060/zuul/upload/image
则可以上传成功。Spring Boot Jpa实现实体联合主键:实体类加上
@IdClass(XxxKey.class)
注解,实体类中多个联合注解的属性上都加上@Id
注解。示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
private Long userId;
private Long roleId;
}
public class UserRoleKey implements Serializable {
private static final long serialVersionUID = 1L;
private Long userId;
private Long roleId;
}使用Spring Cloud Sleuth的日志中会包含链路追踪的信息,具体体现为:
1
22017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 — [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 2: Handling print
2017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 — [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 1: Handling home比一般的日志多出了
[bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false]
这些内容,对应[appname,traceId,spanId,exportable]
。- appname:服务名称
- traceId\spanId:链路追踪的两个术语
- exportable: 是否是发送给zipkin
SpringBoot中使用
logback-spring.xml
进行日志打印的配置。示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<property name="appName" value="app-name"/>
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} - [${appName}] - ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %c - %m%n"/>
<appender name="amqp" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
<host>172.17.0.1</host>
<port>5672</port>
<username>guest</username>
<password>guest</password>
<applicationId>okbuy</applicationId>
<routingKeyPattern>logstash</routingKeyPattern>
<declareExchange>true</declareExchange>
<exchangeType>direct</exchangeType>
<exchangeName>okbuy.log</exchangeName>
<generateId>true</generateId>
<maxSenderRetries>2</maxSenderRetries>
<charset>UTF-8</charset>
<durable>true</durable>
<deliveryMode>PERSISTENT</deliveryMode>
<layout>
<pattern>${logPattern}</pattern>
</layout>
</appender>
<appender name="stdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${logPattern}</pattern>
<charset>utf8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<appender name="asyncStdoutAppender" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>1024</queueSize>
<appender-ref ref="stdoutAppender"/>
<includeCallerData>true</includeCallerData>
</appender>
<root>
<level value="WARN"/>
<appender-ref ref="amqp"/>
<appender-ref ref="asyncStdoutAppender"/>
</root>
</configuration>Spring的事务控制和数据库的事务控制之间的关系:
- 数据库开启事务、提交事务、回滚事务对应jdbc的三个api,Spring事务控制的本质是通过AOP把这三个方法增强在不同的地方调用,实现Spring的事务在方法之间的传播。
- 数据库在开启事务到提交的过程中,数据库本身有异常都会回滚。
- 业务异常导致数据库回滚,是Spring通过调用
jdbc rollback
的api实现的。
Spring Security OAuth2 Client项目中@EnableOAuth2Sso注解修饰Application启动类时不起作用,需要放在@Configuration修饰的类上(通常是继承WebSecurityConfigurerAdapter的配置类)才能正常工作。
检查幂等(是否重复请求)伪代码:
1
2
3
4
5
6CREATE TABLE `tb_unique` (
`request_id` varchar(64) NOT NULL COMMENT 'request id',
`gmt_create` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
`gmt_modified` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '修改时间',
PRIMARY KEY (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='幂等表';1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25bool checkUnique(String requestId) {
try {
UniqueDO uniqueDO = buildUniqueDO(request);
//reqeustId唯一索引,重复则抛异常DataIntegrityViolationException
uniqueDao.insert(uniqueDO);
//插入成功说明之前没有数据,返回没有被幂等
return false;
} catch (DataIntegrityViolationException ex) {
//出现异常不一定时由于db里面有数据,可能是事务隔离级别导致,
//所以要再查一次,确认数据再db里面存在
UniqueDO uniqueDO = uniqueDao.selectByRequestId(requestId);
if (uniqueDO == null) {
//不存在数据则抛异常让方法重试
throw ex;
}
//存在返回被幂等
return true;
}
}requestId一般由客户端sdk生成和具体业务相关联。
profile为default时会读取
application.yml
的配置,而当profile不是default时,会读取对应的applicaiton-{profile}.yml
的配置。需要注意的是,如果application.yml
和application-{profile}.yml
中存在相同的配置项时,application.yml
的优先级更高,所以一般在application.yml
中设置共用的配置项。在SpringMVC中
@RequestBody
注解修饰的对象如果存在@DateTimeFormat
注解修饰的属性,而且使用 jackson 进行反序列,那么@DateTimeFormat
注解实际上是不起作用的,此时需要使用@JsonFormat
注解进行代替。示例如下:1
2
3//@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;Spring AOP 的切入点(PointCut)表达式使用规则:
1
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
?
的部分表示可省略*
用来代表任意字符..
用来表示任意个参数modifiers-pattern
表示修饰符如 public、protected 等ret-type-pattern
表示方法返回类型declaring-type-pattern
表示特定的类name-pattern
表示方法名称param-pattern
表示参数throws-pattern
表示抛出的异常
fastjson序列化之后如果出现
$ref
说明启用了“重复应用/循环引用”特性,可以通过以下两种方式处理:- 通过全局或局部序列化配置
SerializerFeature.DisableCircularReferenceDetect
来展示实际内容 - 将存在重复引用的对象使用该对象的副本(new一个同类型对象并复制属性值)而不是原对象
- 通过全局或局部序列化配置
Mybatis的if标签中判断字符串相等要使用如下格式:
1
2<if test='field == "value"'>
</if>注意外层用单引号,字符串的值使用双引号。
Bootstrap
- 页面的modal元素记得加上
data-backdrop='static'
和data-keyboard='false'
,禁用非 modal 内点击和点击键盘 ESC 键 取消 modal。
JQuery
批量删除并返回批量删除的结果时,ajax 方法中 async 一定要配置成 false,否则页面无法正确响应批量删除的结果。
使用 qrcodejs 生成二维码图片之后直接使用
$('img').attr('src')
返回的值是undefined,这时需要使用setTimeout(function(), delay_time)
来拿到图片的 src 。js数组遍历删除元素的方法:
1
2
3
4
5
6for (let i = 0; i < arr.length; i++) {
if (...) {
arr.splice(i, 1);
i--;
}
}
CSS
局部列表滚动查看css,height和max-height二选一。
1
2
3
4
5
6
7
8
9.grid-list {
height: 190px; //固定高度的列表
max-height: 500px; //列表最大高度
overflow-x: hidden;
overflow-y: scroll;
}
.grid-list::-webkit-scrollbar {
display: none;
}一个基于网格的响应式布局简单示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.grid-container {
display: grid;
grid-template-rows: 1fr;
grid-gap: 1rem;
}
@media screen and (max-width: 719px) {
.grid-container {
grid-template-columns: 1fr;
}
}
@media screen and (min-width: 719px) {
.grid-container {
grid-template-columns: 1fr 1fr;
}
}浏览器窗口宽度大于719px时,元素呈2列排布,反之呈1列排布。
HTML
- 使用原生HTML进行表单开发时,如果没有指定
<button>
的type
属性,则默认type="submit"
,最终导致点击之后window.location.href
的路径中会自动多一个?
,这是因为原生的表单提交是以path后拼接form的参数进行请求的,所以在进行异步请求时需要指定提交的按钮为<button type="button">
。
Vue.js
使用局部的 filter 代替 methods 中的方法格式化数据显示。
vue 在绑定 date 类型的 input 输入框时,设置默认值要用
:value="date | toDate"
,toDate 为过滤器;当 date 的值是 timestamp 类型时,toDate 可以为以下方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function dateTime(date) {
if (date) {
date = new Date(date);
var month = date.getMonth() + 1;
var day = date.getDate();
var hour = date.getHours();
var minute = date.getMinutes();
var second = date.getSeconds();
return date.getFullYear() + '-' +
(month < 10 ? '0' + month : month) + '-' +
(day < 10 ? '0' + day : day) + ' ' +
(hour < 10 ? '0' + hour : hour) + ':' +
(minute < 10 ? '0' + minute : minute) + ':' +
(second < 10 ? '0' + second : second);
}
return date;
}vue.js 2.5.2版本在 v-for 循环中使用 v-model 绑定数组元素时会报如下错误:
1
You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
从提示信息可以看出 vue 建议使用 array 的某个元素的属性值作为 v-model 绑定的主体,如果元素并不是 Object 类型而是 String 类型时,需要通过 index 来实现元素绑定。具体代码如下:
1
2
3<div v-for="(el, index) in array">
<input type="text" v-model="array[index]">
</div>将数组中位于 index1 的元素移动至 index2 的位置:
1
list.splice(index2, 0, list.splice(index1, 1)[0]);
交换数组中位于 index1 和 index2 的两个元素:
1
list[index1] = list.splice(index2, 1, list[index1])[0];
vue 组件中实时交换位于 index1 和 index2 的两个元素:
1
this.$set(list, index1, list.splice(index2, 1, list[index1])[0]);
Vue的
filters
块中的过滤器不能加载自身data()
块中的变量,如果需要使用data()
中的变量对数据进行转换时可以将过滤器的逻辑写在methods
块中。在使用基于Vue的前端框架进行开发时,有些框架组件无法对点击事件进行监听和处理(如
ant-design-vue
的popconfirm
组件,详见 Could you add a property in PopConfirm to stop click event propagation?),而如果此时正好需要阻止某个组件的点击事件向上层元素传播时,可以使用如下方式:1
2
3<div @click="e => e.stopPropagation()">
<a-popconfirm></a-popconfirm>
</div>Vue的
style scoped
中的样式不起作用时,可以新增一个无scoped修饰的style
来定义css样式,例如:1
2
3
4
5
6
7
8
9
10
11<style scoped>
.scoped-style {
background-color: black;
}
</style>
<style>
.global-style {
color: white;
}
</style>在Vue中使用JSX语法时,需要注意以下方面:
- 所有的运算赋值操作都需要在
{}
中,如获取变量值,调用方法等。 - 不支持Vue的过滤器,需要使用方法来代替。
- 事件监听
@click="handleClick(param)"
=>onClick={this.handleClick.bind(this, param)}
,@click.native="handleNativeClick(param)"
=>nativeOnClick={this.handleNativeClick.bind(this, param)}
,这里需要使用js原生的bind
方法来进行方法调用。 - 不支持Vue的指令,常用的指令的备选解决方案:
v-if="condition"
=>v-show="condition"
或者{condition ? <div>JSX</div> : ''}
;<li v-for="item in items" :key="item">{item}</li>
=>{items.map((item, index) => {return <li>{item}</li>})}
。
- 所有的运算赋值操作都需要在
win7系统在安装高版本node.js(v14.x.x)时,会提示“仅支持更高的win8、win10系统”。但是node.js的生态里,很多新技术必须安装高版本node.js环境才能正常使用和开发(如electron的最新版本要求node.js版本不低于v14.x.x)。可以通过以下方式解决:
- 下载nvm(node.js版本管理器)并安装
- 安装完成后,使用
nvm list available
查看可安装的node.js版本 - 选择最新的
CURRENT
版本或者LTS
版本进行安装,安装完成后使用nvm use 14
切换至高版本node.js即可正常使用
缓存
- 使用 redis-cli 进入 redis 的命令行模式时,可以使用
keys **
查看所有 key 值,使用get [key]
查看 key 对应的 value 值。需要注意的是,使用keys **
查看到的 key 值如果使用 “” 包括,那么get [key]
的 key 也需要用 “” 包括起来。
构建
使用
frontend-maven-plugin
整合前后端项目构建时,需要注意web模块需要放在server模块之前,保证web先构建,这样才能将web构建好的文件复制到server的resources/public
目录下。1
2
3
4<modules>
<module>xxx-web</module>
<module>xxx-server</module>
</modules>同时要注意后台不要对
@RequestMapping("/")
做处理,否则有可能会使得访问"/"
无法显示前端构建好的index.html页面。maven引用私服的jar包时,需要在
pom.xml
文件的<repositories>
标签下中指定私服的<id>
、<name>
、<url>
。gitlab-runner的
config.toml
在[runners.docker]
中配置extra_hosts = ["xxx.xxx.com:172.17.0.1"]
可实现runner容器的ip和host映射关系的配置。设置maven的
setting.xml
中 mirror 节点的mirrorOf
有以下配置策略:- *:everything
- external:*:everything not on the localhost and not file based
- repo,repo1:repo or repo1
- *,!repo1:everything except repo1
部署
windows 下使用 Nginx 相关命令如下:
1
2
3
4
5
6
7
8
9#启动
点击 Nginx 目录下的 nginx.exe;cmd 运行 start nginx
#关闭
nginx -s stop #立即停止nginx,不保存相关信息
nginx -s quit #正常退出nginx,并保存相关信息
#重启(重新加载配置)
nginx -s reloadnginx 的配置文件 root 路径不识别
\
只识别/
。Letsencrypt通配符证书的申请和在nginx配置:
使用 nginx 解决 CORS 问题
使用反向代理将服务端(接口提供方)与客户端(接口调用方)部署在同一个域下。
使用 nginx 解决 X-Frame-Options: deny 问题
使用headers-more插件,推荐直接下载带headers-more模块的openresty,下载地址。
在 nginx.conf 中(一般是在server下)配置:
1
2
3
4# 删除 "X-Frame-Options" header
more_clear_headers 'X-Frame-Options';
# 设置 "X-Frame-Options: SAMEORIGIN" 同域可嵌入iframe
more_set_headers 'X-Frame-Options: SAMEORIGIN';指定MySQL时区,在mysqld.conf中增加
default-time-zone='+8:00'
配置。在做微信公众号开发时,需要在公网上调用开发机器的接口,除了使用花生壳等软件进行内网穿透之外,如果有富余的公网服务器资源,可以使用一些简单方便的内网穿透工具,推荐 ichWebpass。
gitlab pg数据库配置
修改
/var/opt/gitlab/postgresql/data/pg_hba.conf
文件,增加下面的配置:1
host all all 192.168.1.10/22 trust
上述的修改会在使用
gitlab-ctl reconfigure
命令之后失效,通过修改gitlab配置文件中pg数据库的entries可以避免这种情况。具体为:在gitlab.rb中增加
1
2
3
4
5
6
7
8
9
10postgresql['custom_pg_hba_entries'] = {
APPLICATION: [{ # APPLICATION should identify what the settings are used for
type: 'host',
database: 'all',
user: 'all',
cidr: '192.168.1.0/24',
method: 'trust',
#option: 0
}]
}注意: 这里的
APPLICATION
对象是一个数组,有些gitlab版本默认不是数组,需要手动修改。
Git
- GitHub 现在支持创建私有代码仓库了,但使用时需要注意:将 GitHub 的 repository 从 public 切换成 private 再切回 public 之后,需要
push
代码到master
分支才能让已经404的Github Pages
页面恢复正常。
Windows
为了在windows系统上安装docker,需要将win10系统升级到专业版开启HyperV虚拟机才行。
win10专业版升级密钥:DR9VN-GF3CR-RCWT2-H7TR8-82QGT
Linux
Ubuntu/Debian开机启动脚本
spider.sh
示例:1
2
3
4
5
6
7
8
9
10
11
12
13!/bin/bash
## BEGIN INIT INFO
Provides: spider
Required-Start:
Required-Stop:
Default-Start: 2 3 4 5
Default-Stop: 0 1 6
Short-Description: spider
Description: spider service start
## END INIT INFO
[shell content]- 开机启动:
update-rc.d spider.sh defaults
- 移除开机启动:
update-rc.d spider.sh remove
- 开机启动:
使用netstat查看系统网络端口情况:
netstat -tunlp
;如果发现没有PID
(进程号),需要使用sudo netstat -tunlp
。对于新安装的官方Ubuntu-server等系统,可以通过指令
sudo apt-get install build-essential
安装gcc环境。取消sudo密码:
sudo visudo
进入编辑界面;添加行username ALL=(ALL) NOPASSWD:ALL
,username为登录用户名,如果想作用于某个用户组则使用%usergroup ALL=(ALL:ALL) NOPASSWD:ALL
。linux系统添加ssh公钥的方式:
本地
1
2
3
4
5# 覆盖
cat [ssh_pub_key] > ~/.ssh/authorized_keys
# 追加
cat [ssh_pub_key] >> ~/.ssh/authorized_keys远程
1
scp ~/.ssh/id_rsa.pub root@[remote-server-ip]:/root/.ssh/authorized_keys
注: 需要输入远程服务器的登录密码进行认证。
ssh对目录的权限有要求,具体如下:
目录/文件 权限 ~/.ssh 700 ~/.ssh/* 600 ~/.ssh/config 700 在终端中执行
curl -X GET http://www.sample.com/api?a=1&b=2
指令时,实际上只会传入a=1
参数,想要同时传入a=1
和b=2
,可使用以下两种方式:&
符号转义:curl -X GET http://www.sample.com/api?a=1\&b=2
- url加上双引号:
curl -X GET "http://www.sample.com/api?a=1&b=2"
Docker
使用docker-compose的build参数构建docker镜像时,要注意指定的context目录下默认的Dockerfile文件名必须为
Dockerfile
。如果想使用其他文件名,可以通过-f filename
指定。docker环境为docker for windows,有效避免host为windows系统时
docker run -v
绑定的目录映射关系在系统重启之后失效的方法为:使用docker volume create path1
创建volume,再使用-v path1:/container/path
。简单地说就是使用 “自定义数据卷” 代替 “容器自动创建的临时数据卷” 进行映射,完成docker容器的数据持久化。指定docker的dns,防止网络环境变化时导致容器网络的不稳定。
编辑docker的daemon.json
:1
2
3{
"dns": ["114.114.114.114", "8.8.8.8"]
}使用docker-compose来部署redash时,注意需要先创建redash数据库才能启动:
1
2sudo docker-compose run --rm server create_db
sudo docker-compose up -d使用docker方式部署consul时,由于consul在docker虚拟网络中无法正确识别注册服务的hostname,因此需要在注册服务中配置 spring.cloud.consul.discovery.hostname=192.168.1.xxx (注册服务的局域网ip,要注意的是必须是docker宿主机所在地局域网ip,使用172.17.0.1等docker内网ip也不行),才能确保consul正确地进行健康检查。