实现一个app的签到功用,没你想的那么复杂!

刚刚阅读1回复0
zaibaike
zaibaike
  • 管理员
  • 注册排名1
  • 经验值188570
  • 级别管理员
  • 主题37714
  • 回复0
楼主

点选高度存眷社会公家号,塔多思该文及时处置介绍

来源:https://juejin.cn/post/6882657262762983438

1. 打卡表述和感化

打卡,指于规定的账簿上亲笔签名或写一“到”字,暗示生前已经抵达。在APP中接纳此机能,能够削减接纳者黏性和热度。

三个有打卡机能的APP,往往会供给更多实藓科青藓机能,已持续打卡几皇统给与相关的奖赏;而为的是进一步削减接纳者黏性,还会供给更多打卡各项使命机能,完成各项使命也能够获取相联系关系的奖赏。

机能示例

责任编纂带你同时实现三个包容上述示例的打卡机能,看完之后你会辨认出,打卡,没有你想的所以冗杂!

2. 手艺THF1

redis为主载入翻查,mysql远距翻查。传统打卡绝大大都都是间接接纳mysql为贮存DB,在大数据的情况下材料库的压力较大。翻查速度也会跟着信息量减小而削减。所以在需求完稿之后翻查了良多打卡同时实现体例,辨认出用redis做打卡会有很大的优势。

本机能次要就用到redis位图

[1],前面就要详尽教授同时实现过程。3. 同时实现效用

那里Jaunpur,展示一下我们app的打卡同时实现效用

4 机能同时实现

机能大致分为三个大组件

打卡营业流程(打卡,实藓科青藓,已持续,打卡汗青记录)

打卡各项使命(每礼拜各项使命,一般来说各项使命)

打卡时序如下表所示:

4.1.1 表构造设想

因为大部门机能接纳redis贮存,接纳到mysql次要就是为的是贮存接纳者总点数和点数汗青记录,易于翻查打卡汗青记录和接纳者总点数

CREATETABLE`t_user_integral`( `id`varchar(50)NOTNULLCOMMENTid, `user_id`int(11)NOTNULLCOMMENT接纳者id, `integral`int(16)DEFAULT0COMMENT当前点数, `integral_total`int(16)DEFAULT0COMMENT总计点数, `create_time`datetimeDEFAULTNULLONUPDATECURRENT_TIMESTAMPCOMMENT成立天数, `update_time`datetimeDEFAULTNULLONUPDATECURRENT_TIMESTAMPCOMMENT批改天数, PRIMARYKEY(`id`)USINGBTREE )ENGINE=InnoDBDEFAULTCHARSET=utf8ROW_FORMAT=COMPACTCOMMENT=接纳者点数修订版 CREATETABLE`t_user_integral_log`( `id`varchar(50)NOTNULLCOMMENTid, `user_id`int(11)NOTNULLCOMMENT接纳者id, `integral_type`int(3)DEFAULTNULLCOMMENT点数类别1.打卡2.已持续打卡3.社会福利各项使命4.每礼拜各项使命5.实藓科青藓, `integral`int(16)DEFAULT0COMMENT点数, `bak`varchar(100)DEFAULTNULLCOMMENT点数补足美术设想, `operation_time`dateDEFAULTNULLCOMMENT操做天数(打卡和实藓科青藓的详细年份), `create_time`datetimeDEFAULTNULLCOMMENT成立天数, PRIMARYKEY(`id`)USINGBTREE )ENGINE=InnoDBDEFAULTCHARSET=utf8ROW_FORMAT=COMPACTCOMMENT=接纳者点数小溪表 4.1.2 redis key构造设想//相关人员打卡位图key,三个位大敌当前三个接纳者一年的打卡情况,以userSign为标识表记标帜,前面的三个组件是本年的年数和接纳者的id publicfinalstaticStringUSER_SIGN_IN="userSign:%d:%d"; //相关人员实藓科青藓key,三个Hash条目存接纳者三个月的实藓科青藓情况,以userSign:retroactive为标识表记标帜,前面的三个组件是次月的月份和接纳者的id publicfinalstaticStringUSER_RETROACTIVE_SIGN_IN="userSign:retroactive:%d:%d"; //相关人员打卡总天数key,以userSign:count为标识表记标帜,前面的组件是接纳者的id publicfinalstaticStringUSER_SIGN_IN_COUNT="userSign:count:%d"; 4.1.3 同时实现打卡

