Commit 348908b7 by 袁伟铭

第一版

parents
HELP.md
.gradle
/build/
/logs/
**/build/
**/out/
**/logs/
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
/out/
**/target/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
# civil-bigdata
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zq</groupId>
<artifactId>civil-bigdata</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-server</artifactId>
<dependencies>
<dependency>
<groupId>com.zq</groupId>
<artifactId>common-utils</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--Spring devtools 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 远程调用cloud feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--数据库相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${alibaba.druid.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- spring热部署 -->
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency> -->
</dependencies>
</plugin>
</plugins>
<!-- 使用@profiles.active@需要添加以下内容 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<!--开启过滤,用指定的参数替换directory下的文件中的参数-->
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
package com.zq.api;
import com.zq.common.config.base.SpringContextHolder;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableAsync
@EnableScheduling
@EnableFeignClients
@MapperScan("com.zq.api.dao")
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.zq.api", "com.zq.common.config"})
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
@Bean
public SpringContextHolder springContextHolder() {
return new SpringContextHolder();
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.cache;
import java.util.Collection;
import java.util.Set;
public interface Cache {
/**
* 设置缓存名称
*
* 2015年4月26日 下午8:31:14
*
* @param name
* @return
*/
Cache name(String name);
/**
* 根据key获取缓存数据
*
* 2015年4月26日 下午8:31:25
*
* @param key
* @return
*/
<T> T get(String key);
/**
* 添加缓存获取
*
* 2015年4月26日 下午8:31:46
*
* @param key
* @param value
* @return
*/
Cache add(String key, Object value);
/**
* 移除缓存数据
*
* 2015年4月26日 下午8:31:52
*
* @param key
* @return
*/
Object remove(String key);
/**
* 清楚所有数据
*
* 2015年4月26日 下午8:31:52
*
* @return
*/
void clear();
/**
* 获取缓存数量
*
* 2015年4月26日 下午8:31:59
*
* @return
*/
int size();
/**
* 返回数据key列表
*
* 2017年1月18日 下午4:24:01
*
* @return
*/
Set<String> keys();
/**
* 返回数据缓存列表
*
* 2015年4月26日 下午8:33:11
*
* @return
*/
<T> Collection<T> values();
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.cache;
import com.zq.api.cache.impl.MemorySerializeCache;
import com.zq.api.utils.serializable.SerializerManage;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 缓存管理接口
* <p>
* 2015年4月26日 下午8:47:45
*/
public class CacheManager {
private static ConcurrentHashMap<String, Cache> cacheManager = new ConcurrentHashMap<>();
static ICacheManager _CreateCache;
protected CacheManager() {
}
static {
_CreateCache = new ICacheManager() {
public Cache getCache() {
return new MemorySerializeCache(SerializerManage.getDefault());
}
};
}
public static void setCache(ICacheManager thisCache) {
_CreateCache = thisCache;
}
public static Cache get(String name) {
Cache cache = cacheManager.get(name);
if (cache == null) {
synchronized (cacheManager) {
cache = cacheManager.get(name);
if (cache == null) {
cache = _CreateCache.getCache();
cache.name(name);
cacheManager.put(name, cache);
}
}
}
return cache;
}
public static int size() {
return cacheManager.size();
}
public static Collection<Cache> values() {
return cacheManager.values();
}
public static Set<String> keys() {
return cacheManager.keySet();
}
}
\ No newline at end of file
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.cache;
public interface ICacheManager {
Cache getCache();
}
\ No newline at end of file
package com.zq.api.cache.impl;
import cn.hutool.core.util.StrUtil;
import com.zq.api.cache.Cache;
import com.zq.api.cache.CacheManager;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ApiCache {
private final static String cacheName = "ApiCache";
private static Cache cache;
public static void init() {
if (cache == null) {
log.info("####API Cache初始化......");
cache = CacheManager.get(cacheName);
}
}
public static <T> T getCache(String key) {
if (StrUtil.isEmpty(key)) {
return null;
}
init();
return cache.get(key);
}
@SuppressWarnings("unchecked")
public static <T> T removeCache(String key) {
if (StrUtil.isEmpty(key)) {
return null;
}
init();
return (T) cache.remove(key);
}
public static Cache addCache(String key, Object value) {
if (StrUtil.isEmpty(key)) {
return null;
}
init();
cache.add(key, value);
return cache;
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.cache.impl;
import com.zq.api.cache.Cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 内存
* <p>
* 2015年4月26日 下午8:24:11
*/
public class MemoryCache implements Cache {
protected String name;
protected Map<String, Object> map = new ConcurrentHashMap<String, Object>();
public MemoryCache() {
}
public String name() {
return name;
}
public MemoryCache name(String name) {
this.name = name;
return this;
}
public MemoryCache add(String key, Object value) {
map.put(key, value);
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) map.get(key);
}
public Object remove(String key) {
return map.remove(key);
}
public void clear() {
map.clear();
}
public int size() {
return map.size();
}
public Set<String> keys() {
if (map.size() == 0) {
return null;
}
return map.keySet();
}
@SuppressWarnings("unchecked")
public <T> Collection<T> values() {
if (map.size() == 0) {
return null;
}
Collection<T> list = new ArrayList<T>();
for (Object obj : map.values()) {
list.add((T) obj);
}
return list;
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.cache.impl;
import com.zq.api.cache.Cache;
import com.zq.api.utils.serializable.Serializer;
import com.zq.api.utils.serializable.SerializerManage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 内存序列化
* <p>
* 2015年4月26日 下午8:24:23
*/
public class MemorySerializeCache implements Cache {
protected Serializer serializer;
protected String name;
protected Map<String, byte[]> map = new ConcurrentHashMap<String, byte[]>();
public MemorySerializeCache() {
this.serializer = SerializerManage.getDefault();
}
public MemorySerializeCache(Serializer serializer) {
this.serializer = serializer;
}
public String name() {
return name;
}
public MemorySerializeCache name(String name) {
this.name = name;
return this;
}
public MemorySerializeCache add(String key, Object value) {
try {
map.put(key, serializer.serialize(value));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
try {
return (T) serializer.deserialize(map.get(key));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public Object remove(String key) {
return map.remove(key);
}
public void clear() {
map.clear();
}
public int size() {
return map.size();
}
public Set<String> keys() {
if (map.size() == 0) {
return null;
}
return map.keySet();
}
@SuppressWarnings("unchecked")
public <T> Collection<T> values() {
if (map.size() == 0) {
return null;
}
Collection<T> list = new ArrayList<T>();
for (byte[] obj : map.values()) {
try {
list.add((T) serializer.deserialize(obj));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
}
package com.zq.api.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class ApiCommonConfig implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
ConfigCache.init();
log.info("------完成初始化系统配置表-------");
}
}
package com.zq.api.config;
import com.zq.api.interceptor.ApiInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class ApiInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApiInterceptor()).addPathPatterns("/api/**");
// .excludePathPatterns("/static/**")
// .excludePathPatterns("/login")
// .excludePathPatterns("/logout")
// .excludePathPatterns("/getImage")
// .excludePathPatterns("/do_login")
// .excludePathPatterns("/index");
}
}
package com.zq.api.config;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.alibaba.fastjson.JSON;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.vo.ApiTokenVo;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
/**
* @author /
*/
@Slf4j
@Component
public class ApiTokenUtils implements InitializingBean {
private static SecurityProperties properties;
private static final String APP_TOKEN_KEY = "appToken";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
public ApiTokenUtils(SecurityProperties securityProperties) {
ApiTokenUtils.properties = securityProperties;
}
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(ApiTokenVo tokenVo, long minutes) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder jwtBuilder = Jwts.builder()
.setSubject(tokenVo.getPhone())
.setIssuedAt(now)
.claim(APP_TOKEN_KEY, tokenVo)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(tokenVo.getUserId().toString());
if (minutes >= 0) {
long expMillis = nowMillis + (minutes * 60 * 1000);
Date exp = new Date(expMillis);
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
public static ApiTokenVo getAppTokenVo(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
return JSON.parseObject(JSON.toJSONString(claims.get(APP_TOKEN_KEY)), ApiTokenVo.class);
} catch (Exception e) {
return null;
}
}
public static boolean isTokenValid(String token) {
try {
//解析JWT字符串中的数据,并进行最基础的验证
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
return true;
}
//在解析JWT字符串时,如果密钥不正确,将会解析失败,抛出SignatureException异常,说明该JWT字符串是伪造的
//在解析JWT字符串时,如果‘过期时间字段’已经早于当前时间,将会抛出ExpiredJwtException异常,说明本次请求已经失效
catch (SignatureException | ExpiredJwtException e) {
return false;
}
}
}
package com.zq.api.config;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zq.api.cache.Cache;
import com.zq.api.cache.CacheManager;
import com.zq.api.dao.SysConfigDao;
import com.zq.api.utils.NumberUtils;
import com.zq.common.config.base.SpringContextHolder;
import com.zq.common.entity.SysConfig;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 配置管理缓存
* <p>
* 2016年12月17日 下午11:26:25
*/
@Slf4j
public class ConfigCache {
private final static String cacheName = "ConfigCache";
private static Cache cache;
private ConfigCache() {
}
public static void init() {
if (cache == null) {
cache = CacheManager.get(cacheName);
}
log.info("####参数配置Cache初始化......");
Map<String, SysConfig> cacheMap = new HashMap<>();
SysConfigDao configDao = SpringContextHolder.getBean(SysConfigDao.class);
List<SysConfig> sysConfigList = configDao.selectList(Wrappers.lambdaQuery(null));
for (SysConfig config : sysConfigList) {
cacheMap.put(config.getCode(), config);
}
cache.add("cacheMap", cacheMap);
}
public static void update() {
init();
}
public static SysConfig getSysConfig(String code) {
return getSysConfigMap().get(code);
}
public static String getValue(String code) {
return getSysConfig(code) == null ? null : getSysConfig(code).getValue();
}
public static int getValueToInt(String code) {
return NumberUtils.parseInt(getValue(code));
}
public static Boolean getValueToBoolean(String code) {
String val = getValue(code);
try {
return Boolean.valueOf(val);
} catch (Exception e) {
return false;
}
}
private static Map<String, SysConfig> getSysConfigMap() {
return cache.get("cacheMap");
}
}
package com.zq.api.config;
import com.zq.common.http.HttpRequestUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
public class FeignConfig {
/**
* 解决fein远程调用丢失请求头
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest request = HttpRequestUtils.getRequest();
template.header("X-App-Token", request.getParameter("token"));
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
// 跳过content-length
if (name.equals("content-length")) {
continue;
}
String values = request.getHeader(name);
template.header(name, values);
}
}
}
};
}
}
package com.zq.api.constant;
/**
* API响应码
*/
public enum ApiCodeEnum {
SUCCESS("200", "成功"),
UNKNOWN_ERROR("100", "未知错误"),
LOGIN_VALID_ERROR("101", "登陆验证失败"),
VERSION_ERROR("102", "版本号错误"),
METHOD_ERROR("103", "调用方法不存在"),
METHOD_HANDLER_ERROR("104", "调用方法异常"),
PARAM_ERROR("105", "传递参数异常"),
IP_BLACK("106", "IP黑名单拦截"),
SERVER_MAINTAIN("107", "API服务维护中"),
CHECK_SIGN_VALID_ERROR("108", "签名校验失败"),
BUSINESS_ERROR("400", "业务处理失败"),
SERVER_ERROR("500", "服务器繁忙"),
;
private String code;
private String msg;
ApiCodeEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String code() {
return code;
}
public String msg() {
return msg;
}
}
package com.zq.api.controller;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.zq.api.constant.ApiCodeEnum;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.service.ApiService;
import com.zq.api.utils.ApiUtils;
import com.zq.common.config.security.ApiTokenUtils;
import com.zq.common.utils.ThrowableUtil;
import com.zq.common.vo.ApiTokenVo;
import com.zq.common.vo.ResultVo;
import feign.FeignException;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Api(tags = "API接口")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class ApiController {
private final ApiService apiService;
// 允许用户未登录状态下执行的方法名
private String[] allowMethod = {"test"};
/**
* 获取信息入口
* <p>
* 2016年10月3日 下午1:38:27
*/
@RequestMapping("/action")
public ApiResp action(HttpServletRequest request) {
long start = System.currentTimeMillis();
ApiForm form = ServletUtil.toBean(request, ApiForm.class, true);
// 不处理Request Method:OPTIONS的请求
if (request.getMethod().equals("OPTIONS")) {
return ApiUtils.getSuccessResp(form);
}
//解析业务参数
if (!form.parseBizContent()) {
return ApiUtils.getParamError(form);
}
String method = form.getMethod();
if (StrUtil.isBlank(method)) {
method = request.getParameter("method");
form.setMethod(method);
}
if (StrUtil.isBlank(form.getToken())) {
boolean contains = Arrays.asList(allowMethod).contains(method);
if (!contains) {
return ApiUtils.getLoginValidError(form);
}
}
ApiTokenVo tokenVo = ApiTokenUtils.getAppTokenVo(form.getToken());
if (tokenVo == null) {
boolean contains = Arrays.asList(allowMethod).contains(method);
if (!contains) {
return ApiUtils.getLoginValidError(form);
}
} else {
form.setUserId(String.valueOf(tokenVo.getUserId()));
form.setApiTokenVo(tokenVo);
}
String stackTrace = "";
// 调用接口方法
ApiResp resp;
try {
resp = apiService.action(form);
} catch (Exception e) {
stackTrace = ThrowableUtil.getStackTrace(e);
e.printStackTrace();
// 判断指定异常是否来自或者包含指定异常
if (ExceptionUtil.isFromOrSuppressedThrowable(e, FeignException.Unauthorized.class)) {
resp = ApiUtils.toApiResp(form, ResultVo.fail(401, "Unauthorized"));
} else {
resp = ApiUtils.getMethodHandlerError(form);
}
}
// 没有数据输出空
resp = resp == null ? new ApiResp(form) : resp;
String logType = resp.isSuccess() ? "INFO" : "ERROR";
// 如果是500错误, 服务会返回错误的堆栈信息
if (resp.getCode().equals(ApiCodeEnum.SERVER_ERROR.code())) {
stackTrace = resp.getMsg();
resp.setMsg(ApiCodeEnum.SERVER_ERROR.msg());
}
// 调试日志
if (ApiUtils.DEBUG) {
System.out.println("API DEBUG ACTION \n[from=" + form + "]" //
+ "\n[resp=" + JSON.toJSONString(resp) + "]" //
+ "\n[time=" + (System.currentTimeMillis() - start) + "ms]");
}
String clientIP = ServletUtil.getClientIP(request);
apiService.addLog(form, clientIP, logType, resp.getMsg(), stackTrace, System.currentTimeMillis() - start);
return resp;
}
/**
* 开关调试日志
* <p>
* 2016年10月3日 下午5:47:46
*/
@RequestMapping("/debug")
public ApiResp debug(HttpServletRequest request) {
ApiForm from = ServletUtil.toBean(request, ApiForm.class, true);
ApiUtils.DEBUG = !ApiUtils.DEBUG;
return new ApiResp(from).setData(ApiUtils.DEBUG);
}
}
package com.zq.api.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.common.entity.ApiLog;
import org.springframework.stereotype.Repository;
/**
* (TApiLog)表数据库访问层
*
* @author wilmiam
* @since 2021-04-07 14:42:23
*/
@Repository
public interface ApiLogDao extends BaseMapper<ApiLog> {
}
\ No newline at end of file
package com.zq.api.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.common.entity.SysConfig;
import org.springframework.stereotype.Repository;
/**
* 系统配置表(SysConfig)表数据库访问层
*
* @author wilmiam
* @since 2020-10-14 11:52:04
*/
@Repository
public interface SysConfigDao extends BaseMapper<SysConfig> {
}
\ No newline at end of file
package com.zq.api.feign;
import com.zq.api.config.FeignConfig;
import com.zq.common.vo.ResultVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@FeignClient(name = "CMS-SERVER", configuration = FeignConfig.class) //指定调用哪个微服务
@RequestMapping("/cms")
public interface CmsFeign {
@GetMapping("/adviceFeedback/getAdviceFeedbackById")
ResultVo test(Map<String, Object> paramsMap);
}
package com.zq.api.form;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zq.api.config.ConfigCache;
import com.zq.api.utils.ApiUtils;
import com.zq.api.utils.NumberUtils;
import com.zq.common.encrypt.EncryptUtils;
import com.zq.common.encrypt.RsaUtils;
import com.zq.common.vo.ApiTokenVo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.TreeMap;
/**
* api 基础form
* <p>
* 2016年9月29日 上午10:29:27
*/
@Slf4j
@Data
public class ApiForm {
private String appId;
private String token;
private String userId;
private String method; // 请求方法
private String charset; // 编码
private String format; // 返回参数格式
private String signType; // 签名类型
private String sign; // 签名
private String timestamp; // 时间戳, 单位: 毫秒
private String nonce; // 随机字串(建议使用UUID)
private String version; // 接口版本
private String apiNo; // 接口码
private String bizContent; // 请求业务参数
private JSONObject bizContentJson; // 请求业务的json对象
private ApiTokenVo apiTokenVo;
public boolean parseBizContent() {
try {
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT"); // API参数是否加密
if (StrUtil.isNotBlank(bizContent) && flag) {
bizContent = ApiUtils.decode(bizContent);
}
bizContentJson = JSON.parseObject(bizContent);
if (bizContentJson == null) {
bizContentJson = new JSONObject();
}
return true;
} catch (Exception e) {
log.error("bizContent解析失败:{}", e.getMessage());
return false;
}
}
public JSONObject getContentJson() {
if (bizContentJson != null) {
return bizContentJson;
}
parseBizContent();
return bizContentJson;
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public double getDouble(String key) {
return NumberUtils.parseDbl(get(key));
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public long getLong(String key) {
return NumberUtils.parseLong(get(key));
}
public float getFloat(String key) {
return NumberUtils.parseFloat(get(key));
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public int getInt(String key) {
return NumberUtils.parseInt(get(key));
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public String get(String key) {
return getContentJson().getString(key);
}
/**
* 获取参数
*
* @param key
* @return
*/
public Boolean getBoolean(String key) {
return getContentJson().getBoolean(key);
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public JSONObject getJSONObject(String key) {
return getContentJson().getJSONObject(key);
}
/**
* 获取参数
* <p>
* 2016年10月3日 下午9:02:45
*
* @param key
* @return
*/
public JSONArray getJSONArray(String key) {
return getContentJson().getJSONArray(key);
}
/**
* 获取参数
*
* @return
*/
public Map<String, Object> getParamsMap() {
return getParamsMap(false);
}
/**
* 获取参数
*
* @return
*/
public Map<String, Object> getParamsMap(boolean isSetUserId) {
return getParamsMap(isSetUserId, null);
}
/**
* 获取参数
*
* @return
*/
public Map<String, Object> getParamsMap(boolean isSetUserId, String key) {
JSONObject json = getContentJson();
Map<String, Object> innerMap = json.getInnerMap();
innerMap.put("token", getToken());
if (isSetUserId) {
if (StringUtils.isBlank(key)) {
innerMap.put("userId", userId);
} else {
innerMap.put(key, userId);
}
}
return innerMap;
}
public TreeMap<String, String> getSignTreeMap() {
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("timestamp", this.timestamp);
treeMap.put("nonce", this.nonce);
treeMap.put("method", this.method);
treeMap.put("version", this.version);
String bizContent = StrUtil.isBlank(this.bizContent) ? "" : this.bizContent;
treeMap.put("bizContent", bizContent);
return treeMap;
}
}
package com.zq.api.form;
import com.zq.api.constant.ApiCodeEnum;
import lombok.Getter;
@Getter
public class ApiResp {
private String apiNo = "";
private String code = ApiCodeEnum.SUCCESS.code();
private String msg = ApiCodeEnum.SUCCESS.msg();
private Long timestamp = System.currentTimeMillis();
private Object data;
public ApiResp(ApiForm form) {
this.apiNo = form.getApiNo() == null ? "" : form.getApiNo();
}
public ApiResp(ApiCodeEnum apiCodeEnum) {
this.code = apiCodeEnum.code();
this.msg = apiCodeEnum.msg();
}
public ApiResp(ApiForm form, ApiCodeEnum apiCodeEnum) {
this.code = apiCodeEnum.code();
this.msg = apiCodeEnum.msg();
}
public ApiResp setApiNo(String apiNo) {
this.apiNo = apiNo;
return this;
}
public ApiResp setCode(String code) {
this.code = code;
return this;
}
public ApiResp setMsg(String msg) {
this.msg = msg;
return this;
}
public ApiResp setTimestamp(Long timestamp) {
this.timestamp = timestamp;
return this;
}
public ApiResp setData(Object data) {
this.data = data;
return this;
}
public Boolean isSuccess() {
return this.getCode().equals(ApiCodeEnum.SUCCESS.code());
}
}
package com.zq.api.interceptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.zq.api.config.ConfigCache;
import com.zq.api.form.ApiForm;
import com.zq.api.utils.ApiUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
public class ApiInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
String queryString = request.getQueryString();
System.out.println("get URI = " + request.getRequestURL());
System.out.println("get queryString = " + queryString);
long start = System.currentTimeMillis();
// 获取传递参数
ApiForm form = ServletUtil.toBean(request, ApiForm.class, true);
// 开关
boolean flag = ConfigCache.getValueToBoolean("API.FLAG");
if (!flag) {
ServletUtil.write(response, JSON.toJSONString(ApiUtils.getServerMaintain(form), SerializerFeature.WriteMapNullValue), "application/json;charset=utf-8");
return false;
}
// 黑名单
String ip = ServletUtil.getClientIP(request);
String blackIps = ConfigCache.getValue("API.IP.BLACK");
if (StrUtil.isNotBlank(ip) && StrUtil.isNotBlank(blackIps)) {
List<String> ipList = Arrays.asList(blackIps.split(","));
// 如果黑名单包含该IP返回错误信息
if (ipList.contains(ip)) {
ServletUtil.write(response, JSON.toJSONString(ApiUtils.getIpBlackResp(form), SerializerFeature.WriteMapNullValue), "application/json;charset=utf-8");
return false;
}
} else {
log.debug("WARN: ApiInterceptor can't get ip ...");
}
// 版本验证
String version = form.getVersion();
String versions = ConfigCache.getValue("API.VERSIONS");
if (StrUtil.isEmpty(version)) {
ServletUtil.write(response, JSON.toJSONString(ApiUtils.getVersionErrorResp(form), SerializerFeature.WriteMapNullValue), "application/json;charset=utf-8");
return false;
} else if (!StrUtil.isEmpty(versions)) {
List<String> versionList = Arrays.asList(versions.split(","));
// 如果不支持该版本返回错误信息
if (!versionList.contains(version)) {
ServletUtil.write(response, JSON.toJSONString(ApiUtils.getVersionErrorResp(form), SerializerFeature.WriteMapNullValue), "application/json;charset=utf-8");
return false;
}
}
// 调试日志
if (ApiUtils.DEBUG) {
log.info("API DEBUG INTERCEPTOR \n[path=" + uri + "/" + queryString + "]" //
+ "[from:" + form.toString() + "]" //
+ "\n[time=" + (System.currentTimeMillis() - start) + "ms]");
}
return true;
}
}
package com.zq.api.service;
import cn.hutool.core.date.DateUtil;
import com.zq.api.config.ConfigCache;
import com.zq.api.constant.ApiCodeEnum;
import com.zq.api.dao.ApiLogDao;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.utils.ApiUtils;
import com.zq.api.utils.ReflectionUtils;
import com.zq.common.entity.ApiLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class ApiService {
private final ApiLogDao apiLogDao;
private static List<String> methodList;
static {
methodList = methodList();
}
public IApiLogic getApiLogic(ApiForm form) {
IApiLogic apiLogic = ApiUtils.getApiLogic(form);
return apiLogic;
}
public static List<String> methodList() {
List<String> methodList = new ArrayList<>();
Method[] methods = IApiLogic.class.getMethods();
for (Method method : methods) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1 && (params[0] == ApiForm.class)) {
methodList.add(method.getName());
}
}
return methodList;
}
public ApiResp action(ApiForm form) throws Exception {
if (!methodList.contains(form.getMethod())) {
return ApiUtils.getMethodError(form);
}
// 登陆验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.LOGIN.VALID");
IApiLogic apiLogic = getApiLogic(form);
if (validFlag) {
// 先进行登陆验证。如果验证失败,直接返回错误
ApiResp validResp = apiLogic.valid(form);
if (!validResp.getCode().equals(ApiCodeEnum.SUCCESS.code())) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
return (ApiResp) ReflectionUtils.invokeMethod(apiLogic, form.getMethod(), new Class<?>[]{ApiForm.class}, new Object[]{form});
}
@Async
public void addLog(ApiForm form, String ip, String logType, String respMsg, String errorInfo, Long timeCost) {
apiLogDao.insert(ApiLog.builder()
.appId(form.getAppId())
.userId(form.getUserId())
.method(form.getMethod())
.version(form.getVersion())
.bizContent(form.getBizContent())
.ip(ip)
.logType(logType)
.respMsg(respMsg)
.stackTrace(errorInfo)
.timeCost(timeCost)
.createTime(DateUtil.date().toJdkDate())
.build());
}
}
package com.zq.api.service;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
/**
* API通用接口
* <p>
* 2016年11月15日 下午9:43:49
*/
public interface IApiCommon {
/**
* 登陆接口
* <p>
* 2016年10月1日 下午9:20:12
*
* @param form
* @return
*/
ApiResp login(ApiForm form);
/**
* 登陆后,验证接口
* <p>
* 2016年10月1日 下午9:20:12
*
* @param form
* @return
*/
ApiResp valid(ApiForm form);
/**
* 登出接口
* <p>
* 2016年10月1日 下午9:20:12
*
* @param form
* @return
*/
ApiResp logout(ApiForm form);
/**
* 获取配置信息
* <p>
* 2016年10月1日 下午9:20:12
*
* @param form
* @return
*/
ApiResp config(ApiForm form);
}
package com.zq.api.service;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
/**
* api实现接口
* <p>
* 2016年9月29日 上午11:45:08
*/
public interface IApiLogic extends IApiCommon {
/**
* 测试连接
*
* @param form
* @return
*/
ApiResp test(ApiForm form);
}
package com.zq.api.service.impl;
import com.zq.api.feign.CmsFeign;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.service.IApiLogic;
import com.zq.api.utils.ApiUtils;
import com.zq.common.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ApiV100Logic extends BaseApiLogic implements IApiLogic {
@Autowired
private CmsFeign cmsFeign;
/**
* 测试连接
*
* @param form
* @return
*/
@Override
public ApiResp test(ApiForm form) {
return ApiUtils.toApiResp(form, ResultVo.success());
}
}
package com.zq.api.service.impl;
import com.zq.api.service.IApiLogic;
import org.springframework.stereotype.Component;
@Component
public class ApiV101Logic extends ApiV100Logic implements IApiLogic {
}
package com.zq.api.service.impl;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.service.IApiLogic;
import com.zq.api.utils.ApiUtils;
import java.util.TreeMap;
/**
* API基础类
* <p>
* 2016年11月15日 下午9:48:27
*/
public abstract class BaseApiLogic implements IApiLogic {
@Override
public ApiResp login(ApiForm form) {
return null;
}
@Override
public ApiResp valid(ApiForm form) {
// 不需要验证的方法
if (notValid(form)) {
return ApiUtils.getSuccessResp(form);
}
String timestamp = form.getTimestamp();
// 一分钟内的数据有效
if (Long.parseLong(timestamp) + (60 * 1000) > System.currentTimeMillis()) {
return ApiUtils.getCheckSignValidError(form);
}
String serverSign = ApiUtils.getSign(form.getSignTreeMap());
if (!serverSign.equals(form.getSign())) {
return ApiUtils.getCheckSignValidError(form);
}
return ApiUtils.getSuccessResp(form);
}
/**
* 不需要验证的方法
* <p>
* 2016年11月15日 下午11:03:01
*
* @param form
* @return
*/
protected boolean notValid(ApiForm form) {
return form.getMethod().equals("login");
}
@Override
public ApiResp logout(ApiForm form) {
return new ApiResp(form).setData("ok");
}
@Override
public ApiResp config(ApiForm form) {
return null;
}
}
package com.zq.api.task;
import com.zq.api.config.ConfigCache;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ConfigTask {
@Scheduled(cron = "0/5 * * * * ?")
public void updateConfig() {
ConfigCache.init();
}
}
package com.zq.api.utils;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zq.api.constant.ApiCodeEnum;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.service.IApiLogic;
import com.zq.api.service.impl.ApiV100Logic;
import com.zq.api.service.impl.ApiV101Logic;
import com.zq.common.encrypt.EncryptUtils;
import com.zq.common.encrypt.RsaUtils;
import com.zq.common.vo.ResultVo;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@Component
public class ApiUtils {
private static final Map<String, IApiLogic> map = new HashMap<>();
/**
* 调试日志
*/
public static boolean DEBUG = false;
public ApiUtils(ApiV100Logic apiV100Logic, ApiV101Logic apiV101Logic) {
addApi("1.0.0", apiV100Logic);
addApi("1.0.1", apiV101Logic);
}
public static void addApi(String version, IApiLogic apiLogic) {
map.put(version, apiLogic);
}
public static IApiLogic getApiLogic(ApiForm form) {
return map.get(form.getVersion());
}
/**
* 获取成功响应
*
* @param form
* @return
*/
public static ApiResp getSuccessResp(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.SUCCESS);
}
/**
* api关闭resp
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getServerMaintain(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.SERVER_MAINTAIN);
}
/**
* 获取登录严重异常
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getLoginValidError(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.LOGIN_VALID_ERROR);
}
/**
* 版本错误返回resp
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getVersionErrorResp(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.VERSION_ERROR);
}
/**
* ip黑名单返回resp
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getIpBlackResp(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.IP_BLACK);
}
/**
* 调用方法不存在resp
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getMethodError(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.METHOD_ERROR);
}
/**
* 调用方法异常resp
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getMethodHandlerError(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.METHOD_HANDLER_ERROR);
}
/**
* 传递参数异常
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getParamError(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.PARAM_ERROR);
}
/**
* 传递参数异常
* <p>
* 2016年9月29日 上午11:44:38
*
* @return
*/
public static ApiResp getCheckSignValidError(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.CHECK_SIGN_VALID_ERROR);
}
public static ApiResp toApiResp(ApiForm form, ResultVo resultVo) {
ApiResp apiResp = new ApiResp(form);
if (resultVo.isSuccess()) {
apiResp.setData(resultVo.getData() == null ? "" : resultVo.getData());
} else {
return apiResp.setCode(String.valueOf(resultVo.getErrCode())).setMsg(resultVo.getErrMsg());
}
return apiResp;
}
/**
* 解码
* <p>
* 2017年3月15日 下午1:49:09
*
* @param params
* @return
* @throws UnsupportedEncodingException
*/
public static String decode(String params) throws UnsupportedEncodingException {
params = URLDecoder.decode(params, "utf-8");
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.privateKey);
return params;
}
/**
* 编码
* <p>
* 2017年3月15日 下午1:49:09
*
* @param params
* @return
* @throws UnsupportedEncodingException
*/
public static String encode(String params) throws UnsupportedEncodingException {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.publicKey);
if (StringUtils.isBlank(params)) {
return "";
}
params = URLEncoder.encode(params, "utf-8");
return params;
}
/**
* 获取验证sign
* <p>
* 2017年3月15日 下午3:14:27
*
* @param paramMaps
* @return
*/
public static String getSign(TreeMap<String, String> paramMaps) {
// 原始请求串
StringBuilder src = new StringBuilder();
for (Map.Entry<String, String> entry : paramMaps.entrySet()) {
src.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
return MD5.create().digestHex(src.toString());
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.utils;
import java.math.BigDecimal;
/**
* 数字处理
*
* @author 2012.08.08
* @email
*/
public class NumberUtils {
private NumberUtils() {
}
/**
* 如果是null返回0
*
* @param obj
* @return
*/
public static int parseInt(Object obj) {
int value = 0;
if (obj != null) {
try {
value = Integer.parseInt(obj.toString());
} catch (Exception e) {
value = 0;
}
}
return value;
}
/**
* 如果是null返回BigDecimal.ZERO
*
* @param obj
* @return
*/
public static BigDecimal parseBigDecimal(Object obj) {
BigDecimal value = BigDecimal.ZERO;
if (obj != null) {
try {
value = new BigDecimal(obj.toString());
} catch (Exception e) {
value = BigDecimal.ZERO;
}
}
return value;
}
/**
* 将传入的字符串转成int型数据 . 遇到任何错误返回0
*
* @param str 待解析的字符串
* @return 解析结果
*/
public static int parseInt(String str) {
return parseInt(str, 0);
}
/**
* 将传入的字符串转成int型数据 . 遇到任何错误返回replaceWith
*
* @param str 待解析的字符串
* @param defaultValue 遇到错误时的替换数字 .
* @return 解析结果
*/
public static int parseInt(String str, int defaultValue) {
try {
defaultValue = Integer.parseInt(str);
} catch (Exception e) {
}
return defaultValue;
}
/**
* 将传入的字符串转成double型数据 . 遇到任何错误返回0
*
* @param str 待解析的字符串
* @return 解析结果
*/
public static double parseDbl(String str) {
return parseDbl(str, 0);
}
/**
* 将传入的字符串转成double型数据 . 遇到任何错误返回replaceWith
*
* @param str 待解析的字符串
* @param defaultValue 遇到错误时的替换数字 .
* @return 解析结果
*/
public static double parseDbl(String str, double defaultValue) {
try {
defaultValue = Double.parseDouble(str);
} catch (Exception e) {
}
return defaultValue;
}
public static float parseFloat(String str) {
return parseFloat(str, 0);
}
public static float parseFloat(String str, float b) {
try {
return Float.parseFloat(str);
} catch (Exception e) {
return b;
}
}
/**
* 遇到错误返回0L
*
* @param str
* @return
* @author 王平
* @since 2009.04.30
*/
public static long parseLong(String str) {
return parseLong(str, 0l);
}
/**
* 遇到错误返回defaultValue
*
* @param str
* @return
* @author 王平
* @since 2009.04.30
*/
public static long parseLong(String str, long defaultValue) {
try {
defaultValue = Long.parseLong(str);
} catch (Exception e) {
}
return defaultValue;
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 方法类
*
* @author syh
*
*/
public class ReflectionUtils {
/**
* 循环向上转型, 获取对象的 DeclaredMethod
*
* @param object
* : 子类对象
* @param methodName
* : 父类中的方法名
* @param parameterTypes
* : 父类中的方法参数类型
* @return 父类中的方法对象
*/
private static Method getDeclaredMethod(Object object, String methodName, Class<?>... parameterTypes) {
Method method = null;
Class<?> clazz = object.getClass();
while (clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName, parameterTypes);
return method;
} catch (Exception e) {
// 未获取到就往上找
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* 直接调用对象方法, 而忽略修饰符(private, protected, default)
*
* @param object
* : 子类对象
* @param methodName
* : 父类中的方法名
* @param parameterTypes
* : 父类中的方法参数类型
* @param parameters
* : 父类中的方法参数
* @return 父类中方法的执行结果
*/
public static Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes, Object[] parameters) throws InvocationTargetException, IllegalAccessException {
// 根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象
Method method = getDeclaredMethod(object, methodName, parameterTypes);
if (method == null) {
System.err.println(object.getClass() + "未获取到" + methodName + "方法!");
return null;
}
// 抑制Java对方法进行检查,主要是针对私有方法而言
method.setAccessible(true);
// 调用object 的 method 所代表的方法,其方法的参数是 parameters
return method.invoke(object, parameters);
}
/**
* 循环向上转型, 获取对象的 DeclaredField
*
* @param object
* : 子类对象
* @param fieldName
* : 父类中的属性名
* @return 父类中的属性对象
*/
private static Field getDeclaredField(Object object, String fieldName) {
Field field = null;
Class<?> clazz = object.getClass();
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField(fieldName);
return field;
} catch (Exception e) {
// 未获取到就往上找
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
*
* @param object
* : 子类对象
* @param fieldName
* : 父类中的属性名
* @param value
* : 将要设置的值
*/
public static void setFieldValue(Object object, String fieldName, Object value) {
// 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
Field field = getDeclaredField(object, fieldName);
if (field == null) {
System.err.println(object.getClass() + "未获取到" + fieldName + "属性!");
return;
}
// 抑制Java对其的检查
field.setAccessible(true);
try {
// 将 object 中 field 所代表的值 设置为 value
field.set(object, value);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
*
* @param object
* : 子类对象
* @param fieldName
* : 父类中的属性名
* @return : 父类中的属性值
*/
public static Object getFieldValue(Object object, String fieldName) {
// 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
Field field = getDeclaredField(object, fieldName);
if (field == null) {
System.err.println(object.getClass() + "未获取到" + fieldName + "属性!");
return null;
}
// 抑制Java对其的检查
field.setAccessible(true);
try {
// 获取 object 中 field 所代表的属性值
return field.get(object);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.utils.serializable;
import java.io.*;
public class JavaSerializer implements Serializer {
public String name() {
return "java";
}
public byte[] serialize(Object obj) throws IOException {
ObjectOutputStream oos = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
} finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
}
}
}
@SuppressWarnings("unchecked")
public <T> T deserialize(byte[] bits) throws IOException {
if (bits == null || bits.length == 0)
return null;
ObjectInputStream ois = null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bits);
ois = new ObjectInputStream(bais);
return (T) ois.readObject();
} catch (ClassNotFoundException e) {
throw new IOException("没有找到类型", e);
} finally {
try {
if (ois != null)
ois.close();
} catch (IOException e) {
throw new IOException("deserialize关闭失败", e);
}
}
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.utils.serializable;
import java.io.IOException;
public interface Serializer {
String name();
byte[] serialize(Object obj) throws IOException;
<T> T deserialize(byte[] bytes) throws IOException;
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.api.utils.serializable;
import java.util.HashMap;
import java.util.Map;
public class SerializerManage {
private static final Map<String, Serializer> map = new HashMap<>();
private static String DEFAULT_KEY;
static {
JavaSerializer javaSerializer = new JavaSerializer();
add(javaSerializer);
DEFAULT_KEY = javaSerializer.name();
}
public static void setDefaultKey(String defaultKey) {
DEFAULT_KEY = defaultKey;
}
/**
* V3.1以前版本兼容
* <p>
* 2017年1月18日 下午2:38:00
*
* @param key
* @param serializer
*/
public static void add(String key, Serializer serializer) {
map.put(key, serializer);
}
public static void add(Serializer serializer) {
map.put(serializer.name(), serializer);
}
public static Serializer get(String key) {
return map.get(key);
}
public static Serializer getDefault() {
return map.get(DEFAULT_KEY);
}
public static byte[] serialize(Object obj) throws Exception {
return getDefault().serialize(obj);
}
public static Object deserialize(byte[] bytes) throws Exception {
return getDefault().deserialize(bytes);
}
}
server:
port: ${api.port}
#配置数据源
spring:
application:
name: ${api.name}
servlet:
#上传文件限制
multipart:
#单个文件大小
max-file-size: 20MB
#设置总上传的数据大小
max-request-size: 50MB
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
redis:
#数据库索引
database: 0
host: ${redis.url}
port: ${redis.port}
password: ${redis.password}
#连接超时时间
timeout: 5000
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: ${jdbc.driver-class-name}
username: ${jdbc.username}
password: ${jdbc.password}
url: ${jdbc.url}
# 初始连接数
initial-size: 5
# 最小连接数
min-idle: 10
# 最大连接数
max-active: 20
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 检测连接是否有效
validation-query: select 1
# 配置监控统计
webStatFilter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
filter:
stat:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# mybatis plus 配置
mybatis-plus:
global-config:
db-config:
select-strategy: not_empty
update-strategy: not_empty
#logging.level.com.zq.drug.dao: debug
\ No newline at end of file
spring:
cloud:
config:
name: config
profile: @profiles.active@
discovery:
enabled: true
service-id: CONFIG-SERVER
uri: http://127.0.0.1:8300/
eureka:
client:
serviceUrl:
defaultZone: http://admin:123456@127.0.0.1:8800/eureka/
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zq</groupId>
<artifactId>civil-bigdata</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-utils</artifactId>
<properties>
<http.client.version>4.5.12</http.client.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--spring boot 集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<!-- 解析客户端操作系统、浏览器信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
<!-- jfinal -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-weixin</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-ext3</artifactId>
<version>4.0.3</version>
</dependency>
</dependencies>
</project>
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation;
import java.lang.annotation.*;
/**
* @author jacky
* 用于标记匿名访问方法
*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>
* 用于判断是否过滤数据权限
* 1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class]
* 2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例:
* 应该是 @DataPermission(joinName = "dept", fieldName = "id")
* </p>
*
* @author Zheng Jie
* @website https://el-admin.vip
* @date 2020-05-07
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
/**
* Entity 中的字段名称
*/
String fieldName() default "";
/**
* Entity 中与部门关联的字段名称
*/
String joinName() default "";
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation;
import com.zq.common.config.limit.LimitType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author jacky
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
// 资源名称,用于描述接口功能
String name() default "";
// 资源 key
String key() default "";
// key prefix
String prefix() default "";
// 时间的,单位秒
int period();
// 限制访问次数
int count();
// 限制类型
LimitType limitType() default LimitType.CUSTOMER;
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Zheng Jie
* @date 2018-11-24
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Zheng Jie
* @date 2019-6-4 13:52:30
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
// Dong ZhaoYang 2017/8/7 基本对象的属性名
String propName() default "";
// Dong ZhaoYang 2017/8/7 查询方式
Type type() default Type.EQUAL;
/**
* 连接查询的属性名,如User类中的dept
*/
String joinName() default "";
/**
* 默认左连接
*/
Join join() default Join.LEFT;
/**
* 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
*/
String blurry() default "";
enum Type {
// jie 2019/6/4 相等
EQUAL
// Dong ZhaoYang 2017/8/7 大于等于
, GREATER_THAN
// Dong ZhaoYang 2017/8/7 小于等于
, LESS_THAN
// Dong ZhaoYang 2017/8/7 中模糊查询
, INNER_LIKE
// Dong ZhaoYang 2017/8/7 左模糊查询
, LEFT_LIKE
// Dong ZhaoYang 2017/8/7 右模糊查询
, RIGHT_LIKE
// Dong ZhaoYang 2017/8/7 小于
, LESS_THAN_NQ
// jie 2019/6/4 包含
, IN
// 不等于
,NOT_EQUAL
// between
,BETWEEN
// 不为空
,NOT_NULL
// 为空
,IS_NULL
}
/**
* @author Zheng Jie
* 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
*/
enum Join {
/** jie 2019-6-4 13:18:30 */
LEFT, RIGHT, INNER
}
}
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation.rest;
import com.zq.common.annotation.AnonymousAccess;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
/**
* Annotation for mapping HTTP {@code DELETE} requests onto specific handler
* methods.
* 支持匿名访问 DeleteMapping
*
* @author liaojinlong
* @see AnonymousGetMapping
* @see AnonymousPostMapping
* @see AnonymousPutMapping
* @see AnonymousPatchMapping
* @see RequestMapping
*/
@AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.DELETE)
public @interface AnonymousDeleteMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation.rest;
import com.zq.common.annotation.AnonymousAccess;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
/**
* Annotation for mapping HTTP {@code GET} requests onto specific handler
* methods.
* <p>
* 支持匿名访问 GetMapping
*
* @author liaojinlong
* @see RequestMapping
*/
@AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface AnonymousGetMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
*
* @since 4.3.5
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation.rest;
import com.zq.common.annotation.AnonymousAccess;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
/**
* Annotation for mapping HTTP {@code PATCH} requests onto specific handler
* methods.
* * 支持匿名访问 PatchMapping
*
* @author liaojinlong
* @see AnonymousGetMapping
* @see AnonymousPostMapping
* @see AnonymousPutMapping
* @see AnonymousDeleteMapping
* @see RequestMapping
*/
@AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PATCH)
public @interface AnonymousPatchMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation.rest;
import com.zq.common.annotation.AnonymousAccess;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
/**
* Annotation for mapping HTTP {@code POST} requests onto specific handler
* methods.
* 支持匿名访问 PostMapping
*
* @author liaojinlong
* @see AnonymousGetMapping
* @see AnonymousPostMapping
* @see AnonymousPutMapping
* @see AnonymousDeleteMapping
* @see RequestMapping
*/
@AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface AnonymousPostMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.annotation.rest;
import com.zq.common.annotation.AnonymousAccess;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.*;
/**
* Annotation for mapping HTTP {@code PUT} requests onto specific handler
* methods.
* * 支持匿名访问 PutMapping
*
* @author liaojinlong
* @see AnonymousGetMapping
* @see AnonymousPostMapping
* @see AnonymousPutMapping
* @see AnonymousDeleteMapping
* @see RequestMapping
*/
@AnonymousAccess
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PUT)
public @interface AnonymousPutMapping {
/**
* Alias for {@link RequestMapping#name}.
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
package com.zq.common.config.base;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 异步执行配置
*
* @author
* @since 2018-01-19
*/
@Configuration
@ConditionalOnProperty(value = "async.pool.enable", havingValue = "true")
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);
@Value("${async.pool.core-pool-size:5}")
private int corePoolSize;
@Value("${async.pool.queue-capacity:10}")
private int maxPoolSize;
@Value("${async.pool.max-pool-size:25}")
private int queueCapacity;
@Value("${async.pool.keepalive-seconds:10}")
private int threadTimeout;
@Override
public Executor getAsyncExecutor() {
log.debug(">> 初始化spring线程池...");
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 当一个任务通过execute(Runnable)方法欲添加到线程池时:
// - 若线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也创建新的线程来处理被添加的任务。
// - 若线程池中的数量等于corePoolSize,但缓冲队列workQueue未满,则任务被放入缓冲队列。
// - 若线程池中的数量大于corePoolSize,缓冲队列workQueue满,且线程池中的数量小于maximumPoolSize,则创建新线程来处理被添加的任务。
// - 若线程池中的数量大于corePoolSize,缓冲队列workQueue满,且线程池中的数量等于maximumPoolSize,则通过handler所指定的策略来处理此任务。
// 即:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,若三者都满,则使用handler处理被拒绝的任务。
// - 若线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。
// 线程池维护线程的最少数量
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 线程池维护线程的最大数量
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 线程池所使用的缓冲队列
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 线程池维护线程所允许的空闲时间
threadPoolTaskExecutor.setKeepAliveSeconds(threadTimeout);
// don't forget to initialize the thread pool
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, params) -> {
String methodName = method.getDeclaringClass().getName() + method.getName();
log.error(">> 异步执行错误: {}({})", methodName, params, throwable);
};
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.base;
/**
* @author: liaojinlong
* @date: 2020/6/9 17:02
* @since: 1.0
* @see {@link SpringContextHolder}
* 针对某些初始化方法,在SpringContextHolder 初始化前时,<br>
* 可提交一个 提交回调任务。<br>
* 在SpringContextHolder 初始化后,进行回调使用
*/
public interface CallBack {
/**
* 回调执行方法
*/
void executor();
/**
* 本回调任务名称
*
* @return /
*/
default String getCallBackName() {
return Thread.currentThread().getId() + ":" + this.getClass().getName();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.base;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import java.util.ArrayList;
import java.util.List;
/**
* @author
* @date 2019-01-07
*/
@Slf4j
@Configuration
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static final List<CallBack> CALL_BACKS = new ArrayList<>();
private static boolean addCallback = true;
/**
* 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
* 在SpringContextHolder 初始化后,进行回调使用
*
* @param callBack 回调函数
*/
public synchronized static void addCallBacks(CallBack callBack) {
if (addCallback) {
SpringContextHolder.CALL_BACKS.add(callBack);
} else {
log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
callBack.executor();
}
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param defaultValue 默认值
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
T result = defaultValue;
try {
result = getBean(Environment.class).getProperty(property, requiredType);
} catch (Exception ignored) {
}
return result;
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @return /
*/
public static String getProperties(String property) {
return getProperties(property, null, String.class);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, Class<T> requiredType) {
return getProperties(property, null, requiredType);
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
}
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
private static void clearHolder() {
log.debug("清除SpringContextHolder中的ApplicationContext:"
+ applicationContext);
applicationContext = null;
}
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextHolder.applicationContext != null) {
log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
}
SpringContextHolder.applicationContext = applicationContext;
if (addCallback) {
for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
callBack.executor();
}
CALL_BACKS.clear();
}
SpringContextHolder.addCallback = false;
}
}
package com.zq.common.config.base;
import com.zq.common.exception.BusinessException;
import com.zq.common.vo.ResultVo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import javax.servlet.http.HttpServletRequest;
/**
* API接口统一异常处理类
* <p>
* &#64;RestControllerAdvice = &#64;ControllerAdvice + &#64;ResponseBody
* </p>
*
* @author wilmiam
* @since 2017-12-21
*/
@ResponseStatus(HttpStatus.OK)
@RestControllerAdvice
public class UnifiedExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(UnifiedExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
public ResultVo handleBusinessException(BusinessException ex, HttpServletRequest request) {
log.info(">> business exception: {}, {}, {}", request.getRequestURI(), ex.getCode(), ex.getMessage());
String errMessage = ex.getMessage();
// 防止空的错误信息
if (StringUtils.isBlank(errMessage)) {
errMessage = "服务器繁忙";
log.warn(">> 空的业务错误信息", ex);
}
return ResultVo.fail(ex.getCode(), errMessage);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVo handleArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.info(">> argument not valid error: {} {}", request.getRequestURI(), ex.getMessage());
return ResultVo.fail(HttpStatus.BAD_REQUEST.value(), "无效的请求参数" + ex.getParameter().getParameterName());
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResultVo handleMethodNotSupportedException(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
log.info(">> method not supported error: {} {}, expected: {}",
ex.getMethod(), request.getRequestURI(), ex.getSupportedHttpMethods());
return ResultVo.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), "不支持此" + ex.getMethod() + "请求");
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResultVo handleMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) {
String detail = ex.getClass().getName();
if (ex.getSupportedMediaTypes() != null && !ex.getSupportedMediaTypes().isEmpty()) {
detail = MediaType.toString(ex.getSupportedMediaTypes());
detail = "支持的content-type: " + detail;
}
log.info(">> http media type not supported error: {} {}, expected: {}",
request.getContentType(), request.getRequestURI(), detail);
return ResultVo.fail(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "不支持的Content-Type: " + request.getContentType());
}
@ExceptionHandler(HttpMessageConversionException.class)
public ResultVo handleMessageConversionException(HttpMessageConversionException ex, HttpServletRequest request) {
log.info(">> message conversion error: {} {}", request.getRequestURI(), ex.getMessage());
return ResultVo.fail(HttpStatus.BAD_REQUEST.value(), "无法解析请求消息");
}
@ExceptionHandler({MissingServletRequestPartException.class, MissingServletRequestParameterException.class})
public ResultVo handleMissingServletRequestPartException(Exception ex, HttpServletRequest request) {
log.info(">> missing servlet request part/param error: {} {}", request.getRequestURI(), ex.getMessage());
String paranmName = "";
if (ex instanceof MissingServletRequestPartException) {
paranmName = ((MissingServletRequestPartException) ex).getRequestPartName();
} else if (ex instanceof MissingServletRequestParameterException) {
paranmName = ((MissingServletRequestParameterException) ex).getParameterName();
}
return ResultVo.fail(HttpStatus.BAD_REQUEST.value(), "缺少请求参数" + paranmName);
}
@ExceptionHandler(DataAccessException.class)
public ResultVo handleDataAccessException(DataAccessException ex, HttpServletRequest request) {
log.error(">> 访问数据失败 " + request.getRequestURI(), ex);
// log.info(">> 访问参数QueryString:" + ServletUtil.getParamMap(request));
// log.info(">> 访问参数body:" + ServletUtil.getBody(request));
return ResultVo.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器繁忙");
}
@ExceptionHandler(value = Exception.class)
public ResultVo defaultErrorHandler(Exception ex, HttpServletRequest request) {
log.error(">> 服务器内部错误 " + request.getRequestURI(), ex);
return ResultVo.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器繁忙");
}
}
\ No newline at end of file
package com.zq.common.config.interceptor;
import com.zq.common.config.redis.CacheKeys;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.context.ContextUtils;
import com.zq.common.vo.ApiTokenVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
@RequiredArgsConstructor
public class UserInfoInterceptor extends HandlerInterceptorAdapter {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getRequestURI().contains("/app/")) {
String token = getToken(request);
log.info(">> [UserInfo token] {}", token);
ApiTokenVo tokenVo = redisUtils.getObj(CacheKeys.appTokenKey(token), ApiTokenVo.class);
ContextUtils.setUserContext(tokenVo);
}
return true;
}
private String getToken(HttpServletRequest request) {
String header = request.getHeader(properties.getHeader());
if (StringUtils.isNotBlank(header)) {
return header.replace(properties.getTokenStartWith(), "");
}
return "";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.limit;
import com.zq.common.annotation.Limit;
import com.zq.common.http.HttpRequestUtils;
import com.zq.common.utils.AssertUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* @author /
*/
@Aspect
@Component
public class LimitAspect {
private final StringRedisTemplate stringRedisTemplate;
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
public LimitAspect(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Pointcut("@annotation(com.zq.common.annotation.Limit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = signature.getMethod();
Limit limit = signatureMethod.getAnnotation(Limit.class);
LimitType limitType = limit.limitType();
String key = limit.key();
if (StringUtils.isBlank(key)) {
if (limitType == LimitType.IP) {
key = HttpRequestUtils.getClientIp(request);
} else {
key = signatureMethod.getName();// 获取方法名
if ("sendCode".equals(key)) {
key = (String) joinPoint.getArgs()[0];// 获取方法的第一个参数
}
}
}
key = StringUtils.join(limit.prefix(), "_", key, "_", signatureMethod.getName());
/*String obj = stringRedisTemplate.opsForValue().get(key);
int currentLimit = 0;
if (obj != null) {
currentLimit = Integer.parseInt(obj);
}
if (currentLimit + 1 > limit.count()) {
throw new BusinessException("访问次数受限制");
}
stringRedisTemplate.opsForValue().set(key, (currentLimit + 1) + "");
stringRedisTemplate.expire(key, limit.period(), TimeUnit.SECONDS);
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", currentLimit + 1, key, limit.name());
return joinPoint.proceed();*/
List<String> keys = Collections.singletonList(key);
String luaScript = buildLuaScript();
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limit.count()), String.valueOf(limit.period()));
AssertUtils.isTrue(count != null && count != 0, "访问次数受限制");
logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
return joinPoint.proceed();
}
/**
* 限流脚本
*/
private String buildLuaScript() {
return "-- lua 下标从 1 开始" +
"\n-- 限流 key" +
"\nlocal key = KEYS[1]" +
"\n-- 限流大小" +
"\nlocal limit = tonumber(ARGV[1])" +
"\nlocal perSeconds = tonumber(ARGV[2])" +
"\n" +
"\n-- 获取当前流量大小" +
"\nlocal currentLimit = tonumber(redis.call('get', key) or 0)" +
"\n" +
"\nif currentLimit + 1 > limit then" +
"\n -- 达到限流大小 返回" +
"\n return 0;" +
"\nelse" +
"\n -- 没有达到阈值 value + 1" +
"\n redis.call('INCRBY', key, 1)" +
"\n -- EXPIRE的单位是秒" +
"\n redis.call('EXPIRE', key, perSeconds)" +
"\n return currentLimit + 1" +
"\nend";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.limit;
/**
* 限流枚举
* @author /
*/
public enum LimitType {
// 默认
CUSTOMER,
// by ip addr
IP
}
package com.zq.common.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.github.pagehelper.PageInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class MybatisConfig {
/**
* MybatisPlus分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* 官方原话
* MybatisPlus新的分页插件,一缓和二缓遵循mybatis的规则,
* 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@SuppressWarnings(value = {"deprecation"})
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
// pagehelper分页插件
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.put("helperDialect", "mysql");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
}
package com.zq.common.config.redis;
public abstract class CacheKeys {
public static final String PREFIX = "drug.";
private static final String APP_TOKEN = PREFIX + "app-token.";
/**
* 构建app端用户token的缓存key
*
* @param token app登陆后的token
* @return
*/
public static String appTokenKey(String token) {
return APP_TOKEN + token;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.redis;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* @author Zheng Jie
* @date 2018-11-24
*/
@Slf4j
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
/**
* 设置 redis 数据默认过期时间,默认2小时
* 设置@cacheable 序列化方式
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
return configuration;
}
@SuppressWarnings("all")
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
//序列化
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// 全局开启AutoType,这里方便开发,使用全局的方式
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 建议使用这种方式,小范围指定白名单
// ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 自定义缓存key生成策略,默认将使用该策略
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String, Object> container = new HashMap<>(3);
Class<?> targetClassClass = target.getClass();
// 类地址
container.put("class", targetClassClass.toGenericString());
// 方法名称
container.put("methodName", method.getName());
// 包名称
container.put("package", targetClassClass.getPackage());
// 参数列表
for (int i = 0; i < params.length; i++) {
container.put(String.valueOf(i), params[i]);
}
// 转为JSON字符串
String jsonString = JSON.toJSONString(container);
// 做SHA256 Hash计算,得到一个SHA256摘要作为Key
return DigestUtils.sha256Hex(jsonString);
};
}
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
}
}
/**
* Value 序列化
*
* @param <T>
* @author /
*/
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Class<T> clazz;
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
}
@Override
public T deserialize(byte[] bytes) {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, StandardCharsets.UTF_8);
return JSON.parseObject(str, clazz);
}
}
/**
* 重写序列化器
*
* @author /
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
private StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
}
package com.zq.common.config.security;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.alibaba.fastjson.JSON;
import com.zq.common.vo.ApiTokenVo;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
/**
* @author /
*/
@Slf4j
@Component
public class ApiTokenUtils implements InitializingBean {
private static SecurityProperties properties;
private static final String APP_TOKEN_KEY = "appToken";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
public ApiTokenUtils(SecurityProperties securityProperties) {
ApiTokenUtils.properties = securityProperties;
}
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(ApiTokenVo tokenVo, long minutes) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder jwtBuilder = Jwts.builder()
.setSubject(tokenVo.getPhone())
.setIssuedAt(now)
.claim(APP_TOKEN_KEY, tokenVo)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(tokenVo.getUserId().toString());
if (minutes >= 0) {
long expMillis = nowMillis + (minutes * 60 * 1000);
Date exp = new Date(expMillis);
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
public static ApiTokenVo getAppTokenVo(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
return JSON.parseObject(JSON.toJSONString(claims.get(APP_TOKEN_KEY)), ApiTokenVo.class);
} catch (Exception e) {
return null;
}
}
public static Claims getClaims(String token) {
try {
//解析JWT字符串中的数据,并进行最基础的验证
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
return claims;
}
//在解析JWT字符串时,如果密钥不正确,将会解析失败,抛出SignatureException异常,说明该JWT字符串是伪造的
//在解析JWT字符串时,如果‘过期时间字段’已经早于当前时间,将会抛出ExpiredJwtException异常,说明本次请求已经失效
catch (SignatureException | ExpiredJwtException e) {
return null;
}
}
public static boolean isTokenValid(String token) {
return getClaims(token) != null;
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.config.security;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Jwt参数配置
*
* @author Zheng Jie
* @date 2019年11月28日
*/
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class SecurityProperties {
/**
* Request Headers : Authorization
*/
private String header;
/**
* 令牌前缀,最后留个空格 Bearer
*/
private String tokenStartWith;
/**
* 必须使用最少88位的Base64对该令牌进行编码
*/
private String base64Secret;
/**
* 令牌过期时间 此处单位/毫秒
*/
private Long tokenValidityInSeconds;
/**
* 在线用户 key,根据 key 查询 redis 中在线用户的数据
*/
private String onlineKey;
/**
* 验证码 key
*/
private String codeKey;
/**
* token 续期检查
*/
private Long detect;
/**
* 续期时间
*/
private Long renew;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.constant;
/**
* 常用静态常量
*
* @author Zheng Jie
* @date 2018-12-26
*/
public class CloudConstant {
/**
* 用于IP定位转换
*/
public static final String REGION = "内网IP|内网IP";
/**
* win 系统
*/
public static final String WIN = "win";
/**
* mac 系统
*/
public static final String MAC = "mac";
/**
* 常用接口
*/
public static class Url {
// IP归属地查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
}
}
package com.zq.common.context;
import com.zq.common.vo.ApiTokenVo;
import com.zq.common.vo.OnlineUserDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ContextUtils {
private static final String APP_TOKEN_CONTEXT_KEY = "app-token";
private static final String ADMIN_TOKEN_CONTEXT_KEY = "admin-token";
public static void setUserContext(ApiTokenVo apiTokenVo) {
if (apiTokenVo != null) {
ThreadContext.set(APP_TOKEN_CONTEXT_KEY, apiTokenVo);
}
}
public static ApiTokenVo getUserContext() {
return ThreadContext.get(APP_TOKEN_CONTEXT_KEY);
}
public static Long getUserUserId() {
ApiTokenVo apiTokenVo = ThreadContext.get(APP_TOKEN_CONTEXT_KEY);
return apiTokenVo.getUserId();
}
public static void setAdminContext(OnlineUserDto onlineUserDto) {
if (onlineUserDto != null) {
ThreadContext.set(ADMIN_TOKEN_CONTEXT_KEY, onlineUserDto);
}
}
public static OnlineUserDto getAdminContext() {
return ThreadContext.get(ADMIN_TOKEN_CONTEXT_KEY);
}
public static Long getAdminUserId() {
OnlineUserDto userDto = ThreadContext.get(ADMIN_TOKEN_CONTEXT_KEY);
return userDto.getUserId();
}
}
package com.zq.common.context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 基于ThreadLocal的线程相关上下文帮助类, 用于在同一线程下传递变量.
*
* @date 2017-12-18
*/
public class ThreadContext {
private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
private static ThreadLocal<Map<String, Object>> threadLocalMap = ThreadLocal.withInitial(HashMap::new);
/**
* Don't let anyone instantiate this class
*/
private ThreadContext() {
}
/**
* 根据指定的key获取当前线程相关的变量值
*
* @param key 变量的key
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回{@code null}
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
return (T) threadLocalMap.get().get(key);
}
/**
* 根据指定的key获取当前线程相关的变量值, 若为{@code null}则返回指定的默认值
*
* @param key 变量的key
* @param defaultValue 默认值
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回defaultValue
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key, T defaultValue) {
T value = get(key);
return value == null ? defaultValue : value;
}
/**
* 设置线程相关上下文的变量值
*
* @param key 变量的key
* @param value 变量值
*/
public static void set(String key, Object value) {
threadLocalMap.get().put(key, value);
}
/**
* 删除指定key的变量
*
* @param key 变量的key
*/
public static void remove(String key) {
threadLocalMap.get().remove(key);
}
/**
* 清除当前线程相关的上下文
*/
public static void close() {
threadLocalMap.remove();
}
}
package com.zq.common.encrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* @author wilmiam
* @since 2013-11-03
*/
public class AesUtil {
private static final Logger log = LoggerFactory.getLogger(AesUtil.class);
private static final String ALGORITHM_STRING = "AES/ECB/PKCS5Padding";
/**
* 字符编码
*/
private static final String CHARSET_NAME = "UTF-8";
private static SecretKeySpec getSecretKeySpec(String password) throws Exception {
byte[] rByte;
if (password != null) {
rByte = password.getBytes(CHARSET_NAME);
} else {
rByte = new byte[24];
}
return new SecretKeySpec(rByte, "AES");
}
/**
* @param content
* @param password 必须是16位长度
* @return
*/
private static byte[] encrypt(String content, String password) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM_STRING);
byte[] byteContent = content.getBytes(CHARSET_NAME);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKeySpec(password));
return cipher.doFinal(byteContent);
} catch (Exception e) {
log.error("encrypt error", e);
}
return null;
}
/**
* @param content
* @param password 必须是16位长度
* @return
*/
private static byte[] decrypt(byte[] content, String password) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM_STRING);
cipher.init(Cipher.DECRYPT_MODE, getSecretKeySpec(password));
return cipher.doFinal(content);
} catch (Exception e) {
log.error("decrypt error", e);
}
return null;
}
/**
* 加密
*/
public static String aesEncode(String content, String keyBytes) {
String key = EncryptUtils.md5Encrypt(keyBytes).substring(8, 24);
return Base64.getEncoder().encodeToString((encrypt(content, key)));
}
/**
* 解密
*/
public static String aesDecode(String content, String keyBytes) {
String key = EncryptUtils.md5Encrypt(keyBytes).substring(8, 24);
byte[] b = decrypt(Base64.getDecoder().decode(content), key);
if (b == null) {
return null;
}
try {
return new String(b, CHARSET_NAME);
} catch (Exception e) {
log.error("aesDecode error", e);
return null;
}
}
}
package com.zq.common.encrypt;
import com.zq.common.exception.ServerErrorException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* @author wilmiam
* @since 2013-11-03
*/
public class DesUtils {
private static final Logger log = LoggerFactory.getLogger(DesUtils.class);
private static final String CHARSET = "UTF-8";
private static byte[] desEncrypt(byte[] plainText, byte[] rawKeyData) throws GeneralSecurityException {
return getDesCipher(Cipher.ENCRYPT_MODE, rawKeyData).doFinal(plainText);
}
private static byte[] desDecrypt(byte[] encryptText, byte[] rawKeyData) throws GeneralSecurityException {
return getDesCipher(Cipher.DECRYPT_MODE, rawKeyData).doFinal(encryptText);
}
private static Cipher getDesCipher(int mode, byte[] rawKeyData) throws GeneralSecurityException {
SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(rawKeyData);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(mode, key, sr);
return cipher;
}
public static String encrypt(String input, String key) {
try {
return Base64.getEncoder().encodeToString(desEncrypt(input.getBytes(CHARSET), key.getBytes(CHARSET)));
} catch (GeneralSecurityException | IOException e) {
log.error(">> DES encrypt error", e);
throw new ServerErrorException("解密失败");
}
}
public static String decrypt(String input, String key) {
try {
byte[] result = Base64.getDecoder().decode(input);
return new String(desDecrypt(result, key.getBytes(CHARSET)), CHARSET);
} catch (GeneralSecurityException | IOException e) {
log.error(">> DES decrypt error", e);
throw new ServerErrorException("解密失败");
}
}
/**
* 对使用.net的 System.Security.Cryptography.DESCryptoServiceProvider 类进行DES加密的字符串进行解密
*
* @param message
* @param key
* @return
*/
public static String dotNetDecrypt(String message, String key) {
if (StringUtils.isBlank(message)) {
return message;
}
try {
byte[] bytesrc = Base64.getDecoder().decode(message);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
IvParameterSpec iv = new IvParameterSpec(key.getBytes(StandardCharsets.UTF_8));
// 使用秘钥和IV向量初始化加密算法
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
byte[] retByte = cipher.doFinal(bytesrc);
return new String(retByte);
} catch (GeneralSecurityException e) {
log.error(">> DES decrypt error", e);
throw new RuntimeException("解密失败");
}
}
public static byte[] dotNetEncrypt(String message, String key) throws Exception {
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
DESKeySpec desKeySpec = new DESKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
IvParameterSpec iv = new IvParameterSpec(key.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
return cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
}
}
package com.zq.common.encrypt;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* @author wilmiam
* @since 2013-11-03
*/
public class RsaUtils {
/**
* 获取公钥的key
*/
public static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 获取私钥的key
*/
public static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
public static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMTaoTuj4LU9WAMeaVWcpwgyMcdvAA3JRDcG0+pWG086c+WPdSggNNZaVw3szCKOTnWvNc6SoqjpjpbQpC57uag67VzKWLmsZoF6SXjCARyRaEkfK2VRHTfkVpyd8FF16gebVhhyjbkkja9JVEekwqOGzfmnfSKfx5LwvcSxdiSrAgMBAAECgYBZgGHQQPk4zhRHDrurnhbfhhrV5yTqH7kxH5yYLeAqzJPHKsuEm+gKEXcFMMW7bGJF5YycSFVGYTJgZapQLBbDlrZdM8SjxsNyrCKI3v3LNQDsqs5x751HfFVvTme7wroN/uJszUaQJPagEUckMkHvpv7XWoL3Wbz7oy94T3ENoQJBAPAhj2yo9jRZv5JRlYy5BFwqYpxSWqGjzr2k2YiGqB9/y/pDpDx3q42FaBcOlOOeh/My+iVNLcezqgj+U0yx79ECQQDR3Oz9ckCm2q7AMCLFmp9cs4dws6DLim35awOvLIXtm/Z1tRNyuLqb6g2VM4O/QiTu64F3+ljKiOWHAcgxqUe7AkEArTuYy4vs6gFhCb6fg8Cp24+cSifDSF7zM67sW+jA+tBoJ+iKYDD46wS1/gQ/9yGT9Cfve998ylfbr9dB4s9vMQJAOH/uHd3gogtF+N/8vI6AUQjUcfcqVyIRsZCqEUM/W1Ud6VqyvbQWKVu+BGk2EwvPvbMRzCdOOFja0pocN6KHeQJAQPwlDo1IHJI5F60CvfIG8dIwtGexMnd4NNHQ4KH0peK9jUCPkkpW0No5ZEtKNgfdPk23erfyx5cGqocvnoUpoQ==";
public static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDE2qE7o+C1PVgDHmlVnKcIMjHHbwANyUQ3BtPqVhtPOnPlj3UoIDTWWlcN7Mwijk51rzXOkqKo6Y6W0KQue7moOu1cyli5rGaBekl4wgEckWhJHytlUR035FacnfBRdeoHm1YYco25JI2vSVRHpMKjhs35p30in8eS8L3EsXYkqwIDAQAB";
/**
* 生成密钥对(公钥和私钥)
*
* @return
* @throws Exception
*/
public static Map<String, Key> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Key> keyMap = new HashMap<>(4);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 用私钥对信息生成数字签名
*
* @param data 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 校验数字签名
*
* @param data 已加密数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.getDecoder().decode(sign));
}
/**
* <p>
* 公钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
return byteTransfer(getCipherByRsaPublicKey(Cipher.DECRYPT_MODE, publicKey), encryptedData, MAX_ENCRYPT_BLOCK);
}
/**
* <p>
* 公钥加密
* </p>
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
return byteTransfer(getCipherByRsaPublicKey(Cipher.ENCRYPT_MODE, publicKey), data, MAX_ENCRYPT_BLOCK);
}
private static Cipher getCipherByRsaPublicKey(int mode, String publicKey) throws GeneralSecurityException {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
return cipher;
}
/**
* <P>
* 私钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
return byteTransfer(getCipherByRsaPrivateKey(Cipher.DECRYPT_MODE, privateKey), encryptedData, MAX_DECRYPT_BLOCK);
}
/**
* 使用私钥对数据进行加密
*
* @param data 源数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
return byteTransfer(getCipherByRsaPrivateKey(Cipher.ENCRYPT_MODE, privateKey), data, MAX_ENCRYPT_BLOCK);
}
private static Cipher getCipherByRsaPrivateKey(int mode, String key) throws GeneralSecurityException {
byte[] keyBytes = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(mode, privateK);
return cipher;
}
/**
* <p>
* 获取私钥
* </p>
*
* @param keyMap 密钥对
* @return
*/
public static String getPrivateKey(Map<String, Key> keyMap) {
Key key = keyMap.get(PRIVATE_KEY);
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 获取公钥
*
* @param keyMap 密钥对
* @return
*/
public static String getPublicKey(Map<String, Key> keyMap) {
Key key = keyMap.get(PUBLIC_KEY);
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 根据cipher的模式, 对数据进行分段加密/解密
*
* @param cipher
* @param data
* @return
* @throws GeneralSecurityException,IOException
*/
private static byte[] byteTransfer(Cipher cipher, byte[] data, int maxBlock) throws GeneralSecurityException, IOException {
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 根据cipher的模式, 对数据分段加密/解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > maxBlock) {
cache = cipher.doFinal(data, offSet, maxBlock);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * maxBlock;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
}
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment