ModelBase.lua 9.47 KB
local ModelBase = class("ModelBase")
ModelBase.key = "key"
ModelBase.schema = {}

local string_format = string.format
local table_insert = table.insert
local table_unpack = table.unpack
local assert = assert
local next = next
local ipairs = ipairs
local pairs = pairs
local tostring = tostring
local tonumber = tonumber
local redisproxy = redisproxy

local function filterProperties(properties, filter)
    for i, field in ipairs(filter) do
        properties[field] = nil
    end
end

function ModelBase:ctor(properties)
    self.isModelBase_ = true
    self.cacheFields = {} --缓存字段 不更新数据库的字段

    self[self.class.key .. "_"] = properties[self.class.key] --数据库key
    properties[self.class.key] = nil

    if not self:isValidKey() then
        print(string_format("%s [%s:key] should be give in new(ctor)", tostring(self), self.class.__cname))
        return
    end

    if type(properties) ~= "table" then properties = {} end
    self:loadProperties(properties)  --缺少的域将设置默认值
end

-- startCache 和 endCache 在恰当的时候*配对使用*  嵌套使用多次增加引用计数 直到引用计数为0 写入
function ModelBase:startCache( ... )
    for _, field in ipairs({ ... }) do
        if self.class.schema[field] then
            self.cacheFields[field] = (self.cacheFields[field] or 0) + 1
        end
    end
end

--减少缓存引用计数 为时写入, 无参数 强制刷新所有缓存
function ModelBase:endCache( ... )
    local args = { ... }
    local params = {}

    local function doOneCache(field)
        local propname = field .. "_"
        table_insert(params, field)
        if self.class.schema[field][1] == "table" then
            table_insert(params, MsgPack.pack(self[propname]))
        else
            table_insert(params, self[propname])
        end
    end

    if not next(args) then
        for field, _ in pairs(self.cacheFields) do
            doOneCache(field)
        end
        self.cacheFields = {}
    else
        for _, field in ipairs(args) do
            if self.cacheFields[field] then
                self.cacheFields[field] = self.cacheFields[field] - 1
                if self.cacheFields[field] <= 0 then
                    self.cacheFields[field] = nil
                    doOneCache(field)
                end
            end
        end
    end

    if next(params) then
        redisproxy:hmset(self:getKey(), table_unpack(params))
    end
end
--[[--

返回对象的 ID 值。

**Returns:**

-   ID 值

]]
function ModelBase:getKey()
    local id = self[self.class.key .. "_"]
    assert(id ~= nil, string_format("%s [%s:getKey()] Invalid key", tostring(self), self.class.__cname))
    return id
end

function ModelBase:load(properties)
    if not self:isValidKey() then
        print(string_format("%s [%s:id] should be set before load", tostring(self), self.class.__cname))
        return false
    end

    if not properties then
        properties = redisproxy:hgetall(self:getKey())
        properties = table.arrayToMap(properties)
    end
    if not next(properties) then return false end

    self:loadProperties(properties)

    self:onLoad()

    return true
end

--创建model对应的redis数据, 必须已经设置了ID
function ModelBase:create()
    if not self:isValidKey() then
        print(string_format("%s [%s:key] should be set before create", tostring(self), self.class.__cname))
        return nil
    end

    self:save()
    self:onCreate()

    return self
end

-- save 忽略 缓存配置
function ModelBase:save()
    local redisProperties = self:getProperties()

    local params = {}
    for fieldName, value in pairs(redisProperties) do
        local propname = fieldName .. "_"
        table_insert(params, fieldName)
        if self.class.schema[fieldName][1] == "table" then
            table_insert(params, MsgPack.pack(self[propname]))
        else
            table_insert(params, self[propname])
        end
    end
    if next(params) then
        redisproxy:hmset(self:getKey(), table_unpack(params))
    end
end

--[[--

确定对象是否设置了有效的 key。

]]
function ModelBase:isValidKey()
    local propname = self.class.key .. "_"
    local key = self[propname]
    return type(key) == "string" and key ~= ""
end

