设置用户偏好.

This commit is contained in:
lijiahang
2023-10-08 16:55:18 +08:00
parent c7defdb80d
commit fbd815a10e
30 changed files with 942 additions and 36 deletions

View File

@@ -1,6 +1,8 @@
package com.orion.ops.framework.redis.core.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.define.cache.CacheKeyDefine;
import com.orion.lang.utils.Strings;
@@ -20,6 +22,20 @@ public class RedisStrings extends RedisUtils {
private RedisStrings() {
}
/**
* 获取 json
*
* @param key key
* @return JSONObject
*/
public static JSONObject getJson(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return JSON.parseObject(value);
}
/**
* 获取 json
*
@@ -28,7 +44,7 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> T getJson(CacheKeyDefine define) {
return getJson(define.getKey(), define);
return (T) getJson(define.getKey(), define.getType());
}
/**
@@ -40,11 +56,37 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> T getJson(String key, CacheKeyDefine define) {
return (T) getJson(key, define.getType());
}
/**
* 获取 json
*
* @param key key
* @param type type
* @param <T> T
* @return T
*/
public static <T> T getJson(String key, Class<T> type) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return (T) JSON.parseObject(value, define.getType());
return (T) JSON.parseObject(value, type);
}
/**
* 获取 json
*
* @param key key
* @return JSONArray
*/
public static JSONArray getJsonArray(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return JSON.parseArray(value);
}
/**
@@ -55,7 +97,7 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> List<T> getJsonArray(CacheKeyDefine define) {
return getJsonArray(define.getKey(), define);
return (List<T>) getJsonArray(define.getKey(), define.getType());
}
/**
@@ -67,11 +109,23 @@ public class RedisStrings extends RedisUtils {
* @return T
*/
public static <T> List<T> getJsonArray(String key, CacheKeyDefine define) {
return (List<T>) getJsonArray(key, define.getType());
}
/**
* 获取 json
*
* @param key key
* @param type type
* @param <T> T
* @return T
*/
public static <T> List<T> getJsonArray(String key, Class<T> type) {
String value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return (List<T>) JSON.parseArray(value, define.getType());
return JSON.parseArray(value, type);
}
/**

View File

@@ -0,0 +1,25 @@
package com.orion.ops.module.infra.handler.preference.model;
import com.alibaba.fastjson.JSON;
import java.util.Map;
/**
* 偏好
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 13:54
*/
public interface PreferenceModel {
/**
* 转为 map
*
* @return map
*/
default Map<String, Object> toMap() {
return JSON.parseObject(JSON.toJSONString(this));
}
}

View File

@@ -0,0 +1,21 @@
package com.orion.ops.module.infra.handler.preference.strategy;
import com.orion.ops.module.infra.handler.preference.model.PreferenceModel;
/**
* 偏好处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 13:49
*/
public interface IPreferenceStrategy<Model extends PreferenceModel> {
/**
* 获取默认值
*
* @return 默认值
*/
Model getDefault();
}

View File

