目录
- 完成注册功能
- 后端开发完成
- UserController
- UserServiceImpl
- LogininfoMapper
- 前端页面完成
- 绑定数据
- 绑定事件
- 准备登录页
- 管理员登录1
- 需求分析
- 登录设计
- 页面设计
- 表设计
- 流程设计
- 所需技术
- 员工新增级联操作登录信息
- EmployeeServiceImpl
- ShopServiceImpl
- 管理员登录2
- 前端页面
- 后端接口
- LoginController
- LoginServiceImpl
- logininfoMapper
- 前端登录完成
- 后端拦截
- PetHomeWebMvcConfigurer
- LoginInterceptor
- 前端拦截
- 路由拦截
- 其他
分别启动后端,后台,前台和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
@Overridepublic 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@Transactionalpublic void add(Employee employee) {initEmployee(employee);Logininfo logininfo = employee2LoginInfo(employee);logininfoMapper.save(logininfo);employee.setLogininfo(logininfo);employeeMapper.save(employee);}@Overridepublic void update(Employee employee) {//1.修改loginInfoLogininfo logininfo = logininfoMapper.loadById(employee.getLogininfo_id());if(logininfo != null){BeanUtils.copyProperties(employee, logininfo);logininfoMapper.update(logininfo);employeeMapper.update(employee);}}@Overridepublic 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 {@AutowiredILoginService 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 {@Autowiredprivate LogininfoMapper logininfoMapper;@Autowiredprivate RedisTemplate redisTemplate;/*** 账号登录* loginDto 中表明了是哪一个端登录 type = 0*/@Overridepublic 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_logininfoWHERE (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){//保存信息到localstoragevar 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 {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic 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 {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {//判断是否登录? 前端需要给我们传递 tokenString token = request.getHeader("token");// fde6185f-36ac-4df0-9c0e-9d4c4ebb617cif(!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=>{//携带tokenlet 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/accountaxios携带token到后台 main.js
为了后端校验是否已经登录,只要用axios的请求都要携带token后端登录拦截
写拦截器 配置拦截器
前台对后台拦截结果跳转登录页面 main.js 后台拦截器实现后端已经退出登录的跳转登录页面
前端拦截器-页面没有和后台进行数据交互 登录和注册在没有登录情况下也能访问