Commit 314bc5df00ab866ab762f4e22f3ceec4681cc52e
1 parent
7cbc0876
提交服务器初始代码
Showing
55 changed files
with
6014 additions
and
0 deletions
Show diff stats
.gitmodules
... | ... | @@ -0,0 +1,163 @@ |
1 | +call 的调用流程分析 | |
2 | + | |
3 | +一个lua vm 中 任意时刻 只能运行一个 coroutine | |
4 | +导入重入的原因是,call 操作中 让出执行权,另一个coroutine 修改了某个值,则唤醒call的coroutine时,再来操作那个值,可能已经改变了 | |
5 | + | |
6 | +coroutine 理解: | |
7 | +--------------------------------------------------- | |
8 | +function foo (a) | |
9 | + print("foo", a) | |
10 | + return coroutine.yield(2*a) | |
11 | +end | |
12 | + | |
13 | +co = coroutine.create(function (a,b) | |
14 | + print("co-body", a, b) | |
15 | + local r = foo(a+1) | |
16 | + print("co-body", r) | |
17 | + local r, s = coroutine.yield(a+b, a-b) | |
18 | + print("co-body", r, s) | |
19 | + return b, "end" | |
20 | +end) | |
21 | + | |
22 | +print("main", coroutine.resume(co, 1, 10)) | |
23 | +print("main", coroutine.resume(co, "r")) | |
24 | +print("main", coroutine.resume(co, "x", "y")) | |
25 | +print("main", coroutine.resume(co, "x", "y")) | |
26 | +当你运行它,将产生下列输出: | |
27 | + | |
28 | +co-body 1 10 | |
29 | +foo 2 | |
30 | +main true 4 | |
31 | +co-body r | |
32 | +main true 11 -9 | |
33 | +co-body x y | |
34 | +main true 10 end | |
35 | +main false cannot resume dead coroutine | |
36 | + | |
37 | +coroutine.resume 将会返回 yield的参数 | |
38 | +继续执行coroutine.resume,coroutine.yield将返回coroutine.resume除第一个参数的其他参数 | |
39 | + | |
40 | +coroutine.resume | |
41 | +开始或继续协程 co 的运行。 当你第一次延续一个协程,它会从主体函数处开始运行。 | |
42 | + val1, ... 这些值会以参数形式传入主体函数。 如果该协程被让出,resume 会重新启动它; | |
43 | + val1, ... 这些参数会作为让出点的返回值。 | |
44 | + | |
45 | +如果协程运行起来没有错误, resume 返回 true 加上传给 yield 的所有值 (当协程让出), | |
46 | + 或是主体函数的所有返回值(当协程中止)。 如果有任何错误发生, resume 返回 false 加错误消息。 | |
47 | +--------------------------------------------------- | |
48 | +A call B | |
49 | + | |
50 | +skynet.call(addr, typename, ...) | |
51 | + | |
52 | +[A] | |
53 | +1. 根据 typename 找到 消息的 pack 和 unpack 类型 | |
54 | +2. 将消息发送出去 将消息 pack 压入 对方的消息队列 | |
55 | +3. 将当前coroutine 让出执行权, | |
56 | + | |
57 | +考虑 skynet.call 所在coroutine | |
58 | +在dispatch 消息中 | |
59 | +494: suspend(co, coroutine.resume(co, session,source, p.unpack(msg,sz, ...))) | |
60 | +suspend(co, true, "CALL", session) | |
61 | +suspend(co, result, command, param, size) | |
62 | +result = true, command = "CALL", param = session | |
63 | + | |
64 | +4. 初始化 session_id_coroutine[session] = co | |
65 | + | |
66 | +[B] | |
67 | +1. 通过dispatch_message,处理完相关请求 | |
68 | +2. 若中间出现错误,将错误信息转发至A, 正常情况下通过skynet.ret()返回 | |
69 | + | |
70 | +考虑处理消息所在coroutine | |
71 | +在dispatch 消息中 (记录 co->session, so->source) | |
72 | +494: suspend(co, coroutine.resume(co, session,source, p.unpack(msg,sz, ...))) | |
73 | +suspend(co, true, "RETURN", msg, sz) | |
74 | +suspend(co, result, command, param, size) | |
75 | +result = true, command = "RETURN", param = msg, size = sz | |
76 | +c.send(co_address, skynet.PTYPE_RESPONSE, co_session, param, size) | |
77 | +将消息发送回去 | |
78 | + | |
79 | +考虑在B coroutine中 发生错误 | |
80 | +在 lua-skynet.c _cb函数中,对消息分发函数(lua function)作了pcall处理,若发生错误,将错误内容输出到logger服务中 | |
81 | +在 skynet.lua 中 raw_dispatch_message 函数中 | |
82 | +suspend(co, coroutine.resume(co, session, source, p.unpack(msg,sz, ...))) | |
83 | + | |
84 | +若 coroutine.resume 失败 | |
85 | +将返回 false, 以及错误信息 | |
86 | +在 suspend 函数中 if not result then 判断下 将错误信息发送给A(这里有更多信息) 并掉用 | |
87 | +error(debug.traceback(co,tostring(command))) | |
88 | +将错误堆栈 让 c层 捕获到 输出到 logger服务中 | |
89 | + | |
90 | +[A] | |
91 | +1. 从 session_id_coroutine 中 根据session找到coroutine, 并置空session_id_coroutine | |
92 | +2. 在raw_dispatch_message 中 | |
93 | +-- skynet.PTYPE_RESPONSE = 1, read skynet.h | |
94 | +if prototype == 1 then | |
95 | +处理suspend(co, coroutine.resume(co, true, msg, sz)) | |
96 | +这时候在yield_call中 | |
97 | +local succ, msg, sz = coroutine_yield("CALL", session) | |
98 | +返回coroutine.resume 除第一个参数的其他参数 | |
99 | +local succ, msg, sz = true, msg, sz | |
100 | + | |
101 | +==================================================================== | |
102 | +send 操作就比较简单,A端发送出去就不管了,B端发生错误也不会理睬, | |
103 | +它在 B 端 dispatch_message 阶段 suspend 中 走的 command == nil | |
104 | + | |
105 | + | |
106 | +重要的数据结构 | |
107 | +在被调用的对象中,记录调用者信息 | |
108 | +session_coroutine_id[co] = session | |
109 | +session_coroutine_address[co] = sourceAddr | |
110 | +因为 coroutine 是需要被复用的,所以coroutine退出时候,会清理这两个结构 | |
111 | + | |
112 | +调用者 | |
113 | +watching_session[targetAddr] = session | |
114 | +在挂起当前协程的时候,记录 地址和session | |
115 | +1. 出错信息 | |
116 | +2. 当前vm退出,可告知被call对方,出错信息 | |
117 | + | |
118 | +==================================================================== | |
119 | + | |
120 | +queue 的实现 | |
121 | +当运行该coroutine的时候,调用skynet.wake,生成session,coroutine_yield("SLEEP", session) | |
122 | + | |
123 | +在 suspend 中,设置 | |
124 | +session_id_coroutine[session] = co | |
125 | +sleep_session[co] = session | |
126 | + | |
127 | +dispatch_wakeup 中 | |
128 | +coroutine.resume(co, false, "BREAK")) | |
129 | +继续运行 | |
130 | +sleep_session[co] = nil | |
131 | +session_id_coroutine[session] = nil | |
132 | + | |
133 | +function () | |
134 | + option { | |
135 | + yield "sleep" | |
136 | + } | |
137 | + | |
138 | + # A call B, 则 A 是 yield "call"; B 是 yield "return" | |
139 | + operation { | |
140 | + option yield "call" | |
141 | + option yield "return" | |
142 | + } | |
143 | + | |
144 | + yield "exit" | |
145 | +end | |
146 | + | |
147 | +==================================================================== | |
148 | + | |
149 | +redirect 实现 | |
150 | +skynet.redirect = function(dest, source, typename, ...) | |
151 | + return c.redirect(dest, source, proto[typename].id, ...) | |
152 | +end | |
153 | + | |
154 | +使用范例: | |
155 | + skynet.redirect(agent, c.client or 0, "client", 0, msg, sz) | |
156 | + | |
157 | +1. dest | |
158 | +2. source | |
159 | +3. typeId | |
160 | +4. session | |
161 | +5,6 skynet.pack(...) | |
162 | + | |
163 | +==================================================================== | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +==================================================================== | |
2 | +session 从 1 到 0x7FFFFFFF | |
3 | +若收到消息 | |
4 | + | |
5 | +如果 session == 0 | |
6 | + | |
7 | +可能 socket 消息 | |
8 | + | |
9 | +可能 text error 消息 | |
10 | + | |
11 | +如果 type == 1 | |
12 | + | |
13 | +可能 response 消息 来源 : call 回复消息 或者 timeout 通知消息 | |
14 | + | |
15 | +==================================================================== | |
16 | +若发送信息: | |
17 | +c.send(dest, type, session, msg, sz) | |
18 | + | |
19 | +如果 session == 0 则为 send 消息 不需要返回 | |
20 | +如果 session 为 nil 则 需要指定底层分配 如 call 操作 | |
21 | + | |
22 | +type 若 session 为 nil, 则 type | PTYPE_TAG_ALLOCSESSION | |
23 | + | |
24 | +谁来清空内存??? | |
25 | + | |
26 | +在发送端生成,由接受者来清理 | |
27 | + | |
28 | +如果是 LUA_TSTRING 分配内存 | |
29 | + | |
30 | +如果是 LUA_TLIGHTUSERDATA seri 的时候 分配内存,传递指针,dispatch_message的时候释放内存 | |
31 | + | |
32 | +==================================================================== | ... | ... |
... | ... | @@ -0,0 +1,73 @@ |
1 | +lua_createtable(L, 0, 3); 创建大小为3的table | |
2 | + | |
3 | +lua_pushcfunction(L, lcopy); 将lcopy压栈 | |
4 | + | |
5 | +lua_setfield(L, -2, "copy"); | |
6 | + | |
7 | +{ | |
8 | + copy = lcopy, | |
9 | +} | |
10 | + | |
11 | +==================================================================== | |
12 | + | |
13 | +lua_createtable(L, 0, 2); 创建大小为2的table | |
14 | + | |
15 | +lua_pushcfunction(L, ldeletewriter), | |
16 | +lua_setfield(L, -2, "__gc"); | |
17 | + | |
18 | +lua_pushcfunction(L, lupdate), | |
19 | +lua_setfield(L, -2, "__call"); | |
20 | + | |
21 | +table = { | |
22 | + __gc = ldeletewriter, | |
23 | + __call = lupdate, | |
24 | +} | |
25 | + | |
26 | +luaL_Reg writer[] = { | |
27 | + { "new", lnewwriter }, | |
28 | + { NULL, NULL }, | |
29 | +}; | |
30 | +luaL_setfuncs(L, writer, 1); | |
31 | +将 writer 压入栈顶,并将table作为上值 | |
32 | + | |
33 | +==================================================================== | |
34 | + | |
35 | +lua_createtable(L, 0, 2); | |
36 | +lua_pushcfunction(L, ldeletereader), | |
37 | +lua_setfield(L, -2, "__gc"); | |
38 | +lua_pushcfunction(L, lread), | |
39 | +lua_setfield(L, -2, "__call"); | |
40 | + | |
41 | +table = { | |
42 | + __gc = ldeletereader, | |
43 | + __call = lread, | |
44 | +} | |
45 | + | |
46 | +luaL_Reg reader[] = { | |
47 | + { "newcopy", lnewreader }, | |
48 | + { NULL, NULL }, | |
49 | +}; | |
50 | +luaL_setfuncs(L, reader, 1); | |
51 | +将 reader 压入栈顶,并将table作为上值 | |
52 | + | |
53 | +==================================================================== | |
54 | + | |
55 | +{ | |
56 | + copy = lcopy, | |
57 | + writer = ..., | |
58 | + reader = ..., | |
59 | +} | |
60 | + | |
61 | +==================================================================== | |
62 | + | |
63 | + luaL_Reg m[] = { | |
64 | + { "pop", lpop }, | |
65 | + { "push", lpush }, | |
66 | + { "size", lsize }, | |
67 | + { NULL, NULL }, | |
68 | + }; | |
69 | + luaL_newmetatable(L, "dangge.deque"); 创建一个meta表并压栈 | |
70 | + lua_pushvalue(L, -1); 复制一个meta表 | |
71 | + lua_setfield(L, -2, "__index"); meta = {__index = meta} | |
72 | + luaL_setfuncs(L, m, 0); meta = {pop = lpop, push = lpush, size = lsize} | |
73 | + | ... | ... |
... | ... | @@ -0,0 +1,84 @@ |
1 | +stringUtil.lua 中提供的接口 | |
2 | +============ | |
3 | +接口设计参考 redis 方式 | |
4 | + | |
5 | +针对 "K1=V1 K2=V2 ... Kn=Vn" 结构的CRUD(增删改查)操作 | |
6 | +K1~Kn是唯一的;K1~Kn,V1~Vn都为整数,不可为浮点数 | |
7 | + | |
8 | +setv 操作为修改 k 的值 v,若不存在 k 则将k=v附在字符串末尾 | |
9 | +string.setv(str, k, v, delimiter) | |
10 | + | |
11 | +msetv 操作为修改 {[k1]=v1, [k2]=v2, ..., [kn]=vn} 中对应的kv值,若字符串中不包含则以 k=v 的形式附在末尾 | |
12 | +string.msetv(str, vs, delimiter) | |
13 | + | |
14 | +incrv 操作为修改 k 对应的值 +delta,若字符串中不包含则以 k=delta 的形式附在末尾 | |
15 | +string.incrv(str, k, delta, delimiter) | |
16 | + | |
17 | +mincrv 操作为修改 {[k1]=d1, [k2]=d2, ..., [kn]=dn} 中对应的k 是与之对应的 v+d,若字符串中不包含则以 k=d 的形式附在末尾 | |
18 | +string.mincrv(str, ds, delimiter) | |
19 | + | |
20 | +delk 操作为 删除字符串中 包含k 的k=v结构, 若不存在则跳过 | |
21 | +string.delk(str, k, delimiter) | |
22 | + | |
23 | +mdelk 操作为 删除字符串中 包含 {k1, k2, ..., kn} 中任意元素的结构,若不存在则跳过 | |
24 | +string.mdelk(str, ks, delimiter) | |
25 | + | |
26 | +getv 操作为 获取字符串中 包含 k 中的 v 值,若不存在返回default值 | |
27 | +string.getv(str, k, default, delimiter) | |
28 | + | |
29 | +如果要获取多个k的值,则可通过 string.toNumMap() 将字符串转化为 一个table,通过访问table获取相应的值 | |
30 | +ps:尽量将多个操作集中为一个table 然后调用 string.msetv or string.mdelk | |
31 | + | |
32 | +============ | |
33 | +string.setv_dk(str, k1, k2, v, delimiter) | |
34 | +string.msetv_dk(str, vs, delimiter) | |
35 | +string.incrv_dk(str, k1, k2, delta, delimiter) | |
36 | +string.mincrv_dk(str, ds, delimiter) | |
37 | +string.delk_dk(str, k1, k2, delimiter) | |
38 | +string.mdelk_dk(str, ks, delimiter) | |
39 | +string.getv_dk(str, k1, k2, default, delimiter) | |
40 | +string.toNumMap(str, delimiter) | |
41 | + | |
42 | +这里是多键结构,操作含义如上 | |
43 | + | |
44 | +============ | |
45 | + | |
46 | +"N1#N2#...#Nn" 是一个集合 | |
47 | +toArray #为任意分隔符 生成 {N1, N2, ..., Nn} | |
48 | +string.toArray(str, toNum, delimiter) | |
49 | +value 获取某个序号的元素 序号范围 1~n | |
50 | +string.value(str, index, default, notNum, delimiter) | |
51 | +sismember 是否存在 value 元素 | |
52 | +string.sismember(str, value, delimiter) | |
53 | +sadd 添加 值为value 元素,若存在则跳过 | |
54 | +string.sadd(str, value, delimiter) | |
55 | +srem 删除 值为value 元素 | |
56 | +string.srem(str, value, delimiter) | |
57 | + | |
58 | +============ | |
59 | + | |
60 | +直接从字符串中 随机 | |
61 | +string.randWeight(str, bMulti) | |
62 | +string.randLine(str) | |
63 | + | |
64 | +============ | |
65 | + | |
66 | +"1010101" 由1,0组成的元素 | |
67 | +getbit 获取序号pos 的元素 | |
68 | +string.getbit(str, pos) | |
69 | +bitcnt 获取 元素值为1 的个数 | |
70 | +string.bitcnt(str) | |
71 | +设置位置为pos的值为yes | |
72 | +string.setbit(str, pos, yes) | |
73 | + | |
74 | +============ | |
75 | + | |
76 | +lua-strh.c 中为lua层提供的接口,这些都是辅助接口,不要直接在逻辑代码中直接使用,使用stringUtil.lua中封装后的接口 | |
77 | +modify | |
78 | +moddk | |
79 | +getv | |
80 | +getvdk | |
81 | +toarray | |
82 | +tonummap | |
83 | +value | |
84 | + | ... | ... |
... | ... | @@ -0,0 +1,114 @@ |
1 | +function skynet.launch(...) | |
2 | + c.command("LAUNCH", table.concat({...}, " ")) | |
3 | + ...... | |
4 | +end | |
5 | + | |
6 | +lua-skynet.c | _command | skynet_command | |
7 | +skynet_server.c | cmd_launch | skynet_context_new | |
8 | + | |
9 | +==================================================================== | |
10 | + | |
11 | +function skynet.newservice(name, ...) | |
12 | + return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...) | |
13 | +end | |
14 | + | |
15 | +service/launcher.lua | |
16 | + | |
17 | +返回 "RESPONSE", skynet.pack | |
18 | + | |
19 | +newservice 收到 "RESPONSE" | |
20 | + | |
21 | +launch 服务就有所有服务的地址 | |
22 | + | |
23 | +可以统计所有服务,gc某个服务,stats某个服务 | |
24 | + | |
25 | +==================================================================== | |
26 | + | |
27 | +再来看 skynet_context_new | |
28 | + | |
29 | +1. 获取 skynet_module | |
30 | +struct skynet_module { | |
31 | + const char * name; 模块名 | |
32 | + void * module; 模块对应的动态库(snlua, 或者其他动态库) | |
33 | + skynet_dl_create create; 创建函数 | |
34 | + skynet_dl_init init; 初始化函数 | |
35 | + skynet_dl_release release; 释放函数 | |
36 | + skynet_dl_signal signal; 信号函数 | |
37 | +}; | |
38 | + | |
39 | +2. 调用 M->create() 函数 | |
40 | +创建 lua vm | |
41 | +struct snlua { | |
42 | + lua_State * L; | |
43 | + struct skynet_context * ctx; | |
44 | +}; | |
45 | +构建 snlua 结构 | |
46 | + | |
47 | +3. 创建并初始化 上下文 | |
48 | +struct skynet_context { | |
49 | + void * instance; snlua结构 | |
50 | + struct skynet_module * mod; | |
51 | + void * cb_ud; | |
52 | + skynet_cb cb; callback函数指针 | |
53 | + struct message_queue *queue; 消息队列 | |
54 | + FILE * logfile; 日志输出文件 | |
55 | + char result[32]; | |
56 | + uint32_t handle; | |
57 | + int session_id; | |
58 | + int ref; | |
59 | + bool init; | |
60 | + bool endless; | |
61 | + CHECKCALLING_DECL | |
62 | +}; | |
63 | +创建消息队列 | |
64 | + | |
65 | +4. 调用 M->init()函数 | |
66 | +设置回调函数 | |
67 | +skynet_callback(ctx, l , _launch); | |
68 | +ctx->cb 回调函数 _launch | |
69 | +ctx->cb_ud snlua结构 | |
70 | + | |
71 | +"REG" 注册 | |
72 | +发送第一个消息 | |
73 | +skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY, 0, tmp, sz); | |
74 | + | |
75 | +调用 _launch 函数 | |
76 | +skynet_callback(context, NULL, NULL); | |
77 | +将回调函数清空 | |
78 | +接着调用 _init 函数 | |
79 | + | |
80 | +将 skynet_context 的指针地址 存入 lua 环境中 (通过轻量用户数据来实现) | |
81 | +加载常用库 | |
82 | +将 LUA_PATH LUA_CPATH LUA_SERVICE LUA_PRELOAD 写入全局table中 | |
83 | +通过 loader.lua 加载所有的lua库 | |
84 | + | |
85 | +5. 将次级消息队列压入全局消息队列 | |
86 | +skynet_globalmq_push | |
87 | + | |
88 | +6. 在服务主模块中调用 | |
89 | +skynet.start 设置回调函数 | |
90 | +c.callback 将 _cb作为键,值lua 回调函数 写入lua环境中 | |
91 | +skynet_callback | |
92 | + | |
93 | +lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); | |
94 | +lua_State *gL = lua_tothread(L,-1); | |
95 | + | |
96 | +cb_ud 保存 主线程,主线程不会被回收,生命周期同vm | |
97 | + | |
98 | +主线程 栈上永远保存着两个值 | |
99 | +traceback | |
100 | +回调函数 | |
101 | + | |
102 | +调用 回调函数 | |
103 | +r = lua_pcall(L, 5, 0, 1); | |
104 | +栈上第一个函数作为 错误处理函数 | |
105 | + | |
106 | +主线程 调用回调函数 回调函数分配线程去处理 每条消息 | |
107 | + | |
108 | +7. 错误处理 | |
109 | +在 发生错误的 服务 处理 | |
110 | + | |
111 | +回调函数发生在主线程 | |
112 | + | |
113 | + | |
114 | + | ... | ... |
... | ... | @@ -0,0 +1,53 @@ |
1 | +Role.magics = { | |
2 | + talentPoint = { | |
3 | + timestr = "talentLastTime", -- timestamps | |
4 | + limit = function (role) | |
5 | + return globalCsv.talentPointLimit | |
6 | + end, | |
7 | + recover = "talentRecoveryTime", -- globaldefine | |
8 | + collectIn = "in_talentPoint", | |
9 | + collectOut = "out_talentPoint", | |
10 | + cycle = true, -- 回复满,是否复用时间 | |
11 | + },-- 天赋点 | |
12 | +} | |
13 | + | |
14 | +方法: | |
15 | +1. produce 生产 | |
16 | + | |
17 | +2. consume 消费 | |
18 | + | |
19 | +3. onRecoverTimer 定时更新 | |
20 | + | |
21 | +4. onRecoverLogin 登录回复 | |
22 | + | |
23 | +使用前准备: | |
24 | + | |
25 | +在 timestamp 类中添加 talentLastTime ,上次修改时间 | |
26 | + | |
27 | +在 globaldefine 中添加 talentPointLimit ,添加上限值,如上,limit中是function 可以根据role的属性来调整上限值;例如可根据vip调整上限值 | |
28 | + | |
29 | +在 globaldefine 中添加 talentRecoveryTime ,每(多少)个单位时间回复该值,这里默认以分钟为单位 | |
30 | + | |
31 | +collectIn 和 out_talentPoint 变量 是日志字段 | |
32 | + | |
33 | +cycle 是标志位 意思是 满了后消耗,会接着上次剩余时间回复该值 | |
34 | + | |
35 | +==================================================================== | |
36 | + | |
37 | +对外提供接口 | |
38 | + | |
39 | +role:produce(params) | |
40 | +role:consume(params) | |
41 | + | |
42 | +params 可使用字段 | |
43 | + | |
44 | +field | |
45 | + | |
46 | +delta | |
47 | + | |
48 | +cantOver | |
49 | + | |
50 | +notify | |
51 | + | |
52 | + | |
53 | + | ... | ... |
... | ... | @@ -0,0 +1,211 @@ |
1 | +XXTEA_KEY = "699D448D6D24f7F941E9F6E99F823E18" | |
2 | + | |
3 | +MAX_ROLE_NUM = 1000000 | |
4 | + | |
5 | +RESET_TIME = 4 | |
6 | + | |
7 | +MAX_QUALITY_LEVEL = 5 | |
8 | + | |
9 | +MAX_HERO_STAR = 6 | |
10 | +MAX_HERO_LVL = 100 | |
11 | +MAX_EVOL_LVL = 5 -- 最高进化等级 | |
12 | +MAX_BREAK_LVL = 16 | |
13 | + | |
14 | +MAX_VIP = 15 | |
15 | + | |
16 | +MAIL_EXPIRE_TIME = 7*86400 | |
17 | + | |
18 | +MAX_FRIEND_SEARCH_COUNT = 5-- 最大搜索数目 | |
19 | + | |
20 | +MAX_YZ_LEVEL = 10 | |
21 | + | |
22 | +HOUR_ZONE = 9 -- 时区 | |
23 | + | |
24 | +carbonType = { | |
25 | + Normal = 10000, | |
26 | + Special = 20000, | |
27 | + Tower = 30000, | |
28 | + Trial = 40000, | |
29 | + Worldboss = 50000, | |
30 | + Yz = 60000, | |
31 | + Practice = 70000, | |
32 | +} | |
33 | + | |
34 | +FinishType = { | |
35 | + AllKill = 1, --全部清光 | |
36 | + BossKill = 2, --干光boss | |
37 | +} | |
38 | + | |
39 | +StageType = { | |
40 | + Common = 1, -- 普通 | |
41 | + Arrange = 2, -- 传送门 | |
42 | + WeakArrange = 3, -- 脆弱的传送门 | |
43 | + Supply = 4, -- 补给点 | |
44 | + Power = 5, -- 采集点 | |
45 | + Select = 6, -- 抉择点 | |
46 | + Cross = 7, -- 穿越点 | |
47 | + RandSelect = 8, -- 随机selet | |
48 | + OnceArrange = 9, -- 一次性召唤点 | |
49 | + | |
50 | +} | |
51 | + | |
52 | +AiType = { | |
53 | + Warn = 1, -- 警戒型 | |
54 | + Occupy = 2, -- 占领型 | |
55 | + Special = 3, -- 特殊型 | |
56 | + Eat = 4, -- 吞噬特殊型 | |
57 | +} | |
58 | + | |
59 | +MonsterType = { | |
60 | + Boss = 1, -- Boss | |
61 | + Assault = 2, -- 突击怪 | |
62 | + Maker = 3, -- 制造者 | |
63 | + Dregs = 4, -- 渣滓 | |
64 | + Eat = 5, -- 吞噬者 | |
65 | + Ghost = 6, -- 隐藏boss | |
66 | +} | |
67 | + | |
68 | +ItemType = { | |
69 | + RandGiftClose = 1, -- 随机道具 不打开 | |
70 | + RandGiftOpen = 2, -- 随机道具 打开 | |
71 | + Material1 = 3, -- 油 | |
72 | + Material2 = 4, -- 魔力 | |
73 | + Material3 = 5, -- 食材 | |
74 | + Material4 = 6, -- 调料 | |
75 | + HeroSkin = 7, -- 食灵皮肤券 | |
76 | + PackageStudy = 8, -- 套餐研究素材 | |
77 | + Hero = 9, -- 武将 | |
78 | + QuickProp = 10, -- 快速道具 | |
79 | + BuildDraw = 11, -- 建造图纸 | |
80 | + Diamond = 12, -- 钻石 | |
81 | + Medel = 13, -- 勋章 | |
82 | + EnergyItem = 14, -- 次元能量,固定消耗道具 | |
83 | + Seed = 15, -- 种子 | |
84 | + QuickPlant = 16, -- 快速种植 | |
85 | + JobPerfer = 17, -- 职业偏向 | |
86 | + LoveItem = 18, -- 好感度材料 | |
87 | + HeadFarme = 19, -- 头像框 | |
88 | + HeadIcon = 20, -- 头像 | |
89 | + HomeSkin = 21, -- 主城皮肤 | |
90 | + Equip = 22, -- 装备 | |
91 | + Proof = 23, -- 交易凭证 | |
92 | + Dress = 24, -- 时装 | |
93 | + Build = 25, -- 建筑材料 | |
94 | + HeroBag = 26, -- 食灵背包扩展凭证 | |
95 | + EquipBag = 27, -- 武器背包扩展凭证 | |
96 | + GiftBag = 28, -- 礼物背包扩展凭证 | |
97 | + TeamBag = 29, -- 套餐扩展凭证 | |
98 | + HeroBuild = 30, -- 烹饪扩展凭证 | |
99 | + EquipBuild = 31, -- 采购扩展凭证 | |
100 | + PvpCoin = 32, --pvp积分, 商城购买 | |
101 | + ChooseBox = 33, --玩家选择盒子, | |
102 | + DrawCoin = 34, --抽奖券, | |
103 | + ActivityCoin = 35, --活动积分 | |
104 | + DinerBox = 36, --便当盒 | |
105 | + Fitment = 37, --家具 | |
106 | + DinerCar = 38, --餐车 | |
107 | + Accessory = 39, --配件 | |
108 | + DinerCarNum = 40, --餐车数量扩展 | |
109 | + DinerBoxNum = 41, --便当盒数量扩展 | |
110 | + AccMaterial = 42, --升级消耗材料 | |
111 | + DinerCoin = 43, --金币银币 | |
112 | + PangCi = 44, --胖次 | |
113 | + SkillUpM = 45, --技能升级材料 | |
114 | +} | |
115 | + | |
116 | +ROUND = {[5]=true,[6]=true,[9]=true,[13]=true,[14]=true,} | |
117 | + | |
118 | +-- 属性枚举 | |
119 | +AttsEnum = { | |
120 | + hp = 1, -- 血量 | |
121 | + atk = 2, -- 攻击 | |
122 | + phyDef = 3, -- 物理防御 | |
123 | + hit = 4, -- 命中 | |
124 | + miss = 5, -- 闪避 | |
125 | + crit = 6, -- 暴击 | |
126 | + atkSpeed = 7, -- 攻击速度 | |
127 | + critHurt = 8, -- 暴伤 | |
128 | +} | |
129 | + | |
130 | +AttsEnumEx = { | |
131 | + [1] = "hp", -- 血量 | |
132 | + [2] = "atk", -- 攻击 | |
133 | + [3] = "phyDef", -- 物理防御 | |
134 | + [4] = "hit", -- 命中 | |
135 | + [5] = "miss", -- 闪避 | |
136 | + [6] = "crit", -- 暴击 | |
137 | + [7] = "atkSpeed", -- 攻击速度 | |
138 | + [8] = "critHurt", -- 暴伤 | |
139 | +} | |
140 | + | |
141 | +-- 物品起始id | |
142 | +ItemStartId = { | |
143 | + hero = 1000, -- 英雄 | |
144 | + equip = 2000, -- 装备 | |
145 | +} | |
146 | + | |
147 | +HeroType = { | |
148 | + legend = 1, -- 传说英雄 | |
149 | + diamond = 2, -- 钻石英雄 | |
150 | + normal = 3, -- 免费英雄 | |
151 | + lowStar = 4, -- 低星英雄 | |
152 | + monster = 5, -- 怪物 | |
153 | + npc = 6, -- npc | |
154 | + beauty = 7, -- 女神 | |
155 | +} | |
156 | + | |
157 | +ExploreType = { | |
158 | + Common = 1, -- 普通 | |
159 | + Born = 2, -- 出生点 | |
160 | + Score = 3, -- 积分点 | |
161 | + Cure = 4, -- 治疗点 | |
162 | + Item = 5, -- 道具店 | |
163 | + Cross = 6, -- 传送门 | |
164 | + Enemy = 7, -- 战斗点 | |
165 | + Event = 8, -- 抉择点 | |
166 | +} | |
167 | + | |
168 | +ExploreItemType = { | |
169 | + Bomb = 1, -- 炸弹 | |
170 | + Weak = 2, -- 虚弱 | |
171 | + Slow = 3, -- 迟缓 | |
172 | + Invincible = 4, -- 无敌 | |
173 | + Forbiden = 5, -- 封印 | |
174 | + Victory = 6, -- 必胜 | |
175 | + Dice1 = 7, -- 固定点数1 | |
176 | + Dice2 = 8, -- 固定点数2 | |
177 | + Dice3 = 9, -- 固定点数3 | |
178 | + Dice4 = 10, -- 固定点数4 | |
179 | + Dice5 = 11, -- 固定点数5 | |
180 | + Dice6 = 12, -- 固定点数6 | |
181 | + DiceAll = 13, -- 任意点数 | |
182 | + Fight = 14, -- 决斗卡 | |
183 | +} | |
184 | + | |
185 | +RoomEvent = { | |
186 | + move = 1, | |
187 | + useItem = 2, | |
188 | + playerOnStage = 3, | |
189 | + playerStart = 4, | |
190 | + playerEnd = 5, | |
191 | + playerExit = 6, | |
192 | + playerDice = 7, | |
193 | + playerFight = 8, | |
194 | + eventCross = 9, | |
195 | + eventFight = 10, | |
196 | + eventScore = 11, | |
197 | + eventItem = 12, | |
198 | + eventCure = 13, | |
199 | + addScore = 14, | |
200 | + updateMap = 15, | |
201 | + addMainList = 16, | |
202 | + addWaitList = 17, | |
203 | + playerRevive= 18, | |
204 | + playerTalk = 19, | |
205 | + addHealth = 20, | |
206 | +} | |
207 | + | |
208 | +BANTYPE = { | |
209 | + default = 0, | |
210 | + heartWarning = 1, | |
211 | +} | |
0 | 212 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,260 @@ |
1 | +-- 协议号 | |
2 | +actionCodes = { | |
3 | + Sys_heartBeat = 1, | |
4 | + Sys_errorMsg = 3, | |
5 | + Sys_innerErrorMsg = 4, | |
6 | + Sys_commonNotice = 5, | |
7 | + Sys_maintainNotice = 6, | |
8 | + Sys_kickdown = 7, | |
9 | + Sys_runningHorse = 8, | |
10 | + | |
11 | + Gm_clientRequest = 20, | |
12 | + Gm_receiveResponse = 21, | |
13 | + | |
14 | + Role_notifyNewEvent = 100, | |
15 | + Role_queryLoginRpc = 101, | |
16 | + Role_createRpc = 102, | |
17 | + Role_loginRpc = 103, | |
18 | + Role_updateProperty = 105, | |
19 | + Role_updateProperties = 106, | |
20 | + Role_changeFormationRpc = 107, | |
21 | + Role_setCrownRpc = 108, | |
22 | + Role_formationPosRpc = 109, | |
23 | + Role_entrustRpc = 110, | |
24 | + Role_finishEntrustRpc = 111, | |
25 | + Role_updateStoryBook = 112, | |
26 | + Role_finishTalkRpc = 113, | |
27 | + Role_updateHeroBook = 114, | |
28 | + Role_guideRpc = 115, | |
29 | + Role_changeAutoSateRpc = 116, | |
30 | + Role_signRpc = 117, | |
31 | + Role_dailyTaskRpc = 118, | |
32 | + Role_mainTaskRpc = 119, | |
33 | + Role_missionRpc = 120, | |
34 | + Role_formationQuickRpc = 121, | |
35 | + Role_changeNameRpc = 122, | |
36 | + Role_changeHomeBgRpc = 123, | |
37 | + Role_changeHeadIconRpc = 124, | |
38 | + Role_changeHeadFrameRpc = 125, | |
39 | + Role_cookNotesRpc = 126, | |
40 | + Role_cookAddFavoritesRpc = 127, | |
41 | + Role_cookDelFavoritesRpc = 128, | |
42 | + Role_drawCodeRpc = 129, | |
43 | + Role_chat = 130, | |
44 | + Role_signGiftRpc = 131, | |
45 | + Role_changeToRealUserRpc = 132, | |
46 | + Role_cafeSignRpc = 133, | |
47 | + Role_syncTimeRpc = 134, | |
48 | + Role_getLevelRankRpc = 135, | |
49 | + Role_deletAccountRpc = 136, | |
50 | + Role_getItemRankRpc = 137, | |
51 | + | |
52 | + Hero_loadInfos = 201, | |
53 | + Hero_updateProperty = 202, | |
54 | + Hero_decomposeRpc = 203, | |
55 | + Hero_qualityRpc = 204, | |
56 | + Hero_strengthRpc = 205, | |
57 | + Hero_lockRpc = 206, | |
58 | + Hero_researchRpc = 207, | |
59 | + Hero_finishResearchRpc = 208, | |
60 | + Hero_treatRpc = 209, | |
61 | + Hero_finishTreatRpc = 210, | |
62 | + Hero_loveItemRpc = 211, | |
63 | + Hero_finishLoveTaskRpc = 212, | |
64 | + Hero_changeDressRpc = 213, | |
65 | + Hero_quickTreatRpc = 214, | |
66 | + Hero_skillUpRpc = 215, | |
67 | + Hero_changeNameRpc = 216, | |
68 | + Hero_likeHeroRpc = 217, | |
69 | + Hero_commentHeroRpc = 218, | |
70 | + Hero_getCommentsRpc = 219, | |
71 | + Hero_likeCommentRpc = 220, | |
72 | + | |
73 | + | |
74 | + | |
75 | + Item_updateProperty = 301, | |
76 | + | |
77 | + Store_produceRpc = 401, | |
78 | + Store_finishBuildRpc = 402, | |
79 | + Store_diamondBuyRpc = 403, | |
80 | + Store_itemBuyRpc = 404, | |
81 | + Store_rechargeRpc = 405, | |
82 | + Store_ayncPurchaseRpc = 406, | |
83 | + Store_purchaseOrderResult = 407, | |
84 | + Store_iapPurchaseRpc = 408, | |
85 | + Store_iapCancelPurchase = 409, | |
86 | + Store_restorePurchaseRpc = 410, | |
87 | + Store_googlePurchaseRpc = 411, | |
88 | + Store_googleCancelPurchase = 412, | |
89 | + Store_samsungPurchaseRpc = 413, | |
90 | + Store_samsungCancelPurchase = 414, | |
91 | + | |
92 | + Trade_getInfoRpc = 450, | |
93 | + Trade_sellRpc = 451, | |
94 | + Trade_buyRpc = 452, | |
95 | + Trade_giveUpRpc = 453, | |
96 | + Trade_cleanBuyRpc = 454, | |
97 | + | |
98 | + Carbon_arrangeCarbonRpc = 500, | |
99 | + Carbon_moveRpc = 501, | |
100 | + Carbon_actionEndRpc = 502, | |
101 | + Carbon_beginGameRpc = 503, | |
102 | + Carbon_endGameRpc = 504, | |
103 | + Carbon_cancelMoveRpc = 505, | |
104 | + Carbon_updateProperty = 506, | |
105 | + Carbon_endCarbonRpc = 507, | |
106 | + Carbon_exitTeamRpc = 508, | |
107 | + Carbon_supplyRpc = 509, | |
108 | + Map_updateProperty = 510, | |
109 | + Carbon_changePosRpc = 511, | |
110 | + Carbon_generateBoss = 512, | |
111 | + Carbon_givpUpBoss = 513, | |
112 | + Carbon_drawBossAward = 514, | |
113 | + Carbon_SelectRpc = 515, | |
114 | + | |
115 | + Friend_updateProperty = 550, | |
116 | + Friend_listRpc = 551, | |
117 | + Friend_searchRpc = 552, | |
118 | + Friend_randomRpc = 553, | |
119 | + Friend_deleteRpc = 554, | |
120 | + Friend_applyRpc = 555, | |
121 | + Friend_applyListRpc = 556, | |
122 | + Friend_handleApplyRpc = 557, | |
123 | + Friend_report = 558, | |
124 | + | |
125 | + Farm_updateProperty = 600, | |
126 | + Farm_drawMaterial = 601, | |
127 | + Farm_levelUpBuilding = 602, | |
128 | + Farm_changeShowMedal = 603, | |
129 | + Farm_plantRpc = 604, | |
130 | + Farm_useItemRpc = 605, | |
131 | + Farm_getPlantRpc = 606, | |
132 | + Farm_farmInfoRpc = 607, | |
133 | + Farm_changeFarmerRpc = 608, | |
134 | + | |
135 | + Tower_updateProperty = 630, | |
136 | + Tower_battleBeginRpc = 631, | |
137 | + Tower_battleEndRpc = 632, | |
138 | + Tower_resetRpc = 633, | |
139 | + Tower_cureRpc = 634, | |
140 | + Tower_changeFormatRpc = 635, | |
141 | + Tower_getRankRpc = 636, | |
142 | + Tower_formatQuickRpc = 637, | |
143 | + Tower_formationPosRpc = 638, | |
144 | + Tower_breakRpc = 639, | |
145 | + | |
146 | + Email_listRpc = 650, | |
147 | + Email_drawAttachRpc = 651, | |
148 | + Email_checkRpc = 652, | |
149 | + Email_delRpc = 653, | |
150 | + Email_drawAllAttachRpc = 654, | |
151 | + | |
152 | + Pvp_updateProperty = 670, | |
153 | + Pvp_battleBeginRpc = 671, | |
154 | + Pvp_battleEndRpc = 672, | |
155 | + Pvp_resetRpc = 673, | |
156 | + Pvp_enterPvpRpc = 674, | |
157 | + Pvp_getRankRpc = 675, | |
158 | + Pvp_changeFormatRpc = 676, | |
159 | + Pvp_formatQuickRpc = 677, | |
160 | + Pvp_formationPosRpc = 678, | |
161 | + Pvp_skillOrderRpc = 679, | |
162 | + Pvp_shopBuyRpc = 680, | |
163 | + | |
164 | + Equip_updateProperty = 750, | |
165 | + Equip_wearEquipRpc = 751, | |
166 | + Equip_repairEquipRpc = 752, | |
167 | + Equip_forgeEquipAttrsRpc = 753, | |
168 | + Equip_replaceEquipAttrRpc = 754, | |
169 | + Equip_buildEquipRpc = 755, | |
170 | + Equip_finishBuildEquipRpc = 756, | |
171 | + Equip_decomposEquipRpc = 757, | |
172 | + Equip_resetEquipRpc = 758, | |
173 | + Equip_upEquipRpc = 759, | |
174 | + | |
175 | + Activity_getRewardRpc = 801, | |
176 | + Activity_lotteryGiftRpc = 802, | |
177 | + Activity_shopBuyRpc = 803, | |
178 | + Activity_puzzleRewardRpc = 804, | |
179 | + Activity_inheritRewardRpc = 805, | |
180 | + Activity_midAutRewardRpc = 806, | |
181 | + Activity_clearRed = 807, | |
182 | + Activity_halloweenMoveRpc = 808, | |
183 | + Activity_halloweenResetRpc = 809, | |
184 | + Activity_halloweenRewardRpc = 810, | |
185 | + Activity_traditionalRewardRpc = 811, | |
186 | + Activity_africanRpc = 812, | |
187 | + Activity_oldBackRpc = 813, | |
188 | + Activity_orderRewardRpc = 814, | |
189 | + Activity_cookHeroRpc = 815, | |
190 | + | |
191 | + Moon_arrangeCarbonRpc = 850, | |
192 | + Moon_moveRpc = 851, | |
193 | + Moon_actionEndRpc = 852, | |
194 | + Moon_beginGameRpc = 853, | |
195 | + Moon_endGameRpc = 854, | |
196 | + Moon_cancelMoveRpc = 855, | |
197 | + Moon_updateProperty = 856, | |
198 | + Moon_endCarbonRpc = 857, | |
199 | + Moon_exitTeamRpc = 858, | |
200 | + Moon_supplyRpc = 859, | |
201 | + Moon_changePosRpc = 860, | |
202 | + Moon_generateBoss = 861, | |
203 | + Moon_givpUpBoss = 862, | |
204 | + Moon_drawBossAward = 863, | |
205 | + Moon_SelectRpc = 864, | |
206 | + | |
207 | + Paradise_arrangeCarbonRpc = 875, | |
208 | + Paradise_moveRpc = 876, | |
209 | + Paradise_actionEndRpc = 877, | |
210 | + Paradise_beginGameRpc = 878, | |
211 | + Paradise_endGameRpc = 879, | |
212 | + Paradise_cancelMoveRpc = 880, | |
213 | + Paradise_updateProperty = 881, | |
214 | + Paradise_endCarbonRpc = 882, | |
215 | + Paradise_exitTeamRpc = 883, | |
216 | + Paradise_supplyRpc = 884, | |
217 | + Paradise_changePosRpc = 885, | |
218 | + Paradise_SelectRpc = 886, | |
219 | + Paradise_buyCountRpc = 887, | |
220 | + | |
221 | + Diner_loadRpc = 900, | |
222 | + Diner_updateProperty = 901, | |
223 | + Diner_updateProperties = 902, | |
224 | + Diner_accessoryUpdateProperty = 903, | |
225 | + Diner_itemUpdateProperty = 904, | |
226 | + Diner_wearAccessoryRpc = 905, | |
227 | + Diner_upLevelAccessoryRpc = 906, | |
228 | + Diner_supplyMaterialRpc = 907, | |
229 | + Diner_changeCarRpc = 908, | |
230 | + Diner_recycleAccessoryRpc = 909, | |
231 | + Diner_heroToBoxRpc = 910, | |
232 | + Diner_saveBoxFitmentRpc = 911, | |
233 | + Diner_recycleFitmentsRpc = 912, | |
234 | + Diner_sellRpc = 913, | |
235 | + Diner_finishSellRpc = 914, | |
236 | + Diner_shopBuyRpc = 915, | |
237 | + Diner_drawRewardRpc = 916, | |
238 | + Diner_talentUpdateProperty = 917, | |
239 | + | |
240 | + Diner_likeOtherBoxRpc = 918, | |
241 | + Diner_comfortRankRpc = 919, | |
242 | + Diner_nearLikeMeRpc = 920, | |
243 | + Diner_friendListRpc = 921, | |
244 | + Diner_getBoxDataRpc = 922, | |
245 | + Diner_changeTalkRpc = 923, | |
246 | + Diner_changeTalkBgRpc = 924, | |
247 | +} | |
248 | + | |
249 | +rpcResponseBegin = 10000 | |
250 | + | |
251 | +actionHandlers = {} | |
252 | +for key, value in pairs(actionCodes) do | |
253 | + local suffix = string.sub(key, -3, -1) | |
254 | + local handlerName = string.gsub(key, "_", ".") | |
255 | + | |
256 | + if suffix == "Rpc" then | |
257 | + actionHandlers[value + rpcResponseBegin] = handlerName .. "Response" | |
258 | + end | |
259 | + actionHandlers[value] = string.gsub(key, "_", ".") | |
260 | +end | |
0 | 261 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +-- role | |
2 | +R_FARM_KEY = "role:%d:farm" | |
3 | +R_TOWER_KEY = "role:%d:tower" | |
4 | +R_COOKLOG_KEY = "role:%d:cooklog" | |
5 | +R_TRADELOG_KEY = "role:%d:tradelog" | |
6 | +R_PVP_KEY = "role:%d:pvp" | |
7 | +R_DINER_KEY = "role:%d:diner" | |
8 | +-- rank | |
9 | +RANK_PVP = "rank:pvp" | |
10 | +RANK_TRADE = "rank:trade" | |
11 | +RANK_TOWER = "rank:tower" | |
12 | +RANK_BOX = "rank:box" -- 盒子舒适度排行榜 | |
13 | +MAP_LIKE = "map:box:like" --点赞个数 | |
14 | +RANK_LEVEL = "rank:level" --等级排名 | |
15 | +RANK_ITEM = "rank:item" --活动物品排行 | |
16 | +-- 日志 | |
17 | +NOTE_COOK_KEY = "note:cook:%d" | |
18 | + | |
19 | +TRADE_KEY = "trade:%d" | |
20 | +TRADE_ID_KEY = "tradeIDs" | |
21 | + | |
22 | +TASK_ACTIVE = "task:%d:active" -- 记录激活的任务 | |
23 | +TASK_FINISH = "task:%d:finish" -- 记录完成的任务 | |
24 | + | |
25 | +BOSS_SET = "boss:%d:%d" | |
26 | +BOSS_INFO = "boss:battle" | |
27 | + | |
28 | +FRIEND_KEY = "role:%d:friend" --哈希表 | |
29 | +FRIEND_APPLY_KEY = "role:%d:apply" -- set | |
30 | +FRIEND_DINER_LIKE_KEY = "role:%d:diner:like" -- list | |
31 | + | |
32 | +UNION_SET = "global:union" | |
33 | +UNION_KEY = "union:%d" | |
34 | +UNION_ROLE = "union:%d:%d" | |
35 | +UNION_ROLE_SET = "union:%d:roleList" | |
36 | +UNION_APPLY_SET = "union:%d:applyList" | |
37 | + | |
38 | +PVP_KING = "pvp:king" --王者传奇组 | |
39 | +PVP_HONOR = "pvp:honor" --荣耀咸鱼组 | |
40 | +PVP_NOOB = "pvp:noob" --菜鸟组 | |
41 | +PVP_INFO = "pvp:info" --玩家信息组 | |
0 | 42 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,18 @@ |
1 | +local _M = {} | |
2 | +local redisproxy = redisproxy | |
3 | +function _M.clientRequest(agent, data) | |
4 | + local msg = MsgPack.unpack(data) | |
5 | + local role = agent.role | |
6 | + local action = _M[msg.cmd] | |
7 | + local bin = MsgPack.pack({ cmd = "指令失败" }) | |
8 | + if not action then | |
9 | + SendPacket(actionCodes.Gm_receiveResponse, bin) | |
10 | + return true | |
11 | + end | |
12 | + local ret = action(role, msg) | |
13 | + bin = MsgPack.pack({ cmd = ret }) | |
14 | + SendPacket(actionCodes.Gm_receiveResponse, bin) | |
15 | + return true | |
16 | +end | |
17 | + | |
18 | +return _M | |
0 | 19 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,65 @@ |
1 | +local string_format = string.format | |
2 | +local mcast_util = mcast_util | |
3 | +local gmFuncs = require "actions.GmAction" | |
4 | + | |
5 | +local function proc_online(cmd, roleId, pms) | |
6 | + local agent = datacenter.get("agent", roleId) | |
7 | + if agent then | |
8 | + local ok, result = pcall(skynet.call, agent.serv, "lua", cmd, pms) | |
9 | + return ok and result or "指令在线失败" | |
10 | + end | |
11 | + return "not_online" | |
12 | +end | |
13 | + | |
14 | +local _M = {} | |
15 | +local __const = {["id"]=1,["pm1"]=1,["pm2"]=1,["pm3"]=1} | |
16 | + | |
17 | +local _T = setmetatable({}, {__index = function(_, k) | |
18 | + return function (pms) | |
19 | + for k, v in pairs(pms) do | |
20 | + -- tonum(v, v) 能tonumber 则 转换,不能则返回原来的值 | |
21 | + pms[k] = __const[k] and tonum(v, v) or v | |
22 | + end | |
23 | + -- 群体操作 | |
24 | + if not pms.id or pms.id == 0 then | |
25 | + return _M[k] and _M[k](pms) or "指令不存在" | |
26 | + end | |
27 | + -- 个体操作 | |
28 | + if not gmFuncs[k] then return "指令不存在" end | |
29 | + -- 在线操作 | |
30 | + local isOn = proc_online(k, pms.id, pms) | |
31 | + if isOn ~= "not_online" then | |
32 | + return isOn | |
33 | + end | |
34 | + -- 如果_M有直接操作,跳过load角色 | |
35 | + if _M[k] then return _M[k](pms) end | |
36 | + -- 离线操作 | |
37 | + local role = require("models.Role").new({key = string_format("role:%d", pms.id)}) | |
38 | + local ret = role:load() | |
39 | + if not ret then | |
40 | + return "角色不存在" | |
41 | + end | |
42 | + role:loadAll() | |
43 | + return gmFuncs[k](role, pms) | |
44 | + end | |
45 | +end}) | |
46 | + | |
47 | +-- 在线广播 | |
48 | +function _M.broadcast(pms) | |
49 | + local bin = MsgPack.pack({body = pms.pm2}) | |
50 | + local codes = { | |
51 | + ["common"] = actionCodes.Sys_commonNotice, | |
52 | + ["maintain"] = actionCodes.Sys_maintainNotice, | |
53 | + } | |
54 | + if not codes[pms.pm1] then return "错误" end | |
55 | + | |
56 | + mcast_util.pub_world(codes[pms.pm1], bin) | |
57 | + return "广播成功" | |
58 | +end | |
59 | + | |
60 | +function _M.online(pms) | |
61 | + local count = datacenter.get("onlineCount") or 0 | |
62 | + return count | |
63 | +end | |
64 | + | |
65 | +return _T | |
0 | 66 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,319 @@ |
1 | +local ipairs = ipairs | |
2 | +local table = table | |
3 | +local math = math | |
4 | +local next = next | |
5 | +local string = string | |
6 | +local redisproxy = redisproxy | |
7 | +local MsgPack = MsgPack | |
8 | +local getRandomName = getRandomName | |
9 | +local mcast_util = mcast_util | |
10 | +local string_format = string.format | |
11 | +local tonumber = tonumber | |
12 | +local require = require | |
13 | +local table_insert = table.insert | |
14 | +local tconcat = table.concat | |
15 | +local httpc = require("http.httpc") | |
16 | + | |
17 | +local WAVE_HERO_NUMS = 150 | |
18 | +local WAVE_EQUIP_NUMS = 150 | |
19 | + | |
20 | +local function validName(name) | |
21 | + name = string.upper(name) | |
22 | + local exist = redisproxy:exists(string_format("user:%s", name)) | |
23 | + if exist then return "existed" end | |
24 | + | |
25 | + local SERV = string_format("NAMED%d", math.random(1, 5)) | |
26 | + local legal = skynet.call(SERV, "lua", "check", name) | |
27 | + return legal and "ok" or "illegal" | |
28 | +end | |
29 | + | |
30 | +-- 随机玩家名 | |
31 | +local function randomRoleName() | |
32 | + -- 过滤已经存在的名字 | |
33 | + local name | |
34 | + repeat | |
35 | + name = getRandomName() | |
36 | + until validName(name) == "ok" | |
37 | + return name | |
38 | +end | |
39 | + | |
40 | +local function setRoleName(uid, roleId) | |
41 | + local result | |
42 | + local name | |
43 | + local dbName | |
44 | + repeat | |
45 | + name = randomRoleName() | |
46 | + dbName = string.upper(name) | |
47 | + result = redisproxy:setnx(string_format("user:%s", dbName), roleId) | |
48 | + until result == 1 | |
49 | + redisproxy:set(string_format("uid:%s", uid), dbName) | |
50 | + return name | |
51 | +end | |
52 | + | |
53 | +local _M = {} | |
54 | +function _M.loginRpc( agent, data ) | |
55 | + local msg = MsgPack.unpack(data) | |
56 | + local response = {} | |
57 | + | |
58 | + if msg.version ~= globalCsv.version then | |
59 | + response.result = "UPDATE_TIP" | |
60 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
61 | + return true | |
62 | + end | |
63 | + | |
64 | + -- 1. | |
65 | + local roleId = redisproxy:get(string_format("user:%s", string.upper(msg.name))) | |
66 | + if not roleId then | |
67 | + response.result = "NOT_EXIST" | |
68 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
69 | + return true | |
70 | + end | |
71 | + | |
72 | + roleId = tonumber(roleId) | |
73 | + | |
74 | + --维护不能登录 | |
75 | + local maintain = tonumber(redisproxy:hget("autoincrement_set", "maintain")) | |
76 | + if maintain and maintain > 0 then | |
77 | + if tonumber(redisproxy:hget(string_format("role:%d", roleId), "ignoreMaintain")) ~= 1 then | |
78 | + response.result = "MAINTAIN_TIP" | |
79 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
80 | + return true | |
81 | + end | |
82 | + end | |
83 | + | |
84 | + local now = skynet.timex() | |
85 | + local role = agent.role | |
86 | + -- 2 | |
87 | + if not role then | |
88 | + local roleKey = string_format("role:%d", roleId) | |
89 | + if not redisproxy:exists(roleKey) then | |
90 | + response.result = "DB_ERROR" | |
91 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
92 | + return true | |
93 | + end | |
94 | + -- 2a | |
95 | + role = require("models.Role").new({key = roleKey}) | |
96 | + role:load() | |
97 | + role:loadAll() | |
98 | + else | |
99 | + role:reloadWhenLogin() | |
100 | + end | |
101 | + | |
102 | + if not msg.isGMlogin then | |
103 | + local banTime = role:getProperty("banTime") | |
104 | + if banTime > now then | |
105 | + response.result = "BAN_TIP" | |
106 | + response.banTime = banTime | |
107 | + response.banType = role:getProperty("banType") | |
108 | + response.roleId = roleId | |
109 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
110 | + return true | |
111 | + end | |
112 | + if banTime ~= 0 then | |
113 | + -- 清除封号状态 | |
114 | + role:setBan(0) | |
115 | + end | |
116 | + end | |
117 | + SERV_OPEN = redisproxy:hget("autoincrement_set", "server_start") | |
118 | + local lastLoginTime = role:getProperty("lastLoginTime") | |
119 | + | |
120 | + -- 跨天登陆事件 | |
121 | + if not role:onCrossDay(now) then | |
122 | + role:checkActivityStatus(lastLoginTime, now) | |
123 | + if role:getProperty("carbonDouble") == "" then | |
124 | + role:initCarbonDouble() | |
125 | + end | |
126 | + end | |
127 | + -- 登陆回复 | |
128 | + role:onRecoverLogin(now) | |
129 | + -- 引导是否连续 | |
130 | + role:checkGuide() | |
131 | + | |
132 | + | |
133 | + role:setProperty("lastLoginTime", now) | |
134 | + if msg.device and type(msg.device) == "string" then | |
135 | + role:setProperty("device", msg.device) | |
136 | + end | |
137 | + | |
138 | + response.role = role:data() | |
139 | + response.result = "SUCCESS" | |
140 | + response.serverTime = now | |
141 | + | |
142 | + -- 需要加载模块数据 | |
143 | + local modules = { | |
144 | + "carbons","maps", | |
145 | + } | |
146 | + | |
147 | + local heroIds = {} | |
148 | + for heroId, _ in pairs(role.heros) do | |
149 | + table.insert(heroIds, heroId) | |
150 | + end | |
151 | + local heroWave = math.ceil(#heroIds / WAVE_HERO_NUMS) | |
152 | + | |
153 | + local equipIds = {} | |
154 | + for equipId, _ in pairs(role.equips) do | |
155 | + table.insert(equipIds, equipId) | |
156 | + end | |
157 | + local equipWave = math.ceil(#equipIds / WAVE_EQUIP_NUMS) | |
158 | + | |
159 | + if #heroIds <= 50 then | |
160 | + heroWave = 0 | |
161 | + table_insert(modules, "heros") | |
162 | + end | |
163 | + if #equipIds <= 50 then | |
164 | + equipWave = 0 | |
165 | + table_insert(modules, "equips") | |
166 | + end | |
167 | + | |
168 | + for _, name in ipairs(modules) do | |
169 | + response[name] = {} | |
170 | + for id, unit in pairs(role[name]) do | |
171 | + response[name][id] = unit:data() | |
172 | + end | |
173 | + end | |
174 | + response.wave = 1 + heroWave + equipWave | |
175 | + | |
176 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(response)) | |
177 | + | |
178 | + local heroIndex = 1 | |
179 | + for index = 2, 1 + heroWave do | |
180 | + local heroResponse = {heros = {}} | |
181 | + for i = heroIndex, heroIndex + WAVE_HERO_NUMS do | |
182 | + local heroId = heroIds[i] | |
183 | + if not heroId then | |
184 | + break | |
185 | + end | |
186 | + local hero = role.heros[heroId] | |
187 | + table_insert(heroResponse.heros, hero:data()) | |
188 | + heroIndex = heroIndex + 1 | |
189 | + end | |
190 | + heroResponse.heroWave = index | |
191 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(heroResponse)) | |
192 | + end | |
193 | + local equipIndex = 1 | |
194 | + for index = 2 + heroWave, 1 + heroWave + equipWave do | |
195 | + local equipResponse = {equips = {}} | |
196 | + for i = equipIndex, equipIndex + WAVE_EQUIP_NUMS do | |
197 | + local equipId = equipIds[i] | |
198 | + if not equipId then | |
199 | + break | |
200 | + end | |
201 | + local equip = role.equips[equipId] | |
202 | + table_insert(equipResponse.equips, equip:data()) | |
203 | + equipIndex = equipIndex + 1 | |
204 | + end | |
205 | + equipResponse.equipWave = index | |
206 | + SendPacket(actionCodes.Role_loginRpc, MsgPack.pack(equipResponse)) | |
207 | + end | |
208 | + | |
209 | + -- role:log("login", { ip = agent.ip, diamond = role:getProperty("diamond"), reDiamond = role:getProperty("reDiamond")}) | |
210 | + | |
211 | + datacenter.set("agent", roleId, { | |
212 | + serv = skynet.self(), | |
213 | + fd = agent.client_fd, | |
214 | + gate_serv = agent.gate_serv, | |
215 | + }) | |
216 | + agent.role = role | |
217 | + role.lessmsg = false | |
218 | + | |
219 | + start_agent_timer() | |
220 | + -- 注册全服广播 | |
221 | + local channel = math.randomInt(1, 1) | |
222 | + local w_channel = datacenter.get( ("MC_W_CHANNEL" .. channel) ) | |
223 | + if w_channel then | |
224 | + mcast_util.sub_world(w_channel) | |
225 | + end | |
226 | + return true | |
227 | +end | |
228 | + | |
229 | +function _M.createRpc(agent, data) | |
230 | + local msg = MsgPack.unpack(data) | |
231 | + local response = {} | |
232 | + | |
233 | + -- 再次检查uid | |
234 | + local uid = tostring(msg.uid) | |
235 | + local user = redisproxy:get(string_format("uid:%s", uid)) | |
236 | + if user then | |
237 | + response.result = "SUCCESS" | |
238 | + response.roleName = user | |
239 | + SendPacket(actionCodes.Role_createRpc, MsgPack.pack(response)) | |
240 | + return true | |
241 | + end | |
242 | + | |
243 | + local roleId = getNextRoleId() | |
244 | + if not roleId then | |
245 | + response.result = "DB_FULL" | |
246 | + SendPacket(actionCodes.Role_createRpc, MsgPack.pack(response)) | |
247 | + return true | |
248 | + end | |
249 | + local roleName = setRoleName(msg.uid, roleId) | |
250 | + | |
251 | + local newRole = require("models.Role").new({ | |
252 | + key = string_format("role:%d", roleId), | |
253 | + id = roleId, | |
254 | + uid = tostring(msg.uid), | |
255 | + subId = msg.subId or 0, | |
256 | + name = roleName, | |
257 | + uname = msg.uname or "", | |
258 | + device = tostring(msg.device) | |
259 | + }) | |
260 | + | |
261 | + if newRole:create() then | |
262 | + --更新USER表 | |
263 | + response.result = "SUCCESS" | |
264 | + response.roleId = roleId | |
265 | + response.roleName = string.upper(roleName) | |
266 | + else | |
267 | + response.result = "DB_ERROR" | |
268 | + SendPacket(actionCodes.Role_createRpc, MsgPack.pack(response)) | |
269 | + return true | |
270 | + end | |
271 | + | |
272 | + -- 给角色自动加载当前副本数据 | |
273 | + newRole:addCarbon({carbonId = 10101, status = 0, starNum = 0}) | |
274 | + -- 给角色自动加载农场信息 | |
275 | + newRole:addFarm() | |
276 | + -- 给角色自动加载爬塔信息 | |
277 | + newRole:addTower() | |
278 | + -- 给角色增加pvpInfo | |
279 | + newRole:addPvpInfo() | |
280 | + --给新角色增加diner | |
281 | + newRole:addDiner() | |
282 | + -- 关闭 锁定新食灵 | |
283 | + local autoStatus = newRole:getProperty("autoStatus") | |
284 | + newRole:setProperty("autoStatus", autoStatus:setv(3,1)) | |
285 | + | |
286 | + for index, hero in ipairs(globalCsv["birthHero"]) do | |
287 | + hero.notNotify = true | |
288 | + hero.desc = "birth_award" | |
289 | + local heroId = newRole:awardHero(hero.type, hero) | |
290 | + if index == 1 then | |
291 | + local newHero = newRole.heros[heroId] | |
292 | + newHero:setProperty("lock", 1) | |
293 | + newHero:setProperty("formation", 1) | |
294 | + newRole:setProperty("crown", heroId) | |
295 | + newRole:setProperty("formationJson", json.encode({["1"] = {list = {["1"] = heroId}, pos = {["1"] = heroId}, lock = {}}})) | |
296 | + end | |
297 | + end | |
298 | + -- 出生道具 | |
299 | + local ucode = getActionCode(newRole) | |
300 | + for id, num in pairs(globalCsv["birthItem"]:toNumMap()) do | |
301 | + newRole:awardItemCsv(id, {count = num, desc = "birth_award", notNotify = true, ucode = ucode}) | |
302 | + end | |
303 | + | |
304 | + -- 欢迎邮件 | |
305 | + -- redisproxy:insertEmail({roleId = roleId, emailId = 1}) | |
306 | + -- redisproxy:insertEmail({roleId = roleId, emailId = 2}) | |
307 | + | |
308 | + newRole:log("create", { ip = agent.ip, ucode = ucode}) | |
309 | + | |
310 | + SendPacket(actionCodes.Role_createRpc, MsgPack.pack(response)) | |
311 | + return true | |
312 | +end | |
313 | + | |
314 | +function _M.syncTimeRpc(agent, data) | |
315 | + SendPacket(actionCodes.Role_syncTimeRpc, MsgPack.pack({nowTime = skynet.timex()})) | |
316 | + return true | |
317 | +end | |
318 | + | |
319 | +return _M | |
0 | 320 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,286 @@ |
1 | +require "ProtocolCode" | |
2 | +require "shared.init" | |
3 | +require "utils.init" | |
4 | +require "GlobalVar" | |
5 | +require "RedisKeys" | |
6 | +require "skynet.manager" | |
7 | + | |
8 | +local harbor = require "skynet.harbor" | |
9 | +local queue = require "skynet.queue" | |
10 | +local netpack = require "skynet.netpack" | |
11 | +local socket = require "skynet.socket" | |
12 | +local sharedata = require "skynet.sharedata" | |
13 | +local xxtea = require "xxtea" | |
14 | + | |
15 | +skynet = require "skynet" | |
16 | +redisproxy = require "shared.redisproxy" | |
17 | +datacenter = require "skynet.datacenter" | |
18 | +mcast_util = require "services/mcast_util" | |
19 | +globalCsv = require "csvdata/GlobalDefine" | |
20 | + | |
21 | +local CMD = {} | |
22 | +local agentInfo = {} -- { client_fd, role, gate_serv, open_timer} | |
23 | + | |
24 | +local agent_util, cs | |
25 | + | |
26 | +--- {{{ 定时器相关 | |
27 | +local function handle_timeout() | |
28 | + if not agentInfo.open_timer then return end | |
29 | + | |
30 | + if not agentInfo.role then | |
31 | + skynet.timeout(100, handle_timeout) | |
32 | + return | |
33 | + end | |
34 | + | |
35 | + agent_util:update(agentInfo) | |
36 | + skynet.timeout(100, handle_timeout) | |
37 | +end | |
38 | + | |
39 | +function start_agent_timer() | |
40 | + agentInfo.open_timer = true | |
41 | + skynet.timeout(150, handle_timeout) | |
42 | +end | |
43 | + | |
44 | +function cancel_agent_timer() | |
45 | + agentInfo.open_timer = false | |
46 | +end | |
47 | +---- 定时器相关 }}} | |
48 | + | |
49 | +function SendPacket(actionCode, bin, client_fd) | |
50 | + if #bin > 0 then bin = xxtea.encrypt(bin, XXTEA_KEY) end | |
51 | + | |
52 | + local handlerName = actionHandlers[actionCode] | |
53 | + if string.sub(handlerName, -3, -1) == "Rpc" then | |
54 | + actionCode = actionCode + rpcResponseBegin | |
55 | + end | |
56 | + | |
57 | + local client_fd = client_fd or agentInfo.client_fd | |
58 | + local head = string.pack("H", actionCode) | |
59 | + return socket.write(client_fd, netpack.pack(head .. bin)) | |
60 | +end | |
61 | + | |
62 | +function rpcAgent(roleId, funcName, ...) | |
63 | + local agent = datacenter.get("agent", roleId) | |
64 | + if agent then | |
65 | + return skynet.call(agent.serv, "lua", funcName, ...) | |
66 | + end | |
67 | +end | |
68 | + | |
69 | +function rpcParter(serv, func, ...) | |
70 | + if serv then | |
71 | + local ok, result = pcall(skynet.call, serv, "role", func, ...) | |
72 | + if ok then | |
73 | + return result | |
74 | + end | |
75 | + end | |
76 | +end | |
77 | + | |
78 | +local string_format = string.format | |
79 | +local table_unpack = table.unpack | |
80 | +function rpcRole(roleId, funcName, ...) | |
81 | + local fields = ... | |
82 | + local agent = datacenter.get("agent", roleId) | |
83 | + if agent and agent.serv then | |
84 | + if funcName == "getProperties" then | |
85 | + return true, skynet.call(agent.serv, "role", funcName, fields) | |
86 | + else | |
87 | + return true, skynet.call(agent.serv, "role", funcName, ...) | |
88 | + end | |
89 | + else | |
90 | + local rediskey = string_format("role:%d", roleId) | |
91 | + if funcName == "setProperty" then | |
92 | + return false, redisproxy:hset(rediskey, ...) | |
93 | + elseif funcName == "getProperty" then | |
94 | + return false, redisproxy:hget(rediskey, ...) | |
95 | + elseif funcName == "getProperties" then | |
96 | + local sRole = require("models.Role") | |
97 | + local returnValue = redisproxy:hmget(rediskey, table_unpack(...)) | |
98 | + local ret = {} | |
99 | + for index, key in ipairs(fields) do | |
100 | + local typ = sRole.schema[key][1] | |
101 | + local def = sRole.schema[key][2] | |
102 | + if typ == "number" then | |
103 | + ret[key] = tonumber(returnValue[index] or def) | |
104 | + else | |
105 | + ret[key] = returnValue[index] | |
106 | + end | |
107 | + end | |
108 | + return false, ret | |
109 | + elseif funcName == "setProperties" then | |
110 | + local result = {} | |
111 | + for k,v in pairs(fields) do | |
112 | + result[#result+1] = k | |
113 | + result[#result+1] = v | |
114 | + end | |
115 | + return false, redisproxy:hmset(rediskey, table_unpack(result)) | |
116 | + end | |
117 | + end | |
118 | +end | |
119 | + | |
120 | +function rpcUnion(funcName, ...) | |
121 | + local serv = agentInfo.userv | |
122 | + if not serv then | |
123 | + local consortiaId = agentInfo.role:getProperty("consortiaId") | |
124 | + if consortiaId == 0 then return true,1000 end | |
125 | + local union = datacenter.get("union", consortiaId) | |
126 | + if not union or not union.serv then | |
127 | + skynet.error("rpcUnion error: union serv addr not exist", funcName, consortiaId) | |
128 | + return | |
129 | + end | |
130 | + serv = union.serv | |
131 | + end | |
132 | + return skynet.call(serv, "lua", funcName, ...) | |
133 | +end | |
134 | + | |
135 | +function rpcOtherUnion(id, funcName, ...) | |
136 | + local union = datacenter.get("union", id) | |
137 | + if union and union.serv then | |
138 | + return skynet.call(union.serv, "lua", funcName, ...) | |
139 | + end | |
140 | +end | |
141 | + | |
142 | +skynet.register_protocol { | |
143 | + name = "client", | |
144 | + id = skynet.PTYPE_CLIENT, | |
145 | + unpack = function (msg, sz) | |
146 | + local data = skynet.tostring(msg, sz) | |
147 | + local cmd = string.unpack("H", string.sub(data, 1, 2)) | |
148 | + return cmd, string.sub(data, 3) | |
149 | + end, | |
150 | + dispatch = function(session, address, cmd, data) | |
151 | + cs(function() | |
152 | + if cmd == actionCodes.Sys_heartBeat then | |
153 | + agent_util:heart_beat(agentInfo) | |
154 | + return | |
155 | + end | |
156 | + local actionName = actionHandlers[cmd] | |
157 | + if not actionName then | |
158 | + print("actionName not exist", actionName) | |
159 | + return | |
160 | + end | |
161 | + local modName, funcName = actionName:match("(%w+)%.(%w+)") | |
162 | + | |
163 | + local ok, action = pcall(require, "actions." .. modName .. "Action") | |
164 | + if not ok then | |
165 | + print("require module name error", action, modName) | |
166 | + return | |
167 | + end | |
168 | + | |
169 | + local method = action[funcName] | |
170 | + | |
171 | + if type(method) ~= "function" then | |
172 | + print("ERROR_SERVER_INVALID_ACTION", modName, funcName) | |
173 | + return | |
174 | + end | |
175 | + | |
176 | + if #data > 0 then data = xxtea.decrypt(data, XXTEA_KEY) end | |
177 | + local result = method(agentInfo, data) | |
178 | + if not result then | |
179 | + SendPacket(actionCodes.Sys_innerErrorMsg, MsgPack.pack({id = cmd})) | |
180 | + end | |
181 | + end) | |
182 | + end | |
183 | +} | |
184 | + | |
185 | +skynet.register_protocol { | |
186 | + name = "role", | |
187 | + id = 12, | |
188 | + pack = skynet.pack, | |
189 | + unpack = skynet.unpack, | |
190 | + dispatch = function(session, address, submethod, ...) | |
191 | + local result | |
192 | + if not agentInfo.role then | |
193 | + result = "__OFFLINE__" | |
194 | + else | |
195 | + result = agentInfo.role[submethod](agentInfo.role, ...) | |
196 | + end | |
197 | + | |
198 | + skynet.ret(skynet.pack(result)) | |
199 | + end, | |
200 | +} | |
201 | + | |
202 | +-- function CMD.start(gate, fd, ip) | |
203 | +function CMD.start(session, source, gate, fd, ip) | |
204 | + ignoreHeartbeat = false | |
205 | + | |
206 | + agentInfo.client_fd = fd | |
207 | + agentInfo.gate_serv = gate | |
208 | + agentInfo.ip = ip | |
209 | + | |
210 | + agent_util:reset() | |
211 | + math.randomInit() | |
212 | + | |
213 | + -- 这里将消息伪装成 watchdog 发出,这样就由 A->B->C->B->A 变成 A->B->C->A | |
214 | + skynet.redirect(gate, source, "lua", session, skynet.pack("forward", fd, 0, skynet.self())) | |
215 | +end | |
216 | + | |
217 | +function CMD.close() | |
218 | + cancel_agent_timer() | |
219 | + mcast_util.usub_world() | |
220 | + mcast_util.usub_union() | |
221 | + | |
222 | + local role = agentInfo.role | |
223 | + if not role then return end | |
224 | + role:log("logout", {online = skynet.timex()-role:getProperty("lastLoginTime")}) | |
225 | + role:onOfflineEvent() | |
226 | +end | |
227 | + | |
228 | +function CMD.exit() | |
229 | + if agentInfo.role then | |
230 | + -- role:log("logout", {online = skynet.timex()-role:getProperty("lastLoginTime")}) | |
231 | + datacenter.set("agent", agentInfo.role:getProperty("id"), nil) | |
232 | + end | |
233 | + skynet.exit() | |
234 | +end | |
235 | + | |
236 | +function CMD.subUnion(consortiaId, union) | |
237 | + mcast_util.sub_union(consortiaId, union.chan) | |
238 | + agentInfo.userv = union.serv | |
239 | +end | |
240 | + | |
241 | +function CMD:usubUnion() | |
242 | + mcast_util.usub_union() | |
243 | + agentInfo.userv = nil | |
244 | +end | |
245 | + | |
246 | +local function routeGM(cmd, params) | |
247 | + if type(params) ~= "table" or not agentInfo.role then | |
248 | + return "指令失败" | |
249 | + end | |
250 | + local _M = require "actions.GmAction" | |
251 | + return _M[cmd](agentInfo.role, params) | |
252 | +end | |
253 | + | |
254 | +skynet.start(function() | |
255 | + skynet.dispatch("lua", function(session, source, command, ...) | |
256 | + local f = CMD[command] | |
257 | + if f then | |
258 | + if command == "exit" then | |
259 | + f(...) | |
260 | + elseif command == "start" then | |
261 | + f(session, source, ...) | |
262 | + else | |
263 | + skynet.ret(skynet.pack(f(...))) | |
264 | + end | |
265 | + else | |
266 | + skynet.ret(skynet.pack(routeGM(command, ...))) | |
267 | + end | |
268 | + end) | |
269 | + | |
270 | + redisd = harbor.queryname("REDIS") | |
271 | + if tonumber(skynet.getenv "logd") == 1 then | |
272 | + logd = harbor.queryname("LOGD") | |
273 | + end | |
274 | + | |
275 | + cs = queue() | |
276 | + | |
277 | + -- csv | |
278 | + csvdb = sharedata.query("csvdata") | |
279 | + -- 错误码特殊处理 | |
280 | + -- todo | |
281 | + -- for key, value in pairs(csvdb["sys_codesCsv"]) do | |
282 | + -- _G[string.upper(value.varname)] = key | |
283 | + -- end | |
284 | + | |
285 | + agent_util = require "services/agent_util" | |
286 | +end) | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +root = "./" | |
2 | +thread = 8 | |
3 | +logger = "server.log" | |
4 | +harbor = 0 | |
5 | +start = "main" -- main script | |
6 | +bootstrap = "snlua bootstrap" -- The service for bootstrap | |
7 | +logd = 0 -- 是否开启日志 | |
8 | +servId = 1 | |
9 | +baseId = 0 | |
10 | +codeurl = "127.0.0.1:8686" | |
11 | + | |
12 | +lua_path = root .."skynet/lualib/?.lua;"..root.."src/?.lua;"..root.."tools/?.lua" | |
13 | +luaservice = root.."skynet/service/?.lua;"..root.."src/?.lua" | |
14 | +lualoader = "skynet/lualib/loader.lua" | |
15 | +preload = "./src/preload.lua" -- run preload.lua before every lua service run | |
16 | +cpath = root.."skynet/cservice/?.so" | |
17 | +lua_cpath = "skynet/luaclib/?.so" | |
0 | 18 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | +Subproject commit 3aabf5c43c36e6da7f63f237301b4772f0129e88 | ... | ... |
... | ... | @@ -0,0 +1,29 @@ |
1 | +local skynet = require "skynet" | |
2 | + | |
3 | +local max_client = 64 | |
4 | + | |
5 | +skynet.start(function() | |
6 | + print("Server start") | |
7 | + skynet.newservice("console") | |
8 | + skynet.newservice("debug_console", 3001) | |
9 | + | |
10 | + local ngxd = skynet.newservice("services/ngxd", 3002) | |
11 | + local watchdog = skynet.newservice("services/watchdog", max_client) | |
12 | + skynet.call(watchdog, "lua", "start", { | |
13 | + port = 3003, | |
14 | + maxclient = max_client, | |
15 | + ngxd = ngxd, | |
16 | + | |
17 | + redishost = "127.0.0.1", | |
18 | + redisport = 6379, | |
19 | + redisdb = 4, | |
20 | + auth = nil, | |
21 | + | |
22 | + mongohost = "127.0.0.1", | |
23 | + mongoport = nil, | |
24 | + mongouser = nil, | |
25 | + mongopswd = nil, | |
26 | + }) | |
27 | + | |
28 | + skynet.exit() | |
29 | +end) | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +local Role = class("Role", require("shared.ModelBase")) | |
2 | + | |
3 | +function Role:ctor( properties ) | |
4 | + Role.super.ctor(self, properties) | |
5 | + | |
6 | + self.heros = {} | |
7 | + self.ignoreHeartbeat = false | |
8 | + | |
9 | +end | |
10 | + | |
11 | +Role.schema = { | |
12 | + key = {"string"}, | |
13 | + id = {"number"}, | |
14 | + uid = {"string", ""}, | |
15 | + sid = {"number", 0}, | |
16 | + name = {"string", ""}, | |
17 | +} | |
18 | + | |
19 | +Role.fields = { | |
20 | + id = true, | |
21 | + uid = true, | |
22 | + sid = true, | |
23 | + name = true, | |
24 | +} | |
25 | + | |
26 | +function Role:data() | |
27 | + return { | |
28 | + id = self:getProperty("id"), | |
29 | + } | |
30 | +end | |
31 | + | |
32 | +return Role | |
0 | 33 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +local _M = {} | |
2 | + | |
3 | +_M["rankDetails"] = { | |
4 | + file = "src/rdsscripts/rankDetails.lua", | |
5 | + sha1 = nil, | |
6 | +} | |
7 | +_M["insertEmail"] = { | |
8 | + file = "src/rdsscripts/insertEmail.lua", | |
9 | + sha1 = nil, | |
10 | +} | |
11 | +_M["refreshAssist"] = { | |
12 | + file = "src/rdsscripts/refreshAssist.lua", | |
13 | + sha1 = nil, | |
14 | +} | |
15 | +_M["assistInfo"] = { | |
16 | + file = "src/rdsscripts/assistInfo.lua", | |
17 | + sha1 = nil, | |
18 | +} | |
19 | + | |
20 | +return _M | |
0 | 21 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,55 @@ |
1 | +local roleId = tonumber(KEYS[1]) | |
2 | + | |
3 | +local formationJson = redis.call("hget", string.format("role:%d", roleId), "pveFormationJson") | |
4 | + | |
5 | +local formation = cjson.decode(formationJson) | |
6 | + | |
7 | +local function formatAttrEx(str) | |
8 | + local tb = {} | |
9 | + for k, v in str:gmatch("([%d.]+)=([%d.]+)") do | |
10 | + tb[#tb+1] = {tonumber(k), tonumber(v)} | |
11 | + end | |
12 | + return tb | |
13 | +end | |
14 | + | |
15 | +local function formatEquips(str) | |
16 | + local tb = {} | |
17 | + for k, v in str:gmatch("([%d.]+)=([%d.]+)") do | |
18 | + tb[tonumber(k)] = tonumber(v) | |
19 | + end | |
20 | + return tb | |
21 | +end | |
22 | + | |
23 | +local heroFields = {"type", "level", "star", "evolveCount", "wake", "breakLevel", "equips"} | |
24 | +local equipFields = {"type", "level", "evolCount", "attrEx"} | |
25 | +for _, hero in ipairs(formation.heros) do | |
26 | + if hero.leader then | |
27 | + local heroInfo = redis.call("hmget", string.format("hero:%d:%d", roleId, hero.id), unpack(heroFields)) | |
28 | + local equipstr = heroInfo[7] | |
29 | + local equips = {} | |
30 | + for part, equipId in equipstr:gmatch("(%d+)=(%d+)") do | |
31 | + part, equipId = tonumber(part), tonumber(equipId) | |
32 | + if equipId ~= 0 then | |
33 | + local equipInfo = redis.call("hmget", string.format("equip:%d:%d", roleId, equipId), unpack(equipFields)) | |
34 | + equips[equipId] = {} | |
35 | + for index, value in ipairs(equipInfo) do | |
36 | + equips[equipId][equipFields[index]] = index ~= 4 and tonumber(value) or formatAttrEx(equipInfo[4] or "") | |
37 | + end | |
38 | + end | |
39 | + end | |
40 | + return cmsgpack.pack { | |
41 | + type = tonumber(heroInfo[1]), | |
42 | + level = tonumber(heroInfo[2]), | |
43 | + star = tonumber(heroInfo[3]), | |
44 | + evolveCount = tonumber(heroInfo[4]), | |
45 | + wake = tonumber(heroInfo[5]), | |
46 | + breakLevel = tonumber(heroInfo[6]), | |
47 | + equips = formatEquips(heroInfo[7]), | |
48 | + equipDtls = equips, | |
49 | + } | |
50 | + end | |
51 | +end | |
52 | + | |
53 | +return cmsgpack.pack {} | |
54 | + | |
55 | + | ... | ... |
... | ... | @@ -0,0 +1,27 @@ |
1 | +local con1 = KEYS[4] or "" | |
2 | +local con2 = KEYS[5] or "" | |
3 | +local con3 = KEYS[6] or "" | |
4 | +local att1 = KEYS[7] or "" | |
5 | +local att2 = KEYS[8] or "" | |
6 | +local att3 = KEYS[9] or "" | |
7 | +local title = KEYS[10] or "" | |
8 | +local content = KEYS[11] or "" | |
9 | +local attachments = KEYS[12] or "" | |
10 | + | |
11 | +-- local roleInfo = redis.call("HGET", string.format("role:%d", KEYS[1]), "delete") | |
12 | + | |
13 | +-- if tonumber(roleInfo) == 1 then return end | |
14 | + | |
15 | +local id = redis.call("HINCRBY", string.format("role:%d:autoincr", KEYS[1]), "email", 1) | |
16 | +redis.call("LPUSH", string.format("role:%d:emailIds", KEYS[1]), id) | |
17 | +local deleteIds = redis.call("LRANGE", string.format("role:%d:emailIds", KEYS[1]), 50, -1) | |
18 | +for _, deleteId in ipairs(deleteIds) do | |
19 | + redis.call("DEL", string.format("email:%d:%d", KEYS[1], deleteId)) | |
20 | +end | |
21 | + | |
22 | +redis.call("LTRIM", string.format("role:%d:emailIds", KEYS[1]), 0, 49) | |
23 | +redis.call("HMSET", string.format("email:%d:%d", KEYS[1], id), "id", tostring(id), "emailId", KEYS[2], | |
24 | + "status", "0", "createtime", KEYS[3], | |
25 | + "con1", con1, "con2", con2, "con3", con3, | |
26 | + "att1", att1, "att2", att2, "att3", att3, | |
27 | + "title", title, "content", content, "attachments", attachments) | ... | ... |
... | ... | @@ -0,0 +1,30 @@ |
1 | +local field = KEYS[1] | |
2 | +local roleId = tonumber(KEYS[2]) | |
3 | + | |
4 | +local formationJson = redis.call("hget", string.format("role:%d", roleId), field .. "FormationJson") | |
5 | + | |
6 | +local formation = cjson.decode(formationJson) | |
7 | + | |
8 | +local response = {formation = {}, heros = {}} | |
9 | + | |
10 | +if formation.heros then | |
11 | + for _, hero in ipairs(formation.heros) do | |
12 | + table.insert(response.formation, hero.id) | |
13 | + end | |
14 | +end | |
15 | + | |
16 | +local heroIds = redis.call("smembers", string.format("role:%d:heroIds", roleId)) | |
17 | + | |
18 | +local heroFields = {"type", "level", "star", "evolveCount", "wake", "breakLevel"} | |
19 | +for _, heroId in ipairs(heroIds) do | |
20 | + local heroId = tonumber(heroId) | |
21 | + local heroInfo = redis.call("hmget", string.format("hero:%d:%d", roleId, heroId), unpack(heroFields)) | |
22 | + | |
23 | + local tb = {} | |
24 | + for k, v in ipairs(heroInfo) do | |
25 | + tb[heroFields[k]] = tonumber(v) | |
26 | + end | |
27 | + response.heros[heroId] = tb | |
28 | +end | |
29 | + | |
30 | +return cmsgpack.pack(response) | |
0 | 31 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,58 @@ |
1 | +local roleId = tonumber(KEYS[1]) | |
2 | + | |
3 | +local friendKey = "role:%d:friend" | |
4 | +local assistKey = "role:%d:assist" | |
5 | + | |
6 | +local function formatTable(tbl) | |
7 | + local t = {} | |
8 | + for _, id in ipairs(tbl) do | |
9 | + t[tonumber(id)] = 1 | |
10 | + end | |
11 | + return t | |
12 | +end | |
13 | + | |
14 | +local friendIds = redis.call("smembers", friendKey:format(roleId)) | |
15 | +local assistIds = formatTable(redis.call("smembers", assistKey:format(roleId))) | |
16 | + | |
17 | +local heroFields = {"type", "level", "star", "evolveCount", "wake", "breakLevel", "battleValue", "dress"} | |
18 | +local function getLeader(id, formation) | |
19 | + for _, hero in ipairs(formation.heros) do | |
20 | + if hero.leader then | |
21 | + local heroInfo = redis.call("hmget", string.format("hero:%d:%d", id, hero.id), unpack(heroFields)) | |
22 | + return { | |
23 | + type = tonumber(heroInfo[1]), | |
24 | + level = tonumber(heroInfo[2]), | |
25 | + star = tonumber(heroInfo[3]), | |
26 | + evolveCount = tonumber(heroInfo[4]), | |
27 | + wake = tonumber(heroInfo[5]), | |
28 | + breakLevel = tonumber(heroInfo[6]), | |
29 | + battleValue = tonumber(heroInfo[7]), | |
30 | + dress = tonumber(heroInfo[8]), | |
31 | + } | |
32 | + end | |
33 | + end | |
34 | +end | |
35 | + | |
36 | +local response = {} | |
37 | +for _, id in ipairs(friendIds) do | |
38 | + id = tonumber(id) | |
39 | + local dtls = redis.call("hmget", string.format("role:%d", id), | |
40 | + "name", "level", "vip", "pveFormationJson") | |
41 | + local formation = cjson.decode(dtls[4]) | |
42 | + local leader = getLeader(id, formation) | |
43 | + if leader then | |
44 | + table.insert(response, { | |
45 | + roleId = id, | |
46 | + name = dtls[1], | |
47 | + level = tonumber(dtls[2]), | |
48 | + vip = tonumber(dtls[3]), | |
49 | + leader = leader, | |
50 | + used = assistIds[id] or 0, | |
51 | + }) | |
52 | + end | |
53 | +end | |
54 | + | |
55 | +return cmsgpack.pack(response) | |
56 | + | |
57 | + | |
58 | + | ... | ... |
... | ... | @@ -0,0 +1,208 @@ |
1 | +local skynet = require "skynet" | |
2 | +local socket = require "skynet.socket" | |
3 | +local redisproxy = require "shared.redisproxy" | |
4 | +local netpack = require "skynet.netpack" | |
5 | +local xxtea = require "xxtea" | |
6 | +local deque = require "deque" | |
7 | +local datacenter = require "skynet.datacenter" | |
8 | + | |
9 | +local pcall = pcall | |
10 | +local string_format = string.format | |
11 | + | |
12 | +local poold | |
13 | + | |
14 | +-- agent过期时间 10分钟 | |
15 | +local AGENT_EXPIRE_TIME = 300 | |
16 | + | |
17 | +local _M = { | |
18 | + -- fd -> uid | |
19 | + f2u = {}, | |
20 | + -- uid -> 32 << fd | agent | |
21 | + u2f = {}, | |
22 | + -- fd -> ip | |
23 | + f2i = {}, | |
24 | + -- fd -> expire | |
25 | + f2e = {}, | |
26 | + online = 0, | |
27 | +} | |
28 | + | |
29 | +local function get_f(pack) | |
30 | + return (pack >> 32) & 0x7fffffff | |
31 | +end | |
32 | + | |
33 | +local function get_a(pack) | |
34 | + return pack & 0xffffffff | |
35 | +end | |
36 | + | |
37 | +local function set_pack(fd, agent) | |
38 | + return (fd << 32) | agent | |
39 | +end | |
40 | + | |
41 | +function _M:init(obj, serice) | |
42 | + self.factory = deque.clone(obj) | |
43 | + poold = serice | |
44 | +end | |
45 | + | |
46 | +-- @desc: agent退出 | |
47 | +function _M:exit_agent(fd) | |
48 | + self.f2e[fd] = nil | |
49 | + local uid = self.f2u[fd] | |
50 | + if not uid then return end | |
51 | + | |
52 | + local pack = self.u2f[uid] | |
53 | + if not pack then | |
54 | + self.f2u[fd] = nil | |
55 | + return | |
56 | + end | |
57 | + | |
58 | + local agent = get_a(pack) | |
59 | + | |
60 | + pcall(skynet.send, agent, "lua", "exit") | |
61 | + pcall(skynet.send, poold, "lua", "feed") | |
62 | + | |
63 | + self.f2u[fd] = nil | |
64 | + self.u2f[uid] = nil | |
65 | +end | |
66 | + | |
67 | +-- @desc: 客户端连入 | |
68 | +function _M:socket_open(fd, addr) | |
69 | + self.f2i[fd] = addr | |
70 | +end | |
71 | + | |
72 | +-- @desc: 网络关闭 | |
73 | +function _M:socket_close(fd) | |
74 | + self.f2i[fd] = nil | |
75 | + local uid = self.f2u[fd] | |
76 | + if not uid then return end | |
77 | + self.f2e[fd] = skynet.timex() + AGENT_EXPIRE_TIME | |
78 | + | |
79 | + if not self.u2f[uid] then | |
80 | + self.f2u[fd] = nil | |
81 | + return | |
82 | + end | |
83 | + local agent = get_a(self.u2f[uid]) | |
84 | + local ok, _ = pcall(skynet.call, agent, "lua", "close") | |
85 | + if not ok then self:exit_agent(fd) end | |
86 | +end | |
87 | + | |
88 | +-- @desc: 网络出错 | |
89 | +function _M:socket_error(fd) | |
90 | + self.f2i[fd] = nil | |
91 | + local uid = self.f2u[fd] | |
92 | + if not uid then return end | |
93 | + | |
94 | + if not self.u2f[uid] then | |
95 | + self.f2u[fd] = nil | |
96 | + return | |
97 | + end | |
98 | + local agent = get_a(self.u2f[uid]) | |
99 | + -- 无论失败否,应该让逻辑走下去,保证agent状态以及索引正确 | |
100 | + pcall(skynet.call, agent, "lua", "close") | |
101 | + self:exit_agent(fd) | |
102 | +end | |
103 | + | |
104 | +local function table_nums(t) | |
105 | + local count = 0 | |
106 | + for k, v in pairs(t) do | |
107 | + count = count + 1 | |
108 | + end | |
109 | + return count | |
110 | +end | |
111 | + | |
112 | +local next_check_time = 0 | |
113 | +local next_log_time = 0 | |
114 | +local CHECK_AGENT_STATUS_INTERVAL = 100 -- 检查agent状态的定时间隔 | |
115 | +-- @desc: 检查agent状态,若过期,则让agent退出;并定时打日志统计在线人数 | |
116 | +function _M:check_agent_status() | |
117 | + local now = skynet.timex() | |
118 | + if now >= next_check_time then | |
119 | + next_check_time = now + CHECK_AGENT_STATUS_INTERVAL | |
120 | + for fd, expire in pairs(self.f2e) do | |
121 | + if expire < now then | |
122 | + self:exit_agent(fd) | |
123 | + end | |
124 | + end | |
125 | + end | |
126 | + | |
127 | + if now >= next_log_time and now % 60 == 0 and logd then | |
128 | + next_log_time = now + 60 | |
129 | + local count = table_nums(self.u2f) | |
130 | + datacenter.set("onlineCount", count) | |
131 | + pcall(skynet.send, logd, "lua", "log", "online", {count = count}) | |
132 | + end | |
133 | +end | |
134 | + | |
135 | +local function query_agent_response(fd, response) | |
136 | + local head = string.pack("H", actionCodes.Role_queryLoginRpc + rpcResponseBegin) | |
137 | + | |
138 | + local bin = MsgPack.pack(response) | |
139 | + if #bin > 0 then bin = xxtea.encrypt(bin, XXTEA_KEY) end | |
140 | + socket.write(fd, netpack.pack(head .. bin)) | |
141 | +end | |
142 | + | |
143 | +-- @desc: 玩家登陆第一个包,queryLogin,watchdog为客户端分配一个agent,并告诉gate分配成功,之后的消息直接走agent | |
144 | +function _M:query_agent(fd, uid) | |
145 | + local pack = self.u2f[uid] | |
146 | + if pack then | |
147 | + local f = get_f(pack) | |
148 | + if fd == f then | |
149 | + skynet.error(string.format("%s same fd %d", uid, fd)) | |
150 | + return | |
151 | + end | |
152 | + | |
153 | + -- self.f2u[f] 肯定存在;self.f2e[f]不存在,则说明在线,则需要踢下线 | |
154 | + if not self.f2e[f] then | |
155 | + local head = string.pack("H", actionCodes.Sys_kickdown) | |
156 | + -- local bin = MsgPack.pack({body = "该账号已登上其他机器"}) | |
157 | + local bin = MsgPack.pack({body = "既にこのアカウントを利用している端末があります。"}) | |
158 | + if #bin > 0 then bin = xxtea.encrypt(bin, XXTEA_KEY) end | |
159 | + socket.write(f, netpack.pack(head .. bin)) | |
160 | + skynet.timeout(10, function () | |
161 | + skynet.call(gate_serv, "lua", "kick", f) | |
162 | + end) | |
163 | + end | |
164 | + | |
165 | + local agent = get_a(pack) | |
166 | + local ok = pcall(skynet.call, agent, "lua", "start", gate_serv, fd, self.f2i[fd]) | |
167 | + if not ok then | |
168 | + query_agent_response(fd, {ret = "INNER_ERROR"}) | |
169 | + return | |
170 | + end | |
171 | + | |
172 | + self.f2e[f] = nil | |
173 | + self.f2u[f] = nil | |
174 | + self.u2f[uid] = set_pack(fd, agent) | |
175 | + else | |
176 | + -- 该uid未存储,则说明至少超过10分钟未登陆,由agent池服务pop出一个agent | |
177 | + local agent = self.factory:pop() | |
178 | + if not agent then | |
179 | + -- 服务器满 | |
180 | + query_agent_response(fd, {ret = "RET_SERVER_FULL"}) | |
181 | + return | |
182 | + end | |
183 | + | |
184 | + local ok = pcall(skynet.call, agent, "lua", "start", gate_serv, fd, self.f2i[fd]) | |
185 | + if not ok then | |
186 | + self.factory:push(agent) | |
187 | + query_agent_response(fd, {ret = "INNER_ERROR"}) | |
188 | + return | |
189 | + end | |
190 | + | |
191 | + self.u2f[uid] = set_pack(fd, agent) | |
192 | + end | |
193 | + | |
194 | + self.f2u[fd] = uid | |
195 | + | |
196 | + local response = {} | |
197 | + | |
198 | + local user = redisproxy:get(string_format("uid:%s", uid)) | |
199 | + if user then | |
200 | + response.ret = "RET_HAS_EXISTED" | |
201 | + response.name = user | |
202 | + else | |
203 | + response.ret = "RET_NOT_EXIST" | |
204 | + end | |
205 | + query_agent_response(fd, response) | |
206 | +end | |
207 | + | |
208 | +return _M | |
0 | 209 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,109 @@ |
1 | + | |
2 | +local _M = { } | |
3 | + | |
4 | +-- 超时次数 | |
5 | +local heartTimeoutCount = 0 | |
6 | +-- 加速次数 | |
7 | +local heartQuickCount = 0 | |
8 | +-- 上次检查心跳时间 | |
9 | +local lastHeartCheckTime = 0 | |
10 | +-- 下次进入定时检查的时间 | |
11 | +local nextCheckTime = 0 | |
12 | +-- 心跳误差允许范围 | |
13 | +local HEART_BEAT_ERROR_LIMIT = 1 | |
14 | +-- 最大超时次数 | |
15 | +local HEART_TIMEOUT_COUNT_MAX = 20 | |
16 | +-- 最大加速次数 | |
17 | +local HEART_QUICK_COUNT_MAX = 5 | |
18 | +-- 心跳定时间隔 | |
19 | +local HEART_TIMER_INTERVAL = 5 | |
20 | + | |
21 | +local function check_heart_beat(agent, now) | |
22 | + -- 充值等操作不检查心跳 | |
23 | + local role = agent.role | |
24 | + if role.ignoreHeartbeat then | |
25 | + lastHeartCheckTime = now - HEART_TIMER_INTERVAL | |
26 | + heartTimeoutCount = 0 | |
27 | + return | |
28 | + end | |
29 | + if lastHeartCheckTime - now > HEART_TIMER_INTERVAL or | |
30 | + now - lastHeartCheckTime > HEART_TIMER_INTERVAL then | |
31 | + heartTimeoutCount = heartTimeoutCount + 1 | |
32 | + if heartTimeoutCount >= HEART_TIMEOUT_COUNT_MAX then | |
33 | + skynet.error("timeout! then agent will shut down by self", agent.client_fd, role:getProperty("name"), role:getProperty("id")) | |
34 | + skynet.call(agent.gate_serv, "lua", "kick", agent.client_fd) | |
35 | + heartTimeoutCount = 0 | |
36 | + end | |
37 | + else | |
38 | + heartTimeoutCount = 0 | |
39 | + end | |
40 | +end | |
41 | + | |
42 | +local PointDataMark = {} | |
43 | +local resetTimeStr = string.format("%02d00", RESET_TIME) | |
44 | + | |
45 | +local function check_daily_reset(agent, now) | |
46 | + local date = os.date("*t", now) | |
47 | + local timeStr = string.format("%02d%02d", date.hour, date.min) | |
48 | + local dataStr = date.year .. string.format("%02d", date.month) .. string.format("%02d", date.day) | |
49 | + | |
50 | + local function timeEffect(checkTimeStr) | |
51 | + if timeStr ~= checkTimeStr then | |
52 | + return false | |
53 | + end | |
54 | + if PointDataMark[dataStr] and PointDataMark[dataStr][checkTimeStr] then | |
55 | + return false | |
56 | + end | |
57 | + PointDataMark[dataStr] = PointDataMark[dataStr] or {} | |
58 | + PointDataMark[dataStr][checkTimeStr] = true | |
59 | + return true | |
60 | + end | |
61 | + | |
62 | + if timeEffect(resetTimeStr) then | |
63 | + -- 刷新每日数据 | |
64 | + local role = agent.role | |
65 | + if role then | |
66 | + role:onCrossDay(now, true) | |
67 | + end | |
68 | + end | |
69 | +end | |
70 | + | |
71 | +function _M:update(agent) | |
72 | + local now = skynet.timex() | |
73 | + local role = agent.role | |
74 | + if now >= nextCheckTime then | |
75 | + pcall(check_heart_beat, agent, now) | |
76 | + nextCheckTime = now + HEART_TIMER_INTERVAL | |
77 | + end | |
78 | + pcall(check_daily_reset, agent, now) | |
79 | + pcall(role.onRecoverTimer, role, now) | |
80 | +end | |
81 | + | |
82 | +function _M:heart_beat(agent) | |
83 | + local now = skynet.timex() | |
84 | + if now == lastHeartCheckTime then | |
85 | + return | |
86 | + end | |
87 | + if now - lastHeartCheckTime <= HEART_TIMER_INTERVAL - HEART_BEAT_ERROR_LIMIT then | |
88 | + heartQuickCount = heartQuickCount + 1 | |
89 | + if heartQuickCount == HEART_QUICK_COUNT_MAX then | |
90 | + -- 将错误写入日志 | |
91 | + local role = agent.role | |
92 | + skynet.error("Warning, heart beating is too quick, shut down the agent", agent.client_fd, role:getProperty("name"), role:getProperty("id")) | |
93 | + -- skynet.call(agent.gate_serv, "lua", "kick", agent.client_fd) | |
94 | + role:warningHeartTooQuick() | |
95 | + heartQuickCount = 0 | |
96 | + end | |
97 | + else | |
98 | + heartQuickCount = 0 | |
99 | + end | |
100 | + lastHeartCheckTime = now | |
101 | +end | |
102 | + | |
103 | +function _M:reset() | |
104 | + heartTimeoutCount = 0 | |
105 | + heartQuickCount = 0 | |
106 | + lastHeartCheckTime = skynet.timex() | |
107 | +end | |
108 | + | |
109 | +return _M | ... | ... |
... | ... | @@ -0,0 +1,56 @@ |
1 | +local skynet = require "skynet" | |
2 | +require "skynet.manager" | |
3 | +local crab = require "crab.c" | |
4 | + | |
5 | +local table_insert = table.insert | |
6 | +local table_unpack = table.unpack | |
7 | +local mode, id, dict = ... | |
8 | + | |
9 | +local function toutf8(name) | |
10 | + local t = {} | |
11 | + for _, v in utf8.codes(name) do | |
12 | + table_insert(t, v) | |
13 | + end | |
14 | + return t | |
15 | +end | |
16 | + | |
17 | +if mode == "sub" then | |
18 | + local CMD = {} | |
19 | + dict = tonumber(dict) | |
20 | + | |
21 | + function CMD.check(name) | |
22 | + if name:find("%c") then | |
23 | + return false | |
24 | + end | |
25 | + local utftb = toutf8(name) | |
26 | + if crab.filter(dict, utftb) then | |
27 | + return false, utf8.char(table_unpack(utftb)) | |
28 | + end | |
29 | + return true | |
30 | + end | |
31 | + | |
32 | + skynet.start(function() | |
33 | + skynet.dispatch("lua", function(_, _, command, ...) | |
34 | + local f = CMD[command] | |
35 | + skynet.ret(skynet.pack(f(...))) | |
36 | + end) | |
37 | + | |
38 | + skynet.register(string.format("CHATED%d", id)) | |
39 | + end) | |
40 | +else | |
41 | + skynet.start(function() | |
42 | + local ok, forbidNames = pcall(require, "csvdata.forbid_chat") | |
43 | + if not ok then forbidNames = {} end | |
44 | + | |
45 | + local words = {} | |
46 | + for _, data in ipairs(forbidNames) do | |
47 | + local ok, utftb = pcall(toutf8, data.name) | |
48 | + if ok then table.insert(words, utftb) end | |
49 | + end | |
50 | + local d = crab.open(words) | |
51 | + | |
52 | + for i = 1, 5 do | |
53 | + skynet.newservice(SERVICE_NAME, "sub", i, d) | |
54 | + end | |
55 | + end) | |
56 | +end | ... | ... |
... | ... | @@ -0,0 +1,64 @@ |
1 | +local sharedata = require "skynet.sharedata" | |
2 | +local skynet = require "skynet" | |
3 | +local lfs = require "lfs" | |
4 | +local redisproxy = require "shared.redisproxy" | |
5 | +require "shared.init" | |
6 | +require "utils.init" | |
7 | +require "csvdata.init" | |
8 | +require "skynet.manager" | |
9 | +require "RedisKeys" | |
10 | + | |
11 | +local csvdb = {} | |
12 | + | |
13 | +local function formatFileName(filename) | |
14 | + filename = string.trim(filename) | |
15 | + local basename = filename:match("([^/]+)%.lua$") | |
16 | + if not basename then return end | |
17 | + local loadname = filename:match("^src/([^.]+)%.lua$") | |
18 | + loadname = loadname:gsub('/', '.') | |
19 | + return basename, loadname | |
20 | +end | |
21 | + | |
22 | +local function travCsv(rootPath, pathes) | |
23 | + pathes = pathes or {} | |
24 | + local modified = false | |
25 | + local ok, files, iter = pcall(lfs.dir, rootPath) | |
26 | + if not ok then return modified end | |
27 | + for entry in files, iter do | |
28 | + -- 过滤 . 开始的字符串包括 . .. .git .开头的文件名 | |
29 | + if string.byte(entry, 1) ~= 46 then | |
30 | + local pathfile = rootPath .. '/' .. entry | |
31 | + local attrs = lfs.attributes(pathfile) | |
32 | + if attrs.mode == 'directory' then | |
33 | + modified = travCsv(pathfile, pathes) or modified | |
34 | + else | |
35 | + local basename, loadname = formatFileName(pathfile) | |
36 | + if basename and tonum(pathes[loadname]) < attrs.modification then | |
37 | + modified = true | |
38 | + if basename == "init" then | |
39 | + require(loadname) | |
40 | + else | |
41 | + csvdb[basename .. "Csv"] = require(loadname) | |
42 | + end | |
43 | + pathes[loadname] = attrs.modification | |
44 | + end | |
45 | + end | |
46 | + end | |
47 | + end | |
48 | + return modified | |
49 | +end | |
50 | + | |
51 | +-- 每分钟检查是否有更改 | |
52 | +local file2timeMap = {} | |
53 | +local function handle_timeout() | |
54 | + if travCsv("src/csvdata", file2timeMap) then | |
55 | + sharedata.update("csvdata", csvdb) | |
56 | + end | |
57 | + skynet.timeout(100*5, handle_timeout) | |
58 | +end | |
59 | + | |
60 | +skynet.start(function () | |
61 | + travCsv("src/csvdata", file2timeMap) | |
62 | + sharedata.new("csvdata", csvdb) | |
63 | + -- handle_timeout() | |
64 | +end) | ... | ... |
... | ... | @@ -0,0 +1,54 @@ |
1 | +require "csvdata.init" | |
2 | +require "shared.init" | |
3 | +require "utils.init" | |
4 | +require "GlobalVar" | |
5 | +require "RedisKeys" | |
6 | +require "ProtocolCode" | |
7 | +require "skynet.manager" | |
8 | +local harbor = require "skynet.harbor" | |
9 | + | |
10 | +skynet = require "skynet" | |
11 | + | |
12 | +redisproxy = require("shared.redisproxy") | |
13 | +globalCsv = require "csvdata/GlobalDefine" | |
14 | + | |
15 | +SendPacket = function ( ... ) end | |
16 | + | |
17 | +local function initRedisDb( ... ) | |
18 | + local servId = tonumber(skynet.getenv("servId")) | |
19 | + if servId then | |
20 | + redisproxy:hsetnx("autoincrement_set", "role", servId * MAX_ROLE_NUM) | |
21 | + redisproxy:hsetnx("autoincrement_set", "union", servId * MAX_ROLE_NUM) | |
22 | + redisproxy:hsetnx("autoincrement_set", "trade", servId * MAX_ROLE_NUM * 100) | |
23 | + redisproxy:hsetnx("autoincrement_set", "email", 0) | |
24 | + redisproxy:hsetnx("autoincrement_set", "emailTimestamp", 0) | |
25 | + redisproxy:hsetnx("autoincrement_set", "delay_email", 0) | |
26 | + end | |
27 | +end | |
28 | + | |
29 | +local steps = { | |
30 | + [1] = { | |
31 | + handler = initRedisDb, | |
32 | + desc = "initialize redis database " | |
33 | + } | |
34 | +} | |
35 | + | |
36 | +skynet.start(function () | |
37 | + redisd = harbor.queryname("REDIS") | |
38 | + | |
39 | + redisproxy = require("shared.redisproxy") | |
40 | + | |
41 | + local new = redisproxy:hsetnx("autoincrement_set", "server_start", os.date("%Y%m%d", skynet.timex())) | |
42 | + if not new then | |
43 | + print("server has been initialized...") | |
44 | + skynet.exit() | |
45 | + return | |
46 | + end | |
47 | + | |
48 | + for _, action in ipairs(steps) do | |
49 | + print(action.desc .. "start ...") | |
50 | + action.handler() | |
51 | + print(action.desc .. "finished ...") | |
52 | + end | |
53 | + skynet.exit() | |
54 | +end) | |
0 | 55 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,109 @@ |
1 | +local skynet = require "skynet" | |
2 | +local harbor = require "skynet.harbor" | |
3 | +local json = require("shared.json") | |
4 | +local redisproxy = require("shared.redisproxy") | |
5 | + | |
6 | +require "shared.init" | |
7 | +require "utils.init" | |
8 | +require "RedisKeys" | |
9 | +require "skynet.manager" | |
10 | + | |
11 | +local ipairs = ipairs | |
12 | +local table_insert = table.insert | |
13 | +local tarr2tab = table.array2Table | |
14 | +local string_format = string.format | |
15 | + | |
16 | +local pointDataMark = {} | |
17 | +local utils = {} | |
18 | + | |
19 | +local CHECK_MAIL_STATUS_INTERVAL = 60 | |
20 | + | |
21 | +local function mailQuene() | |
22 | + local delayEmail = tonum(redisproxy:hget("autoincrement_set", "delay_email")) | |
23 | + if delayEmail == 0 then | |
24 | + return | |
25 | + end | |
26 | + local begin = math.max(delayEmail - 100, 1) | |
27 | + local mails = redisproxy:pipelining(function (red) | |
28 | + for id = begin, delayEmail do | |
29 | + red:hgetall(string.format("delayEmail:%s", id)) | |
30 | + end | |
31 | + end) | |
32 | + if not mails then | |
33 | + return | |
34 | + end | |
35 | + local now = skynet.timex() | |
36 | + local mailList = {} | |
37 | + for index, data in ipairs(mails) do | |
38 | + if next(data) then | |
39 | + local email = tarr2tab(data) | |
40 | + if tonum(email.startTime) <= now then | |
41 | + table_insert(mailList, email) | |
42 | + | |
43 | + if #mailList > 100 then | |
44 | + break | |
45 | + end | |
46 | + end | |
47 | + end | |
48 | + end | |
49 | + if #mailList == 0 then | |
50 | + return | |
51 | + end | |
52 | + redisproxy:pipelining(function (red) | |
53 | + for _, email in ipairs(mailList) do | |
54 | + red:del(string_format("delayEmail:%s", email.id)) | |
55 | + end | |
56 | + end) | |
57 | + table.sort(mailList, function(a, b) | |
58 | + return tonum(a.id) < tonum(b.id) | |
59 | + end) | |
60 | + for _, email in ipairs(mailList) do | |
61 | + local gid = redisproxy:hincrby("autoincrement_set", "email", 1) | |
62 | + if email.mid then | |
63 | + redisproxy:hmset(string_format("globalEmail:%s", gid), | |
64 | + "id", gid, | |
65 | + "createtime", email.endTime, | |
66 | + "title", email.title, | |
67 | + "content", email.content, | |
68 | + "attachments", email.attachments, | |
69 | + "endtime", email.endTime, | |
70 | + "mid", email.mid, | |
71 | + "timestamp", email.startTime | |
72 | + ) | |
73 | + else | |
74 | + redisproxy:hmset(string_format("globalEmail:%s", gid), | |
75 | + "id", gid, | |
76 | + "createtime", email.endTime, | |
77 | + "title", email.title, | |
78 | + "content", email.content, | |
79 | + "attachments", email.attachments, | |
80 | + "endtime", email.endTime, | |
81 | + "timestamp", email.startTime | |
82 | + ) | |
83 | + end | |
84 | + end | |
85 | + redisproxy:hset("autoincrement_set", "emailTimestamp", now) | |
86 | +end | |
87 | + | |
88 | +-- @desc: 定时邮件队列检测 | |
89 | +local function check_mail_queue() | |
90 | + pcall(mailQuene) | |
91 | + skynet.timeout(CHECK_MAIL_STATUS_INTERVAL, check_mail_queue) | |
92 | +end | |
93 | + | |
94 | +local CMD = {} | |
95 | +function CMD.start() | |
96 | + check_mail_queue() | |
97 | +end | |
98 | + | |
99 | +local function __init__() | |
100 | + skynet.dispatch("lua", function(_, _, command, ...) | |
101 | + if CMD[command] then | |
102 | + skynet.ret(skynet.pack(CMD[command](...))) | |
103 | + end | |
104 | + end) | |
105 | + redisd = harbor.queryname("REDIS") | |
106 | + skynet.register("GLOBALD") | |
107 | +end | |
108 | + | |
109 | +skynet.start(__init__) | ... | ... |
... | ... | @@ -0,0 +1,118 @@ |
1 | +local skynet = require "skynet" | |
2 | +local mongo = require "mongo" | |
3 | +local queue = require "skynet.queue" | |
4 | +local bson = require "bson" | |
5 | +local socketdriver = require "socketdriver" | |
6 | + | |
7 | +local serverId = tonumber(skynet.getenv("servId")) | |
8 | + | |
9 | +require "shared.init" | |
10 | +require "skynet.manager" | |
11 | + | |
12 | +local table_insert = table.insert | |
13 | +local pairs = pairs | |
14 | +local ipairs = ipairs | |
15 | +local string_format = string.format | |
16 | + | |
17 | +local CMD, cache, client, cs = {}, {} | |
18 | +local auto = {} | |
19 | + | |
20 | +local rsyslog_fd | |
21 | + | |
22 | +-- function getNextSequence(collect) | |
23 | +-- local index = auto[collect] | |
24 | +-- if not index then | |
25 | +-- local res = client.counters:findAndModify({ | |
26 | +-- query = {}, | |
27 | +-- update = {["$inc"] = {[collect] = 1}}, | |
28 | +-- upsert = true, | |
29 | +-- new = true, | |
30 | +-- }) | |
31 | +-- index = res.value[collect] | |
32 | +-- else | |
33 | +-- index = index + 1 | |
34 | +-- end | |
35 | +-- auto[collect] = index | |
36 | +-- return index | |
37 | +-- end | |
38 | + | |
39 | +local dateTypes = {"timestamp", "createTime", "lastLoginTime"} | |
40 | +local stringTypes = {"s1", "s2", "s3"} | |
41 | +local intTypes = {"int1", "int2", "int3", "int4"} | |
42 | +function CMD.log(collect, doc) | |
43 | + -- write to rsyslog | |
44 | + local now = skynet.timex() | |
45 | + doc["timestamp"] = now | |
46 | + doc["server"] = serverId | |
47 | + | |
48 | + for _, field in pairs(stringTypes) do | |
49 | + if doc[field] then | |
50 | + doc[field] = tostring(doc[field]) | |
51 | + end | |
52 | + end | |
53 | + for _, field in pairs(intTypes) do | |
54 | + if doc[field] then | |
55 | + doc[field] = tonumber(doc[field]) | |
56 | + end | |
57 | + end | |
58 | + for _, field in pairs(dateTypes) do | |
59 | + if doc[field] then | |
60 | + doc[field .. "_t"] = tonumber(os.date("%Y%m%d%H%M%S", doc[field])) | |
61 | + end | |
62 | + end | |
63 | + -- Local-6 + Info | |
64 | + socketdriver.send(rsyslog_fd, string.format("<182>%s: %s\n", collect, json.encode(doc))) | |
65 | + | |
66 | + -- if not cache[collect] then cache[collect] = {} end | |
67 | + -- doc["timestamp"] = now | |
68 | + -- doc["_id"] = getNextSequence(collect) | |
69 | + -- table_insert(cache[collect], doc) | |
70 | +end | |
71 | + | |
72 | +function CMD.open(conf) | |
73 | + rsyslog_fd = socketdriver.connect("127.0.0.1", 514) | |
74 | + socketdriver.start(rsyslog_fd) | |
75 | + | |
76 | + -- local db = mongo.client { | |
77 | + -- host = conf.mongohost, | |
78 | + -- port = conf.mongoport or 27017, | |
79 | + -- username = conf.mongouser, | |
80 | + -- password = conf.mongopswd, | |
81 | + -- } | |
82 | + | |
83 | + -- assert(db, "mongo connect error") | |
84 | + | |
85 | + -- local servId = skynet.getenv "servId" | |
86 | + -- client = db["s"..servId] | |
87 | +end | |
88 | + | |
89 | +-- local function __loop__() | |
90 | +-- while true do | |
91 | +-- cs(function () | |
92 | +-- for col, docs in pairs(cache) do | |
93 | +-- client[col]:batch_insert(docs) | |
94 | +-- client.counters:update({}, {["$set"]={[col]=auto[col]}}) | |
95 | +-- end | |
96 | +-- cache = {} | |
97 | +-- end) | |
98 | +-- skynet.sleep(200) | |
99 | +-- end | |
100 | +-- end | |
101 | + | |
102 | +local function __init__() | |
103 | + skynet.dispatch("lua", function (_, _, command, ...) | |
104 | + local f = CMD[command] | |
105 | + if command == "open" then | |
106 | + skynet.ret(skynet.pack(f(...))) | |
107 | + else | |
108 | + local collect, doc = ... | |
109 | + cs(function() f(collect, doc) end) | |
110 | + end | |
111 | + end) | |
112 | + cs = queue() | |
113 | + | |
114 | + -- skynet.fork(__loop__) | |
115 | + skynet.register "LOGD" | |
116 | +end | |
117 | + | |
118 | +skynet.start(__init__) | ... | ... |
... | ... | @@ -0,0 +1,109 @@ |
1 | + | |
2 | +local _M = {} | |
3 | + | |
4 | +local mc = require "skynet.multicast" | |
5 | +local datacenter = require "skynet.datacenter" | |
6 | +local skynet = require "skynet" | |
7 | + | |
8 | +local chan_w, chan_g | |
9 | +local chan_ws = {} | |
10 | + | |
11 | +function _M.sub_world(w_channel) | |
12 | + chan_w = mc.new { | |
13 | + channel = w_channel, | |
14 | + dispatch = function (channel, source, ...) | |
15 | + if select("#", ...) == 0 then | |
16 | + return | |
17 | + end | |
18 | + local actionCode, bin = ... | |
19 | + if SendPacket then | |
20 | + SendPacket(actionCode, bin) | |
21 | + end | |
22 | + end | |
23 | + } | |
24 | + chan_w:subscribe() | |
25 | +end | |
26 | + | |
27 | +function _M.usub_world() | |
28 | + if chan_w then | |
29 | + chan_w:unsubscribe() | |
30 | + chan_w = nil | |
31 | + end | |
32 | +end | |
33 | + | |
34 | +function _M.sub_worlds(w_channels) | |
35 | + for _, cell in pairs(w_channels) do | |
36 | + local chan = mc.new { | |
37 | + channel = cell, | |
38 | + dispatch = function (channel, source, ...) | |
39 | + if select("#", ...) == 0 then | |
40 | + return | |
41 | + end | |
42 | + local actionCode, bin = ... | |
43 | + if SendPacket then | |
44 | + SendPacket(actionCode, bin) | |
45 | + end | |
46 | + end | |
47 | + } | |
48 | + chan:subscribe() | |
49 | + table.insert(chan_ws, chan) | |
50 | + end | |
51 | +end | |
52 | + | |
53 | +function _M.pub_world(actionCode, bin) | |
54 | + if not bin then | |
55 | + return | |
56 | + end | |
57 | + if #chan_ws > 0 then | |
58 | + for _, chan in pairs(chan_ws) do | |
59 | + chan:publish(actionCode, bin) | |
60 | + end | |
61 | + return | |
62 | + end | |
63 | + if not bin or not chan_w then return end | |
64 | + chan_w:publish(actionCode, bin) | |
65 | +end | |
66 | + | |
67 | +function _M.sub_union(gid, chan) | |
68 | + chan_g = mc.new { | |
69 | + channel = chan, | |
70 | + dispatch = function (channel, source, ...) | |
71 | + if select("#", ...) == 0 then | |
72 | + return | |
73 | + end | |
74 | + local actionCode, bin = ... | |
75 | + if SendPacket then | |
76 | + SendPacket(actionCode, bin) | |
77 | + end | |
78 | + end | |
79 | + } | |
80 | + chan_g:subscribe() | |
81 | +end | |
82 | + | |
83 | +function _M.usub_union() | |
84 | + if chan_g then | |
85 | + chan_g:unsubscribe() | |
86 | + chan_g = nil | |
87 | + end | |
88 | +end | |
89 | + | |
90 | +function _M.pub_union(actionCode, bin) | |
91 | + if not bin or not chan_g then return end | |
92 | + chan_g:publish(actionCode, bin) | |
93 | +end | |
94 | + | |
95 | +function _M.pub_person(fromFd, toRoleId, actionCode, bin) | |
96 | + if 0 == redisproxy:exists(string.format("role:%d", toRoleId)) then | |
97 | + return SYS_ERROR_CHAT_NOT_EXIST | |
98 | + end | |
99 | + -- 若在线,实时发送聊天信息 | |
100 | + local agent = datacenter.get("agent", toRoleId) | |
101 | + if agent then | |
102 | + SendPacket(actionCode, bin, agent.fd) | |
103 | + SendPacket(actionCode, bin, fromFd) | |
104 | + return | |
105 | + end | |
106 | + return SYS_ERROR_CHAT_NOT_ONLINE | |
107 | +end | |
108 | + | |
109 | +return _M | |
0 | 110 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,56 @@ |
1 | +local skynet = require "skynet" | |
2 | +require "skynet.manager" | |
3 | +local crab = require "crab.c" | |
4 | + | |
5 | +local table_insert = table.insert | |
6 | +local table_unpack = table.unpack | |
7 | +local mode, id, dict = ... | |
8 | + | |
9 | +local function toutf8(name) | |
10 | + local t = {} | |
11 | + for _, v in utf8.codes(name) do | |
12 | + table_insert(t, v) | |
13 | + end | |
14 | + return t | |
15 | +end | |
16 | + | |
17 | +if mode == "sub" then | |
18 | + local CMD = {} | |
19 | + dict = tonumber(dict) | |
20 | + | |
21 | + function CMD.check(name) | |
22 | + if name:find("%c") then | |
23 | + return false | |
24 | + end | |
25 | + local utftb = toutf8(name) | |
26 | + if crab.filter(dict, utftb) then | |
27 | + return false, utf8.char(table_unpack(utftb)) | |
28 | + end | |
29 | + return true | |
30 | + end | |
31 | + | |
32 | + skynet.start(function() | |
33 | + skynet.dispatch("lua", function(_, _, command, ...) | |
34 | + local f = CMD[command] | |
35 | + skynet.ret(skynet.pack(f(...))) | |
36 | + end) | |
37 | + | |
38 | + skynet.register(string.format("NAMED%d", id)) | |
39 | + end) | |
40 | +else | |
41 | + skynet.start(function() | |
42 | + local ok, forbidNames = pcall(require, "csvdata.name_forbid") | |
43 | + if not ok then forbidNames = {} end | |
44 | + | |
45 | + local words = {} | |
46 | + for _, data in ipairs(forbidNames) do | |
47 | + local ok, utftb = pcall(toutf8, data.name) | |
48 | + if ok then table.insert(words, utftb) end | |
49 | + end | |
50 | + local d = crab.open(words) | |
51 | + | |
52 | + for i = 1, 5 do | |
53 | + skynet.newservice(SERVICE_NAME, "sub", i, d) | |
54 | + end | |
55 | + end) | |
56 | +end | ... | ... |
... | ... | @@ -0,0 +1,104 @@ |
1 | +skynet = require "skynet" | |
2 | +redisproxy = require "shared.redisproxy" | |
3 | +netpack = require "skynet.netpack" | |
4 | +datacenter = require "skynet.datacenter" | |
5 | +sharedata = require "skynet.sharedata" | |
6 | +mcast_util = require "services/mcast_util" | |
7 | +globalCsv = require "csvdata/GlobalDefine" | |
8 | + | |
9 | +local socket = require "skynet.socket" | |
10 | +local harbor = require "skynet.harbor" | |
11 | + | |
12 | +require "shared.init" | |
13 | +require "utils.init" | |
14 | +require "ProtocolCode" | |
15 | +require "skynet.manager" | |
16 | +require "RedisKeys" | |
17 | +require "GlobalVar" | |
18 | + | |
19 | +local port = tonumber(...) | |
20 | +local SEP = "$end$" | |
21 | +local actions = {} | |
22 | + | |
23 | +SendPacket = function(...) end | |
24 | +rpcAgent = function(...) end | |
25 | +rpcParter = function(...) end | |
26 | +rpcRole = function(...) end | |
27 | +rpcUnion = function(...) end | |
28 | +rpcOtherUnion = function(...) end | |
29 | + | |
30 | +skynet.register_protocol { | |
31 | + name = "role", | |
32 | + id = 12, | |
33 | + pack = skynet.pack, | |
34 | + unpack = skynet.unpack, | |
35 | +} | |
36 | + | |
37 | +local function process(request) | |
38 | + local req = json.decode(request) | |
39 | + if req["handle"] == "checkStatus" then | |
40 | + return "success" | |
41 | + end | |
42 | + | |
43 | + local modName, funcName = req["handle"]:match("([%w_]+)%.([%w_]+)") | |
44 | + if not modName or not funcName then | |
45 | + return table.concat({"handle格式错误", modName, funcName}, "\t") | |
46 | + end | |
47 | + local action = require("actions." .. modName .. "Action") | |
48 | + return action[funcName](req) | |
49 | +end | |
50 | + | |
51 | +local function main_loop(stdin, send) | |
52 | + socket.lock(stdin) | |
53 | + while true do | |
54 | + local request = socket.readline(stdin, SEP) | |
55 | + if not request then | |
56 | + break | |
57 | + end | |
58 | + | |
59 | + local response = process(request) | |
60 | + if response then send(response) end | |
61 | + end | |
62 | + socket.unlock(stdin) | |
63 | +end | |
64 | + | |
65 | +local function start() | |
66 | + local listen_socket = socket.listen("0.0.0.0", port) | |
67 | + print("Start nginx proxy at port: ", port) | |
68 | + | |
69 | + redisd = harbor.queryname("REDIS") | |
70 | + | |
71 | + csvdb = sharedata.query("csvdata") | |
72 | + | |
73 | + socket.start(listen_socket, function(id, addr) | |
74 | + local function send(...) | |
75 | + local t = { ... } | |
76 | + socket.write(id, table.concat(t, "\t")) | |
77 | + socket.write(id, SEP) | |
78 | + end | |
79 | + socket.start(id) | |
80 | + skynet.fork(main_loop, id, send) | |
81 | + end) | |
82 | + | |
83 | + -- 注册全服广播 | |
84 | + local channels = {} | |
85 | + for i = 1, 10 do | |
86 | + local channel = datacenter.get("MC_W_CHANNEL" .. i) | |
87 | + if channel then | |
88 | + table.insert(channels, channel) | |
89 | + end | |
90 | + end | |
91 | + if #channels > 0 then | |
92 | + mcast_util.sub_worlds(channels) | |
93 | + end | |
94 | +end | |
95 | + | |
96 | +local function __init__() | |
97 | + skynet.dispatch("lua", function(_,_, command, ...) | |
98 | + if command == "start" then | |
99 | + skynet.ret(skynet.pack(start(...))) | |
100 | + end | |
101 | + end) | |
102 | +end | |
103 | + | |
104 | +skynet.start(__init__) | ... | ... |
... | ... | @@ -0,0 +1,45 @@ |
1 | +--[[ | |
2 | + deque 为了减少锁竞争,尽量保证 a服务 push;b服务 pop | |
3 | +]] | |
4 | +local skynet = require "skynet" | |
5 | +local deque = require "deque" | |
6 | + | |
7 | +local CMD = {} | |
8 | +local factory | |
9 | + | |
10 | +local dead = 0 | |
11 | +-- agent死亡,通知poold补充,当累计到5个agent时,马上生成5个agent放入poold中 | |
12 | +-- 当然这里也可以写得更复杂,参考redis落地规则 | |
13 | +function CMD.feed() | |
14 | + dead = dead + 1 | |
15 | + if dead == 5 then | |
16 | + dead = 0 | |
17 | + for i=1, 5 do | |
18 | + factory:push(skynet.newservice("agent")) | |
19 | + end | |
20 | + end | |
21 | +end | |
22 | + | |
23 | +-- 系统启动,生成count个agent放入双端队列中 | |
24 | +function CMD.start(count) | |
25 | + factory, addr = deque.new(count) | |
26 | + | |
27 | + for i=1, count do | |
28 | + factory:push(skynet.newservice("agent")) | |
29 | + end | |
30 | + | |
31 | + return addr | |
32 | +end | |
33 | + | |
34 | +local function __init__() | |
35 | + skynet.dispatch("lua", function (_, _, command, ...) | |
36 | + local f = CMD[command] | |
37 | + if command == "start" then | |
38 | + skynet.ret(skynet.pack(f(...))) | |
39 | + return | |
40 | + end | |
41 | + f(...) | |
42 | + end) | |
43 | +end | |
44 | + | |
45 | +skynet.start(__init__) | ... | ... |
... | ... | @@ -0,0 +1,39 @@ |
1 | +local skynet = require "skynet" | |
2 | +require "skynet.manager" | |
3 | +local redis = require "skynet.db.redis" | |
4 | + | |
5 | +local db | |
6 | + | |
7 | +local command = {} | |
8 | + | |
9 | +function command.open(conf) | |
10 | + db = redis.connect({ | |
11 | + host = conf.redishost, | |
12 | + port = conf.redisport, | |
13 | + db = conf.redisdb or 0, | |
14 | + auth = conf.auth, | |
15 | + }) | |
16 | + | |
17 | + --[[ | |
18 | + local csvdata = require("csvdata.world_boss_battle") | |
19 | + for i=1, #csvdata do | |
20 | + for j=1, #csvdata[i] do | |
21 | + db:del(string.format("boss:%d:%d", i, j)) | |
22 | + end | |
23 | + end | |
24 | + --]] | |
25 | + db:del("rtpvp") | |
26 | + | |
27 | +end | |
28 | + | |
29 | +skynet.start(function() | |
30 | + skynet.dispatch("lua", function(session, address, cmd, ...) | |
31 | + if cmd == "open" then | |
32 | + local f = command[string.lower(cmd)] | |
33 | + skynet.ret(skynet.pack(f(...))) | |
34 | + else | |
35 | + skynet.ret(skynet.pack(db[string.lower(cmd)](db, ...))) | |
36 | + end | |
37 | + end) | |
38 | + skynet.register "REDIS" | |
39 | +end) | |
0 | 40 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,143 @@ |
1 | +require "shared.init" | |
2 | +require "utils.init" | |
3 | +require "ProtocolCode" | |
4 | +require "GlobalVar" | |
5 | +require "RedisKeys" | |
6 | +require "skynet.manager" | |
7 | + | |
8 | +local sharedata = require "sharedata" | |
9 | +local redisproxy = require "shared.redisproxy" | |
10 | +local datacenter = require "datacenter" | |
11 | +local queue = require "skynet.queue" | |
12 | + | |
13 | +skynet = require "skynet" | |
14 | +globalCsv = require "csvdata.GlobalDefine" | |
15 | + | |
16 | +local table_pack = table.pack | |
17 | +local table_unpack = table.unpack | |
18 | +local string_format = string.format | |
19 | +local pairs = pairs | |
20 | +local ipairs = ipairs | |
21 | +local tonumber = tonumber | |
22 | + | |
23 | +-- 维护在线状态 | |
24 | + | |
25 | +local unionInfo, CMD, cs = {}, {} | |
26 | + | |
27 | +skynet.register_protocol { | |
28 | + name = "role", | |
29 | + id = 12, | |
30 | + pack = skynet.pack, | |
31 | + unpack = skynet.unpack, | |
32 | +} | |
33 | + | |
34 | +local function handle_timeout() | |
35 | + local now = skynet.timex() | |
36 | + unionInfo:onTimer(now) | |
37 | + skynet.timeout(100, handle_timeout) | |
38 | +end | |
39 | + | |
40 | +--[[ | |
41 | +getProperties({"field1", "field2", ...}) | |
42 | +return: {field1 = value1, field2 = value2, ...} | |
43 | +------- | |
44 | +setProperties({field1 = value1, field2 = value2, ...}) | |
45 | +]] | |
46 | +function rpcRole(roleId, funcName, ...) | |
47 | + if not unionInfo.members[roleId] then return end | |
48 | + local serv = unionInfo.members[roleId].serv | |
49 | + if serv then | |
50 | + if funcName == "getProperties" then | |
51 | + return skynet.call(serv, "role", funcName, table_unpack(...)) | |
52 | + else | |
53 | + return skynet.call(serv, "role", funcName, ...) | |
54 | + end | |
55 | + else | |
56 | + local rediskey = string_format("role:%d", roleId) | |
57 | + if funcName == "getProperty" then | |
58 | + return redisproxy:hget(rediskey, ...) | |
59 | + elseif funcName == "setProperty" then | |
60 | + return redisproxy:hset(rediskey, ...) | |
61 | + elseif funcName == "getProperties" then | |
62 | + local sRole = require "models.Role" | |
63 | + local fields = table_pack(...) | |
64 | + local rets = redisproxy:hmget(rediskey, table_unpack(fields, 1, fields.n)) | |
65 | + local result = {} | |
66 | + for i=1, fields.n do | |
67 | + local typ = sRole.schema[fields[i]][1] | |
68 | + local def = sRole.schema[fields[i]][2] | |
69 | + if typ == "number" then | |
70 | + result[fields[i]] = tonumber(rets[i] or def) | |
71 | + else | |
72 | + result[fields[i]] = rets[i] | |
73 | + end | |
74 | + end | |
75 | + return result | |
76 | + elseif funcName == "setProperties" then | |
77 | + local fields = ... | |
78 | + local params = {} | |
79 | + for field, value in pairs(fields) do | |
80 | + params[#params+1] = field | |
81 | + params[#params+1] = value | |
82 | + end | |
83 | + return redisproxy:hmset(rediskey, table_unpack(params)) | |
84 | + end | |
85 | + end | |
86 | +end | |
87 | + | |
88 | +-- 加载联盟数据 | |
89 | +function CMD.load(unionId) | |
90 | + unionInfo = require("models.Union").new({key = UNION_KEY:format(unionId)}) | |
91 | + unionInfo:load() | |
92 | + unionInfo:loadMembers() | |
93 | +end | |
94 | + | |
95 | +-- 创建一个联盟 | |
96 | +function CMD.new(roleId) | |
97 | + local unionId = redisproxy:hincrby("autoincrement_set", "union", 1) | |
98 | + unionInfo = require("models.Union").new({ | |
99 | + key = UNION_KEY:format(unionId), | |
100 | + master = roleId | |
101 | + }) | |
102 | + unionInfo:create() | |
103 | + unionInfo:addMember(roleId, true) | |
104 | + redisproxy:sadd(UNION_SET, unionId) | |
105 | + return unionId | |
106 | +end | |
107 | + | |
108 | +-- 登录/登出 需要向联盟服务报备 | |
109 | +function CMD.sign(cmd, roleId, serv) | |
110 | + if not unionInfo.members[roleId] then return end | |
111 | + if cmd == "login" then | |
112 | + unionInfo.members[roleId].serv = serv | |
113 | + elseif cmd == "logout" then | |
114 | + unionInfo.members[roleId].serv = nil | |
115 | + end | |
116 | +end | |
117 | + | |
118 | +local function __init__() | |
119 | + skynet.dispatch("lua", function(_, _, command, ...) | |
120 | + cs(function (...) | |
121 | + if CMD[command] then | |
122 | + skynet.ret(skynet.pack(CMD[command](...))) | |
123 | + return | |
124 | + else | |
125 | + if unionInfo and unionInfo[submethod] then | |
126 | + if command == 'dismiss' then | |
127 | + unionInfo:dismiss(...) | |
128 | + return | |
129 | + end | |
130 | + local result = unionInfo[submethod](unionInfo, ...) | |
131 | + skynet.ret(skynet.pack(result)) | |
132 | + return | |
133 | + end | |
134 | + end | |
135 | + skynet.error("uniond commond error cmd=", command) | |
136 | + end) | |
137 | + end) | |
138 | + skynet.register("UNIOND") | |
139 | + csvdb = sharedata.query("csvdata") | |
140 | + cs = queue() | |
141 | +end | |
142 | + | |
143 | +skynet.start(__init__) | |
0 | 144 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,125 @@ |
1 | +local skynet = require "skynet" | |
2 | +local redisproxy = require "shared.redisproxy" | |
3 | +local socket = require "skynet.socket" | |
4 | +local netpack = require "skynet.netpack" | |
5 | +local datacenter = require "skynet.datacenter" | |
6 | +local snax = require "skynet.snax" | |
7 | +local agent_ctrl = require "services.agent_ctrl" | |
8 | +local xxtea = require "xxtea" | |
9 | +local mc = require "skynet.multicast" | |
10 | + | |
11 | +require "ProtocolCode" | |
12 | +require "GlobalVar" | |
13 | +require "shared.init" | |
14 | +require "utils.init" | |
15 | + | |
16 | +local CMD, SOCKET = {}, {} | |
17 | +local globald | |
18 | + | |
19 | +local pool_size = tonumber(...) | |
20 | + | |
21 | +function SOCKET.open(fd, addr) | |
22 | + skynet.call(gate_serv, "lua", "accept" , fd) | |
23 | + agent_ctrl:socket_open(fd, addr) | |
24 | +end | |
25 | + | |
26 | +function SOCKET.close(fd) | |
27 | + print("socket close", fd) | |
28 | + agent_ctrl:socket_close(fd) | |
29 | +end | |
30 | + | |
31 | +function SOCKET.error(fd, msg) | |
32 | + print("socket error",fd, msg) | |
33 | + agent_ctrl:socket_error(fd) | |
34 | +end | |
35 | + | |
36 | +function SOCKET.data(fd, msg) | |
37 | + local cmd = string.unpack("H", string.sub(msg, 1, 2)) | |
38 | + if cmd == actionCodes.Role_queryLoginRpc then | |
39 | + local data = MsgPack.unpack(xxtea.decrypt(string.sub(msg, 3), XXTEA_KEY)) | |
40 | + -- TODO: 先检测uid的合法性 | |
41 | + if not data or not data.uid then return end | |
42 | + agent_ctrl:query_agent(fd, data.uid) | |
43 | + end | |
44 | +end | |
45 | + | |
46 | +local use_logd = tonumber(skynet.getenv "logd") | |
47 | + | |
48 | +-- @desc: agent状态定时检测 | |
49 | +function check_agent_status() | |
50 | + pcall(agent_ctrl.check_agent_status, agent_ctrl) | |
51 | + skynet.timeout(1, check_agent_status) | |
52 | +end | |
53 | + | |
54 | +-- 创建world以及union channel 用于广播 | |
55 | +function create_mutilcast() | |
56 | + for i = 1, 10 do | |
57 | + local chan_w = mc:new() | |
58 | + datacenter.set("MC_W_CHANNEL" .. i, chan_w.channel) | |
59 | + end | |
60 | +end | |
61 | + | |
62 | +function CMD.start(conf) | |
63 | + skynet.call(gate_serv, "lua", "open" , conf) | |
64 | + skynet.call(redisd, "lua", "open", conf) | |
65 | + | |
66 | + if use_logd == 1 then | |
67 | + skynet.call(logd, "lua", "open", conf) | |
68 | + end | |
69 | + -- 开启agent状态检测定时器 | |
70 | + check_agent_status() | |
71 | + -- 创建广播服务 | |
72 | + create_mutilcast() | |
73 | + | |
74 | + skynet.call(conf.ngxd, "lua", "start") | |
75 | + | |
76 | + -- roomServer = skynet.newservice("services/roomServer") | |
77 | + -- skynet.call(roomServer, "lua", "start") | |
78 | + | |
79 | + globald = skynet.newservice("services/globald") | |
80 | + skynet.call(globald, "lua", "start") | |
81 | + | |
82 | + skynet.newservice("services/dbseed") | |
83 | + | |
84 | +end | |
85 | + | |
86 | +function CMD.forceClose(fd) | |
87 | + agent_ctrl:exit_agent(fd) | |
88 | +end | |
89 | + | |
90 | +skynet.start(function() | |
91 | + skynet.dispatch("lua", function(session, source, cmd, subcmd, ...) | |
92 | + if cmd == "socket" then | |
93 | + local f = SOCKET[subcmd] | |
94 | + f(...) | |
95 | + -- socket api don't need return | |
96 | + else | |
97 | + local f = assert(CMD[cmd]) | |
98 | + skynet.ret(skynet.pack(f(subcmd, ...))) | |
99 | + end | |
100 | + end) | |
101 | + -- 数据库服务 | |
102 | + redisd = skynet.newservice("services/redisd") | |
103 | + | |
104 | + -- load all csv data | |
105 | + skynet.newservice("services/csvdatad") | |
106 | + print("launch csvdatad ...") | |
107 | + | |
108 | + -- 日志服务 | |
109 | + if use_logd == 1 then | |
110 | + logd = skynet.newservice("services/logd") | |
111 | + end | |
112 | + | |
113 | + local poold = skynet.newservice("services/poold") | |
114 | + local obj = skynet.call(poold, "lua", "start", pool_size) | |
115 | + | |
116 | + agent_ctrl:init(obj, poold) | |
117 | + | |
118 | + print(string.format("launch %d agent at the beginning", pool_size)) | |
119 | + | |
120 | + -- 全局工具函数 | |
121 | + skynet.newservice("services/named") | |
122 | + skynet.newservice("services/chated") | |
123 | + -- 网关服务 | |
124 | + gate_serv = skynet.newservice("gate") | |
125 | +end) | ... | ... |
... | ... | @@ -0,0 +1,240 @@ |
1 | +local ModelBase = class("ModelBase") | |
2 | +ModelBase.key = "key" | |
3 | +ModelBase.schema = { | |
4 | + key = {"string"} | |
5 | +} | |
6 | +ModelBase.fields = {} -- 数据库字段 field, update 是否立即更新 | |
7 | + | |
8 | +local string_format = string.format | |
9 | +local table_insert = table.insert | |
10 | +local table_unpack = table.unpack | |
11 | +local assert = assert | |
12 | +local next = next | |
13 | +local ipairs = ipairs | |
14 | +local pairs = pairs | |
15 | +local tostring = tostring | |
16 | +local tonumber = tonumber | |
17 | +local redisproxy = redisproxy | |
18 | + | |
19 | +local function filterProperties(properties, filter) | |
20 | + for i, field in ipairs(filter) do | |
21 | + properties[field] = nil | |
22 | + end | |
23 | +end | |
24 | + | |
25 | +function ModelBase:ctor(properties) | |
26 | + self.isModelBase_ = true | |
27 | + -- self.dirtyFields = {} | |
28 | + | |
29 | + if type(properties) ~= "table" then properties = {} end | |
30 | + self:setProperties(properties, true) --缺少的域将设置默认值 | |
31 | +end | |
32 | + | |
33 | +--[[-- | |
34 | + | |
35 | +返回对象的 ID 值。 | |
36 | + | |
37 | +**Returns:** | |
38 | + | |
39 | +- ID 值 | |
40 | + | |
41 | +]] | |
42 | +function ModelBase:getKey() | |
43 | + local id = self[self.class.key .. "_"] | |
44 | + assert(id ~= nil, string_format("%s [%s:getKey()] Invalid key", tostring(self), self.class.__cname)) | |
45 | + return id | |
46 | +end | |
47 | + | |
48 | +function ModelBase:load(properties) | |
49 | + if not self:isValidKey() then | |
50 | + print(string_format("%s [%s:id] should be set before load", tostring(self), self.class.__cname)) | |
51 | + return false | |
52 | + end | |
53 | + | |
54 | + if not properties then | |
55 | + properties = redisproxy:hgetall(self:getKey()) | |
56 | + end | |
57 | + | |
58 | + if not next(properties) then return false end | |
59 | + | |
60 | + self:setProperties(properties, true) | |
61 | + | |
62 | + self:onLoad() | |
63 | + | |
64 | + return true | |
65 | +end | |
66 | + | |
67 | +--创建model对应的redis数据, 必须已经设置了ID | |
68 | +function ModelBase:create() | |
69 | + if not self:isValidKey() then | |
70 | + print(string_format("%s [%s:key] should be set before create", tostring(self), self.class.__cname)) | |
71 | + return nil | |
72 | + end | |
73 | + | |
74 | + --将所有的域都置为dirty, 存储到redis | |
75 | + -- for fieldName, update in pairs(self.class.fields) do | |
76 | + -- self.dirtyFields[fieldName] = true | |
77 | + -- end | |
78 | + self:save() | |
79 | + self:onCreate() | |
80 | + | |
81 | + return self | |
82 | +end | |
83 | + | |
84 | +function ModelBase:save() | |
85 | + local redisProperties = self:getProperties() | |
86 | + | |
87 | + local params = {} | |
88 | + for fieldName, value in pairs(redisProperties) do | |
89 | + -- if self.dirtyFields[fieldName] then | |
90 | + local propname = fieldName .. "_" | |
91 | + table_insert(params, fieldName) | |
92 | + table_insert(params, self[propname]) | |
93 | + -- end | |
94 | + end | |
95 | + if next(params) then | |
96 | + redisproxy:hmset(self:getKey(), table_unpack(params)) | |
97 | + end | |
98 | +end | |
99 | + | |
100 | +--[[-- | |
101 | + | |
102 | +确定对象是否设置了有效的 key。 | |
103 | + | |
104 | +]] | |
105 | +function ModelBase:isValidKey() | |
106 | + local propname = self.class.key .. "_" | |
107 | + local key = self[propname] | |
108 | + return type(key) == "string" and key ~= "" | |
109 | +end | |
110 | + | |
111 | +--[[-- | |
112 | + | |
113 | +修改对象的属性。 | |
114 | +NOTE: 如果properties缺少schema中的域, 将用默认值来填充 | |
115 | + | |
116 | +**Parameters:** | |
117 | + | |
118 | +- properties: 包含属性值的数组 | |
119 | + | |
120 | +]] | |
121 | +function ModelBase:setProperties(properties, notWrite) | |
122 | + assert(type(properties) == "table", "Invalid properties") | |
123 | + -- string_format("%s [%s:setProperties()] Invalid properties", tostring(self), self.class.__cname)) | |
124 | + | |
125 | + local params = {} | |
126 | + for field, schema in pairs(self.class.schema) do | |
127 | + local typ, def = table_unpack(schema) | |
128 | + local propname = field .. "_" | |
129 | + | |
130 | + local val = properties[field] or def | |
131 | + if val ~= nil then | |
132 | + if typ == "number" then val = tonumber(val) end | |
133 | + assert(type(val) == typ, | |
134 | + string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s", | |
135 | + tostring(self), self.class.__cname, field, typ, type(val))) | |
136 | + self[propname] = val | |
137 | + table_insert(params, field) | |
138 | + table_insert(params, val) | |
139 | + end | |
140 | + end | |
141 | + if not notWrite and next(params) then | |
142 | + redisproxy:hmset(self:getKey(), table_unpack(params)) | |
143 | + end | |
144 | +end | |
145 | + | |
146 | +--[[-- | |
147 | + | |
148 | +取得对象的属性值。 | |
149 | + | |
150 | +**Parameters:** | |
151 | + | |
152 | +- fields: 要取得哪些属性的值,如果未指定该参数,则返回 fields 中设定的属性 | |
153 | +- filter: 要从结果中过滤掉哪些属性,如果未指定则不过滤 | |
154 | + | |
155 | +**Returns:** | |
156 | + | |
157 | +- 包含属性值的数组 | |
158 | + | |
159 | +]] | |
160 | +function ModelBase:getProperties(fields, filter) | |
161 | + local schema = self.class.schema | |
162 | + if type(fields) ~= "table" then fields = table.keys(self.class.fields) end | |
163 | + | |
164 | + local properties = {} | |
165 | + for i, field in ipairs(fields) do | |
166 | + local propname = field .. "_" | |
167 | + local typ = schema[field][1] | |
168 | + local val = self[propname] | |
169 | + assert(type(val) == typ, | |
170 | + string_format("%s [%s:getProperties()] Type mismatch, %s expected %s, actual is %s", | |
171 | + tostring(self), self.class.__cname, field, typ, type(val))) | |
172 | + properties[field] = val | |
173 | + end | |
174 | + | |
175 | + if type(filter) == "table" then | |
176 | + filterProperties(properties, filter) | |
177 | + end | |
178 | + | |
179 | + return properties | |
180 | +end | |
181 | + | |
182 | +function ModelBase:getProperty(property) | |
183 | + if type(property) ~= "string" then return nil end | |
184 | + if not self.class.schema[property] then return nil end | |
185 | + return self:getProperties({property})[property] | |
186 | +end | |
187 | + | |
188 | +function ModelBase:setProperty(property, value, update) | |
189 | + if not self.class.schema[property] then | |
190 | + print(string_format("%s [%s:setProperty()] Invalid property : %s", | |
191 | + tostring(self), self.class.__cname, property)) | |
192 | + return | |
193 | + end | |
194 | + | |
195 | + local typ, def = table_unpack(self.class.schema[property]) | |
196 | + local propname = property .. "_" | |
197 | + | |
198 | + if typ == "number" then value = tonumber(value) end | |
199 | + assert(type(value) == typ, | |
200 | + string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s", | |
201 | + tostring(self), self.class.__cname, property, typ, type(value))) | |
202 | + self[propname] = value | |
203 | + | |
204 | + if self.class.fields[property] or update then | |
205 | + redisproxy:hset(self:getKey(), property, value) | |
206 | + else | |
207 | + -- self.dirtyFields[property] = true -- record the fields been modified | |
208 | + end | |
209 | +end | |
210 | + | |
211 | +function ModelBase:incrProperty(property, value, update) | |
212 | + if not self.class.schema[property] then | |
213 | + print(string_format("%s [%s:setProperty()] Invalid property : %s", | |
214 | + tostring(self), self.class.__cname, property)) | |
215 | + return | |
216 | + end | |
217 | + | |
218 | + local typ, def = table_unpack(self.class.schema[property]) | |
219 | + local propname = property .. "_" | |
220 | + | |
221 | + if typ == "number" then value = tonumber(value) end | |
222 | + assert(type(value) == typ, | |
223 | + string_format("%s [%s:setProperties()] Type mismatch, %s expected %s, actual is %s", | |
224 | + tostring(self), self.class.__cname, property, typ, type(value))) | |
225 | + self[propname] = self[propname] + value | |
226 | + | |
227 | + if self.class.fields[property] or update then | |
228 | + return redisproxy:hincrby(self:getKey(), property, value) | |
229 | + else | |
230 | + -- self.dirtyFields[property] = true -- record the fields been modified | |
231 | + end | |
232 | +end | |
233 | + | |
234 | +function ModelBase:onLoad() | |
235 | +end | |
236 | + | |
237 | +function ModelBase:onCreate() | |
238 | +end | |
239 | + | |
240 | +return ModelBase | |
0 | 241 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +local crypto = {} | |
2 | + | |
3 | +function crypto.encryptXXTEA(plaintext, key) | |
4 | + return CCCrypto:encryptXXTEALua(plaintext, string.len(plaintext), key, string.len(key)) | |
5 | +end | |
6 | + | |
7 | +function crypto.decryptXXTEA(ciphertext, key) | |
8 | + return CCCrypto:decryptXXTEALua(ciphertext, string.len(ciphertext), key, string.len(key)) | |
9 | +end | |
10 | + | |
11 | +function crypto.encodeBase64(plaintext) | |
12 | + return CCCrypto:encodeBase64Lua(plaintext, string.len(plaintext)) | |
13 | +end | |
14 | + | |
15 | +function crypto.decodeBase64(ciphertext) | |
16 | + return CCCrypto:decodeBase64Lua(ciphertext) | |
17 | +end | |
18 | + | |
19 | +function crypto.md5(input, isRawOutput) | |
20 | + if type(isRawOutput) ~= "boolean" then isRawOutput = false end | |
21 | + return CCCrypto:MD5Lua(input, isRawOutput) | |
22 | +end | |
23 | + | |
24 | +return crypto | ... | ... |
... | ... | @@ -0,0 +1,327 @@ |
1 | +--[[ | |
2 | + | |
3 | +Copyright (c) 2011-2012 qeeplay.com | |
4 | + | |
5 | +http://dualface.github.com/quick-cocos2d-x/ | |
6 | + | |
7 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | +of this software and associated documentation files (the "Software"), to deal | |
9 | +in the Software without restriction, including without limitation the rights | |
10 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | +copies of the Software, and to permit persons to whom the Software is | |
12 | +furnished to do so, subject to the following conditions: | |
13 | + | |
14 | +The above copyright notice and this permission notice shall be included in | |
15 | +all copies or substantial portions of the Software. | |
16 | + | |
17 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
23 | +THE SOFTWARE. | |
24 | + | |
25 | +]] | |
26 | + | |
27 | +--[[-- | |
28 | + | |
29 | +Debug functions. | |
30 | + | |
31 | +## Functions ## | |
32 | + | |
33 | +- echo | |
34 | +- echoInfo | |
35 | +- echoError | |
36 | +- printf | |
37 | + | |
38 | +]] | |
39 | +local skynet = require "skynet" | |
40 | +if ngx and ngx.say then | |
41 | + echo_ = ngx.say | |
42 | +elseif CCLuaLog then | |
43 | + echo_ = CCLuaLog | |
44 | +end | |
45 | +if not echo_ then echo_ = print end | |
46 | +if not echof_ then echof_ = skynet.error end | |
47 | + | |
48 | +io.output():setvbuf('no') | |
49 | + | |
50 | +function echo(...) | |
51 | + local arr = {} | |
52 | + for i, a in ipairs({...}) do | |
53 | + arr[#arr + 1] = tostring(a) | |
54 | + end | |
55 | + | |
56 | + echo_(table.concat(arr, "\t")) | |
57 | +end | |
58 | + | |
59 | +function echof( ... ) | |
60 | + local arr = {} | |
61 | + for i, a in ipairs({...}) do | |
62 | + arr[#arr + 1] = tostring(a) | |
63 | + end | |
64 | + | |
65 | + echof_(table.concat(arr, "\t")) | |
66 | +end | |
67 | + | |
68 | +if CCLuaLog then print = echo end | |
69 | + | |
70 | +--[[-- | |
71 | + | |
72 | +Output a formatted string. | |
73 | + | |
74 | +Depends on the platform, output to console or log file. @see echo(). | |
75 | + | |
76 | +@param string format | |
77 | +@param mixed ... | |
78 | + | |
79 | +@see echo | |
80 | + | |
81 | +]] | |
82 | +function printf(fmt, ...) | |
83 | + echo(string.format(tostring(fmt), ...)) | |
84 | +end | |
85 | + | |
86 | +function echoError(fmt, ...) | |
87 | + echo(string.format("[ERR] %s%s", string.format(tostring(fmt), ...), debug.traceback("", 2))) | |
88 | +end | |
89 | + | |
90 | +function echoInfo(fmt, ...) | |
91 | + echo("[INFO] " .. string.format(tostring(fmt), ...)) | |
92 | +end | |
93 | + | |
94 | +function echoLog(tag, fmt, ...) | |
95 | + echo(string.format("[%s] %s", string.upper(tostring(tag)), string.format(tostring(fmt), ...))) | |
96 | +end | |
97 | + | |
98 | +function getPackageName(moduleName) | |
99 | + local packageName = "" | |
100 | + local pos = string.find(string.reverse(moduleName), "%.") | |
101 | + if pos then | |
102 | + packageName = string.sub(moduleName, 1, string.len(moduleName) - pos + 1) | |
103 | + end | |
104 | + return packageName | |
105 | +end | |
106 | + | |
107 | +--[[-- | |
108 | + | |
109 | +Dumps information about a variable. | |
110 | + | |
111 | +@param mixed object | |
112 | +@param string label | |
113 | +@param bool isReturnContents | |
114 | +@param int nesting | |
115 | +@return nil|string | |
116 | + | |
117 | +]] | |
118 | +function dump(object, label, isReturnContents, nesting) | |
119 | + if type(nesting) ~= "number" then nesting = 99 end | |
120 | + | |
121 | + local lookupTable = {} | |
122 | + local result = {} | |
123 | + | |
124 | + local function _v(v) | |
125 | + if type(v) == "string" then | |
126 | + v = "\"" .. v .. "\"" | |
127 | + end | |
128 | + return tostring(v) | |
129 | + end | |
130 | + | |
131 | + local traceback = string.split(debug.traceback("", 2), "\n") | |
132 | + echo("dump from: " .. string.trim(traceback[3])) | |
133 | + | |
134 | + local function _dump(object, label, indent, nest, keylen) | |
135 | + label = label or "<var>" | |
136 | + spc = "" | |
137 | + if type(keylen) == "number" then | |
138 | + spc = string.rep(" ", keylen - string.len(_v(label))) | |
139 | + end | |
140 | + if type(object) ~= "table" then | |
141 | + result[#result +1 ] = string.format("%s%s%s = %s", indent, _v(label), spc, _v(object)) | |
142 | + elseif lookupTable[object] then | |
143 | + result[#result +1 ] = string.format("%s%s%s = *REF*", indent, label, spc) | |
144 | + else | |
145 | + lookupTable[object] = true | |
146 | + if nest > nesting then | |
147 | + result[#result +1 ] = string.format("%s%s = *MAX NESTING*", indent, label) | |
148 | + else | |
149 | + result[#result +1 ] = string.format("%s%s = {", indent, _v(label)) | |
150 | + local indent2 = indent.." " | |
151 | + local keys = {} | |
152 | + local keylen = 0 | |
153 | + local values = {} | |
154 | + for k, v in pairs(object) do | |
155 | + keys[#keys + 1] = k | |
156 | + local vk = _v(k) | |
157 | + local vkl = string.len(vk) | |
158 | + if vkl > keylen then keylen = vkl end | |
159 | + values[k] = v | |
160 | + end | |
161 | + table.sort(keys, function(a, b) | |
162 | + if type(a) == "number" and type(b) == "number" then | |
163 | + return a < b | |
164 | + else | |
165 | + return tostring(a) < tostring(b) | |
166 | + end | |
167 | + end) | |
168 | + for i, k in ipairs(keys) do | |
169 | + _dump(values[k], k, indent2, nest + 1, keylen) | |
170 | + end | |
171 | + result[#result +1] = string.format("%s}", indent) | |
172 | + end | |
173 | + end | |
174 | + end | |
175 | + _dump(object, label, "- ", 1) | |
176 | + | |
177 | + if isReturnContents then | |
178 | + return table.concat(result, "\n") | |
179 | + end | |
180 | + | |
181 | + for i, line in ipairs(result) do | |
182 | + echo(line) | |
183 | + end | |
184 | +end | |
185 | + | |
186 | + | |
187 | +--[[-- | |
188 | + | |
189 | +Dumps information about a variable into file. | |
190 | + | |
191 | +@param mixed object | |
192 | +@param string label | |
193 | +@param bool isReturnContents | |
194 | +@param int nesting | |
195 | +@return nil|string | |
196 | + | |
197 | +]] | |
198 | +function dump2file(object, label, isReturnContents, nesting) | |
199 | + if type(nesting) ~= "number" then nesting = 99 end | |
200 | + | |
201 | + local lookupTable = {} | |
202 | + local result = {} | |
203 | + | |
204 | + local function _v(v) | |
205 | + if type(v) == "string" then | |
206 | + v = "\"" .. v .. "\"" | |
207 | + end | |
208 | + return tostring(v) | |
209 | + end | |
210 | + | |
211 | + local traceback = string.split(debug.traceback("", 2), "\n") | |
212 | + echo("dump from: " .. string.trim(traceback[3])) | |
213 | + | |
214 | + local function _dump(object, label, indent, nest, keylen) | |
215 | + label = label or "<var>" | |
216 | + spc = "" | |
217 | + if type(keylen) == "number" then | |
218 | + spc = string.rep(" ", keylen - string.len(_v(label))) | |
219 | + end | |
220 | + if type(object) ~= "table" then | |
221 | + result[#result +1 ] = string.format("%s%s%s = %s", indent, _v(label), spc, _v(object)) | |
222 | + elseif lookupTable[object] then | |
223 | + result[#result +1 ] = string.format("%s%s%s = *REF*", indent, label, spc) | |
224 | + else | |
225 | + lookupTable[object] = true | |
226 | + if nest > nesting then | |
227 | + result[#result +1 ] = string.format("%s%s = *MAX NESTING*", indent, label) | |
228 | + else | |
229 | + result[#result +1 ] = string.format("%s%s = {", indent, _v(label)) | |
230 | + local indent2 = indent.." " | |
231 | + local keys = {} | |
232 | + local keylen = 0 | |
233 | + local values = {} | |
234 | + for k, v in pairs(object) do | |
235 | + keys[#keys + 1] = k | |
236 | + local vk = _v(k) | |
237 | + local vkl = string.len(vk) | |
238 | + if vkl > keylen then keylen = vkl end | |
239 | + values[k] = v | |
240 | + end | |
241 | + table.sort(keys, function(a, b) | |
242 | + if type(a) == "number" and type(b) == "number" then | |
243 | + return a < b | |
244 | + else | |
245 | + return tostring(a) < tostring(b) | |
246 | + end | |
247 | + end) | |
248 | + for i, k in ipairs(keys) do | |
249 | + _dump(values[k], k, indent2, nest + 1, keylen) | |
250 | + end | |
251 | + result[#result +1] = string.format("%s}", indent) | |
252 | + end | |
253 | + end | |
254 | + end | |
255 | + _dump(object, label, "- ", 1) | |
256 | + | |
257 | + if isReturnContents then | |
258 | + return table.concat(result, "\n") | |
259 | + end | |
260 | + | |
261 | + for i, line in ipairs(result) do | |
262 | + echof(line) | |
263 | + end | |
264 | +end | |
265 | + | |
266 | +--[[-- | |
267 | + | |
268 | +Outputs or returns a parsable string representation of a variable. | |
269 | + | |
270 | +@param mixed object | |
271 | +@param string label | |
272 | +@return string | |
273 | + | |
274 | +]] | |
275 | +function vardump(object, label) | |
276 | + local lookupTable = {} | |
277 | + local result = {} | |
278 | + | |
279 | + local function _v(v) | |
280 | + if type(v) == "string" then | |
281 | + v = "\"" .. v .. "\"" | |
282 | + end | |
283 | + return tostring(v) | |
284 | + end | |
285 | + | |
286 | + local function _vardump(object, label, indent, nest) | |
287 | + label = label or "<var>" | |
288 | + local postfix = "" | |
289 | + if nest > 1 then postfix = "," end | |
290 | + if type(object) ~= "table" then | |
291 | + if type(label) == "string" then | |
292 | + result[#result +1] = string.format("%s%s = %s%s", indent, label, _v(object), postfix) | |
293 | + else | |
294 | + result[#result +1] = string.format("%s%s%s", indent, _v(object), postfix) | |
295 | + end | |
296 | + elseif not lookupTable[object] then | |
297 | + lookupTable[object] = true | |
298 | + | |
299 | + if type(label) == "string" then | |
300 | + result[#result +1 ] = string.format("%s%s = {", indent, label) | |
301 | + else | |
302 | + result[#result +1 ] = string.format("%s{", indent) | |
303 | + end | |
304 | + local indent2 = indent .. " " | |
305 | + local keys = {} | |
306 | + local values = {} | |
307 | + for k, v in pairs(object) do | |
308 | + keys[#keys + 1] = k | |
309 | + values[k] = v | |
310 | + end | |
311 | + table.sort(keys, function(a, b) | |
312 | + if type(a) == "number" and type(b) == "number" then | |
313 | + return a < b | |
314 | + else | |
315 | + return tostring(a) < tostring(b) | |
316 | + end | |
317 | + end) | |
318 | + for i, k in ipairs(keys) do | |
319 | + _vardump(values[k], k, indent2, nest + 1) | |
320 | + end | |
321 | + result[#result +1] = string.format("%s}%s", indent, postfix) | |
322 | + end | |
323 | + end | |
324 | + _vardump(object, label, "", 1) | |
325 | + | |
326 | + return table.concat(result, "\n") | |
327 | +end | ... | ... |
... | ... | @@ -0,0 +1,846 @@ |
1 | +--[[ | |
2 | + | |
3 | +Copyright (c) 2011-2012 qeeplay.com | |
4 | + | |
5 | +http://dualface.github.com/quick-cocos2d-x/ | |
6 | + | |
7 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | +of this software and associated documentation files (the "Software"), to deal | |
9 | +in the Software without restriction, including without limitation the rights | |
10 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | +copies of the Software, and to permit persons to whom the Software is | |
12 | +furnished to do so, subject to the following conditions: | |
13 | + | |
14 | +The above copyright notice and this permission notice shall be included in | |
15 | +all copies or substantial portions of the Software. | |
16 | + | |
17 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
23 | +THE SOFTWARE. | |
24 | + | |
25 | +]] | |
26 | + | |
27 | +--[[-- | |
28 | + | |
29 | +Convert to number. | |
30 | + | |
31 | +@param mixed v | |
32 | +@return number | |
33 | + | |
34 | +]] | |
35 | +function tonum(v, default) | |
36 | + default = default or 0 | |
37 | + return tonumber(v) or default | |
38 | +end | |
39 | + | |
40 | +--[[-- | |
41 | + | |
42 | +Convert to integer. | |
43 | + | |
44 | +@param mixed v | |
45 | +@return number(integer) | |
46 | + | |
47 | +]] | |
48 | +function toint(v) | |
49 | + return math.round(tonumber(v)) | |
50 | +end | |
51 | + | |
52 | +--[[-- | |
53 | + | |
54 | +Convert to boolean. | |
55 | + | |
56 | +@param mixed v | |
57 | +@return boolean | |
58 | + | |
59 | +]] | |
60 | +function tobool(v) | |
61 | + return (v ~= nil and v ~= false) | |
62 | +end | |
63 | + | |
64 | +--[[-- | |
65 | + | |
66 | +Convert to table. | |
67 | + | |
68 | +@param mixed v | |
69 | +@return table | |
70 | + | |
71 | +]] | |
72 | +function totable(v) | |
73 | + if type(v) ~= "table" then v = {} end | |
74 | + return v | |
75 | +end | |
76 | + | |
77 | +--[[-- | |
78 | + | |
79 | +Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string). string.format() alias. | |
80 | + | |
81 | +@param string format | |
82 | +@param mixed ... | |
83 | +@return string | |
84 | + | |
85 | +]] | |
86 | +function format(...) | |
87 | + return string.format(...) | |
88 | +end | |
89 | + | |
90 | +--[[-- | |
91 | + | |
92 | +Creating a copy of an table with fully replicated properties. | |
93 | + | |
94 | +**Usage:** | |
95 | + | |
96 | + -- Creating a reference of an table: | |
97 | + local t1 = {a = 1, b = 2} | |
98 | + local t2 = t1 | |
99 | + t2.b = 3 -- t1 = {a = 1, b = 3} <-- t1.b changed | |
100 | + | |
101 | + -- Createing a copy of an table: | |
102 | + local t1 = {a = 1, b = 2} | |
103 | + local t2 = clone(t1) | |
104 | + t2.b = 3 -- t1 = {a = 1, b = 2} <-- t1.b no change | |
105 | + | |
106 | + | |
107 | +@param mixed object | |
108 | +@return mixed | |
109 | + | |
110 | +]] | |
111 | +function clone(object) | |
112 | + local lookup_table = {} | |
113 | + local function _copy(object) | |
114 | + if type(object) ~= "table" then | |
115 | + return object | |
116 | + elseif lookup_table[object] then | |
117 | + return lookup_table[object] | |
118 | + end | |
119 | + local new_table = {} | |
120 | + lookup_table[object] = new_table | |
121 | + for key, value in pairs(object) do | |
122 | + new_table[_copy(key)] = _copy(value) | |
123 | + end | |
124 | + return setmetatable(new_table, getmetatable(object)) | |
125 | + end | |
126 | + return _copy(object) | |
127 | +end | |
128 | + | |
129 | +--[[-- | |
130 | + | |
131 | +Create an class. | |
132 | + | |
133 | +**Usage:** | |
134 | + | |
135 | + local Shape = class("Shape") | |
136 | + | |
137 | + -- base class | |
138 | + function Shape:ctor(shapeName) | |
139 | + self.shapeName = shapeName | |
140 | + printf("Shape:ctor(%s)", self.shapeName) | |
141 | + end | |
142 | + | |
143 | + function Shape:draw() | |
144 | + printf("draw %s", self.shapeName) | |
145 | + end | |
146 | + | |
147 | + -- | |
148 | + | |
149 | + local Circle = class("Circle", Shape) | |
150 | + | |
151 | + function Circle:ctor() | |
152 | + Circle.super.ctor(self, "circle") -- call super-class method | |
153 | + self.radius = 100 | |
154 | + end | |
155 | + | |
156 | + function Circle:setRadius(radius) | |
157 | + self.radius = radius | |
158 | + end | |
159 | + | |
160 | + function Circle:draw() -- overrideing super-class method | |
161 | + printf("draw %s, raidus = %0.2f", self.shapeName, self.raidus) | |
162 | + end | |
163 | + | |
164 | + -- | |
165 | + | |
166 | + local Rectangle = class("Rectangle", Shape) | |
167 | + | |
168 | + function Rectangle:ctor() | |
169 | + Rectangle.super.ctor(self, "rectangle") | |
170 | + end | |
171 | + | |
172 | + -- | |
173 | + | |
174 | + local circle = Circle.new() -- output: Shape:ctor(circle) | |
175 | + circle:setRaidus(200) | |
176 | + circle:draw() -- output: draw circle, radius = 200.00 | |
177 | + | |
178 | + local rectangle = Rectangle.new() -- output: Shape:ctor(rectangle) | |
179 | + rectangle:draw() -- output: draw rectangle | |
180 | + | |
181 | + | |
182 | +@param string classname | |
183 | +@param table|function super-class | |
184 | +@return table | |
185 | + | |
186 | +]] | |
187 | +function class(classname, super) | |
188 | + local superType = type(super) | |
189 | + local cls | |
190 | + | |
191 | + if superType ~= "function" and superType ~= "table" then | |
192 | + superType = nil | |
193 | + super = nil | |
194 | + end | |
195 | + | |
196 | + if superType == "function" or (super and super.__ctype == 1) then | |
197 | + -- inherited from native C++ Object | |
198 | + cls = {} | |
199 | + | |
200 | + if superType == "table" then | |
201 | + -- copy fields from super | |
202 | + for k,v in pairs(super) do cls[k] = v end | |
203 | + cls.__create = super.__create | |
204 | + cls.super = super | |
205 | + else | |
206 | + cls.__create = super | |
207 | + cls.ctor = function() end | |
208 | + end | |
209 | + | |
210 | + cls.__cname = classname | |
211 | + cls.__ctype = 1 | |
212 | + | |
213 | + function cls.new(...) | |
214 | + local instance = cls.__create(...) | |
215 | + -- copy fields from class to native object | |
216 | + for k,v in pairs(cls) do instance[k] = v end | |
217 | + instance.class = cls | |
218 | + instance:ctor(...) | |
219 | + return instance | |
220 | + end | |
221 | + | |
222 | + else | |
223 | + -- inherited from Lua Object | |
224 | + if super then | |
225 | + cls = clone(super) | |
226 | + cls.super = super | |
227 | + else | |
228 | + cls = {ctor = function() end} | |
229 | + end | |
230 | + | |
231 | + cls.__cname = classname | |
232 | + cls.__ctype = 2 -- lua | |
233 | + cls.__index = cls | |
234 | + | |
235 | + function cls.new(...) | |
236 | + local instance = setmetatable({}, cls) | |
237 | + instance.class = cls | |
238 | + instance:ctor(...) | |
239 | + return instance | |
240 | + end | |
241 | + end | |
242 | + | |
243 | + return cls | |
244 | +end | |
245 | + | |
246 | +--[[-- | |
247 | + | |
248 | +]] | |
249 | +function import(moduleName, currentModuleName) | |
250 | + local currentModuleNameParts | |
251 | + local moduleFullName = moduleName | |
252 | + local offset = 1 | |
253 | + | |
254 | + while true do | |
255 | + if string.byte(moduleName, offset) ~= 46 then -- . | |
256 | + moduleFullName = string.sub(moduleName, offset) | |
257 | + if currentModuleNameParts and #currentModuleNameParts > 0 then | |
258 | + moduleFullName = table.concat(currentModuleNameParts, ".") .. "." .. moduleFullName | |
259 | + end | |
260 | + break | |
261 | + end | |
262 | + offset = offset + 1 | |
263 | + | |
264 | + if not currentModuleNameParts then | |
265 | + if not currentModuleName then | |
266 | + local n,v = debug.getlocal(3, 1) | |
267 | + currentModuleName = v | |
268 | + end | |
269 | + | |
270 | + currentModuleNameParts = string.split(currentModuleName, ".") | |
271 | + end | |
272 | + table.remove(currentModuleNameParts, #currentModuleNameParts) | |
273 | + end | |
274 | + | |
275 | + return require(moduleFullName) | |
276 | +end | |
277 | + | |
278 | +--[[-- | |
279 | + | |
280 | +]] | |
281 | +function handler(target, method) | |
282 | + return function(...) return method(target, ...) end | |
283 | +end | |
284 | + | |
285 | +--[[-- | |
286 | + | |
287 | +]] | |
288 | +function handlerObject(object) | |
289 | + return function(event, ...) | |
290 | + if object[event] then | |
291 | + return object[event](object, ...) | |
292 | + end | |
293 | + end | |
294 | +end | |
295 | + | |
296 | +--[[-- | |
297 | + | |
298 | +Returns a associative table containing the matching values. | |
299 | + | |
300 | +@param table arr | |
301 | +@param table names | |
302 | +@return array | |
303 | + | |
304 | +]] | |
305 | +function export(arr, names) | |
306 | + local args = {} | |
307 | + for k, def in pairs(names) do | |
308 | + if type(k) == "number" then | |
309 | + args[def] = arr[def] | |
310 | + else | |
311 | + args[k] = arr[k] or def | |
312 | + end | |
313 | + end | |
314 | + return args | |
315 | +end | |
316 | + | |
317 | +--[[-- | |
318 | + | |
319 | +hecks if the given key or index exists in the table. | |
320 | + | |
321 | +@param table arr | |
322 | +@param mixed key | |
323 | +@return boolean | |
324 | + | |
325 | +]] | |
326 | +function isset(arr, key) | |
327 | + return type(arr) == "table" and arr[key] ~= nil | |
328 | +end | |
329 | + | |
330 | +--[[-- | |
331 | + | |
332 | +Rounds a float. | |
333 | + | |
334 | +@param number num | |
335 | +@return number(integer) | |
336 | + | |
337 | +]] | |
338 | +function math.round(num) | |
339 | + return math.floor(num + 0.5) | |
340 | +end | |
341 | + | |
342 | +--[[-- | |
343 | + | |
344 | +Checks whether a file exists. | |
345 | + | |
346 | +@param string path | |
347 | +@return boolean | |
348 | + | |
349 | +]] | |
350 | +function io.exists(path) | |
351 | + local file = io.open(path, "r") | |
352 | + if file then | |
353 | + io.close(file) | |
354 | + return true | |
355 | + end | |
356 | + return false | |
357 | +end | |
358 | + | |
359 | +--[[-- | |
360 | + | |
361 | +Reads entire file into a string, or return FALSE on failure. | |
362 | + | |
363 | +@param string path | |
364 | +@return string | |
365 | + | |
366 | +]] | |
367 | +function io.readfile(path) | |
368 | + local file = io.open(path, "r") | |
369 | + if file then | |
370 | + local content = file:read("*a") | |
371 | + io.close(file) | |
372 | + return content | |
373 | + end | |
374 | + return nil | |
375 | +end | |
376 | + | |
377 | +--[[-- | |
378 | + | |
379 | +Write a string to a file, or return FALSE on failure. | |
380 | + | |
381 | +@param string path | |
382 | +@param string content | |
383 | +@param string mode | |
384 | +@return boolean | |
385 | + | |
386 | +### Note: | |
387 | +The mode string can be any of the following: | |
388 | + "r": read mode | |
389 | + "w": write mode; | |
390 | + "a": append mode; | |
391 | + "r+": update mode, all previous data is preserved; | |
392 | + "w+": update mode, all previous data is erased; (the default); | |
393 | + "a+": append update mode, previous data is preserved, writing is only allowed at the end of file. | |
394 | + | |
395 | +]] | |
396 | +function io.writefile(path, content, mode) | |
397 | + mode = mode or "w+" | |
398 | + local file = io.open(path, mode) | |
399 | + if file then | |
400 | + if file:write(content) == nil then return false end | |
401 | + io.close(file) | |
402 | + return true | |
403 | + else | |
404 | + return false | |
405 | + end | |
406 | +end | |
407 | + | |
408 | +--[[-- | |
409 | + | |
410 | +Returns information about a file path. | |
411 | + | |
412 | +**Usage:** | |
413 | + | |
414 | + local path = "/var/app/test/abc.png" | |
415 | + local pathinfo = io.pathinfo(path) | |
416 | + -- pathinfo.dirname = "/var/app/test/" | |
417 | + -- pathinfo.filename = "abc.png" | |
418 | + -- pathinfo.basename = "abc" | |
419 | + -- pathinfo.extname = ".png" | |
420 | + | |
421 | + | |
422 | +@param string path | |
423 | +@return table | |
424 | + | |
425 | +]] | |
426 | +function io.pathinfo(path) | |
427 | + local pos = string.len(path) | |
428 | + local extpos = pos + 1 | |
429 | + while pos > 0 do | |
430 | + local b = string.byte(path, pos) | |
431 | + if b == 46 then -- 46 = char "." | |
432 | + extpos = pos | |
433 | + elseif b == 47 then -- 47 = char "/" | |
434 | + break | |
435 | + end | |
436 | + pos = pos - 1 | |
437 | + end | |
438 | + | |
439 | + local dirname = string.sub(path, 1, pos) | |
440 | + local filename = string.sub(path, pos + 1) | |
441 | + extpos = extpos - pos | |
442 | + local basename = string.sub(filename, 1, extpos - 1) | |
443 | + local extname = string.sub(filename, extpos) | |
444 | + return { | |
445 | + dirname = dirname, | |
446 | + filename = filename, | |
447 | + basename = basename, | |
448 | + extname = extname | |
449 | + } | |
450 | +end | |
451 | + | |
452 | +--[[-- | |
453 | + | |
454 | +Gets file size, or return FALSE on failure. | |
455 | + | |
456 | +@param string path | |
457 | +@return number(integer) | |
458 | + | |
459 | +]] | |
460 | +function io.filesize(path) | |
461 | + local size = false | |
462 | + local file = io.open(path, "r") | |
463 | + if file then | |
464 | + local current = file:seek() | |
465 | + size = file:seek("end") | |
466 | + file:seek("set", current) | |
467 | + io.close(file) | |
468 | + end | |
469 | + return size | |
470 | +end | |
471 | + | |
472 | +--[[-- | |
473 | + | |
474 | +Count all elements in an table. | |
475 | + | |
476 | +@param table t | |
477 | +@return number(integer) | |
478 | + | |
479 | +]] | |
480 | +function table.nums(t) | |
481 | + local count = 0 | |
482 | + for k, v in pairs(t) do | |
483 | + count = count + 1 | |
484 | + end | |
485 | + return count | |
486 | +end | |
487 | + | |
488 | +--[[-- | |
489 | + | |
490 | +Return all the keys or a subset of the keys of an table. | |
491 | + | |
492 | +**Usage:** | |
493 | + | |
494 | + local t = {a = 1, b = 2, c = 3} | |
495 | + local keys = table.keys(t) | |
496 | + -- keys = {"a", "b", "c"} | |
497 | + | |
498 | + | |
499 | +@param table t | |
500 | +@return table | |
501 | + | |
502 | +]] | |
503 | +function table.keys(t) | |
504 | + local keys = {} | |
505 | + for k, v in pairs(t) do | |
506 | + keys[#keys + 1] = k | |
507 | + end | |
508 | + return keys | |
509 | +end | |
510 | + | |
511 | +--[[-- | |
512 | + | |
513 | +Return all the values of an table. | |
514 | + | |
515 | +**Usage:** | |
516 | + | |
517 | + local t = {a = "1", b = "2", c = "3"} | |
518 | + local values = table.values(t) | |
519 | + -- values = {1, 2, 3} | |
520 | + | |
521 | + | |
522 | +@param table t | |
523 | +@return table | |
524 | + | |
525 | +]] | |
526 | +function table.values(t) | |
527 | + local values = {} | |
528 | + for k, v in pairs(t) do | |
529 | + values[#values + 1] = v | |
530 | + end | |
531 | + return values | |
532 | +end | |
533 | + | |
534 | +--[[-- | |
535 | + | |
536 | +Merge tables. | |
537 | + | |
538 | +**Usage:** | |
539 | + | |
540 | + local dest = {a = 1, b = 2} | |
541 | + local src = {c = 3, d = 4} | |
542 | + table.merge(dest, src) | |
543 | + -- dest = {a = 1, b = 2, c = 3, d = 4} | |
544 | + | |
545 | + | |
546 | +@param table dest | |
547 | +@param table src | |
548 | + | |
549 | +]] | |
550 | +function table.merge(dest, src) | |
551 | + for k, v in pairs(src) do | |
552 | + dest[k] = v | |
553 | + end | |
554 | +end | |
555 | + | |
556 | + | |
557 | +--[[-- | |
558 | + | |
559 | +insert list. | |
560 | + | |
561 | +**Usage:** | |
562 | + | |
563 | + local dest = {1, 2, 3} | |
564 | + local src = {4, 5, 6} | |
565 | + table.insertTo(dest, src) | |
566 | + -- dest = {1, 2, 3, 4, 5, 6} | |
567 | + dest = {1, 2, 3} | |
568 | + table.insertTo(dest, src, 5) | |
569 | + -- dest = {1, 2, 3, nil, 4, 5, 6} | |
570 | + | |
571 | + | |
572 | +@param table dest | |
573 | +@param table src | |
574 | +@param table begin insert position for dest | |
575 | +]] | |
576 | +function table.insertTo(dest, src, begin) | |
577 | + begin = tonumber(begin) | |
578 | + if begin == nil then | |
579 | + begin = #dest + 1 | |
580 | + end | |
581 | + | |
582 | + local len = #src | |
583 | + for i = 0, len - 1 do | |
584 | + dest[i + begin] = src[i + 1] | |
585 | + end | |
586 | +end | |
587 | + | |
588 | +function table.maxkey(tb) | |
589 | + local max = 0 | |
590 | + for k, v in pairs(tb) do | |
591 | + if k > max then | |
592 | + max = k | |
593 | + end | |
594 | + end | |
595 | + return max | |
596 | +end | |
597 | + | |
598 | +function table.minkey(tb) | |
599 | + local min = math.huge | |
600 | + for k, v in pairs(tb) do | |
601 | + if k < min then | |
602 | + min = k | |
603 | + end | |
604 | + end | |
605 | + return min | |
606 | +end | |
607 | + | |
608 | +--[[-- | |
609 | + | |
610 | +Convert special characters to HTML entities. | |
611 | + | |
612 | +The translations performed are: | |
613 | + | |
614 | +- '&' (ampersand) becomes '&' | |
615 | +- '"' (double quote) becomes '"' | |
616 | +- "'" (single quote) becomes ''' | |
617 | +- '<' (less than) becomes '<' | |
618 | +- '>' (greater than) becomes '>' | |
619 | + | |
620 | +@param string input | |
621 | +@return string | |
622 | + | |
623 | +]] | |
624 | +function string.htmlspecialchars(input) | |
625 | + for k, v in pairs(string._htmlspecialchars_set) do | |
626 | + input = string.gsub(input, k, v) | |
627 | + end | |
628 | + return input | |
629 | +end | |
630 | +string._htmlspecialchars_set = {} | |
631 | +string._htmlspecialchars_set["&"] = "&" | |
632 | +string._htmlspecialchars_set["\""] = """ | |
633 | +string._htmlspecialchars_set["'"] = "'" | |
634 | +string._htmlspecialchars_set["<"] = "<" | |
635 | +string._htmlspecialchars_set[">"] = ">" | |
636 | + | |
637 | +--[[-- | |
638 | + | |
639 | +Inserts HTML line breaks before all newlines in a string. | |
640 | + | |
641 | +Returns string with '<br />' inserted before all newlines (\n). | |
642 | + | |
643 | +@param string input | |
644 | +@return string | |
645 | + | |
646 | +]] | |
647 | +function string.nl2br(input) | |
648 | + return string.gsub(input, "\n", "<br />") | |
649 | +end | |
650 | + | |
651 | +--[[-- | |
652 | + | |
653 | +Returns a HTML entities formatted version of string. | |
654 | + | |
655 | +@param string input | |
656 | +@return string | |
657 | + | |
658 | +]] | |
659 | +function string.text2html(input) | |
660 | + input = string.gsub(input, "\t", " ") | |
661 | + input = string.htmlspecialchars(input) | |
662 | + input = string.gsub(input, " ", " ") | |
663 | + input = string.nl2br(input) | |
664 | + return input | |
665 | +end | |
666 | + | |
667 | +--[[-- | |
668 | + | |
669 | +Split a string by string. | |
670 | + | |
671 | +@param string str | |
672 | +@param string delimiter | |
673 | +@return table | |
674 | + | |
675 | +]] | |
676 | +function string.split(str, delimiter) | |
677 | + if (delimiter=='') then return false end | |
678 | + local pos,arr = 0, {} | |
679 | + -- for each divider found | |
680 | + for st,sp in function() return string.find(str, delimiter, pos, true) end do | |
681 | + table.insert(arr, string.sub(str, pos, st - 1)) | |
682 | + pos = sp + 1 | |
683 | + end | |
684 | + table.insert(arr, string.sub(str, pos)) | |
685 | + return arr | |
686 | +end | |
687 | + | |
688 | +--[[-- | |
689 | + | |
690 | +Strip whitespace (or other characters) from the beginning of a string. | |
691 | + | |
692 | +@param string str | |
693 | +@return string | |
694 | + | |
695 | +]] | |
696 | +function string.ltrim(str) | |
697 | + return string.gsub(str, "^[ \t\n\r]+", "") | |
698 | +end | |
699 | + | |
700 | +--[[-- | |
701 | + | |
702 | +Strip whitespace (or other characters) from the end of a string. | |
703 | + | |
704 | +@param string str | |
705 | +@return string | |
706 | + | |
707 | +]] | |
708 | +function string.rtrim(str) | |
709 | + return string.gsub(str, "[ \t\n\r]+$", "") | |
710 | +end | |
711 | + | |
712 | +--[[-- | |
713 | + | |
714 | +Strip whitespace (or other characters) from the beginning and end of a string. | |
715 | + | |
716 | +@param string str | |
717 | +@return string | |
718 | + | |
719 | +]] | |
720 | +function string.trim(str) | |
721 | + str = string.gsub(str, "^[ \t\n\r]+", "") | |
722 | + return string.gsub(str, "[ \t\n\r]+$", "") | |
723 | +end | |
724 | + | |
725 | +--[[-- | |
726 | + | |
727 | +Make a string's first character uppercase. | |
728 | + | |
729 | +@param string str | |
730 | +@return string | |
731 | + | |
732 | +]] | |
733 | +function string.ucfirst(str) | |
734 | + return string.upper(string.sub(str, 1, 1)) .. string.sub(str, 2) | |
735 | +end | |
736 | + | |
737 | +--[[-- | |
738 | + | |
739 | +@param string str | |
740 | +@return string | |
741 | + | |
742 | +]] | |
743 | +function string.urlencodeChar(char) | |
744 | + return "%" .. string.format("%02X", string.byte(c)) | |
745 | +end | |
746 | + | |
747 | +--[[-- | |
748 | + | |
749 | +URL-encodes string. | |
750 | + | |
751 | +@param string str | |
752 | +@return string | |
753 | + | |
754 | +]] | |
755 | +function string.urlencode(str) | |
756 | + -- convert line endings | |
757 | + str = string.gsub(tostring(str), "\n", "\r\n") | |
758 | + -- escape all characters but alphanumeric, '.' and '-' | |
759 | + str = string.gsub(str, "([^%w%.%- ])", string.urlencodeChar) | |
760 | + -- convert spaces to "+" symbols | |
761 | + return string.gsub(str, " ", "+") | |
762 | +end | |
763 | + | |
764 | +--[[-- | |
765 | + | |
766 | +Get UTF8 string length. | |
767 | + | |
768 | +@param string str | |
769 | +@return int | |
770 | + | |
771 | +]] | |
772 | +function string.utf8len(str) | |
773 | + local len = #str | |
774 | + local left = len | |
775 | + local cnt = 0 | |
776 | + local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc} | |
777 | + while left ~= 0 do | |
778 | + local tmp = string.byte(str, -left) | |
779 | + local i = #arr | |
780 | + while arr[i] do | |
781 | + if tmp >= arr[i] then | |
782 | + left = left - i | |
783 | + break | |
784 | + end | |
785 | + i = i - 1 | |
786 | + end | |
787 | + cnt = cnt + 1 | |
788 | + end | |
789 | + return cnt | |
790 | +end | |
791 | + | |
792 | +--[[-- | |
793 | + | |
794 | +Return formatted string with a comma (",") between every group of thousands. | |
795 | + | |
796 | +**Usage:** | |
797 | + | |
798 | + local value = math.comma("232423.234") -- value = "232,423.234" | |
799 | + | |
800 | + | |
801 | +@param number num | |
802 | +@return string | |
803 | + | |
804 | +]] | |
805 | +function string.formatNumberThousands(num) | |
806 | + local formatted = tostring(tonumber(num)) | |
807 | + while true do | |
808 | + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') | |
809 | + if k == 0 then break end | |
810 | + end | |
811 | + return formatted | |
812 | +end | |
813 | + | |
814 | +function table.find(t, item) | |
815 | + return table.keyOfItem(t, item) ~= nil | |
816 | +end | |
817 | + | |
818 | +function table.keyOfItem(t, item) | |
819 | + for k,v in pairs(t) do | |
820 | + if v == item then return k end | |
821 | + end | |
822 | + return nil | |
823 | +end | |
824 | + | |
825 | +function table.removeItem(list, item, removeAll) | |
826 | + local rmCount = 0 | |
827 | + for i = 1, #list do | |
828 | + if list[i - rmCount] == item then | |
829 | + table.remove(list, i - rmCount) | |
830 | + if removeAll then | |
831 | + rmCount = rmCount + 1 | |
832 | + else | |
833 | + break | |
834 | + end | |
835 | + end | |
836 | + end | |
837 | +end | |
838 | + | |
839 | +function table.array2Table(arr) | |
840 | + local ret = {} | |
841 | + for i=1, #arr, 2 do | |
842 | + ret[arr[i]] = arr[i+1] | |
843 | + end | |
844 | + return ret | |
845 | +end | |
846 | + | ... | ... |
... | ... | @@ -0,0 +1,431 @@ |
1 | +----------------------------------------------------------------------------- | |
2 | +-- JSON4Lua: JSON encoding / decoding support for the Lua language. | |
3 | +-- json Module. | |
4 | +-- Author: Craig Mason-Jones | |
5 | +-- Homepage: http://github.com/craigmj/json4lua/ | |
6 | +-- Version: 1.0.0 | |
7 | +-- This module is released under the MIT License (MIT). | |
8 | +-- Please see LICENCE.txt for details. | |
9 | +-- | |
10 | +-- USAGE: | |
11 | +-- This module exposes two functions: | |
12 | +-- json.encode(o) | |
13 | +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. | |
14 | +-- json.decode(json_string) | |
15 | +-- Returns a Lua object populated with the data encoded in the JSON string json_string. | |
16 | +-- | |
17 | +-- REQUIREMENTS: | |
18 | +-- compat-5.1 if using Lua 5.0 | |
19 | +-- | |
20 | +-- CHANGELOG | |
21 | +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). | |
22 | +-- Fixed Lua 5.1 compatibility issues. | |
23 | +-- Introduced json.null to have null values in associative arrays. | |
24 | +-- json.encode() performance improvement (more than 50%) through table.concat rather than .. | |
25 | +-- Introduced decode ability to ignore /**/ comments in the JSON string. | |
26 | +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. | |
27 | +----------------------------------------------------------------------------- | |
28 | + | |
29 | +----------------------------------------------------------------------------- | |
30 | +-- Imports and dependencies | |
31 | +----------------------------------------------------------------------------- | |
32 | +local math = require('math') | |
33 | +local string = require("string") | |
34 | +local table = require("table") | |
35 | + | |
36 | +----------------------------------------------------------------------------- | |
37 | +-- Module declaration | |
38 | +----------------------------------------------------------------------------- | |
39 | +local json = {} -- Public namespace | |
40 | +local json_private = {} -- Private namespace | |
41 | + | |
42 | +-- Public functions | |
43 | + | |
44 | +-- Private functions | |
45 | +local decode_scanArray | |
46 | +local decode_scanComment | |
47 | +local decode_scanConstant | |
48 | +local decode_scanNumber | |
49 | +local decode_scanObject | |
50 | +local decode_scanString | |
51 | +local decode_scanWhitespace | |
52 | +local encodeString | |
53 | +local isArray | |
54 | +local isEncodable | |
55 | + | |
56 | +table.getn = function (t) | |
57 | + if t.n then | |
58 | + return t.n | |
59 | + else | |
60 | + local n = 0 | |
61 | + for i in pairs(t) do | |
62 | + if type(i) == "number" then | |
63 | + n = math.max(n, i) | |
64 | + end | |
65 | + end | |
66 | + return n | |
67 | + end | |
68 | +end | |
69 | + | |
70 | +----------------------------------------------------------------------------- | |
71 | +-- PUBLIC FUNCTIONS | |
72 | +----------------------------------------------------------------------------- | |
73 | +--- Encodes an arbitrary Lua object / variable. | |
74 | +-- @param v The Lua object / variable to be JSON encoded. | |
75 | +-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) | |
76 | +function json.encode (v) | |
77 | + -- Handle nil values | |
78 | + if v==nil then | |
79 | + return "null" | |
80 | + end | |
81 | + | |
82 | + local vtype = type(v) | |
83 | + | |
84 | + -- Handle strings | |
85 | + if vtype=='string' then | |
86 | + return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string | |
87 | + end | |
88 | + | |
89 | + -- Handle booleans | |
90 | + if vtype=='number' or vtype=='boolean' then | |
91 | + return tostring(v) | |
92 | + end | |
93 | + | |
94 | + -- Handle tables | |
95 | + if vtype=='table' then | |
96 | + local rval = {} | |
97 | + -- Consider arrays separately | |
98 | + local bArray, maxCount = isArray(v) | |
99 | + if bArray then | |
100 | + for i = 1,maxCount do | |
101 | + table.insert(rval, json.encode(v[i])) | |
102 | + end | |
103 | + else -- An object, not an array | |
104 | + for i,j in pairs(v) do | |
105 | + if isEncodable(i) and isEncodable(j) then | |
106 | + table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) | |
107 | + end | |
108 | + end | |
109 | + end | |
110 | + if bArray then | |
111 | + return '[' .. table.concat(rval,',') ..']' | |
112 | + else | |
113 | + return '{' .. table.concat(rval,',') .. '}' | |
114 | + end | |
115 | + end | |
116 | + | |
117 | + -- Handle null values | |
118 | + if vtype=='function' and v==null then | |
119 | + return 'null' | |
120 | + end | |
121 | + | |
122 | + assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) | |
123 | +end | |
124 | + | |
125 | + | |
126 | +--- Decodes a JSON string and returns the decoded value as a Lua data structure / value. | |
127 | +-- @param s The string to scan. | |
128 | +-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. | |
129 | +-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, | |
130 | +-- and the position of the first character after | |
131 | +-- the scanned JSON object. | |
132 | +function json.decode(s, startPos) | |
133 | + startPos = startPos and startPos or 1 | |
134 | + startPos = decode_scanWhitespace(s,startPos) | |
135 | + assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') | |
136 | + local curChar = string.sub(s,startPos,startPos) | |
137 | + -- Object | |
138 | + if curChar=='{' then | |
139 | + return decode_scanObject(s,startPos) | |
140 | + end | |
141 | + -- Array | |
142 | + if curChar=='[' then | |
143 | + return decode_scanArray(s,startPos) | |
144 | + end | |
145 | + -- Number | |
146 | + if string.find("+-0123456789.e", curChar, 1, true) then | |
147 | + return decode_scanNumber(s,startPos) | |
148 | + end | |
149 | + -- String | |
150 | + if curChar==[["]] or curChar==[[']] then | |
151 | + return decode_scanString(s,startPos) | |
152 | + end | |
153 | + if string.sub(s,startPos,startPos+1)=='/*' then | |
154 | + return decode(s, decode_scanComment(s,startPos)) | |
155 | + end | |
156 | + -- Otherwise, it must be a constant | |
157 | + return decode_scanConstant(s,startPos) | |
158 | +end | |
159 | + | |
160 | +--- The null function allows one to specify a null value in an associative array (which is otherwise | |
161 | +-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } | |
162 | +function null() | |
163 | + return null -- so json.null() will also return null ;-) | |
164 | +end | |
165 | +----------------------------------------------------------------------------- | |
166 | +-- Internal, PRIVATE functions. | |
167 | +-- Following a Python-like convention, I have prefixed all these 'PRIVATE' | |
168 | +-- functions with an underscore. | |
169 | +----------------------------------------------------------------------------- | |
170 | + | |
171 | +--- Scans an array from JSON into a Lua object | |
172 | +-- startPos begins at the start of the array. | |
173 | +-- Returns the array and the next starting position | |
174 | +-- @param s The string being scanned. | |
175 | +-- @param startPos The starting position for the scan. | |
176 | +-- @return table, int The scanned array as a table, and the position of the next character to scan. | |
177 | +function decode_scanArray(s,startPos) | |
178 | + local array = {} -- The return value | |
179 | + local stringLen = string.len(s) | |
180 | + assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) | |
181 | + startPos = startPos + 1 | |
182 | + -- Infinite loop for array elements | |
183 | + repeat | |
184 | + startPos = decode_scanWhitespace(s,startPos) | |
185 | + assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') | |
186 | + local curChar = string.sub(s,startPos,startPos) | |
187 | + if (curChar==']') then | |
188 | + return array, startPos+1 | |
189 | + end | |
190 | + if (curChar==',') then | |
191 | + startPos = decode_scanWhitespace(s,startPos+1) | |
192 | + end | |
193 | + assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') | |
194 | + object, startPos = json.decode(s,startPos) | |
195 | + table.insert(array,object) | |
196 | + until false | |
197 | +end | |
198 | + | |
199 | +--- Scans a comment and discards the comment. | |
200 | +-- Returns the position of the next character following the comment. | |
201 | +-- @param string s The JSON string to scan. | |
202 | +-- @param int startPos The starting position of the comment | |
203 | +function decode_scanComment(s, startPos) | |
204 | + assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) | |
205 | + local endPos = string.find(s,'*/',startPos+2) | |
206 | + assert(endPos~=nil, "Unterminated comment in string at " .. startPos) | |
207 | + return endPos+2 | |
208 | +end | |
209 | + | |
210 | +--- Scans for given constants: true, false or null | |
211 | +-- Returns the appropriate Lua type, and the position of the next character to read. | |
212 | +-- @param s The string being scanned. | |
213 | +-- @param startPos The position in the string at which to start scanning. | |
214 | +-- @return object, int The object (true, false or nil) and the position at which the next character should be | |
215 | +-- scanned. | |
216 | +function decode_scanConstant(s, startPos) | |
217 | + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } | |
218 | + local constNames = {"true","false","null"} | |
219 | + | |
220 | + for i,k in pairs(constNames) do | |
221 | + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then | |
222 | + return consts[k], startPos + string.len(k) | |
223 | + end | |
224 | + end | |
225 | + assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) | |
226 | +end | |
227 | + | |
228 | +--- Scans a number from the JSON encoded string. | |
229 | +-- (in fact, also is able to scan numeric +- eqns, which is not | |
230 | +-- in the JSON spec.) | |
231 | +-- Returns the number, and the position of the next character | |
232 | +-- after the number. | |
233 | +-- @param s The string being scanned. | |
234 | +-- @param startPos The position at which to start scanning. | |
235 | +-- @return number, int The extracted number and the position of the next character to scan. | |
236 | +function decode_scanNumber(s,startPos) | |
237 | + local endPos = startPos+1 | |
238 | + local stringLen = string.len(s) | |
239 | + local acceptableChars = "+-0123456789.e" | |
240 | + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) | |
241 | + and endPos<=stringLen | |
242 | + ) do | |
243 | + endPos = endPos + 1 | |
244 | + end | |
245 | + local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) | |
246 | + local stringEval = load(stringValue) | |
247 | + assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) | |
248 | + return stringEval(), endPos | |
249 | +end | |
250 | + | |
251 | +--- Scans a JSON object into a Lua object. | |
252 | +-- startPos begins at the start of the object. | |
253 | +-- Returns the object and the next starting position. | |
254 | +-- @param s The string being scanned. | |
255 | +-- @param startPos The starting position of the scan. | |
256 | +-- @return table, int The scanned object as a table and the position of the next character to scan. | |
257 | +function decode_scanObject(s,startPos) | |
258 | + local object = {} | |
259 | + local stringLen = string.len(s) | |
260 | + local key, value | |
261 | + assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) | |
262 | + startPos = startPos + 1 | |
263 | + repeat | |
264 | + startPos = decode_scanWhitespace(s,startPos) | |
265 | + assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') | |
266 | + local curChar = string.sub(s,startPos,startPos) | |
267 | + if (curChar=='}') then | |
268 | + return object,startPos+1 | |
269 | + end | |
270 | + if (curChar==',') then | |
271 | + startPos = decode_scanWhitespace(s,startPos+1) | |
272 | + end | |
273 | + assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') | |
274 | + -- Scan the key | |
275 | + key, startPos = json.decode(s,startPos) | |
276 | + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
277 | + startPos = decode_scanWhitespace(s,startPos) | |
278 | + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
279 | + assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) | |
280 | + startPos = decode_scanWhitespace(s,startPos+1) | |
281 | + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
282 | + value, startPos = json.decode(s,startPos) | |
283 | + object[key]=value | |
284 | + until false -- infinite loop while key-value pairs are found | |
285 | +end | |
286 | + | |
287 | +-- START SoniEx2 | |
288 | +-- Initialize some things used by decode_scanString | |
289 | +-- You know, for efficiency | |
290 | +local escapeSequences = { | |
291 | + ["\\t"] = "\t", | |
292 | + ["\\f"] = "\f", | |
293 | + ["\\r"] = "\r", | |
294 | + ["\\n"] = "\n", | |
295 | + ["\\b"] = "\b" | |
296 | +} | |
297 | +setmetatable(escapeSequences, {__index = function(t,k) | |
298 | + -- skip "\" aka strip escape | |
299 | + return string.sub(k,2) | |
300 | +end}) | |
301 | +-- END SoniEx2 | |
302 | + | |
303 | +--- Scans a JSON string from the opening inverted comma or single quote to the | |
304 | +-- end of the string. | |
305 | +-- Returns the string extracted as a Lua string, | |
306 | +-- and the position of the next non-string character | |
307 | +-- (after the closing inverted comma or single quote). | |
308 | +-- @param s The string being scanned. | |
309 | +-- @param startPos The starting position of the scan. | |
310 | +-- @return string, int The extracted string as a Lua string, and the next character to parse. | |
311 | +function decode_scanString(s,startPos) | |
312 | + assert(startPos, 'decode_scanString(..) called without start position') | |
313 | + local startChar = string.sub(s,startPos,startPos) | |
314 | + -- START SoniEx2 | |
315 | + -- PS: I don't think single quotes are valid JSON | |
316 | + assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') | |
317 | + --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) | |
318 | + local t = {} | |
319 | + local i,j = startPos,startPos | |
320 | + while string.find(s, startChar, j+1) ~= j+1 do | |
321 | + local oldj = j | |
322 | + i,j = string.find(s, "\\.", j+1) | |
323 | + local x,y = string.find(s, startChar, oldj+1) | |
324 | + if not i or x < i then | |
325 | + i,j = x,y-1 | |
326 | + end | |
327 | + table.insert(t, string.sub(s, oldj+1, i-1)) | |
328 | + if string.sub(s, i, j) == "\\u" then | |
329 | + local a = string.sub(s,j+1,j+4) | |
330 | + j = j + 4 | |
331 | + local n = tonumber(a, 16) | |
332 | + assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) | |
333 | + -- math.floor(x/2^y) == lazy right shift | |
334 | + -- a % 2^b == bitwise_and(a, (2^b)-1) | |
335 | + -- 64 = 2^6 | |
336 | + -- 4096 = 2^12 (or 2^6 * 2^6) | |
337 | + local x | |
338 | + if n < 0x80 then | |
339 | + x = string.char(n % 0x80) | |
340 | + elseif n < 0x800 then | |
341 | + -- [110x xxxx] [10xx xxxx] | |
342 | + x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) | |
343 | + else | |
344 | + -- [1110 xxxx] [10xx xxxx] [10xx xxxx] | |
345 | + x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) | |
346 | + end | |
347 | + table.insert(t, x) | |
348 | + else | |
349 | + table.insert(t, escapeSequences[string.sub(s, i, j)]) | |
350 | + end | |
351 | + end | |
352 | + table.insert(t,string.sub(j, j+1)) | |
353 | + assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") | |
354 | + return table.concat(t,""), j+2 | |
355 | + -- END SoniEx2 | |
356 | +end | |
357 | + | |
358 | +--- Scans a JSON string skipping all whitespace from the current start position. | |
359 | +-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. | |
360 | +-- @param s The string being scanned | |
361 | +-- @param startPos The starting position where we should begin removing whitespace. | |
362 | +-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string | |
363 | +-- was reached. | |
364 | +function decode_scanWhitespace(s,startPos) | |
365 | + local whitespace=" \n\r\t" | |
366 | + local stringLen = string.len(s) | |
367 | + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do | |
368 | + startPos = startPos + 1 | |
369 | + end | |
370 | + return startPos | |
371 | +end | |
372 | + | |
373 | +--- Encodes a string to be JSON-compatible. | |
374 | +-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) | |
375 | +-- @param s The string to return as a JSON encoded (i.e. backquoted string) | |
376 | +-- @return The string appropriately escaped. | |
377 | + | |
378 | +local escapeList = { | |
379 | + ['"'] = '\\"', | |
380 | + ['\\'] = '\\\\', | |
381 | + ['/'] = '\\/', | |
382 | + ['\b'] = '\\b', | |
383 | + ['\f'] = '\\f', | |
384 | + ['\n'] = '\\n', | |
385 | + ['\r'] = '\\r', | |
386 | + ['\t'] = '\\t' | |
387 | +} | |
388 | + | |
389 | +function json_private.encodeString(s) | |
390 | + local s = tostring(s) | |
391 | + return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat | |
392 | +end | |
393 | + | |
394 | +-- Determines whether the given Lua type is an array or a table / dictionary. | |
395 | +-- We consider any table an array if it has indexes 1..n for its n items, and no | |
396 | +-- other data in the table. | |
397 | +-- I think this method is currently a little 'flaky', but can't think of a good way around it yet... | |
398 | +-- @param t The table to evaluate as an array | |
399 | +-- @return boolean, number True if the table can be represented as an array, false otherwise. If true, | |
400 | +-- the second returned value is the maximum | |
401 | +-- number of indexed elements in the array. | |
402 | +function isArray(t) | |
403 | + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable | |
404 | + -- (with the possible exception of 'n') | |
405 | + local maxIndex = 0 | |
406 | + for k,v in pairs(t) do | |
407 | + if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair | |
408 | + if (not isEncodable(v)) then return false end -- All array elements must be encodable | |
409 | + maxIndex = math.max(maxIndex,k) | |
410 | + else | |
411 | + if (k=='n') then | |
412 | + if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements | |
413 | + else -- Else of (k=='n') | |
414 | + if isEncodable(v) then return false end | |
415 | + end -- End of (k~='n') | |
416 | + end -- End of k,v not an indexed pair | |
417 | + end -- End of loop across all pairs | |
418 | + return true, maxIndex | |
419 | +end | |
420 | + | |
421 | +--- Determines whether the given Lua object / table / variable can be JSON encoded. The only | |
422 | +-- types that are JSON encodable are: string, boolean, number, nil, table and json.null. | |
423 | +-- In this implementation, all other types are ignored. | |
424 | +-- @param o The object to examine. | |
425 | +-- @return boolean True if the object should be JSON encoded, false if it should be ignored. | |
426 | +function isEncodable(o) | |
427 | + local t = type(o) | |
428 | + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) | |
429 | +end | |
430 | + | |
431 | +return json | |
0 | 432 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,72 @@ |
1 | +local skynet = require "skynet" | |
2 | +local harbor = require "skynet.harbor" | |
3 | + | |
4 | +local table_insert = table.insert | |
5 | + | |
6 | +local redisproxy = {} | |
7 | + | |
8 | +setmetatable(redisproxy, { __index = function(t, k) | |
9 | + local cmd = string.upper(k) | |
10 | + local f = function (self, ...) | |
11 | + local ok, result = pcall(skynet.call, redisd, "lua", cmd, ...) | |
12 | + if not ok then | |
13 | + skynet.error(cmd, ..., "\n", debug.traceback(coroutine.running(), nil)) | |
14 | + return | |
15 | + end | |
16 | + return result | |
17 | + end | |
18 | + t[k] = f | |
19 | + return f | |
20 | +end}) | |
21 | + | |
22 | +function redisproxy:runScripts(name, ...) | |
23 | + local RedisScripts = require("rdsscripts/RedisScripts") | |
24 | + | |
25 | + if not RedisScripts[name].sha1 then | |
26 | + local content = io.readfile(RedisScripts[name].file) | |
27 | + RedisScripts[name].sha1 = self:script("LOAD", content) | |
28 | + end | |
29 | + | |
30 | + -- 不存在脚本(系统问题或者需要刷新脚本) | |
31 | + local existScript = self:script("EXISTS", RedisScripts[name].sha1) | |
32 | + if existScript[1] == 0 then | |
33 | + local content = io.readfile(RedisScripts[name].file) | |
34 | + RedisScripts[name].sha1 = self:script("LOAD", content) | |
35 | + end | |
36 | + | |
37 | + return self:evalsha(RedisScripts[name].sha1, ...) | |
38 | +end | |
39 | + | |
40 | +local meta = {__index = function (tab, name) return function (_, ...) tab[#tab+1]={name, ...} end end} | |
41 | +function redisproxy:pipelining(block) | |
42 | + local ops = setmetatable({{"multi"}}, meta) | |
43 | + block(ops) | |
44 | + if #ops == 1 then return end | |
45 | + ops[#ops+1]={"exec"} | |
46 | + return self:pipeline(ops) | |
47 | +end | |
48 | + | |
49 | +function redisproxy:insertEmail(params) | |
50 | + local pms = { | |
51 | + roleId = params.roleId, | |
52 | + emailId = params.emailId, | |
53 | + createtime = params.createtime or skynet.timex(), | |
54 | + con1 = params.con1 or "", | |
55 | + con2 = params.con2 or "", | |
56 | + con3 = params.con3 or "", | |
57 | + att1 = params.att1 or "", | |
58 | + att2 = params.att2 or "", | |
59 | + att3 = params.att3 or "", | |
60 | + title = params.title or "", | |
61 | + content = params.content or "", | |
62 | + attachments = params.attachments or "", | |
63 | + } | |
64 | + self:runScripts("insertEmail", 12, | |
65 | + pms.roleId, pms.emailId, pms.createtime, | |
66 | + pms.con1, pms.con2, pms.con3, | |
67 | + pms.att1, pms.att2, pms.att3, | |
68 | + pms.title, pms.content, pms.attachments) | |
69 | + return true | |
70 | +end | |
71 | + | |
72 | +return redisproxy | |
0 | 73 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,313 @@ |
1 | +local skynet = require "skynet" | |
2 | +local cjson = require "shared.json" | |
3 | +local md5 = require("md5") | |
4 | +local httpc = require("http.httpc") | |
5 | + | |
6 | +local servId = tonumber(skynet.getenv("servId")) | |
7 | +function getRandomName() | |
8 | + local nameCsv = csvdb["name_combCsv"] | |
9 | + local part1 = nameCsv[1][math.random(1, #nameCsv[1])].name | |
10 | + local part2 = nameCsv[2][math.random(1, #nameCsv[2])].name | |
11 | + local part3 = nameCsv[3][math.random(1, #nameCsv[3])].name | |
12 | + | |
13 | + return table.concat({part1, part2, part3}) | |
14 | +end | |
15 | + | |
16 | +function getActionCode(role) | |
17 | + if not role.uniqueCount then | |
18 | + role.uniqueCount = 0 | |
19 | + end | |
20 | + local action = {role:getProperty("id"), skynet.timex(), role.uniqueCount} | |
21 | + role.uniqueCount = role.uniqueCount + 1 | |
22 | + return table.concat(action, "_") | |
23 | +end | |
24 | + | |
25 | +-- begin 数据库自增字段 | |
26 | +function getNextRoleId() | |
27 | + local roleId = redisproxy:hget("autoincrement_set", "role") | |
28 | + if roleId - servId * MAX_ROLE_NUM >= MAX_ROLE_NUM - 1 then | |
29 | + return | |
30 | + end | |
31 | + return redisproxy:hincrby("autoincrement_set", "role", 1) | |
32 | +end | |
33 | + | |
34 | +function getNextTradeId() | |
35 | + return redisproxy:hincrby("autoincrement_set", "trade", 1) | |
36 | +end | |
37 | + | |
38 | +-- end 数据库自增字段 | |
39 | + | |
40 | +-- 查找上限 | |
41 | +-- [10, 20, 30, 40] 查找15, 返回指向10的元素 | |
42 | +function lowerBoundSeach(data, searchKey) | |
43 | + -- 先排序 | |
44 | + local lastKey = nil | |
45 | + local keys = table.keys(data) | |
46 | + table.sort(keys) | |
47 | + for _, key in ipairs(keys) do | |
48 | + if key > searchKey then | |
49 | + break | |
50 | + end | |
51 | + lastKey = key | |
52 | + end | |
53 | + | |
54 | + return lastKey and data[lastKey] or nil | |
55 | +end | |
56 | + | |
57 | +-- 将201402021800或者20140202的格式转化成unixtime | |
58 | +function toUnixtime(timeStr) | |
59 | + local strLength = string.len(timeStr) | |
60 | + if strLength ~= 8 and strLength ~= 10 then return end | |
61 | + local year = string.sub(timeStr, 1, 4) | |
62 | + local month = string.sub(timeStr, 5, 6) | |
63 | + local day = string.sub(timeStr, 7, 8) | |
64 | + local hour, minute = 0, 0 | |
65 | + if strLength == 10 then | |
66 | + hour = string.sub(timeStr, 9, 10) | |
67 | + --minute = string.sub(timeStr, 11, 12) | |
68 | + end | |
69 | + return os.time{year=year, month=month, day=day, hour=hour, min=minute, sec=0} | |
70 | +end | |
71 | + | |
72 | +-- 判断时间点是不是当天 | |
73 | +function isToday(curTimestamp) | |
74 | + local curTm = os.date("*t", curTimestamp) | |
75 | + local nowTm = os.date("*t", skynet.timex()) | |
76 | + return curTm.year == nowTm.year and curTm.month == nowTm.month and curTm.day == nowTm.day | |
77 | +end | |
78 | + | |
79 | +function withTimeHead(value, now) | |
80 | + now = now or skynet.timex() | |
81 | + return (2000000000-now) .. "." .. value | |
82 | +end | |
83 | + | |
84 | +function cutTimeHead(value) | |
85 | + return tonumber(value:match("%d+%.(%d+)")) | |
86 | +end | |
87 | + | |
88 | +-- 判断是不是同一个星期 | |
89 | +function isCrossWeek(target, now) | |
90 | + now = now or skynet.timex() | |
91 | + return specMonday(target) ~= specMonday(now - RESET_TIME * 3600) | |
92 | +end | |
93 | + | |
94 | +-- 两个时间相隔多少星期 | |
95 | +function crossWeek(target, now) | |
96 | + now = now or skynet.timex() | |
97 | + return math.floor((specMonday(target)-specMonday(now))/604800) | |
98 | +end | |
99 | + | |
100 | +-- 判断是不是同一个月 | |
101 | +function isCrossMonth(target, now) | |
102 | + now = now or skynet.timex() | |
103 | + local tarTm = os.date("*t", target) | |
104 | + local nowTm = os.date("*t", now - RESET_TIME * 3600) | |
105 | + if tarTm.year == nowTm.year and tarTm.month == nowTm.month then | |
106 | + return false | |
107 | + else | |
108 | + return true | |
109 | + end | |
110 | +end | |
111 | + | |
112 | +function isCrossDay(lastTime, now) | |
113 | + if lastTime == 0 then return true end | |
114 | + now = now or skynet.timex() | |
115 | + local today4h = specTime({hour = RESET_TIME}, now - RESET_TIME * 3600) | |
116 | + return lastTime < today4h and now >= today4h | |
117 | +end | |
118 | + | |
119 | +function crossDay(target, now) | |
120 | + now = now or skynet.timex() | |
121 | + | |
122 | + local tarTime = specTime({hour = RESET_TIME}, target) | |
123 | + --local nowTime = specTime({hour = RESET_TIME}, now) --当前时间有没有过一天而不是当前天四点 | |
124 | + return math.floor((now - tarTime)/ 86400) | |
125 | +end | |
126 | + | |
127 | +function crossWeekFromOpen(now) | |
128 | + now = now or skynet.timex() | |
129 | + local openTime = os.time{ | |
130 | + year = tonum(SERV_OPEN:sub(1,4)), | |
131 | + month = tonum(SERV_OPEN:sub(5,6)), | |
132 | + day = tonum(SERV_OPEN:sub(7,8)), | |
133 | + } | |
134 | + return crossWeek(openTime, now) | |
135 | +end | |
136 | + | |
137 | +function crossDayFromOpen(now) | |
138 | + now = now or skynet.timex() | |
139 | + local openTime = os.time{ | |
140 | + year = tonum(SERV_OPEN:sub(1,4)), | |
141 | + month = tonum(SERV_OPEN:sub(5,6)), | |
142 | + day = tonum(SERV_OPEN:sub(7,8)), | |
143 | + hour = RESET_TIME, | |
144 | + } | |
145 | + return crossDay(openTime, now) | |
146 | +end | |
147 | + | |
148 | +-- 30*86400 = 2592000 | |
149 | +function monthLater(now) | |
150 | + now = now or skynet.timex() | |
151 | + now = now - RESET_TIME * 3600 -- 排除0-4点钟 影响 | |
152 | + return specTime({hour = RESET_TIME}, now) + 2592000 | |
153 | +end | |
154 | +-- 一天以后 | |
155 | +function dayLater(now) | |
156 | + now = now or skynet.timex() | |
157 | + now = now - RESET_TIME * 3600 -- 排除0-4点钟 影响 | |
158 | + return specTime({hour = RESET_TIME}, now) + 86400 | |
159 | +end | |
160 | +-- 到下一个时间点的秒数差和下一个时间点的unixtime | |
161 | +function diffTime(params) | |
162 | + params = params or {} | |
163 | + local currentTime = skynet.timex() | |
164 | + | |
165 | + local curTm = os.date("*t", currentTime) | |
166 | + local nextYear = params.year or curTm.year | |
167 | + local nextMonth = params.month or curTm.month | |
168 | + local nextDay = params.day or curTm.day + 1 | |
169 | + local nextHour = params.hour or 0 | |
170 | + local nextMinute = params.min or 0 | |
171 | + local nextSecond = params.sec or 0 | |
172 | + | |
173 | + local nextUnixTime = os.time({ year = nextYear, month = nextMonth, day = nextDay, hour = nextHour, min = nextMinute, sec = nextSecond}) | |
174 | + return os.difftime(nextUnixTime, currentTime), nextUnixTime | |
175 | +end | |
176 | + | |
177 | +-- 取今天特殊时刻时间戳 | |
178 | +function specTime(pms, now) | |
179 | + now = now or skynet.timex() | |
180 | + local tm = os.date("*t", now) | |
181 | + local year = pms.year or tm.year | |
182 | + local month = pms.month or tm.month | |
183 | + local day = pms.day or tm.day | |
184 | + local hour = pms.hour or 0 | |
185 | + local min = pms.min or 0 | |
186 | + local sec = pms.sec or 0 | |
187 | + return os.time({year = year, month = month, day = day, hour = hour, min = min, sec = sec}) | |
188 | +end | |
189 | + | |
190 | +function specMonday(now) | |
191 | + now = now or skynet.timex() | |
192 | + local tm = os.date("*t", now) | |
193 | + local wday = (tm.wday + 6) % 7 | |
194 | + if wday == 0 then wday = 7 end | |
195 | + | |
196 | + local time = os.time({year = tm.year, month = tm.month, day = tm.day}) | |
197 | + return time - (wday - 1) * 86400 | |
198 | +end | |
199 | + | |
200 | +function isSpecTime(startHour, endHour, specday) | |
201 | + local tm = os.date("*t", skynet.timex()) | |
202 | + if specday then | |
203 | + local day = (tm.wday+6)%7 | |
204 | + if day == 0 then day = 7 end | |
205 | + return specday == day and tm.hour >= startHour and tm.hour < endHour | |
206 | + end | |
207 | + return tm.hour >= startHour and tm.hour < endHour | |
208 | +end | |
209 | + | |
210 | +function getSecond(timeStr) | |
211 | + timeStr = timeStr or "0000" | |
212 | + local hour = tonumber(string.sub(timeStr, 1, 2)) | |
213 | + local min = tonumber(string.sub(timeStr, 3, 4)) | |
214 | + return hour * 3600 + min * 60 | |
215 | +end | |
216 | + | |
217 | +function urlencode(str) | |
218 | + if (str) then | |
219 | + str = string.gsub (str, "\n", "\r\n") | |
220 | + str = string.gsub (str, "([^%w ])", | |
221 | + function (c) return string.format ("%%%02X", string.byte(c)) end) | |
222 | + str = string.gsub (str, " ", "+") | |
223 | + end | |
224 | + return str | |
225 | +end | |
226 | + | |
227 | +function isnan(value) | |
228 | + return value ~= value | |
229 | +end | |
230 | + | |
231 | +-- 推送通知 | |
232 | +local serverid = skynet.getenv "serverid" | |
233 | +local secretKey = "467C2221D3A20FE69D23A33E8940C2C5" | |
234 | + | |
235 | +-- 推送该服务器的所有用户 | |
236 | +-- tag都是交集处理 | |
237 | +function notifyClients(msg, otherTags) | |
238 | + local tags = { serverid } | |
239 | + for _, tag in ipairs(otherTags or {}) do | |
240 | + table.insert(tags, tag) | |
241 | + end | |
242 | + | |
243 | + local content = { | |
244 | + ["appid"] = "1000013239", | |
245 | + ["audience"] = { | |
246 | + [otherTags and "tag_and" or "tag"] = tags, | |
247 | + }, | |
248 | + -- ["audience"] = "all", | |
249 | + ["notification"] = { | |
250 | + ["alert"] = msg, | |
251 | + }, | |
252 | + ["options"] = { | |
253 | + ["ttl"] = 60 * 120 | |
254 | + } | |
255 | + } | |
256 | + | |
257 | + local contentJson = cjson.encode(content) | |
258 | + local header = { | |
259 | + ["content-type"] = "application/x-www-form-urlencoded", | |
260 | + ["X-MJPUSH-SIGNATURE"] = md5.sumhexa(urlencode(contentJson .. "&" .. secretKey)) | |
261 | + } | |
262 | + | |
263 | + local status, body = httpc.request("POST", "push.mjyun.com", "/api/push", {}, header, contentJson) | |
264 | + if tonumber(status) ~= 200 then | |
265 | + skynet.error(status, body) | |
266 | + end | |
267 | +end | |
268 | + | |
269 | +-- { uid, msg, scheduleTime} | |
270 | +function notifyClient(params) | |
271 | + params = params or {} | |
272 | + params.key = "zhaolugame20170831" | |
273 | + | |
274 | + local status, body = httpc.get(skynet.getenv("codeurl"), | |
275 | + "/mipush/notify_user?" .. httpGetFormatData(params), {}, {}) | |
276 | + if tonumber(status) ~= 200 then | |
277 | + skynet.error(status, body) | |
278 | + return | |
279 | + end | |
280 | + | |
281 | + return body | |
282 | +end | |
283 | + | |
284 | +-- { uid, msgId} | |
285 | +function deleteNotify(params) | |
286 | + params = params or {} | |
287 | + params.key = "zhaolugame20170831" | |
288 | + | |
289 | + local status, body = httpc.get(skynet.getenv("codeurl"), | |
290 | + "/mipush/delete_notify?" .. httpGetFormatData(params), {}, {}) | |
291 | + if tonumber(status) ~= 200 then | |
292 | + skynet.error(status, body) | |
293 | + return | |
294 | + end | |
295 | + | |
296 | + return body | |
297 | +end | |
298 | + | |
299 | +--http get数据 | |
300 | +function httpGetFormatData(params) | |
301 | + local function escape(s) | |
302 | + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) | |
303 | + return string.format("%%%02X", string.byte(c)) | |
304 | + end)) | |
305 | + end | |
306 | + | |
307 | + local body = {} | |
308 | + for k, v in pairs(params) do | |
309 | + table.insert(body, string.format("%s=%s", escape(k), escape(v))) | |
310 | + end | |
311 | + | |
312 | + return table.concat(body, "&") | |
313 | +end | |
0 | 314 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,42 @@ |
1 | +-- 初始化 | |
2 | +function math.randomInit(seed) | |
3 | + seed = seed or skynet.timex() | |
4 | + math.randomseed(tonumber(tostring(seed):reverse():sub(1,6))) | |
5 | +end | |
6 | + | |
7 | +-- 随机浮点数 | |
8 | +function math.randomFloat(lower, greater) | |
9 | + return math.min(lower, greater) + math.random() * math.abs(greater - lower); | |
10 | +end | |
11 | + | |
12 | +function math.randomInt(lower, greater) | |
13 | + return math.random(math.min(lower, greater), math.max(lower, greater)) | |
14 | +end | |
15 | + | |
16 | +-- 根据权重值从数据集合里面随机出 | |
17 | +-- @param dataset 数据集合 | |
18 | +-- @param field 权重域 | |
19 | +function math.randWeight(dataset, field) | |
20 | + if not dataset then return nil end | |
21 | + | |
22 | + field = field or "weight" | |
23 | + | |
24 | + -- 计算权值总和 | |
25 | + local weightSum = 0 | |
26 | + for key, value in pairs(dataset) do | |
27 | + weightSum = weightSum + tonumber(value[field]) | |
28 | + end | |
29 | + | |
30 | + local randWeight = math.randomFloat(0, weightSum) | |
31 | + for key, value in pairs(dataset) do | |
32 | + if tonumber(value[field]) > 0 then | |
33 | + if randWeight > tonumber(value[field]) then | |
34 | + randWeight = randWeight - tonumber(value[field]) | |
35 | + else | |
36 | + return key | |
37 | + end | |
38 | + end | |
39 | + end | |
40 | + | |
41 | + return nil | |
42 | +end | |
0 | 43 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,334 @@ |
1 | +local ipairs = ipairs | |
2 | +local pairs = pairs | |
3 | +local table_insert = table.insert | |
4 | +local table_concat = table.concat | |
5 | +local table_remove = table.remove | |
6 | +local string_format = string.format | |
7 | +local type = type | |
8 | +local tonumber = tonumber | |
9 | +local next = next | |
10 | + | |
11 | +local strh = require "strh" | |
12 | + | |
13 | +function string.setv(str, k, v, delimiter) | |
14 | + delimiter = delimiter or " " | |
15 | + -- 若存在则替换,若无则append | |
16 | + return strh.modify(str, {[tonumber(k)]=tonumber(v)}, false, delimiter) | |
17 | +end | |
18 | + | |
19 | +function string.msetv(str, vs, delimiter) | |
20 | + delimiter = delimiter or " " | |
21 | + if not next(vs) then return str end | |
22 | + return strh.modify(str, vs, false, delimiter) | |
23 | +end | |
24 | + | |
25 | +function string.incrv(str, k, delta, delimiter) | |
26 | + delimiter = delimiter or " " | |
27 | + return strh.modify(str, {[tonumber(k)]=tonumber(delta)}, true, delimiter) | |
28 | +end | |
29 | + | |
30 | +function string.mincrv(str, ds, delimiter) | |
31 | + delimiter = delimiter or " " | |
32 | + if not next(ds) then return str end | |
33 | + return strh.modify(str, ds, true, delimiter) | |
34 | +end | |
35 | + | |
36 | +function string.delk(str, k, delimiter) | |
37 | + delimiter = delimiter or " " | |
38 | + return strh.modify(str, {[tonumber(k)]=""}, false, delimiter) | |
39 | +end | |
40 | + | |
41 | +function string.mdelk(str, ks, delimiter) | |
42 | + delimiter = delimiter or " " | |
43 | + local mod = {} | |
44 | + for _, k in ipairs(ks) do | |
45 | + mod[k] = "" | |
46 | + end | |
47 | + return strh.modify(str, mod, false, delimiter) | |
48 | +end | |
49 | + | |
50 | +function string.getv(str, k, default, delimiter) | |
51 | + default = default or -1 | |
52 | + delimiter = delimiter or " " | |
53 | + return strh.getv(str, k, default, delimiter) | |
54 | +end | |
55 | + | |
56 | +function string.toArray(str, toNum, delimiter) | |
57 | + delimiter = delimiter or " " | |
58 | + return strh.toarray(str, tobool(toNum), delimiter) | |
59 | +end | |
60 | + | |
61 | +function string.value(str, index, default, notNum, delimiter) | |
62 | + delimiter = delimiter or " " | |
63 | + local bfind, value = strh.value(str, index, delimiter) | |
64 | + if not notNum then | |
65 | + value = tonumber(value) | |
66 | + end | |
67 | + return bfind and value or (default or value) | |
68 | +end | |
69 | + | |
70 | +function string.setv_dk(str, k1, k2, v, delimiter) | |
71 | + delimiter = delimiter or " " | |
72 | + local mod = {[k1]={[k2]=v}} | |
73 | + return strh.moddk(str, mod, false, delimiter) | |
74 | +end | |
75 | + | |
76 | +-- {{k1,k2,v}} | |
77 | +function string.msetv_dk(str, vs, delimiter) | |
78 | + delimiter = delimiter or " " | |
79 | + if not next(vs) then return str end | |
80 | + local mod = {} | |
81 | + for _, t in ipairs(vs) do | |
82 | + local k1 = tonumber(t[1]) | |
83 | + local k2 = tonumber(t[2]) | |
84 | + local v = tonumber(t[3]) | |
85 | + if mod[k1] then | |
86 | + mod[k1][k2] = v | |
87 | + else | |
88 | + mod[k1] = {[k2] = v} | |
89 | + end | |
90 | + end | |
91 | + return strh.moddk(str, mod, false, delimiter) | |
92 | +end | |
93 | + | |
94 | +function string.incrv_dk(str, k1, k2, delta, delimiter) | |
95 | + delimiter = delimiter or " " | |
96 | + local mod = {[tonumber(k1)]={[tonumber(k2)]=tonumber(delta)}} | |
97 | + return strh.moddk(str, mod, true, delimiter) | |
98 | +end | |
99 | + | |
100 | +function string.mincrv_dk(str, ds, delimiter) | |
101 | + delimiter = delimiter or " " | |
102 | + if not next(ds) then return str end | |
103 | + local mod = {} | |
104 | + for _, t in ipairs(ds) do | |
105 | + local k1 = tonumber(t[1]) | |
106 | + local k2 = tonumber(t[2]) | |
107 | + local delta = tonumber(t[3]) | |
108 | + if mod[k1] then | |
109 | + mod[k1][k2] = delta | |
110 | + else | |
111 | + mod[k1] = {[k2] = delta} | |
112 | + end | |
113 | + end | |
114 | + return strh.moddk(str, mod, true, delimiter) | |
115 | +end | |
116 | + | |
117 | +function string.delk_dk(str, k1, k2, delimiter) | |
118 | + delimiter = delimiter or " " | |
119 | + local mod = {[tonumber(k1)]={[tonumber(k2)]=""}} | |
120 | + return strh.moddk(str, mod, false, delimiter) | |
121 | +end | |
122 | + | |
123 | +function string.mdelk_dk(str, ks, delimiter) | |
124 | + delimiter = delimiter or " " | |
125 | + local mod = {} | |
126 | + for _, t in ipairs(ks) do | |
127 | + local k1 = tonumber(t[1]) | |
128 | + local k2 = tonumber(t[2]) | |
129 | + if mod[k1] then | |
130 | + mod[k1][k2] = "" | |
131 | + else | |
132 | + mod[k1] = {[k2] = ""} | |
133 | + end | |
134 | + end | |
135 | + return strh.moddk(str, mod, false, delimiter) | |
136 | +end | |
137 | + | |
138 | +function string.getv_dk(str, k1, k2, default, delimiter) | |
139 | + default = default or -1 | |
140 | + delimiter = delimiter or " " | |
141 | + return strh.getvdk(str, k1, k2, default, delimiter) | |
142 | +end | |
143 | + | |
144 | +function string.toNumMap(str, delimiter) | |
145 | + delimiter = delimiter or " " | |
146 | + return strh.tonummap(str, delimiter) | |
147 | +end | |
148 | + | |
149 | +--[[ | |
150 | +from: 1=2=3 | |
151 | +to: {"1","2","3"} | |
152 | +]] | |
153 | +function string.split2(str, pattern) | |
154 | + pattern = pattern or "[%d.=]+" | |
155 | + local tb = {} | |
156 | + for v in str:gmatch(pattern) do | |
157 | + table_insert(tb, v) | |
158 | + end | |
159 | + return tb | |
160 | +end | |
161 | + | |
162 | +--[[ | |
163 | +from: 1=2 3=4 | |
164 | +to: {["1"]="2",["3"]="4"} | |
165 | +]] | |
166 | +function string.tomap(str) | |
167 | + local tb = {} | |
168 | + for k, v in str:gmatch("([%d.]+)=([%d.]+)") do | |
169 | + tb[k] = v | |
170 | + end | |
171 | + return tb | |
172 | +end | |
173 | + | |
174 | +--[[ | |
175 | +from: 1=2 3=4 5=6.1 | |
176 | +to: {{1,2},{3,4},{5,6.1}} | |
177 | +]] | |
178 | +function string.toAttrMap(str, pattern) | |
179 | + pattern = pattern or "([%d.]+)=([%d.]+)" | |
180 | + local tb = {} | |
181 | + for k, v in str:gmatch(pattern) do | |
182 | + tb[#tb+1] = {tonum(k), tonum(v)} | |
183 | + end | |
184 | + return tb | |
185 | +end | |
186 | + | |
187 | +--[[ | |
188 | +from: "1=2=3 4=5=6" | |
189 | +to: {{"1", "2", "3"}, {"4", "5", "6"}} | |
190 | +]] | |
191 | +function string.toTableArray(str, toNum, delimiter, pattern) | |
192 | + pattern = pattern or "[%d.=]+" | |
193 | + delimiter = delimiter or "=" | |
194 | + local tb = {} | |
195 | + for v in str:gmatch(pattern) do | |
196 | + tb[#tb+1] = v:toArray(toNum, delimiter) | |
197 | + end | |
198 | + return tb | |
199 | +end | |
200 | + | |
201 | +function string.toTableArraySec(str, delimiter) | |
202 | + delimiter = delimiter or " " | |
203 | + local array = {} | |
204 | + local tempArray = string.split(string.trim(str), delimiter) | |
205 | + for _, value in ipairs(tempArray) do | |
206 | + local trimValue = string.trim(value) | |
207 | + if trimValue ~= "" then | |
208 | + value = string.split(trimValue, "=") | |
209 | + table_insert(array, value) | |
210 | + end | |
211 | + end | |
212 | + | |
213 | + return array | |
214 | +end | |
215 | + | |
216 | +--[[ | |
217 | +from: "x1=x2=x3=x4 y1=y2=y3=y4" | |
218 | +to: {[x1]={x2,x3,x4},[y1]={y2,y3,y4}} | |
219 | +]] | |
220 | +function string.toAttArray(str) | |
221 | + local tb = {} | |
222 | + for v1, vt in str:gmatch("([%d.]+)=([%d.=]+)") do | |
223 | + tb[tonum(v1)] = vt:toArray(true) | |
224 | + end | |
225 | + return tb | |
226 | +end | |
227 | + | |
228 | +--[[ | |
229 | +x=x...=x x=x...=x | |
230 | +最后一个x为权值 返回权值之外的值 | |
231 | +]] | |
232 | +function string.randWeight(str, bMulti) | |
233 | + if not str or str == "" then return nil end | |
234 | + | |
235 | + local tab, sum = {}, 0 | |
236 | + for index, vstr in ipairs(str:toArray()) do | |
237 | + local tmp = vstr:toArray(true, "=") | |
238 | + sum = sum + tmp[#tmp] | |
239 | + tab[index] = tmp | |
240 | + end | |
241 | + | |
242 | + local weight = math.randomFloat(0, sum) | |
243 | + | |
244 | + for _, v in ipairs(tab) do | |
245 | + local val = v[#v] | |
246 | + if val < weight then | |
247 | + weight = weight - val | |
248 | + else | |
249 | + if bMulti then | |
250 | + table_remove(v) | |
251 | + return v | |
252 | + else | |
253 | + return v[1] | |
254 | + end | |
255 | + end | |
256 | + end | |
257 | +end | |
258 | + | |
259 | +function string.randLine(str) | |
260 | + local num = str:nums("%d+") | |
261 | + local index = math.random(num) | |
262 | + return tonumber(str:value(index)) | |
263 | +end | |
264 | + | |
265 | +--[[ | |
266 | +结构类似如: | |
267 | +v1;v2;v3; | |
268 | +]] | |
269 | +function string.sismember(str, value, delimiter) | |
270 | + delimiter = delimiter or ";" | |
271 | + for _, v in ipairs(str:toArray(true, delimiter)) do | |
272 | + if v == val then | |
273 | + return true | |
274 | + end | |
275 | + end | |
276 | +end | |
277 | + | |
278 | +function string.sadd(str, value, delimiter) | |
279 | + delimiter = delimiter or ";" | |
280 | + if str:sismember(value) then | |
281 | + return str | |
282 | + end | |
283 | + return table_concat({str, value, delimiter}) | |
284 | +end | |
285 | + | |
286 | +function string.srem(str, value, delimiter) | |
287 | + delimiter = delimiter or ";" | |
288 | + local form = table_concat({"([^", delimiter, "]+)", delimiter}) | |
289 | + local str = str:gsub(form, {[tostring(value)]=""}) | |
290 | + return str | |
291 | +end | |
292 | + | |
293 | +--[[ | |
294 | +from: 1 2 3 4 5 | |
295 | +to: 数字的个数 这里是5 | |
296 | +]] | |
297 | +function string.nums(str, format) | |
298 | + format = format or "%d+" | |
299 | + local count = 0 | |
300 | + for _ in str:gmatch(format) do | |
301 | + count = count + 1 | |
302 | + end | |
303 | + return count | |
304 | +end | |
305 | + | |
306 | +-- getbit setbit 支持负索引 | |
307 | +function string.getbit(str, pos) | |
308 | + local len = #str | |
309 | + if pos > len or len+pos < 0 then return 48 end | |
310 | + if pos < 0 and len+pos >= 0 then pos = len+pos+1 end | |
311 | + return str:byte(pos) | |
312 | +end | |
313 | + | |
314 | +function string.bitcnt(str) | |
315 | + local cnt = 0 | |
316 | + for i=1, #str do | |
317 | + cnt = cnt + str:byte(i) - 48 | |
318 | + end | |
319 | + return cnt | |
320 | +end | |
321 | + | |
322 | +function string.setbit(str, pos, yes) | |
323 | + yes = yes or "1" | |
324 | + local len = #str | |
325 | + if pos < 0 then | |
326 | + if len+pos < 0 then return str end | |
327 | + pos = len+pos+1 | |
328 | + end | |
329 | + if len < pos then | |
330 | + return str .. string.rep("0", pos-len-1) .. yes | |
331 | + else | |
332 | + return str:sub(1, pos-1) .. yes .. str:sub(pos+1, -1) | |
333 | + end | |
334 | +end | |
0 | 335 | \ No newline at end of file | ... | ... |