详解SpringBoot项目整合Vue做一个完整的用户注册功能

前言

用户注册功能是每一个系统的入口门面功能,很多人可能会以为很简单,不就是一个简单的CRUD吗?其实不然,要把前后端功能都做出来,页面跳转也没问题,还真不简单。这次笔者做这么一个看似简单的用户注册功能就花了足足两天多时间,中间调试和解决Bug也花了好长时间。这次我就把自己做出的完整功能的实现过程作了一个提炼分享到我的公众号上来。希望有需要了解如何实现用户注册完整过程的读者朋友能够仔细看一看。

说明:本文前后端代码的实现分别在本人之前二次开发的开源项目vue-element-adminvueblog两个项目的基础上进行

1 实现用户注册流程

1.1 用户注册完整流程

详解SpringBoot项目整合Vue做一个完整的用户注册功能_第1张图片

1.2 用户注册信息及校验

详解SpringBoot项目整合Vue做一个完整的用户注册功能_第2张图片

2 后台接口设计

2.1 上传头像接口

2.1.1 接口url

http://localhost:8081/blog/upload/user/avatar

2.1.2 请求类型

POST

2.1.3 接口入参

参数名称 参数类型 是否必传 备注
file MultipartFile 多媒体图片文件

2.1.4 接口出参

参数名称 参数类型 示例值 备注
status Integer 200 状态码:200-成功; 500-失败
msg String “success” 响应信息:“success”-上传头像成功; "upload file failed"-上传头像失败
data String vueblog2022.oss-cn-shenzhen.aliyuncs.com/avatar/63be… 上传头像成功后的下载地址

2.2 用户注册接口

2.2.1 接口url

http://localhost:8081/blog/user/reg

2.2.2 请求类型

POST

2.2.3 接口入参

参数名称 参数类型 是否必填 备注
username String 用户账号
nickname String 用户昵称
password String 用户登录密码
userface String 用户头像链接地址
phoneNum Long 用户手机号码
email String 用户邮箱地址

2.2.3 接口出参

参数名称 参数类型 示例值 备注
status Integer 200 响应码: 200-成功;500-失败
msg String 注册成功 响应消息
data Integer 0 注册成功标识:0-注册成功;1-用户名重复; null-内部服务异常

3 后端代码实现

3.1 用户头像上传接口编码实现

文件上传,这里选用了阿里云的对象存储,需要先开通阿里云对象存储服务,关于如何开通阿里云短信服务并将阿里云对象存储服务集成到SpringBoot项目中,请参考我之前发布的文章SpringBoot项目集成阿里云对象存储服务实现文件上传

3.1.1 服务层编码

新建OssClientService类继承阿里云对象存储服务SDK完成图片上传功能

@Service
public class OssClientService {

    @Resource
    private OssProperties ossProperties;

    private static final Logger logger =  LoggerFactory.getLogger(OssClientService.class);

    public String uploadFile(MultipartFile file){
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndPoint(), ossProperties.getAccessKey(),
                ossProperties.getSecretKey());
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        String objectName = "avatar/" + uuid + ".png";
        String imageUrl = null;
        try {
            InputStream inputStream =  file.getInputStream();  
            ossClient.putObject(ossProperties.getBucketName(), objectName, inputStream);
            imageUrl = "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndPoint() + "/" + objectName;
        } catch (OSSException oe) {
            logger.error("Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.");
            logger.error("Error Message:" + oe.getErrorMessage());
            logger.error("Error Code:" + oe.getErrorCode());
            logger.error("RequestId: " + oe.getRequestId());
            logger.error("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            logger.error("Caught an ClientException, which means the client encountered a serious internal problem " +
                    "while trying to communicate with OSS,such as not being able to access the network");
            logger.error("Error Message:" + ce.getErrorMessage());
        } catch (FileNotFoundException fe) {
            logger.error("file not found exception");
            logger.error("Error Message:" + fe.getMessage(), fe);
        } catch (IOException exception){
            logger.error("file get input stream error, caused by " + exception.getMessage(), exception);
        }
        finally {
            if (ossClient!=null) {
                ossClient.shutdown();
            }
        }
        return imageUrl;
    }
}

注意:升级到3.9.1版本后的aliyun-sdk-oss需要在每次上传文件时新建一个OSS实例, 上传完文件之后再调用shutdown方法关闭这个实例

