怎么做网站seo,网站改版与优化协议书,上海人才网官网查询,深圳网站建设公司那家好客户管理模块 1.认证模块1.1 认证方式介绍1.1.1 小程序认证1.1.2 手机验证码登录1.1.3 账号密码认证 1.2 小程序认证1.2.1 小程序申请1.2.2 创建客户后端工程jzo2o-customer1.2.3 开发部署前端1.2.4 小程序认证流程1.2.4.1 customer小程序认证接口设计Controller层Service层调用… 客户管理模块 1.认证模块1.1 认证方式介绍1.1.1 小程序认证1.1.2 手机验证码登录1.1.3 账号密码认证 1.2 小程序认证1.2.1 小程序申请1.2.2 创建客户后端工程jzo2o-customer1.2.3 开发部署前端1.2.4 小程序认证流程1.2.4.1 customer小程序认证接口设计Controller层Service层调用Publics模块获取openid查询数据库构建token保存token到微服务的ThreadLocal中 1.2.5 手机验证码认证1.2.5.1 开发与部署app前端1.2.5.2 customer短信认证接口设计需求分析Controller层Service层 1.2.6 账号密码验证1.2.6.1 前端设计和部署1.2.6.2 customer账号密码验证接口设计Controller设计Service层设计顺便复习下BCrypt加密 1.2.7 注册功能实现1.2.7.1 需求1.2.7.2 Costomer注册功能接口实现Controller开发Service开发演示 1.2.8 找回密码功能1.2.8.1 需求1.2.8.2 Costomer找回密码功能接口实现Controller开发Service开发演示 1.认证模块
一般情况有用户交互的项目都有认证授权功能首先要搞清楚两个概念认证和授权。 认证: 就是校验用户的身份是否合法常见的认证方式有账号密码登录、手机验证码登录等。 授权:则是该用户登录系统成功后当用户去点击菜单或操作数据时系统判断该用户是否有权限有权限则允许继续操作没有权限则拒绝访问。
本项目包括四个端用户端(小程序)、服务端app、机构端(PC)、运营管理端(PC) 分别对应四类用户角色家政需求方即c端用户家政服务人员、家政服务公司机构、平台运营人员。
1.1 认证方式介绍
1.1.1 小程序认证
用户端通过小程序使用平台初次使用小程序会进行认证具体流程如下
1.1.2 手机验证码登录
服务人员通过app登录采用手机验证码认证方式输入手机号、发送验证码验证码校验通过则认证通过初次认证通过将自动注册服务人员信息。
1.1.3 账号密码认证
机构端认证方式是账号密码认证方式通过pc浏览器进入登录界面输入账号和密码登录系统 机构端提供单独的注册页面输入手机号接收验证码进行注册
1.2 小程序认证
下边测试用户端小程序的认证流程先参考微信官方提供的小程序登录流程先大概知道小程序认证流程需要几部分如下图 从图上可以看出小程序认证流程需要三部分 小程序即前端程序 开发者服务器后端微服务程序。 微信接口服务即微信服务器。 1.前端调用wx.login()获取登录凭证code 2.前端请求后端进行认证发送code 3.后端请求微信获取openid发送appid、app密钥、code参数微信返回openid 4.后端生成认证成功凭证返回给前端。 5.前端存储用户认证成功凭证
1.2.1 小程序申请
开发小程序首先要申请小程序账号参考官方文档https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E7%94%B3%E8%AF%B7%E8%B4%A6%E5%8F%B7 通过阅读上述文档并且完成操作本人已经完成小程序注册并保存好了密匙
1.2.2 创建客户后端工程jzo2o-customer
此处不展开了 具体就是新建工程同步Gitee修改nacos配置文件的小程序认证部分等等
1.2.3 开发部署前端
这部分也不再展开
1.2.4 小程序认证流程
整个过程包括三部分 小程序即前端程序 开发者服务器后端微服务程序。 微信接口服务即微信服务器。
具体的流程如下 1.前端调用wx.login()获取登录凭证code 2.前端请求后端进行认证发送code 3.后端请求微信获取openid 4.后端生成认证成功凭证返回给前端。 根据官方的认证流程我们定义本项目小程序认证的交互流程 customer工程提供认证接口publics工程作为一个公共服务提供与微信通信的接口。抽取通用服务 前端与cutomer交互不与publics交互。
1.2.4.1 customer小程序认证接口设计
首先认证的前端请求如下
Controller层
RestController(openLoginController)
RequestMapping(/open/login)
Api(tags 白名单接口 - 客户登录相关接口)
public class LoginController {Resourceprivate ILoginService loginService;PostMapping(/worker)ApiOperation(服务人员/机构人员登录接口)public LoginResDTO loginForWorker(RequestBody LoginForWorkReqDTO loginForWorkReqDTO) {if(UserType.INSTITUTION loginForWorkReqDTO.getUserType()){return loginService.loginForPassword(loginForWorkReqDTO);}else{return loginService.loginForVerify(loginForWorkReqDTO);}}/*** c端用户登录接口*/PostMapping(/common/user)ApiOperation(c端用户登录接口)public LoginResDTO loginForCommonUser(RequestBody LoginForCustomerReqDTO loginForCustomerReqDTO) {return loginService.loginForCommonUser(loginForCustomerReqDTO);}}Service层 Overridepublic LoginResDTO loginForCommonUser(LoginForCustomerReqDTO loginForCustomerReqDTO) {// code换openIdOpenIdResDTO openIdResDTO wechatApi.getOpenId(loginForCustomerReqDTO.getCode());if(ObjectUtil.isEmpty(openIdResDTO) || ObjectUtil.isEmpty(openIdResDTO.getOpenId())){// openid申请失败throw new CommonException(ErrorInfo.Code.LOGIN_TIMEOUT, ErrorInfo.Msg.REQUEST_FAILD);}CommonUser commonUser commonUserService.findByOpenId(openIdResDTO.getOpenId());//如果未从数据库查到需要新增数据if (ObjectUtil.isEmpty(commonUser)) {commonUser BeanUtil.toBean(loginForCustomerReqDTO, CommonUser.class);long snowflakeNextId IdUtil.getSnowflakeNextId();commonUser.setId(snowflakeNextId);commonUser.setOpenId(openIdResDTO.getOpenId());commonUser.setNickname(普通用户 RandomUtil.randomInt(10000,99999));commonUserService.save(commonUser);}else if(CoCummonStatusConstants.USER_STATUS_FREEZE commonUser.getStatus()) {// 被冻结throw new CommonException(ErrorInfo.Code.ACCOUNT_FREEZED, commonUser.getAccountLockReason());}//构建tokenString token jwtTool.createToken(commonUser.getId(), commonUser.getNickname(), commonUser.getAvatar(), UserType.C_USER);return new LoginResDTO(token);}调用Publics模块获取openid
其中customer模块的OpenIdResDTO openIdResDTO wechatApi.getOpenId(loginForCustomerReqDTO.getCode());代码就是调用publics模块的接口从而获取openid Resourceprivate WechatService wechatService;OverrideGetMapping(/getOpenId)ApiOperation(获取openId)ApiImplicitParams({ApiImplicitParam(name code, value 登录凭证, required true, dataTypeClass String.class)})public OpenIdResDTO getOpenId(RequestParam(code) String code) {String openId wechatService.getOpenid(code);return new OpenIdResDTO(openId);}而在其中调用了第三方模块的wechatService.getOpenid接口其中内容如下 public String getOpenid(String code) {MapString, Object requestUrlParam this.getAppConfig();requestUrlParam.put(js_code, code);String result HttpUtil.get(https://api.weixin.qq.com/sns/jscode2session?grant_typeauthorization_code, requestUrlParam);log.info(getOpenid result:{}, result);JSONObject jsonObject JSONUtil.parseObj(result);if (ObjectUtil.isNotEmpty(jsonObject.getInt(errcode))) {throw new ServerErrorException(jsonObject.getStr(errmsg));} else {return jsonObject.getStr(openid);}}功能很简单通过调用微信官方api获取到数据从数据中解析出openid返回publics模块最后返回到customer模块
查询数据库构建token
customer模块拿到openid后开始查询数据库
CommonUser commonUser commonUserService.findByOpenId(openIdResDTO.getOpenId());假如数据库没查到说明第一次使用那么就会自动注册 if (ObjectUtil.isEmpty(commonUser)) {commonUser BeanUtil.toBean(loginForCustomerReqDTO, CommonUser.class);long snowflakeNextId IdUtil.getSnowflakeNextId();commonUser.setId(snowflakeNextId);commonUser.setOpenId(openIdResDTO.getOpenId());commonUser.setNickname(普通用户 RandomUtil.randomInt(10000,99999));commonUserService.save(commonUser);}那么如果查到了说明注册过了就会生成token //构建tokenString token jwtTool.createToken(commonUser.getId(), commonUser.getNickname(), commonUser.getAvatar(), UserType.C_USER);return new LoginResDTO(token);保存token到微服务的ThreadLocal中
通过上述流程前端就获得了token 当拿到了token后就会携带token去访问网关 // 2.获取token解析工具JwtTool工具// 2.1.获取tokenString token GatewayWebUtils.getRequestHeader(exchange, HEADER_TOKEN);if (StringUtils.isEmpty(token)) {return GatewayWebUtils.toResponse(exchange,HttpStatus.FORBIDDEN.value(),ErrorInfo.Msg.REQUEST_FORBIDDEN);}// 2.2.获取tokenKeyString tokenKey applicationProperties.getTokenKey().get(JwtTool.getUserType(token) );if (StringUtils.isEmpty(token)) {return GatewayWebUtils.toResponse(exchange,HttpStatus.FORBIDDEN.value(),ErrorInfo.Msg.REQUEST_FORBIDDEN);}如果网关校验token通过就会放行到微服务微服务就会将token放入ThreadLocal中由于所有的微服务都需要走这个流程将token放入ThreadLocal中因此抽取这部分到MVC模块的拦截器 Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.尝试获取头信息中的用户信息String userInfo request.getHeader(HeaderConstants.USER_INFO);// 2.判断是否为空if (userInfo null) {return true;}try {// 3.base64解码用户信息String decodeUserInfo Base64Utils.decodeStr(userInfo);CurrentUserInfo currentUserInfo JsonUtils.toBean(decodeUserInfo, CurrentUserInfo.class);// 4.转为用户id并保存UserContext.set(currentUserInfo);return true;} catch (NumberFormatException e) {log.error(用户身份信息格式不正确{}, 原因{}, userInfo, e.getMessage());return true;}}其中ThreadLocal定义如下
public class UserContext {private static final ThreadLocalCurrentUserInfo THREAD_LOCAL_USER new ThreadLocal();/*** 获取当前用户id** return 用户id*/public static Long currentUserId() {return THREAD_LOCAL_USER.get().getId();}public static CurrentUserInfo currentUser() {return THREAD_LOCAL_USER.get();}/*** 设置当前用户id** param currentUserInfo 当前用户信息*/public static void set(CurrentUserInfo currentUserInfo) {THREAD_LOCAL_USER.set(currentUserInfo);}/*** 清理当前线程中的用户信息*/public static void clear(){THREAD_LOCAL_USER.remove();}
}那么微服务之后获取token只需要从ThreadLocal获取即可 之后相信大家对于本项目的认证流程很熟悉了
1.2.5 手机验证码认证
1.2.5.1 开发与部署app前端
开发和部署细节省略
1.2.5.2 customer短信认证接口设计
需求分析
首先散户打开app会输入手机号点击获取验证码 之后会发出这个请求 散户输入验证码后点击登录即可进入主界面 短信验证流程如下 那么下面我去实现这个功能
Controller层
发送验证码
RestController
RequestMapping(/sms-code)
Api(tags 验证码相关接口)
public class SmsCodeController {Resourceprivate ISmsCodeService smsCodeService;PostMapping(/send)ApiOperation(发送短信验证码)public void smsCodeSend(RequestBody SmsCodeSendReqDTO smsCodeSendReqDTO) {smsCodeService.smsCodeSend(smsCodeSendReqDTO);}
}验证验证码 Resourceprivate ILoginService loginService;PostMapping(/worker)ApiOperation(服务人员/机构人员登录接口)public LoginResDTO loginForWorker(RequestBody LoginForWorkReqDTO loginForWorkReqDTO) {if(UserType.INSTITUTION loginForWorkReqDTO.getUserType()){return loginService.loginForPassword(loginForWorkReqDTO);}else{return loginService.loginForVerify(loginForWorkReqDTO);}}Service层
当用户点击了发送验证码后首先会调用Public模块的服务生成一个随机的验证码存储在redis中为验证做准备 Overridepublic void smsCodeSend(SmsCodeSendReqDTO smsCodeSendReqDTO) {if(StringUtils.isEmpty(smsCodeSendReqDTO.getPhone()) || StringUtils.isEmpty(smsCodeSendReqDTO.getBussinessType())) {log.debug(不能发送短信验证码phone:{},bussinessType:{}, smsCodeSendReqDTO.getPhone(), smsCodeSendReqDTO.getBussinessType());return;}String redisKey String.format(CommonRedisConstants.RedisKey.VERIFY_CODE, smsCodeSendReqDTO.getPhone(), smsCodeSendReqDTO.getBussinessType());// 取6位随机数
// String verifyCode (int)(Math.random() * 1000000) ;String verifyCode 123456;//为方便测试固定为123456log.info(向手机号{}发送验证码{},smsCodeSendReqDTO.getPhone(),verifyCode);//todo调用短信平台接口向指定手机发验证码...//短信验证码有效期5分钟redisTemplate.opsForValue().set(redisKey, verifyCode, 300, TimeUnit.SECONDS);}当用户输入验证码后点击登录按钮则进行校验 这次是调用Customer模块的服务 发送的请求参数是手机号验证码以及验证身份的代码收到请求后先判空若不为空则交由Public模块去验证验证码的正确性假如验证码没错那么检查用户是否冻结或者没有注册若没有注册则自动注册存入数据库生成并返回token Overridepublic LoginResDTO loginForVerify(LoginForWorkReqDTO loginForWorkReqDTO) {// 数据校验if(StringUtils.isEmpty(loginForWorkReqDTO.getVeriryCode())){throw new BadRequestException(验证码错误请重新获取);}//远程调用publics服务校验验证码是否正确boolean verifyResult smsCodeApi.verify(loginForWorkReqDTO.getPhone(), SmsBussinessTypeEnum.SERVE_STAFF_LOGIN, loginForWorkReqDTO.getVeriryCode()).getIsSuccess();if(!verifyResult) {throw new BadRequestException(验证码错误请重新获取);}// 登录校验// 根据手机号和用户类型获取服务人员或机构信息ServeProvider serveProvider serveProviderService.findByPhoneAndType(loginForWorkReqDTO.getPhone(), loginForWorkReqDTO.getUserType());// 账号禁用校验if(serveProvider ! null CommonStatusConstants.USER_STATUS_FREEZE serveProvider.getStatus()) {throw new CommonException(ErrorInfo.Code.ACCOUNT_FREEZED, serveProvider.getAccountLockReason());}// 自动注册if(serveProvider null) {serveProvider serveProviderService.add(loginForWorkReqDTO.getPhone(), UserType.WORKER, null);}// 生成登录tokenString token jwtTool.createToken(serveProvider.getId(), serveProvider.getName(), serveProvider.getAvatar(), loginForWorkReqDTO.getUserType());return new LoginResDTO(token);}Public模块校验代码如下 首先根据手机号和人员类型拼接出key从Redis取出相应的验证码之后进行一个校验由于验证码只能用一次通过校验则立马删除Redis的该条记录 Overridepublic boolean verify(String phone, SmsBussinessTypeEnum bussinessType, String verifyCode) {// 1.验证前准备String redisKey String.format(CommonRedisConstants.RedisKey.VERIFY_CODE, phone, bussinessType.getType());String verifyCodeInRedis redisTemplate.opsForValue().get(redisKey);// 2.短验验证验证通过后删除codecode只能使用一次boolean verifyResult StringUtils.isNotEmpty(verifyCode) verifyCode.equals(verifyCodeInRedis);if(verifyResult) {redisTemplate.delete(redisKey);}return verifyResult;}所以大家应该理解了下图
1.2.6 账号密码验证
1.2.6.1 前端设计和部署 1.2.6.2 customer账号密码验证接口设计
Controller设计 Resourceprivate ILoginService loginService;PostMapping(/worker)ApiOperation(服务人员/机构人员登录接口)public LoginResDTO loginForWorker(RequestBody LoginForWorkReqDTO loginForWorkReqDTO) {if(UserType.INSTITUTION loginForWorkReqDTO.getUserType()){return loginService.loginForPassword(loginForWorkReqDTO);}else{return loginService.loginForVerify(loginForWorkReqDTO);}}Service层设计
首先会进行判空若不为空则根据账号去查数据库以及进行密码校验BCrypt加密若通过则生成并返回token Overridepublic LoginResDTO loginForPassword(LoginForWorkReqDTO loginForWorkReqDTO) {// 1.数据校验if(StringUtils.isEmpty(loginForWorkReqDTO.getPassword())) {throw new BadRequestException(请输入密码);}// 2.登录校验// 2.1.根据手机号和用户类型获取服务人员或机构信息ServeProvider serveProvider serveProviderService.findByPhoneAndType(loginForWorkReqDTO.getPhone(), loginForWorkReqDTO.getUserType());// 账号禁用校验if(serveProvider ! null CommonStatusConstants.USER_STATUS_FREEZE serveProvider.getStatus()) {throw new CommonException(ErrorInfo.Code.ACCOUNT_FREEZED, serveProvider.getAccountLockReason());}//密码校验if(serveProvider null || !passwordEncoder.matches(loginForWorkReqDTO.getPassword(), serveProvider.getPassword())){throw new BadRequestException(账号或密码错误请重新输入);}// 3.生成登录tokenString token jwtTool.createToken(serveProvider.getId(), serveProvider.getName(), serveProvider.getAvatar(), loginForWorkReqDTO.getUserType());return new LoginResDTO(token);}顺便复习下BCrypt加密
BCrypt是一种密码哈希函数通常用于存储用户密码的安全性。它是基于 Blowfish 密码算法的一种单向哈希函数 使用很简单如下
public static void main(String[] args) {BCryptPasswordEncoder passwordEncoder new BCryptPasswordEncoder();/**$2a$10$1sp7I0OdYH3Azs/2lK8YYeuiaGZzOGshGT9j.IYArZftsGNsXqlma$2a$10$m983E2nmJ7ITlesbXzjbzO/M7HL2wP8EgpgX.pPACDm1wG38Lt.na$2a$10$rZvathrW98vVPenLhOnl0OMpUtRTdBkWJ45IkIsTebITS9AFgKqGK$2a$10$2gaMKWCRoKdc42E0jsq7b.munjzOSPOM4yr3GG9M6194E7dOH5LyS$2a$10$I/n93PIKpKL8m4O3AuT5kuZncZhfqV51bfx5sJrplnYoM7FimdboC*/for (int i 0; i 5; i) {//对密码进行哈希String encode passwordEncoder.encode(11111);System.out.println(encode);}//校验哈希串和密码是否匹配boolean matches passwordEncoder.matches(11111, $2a$10$m983E2nmJ7ITlesbXzjbzO/M7HL2wP8EgpgX.pPACDm1wG38Lt.na);System.out.println(matches);
}1.2.7 注册功能实现
1.2.7.1 需求
用户点击注册按钮 输入手机号获取验证码再输入密码进行注册接口设计如下 接口地址POST/customer/open/serve-provider/institution/register
1.2.7.2 Costomer注册功能接口实现
Controller开发
RestController(openServeProviderController)
RequestMapping(/open/serve-provider)
Api(tags 白名单接口 - 服务人员或机构相关接口)
public class ServeProviderController {Autowiredprivate IServeProviderService iServeProviderService;PostMapping(/register)public void institutionRegister(RequestBody InstitutionRegisterReqDTO institutionRegisterReqDTO){iServeProviderService.registerInstitution(institutionRegisterReqDTO);}
}
Service开发
首先校验验证码假如验证码没问题就进行注册 /*** 机构注册* param institutionRegisterReqDTO*/Overridepublic void registerInstitution(InstitutionRegisterReqDTO institutionRegisterReqDTO) {//1.验证验证码boolean verifyResult smsCodeApi.verify(institutionRegisterReqDTO.getPhone(),SmsBussinessTypeEnum.INSTITION_REGISTER,institutionRegisterReqDTO.getVerifyCode()).getIsSuccess();if(verifyResult){throw new BadRequestException(验证码校验失败);}//2.新增机构owner.add(institutionRegisterReqDTO.getPhone(),UserType.INSTITUTION,institutionRegisterReqDTO.getPassword());}其中注册接口如下先验证手机号是否已经被注册没有则写入数据库 OverrideTransactional(rollbackFor Exception.class)public ServeProvider add(String phone, Integer type, String password) {// 校验手机号是否存在ServeProvider existServeProvider lambdaQuery().eq(ServeProvider::getPhone, phone).one();if (existServeProvider ! null) {if(existServeProvider.getType().equals(UserType.WORKER)){throw new BadRequestException(该账号已被服务人员注册);}else {throw new BadRequestException(该账号已被机构注册);}}//新增服务人员/机构信息ServeProvider serveProvider new ServeProvider();serveProvider.setPhone(phone);serveProvider.setPassword(password);serveProvider.setType(type);serveProvider.setStatus(CommonStatusConstants.USER_STATUS_NORMAL);serveProvider.setCode(IdUtils.getSnowflakeNextIdStr());baseMapper.insert(serveProvider);//新增服务人员/机构配置信息同步表信息,方便后期对配置项进行配置serveProviderSettingsService.add(serveProvider.getId(), type);return serveProvider;}OverrideTransactional(rollbackFor {Exception.class})public void add(Long id, Integer serveProviderType) {ServeProviderSettings serveProviderSettings new ServeProviderSettings();serveProviderSettings.setId(id);if(baseMapper.insert(serveProviderSettings) 0 || serveProviderSyncService.add(id, serveProviderType) 0){throw new DBException(请求失败);}}演示 1.2.8 找回密码功能
1.2.8.1 需求
首先用户忘记密码点击找回密码 全部输入后发出请求接口如下 POST/customer/agency/serve-provider/institution/resetPassword 首先校验验证码是否正确。 校验手机号是否存在数据库。 通过校验最后修改密码密码的加密方式参考机构注册接口。
1.2.8.2 Costomer找回密码功能接口实现
Controller开发
RestController(agencyServeProviderController)
RequestMapping(/agency/serve-provider)
Api(tags 机构端 - 服务人员或机构相关接口)
public class ServeProviderController {Resourceprivate IServeProviderService serveProviderService;GetMapping(/currentUserInfo)ApiOperation(获取当前用户信息)public ServeProviderInfoResDTO currentUserInfo() {return serveProviderService.currentUserInfo();}PostMapping(/institution/resetPassword)ApiOperation(机构登录密码重置接口)public void resetPassword(RequestBody InstitutionResetPasswordReqDTO institutionResetPasswordReqDTO){serveProviderService.resetPassword(institutionResetPasswordReqDTO);}
}
Service开发 /*** c机构重置密码* param institutionResetPasswordReqDTO*/Overridepublic void resetPassword(InstitutionResetPasswordReqDTO institutionResetPasswordReqDTO) {//1.校验验证码Boolean verifyResult smsCodeApi.verify(institutionResetPasswordReqDTO.getPhone(),SmsBussinessTypeEnum.INSTITUTION_RESET_PASSWORD,institutionResetPasswordReqDTO.getVerifyCode()).getIsSuccess();if(!verifyResult){throw new BadRequestException(验证码校验错误);}//2.校验数据库是否已经存在这个手机号ServeProvider serveProvider lambdaQuery().eq(ServeProvider::getPhone, institutionResetPasswordReqDTO.getPhone()).one();if(serveProvider null){throw new BadRequestException(手机号不存在);}//3.修改密码lambdaUpdate().eq(ServeProvider::getPhone, institutionResetPasswordReqDTO.getPhone()).set(ServeProvider::getPassword,passwordEncoder.encode(institutionResetPasswordReqDTO.getPassword())).update();}演示