云盘项目

项目技术与功能

技术架构

  • 前端
    • vue
  • 后端
    • springboot
    • mysql
    • redis
    • ffmpeg

功能

仿百度网盘

前置准备

mysql安装(我用的phpstudy自带的)

navicat(连接mysql的工具)

redis(安装后,服务默认自动)

Another-redis(redis连接工具)

ffmpeg密码 9n15

maven配置(后端写java,需要装这个)密码1234

jrebel(热部署工具)

1
2
3
4
5
6
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>

然后配置其conf中settings.xml的阿里云镜像

(redis和ffmpeg、maven为了直接在dos上执行,而不用先跳转其路径再执行,用环境变量)

在系统变量的Path中添加Redis和ffmpeg/bin、maven/bin的路径

dos上执行 ffmpeg -version进行检测

dos上执行 mvn -version检测,如果出现JAVA_HOME之类的报错,请确认jdk是否在环境变量中,命名为JAVA_HOME

项目构建

1.新建文件夹命名,用IDEA打开该工作空间

2.配置jdk

fie->project struct

3.maven设置

setings搜索maven

file encoding设置为utf-8

4.工程创建

pom.xml

选择工作空间,右键new->Module

方框中不推荐,其本质还是Maven,选择如上图的webapp

等待pom.xml依赖生成,如果有现成的pom.xml内容,可以先复制内容到word,再复制到文件中(目的是解决格式问题)

1
是些project、dependency等玩意儿

application.properties

main文件夹中删除其他文件夹,添加java与resouces文件夹,如果没有自动识别

File->project struct->Module->Sources,对相应文件夹标记即可

在java资源下添加包com.easypan,包中添加类;resources下添加 application.properties文件

1
这里就是些web端口配置啊,日志啊,邮件啊之类的

然后数据库的url由于是easypan?…

那么数据库就要建立一个easypan

logback-spring.xml

1
日志的配置

把JetBrains\IntelliJ IDEA 2020.1.2\bin中的idea.properties内容改下

1
2
3
4
5
6
7
8
9
10
11
idea.config.path=C:/Program Files/JetBrains/.IntelliJ IDEA 2020.1.2/config

#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to IDE system folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
idea.system.path=C:/Program Files/JetBrains/.IntelliJ IDEA 2020.1.2/system

#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to user installed plugins folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
idea.plugins.path=C:/Program Files/JetBrains/.IntelliJ IDEA 2020.1.2/plugins

重新启动idea,即可使用

之后是激活jrebel

对入口类进行jrebel debug运行

目前项目构造

登录注册

1.数据库设计

