B071-项目实战-用户模块--手机号注册 管理员登录


分别启动后端,后台,前台和redis
ph-admin:npm run dev,ph-web:live-server --port=80
redis:redis-server.exe redis.windows.conf

完成注册功能

在这里插入图片描述

后端开发完成
UserController
    /**
     *  用户注册接口
     * @param userDto 自定义Dto接收前端参数
     * @return
     */
    @PostMapping("/register/code")
    public AjaxResult redisterCode(@RequestBody UserDto userDto){
        try {
            userService.redisterCode(userDto);
            return AjaxResult.me();
        } catch (BusinessException e) {
            return AjaxResult.me().setMessage(e.getMessage());
        }catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setMessage("系统繁忙,稍后重试!!");
        }
    }
UserServiceImpl
	@Override
    public void redisterCode(UserDto userDto) {
        //1.校验
        //  1.1空校验
        if(StringUtils.isEmpty(userDto.getPhone()) ||
                StringUtils.isEmpty(userDto.getVerifycode()) ||
                StringUtils.isEmpty(userDto.getPassword()) ||
                StringUtils.isEmpty(userDto.getConfirmPwd()) ){
           throw new BusinessException("请输入完整信息在进行注册!");
        }
        //  1.2两次密码是否一致
        if(!userDto.getPassword().equals(userDto.getConfirmPwd())){
            throw new BusinessException("密码不一致,请重新输入!");
        }
        //  1.3是否被注册
        Logininfo obj = logininfoMapper.loadByDto(userDto);
        if(obj != null){
            throw new BusinessException("用户已经存在!");
        }
        //2.验证码是否存在
        Object codeObj = redisTemplate.opsForValue().get(UserConstant.USER_VERFIY_CODE+":"+userDto.getPhone());
        //  2.1 验证码是否过期
        if(codeObj == null){
            throw new BusinessException("验证码已经过期,请重新发送验证码!");
        }
        String code = ((String)codeObj).split(":")[0];
        //  2.2 验证码是否正确
        if(!userDto.getVerifycode().equalsIgnoreCase(code)){
            throw new BusinessException("请输入正确的验证码!");
        }
        //3.登录成功
        // 3.1登录信息表
        User user = userDto2User(userDto);
        Logininfo logininfo = user2LoginInfo(user);
        logininfoMapper.save(logininfo);//返回自增id
        // 3.2用户表
        user.setInfo(logininfo);//引用属性传值
        userMapper.save(user);
    }

    /**
     * 将dto转换成实体对象
     * @param userDto
     * @return
     */
    private User userDto2User(UserDto userDto) {
        //1.定义需要返回的对象
        User user = new User();
        //2.封装数据
        user.setUsername(userDto.getPhone());
        user.setPhone(userDto.getPhone());
        /*private String salt;
        private String password;*/
        String salt = StrUtils.getComplexRandomString(32);
        String md5Pwd = MD5Utils.encrypByMd5(userDto.getPassword() + salt);
        user.setSalt(salt);//存储加密使用的盐值
        user.setPassword(md5Pwd);//存储使用盐值加密之后的密码
        //3.返回
        return user;
    }

    /**
     * 通过user拷贝LoginInfo
     * @param user
     * @return
     */
    private Logininfo user2LoginInfo(User user) {
        Logininfo info = new Logininfo();
        //直接使用工具类拷贝  同名原则拷贝属性值
        BeanUtils.copyProperties(user, info);
        info.setType(1);//设置前端用户默认值
        return info;
    }
LogininfoMapper
    <!--Logininfo loadByDto(UserDto userDto);-->
    <select id="loadByDto" parameterType="cn.itsource.user.dto.UserDto" resultType="Logininfo">
        SELECT * FROM t_logininfo WHERE phone =  #{phone} and type = #{type}
    </select>
前端页面完成
绑定数据
		data:{
			phoneUserForm:{
				phone:"",
				verifycode:"",
				password:"1",
				confirmPwd:"1",
				type:1
			}
		},
