小熊家务帮day5-day7 客户管理模块1 (小程序认证,手机验证码认证,账号密码认证,修改密码,找回密码等)
客户管理模块
- 1.认证模块
- 1.1 认证方式介绍
- 1.1.1 小程序认证
- 1.1.2 手机验证码登录
- 1.1.3 账号密码认证
- 1.2 小程序认证
- 1.2.1 小程序申请
- 1.2.2 创建客户后端工程jzo2o-customer
- 1.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;@Override@GetMapping("/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) {Map<String, Object> requestUrlParam = this.getAppConfig();requestUrlParam.put("js_code", code);String result = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_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 ThreadLocal<CurrentUserInfo> 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.短验验证,验证通过后删除code,code只能使用一次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());}
其中注册接口如下,先验证手机号是否已经被注册,没有则写入数据库:
@Override@Transactional(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;}
@Override@Transactional(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();}
演示
相关文章:

小熊家务帮day5-day7 客户管理模块1 (小程序认证,手机验证码认证,账号密码认证,修改密码,找回密码等)
客户管理模块 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层调用…...

计算机图形学入门02:线性代数基础
1.向量(Vetors) 向量表示一个方向,还能表示长度(向量的摸)。一般使用单位向量表示方向。 向量加减:平行四边形法则、三角形法则。比卡尔坐标系描述向量,坐标直接相加。 1.1向量点乘(…...

函数:计算数组的元素和
一、计算数组的元素和 参数传递给函数时,实际上只有数组的首地址作为指针传递给了函数。 在函数定义中的int a[ ]等价于int *a。在只有地址信息的情况下,是无法知道数组里有多少个元素的,因此在计算数组中的元素和时,要加一个参…...

如何进行数据库分库分表
当数据库的数据量增长到一定程度,单一数据库或表可能会遇到性能瓶颈,此时分库分表是一种常见的解决方案。以下是如何进行数据库分库分表的详细步骤和考虑因素,结合了参考文章中的相关信息: 一、分库分表概述 分库分表是为了解决…...

Spring-Cloud-CircuitBreaker-Resilience4j (3.1.1)
介绍 Resilience4j 是一个专为函数式编程而设计的轻量级容错库。Resilience4j 提供高阶函数(装饰器),以增强任何功能接口、lambda 表达式或方法引用,包括断路器、速率限制器、重试或隔板。您可以在任何函数接口、lambda 表达式或…...

重构与优化-组织数据(3)
重构组织数据是一个系统性的工程,旨在改进数据的存储方式、访问效率、质量和可用性,以更好地支持业务运营、分析决策和未来发展。以下是重构组织数据的一些关键说明点: 目的与动机 提升效率:通过优化数据结构、减少冗余数据和改善索引策略,加快数据查询和处理速度。 增强…...

游戏交易平台源码游戏帐号交易平台系统源码
功能介绍 1:后台可以添加删除游戏分类 2:会员中心可以出售游戏币,账号,装备 3:后台可以对会员和商品进行管理 4:多商家入驻,商家发布信息 5:手机版功能可以生成APP 6:在线支付可支持微信和支…...

Matlab里面的浮点数与FPGA定点数的相互转化应用(含Matlab代码,封装成函数可直接调用)
微信公众号获取更多FPGA相关源码: 1.前言 Matlab里面计算通常用的是浮点数,而FPGA在做数字信号处理时,为了节约资源,常常使用的是定点数。在实践中,我们经常需要将Matlab实现中的算法,用FPGA进行实现。 …...

机器学习笔记——欠拟合、过拟合
欠拟合 将训练损失和测试损失都比较大的拟合叫欠拟合,那么他的预测精度很低 1.一般出现在模型的复杂度小于数据本身的复杂度导致的,这个可能就是模型对数据的分布和实际数据分布之间的差异,这个就可能需要更换模型 2.还可能出现在梯度下降算…...

【二进制部署k8s-1.29.4】七、验证master的安装
文章目录 简介 一.确认kubectl命令是否正常运行二.确认etcd安装是否正常运行三.确认kube-apiserver,kube-controller-manager,kube-scheduler安装是否正常四.配置apiserver和kubelet的访问授权五.master端安装脚本4.1.安装master端所需文件4.2.master快捷安装脚本 简介 本章节主…...

springboot获取当前数据库连接
要获取当前 Spring DataSource 的 URL,可以通过以下几种方法: 方法一:使用 JdbcTemplate 如果你使用的是 Spring 的 JdbcTemplate,可以通过 javax.sql.DataSource 获取连接,再获取它的 URL。 示例代码: …...

【学习笔记】Windows GDI绘图(九)Graphics详解(上)
文章目录 Graphics 定义创建Graphics对象的方法通过Graphics绘制不同的形状、线条、图像和文字等通过Graphics操作对象坐标 Graphics属性Clip(裁切/绘制区域)ClipBounds获取裁切区域矩形范围CompositiongMode合成方式CompositingQuality渲染质量DpiX和DpiY 水平、垂直分辨率Int…...

公告:公众号铁粉粉丝介绍以及说明
大家好,我是公众号博主--夏目 机械电气电机杂谈是我个人建立,为分享机械,电气,电机知识为主,闲谈杂聊社会时事,职场见闻,生活琐事,成长趣事,学习心得,读书观影…...

BioTech - 使用 CombFold 算法 实现 大型蛋白质复合物结构 的组装过程
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/139242199 CombFold 是用于预测大型蛋白质复合物结构的组合和分层组装算法,利用 AlphaFold2 预测的亚基之间的成对相互作用。 CombFold 算法的关键特点包括: 组合和…...

代码随想录算法训练营第36期DAY46
DAY46 完全背包 在闫氏DP法里学过:第i个物品选k个,纸质直至不能选,k从0开始取。就有递推式了。 代码随想录的视频也看了。 518零钱兑换ii 注意与 目标和 那题区分开。 完全背包问题,正向遍历背包容量,就能实现“多次…...

港湾周评|李小加“刀刃向内”裁员
《港湾商业观察》李镭 近年来争议颇大的滴灌通风波不断。 在交100万付费上班不久,最新又被曝出裁员。这位前港交所总裁、金融圈鼎鼎大名的李小加,没想到成立不足三年便迎来了重大挑战。 日前,滴灌通确认了公司组织架构已经调整,…...

超大功率光伏并网逆变器学习(三相)
1.超大功率用的IGBT开关频率通常很低,比如6KHz 2.线电压和相电压的关系 相电压 A AB线电压-CA线电压 相电压 B BC线电压-AB线电压 相电压 C CA线电压-BC线电压 3.坐标变换 ABC三相信号通过Clark坐标变换得到αβ两相静止信号,其中α与A相重合,β与α…...

大豆、棉花深度学习数据集大合集
最近收集了一大波关于大豆和棉花的深度学习数据集,主要有叶片的识别、分类、计数以及病害检测等。 数据集的价值 科研价值:这些数据集为植物学、农业信息技术、机器学习等领域的科研人员提供了宝贵的资源。它们可以用于训练和优化各种深度学习模型&…...

教育数字展馆助力全球教育传播,科技引领数字化教育潮流
一、教育数字展馆助力教育传播 1、提高教育资源的可及性 教育数字展馆通过VR和WEB3D技术,将丰富的教育资源呈现在用户面前。不论是名校的经典课程,还是专家的精彩讲座,均可通过教育数字展馆实现线上展示。用户只需登录平台,即可…...

14.微信小程序之地理定位功能
目录 1.地理定位介绍 1.1 申请开通 1.2 使用方法 2.拒绝授权后的解决方案 3.开通腾讯位置服务 4.LBS 逆地址解析 1.地理定位介绍 小程序地理定位是指通过小程序开发平台提供的 API,来获取用户的地理位置信息。用户在使用小程序时,可以授权小程序获…...

理解lambda表达式
Lambda表达式: 这里不再过多叙述什么事lambda表达式,就说下怎么使用,首先和lambda表达式同时存在的就是另一个定义,就是匿名内部类。匿名内部类首先需要一个接口。 下面用一个例子说明lambda表达式: public class Hel…...

【面试】Java的前端编译器和后端编译器
目录 1. 说明2. 前端编译器2.1 主要功能2.2 工作原理 3. 后端编译器3.1 主要功能3.2 工作原理 1. 说明 1.在Java的编译过程中,编译器通常被划分为前端编译器和后端编译器,各自负责不同的任务。2.前端编译器主要负责源代码的词法分析、语法分析和语义检查…...

教育小程序的性能优化:从前端到后端的综合提升策略
随着教育小程序的普及,其性能直接影响用户体验和教学效果。本文将从前端到后端,详细探讨教育小程序的性能优化策略,帮助开发者打造高效、流畅的教育应用。 一、前端性能优化策略 代码优化 减少HTTP请求:合并CSS、JavaScript文件…...

单链表实现通讯录
之前我们完成了基于顺序表(动态)实现通讯录,现在我们链表学完了,可以尝试着使用链表来实现我们的通讯录。 首先我们要明白我们写的通讯录是由一个个节点组成的,每个节点里存储的就是我们的联系人信息。也就是说 我们需…...

Linux 命令操作技巧
Linux命令行界面提供了丰富的快捷键来提高操作效率,以下是一些常用的Linux终端快捷键,主要基于Bash shell: Tab - 自动补全:输入命令、文件名、目录名或命令选项的开头部分,然后按Tab键,系统会自动补全剩余…...

深度学习21天 —— 卷积神经网络(CNN):识别验证码( 第12天)
目录 一、前期准备 1.1 标签数字化 1.2 加载数据 1.3 配置数据 二、其他 2.1 损失函数 categorical_crossentropy 2.2 plt.legend(loc ) 2.3 history.history 活动地址:CSDN21天学习挑战赛 学习:深度学习100例-卷积神经网络(CNN&…...

利用 Docker 简化Redis部署:快速搭建Redis服务
利用 Docker 简化Redis部署:快速搭建Redis服务 目录 利用 Docker 简化Redis部署:快速搭建Redis服务为什么选择 Docker准备工作拉取Redis镜像快速运行Redis容器验证Redis服务总结 在现代软件开发中,Redis作为一种高性能的键值数据库࿰…...

Web前端框架:深入探索与实践
Web前端框架:深入探索与实践 在当下数字化飞速发展的时代,Web前端框架的选择与应用成为了开发者们关注的焦点。Node.js,作为一种强大的后端技术,在前端框架的构建中也发挥着不可或缺的作用。本文将围绕Node.js Web前端框架&#…...

【算法】贪心算法——柠檬水找零
题解:柠檬水找零(贪心算法) 目录 1.题目2.题解3.参考代码4.证明5.总结 1.题目 题目链接:LINK 2.题解 分情况讨论 贪心算法 当顾客为5元时,收下当顾客为10元时,收下10元并找回5元当顾客为20元时,收下20元并找回10…...

Jmeter安装教程
1 Jmeter下载 Jmeter下载地址:https://jmeter.apache.org/download_jmeter.cgi,选择需要的版本点击下载 解压jmeter安装包 解压后的安装包如下: 2 配置Jmeter环境变量 进入环境变量配置页面:计算机->属性->高级系统设置-&…...