3.1.2 控制器层编码

新建UploadFileController类完成从前端接收附件参数,并调用OssClientService服务实现图片上传

@RestController
@RequestMapping("/upload")
public class UploadFileController {

    @Resource
    private OssClientService ossClientService;

    @PostMapping("/user/avatar")
    @ApiOperation(value = "userAvatar", notes = "用户上传头像接口",
    produces = "application/octet-stream", consumes = "application/json")
    public RespBean uploadUserAvatar(HttpServletRequest request){
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        // 获取上传文件对象
        MultipartFile file = multipartRequest.getFile("file");
        RespBean respBean = new RespBean();
        String downloadUrl = ossClientService.uploadFile(file);
        if (!StringUtils.isEmpty(downloadUrl)) {
            respBean.setStatus(200);
            respBean.setMsg("success");
            respBean.setData(downloadUrl);
        } else {
            respBean.setStatus(500);
            respBean.setMsg("upload file failed");
        }
        return respBean;
    }
}

3.2 用户注册接口

3.2.1 数据库访问层编码

UserMapper接口类中新增注册用户抽象方法

int registerUser(UserDTO user);

然后在UserMapper.xml文件中完成用户数据入库sql编写


        INSERT INTO user(username, nickname, password, phoneNum,email, userface, regTime,enabled)
        values(#{username,jdbcType=VARCHAR},#{nickname,jdbcType=VARCHAR},
        #{password,jdbcType=VARCHAR}, #{phoneNum,jdbcType=BIGINT}, #{email,jdbcType=VARCHAR},
        #{userface,jdbcType=VARCHAR},now(),1)
    

3.2.2 服务层编码

CustomUserDetailsService接口类中添加注册用户抽象方法

int registerUser(UserDTO user);

然后在 CustomUserDetailsService接口类的实现类UserService类中完成用户注册逻辑

    @Override
    public int registerUser(UserDTO user) {
        // 判断用户是否重复注册
        UserDTO userDTO  = userMapper.loadUserByUsername(user.getUsername());
        if (userDTO != null) {
            return 1;
        }
        //插入用户, 插入之前先对密码进行加密
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setEnabled(1);//用户可用
        int result = userMapper.registerUser(user);
        //配置用户的角色,默认都是普通用户
        List roleIds = Arrays.asList(2);
        int i = rolesMapper.setUserRoles(roleIds, user.getId());
        boolean b = i == roleIds.size() && result == 1;
        if (b) {
            // 注册成功
            return 0;
        } else {
            // 注册失败
            return 2;
        }
    }

3.2.3 控制器层编码

LoginRegController类中完成用户登录接口从前端接收参数到调用UserService服务类完成用户注册业务

@RequestMapping(value = "/login_page", method = RequestMethod.GET)
    @ApiOperation(value = "loginPage", notes = "尚未登录跳转", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
    public RespBean loginPage() {
        return new RespBean(ResponseStateConstant.UN_AUTHORIZED, "尚未登录,请登录!");
    }

    @PostMapping("/user/reg")
    @ApiOperation(value = "reg", notes = "用户注册", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
    public RespBean reg(@RequestBody UserDTO user) {
        int result = userService.registerUser(user);
        if (result == 0) {
            //成功
            return new RespBean(ResponseStateConstant.SERVER_SUCCESS, "注册成功!");
        } else if (result == 1) {
            return new RespBean(ResponseStateConstant.DUPLICATE_ERROR, "用户名重复,注册失败!");
        } else {
            //失败
            return new RespBean(ResponseStateConstant.SERVER_ERROR, "注册失败!");
        }
    }

由于以上两个接口都是需要放开权限控制的,因此完成以上两个接口的编码后还需要在security配置类WebSecurityConfig类中支持匿名访问

只需要在configure(HttpSecurity http)方法中添加如下几行代码即可

http.authorizeRequests()
                .antMatchers("/user/reg").anonymous()
                .antMatchers("/upload/user/avatar").anonymous()

完成后端编码后可以启动Mysql服务和redis服务,然后运行BlogserverApplication类中的Main方法成功后就可以通过postman工具测试接口了

4 前端代码实现

4.1 完成用户注册界面vue组件编码

src/views目录下新建register文件夹,然后在register目录下新建index.vue文件

完成用户注册组件编码

这里的文件上传选择了element-ui组件库中的upload组件





4.2 工具类中增加校验方法

src/utils/validate.js中增加校验密码和手机号码的方法

export function validatePhoneNum(phoneNum) {
  const reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
  return reg.test(phoneNum)
}

export function validatePassword(password) {
  // 强密码:字母+数字+特殊字符
  const reg = /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&*]+$)(?![\d!@#$%^&*]+$)[a-zA-Z\d!@#$%^&*]+$/
  return reg.test(password)
}

以上校验均使用正则表达式校验

4.3 API文件中添加用户注册方法

src/api/user.js文件中新增用户注册接口方法

export function register(data) {
  return request({
    url: '/user/reg',
    method: 'post',
    data
  })
}

4.4 全局方法中添加用户注册方法

src/store/modules/user.js 文件中的actions对象中增加用户注册行为方法

const actions = {
  // user register
  register({ commit }, registerInfo) {
    return new Promise((resolve, reject) => {
      register(registerInfo).then(response => {
        if (response.status === 200 && response.data.status === 200) {
          const resInfo = { status: response.status, msg: '注册成功' }
          resolve(resInfo)
        } else {
          const resInfo = { status: response.status, msg: response.data.msg }
          resolve(resInfo)
        }
      }).catch(error => {
        console.error(error)
        reject(error)
      })
    })
  },
    // ......省略其他已有方法
}

因为用户注册完之后需要跳转到登录界面,直接在注册页面调用后台用户注册接口成功后调用this.$router.push方法发现无法实现页面的跳转效果, 因此改为在vuex的全局dispatch中调用注册接口

4.5 路由列表中添加用户注册组件

src/router/index.js文件的固定路由列表中添加注册组件的路由

import Register from '@/views/register/index'

export const constantRoutes = [
  {
    id: '0',
    path: '/register',
    component: Register,
    hidden: true
  },
   //...... 省略其他路由
 ]

4.6 登录组件中添加用户注册的跳转链接

src/views/login/index.vue文件中的模板代码部分的登录按钮标签下面添加如下两行代码

忘记密码 注册账号

同时对忘记密码注册账号两个链接添加样式(忘记密码功能尚待实现)

4.7 路由跳转控制中添加白名单

在路由跳转控制文件src/permission.js文件中将注册用户的路由添加到白名单中

const whiteList = ['/login', '/register', '/auth-redirect'] // no redirect whitelist

如果不在白名单中加上用户注册的路由,你会发现在用户登录界面压根无法跳转到用户注册界面的

5 效果体验

在启动后端服务后,在vue-element-admin项目下通过 鼠标右键->git bash进入命令控制台

然后输入npm run dev 项目启动前端服务

然后在谷歌浏览器中输入:http://localhost:3000/回车进入登录界面

详解SpringBoot项目整合Vue做一个完整的用户注册功能_第3张图片

点击下面的【注册账号】链接就能跳转到用【用户注册】页面

详解SpringBoot项目整合Vue做一个完整的用户注册功能_第4张图片

填写好用户注册信息后就可以点击下面的【提交】按钮提交注册了,注册成功后系统会弹框提示用户中注册成功,并重新跳转到【用户登录】界面

6 写在最后

本文演示了在spring-boot项目中继承阿里云对象存储sdk实现了图片上传和用户提交登录两个接口的详细实现,同时前端使用element-ui库中的upload组件调用后端图片上传接口实现了附件上传功能,实现了一个完整的用户登录信息的校验和提交注册及注册成功后的页面跳转等功能。

相信对想要了解一个系统的用户模块是如何实现用户的注册以及注册成功后的页面跳转的完整功能的是如何实现的读者朋友一定会有所帮助的!

本文前后端项目代码git仓库地址如下,对源码感兴趣的读者朋友可以克隆到本地参考

blogserver项目gitee仓库地址

vue-element-admin项目gitee仓库地址

到此这篇关于SpringBoot项目整合Vue做一个完整的用户注册功能的文章就介绍到这了,更多相关SpringBoot项目整合Vue做一个完整的用户注册功能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(详解SpringBoot项目整合Vue做一个完整的用户注册功能)