在navicat工具中去新建表,然后给表进行注释,并添加对应的列,进行相应注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `user_info` (
`user_id` varchar(15) NOT NULL COMMENT '用户ID',
`nick_name` varchar(20) DEFAULT NULL COMMENT '昵称',
`email` varchar(150) DEFAULT NULL COMMENT '邮箱',
`qq_open_id` varchar(35) DEFAULT NULL COMMENT 'qqid',
`qq_image` varchar(150) DEFAULT NULL COMMENT 'qq头像',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`join_time` datetime DEFAULT NULL COMMENT '创建时间',
`last_login_time` datetime DEFAULT NULL COMMENT '最近登录时间',
`status` tinyint(1) DEFAULT NULL COMMENT '0:禁用,1:启用',
`use_space` bigint(20) DEFAULT NULL COMMENT '云盘使用空间,单位byte',
`total_space` bigint(20) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';

再把索引建立一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE `user_info` (
`user_id` varchar(15) NOT NULL COMMENT '用户ID',
`nick_name` varchar(20) DEFAULT NULL COMMENT '昵称',
`email` varchar(150) DEFAULT NULL COMMENT '邮箱',
`qq_open_id` varchar(35) DEFAULT NULL COMMENT 'qqid',
`qq_image` varchar(150) DEFAULT NULL COMMENT 'qq头像',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`join_time` datetime DEFAULT NULL COMMENT '创建时间',
`last_login_time` datetime DEFAULT NULL COMMENT '最近登录时间',
`status` tinyint(1) DEFAULT NULL COMMENT '0:禁用,1:启用',
`use_space` bigint(20) DEFAULT NULL COMMENT '云盘使用空间,单位byte',
`total_space` bigint(20) DEFAULT NULL COMMENT '总空间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `key_email` (`email`),
UNIQUE KEY `key_qq_open_id` (`qq_open_id`),
UNIQUE KEY `key_nick_name` (`nick_name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';

之后貌似就涉及到了,如何用IDEA去实现根据表内容自动生成实体类

B站 老罗

CSDN 二木成林

CSDN 新晨Zeng(是一种方法参考,但没有用这个)

自动设计类

首先,navicat写好了表,且对每一列注释好,对表名也有注释

IDEA,去利用自带的database工具进行连接mysql,(先安装相应driver),再连接对应数据库

在pom.xml中引入依赖

1
2
3
4
5
6
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>

下载easycode插件去自动生成代码

CSDN ShaneHolmes(我看的这个)

CSDN Darren Gong(maven依赖和application.properties参考这个)

为了加注解的话

往application.properties加入

1
2
3
############## Mybatis ##############
mybatis.mapper-locations=classpath:mapper/*Dao.xml
mybatis.type-aliases-package=com.easypan.entity

Dao层上加入 @Mapper

启动类上加入@MapperScan("com.easypan.dao")

插入一行数据后,jrebel debug启动类,访问得

2.部署前端

下载nginx

我之前练习注入靶场的时候,下载了小皮phpstudy,我就用它自带的nginx吧

下载前端编译程序

编译nginx

更改nginx的nginx.conf配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  server {
listen 8000;#访问端口
#listen somename:8080;
server_name easypan.yuleiyun.com;
charset utf-8;

location / { #路由规则
alias D:/phpstudy_pro/WWW/sqli/; #将代码项目build后放进sqli 真实静态资源的路径
#root html; #root 以location块名称为文件夹下的路径
# root 和 alias 取其1即可

index index.html index.htm; #index 首页设置
}
location /api {
#alias
proxy_pass http://localhost:7090/api;# 跨域访问
proxy_set_header x-forwarded-for $remote_addr;#客户端ip remote_addr
}
}

关于那个server_name easypan.yuleiyun.com

可以下载一个工具switchHost,用来对127.0.0.1 easypan.yuleiyun.com的映射

更改host映射

关于配置文件的详解,参考 CSDN 皮卡丘的猫,以及另一条

在nginx所在文件夹,shift+鼠标右键,启动powershell

start .\nginx.exe 启动

.\nginx.exe -s reload 每修改一次conf,执行一次

.\nginx.exe -s stop //停止

访问得

D:/phpstudy_pro/WWW/sqli/目录下肯定有一个html或htm文件的

CSDN 普通网友

3.前端转战vs code

先构建项目,下载node.js

此处为v16.17.0版本

我之前下过node,cmd输入where node,找到路径,删掉,下载对应版本node,添加环境变量即可

(题外话):删除了原版本的node后,在git中用hexo更新博客,发现没有该命令了,只能重新npm install -g hexo-cli

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
PS E:\myworkspace_front> node -v
v16.17.0
PS E:\myworkspace_front> npm config get registry
https://registry.npm.taobao.org/
PS E:\myworkspace_front> npm config set registry https://registry.npmmirror.com
PS E:\myworkspace_front> npm init vite@latest easypan-front
Need to install the following packages:
create-vite@4.4.1
Ok to proceed? (y)
√ Select a framework: » Vue
√ Select a variant: » Customize with create-vue ↗
Need to install the following packages:
create-vue@3.7.2
Ok to proceed? (y) y

//构建项目
PS E:\myworkspace_front> npm init vite@latest easypan-front
√ Select a framework: » Vue
√ Select a variant: » Customize with create-vue ↗

Vue.js - The Progressive JavaScript Framework

√ Add TypeScript? ... No
√ Add JSX Support? ... No
√ Add Vue Router for Single Page Application development? ... Yes
√ Add Vitest for Unit Testing? ... No
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No

Scaffolding project in E:\myworkspace_front\easypan-front...

Done. Now run:

cd easypan-front
npm install
npm run dev
1
2
3
4
5
//安装项目依赖
PS E:\myworkspace_front> cd .\easypan-front\
PS E:\myworkspace_front\easypan-front>
npm install @highlightjs/vue-plugin @moefe/vue-aplayer aplayer axios docx-preview dplayer element-plus highlight.js hls.js js-md5 sass sass-loader spark-md5 vue-clipboard3 vue-cookies vue-pdf-embed vue-router vue3-pdfjs xlsx --save

可以看到文件夹

之后npm run dev

1
2
3
4
5
6

VITE v4.4.9 ready in 3323 ms

➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help

对vite.config.js进行修改,可以自定义端口,不用5173

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
port:1079,
hmr:true,
proxy:{
"/api":{
target:"http://localhost:7090",
changeOrigin:true,
pathRewrite:{
"^api":"/api"
}
}
}
}

访问127.0.0.1:1079

访问前端

登录注册1

src文件夹->main.js,输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
//import router from './router'
//引入element plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//图标 图标在附件中
//import '@/asserts/icon/iconfont.css'
//import '@/asserts/base.scss'

//引入cookies
import VueCookies from 'vue-cookies'
...
app.use(ElementPlus) //为了以后用到el-button

index.html改为

1
2
<script src="/hls.min.js"></script> 
<title>Easypan</title>

将页面标题更改

router中index.js更改

路由

1
2
3
4
5
6
7
8
 routes: [
{
path: '/',
name: 'test',
component: ()=>import("@/views/test.vue")
}
]
})

views中新建视图test.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
test
</div>
</template>

<script setup>


</script>

<style lang="scss" scoped>

</style>

App.vue内容更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<el-config-provider :locale="locale" :message="config">
<router-view></router-view>
</el-config-provider>
</template>


<script setup>
import { reactive }from "vue";
import zhCn from "element-plus/dist/locale/zh-Cn.mjs";
const locale = zhCn;
const config = reactive({
max: 1,
});

</script>

<style lang="scss" scoped>

</style>

①页面的加载过程就是:index.html->main.js->App.vue->index.js->xxx.vue
②main.js通过实例化 vue,把组件和入口页面联系起来

访问显示test

如果没有显示,请先查看main.js是否导入了不正确的玩意儿,先注释掉

如果将Test.vue中的内容改为

1
<el-button type="primary">test</el-button>

显示为

按钮test

icon/img图片在此处找

最终结果

仅对原Test.vue更改为Login.vue,并写成基本的账号密码表单格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<template>
<div class="login-body">
<div class="bg"></div>
<div class="login-panel">
<el-form class="login-register"
:model="FormData"
:rules="rules"
ref="formDataRef"

@submit.prevent
>
<div class="login-title">Easypan</div>
<!-- input输入 -->
<el-form-item prop="email">
<el-input
size="large"
clearable placeholder="请输入邮箱" v-model.trim="formData.email"
maxLength="150"
>
<template #prefix>
<span class="iconfont icon-account"></span>
</template>
</el-input>
</el-form-item>

<!-- 密码 -->
<el-form-item prop="password">
<el-input
type="password"
size="large"
clearable placeholder="请输入密码" v-model.trim="formData.password"
maxLength="150"
show-password
>
<template #prefix>
<span class="iconfont icon-password"></span>
</template>
</el-input>
</el-form-item>

<!-- 验证码 -->
<el-form-item prop="checkcode">
<div class="checkcode-panel">
<el-input

size="large"
clearable placeholder="请输入验证码" v-model.trim="formData.checkcode"
maxLength="150"
>
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<img
:src="Checkcodeurl"
class="check-code"
@click="Changecheckcode(0)"
>
</div>
</el-form-item>

<!-- 下拉框 -->
<el-form-item>
<div class="remember-panel">
<el-checkbox
v-model="formData.rememberMe"
>记住我
</el-checkbox>
</div>

<div class="no-account">
<a href="javascrip:void(0)"
class="a-link"
>忘记密码?</a>
<a href="javascrip:void(0)"
class="a-link"
>注册账号</a>
</div>
</el-form-item>
<!-- 登录 -->
<el-form-item>
<el-button type="primary"
class="op-btn"
size="large"
>
<span>注册</span>
</el-button>
</el-form-item>

</el-form>
</div>
</div>
</template>

<script setup>
import {ref,reactive,getCurrentInstance,nextTick} from "vue"
const {proxy}=getCurrentInstance();

// 验证码
const api={
checkcode:"/api/checkcode",
};

const formData=ref({});
const formDataRef=ref();
const rules={
title:[{required: true,message:"请输入内容"}],
};
//验证码
const Checkcodeurl=ref(api.checkcode);
const Changecheckcode=(type) => {
Checkcodeurl.value =
api.checkcode+"?type="+type+"&time="+new Date().getTime();
}
</script>

<style lang="scss" scoped>
.login-body{
height: calc(100vh);
background-size:cover;
background: url("../assets/bd.jpg");
display:flex;
.bg{//插图大小及位置
flex: 1;
background-size:cover;
background-position: center;
background-size:800px;
background-repeat: no-repeat;
background-image: url("../assets/bg_mid.png");
}
.login-panel{//空出右边部分
width: 430px;
margin-right:15%;
margin-top:calc((100vh - 500px)/2);
.login-register{
padding:25px;
background:#fff;
border-radius:5px;
.login-title{
text-align: center;
font-size:18px;
font-weight:bold;
margin-bottom:20px;
}

}
}
.checkcode-panel{
width:100%;
display:flex;
.check-code{
margin-left:5px;
cursor:pointer;
}
}
.remember-panel{
width:100%;
}
.no-account{
width:100%;
display:flex;
justify-content:space-between;
}
.op-btn{
width:100%;
}
}

</style>

1.后端实现图片验证码

controller/dao/service/entity的关系

在启动类中添加

1
2
3
4
// 定义图形验证码的长和宽
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
// 图形验证码写出到指定文件
lineCaptcha.write("E:/test/captcha.png");

运行后,即可以看到一个随机的验证码图片

加载hutool-captcha依赖,创建一个控制器写出相关生成验证码代码,访问得

console出现

JRebel: Reconfiguring bean ‘UserInfoController’ [com.easypan.controller.AccountController]
验证码值:2jkh
获取图形码

然后启动前端,刷新

2.前端链接跳转

忘记密码和注册账号的跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="no-account">
<a href="javascrip:void(0)"
class="a-link"
@click="showPanel(2)"
>忘记密码?</a>
<a href="javascrip:void(0)"
class="a-link"
@click="showPanel(0)"
>注册账号</a>
</div>

...
//操作类型: 0:注册,1:登录,2:重置密码
const opType=ref(1); //默认为登录时
const showPanel=(type) =>{
opType.value=type;
};

如果是忘记密码和注册账号时的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<!-- 登录密码 -->
<el-form-item prop="password" v-if="opType==1">
<el-input
type="password"
size="large"
clearable placeholder="请输入密码"
v-model.trim="formData.password"
maxLength="150"
show-password
>
<template #prefix>
<span class="iconfont icon-password"></span>
</template>
</el-input>
</el-form-item>
<!-- 注册 -->
<div v-if="opType == 0||opType == 2">
<el-form-item prop="emailCode">
<div class="send-email-panel">
<el-input
size="large"
placeholder="请输入邮箱验证码"
v-model.trim="formData.emailCode"
>
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<!-- 按钮 -->
<el-button class="send-email-btn" type="primary"
size="large"
>
获取验证码
</el-button>
</div>
<el-popover placement="left" :width="500" trigger="hover">
<div>
<p>忘了就算了</p>
</div>
<template #reference>
<span class="a-link" :style="{'font-size':'14px'}">
未收到验证码?
</span>
</template>
</el-popover>
</el-form-item>
<!-- 注册的昵称 -->
<el-from-item prop="nickname" v-if="opType == 0">
<el-input
size="large"
placeholder="请输入昵称"
v-model.trim="formData.nickname"
maxLength="20"
>
<template #prefix>
<span class="iconfont icon-nickname"></span>
</template>
</el-input>
</el-from-item>

<!-- 注册密码,找回密码 -->
<el-from-item prop="registerpass">
<el-input
size="large"
placeholder="请输入密码"
v-model.trim="formData.registerpass"
>
<template #prefix>
<span class="iconfont icon-nickname"></span>
</template>
</el-input>
</el-from-item>
<!-- 重复密码 -->
<el-from-item prop="reregisterpass">
<el-input
size="large"
placeholder="请再次输入密码"
v-model.trim="formData.reregisterpass"
>
<template #prefix>
<span class="iconfont icon-reregisterpass"></span>
</template>
</el-input>
</el-from-item>

忘记密码的界面

忘记密码

已有账号

3.引进组件Dialog.vue

在compnents文件夹下去新建一个Dialog.vue类,为了注册界面时的获取验证码模块的弹窗

然后在main.js中去引用该组件

1
2
3
import Dialog from '@/components/Dialog.vue'
...
app.component("Dialog",Dialog)

然后在Login.vue中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 <!-- 发送邮箱验证码 -->
<!-- dc即DiaglogConfig -->
<Dialog
:show="dcSendMailCode.show"
:title="dcSendMailCode.title"
:buttons="dcSendMailCode.buttons"
width="500px"
:showCancel="false"
@close="dcSendMailCode.show = false"
>
<el-form
:model="formDataSendMailCode"
:rules="rules2"
ref="formDataSendMailCodeRef"
label-width="80px"
@submit.prevent
>
<el-form-item label="邮箱">
{{ formData.email }}
</el-form-item>
<el-form-item label="验证码" prop="checkcode">
<div class="check-code-panel">
<el-input
size="large"
placeholder="请输入验证码"
v-model.trim="formDataSendMailCode.checkcode"
>
<template #prefix>
<span class="iconfont icon-checkcode"></span>
</template>
</el-input>
<img
:src="Checkcodeurl"
class="check-code"
@click="changeCheckCode(1)"
/>
</div>

</el-form-item>
</el-form>
</Dialog>

...
//发送邮箱验证码弹窗
const formDataSendMailCode = ref({});
const formDataSendMailCodeRef = ref();
//注意,所有的rule均采用rules2,校验输入邮箱不为空
const rules2={
email:[{required: true,message:"请输入正确的邮箱"}],
};

const dcSendMailCode = reactive({
show: false,
title: "发送邮箱验证码",
buttons: [
{
type: "primary",
text: "发送验证码",
click: () => {
sendEmailCode();
},
},
],
});

//获取验证码按钮所采用的方法
const getemailcode= () =>{
formDataRef.value.validateField("email",(valid)=>{
if(!valid)//邮箱无效
{
return;
}
dcSendMailCode.show=true;
});
};


当输入无效邮箱时

校验提示,按钮灰

输入有效邮箱时

弹窗

但是,此时的两处验证码采用同一个方法,验证码一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 ...
<img
:src="Checkcodeurlsendemailcode"
class="check-code"
@click="Changecheckcode(1)"
/>
</div>

...
const Changecheckcode=(type) => {
if(type == 0)
{
Checkcodeurl.value =
api.checkcode+"?type="+type+"&time="+new Date().getTime();
}
else
{
Checkcodeurlsendemailcode.value =
api.checkcode+"?type="+type+"&time="+new Date().getTime();
}
}

此时弹窗的验证码改变与登录界面验证码无关

4.输入校验及输入清空

输入校验

在src下新建utils文件夹,引入Verify.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const regs = {
email: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/,
number: /^([0]|[1-9][0-9]*)$/,
password: /^(?=.*\d)(?=.*[a-zA-Z])[\da-zA-Z~!@#$%^&*_]{8,}$/,
shareCode: /^[A-Za-z0-9]+$/
}
const verify = (rule, value, reg, callback) => {
if (value) {
if (reg.test(value)) {
callback()
} else {
callback(new Error(rule.message))
}
} else {
callback()
}
}

export default {
email: (rule, value, callback) => {
return verify(rule, value, regs.email, callback)
},
number: (rule, value, callback) => {
return verify(rule, value, regs.number, callback)
},
password: (rule, value, callback) => {
return verify(rule, value, regs.password, callback)
},
shareCode: (rule, value, callback) => {
return verify(rule, value, regs.shareCode, callback)
},
}

在main.js中设置全局组件

1
2
3
4
import Verify from '@/utils/Verify.js'
...
//配置全局组件
app.config.globalProperties.Verify=Verify;

在Login.vue中引用

1
2
3
4
5
6
const rules2={
email:[
{required: true,message:"邮箱不能为空"},
{validator:proxy.Verify.email,message:"请输入正确邮箱"}
],
};

如果对所有prop进行校验的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//保持密码一致性
const checkpass=(rule,value,callback)=>{
if(value!== formData.value.registerpass)
{
callback(new Error(rule.message));
}
else callback();
};

...

const rules2={
email:[
{required: true,message:"邮箱不能为空"},
{validator:proxy.Verify.email,message:"请输入正确邮箱"}
],
password:[{required: true,message:"密码不能为空"}],
emailCode:[{required: true,message:"邮箱验证码不能为空"}],
nickname:[{required: true,message:"昵称不能为空"}],
registerpass:[
{required: true,message:"密码不能为空"},

{
validator:proxy.Verify.password,
message:"密码需要数字/字母/特殊符号8-18位"
}
],
reregisterpass:[
{required: true,message:"再次输入密码"},

{
validator:checkpass,
message:"两次密码不一致"
}
],
checkcode:[{required: true,message:"验证码不为空"}]
};

输入清空

因为此时切换界面,跳转又返回到原界面时,输入的值是保留着的,比如弹窗验证码输入222,叉掉,再进入弹窗时,222保留

所以:

弹窗验证码输入清除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const getemailcode= () =>{
formDataRef.value.validateField("email",(valid)=>{
if(!valid)
{
return;
}
dcSendMailCode.show=true;
nextTick(()=>{
Changecheckcode(1);
formDataSendMailCodeRef.value.resetFields();
formDataSendMailCode.value={
email:formData.value.email,
};
});
});
};

如果是表单的清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//操作类型: 0:注册,1:登录,2:重置密码
const opType=ref(1); //默认登录
const showPanel=(type) =>{
opType.value=type;
//重置表单
restForm();
};
...
const restForm=()=>{
//刷新验证码
Changecheckcode(0);
formDataRef.value.resetFields();
formData.value={};
//登录
if(opType==1){}
}

5.提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <!-- 界面按钮 -->
<el-form-item>
<el-button type="primary"
class="op-btn"
size="large"
@click="dosubmit"
....

//提交按钮
const dosubmit=()=>{
formDataRef.value.validate(async (valid)=>{
if(!valid){
return;
}
});
};

6.发送验证码方法

qq邮箱要开启smtp服务,获得授权码,复制在后端的application.properties中

前端

utils文件夹引入Message.js和Request.js

在main.js引用它们

1
2
3
4
import Message from '@/utils/Message'
import Request from '@/utils/Request'
app.config.globalProperties.Message=Message;
app.config.globalProperties.Request=Request;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//定义邮箱验证码弹窗
const dcSendMailCode = reactive({
show: false,
title: "发送邮箱验证码",
buttons: [
{
type: "primary",
text: "发送验证码",
click: () => {
sendEmailCode();
},
},
],
});
//校验邮箱验证码
const getemailcode= () =>{
formDataRef.value.validateField("email",(valid)=>{
if(!valid)
{
return;
}
dcSendMailCode.show=true;
nextTick(()=>{
Changecheckcode(1);
formDataSendMailCodeRef.value.resetFields();
formDataSendMailCode.value={
email:formData.value.email,
};
});
});
};
//发送邮箱验证码
//发送邮箱验证码
const sendEmailCode=()=>{
// debugger;
formDataSendMailCodeRef.value.validate(async (valid)=>{
if(!valid){
return;
}
const params=Object.assign({},formDataSendMailCode.value);
params.type=onType.value==0?0:1;
let result =await proxy.Request({
url:api.sendEmailCode,
params:params,
errorCallback:()=>{
Changecheckcode(1);
},
});
if(!result)
{
return;
}
proxy.Message.success("验证码发送成功,请登录邮箱查看");
dcSendMailCode.show=false;
});
};

后端

navicate新建表email_code

1
2
3
4
5
6
7
CREATE TABLE `email_code` (
`email` varchar(150) NOT NULL COMMENT '邮箱名',
`code` varchar(5) NOT NULL COMMENT '验证码',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`status` tinyint(1) DEFAULT NULL COMMENT '0:未使用 1:已使用',
PRIMARY KEY (`email`,`code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='邮箱验证码';

AccountController类中编写sendEmailCode方法,验证码正确就发送邮箱

1
2
3
4
5
6
7
8
9
public ResponseVO sendEmailCode(HttpSession session, String email, String checkcode, Integer type){

// 如果验证码错误

// 如果验证码正确,发送邮箱验证码

// 删除session中保存的邮箱验证码

}

其中涉及到了 EmailCodeService 接口、ResponseVO

1
2
3
4
5
6
public interface EmailCodeService {

void sendEmailCode(String toEmail, Integer type);

void checkCode(String email, String code);
}

EmailCodeServiceImpl类实现接口的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Override
@Transactional(rollbackFor = Exception.class)
public void sendEmailCode(String toEmail, Integer type) {
//如果是注册,校验邮箱是否已存在
if (type == Constants.REGISTER_ZERO) {
UserInfo userInfo = userInfoMapper.selectByEmail(toEmail);
if (null != userInfo) {
throw new BusinessException("邮箱已经存在");
}
}

String code = Stringtools.getRandomNumber(Constants.LENGTH_5);
//ToDO 发送验证码
sendEmailCode(toEmail, code);

// 数据库中的其他验证码置于不可用
emailCodeMapper.disableEmailCode(toEmail);

// 封装EmailCode对象
EmailCode emailCode = new EmailCode();
emailCode.setCode(code);
emailCode.setEmail(toEmail);
emailCode.setStatus(Constants.REGISTER_ZERO);
emailCode.setCreateTime(new Date());

// 插入数据库
emailCodeMapper.insert(emailCode);
}

@Override
public void checkCode (String email, String code){
EmailCode emailCode = emailCodeMapper.selectByEmailAndCode(email, code);
// 如果没查到数据
if (emailCode == null) {
throw new BusinessException("邮箱验证码不正确");
}
// 如果已经失效或超时
if (emailCode.getStatus() == 1 || System.currentTimeMillis() - emailCode.getCreateTime()
.getTime() > Constants.LENGTH_15 * 1000 * 60) {
throw new BusinessException("邮箱验证已失效");
}

emailCodeMapper.disableEmailCode(email);
}
}

先写EmailCodeService的接口

1
2
3
4
5
public interface EmailCodeService {

void sendEmailCode(String toEmail, Integer type);

}

EmailCodeServiceImpl类去实现即可

1

效果:

数据库更新为:

向同一邮箱发送多条随机码时,除了最新发送的,其余的状态都置为1

7.注册按钮提交

前端,引入cookies

main.js中

1
2
3
import VueCookies from 'vue-cookies'
...
app.config.globalProperties.VueCookies=VueCookies;

Login.vue中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {useRouter,useRoute} from "vue-router"
import md5 from "js-md5"
...
const router=useRouter();
const route=useRoute();

...
//重置表单
const restForm=()=>{
//刷新验证码
Changecheckcode(0);
formDataRef.value.resetFields();
formData.value={};
//登录
if(opType==1){
const cookielogininfo=proxy.VueCookies.get("Logininfo");
if(cookielogininfo){
formData.value=cookielogininfo;
}
}
}
....
//提交按钮(登、重置、注册)
const dosubmit=()=>{
formDataRef.value.validate(async (valid)=>{
if(!valid){
return;
}
let params={};
Object.assign(params,formData.value);
//注册 找回密码
if(opType.value==0||opType.value==2)
{
params.password=params.registerpass;
delete params.registerpass;
delete params.reregisterpass;
}
//登录
if(opType.value==1)
{
let cookielogininfo=proxy.VueCookies.get("Logininfo");
let cookiepass=cookielogininfo==null?null:cookielogininfo.password
if(params.password!==cookiepass)
{
params.password=md5(params.password);
}
}
let url=null;
//注册
if(opType.value==0){
url=api.register;
}
//登录
else if(opType.value==1)
{
url=api.login;
}
//重置密码
else
{
url=api.restpwd;
}

let result=await proxy.Request({
url:url,
params:params,
errorCallback:()=>{
Changecheckcode(0);
}
});
if(!result) {
return;
}
//注册返回
if(opType.value==0){
proxy.Message.success("注册成功,请登录");
//返回到登录页面
showPanel(1);
}
//登录了
else if(opType.value==1){
if(params.rememberMe){

//以下内容存进cookie
const Logininfo={
email:params.email,
password:params.password,
rememberMe:params.rememberMe
};
proxy.VueCookies.set("Logininfo",Logininfo,"7d");

}else {
proxy.VueCookies.remove("Logininfo");
}
proxy.Message.success("登录成功");
//存储cookie
proxy.VueCookies.set("userInfo",result.data,0);
//重定向到原始页面
const redirectUrl=route.query.redirectUrl||"/";
router.push(redirectUrl);
}
//重置密码
else if(opType.value==2)
{
proxy.Message.success("重置密码成功,请登录");
//返回到登录页面
showPanel(1);
}
});
};

8.qq登录

emmm,略,不实现这个先

登录后布局页

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
{
path: '/',
name: 'Layout',
component: () => import("@/views/Layout.vue"),
children: [{
path: '/',
redirect: "/main/all"
},
{
path: '/main/:category',
name: '首页',
meta: {
needLogin: true,
menuCode: "main"
},
component: () =>
import ("@/views/main/Main.vue")
},
{
path: '/myshare',
name: '我的分享',
meta: {
needLogin: true,
menuCode: "share"
},
component: () =>
import ("@/views/share/Share.vue")
},
{
path: '/recycle',
name: '回收站',
meta: {
needLogin: true,
menuCode: "recycle"
},
component: () =>
import ("@/views/recycle/Recycle.vue")
},
{
path: '/settings/sysSetting',
name: '系统设置',
meta: {
needLogin: true,
menuCode: "settings"
},
component: () =>
import ("@/views/admin/SysSettings.vue")
},
{
path: '/settings/userList',
name: '用户管理',
meta: {
needLogin: true,
menuCode: "settings"
},
component: () =>
import ("@/views/admin/UserList.vue")
},
{
path: '/settings/fileList',
name: '用户文件',
meta: {
needLogin: true,
menuCode: "settings"
},
component: () =>
import ("@/views/admin/FileList.vue")
}]
}

Layout.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
<template>
<div class="layout">
<div class="header">
<!-- 左上角logo -->
<div class="logo">
<span class="iconfont icon-pan"></span>
<div class="name">Easy云盘</div>

</div>
<!-- 右边弹窗 -->
<!-- offset指与顶部的距离 -->
<!-- transition指动画 -->
<div class="right-panel">
<el-popover
:width="800"
trigger=click
v-model="visible"
:offset="20"
transition="none"
:hide-after="0"
:popper-style="{padding: '0px'}"
>
<template #reference>
<span class="iconfont icon-transfer"></span>
</template>
<template #default>上传区域在这里!</template>
</el-popover>

<!-- 右侧头像和下拉信息 -->
<el-dropdown>
<div class="user-info">
<div class="image"></div>
<span class="nick-name">{{ userInfo.nickName }}</span>
</div>
<!-- 下拉菜单 -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item> 修改头像</el-dropdown-item>
<el-dropdown-item> 修改密码</el-dropdown-item>
<el-dropdown-item> 退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 主体部分 -->
<div class="body">

<!-- 左侧列 -->
<div class="left-sider">
<!-- 一级目录 -->
<div class="menu-list">
<!-- 将<scrip>中定义的菜单内容循环显示 -->
<div class="menu-item" v-for="item in menus">

<!-- 借助iconfont.css显示官方图标-->
<div
:class="[
'menu-item',
item.menuCode == currentMenu.menuCode ? 'active' : '',
]"
@click="jump(item)"
>
<!-- 显示图标 -->
<div :class="['iconfont', 'icon-' + item.icon]"></div>
<!-- 显示对应name -->
<div class="text">{{item.name}}</div>
</div>
</div>
</div>

<!-- 二级目录 -->
<div class="menu-sub-list">
<!-- 子内容 -->
<!-- :class 是选中的部分高亮 -->
<div
v-for="sub in currentMenu.children"
:class="['menu-item-sub', currentPath == sub.path ? 'active' : '']"
@click="jump(sub)"
>
<span
:class="['iconfont','icon-'+sub.icon]"
v-if="sub.icon"></span>
<span class="text">{{ sub.name }}</span>
</div>
<!-- 存在该属性就显示 -->
<div class="tips" v-if="currentMenu&&currentMenu.tips">
{{currentMenu.tips}}</div>

<!-- 这个是要取数据才显示的 -->
<div class="space-info">
<div>!!!空间使用</div>
<div class="percent"></div>
</div>
</div>

</div>


<div class="body-content">
<!-- v-slot="{ Component } 解构插槽 -->
<!-- 让router-view的插槽能够访问子组件中的数据 -->
<!-- 访问的数据就是Component -->
<router-view v-slot="{ Component }">
<!-- 调用Main子组件 将Main中的数据接收到Framework中 -->
<component :is="Component"></component>
</router-view>
</div>
</div>

</div>

</template>

<script setup>
import {
ref,
reactive,
getCurrentInstance,
watch,
nextTick,
computed,
} from "vue";

import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();

const { proxy } = getCurrentInstance();
const userInfo = ref({nickName:"张三"});


// 菜单栏
const menus = [
{
// 一级目录
icon: "cloude",
name: "首页",
menuCode: "main",
path: "/main/all",
allShow: true,
// 二级目录
children: [
{
icon: "all",
name: "全部",
category: "all",
path: "/main/all",
},
{
icon: "video",
name: "视频",
category: "video",
path: "/main/video",
},
{
icon: "music",
name: "音频",
category: "music",
path: "/main/music",
},
{
icon: "image",
name: "图片",
category: "image",
path: "/main/image",
},
{
icon: "doc",
name: "文档",
category: "doc",
path: "/main/doc",
},
{
icon: "more",
name: "其他",
category: "others",
path: "/main/others",
},
],
},
{
path: "/myshare",
icon: "share",
name: "分享",
menuCode: "share",
allShow: true,
children: [
{
name: "分享记录",
path: "/myshare",
},
],
},
{
path: "/recycle",
icon: "del",
name: "回收站",
menuCode: "recycle",
tips: "回收站为你保存10天内删除的文件",
allShow: true,
children: [
{
name: "删除的文件",
path: "/recycle",
},
],
},
{
path: "/settings/fileList",
icon: "settings",
name: "设置",
menuCode: "settings",
allShow: false,
children: [
{
name: "用户文件",
path: "/settings/fileList",
},
{
name: "用户管理",
path: "/settings/userList",
},
{
path: "/settings/sysSetting",
name: "系统设置",
},
],
},
];

const currentMenu=ref({});
const currentPath=ref();
//设置当前菜单栏
const setMenu=(menuCode,path)=>{

const menu=menus.find((item)=>{
return item.menuCode === menuCode;
});
currentMenu.value = menu;
currentPath.value = path;
};

//setMenu的 高亮部分
watch(
() => route,
(newVal, oldVal) => {
if (newVal.meta.menuCode) {
setMenu(newVal.meta.menuCode, newVal.path);
}
},
{ immediate: true, deep: true }
);

// 菜单栏选项跳转
const jump = (data) => {
if (!data.path || data.menuCode == currentMenu.value.menuCode) {
return;
}
router.push(data.path);
};
</script>
<style lang="scss" scoped>
.header {
box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%);
height: 56px;
padding-left: 24px;
padding-right: 24px;
position: relative;
z-index: 200;
display: flex;
align-items: center;
justify-content: space-between;

.logo {
display: flex;
align-items: center;
.icon-pan {
font-size: 40px;
color: #1296db;
}

.name {
font-weight: bold;
margin-left: 5px;
font-size: 25px;
color: #05a1f5;
}
}
.right-panel {
display: flex;
align-items: center;
.icon-transfer {
cursor: pointer;
}

.user-info {
margin-right: 10px;
display: flex;
align-items: center;
cursor: pointer;
// 头像
.image {
margin: 0px 5px 0px 15px;
}
// 昵称
.nick-name {
color: #05a1f5;
}
}
}
}
.body {
display: flex;
.left-sider {
border-right: 1px solid #f1f2f4;
display: flex;
.menu-list {
height: calc(100vh - 56px);
width: 80px;
box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%);
border-right: 1px solid #f1f2f4;
.menu-item {
text-align: center;
font-size: 14px;
font-weight: bold;
padding: 20px 0px;
cursor: pointer;
&:hover {
background: #f3f3f3;
}
.iconfont {
font-weight: normal;
font-size: 28px;
}
}
.active {
.iconfont {
color: #06a7ff;
}
.text {
color: #06a7ff;
}
}
}
.menu-sub-list {
width: 200px;
padding: 20px 10px 0px;
position: relative;
.menu-item-sub {
text-align: center;
line-height: 40px;
border-radius: 5px;
cursor: pointer;
&:hover {
background: #f3f3f3;
}
.iconfont {
font-size: 14px;
margin-right: 20px;
}
.text {
font-size: 13px;
}
}
.active {
background: #eef9fe;
.iconfont {
color: #05a1f5;
}
.text {
color: #05a1f5;
}
}

.tips {
margin-top: 10px;
color: #888888;
font-size: 13px;
}

.space-info {
position: absolute;
bottom: 10px;
width: 100%;
padding: 0px 5px;
.percent {
padding-right: 10px;
}
.space-use {
margin-top: 5px;
color: #7e7e7e;
display: flex;
justify-content: space-around;
.use {
flex: 1;
}
.iconfont {
cursor: pointer;
margin-right: 20px;
color: #05a1f5;
}
}
}
}
}
.body-content {
flex: 1;
width: 0;
padding-left: 20px;
}
}
</style>

