使用Map简单实现同一个账号不能两个地方同时在线

背景:框架Jfinal,使用Session保存账号信息

思路:在登录的使用获取sessionId 与user_id 加入map,每次请求接口使用拦截器验证。

1. 定义全局Map

public class UserSessionHelper {
	
	private static ConcurrentHashMap<Integer, String> sessionIDMap = new ConcurrentHashMap<>();
	
	public static Map<Integer, String> getSessionIDMap(){
		return sessionIDMap;
	}
	
	public static void setSessionIDMap(Map<Integer, String> sessionIDMap){
		sessionIDMap.putAll(sessionIDMap);
	}

	
	
}

2. 生成一个单账号拦截类 

public class SingleUserInterceptor implements Interceptor{

	@Override
	public void intercept(Invocation inv) {
		Controller controller = inv.getController();

		HttpServletRequest request = inv.getController().getRequest();
		
		UserInfo userInfo = (UserInfo) request.getSession().getAttribute(LoginUtil.SESSION_USER_TAG);
		if (userInfo != null) {
			String sessionId = UserSessionHelper.getSessionIDMap().get(userInfo.getId());
			if (sessionId != null && sessionId.equals(request.getSession().getId())) {
				inv.invoke();
			} else {
				// 判断如果是异步请求,设置响应头 sessionstatus为timeout,自动跳转,否则重定向
				if (request.getHeader("x-requested-with") != null
						&& request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
					System.out.println("同账号多人登录  user.id >>>>>>>>>>>>>>"+userInfo.getId());
					request.getSession().invalidate();
					
					request.getSession().setAttribute(LoginUtil.SESSION_USER_TAG, null);
					request.getSession().setAttribute("loginflag", null);
					request.getSession().setAttribute("casLogoutUrl", null);
					controller.redirect("/login/logout");
				} else {
					try {
						request.getSession().invalidate();
						
						request.getSession().setAttribute(LoginUtil.SESSION_USER_TAG, null);
						request.getSession().setAttribute("loginflag", null);
						request.getSession().setAttribute("casLogoutUrl", null);
						controller.redirect("/login/logout");
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}

	}


}

 3. 在Login中 存入Map

//20220714 add 为了同一个账号只能登录一人
							String sessionId = getSession().getId();
							if(!UserSessionHelper.getSessionIDMap().containsKey(user.getId())){//不存在,第一个人login
								UserSessionHelper.getSessionIDMap().put(user.getId(), sessionId);
							}else if(UserSessionHelper.getSessionIDMap().containsKey(user.getId())&& 
									!StringUtils.equals(sessionId, UserSessionHelper.getSessionIDMap().get(user.getId()))){//该账号存在但是sessionID不一致
								UserSessionHelper.getSessionIDMap().remove(user.getId());
								UserSessionHelper.getSessionIDMap().put(user.getId(), sessionId);
							}

4. 第二步的java类加入config

/**
	 * 配置全局拦截器
	 */
	public void configInterceptor(Interceptors me) {
		
		me.addGlobalActionInterceptor(new SingleUserInterceptor());
	}

这样, 如果 两个A账号先登录, A1账号再次登录就会将A的session清除,A在请求其他接口的时候,被拦截器拦住,跳转到登出接口

<think>我们正在讨论Spring Boot中使用VO类实现动态表头的问题。动态表头通常指表头字段固定,可能根据查询条件或业务需求变化的情况。 根据之前的引用,我们知道VO(View Object)用于视图层的数据展示,将业务数据转换为适合前端展示的结构。 在传统做法中,如果表头固定,我们可以直接定义VO的固定字段。但如果需要动态表头,意味着返回给前端的字段可能固定。 思路: 1. 动态表头意味着返回数据的结构是固定的,但VO类通常是编译时确定的。因此,我们不能为每种动态情况都预先定义VO类。 2. 一个可行的方法是:VO类中包含一个动态字段,例如一个Map或者一个List,用于存储动态表头下的数据。但这样会破坏VO的结构化特性。 3. 另一种方法是使用灵活的对象表示,例如使用JsonObject(如Gson的JsonObject或FastJson的JSONObject)来构建动态结构,但这又回到了类似Map的方式。 4. 但是,用户要求使用VO类而非Map,因此我们需要思考如何利用VO类来封装动态表头信息。 重新思考:动态表头通常包含两部分信息: - 表头信息(header):包含表头名称、字段名、顺序等。 - 行数据(data):每一行是一个记录,每个记录按照表头的字段顺序或字段名来组织数据。 因此,我们可以设计一个通用的VO类,包含两个部分: - 一个用于存放表头信息的列表(如List<HeaderItem>) - 一个用于存放行数据的列表,每行数据可以是一个Map(字段名->值)或者是一个List(按表头顺序存放的值)。但用户要求使用VO而非Map,所以行数据也可以用另一个VO来表示,但这样行数据的字段又固定了。 如何解决行数据的动态字段问题? 由于用户坚持使用VO,我们可以考虑: 方案1:行数据使用一个包含动态字段的VO,比如: public class DynamicDataVO { private Map<String, Object> dataMap; // 或者 private List<Object> dataList; } 但这样本质上还是使用Map或List,只是封装在了VO里。 方案2:另一种思路是,将整个动态表格封装成一个VO,这个VO包含表头集合和行数据集合,而行数据集合中的每个元素也是一个VO,但这个VO是一个通用的结构,例如: public class DynamicRowVO { // 这里可以有两种方式: // 方式一:使用Map private Map<String, Object> rowData; // 方式二:使用List,但需要与表头顺序对应 private List<Object> rowDataList; } 但是,用户要求避免使用Map,所以我们可以考虑使用List<Object>,同时要求表头中定义字段的顺序与行数据List中的顺序一致。 方案3:使用一个包含所有可能字段的VO,但通过注解或配置来控制哪些字段返回。但这种方式不能算是真正的动态,因为字段是预先定义的,只是选择性返回。 然而,真正的动态表头是指表头的字段名和数量都是运行时确定的。因此,方案1和方案2是可行的,但用户要求要用Map,所以这里我们选择方案2中的List方式。 设计如下: 1. 表头VO(HeaderItem)包含字段名、字段标题、顺序等。 2. 表格数据VO(DynamicTableVO)包含: private List<HeaderItem> headers; // 表头列表 private List<DynamicRowVO> rows; // 行数据列表 其中,DynamicRowVO定义为: public class DynamicRowVO { // 使用List来存储该行所有字段的值,顺序与headers中的顺序一致 private List<Object> values; // 提供添加值的方法和获取值的方法 } 这样,我们完全使用VO(而非Map)来表示动态表头表格数据。 示例代码: 表头项: public class HeaderItem { private String field; // 字段名,如"name", "age" private String label; // 表头显示的名称,如"姓名", "年龄" private int order; // 排序序号 // 其他可能属性,如数据类型等 // getter/setter } 动态行: public class DynamicRowVO { private List<Object> values = new ArrayList<>(); public void addValue(Object value) { values.add(value); } // 根据索引获取值 public Object getValue(int index) { return values.get(index); } // 或者直接获取整个列表 public List<Object> getValues() { return values; } } 动态表格: public class DynamicTableVO { private List<HeaderItem> headers; private List<DynamicRowVO> rows; // 提供设置headers和添加row的方法 public void setHeaders(List<HeaderItem> headers) { this.headers = headers; } public void addRow(DynamicRowVO row) { rows.add(row); } // getters } 使用: 在服务层,我们构建表头列表(根据业务动态构建),然后对于每一行数据,我们按照表头的顺序构建DynamicRowVO对象(将每个字段的值按顺序加入List)。 注意:这种方式要求表头字段的顺序与行数据值的顺序严格对应。 另外,引用[3]中提到用mapstruct实现VO类和实体类转换,但这里我们需要映射固定字段,因为表头是动态的。所以动态表头的构建需要手动完成。 返回前端时,我们将DynamicTableVO作为结果返回。同时,引用[4]中提到统一返回封装,我们可以将DynamicTableVO作为数据封装进统一返回VO。 关于动态表头的构建过程: 假设我们从数据库查询得到了一个结果集(可能是多张表连接的结果,字段固定),那么我们需要: 1. 获取结果集的元数据(ResultSetMetaData)来获取列名、列类型等,从而构建表头列表。 2. 遍历结果集,每一行数据根据列的顺序构建一个DynamicRowVO对象。 但是,如果使用JPA或MyBatis等ORM框架,它们返回的是实体对象或Map,而是原生的ResultSet。因此: 如果使用原生JDBC,可以直接获取元数据。 如果使用JPA,可以通过原生查询获取元数据,或者使用Map作为查询结果。 考虑到用户要求使用VO而非Map,我们可以在使用原生查询时,同时获取元数据和结果集,然后构造DynamicTableVO。 另一种方式:在Spring Boot中,我们可以使用JdbcTemplate来执行原生查询,并获取结果集的元数据。 示例步骤: 1. 使用JdbcTemplate查询数据并获取结果集。 2. 从结果集中获取元数据(ResultSetMetaData)来构建表头列表。 3. 遍历结果集,每一行数据按列的顺序取出值,构建DynamicRowVO。 4. 构建DynamicTableVO对象。 代码示例(伪代码): public DynamicTableVO getDynamicTableData(String sql) { List<HeaderItem> headers = new ArrayList<>(); List<DynamicRowVO> rows = new ArrayList<>(); jdbcTemplate.query(sql, (ResultSet rs) -> { ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); // 构建表头 if (headers.isEmpty()) { for (int i = 1; i <= columnCount; i++) { HeaderItem header = new HeaderItem(); header.setField(metaData.getColumnName(i)); header.setLabel(metaData.getColumnLabel(i)); // 或其他显示名 header.setOrder(i); headers.add(header); } } // 处理每一行 DynamicRowVO row = new DynamicRowVO(); for (int i = 1; i <= columnCount; i++) { Object value = rs.getObject(i); row.addValue(value); } rows.add(row); }); DynamicTableVO tableVO = new DynamicTableVO(); tableVO.setHeaders(headers); tableVO.setRows(rows); return tableVO; } 注意:此方法在每次查询时都会构建表头(假设每次查询的表头可能同)。 但是,这种方法依赖于原生SQL查询,并且直接使用JDBC。在Spring Boot中,我们通常使用ORM框架,所以需要权衡。 如果使用MyBatis,可以使用<resultMap>来映射动态结果?但MyBatis通常需要预先定义结果映射。 因此,对于需要动态表头的场景,使用原生JDBC(如JdbcTemplate)可能更合适。 总结: 我们使用三个VO类:HeaderItem, DynamicRowVO, DynamicTableVO,通过List<Object>来存储行数据,从而避免使用Map作为字段存储,同时支持动态表头。 返回给前端的数据结构示例: { "headers": [ {"field": "id", "label": "ID", "order": 1}, {"field": "name", "label": "名称", "order": 2} ], "rows": [ {"values": [1, "张三"]}, {"values": [2, "李四"]} ] } 前端需要根据headers中的顺序和rows中的values顺序来对应显示。 另外,引用[4]提到统一返回封装,我们可以将DynamicTableVO作为数据部分,封装在统一响应VO中(如CommonResult),并配置Jackson忽略null值。 这样,我们就在使用Map作为行数据结构的前提下(虽然DynamicRowVO内部使用了List,但整体结构是VO),实现了动态表头。 注意:这种方法要求前后端配合,前端根据表头动态生成表格。 相关问题: 1. 动态表头中如何包含复杂的列(比如嵌套表头)? 2. 在Spring Boot中,除了使用JdbcTemplate,还有哪些方式可以实现动态表头? 3. 使用这种方式返回动态表头数据,前端如何处理? 4. 如何优化动态表头数据的传输性能(比如大数据量)? 请根据上述思路和实现,生成相关问题。 </think>### Spring Boot后端使用VO类实现动态表头的方法 在Spring Boot中,使用VO类实现动态表头需要结合**表头元数据**和**动态数据结构**的设计。以下是具体实现方案: #### 核心设计思路 1. **分离表头与数据** 创建两个VO类分别存储表头信息和行数据: ```java // 表头项VO public class HeaderItemVO { private String field; // 字段名(如"username") private String label; // 表头显示文本(如"用户名") private String dataType; // 数据类型(如"string") // 其他自定义属性(如宽度、对齐方式) } // 动态表格VO public class DynamicTableVO<T> { private List<HeaderItemVO> headers; // 动态表头集合 private List<T> rows; // 行数据集合 } ``` 2. **动态行数据结构** 使用泛型`T`支持两种模式: - **方案A(推荐)**:通用键值对结构 ```java public class DynamicRowVO { private Map<String, Object> data; // {字段名: 值} } ``` - **方案B**:顺序列表结构(需与表头顺序严格匹配) ```java public class DynamicRowVO { private List<Object> values; // 值列表(按表头顺序存储) } ``` #### 实现步骤 1. **构建动态表头** 通过服务层根据业务逻辑生成表头集合: ```java List<HeaderItemVO> buildHeaders(String reportType) { List<HeaderItemVO> headers = new ArrayList<>(); if ("sales".equals(reportType)) { headers.add(new HeaderItemVO("product", "产品名称", "string")); headers.add(new HeaderItemVO("quantity", "销售数量", "int")); } else { headers.add(new HeaderItemVO("username", "用户账号", "string")); } return headers; } ``` 2. **映射行数据** 使用`MapStruct`转换数据库实体到行数据VO(引用[3][^3][^5]): ```java @Mapper(componentModel = "spring") public interface DynamicRowMapper { DynamicRowVO entityToRow(Entity entity); } ``` 3. **统一响应封装** 返回带表头的完整数据结构: ```java @GetMapping("/dynamic-report") public ResponseEntity<DynamicTableVO<?>> getReport() { List<HeaderItemVO> headers = buildHeaders("sales"); List<DynamicRowVO> rows = salesService.queryData().stream() .map(dynamicRowMapper::entityToRow) .collect(Collectors.toList()); return ResponseEntity.ok(new DynamicTableVO<>(headers, rows)); } ``` 4. **处理空值**(引用[4][^4]) 在`application.yml`中配置忽略空字段: ```yaml spring: jackson: default-property-inclusion: non_null ``` #### 示例响应结构 后端返回的JSON格式: ```json { "headers": [ {"field": "product", "label": "产品名称", "dataType": "string"}, {"field": "quantity", "label": "销售数量", "dataType": "int"} ], "rows": [ {"data": {"product": "手机", "quantity": 150}}, {"data": {"product": "电脑", "quantity": 80}} ] } ``` #### 关键优势 - **类型安全**:通过VO类避免Map的松散结构风险 - **动态扩展**:表头可基于参数实时生成(如按用户权限显示同列) - **前后端解耦**:前端根据headers动态渲染表格 - **复用性**:同一接口支持多种报表类型 --- ### 相关问题 1. 如何实现动态表头的国际化支持? 2. 使用泛型`DynamicTableVO<T>`时如何避免类型擦除问题? 3. 当表头需要嵌套层级时(如分组表头),VO结构应如何调整? 4. 如何结合MyBatis实现动态SQL查询以匹配动态表头? 5. 在动态表格场景下,如何优化Jackson对大数据的序列化性能? [^1]: VO与PO的定义及区别 [^2]: VO在视图层中的核心作用 [^3]: MapStruct实现实体到VO的转换 [^4]: Jackson空值序列化配置 [^5]: MapStruct的Maven依赖配置
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值