提交 97fa2e0f 作者: 方治民

feat: I18n 模块完整实现、依赖升级、细节优化

上级 be944c68
...@@ -7,8 +7,8 @@ bootJar { ...@@ -7,8 +7,8 @@ bootJar {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// 💬 Mock/Test Env // 💬 Mock/Test Env
runtimeOnly 'com.h2database:h2' runtimeOnly 'com.h2database:h2'
// 💬 Prod/Dev Env // 💬 Prod/Dev Env
......
...@@ -13,10 +13,12 @@ import lombok.RequiredArgsConstructor; ...@@ -13,10 +13,12 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException; import org.apache.catalina.connector.ClientAbortException;
import org.aspectj.bridge.AbortException; import org.aspectj.bridge.AbortException;
import org.hibernate.validator.internal.engine.ConstraintViolationImpl;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError; import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
...@@ -49,10 +51,10 @@ public class GlobalExceptionHandler { ...@@ -49,10 +51,10 @@ public class GlobalExceptionHandler {
value = { BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class } value = { BindException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class }
) )
public Result<String> bindErrorHandler(Exception e) { public Result<String> bindErrorHandler(Exception e) {
String error = null; String details = null;
if (e instanceof ConstraintViolationException) { if (e instanceof ConstraintViolationException) {
error = ((ConstraintViolationException) e).getConstraintViolations().iterator().next().getMessage(); details = ((ConstraintViolationException) e).getConstraintViolations().iterator().next().getMessage();
} else { } else {
BindingResult result = null; BindingResult result = null;
if (e instanceof MethodArgumentNotValidException) { if (e instanceof MethodArgumentNotValidException) {
...@@ -62,12 +64,26 @@ public class GlobalExceptionHandler { ...@@ -62,12 +64,26 @@ public class GlobalExceptionHandler {
} }
if (result != null) { if (result != null) {
ObjectError next = result.getAllErrors().iterator().next(); ObjectError error = result.getAllErrors().iterator().next();
error = i18n.get(next); if (error instanceof FieldError fieldError) {
// TODO: 可以优化成提取 @ApiModelProperty value 中文描述
// 构建明确的字段错误提示, 例如: id 不能为 null, 如果自己填写了 message 则不追加 field 字段前缀
ConstraintViolationImpl<?> violation = error.unwrap(ConstraintViolationImpl.class);
String template = violation.getMessageTemplate();
String prefix = "";
// 如果是模板字符串, 则在消息前添加字段提示
if (template.startsWith("{") && template.endsWith("}")) {
prefix = fieldError.getField() + " ";
}
details = prefix + i18n.get(fieldError);
} else {
details = i18n.get(error);
}
} }
} }
return Result.no(Status.EXPECTATION_FAILED, error); return Result.no(Status.EXPECTATION_FAILED, details);
} }
/** /**
...@@ -105,7 +121,7 @@ public class GlobalExceptionHandler { ...@@ -105,7 +121,7 @@ public class GlobalExceptionHandler {
*/ */
@ExceptionHandler(FailStatusException.class) @ExceptionHandler(FailStatusException.class)
public Result<String> failStatusExceptionHandler(FailStatusException e) { public Result<String> failStatusExceptionHandler(FailStatusException e) {
return Result.no(e.getStatus(), i18n.get(e.getMessage(), e.getStatus().getReasonPhrase())); return Result.no(e.getStatus(), e.getMessage());
} }
/** /**
......
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
package com.yiring.app.constant; package com.yiring.app.constant;
import com.yiring.app.exception.CodeException; import com.yiring.app.exception.CodeException;
import com.yiring.common.core.I18n;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import org.jetbrains.annotations.PropertyKey;
/** /**
* 业务状态码(TODO: 结合 spring message i18n) * 业务状态码
* eg: <code>throw new CodeException(Code.FAIL)</code> * eg: <code>throw Code.$100000.exception()</code>
* *
* @author Jim * @author Jim
* @version 0.1 * @version 0.1
...@@ -16,18 +18,24 @@ import io.swagger.annotations.ApiModel; ...@@ -16,18 +18,24 @@ import io.swagger.annotations.ApiModel;
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
@ApiModel("业务状态码") @ApiModel("业务状态码")
public enum Code { public enum Code {
SUCCESS(0, "成功"), SUCCESS(0, "Code.0"),
// =============================================================
// TODO: 扩展业务状态 // TODO: 扩展业务状态
// eg: // eg:
// 10001: 用户被禁止登录 // 100001: 用户被禁止登录
FAIL(10000, "测试错误"); $100000(100000, "Code.100000"),
$100001(100001, "Code.100001"),
$100002(100002, "Code.100002")
// =============================================================
;
private final int value; private final int value;
private final String reasonPhrase; private final String reasonPhrase;
Code(int value, String reasonPhrase) { Code(int value, @PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String reasonPhrase) {
this.value = value; this.value = value;
this.reasonPhrase = reasonPhrase; this.reasonPhrase = reasonPhrase;
} }
......
...@@ -49,6 +49,9 @@ public class UserExtension extends BasicEntity implements Serializable { ...@@ -49,6 +49,9 @@ public class UserExtension extends BasicEntity implements Serializable {
@Comment("年龄") @Comment("年龄")
Integer age; Integer age;
@Comment("简介")
String introduction;
public UserExtension(User user) { public UserExtension(User user) {
this.user = user; this.user = user;
} }
......
...@@ -122,9 +122,9 @@ public class UploadProcessServiceImpl implements UploadProcessService { ...@@ -122,9 +122,9 @@ public class UploadProcessServiceImpl implements UploadProcessService {
int flag = 0; int flag = 0;
Frame frame = null; Frame frame = null;
while (flag <= ftp) { while (flag <= ftp) {
//获取帧 // 获取帧
frame = grabber.grabImage(); frame = grabber.grabImage();
//过滤前3帧,避免出现全黑图片 // 过滤前3帧,避免出现全黑图片
if ((flag > 3) && (frame != null)) { if ((flag > 3) && (frame != null)) {
break; break;
} }
......
...@@ -10,6 +10,7 @@ import com.yiring.app.vo.user.UserExtensionVo; ...@@ -10,6 +10,7 @@ import com.yiring.app.vo.user.UserExtensionVo;
import com.yiring.auth.annotation.AuthIgnore; import com.yiring.auth.annotation.AuthIgnore;
import com.yiring.auth.domain.user.User; import com.yiring.auth.domain.user.User;
import com.yiring.auth.util.Auths; import com.yiring.auth.util.Auths;
import com.yiring.common.core.I18n;
import com.yiring.common.core.Result; import com.yiring.common.core.Result;
import com.yiring.common.core.Status; import com.yiring.common.core.Status;
import com.yiring.common.param.PageParam; import com.yiring.common.param.PageParam;
...@@ -35,6 +36,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -35,6 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
/** /**
* 示例接口 * 示例接口
*
* @author Jim * @author Jim
*/ */
...@@ -48,14 +50,13 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -48,14 +50,13 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ExampleController { public class ExampleController {
final I18n i18n;
final Auths auths; final Auths auths;
final UserExtensionRepository userExtensionRepository; final UserExtensionRepository userExtensionRepository;
String text = "😎 Hello World";
@GetMapping @GetMapping
public Result<String> hello() { public Result<String> hello() {
return Result.ok(text); return Result.ok("example.hello");
} }
/** /**
...@@ -63,7 +64,7 @@ public class ExampleController { ...@@ -63,7 +64,7 @@ public class ExampleController {
*/ */
@GetMapping("fail") @GetMapping("fail")
public Result<String> fail() { public Result<String> fail() {
throw Code.FAIL.exception(); throw Code.$100000.exception();
} }
@SaCheckLogin @SaCheckLogin
...@@ -71,6 +72,7 @@ public class ExampleController { ...@@ -71,6 +72,7 @@ public class ExampleController {
public Result<PageVo<String>> page(@Validated PageParam pageParam) { public Result<PageVo<String>> page(@Validated PageParam pageParam) {
log.info("PageParam: {}", pageParam); log.info("PageParam: {}", pageParam);
String text = i18n.get("example.hello");
List<String> data = Arrays.asList(text.split(" ")); List<String> data = Arrays.asList(text.split(" "));
PageVo<String> vo = PageVo.build(data, data.size()); PageVo<String> vo = PageVo.build(data, data.size());
return Result.ok(vo); return Result.ok(vo);
......
...@@ -8,12 +8,13 @@ server: ...@@ -8,12 +8,13 @@ server:
spring: spring:
application: application:
name: "basic-api-app" name: "basic-api-app"
messages:
basename: i18n/messages
servlet: servlet:
multipart: multipart:
max-file-size: 50MB max-file-size: 50MB
max-request-size: 100MB max-request-size: 100MB
messages:
basename: i18n/status,i18n/code,i18n/messages
always-use-message-format: true
profiles: profiles:
include: auth, conf-patch include: auth, conf-patch
active: dev-postgresql active: dev-postgresql
......
# \u81EA\u5B9A\u4E49\u63D0\u793A\u6D88\u606F
# example.hello=\uD83D\uDE0E Hello World
example.hello=\uD83D\uDE0E Hello World
# \u4E1A\u52A1\u72B6\u6001\u7801\u9519\u8BEF\u6D88\u606F
Code.0=OK
Code.100000=\u7528\u6237\u540D\u5DF2\u5B58\u5728
Code.100001=\u624B\u673A\u53F7\u5DF2\u5B58\u5728
Code.100002=\u90AE\u7BB1\u5DF2\u5B58\u5728
Code.100003=\u8D26\u53F7\u5BC6\u7801\u9519\u8BEF
Code.100004=\u7528\u6237\u88AB\u7981\u7528, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100005=\u7528\u6237\u88AB\u7981\u6B62\u767B\u5F55, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
common.result.success = \u6210\u529F
upload.filename.null = \u4E0A\u4F20\u6587\u4EF6\u6CA1\u6709\u6587\u4EF6\u540D
NotEmpty.downloadParam.object = \u6587\u4EF6\u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A
# \u81EA\u5B9A\u4E49\u63D0\u793A\u6D88\u606F
# example.hello=\uD83D\uDE0E Hello World
example.hello=\uD83D\uDE0E Hello World
# \u4E1A\u52A1\u72B6\u6001\u7801\u9519\u8BEF\u6D88\u606F
Code.0=OK
Code.100000=\u7528\u6237\u540D\u5DF2\u5B58\u5728
Code.100001=\u624B\u673A\u53F7\u5DF2\u5B58\u5728
Code.100002=\u90AE\u7BB1\u5DF2\u5B58\u5728
Code.100003=\u8D26\u53F7\u5BC6\u7801\u9519\u8BEF
Code.100004=\u7528\u6237\u88AB\u7981\u7528, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100005=\u7528\u6237\u88AB\u7981\u6B62\u767B\u5F55, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
javax.validation.constraints.AssertFalse.message=\u53EA\u80FD\u4E3A false
javax.validation.constraints.AssertTrue.message=\u53EA\u80FD\u4E3A true
javax.validation.constraints.DecimalMax.message=\u5FC5\u987B\u5C0F\u4E8E${inclusive == true ? '\u6216\u7B49\u4E8E' : ''}{value}
javax.validation.constraints.DecimalMin.message=\u5FC5\u987B\u5927\u4E8E${inclusive == true ? '\u6216\u7B49\u4E8E' : ''}{value}
javax.validation.constraints.Digits.message=\u6570\u5B57\u7684\u503C\u8D85\u51FA\u4E86\u5141\u8BB8\u8303\u56F4(\u53EA\u5141\u8BB8\u5728{integer}\u4F4D\u6574\u6570\u548C{fraction}\u4F4D\u5C0F\u6570\u8303\u56F4\u5185)
javax.validation.constraints.Email.message=\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
javax.validation.constraints.Future.message=\u9700\u8981\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4
javax.validation.constraints.FutureOrPresent.message=\u9700\u8981\u662F\u4E00\u4E2A\u5C06\u6765\u6216\u73B0\u5728\u7684\u65F6\u95F4
javax.validation.constraints.Max.message=\u6700\u5927\u4E0D\u80FD\u8D85\u8FC7{value}
javax.validation.constraints.Min.message=\u6700\u5C0F\u4E0D\u80FD\u5C0F\u4E8E{value}
javax.validation.constraints.Negative.message=\u5FC5\u987B\u662F\u8D1F\u6570
javax.validation.constraints.NegativeOrZero.message=\u5FC5\u987B\u662F\u8D1F\u6570\u6216\u96F6
javax.validation.constraints.NotBlank.message=\u4E0D\u80FD\u4E3A\u7A7A
javax.validation.constraints.NotEmpty.message=\u4E0D\u80FD\u4E3A\u7A7A
javax.validation.constraints.NotNull.message=\u4E0D\u80FD\u4E3A null
javax.validation.constraints.Null.message=\u5FC5\u987B\u4E3A null
javax.validation.constraints.Past.message=\u9700\u8981\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65F6\u95F4
javax.validation.constraints.PastOrPresent.message=\u9700\u8981\u662F\u4E00\u4E2A\u8FC7\u53BB\u6216\u73B0\u5728\u7684\u65F6\u95F4
javax.validation.constraints.Pattern.message=\u9700\u8981\u5339\u914D\u6B63\u5219\u8868\u8FBE\u5F0F"{regexp}"
javax.validation.constraints.Positive.message=\u5FC5\u987B\u662F\u6B63\u6570
javax.validation.constraints.PositiveOrZero.message=\u5FC5\u987B\u662F\u6B63\u6570\u6216\u96F6
javax.validation.constraints.Size.message=\u4E2A\u6570\u5FC5\u987B\u5728{min}\u548C{max}\u4E4B\u95F4
org.hibernate.validator.constraints.CreditCardNumber.message=\u4E0D\u5408\u6CD5\u7684\u4FE1\u7528\u5361\u53F7\u7801
org.hibernate.validator.constraints.Currency.message=\u4E0D\u5408\u6CD5\u7684\u8D27\u5E01 (\u5FC5\u987B\u662F{value}\u5176\u4E2D\u4E4B\u4E00)
org.hibernate.validator.constraints.EAN.message=\u4E0D\u5408\u6CD5\u7684{type}\u6761\u5F62\u7801
org.hibernate.validator.constraints.Email.message=\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
org.hibernate.validator.constraints.Length.message=\u957F\u5EA6\u9700\u8981\u5728{min}\u548C{max}\u4E4B\u95F4
org.hibernate.validator.constraints.CodePointLength.message=\u957F\u5EA6\u9700\u8981\u5728{min}\u548C{max}\u4E4B\u95F4
org.hibernate.validator.constraints.LuhnCheck.message=${validatedValue}\u7684\u6821\u9A8C\u7801\u4E0D\u5408\u6CD5, Luhn\u6A2110\u6821\u9A8C\u548C\u4E0D\u5339\u914D
org.hibernate.validator.constraints.Mod10Check.message=${validatedValue}\u7684\u6821\u9A8C\u7801\u4E0D\u5408\u6CD5, \u6A2110\u6821\u9A8C\u548C\u4E0D\u5339\u914D
org.hibernate.validator.constraints.Mod11Check.message=${validatedValue}\u7684\u6821\u9A8C\u7801\u4E0D\u5408\u6CD5, \u6A2111\u6821\u9A8C\u548C\u4E0D\u5339\u914D
org.hibernate.validator.constraints.ModCheck.message=${validatedValue}\u7684\u6821\u9A8C\u7801\u4E0D\u5408\u6CD5, {modType}\u6821\u9A8C\u548C\u4E0D\u5339\u914D
org.hibernate.validator.constraints.NotBlank.message=\u4E0D\u80FD\u4E3A\u7A7A
org.hibernate.validator.constraints.NotEmpty.message=\u4E0D\u80FD\u4E3A\u7A7A
org.hibernate.validator.constraints.ParametersScriptAssert.message=\u6267\u884C\u811A\u672C\u8868\u8FBE\u5F0F"{script}"\u6CA1\u6709\u8FD4\u56DE\u671F\u671B\u7ED3\u679C
org.hibernate.validator.constraints.Range.message=\u9700\u8981\u5728{min}\u548C{max}\u4E4B\u95F4
org.hibernate.validator.constraints.ScriptAssert.message=\u6267\u884C\u811A\u672C\u8868\u8FBE\u5F0F"{script}"\u6CA1\u6709\u8FD4\u56DE\u671F\u671B\u7ED3\u679C
org.hibernate.validator.constraints.URL.message=\u9700\u8981\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL
org.hibernate.validator.constraints.time.DurationMax.message=\u5FC5\u987B\u5C0F\u4E8E${inclusive == true ? '\u6216\u7B49\u4E8E' : ''}${days == 0 ? '' : days += '\u5929'}${hours == 0 ? '' : hours += '\u5C0F\u65F6'}${minutes == 0 ? '' : minutes += '\u5206\u949F'}${seconds == 0 ? '' : seconds += '\u79D2'}${millis == 0 ? '' : millis += '\u6BEB\u79D2'}${nanos == 0 ? '' : nanos += '\u7EB3\u79D2'}
org.hibernate.validator.constraints.time.DurationMin.message=\u5FC5\u987B\u5927\u4E8E${inclusive == true ? '\u6216\u7B49\u4E8E' : ''}${days == 0 ? '' : days += '\u5929'}${hours == 0 ? '' : hours += '\u5C0F\u65F6'}${minutes == 0 ? '' : minutes += '\u5206\u949F'}${seconds == 0 ? '' : seconds += '\u79D2'}${millis == 0 ? '' : millis += '\u6BEB\u79D2'}${nanos == 0 ? '' : nanos += '\u7EB3\u79D2'}
...@@ -57,6 +57,9 @@ public class User extends BasicEntity implements Serializable { ...@@ -57,6 +57,9 @@ public class User extends BasicEntity implements Serializable {
@Serial @Serial
private static final long serialVersionUID = -5787847701210907511L; private static final long serialVersionUID = -5787847701210907511L;
@Comment("头像")
String avatar;
@Comment("真实姓名") @Comment("真实姓名")
String realName; String realName;
...@@ -72,12 +75,6 @@ public class User extends BasicEntity implements Serializable { ...@@ -72,12 +75,6 @@ public class User extends BasicEntity implements Serializable {
@Comment("密码") @Comment("密码")
String password; String password;
@Comment("简介")
String introduction;
@Comment("头像")
String avatar;
@Comment("是否启用") @Comment("是否启用")
Boolean enabled; Boolean enabled;
......
...@@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@Validated @Validated
@SuppressWarnings({ "deprecation" }) @SuppressWarnings({ "deprecation", "all" })
@ApiSupport(order = -99) @ApiSupport(order = -99)
@Api(tags = "身份认证", description = "Auth") @Api(tags = "身份认证", description = "Auth")
@RestController @RestController
...@@ -53,27 +53,26 @@ public class AuthController { ...@@ -53,27 +53,26 @@ public class AuthController {
// 检查用户名是否存在 // 检查用户名是否存在
long count = userRepository.count(Example.of(User.builder().username(param.getUsername()).build())); long count = userRepository.count(Example.of(User.builder().username(param.getUsername()).build()));
if (count > 0) { if (count > 0) {
return Result.no(Status.BAD_REQUEST, "用户名已存在"); throw Status.BAD_REQUEST.exception("Code.100000");
} }
// 检查手机号是否存在 // 检查手机号是否存在
count = userRepository.count(Example.of(User.builder().mobile(param.getMobile()).build())); count = userRepository.count(Example.of(User.builder().mobile(param.getMobile()).build()));
if (count > 0) { if (count > 0) {
return Result.no(Status.BAD_REQUEST, "手机号已存在"); throw Status.BAD_REQUEST.exception("Code.100001");
} }
// 检查邮箱是否存在 // 检查邮箱是否存在
if (StrUtil.isNotBlank(param.getEmail())) { if (StrUtil.isNotBlank(param.getEmail())) {
count = userRepository.count(Example.of(User.builder().email(param.getEmail()).build())); count = userRepository.count(Example.of(User.builder().email(param.getEmail()).build()));
if (count > 0) { if (count > 0) {
return Result.no(Status.BAD_REQUEST, "邮箱已存在"); throw Status.BAD_REQUEST.exception("Code.100002");
} }
} }
// 构建用户信息写入数据库 // 构建用户信息写入数据库
User user = User User user = User
.builder() .builder()
.introduction(param.getIntroduction())
.avatar(param.getAvatar()) .avatar(param.getAvatar())
.mobile(param.getMobile()) .mobile(param.getMobile())
.realName(param.getRealName()) .realName(param.getRealName())
...@@ -89,38 +88,36 @@ public class AuthController { ...@@ -89,38 +88,36 @@ public class AuthController {
@ApiOperation(value = "登录") @ApiOperation(value = "登录")
@PostMapping("login") @PostMapping("login")
public Result<LoginVo> login(@Valid LoginParam param, HttpServletRequest request) { public Result<LoginVo> login(@Valid LoginParam param, HttpServletRequest request) {
String details = "账号密码错误";
// 查询用户信息是否匹配 // 查询用户信息是否匹配
User user = userRepository.findByAccount(param.getAccount()); User user = userRepository.findByAccount(param.getAccount());
if (user == null) { if (user == null) {
return Result.no(Status.BAD_REQUEST, details); throw Status.BAD_REQUEST.exception("Code.100003");
} }
// 检查密码 // 检查密码
String cps = SaSecureUtil.sha256(param.getPassword()); String cps = SaSecureUtil.sha256(param.getPassword());
if (!cps.equals(user.getPassword())) { if (!cps.equals(user.getPassword())) {
return Result.no(Status.BAD_REQUEST, details); throw Status.BAD_REQUEST.exception("Code.100003");
} }
// 检查用户是否已被删除 // 检查用户是否已被删除
if (user.getDeleteTime() != null) { if (user.getDeleteTime() != null) {
return Result.no(Status.FORBIDDEN, "用户被禁用, 请联系管理员"); throw Status.BAD_REQUEST.exception("Code.100004");
} }
// 检查用户是否被允许登录 // 检查用户是否被允许登录
if (!Boolean.TRUE.equals(user.getEnabled())) { if (!Boolean.TRUE.equals(user.getEnabled())) {
return Result.no(Status.FORBIDDEN, "用户被禁止登录, 请联系管理员"); throw Status.BAD_REQUEST.exception("Code.100005");
} }
// 登录
StpUtil.login(user.getId());
// 更新用户信息 // 更新用户信息
user.setLastLoginIp(Commons.getClientIpAddress(request)); user.setLastLoginIp(Commons.getClientIpAddress(request));
user.setLastLoginTime(LocalDateTime.now()); user.setLastLoginTime(LocalDateTime.now());
userRepository.saveAndFlush(user); userRepository.saveAndFlush(user);
// 登录
StpUtil.login(user.getId());
// 构建用户所需信息 // 构建用户所需信息
LoginVo vo = LoginVo.builder().userId(user.getId()).token(StpUtil.getTokenValue()).build(); LoginVo vo = LoginVo.builder().userId(user.getId()).token(StpUtil.getTokenValue()).build();
return Result.ok(vo); return Result.ok(vo);
......
...@@ -52,7 +52,6 @@ public class UserViewController { ...@@ -52,7 +52,6 @@ public class UserViewController {
.username(user.getUsername()) .username(user.getUsername())
.realName(user.getRealName()) .realName(user.getRealName())
.avatar(user.getAvatar()) .avatar(user.getAvatar())
.desc(user.getIntroduction())
.roles(Permissions.toRoleVos(user.getRoles())) .roles(Permissions.toRoleVos(user.getRoles()))
.build(); .build();
return Result.ok(userInfoVo); return Result.ok(userInfoVo);
......
dependencies { dependencies {
implementation project(":basic-common:util") implementation project(":basic-common:util")
implementation project(":basic-common:i18n")
implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// 本地依赖 // 本地依赖
implementation fileTree(dir: project.rootDir.getPath() + '\\libs', includes: ['*jar']) implementation fileTree(dir: project.rootDir.getPath() + '\\libs', includes: ['*jar'])
...@@ -31,4 +32,6 @@ dependencies { ...@@ -31,4 +32,6 @@ dependencies {
exclude group: 'org.locationtech.jts' exclude group: 'org.locationtech.jts'
} }
// https://mvnrepository.com/artifact/org.jetbrains/annotations
implementation "org.jetbrains:annotations:${jetbrainsAnnotationsVersion}"
} }
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.common.core; package com.yiring.common.core;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
...@@ -11,6 +12,7 @@ import lombok.Builder; ...@@ -11,6 +12,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.PropertyKey;
/** /**
* 标准的响应对象(所有的接口响应内容格式都应该是一致的) * 标准的响应对象(所有的接口响应内容格式都应该是一致的)
...@@ -33,6 +35,15 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -33,6 +35,15 @@ public class Result<T extends Serializable> implements Serializable {
private static final long serialVersionUID = -4802543396830024571L; private static final long serialVersionUID = -4802543396830024571L;
/** /**
* 注入 I18n
*/
protected static final I18n i18n;
static {
i18n = SpringUtil.getBean(I18n.class);
}
/**
* 接口响应时间 * 接口响应时间
*/ */
@ApiModelProperty(value = "响应时间", example = "2021-01-01 00:00:00") @ApiModelProperty(value = "响应时间", example = "2021-01-01 00:00:00")
...@@ -87,7 +98,25 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -87,7 +98,25 @@ public class Result<T extends Serializable> implements Serializable {
* @see com.yiring.common.core.Status * @see com.yiring.common.core.Status
*/ */
public static <T extends Serializable> Result<T> ok() { public static <T extends Serializable> Result<T> ok() {
return (Result<T>) Result.builder().status(Status.OK.value()).message(Status.OK.getReasonPhrase()).build(); return (Result<T>) Result.builder().status(Status.OK.value()).message(t(Status.OK.getReasonPhrase())).build();
}
/**
* 返回成功响应内容
*
* @param body {@link String} {@link I18n}
* @return Result
* @see com.yiring.common.core.Status
*/
public static <T extends Serializable> Result<T> ok(
@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String body
) {
return (Result<T>) Result
.builder()
.status(Status.OK.value())
.message(t(Status.OK.getReasonPhrase()))
.body(t(body))
.build();
} }
/** /**
...@@ -100,7 +129,7 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -100,7 +129,7 @@ public class Result<T extends Serializable> implements Serializable {
return (Result<T>) Result return (Result<T>) Result
.builder() .builder()
.status(Status.OK.value()) .status(Status.OK.value())
.message(Status.OK.getReasonPhrase()) .message(t(Status.OK.getReasonPhrase()))
.body(body) .body(body)
.build(); .build();
} }
...@@ -121,7 +150,9 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -121,7 +150,9 @@ public class Result<T extends Serializable> implements Serializable {
* @return Result * @return Result
* @see Status#BAD_REQUEST * @see Status#BAD_REQUEST
*/ */
public static <T extends Serializable> Result<T> no(String details) { public static <T extends Serializable> Result<T> no(
@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String details
) {
return no(Status.BAD_REQUEST, details); return no(Status.BAD_REQUEST, details);
} }
...@@ -141,7 +172,10 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -141,7 +172,10 @@ public class Result<T extends Serializable> implements Serializable {
* @return Result * @return Result
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Status status, String details) { public static <T extends Serializable> Result<T> no(
Status status,
@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String details
) {
return no(status, null, details, null); return no(status, null, details, null);
} }
...@@ -161,13 +195,18 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -161,13 +195,18 @@ public class Result<T extends Serializable> implements Serializable {
* @return Result * @return Result
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Status status, Integer code, String details, Throwable error) { public static <T extends Serializable> Result<T> no(
Status status,
Integer code,
@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String details,
Throwable error
) {
Result<T> result = (Result<T>) Result Result<T> result = (Result<T>) Result
.builder() .builder()
.status(status.value()) .status(status.value())
.message(status.getReasonPhrase()) .message(t(status.getReasonPhrase()))
.code(code) .code(code)
.details(details) .details(t(details))
.build(); .build();
if (error != null) { if (error != null) {
...@@ -176,4 +215,18 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -176,4 +215,18 @@ public class Result<T extends Serializable> implements Serializable {
return result; return result;
} }
/**
* i18n 默认值获取简单包装
*
* @param message 文本消息
* @return i18n 翻译结果文本消息
*/
public static String t(String message) {
if (message == null) {
return null;
}
return i18n.get(message, message);
}
} }
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
package com.yiring.common.core; package com.yiring.common.core;
import com.yiring.common.exception.FailStatusException; import com.yiring.common.exception.FailStatusException;
import org.jetbrains.annotations.PropertyKey;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* API 响应状态码 * API 响应状态码
* 包含系统和业务两个维度 * 包含系统和业务两个维度
*
* @author ifzm * @author ifzm
*/ */
...@@ -15,73 +17,73 @@ public enum Status { ...@@ -15,73 +17,73 @@ public enum Status {
/** /**
* 成功 * 成功
*/ */
OK(200, "OK"), OK(200, "Status.OK"),
/** /**
* 用户认证失败 * 用户认证失败
*/ */
NON_AUTHORITATIVE_INFORMATION(203, "认证失败"), NON_AUTHORITATIVE_INFORMATION(203, "Status.NON_AUTHORITATIVE_INFORMATION"),
/** /**
* 失败的请求,通常是一些验证错误 * 失败的请求,通常是一些验证错误
*/ */
BAD_REQUEST(400, "FAIL"), BAD_REQUEST(400, "Status.BAD_REQUEST"),
/** /**
* 鉴权失败 * 鉴权失败
*/ */
UNAUTHORIZED(401, "凭证过期"), UNAUTHORIZED(401, "Status.UNAUTHORIZED"),
/** /**
* Token 错误/失效 * Token 错误/失效
*/ */
FORBIDDEN(403, "禁止访问"), FORBIDDEN(403, "Status.FORBIDDEN"),
/** /**
* 找不到资源 * 找不到资源
*/ */
NOT_FOUND(404, "Not Found"), NOT_FOUND(404, "Status.NOT_FOUND"),
/** /**
* 不支持的请求类型 * 不支持的请求类型
*/ */
METHOD_NOT_ALLOWED(405, "不支持的请求类型"), METHOD_NOT_ALLOWED(405, "Status.METHOD_NOT_ALLOWED"),
/** /**
* 参数校验失败 * 参数校验失败
*/ */
EXPECTATION_FAILED(417, "无效参数"), EXPECTATION_FAILED(417, "Status.EXPECTATION_FAILED"),
/** /**
* 服务器错误 * 服务器错误
*/ */
INTERNAL_SERVER_ERROR(500, "服务器错误"), INTERNAL_SERVER_ERROR(500, "Status.INTERNAL_SERVER_ERROR"),
/** /**
* 未知错误 * 未知错误
*/ */
UNKNOWN_ERROR(500, "未知错误"), UNKNOWN_ERROR(500, "Status.UNKNOWN_ERROR"),
/** /**
* API 未实现 * API 未实现
*/ */
NOT_IMPLEMENTED(501, "API 未实现"), NOT_IMPLEMENTED(501, "Status.NOT_IMPLEMENTED"),
/** /**
* 服务异常(网关提醒) * 服务异常(网关提醒)
*/ */
BAD_GATEWAY(502, "Bad Gateway"), BAD_GATEWAY(502, "Status.BAD_GATEWAY"),
/** /**
* 服务暂停(网关提醒) * 服务暂停(网关提醒)
*/ */
SERVICE_UNAVAILABLE(503, "Service Unavailable"); SERVICE_UNAVAILABLE(503, "Status.SERVICE_UNAVAILABLE");
private final int value; private final int value;
private final String reasonPhrase; private final String reasonPhrase;
Status(int value, String reasonPhrase) { Status(int value, @PropertyKey(resourceBundle = "i18n.status") String reasonPhrase) {
this.value = value; this.value = value;
this.reasonPhrase = reasonPhrase; this.reasonPhrase = reasonPhrase;
} }
...@@ -152,15 +154,16 @@ public enum Status { ...@@ -152,15 +154,16 @@ public enum Status {
* *
* @param message 异常消息 * @param message 异常消息
*/ */
public FailStatusException exception(String message) { public FailStatusException exception(@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String message) {
return new FailStatusException(this, message); return new FailStatusException(this, message);
} }
/** /**
* 暴露异常 * 暴露异常
*
* @param message 异常消息 * @param message 异常消息
*/ */
public void expose(String message) throws FailStatusException { public void expose(@PropertyKey(resourceBundle = I18n.RESOURCE_BUNDLE) String message) throws FailStatusException {
throw exception(message); throw exception(message);
} }
} }
...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty; ...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotBlank;
import lombok.*; import lombok.*;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -30,6 +30,6 @@ public class IdParam implements Serializable { ...@@ -30,6 +30,6 @@ public class IdParam implements Serializable {
private static final long serialVersionUID = -8690942241103456893L; private static final long serialVersionUID = -8690942241103456893L;
@ApiModelProperty(value = "id", example = "1", required = true) @ApiModelProperty(value = "id", example = "1", required = true)
@NotNull(message = "id 不能为空") @NotBlank
String id; String id;
} }
...@@ -9,7 +9,7 @@ import java.util.Arrays; ...@@ -9,7 +9,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotBlank;
import lombok.*; import lombok.*;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -33,7 +33,7 @@ public class IdsParam implements Serializable { ...@@ -33,7 +33,7 @@ public class IdsParam implements Serializable {
private static final long serialVersionUID = -8379896695668632733L; private static final long serialVersionUID = -8379896695668632733L;
@ApiModelProperty(value = "ids 多个以逗号分割", example = "1,2", required = true) @ApiModelProperty(value = "ids 多个以逗号分割", example = "1,2", required = true)
@NotEmpty(message = "ids 不能为空") @NotBlank
String ids; String ids;
/** /**
......
/* (C) 2021 YiRing, Inc. */
package com.yiring.common.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serial;
import java.io.Serializable;
import javax.validation.Valid;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 公共的 Boolean 查询参数
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@ApiModel(value = "BooleanParam", description = "公共的 Boolean 查询参数")
@Valid
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class OptionalBooleanParam implements Serializable {
@Serial
private static final long serialVersionUID = -3100195332181882287L;
@ApiModelProperty(value = "value", example = "true")
Boolean value;
}
...@@ -6,13 +6,14 @@ import io.swagger.annotations.ApiModelProperty; ...@@ -6,13 +6,14 @@ import io.swagger.annotations.ApiModelProperty;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Min;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
...@@ -35,11 +36,11 @@ public class OptionalPageParam implements Serializable { ...@@ -35,11 +36,11 @@ public class OptionalPageParam implements Serializable {
private static final long serialVersionUID = 6103761701912769946L; private static final long serialVersionUID = 6103761701912769946L;
@ApiModelProperty(value = "分页条数", example = "10", required = true) @ApiModelProperty(value = "分页条数", example = "10", required = true)
@DecimalMin(value = "1", message = "分页条数不能小于1") @Range(min = 1, max = 100)
Integer pageSize; Integer pageSize;
@ApiModelProperty(value = "当前页数", example = "1", required = true) @ApiModelProperty(value = "当前页数", example = "1", required = true)
@DecimalMin(value = "1", message = "当前页数不能小于1") @Min(1)
Integer pageNo; Integer pageNo;
@ApiModelProperty(value = "排序字段", example = "id") @ApiModelProperty(value = "排序字段", example = "id")
......
...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty; ...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
...@@ -14,6 +14,7 @@ import lombok.Data; ...@@ -14,6 +14,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
...@@ -36,13 +37,13 @@ public class PageParam implements Serializable { ...@@ -36,13 +37,13 @@ public class PageParam implements Serializable {
private static final long serialVersionUID = 6103761701912769946L; private static final long serialVersionUID = 6103761701912769946L;
@ApiModelProperty(value = "分页条数", example = "10", required = true) @ApiModelProperty(value = "分页条数", example = "10", required = true)
@NotNull(message = "分页条数不能为空") @NotNull
@DecimalMin(value = "1", message = "分页条数不能小于1") @Range(min = 1, max = 100)
Integer pageSize; Integer pageSize;
@ApiModelProperty(value = "当前页数", example = "1", required = true) @ApiModelProperty(value = "当前页数", example = "1", required = true)
@NotNull(message = "当前页数不能为空") @NotNull
@DecimalMin(value = "1", message = "当前页数不能小于1") @Min(1)
Integer pageNo; Integer pageNo;
@ApiModelProperty(value = "排序字段", example = "id") @ApiModelProperty(value = "排序字段", example = "id")
...@@ -63,7 +64,7 @@ public class PageParam implements Serializable { ...@@ -63,7 +64,7 @@ public class PageParam implements Serializable {
return Pageable.unpaged(); return Pageable.unpaged();
} }
return PageParam.toPageable(param.getSortField(), param.getSortOrder(), param.pageSize, param.getPageNo()); return PageParam.toPageable(param.getSortField(), param.getSortOrder(), param.getPageSize(), param.getPageNo());
} }
/** /**
...@@ -71,8 +72,8 @@ public class PageParam implements Serializable { ...@@ -71,8 +72,8 @@ public class PageParam implements Serializable {
* *
* @param sortField 排序字段 * @param sortField 排序字段
* @param sortOrder 排序方向 * @param sortOrder 排序方向
* @param pageSize 分页大小 * @param pageSize 分页大小
* @param pageNo 分页页码 * @param pageNo 分页页码
* @return Pageable * @return Pageable
*/ */
public static Pageable toPageable(String sortField, Sort.Direction sortOrder, Integer pageSize, Integer pageNo) { public static Pageable toPageable(String sortField, Sort.Direction sortOrder, Integer pageSize, Integer pageNo) {
......
...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty; ...@@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotBlank;
import lombok.*; import lombok.*;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -30,6 +30,6 @@ public class PidParam implements Serializable { ...@@ -30,6 +30,6 @@ public class PidParam implements Serializable {
private static final long serialVersionUID = -8690942241103456893L; private static final long serialVersionUID = -8690942241103456893L;
@ApiModelProperty(value = "pid", example = "0", required = true) @ApiModelProperty(value = "pid", example = "0", required = true)
@NotNull(message = "pid 不能为空") @NotBlank
String pid; String pid;
} }
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.validation;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* 枚举参数校验
*
* @author Jim
* @version 0.1
* 2022/9/29 16:04
*/
@Documented
@Retention(value = RUNTIME)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Constraint(validatedBy = { EnumValueValidated.class })
public @interface EnumValue {
/**
* 是否需要(true:不能为空,false:可以为空)
*/
boolean isRequire() default false;
/**
* 字符串数组
*/
String[] strValues() default {};
/**
* int数组
*/
int[] intValues() default {};
/**
* 枚举类
*/
Class<?>[] enumClass() default {};
String message() default "所传参数不在允许的值范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
}
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.validation;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 枚举参数校验器
*
* @author Jim
* @version 0.1
* 2022/9/29 16:06
*/
public class EnumValueValidated implements ConstraintValidator<EnumValue, Object> {
private boolean isRequire;
private Set<String> strValues;
private List<Integer> intValues;
@Override
public void initialize(EnumValue constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
strValues = Set.of(constraintAnnotation.strValues());
intValues = Arrays.stream(constraintAnnotation.intValues()).boxed().collect(Collectors.toList());
isRequire = constraintAnnotation.isRequire();
// 将枚举类的 name 转小写存入 strValues 里面,作为校验参数
Optional
.ofNullable(constraintAnnotation.enumClass())
.ifPresent(e ->
Arrays
.stream(e)
.forEach(c ->
Arrays.stream(c.getEnumConstants()).forEach(v -> strValues.add(v.toString().toLowerCase()))
)
);
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null && !isRequire) {
return true;
}
if (value instanceof String) {
return strValues.contains(value);
}
if (value instanceof Integer) {
return intValues.stream().anyMatch(e -> e.equals(value));
}
return false;
}
}
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation "org.jetbrains:annotations:${jetbrainsAnnotationsVersion}"
} }
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
package com.yiring.common.config; package com.yiring.common.config;
import java.util.Locale; import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
...@@ -16,6 +19,7 @@ import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; ...@@ -16,6 +19,7 @@ import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
*/ */
@Configuration @Configuration
@RequiredArgsConstructor
public class I18nConfig { public class I18nConfig {
@Bean @Bean
...@@ -25,4 +29,16 @@ public class I18nConfig { ...@@ -25,4 +29,16 @@ public class I18nConfig {
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver; return resolver;
} }
@Bean
public LocalValidatorFactoryBean getValidator() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setDefaultEncoding("UTF-8");
messageSource.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
messageSource.setBasenames("classpath:/ValidationMessages", "classpath:i18n/validation");
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
} }
...@@ -3,6 +3,7 @@ package com.yiring.common.core; ...@@ -3,6 +3,7 @@ package com.yiring.common.core;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.PropertyKey;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
...@@ -21,10 +22,13 @@ import org.springframework.stereotype.Component; ...@@ -21,10 +22,13 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor @RequiredArgsConstructor
public class I18n { public class I18n {
public static final String RESOURCE_BUNDLE = "i18n.messages";
final MessageSource messageSource; final MessageSource messageSource;
/** /**
* 根据 MessageSourceResolvable 获取 I18n 消息 * 根据 MessageSourceResolvable 获取 I18n 消息
*
* @param resolvable MessageSourceResolvable * @param resolvable MessageSourceResolvable
* @return 消息内容 * @return 消息内容
*/ */
...@@ -36,32 +40,38 @@ public class I18n { ...@@ -36,32 +40,38 @@ public class I18n {
/** /**
* 根据 code 和注入参数获取 I18n 消息 * 根据 code 和注入参数获取 I18n 消息
* eg: * eg:
* default.nonnull = {0}不能为空 * default.nonnull = {0}不能为空
* message.username.not-empty = 用户姓名不能为空 * message.username.not-empty = 用户姓名不能为空
* I18n.get("default.nonnull", "用户姓名") * I18n.get("default.nonnull", "用户姓名")
* I18n.get("message.username.not-empty") * I18n.get("message.username.not-empty")
* @param code 消息标识 *
* @param key 消息标识
* @param args 注入参数 * @param args 注入参数
* @return 消息内容 * @return 消息内容
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public String get(String code, Object... args) { public String get(@PropertyKey(resourceBundle = RESOURCE_BUNDLE) String key, Object... args) {
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); return messageSource.getMessage(key, args, LocaleContextHolder.getLocale());
} }
/** /**
* 根据 code 和注入参数获取 I18n 消息 * 根据 code 和注入参数获取 I18n 消息
* eg: * eg:
* default.nonnull = {0}不能为空 * default.nonnull = {0}不能为空
* I18n.get("default.nonnull", "用户姓名") * I18n.get("default.nonnull", "用户姓名")
* I18n.get("message.username.not-empty", "用户姓名不能为空") * I18n.get("message.username.not-empty", "用户姓名不能为空")
* @param code 消息标识 *
* @param key 消息标识
* @param defaultMessage 默认消息 * @param defaultMessage 默认消息
* @param args 注入参数 * @param args 注入参数
* @return 消息内容 * @return 消息内容
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public String get(String code, String defaultMessage, Object... args) { public String get(
return messageSource.getMessage(code, args, defaultMessage, LocaleContextHolder.getLocale()); @PropertyKey(resourceBundle = RESOURCE_BUNDLE) String key,
String defaultMessage,
Object... args
) {
return messageSource.getMessage(key, args, defaultMessage, LocaleContextHolder.getLocale());
} }
} }
Status.OK=OK
Status.NON_AUTHORITATIVE_INFORMATION=Non-Authoritative Information
Status.BAD_REQUEST=Bad Request
Status.UNAUTHORIZED=Unauthorized
Status.FORBIDDEN=Forbidden
Status.NOT_FOUND=Not Found
Status.METHOD_NOT_ALLOWED=Method Not Allowed
Status.EXPECTATION_FAILED=Expectation Failed
Status.INTERNAL_SERVER_ERROR=Internal Server Error
Status.UNKNOWN_ERROR=Unknown Error
Status.NOT_IMPLEMENTED=Not Implemented
Status.BAD_GATEWAY=Bad Gateway
Status.SERVICE_UNAVAILABLE=Service Unavailable
Status.OK=OK
Status.NON_AUTHORITATIVE_INFORMATION=\u8BA4\u8BC1\u5931\u8D25
Status.BAD_REQUEST=\u8BF7\u6C42\u5931\u8D25
Status.UNAUTHORIZED=\u51ED\u8BC1\u8FC7\u671F
Status.FORBIDDEN=\u7981\u6B62\u8BBF\u95EE
Status.NOT_FOUND=\u627E\u4E0D\u5230\u8D44\u6E90
Status.METHOD_NOT_ALLOWED=\u4E0D\u652F\u6301\u7684\u8BF7\u6C42\u7C7B\u578B
Status.EXPECTATION_FAILED=\u65E0\u6548\u53C2\u6570
Status.INTERNAL_SERVER_ERROR=\u670D\u52A1\u5668\u9519\u8BEF
Status.UNKNOWN_ERROR=\u672A\u77E5\u9519\u8BEF
Status.NOT_IMPLEMENTED=API \u672A\u5B9E\u73B0
Status.BAD_GATEWAY=\u670D\u52A1\u5F02\u5E38
Status.SERVICE_UNAVAILABLE=\u670D\u52A1\u6682\u505C
...@@ -35,6 +35,6 @@ public class DownloadParam implements Serializable { ...@@ -35,6 +35,6 @@ public class DownloadParam implements Serializable {
String bucket; String bucket;
@ApiModelProperty(value = "object", example = "cat.jpg", required = true) @ApiModelProperty(value = "object", example = "cat.jpg", required = true)
@NotEmpty @NotEmpty(message = "文件对象不能为空")
String object; String object;
} }
...@@ -34,8 +34,9 @@ public class FileUtils { ...@@ -34,8 +34,9 @@ public class FileUtils {
/** /**
* 文件下载 * 文件下载
*
* @param response HttpServletResponse * @param response HttpServletResponse
* @param file File * @param file File
* @throws IOException IOException * @throws IOException IOException
*/ */
public void download(HttpServletResponse response, File file) throws IOException { public void download(HttpServletResponse response, File file) throws IOException {
...@@ -51,11 +52,12 @@ public class FileUtils { ...@@ -51,11 +52,12 @@ public class FileUtils {
/** /**
* 文件下载 * 文件下载
* @param response HttpServletResponse *
* @param object 文件流 * @param response HttpServletResponse
* @param length 文件大小 * @param object 文件流
* @param filename 文件名称(带后缀) * @param length 文件大小
* @param contentType 文档类型 * @param filename 文件名称(带后缀)
* @param contentType 文档类型
* @param lastModified 最后修改时间 * @param lastModified 最后修改时间
* @throws IOException IOException * @throws IOException IOException
*/ */
...@@ -74,6 +76,7 @@ public class FileUtils { ...@@ -74,6 +76,7 @@ public class FileUtils {
response.setContentType(contentType); response.setContentType(contentType);
IOUtils.copy(object, response.getOutputStream()); IOUtils.copy(object, response.getOutputStream());
object.close(); object.close();
response.flushBuffer();
} }
/** /**
......
...@@ -27,7 +27,7 @@ ext { ...@@ -27,7 +27,7 @@ ext {
// https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter // https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter
saTokenVersion = '1.31.0' saTokenVersion = '1.31.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all // https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion = '5.8.7' hutoolVersion = '5.8.8'
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 // https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
fastJsonVersion = '2.0.14' fastJsonVersion = '2.0.14'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core // https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
...@@ -35,15 +35,17 @@ ext { ...@@ -35,15 +35,17 @@ ext {
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
okhttpVersion = '4.10.0' okhttpVersion = '4.10.0'
// https://mvnrepository.com/artifact/io.minio/minio // https://mvnrepository.com/artifact/io.minio/minio
minioVersion = '8.4.4' minioVersion = '8.4.5'
// https://mvnrepository.com/artifact/com.vladmihalcea/hibernate-types-55 // https://mvnrepository.com/artifact/com.vladmihalcea/hibernate-types-55
hibernateTypesVersion = '2.19.2' hibernateTypesVersion = '2.19.2'
// https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial // https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial
hibernateSpatialVersion = '5.6.11.Final' hibernateSpatialVersion = '5.6.12.Final'
// https://mvnrepository.com/artifact/org.locationtech.jts/jts-core // https://mvnrepository.com/artifact/org.locationtech.jts/jts-core
jtsVersion = '1.19.0' jtsVersion = '1.19.0'
// https://mvnrepository.com/artifact/com.github.liaochong/myexcel // https://mvnrepository.com/artifact/com.github.liaochong/myexcel
myexcelVersion = '4.2.2' myexcelVersion = '4.2.2'
// https://mvnrepository.com/artifact/org.jetbrains/annotations
jetbrainsAnnotationsVersion = '23.0.0'
} }
allprojects { allprojects {
...@@ -84,7 +86,7 @@ subprojects { ...@@ -84,7 +86,7 @@ subprojects {
} }
} }
[compileJava,compileTestJava,javadoc]*.options*.encoding ='UTF-8' [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
...@@ -128,7 +130,7 @@ subprojects { ...@@ -128,7 +130,7 @@ subprojects {
} }
} }
task hooks() { task preCommit() {
// fix: CI/CD // fix: CI/CD
try { try {
// GitHook pre-commit (spotless, spotbugs) // GitHook pre-commit (spotless, spotbugs)
...@@ -141,12 +143,13 @@ task hooks() { ...@@ -141,12 +143,13 @@ task hooks() {
RESULT=\$? RESULT=\$?
exit \$RESULT exit \$RESULT
""" """
} catch (ignored) {} } catch (ignored) {
}
} }
gradle.getTaskGraph().whenReady { gradle.getTaskGraph().whenReady {
def skipHooks = gradle.startParameter.getSystemPropertiesArgs().containsKey('skip-hooks') def skipHooks = gradle.startParameter.getSystemPropertiesArgs().containsKey('skip-hooks')
if (!skipHooks) { if (!skipHooks) {
hooks preCommit
} }
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
> IDEA > IDEA
<!-- prettier-ignore --> <!-- prettier-ignore -->
-[IDE Eval Rest](https://www.cnblogs.com/wang-cong/p/15150585.html) - IDEA 无限重置试用插件 -[IDE Eval Rest](https://www.cnblogs.com/wang-cong/p/15150585.html) - IDEA 无限重置试用插件
-[GitHub Copilot](https://plugins.jetbrains.com/plugin/17718-github-copilot) -[GitHub Copilot](https://plugins.jetbrains.com/plugin/17718-github-copilot)
-[.ignore](https://plugins.jetbrains.com/plugin/7495--ignore) -[.ignore](https://plugins.jetbrains.com/plugin/7495--ignore)
...@@ -10,6 +11,7 @@ ...@@ -10,6 +11,7 @@
-[Grep Console](https://plugins.jetbrains.com/plugin/7125-grep-console) -[Grep Console](https://plugins.jetbrains.com/plugin/7125-grep-console)
-[Rainbow Brackets](https://plugins.jetbrains.com/plugin/10080-rainbow-brackets) -[Rainbow Brackets](https://plugins.jetbrains.com/plugin/10080-rainbow-brackets)
-[Spotless Gradle](https://plugins.jetbrains.com/plugin/18321-spotless-gradle) -[Spotless Gradle](https://plugins.jetbrains.com/plugin/18321-spotless-gradle)
-[Easy I18n](https://plugins.jetbrains.com/plugin/16316-easy-i18n)
- [Prettier](https://plugins.jetbrains.com/plugin/10456-prettier) - [Prettier](https://plugins.jetbrains.com/plugin/10456-prettier)
- [Nyan Progress Bar](https://plugins.jetbrains.com/plugin/8575-nyan-progress-bar) - [Nyan Progress Bar](https://plugins.jetbrains.com/plugin/8575-nyan-progress-bar)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论