需求
最近有
Android
和Lua
联动的需求,但考虑到安全性想采用Go
实现云端布局分发,Lua
获取到本地再进行解压刷新。
思路
后端:
Golang
安卓端:Java
+Lua
- 云端编写代码,
Go-server
实时进行分发 - 安卓端定时监听云端的文件变化, 获取到本地后解压并装载.
- 通过
SQLite
等本地数据库,实现持久化存储云端布局和相关配置. - 后续拓展可以使用
Java + Lua
重新打包apk包
代码实现
- 云更
- 安卓打包
- 自定义请求库
Go 云端更新方案
go_server.go
package main
import (
"archive/zip"
"bytes"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/gin-gonic/gin"
)
var (
mu sync.Mutex
zipBuffer *bytes.Buffer
lastAccess time.Time
accessPeriod = 10 * time.Second
)
func main() {
r := gin.Default()
r.GET("/api/v1/file", func(c *gin.Context) {
mu.Lock()
defer mu.Unlock()
if time.Since(lastAccess) < accessPeriod {
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "暂时没有"})
return
}
lastAccess = time.Now()
if zipBuffer == nil {
c.JSON(http.StatusNoContent, gin.H{"message": "No file available"})
return
}
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename=demo.zip")
c.Data(http.StatusOK, "application/zip", zipBuffer.Bytes())
})
go func() {
for {
packFiles()
time.Sleep(10 * time.Second)
}
}()
if err := r.Run(":9090"); err != nil {
log.Fatal(err)
}
}
func packFiles() {
mu.Lock()
defer mu.Unlock()
// 检查目录下是否有文件变更
files, err := os.ReadDir("res") // 替换为你的目录路径
if err != nil {
log.Println("Failed to read directory:", err)
return
}
if len(files) == 0 {
log.Println("No files available in the directory")
return
}
// 创建一个内存缓冲区来存储zip文件
buf := new(bytes.Buffer)
zipWriter := zip.NewWriter(buf)
// 遍历目录下的文件,并将它们添加到zip文件中
for _, file := range files {
if file.IsDir() {
continue
}
filePath := filepath.Join("res", file.Name()) // 替换为你的目录路径
data, err := os.ReadFile(filePath)
if err != nil {
log.Println("Failed to read file:", file.Name(), err)
continue
}
fileWriter, err := zipWriter.Create(file.Name())
if err != nil {
log.Println("Failed to create zip file entry:", file.Name(), err)
continue
}
_, err = fileWriter.Write(data)
if err != nil {
log.Println("Failed to write to zip file:", file.Name(), err)
}
}
err = zipWriter.Close()
if err != nil {
log.Println("Failed to close zip writer:", err)
return
}
// 将zip文件存储到缓存中
zipBuffer = buf
fmt.Println("Files packed into zip successfully")
}
Lua 安卓apk打包
bin.lua
require "lua.import"
import "java.util.zip.ZipOutputStream"
import "android.net.Uri"
import "java.io.File"
import "android.widget.Toast"
import "java.util.zip.CheckedInputStream"
import "java.io.FileInputStream"
import "android.content.Intent"
import "java.security.Signer"
import "java.util.ArrayList"
import "java.io.FileOutputStream"
import "java.io.BufferedOutputStream"
import "java.util.zip.ZipInputStream"
import "java.io.BufferedInputStream"
import "java.util.zip.ZipEntry"
import "android.app.ProgressDialog"
import "java.util.zip.CheckedOutputStream"
import "java.util.zip.Adler32"
local bin_dlg, error_dlg
local function update(s)
bin_dlg.setMessage(s)
end
local function callback(s)
LuaUtil.rmDir(File(activity.getLuaExtDir("bin/.temp")))
bin_dlg.hide()
bin_dlg.Message = ""
if not s:find("成功") then
error_dlg.Message = s
error_dlg.show()
end
end
local function create_bin_dlg()
if bin_dlg then
return
end
bin_dlg = ProgressDialog(activity);
bin_dlg.setTitle("正在打包");
bin_dlg.setMax(100);
end
local function create_error_dlg2()
if error_dlg then
return
end
error_dlg = AlertDialogBuilder(activity)
error_dlg.Title = "出错"
error_dlg.setPositiveButton("确定", nil)
end
local function binapk(luapath, apkpath)
require "lua.import"
import "console"
compile "mao"
compile "sign"
import "java.util.zip.*"
import "java.io.*"
import "mao.res.*"
import "apksigner.*"
local b = byte[2 ^ 16]
local function copy(input, output)
LuaUtil.copyFile(input, output)
input.close()
--[[local l=input.read(b)
while l>1 do
output.write(b,0,l)
l=input.read(b)
end]]
end
local function copy2(input, output)
LuaUtil.copyFile(input, output)
end
local temp = File(apkpath).getParentFile();
if (not temp.exists()) then
if (not temp.mkdirs()) then
error("create file " .. temp.getName() .. " fail");
end
end
local tmp = activity.getLuaPath("tmp.apk")
local info = activity.getApplicationInfo()
local ver = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionName
local code = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode
--local zip=ZipFile(info.publicSourceDir)
local zipFile = File(info.publicSourceDir)
local fis = FileInputStream(zipFile);
--local checksum = CheckedInputStream(fis, Adler32());
local zis = ZipInputStream(BufferedInputStream(fis));
local fot = FileOutputStream(tmp)
--local checksum2 = CheckedOutputStream(fot, Adler32());
local out = ZipOutputStream(BufferedOutputStream(fot))
local f = File(luapath)
local errbuffer = {}
local replace = {}
local checked = {}
local lualib = {}
local md5s = {}
local libs = File(activity.ApplicationInfo.nativeLibraryDir).list()
libs = luajava.astable(libs)
for k, v in ipairs(libs) do
--libs[k]="lib/armeabi/"..libs[k]
replace[v] = true
end
local mdp = activity.Application.MdDir
local function getmodule(dir)
local mds = File(activity.Application.MdDir .. dir).listFiles()
mds = luajava.astable(mds)
for k, v in ipairs(mds) do
if mds[k].isDirectory() then
getmodule(dir .. mds[k].Name .. "/")
else
mds[k] = "lua" .. dir .. mds[k].Name
replace[mds[k]] = true
end
end
end
getmodule("/")
local function checklib(path)
if checked[path] then
return
end
local cp, lp
checked[path] = true
local f = io.open(path)
local s = f:read("*a")
f:close()
for m, n in s:gmatch("require *%(? *\"([%w_]+)%.?([%w_]*)") do
cp = string.format("lib%s.so", m)
if n ~= "" then
lp = string.format("lua/%s/%s.lua", m, n)
m = m .. '/' .. n
else
lp = string.format("lua/%s.lua", m)
end
if replace[cp] then
replace[cp] = false
end
if replace[lp] then
checklib(mdp .. "/" .. m .. ".lua")
replace[lp] = false
lualib[lp] = mdp .. "/" .. m .. ".lua"
end
end
for m, n in s:gmatch("import *%(? *\"([%w_]+)%.?([%w_]*)") do
cp = string.format("lib%s.so", m)
if n ~= "" then
lp = string.format("lua/%s/%s.lua", m, n)
m = m .. '/' .. n
else
lp = string.format("lua/%s.lua", m)
end
if replace[cp] then
replace[cp] = false
end
if replace[lp] then
checklib(mdp .. "/" .. m .. ".lua")
replace[lp] = false
lualib[lp] = mdp .. "/" .. m .. ".lua"
end
end
end
replace["libluajava.so"] = false
local function addDir(out, dir, f)
local entry = ZipEntry("assets/" .. dir)
out.putNextEntry(entry)
local ls = f.listFiles()
for n = 0, #ls - 1 do
local name = ls[n].getName()
if name==(".using") then
checklib(luapath .. dir .. name)
elseif name:find("%.apk$") or name:find("%.luac$") or name:find("^%.") then
elseif name:find("%.lua$") then
checklib(luapath .. dir .. name)
local path, err = console.build(luapath .. dir .. name)
if path then
if replace["assets/" .. dir .. name] then
table.insert(errbuffer, dir .. name .. "/.aly")
end
local entry = ZipEntry("assets/" .. dir .. name)
out.putNextEntry(entry)
replace["assets/" .. dir .. name] = true
copy(FileInputStream(File(path)), out)
table.insert(md5s, LuaUtil.getFileMD5(path))
os.remove(path)
else
table.insert(errbuffer, err)
end
elseif name:find("%.aly$") then
local path, err = console.build(luapath .. dir .. name)
if path then
name = name:gsub("aly$", "lua")
if replace["assets/" .. dir .. name] then
table.insert(errbuffer, dir .. name .. "/.aly")
end
local entry = ZipEntry("assets/" .. dir .. name)
out.putNextEntry(entry)
replace["assets/" .. dir .. name] = true
copy(FileInputStream(File(path)), out)
table.insert(md5s, LuaUtil.getFileMD5(path))
os.remove(path)
else
table.insert(errbuffer, err)
end
elseif ls[n].isDirectory() then
addDir(out, dir .. name .. "/", ls[n])
else
local entry = ZipEntry("assets/" .. dir .. name)
out.putNextEntry(entry)
replace["assets/" .. dir .. name] = true
copy(FileInputStream(ls[n]), out)
table.insert(md5s, LuaUtil.getFileMD5(ls[n]))
end
end
end
this.update("正在编译...");
if f.isDirectory() then
require "permission"
dofile(luapath .. "init.lua")
if user_permission then
for k, v in ipairs(user_permission) do
user_permission[v] = true
end
end
local ss, ee = pcall(addDir, out, "", f)
if not ss then
table.insert(errbuffer, ee)
end
--print(ee,dump(errbuffer),dump(replace))
local wel = File(luapath .. "icon.png")
if wel.exists() then
local entry = ZipEntry("res/drawable/icon.png")
out.putNextEntry(entry)
replace["res/drawable/icon.png"] = true
copy(FileInputStream(wel), out)
end
local wel = File(luapath .. "welcome.png")
if wel.exists() then
local entry = ZipEntry("res/drawable/welcome.png")
out.putNextEntry(entry)
replace["res/drawable/welcome.png"] = true
copy(FileInputStream(wel), out)
end
else
return "error"
end
--print(dump(lualib))
for name, v in pairs(lualib) do
local path, err = console.build(v)
if path then
local entry = ZipEntry(name)
out.putNextEntry(entry)
copy(FileInputStream(File(path)), out)
table.insert(md5s, LuaUtil.getFileMD5(path))
os.remove(path)
else
table.insert(errbuffer, err)
end
end
function touint32(i)
local code = string.format("%08x", i)
local uint = {}
for n in code:gmatch("..") do
table.insert(uint, 1, string.char(tonumber(n, 16)))
end
return table.concat(uint)
end
this.update("正在打包...");
local entry = zis.getNextEntry();
while entry do
local name = entry.getName()
local lib = name:match("([^/]+%.so)$")
if replace[name] then
elseif lib and replace[lib] then
elseif name:find("^assets/") then
elseif name:find("^lua/") then
elseif name:find("META%-INF") then
elseif !name:find("%a") then
else
local entry = ZipEntry(name)
out.putNextEntry(entry)
if entry.getName() == "AndroidManifest.xml" then
if path_pattern and #path_pattern > 1 then
path_pattern = ".*\\\\." .. path_pattern:match("%w+$")
end
local list = ArrayList()
local xml = AXmlDecoder.read(list, zis)
local req = {
[activity.getPackageName()] = packagename,
[info.nonLocalizedLabel] = appname,
[ver] = appver,
[".*\\\\.lua"] = "",
[".*\\\\.luac"] = "",
}
if path_pattern==nil or path_pattern=="" then
req[".*\\\\.alp"] = ""
req["application/alp"] = "application/1234567890"
else
path_pattern=path_pattern:match("%w+$") or path_pattern
req[".*\\\\.alp"] = ".*\\\\."..path_pattern
req["application/alp"] = "application/"..path_pattern
end
for n = 0, list.size() - 1 do
local v = list.get(n)
if req[v] then
list.set(n, req[v])
elseif user_permission then
local p = v:match("%.permission%.([%w_]+)$")
if p and (not user_permission[p]) then
list.set(n, "android.permission.UNKNOWN")
end
end
end
local pt = activity.getLuaPath(".tmp")
local fo = FileOutputStream(pt)
xml.write(list, fo)
local code = activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode
fo.close()
local f = io.open(pt)
local s = f:read("a")
f:close()
s = string.gsub(s, touint32(code), touint32(tointeger(appcode) or 1),1)
s = string.gsub(s, touint32(18), touint32(tointeger(appsdk) or 18),1)
local f = io.open(pt, "w")
f:write(s)
f:close()
local fi = FileInputStream(pt)
copy(fi, out)
os.remove(pt)
elseif not entry.isDirectory() then
copy2(zis, out)
end
end
entry = zis.getNextEntry()
end
out.setComment(table.concat(md5s))
--print(table.concat(md5s,"/n"))
zis.close();
out.closeEntry()
out.close()
if #errbuffer == 0 then
this.update("正在签名...");
os.remove(apkpath)
Signer.sign(tmp, apkpath)
os.remove(tmp)
activity.installApk(apkpath)
--[[import "android.net.*"
import "android.content.*"
i = Intent(Intent.ACTION_VIEW);
i.setDataAndType(activity.getUriForFile(File(apkpath)), "application/vnd.android.package-archive");
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.update("正在打开...");
activity.startActivityForResult(i, 0);]]
return "打包成功:" .. apkpath
else
os.remove(tmp)
this.update("打包出错:\n " .. table.concat(errbuffer, "\n"));
return "打包出错:\n " .. table.concat(errbuffer, "\n")
end
end
--luabindir=activity.getLuaExtDir("bin")
--print(activity.getLuaExtPath("bin","a"))
local function bin(path)
local p = {}
local e, s = pcall(loadfile(path .. "init.lua", "bt", p))
if e then
create_error_dlg2()
create_bin_dlg()
bin_dlg.show()
activity.newTask(binapk, update, callback).execute { path, activity.getLuaExtPath("bin", p.appname .. "_" .. p.appver .. ".apk") }
else
Toast.makeText(activity, "工程配置文件错误." .. s, Toast.LENGTH_SHORT).show()
end
end
--bin(activity.getLuaExtDir("project/demo").."/")
return bin
自定义的Lua请求库
knet.lua
--
-- 更为便捷的请求封装
--
local kox = {
_URL = nil,
_DATA = nil,
SomeThing = {},
_COOKIES = nil,
_METHOD = "get",
_ChARSET = nil,
_HEADERS = {
["User-Agent"] =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69"
}
}
---设置请求地址
---@param fetchUrl string
---@return table
function kox:setUrl(fetchUrl)
if self._URL == nil then
self._URL = fetchUrl
end
return self
end
--- 增加请求头
---@param key string
---@param value string
function kox:setHeaders(key, value, ...)
local op = { ... }
self._HEADERS[string.lower(key)] = value
if #op > 0 then
for k, v in pairs(op) do
self._HEADERS[string.lower(tostring(k))] = v
end
end
return self
end
--- 设置请求模式
---@param method string
function kox:setMethod(method)
self._METHOD = string.lower(method)
return self
end
--- 增加参数
---@param data string
function kox:setData(data)
self._DATA = data
return self
end
--- 发送请求
---@param callBack function
function kox:send(callBack, errorBack)
xpcall(function()
if self._METHOD == "get" then
Http.get(self._URL, self._COOKIES, self._ChARSET, self._HEADERS, callBack)
elseif self._METHOD == "post" then
Http.post(self._URL, self._DATA, self._COOKIES, self._ChARSET, self._HEADERS, callBack)
end
end, errorBack)
return self
end
-- 默认传递参数
function kox.Err(err)
print(err)
end
return kox
--[[
Useage:
local Request = import "kox.knet"
Request:setUrl("https://www.baidu.com")
Request:setMethod("Post")
Request:setData(self.Params)
Request:setHeaders("Content-Type", "application/x-www-form-urlencoded")
Request:send(self.SuccessCaller, kox.Err)
]]
标签:end,name,..,AideLua,import,Go,path,local,云端
From: https://www.cnblogs.com/kongxiaoan/p/18216177