接口restful的形式,头信息里传入接纳者id

@ApiOperation("接纳者打卡") @PostMapping("/signIn") @LoginValidate publicResponseResultsaveSignIn(@RequestHeaderIntegeruserId){ returnuserIntegralLogService.saveSignIn(userId); }

sevice同时实现层

publicResponseResultsaveSignIn(IntegeruserId){ //那里是我们的公司同一返回类 ResponseResultresponseResult=ResponseResult.newSingleData(); //用String.format拼拆好单个接纳者的位图key StringsignKey=String.format(RedisKeyConstant.USER_SIGN_IN,LocalDate.now().getYear(),userId); //位图的偏移点为当天的年份,现在天,偏移值就是1010 longmonthAndDay=Long.parseLong(LocalDate.now().format(DateTimeFormatter.ofPattern("MMdd"))); responseResult.setMessage("今日已打卡"); responseResult.setCode((byte)-1); //检测能否接纳者今日打卡过,用getBit能够取出该接纳者详细年份的打卡情况(位图的值只要三个,1或者0,那里1代表true) if(!cacheClient.getBit(signKey,monthAndDay)){ //位图的set办法会返回该位图未改动前的数值,那里若是之前没有打卡过默认是0,也就是false booleanoldResult=cacheClient.setbit(signKey,monthAndDay); if(!oldResult){ //计算出那个月该接纳者的到今天的已持续打卡天数,此办法参照下方计算已持续打卡天数的代码块 intsignContinuousCount=getContinuousSignCount(userId); //此办法参照下方汗青记录打卡点数类别和已持续打卡点数代码块 doSaveUserIntegral(userId,signContinuousCount); responseResult.setCode((byte)0); } } returnresponseResult; }

计算已持续打卡天数

/** *@description:以获取已持续打卡天数 *@author:chenyunxuan *@updateTime:2020/8/254:43下战书 */ privateintgetContinuousSignCount(IntegeruserId){ intsignCount=0; LocalDatedate=LocalDate.now(); StringsignKey=String.format(RedisKeyConstant.USER_SIGN_IN,date.getYear(),userId); //那里取出的是位图三个偏移值区间的值,区间起始值为次月的第一天,范畴值为次月的总天数(参考号令bitfield) List<Long>list=cacheClient.getBit(signKey,date.getMonthValue()*100+1,date.getDayOfMonth()); if(list!=null&&list.size()>0){ //可能该接纳者那个月就没有打卡过,需要判断一下,若是是空就给三个默认值0 longv=list.get(0)==null?0:list.get(0); for(inti=0;i<date.getDayOfMonth();i++){ //若是是已持续打卡得到的long值右移一位再左移一位后与原始值不相等,已持续天数加一 if(v>>1<<1==v)returnsignCount; signCount+=1; v>>=1; } } returnsignCount; }

汗青记录打卡点数类别和已持续打卡点数

publicBooleandoSaveUserIntegral(intuserId,intsignContinuousCount){ intcount=0; //叠加打卡次数 cacheClient.incrValue(String.format(RedisKeyConstant.USER_SIGN_IN_COUNT,userId)); List<UserIntegralLog>userIntegralLogList=newLinkedList<>(); userIntegralLogList.add(UserIntegralLog.builder() .createTime(LocalDateTime.now()) .operationTime(LocalDate.now()) .bak(BusinessConstant.Integral.NORMAL_SIGN_COPY) .integral(BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL) .integralType(BusinessConstant.Integral.SIGN_TYPE_NORMAL) .userId(userId) .build()); count+=BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL; //已持续打卡处置,以获取缓存设置装备摆设已持续打卡奖赏 //因为每个月的天数都不是一般来说的,已持续打卡奖赏是用的redishash载入的.所以那个处所用32取代三个月的已持续打卡天数,详细设置装备摆设鄙人方图中 if(signContinuousCount==LocalDate.now().lengthOfMonth()){ signContinuousCount=32; } Map<String,String>configurationHashMap=cacheClient.hgetAll("userSign:configuration"); Stringconfiguration=configurationHashMap.get(signContinuousCount); if(null!=configuration){ intgiveIntegral=0; JSONObjectitem=JSONObject.parseObject(configuration); giveIntegral=item.getInteger("integral"); if(giveIntegral!=0){ if(signContinuousCount==32){ signContinuousCount=LocalDate.now().lengthOfMonth(); } userIntegralLogList.add(UserIntegralLog.builder() .createTime(LocalDateTime.now()) .bak(String.format(BusinessConstant.Integral.CONTINUOUS_SIGN_COPY,signContinuousCount)) .integral(giveIntegral) .integralType(BusinessConstant.Integral.SIGN_TYPE_CONTINUOUS) .userId(userId) .build()); count+=giveIntegral; } } //改动总点数和批量载入点数汗青记录 returnupdateUserIntegralCount(userId,count)&&userIntegralLogService.saveBatch(userIntegralLogList); }

已持续打卡以获取的点数设置装备摆设和美术设想设置装备摆设

4.1.4 同时实现实藓科青藓

实藓科青藓机能是三个打卡补足机能,次要就就是便利接纳者在忘了打卡的情况下也能通过实藓科青藓机能到达响应的已持续打卡前提,从而得到奖赏.

实藓科青藓主办法

//day暗示需要实藓科青藓的年份,因为我们平台的打卡周期是三个月所以只需要传日的信息就能够,入7号传入7 publicResponseResultsaveSignInRetroactive(IntegeruserId,Integerday){ Booleanresult=Boolean.TRUE; ResponseResultresponseResult=ResponseResult.newSingleData(); responseResult.setMessage("今日无需实藓科青藓哟"); responseResult.setCode((byte)-1); LocalDatetimeNow=LocalDate.now(); //检测能否实藓科青藓达上限 StringretroactiveKey=String.format(RedisKeyConstant.USER_RETROACTIVE_SIGN_IN,timeNow.getMonthValue(),userId); //从redis中取出接纳者的次月实藓科青藓的集合set.我们平台的限造是三次实藓科青藓 Set<String>keys=cacheClient.hkeys(retroactiveKey); if(CollUtil.isNotEmpty(keys)&&keys.size()==3){ responseResult.setMessage("本月实藓科青藓次数已达上限"); result=Boolean.FALSE; } //查抄实藓科青藓点数能否足够,那里就是三个简单的单表翻查,用于翻查点数能否足够本次消耗 UserIntegraluserIntegral=userIntegralService.getOne(newLambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId,userId)); //那里只是简单的做了三个map放置三次实藓科青藓别离消耗的点数(key:次数value:消耗点数),也可参照之前已持续打卡设置装备摆设放入redis缓存中易于后台办理系统可设置装备摆设 IntegerreduceIntegral=getReduceIntegral().get(keys.size()+1); if(reduceIntegral>userIntegral.getIntegral()){ responseResult.setMessage("您的橙汁值不敷"); result=Boolean.FALSE; } if(result){ LocalDateretroactiveDate=LocalDate.of(timeNow.getYear(),timeNow.getMonthValue(),day); StringsignKey=String.format(RedisKeyConstant.USER_SIGN_IN,timeNow.getYear(),userId); longmonthAndDay=Long.parseLong(retroactiveDate.format(DateTimeFormatter.ofPattern("MMdd"))); //后端检测能否接纳者今日打卡过同时实藓科青藓年份不成大于今天的年份 if(!cacheClient.getBit(signKey,monthAndDay)&&timeNow.getDayOfMonth()>day){ booleanoldResult=cacheClient.setbit(signKey,monthAndDay); if(!oldResult){ //实藓科青藓汗青记录(:月份)过月清零,过时天数是计算出当前天数的差值,实藓科青藓次数是三个月一刷新的 cacheClient.hset(retroactiveKey,retroactiveDate.getDayOfMonth()+"","1", (Math.max(retroactiveDate.lengthOfMonth()-timeNow.getDayOfMonth(),1))*60*60*24); //那里就是对点数修订版削减.和对点数汗青记录停止汗青记录.参照下方代码块 doRemoveUserIntegral(userId,reduceIntegral,RETROACTIVE_SIGN_COPY); responseResult.setCode((byte)0); responseResult.setMessage("实藓科青藓胜利"); } } } returnresponseResult; }

点数削减并载入点数变更汗青记录

publicBooleandoRemoveUserIntegral(intuserId,intreduceIntegral,Stringbak){ returnupdateUserIntegralCount(userId,-reduceIntegral) &&userIntegralLogService.save(UserIntegralLog.builder() .createTime(LocalDateTime.now()) .operationTime(LocalDate.now()) .bak(bak) .integral(-reduceIntegral) .integralType(BusinessConstant.Integral.RETROACTIVE_SIGN_COPY.equals(bak)? BusinessConstant.Integral.SIGN_TYPE_RETROACTIVE:BusinessConstant.Integral.SIGN_TYPE_WELFARE) .userId(userId) .build()); }

至此,示例中的打卡与实藓科青藓机能同时实现完成,接下来我们看看打卡日历和打卡各项使命若何同时实现。

微信搜刮社会公家号:Java项目精选,回复:java 领取材料 。

5 打卡日历周期

打卡周期:常用的打卡周期为一周或者三个月。我们的app接纳的是三个月的计划(市道上的打卡日历界面都大同小异,接下来就要给各人分享以月为周期的打卡日历同时实现计划和伴生的打卡各项使命同时实现计划)

6 展示效用和接口阐发 6.1 效用图 6.2 需求阐发

通过图上阐发,可大致把那个界面分红四个部门

头部的总点数部门

最关键的打卡日历展示部门

已持续打卡美术设想设置装备摆设部门

打卡各项使命展示部门

通过火析我把那个界面分红了三个接口

/signInGET协议 用于翻查头部的总点数和打卡日历部门.

/signIn/configurationGET协议 翻查已持续打卡美术设想设置装备摆设,若是不需要后台可设置装备摆设已持续打卡以获取点数的数量和美术设想,此接口可省略,前端写死.

/signIn/taskGET协议 用于翻查打卡各项使命,和各个各项使命的完成情况.

7 翻查总点数,打卡日历接口publicResponseResultselectSignIn(IntegeruserId,Integeryear,Integermonth){ booleansignFlag=Boolean.FALSE; StringsignKey=String.format(RedisKeyConstant.USER_SIGN_IN,year,userId); LocalDatedate=LocalDate.of(year,month,1); //那个办法前面的该文有介绍过.是翻查出三个偏移值区间的位图集合 List<Long>list=cacheClient.getBit(signKey,month*100+1,date.lengthOfMonth()); //翻查reids中当前接纳者实藓科青藓的hash条目(hash条目标key为实藓科青藓的年份,value存在就申明那个年份实藓科青藓了) StringretroactiveKey=String.format(RedisKeyConstant.USER_RETROACTIVE_SIGN_IN,date.getMonthValue(),userId); Set<String>keys=cacheClient.hkeys(retroactiveKey); TreeMap<Integer,Integer>signMap=newTreeMap<>(); if(list!=null&&list.size()>0){ //由低位到高位,为0暗示未签,为1暗示已签 longv=list.get(0)==null?0:list.get(0); //轮回次数为次月的天数 for(inti=date.lengthOfMonth();i>0;i--){ LocalDated=date.withDayOfMonth(i); inttype=0; if(v>>1<<1!=v){ //情况为一般打卡 type=1; //那里和当前年份比照,便利前端特殊标识表记标帜今天能否打卡 if(d.compareTo(LocalDate.now())==0){ signFlag=Boolean.TRUE; } } if(keys.contains(d.getDayOfMonth()+"")){ //情况为实藓科青藓 type=2; } //返回给前端次月的所丰年份,和签,实藓科青藓或者未签的情况 signMap.put(Integer.parseInt(d.format(DateTimeFormatter.ofPattern("dd"))),type); v>>=1; } } ResponseResultresponseResult=ResponseResult.newSingleData(); Map<String,Object>result=newHashMap<>(2); //前文有介绍过那个表贮存了接纳者的总点数 UserIntegraluserIntegral=userIntegralService.getOne(newLambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId,userId)); //接纳者总点数 result.put("total",userIntegral.getIntegral()); //接纳者今日能否打卡 result.put("todaySignFlag",signFlag?1:0); //后端返回年份是为的是避免手机端间接批改系统天数招致的问题 result.put("today",LocalDate.now().getDayOfMonth()); //次月的打卡情况 result.put("signCalendar",signMap); //返回给前端那个月的第一天是礼拜几,便利前端衬着日历图的时候定位 result.put("firstDayOfWeek",date.getDayOfWeek().getValue()); //办事器的当前月份(同上,避免手机端间接批改系统天数) result.put("monthValue",date.getMonthValue()); //接纳者次月实藓科青藓的次数 result.put("retroactiveCount",keys.size()); //日历部门会有上月的结尾几天的数据,所以那里需要返回给前端上个月共有几天 result.put("lengthOfLastMonth",date.minusMonths(1).lengthOfMonth()); responseResult.setData(result); returnresponseResult; }

因为整体接纳了Redis位图的翻查,每个接纳者的打卡数据都是通过key隔分开的,天数冗杂度为O(1).实测百毫秒内可返回数据

8.翻查打卡各项使命和各项使命的完成情况

那一部门接纳的是redis和mysql连系翻查的体例.各项使命我们做了后台可设置装备摆设.分为只能完成一次的社会福利各项使命和每天都能够重置的每礼拜各项使命.

8.1 表构造

构造设想那张各项使命表的时候,总要就是类别和跳转体例需要留意.因为差别的各项使命有差别的机能划分.用jump_type去区分各自的机能区域.jump_source能够是H5地址也能够是手机端的路由地址.能够做到灵敏调控.前端挪用完成各项使命的接口传入各项使命相联系关系的task_tag就能够完成指定的各项使命

CREATETABLE`t_user_integral_task`( `id`bigint(11)NOTNULLAUTO_INCREMENTCOMMENTid, `task_type`tinyint(4)DEFAULT1COMMENT各项使命类别1.每礼拜各项使命2社会福利各项使命, `task_tag`varchar(100)DEFAULTNULLCOMMENT各项使命前端标识表记标帜(大写字母组合), `task_title`varchar(100)DEFAULTNULLCOMMENT各项使命题目, `icon`varchar(255)DEFAULTNULLCOMMENT小图标, `task_copy`varchar(100)DEFAULTNULLCOMMENT各项使命美术设想, `integral`int(16)DEFAULT0COMMENT各项使命赠送点数数, `jump_type`tinyint(4)DEFAULTNULLCOMMENT跳转体例 1.跳转指定商品 2.跳转链接 3.跳转指定接口,4:跳转随机商品, `jump_source`textCOMMENT跳转或分享的地址, `sort`tinyint(2)DEFAULT0COMMENT排序号, `delete_flag`tinyint(2)DEFAULT0COMMENT删除/隐藏,0:未删除/未隐藏,1:已删除/已隐藏, `create_time`datetimeDEFAULTNULLCOMMENT成立天数, `update_time`datetimeDEFAULTNULLONUPDATECURRENT_TIMESTAMPCOMMENT批改天数, PRIMARYKEY(`id`)USINGBTREE )ENGINE=InnoDBAUTO_INCREMENT=13DEFAULTCHARSET=utf8ROW_FORMAT=COMPACTCOMMENT=接纳者各项使命表 8.2 各项使命翻查

因为每礼拜各项使命和社会福利各项使命大要也就十条摆布,所以mysql翻查长短常快速的.然后完成情况贮存在redis中,天数冗杂度为O(1)

publicResponseResultselectSignInTask(IntegeruserId){ ResponseResultresponseResult=ResponseResult.newSingleData(); //先查出打卡各项使命的mysql汗青记录. List<UserIntegralTask>userIntegralTaskList=list(newLambdaQueryWrapper<UserIntegralTask>() .orderByDesc(UserIntegralTask::getTaskType).orderByAsc(UserIntegralTask::getSort)); //成立三个map,key为各项使命的task_tag,value存在则是完成了该各项使命. //每礼拜各项使命和社会福利各项使命分为三个reidshash贮存.每礼拜各项使命的key中包容当天算份,过时天数为一天.社会福利各项使命则是永久保留 Map<String,String>completeFlagMap=newHashMap<>(userIntegralTaskList.size()); Map<String,String>welfareMap=cacheClient.hgetAll(String.format(RedisKeyConstant.USER_SIGN_WELFARE_TASK,userId)); if(CollUtil.isNotEmpty(welfareMap))completeFlagMap.putAll(welfareMap); Map<String,String>dailyMap=cacheClient.hgetAll(String.format(RedisKeyConstant.USER_SIGN_DAILY_TASK,LocalDate.now().getDayOfMonth(),userId)); //把三个hash合并 if(CollUtil.isNotEmpty(dailyMap))completeFlagMap.putAll(dailyMap); //轮回库中的各项使命条目,并用hash的get办法翻查能否完成,然后给到前端 userIntegralTaskList.forEach(task->{ task.setCreateTime(null); task.setUpdateTime(null); task.setIntegral(null); Stringvalue=completeFlagMap.get(task.getTaskTag()); if(null==value){ task.setCompleteFlag(0); }else{ task.setCompleteFlag(1); } }); responseResult.setData(userIntegralTaskList); returnresponseResult; } 8.3 完成各项使命

完成各项使命的办法.设定为三个公共办法.传入相联系关系的task_tag标识表记标帜去完成指定各项使命.也就只需要判断一下他是每礼拜各项使命仍是社会福利各项使命.别离载入差别的redis hash里.

//伪代码 publicResponseResultsaveSignInTask(IntegeruserId,Stringtag){ //翻查出mysql中相联系关系的tag各项使命,以获取关键信息.(`integral`) .... //载入点数汗青记录表.相联系关系当前各项使命title的汗青记录 ... //在redis里载入当前接纳者的那个各项使命完成情况(那里要留意若是是每礼拜各项使命要给hash条目给一天的过时天数,避免脏数据长天数不被清理,占用redis的内存空间) }

< END >

PS:若是觉得我的分享不错,欢送各人随手点赞、在看。

高度存眷社会公家号:Java后端编程,回复下面关键字 要Java进修完好道路,回复道路缺Java入门视频,回复:视频要Java面试经历,回复面试缺Java项目,回复:项目进Java粉丝群:加群 PS:若是觉得我的分享不错,欢送各人随手点赞、在看。(完)

加我"微信"以获取一份 最新Java面试题材料

请备注:666,否则欠亨过~

比来好文

1、再见了,收费的XShell,我改用国产良心东西!

2、给IDEA换个酷炫的主题,实的太都雅了!

3、SpringBoot快速开发利器:Spring Boot CLI

4、基于SpringBoot 的CMS系统,拿去开发企业官网

5、本机号码一键登录原理与应用

比来面试BAT,整理一份面试材料《Java面试BAT通关手册》,笼盖了Java核心手艺、JVM、Java并发、SSM、微办事、材料库、数据构造等等。以获取体例:高度存眷社会公家号并回复java领取,更多内容陆续送上。明天见(。・ω・。)

0
回帖 返回游戏电竞

实现一个app的签到功用,没你想的那么复杂! 期待您的回复!

取消