@@ -56,7 +56,7 @@ public class PermissionController {
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/user")
@Operation(summary = "获取用户权限")
@Operation(summary = "获取用户权限聚合信息")
public UserPermissionVO getUserPermission() {
return permissionService.getUserPermission();
}

View File

@@ -0,0 +1,28 @@
### 更新用户偏好-全部
PUT {{baseUrl}}/infra/preference/update
Content-Type: application/json
Authorization: {{token}}
{
"type": "SYSTEM",
"config": {
}
}
### 更新用户偏好-部分
PUT {{baseUrl}}/infra/preference/update-partial
Content-Type: application/json
Authorization: {{token}}
{
"type": "SYSTEM",
"config": {
}
}
### 查询用户偏好
GET {{baseUrl}}/infra/preference/get?type=SYSTEM
Authorization: {{token}}

View File

@@ -0,0 +1,55 @@
package com.orion.ops.module.infra.controller;
import com.orion.ops.framework.common.annotation.RestWrapper;
import com.orion.ops.module.infra.entity.request.preference.PreferenceUpdateRequest;
import com.orion.ops.module.infra.entity.vo.PreferenceVO;
import com.orion.ops.module.infra.service.PreferenceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 用户偏好 api
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Tag(name = "infra - 用户偏好服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/preference")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class PreferenceController {
@Resource
private PreferenceService preferenceService;
@PutMapping("/update")
@Operation(summary = "更新用户偏好-整体")
public Integer updatePreference(@Validated @RequestBody PreferenceUpdateRequest request) {
return preferenceService.updatePreference(request, false);
}
@PutMapping("/update-partial")
@Operation(summary = "更新用户偏好-部分")
public Integer updatePreferencePartial(@Validated @RequestBody PreferenceUpdateRequest request) {
return preferenceService.updatePreference(request, true);
}
@GetMapping("/get")
@Operation(summary = "查询用户偏好")
@Parameter(name = "type", description = "type", required = true)
public PreferenceVO getPreference(@RequestParam("type") String type) {
return preferenceService.getPreferenceByType(type);
}
}

View File

@@ -0,0 +1,28 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.domain.PreferenceDO;
import com.orion.ops.module.infra.entity.request.preference.PreferenceUpdateRequest;
import com.orion.ops.module.infra.entity.vo.PreferenceVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* 用户偏好 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Mapper
public interface PreferenceConvert {
PreferenceConvert MAPPER = Mappers.getMapper(PreferenceConvert.class);
@Mapping(target = "config", ignore = true)
PreferenceDO to(PreferenceUpdateRequest request);
@Mapping(target = "config", ignore = true)
PreferenceVO to(PreferenceDO domain);
}

View File

@@ -7,7 +7,7 @@ import com.orion.ops.module.infra.entity.request.user.SystemUserQueryRequest;
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest;
import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateStatusRequest;
import com.orion.ops.module.infra.entity.vo.SystemUserVO;
import com.orion.ops.module.infra.entity.vo.UserBaseInfoVO;
import com.orion.ops.module.infra.entity.vo.UserCollectInfoVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@@ -39,6 +39,6 @@ public interface SystemUserConvert {
LoginUser toLoginUser(SystemUserDO domain);
UserBaseInfoVO toBaseInfo(LoginUser user);
UserCollectInfoVO toCollectInfo(LoginUser user);
}

View File

@@ -0,0 +1,28 @@
package com.orion.ops.module.infra.dao;
import com.orion.ops.framework.mybatis.core.mapper.IMapper;
import com.orion.ops.framework.mybatis.core.query.Conditions;
import com.orion.ops.module.infra.entity.domain.PreferenceDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户偏好 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Mapper
public interface PreferenceDAO extends IMapper<PreferenceDO> {
/**
* 通过 userId 删除
*
* @param userId userId
* @return effect
*/
default int deleteByUserId(Long userId) {
return this.delete(Conditions.eq(PreferenceDO::getUserId, userId));
}
}

View File

@@ -0,0 +1,25 @@
package com.orion.ops.module.infra.define;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.define.cache.CacheKeyBuilder;
import com.orion.lang.define.cache.CacheKeyDefine;
import java.util.concurrent.TimeUnit;
/**
* 用户偏好缓存 key
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
public interface PreferenceCacheKeyDefine {
CacheKeyDefine PREFERENCE = new CacheKeyBuilder()
.key("user:preference:{}:{}")
.desc("用户偏好 ${userId} ${type}")
.type(JSONObject.class)
.timeout(1, TimeUnit.DAYS)
.build();
}

View File

@@ -0,0 +1,45 @@
package com.orion.ops.module.infra.entity.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.orion.ops.framework.mybatis.core.domain.BaseDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
/**
* 用户偏好 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName(value = "preference", autoResultMap = true)
@Schema(name = "PreferenceDO", description = "用户偏好 实体对象")
public class PreferenceDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "用户id")
@TableField("user_id")
private Long userId;
@Schema(description = "类型")
@TableField("type")
private String type;
@Schema(description = "偏好配置")
@TableField("config")
private String config;
}

View File

@@ -0,0 +1,38 @@
package com.orion.ops.module.infra.entity.request.preference;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Map;
/**
* 用户偏好 更新请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "PreferenceUpdateRequest", description = "用户偏好 更新请求对象")
public class PreferenceUpdateRequest implements Serializable {
@NotBlank
@Size(max = 12)
@Schema(description = "类型")
private String type;
@NotEmpty
@Schema(description = "偏好配置")
private Map<String, Object> config;
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Map;
/**
* 用户偏好 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "PreferenceVO", description = "用户偏好 视图响应对象")
public class PreferenceVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "偏好配置")
private Map<String, Object> config;
}

View File

@@ -6,6 +6,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 用户基本信息 视图响应对象
*
@@ -17,8 +19,8 @@ import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "UserBaseInfoVO", description = "用户基本信息 视图响应对象")
public class UserBaseInfoVO {
@Schema(name = "UserCollectInfoVO", description = "用户聚合信息 视图响应对象")
public class UserCollectInfoVO {
@Schema(description = "id")
private Long id;
@@ -32,4 +34,10 @@ public class UserBaseInfoVO {
@Schema(description = "头像地址")
private String avatar;
@Schema(description = "系统偏好")
private Map<String, Object> systemPreference;
@Schema(description = "提示偏好")
private Map<String, Object> tipsPreference;
}

View File

@@ -22,8 +22,8 @@ import java.util.List;
@Schema(name = "UserPermissionVO", description = "用户权限 视图响应对象")
public class UserPermissionVO {
@Schema(description = "用户基本信息")
private UserBaseInfoVO user;
@Schema(description = "用户聚合信息")
private UserCollectInfoVO user;
@Schema(description = "该用户已启用的角色")
private List<String> roles;

View File

@@ -0,0 +1,66 @@
package com.orion.ops.module.infra.enums;
import com.orion.ops.module.infra.handler.preference.model.PreferenceModel;
import com.orion.ops.module.infra.handler.preference.strategy.IPreferenceStrategy;
import com.orion.spring.SpringHolder;
import lombok.Getter;
/**
* 偏好类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 11:31
*/
@Getter
public enum PreferenceTypeEnum {
/**
* 系统偏好
*/
SYSTEM("systemPreferenceStrategy"),
/**
* 提示偏好
*/
TIPS("tipsPreferenceStrategy"),
;
PreferenceTypeEnum(String beanName) {
this.type = this.name();
this.beanName = beanName;
}
private final String type;
/**
* 策越 bean 名称
* 可能跨模块所以不用 class
*/
private final String beanName;
public static PreferenceTypeEnum of(String type) {
if (type == null) {
return null;
}
for (PreferenceTypeEnum value : values()) {
if (value.type.equals(type)) {
return value;
}
}
return null;
}
/**
* 获取策略
*
* @param <M> model
* @param <T> type
* @return IPreferenceStrategy
*/
public <M extends PreferenceModel, T extends IPreferenceStrategy<M>> T getStrategy() {
return SpringHolder.getBean(beanName);
}
}

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.infra.handler.preference.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 系统偏好模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 13:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AppPreferenceModel implements PreferenceModel {
@Schema(description = "是否使用侧边菜单")
private Boolean menu;
@Schema(description = "是否使用顶部菜单")
private Boolean topMenu;
@Schema(description = "是否显示导航栏")
private Boolean navbar;
@Schema(description = "是否显示页脚")
private Boolean footer;
@Schema(description = "是否开启多页签")
private Boolean tabBar;
@Schema(description = "是否开启色弱模式")
private Boolean colorWeak;
@Schema(description = "菜单宽度")
private Number menuWidth;
@Schema(description = "主机视图")
private String hostView;
@Schema(description = "主机秘钥视图")
private String hostKeyView;
@Schema(description = "主机身份视图")
private String hostIdentityView;
}

View File

@@ -0,0 +1,25 @@
package com.orion.ops.module.infra.handler.preference.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 提示偏好模型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 13:59
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TipsPreferenceModel implements PreferenceModel {
@Schema(description = "是否提示过系统偏好设置模态框")
private Boolean tippedSystemPreferenceModal;
}

View File

@@ -0,0 +1,36 @@
package com.orion.ops.module.infra.handler.preference.strategy;
import com.orion.ops.module.infra.handler.preference.model.AppPreferenceModel;
import org.springframework.stereotype.Component;
/**
* 系统偏好处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 13:48
*/
@Component
public class SystemPreferenceStrategy implements IPreferenceStrategy<AppPreferenceModel> {
private static final String TABLE = "table";
private static final String CARD = "card";
@Override
public AppPreferenceModel getDefault() {
return AppPreferenceModel.builder()
.menu(true)
.topMenu(false)
.navbar(true)
.footer(true)
.tabBar(true)
.menuWidth(220)
.colorWeak(false)
.hostView(TABLE)
.hostKeyView(CARD)
.hostIdentityView(CARD)
.build();
}
}

View File

@@ -0,0 +1,23 @@
package com.orion.ops.module.infra.handler.preference.strategy;
import com.orion.ops.module.infra.handler.preference.model.TipsPreferenceModel;
import org.springframework.stereotype.Component;
/**
* 提示偏好处理策略
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/10/8 15:11
*/
@Component
public class TipsPreferenceStrategy implements IPreferenceStrategy<TipsPreferenceModel> {
@Override
public TipsPreferenceModel getDefault() {
return TipsPreferenceModel.builder()
.tippedSystemPreferenceModal(false)
.build();
}
}

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.infra.service;
import com.orion.ops.module.infra.entity.request.preference.PreferenceUpdateRequest;
import com.orion.ops.module.infra.entity.vo.PreferenceVO;
import com.orion.ops.module.infra.enums.PreferenceTypeEnum;
import java.util.Map;
import java.util.concurrent.Future;
/**
* 用户偏好 服务类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
public interface PreferenceService {
/**
* 更新用户偏好
*
* @param request request
* @param partial 是否为部分更新
* @return effect
*/
Integer updatePreference(PreferenceUpdateRequest request, boolean partial);
/**
* 查询用户偏好
*
* @param type type
* @return row
*/
PreferenceVO getPreferenceByType(String type);
/**
* 获取用户偏好
*
* @param userId userId
* @param type type
* @return 偏好
*/
Future<Map<String, Object>> getPreference(Long userId, PreferenceTypeEnum type);
/**
* 删除用户偏好
*
* @param userId userId
*/
void deletePreferenceByUserId(Long userId);
}

