Grails中实现用户即时在线状态的记录与读取

 之前有一需求:网站用户线上即时消费扣点,商户可以在线查看其商品的即时在线消费用户情况。目前网上查找到的资料有两种:
一是通过访问数据库,在数据库用户表比如Users表中添加一个字段 bit 当有人用这个用户登录的时候就把它变成1,但这方法会有以下问题:第一数据库更新问题,需要很大的性能而且如果大量用户这样做,后果不堪设想。第二:服务器无法判断设么时候用户推出登录;
二是通过JS来实现,但这种方法无法解决断网问题,还有就是如果一个用户在同一个计算机上登录两次那么第二次将不能登录,如果一个用户关闭浏览器服务器不知道,这个用户还想登录,后果就是登不上去了。

另外有一种个人觉得比较好的方案:
思想:就是用类似QQ的方法,如果一个用户再次登录,退出第一个登录的用户,保留第二个
存储:利用用户池java.util.Map作为用户池容器,里面放着用户标示和sessionId,这个容器放到application对象中介可以;
原理:当用户登录时直接put进去就可以了,如果这个对象存在就会被更新
其他:编写listener用HttpSessionAttributeListener地实现处理session属性的removed在removed的时候清楚这个用户在对象池中的状态
检查:编写一个用户Filter来检查请求的sessionId和用户池是否匹配就可以了如果不匹配清楚这个session中的用户对象迫使下线
针对项目使用的是Grails架构,把实现此功能的过程总结如下:
1、把消费记录记到application中,消费的controller为ConsumController.groovy,即时消费的action为:

def instantPay = {
        def personService = PersonService.get(params.id)
        def user = User.findByUserName(session.userName)
        if(!user){
            render(text:"請登入會員!",contentType:"text/html",encoding:"UTF-8")
        }
        int leftPoint = consumeRecordService.queryLeftPoint(user)
        if(leftPoint < personService?.unitPrice){
            render "對不起,您的點數不夠,請充值!"
        }
        
        String consumeId = session.getAttribute("consumeId")
        Long consumeRecordId = consumeId?.toLong()
        //把該用戶添加到產品服務即時計費列表,列表保存在application当中
        def pscontext = request.session.servletContext

        Map serviceMap = pscontext.getAttribute("serviceMap")
        if(!serviceMap){
            serviceMap = new HashMap()
        }
        ConsumeRecord cvalue = serviceMap.get(consumeRecordId)

        if(!cvalue || cvalue?.member != user){
            serviceMap = presenceService.putPresence(consumeRecordId,serviceMap)
            pscontext.setAttribute ("serviceMap",serviceMap)
            
        }


        Long cid = new Long(0)

        if(consumeId != null && !consumeId.equals("")){
           cid = consumeId.toLong()
        }
        Long id = consumeRecordService.getFirstConsume(cid, personService.id, user.id, 1, personService.unitPrice)

        session.setAttribute ("consumeId", id)
        
        leftPoint = consumeRecordService.queryLeftPoint(user)
        ConsumeRecord consumeRecord = ConsumeRecord.get(id)
        Date startTime = consumeRecord.startTime
        Date endTime = consumeRecord.endTime
        Long leftsec = ((endTime.getTime() - startTime.getTime())/1000)/60
        response.characterEncoding = "utf-8"
        render(text:"您已在線使用該服務時間為:${leftsec}分鐘,剩余點數為:${leftPoint}",contentType:"text/html")
       // render "您已使用的服務時間為:${leftsec}分鐘,剩余點數為:${leftPoint}"
    }

此Controoler当中有用到presenceService来对保存在application中的即时在线消费用户状态的管理,presenceService内容如下:

/**
 * Created by IntelliJ IDEA.
 * User: Administrator
 * Date: 2008-6-12
 * Time: 16:53:54
 * To change this template use File | Settings | File Templates.
 */
