背景
国际版项目,每个用户有自己的时区,数据库存储的时间固定是东八区,现在需要根据用户设置的时区显示时间
解决方案很多,这里的方案是 实体类增加时间格式化属性(赋值,前端显示),使用 Spring AOP 全局修改返回对象属性值
实体类可以不加属性,直接修改原来的Date
有个问题是Date类型的数据以时间戳的形式返回给前端,时间戳会根据浏览器客户端时区的改变而改变
所以需要后端定义一个String类型格式化时间,前端显示String
一:实体类增加格式化属性
二:自定义注解
package com.sendcloud.marketing.api.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.web.bind.annotation.Mapping;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface HandleDateField {
String[] dateFiled();
}
三:AOP修改时间
package com.sendcloud.marketing.api.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import com.sendcloud.marketing.api.controller.BaseController;
import com.sendcloud.marketing.api.core.R;
import com.sendcloud.marketing.common.util.DateUtils;
import com.sendcloud.marketing.model.UserInfo;
import com.sendcloud.marketing.service.UserInfoService;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class ChangeDateForTimeZoneAspect {
@Autowired
private UserInfoService userInfoService;
@Resource(name = "redisTemplateObject")
private RedisTemplate<Object,Object> redisTemplate;
private Object changObjectValue(Object _obj, Set<String> propertySet,
String timeZoneId) throws Exception {
Class<?> resultClz = _obj.getClass();
for(String property:propertySet){
Field field = getField(resultClz, property);
if(field!=null){
//权限,允许修改
field.setAccessible(true);
//获得数据库中的属性值
Object fieldValue = field.get(_obj);
if(StringUtils.isNotBlank(ObjectUtils.toString(fieldValue))){
//根据用户时区转换后的时间
Date date = DateUtils.timeZoneTransfer((Date)fieldValue, 8, Integer.valueOf(timeZoneId));
String formatDate = DateUtils.format(date, "yyyy-MM-dd HH:mm:ss");
//反射修改对象属性
setField(resultClz,field,_obj,formatDate);
}
}
}
return _obj;
}
/*
反射获取对象
若当前对象没有field属性,向父类找,直到没有父类
*/
public Field getField(Class<?> clazz,String field){
//没有父类时结束
if (clazz == null)
return null;
try {
return clazz.getDeclaredField(field);
} catch (NoSuchFieldException e) {
//方法重载
return getField(clazz.getSuperclass(), field);
}
}
/*
反射修改对象属性
set 父类只找一次
*/
public Field setField(Class<?> clazz,Field field,Object _obj,String formatDate) throws Exception{
//验证参数
if (clazz == null)
return null;
try {
Field f = clazz.getDeclaredField(field.getName() + "Fmt");
f.setAccessible(true);
f.set(_obj,formatDate);
return f;
} catch (NoSuchFieldException e) {
Field f = clazz.getSuperclass().getDeclaredField(field.getName() + "Fmt");
f.setAccessible(true);
f.set(_obj,formatDate);
return f;
}
}
@Around(value = "@annotation(com.sendcloud.marketing.api.util.HandleDateField)")
public Object hanle(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint
.getSignature();
Method method = methodSignature.getMethod();
HandleDateField ananHandleDateField = method
.getAnnotation(HandleDateField.class);
String[] propertys = ananHandleDateField.dateFiled();
Set<String> propertySet = new HashSet<String>();
if (propertys != null) {
for (String e : propertys) {
propertySet.add(e);
}
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
Integer userId =(Integer) redisTemplate.opsForValue().get(BaseController.CONTACT_API_TOKEN_PREFIX + request.getHeader("token"));
UserInfo userInfo = userInfoService.findUserInfo(userId);
Object returnValue = joinPoint.proceed();
if(StringUtils.isEmpty(userInfo.getTimeZone())){
return returnValue;
}
//R为自定义的返回对象
if (returnValue instanceof R) {
R r = (R) returnValue;
Map<String, ?> map = r.getData();
for (Object obj : map.values()) {
//若接口返回的数据类型为对象
if(obj instanceof Object){
changObjectValue(obj,propertySet,userInfo.getTimeZone());
}
//若接口返回的数据类型为集合类型
if (obj instanceof List) {
List<Object> objList = (List<Object>) obj;
for (Object _obj : objList) {
changObjectValue(_obj,propertySet,userInfo.getTimeZone());
}
}
}
}
return returnValue;
}
}
时区转换方法
/**
* 时区转换
* @param dateTime 日期时间
* @param targetTimeZone 目标时区 +8,0,+9,-1 等等
* @return
*/
public static Date timeZoneTransfer(Date dateTime, int nowTimeZone,int targetTimeZone){
if (dateTime == null) {
return null;
}
String time = format(dateTime);
//转换位timezone可以识别的格式
String tTimezone = "";
String nTimezone = "";
tTimezone = getTimeZone(targetTimeZone, tTimezone);
nTimezone = getTimeZone(nowTimeZone, nTimezone);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_SEC_STYLE);
//默认中国时区
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT" + nTimezone));
Date date;
try {
date = simpleDateFormat.parse(time);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT" + tTimezone));
try {
dateTime = format(simpleDateFormat.format(date),DATE_SEC_STYLE);
} catch (ParseException e) {
e.printStackTrace();
}
return dateTime;
}
四:controller 层接口添加注解
五:测试
注释自定义注解,fmt属性为null
添加自定义注解后,成功写入值
测试通过