Kaynağa Gözat

运营后台和普通用户的登录分开

liuchengsen 1 ay önce
ebeveyn
işleme
020fdaa0db

+ 0 - 617
.idea/dbnavigator.xml

@@ -1,617 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="DBNavigator.Project.DDLFileAttachmentManager">
-    <mappings />
-    <preferences />
-  </component>
-  <component name="DBNavigator.Project.DataEditorManager">
-    <record-view-column-sorting-type value="BY_INDEX" />
-    <value-preview-text-wrapping value="false" />
-    <value-preview-pinned value="false" />
-  </component>
-  <component name="DBNavigator.Project.DatabaseAssistantManager">
-    <assistants>
-      <assistant-state connection-id="virtual-oracle-connection" assistant-type="SELECT_AI" default-profile-name="" selected-chat-id="" availability="UNCERTAIN" acknowledgement="NONE">
-        <tools />
-      </assistant-state>
-      <assistant-state connection-id="6e17216c-4396-42b7-a333-2d90a3c933e5" assistant-type="PUBLIC" default-profile-name="" selected-chat-id="" availability="AVAILABLE" acknowledgement="NONE" selected="true">
-        <tools />
-      </assistant-state>
-    </assistants>
-    <selection-state>
-      <model-selection />
-    </selection-state>
-  </component>
-  <component name="DBNavigator.Project.DatabaseBrowserManager">
-    <autoscroll-to-editor value="false" />
-    <autoscroll-from-editor value="true" />
-    <show-object-properties value="true" />
-    <loaded-nodes />
-  </component>
-  <component name="DBNavigator.Project.DatabaseConsoleManager">
-    <connection id="6e17216c-4396-42b7-a333-2d90a3c933e5">
-      <console name="Connection" type="STANDARD" schema="mysql" session="Main" />
-    </connection>
-  </component>
-  <component name="DBNavigator.Project.DatabaseEditorStateManager">
-    <last-used-providers />
-  </component>
-  <component name="DBNavigator.Project.DatabaseFileManager">
-    <open-files />
-  </component>
-  <component name="DBNavigator.Project.DatabasePrerequisiteManager">
-    <prerequisites />
-  </component>
-  <component name="DBNavigator.Project.DatabaseSessionManager">
-    <connection id="6e17216c-4396-42b7-a333-2d90a3c933e5" />
-  </component>
-  <component name="DBNavigator.Project.ExecutionManager">
-    <retain-sticky-names value="false" />
-  </component>
-  <component name="DBNavigator.Project.ObjectQuickFilterManager">
-    <last-used-operator value="EQUAL" />
-    <filters />
-  </component>
-  <component name="DBNavigator.Project.Settings">
-    <connections>
-      <connection source-id="" id="6e17216c-4396-42b7-a333-2d90a3c933e5" active="true" signed="true">
-        <database>
-          <name value="Connection" />
-          <description value="" />
-          <database-type value="MYSQL" />
-          <config-type value="BASIC" />
-          <database-version value="5.7" />
-          <driver-source value="BUNDLED" />
-          <driver-library value="" />
-          <driver value="" />
-          <url-type value="DATABASE" />
-          <host value="localhost" />
-          <port value="3306" />
-          <database value="mysql" />
-          <tns-folder value="" />
-          <tns-profile value="" />
-          <server-type value="" />
-          <protocol value="" />
-          <url-parameters />
-          <type value="USER_PASSWORD" />
-          <user value="root" />
-          <token-type value="" />
-          <token-config-file value="" />
-          <token-profile value="" />
-          <adb-compartment-ocid value="" />
-          <adb-database-ocid value="" />
-          <azure-token-database-id-uri value="" />
-          <azure-token-tenant-id value="" />
-          <azure-token-client-id value="" />
-          <azure-token-client-certificate-file value="" />
-          <session-user value="root@localhost" />
-        </database>
-        <properties>
-          <auto-commit value="false" />
-        </properties>
-        <ssh-settings>
-          <active value="false" />
-          <proxy-host value="" />
-          <proxy-port value="22" />
-          <proxy-user value="" />
-          <auth-type value="PASSWORD" />
-          <key-file value="" />
-        </ssh-settings>
-        <ssl-settings>
-          <active value="false" />
-          <certificate-authority-file value="" />
-          <client-certificate-file value="" />
-          <client-key-file value="" />
-        </ssl-settings>
-        <details>
-          <charset value="UTF-8" />
-          <session-management value="true" />
-          <ddl-file-binding value="true" />
-          <database-logging value="true" />
-          <connect-automatically value="true" />
-          <restore-workspace value="true" />
-          <restore-workspace-deep value="false" />
-          <environment-type value="default" />
-          <connectivity-timeout value="30" />
-          <idle-time-to-disconnect value="30" />
-          <idle-time-to-disconnect-pool value="5" />
-          <credential-expiry-time value="10" />
-          <max-connection-pool-size value="7" />
-          <alternative-statement-delimiter value="" />
-        </details>
-        <debugger>
-          <compile-dependencies value="true" />
-          <tcp-host-address value="" />
-          <tcp-port-from value="4000" />
-          <tcp-port-to value="4999" />
-          <jdwp-tunnel-type value="NONE" />
-          <debugger-type value="ASK" />
-          <reverse-ssh-tunnel>
-            <host value="" />
-            <port value="22" />
-            <bind-host value="127.0.0.1" />
-            <bind-port value="0" />
-            <auth-type value="PASSWORD" />
-            <user value="" />
-            <key-file value="" />
-          </reverse-ssh-tunnel>
-        </debugger>
-        <object-filters hide-empty-schemas="false" hide-pseudo-columns="false" hide-audit-columns="false">
-          <object-filters />
-          <object-type-filter use-master-settings="true">
-            <object-type name="SCHEMA" enabled="true" />
-            <object-type name="USER" enabled="true" />
-            <object-type name="ROLE" enabled="true" />
-            <object-type name="PRIVILEGE" enabled="true" />
-            <object-type name="CHARSET" enabled="true" />
-            <object-type name="TABLE" enabled="true" />
-            <object-type name="VIEW" enabled="true" />
-            <object-type name="JSON_VIEW" enabled="true" />
-            <object-type name="MATERIALIZED_VIEW" enabled="true" />
-            <object-type name="NESTED_TABLE" enabled="true" />
-            <object-type name="COLUMN" enabled="true" />
-            <object-type name="INDEX" enabled="true" />
-            <object-type name="CONSTRAINT" enabled="true" />
-            <object-type name="DATASET_TRIGGER" enabled="true" />
-            <object-type name="DATABASE_TRIGGER" enabled="true" />
-            <object-type name="SYNONYM" enabled="true" />
-            <object-type name="SEQUENCE" enabled="true" />
-            <object-type name="PROCEDURE" enabled="true" />
-            <object-type name="FUNCTION" enabled="true" />
-            <object-type name="PACKAGE" enabled="true" />
-            <object-type name="TYPE" enabled="true" />
-            <object-type name="TYPE_ATTRIBUTE" enabled="true" />
-            <object-type name="ARGUMENT" enabled="true" />
-            <object-type name="JAVA_CLASS" enabled="true" />
-            <object-type name="JAVA_FIELD" enabled="true" />
-            <object-type name="JAVA_METHOD" enabled="true" />
-            <object-type name="JAVA_RESOURCE" enabled="true" />
-            <object-type name="DIMENSION" enabled="true" />
-            <object-type name="CLUSTER" enabled="true" />
-            <object-type name="DBLINK" enabled="true" />
-            <object-type name="CREDENTIAL" enabled="true" />
-            <object-type name="AI_PROFILE" enabled="true" />
-            <object-type name="AI_MODEL" enabled="true" />
-          </object-type-filter>
-        </object-filters>
-      </connection>
-    </connections>
-    <browser-settings>
-      <general>
-        <display-mode value="TABBED" />
-        <navigation-history-size value="100" />
-        <show-object-details value="false" />
-        <enable-sticky-paths value="true" />
-        <enable-quick-filters value="false" />
-      </general>
-      <filters>
-        <object-type-filter>
-          <object-type name="SCHEMA" enabled="true" />
-          <object-type name="USER" enabled="true" />
-          <object-type name="ROLE" enabled="true" />
-          <object-type name="PRIVILEGE" enabled="true" />
-          <object-type name="CHARSET" enabled="true" />
-          <object-type name="TABLE" enabled="true" />
-          <object-type name="VIEW" enabled="true" />
-          <object-type name="JSON_VIEW" enabled="true" />
-          <object-type name="MATERIALIZED_VIEW" enabled="true" />
-          <object-type name="NESTED_TABLE" enabled="true" />
-          <object-type name="COLUMN" enabled="true" />
-          <object-type name="INDEX" enabled="true" />
-          <object-type name="CONSTRAINT" enabled="true" />
-          <object-type name="DATASET_TRIGGER" enabled="true" />
-          <object-type name="DATABASE_TRIGGER" enabled="true" />
-          <object-type name="SYNONYM" enabled="true" />
-          <object-type name="SEQUENCE" enabled="true" />
-          <object-type name="PROCEDURE" enabled="true" />
-          <object-type name="FUNCTION" enabled="true" />
-          <object-type name="PACKAGE" enabled="true" />
-          <object-type name="TYPE" enabled="true" />
-          <object-type name="TYPE_ATTRIBUTE" enabled="true" />
-          <object-type name="ARGUMENT" enabled="true" />
-          <object-type name="JAVA_CLASS" enabled="true" />
-          <object-type name="JAVA_FIELD" enabled="true" />
-          <object-type name="JAVA_METHOD" enabled="true" />
-          <object-type name="JAVA_RESOURCE" enabled="true" />
-          <object-type name="DIMENSION" enabled="true" />
-          <object-type name="CLUSTER" enabled="true" />
-          <object-type name="DBLINK" enabled="true" />
-          <object-type name="CREDENTIAL" enabled="true" />
-          <object-type name="AI_PROFILE" enabled="true" />
-          <object-type name="AI_MODEL" enabled="true" />
-        </object-type-filter>
-      </filters>
-      <sorting>
-        <object-type name="COLUMN" sorting-type="NAME" />
-        <object-type name="FUNCTION" sorting-type="NAME" />
-        <object-type name="PROCEDURE" sorting-type="NAME" />
-        <object-type name="ARGUMENT" sorting-type="POSITION" />
-        <object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
-      </sorting>
-      <default-editors>
-        <object-type name="VIEW" editor-type="SELECTION" />
-        <object-type name="PACKAGE" editor-type="SELECTION" />
-        <object-type name="TYPE" editor-type="SELECTION" />
-      </default-editors>
-    </browser-settings>
-    <navigation-settings>
-      <lookup-filters>
-        <lookup-objects>
-          <object-type name="SCHEMA" enabled="true" />
-          <object-type name="USER" enabled="false" />
-          <object-type name="ROLE" enabled="false" />
-          <object-type name="PRIVILEGE" enabled="false" />
-          <object-type name="CHARSET" enabled="false" />
-          <object-type name="TABLE" enabled="true" />
-          <object-type name="VIEW" enabled="true" />
-          <object-type name="JSON VIEW" enabled="true" />
-          <object-type name="MATERIALIZED VIEW" enabled="true" />
-          <object-type name="INDEX" enabled="true" />
-          <object-type name="CONSTRAINT" enabled="true" />
-          <object-type name="DATASET TRIGGER" enabled="true" />
-          <object-type name="DATABASE TRIGGER" enabled="true" />
-          <object-type name="SYNONYM" enabled="false" />
-          <object-type name="SEQUENCE" enabled="true" />
-          <object-type name="PROCEDURE" enabled="true" />
-          <object-type name="FUNCTION" enabled="true" />
-          <object-type name="PACKAGE" enabled="true" />
-          <object-type name="TYPE" enabled="true" />
-          <object-type name="JAVA CLASS" enabled="true" />
-          <object-type name="INNER CLASS" enabled="true" />
-          <object-type name="JAVA FIELD" enabled="true" />
-          <object-type name="JAVA METHOD" enabled="true" />
-          <object-type name="JAVA PARAMETER" enabled="true" />
-          <object-type name="JAVA RESOURCE" enabled="true" />
-          <object-type name="DIMENSION" enabled="false" />
-          <object-type name="CLUSTER" enabled="false" />
-          <object-type name="DBLINK" enabled="false" />
-          <object-type name="CREDENTIAL" enabled="false" />
-        </lookup-objects>
-        <force-database-load value="false" />
-        <prompt-connection-selection value="true" />
-        <prompt-schema-selection value="true" />
-      </lookup-filters>
-    </navigation-settings>
-    <dataset-grid-settings>
-      <general>
-        <enable-zooming value="true" />
-        <enable-column-tooltip value="true" />
-      </general>
-      <sorting>
-        <nulls-first value="true" />
-        <max-sorting-columns value="4" />
-      </sorting>
-      <audit-columns>
-        <column-names value="" />
-        <visible value="true" />
-        <editable value="false" />
-      </audit-columns>
-    </dataset-grid-settings>
-    <dataset-editor-settings>
-      <text-editor-popup>
-        <active value="false" />
-        <active-if-empty value="false" />
-        <data-length-threshold value="100" />
-        <popup-delay value="1000" />
-      </text-editor-popup>
-      <values-actions-popup>
-        <show-popup-button value="true" />
-        <element-count-threshold value="1000" />
-        <data-length-threshold value="250" />
-      </values-actions-popup>
-      <general>
-        <fetch-block-size value="100" />
-        <fetch-timeout value="30" />
-        <trim-whitespaces value="true" />
-        <convert-empty-strings-to-null value="true" />
-        <select-content-on-cell-edit value="true" />
-        <large-value-preview-active value="true" />
-      </general>
-      <filters>
-        <prompt-filter-dialog value="true" />
-        <default-filter-type value="BASIC" />
-      </filters>
-      <qualified-text-editor text-length-threshold="300">
-        <content-types>
-          <content-type name="Text" enabled="true" />
-          <content-type name="Properties" enabled="true" />
-          <content-type name="XML" enabled="true" />
-          <content-type name="DTD" enabled="true" />
-          <content-type name="HTML" enabled="true" />
-          <content-type name="XHTML" enabled="true" />
-          <content-type name="Java" enabled="true" />
-          <content-type name="SQL" enabled="true" />
-          <content-type name="PL/SQL" enabled="true" />
-          <content-type name="JPA QL" enabled="true" />
-          <content-type name="JSON" enabled="true" />
-          <content-type name="JSON5" enabled="true" />
-          <content-type name="Groovy" enabled="true" />
-          <content-type name="YAML" enabled="true" />
-          <content-type name="Manifest" enabled="true" />
-        </content-types>
-      </qualified-text-editor>
-      <record-navigation>
-        <navigation-target value="VIEWER" />
-      </record-navigation>
-    </dataset-editor-settings>
-    <code-editor-settings>
-      <general>
-        <show-object-navigation-gutter value="false" />
-        <show-spec-declaration-navigation-gutter value="true" />
-        <enable-spellchecking value="true" />
-        <enable-reference-spellchecking value="false" />
-      </general>
-      <confirmations>
-        <save-changes value="false" />
-        <revert-changes value="true" />
-        <exit-on-changes value="ASK" />
-      </confirmations>
-    </code-editor-settings>
-    <code-completion-settings>
-      <filters>
-        <basic-filter>
-          <filter-element type="RESERVED_WORD" id="keyword" selected="true" />
-          <filter-element type="RESERVED_WORD" id="function" selected="true" />
-          <filter-element type="RESERVED_WORD" id="parameter" selected="true" />
-          <filter-element type="RESERVED_WORD" id="datatype" selected="true" />
-          <filter-element type="RESERVED_WORD" id="exception" selected="true" />
-          <filter-element type="OBJECT" id="schema" selected="true" />
-          <filter-element type="OBJECT" id="role" selected="true" />
-          <filter-element type="OBJECT" id="user" selected="true" />
-          <filter-element type="OBJECT" id="privilege" selected="true" />
-          <user-schema>
-            <filter-element type="OBJECT" id="table" selected="true" />
-            <filter-element type="OBJECT" id="view" selected="true" />
-            <filter-element type="OBJECT" id="json view" selected="true" />
-            <filter-element type="OBJECT" id="materialized view" selected="true" />
-            <filter-element type="OBJECT" id="index" selected="true" />
-            <filter-element type="OBJECT" id="constraint" selected="true" />
-            <filter-element type="OBJECT" id="trigger" selected="true" />
-            <filter-element type="OBJECT" id="synonym" selected="false" />
-            <filter-element type="OBJECT" id="sequence" selected="true" />
-            <filter-element type="OBJECT" id="procedure" selected="true" />
-            <filter-element type="OBJECT" id="function" selected="true" />
-            <filter-element type="OBJECT" id="package" selected="true" />
-            <filter-element type="OBJECT" id="type" selected="true" />
-            <filter-element type="OBJECT" id="dimension" selected="true" />
-            <filter-element type="OBJECT" id="cluster" selected="true" />
-            <filter-element type="OBJECT" id="dblink" selected="true" />
-          </user-schema>
-          <public-schema>
-            <filter-element type="OBJECT" id="table" selected="false" />
-            <filter-element type="OBJECT" id="view" selected="false" />
-            <filter-element type="OBJECT" id="json view" selected="false" />
-            <filter-element type="OBJECT" id="materialized view" selected="false" />
-            <filter-element type="OBJECT" id="index" selected="false" />
-            <filter-element type="OBJECT" id="constraint" selected="false" />
-            <filter-element type="OBJECT" id="trigger" selected="false" />
-            <filter-element type="OBJECT" id="synonym" selected="false" />
-            <filter-element type="OBJECT" id="sequence" selected="false" />
-            <filter-element type="OBJECT" id="procedure" selected="false" />
-            <filter-element type="OBJECT" id="function" selected="false" />
-            <filter-element type="OBJECT" id="package" selected="false" />
-            <filter-element type="OBJECT" id="type" selected="false" />
-            <filter-element type="OBJECT" id="dimension" selected="false" />
-            <filter-element type="OBJECT" id="cluster" selected="false" />
-            <filter-element type="OBJECT" id="dblink" selected="false" />
-          </public-schema>
-          <any-schema>
-            <filter-element type="OBJECT" id="table" selected="true" />
-            <filter-element type="OBJECT" id="view" selected="true" />
-            <filter-element type="OBJECT" id="json view" selected="true" />
-            <filter-element type="OBJECT" id="materialized view" selected="true" />
-            <filter-element type="OBJECT" id="index" selected="true" />
-            <filter-element type="OBJECT" id="constraint" selected="true" />
-            <filter-element type="OBJECT" id="trigger" selected="true" />
-            <filter-element type="OBJECT" id="synonym" selected="true" />
-            <filter-element type="OBJECT" id="sequence" selected="true" />
-            <filter-element type="OBJECT" id="procedure" selected="true" />
-            <filter-element type="OBJECT" id="function" selected="true" />
-            <filter-element type="OBJECT" id="package" selected="true" />
-            <filter-element type="OBJECT" id="type" selected="true" />
-            <filter-element type="OBJECT" id="dimension" selected="true" />
-            <filter-element type="OBJECT" id="cluster" selected="true" />
-            <filter-element type="OBJECT" id="dblink" selected="true" />
-          </any-schema>
-        </basic-filter>
-        <extended-filter>
-          <filter-element type="RESERVED_WORD" id="keyword" selected="true" />
-          <filter-element type="RESERVED_WORD" id="function" selected="true" />
-          <filter-element type="RESERVED_WORD" id="parameter" selected="true" />
-          <filter-element type="RESERVED_WORD" id="datatype" selected="true" />
-          <filter-element type="RESERVED_WORD" id="exception" selected="true" />
-          <filter-element type="OBJECT" id="schema" selected="true" />
-          <filter-element type="OBJECT" id="user" selected="true" />
-          <filter-element type="OBJECT" id="role" selected="true" />
-          <filter-element type="OBJECT" id="privilege" selected="true" />
-          <user-schema>
-            <filter-element type="OBJECT" id="table" selected="true" />
-            <filter-element type="OBJECT" id="view" selected="true" />
-            <filter-element type="OBJECT" id="json view" selected="true" />
-            <filter-element type="OBJECT" id="materialized view" selected="true" />
-            <filter-element type="OBJECT" id="index" selected="true" />
-            <filter-element type="OBJECT" id="constraint" selected="true" />
-            <filter-element type="OBJECT" id="trigger" selected="true" />
-            <filter-element type="OBJECT" id="synonym" selected="true" />
-            <filter-element type="OBJECT" id="sequence" selected="true" />
-            <filter-element type="OBJECT" id="procedure" selected="true" />
-            <filter-element type="OBJECT" id="function" selected="true" />
-            <filter-element type="OBJECT" id="package" selected="true" />
-            <filter-element type="OBJECT" id="type" selected="true" />
-            <filter-element type="OBJECT" id="dimension" selected="true" />
-            <filter-element type="OBJECT" id="cluster" selected="true" />
-            <filter-element type="OBJECT" id="dblink" selected="true" />
-          </user-schema>
-          <public-schema>
-            <filter-element type="OBJECT" id="table" selected="true" />
-            <filter-element type="OBJECT" id="view" selected="true" />
-            <filter-element type="OBJECT" id="json view" selected="true" />
-            <filter-element type="OBJECT" id="materialized view" selected="true" />
-            <filter-element type="OBJECT" id="index" selected="true" />
-            <filter-element type="OBJECT" id="constraint" selected="true" />
-            <filter-element type="OBJECT" id="trigger" selected="true" />
-            <filter-element type="OBJECT" id="synonym" selected="true" />
-            <filter-element type="OBJECT" id="sequence" selected="true" />
-            <filter-element type="OBJECT" id="procedure" selected="true" />
-            <filter-element type="OBJECT" id="function" selected="true" />
-            <filter-element type="OBJECT" id="package" selected="true" />
-            <filter-element type="OBJECT" id="type" selected="true" />
-            <filter-element type="OBJECT" id="dimension" selected="true" />
-            <filter-element type="OBJECT" id="cluster" selected="true" />
-            <filter-element type="OBJECT" id="dblink" selected="true" />
-          </public-schema>
-          <any-schema>
-            <filter-element type="OBJECT" id="table" selected="true" />
-            <filter-element type="OBJECT" id="view" selected="true" />
-            <filter-element type="OBJECT" id="json view" selected="true" />
-            <filter-element type="OBJECT" id="materialized view" selected="true" />
-            <filter-element type="OBJECT" id="index" selected="true" />
-            <filter-element type="OBJECT" id="constraint" selected="true" />
-            <filter-element type="OBJECT" id="trigger" selected="true" />
-            <filter-element type="OBJECT" id="synonym" selected="true" />
-            <filter-element type="OBJECT" id="sequence" selected="true" />
-            <filter-element type="OBJECT" id="procedure" selected="true" />
-            <filter-element type="OBJECT" id="function" selected="true" />
-            <filter-element type="OBJECT" id="package" selected="true" />
-            <filter-element type="OBJECT" id="type" selected="true" />
-            <filter-element type="OBJECT" id="dimension" selected="true" />
-            <filter-element type="OBJECT" id="cluster" selected="true" />
-            <filter-element type="OBJECT" id="dblink" selected="true" />
-          </any-schema>
-        </extended-filter>
-      </filters>
-      <sorting enabled="true">
-        <sorting-element type="RESERVED_WORD" id="keyword" />
-        <sorting-element type="RESERVED_WORD" id="datatype" />
-        <sorting-element type="OBJECT" id="column" />
-        <sorting-element type="OBJECT" id="table" />
-        <sorting-element type="OBJECT" id="view" />
-        <sorting-element type="OBJECT" id="json view" />
-        <sorting-element type="OBJECT" id="materialized view" />
-        <sorting-element type="OBJECT" id="index" />
-        <sorting-element type="OBJECT" id="constraint" />
-        <sorting-element type="OBJECT" id="trigger" />
-        <sorting-element type="OBJECT" id="synonym" />
-        <sorting-element type="OBJECT" id="sequence" />
-        <sorting-element type="OBJECT" id="procedure" />
-        <sorting-element type="OBJECT" id="function" />
-        <sorting-element type="OBJECT" id="package" />
-        <sorting-element type="OBJECT" id="type" />
-        <sorting-element type="OBJECT" id="dimension" />
-        <sorting-element type="OBJECT" id="cluster" />
-        <sorting-element type="OBJECT" id="dblink" />
-        <sorting-element type="OBJECT" id="schema" />
-        <sorting-element type="OBJECT" id="role" />
-        <sorting-element type="OBJECT" id="user" />
-        <sorting-element type="RESERVED_WORD" id="function" />
-        <sorting-element type="RESERVED_WORD" id="parameter" />
-      </sorting>
-      <format>
-        <enforce-code-style-case value="true" />
-      </format>
-    </code-completion-settings>
-    <execution-engine-settings>
-      <statement-execution>
-        <fetch-block-size value="100" />
-        <execution-timeout value="20" />
-        <debug-execution-timeout value="600" />
-        <focus-result value="false" />
-        <prompt-execution value="false" />
-      </statement-execution>
-      <script-execution>
-        <command-line-interfaces />
-        <execution-timeout value="300" />
-      </script-execution>
-      <method-execution>
-        <execution-timeout value="30" />
-        <debug-execution-timeout value="600" />
-        <parameter-history-size value="10" />
-      </method-execution>
-      <method-execution>
-        <execution-timeout value="30" />
-        <debug-execution-timeout value="600" />
-        <parameter-history-size value="10" />
-      </method-execution>
-    </execution-engine-settings>
-    <operation-settings>
-      <transactions>
-        <uncommitted-changes>
-          <on-project-close value="ASK" />
-          <on-disconnect value="ASK" />
-          <on-autocommit-toggle value="ASK" />
-        </uncommitted-changes>
-        <multiple-uncommitted-changes>
-          <on-commit value="ASK" />
-          <on-rollback value="ASK" />
-        </multiple-uncommitted-changes>
-      </transactions>
-      <session-browser>
-        <disconnect-session value="ASK" />
-        <kill-session value="ASK" />
-        <reload-on-filter-change value="false" />
-      </session-browser>
-      <compiler>
-        <compile-type value="KEEP" />
-        <compile-dependencies value="ASK" />
-        <always-show-controls value="false" />
-      </compiler>
-    </operation-settings>
-    <ddl-file-settings>
-      <extensions>
-        <mapping file-type-id="VIEW" extensions="vw" />
-        <mapping file-type-id="TRIGGER" extensions="trg" />
-        <mapping file-type-id="PROCEDURE" extensions="prc" />
-        <mapping file-type-id="FUNCTION" extensions="fnc" />
-        <mapping file-type-id="PACKAGE" extensions="pkg" />
-        <mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
-        <mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
-        <mapping file-type-id="TYPE" extensions="tpe" />
-        <mapping file-type-id="TYPE_SPEC" extensions="tps" />
-        <mapping file-type-id="TYPE_BODY" extensions="tpb" />
-        <mapping file-type-id="JAVA_SOURCE" extensions="sql" />
-      </extensions>
-      <general>
-        <lookup-ddl-files value="true" />
-        <create-ddl-files value="false" />
-        <synchronize-ddl-files value="true" />
-        <use-qualified-names value="false" />
-        <make-scripts-rerunnable value="true" />
-      </general>
-    </ddl-file-settings>
-    <assistant-settings>
-      <credential-settings>
-        <credentials />
-      </credential-settings>
-      <profile-settings>
-        <profiles />
-      </profile-settings>
-    </assistant-settings>
-    <general-settings>
-      <regional-settings>
-        <date-format value="MEDIUM" />
-        <number-format value="UNGROUPED" />
-        <locale value="SYSTEM_DEFAULT" />
-        <use-custom-formats value="false" />
-      </regional-settings>
-      <environment>
-        <environment-types>
-          <environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
-          <environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
-          <environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
-          <environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
-        </environment-types>
-        <visibility-settings>
-          <connection-tabs value="true" />
-          <dialog-headers value="true" />
-          <object-editor-tabs value="true" />
-          <script-editor-tabs value="false" />
-          <execution-result-tabs value="true" />
-        </visibility-settings>
-      </environment>
-    </general-settings>
-  </component>
-  <component name="DBNavigator.Project.StatementExecutionManager">
-    <execution-variables />
-    <execution-variable-types />
-  </component>
-</project>

