整体步骤
- 利用certbot创建/续期证书
- 利用金山云API接口更新证书
This is a love story
I will post snippets here.
公司局域网下,让NAS在内网的时候走内网,在外网的时候走外网,省一些带宽。
所以公司路由器我设置了下发自己为默认的解析服务器。
Continue reading “Clash的Nameserver-provider”tell application "System Preferences" -- 进入系统面板
set the current pane to pane id "com.apple.preferences.Bluetooth" -- 设置当前tab为蓝牙
tell application "System Events" -- 触发系统事件
tell process "System Preferences" -- 进入系统面板程序
tell window "Bluetooth" -- 找到蓝牙窗口
tell button 3 -- button 3是开启/关闭蓝牙按钮
click -- 点击
end tell
end tell
end tell
quit
end tell
end tell
同时,配合上Automator,制作成一个action,再为这个action添加一个快捷键
<resultMap id="UserBasic" type="com.alibaba.force.dal.model.capsulation.UserBasic">
<constructor>
<arg column="u_name" jdbcType="VARCHAR" javaType="java.lang.String"/>
<arg column="u_username" jdbcType="VARCHAR" javaType="java.lang.String"/>
<arg column="u_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
<arg column="u_state" jdbcType="VARCHAR" javaType="java.lang.String"/>
<arg column="u_avatar" jdbcType="VARCHAR" javaType="java.lang.String"/>
<arg column="u_extern_uid" jdbcType="VARCHAR" javaType="java.lang.String"/>
</constructor>
</resultMap>
@ToString
@Getter
@Setter
public class UserBasic extends UserSafe {
private Long id;
private String state;
@JSONField(name = "avatar_url")
private String avatarUrl;
@JSONField(name = "extern_uid")
private String externUid;
/**
* @param name
* @param username
* @param id
* @param state
* @param avatar 注意,这里需要做一些额外操作
* @param externUid
*/
public UserBasic(String name, String username, Long id, String state, String avatar, String externUid) {
super(name, username);
this.id = id;
this.state = state;
this.externUid = externUid;
this.setAvatarUrl(UsersUtil.getAvatarUrl(UsersBuilder.newBuilder().setId(id).setAvatar(avatar)
.setExternUid(externUid).build()));
}
public UserBasic() {
}
@Override
public UserBasic merge(@NonNull Users users) {
super.merge(users);
this.setId(users.getId());
this.setState(users.getState());
this.setAvatarUrl(UsersUtil.getAvatarUrl(users));
this.setExternUid(users.getExternUid());
return this;
}
}
注意入参的顺序保持一致
association字段可以直接增加ResultMap的子属性。
<resultMap id="MigrateTaskRecordDO" type="com.alibaba.force.dal.model.capsulation.MigrateTaskRecordDO">
<id column="id" jdbcType="BIGINT" property="id" />
<id column="u_id"/>
<result column="gmt_create" jdbcType="TIMESTAMP" property="gmtCreate" />
<result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified" />
<association property="creator" javaType**="com.alibaba.force.dal.model.capsulation.UserBasic" resultMap="UserBasic"/>
</resultMap>
association中的resultMap所指向的字段也必须是申明过的ResultMap对象。
result需要放置在association之前。
注意上面例子中的多个
有这么一个场景,会产生坑:
当查询1对多的关系时,如果采用了嵌套的方式(association、collection),则MyBatis会在DefaultResultSetHandler中调用handleRowValuesForNestedResultMap
方法。
该方法采用了计算key(createRowKey)的方式来记录已经遍历过的对象,并且下次从缓存中拿(减少内存的消耗,共享嵌套的重复对象)。
而由于N+1的select一定是根据这个“1”去查询其他的“N”的,如果此时<id>不变,为主键,那么就会被丢弃很多结果(理论上不重复,但是由于上述的计算规则,被MyBatis认为是重复)。因此,使用多个联合字段作为id是最棒的解。这些联合ID需要是最终确定一个不重复对象的唯一值。
MyBatis版本:3.4.4
In Spring, we can use FilterChainProxy to manage custom filters properly.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="webFilterChain" class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg name="requestMatcher">
<bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
<constructor-arg name="requestMatcher">
<bean class="org.springframework.security.web.util.matcher.RegexRequestMatcher">
<constructor-arg name="httpMethod" value=""/>
<constructor-arg name="pattern" value="/api/.*"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg name="filters">
<list>
<bean class="org.springframework.security.web.csrf.CsrfFilter">
<constructor-arg name="csrfTokenRepository">
<bean class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"/>
</constructor-arg>
</bean>
<ref bean="springSessionRepositoryFilter"/>
</list>
</constructor-arg>
</bean>
<bean id="customFilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<ref bean="webFilterChain"/>
</list>
</constructor-arg>
</bean>
</beans>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
<!-- ... -->
classpath:spring/filter.xml
</param-value>
</context-param>
<!-- Custom Filter -->
<filter>
<filter-name>customFilterChainProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>customFilterChainProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
customFilterChainProxy filter-name in web.xml. This tells Container(Tomcat) to add filter by Class org.springframework.web.filter.DelegatingFilterProxy.
DelegatingFilterProxy : Spring will search filter-name (customFilterChainProxy) in Spring context for Good Management.And customFilterChainProxy is initialized in filter.xml resource file.
SpringFox
package com.alibaba.force.api.shared.swagger;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author van.yzt
* @date 2017/07/31
*/
@Configuration
@EnableSwagger2
@EnableWebMvc
public class SwaggerConfig {
@Bean
public Docket healthDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("Health").select()
.apis(RequestHandlerSelectors.basePackage("com.alibaba.force.api.health"))
.paths(PathSelectors.any()).build().tags(new Tag("System API", "This is for health."))
.useDefaultResponseMessages(false)
.apiInfo(apiInfo("System API", "This is Force system API"));
}
@Bean
public Docket internalDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("Internal").select()
.apis(RequestHandlerSelectors.basePackage("com.alibaba.force.api.internal"))
.paths(PathSelectors.any()).build().tags(new Tag("System API", "This is for internal API."))
.useDefaultResponseMessages(false)
.globalOperationParameters(privateToken())
.apiInfo(apiInfo("Internal API", "This is Force internal API"));
}
@Bean
public Docket repositoryDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("Repository").select()
.apis(RequestHandlerSelectors.basePackage("com.alibaba.force.api.repository"))
.paths(PathSelectors.any()).build().tags(new Tag("Repository API", "This is for repository api."))
.useDefaultResponseMessages(false)
.globalOperationParameters(privateToken())
.apiInfo(apiInfo("Repository API", "This is Force repository API"));
}
@Bean
public Docket userDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("User").select()
.apis(RequestHandlerSelectors.basePackage("com.alibaba.force.api.user"))
.paths(PathSelectors.any()).build().tags(new Tag("User API", "This is for user api."))
.useDefaultResponseMessages(false)
.globalOperationParameters(privateToken())
.apiInfo(apiInfo("User API", "This is Force user API"));
}
@Bean
public Docket keyDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("Key").select()
.apis(RequestHandlerSelectors.basePackage("com.alibaba.force.api.key"))
.paths(PathSelectors.any()).build().tags(new Tag("Key API", "This is for key api."))
.useDefaultResponseMessages(false)
.globalOperationParameters(privateToken())
.apiInfo(apiInfo("Key API", "This is Force key API"));
}
private List<Parameter> privateToken() {
return new ArrayList<Parameter>() {{
add(new ParameterBuilder()
.name("private_token")
.description("Private token for person")
.modelRef(new ModelRef("String"))
.parameterType("query")
.required(true)
.build());
}};
}
private ApiInfo apiInfo(String title, String description) {
return new ApiInfo(title, description, "v3", "", DEFAULT_CONTACT, null, null);
}
private static final Contact DEFAULT_CONTACT = new Contact("van.yzt",
"https://lark.alipay.com/aone/code+voice/gt7niw", "van.yzt@alibaba-inc.com");
}
Register custom converter:
When use Spring <mvc:annotation-driven/>, Spring will automatically initialize some MVC usefull bean @See WebMvcConfigurerComposite. It contains :
@Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
And then, <mvc:annotation-driven conversion-service="mvcConversionService" /> and we can you it:
/**
* @author van.yzt
* @date 2017/08/29
*/
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToAccessLevelEnum());
super.addFormatters(registry);
}
final class StringToAccessLevelEnum implements Converter<String, AccessLevelEnum> {
@Override
public AccessLevelEnum convert(String source) {
return AccessLevelEnum.fromValue(Integer.parseInt(source));
}
}
}
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</exclusion>
</exclusions>
</dependency>
...
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.6.RELEASE</version>
</dependency>
package com.alibaba.force.web.config;
import com.alibaba.force.api.shared.aop.ForceHandlerInterceptor;
import com.alibaba.force.common.enums.VisibilityLevelEnum;
import com.alibaba.force.common.util.PropertyUtils;
import com.alibaba.force.dal.model.AccessLevelEnum;
import com.lambdaworks.redis.resource.ClientResources;
import com.lambdaworks.redis.resource.DefaultClientResources;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author van.yzt
* @date 2017/08/29
*/
@Configuration
@EnableWebMvc
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 108000)
public class WebConfig extends WebMvcConfigurerAdapter {
...
@Bean
public LettuceConnectionFactory connectionFactory() {
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
PropertyUtils.getRedisSentinelMasterName(),
PropertyUtils.getRedisSentinelNodes());
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisSentinelConfiguration);
ClientResources clientResources = new LettuceRedisClientResourceConfig(DefaultClientResources.builder());
lettuceConnectionFactory.setClientResources(clientResources);
lettuceConnectionFactory.setTimeout(5000);
lettuceConnectionFactory.setPassword(PropertyUtils.getRedisConfigPassword());
lettuceConnectionFactory.setShareNativeConnection(true);
return lettuceConnectionFactory;
}
...
}
This configuration uses Redis Sentinel.
redis.sentinel.master.name=force-master
redis.sentinel.nodes=10.101.104.13:26379,10.101.104.13:26380,10.101.104.13:26381
redis.config.password=YPr2Ymj^8$8a2*aD0F7z^L
采用In-Process作为Transport协议,直接生成一个本地的Server,将Server端mock,从而实现Clinet端的代码测试。gRPC的维护者们不允许Client去做mock。
依照上述步骤,结合原力项目(代码服务)场景:
我们需要对某一个路径下的仓库检测其是否存,用gRPC协议调用远端的Server完成。
/**
*{@inheritDoc}
*
* @param projectPathWithNamespace
* @return
*/
@Override
public Optional<Projects> isExist(@NonNull String projectPathWithNamespace) {
Optional<Projects> projectsOptional = projectsDao.findByNamespace(projectPathWithNamespace);
if (projectsOptional.isPresent()) {
RepositoryExistsRequest repositoryExistsRequest = RepositoryExistsRequest.newBuilder()
.setRepository(GrpcUtil.generateRepository(projectPathWithNamespace)).build();
RepositoryServiceBlockingStub stub = grpcStub.get(RepositoryServiceBlockingStub.class,
GrpcUtil.getNamespace(projectPathWithNamespace));
RepositoryExistsResponse response = stub.repositoryExists(repositoryExistsRequest);
// todo 通知清理脏数据
if (!response.getExists()) {
return Optional.empty();
}
}
return projectsOptional;
}
系统在这里统一处理了stub的生成:grpcStub.get(...)
/**
* Extend {@link RepositoryServiceGrpc.RepositoryServiceImplBase} in order to implement the mock method.
*
* @author van.yzt
* @date 2017/09/27
*/
public class MockRepositoryServiceImplBase extends RepositoryServiceGrpc.RepositoryServiceImplBase {
/**
* Implement the exist check method.
*
* @param request
* @param responseObserver
*/
@Override
public void repositoryExists(RepositoryExistsRequest request,
StreamObserver<RepositoryExistsResponse> responseObserver) {
responseObserver.onNext(RepositoryExistsResponse.newBuilder().setExists(true).build());
responseObserver.onCompleted();
}
}
@Rule
private GrpcStub grpcStub = new MockGrpcStub();
...
@Before
public void setUp() throws Exception {
RepositoryServiceImplBase repositoryServiceImplBase = Mockito.spy(new MockRepositoryServiceImplBase() {});
// 注册到In-Process中
grpcStub.getServiceRegistry().addService(repositoryServiceImplBase);
}
@Test
public void isExistTest() throws Exception {
Mockito.when(mockProjectsDao.findByNamespace(TestDataBuilder.ABSOLUTE_PATH)).thenReturn(
Optional.of(TestDataBuilder.buildProjects()));
Assert.assertNotNull(projects.isExist(TestDataBuilder.ABSOLUTE_PATH));
}
/**
* This stub generate service is used for unit test.
*
* @author van.yzt
* @date 2017/09/27
*/
public class MockGrpcStub extends ExternalResource implements AbstractMockGrpcStub {
private ManagedChannel channel;
private Server server;
private String serverName;
private MutableHandlerRegistry serviceRegistry;
private boolean useDirectExecutor;
public AbstractMockGrpcStub directExecutor() {
useDirectExecutor = true;
return this;
}
/**
* 获取固定的headers
*
* @param namespace
* @param timestamp
* @return
*/
private Map<String, String> headers(@NonNull String namespace, @NonNull Long timestamp) {
final Map<String, String> headers = new HashMap<>(2);
// Some operations
return headers;
}
@SuppressWarnings("unchecked")
@Override
public <T extends AbstractStub<T>> T get(@NonNull Class<T> stubClass, @NonNull final String namespace) {
try {
Constructor constructor = stubClass.getDeclaredConstructor(Channel.class);
constructor.setAccessible(true);
AbstractStub stub = (AbstractStub)constructor.newInstance(channel);
/**
* 不需要关心并发
*/
stub = attachHeaders(stub, headers(namespace, System.currentTimeMillis()));
return (T)stub;
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new GrpcStubCreateException("gRPC create stub failed.", e);
} catch (NoSuchMethodException e) {
/**
* This place means Google Protocol Buffers generate result format changed !!!!!
*
* When writing this code, the XXXBlockingStub class has private constructor like
* 'XXXBlockingStub(io.grpc.Channel)'
*/
throw new GrpcStubCreateException("gRPC create stub failed because of the format changed.", e);
}
}
/**
* Returns a {@link ManagedChannel} connected to this service.
*/
public final ManagedChannel getChannel() {
return channel;
}
/**
* Returns the underlying gRPC {@link Server} for this service.
*/
public final Server getServer() {
return server;
}
/**
* Returns the randomly generated server name for this service.
*/
public final String getServerName() {
return serverName;
}
/**
* Returns the service registry for this service. The registry is used to add service instances
* (e.g. {@link io.grpc.BindableService} or {@link io.grpc.ServerServiceDefinition} to the server.
*/
public final MutableHandlerRegistry getServiceRegistry() {
return serviceRegistry;
}
/**
* After the test has completed, clean up the channel and server.
*/
@Override
protected void after() {
serverName = null;
serviceRegistry = null;
channel.shutdown();
server.shutdown();
try {
channel.awaitTermination(1, TimeUnit.MINUTES);
server.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} finally {
channel.shutdownNow();
channel = null;
server.shutdownNow();
server = null;
}
}
/**
* Before the test has started, create the server and channel.
*/
@Override
protected void before() throws Throwable {
serverName = UUID.randomUUID().toString();
serviceRegistry = new MutableHandlerRegistry();
InProcessServerBuilder serverBuilder = InProcessServerBuilder.forName(serverName)
.fallbackHandlerRegistry(serviceRegistry);
if (useDirectExecutor) {
serverBuilder.directExecutor();
}
server = serverBuilder.build().start();
InProcessChannelBuilder channelBuilder = InProcessChannelBuilder.forName(serverName);
if (useDirectExecutor) {
channelBuilder.directExecutor();
}
channel = channelBuilder.build();
}
Mockito.when(mockTransactionTemplate.execute(Mockito.any()))
.thenAnswer(invocationOnMock -> {
Object[] args = invocationOnMock.getArguments();
TransactionCallback arg = (TransactionCallback)args[0];
return arg.doInTransaction(new SimpleTransactionStatus());
});