项目介绍
1. 后端
1.1. SaToken认证与授权
SaToken框架是国内新崛起的开源免费认证与授权框架,2021年荣获码云的GVP头衔。相关资料,大家可以看它的官方手册文档,里面有入门说明和AP接口个绍。在本课程项目中没有使用Shiro和SpringSecurity的原因非常简单:Shiro和SpringSecurity都没有内置JWT功能,所以我们需要额外配置JWT,过程非常繁琐。恰好SaToken内置了JWT功能,而且Token的缓存和过期管理都实现了,于是我就用SaToken框架。等你熟悉了SaToken框架之后,基本上再也不想碰Shiro和SpringSecurity了。
1.1.1. Token认证的原理
传统的单体JavaWeb项目通常采用HttpSession保存登陆成功的凭证,但是HttpSession需要浏览器的Cookie机制配合。也就是说Wb项目的客户端只能是浏览器,不可以是APP、机顶盒、智能家电等。
为了让Java项目兼容更多的客户端设备,所以我们要放弃在客户端用Cookie保存Sessionld的做法。我们在服务端生成Token字符串,然后交给客户端保存。APP也好、浏览器也好、机顶盒也好,都能保存字符串。每次发送HTTP请求的时候,在请求头上附带Token字符串,SaToken框架就能解析出该Token是否合法,如果没有问题,就允许客户端请求后端的Web方法。
即便有人破解了SaToken生成令牌的算法,我们也不用担心,因为我们开启了Redis保存Token副本的功能。如果SaToken检测到请求头的Token在Redis中没有缓存副本,那么这个Token肯定是伪造的,所以就拒绝请求。另外Redis中的Token有过期时间,客户端提交的Token是过期的,SaToken也能判断出来,然后拒绝请求。
1.1.2. 判断是否登录
想要判断用户是否已经在客户端登录,我们在Web方法上添加@SaCheckLogin
注解即可。SaToken只要检测到Token是有效的,就会允许HTTP请求调用该Web方法。
@PostMapping("/createFaceModel")
@SaCheckLogin // 验证用户是否已经登录,只有登录了才能调用此方法。是SaToken框架来执行的,从请求头中获取Token看是否有效。
public R createFaceModel(@RequestBody @Valid CreateFaceModelForm form){
int userId StpUtil.getLoginIdAsInt();
form.setUserId(userId);
Map param BeanUtil.beanToMap(form);
faceAuthService.createFaceModel(param);
return R.ok();
}
无论哪位用户登陆小程序,拥有的权限都是相同的。不存在不同身份的人去小程序,能登陆小程序的人,都是相同身份的人。所以后端Java项目不需要判断他们是否具备不同的权限,这些用户具有相同的权限,因此我们只需要判断他们是否登陆即可。
1.1.3. 判断是否具有权限
在MIS端登陆的用户可能是超级管理员,也可能是别的角色,他们拥有的权限是不同的。所以我们要在shanyi-mis
项目中验证用户是否登陆,而目还要验证他们是否具备相关的权限才能调用某个Web方法。这需要用到@SaCheckPermission
注解了。
@PostMapping("/searchByPage")
@SaCheckLogin // 用户必须登录
// 用户必须具备相关的权限,ROOT或(SaMode.OR)USER:SELECT
@SaCheckPermission(value = {"ROOT","USER:SELECT"}, mode = SaMode.OR)
public R searchByPage(@RequestBody @Valid SearchDoctorByPageForm form){
Map param BeanUtil.beanToMap(form);
int page = form.getPage();
int length = form.getLength();
int start = (page -1) * length;
param.put("start", start);
PageUtils pageUtils = misUserService.searchByPage(param);
return R.ok().put("result", pageUtils);
}
每当SaToken框架执行到@SaCheckPermission
注解的时候,就会调用StpInterfaceImpl
类。从里面的getPermissionList()
函数中获取用户具备的权限,然后跟注解要求的权限比对。如果用户拥有相关权限就可以调用Web方法,否则就拒绝请求抛出异常。
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private MisUserDao userDao;
/**
* 返回一个用户所拥有的权限集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginKey) {
int userId = Integer.parseInt(loginId.toString());
ArrayList<String> list = userDao.searchUserPermissions(userId);
return list;
}
/**
* 返回一个用户所拥有的角色标识集合(不建议使用,所以直接返回空)
*/
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
return null;
}
}
1.2. 哈希算法对密码加密
1.2.1. 哈希加密
1.2.2. 哈希字典反破解
1.2.3. 多次哈希加密
1.2.4. 原始数据加盐混淆
1.3. 自定义返回类R
1.3.1. 默认调用成功
return R.ok();
返回格式:
{
"code": 200,
"msg": "success"
}
1.3.2. 自定义msg为String
当然也可以自定义返回的msg
消息。
return R.ok("操作成功");
返回格式:
{
"code": 200,
"msg": "操作成功"
}
1.3.3. 自定义msg为Map
HashMap map = userService.searchById(form.getId());
return R.ok(map);
返回格式:
{
"msg": "success",
"birthday": "1901-01-01",
"code": 200,
"address": "山东济南",
"goal": "增肌",
"nickName": "LxPl2net",
"sex": "男",
"weight": "80.2",
"diseases": [
"糖尿病"
],
"dislikes": [],
"avatar": "https://thirdwx.qlogo.cn/mmopen/vi_32/5icHNmy3LXpUEY3JHKrm4P0twqobiaYuemvXr4s6aK2rJKQ7RkVOaP24nRSkEfwErCIzkSUyvibFicJ37IA5Yppumg/132",
"uuid": "c948897c4ace4268a22a1922ea7bba18",
"phone": "15523x81028",
"name": "李x",
"id": 3,
"email": "25282x331@qq.com",
"height": "1.94",
"bmi": "21.31",
"likes": [
"辣椒"
],
"status": 1
}
1.3.4. 使用put()绑定数据,可链式调用
PageUtils pageUtils = userService.searchByPage(param);
return R.ok().put("result", pageUtils); // 后面如果还有想要绑定的数据,可以继续.put()
返回格式:
{
"msg": "success",
"result": {
"totalCount": 2,
"pageSize": 10,
"totalPage": 1,
"pageIndex": 1,
"list": [
{
"phone": "15523x81028",
"nickName": "LxPl2net",
"name": "李x",
"id": 3,
"email": "25282x331@qq.com",
"status": 1
},
{
"phone": "15925823218",
"createTime": "2024-04-23",
"nickName": "k2mi",
"name": "李xx",
"id": 4,
"email": "2291262972@qq.com",
"status": 1
}
]
},
"code": 200
}
1.3.5. 返回错误
当然,也有对应的返回错误信息的方法。
1.4. 分页工具类
可以将查询出来的数据与分页的相关参数进行封装,返回给前端。
使用方法:
2. MIS端前端
2.1. 前端项目架构
├─src #
│ │ #
│ ├─node_modules # npm下载下来的依赖库
| | #
│ ├─assets # 放图片、scss文件等静态资源
│ │ │ 404.png #
│ │ │ #
│ │ ├─login # 登录界面用到的静态资源
│ │ │ bg.svg # 登录界面背景
│ │ │ sy-logo.png # 登录界面Logo
│ │ │ #
│ │ └─scss #
│ │ base.scss #
│ │ index.scss #
│ │ normalize.scss #
│ │ variables.scss #
│ │ #
│ ├─components # 自定义组件
│ │ SvgIcon.vue # 自定义图标组件
│ │ #
│ ├─icons # 图标
│ │ └─svg #
│ │ icon-admin.svg #
│ │ icon-assurance_fill.svg #
│ │ icon-bianji.svg #
│ │ ... #
│ │ #
│ ├─router #
│ │ index.js # 路由配置
│ │ #
│ ├─utils #
│ │ validate.js # 前端验证:在提交ajax请求之前在前端进行验证
│ │ #
│ ├─views # 各路由页面
│ │ 404.vue #
│ │ app_user-add-or-update.vue #
│ │ app_user.less #
│ │ app_user.vue #
│ │ home.less #
│ │ home.vue #
│ │ login.less #
│ │ login.vue # 登录界面
│ │ main.vue #
│ │ update-password.vue #
│ │ user.less #
│ │ user.vue #
│ │ #
│ │ App.vue # App.vue页面:路由切换页面时
| | # 其实就是让App.vue加载不同页面进来
| | #
│ │ main.js # 前端项目的入口文件:封装全局Ajax公共函数,
│ # 封装用于判断用户是否具有某些权限的公共函数
│ #
├─static #
│ #
│ .gitignore #
│ favicon.png # 网站logo:在这里修改网站logo图标
│ index.html # 基座文件:在这里修改网站名
│ package-lock.json #
│ package.json # 前端项目使用的依赖库
│ vite.config.js # vite脚手架配置文件:在这里修改端口号、跨域、
# 自启浏览器、引入elementplus和svg组件库等
2.2. 自定义封装Ajax请求
我们使用的是JQuery的Ajax技术,并不是网上推荐的Axios技术。到底是出于什么原因放弃Axios了呢?原因和简单:JQuery支持同步和异步的Ajax请求,而Axios只支持异步请求。说到这里,大家可能会产生疑惑:Ajax本来不就应该是异步执行的么?
其实还是前端代码写的少,而且没有做过复杂业务的项目,所以就以为Ajax本应该是异步的。在特定的场景下,我们只能用同步的Ajax请求,而决不能用异步请求。先说一个现实生活中的例子,比如说你在打游戏的时候,别人直接过来拔掉电脑电源,游戏都没来得及存档就关机了。明白这个道理,下面的例子就好理解了。比如说我们的项目使用了ElementPlus组件库,其中有很多组件有回调函数。如果我们如果在回调函数中发起异步Ajax请求,ElementPlus的回调函数不会等着异步请求执行完,直接就结束了,并且销毁了调用栈,从而导致异步Ajax强制被退出。这就像直接拔掉电脑电源,不管你电脑正在运行什么程序都直接被关闭。
2.3. 前端权限验证
用户登陆成功之后,后端Java项目会在HTTP响应里放入Token令牌和用户拥有的权限列表。前端项目需要把Token和权限列表保存到浏览器的Storage里面。前端VUE页面在渲染的时候,需要根据权限判定哪些控件可以显示,这就逼着我们把权限验证封装成公共函数。
app.config.globalProperties.isAuth function(permission) {
let permissions = localStorage.getItem("permissions");
let flag = false
for (let one of permission) {
if (permissions.includes(one)) {
flag true
break;
}
}
return flag;
}
Vue页面的组件需要验证权限的时候,可以写成下面的样子:
<template>
<div v-if="isAuth(['ROOT', 'USER:SELECT'])">
...
</div>
</template>
3. 小程序端前端
3.1. uView组件库
官网
https://www.uviewui.com/
uView自带icons图标
https://www.uviewui.com/components/icon.html
3.2. 小程序分包
微信规定小程序打包后的体积不能超过2M。如果小程序页面较代码打包体积超过了2M,我们可以采用分包的形式,把主包的页面拆分到其他分包中,只要每个分包的体积不超过2M,而且小程序打包总体积不超过64M就可以。本课程的小程序项目就采用了分包加载的方式,主包是pages目录,分包是user这些目录。
3.3. 封装Ajax请求
与前端Vue项目类似,小程序项目也定义了全局的Ajax封装函数,在main.js文件里面有该函数的声明。
3.4. 小程序项目架构
├─.hbuilderx # hbuilderx自动生成的目录,不用管,可以删除
│ launch.json #
│ #
├─node_modules # npm下载下来的依赖库
| #
├─pages # 小程序的页面,主包
│ ├─activity #
│ │ activity.less #
│ │ activity.vue #
│ │ #
│ ├─camera #
│ │ camera.less #
│ │ camera.vue #
│ │ #
│ ├─chat #
│ │ chat.less #
│ │ chat.vue #
│ │ #
│ ├─check_in #
│ │ check_in.less #
│ │ check_in.vue #
│ │ #
│ └─mine #
│ mine.less #
│ mine.vue #
│ #
├─static # 静态资源,放了一些图标,但主要还是存在minio中
│ ├─picture #
│ │ u274.png #
│ │ #
│ └─tab_bar #
│ dkw_拍摄.png #
│ dkw_拍摄3.png #
│ 个人中心.png #
│ 个人中心1.png #
│ 打卡2.png #
│ 打卡4.png #
│ 活动2.png #
│ 活动5.png #
│ 聊天.png #
│ 聊天3.png #
│ #
├─user # 分包
│ └─fill_user_info #
│ fill_user_info.less #
│ fill_user_info.vue #
│ #
├─unpackage # 打包的内容
│ └─dist #
| #
├─uview-ui # uView组件库
│ ├─components # 注意:这里面有个包叫u-icon,里面有个文件叫做icons.js,
| | ... # 里面是uView自带的icons
| #
│ .gitignore #
│ App.vue #
│ index.html # 基座文件
│ main.js # 入口文件,封装了Ajax
│ manifest.json # 小程序相关配置
│ package-lock.json #
│ package.json # 小程序依赖的第三方库
│ pages.json # 路由配置
│ style.less # 自定义全局样式
│ uni.scss # 小程序自带的样式文件