View File

@@ -17,6 +17,7 @@ import com.orion.ops.module.infra.enums.FavoriteTypeEnum;
import com.orion.ops.module.infra.service.FavoriteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -111,6 +112,7 @@ public class FavoriteServiceImpl implements FavoriteService {
}
@Override
@Async("asyncExecutor")
public void deleteFavoriteByUserId(Long userId) {
if (userId == null) {
return;
@@ -127,6 +129,7 @@ public class FavoriteServiceImpl implements FavoriteService {
}
@Override
@Async("asyncExecutor")
public void deleteFavoriteByUserIdList(List<Long> userIdList) {
if (Lists.isEmpty(userIdList)) {
return;

View File

@@ -16,20 +16,24 @@ import com.orion.ops.module.infra.entity.domain.SystemRoleDO;
import com.orion.ops.module.infra.entity.domain.SystemRoleMenuDO;
import com.orion.ops.module.infra.entity.dto.SystemMenuCacheDTO;
import com.orion.ops.module.infra.entity.vo.SystemMenuVO;
import com.orion.ops.module.infra.entity.vo.UserBaseInfoVO;
import com.orion.ops.module.infra.entity.vo.UserCollectInfoVO;
import com.orion.ops.module.infra.entity.vo.UserPermissionVO;
import com.orion.ops.module.infra.enums.MenuStatusEnum;
import com.orion.ops.module.infra.enums.MenuTypeEnum;
import com.orion.ops.module.infra.enums.PreferenceTypeEnum;
import com.orion.ops.module.infra.enums.RoleStatusEnum;
import com.orion.ops.module.infra.service.PermissionService;
import com.orion.ops.module.infra.service.PreferenceService;
import com.orion.ops.module.infra.service.SystemMenuService;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -66,11 +70,14 @@ public class PermissionServiceImpl implements PermissionService {
@Resource
private SystemMenuService systemMenuService;
@Resource
private PreferenceService preferenceService;
@PostConstruct
@Override
public void initPermissionCache() {
long start = System.currentTimeMillis();
log.info("initRoleMenuCache-start");
log.info("initPermissionCache-start");
roleCache.clear();
menuCache.clear();
roleMenuCache.clear();
@@ -104,7 +111,7 @@ public class PermissionServiceImpl implements PermissionService {
.map(SystemRoleDO::getCode)
.ifPresent(code -> roleMenuCache.put(code, roleMenus));
});
log.info("initRoleMenuCache-end used: {}ms", System.currentTimeMillis() - start);
log.info("initPermissionCache-end used: {}ms", System.currentTimeMillis() - start);
}
@Override
@@ -186,10 +193,16 @@ public class PermissionServiceImpl implements PermissionService {
return systemMenuService.buildSystemMenuTree(menus);
}
@SneakyThrows
@Override
public UserPermissionVO getUserPermission() {
// 获取用户信息
UserBaseInfoVO user = SystemUserConvert.MAPPER.toBaseInfo(SecurityUtils.getLoginUser());
UserCollectInfoVO user = SystemUserConvert.MAPPER.toCollectInfo(SecurityUtils.getLoginUser());
Long id = user.getId();
// 获取用户系统偏好
Future<Map<String, Object>> systemPreference = preferenceService.getPreference(id, PreferenceTypeEnum.SYSTEM);
// 获取用户提示偏好
Future<Map<String, Object>> tipsPreference = preferenceService.getPreference(id, PreferenceTypeEnum.TIPS);
// 获取用户角色
List<String> roles = this.getUserEnabledRoles();
// 获取用户权限
@@ -213,6 +226,9 @@ public class PermissionServiceImpl implements PermissionService {
.collect(Collectors.toList());
}
}
// 获取异步结果
user.setSystemPreference(systemPreference.get());
user.setTipsPreference(tipsPreference.get());
// 组装数据
return UserPermissionVO.builder()
.user(user)

View File

@@ -0,0 +1,165 @@
package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.infra.dao.PreferenceDAO;
import com.orion.ops.module.infra.define.PreferenceCacheKeyDefine;
import com.orion.ops.module.infra.entity.domain.PreferenceDO;
import com.orion.ops.module.infra.entity.request.preference.PreferenceUpdateRequest;
import com.orion.ops.module.infra.entity.vo.PreferenceVO;
import com.orion.ops.module.infra.enums.PreferenceTypeEnum;
import com.orion.ops.module.infra.service.PreferenceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
/**
* 用户偏好 服务实现类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-9-27 18:37
*/
@Slf4j
@Service
public class PreferenceServiceImpl implements PreferenceService {
@Resource
private PreferenceDAO preferenceDAO;
@Override
public Integer updatePreference(PreferenceUpdateRequest request, boolean partial) {
Long userId = SecurityUtils.getLoginUserId();
String type = request.getType();
Valid.valid(PreferenceTypeEnum::of, type);
// 查询
PreferenceDO preference = preferenceDAO.of()
.wrapper(this.buildQueryWrapper(userId, type))
.getOne();
int effect;
if (preference == null) {
// 直接插入
PreferenceDO insertRecord = new PreferenceDO();
insertRecord.setUserId(userId);
insertRecord.setType(type);
insertRecord.setConfig(JSON.toJSONString(request.getConfig()));
effect = preferenceDAO.insert(insertRecord);
} else {
// 更新
PreferenceDO updateRecord = new PreferenceDO();
updateRecord.setId(preference.getId());
if (partial) {
// 部分更新
JSONObject config = JSON.parseObject(preference.getConfig());
config.putAll(request.getConfig());
updateRecord.setConfig(JSON.toJSONString(config));
} else {
// 全部更新
updateRecord.setConfig(JSON.toJSONString(request.getConfig()));
}
effect = preferenceDAO.updateById(updateRecord);
// 删除缓存
RedisStrings.delete(PreferenceCacheKeyDefine.PREFERENCE.format(userId, type));
}
return effect;
}
@Override
public PreferenceVO getPreferenceByType(String type) {
Long userId = SecurityUtils.getLoginUserId();
PreferenceTypeEnum typeEnum = Valid.valid(PreferenceTypeEnum::of, type);
Map<String, Object> config = this.getPreferenceByCache(userId, typeEnum);
// 返回
return PreferenceVO.builder()
.config(config)
.build();
}
@Override
@Async("asyncExecutor")
public Future<Map<String, Object>> getPreference(Long userId, PreferenceTypeEnum type) {
Map<String, Object> config = this.getPreferenceByCache(userId, type);
return CompletableFuture.completedFuture(config);
}
@Override
@Async("asyncExecutor")
public void deletePreferenceByUserId(Long userId) {
// 删除
int effect = preferenceDAO.deleteByUserId(userId);
log.info("PreferenceService-deletePreferenceById userId: {}, effect: {}", userId, effect);
// 删除缓存
List<String> deleteKeys = Arrays.stream(PreferenceTypeEnum.values())
.map(s -> PreferenceCacheKeyDefine.PREFERENCE.format(userId, s))
.collect(Collectors.toList());
RedisStrings.delete(deleteKeys);
}
/**
* 通过缓存获取偏好
*
* @param userId userId
* @param type type
* @return config
*/
private Map<String, Object> getPreferenceByCache(Long userId, PreferenceTypeEnum type) {
String typeValue = type.getType();
// 查询缓存 用 string 防止数据类型丢失
String key = PreferenceCacheKeyDefine.PREFERENCE.format(userId, type);
Map<String, Object> config = RedisStrings.getJson(key);
boolean setCache = Maps.isEmpty(config);
// 查询数据库
if (Maps.isEmpty(config)) {
config = preferenceDAO.of()
.wrapper(this.buildQueryWrapper(userId, typeValue))
.optionalOne()
.map(PreferenceDO::getConfig)
.map(JSON::parseObject)
.orElse(null);
}
// 初始化
if (Maps.isEmpty(config)) {
config = type.getStrategy()
.getDefault()
.toMap();
// 插入
PreferenceDO entity = new PreferenceDO();
entity.setUserId(userId);
entity.setType(typeValue);
entity.setConfig(JSON.toJSONString(config));
preferenceDAO.insert(entity);
}
// 设置缓存
if (setCache) {
RedisStrings.setJson(key, PreferenceCacheKeyDefine.PREFERENCE, config);
}
return config;
}
/**
* 构建查询 wrapper
*
* @param userId userId
* @param type type
* @return wrapper
*/
private LambdaQueryWrapper<PreferenceDO> buildQueryWrapper(Long userId, String type) {
return preferenceDAO.wrapper()
.eq(PreferenceDO::getUserId, userId)
.eq(PreferenceDO::getType, type);
}
}

View File

@@ -22,6 +22,7 @@ import com.orion.ops.module.infra.entity.vo.SystemUserVO;
import com.orion.ops.module.infra.enums.UserStatusEnum;
import com.orion.ops.module.infra.service.AuthenticationService;
import com.orion.ops.module.infra.service.FavoriteService;
import com.orion.ops.module.infra.service.PreferenceService;
import com.orion.ops.module.infra.service.SystemUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@@ -52,6 +53,9 @@ public class SystemUserServiceImpl implements SystemUserService {
@Resource
private FavoriteService favoriteService;
@Resource
private PreferenceService preferenceService;
@Resource
private RedisTemplate<String, String> redisTemplate;
@@ -169,6 +173,8 @@ public class SystemUserServiceImpl implements SystemUserService {
redisTemplate.delete(UserCacheKeyDefine.USER_INFO.format(id));
// 删除用户收藏
favoriteService.deleteFavoriteByUserId(id);
// 删除用户偏好
preferenceService.deletePreferenceByUserId(id);
return effect;
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orion.ops.module.infra.dao.PreferenceDAO">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.orion.ops.module.infra.entity.domain.PreferenceDO">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="creator" property="creator"/>
<result column="updater" property="updater"/>
<result column="deleted" property="deleted"/>
<result column="user_id" property="userId"/>
<result column="type" property="type"/>
<result column="config" property="config"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, type, config, create_time, update_time, creator, updater, deleted
</sql>
</mapper>

View File

@@ -0,0 +1,40 @@
import axios from 'axios';
type Preference = 'SYSTEM' | 'TIPS'
/**
* 用户偏好更新请求
*/
export interface PreferenceUpdateRequest {
type: Preference;
config: object;
}
/**
* 用户偏好查询响应
*/
export interface PreferenceQueryResponse {
config: object;
}
/**
* 更新用户偏好-整体
*/
export function updatePreference(request: PreferenceUpdateRequest) {
return axios.put('/infra/preference/update', request);
}
/**
* 更新用户偏好-部分
*/
export function updatePreferencePartial(request: PreferenceUpdateRequest) {
return axios.put('/infra/preference/update-partial', request);
}
/**
* 查询用户偏好
*/
export function getPreference(type: Preference) {
return axios.get<PreferenceQueryResponse>('/infra/preference/get', { params: { type } });
}

View File

@@ -4,9 +4,9 @@
<div v-for="option in options" :key="option.name" class="option-wrapper">
<!-- 偏好项 -->
<span>{{ option.name }}</span>
<!-- input -->
<!-- 偏好值 -->
<form-wrapper :name="option.key"
:type="option.type"
:type="option.type as string"
:default-value="option.defaultVal"
:options="option.options"
@input-change="handleChange" />
@@ -19,6 +19,8 @@
import { useAppStore } from '@/store';
import FormWrapper from './form-wrapper.vue';
import { RadioOption } from '@arco-design/web-vue/es/radio/interface';
import { updatePreferencePartial } from '@/api/user/preference';
import { Message } from '@arco-design/web-vue';
interface OptionsProps {
name: string;
@@ -37,6 +39,7 @@
},
},
});
const appStore = useAppStore();
/**
@@ -46,10 +49,6 @@
key: string;
value: unknown;
}) => {
// 色弱模式
if (key === 'colorWeak') {
document.body.style.filter = value ? 'invert(80%)' : 'none';
}
// 顶部菜单
if (key === 'topMenu') {
appStore.updateSettings({
@@ -57,9 +56,21 @@
});
}
// 修改配置
appStore.updateSettings({ [key]: value });
// TODO 同步偏好
const updateConfig = { [key]: value };
appStore.updateSettings(updateConfig);
// 同步偏好
Message.clear();
const loading = Message.loading('同步中...');
try {
await updatePreferencePartial({
type: 'SYSTEM',
config: updateConfig
});
Message.success('同步成功');
} catch (e) {
} finally {
loading.close();
}
};
</script>

View File

@@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import { AppState } from './types';
import TimeScale from 'echarts/types/src/scale/Time';
const defaultConfig: AppState = {
// 应用设置
@@ -36,16 +37,17 @@ export default defineStore('app', {
},
actions: {
// 修改颜色主题
toggleTheme(dark: boolean) {
this.updateSettings({
theme: dark ? 'dark' : 'light'
});
document.body.setAttribute('arco-theme', dark ? 'dark' : 'light');
},
// 更新配置
updateSettings(partial: Partial<AppState>) {
// 主题颜色
if (partial.theme !== undefined) {
document.body.setAttribute('arco-theme', partial.theme);
}
// 色弱模式
if (partial.colorWeak !== undefined) {
document.body.style.filter = partial.colorWeak ? 'invert(80%)' : 'none';
}
// 修改配置
this.$patch(partial as object);
},
},

View File

@@ -4,7 +4,7 @@ import { clearToken, setToken } from '@/utils/auth';
import { md5 } from '@/utils';
import { removeRouteListener } from '@/utils/route-listener';
import { UserState } from './types';
import { useMenuStore, useTabBarStore } from '@/store';
import { useAppStore, useMenuStore, useTabBarStore } from '@/store';
export default defineStore('user', {
state: (): UserState => ({
@@ -30,7 +30,6 @@ export default defineStore('user', {
// 获取用户信息
async info() {
// TODO 查询偏好
const { data } = await getUserPermission();
// 设置用户信息
this.setInfo({
@@ -41,8 +40,9 @@ export default defineStore('user', {
roles: data.roles,
permission: data.permissions,
});
// TODO 设置用户偏好
// 设置用户偏好
const appStore = useAppStore();
appStore.updateSettings(data.user.systemPreference);
},
// 登录