class PresenceService {
    boolean transactional = true
    Map allPresencesMap = new HashMap()
//保存到application中
    def Map putPresence(Long consumeId, Map serviceMap){
        ConsumeRecord consumeRecord = new ConsumeRecord()
        if(consumeId){
            consumeRecord = ConsumeRecord.get(consumeId)
        }

        if(serviceMap){
            allPresencesMap = serviceMap
        }
        allPresencesMap.put(consumeId,consumeRecord)
        return allPresencesMap
    }

//从application中删除
    def void removePresence(Long consumeId, Map serviceMap){
        if(serviceMap){
           serviceMap.remove(consumeId)
        }
    }
//从application中删除所有的的在线用户
    def void removePresenceList(Long personServiceId){
        Map ctx = personServices?.personService
        if ( ctx ) {
            personServices.remove( personServiceId );
            ctx.clear();
        }
    }
//从application中取得所有的在线用户
    def List getPresenceList(User SP, Map serviceMap){
        List result = [];
        if(serviceMap){
            for(m in serviceMap){
                ConsumeRecord consumeRecord = m.value
                if(consumeRecord && consumeRecord?.id ){
                    if(consumeRecord?.serviceProvider.userName == SP.userName){
                        result << consumeRecord
                    }
                }

            }

        }
        return result;
    }
//取得指定的在线用户
    def Presence getPresence( String key, Long personServiceId ) {

        Map ctx = personServices?.personService

        if ( !ctx ) {
            ctx = new HashMap();
            personServices.put( personServiceId, ctx );
        }

        Presence result = ( Presence ) ctx.get( key );

        return result;
    }

}

 
2、要即时地把在线消费的用户记录到application中,这里用到了JOB进行定时作业。在Grails中集合了quartz这个plugin,使用grails create-job OnlineCheck,内容如下:

import org.codehaus.groovy.grails.web.context.ServletContextHolder

class OnlineCheckJob {
    def startDelay = 60000
    def timeout = 1000 * 91 // execute job once in 1.5 seconds
    def group = "OnlineStatus"
    PresenceService presenceService
    def execute() {
        println "Job:refresh online status......."
        def cxt = ServletContextHolder.getServletContext()
        //取得保存在application中的serviceMap
        Map serviceMap = cxt.getAttribute("serviceMap")

        Date now = new Date();
        //如果serviceMap在application中存在,则对其进行遍历,取得即时在线消费的用户
        println("presenceService :${presenceService}")
        if(serviceMap){
            for(m in serviceMap){
                // Map comsumeMap = new HashMap();
                ConsumeRecord consumeRecord = m.value
                if(consumeRecord != null && consumeRecord.id != null){
                    Date endTime = consumeRecord.endTime
                   //如果consumeRecord的最后消费时间大于当前时间65秒,则把当前在线消费的用户踢下线
             if(((now?.getTime() - endTime?.getTime()) / 1000 ) > 65){
                        presenceService.removePresence (consumeRecord.id, serviceMap)
                        println("remove online status:consumeRecordId=${consumeRecord.id}")
                    }
                }

            }
        }
        
    }
}

 

 

3、记录到application中了之后,就可以从application中读取保存在里面的在线用户了,建立一个controller,读取在线状态的action为:

def onlinestatus= {
        User sp = User.findByUserName(session.userName)  //sp为商户
        def pscontext = request.session.servletContext
        Map serviceMap = pscontext.getAttribute("serviceMap")
        List result = presenceService.getPresenceList(sp,serviceMap)

        request.setAttribute ("resultList",result)
        //[resultList:result]

        [resultList:result]  
    }

 如果要即时获取此在线消费用户的列表,则要使用Ajax来定时执行此action了:

<g:javascript>

function timerUpdater() {
              var myAjax = new Ajax.PeriodicalUpdater(
              {success:'onlinelist'},// 成功或失敗更新的页面div元素
                      '/user/onlinestatus', // 请求的URL
              {
                  asynchronous:true,
                  method: 'post', // HTTP请求的方式为GET
                  evalScripts: true, // 是否执行请求页面中的脚本
                  frequency: 60, // 更新的频率
                  decay: 2 // 衰减系数
              }
                      );
              return false;
          }
</g:javascript>

 

 保存所有之后,即可运行测试了。。。。。。。。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值