<form method="post">
	 <div class="user-phone">
		<label for="phone"><i class="am-icon-mobile-phone am-icon-md"></i></label>
		<input type="tel" v-model="phoneUserForm.phone" name="" id="phone" placeholder="请输入手机号">
	 </div>
	<div class="verification">
		<label for="code"><i class="am-icon-code-fork"></i></label>
		<input type="tel" name="" v-model="phoneUserForm.verifycode" id="code" placeholder="请输入验证码">
		<!--<a class="btn" href="javascript:void(0);" οnclick="sendMobileCode();" id="sendMobileCode">
			<span id="dyMobileButton">获取</span></a>-->
		<button type="button" @click="sendMobileCode">获取</button>
	</div>
	 <div class="user-pass">
		<label for="password"><i class="am-icon-lock"></i></label>
		<input type="password" name="" v-model="phoneUserForm.password" id="password" placeholder="设置密码">
	 </div>
	 <div class="user-pass">
		<label for="passwordRepeat"><i class="am-icon-lock"></i></label>
		<input type="password" name="" v-model="phoneUserForm.confirmPwd" id="passwordRepeat" placeholder="确认密码">
	 </div>
</form>
绑定事件
<div class="am-cf">
	<input type="submit" name="" @click="register" value="注册" class="am-btn am-btn-primary am-btn-sm am-fl">
</div>
            register(){
                this.$http.post("/user/register/code",this.phoneUserForm)
					.then(result=>{
                        result = result.data;
                        if(result.success){
                            //成功之后? 提示成功
                            alert("注册成功!");
							//跳转到登录页
							location.href="login.html";
						}else{
                            alert("注册失败:"+result.message);
						}
					})
					.catch(result=>{
					    alert("系统异常!");
					})

			},
准备登录页

拷贝login.html到根目录并修改路径

管理员登录1

需求分析,页面,表设计,流程设计,

需求分析

见文档

登录设计
页面设计

见文档

表设计

见文档

流程设计

在这里插入图片描述
+文档

所需技术

见文档

员工新增级联操作登录信息

店铺入驻保存shop表和employee表的时候要同步存入logininfo表
员工新增保存employee表的时候也要保存logininfo表

做法:分别在EmployeeServiceImpl和UserServiceImpl重写add,update,del方法,方法内加入同步操作logininfo表的代码

EmployeeServiceImpl
	/**
     * 针对于loginInfo的级联操作:员工和用户都应该有
     * 这里我们只写 员工的
     */
    @Override
    @Transactional
    public void add(Employee employee) {
        initEmployee(employee);
        Logininfo logininfo = employee2LoginInfo(employee);
        logininfoMapper.save(logininfo);
        employee.setLogininfo(logininfo);
        employeeMapper.save(employee);
    }

    @Override
    public void update(Employee employee) {
        //1.修改loginInfo
        Logininfo logininfo = logininfoMapper.loadById(employee.getLogininfo_id());
        if(logininfo != null){
            BeanUtils.copyProperties(employee, logininfo);
            logininfoMapper.update(logininfo);
            employeeMapper.update(employee);
        }
    }

    @Override
    public void delete(Long id) {
        Employee employee = employeeMapper.loadById(id);
        if(employee != null){
            logininfoMapper.remove(employee.getLogininfo_id());
            employeeMapper.remove(id);
        }
    }

    private void initEmployee(Employee employee) {
        //设置盐值
        String salt = StrUtils.getComplexRandomString(32);
        String md5Pwd = MD5Utils.encrypByMd5(employee.getPassword()+salt);
        employee.setSalt(salt);
        employee.setPassword(md5Pwd);
    }

    private Logininfo employee2LoginInfo(Employee employee) {
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(employee, logininfo);
        logininfo.setType(0);
        return logininfo;
    }
ShopServiceImpl
        //2.先保存员工数据 保存之后返回自增id
//        employeeMapper.save(admin);
        employeeService.add(admin);

管理员登录2

前端页面

ph-admin - login.vue
标签元素 - data - 按钮 - methods

<template>
  <el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px" class="demo-ruleForm login-container">
    <h3 class="title">系统登录</h3>
    <el-form-item prop="account">
      <el-input type="text" v-model="ruleForm2.username" auto-complete="off" placeholder="账号"></el-input>
    </el-form-item>
    <el-form-item prop="checkPass">
      <el-input type="password" v-model="ruleForm2.password" auto-complete="off" placeholder="密码"></el-input>
    </el-form-item>
    <el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>
    <el-form-item style="width:100%;">
      <el-button type="primary" style="width:47%;" @click.native.prevent="login" :loading="logining">登录</el-button>
      <el-button type="success" style="width:47%;" @click.native.prevent="goRegister">店铺入驻</el-button>
    </el-form-item>
  </el-form>
</template>