+ 1 - 1
zhijiayun-gateway/src/main/resources/application-dev.yml

@@ -6,7 +6,7 @@
 spring:
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://localhost:3308/zhijiayun_pharmacy?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+    url: jdbc:mysql://localhost:3308/zhijiayun_pharmacy_dev?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
     username: root
     password: 123456
     hikari:

+ 0 - 9
zhijiayun-gateway/src/main/resources/application.yml

@@ -21,15 +21,6 @@ spring:
       max-file-size: 10MB
       max-request-size: 20MB
       file-size-threshold: 2MB
-
-  # SQL 初始化(建表脚本)
-  sql:
-    init:
-      mode: always
-      schema-locations: classpath:db/schema.sql
-      encoding: utf-8
-      continue-on-error: true  # 允许ALTER TABLE重复列报错,不影响启动
-
 # JWT 配置(公共,各环境可覆盖)
 jwt:
   secret: YourSuperSecretKeyForJWTTokenGenerationMustBeAtLeast256BitsLong!!

+ 24 - 0
zhijiayun-gateway/src/main/resources/db/migration-v7.sql

@@ -0,0 +1,24 @@
+-- ============================================
+-- 迁移 v7:补填缺失的邀请关系记录
+-- 数据源:
+--   t_user.inviter_id  → 谁邀请了这个用户(被邀请人→邀请人)
+--   t_user_membership  → 邀请人获得的会员奖励记录(source='INVITE')
+-- 目标:补建 t_invite_relation 中缺失的邀请关系
+-- ============================================
+
+INSERT INTO t_invite_relation (inviter_id, invitee_id, invite_code_id, registered, reward_granted, status, create_time, update_time)
+SELECT
+    u.inviter_id,
+    u.id,
+    NULL,
+    1,
+    1,    -- reward_granted:每次邀请默认奖励 1 个月会员
+    1,
+    NOW(),
+    NOW()
+FROM t_user u
+WHERE u.inviter_id IS NOT NULL
+  AND NOT EXISTS (
+      SELECT 1 FROM t_invite_relation ir
+      WHERE ir.invitee_id = u.id
+  );

