fix:修复页面书签数据缓存问题

This commit is contained in:
fanxb 2020-05-10 14:07:11 +08:00
parent 2be78755f4
commit 6e91e49404
13 changed files with 138 additions and 185 deletions

View File

@ -40,6 +40,6 @@ public class PinyinUpdateConsumer implements RedisConsumer {
bookmarkDao.updateSearchKey(bookmarks.get(i).getBookmarkId(), resList.get(i));
}
//更新本用户书签更新时间
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(bookmarks.get(0).getUserId()));
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_VERSION, new UserBookmarkUpdate(bookmarks.get(0).getUserId()));
}
}

View File

@ -74,7 +74,7 @@ public class BookmarkServiceImpl implements BookmarkService {
dealBookmark(userId, elements.get(i), path, sortBase + count + i - 1, bookmarks);
}
}
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(userId, System.currentTimeMillis()));
updateVersion(userId);
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, bookmarks);
RedisUtil.addToMq(RedisConstant.BOOKMARK_PINYIN_CHANGE, bookmarks);
@ -172,8 +172,8 @@ public class BookmarkServiceImpl implements BookmarkService {
bookmarkDao.deleteUserBookmark(userId, bookmarkIdList);
}
set.addAll(bookmarkIdList.stream().map(String::valueOf).collect(Collectors.toSet()));
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(userId, System.currentTimeMillis()));
RedisUtil.addToMq(RedisConstant.BOOKMARK_DELETE_ES, set);
updateVersion(userId);
}
@Override
@ -191,7 +191,7 @@ public class BookmarkServiceImpl implements BookmarkService {
if (bookmark.getType() == 0) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, Collections.singleton(bookmark));
}
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(userId, System.currentTimeMillis()));
updateVersion(userId);
return bookmark;
}
@ -204,7 +204,7 @@ public class BookmarkServiceImpl implements BookmarkService {
if (bookmark.getType() == 0) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, Collections.singleton(bookmark));
}
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(userId, System.currentTimeMillis()));
updateVersion(userId);
}
@ -225,7 +225,7 @@ public class BookmarkServiceImpl implements BookmarkService {
}
//更新被移动节点的path和sort
bookmarkDao.updatePathAndSort(userId, body.getBookmarkId(), body.getTargetPath(), body.getSort());
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(userId, System.currentTimeMillis()));
updateVersion(userId);
}
@Override
@ -239,5 +239,16 @@ public class BookmarkServiceImpl implements BookmarkService {
return esUtil.search(EsConstant.BOOKMARK_INDEX, builder, BookmarkEs.class);
}
/**
* 功能描述: 向mq发送消息通知数据更新
*
* @param userId userId
* @author fanxb
* @date 2020/5/10 12:07
*/
private void updateVersion(int userId) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_VERSION, userId);
}
}

View File

@ -61,7 +61,7 @@ public class PinYinServiceImpl implements PinYinService {
i = bookmarks.get(SIZE - 1).getBookmarkId();
}
//更新所有用户的上次刷新时间
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_TIME, new UserBookmarkUpdate(-1, System.currentTimeMillis()));
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_VERSION, new UserBookmarkUpdate(-1, System.currentTimeMillis()));
}
@Override

View File

