APP首页设计
这是我们APP原来的首页,整体的样式还是比较朴素的,特别在搞活动的时候,运营只能修改Banner、导航和商品推荐区的头图,来营造氛围,颇为令人诟病
为此,我们参考京东和淘宝的APP,将APP首页进行了重新设计,将其分成为这几个部分
- 背景
- 顶部搜索栏
- Banner区
- 资源位
- 导航区
- 营销活动区
- 商品推荐区
- 底部Tab按钮区
资源位可以横向切分成任意块,每一块拥有单独的点击事件。我们计划让运营可以在后台对APP首页的这八个部分进行前景图或背景图的配置,以此满足营造氛围感的需求。
这是我们预期的样子
确实好看很多吧!
为了支持这种设计,我们需要一个数据结构来存储配置
@Data
@EqualsAndHashCode(callSuper = true)
public class UiConfigurationDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 配置名称
*/
@ApiModelProperty("配置名称")
private String name;
/**
* 平台
* @see PlatformEnum
*/
@ApiModelProperty("平台")
private Integer platform;
/**
* 屏幕类型
* @see ScreenTypeEnum
*/
@ApiModelProperty("屏幕类型")
private Integer screenType;
/**
* 配置类型
*/
@EnumField(ConfigurationTypeEnum.class)
@ApiModelProperty("配置类型")
private Integer type;
/**
* 是否启用
*/
@ApiModelProperty("是否启用")
private Boolean useable;
/**
* 生效时间段 - 开始
*/
@ApiModelProperty("生效时间段 - 开始")
private Long startTime;
/**
* 生效时间段 - 结束
*/
@ApiModelProperty("生效时间段 - 结束")
private Long endTime;
/**
* 内部模块
*/
@ApiModelProperty("内部模块")
private List<UiModuleDTO> modules;
}
@Data
@NoArgsConstructor
public class UiModuleDTO implements Serializable, Redirect {
private static final long serialVersionUID = 1L;
/**
* 模块ID
*/
@ApiModelProperty("模块ID")
private Long id;
/**
* 模块名称
*/
@ApiModelProperty("模块名称")
private String name;
/**
* 前景图ID
*/
@ApiModelProperty("前景图ID")
private Long foreground;
/**
* 前景图URL
*/
@ApiModelProperty("前景图URL")
@JSONField(serialize = false)
private String foregroundUrl;
/**
* 背景图ID
*/
@ApiModelProperty("背景图ID")
private Long background;
/**
* 背景图URL
*/
@ApiModelProperty("背景图URL")
@JSONField(serialize = false)
private String backgroundUrl;
@ApiModelProperty("是否可见")
private Boolean visible;
/**
* 跳转类型
*/
@EnumField(YanXuanEnum.RedirectTypeEnum.class)
@ApiModelProperty("跳转类型")
private Integer redirectType;
/**
* 跳转目标
*/
@ApiModelProperty("跳转目标")
private String redirectTarget;
/**
* UI模块
*/
@ApiModelProperty("内部模块")
private List<UiModuleDTO> modules;
public UiModuleDTO(Long id, String name) {
this.id = id;
this.name = name;
this.foreground = 0L;
this.background = 0L;
this.visible = Boolean.FALSE;
this.redirectType = 0;
this.redirectTarget = "";
this.modules = new ArrayList<>();
}
}
考虑到IOS和Android之间UI规划和屏幕样式可能会有差别,我们在配置上标记了适用的系统和屏幕类型,而真正存储配置内容的UiModuleDTO类则采用了自由嵌套的结构。
很明显,UI配置是一个读多写少的场景,我们希望它可以提供尽可能好的并发读性能,所以我们打算将其存储在Redis中。另外考虑到运营会同时创建多个UI配置,有定期生效的需求,我们设计了这么几个Key
/**
* [id]指代具体的id值
* [uiConfiguration]指代uiConfiguration实例对象
* [platform]指标适用平台
* [screenType]指代适用屏幕样式
* [startTime]指代uiConfiguration实例对象
* [endTime]指代uiConfiguration实例对象
*/
// key1:用String存储ID对应的UI配置实例对象
SET uiConfiguration:id:[id] [uiConfiguration]
// key2:用zset存储ID对应的开始生效时间
ZADD uiConfiguration:[platform]:[screenType]:startTime [id] [startTime]
// key3:用zset存储ID对应的结束生效时间
ZADD uiConfiguration:[platform]:[screenType]:endTime [id] [endTime]
APP请求UI配置内容时,会携带platform和screenType参数,后端根据这两个参数可以构造出key2和key3。然后使用ZRANGEBYSCORE
命令在key2中找出结束生效时间小于当前时间的失效ID集合,如果失效ID集合不为空,则把这些ID在key1、key2和key3中对应的值删除。
再使用ZRANGEBYSCORE
命令在key3中找出开始生效时间小于当前时间的ID集合,若ID集合为空,说明没有可用的配置,反之则取开始时间最早的一个ID,然后在key1中找出具体的配置内容返回给APP
这里面有一个zset的小细节,当score值相同时,zset会根据value进行排序,这里使用的是字典序。我们预期的是当开始生效时间相同时,应该取ID小的那条记录。但如果ID是99、100之类的数字时,你会发现实际取到的是100,因为根据字典序的规则100要比99小。为了规避这个问题,我们在实际开发中,将ID处理成了统一长度的19位字符串(在数字ID前面填充0)
有同学可能会觉得每次查询的时候都去删除缓存很不合理,但大多数时候查询出的失效ID集合都是空哈,所以实际去删除缓存的次数不会多。
代码参考
@Slf4j
@Component
public class UiConfigurationDubboServiceImpl implements UiConfigurationDubboService {
private final static List<UiModuleDTO> EMPTY_MODULES;
private final UiConfigurationCache cache;
static {
EMPTY_MODULES = new ArrayList<>();
EMPTY_MODULES.add(new UiModuleDTO(1L, "搜索栏顶部"));
EMPTY_MODULES.add(new UiModuleDTO(2L, "首页底图"));
EMPTY_MODULES.add(new UiModuleDTO(3L, "资源位"));
EMPTY_MODULES.add(new UiModuleDTO(4L, "首页导航区"));
EMPTY_MODULES.add(new UiModuleDTO(5L, "固定活动区1"));
EMPTY_MODULES.add(new UiModuleDTO(6L, "固定活动区2"));
}
/*
* 此处省略N行代码
*/
/**
* UI配置缓存类
*/
private static class UiConfigurationCache {
private final static String START_TIME_SET_KEY = "uiConfiguration:startTime:";
private final static String END_TIME_SET_KEY = "uiConfiguration:endTime:";
private final static String CONFIGURATION_KEY_PREFIX = "uiConfiguration:id:";
private final RedisTemplate<String, Object> redisTemplate;
public UiConfigurationCache(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
private void addCache(UiConfigurationDTO config) {
String prefix = getPrefix(config);
Long id = config.getId();
String strId = String.format("9d", id);
if (ConfigurationTypeEnum.TEMPORARY.getCode().equals(config.getType())) {
redisTemplate.opsForZSet().add(START_TIME_SET_KEY prefix, strId, config.getStartTime());
redisTemplate.opsForZSet().add(END_TIME_SET_KEY prefix, strId, config.getEndTime());
redisTemplate.opsForValue().set(CONFIGURATION_KEY_PREFIX prefix ":" id, config);
} else {
redisTemplate.opsForValue().set(CONFIGURATION_KEY_PREFIX prefix ":default", config);
}
}
private String getPrefix(UiConfigurationDTO config) {
String prefix = "";
if (Objects.equals(config.getPlatform(),PlatformEnum.ANDROID.getCode())){
prefix = prefix config.getPlatform();
} else {
prefix = prefix config.getPlatform() ":" config.getScreenType();
}
return prefix;
}
private void delCache(UiConfigurationDTO config) {
String prefix = getPrefix(config);
Long id = config.getId();
String strId = String.format("9d", id);
if (ConfigurationTypeEnum.TEMPORARY.getCode().equals(config.getType())) {
redisTemplate.opsForZSet().remove(START_TIME_SET_KEY prefix, strId);
redisTemplate.opsForZSet().remove(END_TIME_SET_KEY prefix, strId);
redisTemplate.delete(CONFIGURATION_KEY_PREFIX prefix ":" id);
} else {
redisTemplate.delete(CONFIGURATION_KEY_PREFIX prefix ":default");
}
}
private UiConfigurationDTO getConfiguration(Integer platform) {
long nowTime = System.currentTimeMillis();
// 获取已失效的配置
Set<Object> disableConfigurationIds = redisTemplate.opsForZSet().rangeByScore(END_TIME_SET_KEY platform, 0, nowTime - 1);
if (CollectionUtil.isNotEmpty(disableConfigurationIds)) {
// 移除失效的UI配置缓存
disableConfigurationIds.forEach(id -> {
String strId = String.format("9d", id);
redisTemplate.opsForZSet().remove(START_TIME_SET_KEY platform, strId);
redisTemplate.opsForZSet().remove(END_TIME_SET_KEY platform, strId);
redisTemplate.delete(CONFIGURATION_KEY_PREFIX platform ":" id);
});
}
Set<Object> configIds = redisTemplate.opsForZSet().rangeByScore(START_TIME_SET_KEY platform, 0, nowTime);
if (CollectionUtil.isEmpty(configIds)) {
return (UiConfigurationDTO) redisTemplate.opsForValue().get(CONFIGURATION_KEY_PREFIX platform ":default");
} else {
Optional<Object> optional = configIds.stream().findFirst();
String strId = (String) optional.orElseThrow(() -> new RuntimeException("未知异常"));
long id = Long.parseLong(strId);
return (UiConfigurationDTO) redisTemplate.opsForValue().get(CONFIGURATION_KEY_PREFIX platform ":" id);
}
}
private UiConfigurationDTO getConfiguration(Integer platform, Integer screenType) {
long nowTime = System.currentTimeMillis();
UiConfigurationDTO config = new UiConfigurationDTO();
config.setPlatform(platform);
config.setScreenType(screenType);
String prefix = getPrefix(config);
// 获取已失效的配置
Set<Object> disableConfigurationIds = redisTemplate.opsForZSet().rangeByScore(END_TIME_SET_KEY prefix, 0, nowTime - 1);
if (CollectionUtil.isNotEmpty(disableConfigurationIds)) {
// 移除失效的UI配置缓存
disableConfigurationIds.forEach(strId -> {
long id = Long.parseLong((String) strId);
redisTemplate.opsForZSet().remove(START_TIME_SET_KEY prefix, strId);
redisTemplate.opsForZSet().remove(END_TIME_SET_KEY prefix, strId);
redisTemplate.delete(CONFIGURATION_KEY_PREFIX prefix ":" id);
});
}
Set<Object> configIds = redisTemplate.opsForZSet().rangeByScore(START_TIME_SET_KEY prefix, 0, nowTime);
if (CollectionUtil.isEmpty(configIds)) {
return (UiConfigurationDTO) redisTemplate.opsForValue().get(CONFIGURATION_KEY_PREFIX prefix ":default");
} else {
Optional<Object> optional = configIds.stream().findFirst();
String strId = (String) optional.orElseThrow(() -> new RuntimeException("未知异常"));
long id = Long.parseLong(strId);
return (UiConfigurationDTO) redisTemplate.opsForValue().get(CONFIGURATION_KEY_PREFIX prefix ":" id);
}
}
}
}
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhiabeic
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22 -
excel打印预览压线压字怎么办
PHP中文网 06-22