Compare commits
9 Commits
69d847f337
...
9bcf17a118
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bcf17a118 | |||
| e08a50a9b2 | |||
| 945a2c5e9d | |||
| 1688666dca | |||
| 278bf7c59a | |||
| 8a7946ce01 | |||
| f2689ab812 | |||
| 3ae1ccedb7 | |||
| 8de027e0c7 |
@ -294,6 +294,26 @@ public class TimiSpring {
|
||||
getRequest().removeAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求 URL 参数
|
||||
*
|
||||
* @param key 键
|
||||
* @return 参数值
|
||||
*/
|
||||
public static String getRequestArg(String key) {
|
||||
return getRequest().getParameter(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求 URL 参数(多值)
|
||||
*
|
||||
* @param key 键
|
||||
* @return 参数值
|
||||
*/
|
||||
public static String[] getRequestArgs(String key) {
|
||||
return getRequest().getParameterValues(key);
|
||||
}
|
||||
|
||||
public static void addCookie(Cookie cookie) {
|
||||
getResponse().addCookie(cookie);
|
||||
}
|
||||
|
||||
17
src/main/java/com/imyeyu/spring/annotation/CaptchaValid.java
Normal file
17
src/main/java/com/imyeyu/spring/annotation/CaptchaValid.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.imyeyu.spring.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 图形验证码校验注解
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-07-15 10:09
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CaptchaValid {
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.imyeyu.spring.annotation;
|
||||
|
||||
import com.imyeyu.spring.bean.CaptchaData;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
|
||||
/**
|
||||
* 图形验证码校验注解处理器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-07-15 10:01
|
||||
*/
|
||||
@Aspect
|
||||
public abstract class CaptchaValidAbstractInterceptor {
|
||||
|
||||
private boolean enable = true;
|
||||
|
||||
/** 注入注解 */
|
||||
@Pointcut("@annotation(com.imyeyu.spring.annotation.CaptchaValid)")
|
||||
public void captchaPointCut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行前
|
||||
*
|
||||
* @param joinPoint 切入点
|
||||
*/
|
||||
@Before("captchaPointCut()")
|
||||
public void doBefore(JoinPoint joinPoint) {
|
||||
if (!enable) {
|
||||
return;
|
||||
}
|
||||
if (joinPoint.getSignature() instanceof MethodSignature ms) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof CaptchaData<?> captchaData) {
|
||||
// 校验请求参数的验证码
|
||||
verify(captchaData.getCaptchaId(), captchaData.getCaptcha());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void verify(String captchaId, String captcha);
|
||||
|
||||
public void enable() {
|
||||
enable = true;
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enable = false;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
package com.imyeyu.spring.bean;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 含验证码数据实体
|
||||
@ -14,23 +12,21 @@ public class CaptchaData<T> {
|
||||
|
||||
/** 来源 */
|
||||
@NotBlank(message = "timijava.code.request_bad")
|
||||
protected String from;
|
||||
protected String captchaId;
|
||||
|
||||
/** 验证码 */
|
||||
@NotBlank(message = "captcha.miss")
|
||||
protected String captcha;
|
||||
|
||||
/** 数据体 */
|
||||
@Valid
|
||||
@NotNull
|
||||
protected T data;
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
public String getCaptchaId() {
|
||||
return captchaId;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
public void setCaptchaId(String captchaId) {
|
||||
this.captchaId = captchaId;
|
||||
}
|
||||
|
||||
public String getCaptcha() {
|
||||
|
||||
@ -61,6 +61,9 @@ public interface BaseMapper<T, P> {
|
||||
@SelectProvider(type = SQLProvider.class, method = "listOrder")
|
||||
List<T> listOrder(long offset, int limit, Map<String, OrderType> orderMap);
|
||||
|
||||
@SelectProvider(type = SQLProvider.class, method = "listAll")
|
||||
List<T> listAll();
|
||||
|
||||
/**
|
||||
* 创建数据。默认自增主键为 id,如需修改请重写此接口
|
||||
*
|
||||
@ -93,6 +96,9 @@ public interface BaseMapper<T, P> {
|
||||
@UpdateProvider(type = SQLProvider.class, method = "update")
|
||||
void update(T t);
|
||||
|
||||
@UpdateProvider(type = SQLProvider.class, method = "updateSelective")
|
||||
void updateSelective(T t);
|
||||
|
||||
/**
|
||||
* 软删除
|
||||
*
|
||||
|
||||
@ -47,7 +47,7 @@ public abstract class AbstractEntityService<T, P> implements BaseService<T, P> {
|
||||
|
||||
public void update(T t) {
|
||||
checkMapper();
|
||||
baseMapper.update(t);
|
||||
baseMapper.updateSelective(t);
|
||||
}
|
||||
|
||||
public void delete(P p) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package com.imyeyu.spring.util;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.validation.ValidationException;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.java.bean.timi.TimiResponse;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.validation.ValidationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
@ -41,7 +41,7 @@ public class GlobalExceptionHandler {
|
||||
if (env.contains("dev") || log.isDebugEnabled()) {
|
||||
log.error("conversion error", e);
|
||||
}
|
||||
return new TimiResponse<>(TimiCode.ARG_BAD);
|
||||
return new TimiResponse<>(TimiCode.ARG_BAD).msgKey("invalid.body");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ public class GlobalExceptionHandler {
|
||||
if (env.contains("dev") || log.isDebugEnabled()) {
|
||||
log.error("header error", e);
|
||||
}
|
||||
return new TimiResponse<>(TimiCode.REQUEST_BAD);
|
||||
return new TimiResponse<>(TimiCode.REQUEST_BAD).msgKey("invalid.header");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,7 +77,7 @@ public class GlobalExceptionHandler {
|
||||
if (env.startsWith("dev") || log.isDebugEnabled()) {
|
||||
log.error("request error", e);
|
||||
}
|
||||
return new TimiResponse<>(TimiCode.REQUEST_BAD);
|
||||
return new TimiResponse<>(TimiCode.REQUEST_BAD).msgKey("invalid.arg");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +89,6 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public TimiResponse<?> error(Throwable e) {
|
||||
if (e instanceof TimiException timiE) {
|
||||
// TODO 400 以下即使是开发环境也不算异常
|
||||
if (env.startsWith("dev") || log.isDebugEnabled()) {
|
||||
log.error(timiE.getMessage(), e);
|
||||
}
|
||||
@ -98,6 +97,6 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
// 致命异常
|
||||
log.error("fatal error", e);
|
||||
return new TimiResponse<>(TimiCode.ERROR);
|
||||
return new TimiResponse<>(TimiCode.ERROR).msgKey("service.error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import com.imyeyu.spring.annotation.table.Column;
|
||||
import com.imyeyu.spring.annotation.table.Id;
|
||||
import com.imyeyu.spring.annotation.table.Table;
|
||||
import com.imyeyu.spring.annotation.table.Transient;
|
||||
import com.imyeyu.spring.entity.BaseEntity;
|
||||
import com.imyeyu.spring.entity.Creatable;
|
||||
import com.imyeyu.spring.entity.Deletable;
|
||||
import com.imyeyu.spring.entity.Destroyable;
|
||||
@ -64,10 +65,27 @@ public class SQLProvider {
|
||||
sql.append(", ");
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 2);
|
||||
} else {
|
||||
if (meta.canCreate && !meta.canUpdate) {
|
||||
sql.append(" ORDER BY created_at DESC");
|
||||
}
|
||||
if (meta.canCreate && meta.canUpdate) {
|
||||
sql.append(" ORDER BY COALESCE(updated_at, created_at) DESC");
|
||||
}
|
||||
}
|
||||
return sql.append(" LIMIT %s, %s".formatted(offset, limit)).toString();
|
||||
}
|
||||
|
||||
public String listAll(ProviderContext context) {
|
||||
EntityMeta meta = getEntityMeta(context);
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT * FROM %s WHERE 1 = 1".formatted(meta.table));
|
||||
if (meta.canDelete) {
|
||||
sql.append(BaseMapper.NOT_DELETE);
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入
|
||||
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
|
||||
@ -182,6 +200,42 @@ public class SQLProvider {
|
||||
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 更新,选择性更新非空属性,需要实体实现 {@link Updatable}
|
||||
*
|
||||
* @param entity 实体
|
||||
* @return SQL
|
||||
*/
|
||||
public String updateSelective(Object entity) {
|
||||
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||
TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass));
|
||||
|
||||
if (entity instanceof BaseEntity baseEntity) {
|
||||
baseEntity.setCreatedAt(null);
|
||||
baseEntity.setDeletedAt(null);
|
||||
}
|
||||
String setClause = meta.fieldColumnList.stream()
|
||||
.filter(fc -> {
|
||||
if (fc.isId) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Ref.getFieldValue(entity, fc.field, Object.class) != null;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.map(fc -> {
|
||||
if (entity instanceof Updatable updatableEntity) {
|
||||
updatableEntity.setUpdatedAt(Time.now());
|
||||
}
|
||||
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 软删除,需要实体实现 {@link Deletable}
|
||||
*
|
||||
@ -258,6 +312,9 @@ public class SQLProvider {
|
||||
/** 只读的列名字段名映射,Map<列名,字段名> */
|
||||
final List<FieldColumn> fieldColumnList;
|
||||
|
||||
/** true 为可创建 */
|
||||
final boolean canCreate;
|
||||
|
||||
/** true 为可更新 */
|
||||
final boolean canUpdate;
|
||||
|
||||
@ -313,6 +370,7 @@ public class SQLProvider {
|
||||
this.selectAllClause = selectAllClause.substring(0, selectAllClause.length() - 1);
|
||||
this.idFieldColumn = idFieldColumn;
|
||||
this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读
|
||||
canCreate = Creatable.class.isAssignableFrom(entityClass);
|
||||
canUpdate = Updatable.class.isAssignableFrom(entityClass);
|
||||
canDelete = Deletable.class.isAssignableFrom(entityClass);
|
||||
canDestroy = Destroyable.class.isAssignableFrom(entityClass);
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.imyeyu.spring.util;
|
||||
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.core.io.support.PropertySourceFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-10-13 16:29
|
||||
*/
|
||||
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||
|
||||
@Override
|
||||
public @org.springframework.lang.NonNull PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
|
||||
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
|
||||
return sources.get(0);
|
||||
}
|
||||
}
|
||||
4
src/main/resources/lang/zhCN.lang
Normal file
4
src/main/resources/lang/zhCN.lang
Normal file
@ -0,0 +1,4 @@
|
||||
invalid.arg=无效的参数
|
||||
invalid.body=无效的请求体
|
||||
invalid.header=无效的请求头
|
||||
service.error=服务错误
|
||||
Reference in New Issue
Block a user