以下内容写于2023年9月17日,likeadmin版本
参照官方教程部署后,访问登录页,能打开但提示404,点登录也是404,在issues中搜到新搭建的环境,登录管理后台,报request failed with status code 404 · Issue #I79LNI · 码多多AI/likeadmin(Python版) - Gitee.com,里面有篇likeadmin抢险体验_热心的裴同学的博客-CSDN博客,照着配完后,能登录,感谢这两位大佬。
在issues中搜到部署完成之后 图片无法加载,哪位遇到,指点一下,谢谢 · Issue #I7ESYX · 码多多AI/likeadmin(Python版) - Gitee.com,里面说了几点
看下路径里面有没有图片
图片端口不对,后台端口应该是8000(具体多少看你的配置),如果是80那没问题
还有就是协议,本地没有证书,不是https
解决了,的确是,后端端口是8000,env文件中的端口配置的是80,所以访问不了
第一点图片文件我看了存在;第二点我看图片请求的80端口,而后端默认8000端口(就是python asgi.py启动后的端口) ,所以要把server文件夹中.env文件中DOMAIN中80改成8000;重启后图片还是不显示,接着第三点把DOMAIN中https改成http,重启后图片可以加载了。感谢这三位大佬。
例如后台中点文章管理,提示404,前端请求的http://127.0.0.1:8000/article/api/article/list?pageNo=1&pageSize=15&title=&cid=&isShow=,但后端接口实际是http://127.0.0.1:8000/api/article/list?pageNo=1&pageSize=15&title=&cid=&isShow=,不知道为什么前端请求时端口号后面多了article。
解决方法是前面likeadmin抢险体验_热心的裴同学的博客-CSDN博客中说的改index.ts中baseUrl,但那篇文章作者是改成空字符串了,咨询前端同事后我这里实际应该改成http://127.0.0.1:8000,这个值的作用类似前端上传图片时用全局baseUrl和图片的相对路径拼接得到图片的绝对路径(只是类似,实际不一样),上线时要改成公网ip和端口,可能还有https。
index.ts中urlPrefix改为'/api',因为上面那篇文章中admin/vite.config.ts中设置了proxy代理/api,所以前缀匹配值urlPrefix要修改为/api。上线时修改admin/vite.config.ts中defineConfig()中server中proxy中的target为公网ip和端口,可能还有https。
fastapi中向客户端返回HTTP错误响应,可以使用HTTPException。
文档中示例代码
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
但我在likeadmin中用同样的写法,想返回401登录失败,触发异常时postman显示
{
"code": 500,
"msg": "系统错误",
"data": []
}
然后把raise改成return,响应变成
{
"code": 200,
"msg": "成功",
"data": {
"status_code": 401,
"detail": "登录失败",
"headers": null
}
}
想起来likeadmin中不带token访问需要token的接口时提示
{
"code": 322,
"msg": "token参数为空",
"data": []
}
这是我想要的响应格式,但不知道怎么实现,请教朋友后,他说可以全局搜索"token参数为空"这几个字,看likeadmin怎么实现的。likeadmin的前后端代码中都能搜到,改的话最好同步修改,比如新增一个状态码时,前后端代码中都新增,下面以后端代码为例,后端代码只搜到一处。
然后搜索TOKEN_EMPTY,进入搜索结果中的第一个
发现它使用了AppException,按住Ctrl键并单击它,进去看看
也就是说,先在likeadmin/server/like/http_base.py中添加自定义的HTTP异常状态码
class HttpResp:
LOGIN_DENY = HttpCode(401, '登录失败') # 客户端面板未在允许登录的时间段内登录
然后使用时
from like.exceptions.base import AppException
from like.http_base import HttpCode, HttpResp
def test():
raise AppException(HttpResp.LOGIN_DENY) # 可以给AppException传入msg参数,是自定义的报错内容提示,如果不传那提示就是HttpResp中的默认值
不知道为什么fastapi的HTTPException在likeadmin中不行。
最后补充下前端需要同步修改的文件:likeadmin/admin/src/enums/requestEnums.ts和/likeadmin/admin/src/utils/request/index.ts,还搜到了uniapp的,这里就不列出了。
新建非超级管理员账号,设置角色,并且角色对应的权限全部勾选,然后登录这个账号,登录成功自动跳转到工作台,页面提示无相关权限。
全局搜索无相关权限,发现定义在http_base.py,对应NO_PERMISSION,再全局搜索NO_PERMISSION,发现在like/dependencies/verify.py中verify_token()末尾用了,下面只放出verify_token()的部分原始源码,这些代码是和解决这个问题有关的。
async def verify_token(request: Request):
"""登录状态及权限校验依赖项"""
role_ids = mapping.get('role_ids')
menus = []
# 校验角色权限是否存在
for role_id in role_ids:
if not await RedisUtil.hexists(AdminConfig.backstage_roles_key, role_id):
await SystemAuthPermService.cache_role_menus_by_role_id(role_id)
menus.extend(await RedisUtil.hget(AdminConfig.backstage_roles_key, role_id))
# 验证是否有权限操作
if not (menus and auths in menus.split(',')):
raise AppException(HttpResp.NO_PERMISSION)
还有上面代码中调用的cache_role_menus_by_role_id()的原始源码,在like/admin/service/system/auth_perm.py中。
async def cache_role_menus_by_role_id(cls, role_id: int):
"""缓存角色菜单"""
auth_perms = await db.fetch_all(
system_auth_perm.select().where(system_auth_perm.c.role_id == role_id))
menu_ids = [i.menu_id for i in auth_perms]
menus = []
if menu_ids:
auth_menus = await db.fetch_all(
system_auth_menu.select().where(
system_auth_menu.c.is_disable == 0,
system_auth_menu.c.id in menu_ids,
system_auth_menu.c.menu_type in ['C', 'A'])
.order_by(system_auth_menu.c.menu_sort, system_auth_menu.c.id))
menus = [i.perms for i in auth_menus if i.perms]
await RedisUtil.hset(AdminConfig.backstage_roles_key, str(role_id), ','.join(menus))
后来才知道,上面这两处原始代码都有错误。
我先在verify_token()的for循环结束后打印menus,写文章时忘了它是空列表还是空字符串,反正是空的。然后打印RedisUtil.hexists(AdminConfig.backstage_roles_key, role_id)和RedisUtil.hget(AdminConfig.backstage_roles_key, role_id),分别是True和空字符串。还打印了auths,写文章时忘了当时是index:console还是common:index,反正这俩都不对,是后来才知道它不对。
我新建账号时已经设了角色和权限,menus中竟然没有,咨询朋友后得知问题可能出在verify_token()中"校验角色权限是否存在"这个for循环中。那就退出登录,清空redis,再登一次新建的账号,看看redis中是什么。由于写文章时这个问题已修复,所以这张图没法复现提供了,就是redis中有一条数据key是2,value是空字符串,2是我当前用户的id,value的内容就是我这个号拥有的权限。
for循环中有个判断,hexists不存在时会向redis写入数据,于是手动从redis中删除前面说的key为2的那条数据,再刷新网页,再刷新redis,发现有了key为2的数据,value仍然是空字符串。那说明是向redis写入数据时有问题,所以看cache_role_menus_by_role_id()。
我打印了cache_role_menus_by_role_id()末尾的menus,写文章时忘了当时它是什么值,可能是空列表或空字符串,反正有问题。然后打印auth_menus,这是执行SQL语句的结果,没查到数据。然后修改auth_menus成下面的样子(省略了order_by,排序不重要),这样可以打印出它对应的SQL语句,有其他打印SQL语句的方法请告诉我。
auth_menus = system_auth_menu.select().where(
system_auth_menu.c.is_disable == 0,
system_auth_menu.c.id in menu_ids,
system_auth_menu.c.menu_type in ['C', 'A']).__str__()
print(auth_menus)
SELECT la_system_auth_menu.id, la_system_auth_menu.pid, la_system_auth_menu.menu_type, la_system_auth_menu.menu_name, la_system_auth_menu.menu_icon, la_system_auth_menu.menu_sort, la_system_auth_menu.perms, la_system_auth_menu.paths, la_system_auth_menu.component, la_system_auth_menu.selected, la_system_auth_menu.params, la_system_auth_menu.is_cache, la_system_auth_menu.is_show, la_system_auth_menu.is_disable, la_system_auth_menu.create_time, la_system_auth_menu.update_time
FROM la_system_auth_menu
WHERE false
发现where false,那当然查不到数据了。然后仔细看它的代码,发现用的in,而likeadmin用的SQLAlchemy(增删改查参考链接1),它的in应该写成.in_(),注意in前有一个点,括号里传入列表或元组,其他支持的类型可以点进去看详情。所以将auth_menus改成下面的样子就可以了,可以打印下SQL语句并手动执行SQL语句去数据库中查询,然后和代码的查询结果对比。
auth_menus = await db.fetch_all(
system_auth_menu.select().where(
system_auth_menu.c.is_disable == 0,
system_auth_menu.c.id.in_(menu_ids),
system_auth_menu.c.menu_type.in_(['C', 'A']))
.order_by(system_auth_menu.c.menu_sort, system_auth_menu.c.id))
修改后verify_token()中menus有数据了,但原始源码对它的处理方式不对。它首先把menus定义成空列表,然后对列表进行了错误的处理,导致处理结果不对,可以打印看看,我就不放图了。根据打印结果,以及它代码中用了extend和split,猜测menus定义时可以是空列表或空字符串,对应两种不同的处理方法。
# menus用列表
menus = []
# 校验角色权限是否存在
for role_id in role_ids:
if not await RedisUtil.hexists(AdminConfig.backstage_roles_key, role_id):
await SystemAuthPermService.cache_role_menus_by_role_id(role_id)
aaa = await RedisUtil.hget(AdminConfig.backstage_roles_key, role_id)
print(aaa) # system:admin:list,system:admin:detail,system:admin:add,system:admin:edit等
print(type(aaa)) # 字符串
menus.extend(i for i in aaa.split(','))
# 验证是否有权限操作
print('-------------')
print(menus) # ['system:admin:list', 'system:admin:detail', 'system:admin:add', 'system:admin:edit']等
print(auths) # common:index:console,此时访问的是后台的工作台页面
print('-------------')
if not (menus and auths in menus):
raise AppException(HttpResp.NO_PERMISSION)
# menus用字符串
menus = ''
# 校验角色权限是否存在
for role_id in role_ids:
if not await RedisUtil.hexists(AdminConfig.backstage_roles_key, role_id):
await SystemAuthPermService.cache_role_menus_by_role_id(role_id)
aaa = await RedisUtil.hget(AdminConfig.backstage_roles_key, role_id)
print(aaa) # system:admin:list,system:admin:detail,system:admin:add,system:admin:edit等
print(type(aaa)) # 字符串
menus += await RedisUtil.hget(AdminConfig.backstage_roles_key, role_id)
# 验证是否有权限操作
print('-------------')
print(menus) # system:admin:list,system:admin:detail,system:admin:add,system:admin:edit等
print(auths) # common:index:console,此时访问的是后台的工作台页面
if isinstance(menus, str):
print(menus.split(',')) # ['system:admin:list', 'system:admin:detail', 'system:admin:add', 'system:admin:edit']等
print('--------------')
if not (menus and auths in menus.split(',')):
raise AppException(HttpResp.NO_PERMISSION)
可能你的auths的打印结果和我的不一样,上面注释中我写的打印的值,是我又修改了一处地方后的,也可能是修改前一直都是,记不清了。因为改完上面后发现还是提示无相关权限,朋友说可能是菜单管理中权限标识配错了。
打印的auths是common:index:console,打印的menus中也是类似system:admin:list这种,三个值中间用冒号连接,而出现问题时,是登录后自动跳转到工作台页面,访问的工作台,菜单管理里配的index:console,配成common:index:console就可以了,这个对应工作台页面的路由。
配置时也有提示,要写什么格式。
感谢朋友的帮助和指导。
一开始,整个项目都在/root中,配置了Nginx HTTP服务器后,浏览器通过url访问不了文件夹/文件,403。
然后在/home中新建一个文件夹,再将项目放在这个文件夹中,再用chmod -R 666给需要通过url访问的文件夹设置权限,浏览器访问某些文件夹时会跳转到其他url,浏览器标签页中显示的网页title也变了,如变成"首页",而有的文件夹能正常访问。
不能访问的文件夹的例子:public文件夹,里面有html文件;public/uploads文件夹,里面有html文件。
然后单独创建一个空文件夹也能访问。发现有的文件夹中含有html文件,打开了某一个后,内容里有"首页"字样,这个文件夹无法通过浏览器访问,删除这个html文件或改扩展名后,这个文件夹就能访问了。应该是文件夹中有html文件时,访问这个文件夹会渲染这个html,所以就看不到这个文件夹中的文件了。