@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* @author fanxb
* @date 2020/1/26 上午11:54
*/
@MqConsumer(RedisConstant.BOOKMARK_UPDATE_TIME)
@MqConsumer(RedisConstant.BOOKMARK_UPDATE_VERSION)
public class UserInfoUpdateConsumer implements RedisConsumer {
@Autowired
@ -20,11 +20,11 @@ public class UserInfoUpdateConsumer implements RedisConsumer {
@Override
public void deal(String message) {
UserBookmarkUpdate item = JSON.parseObject(message, UserBookmarkUpdate.class);
if (item.getUserId() == -1) {
userDao.updateAllBookmarkUpdateTime(item.getUpdateTime());
int userId = Integer.parseInt(message);
if (userId == -1) {
userDao.updateAllBookmarkUpdateVersion();
} else {
userDao.updateLastBookmarkUpdateTime(item);
userDao.updateLastBookmarkUpdateTime(userId);
}
}
}

View File

@ -123,8 +123,8 @@ public interface UserDao {
* @author fanxb
* @date 2020/1/26 下午3:47
*/
@Update("update user set bookmarkChangeTime=#{updateTime} where userId=#{userId}")
void updateLastBookmarkUpdateTime(UserBookmarkUpdate item);
@Update("update user set version=version+1 where userId=#{userId}")
void updateLastBookmarkUpdateTime(int userId);
/**
* 功能描述: 更新所有用户的更新时间
@ -133,6 +133,6 @@ public interface UserDao {
* @author 123
* @date 2020/3/29 18:18
*/
@Update("update user set bookmarkChangeTime=#{time}")
void updateAllBookmarkUpdateTime(long time);
@Update("update user set version=version+1")
void updateAllBookmarkUpdateVersion();
}

View File

@ -14,7 +14,7 @@ public class RedisConstant {
/**
* 某用户书签数据更新时间,该队列左进右出
*/
public static final String BOOKMARK_UPDATE_TIME = "bookmark_update_time";
public static final String BOOKMARK_UPDATE_VERSION = "bookmark_update_version";
/**
* 某个用户上传了文件夹需要进行书签转化
*/

View File

@ -1,6 +1,7 @@
package com.fanxb.bookmark.common.entity;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@ -22,28 +23,12 @@ public class User {
private String email;
private String newEmail;
private String icon;
@JsonIgnore
@JSONField(serialize = false)
private String password;
private long createTime;
private long lastLoginTime;
/**
* 上次更新书签时间
* 书签同步版本
*/
private long bookmarkChangeTime;
public static void main(String[] args) {
User user1 = new User();
User user2 = new User();
Map<User, User> map = new HashMap<>();
map.put(user1, user2);
NewUserObj obj = new NewUserObj();
obj.setMap(map);
NewUserObj newObj = JSON.parseObject(JSON.toJSONString(obj), NewUserObj.class);
System.out.println(newObj.getMap().get(user1));
}
@Data
public static class NewUserObj {
private Map<User, User> map;
}
private int version;
}

View File

@ -133,7 +133,7 @@ public class LoginFilter implements Filter {
UserContextHolder.set(context);
return true;
} catch (Exception e) {
log.error("jwt解密失败{}", jwt, e);
log.error("jwt解密失败{},原因:{}", jwt, e.getMessage());
return false;
}
}

View File

@ -1,9 +1,15 @@
package com.fanxb.bookmark.web;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -22,4 +28,17 @@ public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
// 1.定义一个converters转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2.添加fastjson的配置信息比如: 是否需要格式化返回的json数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
// 3.在converter中添加配置信息
fastConverter.setFastJsonConfig(fastJsonConfig);
// 5.返回HttpMessageConverters对象
return new HttpMessageConverters(fastConverter);
}
}

View File

@ -0,0 +1,4 @@
update bookmark.user
set bookmarkChangeTime=0;
ALTER TABLE `bookmark`.`user`
CHANGE COLUMN `bookmarkChangeTime` `version` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `lastLoginTime`;

View File

