From 9b1de255d28b54c56228730b587fd9bccd6f2a62 Mon Sep 17 00:00:00 2001 From: wanglei <34475144@qq.com> Date: Fri, 24 Oct 2025 22:29:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0orm=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=EF=BC=8C=E6=9C=89=E5=BE=85orm?= =?UTF-8?q?=E7=B1=BB=E8=BF=9B=E8=A1=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.lua | 2 +- src/config/database.lua | 21 +- src/model/user.lua | 5 + src/service/system/account.lua | 2 + src/util/database.lua | 96 ++++++++ src/util/helpers.lua | 153 +++++++++++- src/util/model.lua | 416 +++++++++++++++++++++++++++++++++ 7 files changed, 692 insertions(+), 3 deletions(-) create mode 100644 src/model/user.lua create mode 100644 src/util/database.lua create mode 100644 src/util/model.lua diff --git a/src/config/config.lua b/src/config/config.lua index 3f6b28b..78a7899 100644 --- a/src/config/config.lua +++ b/src/config/config.lua @@ -24,6 +24,6 @@ return { PORT = 5432, -- postgres port USERNAME = "postgres", PASSWORD = "1qaz2wsx", -- postgres password - DATABASE = "postgres" + DATABASE = "postgres", } } \ No newline at end of file diff --git a/src/config/database.lua b/src/config/database.lua index 1718d8f..691cc49 100644 --- a/src/config/database.lua +++ b/src/config/database.lua @@ -13,6 +13,25 @@ return { port = env.POSTGRES.PORT, user = env.POSTGRES.USERNAME, password = env.POSTGRES.PASSWORD, - database = env.POSTGRES.DATABASE + database = env.POSTGRES.DATABASE, + + write = { -- postgresql write database + host = env.POSTGRES.HOST, + port = env.POSTGRES.PORT, + user = env.POSTGRES.USERNAME, + password = env.POSTGRES.PASSWORD, + database = env.POSTGRES.DATABASE + }, + read = { -- postgresql read database + host = env.POSTGRES.HOST, + port = env.POSTGRES.PORT, + user = env.POSTGRES.USERNAME, + password = env.POSTGRES.PASSWORD, + database = env.POSTGRES.DATABASE + }, + charset = 'utf8', + pool_timeout = 1000, -- postgresql pool timeout + pool_size = 100, -- postgresql pool size + timeout = 1000, -- postgresql timeout }, } \ No newline at end of file diff --git a/src/model/user.lua b/src/model/user.lua new file mode 100644 index 0000000..8193881 --- /dev/null +++ b/src/model/user.lua @@ -0,0 +1,5 @@ +local Model = require("util.model") + +local User = Model:new('tbl_user') + +return User \ No newline at end of file diff --git a/src/service/system/account.lua b/src/service/system/account.lua index 56d1b81..6431bc9 100644 --- a/src/service/system/account.lua +++ b/src/service/system/account.lua @@ -126,12 +126,14 @@ function _M.updateAccount(id, jsonData) if ok ~= 0 then return 0x000001,res end + --获取数据表中的数据记录值 local num = 0 for _, row in ipairs(res) do for key, value in pairs(row) do num = value end end + --账号不存在返回错误信息 print("exec result:", num) if num <= 0 then return 0x01000C,nil diff --git a/src/util/database.lua b/src/util/database.lua new file mode 100644 index 0000000..97c9d79 --- /dev/null +++ b/src/util/database.lua @@ -0,0 +1,96 @@ +local pgmoon = require('pgmoon') +local ngx = ngx + +local _M = {} +local WRITE = 'WRITE' +local READ = 'READ' + +local mt = { __index = _M } + +--[[ + Get db connection from connection pool + @return bool, db_context, err +--]] +function _M:get_connection() + if ngx.ctx[self.db_type] then + -- if write before read, make sure write read connection the same + if ngx.ctx[WRITE] then + return ngx.ctx[WRITE], nil + end + return ngx.ctx[self.db_type], nil + end + + local code = 0 + -- 创建一个新的连接 + local conn = pgmoon.new({ + host = self.host, -- postgres host + port = self.port, -- postgres port + user = self.user, + password = self.password, -- postgres password + database = self.database + }); + conn.set_timeout(self.timeout or 1000) + ---- 连接到数据库 + local ok, err = conn:connect() + if not ok then + print("Connection failed: " .. err) + code = 0x000002 + end + --ngx.say("Connection success") + ngx.ctx[self.db_type] = conn + return code,conn +end + +--[[ + 把连接返回到连接池 + 用set_keepalive代替close() 将开启连接池特性,可以为每个nginx工作进程,指定连接最大空闲时间,和连接池最大连接数 + @return void +--]] +function _M.close(self) + if ngx.ctx[READ] then + ngx.ctx[READ]:set_keepalive(self.db_pool_timeout, self.db_pool_size) + ngx.ctx[READ] = nil + end + if ngx.ctx[WRITE] then + ngx.ctx[WRITE]:set_keepalive(self.db_pool_timeout, self.db_pool_size) + ngx.ctx[WRITE] = nil + end +end + +--[[ + 执行数据库语句 + @param sql + @return bool, data, err +--]] +function _M.db_query(self, sql) + local db, err = self:get_connection() + if err ~= nil then + return nil, err + end + local res, errcode, sqlstate + res, err, errcode, sqlstate = db:query(sql) + if not res then + ngx.log(ngx.ERR, err, errcode, sqlstate) + return nil, err + end + + return res, nil +end + +function _M.new(self, opts) + return setmetatable({ + host = opts.host or '127.0.0.1', + port = opts.port or 5432, + user = opts.user or 'postgres', + password = opts.password or '', + database = opts.database or 'postgres', + --charset = opts.charset or 'utf8mb4', + --timeout = opts.timeout, + --max_packet_size = 1024 * 1024, + db_pool_timeout = opts.pool_timeout or 1000, + db_pool_size = opts.pool_size or 1000, + db_type = opts.db_type, + }, mt) +end + +return _M diff --git a/src/util/helpers.lua b/src/util/helpers.lua index f316713..bf5ce1a 100644 --- a/src/util/helpers.lua +++ b/src/util/helpers.lua @@ -4,10 +4,11 @@ --- DateTime: 2025/10/24 11:36 --- local snowflake = require("util.snowflake") +local cjson = require("cjson") local _M = {} -function _M:getUuid() +local function getUuid() local workerId = 0 -- 假设当前机器的ID是1,范围在[0, 31]之间 local datacenterId = 0 -- 数据中心ID,范围在[0, 31]之间 local snow = snowflake.new(workerId, datacenterId) @@ -16,4 +17,154 @@ function _M:getUuid() return snow.int64_to_string(id) end +-- here you need use . not : +local function table_reverse(tbl) + for i=1, math.floor(#tbl / 2) do + tbl[i], tbl[#tbl - i + 1] = tbl[#tbl - i + 1], tbl[i] + end + return tbl +end + +-- remove item in table +local function table_remove(tab, rm) + local result = tab + for k, v in pairs(rm) do + for a_k, a_v in pairs(result) do + -- array + if type(a_k) == 'number' then + -- object + if type(a_v) == 'table' then + result[a_k][v] = nil + elseif v == a_v then + table.remove(result, a_k) + end + else + -- hash array + if v == a_k then + result[a_k] = nil + end + end + end + end + return result +end + +-- unique a array +local function unique(arr) + local hash = {} + local res = {} + for _,v in ipairs(arr) do + if not hash[v] then + hash[v] = true + table.insert(res, v) + end + end + return res +end + +-- make up a string from array +local function implode(arr, symbol) + local implode_str = '' + symbol = symbol or ',' + for key, value in pairs(arr) do + implode_str = implode_str .. value .. symbol + end + return string.sub(implode_str, 1, #implode_str - 1) +end + +-- sort a hashTable by key +local function sort_by_key(tab) + local a = {} + for n in pairs(tab) do + table.insert(a, n) + end + table.sort(a) + local i = 0 -- iterator variable + local iter = function() + -- iterator function + i = i + 1 + if a[i] then + return a[i], tab[a[i]] + else + return nil + end + end + return iter +end + +local function set_cookie(key, value, expires) + local config = require("config.app") + local cookie, err = require("lib.cookie"):new() + if not cookie then + ngx.log(ngx.ERR, err) + return false, err + end + local cookie_payload = { + key = key, + value = value, + path = '/', + domain = config.app_domain, + httponly = true, + } + if expires ~= nil then + cookie_payload.expires = ngx.cookie_time(expires) + end + local ok, err = cookie:set(cookie_payload) + if not ok then + ngx.log(ngx.ERR, err) + return false, err + end + return true +end + +local function get_cookie(key) + local cookie, err = require("lib.cookie"):new() + if not cookie then + ngx.log(ngx.ERR, err) + return false + end + return cookie:get(key) +end + +local function get_local_time() + local config = require("config.app") + local time_zone = ngx.re.match(config.time_zone, "[0-9]+") + if time_zone == nil then + local err = "not set time zone or format error, time zone should look like `+8:00` current is: " .. config.time_zone + ngx.log(ngx.ERR, err) + return false, err + end + -- ngx.time() return UTC+0 timestamp + -- time_zone * 60(sec) * 60(min) + UTC+0 time = current time + return time_zone[0] * 3600 + ngx.time() +end + +local function trim(str, symbol) + symbol = symbol or '%s' -- %s default match space \t \n etc.. + return (string.gsub(string.gsub(str, '^' .. symbol .. '*', ""), symbol .. '*$', '')) +end + +-- data not in order +local function log(...) + local args + if #{...}>1 then + args = {...} + else + args = ... + end + ngx.log(ngx.WARN, cjson.encode(args)) +end + +_M.getUuid = getUuid +_M.log = log +_M.trim = trim +_M.get_cookie = get_cookie +_M.set_cookie = set_cookie +_M.get_local_time = get_local_time +_M.sort_by_key = sort_by_key +_M.implode = implode +_M.unique = unique +_M.table_reverse = table_reverse +_M.table_remove = table_remove + return _M \ No newline at end of file diff --git a/src/util/model.lua b/src/util/model.lua new file mode 100644 index 0000000..03b39dc --- /dev/null +++ b/src/util/model.lua @@ -0,0 +1,416 @@ +local Database = require('util.database') +local dbconf = require("config.database") + +local helpers = require('util.helpers') +local implode = helpers.implode +local unique = helpers.unique +local table_remove = helpers.table_remove + +local _M = {} + +local mt = { __index = _M } + +local WRITE = 'WRITE' +local READ = 'READ' + +local database_write = Database:new({ + host = dbconf.postgres.write.host, + port = dbconf.postgres.write.port, + user = dbconf.postgres.write.user, + password = dbconf.postgres.write.password, + database = dbconf.postgres.write.database, + --charset = dbconf.postgres.charset, + --timeout = dbconf.postgres.timeout, + db_pool_timeout = dbconf.postgres.pool_timeout, + db_pool_size = dbconf.postgres.pool_size, + db_type = WRITE +}) + +local database_read = Database:new({ + host = dbconf.postgres.read.host, + port = dbconf.postgres.read.port, + user = dbconf.postgres.read.user, + password = dbconf.postgres.read.password, + database = dbconf.postgres.read.database, + --charset = dbconf.postgres.charset, + --timeout = dbconf.postgres.timeout, + db_pool_timeout = dbconf.postgres.pool_timeout, + db_pool_size = dbconf.postgres.pool_size, + db_type = READ +}) + +local function transform_value(value) + if value == ngx.null then + value = '' + end + value = value or '' + if string.lower(value) == 'null' then + return 'NULL' + end + return ngx.quote_sql_str(value) +end + +-- return whole relations keys +--获取整个关系的键值 +function _M:get_relation_local_index(parents) + local ids = {} + for key,parent in pairs(parents) do + table.insert( ids, parent[self.relation.local_key] ) + end + return ids +end + +-- return whole relation models +function _M:retrieve_relations(ids) + -- if table is empty + if next(ids) == nil then + return {} + end + local ids_str = implode(unique(ids)) + self.relation_sql = 'select * from '..self.relation.model.table..' where ' .. self.relation.foreign_key .. ' in (' .. ids_str .. ')' + return table_remove(self:query(self.relation_sql, READ), self.relation.model:get_hidden()) +end + +-- return current parent node +function _M:merge_one_relation(parent, relations) + for _, item in pairs(relations) do + if (parent[self.relation.local_key] == item[self.relation.foreign_key]) then + parent[self.relation.key_name] = item + end + end + return parent +end + +function _M:merge_many_relations(parent, relations) + for index, item in pairs(relations) do + if (parent[self.relation.local_key] == item[self.relation.foreign_key]) then + if not parent[self.relation.key_name] then + parent[self.relation.key_name] = {} + end + table.insert(parent[self.relation.key_name], item) + end + end + return parent +end + +function _M:make_relations(parents) + if self.relation.mode ~= 0 then + local relations = self:retrieve_relations(self:get_relation_local_index(parents)) + for key, parent in pairs(parents) do + if self.relation.mode == 1 then + -- belongs to + if table.getn(relations) > 0 then + parents[key] = self:merge_one_relation(parent, relations) + else + parents[key][self.relation.key_name] = nil + end + elseif self.relation.mode == 2 then + -- has many + parents[key] = self:merge_many_relations(parent, relations) + end + end + end + return parents +end + +-- function _M:merge_hidden() +-- if #self.attributes == 0 then +-- return '*' +-- else +-- local result = table_remove(self.attributes, self.hidden) +-- return table.concat(result, ", ") +-- end +-- end + +function _M:find(id,column) + if self.query_sql ~= nil then + ngx.log(ngx.ERR, 'cannot use find() with other query sql') + return nil + end + column = column or 'id' + id = transform_value(id) + local sql = 'select * from '..self.table..' where '..column..'='..id..' limit 1' + local res = self:query(sql, READ) + if table.getn(res) > 0 then + res = self:make_relations(res) + return res[1] + else + return false + end +end + +function _M:all() + if self.query_sql ~= nil then + ngx.log(ngx.ERR, 'cannot use all() with other query sql ', self.query_sql) + return nil + end + local res = self:query('select * from '..self.table, READ) + return self:make_relations(res) +end + +--组装sql语句条件为and相关的字段 +function _M:where(column,operator,value) + value = transform_value(value) + if not self.query_sql then + self.query_sql = 'where '..column.. ' ' .. operator .. ' ' .. value + else + self.query_sql = self.query_sql..' and '..column..' '..operator..' '..value + end + return self +end + +--组装sql语句条件为or相关的字段 +function _M:orwhere(column,operator,value) + value = transform_value(value) + if not self.query_sql then + return ngx.log(ngx.ERR,'orwhere function need a query_sql prefix') + else + self.query_sql = self.query_sql..' or '..column..operator..value + end + return self +end + +function _M:orderby(column,operator) + local operator = operator or 'asc' + if not self.query_sql then + self.query_sql = 'order by '.. self.table .. '.' .. column .. ' ' ..operator + else + if self.has_order_by then + self.query_sql = self.query_sql .. ',' .. column.. ' ' ..operator + else + self.query_sql = self.query_sql .. ' order by ' .. column.. ' ' ..operator + end + end + self.has_order_by = true + return self +end + +--获取数据表中记录的数量 +function _M:count() + local sql = self.query_sql + if not sql then + sql = 'select count(*) from '..self.table + else + sql = 'select count(*) from '..self.table..' '..self.query_sql + end + local res = self:query(sql, READ) + if table.getn(res) > 0 then + return tonumber(res[1]['count(*)']) + else + return 0 + end +end + +-- params: (option)int num +-- return: table +function _M:get(num) + num = num or nil + local limit_sql = '' + if num ~= nil then + limit_sql = 'limit ' .. num + end + if not self.query_sql then + ngx.log(ngx.ERR,'do not have query sql str') + return + end + local sql = 'select * from '..self.table..' '..self.query_sql .. ' ' .. limit_sql + local res = self:query(sql, READ) + if self.relation.local_key ~= nil then + return self:make_relations(res) + end + return res +end + +--根据数据模型中的 +function _M:paginate(page_num, per_page) + page_num = page_num or 1 + per_page = per_page or config.per_page + local sql, count_sql, total + local data={ + data = {}, + next_page = 1, + prev_page = 1, + total = 0 + } + if not self.query_sql then + sql = 'select * from '..self.table..' limit '..per_page*page_num..','..per_page + count_sql = 'select count(*) from '..self.table + else + sql = 'select * from '..self.table .. ' '..self.query_sql .. ' limit '..per_page*(page_num-1)..','..per_page + count_sql = 'select count(*) from '..self.table..' '..self.query_sql + end + total = self:query(count_sql, READ) + if not total then + else + data['total'] = tonumber(total[1]['count(*)']) + data['data'] = self:make_relations(self:query(sql, READ)) + end + if (table.getn(data['data']) + ((page_num - 1)* per_page)) < data['total'] then + data['next_page'] = page_num + 1 + end + if tonumber(page_num) ~= 1 then + data['prev_page'] = page_num - 1 + end + + return data +end + +--返回数据表中的第一条数据记录 +function _M:first() + if not self.query_sql then + ngx.log(ngx.ERR,'do not have query sql str') + return + end + local sql = 'select * from '..self.table..' '..self.query_sql..' limit 1' + local res = self:query(sql, READ) + if next(res) ~= nil then + res = self:make_relations(res) + return res[1] + else + return false + end +end + +--插入数据内容到数据表中 +function _M:create(data) + local columns,values + for column,value in pairs(data) do + value = transform_value(value) + if not columns then + columns = column + values = value + else + columns = columns..','..column + values = values..','..value + end + end + return self:query('insert into '..self.table..'('..columns..') values('..values..')', WRITE) +end + +function _M:with(relation) + self.relation.key_name = relation + if self[relation] == nil then + ngx.log(ngx.ERR, self.table .. ' dont have ' .. relation .. ' function') + end + return self[relation]() +end + +-- 使用数组来存储关系模型 +function _M:has_many(model, foreign_key, local_key) + self.relation.model = model + self.relation.local_key = local_key + self.relation.foreign_key = foreign_key + self.relation.mode = 2 + return self +end + +function _M:belongs_to(model, foreign_key, local_key) + self.relation.model = model + self.relation.local_key = local_key + self.relation.foreign_key = foreign_key + self.relation.mode = 1 + return self +end + +--根据id删除数据库中的数据记录 +function _M:delete(id) + id = id or nil + if not id then + -- 拼接需要delete的字段 + if self.query_sql then + local sql = 'delete from '..self.table..' '..self.query_sql..' limit 1' + return self:query(sql, WRITE) + end + ngx.log(ngx.ERR,'delete function need prefix sql') + ngx.exit(500) + else + return self:query('delete from '..self.table..' where id=' .. id .. ' limit 1', WRITE) + end + return false +end + +--软删除数据,不从数据表中删除记录,只将定义表中的字段值进行处理 +--function _M:soft_delete() +-- if self.query_sql then +-- local sql = 'update '..self.table..' set '..self.soft_delete_column..' = now() '.. self.query_sql ..' limit 1' +-- return self:query(sql, WRITE) +-- end +-- ngx.log(ngx.ERR,'soft_delete function cannot called without restriction') +-- ngx.exit(500) +-- return false +--end + +--更新数据表中的数据记录 +function _M:update(data) + -- 拼接需要update的字段 + local str = nil + for column,value in pairs(data) do + clean_value = transform_value(value) + if not str then + str = column..'='..clean_value + else + str = str..','..column..'='..clean_value + end + end + -- 判断是模型自身执行update还是数据库where限定执行 + if self.query_sql then + local sql = 'update '..self.table..' set '..str..' '..self.query_sql..' limit 1' + return self:query(sql, WRITE) + end + ngx.log(ngx.ERR,'update function cannot called without restriction') + ngx.exit(500) + return false +end + +--查询数据表中的数据记录 +function _M:query(sql, type) + if not sql then + return ngx.log(ngx.ERR,'query() function need sql to query') + end + self.query_sql = nil + self.has_order_by = false + if type == READ then + local result, err = database_read:db_query(sql) + if err ~= nil then + ngx.log(ngx.ERR, "read db error. res: " .. (err or "no reason")) + ngx.exit(500) + return + end + return result + elseif type == WRITE then + local result, err = database_write:db_query(sql) + if err ~= nil then + ngx.log(ngx.ERR, "write db error. res: " .. (err or "no reason")) + ngx.exit(500) + return + end + return result.affected_rows + else + ngx.log(ngx.ERR, 'type invalid, need ' .. READ .. ' or '..WRITE) + ngx.exit(500) + return + end +end + +--获取需要隐藏的列 +function _M:get_hidden() + return self.hidden +end + +--初始化数据表中的字段 +function _M:new(table, attributes, hidden) + return setmetatable({ + table = table, + attributes = attributes or {}, + hidden = hidden or {}, + query_sql = nil, + has_order_by = false, + relation = { + mode = 0 + }, + relation_sql = nil, + --soft_delete_column = 'deleted_at' + }, mt) +end + +return _M \ No newline at end of file