From 11a9ad0105f27eae45ae8f278c5cbdba5aa7e2c4 Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 9 Sep 2026 04:50:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BA=91=E8=B6=85=E6=9C=8D=E5=BE=85?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B8=85=E5=8D=95=20#1/#3=20+=20=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E6=88=90=E5=91=98=E5=85=AC=E5=8F=B8=E5=88=97=E8=A1=A8?= =?UTF-8?q?=20+=20main=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #1 站内消息:新增 GET /opc/messages/unread-count(红点角标)、POST /opc/messages/read(批量/全部已读),MessageRepository 补 unread_count/mark_read - #3 发布活动 500:events 表补 created_at 列(models.Event + alembic 0072 迁移,旧库需执行 alembic upgrade head) - rbac_admin 园区成员接口补充所属公司列表 companies(含 is_admin) - main.py 修复 root() 语法错误,APP_NAME 更新为云南省超级个体服务平台 - 补充 nginx ssl 证书(opc.pinesound.cn) --- .../versions/0072_events_add_created_at.py | 43 +++++++ app/api/routers/rbac_admin.py | 12 +- app/api/routers/rbac_opc.py | 23 ++++ app/infrastructure/models.py | 1 + app/infrastructure/repositories.py | 21 ++++ app/main.py | 2 +- nginx/nginx/ssl/opc.pinesound.cn_bundle.crt | 114 ++++++++++++++++++ 7 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 alembic/versions/0072_events_add_created_at.py create mode 100644 nginx/nginx/ssl/opc.pinesound.cn_bundle.crt diff --git a/alembic/versions/0072_events_add_created_at.py b/alembic/versions/0072_events_add_created_at.py new file mode 100644 index 0000000..374b5f4 --- /dev/null +++ b/alembic/versions/0072_events_add_created_at.py @@ -0,0 +1,43 @@ +"""events 表补充 created_at 列(发布活动按此排序,修复发布活动 500)。 + +- 旧库 events 表无 created_at 列,create_activity 写入时触发 + Unknown column 'created_at' → 500。 +- 本迁移对已有表补可空列;新建表由 models.Event 定义直接包含。 +""" +from __future__ import annotations + +import sqlalchemy as sa +from alembic import op + +revision = "0072_events_add_created_at" +down_revision = "0071_ent_onboard_tables" +branch_labels = None +depends_on = None + + +def _column_exists(bind, table: str, column: str) -> bool: + return bool( + bind.execute( + sa.text( + "SELECT COUNT(*) FROM information_schema.COLUMNS " + "WHERE TABLE_SCHEMA = DATABASE() " + "AND TABLE_NAME = :t AND COLUMN_NAME = :c" + ), + {"t": table, "c": column}, + ).scalar() + ) + + +def upgrade() -> None: + bind = op.get_bind() + if not _column_exists(bind, "events", "created_at"): + op.add_column( + "events", + sa.Column("created_at", sa.String(40), nullable=True, default=""), + ) + + +def downgrade() -> None: + bind = op.get_bind() + if _column_exists(bind, "events", "created_at"): + op.drop_column("events", "created_at") diff --git a/app/api/routers/rbac_admin.py b/app/api/routers/rbac_admin.py index 07ac695..3e04242 100644 --- a/app/api/routers/rbac_admin.py +++ b/app/api/routers/rbac_admin.py @@ -647,7 +647,8 @@ async def admin_park_tenant_members( d["user"] = {"id": m["user_id"], "username": "", "nickname": "", "phone": "", "role": "", "avatar": "", "certification_status": "", "deleted": True} - # 查询该用户在本园区下是否为企业管理员 + # 查询该用户在本园区下的所属公司及是否为企业管理员 + d["companies"] = [] d["company_admin"] = None try: cm_rows = (await db.session.execute( @@ -660,12 +661,17 @@ async def admin_park_tenant_members( ) )).all() for cm, pc in cm_rows: - if cm.member_type == "admin" or cm.is_admin: + is_admin = cm.member_type == "admin" or cm.is_admin + d["companies"].append({ + "company_id": pc.id, + "company_name": pc.name, + "is_admin": is_admin, + }) + if is_admin and d["company_admin"] is None: d["company_admin"] = { "company_id": pc.id, "company_name": pc.name, } - break except Exception: pass diff --git a/app/api/routers/rbac_opc.py b/app/api/routers/rbac_opc.py index fa005d5..e18bb5d 100644 --- a/app/api/routers/rbac_opc.py +++ b/app/api/routers/rbac_opc.py @@ -263,6 +263,29 @@ async def opc_messages( return {"items": await db.messages.list_by_user(user["id"])} +@router.get("/messages/unread-count", summary="站内消息未读数(红点角标)") +async def opc_messages_unread_count( + db: Database = Depends(get_db), + user: dict = Depends(require_roles("opc_member")), +): + return {"unread": await db.messages.unread_count(user["id"])} + + +@router.post("/messages/read", summary="标记站内消息已读") +async def opc_messages_read( + payload: dict, + db: Database = Depends(get_db), + user: dict = Depends(require_roles("opc_member")), +): + """body 传 ``{"ids": [...]}`` 标记指定消息;不传 ids 标记全部已读。""" + raw_ids = payload.get("ids") + ids: list[str] | None = None + if raw_ids: + ids = [str(i) for i in raw_ids if str(i)] + updated = await db.messages.mark_read(user["id"], ids) + return {"ok": True, "updated": updated} + + @router.get("/profile", summary="个人资料") async def opc_profile( db: Database = Depends(get_db), diff --git a/app/infrastructure/models.py b/app/infrastructure/models.py index 4532513..fe8745d 100644 --- a/app/infrastructure/models.py +++ b/app/infrastructure/models.py @@ -976,6 +976,7 @@ class Event(Base): signup_mode: Mapped[str | None] = mapped_column(String, default="mini") # mini 小程序报名 | external 外链报名 publisher_id: Mapped[str | None] = mapped_column(String, default="") # 发布者 publisher_name: Mapped[str | None] = mapped_column(String, default="") + created_at: Mapped[str | None] = mapped_column(String, default="") # 发布/创建时间(按此排序) class Booking(Base): diff --git a/app/infrastructure/repositories.py b/app/infrastructure/repositories.py index 3214077..4a11b21 100644 --- a/app/infrastructure/repositories.py +++ b/app/infrastructure/repositories.py @@ -2470,6 +2470,27 @@ class MessageRepository: )).all() ) + async def mark_read( + self, + user_id: str, + message_ids: list[str] | None = None, + ) -> int: + """标记站内消息已读;message_ids 为空时标记该用户全部消息。 + + Returns: + 本次更新为已读的消息条数。 + """ + stmt = ( + update(Message) + .where(Message.user_id == user_id, Message.read.is_(False)) + ) + if message_ids: + stmt = stmt.where(Message.id.in_(message_ids)) + stmt = stmt.values(read=True) + result = await self.session.execute(stmt) + await self.session.commit() + return int(result.rowcount or 0) + @staticmethod def _to_dict(m: Message) -> dict: return { diff --git a/app/main.py b/app/main.py index 9e86af4..df5c693 100644 --- a/app/main.py +++ b/app/main.py @@ -45,7 +45,7 @@ from app.api.routers import invite as invite_router from app.api.routers import rbac_compute_pricing as compute_pricing_router from app.im import router as im_router -APP_NAME = "云超服Agents 演示后端" +APP_NAME = "云南省超级个体服务平台" @asynccontextmanager diff --git a/nginx/nginx/ssl/opc.pinesound.cn_bundle.crt b/nginx/nginx/ssl/opc.pinesound.cn_bundle.crt new file mode 100644 index 0000000..e7fe971 --- /dev/null +++ b/nginx/nginx/ssl/opc.pinesound.cn_bundle.crt @@ -0,0 +1,114 @@ +-----BEGIN CERTIFICATE----- +MIIHFDCCBPygAwIBAgIULTMMj1JHpgAyKuYia2zWpYunbvcwDQYJKoZIhvcNAQEL +BQAwWzELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJTAjBgNVBAMTHFRydXN0QXNpYSBEViBUTFMgUlNBIENBIDIwMjQw +HhcNMjYwODIyMDYwMDAwWhcNMjYxMTIwMDU1OTU5WjAbMRkwFwYDVQQDExBvcGMu +cGluZXNvdW5kLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtQDR +H3LY/xELOkGOOIt0+e7INzifREex2xvO2TCv8lS5QsSW600W5KVJui5aqOUIa1FB +XnFx4UvMgLqt5Dup5maTudK1kPVx/lSdPSKYvFm6VmOLT0LbqwX/YAoufZi3ga/v +lNPnXlPVqBp57Yy5ocDjeqROYiApC29gS12VN3GuZW6FfMWZt3Sjjcqul3j0QcFf +gB0eM83p6NUMuzeLdY1lTfah0gNqWIbg6vjgpWRGoISU0egF8jXzHujtbCZjmGSa +wpvwCiGnP8urjRPWu8CnMuvnMM/awbeKxnCm6zbgd8jCnu0yBQ+s/dT1NY9a1aDF +bsda84oeh1Vor9JPYQIDAQABo4IDDjCCAwowDAYDVR0TAQH/BAIwADAfBgNVHSME +GDAWgBRhdQVOGvUPwWAgIMIo2Yuz4sF9tDCBlwYIKwYBBQUHAQEEgYowgYcwQwYI +KwYBBQUHMAKGN2h0dHA6Ly9pY2Eud3QudHJ1c3Rhc2lhLmNvbS9UcnVzdEFzaWFE +VlRMU1JTQUNBMjAyNC5jcnQwQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLnd0LnRy +dXN0YXNpYS5jb20vVHJ1c3RBc2lhRFZUTFNSU0FDQTIwMjQwGwYDVR0RBBQwEoIQ +b3BjLnBpbmVzb3VuZC5jbjATBgNVHSAEDDAKMAgGBmeBDAECATATBgNVHSUEDDAK +BggrBgEFBQcDATBIBgNVHR8EQTA/MD2gO6A5hjdodHRwOi8vY3JsLnd0LnRydXN0 +YXNpYS5jb20vVHJ1c3RBc2lhRFZUTFNSU0FDQTIwMjQuY3JsMB0GA1UdDgQWBBRa +rxx7jMxy7/4JhVLhBKkfEQuhYDAOBgNVHQ8BAf8EBAMCBaAwggF9BgorBgEEAdZ5 +AgQCBIIBbQSCAWkBZwB1ANgJVTuUT3r/yBYZb5RPhauw+Pxeh1UmDxXRLnK7RUsU +AAABoChcLDQAAAQDAEYwRAIgRTNq73ITRCk2KkeepTCsgzP9hUMfRBi9AS5NayLZ +MzcCIEKfEFIa1r5ql7nvm+ssANLFJo/5HqWvS1E7SG7We760AHcAdNudWPfUfp39 +eHoWKpkcGM9pjafHKZGMmhiwRQ26RLwAAAGgKFwl4AAABAMASDBGAiEAxzOsRwlC +Q3feu30eGnzyJniYRlHCSxulEJ4ioPwHNs0CIQDojAAu8n/ffYkVzTxdDsmR0CDG +U/ypjAuYbEj8Sfrd1wB1ANdtfRDRp/V3wsfpX9cAv/mCyTNaZeHQswFzF8DIxWl3 +AAABoChcKRgAAAQDAEYwRAIgWumrxIXo1slMAeSOQjqfDMEQAH7KRjT3cPDH/tzA +fBQCICV9Ne43B14pro9okUfo7+F7/MpRWpB437jZUS+XRtH+MA0GCSqGSIb3DQEB +CwUAA4ICAQAmHEI6wsRNP4GkVXslVOOg0V1+gbSpqGk15OE9x2Vafg63MK3acci0 +yFSbGboOZQOoidWLIiG3q04Z5mmgsvujphggYMBx2keUz4BrJ1N+ClRwn7an/vPI +oGOcbCTlNk9/k6odpoFR8lgvGNO/BwckWG4kVyVvhVf30gSDnZ02St+/C4MWP5zd +lHVmMeHSN/odbhCchTlpwRksYgHLj7RHkp0c/wksSdGSsygVER3UoID/TVOaU7rp +VuAuTkOb953tJz2Mbso2AqnpawFtu3aIh/+K6ZljpiyseSm+oG1UVxIJjQdVLbXE +l9GTM344PfcA62Q7spEFt/MIUaVPiObBFloAoWs9182eCEg0rnuUtXEUDMDfJZdv +lEzTaWgkpnagMMiNyPghQnfQ2dfCqkdcYm/MP2V7tmBH0WpJcZIgOB3E37e1zk3O +YSCAxpj7ltuFnmcGwEuBHh/HYlISl3dT2+NwO3AVxzAeb1Wgz6PGJh+kqPmU/1// +uMQqFOgg2kZ0nGo4938qMH0KmXS7ioHrhs6LMikXDxRyj/H7NjSPseSbVjBeBZA7 +licFJdgwZtOsRfu3PM9QOatOdQgQh4sm2h8C85SraHGmnJoeAxU3Wv6uDgNNk/MR +Retkjz/P8l3C+HgCcrITU7vD+ZF5Djm9X2C2pspShcxUsEBrSeWHxA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGvTCCBKWgAwIBAgIUHJ5rQS3rCoVGHeA9vjCYTQVziIswDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDYxOTE1WhcNMzQwNTE1MDYxOTE0WjBbMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjElMCMGA1UEAxMcVHJ1 +c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAK2GCKULRuSjgEhVn7j0cnfuSmrxS5/5H77qmOJwWJkMVE+SgBQn +4mkoxVwF03E517T5YZZumwy5iCxktdnKp5Uq0C7td8yYnZgLb18pUeuzfkJBNWvI +MccfeGCI6qdnl1nGv15loYjP0TEt7C9VyVrK+zmeTxdkBb5xP/hQPFVRWQROnruX +gs7G1jbEX3Hhih+OR1dxcQkk7yVM493zuDVBBlNqH4KA/FawaGNcA/KRKHI0cqyT +Q/GjthO3yyivTlu07/QGV5VTl2oor9ntyXdCmEktu0aKH3kOLRjBE5CiT55Gts7d +1MBRwI3pAjLtDia4+Sz0XmPB/mH1/KO7EKtg+5XVZe3amDpboawWAS2oaKuDKWx/ +fh5AA0ViSS+8nDAbrxE4b/gPno28GZSG41VELtSxZvgA0T8lc1oBBywQc0BWfCC9 +PoSl2xSxhjKjJfY+od+EALfmEnEH3A53iK4z4gqpBo8O29omZ8HYxO9Tb5aemgla +gP+8oWmxv6aZwYOpWHb3AqOxEoTMV0VwzfEqLsMGcP6koLCB51uPGu8kDF8PIpbu +jallH/6K9QySf3S2H4SCme94tAjznz58En48PGoQPCSTDM6CyeEZqR3tEAnUF1gS +4T+hkEiskHWLQ6QosxoFX2+qMMeQNHictf5O/ZPXvIv34yxE5kiiBWN3AgMBAAGj +ggF6MIIBdjASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFLgHkXlcBvRG +/XtZylomkadFK/hTMIGTBggrBgEFBQcBAQSBhjCBgzBBBggrBgEFBQcwAoY1aHR0 +cDovL2ljYS53dC50cnVzdGFzaWEuY29tL1RydXN0QXNpYVRMU1JTQVJvb3RDQS5j +cnQwPgYIKwYBBQUHMAGGMmh0dHA6Ly9vY3NwLnd0LnRydXN0YXNpYS5jb20vVHJ1 +c3RBc2lhVExTUlNBUm9vdENBMBMGA1UdIAQMMAowCAYGZ4EMAQIBMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjBGBgNVHR8EPzA9MDugOaA3hjVodHRwOi8v +Y3JsLnd0LnRydXN0YXNpYS5jb20vVHJ1c3RBc2lhVExTUlNBUm9vdENBLmNybDAd +BgNVHQ4EFgQUYXUFThr1D8FgICDCKNmLs+LBfbQwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQCpIjcVXxHv5IjZr2h2+PLiKzm8CW0EoSftEvjLDMec +Roc7okR/tz3WZ/NWS6YxURaE0Qbt0ZHLOtPisaACcpOCVeoLhHLfF54U2RQposA/ +NK8Gdu1vNHWHDpF9H2MiY+VBPKr+lctYcvV/MipWa7SsBkZEu/Srd5Gp5tU4G7L0 +a/X7xcv/lwjbdvARe0Rz5tp1BEn0P/7exFqN3gkqujo5RvMoegi0EgsuM+ya5bCu +vyfbp9Qzah+atfvzN+sYVaJjxnjogil7RRPNq+eR3nGocA+U0ypKl4ifpr+droZ9 +Fk7axJ7Fs3kkIxomcSINLFv0+m2IFOQvUWn5OBXkeP+NcEaVySJx/TTpHJ7U+Ill +DzZZhAoqdxGb0Ac0SLmtGHnQVCs93aWIMcTNYqJtagE39kN+EcPAVb1znp8a7tw6 +D6kI1cKTLBHMyDBtUAZVmboDacJmQodTQ+tFbIMCRzrP2M51CiXDK4L6W27C8b9V +Upq9WlIFeTfIqtkAq1iHyjv0YdoReprIHD2r/9g5X4mbMmsOFyaMT+lwnT5S0StD ++eiMQauCYnfqf88qJwbT5xCgRbd01yVm/V448GS/5XCZNSer4dToOzGcRvjBJTft +XbdVkfXe0MAP3xhbpoihEKPyorWPLWr/iWcVDq4o25yFTSe/WFa30ZwieLhMDign +Gg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/TCCBOWgAwIBAgIRALLcTDSTLXCc1L8LCZQBaVwwDQYJKoZIhvcNAQEMBQAw +fjELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEiMCAG +A1UEAxMZQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQTAeFw0yNTEwMDcwNjU3MDha +Fw0yOTAxMTQwNjU3MDhaMFgxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFz +aWEgVGVjaG5vbG9naWVzLCBJbmMuMSIwIAYDVQQDExlUcnVzdEFzaWEgVExTIFJT +QSBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwxa4G2qk +RHPl1k70uc9bC8HRmoH1Y7CPQ8G7CFoaeuEHdiYfj2lWvv42YNAMg82U6ufFLVwv +BRYCngqoL+VgJlSW+JhAHayunXRnL1T7lGOY4yZKg8aVtglDUM0Nfd5EDmAST1s1 +vb+ZOG19bNriaHMf+TGlGRBzBSrDMhmF6jSq3UIeMI0/nrUeYdVvvQBycq0SP6qm +SXPyhhWVDBBfUWTO/3e4yW2s5dWY8ZkubOPJRPm1QycITfZ+3kR5u7KMHNrTS2wu +1sN4t0zVpOTa3IqODv8Pw6ZgJijPNr/6VwrsWz/pMBZ7xNvE1GSgMPvl/R1xkt0p +j0FY3gCtOv09fBqosRfwTjR4WCXWhSHreR3Q3KvPYjywx5eL1tCf0/483sXj/DqD +cIQhHQnCofy7KCFlU2B6kG2WOFv/8dd6W23TyXBJSrodOtBQ2jIgGeSLP+vVFiY3 +PPt1nrAHcLgUYHfc9g9cUgq8XW2NUQraxRjIm23csIOzf6NOeEyYJavyfi4gXoIV +ptbYj6zlzTKGyPnk2oniOz6TxRNq/8f3GPxnte8YLFSrQNNsTe3FyLc4T2TElExP +oM2upcYnJXUnbcbanI6wBZnVKBHFIdS4/MeWq0pkWWFrE/xLzJKmD6RRvw5YkNmC +gzmmX7P6bpKzzTMOhajxKdOrWa3W297UK4ECAwEAAaOCAZowggGWMHUGCCsGAQUF +BwEBBGkwZzA7BggrBgEFBQcwAoYvaHR0cDovL3RydXN0YXNpYS5yZXBvc2l0b3J5 +LmNlcnR1bS5wbC9jdG5jYS5jZXIwKAYIKwYBBQUHMAGGHGh0dHA6Ly9zdWJjYS5v +Y3NwLWNlcnR1bS5jb20wHwYDVR0jBBgwFoAUCHbNywf/JPbFze27kLzihDdGdfcw +EgYDVR0TAQH/BAgwBgEB/wIBAjBtBgNVHR8EZjBkMDKgMKAuhixodHRwOi8vY2Vy +dHVtLWNybC53dC50cnVzdGFzaWEuY29tL2N0bmNhLmNybDAuoCygKoYoaHR0cDov +L3RydXN0YXNpYS5jcmwuY2VydHVtLnBsL2N0bmNhLmNybDATBgNVHSUEDDAKBggr +BgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwNQYDVR0gBC4wLDAIBgZngQwBAgEwCAYG +Z4EMAQICMAcGBWeBDAEBMA0GCysGAQQBgttOAgEBMB0GA1UdDgQWBBS4B5F5XAb0 +Rv17WcpaJpGnRSv4UzANBgkqhkiG9w0BAQwFAAOCAQEAIf2vyzSuFzECCTH3LPB/ +bmUH1aXbVm8zVwxof+4tn8EsNYH8CgM2AHxRkXP5jOitqylySAuVIATJLYFmBQJO +XMCgf9NbbuG8P1VgnUQuUhQMChp2zT946Y55xZVANGg4o2+orw4SpclSy/R7eayX +Lccr++uqke9gJlX3FCpi4Sa8sg/kSfSv4p7tuwu4YAk+XPBew3jQc9wueAJwQ/Rz +H4LtICrekL0QuNHb0BhPcn4qELBF6f+IrOBA2MKw1m0C9y6uO8K+xd1JMpG0/RYZ +cGIH+rR5fBymQSNuD+F5I2PeXMPpigPcEdOi/NWZcCLncJLDrEU62T8hkif6MQ4F +Dw== +-----END CERTIFICATE----- \ No newline at end of file