@ -1,8 +1,8 @@
import React from "react";
import { Input, Select, Button, message } from "antd";
import styles from "./index.module.less";
import MainLayout from "../../../layout/MainLayout";
import httpUtil from "../../../util/httpUtil";
import React from 'react';
import { Input, Select, Button, message } from 'antd';
import styles from './index.module.less';
import MainLayout from '../../../layout/MainLayout';
import httpUtil from '../../../util/httpUtil';
const { TextArea } = Input;
const { Option } = Select;
@ -11,15 +11,15 @@ class Feedback extends React.Component {
constructor(props) {
super(props);
this.state = {
content: "",
content: '',
//1:bug2
type: "bug"
type: 'bug'
};
}
async submit() {
await httpUtil.put("/feedback", this.state);
message.success("创建成功");
await httpUtil.put('/feedback', this.state);
message.success('创建成功');
this.props.history.goBack();
}
@ -30,29 +30,19 @@ class Feedback extends React.Component {
<div>反馈页面</div>
<div className={styles.main}>
<div>
<Select
defaultValue={type}
style={{ width: 140 }}
onChange={value => this.setState({ type: value })}
>
<Select defaultValue={type} style={{ width: 140 }} onChange={value => this.setState({ type: value })}>
<Option value="bug">BUG反馈</Option>
<Option value="advice">功能建议</Option>
</Select>
<span className={styles.tips}>
建议通过
<a
href="https://github.com/FleyX/bookmark/issues"
target="github"
>
<a href="https://github.com/FleyX/bookmark/issues" target="github">
github
</a>
反馈
</span>
</div>
<TextArea
rows="8"
onChange={e => this.setState({ content: e.target.value })}
></TextArea>
<TextArea rows="8" onChange={e => this.setState({ content: e.target.value })}></TextArea>
<div>
<Button type="primary" onClick={this.submit.bind(this)}>
确认

View File

@ -1,20 +1,15 @@
import React from "react";
import { Tree, Empty, Button, Spin, Modal } from "antd";
import MainLayout from "../../../layout/MainLayout";
import styles from "./index.module.less";
import { batchDelete, renderTreeNodes, onDrop } from "./function.js";
import {
cacheBookmarkData,
getBookmarkList,
checkCacheStatus,
clearCache
} from "../../../util/cacheUtil";
import httpUtil from "../../../util/httpUtil";
import AddModal from "./AddModal";
import Search from "../../../components/Search";
import React from 'react';
import { Tree, Empty, Button, Spin, Modal } from 'antd';
import MainLayout from '../../../layout/MainLayout';
import styles from './index.module.less';
import { batchDelete, renderTreeNodes, onDrop } from './function.js';
import { cacheBookmarkData, getBookmarkList, checkCacheStatus, clearCache } from '../../../util/cacheUtil';
import httpUtil from '../../../util/httpUtil';
import AddModal from './AddModal';
import Search from '../../../components/Search';
import * as action from "../../../redux/action/BookmarkTreeOverview";
import { connect } from "react-redux";
import * as action from '../../../redux/action/BookmarkTreeOverview';
import { connect } from 'react-redux';
const { confirm } = Modal;
@ -30,11 +25,9 @@ function mapDispatchToProps(dispatch) {
addNode: (item, e) => dispatch(action.addNode(item, e)),
editNode: (item, e) => dispatch(action.editNode(item, e)),
changeIsInit: value => dispatch(action.changeIsInit(value)),
changeCheckedKeys: (keys, nodes) =>
dispatch(action.changeCheckedKeys(keys, nodes)),
changeCheckedKeys: (keys, nodes) => dispatch(action.changeCheckedKeys(keys, nodes)),
changeExpandedKeys: keys => dispatch(action.changeExpandedKeys(keys)),
changeCurrentClickItem: item =>
dispatch(action.changeCurrentClickItem(item)),
changeCurrentClickItem: item => dispatch(action.changeCurrentClickItem(item)),
changeLoadedKeys: keys => dispatch(action.changeLoadedKeys(keys))
};
}
@ -55,12 +48,12 @@ class OverView extends React.Component {
}
async componentWillMount() {
await httpUtil.get("/user/loginStatus");
this.state.timer = setInterval(this.checkCache.bind(this), 10000);
await httpUtil.get('/user/loginStatus');
this.state.timer = setInterval(this.checkCache.bind(this), 30000);
setTimeout(this.checkCache.bind(this), 5000);
this.props.refresh();
await cacheBookmarkData();
this.props.updateTreeData(getBookmarkList(""));
this.props.updateTreeData(getBookmarkList(''));
this.props.changeIsInit(true);
}
@ -73,8 +66,8 @@ class OverView extends React.Component {
if (!(await checkCacheStatus())) {
this.state.showDialog = true;
confirm({
title: "缓存过期",
content: "书签数据有更新,是否立即刷新?",
title: '缓存过期',
content: '书签数据有更新,是否立即刷新?',
async onOk() {
_this.state.showDialog = false;
await clearCache();
@ -94,9 +87,9 @@ class OverView extends React.Component {
const { loadedKeys } = this.props;
return new Promise(resolve => {
const item = e.props.dataRef;
const newPath = item.path + "." + item.bookmarkId;
const newPath = item.path + '.' + item.bookmarkId;
item.children = getBookmarkList(newPath);
this.props.updateTreeData([...getBookmarkList("")]);
this.props.updateTreeData([...getBookmarkList('')]);
loadedKeys.push(item.bookmarkId.toString());
this.props.changeLoadedKeys(loadedKeys);
resolve();
@ -111,9 +104,7 @@ class OverView extends React.Component {
const { expandedKeys, changeExpandedKeys } = this.props;
const item = e.node.props.dataRef;
if (item.type === 0) {
window.open(
item.url.startsWith("http") ? item.url : "http://" + item.url
);
window.open(item.url.startsWith('http') ? item.url : 'http://' + item.url);
} else {
const id = item.bookmarkId.toString();
const index = expandedKeys.indexOf(id);
@ -131,16 +122,7 @@ class OverView extends React.Component {
}
render() {
const {
isEdit,
setIsEdit,
treeData,
addNode,
isInit,
expandedKeys,
checkedKeys,
loadedKeys
} = this.props;
const { isEdit, setIsEdit, treeData, addNode, isInit, expandedKeys, checkedKeys, loadedKeys } = this.props;
const { isLoading } = this.state;
const { changeExpandedKeys } = this.props;
return (
@ -150,54 +132,27 @@ class OverView extends React.Component {
<div className={styles.header}>
<div className={styles.left}>
<span className={styles.myTree}>我的书签树</span>
<Button
size="small"
type="primary"
icon="sync"
shape="circle"
onClick={this.refreshTree.bind(this, null)}
/>
<Button
size="small"
type="primary"
icon="plus"
shape="circle"
onClick={addNode.bind(this, null)}
/>
<Button size="small" type="primary" icon="sync" shape="circle" onClick={this.refreshTree.bind(this, null)} />
<Button size="small" type="primary" icon="plus" shape="circle" onClick={addNode.bind(this, null)} />
{expandedKeys.length > 0 ? (
<Button
type="primary"
size="small"
onClick={changeExpandedKeys.bind(this, [])}
>
<Button type="primary" size="small" onClick={changeExpandedKeys.bind(this, [])}>
收起
</Button>
) : null}
<a
className={styles.help}
href="https://github.com/FleyX/bookmark/blob/master/README.md"
>
<a className={styles.help} href="https://github.com/FleyX/bookmark/blob/master/README.md">
使用帮助
</a>
</div>
<div className={styles.right}>
{isEdit ? (
<React.Fragment>
<Button
size="small"
type="danger"
onClick={batchDelete.bind(this)}
>
<Button size="small" type="danger" onClick={batchDelete.bind(this)}>
删除选中
</Button>
</React.Fragment>
) : null}
<Button
size="small"
type="primary"
onClick={setIsEdit.bind(this, !isEdit)}
>
{isEdit ? "完成" : "编辑"}
<Button size="small" type="primary" onClick={setIsEdit.bind(this, !isEdit)}>
{isEdit ? '完成' : '编辑'}
</Button>
</div>
</div>
@ -218,9 +173,7 @@ class OverView extends React.Component {
>
{renderTreeNodes.call(this, treeData)}
</Tree>
{isInit && treeData.length === 0 ? (
<Empty description="还没有数据" />
) : null}
{isInit && treeData.length === 0 ? <Empty description="还没有数据" /> : null}
</Spin>
<AddModal />
</div>

View File

@ -1,44 +1,29 @@
/* eslint-disable no-undef */
import httpUtil from "./httpUtil";
import config from "./config";
/**
* web版本
*/
export const WEB_VERSION = "webVersion";
import httpUtil from './httpUtil';
/**
* 全部书签数据key
*/
export const TREE_LIST_KEY = "treeListData";
export const TREE_LIST_KEY = 'treeListData';
/**
* 获取全部书签时间
* 当前用户书签版本
*/
export const TREE_LIST_TIME_KEY = "treeListDataTime";
/**
* 书签数据所属用户
*/
export const TREE_LIST_USER_ID = "treeListDataUserId";
export const TREE_LIST_VERSION_KEY = 'treeDataVersion';
/**
* 缓存书签数据
*/
export async function cacheBookmarkData() {
let currentId = JSON.parse(window.atob(window.token.split(".")[1])).userId;
let cacheId = await localforage.getItem(TREE_LIST_USER_ID);
let webVersion = await localforage.getItem(WEB_VERSION);
if ((currentId && currentId !== cacheId) || config.version !== webVersion) {
await localforage.setItem(WEB_VERSION, config.version);
await clearCache();
}
let res = await localforage.getItem(TREE_LIST_KEY);
let key = getCacheKey();
let res = await localforage.getItem(key);
//如果没有缓存
if (!res) {
res = await httpUtil.get("/bookmark/currentUser");
if (!res[""]) {
res[""] = [];
res = await httpUtil.get('/bookmark/currentUser');
if (!res['']) {
res[''] = [];
}
await localforage.setItem(TREE_LIST_KEY, res);
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
await localforage.setItem(TREE_LIST_USER_ID, currentId);
let version = (await httpUtil.get('/user/currentUserInfo')).version;
await localforage.setItem(key, res);
await localforage.setItem(TREE_LIST_VERSION_KEY, version);
}
window[TREE_LIST_KEY] = res;
}
@ -59,27 +44,26 @@ export function getBookmarkList(path) {
* @return 返回true说明未过期否则说明过期了
*/
export async function checkCacheStatus() {
let date = await localforage.getItem(TREE_LIST_TIME_KEY, Date.now());
let userInfo = await httpUtil.get("/user/currentUserInfo");
return !date || date > userInfo.bookmarkChangeTime;
let version = await localforage.getItem(TREE_LIST_VERSION_KEY);
let realVersion = (await httpUtil.get('/user/currentUserInfo')).version;
return version >= realVersion;
}
/**
* 清楚缓存数据
*/
export async function clearCache() {
await localforage.removeItem(TREE_LIST_KEY);
await localforage.removeItem(TREE_LIST_TIME_KEY);
await localforage.removeItem(TREE_LIST_USER_ID);
await localforage.removeItem(getCacheKey());
await localforage.removeItem(TREE_LIST_VERSION_KEY);
}
/**
* 更新本地缓存数据的时间
* 更新本地缓存
*/
export async function updateCurrentChangeTime() {
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
await localforage.setItem(TREE_LIST_KEY, window[TREE_LIST_KEY]);
let version = await localforage.getItem(TREE_LIST_VERSION_KEY);
await localforage.setItem(TREE_LIST_VERSION_KEY, version + 1);
await localforage.setItem(getCacheKey(), window[TREE_LIST_KEY]);
}
/**
@ -91,13 +75,13 @@ export async function addNode(currentNode, node) {
debugger;
let treeDataMap = window[TREE_LIST_KEY];
if (currentNode) {
let key = currentNode.path + "." + currentNode.bookmarkId;
let key = currentNode.path + '.' + currentNode.bookmarkId;
if (!treeDataMap[key]) {
treeDataMap[key] = [];
}
treeDataMap[key].push(node);
} else {
treeDataMap[""].push(node);
treeDataMap[''].push(node);
}
await updateCurrentChangeTime();
}
@ -117,7 +101,7 @@ export async function deleteNodes(nodeList) {
}
//如果是文件夹还是把他的子节点删除
if (item.type === 1) {
let key = item.path + "." + item.bookmarkId;
let key = item.path + '.' + item.bookmarkId;
Object.keys(data).forEach(one => {
if (one.startsWith(key)) {
delete data[one];
@ -147,7 +131,7 @@ export async function moveNode(info) {
const body = {
bookmarkId: current.bookmarkId,
sourcePath: current.path,
targetPath: "",
targetPath: '',
//-1 表示排在最后
sort: -1
};
@ -166,14 +150,13 @@ export async function moveNode(info) {
}
} else {
//移动到一个文件夹下面
body.targetPath = target.path + "." + target.bookmarkId;
body.targetPath = target.path + '.' + target.bookmarkId;
let targetList = data[body.targetPath];
if (!targetList) {
targetList = [];
data[body.targetPath] = targetList;
}
body.sort =
targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1;
body.sort = targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1;
targetList.push(current);
}
//更新节点的path和对应子节点path
@ -183,9 +166,9 @@ export async function moveNode(info) {
if (body.sourcePath !== body.targetPath) {
let keys = Object.keys(data);
//旧路径
let oldPath = body.sourcePath + "." + current.bookmarkId;
let oldPath = body.sourcePath + '.' + current.bookmarkId;
//新路径
let newPath = body.targetPath + "." + current.bookmarkId;
let newPath = body.targetPath + '.' + current.bookmarkId;
keys.forEach(item => {
if (!item.startsWith(oldPath)) {
return;
@ -219,12 +202,20 @@ export async function keySearch(content) {
if (item.searchKey.indexOf(content) > -1) {
res.push(item);
if (res.length >= 12) {
console.info("搜索耗时:" + (Date.now() - time1));
console.info('搜索耗时:' + (Date.now() - time1));
return res;
}
}
}
}
console.info("搜索耗时:" + (Date.now() - time1));
console.info('搜索耗时:' + (Date.now() - time1));
return res;
}
/**
* 获取localstore缓存的key
*/
export function getCacheKey() {
let currentId = JSON.parse(window.atob(window.token.split('.')[1])).userId;
return currentId + TREE_LIST_KEY;
}