上传头像

main.js引入组件Image.vue

1
2
import Avatar from '@/components/Image.vue'
app.component("Avatar",Avatar)

Layout.vue中使用

1
<Avatar></Avatar>

包括修改头像/修改密码部分

修改头像

新定义组件:Avatarupload.vue

新建views,UpdateAvatar.vue,用于实现弹窗功能,引用组件<AvatarUpload>以显示头像,定义show方法

Layout.vue引用<UpdateAvatar>

效果如下:

而读取头像呢,需要后端实现 api/getAvatar/{Userid}接口

更新头像呢,需要后端实现api/updateuseravatar接口

修改密码

views下新建个UpdatePass.vue,实现相应功能

Layout.vue引用<UpdatePass>

要求后端实现:updatePass

退出登录

utils下引入Confirm.js

Layout.vue写logout方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 退出
const logout=async ()=>{
proxy.Confirm('确定要退出咩?',async()=>{

let result=await proxy.Request({
url:api.logout,
});
if(!result) return;

proxy.VueCookies.remove(userInfo);
router.push("/login")
});
};

需要后端实现/logout

文件框架

新建Table.vue,作为组件,去main.js中引用

模板部分:

定义了表、列及分页

1
2
3
4
5
6
7
8
9
10
11
<el-table
ref="dataTable"
:data="dataSource.list || []"
:height="tableHeight"
:stripe="options.stripe"
:border="options.border"
header-row-class-name="table-header-row"
highlight-current-row
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
>

