项目构建
项目构建
安全框架引入
引入Shiro框架,构建用户安全认证
考虑到原有的实现方式是通过request来进行校验,每次都要请求获取用户参数信息。不能够灵活地在任一处获取到当前的登陆用户信息,例如controller=》service,要把request对象传过去,这种方式非常麻烦。
有一种方式是通过缓存方式存储:借助ThreadLoal存储登陆用户信息,
还有一种方式借助安全认证框架统一管理,引入shiro
原有登陆实现session存储登陆用户信息,自定义@AuthCheck注解和AOP 校验AuthInterceptor(权限拦截器)
取消原有session机制管理用户,交由Shiro框架统一管理用户登陆信息
后端接口调整:
- 用户登陆:userController/userLogin=》accountController/login
- 用户注销:userController/userLogout=》accountController/logout
- 获取登陆用户信息:userController/getLoginUser=》accountController/getCurrentLoginUser
- 接口排查:使用shiro接管了用户登陆状态管理,则项目中原有的一些内容已经不再适用,类似获取登陆用户信息、request(从session中获取内容等)都失效需要进行重构,此外鉴权(@AuthCheck也由shiro进行接管)
PS:😤😤😤给后续代码排查留个坑
前端调整:
- 对应前端登陆引用调整:注意fetchUserInfo、登陆方法等相关进行替换,校验登陆流程(项目中用到登陆、注销、获取用户登陆信息相关的接口都要进行排查,否则报错),如果出现登陆之后用户信息没更新则需要排查接口交互响应参数
构建参考思路:之前写的笔记代码复盘:springboot整合shiro单用户、多用户模式构建
基础参考:https://blog.csdn.net/heyl163_/article/details/131504939
参考修改代码说明:
- pom.xml:引入shiro框架
- ShiroConfig:全局配置
- SingleShiroUtil:单类型用户Shiro工具类
- UserRealm:自定义鉴权机制(用户登陆校验、用户访问鉴权)
- AccountController:登陆、注销、获取当前登录用户信息接口定义,提供shiro提供的注解实现鉴权
- AccountService、AccountServiceImpl:相关实现方法定义,其业务逻辑调整为基于Shiro进行构建
- application.yml:自定义controller层需要配置swagger,让它可以扫描到新引入的内容
- 其他代码原有逻辑不变,但是此前所有基于session相关的操作需要取消(例如获取登陆用户信息、鉴权等凡是涉及和登陆用户相关的都由shiro接管)
解决业务代码request无限传递的问题,可以通过自定义工具类在任意位置拿到当前登陆用户信息,而不用依托于request的session
/**
* RequestContextHolder 获取请求信息:此处涉及到一个问题,因为request是游离这个接口规范以外的所需参数,因此在处理的时候要考虑两个方面的问题
* 1.不满足规范考虑剔除,对接参数需求额外提供接口进行处理
* 2.尽量自主获取到参数信息,此处借助RequestContextHolder获取请求信息从而拿到所需数据,但也会引申一个问题:当请求来源不同的时候这个request可能和系统所需的有所出入
* 3.修改规范:确认其他接口是否也是需要这个参数,但这个改造成本可能在后期会显得大,因为一些现有的接口已经按照既定规范执行,唯恐牵一发动全身
*/
// ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// HttpServletRequest request = servletRequestAttributes.getRequest();
用户模块完善
个人中心
首先梳理个人中心功能构建的思路:
修改AvatarDropdown.tsx文件的menuItems内容,参考退出登录功能实现(填充个人中心信息),先定义菜单组件,然后跟踪onMenuClick按钮点击事件触发,可以看到当key为退出的时候触发调用登录退出接口,其他的key都会通过history.push进行页面转发(此处则可根据其具体转发的地址配置相关的路由并实现页面即可)
修改routes:把个人中心页面加载进去(layout设置为false,则会跳转到一个空页面(没有任何样式),如果设置为false则会嵌入导航栏)
// 账号信息相关
{
path: '/account',
// layout: false, 设置为false不引用任何样式,如果为true会自动嵌入导航栏,如果想自定义页面样式可以通过配置这个参数进行控制
routes: [
{name: '个人中心', path: '/account/center', component: './Account/Center'},
{name: '个人设置', path: '/account/settings', component: './Account/Settings'},
],
},
参考index.tsx实现(实现效果为跳转到指定页面,这个页面规则由div进行控制:不会单独嵌入一些路由)
import { useModel } from '@umijs/max';
import React, { useEffect,useState } from 'react';
import {getLoginUserUsingGet} from "@/services/itc-platform/userController";
const Index: React.FC = () => {
const [type, setType] = useState<string>('account');
const { setInitialState } = useModel('@@initialState');
useEffect(()=>{
getLoginUserUsingGet({}).then(res=>{
console.error('res',res)
})
});
return (
// 页面信息定义(自定义样式)
<div className = "my-index">
hello my index
</div>
);
};
export default Index;
借助PageContainer封装(实现效果为设定导航栏控制,会根据PageContainer嵌入一些内容)
import React from 'react';
import { PageContainer } from '@ant-design/pro-components';
const Index: React.FC = () => {
return (
<PageContainer>
hello my index ssss
</PageContainer>
);
};
export default Index;
参考图标信息:Ant Design 图标、图标定义参考
前后台:页面定义
针对管理页面:可以共用组件,但是需要结合用户角色设定不同的访问权限(路由配置导向不同路径,指向同一个组件)
控制数据访问权限和操作权限即可
针对接口信息(需限定权限,管理员不可以违规操作内容)
签到功能
数据表设计
-- 用户签到表
create table user_sign
(
id bigint comment 'id' primary key,
uid bigint not null comment '签到用户ID',
title varchar(512) not null comment '签到说明',
signInTime datetime not null comment '开始时间',
signChannel varchar(512) comment '签到渠道',
score int(11) default 0 comment '获取积分',
isDelete tinyint default 0 not null comment '是否删除'
);
开发小技巧
初始化管理页面模板参考
最基础的管理页面(PageContainer)
调用接口,自定义div
import { useModel } from '@umijs/max';
import React, { useEffect,useState } from 'react';
import {getLoginUserUsingGet} from "@/services/itc-platform/userController";
const Index: React.FC = () => {
const [type, setType] = useState<string>('account');
const { setInitialState } = useModel('@@initialState');
useEffect(()=>{
getLoginUserUsingGet({}).then(res=>{
console.error('res',res)
})
});
return (
// 页面信息定义(自定义样式)
<div className = "my-index">
hello my index
</div>
);
};
export default Index;
获取登录用户信息
import {useModel} from "@@/exports";
// 从全局状态中获取登录用户信息
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};
React在页面渲染之前调用接口获取数据,随后渲染页面
构建思路
1)使用useState定义变量保存、设定数据
2)使用userEffect钩子在组件渲染之前调用接口获取数据(useEffect
在组件挂载后和每次依赖更新后执行,可以在useEffect
中放置你的数据获取逻辑)
3)使用loading阻塞(如果数据还没有完全返回,组件渲染需要用到数据中的某些内容,则可能会报错提示,因此可以自主设定loading阻塞,如果数据还没返回就先不渲染组件)
参考构建步骤
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'你的API接口URL',
);
setData(result.data);
};
fetchData();
}, []); // 空数组意味着仅在组件挂载时调用一次
if (!data) {
return <div>Loading...</div>;
}
return (
<div>
{/* 渲染获取到的数据 */}
<h1>Data: {data.attribute}</h1>
</div>
);
}
export default MyComponent;
参考实现说明(例如想要初始化页面加载数据然后封装组件)
import React ,{ useEffect,useState }from 'react';
// 接口服务引入
import {getUserVoMore} from "@/services/itc-platform/userController";
const Index: React.FC = () => {
// 1.定义全局变量,存储接口调用的响应数据(此处为用户详细信息)
const [userMoreInfo,setUserMoreInfo] = useState();
// 2.调用接口获取数据详情信息(空数组表示仅在组件挂载时调用一次)
useEffect(()=>{
getUserVoMore().then(res=>{
// 根据请求接口响应设定该值
setUserMoreInfo(res.data);
console.error('center响应数据',res)
});
},[])
// 3.如果请求还没加载完成,则等待(否则待组件加载完成数据还没请求完,就会提示渲染报错)
if (!userMoreInfo) {
return <div>Loading...</div>;
}
// 4.其他处理操作定义
- ....... -
// 组件渲染
return (
<PageContainer>
/* 处理数据并展示 */
</PageContainer>
);
};
export default Index;
PS:扩展:可自定义loading加载样式
const loading = (
<span className={styles.action}>
<Spin
size="small"
style={{
marginLeft: 8,
marginRight: 8,
}}
/>
</span>
);
// 如果请求还没加载完成,则等待(否则待组件加载完成数据还没请求完,就会提示渲染报错)
if (!userMoreInfo) {
// return <div>Loading...</div>;
return loading;
}