likeadmin部署

以下内容写于2023年9月17日,likeadmin版本

likeadmin部署_第1张图片

1.登录页404,且无法登录

参照官方教程部署后,访问登录页,能打开但提示404,点登录也是404,在issues中搜到新搭建的环境,登录管理后台,报request failed with status code 404 · Issue #I79LNI · 码多多AI/likeadmin(Python版) - Gitee.com,里面有篇likeadmin抢险体验_热心的裴同学的博客-CSDN博客,照着配完后,能登录,感谢这两位大佬。

2.登录页的图片加载不出来,网站ico也加载不出来

在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,重启后图片可以加载了。感谢这三位大佬。

3.列表页接口404

例如后台中点文章管理,提示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。

4.自定义HTTP异常状态码和HTTP异常返回值

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的前后端代码中都能搜到,改的话最好同步修改,比如新增一个状态码时,前后端代码中都新增,下面以后端代码为例,后端代码只搜到一处。 

 likeadmin部署_第2张图片

 然后搜索TOKEN_EMPTY,进入搜索结果中的第一个

likeadmin部署_第3张图片

 likeadmin部署_第4张图片

发现它使用了AppException,按住Ctrl键并单击它,进去看看

likeadmin部署_第5张图片 

 也就是说,先在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中的默认值

likeadmin部署_第6张图片 

不知道为什么fastapi的HTTPException在likeadmin中不行。

最后补充下前端需要同步修改的文件:likeadmin/admin/src/enums/requestEnums.ts和/likeadmin/admin/src/utils/request/index.ts,还搜到了uniapp的,这里就不列出了。 

5.新建账号后登录成功,工作台页面提示无相关权限

新建非超级管理员账号,设置角色,并且角色对应的权限全部勾选,然后登录这个账号,登录成功自动跳转到工作台,页面提示无相关权限。

likeadmin部署_第7张图片likeadmin部署_第8张图片likeadmin部署_第9张图片

全局搜索无相关权限,发现定义在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的打印结果和我的不一样,上面注释中我写的打印的值,是我又修改了一处地方后的,也可能是修改前一直都是,记不清了。因为改完上面后发现还是提示无相关权限,朋友说可能是菜单管理中权限标识配错了。

likeadmin部署_第10张图片 

打印的auths是common:index:console,打印的menus中也是类似system:admin:list这种,三个值中间用冒号连接,而出现问题时,是登录后自动跳转到工作台页面,访问的工作台,菜单管理里配的index:console,配成common:index:console就可以了,这个对应工作台页面的路由。

likeadmin部署_第11张图片

配置时也有提示,要写什么格式。

likeadmin部署_第12张图片 

感谢朋友的帮助和指导。

6.Nginx HTTP文件服务器无法访问

一开始,整个项目都在/root中,配置了Nginx HTTP服务器后,浏览器通过url访问不了文件夹/文件,403。

然后在/home中新建一个文件夹,再将项目放在这个文件夹中,再用chmod -R 666给需要通过url访问的文件夹设置权限,浏览器访问某些文件夹时会跳转到其他url,浏览器标签页中显示的网页title也变了,如变成"首页",而有的文件夹能正常访问。

不能访问的文件夹的例子:public文件夹,里面有html文件;public/uploads文件夹,里面有html文件。

然后单独创建一个空文件夹也能访问。发现有的文件夹中含有html文件,打开了某一个后,内容里有"首页"字样,这个文件夹无法通过浏览器访问,删除这个html文件或改扩展名后,这个文件夹就能访问了。应该是文件夹中有html文件时,访问这个文件夹会渲染这个html,所以就看不到这个文件夹中的文件了。

你可能感兴趣的:(1024程序员节,python,fastapi)