+ 5 - 2
zhijiayun-gateway/src/test/java/com/xuekairui/gateway/GatewayBaseTest.java

@@ -75,7 +75,7 @@ public abstract class GatewayBaseTest {
                 "t_invite_relation", "t_invite_code", "t_invite_config",
                 "t_search_record", "t_platform_session",
                 "t_user_coupon", "t_coupon",
-                "t_membership_benefit", "t_user_membership", "t_user"
+                "t_membership_benefit", "t_user_membership", "t_admin", "t_user"
             };
             for (String table : tables) {
                 try {
@@ -130,8 +130,11 @@ public abstract class GatewayBaseTest {
      */
     protected String generateAccessToken(Long userId, String role, Long tokenVersion) {
         long ver = tokenVersion != null ? tokenVersion : 0L;
-        // 同步 DB 版本,避免缓存清理后 DB 版本与 token 版本不一致
+        // 同步 DB 版本:管理员更新 t_admin,普通用户更新 t_user
         try {
+            if ("ADMIN".equals(role) || "SUPER_ADMIN".equals(role)) {
+                jdbcTemplate.update("UPDATE t_admin SET token_version = ? WHERE id = ?", ver, userId);
+            }
             jdbcTemplate.update("UPDATE t_user SET token_version = ? WHERE id = ?", ver, userId);
         } catch (Exception ignored) { }
 

+ 1 - 1
zhijiayun-gateway/src/test/resources/application-test.yml

@@ -14,7 +14,7 @@ spring:
 
   # MySQL 数据库
   datasource:
-    url: jdbc:mysql://localhost:3308/zhijiayun_pharmacy?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
+    url: jdbc:mysql://localhost:3308/zhijiayun_pharmacy1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
     username: root
     password: 123456
     driver-class-name: com.mysql.cj.jdbc.Driver

+ 21 - 20
zhijiayun-gateway/src/test/resources/test-schema.sql

@@ -2,26 +2,27 @@
 -- 测试用建表脚本(每次测试前重建所有表)
 -- ============================================
 
-DROP TABLE IF EXISTS t_payment_order;
-DROP TABLE IF EXISTS t_payment_plan;
-DROP TABLE IF EXISTS t_watchlist;
-DROP TABLE IF EXISTS t_operation_audit_log;
-DROP TABLE IF EXISTS t_trial_quota_config;
-DROP TABLE IF EXISTS t_business_license;
-DROP TABLE IF EXISTS t_platform_account;
-DROP TABLE IF EXISTS t_platform_config;
-DROP TABLE IF EXISTS t_crawler_quota_grant;
-DROP TABLE IF EXISTS t_crawler_usage_log;
-DROP TABLE IF EXISTS t_invite_relation;
-DROP TABLE IF EXISTS t_invite_code;
-DROP TABLE IF EXISTS t_invite_config;
-DROP TABLE IF EXISTS t_search_record;
-DROP TABLE IF EXISTS t_platform_session;
-DROP TABLE IF EXISTS t_user_coupon;
-DROP TABLE IF EXISTS t_coupon;
-DROP TABLE IF EXISTS t_membership_benefit;
-DROP TABLE IF EXISTS t_user_membership;
-DROP TABLE IF EXISTS t_user;
+-- DROP TABLE IF EXISTS t_payment_order;
+-- DROP TABLE IF EXISTS t_payment_plan;
+-- DROP TABLE IF EXISTS t_watchlist;
+-- DROP TABLE IF EXISTS t_operation_audit_log;
+-- DROP TABLE IF EXISTS t_trial_quota_config;
+-- DROP TABLE IF EXISTS t_business_license;
+-- DROP TABLE IF EXISTS t_platform_account;
+-- DROP TABLE IF EXISTS t_platform_config;
+-- DROP TABLE IF EXISTS t_crawler_quota_grant;
+-- DROP TABLE IF EXISTS t_crawler_usage_log;
+-- DROP TABLE IF EXISTS t_invite_relation;
+-- DROP TABLE IF EXISTS t_invite_code;
+-- DROP TABLE IF EXISTS t_invite_config;
+-- DROP TABLE IF EXISTS t_search_record;
+-- DROP TABLE IF EXISTS t_platform_session;
+-- DROP TABLE IF EXISTS t_user_coupon;
+-- DROP TABLE IF EXISTS t_coupon;
+-- DROP TABLE IF EXISTS t_membership_benefit;
+-- DROP TABLE IF EXISTS t_user_membership;
+-- DROP TABLE IF EXISTS t_admin;
+-- DROP TABLE IF EXISTS t_user;
 
 CREATE TABLE IF NOT EXISTS t_user (
     id              BIGINT AUTO_INCREMENT PRIMARY KEY,

+ 1 - 1
zhijiayun-invite/src/main/java/com/xuekairui/invite/controller/AdminController.java

@@ -147,7 +147,7 @@ public class AdminController {
      * GET /api/admin/crawler/grants?userId=1&grantType=ADMIN
      */
     @GetMapping("/crawler/grants")
-    public Result<List<CrawlerQuotaGrant>> listGrants(
+    public Result<List<com.xuekairui.user.dto.CrawlerQuotaGrantResponse>> listGrants(
             @RequestParam(required = false) Long userId,
             @RequestParam(required = false) String grantType) {
         return Result.success(inviteService.listGrants(userId, grantType));

+ 9 - 5
zhijiayun-invite/src/main/java/com/xuekairui/invite/dto/InviteRewardResponse.java

@@ -20,18 +20,22 @@ public class InviteRewardResponse {
     @JsonSerialize(using = ToStringSerializer.class)
     private Long inviteeId;
 
-    /** 被邀请人昵称(未注册时显示"待注册") */
     private String inviteeNickname;
 
-    /** 是否已注册:0-仅点击 1-已注册 */
+    /** 被邀请人手机号(脱敏) */
+    private String inviteePhone;
+
+    /** 被邀请人当前会员等级 */
+    private String inviteeMembershipLevel;
+
     private Integer registered;
 
-    /** 注册状态文本:已注册 / 待注册 */
     private String registeredText;
 
-    /** 奖励爬虫次数 */
     private Integer rewardCount;
 
-    /** 邀请时间 */
     private String inviteTime;
+
+    /** 邀请码 */
+    private String inviteCode;
 }

+ 31 - 6
zhijiayun-invite/src/main/java/com/xuekairui/invite/service/InviteService.java

@@ -818,7 +818,7 @@ public class InviteService {
     }
 
     /**
-     * 获取用户邀请记录列表
+     * 获取用户邀请记录列表(批量查询被邀请人,避免 N+1)
      */
     public List<InviteRewardResponse> getInviteRewards(Long userId) {
         List<InviteRelation> relations = inviteRelationMapper.selectList(
@@ -826,21 +826,46 @@ public class InviteService {
                         .eq(InviteRelation::getInviterId, userId)
                         .orderByDesc(InviteRelation::getCreateTime));
 
+        if (relations.isEmpty()) {
+            return List.of();
+        }
+
+        // 批量查被邀请人和邀请码
+        List<Long> inviteeIds = relations.stream()
+                .map(InviteRelation::getInviteeId).distinct().toList();
+        Map<Long, User> userMap = userMapper.selectBatchIds(inviteeIds).stream()
+                .collect(Collectors.toMap(User::getId, u -> u));
+
+        List<Long> codeIds = relations.stream()
+                .map(InviteRelation::getInviteCodeId).filter(id -> id != null).distinct().toList();
+        Map<Long, String> codeMap = codeIds.isEmpty() ? Map.of()
+                : inviteCodeMapper.selectBatchIds(codeIds).stream()
+                        .collect(Collectors.toMap(InviteCode::getId, InviteCode::getCode));
+
+        // 邀请人信息只查一次
+        String inviterNickname = getUserNickname(userId);
+
         return relations.stream().map(r -> {
             boolean isRegistered = r.getRegistered() != null && r.getRegistered() == 1;
-            User invitee = userMapper.selectById(r.getInviteeId());
+            User invitee = userMap.get(r.getInviteeId());
             String inviteeNickname = isRegistered
-                    ? getDisplayName(invitee)
-                    : "待注册";
+                    ? getDisplayName(invitee) : "待注册";
+            String inviteePhone = invitee != null
+                    ? ValidateUtil.maskPhone(invitee.getPhone()) : null;
+            String inviteeLevel = invitee != null && invitee.getMembershipLevel() != null
+                    ? invitee.getMembershipLevel().getCode() : "PLUS";
 
             return InviteRewardResponse.builder()
                     .inviterId(r.getInviterId())
-                    .inviterNickname(getUserNickname(r.getInviterId()))
+                    .inviterNickname(inviterNickname)
                     .inviteeId(r.getInviteeId())
                     .inviteeNickname(inviteeNickname)
+                    .inviteePhone(inviteePhone)
+                    .inviteeMembershipLevel(inviteeLevel)
                     .registered(r.getRegistered() != null ? r.getRegistered() : 0)
                     .registeredText(isRegistered ? "已注册" : "待注册")
                     .rewardCount(r.getRewardGranted())
+                    .inviteCode(r.getInviteCodeId() != null ? codeMap.get(r.getInviteCodeId()) : null)
                     .inviteTime(r.getCreateTime() != null ?
                             r.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null)
                     .build();
@@ -892,7 +917,7 @@ public class InviteService {
     /**
      * 获取发放记录列表
      */
-    public List<CrawlerQuotaGrant> listGrants(Long userId, String grantType) {
+    public List<com.xuekairui.user.dto.CrawlerQuotaGrantResponse> listGrants(Long userId, String grantType) {
         return crawlerQuotaGrantService.listGrants(userId, grantType);
     }
 

+ 4 - 3
zhijiayun-user/src/main/java/com/xuekairui/user/controller/OperationAuditLogController.java

@@ -50,11 +50,12 @@ public class OperationAuditLogController {
     }
 
     @GetMapping("/target/{targetType}/{targetId}")
-    public Result<List<OperationAuditLog>> getTargetHistory(
+    public Result<Page<OperationAuditLog>> getTargetHistory(
             @PathVariable String targetType,
             @PathVariable Long targetId,
-            @RequestParam(defaultValue = "50") int limit) {
-        return Result.success(auditLogService.getTargetOperationHistory(targetType, targetId, limit));
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return Result.success(auditLogService.getTargetOperationHistory(targetType, targetId, page, size));
     }
 
     @GetMapping("/stats")

+ 57 - 0
zhijiayun-user/src/main/java/com/xuekairui/user/dto/CrawlerQuotaGrantResponse.java

@@ -0,0 +1,57 @@
+package com.xuekairui.user.dto;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 爬虫配额发放记录响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrawlerQuotaGrantResponse {
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+
+    private String userNickname;
+
+    /** 手机号(脱敏) */
+    private String userPhone;
+
+    /** 发放类型:INVITE-邀请奖励 / ADMIN-运营手动发放 / PURCHASE-购买会员 */
+    private String grantType;
+
+    /** 发放次数 */
+    private Integer quotaCount;
+
+    /** 已使用 */
+    private Integer usedCount;
+
+    /** 过期时间 */
+    private LocalDateTime expireTime;
+
+    /** 来源用户ID(邀请人) */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long sourceUserId;
+
+    /** 来源用户昵称 */
+    private String sourceUserNickname;
+
+    private String remark;
+
+    /** 状态:1-有效 2-已用完 3-已过期 */
+    private Integer status;
+
+    private LocalDateTime createTime;
+}

+ 4 - 4
zhijiayun-user/src/main/java/com/xuekairui/user/dto/GrantMembershipRequest.java

@@ -14,12 +14,12 @@ import java.time.LocalDateTime;
 @Data
 public class GrantMembershipRequest {
 
-    /**
-     * 目标用户ID
-     */
-    @NotNull(message = "用户ID不能为空")
+    /** 目标用户ID(和 phone 二选一) */
     private Long userId;
 
+    /** 目标用户手机号(和 userId 二选一) */
+    private String phone;
+
     /**
      * 会员等级:PLUS/PRO/ULTRA
      */

+ 5 - 0
zhijiayun-user/src/main/java/com/xuekairui/user/service/AuthService.java

@@ -74,6 +74,9 @@ public class AuthService {
                     .build();
             userMapper.insert(user);
 
+            // 注册即写入 PLUS 会员记录
+            membershipService.initPlusMembership(user.getId());
+
             // 发布用户注册事件(邀请模块监听处理邀请码,赠送会员奖励)
             eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId(), request.getInviteCode()));
 
@@ -137,6 +140,8 @@ public class AuthService {
                     .status(1)
                     .build();
             userMapper.insert(user);
+
+            membershipService.initPlusMembership(user.getId());
         } else {
             // 更新微信信息
             user.setWechatOpenId(wechatInfo.getOpenId());

+ 42 - 6
zhijiayun-user/src/main/java/com/xuekairui/user/service/CrawlerQuotaGrantService.java

@@ -2,6 +2,8 @@ package com.xuekairui.user.service;
 
 import com.xuekairui.common.BusinessException;
 import com.xuekairui.common.ErrorCode;
+import com.xuekairui.common.ValidateUtil;
+import com.xuekairui.user.dto.CrawlerQuotaGrantResponse;
 import com.xuekairui.user.dto.QuotaBreakdownItem;
 import com.xuekairui.user.entity.CrawlerQuotaGrant;
 import com.xuekairui.user.entity.User;
@@ -15,9 +17,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 爬虫配额发放服务(统一管理)
@@ -159,9 +160,9 @@ public class CrawlerQuotaGrantService {
     }
 
     /**
-     * 获取发放记录列表
+     * 获取发放记录列表(批量查用户,返回 DTO)
      */
-    public List<CrawlerQuotaGrant> listGrants(Long userId, String grantType) {
+    public List<CrawlerQuotaGrantResponse> listGrants(Long userId, String grantType) {
         LambdaQueryWrapper<CrawlerQuotaGrant> wrapper = new LambdaQueryWrapper<>();
         if (userId != null) {
             wrapper.eq(CrawlerQuotaGrant::getUserId, userId);
@@ -170,7 +171,42 @@ public class CrawlerQuotaGrantService {
             wrapper.eq(CrawlerQuotaGrant::getGrantType, grantType);
         }
         wrapper.orderByDesc(CrawlerQuotaGrant::getCreateTime);
-        return crawlerQuotaGrantMapper.selectList(wrapper);
+        List<CrawlerQuotaGrant> grants = crawlerQuotaGrantMapper.selectList(wrapper);
+
+        if (grants.isEmpty()) {
+            return List.of();
+        }
+
+        // 批量查目标用户和来源用户
+        Set<Long> allUserIds = new HashSet<>();
+        for (CrawlerQuotaGrant g : grants) {
+            allUserIds.add(g.getUserId());
+            if (g.getSourceUserId() != null) {
+                allUserIds.add(g.getSourceUserId());
+            }
+        }
+        Map<Long, User> userMap = userMapper.selectBatchIds(new ArrayList<>(allUserIds))
+                .stream().collect(Collectors.toMap(User::getId, u -> u));
+
+        return grants.stream().map(g -> {
+            User user = userMap.get(g.getUserId());
+            User sourceUser = g.getSourceUserId() != null ? userMap.get(g.getSourceUserId()) : null;
+            return CrawlerQuotaGrantResponse.builder()
+                    .id(g.getId())
+                    .userId(g.getUserId())
+                    .userNickname(user != null ? user.getNickname() : null)
+                    .userPhone(user != null ? ValidateUtil.maskPhone(user.getPhone()) : null)
+                    .grantType(g.getGrantType())
+                    .quotaCount(g.getQuotaCount())
+                    .usedCount(g.getUsedCount())
+                    .expireTime(g.getExpireTime())
+                    .sourceUserId(g.getSourceUserId())
+                    .sourceUserNickname(sourceUser != null ? sourceUser.getNickname() : null)
+                    .remark(g.getRemark())
+                    .status(g.getStatus())
+                    .createTime(g.getCreateTime())
+                    .build();
+        }).collect(Collectors.toList());
     }
 
     // ==========================================

+ 33 - 1
zhijiayun-user/src/main/java/com/xuekairui/user/service/MembershipService.java

@@ -71,6 +71,30 @@ public class MembershipService {
                 .build();
     }
 
+    /**
+     * 用户注册时写入一条 PLUS(免费)会员记录,确保会员历史可追溯
+     */
+    public void initPlusMembership(Long userId) {
+        UserMembership exists = userMembershipMapper.selectOne(
+                new LambdaQueryWrapper<UserMembership>()
+                        .eq(UserMembership::getUserId, userId)
+                        .eq(UserMembership::getLevel, MembershipLevel.PLUS)
+                        .last("LIMIT 1"));
+        if (exists != null) {
+            return; // 已有 PLUS 记录,不重复写入
+        }
+        UserMembership membership = UserMembership.builder()
+                .userId(userId)
+                .level(MembershipLevel.PLUS)
+                .source("SYSTEM")
+                .effectiveFrom(LocalDateTime.now())
+                .effectiveTo(null) // 永久有效
+                .status(1)
+                .remark("注册即得免费会员")
+                .build();
+        userMembershipMapper.insert(membership);
+    }
+
     /**
      * 查询指定会员等级的权益列表
      */
@@ -90,7 +114,15 @@ public class MembershipService {
      */
     @Transactional
     public MembershipResponse grantMembership(GrantMembershipRequest request, Long operatorId, String operatorRole) {
-        User user = userMapper.selectById(request.getUserId());
+        User user;
+        if (request.getUserId() != null) {
+            user = userMapper.selectById(request.getUserId());
+        } else if (request.getPhone() != null && !request.getPhone().isBlank()) {
+            user = userMapper.selectOne(
+                    new LambdaQueryWrapper<User>().eq(User::getPhone, request.getPhone()));
+        } else {
+            throw new BusinessException(ErrorCode.PARAM_ERROR, "用户ID和手机号至少填写一个");
+        }
         if (user == null) {
             throw new BusinessException(ErrorCode.USER_NOT_FOUND);
         }

+ 12 - 2
zhijiayun-user/src/main/java/com/xuekairui/user/service/OperationAuditLogService.java

@@ -131,8 +131,18 @@ public class OperationAuditLogService {
     /**
      * 查询目标对象的操作历史
      */
-    public List<OperationAuditLog> getTargetOperationHistory(String targetType, Long targetId, int limit) {
-        return auditLogMapper.selectByTarget(targetType, targetId, limit);
+    public Page<OperationAuditLog> getTargetOperationHistory(String targetType, Long targetId,
+                                                              int pageNum, int pageSize) {
+        Page<OperationAuditLog> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<OperationAuditLog> wrapper = new LambdaQueryWrapper<>();
+        if (targetType != null && !targetType.isBlank()) {
+            wrapper.eq(OperationAuditLog::getTargetType, targetType);
+        }
+        if (targetId != null) {
+            wrapper.eq(OperationAuditLog::getTargetId, targetId);
+        }
+        wrapper.orderByDesc(OperationAuditLog::getOperationTime);
+        return auditLogMapper.selectPage(page, wrapper);
     }
 
     /**