Commit 049e2d9c by chentianzhong

22222

parent 01475acb
package com.zq.email.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 设置跨域请求
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}
/*
* 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.email.config;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
/*
* 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.email.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}
/*
* 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.email.config;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.utils.RequestMethodEnum;
import com.zq.sealPlatform.feign.AdminFeignClient;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
* @author Zheng Jie
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
private final AdminFeignClient adminFeignClient;
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
Set<String> allType = anonymousUrls.get(RequestMethodEnum.ALL.getType());
//不使用注解的时候在这添加url放行
allType.add("/data/file/sealPlatform/**");//放行静态资源
httpSecurity
// 禁用 CSRF
.csrf().disable()
// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
//映射的文件地址
.antMatchers("/sealPlatform/webSocket/**").permitAll()
//websocket
.antMatchers("/sealPlatform/webSocket/**").permitAll()
//swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
//webservice
.antMatchers("/sealPlatform/ws/**").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(allType.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
//return new TokenConfigurer(tokenProvider, properties, redisUtils);
return new TokenConfigurer(tokenProvider, properties, adminFeignClient);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(6);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}
package com.zq.email.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author bici
*/
@Configuration
@EnableSwagger2
public class Swagger implements WebMvcConfigurer {
@Value("${swagger.enabled}")
private Boolean enabled;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("sealPlatfrom")
.enable(enabled) //生产环境关闭
.select()
.apis(RequestHandlerSelectors.basePackage("com.zq.sealPlatform.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("后台接口文档")
.description("查看接口文档")
.build();
}
}
/*
* 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.email.config;
import com.zq.common.config.security.SecurityProperties;
import com.zq.email.feign.AdminFeignClient;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final AdminFeignClient adminFeignClient;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, adminFeignClient);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/*
* 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.email.config;
import cn.hutool.core.util.StrUtil;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.utils.AssertUtils;
import com.zq.common.utils.TokenUtils;
import com.zq.common.vo.OnlineUserDto;
import com.zq.email.feign.AdminFeignClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author /
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final AdminFeignClient adminFeignClient;
/**
* @param tokenProvider Token
* @param properties JWT
* @param adminFeignClient redis
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, AdminFeignClient adminFeignClient) {
this.properties = properties;
this.tokenProvider = tokenProvider;
this.adminFeignClient = adminFeignClient;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = adminFeignClient.getTokenUserInfo();
AssertUtils.notNull(onlineUserDto, 500, "微服务异常,获取用户信息失败");
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
// 设置当前用户
TokenUtils.setAdminContext(onlineUserDto);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
}
return null;
}
}
package com.zq.email.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 cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
public static final String AUTHORITIES_KEY = "auth";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.compact();
}
public Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
}
return null;
}
}
package com.zq.email.config;
import com.zq.sealPlatform.webService.service.SealWebService;
import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.xml.ws.Endpoint;
/**
* 注意:
* org.apache.cxf.Bus
* org.apache.cxf.bus.spring.SpringBus
* org.apache.cxf.jaxws.EndpointImpl
* javax.xml.ws.Endpoint
*/
@Configuration
@Slf4j
public class WebServiceConfig {
@Autowired
private SealWebService sealWebService;
@Autowired
private Bus bus;
@Bean(name=Bus.DEFAULT_BUS_ID)
public SpringBus springBus(){
return new SpringBus();
}
/**
* 设置Webservice接口访问前缀
* @return
*/
@Bean(name= "cxfServlet")
public ServletRegistrationBean dispatcherServlet(){
return new ServletRegistrationBean(new CXFServlet(),"/sealPlatform/ws/*");
}
/**
* 将Webservice接口进行暴露
* 访问路径:ip+端口+/app+/webservice
* @return
*/
@Bean
public Endpoint endpointHelloService(){
EndpointImpl endpoint=new EndpointImpl(bus,sealWebService);
endpoint.publish("/getSeal");
return endpoint;
}
}
package com.zq.email.config.feign;
import com.zq.common.utils.HttpRequestUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* @author wilmiam
* @since 2021-07-09 10:34
*/
@Configuration
public class FeignConfig {
private final ObjectFactory<HttpMessageConverters> messageConverters;
public FeignConfig(ObjectFactory<HttpMessageConverters> messageConverters) {
this.messageConverters = messageConverters;
}
/**
* 转发请求头
*/
private static final List<String> FORWARD_HEADERS = Arrays.asList(
"AUTHORIZATION",
"X-FORWARDED-FOR",
"X-FORWARDED-PROTO",
"X-FORWARDED-PORT",
"X-FORWARDED-HOST",
"FORWARDED",
"PROXY-CLIENT-IP",
"WL-PROXY-CLIENT-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-REAL-IP",
"HOST"
);
/**
* 解决fein远程调用丢失请求头
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest request = HttpRequestUtils.getRequest();
if (request == null) {
return;
}
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
// 不要设置content-length
if ("content-length".equals(name)) {
continue;
}
if (FORWARD_HEADERS.contains(name.toUpperCase())) {
String values = request.getHeader(name);
template.header(name, values);
}
}
}
}
};
}
@Bean
public Encoder feignEncoder() {
return new FeignSpringFormEncoder(new SpringEncoder(messageConverters));
}
}
package com.zq.email.config.feign;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.ContentType;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringManyMultipartFilesWriter;
import feign.form.spring.SpringSingleMultipartFileWriter;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
/**
* feign 多文件上传配置
*/
public class FeignSpringFormEncoder extends FormEncoder {
public FeignSpringFormEncoder() {
this(new Default());
}
public FeignSpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) this.getContentProcessor(ContentType.MULTIPART);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType != null && bodyType.equals(MultipartFile[].class)) {
MultipartFile[] file = (MultipartFile[]) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
} else if (bodyType != null && bodyType.equals(MultipartFile.class)) {
MultipartFile file = (MultipartFile) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
}
super.encode(object, bodyType, template);
}
}
package com.zq.email.constants;
public class EmailConstant {
//邮件文件夹通用id(默认)
/**所有公共文件夹父级id*/
public static final String PUBLIC_PARENT_ID="0";
/**个人邮箱(收件箱)*/
public static final String MY_EMAIL_BOX="1";
/**部门公共收件箱*/
public static final String DEPT_EMAIL_BOX="2";
/**发件箱(未成功发动或者撤回的邮件)*/
public static final String OUT_EMAIL_BOX="3";
/**个人已发送(已被阅读。不能撤回的邮件)*/
public static final String ALREADY_SEND_EMAIL_BOX="4";
/**已删除邮件*/
public static final String ALREADY_DEL_EMAIL_BOX="5";
/**其它文件夹*/
public static final String OTHER_FOLDER_EMAIL_BOX="6";
/**邮件标签*/
public static final String EMAIL_LABEL_BOX="7";
//邮件类型
/**个人邮件*/
public static final String TYPE_EMAIL_PERSON="0";
/**部门邮件*/
public static final String TYPE_EMAIL_DEPT="1";
//邮件状态
/**未读(默认)*/
public static final String STATE_EMAIL_UNREAD="0";
/**已读*/
public static final String STATE_EMAIL_READ="1";
//人员类型
/**发送人*/
public static final String TYPE_EMAIL_SEND="1";
/**接收人*/
public static final String TYPE_EMAIL_RECEIVE="2";
//邮件发送状态
/** 未发送 */
public static final String TYPE_EMAIL_NOTSTATE ="0";
/** 已发送 */
public static final String TYPE_EMAIL_STATE ="1";
//邮件服务系统标识
public static final String SYSTEM_TAG = "oa-email";
//新邮件提醒标题(测试)
public static final String EMAIL_REMIND_TITLE ="新邮件提醒";
//新邮件提醒内容(测试)
public static final String EMAIL_RECEIVE_REMIND ="您有一封新的邮件,请及时查收。";
//是个人邮件
public static final String IS_PERSONAL = "1";
//是部门邮件
public static final String IS_DEPT = "1";
//不是个人邮件
public static final String NO_PERSONAL = "0";
//不是部门邮件
public static final String NO_DEPT = "0";
/**是否任务邮件*/
public static final Integer IS_TASK=1;
public static final Integer NO_TASK=0;
}
package com.zq.email.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class DeptDto {
@TableId(value = "DEPT_ID", type = IdType.AUTO)
@ApiModelProperty("部门id")
private Integer deptId;
@ApiModelProperty("上级部门id")
private Integer pid;
@ApiModelProperty("部门名称")
private String name;
@ApiModelProperty(value = "所属法院代码")
private String courtCode;
@ApiModelProperty(value = "机构标识")
private String orgCode;
@ApiModelProperty(value = "简称")
private String abbreviation;
@ApiModelProperty(value = "机构类型")
private Integer orgType;
@ApiModelProperty(value = "联系人")
private String contacts;
@ApiModelProperty("排序号")
private Integer orders;
}
package com.zq.email.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 法院信息表
* </p>
*
* @author Lander
* @since 2021-07-09
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OrgFyxx implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Integer id;
/**
* 法院代码
*/
private Integer code;
/**
* 分级码
*/
private String leverCode;
/**
* 法院名称
*/
private String courtName;
/**
* 法院简称
*/
private String abbrName;
/**
* 联系电话
*/
private String tel;
/**
* 传真电话
*/
private String faxTel;
/**
* 地址
*/
private String address;
/**
* 邮编
*/
private Integer emailCode;
/**
* 说明
*/
private String explains;
/**
* 数据状态
*/
private Integer dataState;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建时间
*/
private Date createTime;
private Integer orgId;
}
package com.zq.email.enums;
import com.zq.common.utils.AssertUtils;
/**
* 是否
*/
public enum WhetherEnum {
NO(0, "否"),
YES(1, "是");
private Integer key;
private String value;
WhetherEnum(Integer key, String value) {
this.key = key;
this.value = value;
}
public static WhetherEnum getEnumByKey(Integer key){
for (WhetherEnum value : WhetherEnum.values()) {
if(value.getKey() == key){
return value;
}
}
return null;
}
/**
* 根据keu获取秘密级别值
* @param key
* @return
*/
public static String getValueByKey(Integer key) {
for (WhetherEnum value : WhetherEnum.values()) {
if (value.getKey().equals(key)) {
return value.getValue();
}
}
return null;
}
public static Integer getByByValue(String value){
AssertUtils.hasText(value, "value不为空");
for (WhetherEnum whetherEnum : WhetherEnum.values()) {
if(whetherEnum.getValue().equals(value)){
return whetherEnum.getKey();
}
}
return null;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
package com.zq.email.feign;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.annotation.Log;
import com.zq.common.vo.CustomerUserVo;
import com.zq.common.vo.OnlineUserDto;
import com.zq.common.vo.ResultVo;
import com.zq.email.feign.fallback.AdminFeignFallbackFactory;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* admin 服务feign调用接口
*/
@FeignClient(name = "ADMIN-SERVER",path = "/admin", fallbackFactory = AdminFeignFallbackFactory.class)
public interface AdminFeignClient {
@GetMapping("/oauth/getOnlineUser")
OnlineUserDto getTokenUserInfo();
@GetMapping("/oauth/getUserInfoByToken")
ResultVo getUserInfoByToken(@RequestParam String token);
@GetMapping("/users/getUserById/{userId}")
@AnonymousAccess
CustomerUserVo getUserById(@PathVariable Long userId);
@AnonymousAccess
@PostMapping("/users/getUserByUserName")
ResultVo<CustomerUserVo> getUserByUserName(@RequestParam String userName);
@Log("根据人员标识查询用户")
@ApiOperation("根据人员标识查询用户")
@AnonymousAccess
@GetMapping("/users/getBypCode/{pCode}")
public ResultVo<CustomerUserVo> getBypCode(@PathVariable String pCode);
// @ApiOperation("根据部门id查询部门信息")
// @GetMapping("/dept/getByDeptId/{deptId}")
// ResultVo<DeptVo> getByDeptId(@PathVariable Long deptId);
//
// @Log("根据部门机构标识查询部门信息")
// @ApiOperation("根据部门机构标识查询部门信息")
// @AnonymousAccess
// @GetMapping("/dept/getByDeptName/{orgCode}")
// public ResultVo<DeptVo> getByOrgCode(@PathVariable("orgCode") String orgCode);
//
// @ApiOperation("根据法院代码查询法院信息")
// @AnonymousAccess
// @GetMapping("/court/getCourtByFYCode/{code}")
// public ResultVo<OrgFyxx> getCourtByFYCode(@PathVariable String code);
//
// @ApiOperation("根据法院分级码查询法院信息")
// @AnonymousAccess
// @GetMapping("/court/getCourtByCode/{code}")
// public ResultVo<OrgFyxx> getCourtByCode(@PathVariable String code);
//
// @AnonymousAccess
// @ApiOperation("分级获取基层院 1-获取全区法院(高院除外) 2-获取中院 3-获取基层院 4-全部法院")
// @GetMapping("/court/findAll/{type}")
// public ResultVo<List<OrgFyxx>> findAllCourt(@PathVariable Integer type);
}
package com.zq.email.feign.fallback;
import com.zq.common.vo.CustomerUserVo;
import com.zq.common.vo.OnlineUserDto;
import com.zq.common.vo.ResultVo;
import com.zq.email.feign.AdminFeignClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class AdminFeignFallbackFactory implements FallbackFactory<AdminFeignClient> {
private static final Logger LOG = LoggerFactory.getLogger(AdminFeignFallbackFactory.class);
@Override
public AdminFeignClient create(Throwable throwable) {
String msg = throwable == null ? "" : throwable.getMessage();
if (!StringUtils.isEmpty(msg)) {
LOG.error("admin服务调用异常,异常原因:"+msg);
}
return new AdminFeignClient() {
@Override
public OnlineUserDto getTokenUserInfo() {
LOG.error("获取当前登入用户信息失败");
return null;
}
@Override
public ResultVo getUserInfoByToken(String token) {
LOG.error("获取当前登入用户信息失败");
return ResultVo.fail("getUserInfoByToken:获取当前登入用户信息失败");
}
@Override
public CustomerUserVo getUserById(Long userId) {
return null;
}
@Override
public ResultVo<CustomerUserVo> getUserByUserName(String userName) {
return ResultVo.fail("根据用户名获取人员信息失败,admin异常信息:"+msg);
}
@Override
public ResultVo<CustomerUserVo> getBypCode(String pCode) {
return ResultVo.fail("根据人员标识获取人员信息失败,admin异常信息:"+msg);
}
// @Override
// public ResultVo<DeptVo> getByDeptId(Long deptId) {
// return ResultVo.fail("根据部门id获取部门信息失败,admin异常信息:"+msg);
// }
//
// @Override
// public ResultVo<DeptVo> getByOrgCode(String orgCode) {
// return ResultVo.fail("根据部门标识获取部门信息失败,admin异常信息:"+msg);
// }
//
// @Override
// public ResultVo<OrgFyxx> getCourtByFYCode(String code) {
// return ResultVo.fail("根据法院代码获取法院信息失败,admin异常信息:"+msg);
// }
//
// @Override
// public ResultVo<OrgFyxx> getCourtByCode(String code) {
// return ResultVo.fail("根据法院分级码获取法院信息失败,admkin异常信息:"+msg);
// }
//
// @Override
// public ResultVo<List<OrgFyxx>> findAllCourt(Integer type) {
// return ResultVo.fail("获取法院信息失败,admin异常信息:"+msg);
// }
};
}
}
package com.zq.email.properties;
/**
* Created by Chen Tianzhong in 2023/3/5
*/
public class SmsProperties {
}
package com.zq.email.utils;
import java.math.BigDecimal;
public class CalculationUtil {
/**
* double类型计算,避免精度丢失。
*
* @param d1
* @param d2
* @param operatorType,目前支持“+”、“-”、“*”、“/”
* @return
*/
public static double doubleMathCalculation(double d1, double d2, String operatorType) {
BigDecimal bigDecimal1 = new BigDecimal(String.valueOf(d1));
BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(d2));
double resultValue = 0.0;
switch (operatorType) {
case "+":
resultValue = bigDecimal1.add(bigDecimal2).doubleValue();
break;
case "-":
resultValue = bigDecimal1.subtract(bigDecimal2).doubleValue();
break;
case "*":
resultValue = bigDecimal1.multiply(bigDecimal2).doubleValue();
break;
case "/":
// 除法保留2位小数,向上取整,四舍五入
resultValue = bigDecimal1.divide(bigDecimal2, 2, BigDecimal.ROUND_HALF_UP).doubleValue();
break;
default:
break;
}
return resultValue;
}
/**
* 取小数点后保留几位小数后的值。
*
* @param d
* @param point
* @return
*/
public static double getDecimalPoint(double d, int point) {
BigDecimal bg = new BigDecimal(d);
double f = 0.0;
/**
*hasRound:为true则需要做四舍五入,为false则走getDecimalPoint方法
*ROUND_UP:非0时,舍弃小数后(整数部分)加1,比如12.49结果为13,-12.49结果为 -13
*ROUND_DOWN:直接舍弃小数
*ROUND_CEILING:如果 BigDecimal 是正的,则做 ROUND_UP 操作;如果为负,则做 ROUND_DOWN 操作 (一句话:取附近较大的整数)
*ROUND_FLOOR: 如果 BigDecimal 是正的,则做 ROUND_DOWN 操作;如果为负,则做 ROUND_UP 操作(一句话:取附近较小的整数)
*ROUND_HALF_UP:四舍五入(取更近的整数)
*ROUND_HALF_DOWN:跟ROUND_HALF_UP 差别仅在于0.5时会向下取整
*ROUND_HALF_EVEN:取最近的偶数
*ROUND_UNNECESSARY:不需要取整,如果存在小数位,就抛ArithmeticException 异常
*/
//先上取整,四舍五入
f = bg.setScale(point, BigDecimal.ROUND_HALF_UP).doubleValue();
return f;
}
}
package com.zq.email.utils;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
public static String url = "jdbc:kingbase8://172.18.3.137:54321/SEAL_PLATFOMR";
public static String driver = "com.kingbase8.Driver";
public static String username = "SYSTEM";
public static String password = "123456";
public static String module = "sealPlatform";
public static String parent = "com.zq";
public static String[] tables = {"COURT_CERT","COURT_SEAL","REVOKE_SEAL_RECORD","SEAL_SYSTEM_INFO","UNIFY_SEAL","UNIFY_SEAL_FILE"};
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("chentianzhong");
//gc.setFileOverride(true);
gc.setOpen(false);
gc.setSwagger2(true);
gc.setIdType(IdType.ASSIGN_UUID);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(url);
dsc.setSchemaName("public");
dsc.setDriverName(driver);
dsc.setUsername(username);
dsc.setPassword(password);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(module);
pc.setParent(parent);
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
//String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
// return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
// + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
return projectPath + "/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
// StrategyConfig strategy = new StrategyConfig();
// strategy.setNaming(NamingStrategy.underline_to_camel);
// strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
// strategy.setEntityLombokModel(true);
// strategy.setRestControllerStyle(true);
// // 公共父类
// //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// // 写于父类中的公共字段
// //strategy.setSuperEntityColumns("id");
// strategy.setInclude(tables);
// strategy.setControllerMappingHyphenStyle(true);
// strategy.setTablePrefix();
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude(tables); //设置当前参与生成的表名,参数为可变参数,不设置的话就是全部表
strategyConfig.setTablePrefix(""); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setNaming(NamingStrategy.underline_to_camel); // 数据库表字段映射到实体的命名策略
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
//strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
//strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
mpg.setStrategy(strategyConfig);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
\ No newline at end of file
package com.zq.email.utils;
import java.time.LocalDateTime;
public class DateUtils {
public static boolean isBetween(LocalDateTime beginTime, LocalDateTime endTime) {
//获取当前时间
LocalDateTime now = LocalDateTime.now();
boolean flag = false;
if (now.isAfter(beginTime) && now.isBefore(endTime)) {
flag = true;
}
return flag;
}
}
package com.zq.email.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@ApiModel(description = "收件箱前端Vo")
@Data
public class InboxReqVo {
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("邮件id集合")
private List<Long> idList;
@ApiModelProperty("文件夹ID")
private Integer folderId;
@ApiModelProperty("人员类型")
private String personType;
@ApiModelProperty("邮件类型")
private String emailType;
@ApiModelProperty("发送状态")
//"发送状态:0未发送/1已发送"
private String sendState;
@ApiModelProperty("labelId 标签id")
private Long labelId;
@ApiModelProperty("年度")
private Integer year;
@ApiModelProperty("开始")
private Date startDate;
@ApiModelProperty("结束")
private Date endDate;
private int page;
private int size;
private String isReade;
private String isDept;
private String sendTime;
}
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