32 changed files with 1227 additions and 48 deletions
@ -0,0 +1,28 @@ |
|||
package cn.nla.common.annotation; |
|||
|
|||
import cn.nla.common.config.EncryptIdValidator; |
|||
|
|||
import javax.validation.Constraint; |
|||
import javax.validation.Payload; |
|||
import java.lang.annotation.Documented; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.Target; |
|||
|
|||
import static java.lang.annotation.ElementType.*; |
|||
import static java.lang.annotation.RetentionPolicy.RUNTIME; |
|||
|
|||
/** |
|||
* 自定义加密校验 |
|||
*/ |
|||
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) |
|||
@Retention(RUNTIME) |
|||
@Documented |
|||
@Constraint(validatedBy = {EncryptIdValidator.class}) |
|||
public @interface EncryptId { |
|||
// 默认错误消息
|
|||
String message() default "加密id格式错误"; |
|||
// 分组
|
|||
Class<?>[] groups() default {}; |
|||
// 负载
|
|||
Class<? extends Payload>[] payload() default {}; |
|||
} |
@ -0,0 +1,20 @@ |
|||
package cn.nla.common.annotation; |
|||
|
|||
import java.lang.annotation.Documented; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.Target; |
|||
|
|||
import static java.lang.annotation.ElementType.METHOD; |
|||
import static java.lang.annotation.ElementType.TYPE; |
|||
import static java.lang.annotation.RetentionPolicy.RUNTIME; |
|||
|
|||
|
|||
/** |
|||
* 该注解作业于类或者方法上,将返回值对象包装返回体结构 |
|||
*/ |
|||
@Retention(RUNTIME) |
|||
@Target({TYPE, METHOD}) |
|||
@Documented |
|||
public @interface ResponseResult { |
|||
|
|||
} |
@ -0,0 +1,23 @@ |
|||
package cn.nla.common.config; |
|||
|
|||
import cn.nla.common.annotation.EncryptId; |
|||
import javax.validation.ConstraintValidator; |
|||
import javax.validation.ConstraintValidatorContext; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
/** |
|||
* 自定义加密校验的方法 |
|||
*/ |
|||
public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> { |
|||
// 字符串的长度: 32~256,以字母a到f或数字0到9开头。
|
|||
private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$"); |
|||
@Override |
|||
public boolean isValid(String value, ConstraintValidatorContext context) { |
|||
// 不为null才进行校验
|
|||
if (value != null) { |
|||
Matcher matcher = PATTERN.matcher(value); |
|||
return matcher.find(); |
|||
} |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,82 @@ |
|||
package cn.nla.common.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.http.HttpMethod; |
|||
import org.springframework.stereotype.Component; |
|||
import springfox.documentation.builders.*; |
|||
import springfox.documentation.oas.annotations.EnableOpenApi; |
|||
import springfox.documentation.schema.ScalarType; |
|||
import springfox.documentation.service.*; |
|||
import springfox.documentation.spi.DocumentationType; |
|||
import springfox.documentation.spring.web.plugins.Docket; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* Swagger配置 |
|||
**/ |
|||
@Data |
|||
@Component |
|||
@EnableOpenApi |
|||
public class SwaggerConfiguration { |
|||
|
|||
/** |
|||
* 对用户服务端的接口文档 |
|||
*/ |
|||
@Bean |
|||
public Docket userApiDoc() { |
|||
return new Docket(DocumentationType.OAS_30) // 版本3.0
|
|||
.groupName("用户端接口文档") |
|||
.pathMapping("/") |
|||
//定义是否开启Swagger,false是关闭,可以通过变量去控制,线上关闭
|
|||
.enable(true) |
|||
//配置文档的元信息
|
|||
.apiInfo(apiInfo()) |
|||
.select() |
|||
.apis(RequestHandlerSelectors.basePackage("cn.nla")) |
|||
//正则匹配请求路径,并分配到当前项目组
|
|||
.paths(PathSelectors.ant("/user/**")) |
|||
.build() |
|||
// 新版SwaggerUI3.0
|
|||
.globalRequestParameters(globalRequestParameters()) |
|||
.globalResponses(HttpMethod.GET, getGlobalResponseMessage()) |
|||
.globalResponses(HttpMethod.POST, getGlobalResponseMessage()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 接口基本信息配置 |
|||
*/ |
|||
private ApiInfo apiInfo() { |
|||
return new ApiInfoBuilder() |
|||
.title("电商平台") |
|||
.description("微服务接口文档") |
|||
.contact(new Contact("yuan", "http://localhost", "yuanjs625@163.com")) |
|||
.version("v1.0") |
|||
.build(); |
|||
} |
|||
|
|||
/** |
|||
* 配置全局通用参数 |
|||
*/ |
|||
private List<RequestParameter> globalRequestParameters() { |
|||
return List.of(new RequestParameterBuilder() |
|||
.name("token") |
|||
.description("登录令牌") |
|||
.in(ParameterType.HEADER) |
|||
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))) |
|||
.required(false) |
|||
.build()); |
|||
} |
|||
|
|||
/** |
|||
* 生成通用的响应信息 |
|||
*/ |
|||
private List<Response> getGlobalResponseMessage() { |
|||
return List.of(new ResponseBuilder() |
|||
.code("4xx") |
|||
.description("请求错误,根据code和msg检查") |
|||
.build()); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
package cn.nla.common.config; |
|||
|
|||
import cn.nla.common.interceptor.ResponseResultInterceptor; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
|||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
|||
import javax.annotation.Resource; |
|||
|
|||
/** |
|||
* 注册拦截器 |
|||
*/ |
|||
@Configuration |
|||
public class WebConfig implements WebMvcConfigurer { |
|||
@Resource |
|||
private ResponseResultInterceptor responseResultInterceptor; |
|||
@Override |
|||
public void addInterceptors(InterceptorRegistry registry) { |
|||
registry.addInterceptor(responseResultInterceptor); |
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
package cn.nla.common.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum BizCodeEnum { |
|||
|
|||
/** |
|||
* 通用操作码 |
|||
*/ |
|||
OPS_REPEAT(110001,"重复操作"), |
|||
/** |
|||
* 系统相关 |
|||
*/ |
|||
SYS_LOGIN(210001,"登录过期,请重新登录"), |
|||
SYS_NO_TOKEN(210002,"token不存在,请重新登录"), |
|||
SYS_NO_USER(210003,"loginUser对象为空"), |
|||
/** |
|||
* 购物车 |
|||
*/ |
|||
CART_FAIL(220001,"添加购物车失败"), |
|||
/** |
|||
*验证码 |
|||
*/ |
|||
CODE_TO_ERROR(240001,"接收号码不合规"), |
|||
CODE_LIMITED(240002,"验证码发送过快"), |
|||
CODE_ERROR(240003,"验证码错误"), |
|||
CODE_CAPTCHA_ERROR(240101,"图形验证码错误"), |
|||
|
|||
/** |
|||
* 账号 |
|||
*/ |
|||
ACCOUNT_REPEAT(250001,"账号已经存在"), |
|||
ACCOUNT_UNREGISTER(250002,"账号不存在"), |
|||
ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"), |
|||
ACCOUNT_UNLOGIN(250004,"账号未登录"), |
|||
/** |
|||
* 优惠券 |
|||
*/ |
|||
COUPON_CONDITION_ERROR(270001,"优惠券条件错误"), |
|||
COUPON_UNAVAILABLE(270002,"没有可用的优惠券"), |
|||
COUPON_NO_EXITS(270003,"优惠券不存在"), |
|||
COUPON_NO_STOCK(270005,"优惠券库存不足"), |
|||
COUPON_OUT_OF_LIMIT(270006,"优惠券领取超过限制次数"), |
|||
COUPON_OUT_OF_TIME(270407,"优惠券不在领取时间范围"), |
|||
COUPON_GET_FAIL(270407,"优惠券领取失败"), |
|||
COUPON_RECORD_LOCK_FAIL(270409,"优惠券锁定失败"), |
|||
/** |
|||
* 订单 |
|||
*/ |
|||
ORDER_CONFIRM_COUPON_FAIL(280001,"创建订单-优惠券使用失败,不满足价格条件"), |
|||
ORDER_CONFIRM_PRICE_FAIL(280002,"创建订单-验价失败"), |
|||
ORDER_CONFIRM_LOCK_PRODUCT_FAIL(280003,"创建订单-商品库存不足锁定失败"), |
|||
ORDER_CONFIRM_ADD_STOCK_TASK_FAIL(280004,"创建订单-新增商品库存锁定任务"), |
|||
ORDER_CONFIRM_TOKEN_NOT_EXIST(280008,"订单令牌缺少"), |
|||
ORDER_CONFIRM_TOKEN_EQUAL_FAIL(280009,"订单令牌不正确"), |
|||
ORDER_CONFIRM_NOT_EXIST(280010,"订单不存在"), |
|||
ORDER_CONFIRM_CART_ITEM_NOT_EXIST(280011,"购物车商品项不存在"), |
|||
/** |
|||
* 收货地址 |
|||
*/ |
|||
ADDRESS_ADD_FAIL(290001,"新增收货地址失败"), |
|||
ADDRESS_DEL_FAIL(290002,"删除收货地址失败"), |
|||
ADDRESS_NO_EXITS(290003,"地址不存在"), |
|||
/** |
|||
* 支付 |
|||
*/ |
|||
PAY_ORDER_FAIL(300001,"创建支付订单失败"), |
|||
PAY_ORDER_CALLBACK_SIGN_FAIL(300002,"支付订单回调验证签失败"), |
|||
PAY_ORDER_CALLBACK_NOT_SUCCESS(300003,"创建支付订单失败"), |
|||
PAY_ORDER_NOT_EXIST(300005,"订单不存在"), |
|||
PAY_ORDER_STATE_ERROR(300006,"订单状态不正常"), |
|||
PAY_ORDER_PAY_TIMEOUT(300007,"订单支付超时"), |
|||
/** |
|||
* 流控操作 |
|||
*/ |
|||
CONTROL_FLOW(500101,"限流控制"), |
|||
CONTROL_DEGRADE(500201,"降级控制"), |
|||
CONTROL_AUTH(500301,"认证控制"), |
|||
/** |
|||
* 文件相关 |
|||
*/ |
|||
FILE_UPLOAD_USER_IMG_FAIL(600101,"用户头像文件上传失败"); |
|||
private int code; |
|||
private String message; |
|||
} |
@ -0,0 +1,22 @@ |
|||
package cn.nla.common.exception; |
|||
|
|||
import cn.nla.common.enums.BizCodeEnum; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
@Getter |
|||
@Setter |
|||
public class BizException extends RuntimeException { |
|||
private int code; |
|||
private String msg; |
|||
public BizException(int code, String msg){ |
|||
super(msg); |
|||
this.code = code; |
|||
this.msg = msg; |
|||
} |
|||
public BizException(BizCodeEnum bizCodeEnum){ |
|||
super(bizCodeEnum.getMessage()); |
|||
this.code = bizCodeEnum.getCode(); |
|||
this.msg = bizCodeEnum.getMessage(); |
|||
} |
|||
} |
@ -0,0 +1,124 @@ |
|||
package cn.nla.common.exception; |
|||
|
|||
import cn.nla.common.annotation.ResponseResult; |
|||
import cn.nla.common.util.JsonData; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.core.MethodParameter; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.http.converter.HttpMessageConverter; |
|||
import org.springframework.http.converter.HttpMessageNotReadableException; |
|||
import org.springframework.http.server.ServerHttpRequest; |
|||
import org.springframework.http.server.ServerHttpResponse; |
|||
import org.springframework.validation.BindingResult; |
|||
import org.springframework.validation.FieldError; |
|||
import org.springframework.web.bind.MethodArgumentNotValidException; |
|||
import org.springframework.web.bind.MissingServletRequestParameterException; |
|||
import org.springframework.web.bind.annotation.ControllerAdvice; |
|||
import org.springframework.web.bind.annotation.ExceptionHandler; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.validation.ConstraintViolationException; |
|||
|
|||
@Slf4j |
|||
@ControllerAdvice |
|||
public class CustomExceptionHandler implements ResponseBodyAdvice<Object> { |
|||
@Override |
|||
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { |
|||
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
|||
if (sra == null) { |
|||
return false; |
|||
} |
|||
HttpServletRequest request = sra.getRequest(); |
|||
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute("RESPONSE_RESULT_ANN"); |
|||
return responseResultAnn != null; |
|||
} |
|||
|
|||
@Override |
|||
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, |
|||
MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, |
|||
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { |
|||
log.info("into ResponseResult handle ...."); |
|||
if (body instanceof JsonData) { |
|||
return body; |
|||
} |
|||
return JsonData.buildSuccess(body); |
|||
} |
|||
|
|||
|
|||
@ExceptionHandler(value = Exception.class) |
|||
@ResponseBody |
|||
public JsonData handle(Exception e) { |
|||
//是不是自定义异常
|
|||
if (e instanceof BizException) { |
|||
BizException bizException = (BizException) e; |
|||
log.error("[业务异常]", e); |
|||
return JsonData.buildCodeAndMsg(bizException.getCode(), bizException.getMsg()); |
|||
} else { |
|||
log.error("[系统异常]", e); |
|||
return JsonData.buildError("全局异常,未知错误"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 忽略参数异常处理器 |
|||
* |
|||
* @param e 忽略参数异常 |
|||
*/ |
|||
//@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|||
@ExceptionHandler(MissingServletRequestParameterException.class) |
|||
@ResponseBody |
|||
public JsonData parameterMissingExceptionHandler(MissingServletRequestParameterException e) { |
|||
log.error("参数异常", e); |
|||
return JsonData.buildError("请求参数[" + e.getParameterName() + "]不能为空"); |
|||
} |
|||
|
|||
/** |
|||
* 缺少请求体异常处理器 |
|||
* |
|||
* @param e 缺少请求体异常 |
|||
*/ |
|||
//@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|||
@ExceptionHandler(HttpMessageNotReadableException.class) |
|||
@ResponseBody |
|||
public JsonData parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) { |
|||
log.error("缺少请求体异常", e); |
|||
return JsonData.buildError("参数体不能为空"); |
|||
} |
|||
|
|||
/** |
|||
* 参数效验异常处理器 |
|||
* |
|||
* @param e 参数验证异常 |
|||
*/ |
|||
//@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|||
@ExceptionHandler(MethodArgumentNotValidException.class) |
|||
@ResponseBody |
|||
public JsonData parameterExceptionHandler(MethodArgumentNotValidException e) { |
|||
log.error("参数效验异常", e); |
|||
BindingResult bindingResult = e.getBindingResult(); |
|||
StringBuilder sb = new StringBuilder("校验失败: "); |
|||
for (FieldError fieldError : bindingResult.getFieldErrors()) { |
|||
// sb.append(fieldError.getDefaultMessage()).append(", ");
|
|||
sb.append("[").append(fieldError.getField()).append("]").append(fieldError.getDefaultMessage()).append(", "); |
|||
} |
|||
return JsonData.buildError(sb.toString()); |
|||
} |
|||
|
|||
/** |
|||
* 自定义校验 |
|||
* |
|||
* @param e 参数验证异常 |
|||
*/ |
|||
@ExceptionHandler({ConstraintViolationException.class}) |
|||
@ResponseBody |
|||
public JsonData handleConstraintViolationException(ConstraintViolationException e) { |
|||
log.error("自定义校验异常", e); |
|||
return JsonData.buildError(e.getMessage()); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
package cn.nla.common.interceptor; |
|||
|
|||
import cn.nla.common.annotation.ResponseResult; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.method.HandlerMethod; |
|||
import org.springframework.web.servlet.HandlerInterceptor; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.lang.reflect.Method; |
|||
|
|||
/** |
|||
* 请求拦截,封装响应对象 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class ResponseResultInterceptor implements HandlerInterceptor { |
|||
@Override |
|||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
|||
if (handler instanceof HandlerMethod) { |
|||
final HandlerMethod handlerMethod = (HandlerMethod) handler; |
|||
final Class<?> clazz = handlerMethod.getBeanType(); |
|||
final Method method = handlerMethod.getMethod(); |
|||
if (clazz.isAnnotationPresent(ResponseResult.class)) { |
|||
// 设置此请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口中进行解析
|
|||
request.setAttribute("RESPONSE_RESULT_ANN", clazz.getAnnotation(ResponseResult.class)); |
|||
} else if (method.isAnnotationPresent(ResponseResult.class)) { |
|||
// 设置此请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口中进行解析
|
|||
request.setAttribute("RESPONSE_RESULT_ANN", method.getAnnotation(ResponseResult.class)); |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
package cn.nla.common.util; |
|||
|
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public class CheckUtil { |
|||
/** |
|||
* 邮箱正则 |
|||
*/ |
|||
private static final Pattern MAIL_PATTERN = Pattern.compile("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"); |
|||
/** |
|||
* 手机号正则,暂时未用 |
|||
*/ |
|||
private static final Pattern PHONE_PATTERN = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"); |
|||
/** |
|||
* @param email 邮箱 |
|||
*/ |
|||
public static boolean isEmail(String email) { |
|||
if (null == email || "".equals(email)) { |
|||
return false; |
|||
} |
|||
Matcher m = MAIL_PATTERN.matcher(email); |
|||
return m.matches(); |
|||
} |
|||
/** |
|||
* @param phone 手机号 |
|||
*/ |
|||
public static boolean isPhone(String phone) { |
|||
if (null == phone || "".equals(phone)) { |
|||
return false; |
|||
} |
|||
Matcher m = PHONE_PATTERN.matcher(phone); |
|||
return m.matches(); |
|||
} |
|||
} |
@ -0,0 +1,92 @@ |
|||
package cn.nla.common.util; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import java.net.InetAddress; |
|||
import java.net.UnknownHostException; |
|||
import java.security.MessageDigest; |
|||
import java.util.Random; |
|||
|
|||
/** |
|||
* 工具类 |
|||
*/ |
|||
@Slf4j |
|||
public class CommonUtil { |
|||
|
|||
/** |
|||
* 获取请求的ip地址 |
|||
*/ |
|||
public static String getIpAddr(HttpServletRequest request) { |
|||
String ipAddress; |
|||
try { |
|||
ipAddress = request.getHeader("x-forwarded-for"); |
|||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { |
|||
ipAddress = request.getHeader("Proxy-Client-IP"); |
|||
} |
|||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { |
|||
ipAddress = request.getHeader("WL-Proxy-Client-IP"); |
|||
} |
|||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { |
|||
ipAddress = request.getRemoteAddr(); |
|||
if (ipAddress.equals("127.0.0.1")) { |
|||
// 根据网卡取本机配置的IP
|
|||
InetAddress inet = null; |
|||
try { |
|||
inet = InetAddress.getLocalHost(); |
|||
} catch (UnknownHostException e) { |
|||
log.error("getIpAddress error", e); |
|||
} |
|||
if (inet != null) { |
|||
ipAddress = inet.getHostAddress(); |
|||
} |
|||
} |
|||
} |
|||
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
|||
if (ipAddress != null && ipAddress.length() > 15) { |
|||
// "***.***.***.***".length()
|
|||
// = 15
|
|||
if (ipAddress.indexOf(",") > 0) { |
|||
ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.error(" getIpAddress Exception", e); |
|||
ipAddress = ""; |
|||
} |
|||
return ipAddress; |
|||
} |
|||
|
|||
/** |
|||
* MD5加密 |
|||
*/ |
|||
public static String MD5(String data) { |
|||
try { |
|||
MessageDigest md = MessageDigest.getInstance("MD5"); |
|||
byte[] array = md.digest(data.getBytes("UTF-8")); |
|||
StringBuilder sb = new StringBuilder(); |
|||
for (byte item : array) { |
|||
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3); |
|||
} |
|||
return sb.toString().toUpperCase(); |
|||
} catch (Exception e) { |
|||
log.error("MD5 error", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 获取验证码随机数 |
|||
* |
|||
* @param length 长度 |
|||
*/ |
|||
public static String getRandomCode(int length) { |
|||
String sources = "0123456789"; |
|||
Random random = new Random(); |
|||
StringBuilder sb = new StringBuilder(); |
|||
for (int j = 0; j < length; j++) { |
|||
sb.append(sources.charAt(random.nextInt(9))); |
|||
} |
|||
return sb.toString(); |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
package cn.nla.common.util; |
|||
|
|||
import cn.nla.common.enums.BizCodeEnum; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class JsonData { |
|||
|
|||
public static final String SUCCESS_MSG = "成功"; |
|||
public static final Integer SUCCESS = 0; |
|||
public static final Integer FAIL = -1; |
|||
/** |
|||
* 状态码 0 表示成功 |
|||
*/ |
|||
private Integer code; |
|||
/** |
|||
* 数据 |
|||
*/ |
|||
private Object data; |
|||
/** |
|||
* 描述 |
|||
*/ |
|||
private String msg; |
|||
/** |
|||
* 成功,不传入数据 |
|||
*/ |
|||
public static JsonData buildSuccess() { |
|||
return new JsonData(SUCCESS, null, SUCCESS_MSG); |
|||
} |
|||
/** |
|||
* 成功,传入数据 |
|||
*/ |
|||
public static JsonData buildSuccess(Object data) { |
|||
return new JsonData(SUCCESS, data, SUCCESS_MSG); |
|||
} |
|||
|
|||
/** |
|||
* 失败,传入描述信息 |
|||
*/ |
|||
public static JsonData buildError(String msg) { |
|||
return new JsonData(FAIL, null, msg); |
|||
} |
|||
/** |
|||
* 自定义状态码和错误信息 |
|||
*/ |
|||
public static JsonData buildCodeAndMsg(int code, String msg) { |
|||
return new JsonData(code, null, msg); |
|||
} |
|||
/** |
|||
* 传入枚举,返回信息 |
|||
*/ |
|||
public static JsonData buildResult(BizCodeEnum codeEnum) { |
|||
return JsonData.buildCodeAndMsg(codeEnum.getCode(), codeEnum.getMessage()); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
package cn.nla.user.config; |
|||
|
|||
import com.google.code.kaptcha.Constants; |
|||
import com.google.code.kaptcha.impl.DefaultKaptcha; |
|||
import com.google.code.kaptcha.util.Config; |
|||
import org.springframework.beans.factory.annotation.Qualifier; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
|
|||
import java.util.Properties; |
|||
|
|||
@Configuration |
|||
public class CaptchaConfig { |
|||
|
|||
/** |
|||
* 验证码配置 Kaptcha配置类名 |
|||
*/ |
|||
@Bean |
|||
@Qualifier("captchaProducer") |
|||
public DefaultKaptcha kaptcha() { |
|||
DefaultKaptcha kaptcha = new DefaultKaptcha(); |
|||
Properties properties = new Properties(); |
|||
// properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
|
|||
// properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "220,220,220");
|
|||
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "38,29,12");
|
|||
// properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "147");
|
|||
// properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "34");
|
|||
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "25");
|
|||
// properties.setProperty(Constants.KAPTCHA_SESSION_KEY, "code");
|
|||
//验证码个数
|
|||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); |
|||
// properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Courier");
|
|||
//字体间隔
|
|||
|
|||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "8"); |
|||
//干扰线颜色
|
|||
// properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white");
|
|||
//干扰实现类
|
|||
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); |
|||
//图片样式
|
|||
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple"); |
|||
//文字来源
|
|||
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789"); |
|||
Config config = new Config(properties); |
|||
kaptcha.setConfig(config); |
|||
return kaptcha; |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
package cn.nla.user.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.context.annotation.Configuration; |
|||
|
|||
@ConfigurationProperties(prefix = "aliyun.oss") |
|||
@Configuration |
|||
@Data |
|||
public class OSSConfig { |
|||
private String endpoint; |
|||
private String accessKeyId; |
|||
private String accessKeySecret; |
|||
private String bucketname; |
|||
} |
@ -1,21 +1,35 @@ |
|||
package cn.nla.user.controller; |
|||
|
|||
import cn.nla.common.annotation.ResponseResult; |
|||
|
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import cn.nla.user.service.AddressService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
/** |
|||
* <p> |
|||
* 电商-公司收发货地址表 前端控制器 |
|||
* </p> |
|||
* |
|||
* @author YJs |
|||
* @since 2024-07-29 |
|||
*/ |
|||
@Slf4j |
|||
@ResponseResult |
|||
@RestController |
|||
@RequestMapping("/addressDO") |
|||
public class AddressController { |
|||
@Resource |
|||
private AddressService addressService; |
|||
|
|||
@PostMapping("query") |
|||
@ResponseResult |
|||
public Object query(@RequestHeader Map<String, String> headers) { |
|||
log.info("请求参数:{}", headers.get("token")); |
|||
return List.of(); |
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,23 @@ |
|||
package cn.nla.user.controller; |
|||
|
|||
import cn.nla.common.util.JsonData; |
|||
import cn.nla.user.service.FileService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import javax.annotation.Resource; |
|||
|
|||
@RestController |
|||
@RequestMapping("/file/v1") |
|||
@Slf4j |
|||
public class FileController { |
|||
@Resource |
|||
private FileService fileService; |
|||
@PostMapping(value = "/uploadFile", name = "上传文件") |
|||
public JsonData uploadImage(MultipartFile file) { |
|||
return JsonData.buildSuccess(fileService.uploadUserHeadImg(file)); |
|||
} |
|||
} |
@ -0,0 +1,112 @@ |
|||
package cn.nla.user.controller; |
|||
|
|||
import cn.nla.common.enums.BizCodeEnum; |
|||
import cn.nla.common.util.CheckUtil; |
|||
import cn.nla.common.util.CommonUtil; |
|||
import cn.nla.common.util.JsonData; |
|||
import cn.nla.user.service.MailService; |
|||
import com.google.code.kaptcha.Producer; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.redis.core.StringRedisTemplate; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import javax.annotation.Resource; |
|||
import javax.imageio.ImageIO; |
|||
import javax.servlet.ServletOutputStream; |
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.awt.image.BufferedImage; |
|||
import java.io.IOException; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
@RestController |
|||
@RequestMapping("/user/v1") |
|||
@Slf4j |
|||
public class NotifyController { |
|||
@Resource |
|||
private Producer captchaProducer; |
|||
@Resource |
|||
private MailService mailService; |
|||
@Autowired |
|||
private StringRedisTemplate redisTemplate; |
|||
/** |
|||
* 图形验证码有效期10分钟 |
|||
*/ |
|||
private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000 * 10; |
|||
|
|||
/** |
|||
* 获取图形验证码 |
|||
* |
|||
* @param request 请求参数 |
|||
* @param response 响应 |
|||
*/ |
|||
@ApiOperation("获取图形验证码") |
|||
@GetMapping("captcha") |
|||
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) { |
|||
String captchaText = captchaProducer.createText(); |
|||
log.info("图形验证码:{}", captchaText); |
|||
redisTemplate.opsForValue().set(getCaptchaKey(request), captchaText, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS); |
|||
BufferedImage bufferedImage = captchaProducer.createImage(captchaText); |
|||
ServletOutputStream outputStream; |
|||
try { |
|||
outputStream = response.getOutputStream(); |
|||
ImageIO.write(bufferedImage, "jpg", outputStream); |
|||
outputStream.flush(); |
|||
outputStream.close(); |
|||
} catch (IOException e) { |
|||
log.error("获取图形验证码异常", e); |
|||
} |
|||
} |
|||
|
|||
|
|||
@ApiOperation("获取图形验证码") |
|||
@GetMapping("send") |
|||
public JsonData sendCode(String from, String to) { |
|||
String cacheKey = String.format(from + "_%s", to); |
|||
String cacheValue = redisTemplate.opsForValue().get(cacheKey); |
|||
//如果不为空,则判断是否60秒内重复发送
|
|||
if (StringUtils.isNotBlank(cacheValue)) { |
|||
long ttl = Long.parseLong(cacheValue.split("_")[1]); |
|||
//当前时间戳-验证码发送时间戳,如果小于60秒,则不给重复发送
|
|||
if (System.currentTimeMillis() - ttl < 1000 * 60) { |
|||
log.info("重复发送验证码,时间间隔:{} 秒", (System.currentTimeMillis() - ttl) / 1000); |
|||
return JsonData.buildResult(BizCodeEnum.CODE_LIMITED); |
|||
} |
|||
} |
|||
//拼接验证码: 验证码+时间戳: 2322_324243232424324
|
|||
String code = CommonUtil.getRandomCode(6); |
|||
String value = code + "_" + System.currentTimeMillis(); |
|||
redisTemplate.opsForValue().set(cacheKey, value, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS); |
|||
if (CheckUtil.isEmail(to)) { |
|||
//邮箱验证码
|
|||
mailService.sendMail(to, "电商验证码", code); |
|||
log.info("发送邮箱验证码[{}]成功!", code); |
|||
return JsonData.buildSuccess(); |
|||
} else if (CheckUtil.isPhone(to)) { |
|||
//短信验证码
|
|||
log.info("要发送短信验证码[{}]", code); |
|||
} |
|||
return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR); |
|||
} |
|||
|
|||
/** |
|||
* 获取缓存的key |
|||
*/ |
|||
private String getCaptchaKey(HttpServletRequest request) { |
|||
String ip = CommonUtil.getIpAddr(request); |
|||
String userAgent = request.getHeader("User-Agent"); |
|||
String key = "user-service:captcha:" + CommonUtil.MD5(ip + userAgent); |
|||
log.info("ip:{}", ip); |
|||
log.info("userAgent:{}", userAgent); |
|||
log.info("key:{}", key); |
|||
return key; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
package cn.nla.user.goup; |
|||
/** |
|||
* 保存 |
|||
*/ |
|||
public interface Save { |
|||
} |
@ -0,0 +1,7 @@ |
|||
package cn.nla.user.goup; |
|||
|
|||
/** |
|||
* 更新 |
|||
*/ |
|||
public interface Update { |
|||
} |
@ -0,0 +1,32 @@ |
|||
package cn.nla.user.model; |
|||
|
|||
import cn.nla.common.annotation.EncryptId; |
|||
import cn.nla.user.goup.Save; |
|||
import cn.nla.user.goup.Update; |
|||
import lombok.Data; |
|||
import org.hibernate.validator.constraints.Length; |
|||
import javax.validation.Valid; |
|||
import javax.validation.constraints.*; |
|||
import java.io.Serializable; |
|||
import java.util.List; |
|||
@Data |
|||
public class StudentBean implements Serializable { |
|||
@NotBlank(message = "用户名不能为空") |
|||
private String name; |
|||
@NotNull(groups = {Save.class, Update.class}) |
|||
@Length(min = 6, max = 20, groups = {Save.class, Update.class}) |
|||
private String account; |
|||
@Min(value = 18, message = "年龄不能小于18岁") |
|||
@NotNull(message = "年龄不能为空") |
|||
private Integer age; |
|||
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手机号格式错误") |
|||
private String phoneNum; |
|||
@Email(message = "邮箱格式错误") |
|||
private String email; |
|||
@Valid |
|||
@NotNull(message = "任课老师不能为空") |
|||
@Size(min = 1, message = "至少有一个老师") |
|||
private List<TeacherBean> teacherBeans; |
|||
@EncryptId(message = "密码格式错误") |
|||
private String password; |
|||
} |
@ -0,0 +1,12 @@ |
|||
package cn.nla.user.model; |
|||
|
|||
import lombok.Data; |
|||
import javax.validation.constraints.Min; |
|||
import javax.validation.constraints.NotEmpty; |
|||
@Data |
|||
public class TeacherBean { |
|||
@NotEmpty(message = "老师姓名不能为空") |
|||
private String teacherName; |
|||
@Min(value = 1, message = "学科类型从1开始计算") |
|||
private int type; |
|||
} |
@ -0,0 +1,8 @@ |
|||
package cn.nla.user.service; |
|||
|
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
public interface FileService { |
|||
|
|||
String uploadUserHeadImg(MultipartFile file); |
|||
} |
@ -0,0 +1,6 @@ |
|||
package cn.nla.user.service; |
|||
|
|||
public interface MailService { |
|||
|
|||
void sendMail(String to, String subject, String content); |
|||
} |
@ -0,0 +1,55 @@ |
|||
package cn.nla.user.service.impl; |
|||
|
|||
import cn.nla.user.config.OSSConfig; |
|||
import cn.nla.user.service.FileService; |
|||
import com.aliyun.oss.OSS; |
|||
import com.aliyun.oss.OSSClientBuilder; |
|||
import com.aliyun.oss.model.PutObjectResult; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.UUID; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class FileServiceImpl implements FileService { |
|||
@Resource |
|||
private OSSConfig ossConfig; |
|||
@Override |
|||
public String uploadUserHeadImg(MultipartFile file) { |
|||
String originalFilename = file.getOriginalFilename(); |
|||
//获取相关配置
|
|||
String bucketName = ossConfig.getBucketname(); |
|||
String endpoint = ossConfig.getEndpoint(); |
|||
String accessKeyId = ossConfig.getAccessKeyId(); |
|||
String accessKeySecret = ossConfig.getAccessKeySecret(); |
|||
//创建oss对象
|
|||
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); |
|||
//构建路径
|
|||
LocalDateTime ldt = LocalDateTime.now(); |
|||
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd"); |
|||
String folder = dtf.format(ldt); |
|||
String fileName = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); |
|||
assert originalFilename != null; |
|||
String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); |
|||
//在oss上创建文件夹test路径
|
|||
String newFileName = "test/" + folder + "/" + fileName + extension; |
|||
try { |
|||
PutObjectResult result = ossClient.putObject(bucketName, newFileName, file.getInputStream()); |
|||
//返回访问路径
|
|||
if (null != result) { |
|||
return "https://" + bucketName + "." + endpoint + "/" + newFileName; |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("上传头像失败", e); |
|||
} finally { |
|||
// 关闭OSS服务
|
|||
ossClient.shutdown(); |
|||
} |
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
package cn.nla.user.service.impl; |
|||
|
|||
import cn.nla.user.service.MailService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.mail.SimpleMailMessage; |
|||
import org.springframework.mail.javamail.JavaMailSender; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import javax.annotation.Resource; |
|||
|
|||
/** |
|||
* 发送邮件 |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class MailServiceImpl implements MailService { |
|||
/** |
|||
* springboot 提供的一个发送邮件的简单抽象,直接注入即可 |
|||
*/ |
|||
@Resource |
|||
private JavaMailSender mailSender; |
|||
@Value("${spring.mail.from}") |
|||
private String from; |
|||
/** |
|||
* 发送邮件 |
|||
* |
|||
* @param to 收件人 |
|||
* @param subject 主题 |
|||
* @param content 内容 |
|||
*/ |
|||
@Override |
|||
public void sendMail(String to, String subject, String content) { |
|||
//创建一个邮箱消息对象
|
|||
SimpleMailMessage message = new SimpleMailMessage(); |
|||
//配置邮箱发送人
|
|||
message.setFrom(from); |
|||
//邮件的收件人
|
|||
message.setTo(to); |
|||
//邮件的主题
|
|||
message.setSubject(subject); |
|||
//邮件的内容
|
|||
message.setText(content); |
|||
mailSender.send(message); |
|||
log.info("邮件发送成功:{}", message); |
|||
} |
|||
} |
@ -0,0 +1,71 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<configuration scan="true" scanPeriod="60 seconds" debug="false"> |
|||
<!-- 自定义配置: 日志存放路径 --> |
|||
<property name="log.path" value="./logs/nla-user-service"/> |
|||
<!-- 日志输出格式 --> |
|||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/> |
|||
<!-- 控制台输出 --> |
|||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<encoder> |
|||
<pattern>${log.pattern}</pattern> |
|||
</encoder> |
|||
</appender> |
|||
<!-- 系统日志输出 --> |
|||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${log.path}/info.log</file> |
|||
<!-- 循环政策:基于时间创建日志文件 --> |
|||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
|||
<!-- 日志文件名格式 --> |
|||
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern> |
|||
<!-- 日志最大的历史 60天 --> |
|||
<maxHistory>60</maxHistory> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>${log.pattern}</pattern> |
|||
</encoder> |
|||
<filter class="ch.qos.logback.classic.filter.LevelFilter"> |
|||
<!-- 过滤的级别 --> |
|||
<level>INFO</level> |
|||
<!-- 匹配时的操作:接收(记录) --> |
|||
<onMatch>ACCEPT</onMatch> |
|||
<!-- 不匹配时的操作:拒绝(不记录) --> |
|||
<onMismatch>DENY</onMismatch> |
|||
</filter> |
|||
</appender> |
|||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${log.path}/error.log</file> |
|||
<!-- 循环政策:基于时间创建日志文件 --> |
|||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
|||
<!-- 日志文件名格式 --> |
|||
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern> |
|||
<!-- 日志最大的历史 60天 --> |
|||
<maxHistory>60</maxHistory> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>${log.pattern}</pattern> |
|||
</encoder> |
|||
<filter class="ch.qos.logback.classic.filter.LevelFilter"> |
|||
<!-- 过滤的级别 --> |
|||
<level>ERROR</level> |
|||
<!-- 匹配时的操作:接收(记录) --> |
|||
<onMatch>ACCEPT</onMatch> |
|||
<!-- 不匹配时的操作:拒绝(不记录) --> |
|||
<onMismatch>DENY</onMismatch> |
|||
</filter> |
|||
</appender> |
|||
<!-- 自定义配置: 系统模块日志级别控制 --> |
|||
<logger name="cn.nla" level="info"/> |
|||
<!-- Spring日志级别控制 --> |
|||
<logger name="org.springframework" level="warn"/> |
|||
<logger name="io.lettuce.core.protocol" level="ERROR"/> |
|||
<!--自定义配置: mapper打印--> |
|||
<logger name="cn.nla.user.mapper" level="DEBUG"/> |
|||
<root level="info"> |
|||
<appender-ref ref="console"/> |
|||
</root> |
|||
<!--系统操作日志--> |
|||
<root level="info"> |
|||
<appender-ref ref="file_info"/> |
|||
<appender-ref ref="file_error"/> |
|||
</root> |
|||
</configuration> |
@ -0,0 +1,26 @@ |
|||
import cn.nla.user.UserApplication; |
|||
import cn.nla.user.model.AddressDO; |
|||
import cn.nla.user.service.AddressService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import net.minidev.json.JSONArray; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.test.context.junit4.SpringRunner; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.List; |
|||
|
|||
@RunWith(SpringRunner.class) |
|||
@SpringBootTest(classes = UserApplication.class) |
|||
@Slf4j |
|||
public class AddressTest { |
|||
|
|||
@Resource |
|||
private AddressService addressService; |
|||
@Test |
|||
public void testAddressDetail() { |
|||
List<AddressDO> list = addressService.list(); |
|||
log.info(JSONArray.toJSONString(list)); |
|||
} |
|||
} |
Loading…
Reference in new issue