diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..ac56891 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..132404b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..677e4c7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,236 @@ + + + 4.0.0 + + org.example + aiobcallback + 1.0-SNAPSHOT + + + 8 + 8 + UTF-8 + 2.0.24 + 3.7.0 + 4.1.1 + 2.9.2 + 5.5.1 + 3.8 + 8.0.11 + 3.4.1 + 1.18.4 + 3.0.11.RELEASE + 2.1.1.RELEASE + 3.9 + 2.17.2 + exam-admin + 0.4.4 + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.14 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.aspectj + aspectjweaver + 1.9.5 + + + + com.alibaba + fastjson + ${fastjson.version} + + + + net.sf.dozer + dozer + ${dozer.version} + + + commons-collections + commons-collections + + + org.slf4j + slf4j-api + + + + + + com.baomidou + mybatis-plus + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + mysql + mysql-connector-java + ${mysql.driver.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.dom4j + dom4j + 2.1.1 + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + org.slf4j + slf4j-api + + + + + + com.github.xiaoymin + swagger-bootstrap-ui + 1.9.3 + + + org.apache.commons + commons-compress + 1.27.1 + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + org.apache.poi + poi-ooxml-schemas + ${poi.version} + + + + + com.auth0 + java-jwt + 3.7.0 + + + + + org.apache.shiro + shiro-spring-boot-starter + 1.8.0 + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + + javax.validation + validation-api + 2.0.1.Final + + + + + com.alibaba + druid-spring-boot-starter + 1.2.6 + + + + commons-io + commons-io + 2.11.0 + + + + + org.apache.commons + commons-pool2 + 2.11.1 + + + + + + ${project.name} + compile + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.1.RELEASE + + + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + false + + + + + \ No newline at end of file diff --git a/src/main/java/org/example/AIOBCallbackApplication.java b/src/main/java/org/example/AIOBCallbackApplication.java new file mode 100644 index 0000000..1e7def9 --- /dev/null +++ b/src/main/java/org/example/AIOBCallbackApplication.java @@ -0,0 +1,32 @@ +package org.example; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; + +@Slf4j +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class}) +public class AIOBCallbackApplication { + public static void main(String[] args) throws UnknownHostException { + ConfigurableApplicationContext application = SpringApplication.run(AIOBCallbackApplication.class, args); + Environment env = application.getEnvironment(); + String ip = InetAddress.getLocalHost().getHostAddress(); + String port = env.getProperty("server.port"); + String path = env.getProperty("server.servlet.context-path"); + + // 未配置默认空白 + if(path == null){ + path = ""; + } + + + log.info("\n----------------------------------------------------------\n\t" + + "百度外呼回调接口启动成功\n\t" + + "----------------------------------------------------------"); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/ability/shiro/CNFilterFactoryBean.java b/src/main/java/org/example/ability/shiro/CNFilterFactoryBean.java new file mode 100644 index 0000000..ce146c9 --- /dev/null +++ b/src/main/java/org/example/ability/shiro/CNFilterFactoryBean.java @@ -0,0 +1,28 @@ +package org.example.ability.shiro; + +import java.util.Map; +import javax.servlet.Filter; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.InvalidRequestFilter; +import org.apache.shiro.web.filter.mgt.DefaultFilter; +import org.apache.shiro.web.filter.mgt.FilterChainManager; + +/** + * 自定义过滤器,用于处理中文URL问题 + * 如:下载文件中包含中文会返回400错误,https://youdomain.com/upload/file/云帆考试系统用户手册.pdf + * @author van + */ +public class CNFilterFactoryBean extends ShiroFilterFactoryBean { + + @Override + protected FilterChainManager createFilterChainManager() { + FilterChainManager manager = super.createFilterChainManager(); + // URL携带中文400,servletPath中文校验bug + Map filterMap = manager.getFilters(); + Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); + if (invalidRequestFilter instanceof InvalidRequestFilter) { + ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); + } + return manager; + } +} diff --git a/src/main/java/org/example/ability/shiro/ShiroRealm.java b/src/main/java/org/example/ability/shiro/ShiroRealm.java new file mode 100644 index 0000000..82db3ec --- /dev/null +++ b/src/main/java/org/example/ability/shiro/ShiroRealm.java @@ -0,0 +1,69 @@ +package org.example.ability.shiro; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.example.ability.shiro.jwt.JwtToken; +import org.springframework.stereotype.Component; + +/** + * 用户登录鉴权和获取用户授权 + * @author bool + */ +@Component +@Slf4j +public class ShiroRealm extends AuthorizingRealm { + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof JwtToken; + } + + + /** + * 详细授权认证 + * @param principals + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + + log.info("++++++++++校验详细权限完成"); + return info; + } + + /** + * 校验用户的账号密码是否正确 + * @param auth + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { + String token = (String) auth.getCredentials(); + if (token == null) { + throw new AuthenticationException("token为空!"); + } + + // 校验token有效性 + return new SimpleAuthenticationInfo(); + } + + /** + * 清除当前用户的权限认证缓存 + * @param principals + */ + @Override + public void clearCache(PrincipalCollection principals) { + super.clearCache(principals); + } + +} diff --git a/src/main/java/org/example/ability/shiro/aop/JwtFilter.java b/src/main/java/org/example/ability/shiro/aop/JwtFilter.java new file mode 100644 index 0000000..1702a32 --- /dev/null +++ b/src/main/java/org/example/ability/shiro/aop/JwtFilter.java @@ -0,0 +1,49 @@ +package org.example.ability.shiro.aop; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.example.aspect.utils.InjectUtils; + +/** + * 鉴权登录拦截器 + * @author bool + */ +@Slf4j +public class JwtFilter extends BasicHttpAuthenticationFilter { + + /** + * 执行登录认证 + * @param request + * @param response + * @param mappedValue + * @return + */ + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + try { + executeLogin(request, response); + return true; + } catch (Exception e) { + // 写出统一错误信息 + InjectUtils.restError((HttpServletResponse) response); + return false; + } + } + + + @Override + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + //String token = httpServletRequest.getHeader(Constants.TOKEN); + // + //JwtToken jwtToken = new JwtToken(token); + //// 提交给realm进行登入,如果错误他会抛出异常并被捕获 + //getSubject(request, response).login(jwtToken); + // 如果没有抛出异常则代表登入成功,返回true + return true; + } +} diff --git a/src/main/java/org/example/ability/shiro/jwt/JwtToken.java b/src/main/java/org/example/ability/shiro/jwt/JwtToken.java new file mode 100644 index 0000000..7f857f4 --- /dev/null +++ b/src/main/java/org/example/ability/shiro/jwt/JwtToken.java @@ -0,0 +1,33 @@ +package org.example.ability.shiro.jwt; + +import lombok.Data; +import org.apache.shiro.authc.AuthenticationToken; + +/** + * @author bool + */ +@Data +public class JwtToken implements AuthenticationToken { + + private static final long serialVersionUID = 1L; + + /** + * JWT的字符token + */ + private String token; + + + public JwtToken(String token) { + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/src/main/java/org/example/ability/shiro/jwt/JwtUtils.java b/src/main/java/org/example/ability/shiro/jwt/JwtUtils.java new file mode 100644 index 0000000..2b13276 --- /dev/null +++ b/src/main/java/org/example/ability/shiro/jwt/JwtUtils.java @@ -0,0 +1,98 @@ +package org.example.ability.shiro.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.DecodedJWT; +import java.util.Calendar; +import java.util.Date; +import org.example.core.utils.file.Md5Util; + +/** + * JWT工具类 + * @author bool + */ +public class JwtUtils { + + /** + * 有效期24小时 + */ + private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; + + + /** + * 校验是否正确 + * @param token + * @param username + * @return + */ + public static boolean verify(String token, String username) { + try { + // 根据密码生成JWT效验器 + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + JWTVerifier verifier = JWT.require(algorithm) + .withClaim("username", username) + .build(); + // 效验TOKEN + verifier.verify(token); + return true; + } catch (Exception exception) { + return false; + } + } + + + + + + /** + * 从Token中解密获得用户名 + * @param token + * @return + */ + public static String getUsername(String token) { + try { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim("username").asString(); + } catch (JWTDecodeException e) { + return null; + } + } + + /** + * 生成JWT Token字符串 + * @param username + * @return + */ + public static String sign(String username) { + Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); + Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username)); + // 附带username信息ExpiredJwtException + return JWT.create() + .withClaim("username", username) + .withExpiresAt(date).sign(algorithm); + + } + + /** + * 根据用户名和秘钥,生成一个新的秘钥,用于JWT加强一些安全性 + * @param userName + * @return + */ + private static String encryptSecret(String userName){ + + // 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录 + // 可自行修改此规则 + Calendar cl = Calendar.getInstance(); + cl.setTimeInMillis(System.currentTimeMillis()); + StringBuffer sb = new StringBuffer(userName) + .append("&") + .append(cl.get(Calendar.MONTH)); + + // 获取MD5 + String secret = Md5Util.md5(sb.toString()); + + return Md5Util.md5(userName + "&" + secret); + } +} diff --git a/src/main/java/org/example/aspect/mybatis/QueryInterceptor.java b/src/main/java/org/example/aspect/mybatis/QueryInterceptor.java new file mode 100644 index 0000000..9199f1e --- /dev/null +++ b/src/main/java/org/example/aspect/mybatis/QueryInterceptor.java @@ -0,0 +1,75 @@ +package org.example.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import java.sql.Connection; +import java.util.Properties; +import lombok.extern.log4j.Log4j2; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.DefaultReflectorFactory; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; + +/** + * 查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等; + * 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题 + * @author bool + */ +@Log4j2 +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),}) +public class QueryInterceptor extends PaginationInterceptor implements Interceptor { + + /** + * 客户ID + */ + private static final String USER_FILTER = "{{userId}}"; + + + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); + MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); + MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); + + //sql语句类型 + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + + // 只过滤查询的 + if (SqlCommandType.SELECT == sqlCommandType) { + // 获得原始SQL + String sql = statementHandler.getBoundSql().getSql(); + + // 不处理 + if(!sql.contains(USER_FILTER)){ + return super.intercept(invocation); + } + // 处理SQL语句 + String outSql = ""; + // 设置SQL + metaObject.setValue("delegate.boundSql.sql", outSql); + // 再分页 + return super.intercept(invocation); + } + + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + @Override + public void setProperties(Properties properties) { + + } + +} diff --git a/src/main/java/org/example/aspect/mybatis/UpdateInterceptor.java b/src/main/java/org/example/aspect/mybatis/UpdateInterceptor.java new file mode 100644 index 0000000..ace06a8 --- /dev/null +++ b/src/main/java/org/example/aspect/mybatis/UpdateInterceptor.java @@ -0,0 +1,79 @@ +package org.example.aspect.mybatis; + +import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler; +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.util.Objects; +import java.util.Properties; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Plugin; +import org.apache.ibatis.plugin.Signature; + +/** + * 自动给创建时间个更新时间加值 + * @author bool + */ +@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) +public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { + + /** + * 创建时间 + */ + private static final String CREATE_TIME = "createTime"; + /** + * 更新时间 + */ + private static final String UPDATE_TIME = "updateTime"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + // SQL操作命令 + SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); + // 获取新增或修改的对象参数 + Object parameter = invocation.getArgs()[1]; + // 获取对象中所有的私有成员变量(对应表字段) + Field[] declaredFields = parameter.getClass().getDeclaredFields(); + if (parameter.getClass().getSuperclass() != null) { + Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields(); + declaredFields = ArrayUtils.addAll(declaredFields, superField); + } + + String fieldName = null; + for (Field field : declaredFields) { + fieldName = field.getName(); + if (Objects.equals(CREATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + } + } + if (Objects.equals(UPDATE_TIME, fieldName)) { + if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { + field.setAccessible(true); + field.set(parameter, new Timestamp(System.currentTimeMillis())); + + } + } + } + return invocation.proceed(); + } + + @Override + public Object plugin(Object target) { + if (target instanceof Executor) { + return Plugin.wrap(target, this); + } + return target; + } + + @Override + public void setProperties(Properties properties) { + } +} diff --git a/src/main/java/org/example/aspect/utils/InjectUtils.java b/src/main/java/org/example/aspect/utils/InjectUtils.java new file mode 100644 index 0000000..8783757 --- /dev/null +++ b/src/main/java/org/example/aspect/utils/InjectUtils.java @@ -0,0 +1,98 @@ +package org.example.aspect.utils; + +import java.io.IOException; +import java.lang.reflect.Field; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.log4j.Log4j2; +import org.example.core.api.ApiError; +import org.example.core.api.ApiRest; +import org.example.core.utils.jackson.JsonHelper; +import org.springframework.stereotype.Component; + +/** + * 注入工具类 + * @author bool + * @date 2019-07-17 09:32 + */ +@Log4j2 +@Component +public class InjectUtils { + + + + /** + * 给对象字段赋值 + * + * @param object 赋值的对象 + * @param value 值 + * @param fields 字段 + * @throws Exception 异常 + */ + public void setValue(Object object, Object value, String... fields) throws Exception { + + //设置同类的属性 + for (String fieldName : fields) { + + //获取当前 + Field field = this.getFiled(object.getClass(), fieldName); + if(field == null){ + continue; + } + + field.setAccessible(true); + field.set(object, value); + } + + } + + /** + * 获取字段名对应的字段 + * + * @param clazz 目标类 + * @param fieldName 字段名 + */ + private Field getFiled(Class clazz, String fieldName) { + + System.out.println("注入的类:"+clazz.toString()); + + //是否具有包含关系 + try { + //获取当前类的属性 + return clazz.getDeclaredField(fieldName); + }catch (Exception e){ + + log.error(clazz.toString() + ": not exist field, try superclass " + fieldName); + + //如果为空且存在父类,则往上找 + if(clazz.getSuperclass()!=null){ + return this.getFiled(clazz.getSuperclass(), fieldName); + } + + return null; + } + } + + + /** + * 打印结果返回 + * @param response + * @throws IOException + */ + public static void restError(HttpServletResponse response) { + + try { + + //固定错误 + ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); + response.getWriter().write(JsonHelper.toJson(apiRest)); + response.getWriter().close(); + + }catch (IOException e){ + + } + + } + +} diff --git a/src/main/java/org/example/config/CorsConfig.java b/src/main/java/org/example/config/CorsConfig.java new file mode 100644 index 0000000..0987e8d --- /dev/null +++ b/src/main/java/org/example/config/CorsConfig.java @@ -0,0 +1,39 @@ +package org.example.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + + +/** + * 网关全局设置,允许跨域 + * @author bool + * @date 2019-08-13 17:28 + */ + +@Configuration +public class CorsConfig { + + @Bean + public FilterRegistrationBean corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + + source.registerCorsConfiguration("/**", config); + FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return bean; + } + +} diff --git a/src/main/java/org/example/config/MultipartConfig.java b/src/main/java/org/example/config/MultipartConfig.java new file mode 100644 index 0000000..6c4104c --- /dev/null +++ b/src/main/java/org/example/config/MultipartConfig.java @@ -0,0 +1,27 @@ +package org.example.config; + +import javax.servlet.MultipartConfigElement; +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +/** + * 文件上传配置 + * @author bool + * @date 2019-07-29 16:23 + */ +@Configuration +public class MultipartConfig { + + @Bean + public MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + // 单个数据大小 + factory.setMaxFileSize(DataSize.ofMegabytes(5000L)); + /// 总上传数据大小 + factory.setMaxRequestSize(DataSize.ofMegabytes(5000L)); + return factory.createMultipartConfig(); + } + +} diff --git a/src/main/java/org/example/config/MybatisConfig.java b/src/main/java/org/example/config/MybatisConfig.java new file mode 100644 index 0000000..41cf01d --- /dev/null +++ b/src/main/java/org/example/config/MybatisConfig.java @@ -0,0 +1,37 @@ +package org.example.config; + +import org.example.aspect.mybatis.QueryInterceptor; +import org.example.aspect.mybatis.UpdateInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Mybatis过滤器配置 + * 注意:必须按顺序进行配置,否则容易出现业务异常 + * @author bool + */ +@Configuration +@MapperScan(basePackages = "org.example.mapper") +public class MybatisConfig { + + /** + * 数据查询过滤器 + */ + @Bean + public QueryInterceptor queryInterceptor() { + QueryInterceptor query = new QueryInterceptor(); + query.setLimit(-1L); + return query; + } + + /** + * 插入数据过滤器 + */ + @Bean + public UpdateInterceptor updateInterceptor() { + return new UpdateInterceptor(); + } + + +} \ No newline at end of file diff --git a/src/main/java/org/example/config/ScheduledConfig.java b/src/main/java/org/example/config/ScheduledConfig.java new file mode 100644 index 0000000..498c4d6 --- /dev/null +++ b/src/main/java/org/example/config/ScheduledConfig.java @@ -0,0 +1,76 @@ +package org.example.config; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +import lombok.extern.log4j.Log4j2; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +/** + * 任务调度配置 + * @author bool + */ +@Log4j2 +@Configuration +@EnableScheduling +@EnableAsync +public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer { + + /** + * 定时任务使用的线程池 + * @return + */ + @Bean(destroyMethod = "shutdown", name = "taskScheduler") + public ThreadPoolTaskScheduler taskScheduler(){ + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(10); + scheduler.setThreadNamePrefix("task-"); + scheduler.setAwaitTerminationSeconds(600); + scheduler.setWaitForTasksToCompleteOnShutdown(true); + return scheduler; + } + + /** + * 异步任务执行线程池 + * @return + */ + @Bean(name = "asyncExecutor") + public ThreadPoolTaskExecutor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setQueueCapacity(1000); + executor.setKeepAliveSeconds(600); + executor.setMaxPoolSize(20); + executor.setThreadNamePrefix("taskExecutor-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { + ThreadPoolTaskScheduler taskScheduler = taskScheduler(); + scheduledTaskRegistrar.setTaskScheduler(taskScheduler); + } + + @Override + public Executor getAsyncExecutor() { + return asyncExecutor(); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (throwable, method, objects) -> { + log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects); + }; + } + +} diff --git a/src/main/java/org/example/config/ShiroConfig.java b/src/main/java/org/example/config/ShiroConfig.java new file mode 100644 index 0000000..2607d32 --- /dev/null +++ b/src/main/java/org/example/config/ShiroConfig.java @@ -0,0 +1,118 @@ +package org.example.config; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.Filter; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.example.ability.shiro.CNFilterFactoryBean; +import org.example.ability.shiro.ShiroRealm; +import org.example.ability.shiro.aop.JwtFilter; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + + +/** + * Shiro配置类 + * @author bool + */ +@Slf4j +@Configuration +public class ShiroConfig { + + /** + * Filter Chain定义说明 + * + * 1、一个URL可以配置多个Filter,使用逗号分隔 + * 2、当设置多个过滤器时,全部验证通过,才视为通过 + * 3、部分过滤器可指定参数,如perms,roles + */ + @Bean("shiroFilterFactoryBean") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean(); + shiroFilterFactoryBean.setSecurityManager(securityManager); + // 拦截器 + Map map = new LinkedHashMap<>(); + + // 需要排除的一些接口 + map.put("/api/**", "anon"); + + map.put("/", "anon"); + map.put("/v2/**", "anon"); + map.put("/doc.html", "anon"); + map.put("/**/*.js", "anon"); + map.put("/**/*.css", "anon"); + map.put("/**/*.html", "anon"); + map.put("/**/*.svg", "anon"); + map.put("/**/*.pdf", "anon"); + map.put("/**/*.jpg", "anon"); + map.put("/**/*.png", "anon"); + map.put("/**/*.ico", "anon"); + + // 字体 + map.put("/**/*.ttf", "anon"); + map.put("/**/*.woff", "anon"); + map.put("/**/*.woff2", "anon"); + map.put("/druid/**", "anon"); + map.put("/swagger-ui.html", "anon"); + map.put("/swagger**/**", "anon"); + map.put("/webjars/**", "anon"); + + // 添加自己的过滤器并且取名为jwt + Map filterMap = new HashMap(1); + filterMap.put("jwt", new JwtFilter()); + shiroFilterFactoryBean.setFilters(filterMap); + map.put("/**", "jwt"); + + shiroFilterFactoryBean.setFilterChainDefinitionMap(map); + return shiroFilterFactoryBean; + } + + @Bean("securityManager") + public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(myRealm); + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + securityManager.setSubjectDAO(subjectDAO); + return securityManager; + } + + /** + * 下面的代码是添加注解支持 + * @return + */ + @Bean + @DependsOn("lifecycleBeanPostProcessor") + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); + defaultAdvisorAutoProxyCreator.setUsePrefix(true); + defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); + return defaultAdvisorAutoProxyCreator; + } + + @Bean + public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } + +} diff --git a/src/main/java/org/example/config/SwaggerConfig.java b/src/main/java/org/example/config/SwaggerConfig.java new file mode 100644 index 0000000..c6ad186 --- /dev/null +++ b/src/main/java/org/example/config/SwaggerConfig.java @@ -0,0 +1,65 @@ +package org.example.config; + +import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; +import io.swagger.annotations.ApiOperation; +import java.util.Collections; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * Swagger配置 + * @author bool + * @date 2020/8/19 20:53 + */ +@Configuration +@EnableSwagger2 +@EnableSwaggerBootstrapUI +@ConfigurationProperties(prefix = "swagger") +public class SwaggerConfig { + + + @Bean + public Docket examApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .groupName("考试模块接口") + .select() + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + //.paths(PathSelectors.ant("/exam/api/**")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(Collections.singletonList(securityScheme())); + } + + + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("考试系统接口") + .description("考试系统接口") + .contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com")) + .version("1.0.0") + .build(); + } + + + /** + * 授权头部 + * @return + */ + @Bean + SecurityScheme securityScheme() { + return new ApiKey("token", "token", "header"); + } + +} diff --git a/src/main/java/org/example/config/jackson/JacksonConfig.java b/src/main/java/org/example/config/jackson/JacksonConfig.java new file mode 100644 index 0000000..3b475fc --- /dev/null +++ b/src/main/java/org/example/config/jackson/JacksonConfig.java @@ -0,0 +1,21 @@ +package org.example.config.jackson; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * JSON序列化配置 + * @author van + */ +@Configuration +public class JacksonConfig { + + @Bean + public SimpleModule xssEscapeModule() { + SimpleModule module = new SimpleModule(); + // 注册反序列化器(处理输入) + module.addDeserializer(String.class, new XssEscapeJsonDeserializer()); + return module; + } +} diff --git a/src/main/java/org/example/config/jackson/XssEscapeJsonDeserializer.java b/src/main/java/org/example/config/jackson/XssEscapeJsonDeserializer.java new file mode 100644 index 0000000..50ccca3 --- /dev/null +++ b/src/main/java/org/example/config/jackson/XssEscapeJsonDeserializer.java @@ -0,0 +1,15 @@ +package org.example.config.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import org.owasp.encoder.Encode; + +public class XssEscapeJsonDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getText(); // 获取原始字符串 + return Encode.forHtml(value); // 转义HTML特殊字符 + } +} \ No newline at end of file diff --git a/src/main/java/org/example/controller/AIOBCallbackController.java b/src/main/java/org/example/controller/AIOBCallbackController.java new file mode 100644 index 0000000..a7e5e34 --- /dev/null +++ b/src/main/java/org/example/controller/AIOBCallbackController.java @@ -0,0 +1,43 @@ +package org.example.controller; + +import com.alibaba.fastjson2.JSONObject; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import org.example.core.api.ApiRest; +import org.example.core.api.controller.BaseController; +import org.example.dto.TaskCallbackDto; +import org.example.service.IAIOBCallbackService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @description: 百度外呼回调接口 + * @author: haown + * @create: 2025-12-22 15:55 + **/ +@Api(tags={"百度外呼回调"}) +@RestController +@RequestMapping("/api") +public class AIOBCallbackController extends BaseController { + + @Resource + private IAIOBCallbackService aiobCallbackService; + + @ApiOperation("任务回调") + @PostMapping("/taskCallBack") + public JSONObject taskCallBack(@RequestBody TaskCallbackDto taskCallbackDto, HttpServletRequest request) { + return aiobCallbackService.taskCallBack(taskCallbackDto.getCallbackType(), taskCallbackDto.getData()); + } + + @ApiOperation("创建实时任务") + @GetMapping("/test") + public ApiRest createActualTimeTask() { + System.out.println("1111111111111111111111"); + return super.success(); + } +} diff --git a/src/main/java/org/example/core/annon/Dict.java b/src/main/java/org/example/core/annon/Dict.java new file mode 100644 index 0000000..c621f69 --- /dev/null +++ b/src/main/java/org/example/core/annon/Dict.java @@ -0,0 +1,21 @@ +package org.example.core.annon; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据字典注解 + * @author bool + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Dict { + + String dicCode(); + + String dicText() default ""; + + String dictTable() default ""; +} diff --git a/src/main/java/org/example/core/api/ApiError.java b/src/main/java/org/example/core/api/ApiError.java new file mode 100644 index 0000000..4790256 --- /dev/null +++ b/src/main/java/org/example/core/api/ApiError.java @@ -0,0 +1,65 @@ +package org.example.core.api; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +/** + * 全局错误码定义,用于定义接口的响应数据, + * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。 + * @author bool + * @date 2019-06-14 21:15 + */ +@NoArgsConstructor +@AllArgsConstructor +public enum ApiError implements Serializable { + + + /** + * 通用错误,接口参数不全 + */ + ERROR_10010001("参数不全或类型错误!"), + ERROR_10010002("您还未登录,请先登录!"), + ERROR_10010003("数据不存在!"), + ERROR_10010012("图形验证码错误!"), + ERROR_10010013("短信验证码错误!"), + ERROR_10010014("不允许重复评论!"), + + /** + * 考试相关错误 + */ + ERROR_20010001("试题被删除,无法继续考试!"), + ERROR_20010002("您有正在进行的考试!"), + + + ERROR_90010001("账号不存在,请确认!"), + ERROR_90010002("账号或密码错误!"), + ERROR_90010003("至少要包含一个角色!"), + ERROR_90010004("管理员账号无法修改!"), + ERROR_90010005("账号被禁用,请联系管理员!"), + ERROR_90010006("活动用户不足,无法开启竞拍!"), + ERROR_90010007("旧密码不正确,请确认!"), + + + ERROR_60000001("数据不存在!"); + + public String msg; + + /** + * 生成Markdown格式文档,用于更新文档用的 + * @param args + */ + public static void main(String[] args) { + for (ApiError e : ApiError.values()) { + System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',"); + } + } + + /** + * 获取错误码 + * @return + */ + public Integer getCode(){ + return Integer.parseInt(this.name().replace("ERROR_", "")); + } +} diff --git a/src/main/java/org/example/core/api/ApiRest.java b/src/main/java/org/example/core/api/ApiRest.java new file mode 100644 index 0000000..bc375bf --- /dev/null +++ b/src/main/java/org/example/core/api/ApiRest.java @@ -0,0 +1,62 @@ +package org.example.core.api; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.example.core.exception.ServiceException; + +/** + * 数据结果返回的封装 + * @author bool + * @date 2018/11/20 09:48 + */ +@Data +@NoArgsConstructor +@ApiModel(value="接口响应", description="接口响应") +public class ApiRest{ + + /** + * 响应消息 + */ + @ApiModelProperty(value = "响应消息") + private String msg; + /** + * 响应代码 + */ + @ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true) + private Integer code; + + /** + * 请求或响应body + */ + @ApiModelProperty(value = "响应内容") + protected T data; + + + /** + * 是否成功 + * @return + */ + public boolean isSuccess(){ + return code.equals(0); + } + + /** + * 构造函数 + * @param error + */ + public ApiRest(ServiceException error){ + this.code = error.getCode(); + this.msg = error.getMsg(); + } + + /** + * 构造函数 + * @param error + */ + public ApiRest(ApiError error){ + this.code = error.getCode(); + this.msg = error.msg; + } +} diff --git a/src/main/java/org/example/core/api/controller/BaseController.java b/src/main/java/org/example/core/api/controller/BaseController.java new file mode 100644 index 0000000..147ec43 --- /dev/null +++ b/src/main/java/org/example/core/api/controller/BaseController.java @@ -0,0 +1,164 @@ +package org.example.core.api.controller; + +import org.example.core.api.ApiError; +import org.example.core.api.ApiRest; +import org.example.core.domain.AjaxResult; +import org.example.core.exception.ServiceException; + +/** + * 基础控制器 + * @author Dav + */ +public class BaseController { + + /** + * 成功默认消息 + */ + private static final Integer CODE_SUCCESS = 0; + private static final String MSG_SUCCESS = "操作成功!"; + + /** + * 失败默认消息 + */ + private static final Integer CODE_FAILURE = 1; + private static final String MSG_FAILURE = "请求失败!"; + + + /** + * 完成消息构造 + * @param code + * @param message + * @param data + * @param + * @return + */ + protected ApiRest message(Integer code, String message, T data){ + ApiRest response = new ApiRest<>(); + response.setCode(code); + response.setMsg(message); + if(data!=null) { + response.setData(data); + } + return response; + } + + /** + * 请求成功空数据 + * @param + * @return + */ + protected ApiRest success(){ + return message(0, "请求成功!", null); + } + + + + /** + * 请求成功,通用代码 + * @param message + * @param data + * @param + * @return + */ + protected ApiRest success(String message, T data){ + return message(CODE_SUCCESS, message, data); + } + + + /** + * 请求成功,仅内容 + * @param data + * @param + * @return + */ + protected ApiRest success(T data){ + return message(CODE_SUCCESS, MSG_SUCCESS, data); + } + + + /** + * 请求失败,完整构造 + * @param code + * @param message + * @param data + * @param + * @return + */ + protected ApiRest failure(Integer code, String message, T data){ + return message(code, message, data); + } + + /** + * 请求失败,消息和内容 + * @param message + * @param data + * @param + * @return + */ + protected ApiRest failure(String message, T data){ + return message(CODE_FAILURE, message, data); + } + + /** + * 请求失败,消息 + * @param message + * @return + */ + protected ApiRest failure(String message){ + return message(CODE_FAILURE, message, null); + } + + /** + * 请求失败,仅内容 + * @param data + * @param + * @return + */ + protected ApiRest failure(T data){ + return message(CODE_FAILURE, MSG_FAILURE, data); + } + + + /** + * 请求失败,仅内容 + * @param + * @return + */ + protected ApiRest failure(){ + return message(CODE_FAILURE, MSG_FAILURE, null); + } + + + + /** + * 请求失败,仅内容 + * @param + * @return + */ + protected ApiRest failure(ApiError error, T data){ + return message(error.getCode(), error.msg, data); + } + + + + /** + * 请求失败,仅内容 + * @param ex + * @param + * @return + */ + protected ApiRest failure(ServiceException ex){ + ApiRest apiRest = message(ex.getCode(), ex.getMsg(), null); + return apiRest; + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } +} diff --git a/src/main/java/org/example/core/api/dto/BaseDTO.java b/src/main/java/org/example/core/api/dto/BaseDTO.java new file mode 100644 index 0000000..099a8b9 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/BaseDTO.java @@ -0,0 +1,14 @@ +package org.example.core.api.dto; + +import java.io.Serializable; +import lombok.Data; + +/** + * 请求和响应的基础类,用于处理序列化 + * @author dav + * @date 2019/3/16 15:56 + */ +@Data +public class BaseDTO implements Serializable { + +} diff --git a/src/main/java/org/example/core/api/dto/BaseIdReqDTO.java b/src/main/java/org/example/core/api/dto/BaseIdReqDTO.java new file mode 100644 index 0000000..b996953 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/BaseIdReqDTO.java @@ -0,0 +1,27 @@ +package org.example.core.api.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + *

+ * 主键通用请求类,用于根据ID查询 + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +@Data +@ApiModel(value="主键通用请求类", description="主键通用请求类") +public class BaseIdReqDTO extends BaseDTO { + + + @ApiModelProperty(value = "主键ID", required=true) + private String id; + + @JsonIgnore + private String userId; + +} diff --git a/src/main/java/org/example/core/api/dto/BaseIdRespDTO.java b/src/main/java/org/example/core/api/dto/BaseIdRespDTO.java new file mode 100644 index 0000000..04cfbc6 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/BaseIdRespDTO.java @@ -0,0 +1,25 @@ +package org.example.core.api.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 主键通用响应类,用于添加后返回内容 + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +@Data +@ApiModel(value="主键通用响应类", description="主键通用响应类") +@AllArgsConstructor +@NoArgsConstructor +public class BaseIdRespDTO extends BaseDTO { + + @ApiModelProperty(value = "主键ID", required=true) + private String id; +} diff --git a/src/main/java/org/example/core/api/dto/BaseIdsReqDTO.java b/src/main/java/org/example/core/api/dto/BaseIdsReqDTO.java new file mode 100644 index 0000000..9db5f97 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/BaseIdsReqDTO.java @@ -0,0 +1,24 @@ +package org.example.core.api.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.util.List; +import lombok.Data; + +/** + * 通用ID列表类操作,用于批量删除、修改状态等 + * @author bool + * @date 2019-08-01 19:07 + */ +@Data +@ApiModel(value="删除参数", description="删除参数") +public class BaseIdsReqDTO extends BaseDTO { + + + @JsonIgnore + private String userId; + + @ApiModelProperty(value = "要删除的ID列表", required = true) + private List ids; +} diff --git a/src/main/java/org/example/core/api/dto/BaseStateReqDTO.java b/src/main/java/org/example/core/api/dto/BaseStateReqDTO.java new file mode 100644 index 0000000..3038ea0 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/BaseStateReqDTO.java @@ -0,0 +1,30 @@ +package org.example.core.api.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 通用状态请求类,用于修改状态什么的 + *

+ * + * @author 聪明笨狗 + * @since 2019-04-20 12:15 + */ +@Data +@ApiModel(value="通用状态请求类", description="通用状态请求类") +@AllArgsConstructor +@NoArgsConstructor +public class BaseStateReqDTO extends BaseDTO { + + + @ApiModelProperty(value = "要修改对象的ID列表", required=true) + private List ids; + + @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true) + private Integer state; +} diff --git a/src/main/java/org/example/core/api/dto/PagingReqDTO.java b/src/main/java/org/example/core/api/dto/PagingReqDTO.java new file mode 100644 index 0000000..7de79b9 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/PagingReqDTO.java @@ -0,0 +1,47 @@ +package org.example.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 分页查询类 + * @param + * @author bool + */ +@ApiModel(value="分页参数", description="分页参数") +@Data +public class PagingReqDTO { + + + @ApiModelProperty(value = "当前页码", required = true, example = "1") + private Integer current; + + @ApiModelProperty(value = "每页数量", required = true, example = "10") + private Integer size; + + @ApiModelProperty(value = "查询参数") + private T params; + + @ApiModelProperty(value = "排序字符") + private String orderBy; + + @JsonIgnore + @ApiModelProperty(value = "当前用户的ID") + private String userId; + + /** + * 转换成MyBatis的简单分页对象 + * @return + */ + public Page toPage(){ + Page page = new Page(); + page.setCurrent(this.current); + page.setSize(this.size); + return page; + } + + +} diff --git a/src/main/java/org/example/core/api/dto/PagingRespDTO.java b/src/main/java/org/example/core/api/dto/PagingRespDTO.java new file mode 100644 index 0000000..df9a994 --- /dev/null +++ b/src/main/java/org/example/core/api/dto/PagingRespDTO.java @@ -0,0 +1,30 @@ +package org.example.core.api.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +/** + * 分页响应类 + * @author bool + * @date 2019-07-20 15:17 + * @param + */ +public class PagingRespDTO extends Page { + + /** + * 获取页面总数量 + * @return + */ + @Override + public long getPages() { + if (this.getSize() == 0L) { + return 0L; + } else { + long pages = this.getTotal() / this.getSize(); + if (this.getTotal() % this.getSize() != 0L) { + ++pages; + } + return pages; + } + } + +} diff --git a/src/main/java/org/example/core/api/utils/JsonConverter.java b/src/main/java/org/example/core/api/utils/JsonConverter.java new file mode 100644 index 0000000..cead204 --- /dev/null +++ b/src/main/java/org/example/core/api/utils/JsonConverter.java @@ -0,0 +1,47 @@ +package org.example.core.api.utils; + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; + +/** + * JSON数据转换器,用于转换返回消息的格式 + * @author dav + * @date 2018/9/11 19:30 + */ +public class JsonConverter { + + /** + * FastJson消息转换器 + * + * @return + */ + public static HttpMessageConverter fastConverter() { + // 定义一个convert转换消息的对象 + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + // 添加FastJson的配置信息 + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + // 默认转换器 + fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat, + SerializerFeature.WriteNullNumberAsZero, + SerializerFeature.MapSortField, + SerializerFeature.WriteNullStringAsEmpty, + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.WriteNullListAsEmpty); + fastJsonConfig.setCharset(Charset.forName("UTF-8")); + // 处理中文乱码问题 + List fastMediaTypes = new ArrayList<>(); + fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); + fastConverter.setSupportedMediaTypes(fastMediaTypes); + // 在convert中添加配置信息 + fastConverter.setFastJsonConfig(fastJsonConfig); + + return fastConverter; + } +} diff --git a/src/main/java/org/example/core/domain/AjaxResult.java b/src/main/java/org/example/core/domain/AjaxResult.java new file mode 100644 index 0000000..867df3d --- /dev/null +++ b/src/main/java/org/example/core/domain/AjaxResult.java @@ -0,0 +1,165 @@ +package org.example.core.domain; + +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author xinyilu + */ +public class AjaxResult extends HashMap { + private static final long serialVersionUID = 1L; + + /** + * 状态码 + */ + public static final String CODE_TAG = "code"; + + /** + * 返回内容 + */ + public static final String MSG_TAG = "msg"; + + /** + * 数据对象 + */ + public static final String DATA_TAG = "data"; + + /** + * 成功默认消息 + */ + private static final Integer CODE_SUCCESS = 0; + private static final String MSG_SUCCESS = "操作成功!"; + + /** + * 失败默认消息 + */ + private static final Integer CODE_FAILURE = 1; + private static final String MSG_FAILURE = "请求失败!"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (!(data == null)) { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) { + return new AjaxResult(CODE_SUCCESS, msg, data); + } + + /** + * 返回错误消息 + * + * @return + */ + public static AjaxResult error() { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(String msg) { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult error(String msg, Object data) { + return new AjaxResult(CODE_FAILURE, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult error(int code, String msg) { + return new AjaxResult(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) { + super.put(key, value); + return this; + } + +} diff --git a/src/main/java/org/example/core/enums/CommonState.java b/src/main/java/org/example/core/enums/CommonState.java new file mode 100644 index 0000000..8d177c1 --- /dev/null +++ b/src/main/java/org/example/core/enums/CommonState.java @@ -0,0 +1,19 @@ +package org.example.core.enums; + +/** + * 通用的状态枚举信息 + * + * @author bool + * @date 2019-09-17 17:57 + */ +public interface CommonState { + + /** + * 普通状态,正常的 + */ + Integer NORMAL = 0; + /** + * 非正常状态,禁用,下架等 + */ + Integer ABNORMAL = 1; +} diff --git a/src/main/java/org/example/core/enums/ConfirmRefundStatusEnum.java b/src/main/java/org/example/core/enums/ConfirmRefundStatusEnum.java new file mode 100644 index 0000000..9273eb0 --- /dev/null +++ b/src/main/java/org/example/core/enums/ConfirmRefundStatusEnum.java @@ -0,0 +1,29 @@ +package org.example.core.enums; + +import lombok.Getter; + +/** + * @Description 确认退款状态枚举 + * @Author 纪寒 + * @Date 2022-10-26 11:15:21 + * @Version 1.0 + */ +@Getter +public enum ConfirmRefundStatusEnum { + + /** + * 未确认 + */ + NOT_CONFIRM("NOT_CONFIRM"), + + /** + * 已确认 + */ + CONFIRMED("CONFIRMED"), + ; + final private String info; + + ConfirmRefundStatusEnum(String info) { + this.info = info; + } +} diff --git a/src/main/java/org/example/core/enums/GooodsOrderStatusEnum.java b/src/main/java/org/example/core/enums/GooodsOrderStatusEnum.java new file mode 100644 index 0000000..2dd2df7 --- /dev/null +++ b/src/main/java/org/example/core/enums/GooodsOrderStatusEnum.java @@ -0,0 +1,69 @@ +package org.example.core.enums; + +import lombok.Getter; + +/** + * @Description 商品订单状态枚举 + * @Author 纪寒 + * @Date 2022-10-19 09:15:38 + * @Version 1.0 + */ +@Getter +public enum GooodsOrderStatusEnum { + + /** + * 待付款 + */ + WAIT_PAY("WAIT_PAY"), + + /** + * 已付款 + */ + PAY("PAY"), + + /** + * 已取消 + */ + CANCEL("CANCEL"), + + /** + * 待收货 + */ + WAIT_RECEIVED_GOODS("WAIT_RECEIVED_GOODS"), + + /** + * 已收货 + */ + RECEIVED_GOODS("RECEIVED_GOODS"), + + /** + * 退款中 + */ + WAIT_REFUND("WAIT_REFUND"), + + /** + * 已退款 + */ + REFUNDED("REFUNDED"), + + /** + * 待退货 + */ + WAIT_RETURNED_GOODS("WAIT_RETURNED_GOODS"), + + /** + * 已退货 + */ + RETURNED_GOODS("RETURNED_GOODS"), + + /** + * 已评价 + */ + EVALUATED("EVALUATED"), + ; + final private String info; + + GooodsOrderStatusEnum(String info) { + this.info = info; + } +} diff --git a/src/main/java/org/example/core/enums/OpenType.java b/src/main/java/org/example/core/enums/OpenType.java new file mode 100644 index 0000000..504661c --- /dev/null +++ b/src/main/java/org/example/core/enums/OpenType.java @@ -0,0 +1,18 @@ +package org.example.core.enums; + +/** + * 开放方式 + * @author bool + */ +public interface OpenType { + + /** + * 完全开放 + */ + Integer OPEN = 1; + + /** + * 部门开放 + */ + Integer DEPT_OPEN = 2; +} diff --git a/src/main/java/org/example/core/enums/PayTypeEnum.java b/src/main/java/org/example/core/enums/PayTypeEnum.java new file mode 100644 index 0000000..71e629e --- /dev/null +++ b/src/main/java/org/example/core/enums/PayTypeEnum.java @@ -0,0 +1,30 @@ +package org.example.core.enums; + +import lombok.Getter; + +/** + * @Description 支付了类型枚举 + * @Author 纪寒 + * @Date 2022-10-18 15:16:02 + * @Version 1.0 + */ +@Getter +public enum PayTypeEnum { + + /** + * 微信 + */ + WECHAT_PAY("WECHAT_PAY"), + + /** + * 支付宝 + */ + ALI_PAY("ALI_PAY"), + ; + + final private String info; + + PayTypeEnum(String info) { + this.info = info; + } +} diff --git a/src/main/java/org/example/core/enums/RefundStatusEnum.java b/src/main/java/org/example/core/enums/RefundStatusEnum.java new file mode 100644 index 0000000..c75e93d --- /dev/null +++ b/src/main/java/org/example/core/enums/RefundStatusEnum.java @@ -0,0 +1,41 @@ +package org.example.core.enums; + +import lombok.Getter; + +/** + * @Description 微信退款通知枚举 + * @Author 纪寒 + * @Date 2022-10-25 13:12:03 + * @Version 1.0 + */ +@Getter +public enum RefundStatusEnum { + /** + * 退款成功 + */ + SUCCESS("SUCCESS"), + + /** + * 退款关闭 + */ + CLOSED("CLOSED"), + + /** + * 退款处理中 + */ + PROCESSING("PROCESSING"), + + /** + * 退款异常,退款到银行发现用户的卡作废或者冻结了, + * 导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款 + */ + ABNORMAL("ABNORMAL"), + + ; + + final private String info; + + RefundStatusEnum(String info) { + this.info = info; + } +} diff --git a/src/main/java/org/example/core/enums/WeChatTradeStateEnum.java b/src/main/java/org/example/core/enums/WeChatTradeStateEnum.java new file mode 100644 index 0000000..b68ec6e --- /dev/null +++ b/src/main/java/org/example/core/enums/WeChatTradeStateEnum.java @@ -0,0 +1,52 @@ +package org.example.core.enums; + +import lombok.Getter; + +/** + * 微信支付回调通知状态 + */ +@Getter +public enum WeChatTradeStateEnum { + + /** + * 支付成功 + */ + SUCCESS("SUCCESS"), + + /** + * 未支付 + */ + NOTPAY("NOTPAY"), + + /** + * 已关闭 + */ + CLOSED("CLOSED"), + + /** + * 转入退款 + */ + REFUND("REFUND"), + + /** + * 已撤销(付款码支付) + */ + REVOKED("REVOKED"), + + /** + * 用户支付中(付款码支付) + */ + USERPAYING("USERPAYING"), + + /** + * 支付失败(其他原因,如银行返回失败) + */ + PAYERROR("PAYERROR") + ; + + final private String info; + + WeChatTradeStateEnum(String info) { + this.info = info; + } +} diff --git a/src/main/java/org/example/core/exception/ServiceException.java b/src/main/java/org/example/core/exception/ServiceException.java new file mode 100644 index 0000000..6f11b6f --- /dev/null +++ b/src/main/java/org/example/core/exception/ServiceException.java @@ -0,0 +1,51 @@ +package org.example.core.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.example.core.api.ApiError; +import org.example.core.api.ApiRest; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceException extends RuntimeException{ + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误消息 + */ + private String msg; + + /** + * 从结果初始化 + * @param apiRest + */ + public ServiceException(ApiRest apiRest){ + this.code = apiRest.getCode(); + this.msg = apiRest.getMsg(); + } + + /** + * 从枚举中获取参数 + * @param apiError + */ + public ServiceException(ApiError apiError){ + this.code = apiError.getCode(); + this.msg = apiError.msg; + } + + /** + * 异常构造 + * @param msg + */ + public ServiceException(String msg){ + this.code = 1; + this.msg = msg; + } + +} diff --git a/src/main/java/org/example/core/exception/ServiceExceptionHandler.java b/src/main/java/org/example/core/exception/ServiceExceptionHandler.java new file mode 100644 index 0000000..9a23eef --- /dev/null +++ b/src/main/java/org/example/core/exception/ServiceExceptionHandler.java @@ -0,0 +1,50 @@ +package org.example.core.exception; + +import org.example.core.api.ApiRest; +import org.springframework.http.HttpStatus; +import org.springframework.ui.Model; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 统一异常处理类 + * @author bool + * @date 2019-06-21 19:27 + */ +@RestControllerAdvice +public class ServiceExceptionHandler { + + /** + * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器 + * @param binder + */ + @InitBinder + public void initWebBinder(WebDataBinder binder){ + + } + + /** + * 把值绑定到Model中,使全局@RequestMapping可以获取到该值 + * @param model + */ + @ModelAttribute + public void addAttribute(Model model) { + + } + + /** + * 捕获ServiceException + * @param e + * @return + */ + @ExceptionHandler({org.example.core.exception.ServiceException.class}) + @ResponseStatus(HttpStatus.OK) + public ApiRest serviceExceptionHandler(ServiceException e) { + return new ApiRest(e); + } + +} \ No newline at end of file diff --git a/src/main/java/org/example/core/utils/BeanMapper.java b/src/main/java/org/example/core/utils/BeanMapper.java new file mode 100644 index 0000000..79ab6b9 --- /dev/null +++ b/src/main/java/org/example/core/utils/BeanMapper.java @@ -0,0 +1,58 @@ +package org.example.core.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.dozer.DozerBeanMapper; + + +/** + * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现: + * + * 1. 持有Mapper的单例. + * 2. 返回值类型转换. + * 3. 批量转换Collection中的所有对象. + * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数. + * + */ +public class BeanMapper { + + /** + * 持有Dozer单例, 避免重复创建DozerMapper消耗资源. + */ + private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper(); + + /** + * 基于Dozer转换对象的类型. + */ + public static T map(Object source, Class destinationClass) { + return dozerBeanMapper.map(source, destinationClass); + } + + /** + * 基于Dozer转换Collection中对象的类型. + */ + public static List mapList(Iterable sourceList, Class destinationClass) { + List destinationList = new ArrayList(); + for (Object sourceObject : sourceList) { + T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass); + destinationList.add(destinationObject); + } + return destinationList; + } + + /** + * 基于Dozer将对象A的值拷贝到对象B中. + */ + public static void copy(Object source, Object destinationObject) { + if(source!=null) { + dozerBeanMapper.map(source, destinationObject); + } + } + + public static List mapList(Collection source, Function mapper) { + return source.stream().map(mapper).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/core/utils/CronUtils.java b/src/main/java/org/example/core/utils/CronUtils.java new file mode 100644 index 0000000..f99b21d --- /dev/null +++ b/src/main/java/org/example/core/utils/CronUtils.java @@ -0,0 +1,31 @@ +package org.example.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 时间转换quartz表达式 + * @author bool + * @date 2020/11/29 下午3:00 + */ +public class CronUtils { + + /** + * 格式化数据 + */ + private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy"; + + /** + * 准确的时间点到表达式 + * @param date + * @return + */ + public static String dateToCron(final Date date){ + SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT); + String formatTimeStr = ""; + if (date != null) { + formatTimeStr = fmt.format(date); + } + return formatTimeStr; + } +} diff --git a/src/main/java/org/example/core/utils/DateUtils.java b/src/main/java/org/example/core/utils/DateUtils.java new file mode 100644 index 0000000..6439dab --- /dev/null +++ b/src/main/java/org/example/core/utils/DateUtils.java @@ -0,0 +1,103 @@ +package org.example.core.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * 日期处理工具类 + * ClassName: DateUtils
+ * date: 2018年12月13日 下午6:34:02
+ * + * @author Bool + * @version + */ +public class DateUtils { + + /** + * + * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数
+ * @author Bool + * @param userCreateTime + * @return + * @since JDK 1.6 + */ + public static int calcExpDays(Date userCreateTime){ + + Calendar start = Calendar.getInstance(); + start.setTime(userCreateTime); + + Calendar now = Calendar.getInstance(); + now.setTime(new Date()); + + long l = now.getTimeInMillis() - start.getTimeInMillis(); + int days = new Long(l / (1000 * 60 * 60 * 24)).intValue(); + return days; + } + + + /** + * + * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示.
+ * @author Bool + * @param format 日期格式化 + * @return + */ + public static String dateNow(String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + Calendar c = new GregorianCalendar(); + return fmt.format(c.getTime()); + } + + /** + * formatDate:格式化日期,返回指定的格式
+ * @author Bool + * @param time + * @param format + * @return + */ + public static String formatDate(Date time, String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + return fmt.format(time.getTime()); + } + + + + /** + * parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化 + * @author Bool + * @param date + * @return + */ + public static Date parseDate(String date) { + return parseDate(date, "yyyy-MM-dd HH:mm:ss"); + } + + + /** + * + * parseDate:将字符串转换成日期,使用指定格式化来格式化 + * @author Bool + * @param date + * @param pattern + * @return + */ + public static Date parseDate(String date, String pattern) { + + if (pattern==null) { + pattern = "yyyy-MM-dd HH:mm:ss"; + } + + SimpleDateFormat fmt = new SimpleDateFormat(pattern); + + try { + + return fmt.parse(date); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + + } +} diff --git a/src/main/java/org/example/core/utils/IpUtils.java b/src/main/java/org/example/core/utils/IpUtils.java new file mode 100644 index 0000000..ce346e3 --- /dev/null +++ b/src/main/java/org/example/core/utils/IpUtils.java @@ -0,0 +1,65 @@ +package org.example.core.utils; + + +import javax.servlet.http.HttpServletRequest; + +/** + * IP获取工具类,用户获取网络请求过来的真实IP + * ClassName: IpUtils
+ * date: 2018年2月13日 下午7:27:52
+ * + * @author Bool + * @version + */ +public class IpUtils { + + + /** + * + * getClientIp:通过请求获取客户端的真实IP地址 + * @author Bool + * @param request + * @return + */ + public static String extractClientIp(HttpServletRequest request) { + + String ip = null; + + //X-Forwarded-For:Squid 服务代理 + String ipAddresses = request.getHeader("X-Forwarded-For"); + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //Proxy-Client-IP:apache 服务代理 + ipAddresses = request.getHeader("Proxy-Client-IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //WL-Proxy-Client-IP:weblogic 服务代理 + ipAddresses = request.getHeader("WL-Proxy-Client-IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //HTTP_CLIENT_IP:有些代理服务器 + ipAddresses = request.getHeader("HTTP_CLIENT_IP"); + } + + if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + //X-Real-IP:nginx服务代理 + ipAddresses = request.getHeader("X-Real-IP"); + } + + //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP + if (ipAddresses != null && ipAddresses.length() != 0) { + ip = ipAddresses.split(",")[0]; + } + + //还是不能获取到,最后再通过request.getRemoteAddr();获取 + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { + ip = request.getRemoteAddr(); + } + + return ip; + } + + +} diff --git a/src/main/java/org/example/core/utils/Reflections.java b/src/main/java/org/example/core/utils/Reflections.java new file mode 100644 index 0000000..d05d74c --- /dev/null +++ b/src/main/java/org/example/core/utils/Reflections.java @@ -0,0 +1,323 @@ +/** + * Copyright (c) 2005-2012 springside.org.cn + */ +package org.example.core.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.springframework.util.Assert; + +/** + * 反射工具类. + * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * @author calvin + * @version 2016-01-15 + */ +@Log4j2 +public class Reflections { + + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + + /** + * 获取类的所有属性,包括父类 + * + * @param object + * @return + */ + public static Field[] getAllFields(Object object) { + Class clazz = object.getClass(); + List fieldList = new ArrayList<>(); + while (clazz != null) { + fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); + clazz = clazz.getSuperclass(); + } + Field[] fields = new Field[fieldList.size()]; + fieldList.toArray(fields); + return fields; + } + + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + public static Object invokeGetter(Object obj, String propertyName) { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")){ + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, Object value) { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i=0; i[] parameterTypes, + final Object[] args) { + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + return method.invoke(obj, args); + } catch (Exception e) { + throw convertReflectionExceptionToUnchecked(e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { + Method method = getAccessibleMethodByName(obj, methodName); + if (method == null) { + throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); + } + + try { + return method.invoke(obj, args); + } catch (Exception e) { + throw convertReflectionExceptionToUnchecked(e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { + try { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } catch (NoSuchFieldException e) {//NOSONAR + // Field不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + try { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } catch (NoSuchMethodException e) { + // Method不在当前类定义,继续向上转型 + continue;// new add + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName) { + Validate.notNull(obj, "object can't be null"); + Validate.notBlank(methodName, "methodName can't be blank"); + + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName)) { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier + .isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + * eg. + * public UserDao extends HibernateDao + * + * @param clazz The class to introspect + * @return the first generic declaration, or Object.class if cannot be determined + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + * + * 如public UserDao extends HibernateDao + * + * @param clazz clazz The class to introspect + * @param index the Index of the generic ddeclaration,start from 0. + * @return the index generic declaration, or Object.class if cannot be determined + */ + public static Class getClassGenricType(final Class clazz, final int index) { + + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) { + log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) { + log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) { + log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) { + return new IllegalArgumentException(e); + } else if (e instanceof InvocationTargetException) { + return new RuntimeException(((InvocationTargetException) e).getTargetException()); + } else if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException("Unexpected Checked Exception.", e); + } +} diff --git a/src/main/java/org/example/core/utils/SpringUtils.java b/src/main/java/org/example/core/utils/SpringUtils.java new file mode 100644 index 0000000..b659ecb --- /dev/null +++ b/src/main/java/org/example/core/utils/SpringUtils.java @@ -0,0 +1,32 @@ +package org.example.core.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring获取工具 + * + * @author bool + * @date 2019-12-09 15:55 + */ +@Component +public class SpringUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + applicationContext = context; + } + + public static T getBean(Class tClass) { + return applicationContext.getBean(tClass); + } + + public static T getBean(String name, Class type) { + return applicationContext.getBean(name, type); + } + +} diff --git a/src/main/java/org/example/core/utils/StringUtils.java b/src/main/java/org/example/core/utils/StringUtils.java new file mode 100644 index 0000000..20ec321 --- /dev/null +++ b/src/main/java/org/example/core/utils/StringUtils.java @@ -0,0 +1,39 @@ +package org.example.core.utils; + +import java.util.Map; + +/** + * 字符串常用工具类 + * @author bool + * @date 2019-05-15 11:40 + */ +public class StringUtils { + + /** + * 判断是否为空字符 + * @param str + * @return + */ + public static boolean isBlank(String str){ + return str==null || "".equals(str); + } + + + /** + * 将MAP转换成一个xml格式,格式为value... + * @param params + * @return + */ + public static String mapToXml(Map params){ + StringBuffer sb = new StringBuffer(""); + for(String key:params.keySet()){ + sb.append("<") + .append(key).append(">") + .append(params.get(key)) + .append(""); + } + + sb.append(""); + return sb.toString(); + } +} diff --git a/src/main/java/org/example/core/utils/excel/ExportExcel.java b/src/main/java/org/example/core/utils/excel/ExportExcel.java new file mode 100644 index 0000000..c2ddba5 --- /dev/null +++ b/src/main/java/org/example/core/utils/excel/ExportExcel.java @@ -0,0 +1,401 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package org.example.core.utils.excel; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.example.core.utils.Reflections; +import org.example.core.utils.excel.annotation.ExcelField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion) + * @author jeeplus + * @version 2016-04-21 + */ +public class ExportExcel { + + private static Logger log = LoggerFactory.getLogger(ExportExcel.class); + + /** + * 工作薄对象 + */ + private SXSSFWorkbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 注解列表(Object[]{ ExcelField, Field/Method }) + */ + List annotationList = Lists.newArrayList(); + + /** + * 构造函数 + * @param title 表格标题,传“空值”,表示无标题 + * @param cls 实体对象,通过annotation.ExportField获取标题 + */ + public ExportExcel(String title, Class cls){ + this(title, cls, 1); + } + + /** + * 构造函数 + * @param title 表格标题,传“空值”,表示无标题 + * @param cls 实体对象,通过annotation.ExportField获取标题 + * @param type 导出类型(1:导出数据;2:导出模板) + * @param groups 导入分组 + */ + public ExportExcel(String title, Class cls, int type, int... groups){ + // Get annotation field + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==type)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==type)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Initialize + List headerList = Lists.newArrayList(); + for (Object[] os : annotationList){ + String t = ((ExcelField)os[0]).title(); + // 如果是导出,则去掉注释 + if (type==1){ + String[] ss = StringUtils.split(t, "**", 2); + if (ss.length==2){ + t = ss[0]; + } + } + headerList.add(t); + } + initialize(title, headerList); + } + /** + * 初始化函数 + * @param title 表格标题,传“空值”,表示无标题 + * @param headerList 表头列表 + */ + private void initialize(String title, List headerList) { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet("Export"); + this.styles = createStyles(wb); + // Create title + if (StringUtils.isNotBlank(title)){ + Row titleRow = sheet.createRow(rownum++); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), + titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1)); + } + // Create header + if (headerList == null){ + throw new RuntimeException("headerList not null!"); + } + Row headerRow = sheet.createRow(rownum++); + headerRow.setHeightInPoints(16); + for (int i = 0; i < headerList.size(); i++) { + Cell cell = headerRow.createCell(i); + cell.setCellStyle(styles.get("header")); + String[] ss = StringUtils.split(headerList.get(i), "**", 2); + if (ss.length==2){ + cell.setCellValue(ss[0]); + Comment comment = this.sheet.createDrawingPatriarch().createCellComment( + new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6)); + comment.setString(new XSSFRichTextString(ss[1])); + cell.setCellComment(comment); + }else{ + cell.setCellValue(headerList.get(i)); + } + sheet.autoSizeColumn(i); + } + for (int i = 0; i < headerList.size(); i++) { + int colWidth = sheet.getColumnWidth(i)*2; + sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth); + } + log.debug("Initialize success."); + } + + /** + * 创建表格样式 + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) { + Map styles = new HashMap<>(16); + + CellStyle style = wb.createCellStyle(); + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + style.setBorderRight(CellStyle.BORDER_THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(CellStyle.BORDER_THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(CellStyle.BORDER_THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(CellStyle.BORDER_THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_LEFT); + styles.put("data1", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_CENTER); + styles.put("data2", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(CellStyle.ALIGN_RIGHT); + styles.put("data3", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); +// style.setWrapText(true); + style.setAlignment(CellStyle.ALIGN_CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(CellStyle.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", style); + + return styles; + } + + /** + * 添加一行 + * @return 行对象 + */ + public Row addRow(){ + return sheet.createRow(rownum++); + } + + + /** + * 添加一个单元格 + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val){ + return this.addCell(row, column, val, 0, Class.class); + } + + /** + * 添加一个单元格 + * @param row 添加的行 + * @param column 添加列号 + * @param val 添加值 + * @param align 对齐方式(1:靠左;2:居中;3:靠右) + * @return 单元格对象 + */ + public Cell addCell(Row row, int column, Object val, int align, Class fieldType){ + Cell cell = row.createCell(column); + CellStyle style = styles.get("data"+(align>=1&&align<=3?align:"")); + try { + if (val == null){ + cell.setCellValue(""); + } else if (val instanceof String) { + cell.setCellValue((String) val); + } else if (val instanceof Integer) { + cell.setCellValue((Integer) val); + } else if (val instanceof Long) { + cell.setCellValue((Long) val); + } else if (val instanceof Double) { + cell.setCellValue((Double) val); + } else if (val instanceof Float) { + cell.setCellValue((Float) val); + } else if (val instanceof Date) { + DataFormat format = wb.createDataFormat(); + style.setDataFormat(format.getFormat("yyyy-MM-dd")); + cell.setCellValue((Date) val); + } else { + if (fieldType != Class.class){ + cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val)); + }else{ + cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val)); + } + } + } catch (Exception ex) { + log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString()); + cell.setCellValue(val.toString()); + } + cell.setCellStyle(style); + return cell; + } + + /** + * 添加数据(通过annotation.ExportField添加数据) + * @return list 数据列表 + */ + public ExportExcel setDataList(List list){ + for (E e : list){ + int colunm = 0; + Row row = this.addRow(); + StringBuilder sb = new StringBuilder(); + for (Object[] os : annotationList){ + ExcelField ef = (ExcelField)os[0]; + Object val = null; + try{ + if (StringUtils.isNotBlank(ef.value())){ + val = Reflections.invokeGetter(e, ef.value()); + }else{ + if (os[1] instanceof Field){ + val = Reflections.invokeGetter(e, ((Field)os[1]).getName()); + }else if (os[1] instanceof Method){ + val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {}); + } + } + }catch(Exception ex) { + log.info(ex.toString()); + val = ""; + } + this.addCell(row, colunm++, val, ef.align(), ef.fieldType()); + sb.append(val + ", "); + } + log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString()); + } + return this; + } + + /** + * 输出数据流 + * @param os 输出数据流 + */ + public ExportExcel write(OutputStream os) throws IOException{ + wb.write(os); + return this; + } + + /** + * 输出到客户端 + * @param fileName 输出文件名 + */ + public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{ + response.reset(); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("application/octet-stream; charset=utf-8"); + response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8")); + write(response.getOutputStream()); + return this; + } + + /** + * 清理临时文件 + */ + public ExportExcel dispose(){ + wb.dispose(); + return this; + } + +} diff --git a/src/main/java/org/example/core/utils/excel/ImportExcel.java b/src/main/java/org/example/core/utils/excel/ImportExcel.java new file mode 100644 index 0000000..d6a4f3e --- /dev/null +++ b/src/main/java/org/example/core/utils/excel/ImportExcel.java @@ -0,0 +1,302 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package org.example.core.utils.excel; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.example.core.utils.Reflections; +import org.example.core.utils.excel.annotation.ExcelField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +/** + * 导入Excel文件(支持“XLS”和“XLSX”格式) + * @author jeeplus + * @version 2016-03-10 + */ +public class ImportExcel { + + private static Logger log = LoggerFactory.getLogger(ImportExcel.class); + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 标题行号 + */ + private int headerNum; + + + + /** + * 构造函数 + * @param multipartFile 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex) + throws InvalidFormatException, IOException { + this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex); + } + + /** + * 构造函数 + * @param is 导入文件对象 + * @param headerNum 标题行号,数据行号=标题行号+1 + * @param sheetIndex 工作表编号 + * @throws InvalidFormatException + * @throws IOException + */ + public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex) + throws IOException { + if (StringUtils.isBlank(fileName)){ + throw new RuntimeException("导入文档为空!"); + }else if(fileName.toLowerCase().endsWith("xls")){ + this.wb = new HSSFWorkbook(is); + }else if(fileName.toLowerCase().endsWith("xlsx")){ + this.wb = new XSSFWorkbook(is); + }else{ + throw new RuntimeException("文档格式不正确!"); + } + if (this.wb.getNumberOfSheets() List getDataList(Class cls, int... groups) throws InstantiationException, IllegalAccessException{ + List annotationList = Lists.newArrayList(); + // Get annotation field + Field[] fs = cls.getDeclaredFields(); + for (Field f : fs){ + ExcelField ef = f.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==2)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, f}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, f}); + } + } + } + // Get annotation method + Method[] ms = cls.getDeclaredMethods(); + for (Method m : ms){ + ExcelField ef = m.getAnnotation(ExcelField.class); + if (ef != null && (ef.type()==0 || ef.type()==2)){ + if (groups!=null && groups.length>0){ + boolean inGroup = false; + for (int g : groups){ + if (inGroup){ + break; + } + for (int efg : ef.groups()){ + if (g == efg){ + inGroup = true; + annotationList.add(new Object[]{ef, m}); + break; + } + } + } + }else{ + annotationList.add(new Object[]{ef, m}); + } + } + } + // Field sorting + Collections.sort(annotationList, new Comparator() { + @Override + public int compare(Object[] o1, Object[] o2) { + return new Integer(((ExcelField)o1[0]).sort()).compareTo( + new Integer(((ExcelField)o2[0]).sort())); + } + }); + // Get excel data + List dataList = Lists.newArrayList(); + for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) { + E e = (E)cls.newInstance(); + int column = 0; + Row row = this.getRow(i); + StringBuilder sb = new StringBuilder(); + for (Object[] os : annotationList){ + Object val = this.getCellValue(row, column++); + if (val != null){ + ExcelField ef = (ExcelField)os[0]; + // Get param type and type cast + Class valType = Class.class; + if (os[1] instanceof Field){ + valType = ((Field)os[1]).getType(); + }else if (os[1] instanceof Method){ + Method method = ((Method)os[1]); + if ("get".equals(method.getName().substring(0, 3))){ + valType = method.getReturnType(); + }else if("set".equals(method.getName().substring(0, 3))){ + valType = ((Method)os[1]).getParameterTypes()[0]; + } + } + //log.debug("Import value type: ["+i+","+column+"] " + valType); + try { + //如果导入的java对象,需要在这里自己进行变换。 + if (valType == String.class){ + String s = String.valueOf(val.toString()); + if(StringUtils.endsWith(s, ".0")){ + val = StringUtils.substringBefore(s, ".0"); + }else{ + val = String.valueOf(val.toString()); + } + }else if (valType == Integer.class){ + val = Double.valueOf(val.toString()).intValue(); + }else if (valType == Long.class){ + val = Double.valueOf(val.toString()).longValue(); + }else if (valType == Double.class){ + val = Double.valueOf(val.toString()); + }else if (valType == Float.class){ + val = Float.valueOf(val.toString()); + }else if (valType == Date.class){ + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + val=sdf.parse(val.toString()); + }else{ + if (ef.fieldType() != Class.class){ + val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString()); + }else{ + val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(), + "fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString()); + } + } + } catch (Exception ex) { + log.info("Get cell value ["+i+","+column+"] error: " + ex.toString()); + val = null; + } + // set entity value + if (os[1] instanceof Field){ + Reflections.invokeSetter(e, ((Field)os[1]).getName(), val); + }else if (os[1] instanceof Method){ + String mthodName = ((Method)os[1]).getName(); + if ("get".equals(mthodName.substring(0, 3))){ + mthodName = "set"+StringUtils.substringAfter(mthodName, "get"); + } + Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val}); + } + } + sb.append(val+", "); + } + dataList.add(e); + log.debug("Read success: ["+i+"] "+sb.toString()); + } + return dataList; + } + +} diff --git a/src/main/java/org/example/core/utils/excel/annotation/ExcelField.java b/src/main/java/org/example/core/utils/excel/annotation/ExcelField.java new file mode 100644 index 0000000..d553f23 --- /dev/null +++ b/src/main/java/org/example/core/utils/excel/annotation/ExcelField.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package org.example.core.utils.excel.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解定义 + * @author jeeplus + * @version 2016-03-10 + */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelField { + + /** + * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”) + */ + String value() default ""; + + /** + * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效) + */ + String title(); + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + int type() default 0; + + /** + * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右) + */ + int align() default 0; + + /** + * 导出字段字段排序(升序) + */ + int sort() default 0; + + /** + * 如果是字典类型,请设置字典的type值 + */ + String dictType() default ""; + + /** + * 反射类型 + */ + Class fieldType() default Class.class; + + /** + * 字段归属组(根据分组导出导入) + */ + int[] groups() default {}; +} diff --git a/src/main/java/org/example/core/utils/excel/fieldtype/ListType.java b/src/main/java/org/example/core/utils/excel/fieldtype/ListType.java new file mode 100644 index 0000000..864356c --- /dev/null +++ b/src/main/java/org/example/core/utils/excel/fieldtype/ListType.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2015-2020 JeePlus All rights reserved. + */ +package org.example.core.utils.excel.fieldtype; + +import java.util.List; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.StringUtils; + +/** + * 字段类型转换 + * @author jeeplus + * @version 2016-5-29 + */ +public class ListType { + + /** + * 获取对象值(导入) + */ + public static Object getValue(String val) { + List list = Lists.newArrayList(); + if(!StringUtils.isBlank(val)) { + for (String s : val.split(",")) { + list.add(s); + } + } + return list; + } + + /** + * 设置对象值(导出) + */ + public static String setValue(Object val) { + if (val != null){ + List list = (List)val; + StringBuffer sb = null; + for (String item: list){ + if(StringUtils.isBlank(item)){ + continue; + } + if(sb == null){ + sb = new StringBuffer(item); + }else{ + sb.append(",").append(item); + } + } + + if(sb!=null) { + return sb.toString().replace("[]", ""); + } + } + return ""; + } + +} diff --git a/src/main/java/org/example/core/utils/file/Md5Util.java b/src/main/java/org/example/core/utils/file/Md5Util.java new file mode 100644 index 0000000..6a48103 --- /dev/null +++ b/src/main/java/org/example/core/utils/file/Md5Util.java @@ -0,0 +1,37 @@ +package org.example.core.utils.file; + +import java.security.MessageDigest; + + +/** + * MD5工具类 + * ClassName: MD5Util
+ * date: 2018年1月13日 下午6:54:53
+ * + * @author Bool + * @version + */ +public class Md5Util { + + + /** + * 简单MD5 + * @param str + * @return + */ + public static String md5(String str) { + + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] array = md.digest(str.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString(); + }catch(Exception e) { + return null; + } + } + +} diff --git a/src/main/java/org/example/core/utils/jackson/JsonHelper.java b/src/main/java/org/example/core/utils/jackson/JsonHelper.java new file mode 100644 index 0000000..539699a --- /dev/null +++ b/src/main/java/org/example/core/utils/jackson/JsonHelper.java @@ -0,0 +1,116 @@ +package org.example.core.utils.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import java.io.IOException; +import java.util.TimeZone; +import org.example.core.exception.ServiceException; + +/** + * JSON工具类 + * @author van + */ +public class JsonHelper { + + /** + * 转换为字符串 + * @param obj + * @return + */ + public static String toJson(Object obj) { + + ObjectMapper mapper = getMapper(); + + try { + return mapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** + * 将字符转换为java对象 + * @param json + * @param clazz + * @return + */ + public static T parseObject(String json, Class clazz) { + ObjectMapper mapper = getMapper(); + try { + return mapper.readValue(json, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 将对象转换为另外一个对象 + * @param object + * @param clazz + * @return + */ + public static T parseObject(Object object, Class clazz) { + ObjectMapper mapper = getMapper(); + try { + return mapper.readValue(toJson(object), clazz); + } catch (InvalidFormatException e){ + throw new ServiceException("数据格式存在错误!"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 复杂对象的转换 + * @param object + * @param typeReference + * @return + * @param + */ + public static T parseObject(Object object, TypeReference typeReference) { + ObjectMapper mapper = getMapper(); + try { + return mapper.readValue(toJson(object), typeReference); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 复杂对象的转换 + * @param json + * @param typeReference + * @return + * @param + */ + public static T parseObject(String json, TypeReference typeReference) { + ObjectMapper mapper = getMapper(); + try { + return mapper.readValue(json, typeReference); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取转换配置 + * @return + */ + public static ObjectMapper getMapper(){ + ObjectMapper mapper = new ObjectMapper(); + mapper.setDateFormat(new MyDateFormat()); + mapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); + + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + return mapper; + } +} diff --git a/src/main/java/org/example/core/utils/jackson/MyDateFormat.java b/src/main/java/org/example/core/utils/jackson/MyDateFormat.java new file mode 100644 index 0000000..eab1902 --- /dev/null +++ b/src/main/java/org/example/core/utils/jackson/MyDateFormat.java @@ -0,0 +1,36 @@ +package org.example.core.utils.jackson; + +import com.fasterxml.jackson.databind.util.StdDateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * JSON时间格式类,用于兼容不同类型日期 + * @author van + */ +public class MyDateFormat extends SimpleDateFormat { + + private StdDateFormat stdDateFormat = new StdDateFormat(); + + public MyDateFormat() { + // 设置默认的日期格式 + super("yyyy-MM-dd HH:mm:ss"); + } + + @Override + public Date parse(String dateStr) throws ParseException { + if (dateStr != null && !dateStr.contains("T")) { + return super.parse(dateStr); + } + return stdDateFormat.parse(dateStr); + } + + @Override + public Object clone() { + MyDateFormat other = (MyDateFormat)super.clone(); + other.stdDateFormat = new StdDateFormat(); + return other; + } + +} diff --git a/src/main/java/org/example/dto/TaskCallbackDataDto.java b/src/main/java/org/example/dto/TaskCallbackDataDto.java new file mode 100644 index 0000000..7641210 --- /dev/null +++ b/src/main/java/org/example/dto/TaskCallbackDataDto.java @@ -0,0 +1,169 @@ +package org.example.dto; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.util.List; +import lombok.Data; + +/** + * @description: 任务单通电话回调传输data + * @author: haown + * @create: 2024-08-29 09:36 + **/ +@Data +public class TaskCallbackDataDto { + + /** + * 通话唯一标识 + */ + private String sessionId; + + /** + * 租户唯一标识 + */ + private Long tenantId; + + /** + * 任务标识 + */ + private String taskId; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 机器人ID + */ + private String robotId; + + /** + * 外呼机器人名称 + */ + private String robotName; + + /** + * 号码组唯一标识,导入名单后,返回的名单号码组对应标识 + */ + private Long memberId; + + /** + * 被叫号码 + */ + private String mobile; + + /** + * 拨打次数 + */ + private Integer callTimes; + + /** + * 主叫号码 + */ + private String callerNum; + + /** + * 接通状态,1-已接通 0-未接通 + */ + private Integer endType; + /** + * 呼叫类型,0-首次呼叫;1-重试;2-预约呼叫;3-实时呼叫 + */ + private Integer callType; + + /** + * 未接通原因 + */ + private String endTypeReason; + + /** + * 生成通话录音唯一标识,可通过该标识,获取录音 + */ + private String contactUUID; + + /** + * 文件id,名单导入任务时生成的文件ID + */ + private Long field; + + /** + * 信息收集内容 + */ + private JSONObject collectInfo; + + /** + * 会话还原记录 + */ + private JSONArray record; + + /** + * 拨号总时长,单位为秒 + */ + private Integer durationTimeLen; + + /** + * 振铃时长,单位为秒 + */ + private Integer ringingTimeLen; + + /** + * 对话时长,单位为秒 + */ + private Integer talkingTimeLen; + + /** + * 呼叫开始时间-Unix时间戳(单位:毫秒) + */ + private Long startTime; + + /** + * 振铃开始时间-Unix时间戳(单位:毫秒) + */ + private Long ringStartTime; + + /** + * 通话开始时间-Unix时间戳(单位:毫秒),未接通显示'-',已接通会有值 + */ + private Long talkingStartTime; + + /** + * 呼叫结束时间-Unix时间戳(单位:毫秒) + */ + private Long endTime; + + /** + * 意向,在外呼机器人-流程节点-信息收集,配置key为「意向」,对应的value值 + */ + private String intent; + + /** + * 动作,HUNGUP:挂机 + */ + private List action; + + /** + * 是否机器人主动挂机 + */ + private Boolean isRobotHangup; + + /** + * 变量,导入名单的变量 + */ + private JSONObject dialogVar; + + /** + * 短信变量 + */ + private JSONObject smsVar; + + /** + * 来自导入信息 + */ + private String extJson; + + /** + * 转接结果,字段为空或值为0未发起,1成功,-1失败 + */ + private Integer transResult; +} diff --git a/src/main/java/org/example/dto/TaskCallbackDto.java b/src/main/java/org/example/dto/TaskCallbackDto.java new file mode 100644 index 0000000..96617af --- /dev/null +++ b/src/main/java/org/example/dto/TaskCallbackDto.java @@ -0,0 +1,22 @@ +package org.example.dto; + +import lombok.Data; + +/** + * @description: 任务单通电话回调传输对象 + * @author: haown + * @create: 2024-08-29 14:06 + **/ +@Data +public class TaskCallbackDto { + + /** + * 回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调 + */ + private Integer callbackType; + + /** + * 任务单通电话回调传输data + */ + private TaskCallbackDataDto data; +} diff --git a/src/main/java/org/example/entity/AIOBCallbackEntity.java b/src/main/java/org/example/entity/AIOBCallbackEntity.java new file mode 100644 index 0000000..739f28d --- /dev/null +++ b/src/main/java/org/example/entity/AIOBCallbackEntity.java @@ -0,0 +1,49 @@ +package org.example.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import java.util.Date; +import lombok.Data; + +/** + * @description: 百度外呼回调传输对象 + * @author: haown + * @create: 2025-12-22 15:11 + **/ +@Data +@TableName("aiob_callback_data") +public class AIOBCallbackEntity extends Model { + + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + /** + * 回调数据类型, 0-任务呼叫单通电话回调 1-号码组终态回调 2-任务状态变更回调 3-实时呼叫单通电话回调 + */ + private Integer callbackType; + + /** + * 回调数据 + */ + private String callbackData; + + /** + * 已读状态,0:未读,1:已读 + */ + private Integer readState; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 修改时间 + */ + private Date updateDate; +} diff --git a/src/main/java/org/example/mapper/AIOBCallbackMapper.java b/src/main/java/org/example/mapper/AIOBCallbackMapper.java new file mode 100644 index 0000000..3ba5836 --- /dev/null +++ b/src/main/java/org/example/mapper/AIOBCallbackMapper.java @@ -0,0 +1,8 @@ +package org.example.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.example.entity.AIOBCallbackEntity; + +public interface AIOBCallbackMapper extends BaseMapper { + +} diff --git a/src/main/java/org/example/service/IAIOBCallbackService.java b/src/main/java/org/example/service/IAIOBCallbackService.java new file mode 100644 index 0000000..c528b7c --- /dev/null +++ b/src/main/java/org/example/service/IAIOBCallbackService.java @@ -0,0 +1,16 @@ +package org.example.service; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import org.example.dto.TaskCallbackDataDto; +import org.example.entity.AIOBCallbackEntity; + +/** + * @description: 百度外呼回调service + * @author: haown + * @create: 2025-12-22 15:11 + **/ +public interface IAIOBCallbackService extends IService { + + JSONObject taskCallBack(Integer callbackType, TaskCallbackDataDto data); +} diff --git a/src/main/java/org/example/service/impl/AIOBCallbackServiceImpl.java b/src/main/java/org/example/service/impl/AIOBCallbackServiceImpl.java new file mode 100644 index 0000000..bdadea8 --- /dev/null +++ b/src/main/java/org/example/service/impl/AIOBCallbackServiceImpl.java @@ -0,0 +1,39 @@ +package org.example.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import java.util.Date; +import javax.annotation.Resource; +import org.example.dto.TaskCallbackDataDto; +import org.example.entity.AIOBCallbackEntity; +import org.example.mapper.AIOBCallbackMapper; +import org.example.service.IAIOBCallbackService; +import org.springframework.stereotype.Service; + +/** + * @description: 百度外呼回调service实现类 + * @author: haown + * @create: 2025-12-22 15:22 + **/ +@Service +public class AIOBCallbackServiceImpl extends ServiceImpl implements IAIOBCallbackService { + + @Resource + private AIOBCallbackMapper aiobCallbackMapper; + + @Override public JSONObject taskCallBack(Integer callbackType, TaskCallbackDataDto data) { + JSONObject retObj = new JSONObject(); + retObj.fluentPut("code", 200).fluentPut("msg", "success"); + if (callbackType == 0 || callbackType == 3) { + // 保存 + AIOBCallbackEntity aiobCallbackEntity = new AIOBCallbackEntity(); + aiobCallbackEntity.setCallbackType(callbackType); + aiobCallbackEntity.setCallbackData(JSON.toJSONString(data)); + aiobCallbackEntity.setReadState(0); + aiobCallbackEntity.setCreateDate(new Date()); + aiobCallbackMapper.insert(aiobCallbackEntity); + } + return retObj; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..ee330e2 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,36 @@ +# 开发环境配置文件 +spring: + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://8.131.93.145:54081/aiob_callback?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: 1qaz!@#$ + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..c8befae --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,93 @@ +# 独立配置文件,可以拿到jar外面跑 +spring: + application: + name: yf-exam-lite + profiles: + active: dev + main: + allow-bean-definition-overriding: true + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + deserialization: + fail_on_unknown_properties: false + parser: + # 允许出现特殊字符和转义符 + allow_unquoted_control_chars: true + #允许出现单引号 + allow_single_quotes: true + serialization: + fail-on-empty-beans: false + mapper: + # 支持类型转换 + allow-coercion-of-scalars: true + + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yf_exam_lite?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: root + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + +server: + port: 8101 + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css + +# 文件上传配置 +conf: + upload: + # 物理文件存储位置,以/结束,windows已正斜杠,如:d:/exam-upload/ + dir: /data/upload/ + # 访问地址,注意不要去除/upload/file/,此节点为虚拟标识符 + # 如:http://localhost:8101/upload/file/exam.jpg,对应物理文件为:/data/upload/exam.jpg + url: http://8.131.93.145:54012/upload/file/ + # 允许上传的文件后缀 + allow-extensions: jpg,jpeg,png + folder: + # 身份证正面存储文件夹名称 + card_front_url: cardfront/ + # 身份证背面存储文件夹名称 + card_back_url: cardback/ + # 身份证正反面复印件 + card_copy_url: cardcopy/ + # 证件照 + photo_url: photo/ + # 学历证明 + certificate_url: certificate/ + # 体检报告 + physical_report_url: physicalreport/ + # 签名图片 + sign_picture_url: signpicture/ + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..ef4e30b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,33 @@ +spring: + application: + name: aiob-callback + profiles: + active: dev + main: + allow-bean-definition-overriding: true + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + deserialization: + fail_on_unknown_properties: false + parser: + # 允许出现特殊字符和转义符 + allow_unquoted_control_chars: true + #允许出现单引号 + allow_single_quotes: true + serialization: + fail-on-empty-beans: false + mapper: + # 支持类型转换 + allow-coercion-of-scalars: true +server: + port: 8101 + servlet: + # 应用的访问路径 + context-path: / + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css diff --git a/src/main/resources/mapper/AIOBCallbackMapper.xml b/src/main/resources/mapper/AIOBCallbackMapper.xml new file mode 100644 index 0000000..7af00e4 --- /dev/null +++ b/src/main/resources/mapper/AIOBCallbackMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + id, callback_type, callback_data, read_state, create_date, update_date + + + diff --git a/target/classes/application-dev.yml b/target/classes/application-dev.yml new file mode 100644 index 0000000..ee330e2 --- /dev/null +++ b/target/classes/application-dev.yml @@ -0,0 +1,36 @@ +# 开发环境配置文件 +spring: + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://8.131.93.145:54081/aiob_callback?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: 1qaz!@#$ + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/target/classes/application-local.yml b/target/classes/application-local.yml new file mode 100644 index 0000000..c8befae --- /dev/null +++ b/target/classes/application-local.yml @@ -0,0 +1,93 @@ +# 独立配置文件,可以拿到jar外面跑 +spring: + application: + name: yf-exam-lite + profiles: + active: dev + main: + allow-bean-definition-overriding: true + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + deserialization: + fail_on_unknown_properties: false + parser: + # 允许出现特殊字符和转义符 + allow_unquoted_control_chars: true + #允许出现单引号 + allow_single_quotes: true + serialization: + fail-on-empty-beans: false + mapper: + # 支持类型转换 + allow-coercion-of-scalars: true + + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yf_exam_lite?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: root + # druid相关配置 + druid: + max-active: 5000 + initial-size: 20 + min-idle: 5 + async-init: true + # 监控统计 + filters: stat,wall + filter: + stat: + log-slow-sql: true + slow-sql-millis: 5000 + wall: + config: + create-table-allow: false + alter-table-allow: false + drop-table-allow: false + truncate-allow: false + +server: + port: 8101 + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css + +# 文件上传配置 +conf: + upload: + # 物理文件存储位置,以/结束,windows已正斜杠,如:d:/exam-upload/ + dir: /data/upload/ + # 访问地址,注意不要去除/upload/file/,此节点为虚拟标识符 + # 如:http://localhost:8101/upload/file/exam.jpg,对应物理文件为:/data/upload/exam.jpg + url: http://8.131.93.145:54012/upload/file/ + # 允许上传的文件后缀 + allow-extensions: jpg,jpeg,png + folder: + # 身份证正面存储文件夹名称 + card_front_url: cardfront/ + # 身份证背面存储文件夹名称 + card_back_url: cardback/ + # 身份证正反面复印件 + card_copy_url: cardcopy/ + # 证件照 + photo_url: photo/ + # 学历证明 + certificate_url: certificate/ + # 体检报告 + physical_report_url: physicalreport/ + # 签名图片 + sign_picture_url: signpicture/ + +# 开启文档 +swagger: + enable: true + +logging: + level: + root: debug + path: logs/${spring.application.name}/ diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..ef4e30b --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,33 @@ +spring: + application: + name: aiob-callback + profiles: + active: dev + main: + allow-bean-definition-overriding: true + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + deserialization: + fail_on_unknown_properties: false + parser: + # 允许出现特殊字符和转义符 + allow_unquoted_control_chars: true + #允许出现单引号 + allow_single_quotes: true + serialization: + fail-on-empty-beans: false + mapper: + # 支持类型转换 + allow-coercion-of-scalars: true +server: + port: 8101 + servlet: + # 应用的访问路径 + context-path: / + # 启用服务端压缩 + compression: + enabled: true + min-response-size: 10 + mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css diff --git a/target/classes/mapper/AIOBCallbackMapper.xml b/target/classes/mapper/AIOBCallbackMapper.xml new file mode 100644 index 0000000..7af00e4 --- /dev/null +++ b/target/classes/mapper/AIOBCallbackMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + id, callback_type, callback_data, read_state, create_date, update_date + + + diff --git a/target/classes/org/example/AIOBCallbackApplication.class b/target/classes/org/example/AIOBCallbackApplication.class new file mode 100644 index 0000000..91e1035 Binary files /dev/null and b/target/classes/org/example/AIOBCallbackApplication.class differ diff --git a/target/classes/org/example/ability/shiro/CNFilterFactoryBean.class b/target/classes/org/example/ability/shiro/CNFilterFactoryBean.class new file mode 100644 index 0000000..3504f16 Binary files /dev/null and b/target/classes/org/example/ability/shiro/CNFilterFactoryBean.class differ diff --git a/target/classes/org/example/ability/shiro/ShiroRealm.class b/target/classes/org/example/ability/shiro/ShiroRealm.class new file mode 100644 index 0000000..2a04ae6 Binary files /dev/null and b/target/classes/org/example/ability/shiro/ShiroRealm.class differ diff --git a/target/classes/org/example/ability/shiro/aop/JwtFilter.class b/target/classes/org/example/ability/shiro/aop/JwtFilter.class new file mode 100644 index 0000000..f3bdd1c Binary files /dev/null and b/target/classes/org/example/ability/shiro/aop/JwtFilter.class differ diff --git a/target/classes/org/example/ability/shiro/jwt/JwtToken.class b/target/classes/org/example/ability/shiro/jwt/JwtToken.class new file mode 100644 index 0000000..9851eb6 Binary files /dev/null and b/target/classes/org/example/ability/shiro/jwt/JwtToken.class differ diff --git a/target/classes/org/example/ability/shiro/jwt/JwtUtils.class b/target/classes/org/example/ability/shiro/jwt/JwtUtils.class new file mode 100644 index 0000000..5aaf1b6 Binary files /dev/null and b/target/classes/org/example/ability/shiro/jwt/JwtUtils.class differ diff --git a/target/classes/org/example/aspect/mybatis/QueryInterceptor.class b/target/classes/org/example/aspect/mybatis/QueryInterceptor.class new file mode 100644 index 0000000..b682a5a Binary files /dev/null and b/target/classes/org/example/aspect/mybatis/QueryInterceptor.class differ diff --git a/target/classes/org/example/aspect/mybatis/UpdateInterceptor.class b/target/classes/org/example/aspect/mybatis/UpdateInterceptor.class new file mode 100644 index 0000000..6f1be67 Binary files /dev/null and b/target/classes/org/example/aspect/mybatis/UpdateInterceptor.class differ diff --git a/target/classes/org/example/aspect/utils/InjectUtils.class b/target/classes/org/example/aspect/utils/InjectUtils.class new file mode 100644 index 0000000..d4a809a Binary files /dev/null and b/target/classes/org/example/aspect/utils/InjectUtils.class differ diff --git a/target/classes/org/example/config/CorsConfig.class b/target/classes/org/example/config/CorsConfig.class new file mode 100644 index 0000000..cb6021a Binary files /dev/null and b/target/classes/org/example/config/CorsConfig.class differ diff --git a/target/classes/org/example/config/MultipartConfig.class b/target/classes/org/example/config/MultipartConfig.class new file mode 100644 index 0000000..00a1dbe Binary files /dev/null and b/target/classes/org/example/config/MultipartConfig.class differ diff --git a/target/classes/org/example/config/MybatisConfig.class b/target/classes/org/example/config/MybatisConfig.class new file mode 100644 index 0000000..b448818 Binary files /dev/null and b/target/classes/org/example/config/MybatisConfig.class differ diff --git a/target/classes/org/example/config/ScheduledConfig.class b/target/classes/org/example/config/ScheduledConfig.class new file mode 100644 index 0000000..9cca7e5 Binary files /dev/null and b/target/classes/org/example/config/ScheduledConfig.class differ diff --git a/target/classes/org/example/config/ShiroConfig.class b/target/classes/org/example/config/ShiroConfig.class new file mode 100644 index 0000000..1a6a1e3 Binary files /dev/null and b/target/classes/org/example/config/ShiroConfig.class differ diff --git a/target/classes/org/example/config/SwaggerConfig.class b/target/classes/org/example/config/SwaggerConfig.class new file mode 100644 index 0000000..d6a94b1 Binary files /dev/null and b/target/classes/org/example/config/SwaggerConfig.class differ diff --git a/target/classes/org/example/config/jackson/JacksonConfig.class b/target/classes/org/example/config/jackson/JacksonConfig.class new file mode 100644 index 0000000..2caec8d Binary files /dev/null and b/target/classes/org/example/config/jackson/JacksonConfig.class differ diff --git a/target/classes/org/example/config/jackson/XssEscapeJsonDeserializer.class b/target/classes/org/example/config/jackson/XssEscapeJsonDeserializer.class new file mode 100644 index 0000000..af6e8ab Binary files /dev/null and b/target/classes/org/example/config/jackson/XssEscapeJsonDeserializer.class differ diff --git a/target/classes/org/example/controller/AIOBCallbackController.class b/target/classes/org/example/controller/AIOBCallbackController.class new file mode 100644 index 0000000..df20782 Binary files /dev/null and b/target/classes/org/example/controller/AIOBCallbackController.class differ diff --git a/target/classes/org/example/core/annon/Dict.class b/target/classes/org/example/core/annon/Dict.class new file mode 100644 index 0000000..5ae69f0 Binary files /dev/null and b/target/classes/org/example/core/annon/Dict.class differ diff --git a/target/classes/org/example/core/api/ApiError.class b/target/classes/org/example/core/api/ApiError.class new file mode 100644 index 0000000..fa791db Binary files /dev/null and b/target/classes/org/example/core/api/ApiError.class differ diff --git a/target/classes/org/example/core/api/ApiRest.class b/target/classes/org/example/core/api/ApiRest.class new file mode 100644 index 0000000..7f9e5dc Binary files /dev/null and b/target/classes/org/example/core/api/ApiRest.class differ diff --git a/target/classes/org/example/core/api/controller/BaseController.class b/target/classes/org/example/core/api/controller/BaseController.class new file mode 100644 index 0000000..f04f7c3 Binary files /dev/null and b/target/classes/org/example/core/api/controller/BaseController.class differ diff --git a/target/classes/org/example/core/api/dto/BaseDTO.class b/target/classes/org/example/core/api/dto/BaseDTO.class new file mode 100644 index 0000000..d3c75cb Binary files /dev/null and b/target/classes/org/example/core/api/dto/BaseDTO.class differ diff --git a/target/classes/org/example/core/api/dto/BaseIdReqDTO.class b/target/classes/org/example/core/api/dto/BaseIdReqDTO.class new file mode 100644 index 0000000..9355eec Binary files /dev/null and b/target/classes/org/example/core/api/dto/BaseIdReqDTO.class differ diff --git a/target/classes/org/example/core/api/dto/BaseIdRespDTO.class b/target/classes/org/example/core/api/dto/BaseIdRespDTO.class new file mode 100644 index 0000000..df99ba0 Binary files /dev/null and b/target/classes/org/example/core/api/dto/BaseIdRespDTO.class differ diff --git a/target/classes/org/example/core/api/dto/BaseIdsReqDTO.class b/target/classes/org/example/core/api/dto/BaseIdsReqDTO.class new file mode 100644 index 0000000..7e1c6b4 Binary files /dev/null and b/target/classes/org/example/core/api/dto/BaseIdsReqDTO.class differ diff --git a/target/classes/org/example/core/api/dto/BaseStateReqDTO.class b/target/classes/org/example/core/api/dto/BaseStateReqDTO.class new file mode 100644 index 0000000..1847c43 Binary files /dev/null and b/target/classes/org/example/core/api/dto/BaseStateReqDTO.class differ diff --git a/target/classes/org/example/core/api/dto/PagingReqDTO.class b/target/classes/org/example/core/api/dto/PagingReqDTO.class new file mode 100644 index 0000000..16ea15b Binary files /dev/null and b/target/classes/org/example/core/api/dto/PagingReqDTO.class differ diff --git a/target/classes/org/example/core/api/dto/PagingRespDTO.class b/target/classes/org/example/core/api/dto/PagingRespDTO.class new file mode 100644 index 0000000..9f3b368 Binary files /dev/null and b/target/classes/org/example/core/api/dto/PagingRespDTO.class differ diff --git a/target/classes/org/example/core/api/utils/JsonConverter.class b/target/classes/org/example/core/api/utils/JsonConverter.class new file mode 100644 index 0000000..e6fb999 Binary files /dev/null and b/target/classes/org/example/core/api/utils/JsonConverter.class differ diff --git a/target/classes/org/example/core/domain/AjaxResult.class b/target/classes/org/example/core/domain/AjaxResult.class new file mode 100644 index 0000000..4fd5172 Binary files /dev/null and b/target/classes/org/example/core/domain/AjaxResult.class differ diff --git a/target/classes/org/example/core/enums/CommonState.class b/target/classes/org/example/core/enums/CommonState.class new file mode 100644 index 0000000..65b3d23 Binary files /dev/null and b/target/classes/org/example/core/enums/CommonState.class differ diff --git a/target/classes/org/example/core/enums/ConfirmRefundStatusEnum.class b/target/classes/org/example/core/enums/ConfirmRefundStatusEnum.class new file mode 100644 index 0000000..2041b82 Binary files /dev/null and b/target/classes/org/example/core/enums/ConfirmRefundStatusEnum.class differ diff --git a/target/classes/org/example/core/enums/GooodsOrderStatusEnum.class b/target/classes/org/example/core/enums/GooodsOrderStatusEnum.class new file mode 100644 index 0000000..30c88ae Binary files /dev/null and b/target/classes/org/example/core/enums/GooodsOrderStatusEnum.class differ diff --git a/target/classes/org/example/core/enums/OpenType.class b/target/classes/org/example/core/enums/OpenType.class new file mode 100644 index 0000000..3f30661 Binary files /dev/null and b/target/classes/org/example/core/enums/OpenType.class differ diff --git a/target/classes/org/example/core/enums/PayTypeEnum.class b/target/classes/org/example/core/enums/PayTypeEnum.class new file mode 100644 index 0000000..e583d05 Binary files /dev/null and b/target/classes/org/example/core/enums/PayTypeEnum.class differ diff --git a/target/classes/org/example/core/enums/RefundStatusEnum.class b/target/classes/org/example/core/enums/RefundStatusEnum.class new file mode 100644 index 0000000..68f4e3f Binary files /dev/null and b/target/classes/org/example/core/enums/RefundStatusEnum.class differ diff --git a/target/classes/org/example/core/enums/WeChatTradeStateEnum.class b/target/classes/org/example/core/enums/WeChatTradeStateEnum.class new file mode 100644 index 0000000..d33bee2 Binary files /dev/null and b/target/classes/org/example/core/enums/WeChatTradeStateEnum.class differ diff --git a/target/classes/org/example/core/exception/ServiceException.class b/target/classes/org/example/core/exception/ServiceException.class new file mode 100644 index 0000000..7b55bfc Binary files /dev/null and b/target/classes/org/example/core/exception/ServiceException.class differ diff --git a/target/classes/org/example/core/exception/ServiceExceptionHandler.class b/target/classes/org/example/core/exception/ServiceExceptionHandler.class new file mode 100644 index 0000000..38ccbfd Binary files /dev/null and b/target/classes/org/example/core/exception/ServiceExceptionHandler.class differ diff --git a/target/classes/org/example/core/utils/BeanMapper.class b/target/classes/org/example/core/utils/BeanMapper.class new file mode 100644 index 0000000..e4a6818 Binary files /dev/null and b/target/classes/org/example/core/utils/BeanMapper.class differ diff --git a/target/classes/org/example/core/utils/CronUtils.class b/target/classes/org/example/core/utils/CronUtils.class new file mode 100644 index 0000000..5b0fe9f Binary files /dev/null and b/target/classes/org/example/core/utils/CronUtils.class differ diff --git a/target/classes/org/example/core/utils/DateUtils.class b/target/classes/org/example/core/utils/DateUtils.class new file mode 100644 index 0000000..5c1f2ce Binary files /dev/null and b/target/classes/org/example/core/utils/DateUtils.class differ diff --git a/target/classes/org/example/core/utils/IpUtils.class b/target/classes/org/example/core/utils/IpUtils.class new file mode 100644 index 0000000..2c6f7da Binary files /dev/null and b/target/classes/org/example/core/utils/IpUtils.class differ diff --git a/target/classes/org/example/core/utils/Reflections.class b/target/classes/org/example/core/utils/Reflections.class new file mode 100644 index 0000000..e8e166d Binary files /dev/null and b/target/classes/org/example/core/utils/Reflections.class differ diff --git a/target/classes/org/example/core/utils/SpringUtils.class b/target/classes/org/example/core/utils/SpringUtils.class new file mode 100644 index 0000000..4a9b5ef Binary files /dev/null and b/target/classes/org/example/core/utils/SpringUtils.class differ diff --git a/target/classes/org/example/core/utils/StringUtils.class b/target/classes/org/example/core/utils/StringUtils.class new file mode 100644 index 0000000..06a7429 Binary files /dev/null and b/target/classes/org/example/core/utils/StringUtils.class differ diff --git a/target/classes/org/example/core/utils/excel/ExportExcel$1.class b/target/classes/org/example/core/utils/excel/ExportExcel$1.class new file mode 100644 index 0000000..bf36393 Binary files /dev/null and b/target/classes/org/example/core/utils/excel/ExportExcel$1.class differ diff --git a/target/classes/org/example/core/utils/excel/ExportExcel.class b/target/classes/org/example/core/utils/excel/ExportExcel.class new file mode 100644 index 0000000..138e13f Binary files /dev/null and b/target/classes/org/example/core/utils/excel/ExportExcel.class differ diff --git a/target/classes/org/example/core/utils/excel/ImportExcel$1.class b/target/classes/org/example/core/utils/excel/ImportExcel$1.class new file mode 100644 index 0000000..a15bec7 Binary files /dev/null and b/target/classes/org/example/core/utils/excel/ImportExcel$1.class differ diff --git a/target/classes/org/example/core/utils/excel/ImportExcel.class b/target/classes/org/example/core/utils/excel/ImportExcel.class new file mode 100644 index 0000000..b68827a Binary files /dev/null and b/target/classes/org/example/core/utils/excel/ImportExcel.class differ diff --git a/target/classes/org/example/core/utils/excel/annotation/ExcelField.class b/target/classes/org/example/core/utils/excel/annotation/ExcelField.class new file mode 100644 index 0000000..49080fc Binary files /dev/null and b/target/classes/org/example/core/utils/excel/annotation/ExcelField.class differ diff --git a/target/classes/org/example/core/utils/excel/fieldtype/ListType.class b/target/classes/org/example/core/utils/excel/fieldtype/ListType.class new file mode 100644 index 0000000..a67b8a3 Binary files /dev/null and b/target/classes/org/example/core/utils/excel/fieldtype/ListType.class differ diff --git a/target/classes/org/example/core/utils/file/Md5Util.class b/target/classes/org/example/core/utils/file/Md5Util.class new file mode 100644 index 0000000..54f10fc Binary files /dev/null and b/target/classes/org/example/core/utils/file/Md5Util.class differ diff --git a/target/classes/org/example/core/utils/jackson/JsonHelper.class b/target/classes/org/example/core/utils/jackson/JsonHelper.class new file mode 100644 index 0000000..d285ac4 Binary files /dev/null and b/target/classes/org/example/core/utils/jackson/JsonHelper.class differ diff --git a/target/classes/org/example/core/utils/jackson/MyDateFormat.class b/target/classes/org/example/core/utils/jackson/MyDateFormat.class new file mode 100644 index 0000000..e32b8e3 Binary files /dev/null and b/target/classes/org/example/core/utils/jackson/MyDateFormat.class differ diff --git a/target/classes/org/example/dto/TaskCallbackDataDto.class b/target/classes/org/example/dto/TaskCallbackDataDto.class new file mode 100644 index 0000000..818b0e3 Binary files /dev/null and b/target/classes/org/example/dto/TaskCallbackDataDto.class differ diff --git a/target/classes/org/example/dto/TaskCallbackDto.class b/target/classes/org/example/dto/TaskCallbackDto.class new file mode 100644 index 0000000..731a56b Binary files /dev/null and b/target/classes/org/example/dto/TaskCallbackDto.class differ diff --git a/target/classes/org/example/entity/AIOBCallbackEntity.class b/target/classes/org/example/entity/AIOBCallbackEntity.class new file mode 100644 index 0000000..1ffaa62 Binary files /dev/null and b/target/classes/org/example/entity/AIOBCallbackEntity.class differ diff --git a/target/classes/org/example/mapper/AIOBCallbackMapper.class b/target/classes/org/example/mapper/AIOBCallbackMapper.class new file mode 100644 index 0000000..65c01da Binary files /dev/null and b/target/classes/org/example/mapper/AIOBCallbackMapper.class differ diff --git a/target/classes/org/example/service/IAIOBCallbackService.class b/target/classes/org/example/service/IAIOBCallbackService.class new file mode 100644 index 0000000..51da580 Binary files /dev/null and b/target/classes/org/example/service/IAIOBCallbackService.class differ diff --git a/target/classes/org/example/service/impl/AIOBCallbackServiceImpl.class b/target/classes/org/example/service/impl/AIOBCallbackServiceImpl.class new file mode 100644 index 0000000..1a42c12 Binary files /dev/null and b/target/classes/org/example/service/impl/AIOBCallbackServiceImpl.class differ