ref,引用名称,dataTabel,可以在代码中使用此引用来直接访问“<el-table>”组件的方法和属性

data,绑定数据源

@row-click/@selection-change,事件监听器

该el-table包含三个 el-table-column,分别为selection选择框、序号和数据列

最后是分页列

脚本部分:

用emit,去将选中行上传给父组件

tableHeight实现页面内部滚动

Main.vue界面

文件列表

assets文件夹中的icon-image引入一些文件类型的图片,比如pdf、doc等

component文件夹也加入icon组件,使得文件类型与图标匹配

后端部分

为了实现切面

AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在Spring的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发

然后把切面功能和核心业务功能”编织”在一起,这就叫AOP
————————————————
版权声明:本文为CSDN博主「IMKKA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45982116/article/details/109570766

aop实现参数校验

引入注解部分,新建annotation,加入GlobalInterceptorVerifyParam

enums文件夹中引入正则VerifyRegexEnum

新建aspect,加入GlobalOpaspect

其目的是什么?

sendEmailCode为例

1
2
3
4
5
6
public ResponseVO sendEmailCode(HttpSession session, String email, String checkcode, Integer type){
try {
// 如果验证码错误
if (!checkcode.equals(session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
throw new BusinessException("验证码错误");
}

如果没有引入全局拦截的话,当在前端输入一个不正确的验证码时,会进入到try的if语句进行判断;

如果引入了全局拦截,则在GlobalOpaspectcheckValue方法中就进行了拦截

等于说以后就不需要判断了,交给AOP去实现这个判断

注册功能

UserInfoservice接口声明register方法UserInfoserviceImpl实现

AccountController调用该方法即可

登录功能

新建dto文件夹的 SessionWebUserDto类,定义了昵称、id、身份标识、头像等字段,及其get和set方法;UserSpaceDto类,定义了userspace和totalspace及其get和set方法

UserInfoservice接口声明login方法,其返回类型为 SessionWebUserDto

AccountController调用该方法即可

登录成功,且跳转到main/all

密码重置

UserInfoservice声明 restPwd 方法

AccountController调用

头像获取

UserInfoservice声明 getAvatar 方法

AccountController调用

主要逻辑为读取默认目录下的默认图片(自定义图片优先于默认)

请求该路径得:

无默认图片时

放入默认图片得

有默认图片

如果真的有命名为123456.jpg的图片时,才会读取指定图片

其它功能:

获取用户信息 getUserInfo(HttpSession session)

获取用户空间 getUseSpace(HttpSession session)

登出 logout

更新头像 updateUserAvatar(HttpSession session, MultipartFile avatar)

更新密码 updatePassword(HttpSession session,String password)

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
CREATE TABLE `file_info` (
`file_id` varchar(10) NOT NULL COMMENT '文件ID',
`user_id` varchar(10) NOT NULL COMMENT '用户ID',
`file_md5` varchar(32) DEFAULT NULL COMMENT '文件md5值',
`file_pid` varchar(10) DEFAULT NULL COMMENT '父级ID',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小',
`file_name` varchar(200) DEFAULT NULL COMMENT '文件名',
`file_cover` varchar(100) DEFAULT NULL COMMENT '封面(图片/视频)',
`file_path` varchar(100) DEFAULT NULL COMMENT '文件路径',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
`folder_type` tinyint(1) DEFAULT NULL COMMENT '0:文件 1:目录',
`file_category` tinyint(1) DEFAULT NULL COMMENT '文件分类 1:视频 2:音频 3:图片 4:文档 5:其它',
`file_type` tinyint(1) DEFAULT NULL COMMENT '1:视频 2:音频 3:图片 4:pdf 5:doc 6:excel 7:txt 8:code 9:zip 10:其它',
`status` tinyint(1) DEFAULT NULL COMMENT '0:转码中 1:转码失败 2:转码成功',
`recovery_time` datetime DEFAULT NULL COMMENT '进入回车站时间',
`del_flag` tinyint(1) DEFAULT NULL COMMENT '标记删除 0:删除 1:回收站 2:正常',
PRIMARY KEY (`file_id`,`user_id`),
KEY `idx_create_time` (`create_time`) USING BTREE,
KEY `idx_user_id` (`user_id`) USING BTREE,
KEY `idx_md5` (`file_md5`),
KEY `idx_file_pid` (`file_pid`),
KEY `idx_del_flag` (`del_flag`),
KEY `idx_recover_time` (`recovery_time`),
KEY `idx_status` (`status`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='文件信息表';

Easycode生成相应代码。

FileInfoController先实现 loadDataList方法,从数据库中加载对象并返回

再实现 uploadFile方法,实现切片上传,再合并

缺陷

验证码输入错误,不自动更新,嘶;而且向邮箱发验证码,第一次必不成功捏

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 是羽泪云诶
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信