--[[--

加载对象的属性进内存。
NOTE: 如果properties缺少schema中的域, 将用默认值来填充

**Parameters:**

-   properties: 包含属性值的数组

]]
function ModelBase:loadProperties(properties)
    assert(type(properties) == "table", "Invalid properties")

    for field, schema in pairs(self.class.schema) do
        local typ, def = table_unpack(schema)
        local propname = field .. "_"

        if typ == "table" and type(properties[field]) == "string" then
            properties[field] = MsgPack.unpack(properties[field])
        end

        local val = properties[field] or def
        if val ~= nil then
            if typ == "number" then val = tonumber(val) end
            assert(type(val) == typ,
               string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s",
                                 tostring(self), self.class.__cname, field, typ, type(val)))
            self[propname] = val
        end
    end
end

--[[--

取得对象的属性值。

**Parameters:**

-   fields: 要取得哪些属性的值,如果未指定该参数,则返回 fields 中设定的属性
-   filter: 要从结果中过滤掉哪些属性,如果未指定则不过滤

**Returns:**

-   包含属性值的数组

]]
function ModelBase:getProperties(fields, filter)
    local schema = self.class.schema
    if type(fields) ~= "table" then fields = table.keys(self.class.schema) end

    local properties = {}
    for i, field in ipairs(fields) do
        local propname = field .. "_"
        local typ = schema[field][1]
        local val = self[propname]
        assert(type(val) == typ,
               string_format("%s [%s:getProperties()] Type mismatch, %s expected %s, actual is %s",
                                 tostring(self), self.class.__cname, field, typ, type(val)))
        properties[field] = val
    end

    if type(filter) == "table" then
        filterProperties(properties, filter)
    end

    return properties
end

function ModelBase:getProperty(property)
    if type(property) ~= "string" then return nil end
    if not self.class.schema[property] then return nil end
    return self:getProperties({property})[property]
end

function ModelBase:setProperty(property, value)
    if not self.class.schema[property] then
        print(string_format("%s [%s:setProperty()] Invalid property : %s",
            tostring(self), self.class.__cname, property))
        return
    end

    local typ, def = table_unpack(self.class.schema[property])
    local propname = property .. "_"

    if typ == "number" then value = tonumber(value) end
    if typ == "table" and not value then
        value = self[propname] -- table 可以用自己的缓冲
    end
    assert(type(value) == typ,
       string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s",
         tostring(self), self.class.__cname, property, typ, type(value)))
    self[propname] = value

    if not self.cacheFields[property] then
        -- table 使用msgpack
        if typ == "table" then
            value = MsgPack.pack(value)
        end
        redisproxy:hset(self:getKey(), property, value)
    end
end

function ModelBase:setProperties(fields)
    local result = {}
    for property, value in pairs(fields) do
         if not self.class.schema[property] then
            print(string_format("%s [%s:setProperty()] Invalid property : %s",
                tostring(self), self.class.__cname, property))
        else
            local typ, def = table_unpack(self.class.schema[property])
            local propname = property .. "_"
            if typ == "number" then value = tonumber(value) end
            if typ == "table" and not value then
                value = self[propname] -- table 可以用自己的缓冲
            end
            assert(type(value) == typ,
               string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s",
                 tostring(self), self.class.__cname, property, typ, type(value)))
            self[propname] = value

            if not self.cacheFields[property] then
                table_insert(result, property)
                if typ == "table" then
                    table_insert(result, MsgPack.pack(self[propname]))
                else
                    table_insert(result, self[propname])
                end
            end
        end
    end
    if next(result) then
        redisproxy:hmset(self:getKey(), table_unpack(result))
    end
end

function ModelBase:incrProperty(property, value)
    if not self.class.schema[property] then
        print(string_format("%s [%s:setProperty()] Invalid property : %s",
            tostring(self), self.class.__cname, property))
        return
    end

    local typ, def = table_unpack(self.class.schema[property])
    local propname = property .. "_"

    if typ == "table" then return end
    if typ == "number" then value = tonumber(value) end

    assert(type(value) == typ,
       string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s",
         tostring(self), self.class.__cname, property, typ, type(value)))
    self[propname] = self[propname] + value

    if not self.cacheFields[property] then
        return redisproxy:hincrby(self:getKey(), property, value)
    end
end

function ModelBase:onLoad()
end

function ModelBase:onCreate()
end

return ModelBase