概述
近期关于开发过程中常见问题整理。
consul 命令
查看consul健康状态
curl http://localhost:8500/v1/agent/health/service/name/safety-api
删除无用consul实例
curl http://123.206.197.169:8500/v1/agent/service/deregister/safety-user-18027
ws连接异常
尝试去掉protocol参数
page-helper 和mybatis-plus冲突解决
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<!-- 解决mybatis-plus冲突 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
spring-boot异常解决
重置GIT配置,包括checkStyle
重置GIT 配置
git config –global –unset core.hookspath
swagger 失效原因
mvc:
static-path-pattern: /media/**
如果非要配置静态配置,可以做兼容性处理,增加配置类。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
}
}
如何使用JS复制DIV或者p标签文本
使用曲线救国方式
<style type="text/css">
.wrapper {position: relative;}
#input {position: absolute;top: 0;left: 0;opacity: 0;z-index: -10;}
</style>
<div class="wrapper">
<p id="text">我把你当兄弟你却想着复制我?</p>
<textarea id="input">这是幕后黑手</textarea>
<button onclick="copyText()">copy</button>
</div>
JS
<script type="text/javascript">
function copyText() {
var text = document.getElementById("text").innerText;
var input = document.getElementById("input");
input.value = text; // 修改文本框的内容
input.select(); // 选中文本
document.execCommand("copy"); // 执行浏览器复制命令
alert("复制成功");
}
</script>
2021
2021 遇到的一些问题和总计
mybatis plus 问题
limit 问题
// 可以使用lambdaQuery查询
QueryWrapper<ContractTemplate> wrapper = new QueryWrapper<>();
wrapper.lambda().orderByDesc(ContractTemplate::getDownCount);
wrapper.last("limit 10");
List<ContractTemplate> list = contractTemplateService.list(wrapper);
带分页组合查询
LambdaQueryChainWrapper<UserCollect> lambdaQuery = userCollectService.lambdaQuery();
lambdaQuery.eq(UserCollect::getUserCode, startQueryReqVo.getUserCode());
lambdaQuery.eq(UserCollect::getType, startQueryReqVo.getType());
lambdaQuery.eq(!StringUtils.isEmpty(startQueryReqVo.getSubType()), UserCollect::getSubType, startQueryReqVo.getSubType());
lambdaQuery.like(!StringUtils.isEmpty(startQueryReqVo.getTags()), UserCollect::getTags, startQueryReqVo.getTags());
// 使用lambda表达式拼装查询语句
lambdaQuery.and(!StringUtils.isEmpty(startQueryReqVo.getSearchKey()),
wapper -> wapper.like(!StringUtils.isEmpty(startQueryReqVo.getSearchKey()), UserCollect::getTitle, startQueryReqVo.getSearchKey()).or().like(!StringUtils.isEmpty(startQueryReqVo.getSearchKey()), UserCollect::getContent, startQueryReqVo.getSearchKey()));
Page<UserCollect> page = lambdaQuery.page(new Page<UserCollect>(startQueryReqVo.getCurrentPage(), startQueryReqVo.getPageSize()));
return ResultVo.success(page);
定时器模式下的事务支持
在定时器中直接执行数据插入等操作是不会被spring管理事务的,所以需要将数据插入更新操作提成单独的service并加上@transactional.
@Scheduled(cron = "0 0/4 * * * ?") // 定时任务
public void syncDataTask() {
save();
}
public void save() {
service.doSomeInsert();
}
// 单独的service
@service
public class service {
/**
* 此事务会被spring管理,但注意定时任务中插入数据最好在一个整体中。
* 以免其他插入或更新操作没有被spring管理而导致数据异常。
**/
@transactional
public void doSomeInsert(){
// insert some record
}
}
AOP中支持事务
可以参考配置全局的事务处理
https://blog.csdn.net/yxw908186797/article/details/90319643
以下情况事务会失效
@Async
@Transactional
public void saveInterfaceLog(Record rec) {
recordMapper.insertRecord(rec);
}
解决方式是将insertRecord(rec)提取到另一个service中, 并由事务管理起来。
@Async
public void saveInterfaceLog(Record rec) {
recordService.insert(rec);
}
// 将异步方法中的执行由事务进行管理
@Transactional
public void insert(Record rec) {
recordMapper.insertRecord(rec);
}
事务总结
在开发过程中我们经常遇到AOP ,异步,多线程,定时器等的使用,但是如果这几类操作中携带了对数据的插入或更新以及删除操作。
正常情况下spring是不支持对这几种情况进行事务管理的,而我们又希望在程序错误的情况下让事务回滚,那么都可以考虑将需要事务操作的内容提为一个单独的服务,单独由事务进行管理。
security框架
spring 提供了现成的security框架机制,其优点类似于拦截器,常常我们将其和JWT进行一些组合来完成权限验证工作,但是使用过程经常遇到一些问题。
比如,某些链接被限制了,但是本身又放开了权限,为什么呢? 先看JWT + security框架实现机制如何。
@EnableWebSecurity
public class SecurityConfig extends JWTSecurityConfig {
@Value("${spring.security.allowCorsOrigin}")
private String allowCorsOrigin;
@Value("${jwt.auth.secret}")
private String authJwtSecret;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 继承jwt配置
super.configure(http);
// 禁用csrf
http.csrf().disable();
http.cors();
// 根据角色校验url
http.authorizeRequests().antMatchers("/admin/**").hasAnyAuthority("ADMIN");
http.antMatcher("/**");
}
@Override
public void configure(WebSecurity web) throws Exception {
// 配置不需要鉴权的url
web.ignoring().antMatchers( // 测试
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/**",
"/api/**"
);
}
@Override
protected String getJWTSecret() {
return authJwtSecret;
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(allowCorsOrigin));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
// JWT权限配置类,指定过滤器、权限拦截类
@Component
public abstract class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JWTUtils jwtUtils;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint()) // 处理未登录提示
.accessDeniedHandler(new JWTAccessDeniedHandler()).and() // 处理权限不足AccessDeniedException
.addFilterAfter(new JWTAuthenticationFilter(), BasicAuthenticationFilter.class) // 做一些简单过滤处理
.authenticationProvider(new JWTAuthenticationProvider(getJWTSecret(), jwtUtils)) // 指定权限校验者
.authorizeRequests().filterSecurityInterceptorOncePerRequest(true).anyRequest() // 开始鉴权
.authenticated();
}
// 标注由子类实现
protected abstract String getJWTSecret();
}
// 此部分为filter中部分内容,过滤器主要设置验证参数Authentication
SecurityContextHolder.clearContext();
if (StringUtils.isNotBlank(token)) {
SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(token)); // 设置用户全局变量
}
// 获取全局变量
public static JWTAuthenticationToken getJWTAuthentication() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
AssertUtils.assertTrue(
authentication != null && authentication.isAuthenticated()
&& authentication instanceof JWTAuthenticationToken,
ErrorCode.NOT_LOGIN, "用户未登录");
return (JWTAuthenticationToken) authentication;
}
// 权限校验类
public class JWTAuthenticationProvider implements AuthenticationProvider {
private String jwtSecret;
private JWTUtils jwtUtils;
public JWTAuthenticationProvider(String jwtSecret, JWTUtils jwtUtils) {
this.jwtSecret = jwtSecret;
this.jwtUtils = jwtUtils;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 自定义JWT权限验证对象
JWTAuthenticationToken jwtAuthentication = (JWTAuthenticationToken) authentication;
// 获取前端表单中输入后返回的用户名、密码
AssertUtils.assertNotNull(jwtAuthentication, ErrorCode.ILLEGAL_PARAMETER, "非法参数");
String jwtToken = jwtAuthentication.getJwtToken();
return jwtUtils.generateAuthenticationToken(jwtToken, jwtSecret); // 生成对应token
}
@Override
public boolean supports(Class<?> authentication) {
return JWTAuthenticationToken.class.isAssignableFrom(authentication);
}
}
// 自定义JWT权限验证对象
@data
public class JWTAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 689177934199815232L;
private String userCode;
private String userPhone;
private String userEmail;
private Integer status;
private String companyName;
private Map<String, Object> attributes = new HashMap<>();
private String jwtToken;
public JWTAuthenticationToken(String jwtToken) {
super(null);
this.jwtToken = jwtToken;
setAuthenticated(false);
}
public JWTAuthenticationToken(String userCode, String userPhone,String userEmail, Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes) {
super(authorities);
this.userCode=userCode;
this.userPhone = userPhone;
this.userEmail = userEmail;
if (!CollectionUtils.isEmpty(attributes)) {
this.attributes = attributes;
}
super.setAuthenticated(true);
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() { // 用于设置设置全局信息
return userEmail;
}
// JWT UTIL 列举部分信息用于理解
@Component
public class JWTUtils {
private static final String TOKEN_KEY_USER_CODE = "userCode";
private static final String TOKEN_KEY_USER_PHONE = "phone";
/**
* 存储用户code与token的对应关系的前缀
*/
public static final String USERCODE_AND_JWTTOKEN_RELATIONSHIP = "USERCODE_AND_JWTTOKEN_RELATIONSHIP_";
@Resource
private RedisTemplate redisTemplate;
/**
* 生成jwt token
*
* @param expireTime 过期时间
* @param phone 手机号
*/
@SuppressWarnings("unchecked")
public String generateJWTToken(Date expireTime,String userCode,String phone,String email,Integer status,String companyName,
Map<String, Object> attributes, String jwtSecret, Long timeout,
TimeUnit timeUnit, JWTTokenTypeEnums jwtTokenTypeEnums) {
Map<String, Object> claims = new HashMap<>();
attributes.put(JWTTokenTypeEnums.TOKEN.toString(), jwtTokenTypeEnums);
claims.put(TOKEN_KEY_ATTRIBUTES, attributes);
String jwtToken = Jwts.builder().addClaims(claims).setExpiration(expireTime).signWith(SignatureAlgorithm.HS512, resetJwtSecret(jwtSecret)).compact();
// 保存用户和对应token的key关系
redisTemplate.opsForValue().set(getUserCodeAndTokenRelationshipKey(jwtTokenTypeEnums, getCtype(attributes), userCode), jwtToken, timeout, timeUnit);
return jwtToken;
}
/**
* 解析jwtToken
*/
public Claims parseJWTToken(String jwtToken, String jwtSecret) {
return Jwts.parser().setSigningKey(resetJwtSecret(jwtSecret)).parseClaimsJws(jwtToken)
.getBody();
}
/**
* 生成认证实体,基于spring security authenticationToken机制
*
* @param authToken token
* @param jwtSecret 秘钥
*/
@SuppressWarnings("unchecked")
public JWTAuthenticationToken generateAuthenticationToken(String authToken, String jwtSecret) {
Claims claims = null;
claims = parseJWTToken(authToken, jwtSecret);
String userCode = claims.get(TOKEN_KEY_USER_CODE);
Map<String, Object> attributes = (Map<String, Object>) claims.get(TOKEN_KEY_ATTRIBUTES);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
return new JWTAuthenticationToken(userCode, userName, personType, authorities,attributes);
}
}
JWT权限验证流程总结
准备好JWT相关配置类和自定义token类,并且和spring security进行关系绑定。
在用户登录成功后为用户生成一个合乎JWT格式的token和refreshToken。(登录接口不需鉴权)
调用其他接口时,则获取filter设置全局token对象JWTAuthenticationToken(包含token值)。
provider负责解析和校验token并从token中获取用户信息,且是否超时,并设置全局用户参数。(JWT属于暴力破解,和Redis关系不大)
如果需要刷新token 则从新生产refreshtoken。
JWT和Redis token的区别
jwt token属于暴力破解模式,直接解析加密串获取用户信息和token时限。
redis 存储token模式相对安全,但是效率稍低。
尝试在JWT中增加自有的加密方式,比如JWT + timestamp + 等再做一次SHA256加密解密等。
用户退出可以设置JWT参数,让其失效。
JAVA 观察者模式
java 自带了观察者模式的实现,通知类实现Observable,实现类创建 noticeAllWatchers(obj)来通知所有观察者。
具体实现类实现Observer,通过update通知观察者处理对应的逻辑。
应用初始化时需要向通知类Observable注册,并在需要的时候调用Observable实现类方法noticeAllWatchers(obj)通知所有观察者;
总结
后面将会继续补充,关于web开发中的一些问题。