<script>
  import { requestLogin } from '../api/api';
  //import NProgress from 'nprogress'
  export default {
    data() {
      return {
        logining: false,
        ruleForm2: {
          username: '3',
          password: '3',
          type:0//表名是后端员工登录
        },
        rules2: {
          account: [
            { required: true, message: '请输入账号', trigger: 'blur' },
            //{ validator: validaePass }
          ],
          checkPass: [
            { required: true, message: '请输入密码', trigger: 'blur' },
            //{ validator: validaePass2 }
          ]
        },
        checked: true
      };
    },
    methods: {
      goRegister(){//这里是拷贝的登录成功只有跳转主页
        this.$router.push({ path: '/shopRegister' });/*店铺入驻*/
      },
      handleReset2() {
        this.$refs.ruleForm2.resetFields();
      },
      login(ev) {
        this.$refs.ruleForm2.validate((valid) => {  //点击登录按钮的时候,触发表单校验
          if (valid) {
            this.logining = true; //开启忙等框
            this.$http.post("/login/account",this.ruleForm2)
                    .then(result=>{
                      this.logining = false;//关闭忙等框
                      result = result.data;
                      if(result.success){
                        //提示登录成功
                        this.$message({
                          message: "登录成功!",
                          type: 'success'
                        });
                        //跳转到主页
                        this.$router.push({ path: '/echarts' });
                      }else{
                        this.$message({
                          message: result.message,
                          type: 'error'
                        });
                      }
                    })
                    .catch(result=>{
                      this.logining = false;//关闭忙等框
                      this.$message({
                        message: "系统异常",
                        type: 'error'
                      });
                    })
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      }
    }
  }
</script>
后端接口
LoginController
/**
 * 统一登录接口
 */
@RestController
@RequestMapping("/login")
public class LoginController {
    @Autowired
    ILoginService loginService;

    /**
     * 账号登录,支持前后端账号登录
     */
    @PostMapping("/account")
    public AjaxResult account(@RequestBody LoginDto loginDto){
        try {
            return loginService.account(loginDto);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setMessage("系统繁忙,稍后重试!");
        }
    }
}
LoginServiceImpl

在这里插入图片描述

/**
 * 统一登录业务层
 */
@Service
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {
    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 账号登录
     * loginDto 中表明了是哪一个端登录 type = 0
     */
    @Override
    public AjaxResult account(LoginDto loginDto) {
        //1.校验
        // 1.1 空校验  loginDto所有字段都要校验
        if(StringUtils.isEmpty(loginDto.getUsername()) ||
                StringUtils.isEmpty(loginDto.getPassword()) ||
                StringUtils.isEmpty(loginDto.getType()) ){
            return AjaxResult.me().setMessage("用户或密码不能为空!");
        }
        // 1.2 判断用户是否存在  查询loginInfo表
        Logininfo logininfo = logininfoMapper.loadByLoginDto(loginDto);
        if (logininfo == null) {
            return AjaxResult.me().setMessage("用户不存在!");
        }
        // 1.3 账号是否被禁用
        if(logininfo.getDisable() != 1){
            return AjaxResult.me().setMessage("账号被禁用,请联系管理员!");
        }
        // 2.判断密码是否正确
        String salt = logininfo.getSalt();
        String md5pwd = logininfo.getPassword();
        String md5PwdTmp = MD5Utils.encrypByMd5(loginDto.getPassword() + salt);
        if(!md5pwd.equals(md5PwdTmp)){
            // 2.2 密码不等  抛错
            return AjaxResult.me().setMessage("用户名或密码错误!");
        }
        // 2.1 如果密码相等  登录成功  存redis,封装返回值
        /**
         * 这里的redis  key就是前端需要存储的token
         */
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
        Map<String,Object> map = new HashMap<>();
        map.put("token", token);
        map.put("logininfo",logininfo);
        return AjaxResult.me().setResultObj(map);
    }
}
logininfoMapper
    <select id="loadByLoginDto" parameterType="cn.ming.basic.dto.LoginDto" resultType="Logininfo">
        SELECT * FROM t_logininfo
        WHERE (username =  #{username} or phone = #{username} or email = #{username})
        and type = #{type}
    </select>
前端登录完成

保存信息到localstorage

	login(ev) {
        this.$refs.ruleForm2.validate((valid) => {  //点击登录按钮的时候,触发表单校验
          if (valid) {
            this.logining = true; //开启忙等框
            this.$http.post("/login/account",this.ruleForm2)
                    .then(result=>{
                      this.logining = false;//关闭忙等框
                      result = result.data;
                      if(result.success){
                        //保存信息到localstorage
                        var resultObj =  result.resultObj;
                        localStorage.setItem("token",resultObj.token);
                        localStorage.setItem("logininfo",JSON.stringify(resultObj.logininfo));
                        //提示登录成功
                        this.$message({
                          message: "登录成功!",
                          type: 'success'
                        });
                        //跳转到主页
                        this.$router.push({ path: '/echarts' });
                      }else{
                        this.$message({
                          message: result.message,
                          type: 'error'
                        });
                      }
                    })
                    .catch(result=>{
                      this.logining = false;//关闭忙等框
                      this.$message({
                        message: "系统异常",
                        type: 'error'
                      });
                    })
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      }

前端右上角展示登录人信息

		mounted() {
			var user = localStorage.getItem('logininfo');
			if (user) {
				user = JSON.parse(user);
				this.sysUserName = user.username || user.email || user.phone || '';
				this.sysUserAvatar = user.avatar || '';
			}
		}
后端拦截

除了登录和注册相关的其他所有ajax请求都应该拦截
如:注册,发送验证码,店铺入驻,图片上传,

PetHomeWebMvcConfigurer
@Configuration
public class PetHomeWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login/**")
                .excludePathPatterns("/user/register/**")
                .excludePathPatterns("/verifycode/**")
                .excludePathPatterns("/fastDfs")
                .excludePathPatterns("/shop/settlement");
    }
}
LoginInterceptor
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        //判断是否登录?  前端需要给我们传递 token
        String token = request.getHeader("token");// fde6185f-36ac-4df0-9c0e-9d4c4ebb617c
        if(!StringUtils.isEmpty(token)){
            Object obj = redisTemplate.opsForValue().get(token);
            if(obj !=null){//已经登录过
                //刷新redis的token存储时间
                redisTemplate.opsForValue().set(token, obj, 30, TimeUnit.MINUTES);
                //放行
                return true;
            }
        }
        //如果已经登录过期
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write("{\"success\":false,\"message\":\"noLogin\"}");
        writer.flush();
        writer.close();

        //@TODO 权限校验
        return false;
    }
}
前端拦截

axios前置拦截:发送axios请求前给请求加上token

axios后置拦截:接收到后端拦截器的nologin信息后跳到登录页

写在main.js,vue下面

/**axios前置拦截器
 *作用:每次发送axios请求,需要携带token给后端
 */
axios.interceptors.request.use(config=>{
  //携带token
  let uToken =  localStorage.getItem("token");
  if(uToken){
    config.headers['token']=uToken;
  }
  return config;//一定要返回配置
},error => {
  Promise.reject(error);
})

/**
 * axios后置拦截:       作用:接收后端拦截器报错,在这里处理
 */
axios.interceptors.response.use(result=>{
  console.log(result.data+"jjjjjjj");
  let data = result.data;
  if(!data.success && data.message==="noLogin"){
    localStorage.removeItem('token');
    localStorage.removeItem('logininfo');
    router.push({ path: '/login' });
  }
  return result;
},error => {
  Promise.reject(error);
})
路由拦截

http://localhost:8081/#/
根首页并没有触发axios请求,是直接访问直接打开的,也需要拦截
登录和注册页面要放行

router.beforeEach((to, from, next) => {
  if (to.path == '/login' || to.path == '/shopRegister') {
    localStorage.removeItem('token');
    localStorage.removeItem('logininfo');
    next()//需要放行
  }else{
    let user = JSON.parse(localStorage.getItem('logininfo'));
    if (!user) {//如果没有值,定位到登录页
      next({ path: '/login' })
    } else {//如果有值,正常访问页面
      next()
    }
  }
})
其他
管理员登录

前提:login.vue中集成axios和vue
整体思路  先不要考虑登录拦截,先把登录做好
1)后台登录接口
2)前台登录实现并且保存loginInfo和token到localStorage,登录成功跳转首页,并展示用户名
3)前台通过axios的前置拦截器携带token到后台
4)后台做token的登录拦截器,如果没有回报错给前台
5)前台通过axios后置拦截器对后台登录拦截错误进行跳转到登录页面
6)前台也要做拦截-有的地址是不需要访问后台

后端接口
LoginDto 	
LoginController	/login/acount	loginAccount
LoginInfoServiceImpl 	loginAcount
xml

后台登录
Login.vue	/login/account

axios携带token到后台 main.js
为了后端校验是否已经登录,只要用axios的请求都要携带token

后端登录拦截
写拦截器	配置拦截器
前台对后台拦截结果跳转登录页面 main.js			后台拦截器实现后端已经退出登录的跳转登录页面
前端拦截器-页面没有和后台进行数据交互		 	  登录和注册在没有登录情况下也能访问
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值