{"id":122270,"date":"2026-11-04T13:57:05","date_gmt":"2026-11-04T05:57:05","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/122270.html"},"modified":"2026-11-04T13:57:05","modified_gmt":"2026-11-04T05:57:05","slug":"http-%e7%8a%b6%e6%80%81%e7%a0%81%ef%bc%9a%e5%ae%a2%e6%88%b7%e7%ab%af%e4%b8%8e%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%9a%84%e9%80%9a%e4%bf%a1%e8%af%ad%e8%a8%80-%e7%ac%ac%e4%ba%8c%e9%83%a8-2","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/122270.html","title":{"rendered":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09"},"content":{"rendered":"<p>        \/* MD \/ \u5bcc\u6587\u672c\u4e2d\u7684 .toc\uff08\u542b\u535a\u5ba2\u56ed\u642c\u5bb6\u7b49\u5d4c\u5957\u7ed3\u6784\uff09\uff1b.toc-box \u5728\u4fa7\u680f\uff0c\u4e0d\u53d7\u5f71\u54cd *\/<br \/>\n        #content_views .toc,<br \/>\n        \/* \u7f16\u8f91\u5668\u5e38\u5728\u76ee\u5f55\u524d\u540e\u63d2\u5165\u7a7a p\uff08:empty \u4ecd\u5360 20px\uff09\uff0c\u4e00\u5e76\u53bb\u6389\u907f\u514d\u9876\u7a7a\u9699 *\/<br \/>\n        #content_views.markdown_views &gt; p:empty:has(+ .toc),<br \/>\n        #content_views.markdown_views &gt; .toc + p:empty,<br \/>\n        \/* \u5bcc\u6587\u672c\u65e7\u7248\u76ee\u5f55\u6807\u8bb0 *\/<br \/>\n        #content_views.htmledit_views #main-toc,<br \/>\n        #content_views.htmledit_views #hr-toc,<br \/>\n        #content_views.htmledit_views p[id*=\\&#8221;-toc\\&#8221;] {<br \/>\n          display: none !important;<br \/>\n        }<br \/>\n        \/* \u76ee\u5f55\u53bb\u6389\u540e\uff0c\u7d27\u8ddf\u7684\u9996\u4e2a\u6807\u9898\u4e0d\u518d\u591a\u51fa\u4e00\u5757\u4e0a\u8fb9\u8ddd *\/<br \/>\n        #content_views.markdown_views &gt; .toc + h1,<br \/>\n        #content_views.markdown_views &gt; .toc + h2,<br \/>\n        #content_views.markdown_views &gt; .toc + h3,<br \/>\n        #content_views.markdown_views &gt; .toc + h4,<br \/>\n        #content_views.markdown_views &gt; .toc + p:empty + h1,<br \/>\n        #content_views.markdown_views &gt; .toc + p:empty + h2,<br \/>\n        #content_views.markdown_views &gt; .toc + p:empty + h3,<br \/>\n        #content_views.markdown_views &gt; .toc + p:empty + h4 {<br \/>\n          margin-top: 0 !important;<br \/>\n        }<\/p>\n<h2><\/h2>\n<h2><\/h2>\n<h3>\u7b2c4\u7ae0&#xff1a;200 OK &#8211; \u6807\u51c6\u6210\u529f\u54cd\u5e94<\/h3>\n<h4>4.1 200 OK\u7684\u5b98\u65b9\u5b9a\u4e49\u4e0e\u8bed\u4e49<\/h4>\n<h5>RFC\u6807\u51c6\u5b9a\u4e49<\/h5>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.1\u8282&#xff0c;200 OK\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>200 (OK)\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u5df2\u6210\u529f\u3002\u5728\u54cd\u5e94\u4e2d\u53d1\u9001\u7684\u6709\u6548\u8d1f\u8f7d\u53d6\u51b3\u4e8e\u8bf7\u6c42\u65b9\u6cd5\u3002\u5bf9\u4e8e\u672c\u89c4\u8303\u5b9a\u4e49\u7684\u65b9\u6cd5&#xff0c;\u6709\u6548\u8d1f\u8f7d\u7684\u9884\u671f\u542b\u4e49\u53ef\u4ee5\u6982\u62ec\u4e3a&#xff1a;<\/p>\n<ul>\n<li>\n<p>GET&#xff1a;\u76ee\u6807\u8d44\u6e90\u7684\u8868\u793a\u5f62\u5f0f<\/p>\n<\/li>\n<li>\n<p>HEAD&#xff1a;\u4e0eGET\u76f8\u540c&#xff0c;\u4f46\u6ca1\u6709\u8868\u793a\u5f62\u5f0f<\/p>\n<\/li>\n<li>\n<p>POST&#xff1a;\u64cd\u4f5c\u7684\u7ed3\u679c\u6216\u5b9e\u73b0\u7684\u72b6\u6001<\/p>\n<\/li>\n<li>\n<p>PUT\u3001DELETE&#xff1a;\u64cd\u4f5c\u7684\u72b6\u6001<\/p>\n<\/li>\n<li>\n<p>OPTIONS&#xff1a;\u901a\u4fe1\u9009\u9879\u7684\u8868\u793a<\/p>\n<\/li>\n<li>\n<p>TRACE&#xff1a;\u5e94\u7528\u63a5\u6536\u5230\u7684\u8bf7\u6c42\u6d88\u606f\u7684\u8868\u793a<\/p>\n<\/li>\n<\/ul>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<p>200 OK\u8868\u793a\u8bf7\u6c42\u5df2\u5b8c\u5168\u6210\u529f\u5730\u5904\u7406\u5b8c\u6210\u3002\u5173\u952e\u7279\u5f81\u5305\u62ec&#xff1a;<\/p>\n<li>\n<p>\u5b8c\u6210\u6027&#xff1a;\u8bf7\u6c42\u5904\u7406\u5df2\u5b8c\u6210&#xff0c;\u6ca1\u6709\u5f85\u5904\u7406\u7684\u540e\u7eed\u64cd\u4f5c<\/p>\n<\/li>\n<li>\n<p>\u786e\u5b9a\u6027&#xff1a;\u54cd\u5e94\u5185\u5bb9\u660e\u786e&#xff0c;\u6ca1\u6709\u6b67\u4e49<\/p>\n<\/li>\n<li>\n<p>\u53ef\u7f13\u5b58\u6027&#xff1a;\u9ed8\u8ba4\u53ef\u7f13\u5b58&#xff08;\u9664\u975e\u660e\u786e\u7981\u6b62&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5e42\u7b49\u6027&#xff1a;\u5bf9\u5e42\u7b49\u65b9\u6cd5&#xff08;GET\u3001HEAD\u3001PUT\u3001DELETE&#xff09;\u7684200\u54cd\u5e94\u662f\u5e42\u7b49\u7684<\/p>\n<\/li>\n<h5>\u8bed\u4e49\u77e9\u9635&#xff1a;\u65b9\u6cd5\u4e0e200 OK<\/h5>\n<p>javascript<\/p>\n<p>class OKStatusHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406\u4e0d\u540cHTTP\u65b9\u6cd5\u7684200 OK\u54cd\u5e94<br \/>\n     *\/<br \/>\n    handle200Response(method, response) {<br \/>\n        const semanticMap &#061; {<br \/>\n            GET: {<br \/>\n                meaning: &#034;\u8d44\u6e90\u8868\u793a&#034;,<br \/>\n                bodyRequired: true,<br \/>\n                cacheBehavior: &#034;default-cacheable&#034;,<br \/>\n                example: this._handleGet200(response)<br \/>\n            },<br \/>\n            HEAD: {<br \/>\n                meaning: &#034;\u8d44\u6e90\u5143\u6570\u636e&#034;,<br \/>\n                bodyRequired: false,<br \/>\n                cacheBehavior: &#034;cacheable&#034;,<br \/>\n                example: this._handleHead200(response)<br \/>\n            },<br \/>\n            POST: {<br \/>\n                meaning: &#034;\u64cd\u4f5c\u7ed3\u679c&#034;,<br \/>\n                bodyRequired: &#034;recommended&#034;,<br \/>\n                cacheBehavior: &#034;non-cacheable&#034;,<br \/>\n                example: this._handlePost200(response)<br \/>\n            },<br \/>\n            PUT: {<br \/>\n                meaning: &#034;\u66f4\u65b0\u72b6\u6001&#034;,<br \/>\n                bodyRequired: &#034;optional&#034;,<br \/>\n                cacheBehavior: &#034;cacheable&#034;,<br \/>\n                example: this._handlePut200(response)<br \/>\n            },<br \/>\n            DELETE: {<br \/>\n                meaning: &#034;\u5220\u9664\u72b6\u6001&#034;,<br \/>\n                bodyRequired: &#034;optional&#034;,<br \/>\n                cacheBehavior: &#034;non-cacheable&#034;,<br \/>\n                example: this._handleDelete200(response)<br \/>\n            },<br \/>\n            PATCH: {<br \/>\n                meaning: &#034;\u90e8\u5206\u66f4\u65b0\u72b6\u6001&#034;,<br \/>\n                bodyRequired: &#034;recommended&#034;,<br \/>\n                cacheBehavior: &#034;non-cacheable&#034;,<br \/>\n                example: this._handlePatch200(response)<br \/>\n            },<br \/>\n            OPTIONS: {<br \/>\n                meaning: &#034;\u53ef\u7528\u65b9\u6cd5&#034;,<br \/>\n                bodyRequired: &#034;optional&#034;,<br \/>\n                cacheBehavior: &#034;cacheable&#034;,<br \/>\n                example: this._handleOptions200(response)<br \/>\n            },<br \/>\n            TRACE: {<br \/>\n                meaning: &#034;\u8bf7\u6c42\u56de\u663e&#034;,<br \/>\n                bodyRequired: true,<br \/>\n                cacheBehavior: &#034;non-cacheable&#034;,<br \/>\n                example: this._handleTrace200(response)<br \/>\n            }<br \/>\n        };<\/p>\n<p>        return semanticMap[method] || {<br \/>\n            meaning: &#034;\u8bf7\u6c42\u6210\u529f&#034;,<br \/>\n            bodyRequired: true,<br \/>\n            cacheBehavior: &#034;unknown&#034;<br \/>\n        };<br \/>\n    }<\/p>\n<p>    _handleGet200(response) {<br \/>\n        \/\/ GET\u7684200\u54cd\u5e94\u5e94\u5305\u542b\u5b8c\u6574\u7684\u8d44\u6e90\u8868\u793a<br \/>\n        return {<br \/>\n            headers: {<br \/>\n                &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n                &#039;ETag&#039;: &#039;&#034;abc123&#034;&#039;,<br \/>\n                &#039;Last-Modified&#039;: &#039;Mon, 23 Jan 2023 10:30:45 GMT&#039;,<br \/>\n                &#039;Cache-Control&#039;: &#039;max-age&#061;3600&#039;<br \/>\n            },<br \/>\n            body: {<br \/>\n                id: 123,<br \/>\n                name: &#039;\u793a\u4f8b\u8d44\u6e90&#039;,<br \/>\n                created_at: &#039;2023-01-23T10:30:45Z&#039;,<br \/>\n                updated_at: &#039;2023-01-23T10:30:45Z&#039;<br \/>\n            }<br \/>\n        };<br \/>\n    }<br \/>\n}<\/p>\n<h4>4.2 200 OK\u7684\u4f7f\u7528\u573a\u666f<\/h4>\n<h5>\u573a\u666f1&#xff1a;\u8d44\u6e90\u68c0\u7d22\u6210\u529f<\/h5>\n<p>python<\/p>\n<p># Flask\u793a\u4f8b&#xff1a;\u8fd4\u56de\u7528\u6237\u8d44\u6e90<br \/>\n&#064;app.route(&#039;\/api\/users\/&lt;int:user_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_user(user_id):<br \/>\n    user &#061; User.query.get(user_id)<\/p>\n<p>    if not user:<br \/>\n        return jsonify({&#034;error&#034;: &#034;User not found&#034;}), 404<\/p>\n<p>    # \u6784\u5efa200 OK\u54cd\u5e94<br \/>\n    response_data &#061; {<br \/>\n        &#034;id&#034;: user.id,<br \/>\n        &#034;username&#034;: user.username,<br \/>\n        &#034;email&#034;: user.email,<br \/>\n        &#034;created_at&#034;: user.created_at.isoformat(),<br \/>\n        &#034;updated_at&#034;: user.updated_at.isoformat()<br \/>\n    }<\/p>\n<p>    # \u6dfb\u52a0ETag\u7528\u4e8e\u7f13\u5b58\u9a8c\u8bc1<br \/>\n    import hashlib<br \/>\n    etag &#061; hashlib.md5(str(response_data).encode()).hexdigest()<\/p>\n<p>    response &#061; jsonify(response_data)<br \/>\n    response.status_code &#061; 200<\/p>\n<p>    # \u8bbe\u7f6e\u7f13\u5b58\u76f8\u5173\u5934\u90e8<br \/>\n    response.headers[&#039;ETag&#039;] &#061; f&#039;&#034;{etag}&#034;&#039;<br \/>\n    response.headers[&#039;Cache-Control&#039;] &#061; &#039;public, max-age&#061;300&#039;  # 5\u5206\u949f<br \/>\n    response.headers[&#039;Last-Modified&#039;] &#061; user.updated_at.strftime(<br \/>\n        &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n    )<\/p>\n<p>    return response<\/p>\n<h5>\u573a\u666f2&#xff1a;\u64cd\u4f5c\u6210\u529f\u6267\u884c<\/h5>\n<p>java<\/p>\n<p>\/\/ Spring Boot\u793a\u4f8b&#xff1a;\u66f4\u65b0\u8d44\u6e90<br \/>\n&#064;PutMapping(&#034;\/api\/products\/{id}&#034;)<br \/>\npublic ResponseEntity&lt;?&gt; updateProduct(<br \/>\n        &#064;PathVariable Long id,<br \/>\n        &#064;Valid &#064;RequestBody ProductUpdateRequest request) {<\/p>\n<p>    Product product &#061; productService.findById(id)<br \/>\n            .orElseThrow(() -&gt; new ResourceNotFoundException(&#034;Product not found&#034;));<\/p>\n<p>    \/\/ \u6267\u884c\u66f4\u65b0<br \/>\n    product.setName(request.getName());<br \/>\n    product.setPrice(request.getPrice());<br \/>\n    product.setUpdatedAt(LocalDateTime.now());<\/p>\n<p>    Product updatedProduct &#061; productService.save(product);<\/p>\n<p>    \/\/ \u6784\u5efa200 OK\u54cd\u5e94<br \/>\n    ProductResponse response &#061; ProductResponse.fromEntity(updatedProduct);<\/p>\n<p>    \/\/ \u6dfb\u52a0ETag<br \/>\n    String etag &#061; generateETag(response);<\/p>\n<p>    return ResponseEntity<br \/>\n            .ok()<br \/>\n            .eTag(etag)<br \/>\n            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))<br \/>\n            .lastModified(updatedProduct.getUpdatedAt().toInstant(ZoneOffset.UTC))<br \/>\n            .body(response);<br \/>\n}<\/p>\n<p>private String generateETag(ProductResponse product) {<br \/>\n    String data &#061; product.getId() &#043; product.getName() &#043;<br \/>\n                  product.getPrice() &#043; product.getUpdatedAt().toString();<br \/>\n    return &#034;\\\\&#034;&#034; &#043; DigestUtils.md5DigestAsHex(data.getBytes()) &#043; &#034;\\\\&#034;&#034;;<br \/>\n}<\/p>\n<h5>\u573a\u666f3&#xff1a;\u6279\u91cf\u64cd\u4f5c\u6210\u529f<\/h5>\n<p>python<\/p>\n<p># \u6279\u91cf\u64cd\u4f5c\u8fd4\u56de200 OK\u793a\u4f8b<br \/>\n&#064;app.route(&#039;\/api\/tasks\/batch&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef batch_create_tasks():<br \/>\n    &#034;&#034;&#034;\u6279\u91cf\u521b\u5efa\u4efb\u52a1&#xff0c;\u5168\u90e8\u6210\u529f\u8fd4\u56de200 OK&#034;&#034;&#034;<\/p>\n<p>    tasks_data &#061; request.get_json()<br \/>\n    if not isinstance(tasks_data, list):<br \/>\n        return jsonify({&#034;error&#034;: &#034;Expected array of tasks&#034;}), 400<\/p>\n<p>    created_tasks &#061; []<\/p>\n<p>    for task_data in tasks_data:<br \/>\n        # \u9a8c\u8bc1\u6bcf\u4e2a\u4efb\u52a1\u6570\u636e<br \/>\n        validation_result &#061; validate_task_data(task_data)<br \/>\n        if not validation_result[&#039;valid&#039;]:<br \/>\n            return jsonify({<br \/>\n                &#034;error&#034;: f&#034;Invalid task data: {validation_result[&#039;message&#039;]}&#034;<br \/>\n            }), 400<\/p>\n<p>        # \u521b\u5efa\u4efb\u52a1<br \/>\n        task &#061; Task(<br \/>\n            title&#061;task_data[&#039;title&#039;],<br \/>\n            description&#061;task_data.get(&#039;description&#039;),<br \/>\n            priority&#061;task_data.get(&#039;priority&#039;, &#039;medium&#039;)<br \/>\n        )<br \/>\n        db.session.add(task)<br \/>\n        created_tasks.append(task)<\/p>\n<p>    # \u5168\u90e8\u6210\u529f&#xff0c;\u63d0\u4ea4\u4e8b\u52a1<br \/>\n    db.session.commit()<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94<br \/>\n    response_data &#061; {<br \/>\n        &#034;status&#034;: &#034;success&#034;,<br \/>\n        &#034;message&#034;: f&#034;Successfully created {len(created_tasks)} tasks&#034;,<br \/>\n        &#034;tasks&#034;: [task.to_dict() for task in created_tasks],<br \/>\n        &#034;count&#034;: len(created_tasks)<br \/>\n    }<\/p>\n<p>    return jsonify(response_data), 200<\/p>\n<h4>4.3 \u54cd\u5e94\u5934\u90e8\u4e0e\u7f13\u5b58\u63a7\u5236<\/h4>\n<h5>\u5fc5\u9700\u4e0e\u63a8\u8350\u7684\u5934\u90e8<\/h5>\n<p>nginx<\/p>\n<p># Nginx\u914d\u7f6e&#xff1a;\u4f18\u5316200\u54cd\u5e94\u7684\u7f13\u5b58\u5934\u90e8<br \/>\nserver {<br \/>\n    location ~ ^\/api\/static\/ {<br \/>\n        # \u9759\u6001\u8d44\u6e90 &#8211; \u957f\u671f\u7f13\u5b58<br \/>\n        add_header Cache-Control &#034;public, max-age&#061;31536000, immutable&#034;;<br \/>\n        add_header ETag $sent_http_etag;<br \/>\n        add_header Last-Modified $sent_http_last_modified;<\/p>\n<p>        # \u542f\u7528gzip\u538b\u7f29<br \/>\n        gzip on;<br \/>\n        gzip_types text\/plain text\/css application\/json application\/javascript;<\/p>\n<p>        # \u4ee3\u7406\u7f13\u5b58\u8bbe\u7f6e<br \/>\n        proxy_cache static_cache;<br \/>\n        proxy_cache_key &#034;$scheme$request_method$host$request_uri&#034;;<br \/>\n        proxy_cache_valid 200 304 1y;<br \/>\n    }<\/p>\n<p>    location ~ ^\/api\/dynamic\/ {<br \/>\n        # \u52a8\u6001\u5185\u5bb9 &#8211; \u77ed\u671f\u7f13\u5b58<br \/>\n        add_header Cache-Control &#034;public, max-age&#061;60, must-revalidate&#034;;<br \/>\n        add_header ETag $sent_http_etag;<br \/>\n        add_header Vary &#034;Accept-Encoding, Cookie&#034;;<\/p>\n<p>        # \u4e0d\u7f13\u5b58\u4e2a\u6027\u5316\u5185\u5bb9<br \/>\n        if ($http_cookie ~* &#034;sessionid&#034;) {<br \/>\n            add_header Cache-Control &#034;private, no-cache, no-store&#034;;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h5>\u7f13\u5b58\u9a8c\u8bc1\u673a\u5236<\/h5>\n<p>javascript<\/p>\n<p>class CacheController {<br \/>\n    \/**<br \/>\n     * \u5904\u7406200\u54cd\u5e94\u7684\u7f13\u5b58\u63a7\u5236<br \/>\n     *\/<br \/>\n    constructor() {<br \/>\n        this.cacheConfig &#061; {<br \/>\n            static: {<br \/>\n                maxAge: 31536000, \/\/ 1\u5e74<br \/>\n                immutable: true<br \/>\n            },<br \/>\n            dynamic: {<br \/>\n                maxAge: 300, \/\/ 5\u5206\u949f<br \/>\n                mustRevalidate: true<br \/>\n            },<br \/>\n            userSpecific: {<br \/>\n                private: true,<br \/>\n                noCache: true<br \/>\n            }<br \/>\n        };<br \/>\n    }<\/p>\n<p>    setCacheHeaders(response, resourceType, request) {<br \/>\n        const config &#061; this.cacheConfig[resourceType] || this.cacheConfig.dynamic;<\/p>\n<p>        let cacheControl &#061; [];<\/p>\n<p>        if (config.private) {<br \/>\n            cacheControl.push(&#039;private&#039;);<br \/>\n        } else {<br \/>\n            cacheControl.push(&#039;public&#039;);<br \/>\n        }<\/p>\n<p>        if (config.maxAge) {<br \/>\n            cacheControl.push(&#096;max-age&#061;${config.maxAge}&#096;);<br \/>\n        }<\/p>\n<p>        if (config.immutable) {<br \/>\n            cacheControl.push(&#039;immutable&#039;);<br \/>\n        }<\/p>\n<p>        if (config.mustRevalidate) {<br \/>\n            cacheControl.push(&#039;must-revalidate&#039;);<br \/>\n        }<\/p>\n<p>        if (config.noCache) {<br \/>\n            cacheControl.push(&#039;no-cache&#039;);<br \/>\n        }<\/p>\n<p>        if (config.noStore) {<br \/>\n            cacheControl.push(&#039;no-store&#039;);<br \/>\n        }<\/p>\n<p>        response.headers[&#039;Cache-Control&#039;] &#061; cacheControl.join(&#039;, &#039;);<\/p>\n<p>        \/\/ \u8bbe\u7f6eETag&#xff08;\u5982\u679c\u652f\u6301&#xff09;<br \/>\n        if (resourceType !&#061;&#061; &#039;userSpecific&#039;) {<br \/>\n            const etag &#061; this.generateETag(response.body);<br \/>\n            response.headers[&#039;ETag&#039;] &#061; &#096;&#034;${etag}&#034;&#096;;<br \/>\n        }<\/p>\n<p>        \/\/ \u8bbe\u7f6eLast-Modified<br \/>\n        if (resourceType &#061;&#061;&#061; &#039;static&#039;) {<br \/>\n            response.headers[&#039;Last-Modified&#039;] &#061; new Date().toUTCString();<br \/>\n        }<\/p>\n<p>        return response;<br \/>\n    }<\/p>\n<p>    generateETag(body) {<br \/>\n        \/\/ \u57fa\u4e8e\u5185\u5bb9\u751f\u6210ETag<br \/>\n        const content &#061; JSON.stringify(body);<br \/>\n        return require(&#039;crypto&#039;)<br \/>\n            .createHash(&#039;md5&#039;)<br \/>\n            .update(content)<br \/>\n            .digest(&#039;hex&#039;);<br \/>\n    }<br \/>\n}<\/p>\n<h4>4.4 \u5ba2\u6237\u7aef\u5904\u7406\u903b\u8f91<\/h4>\n<h5>\u6d4f\u89c8\u5668\u5904\u7406200 OK<\/h5>\n<p>html<\/p>\n<p>&lt;!DOCTYPE html&gt;<br \/>\n&lt;html&gt;<br \/>\n&lt;head&gt;<br \/>\n    &lt;title&gt;200 OK\u5904\u7406\u793a\u4f8b&lt;\/title&gt;<br \/>\n    &lt;script&gt;<br \/>\n        class ResponseHandler {<br \/>\n            async handleResponse(response) {<br \/>\n                console.log(&#096;Status: ${response.status} ${response.statusText}&#096;);<\/p>\n<p>                \/\/ \u68c0\u67e5\u54cd\u5e94\u7c7b\u578b<br \/>\n                const contentType &#061; response.headers.get(&#039;content-type&#039;);<\/p>\n<p>                \/\/ \u57fa\u4e8e\u5185\u5bb9\u7c7b\u578b\u5904\u7406<br \/>\n                if (contentType.includes(&#039;application\/json&#039;)) {<br \/>\n                    return this.handleJSON(response);<br \/>\n                } else if (contentType.includes(&#039;text\/html&#039;)) {<br \/>\n                    return this.handleHTML(response);<br \/>\n                } else if (contentType.includes(&#039;text\/plain&#039;)) {<br \/>\n                    return this.handleText(response);<br \/>\n                } else {<br \/>\n                    return this.handleBlob(response);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            async handleJSON(response) {<br \/>\n                try {<br \/>\n                    const data &#061; await response.json();<\/p>\n<p>                    \/\/ \u68c0\u67e5\u54cd\u5e94\u7ed3\u6784<br \/>\n                    if (data.error) {<br \/>\n                        console.error(&#039;API\u8fd4\u56de\u9519\u8bef:&#039;, data.error);<br \/>\n                        this.showError(data.error);<br \/>\n                        return null;<br \/>\n                    }<\/p>\n<p>                    \/\/ \u6210\u529f\u5904\u7406\u6570\u636e<br \/>\n                    console.log(&#039;\u6570\u636e\u52a0\u8f7d\u6210\u529f:&#039;, data);<br \/>\n                    this.updateUI(data);<br \/>\n                    return data;<br \/>\n                } catch (error) {<br \/>\n                    console.error(&#039;JSON\u89e3\u6790\u9519\u8bef:&#039;, error);<br \/>\n                    this.showError(&#039;\u6570\u636e\u683c\u5f0f\u9519\u8bef&#039;);<br \/>\n                    return null;<br \/>\n                }<br \/>\n            }<\/p>\n<p>            async handleHTML(response) {<br \/>\n                const html &#061; await response.text();<\/p>\n<p>                \/\/ \u5b89\u5168\u5730\u63d2\u5165HTML<br \/>\n                const container &#061; document.getElementById(&#039;content&#039;);<br \/>\n                if (container) {<br \/>\n                    \/\/ \u4f7f\u7528DOMPurify\u8fdb\u884c\u6d88\u6bd2&#xff08;\u5982\u679c\u53ef\u7528&#xff09;<br \/>\n                    if (typeof DOMPurify !&#061;&#061; &#039;undefined&#039;) {<br \/>\n                        container.innerHTML &#061; DOMPurify.sanitize(html);<br \/>\n                    } else {<br \/>\n                        container.innerHTML &#061; html;<br \/>\n                    }<br \/>\n                }<\/p>\n<p>                return html;<br \/>\n            }<\/p>\n<p>            updateUI(data) {<br \/>\n                \/\/ \u66f4\u65b0\u7528\u6237\u754c\u9762\u7684\u903b\u8f91<br \/>\n                if (data.user) {<br \/>\n                    document.getElementById(&#039;username&#039;).textContent &#061; data.user.name;<br \/>\n                }<\/p>\n<p>                if (data.items &amp;&amp; Array.isArray(data.items)) {<br \/>\n                    this.renderList(data.items);<br \/>\n                }<br \/>\n            }<\/p>\n<p>            showError(message) {<br \/>\n                \/\/ \u663e\u793a\u9519\u8bef\u4fe1\u606f<br \/>\n                const errorDiv &#061; document.getElementById(&#039;error-message&#039;);<br \/>\n                if (errorDiv) {<br \/>\n                    errorDiv.textContent &#061; message;<br \/>\n                    errorDiv.style.display &#061; &#039;block&#039;;<\/p>\n<p>                    \/\/ 3\u79d2\u540e\u9690\u85cf<br \/>\n                    setTimeout(() &#061;&gt; {<br \/>\n                        errorDiv.style.display &#061; &#039;none&#039;;<br \/>\n                    }, 3000);<br \/>\n                }<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\n        document.addEventListener(&#039;DOMContentLoaded&#039;, async () &#061;&gt; {<br \/>\n            const handler &#061; new ResponseHandler();<\/p>\n<p>            try {<br \/>\n                const response &#061; await fetch(&#039;\/api\/user\/profile&#039;);<br \/>\n                await handler.handleResponse(response);<br \/>\n            } catch (error) {<br \/>\n                console.error(&#039;\u8bf7\u6c42\u5931\u8d25:&#039;, error);<br \/>\n                handler.showError(&#039;\u7f51\u7edc\u8bf7\u6c42\u5931\u8d25&#039;);<br \/>\n            }<br \/>\n        });<br \/>\n    &lt;\/script&gt;<br \/>\n&lt;\/head&gt;<br \/>\n&lt;body&gt;<br \/>\n    &lt;div id&#061;&#034;error-message&#034; style&#061;&#034;display: none; color: red;&#034;&gt;&lt;\/div&gt;<br \/>\n    &lt;div id&#061;&#034;content&#034;&gt;<br \/>\n        &lt;span id&#061;&#034;username&#034;&gt;\u52a0\u8f7d\u4e2d&#8230;&lt;\/span&gt;<br \/>\n        &lt;ul id&#061;&#034;item-list&#034;&gt;&lt;\/ul&gt;<br \/>\n    &lt;\/div&gt;<br \/>\n&lt;\/body&gt;<br \/>\n&lt;\/html&gt;<\/p>\n<h5>\u79fb\u52a8\u7aef\u5904\u7406\u903b\u8f91<\/h5>\n<p>kotlin<\/p>\n<p>\/\/ Android Kotlin\u793a\u4f8b&#xff1a;\u5904\u7406200 OK\u54cd\u5e94<br \/>\nclass ApiResponseHandler {<\/p>\n<p>    sealed class Result&lt;out T&gt; {<br \/>\n        data class Success&lt;out T&gt;(val data: T) : Result&lt;T&gt;()<br \/>\n        data class Error(val message: String, val code: Int? &#061; null) : Result&lt;Nothing&gt;()<br \/>\n        object NetworkError : Result&lt;Nothing&gt;()<br \/>\n    }<\/p>\n<p>    suspend fun &lt;T&gt; handleResponse(<br \/>\n        request: suspend () -&gt; Response&lt;T&gt;<br \/>\n    ): Result&lt;T&gt; {<br \/>\n        return try {<br \/>\n            val response &#061; request()<\/p>\n<p>            when (response.code()) {<br \/>\n                in 200..299 -&gt; {<br \/>\n                    \/\/ \u6210\u529f\u54cd\u5e94<br \/>\n                    val body &#061; response.body()<br \/>\n                    if (body !&#061; null) {<br \/>\n                        Result.Success(body)<br \/>\n                    } else {<br \/>\n                        Result.Error(&#034;\u54cd\u5e94\u4f53\u4e3a\u7a7a&#034;)<br \/>\n                    }<br \/>\n                }<br \/>\n                in 400..499 -&gt; {<br \/>\n                    \/\/ \u5ba2\u6237\u7aef\u9519\u8bef<br \/>\n                    Result.Error(&#034;\u5ba2\u6237\u7aef\u9519\u8bef: ${response.code()}&#034;)<br \/>\n                }<br \/>\n                in 500..599 -&gt; {<br \/>\n                    \/\/ \u670d\u52a1\u5668\u9519\u8bef<br \/>\n                    Result.Error(&#034;\u670d\u52a1\u5668\u9519\u8bef: ${response.code()}&#034;)<br \/>\n                }<br \/>\n                else -&gt; {<br \/>\n                    Result.Error(&#034;\u672a\u77e5\u72b6\u6001\u7801: ${response.code()}&#034;)<br \/>\n                }<br \/>\n            }<br \/>\n        } catch (e: IOException) {<br \/>\n            Result.NetworkError<br \/>\n        } catch (e: Exception) {<br \/>\n            Result.Error(&#034;\u5904\u7406\u5f02\u5e38: ${e.message}&#034;)<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\n    suspend fun loadUserData(userId: String): Result&lt;User&gt; {<br \/>\n        return handleResponse {<br \/>\n            apiService.getUser(userId) \/\/ \u8fd4\u56deRetrofit\u7684Response&lt;User&gt;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ Retrofit\u63a5\u53e3\u5b9a\u4e49<br \/>\ninterface ApiService {<br \/>\n    &#064;GET(&#034;users\/{id}&#034;)<br \/>\n    suspend fun getUser(&#064;Path(&#034;id&#034;) userId: String): Response&lt;User&gt;<br \/>\n}<\/p>\n<p>\/\/ \u5728ViewModel\u4e2d\u4f7f\u7528<br \/>\nclass UserViewModel : ViewModel() {<br \/>\n    private val _userState &#061; MutableStateFlow&lt;UiState&lt;User&gt;&gt;(UiState.Loading)<br \/>\n    val userState: StateFlow&lt;UiState&lt;User&gt;&gt; &#061; _userState<\/p>\n<p>    fun loadUser(userId: String) {<br \/>\n        viewModelScope.launch {<br \/>\n            _userState.value &#061; UiState.Loading<\/p>\n<p>            when (val result &#061; apiHandler.loadUserData(userId)) {<br \/>\n                is Result.Success -&gt; {<br \/>\n                    _userState.value &#061; UiState.Success(result.data)<br \/>\n                }<br \/>\n                is Result.Error -&gt; {<br \/>\n                    _userState.value &#061; UiState.Error(result.message)<br \/>\n                }<br \/>\n                Result.NetworkError -&gt; {<br \/>\n                    _userState.value &#061; UiState.Error(&#034;\u7f51\u7edc\u8fde\u63a5\u5931\u8d25&#034;)<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>4.5 \u6027\u80fd\u4f18\u5316\u7b56\u7565<\/h4>\n<h5>\u6761\u4ef6\u8bf7\u6c42\u4f18\u5316<\/h5>\n<p>python<\/p>\n<p># Django\u793a\u4f8b&#xff1a;\u4f7f\u7528\u6761\u4ef6\u8bf7\u6c42\u4f18\u5316200\u54cd\u5e94<br \/>\nfrom django.views.decorators.http import condition<br \/>\nfrom django.utils.http import http_date<br \/>\nimport hashlib<br \/>\nfrom datetime import datetime<\/p>\n<p>def resource_etag(request, resource_id):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u8d44\u6e90\u7684ETag&#034;&#034;&#034;<br \/>\n    resource &#061; get_object_or_404(Resource, id&#061;resource_id)<br \/>\n    content &#061; f&#034;{resource.id}{resource.modified_at}{resource.version}&#034;<br \/>\n    return hashlib.md5(content.encode()).hexdigest()<\/p>\n<p>def resource_last_modified(request, resource_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u6700\u540e\u4fee\u6539\u65f6\u95f4&#034;&#034;&#034;<br \/>\n    resource &#061; get_object_or_404(Resource, id&#061;resource_id)<br \/>\n    return resource.modified_at<\/p>\n<p>&#064;condition(etag_func&#061;resource_etag, last_modified_func&#061;resource_last_modified)<br \/>\ndef resource_detail(request, resource_id):<br \/>\n    &#034;&#034;&#034;\u8d44\u6e90\u8be6\u60c5\u89c6\u56fe&#xff0c;\u652f\u6301\u6761\u4ef6\u8bf7\u6c42&#034;&#034;&#034;<br \/>\n    resource &#061; get_object_or_404(Resource, id&#061;resource_id)<\/p>\n<p>    # \u5982\u679c\u6761\u4ef6\u8bf7\u6c42\u5339\u914d&#xff0c;Django\u4f1a\u81ea\u52a8\u8fd4\u56de304<br \/>\n    # \u5426\u5219\u6267\u884c\u4ee5\u4e0b\u903b\u8f91<\/p>\n<p>    data &#061; {<br \/>\n        &#039;id&#039;: resource.id,<br \/>\n        &#039;name&#039;: resource.name,<br \/>\n        &#039;description&#039;: resource.description,<br \/>\n        &#039;created_at&#039;: resource.created_at,<br \/>\n        &#039;updated_at&#039;: resource.modified_at<br \/>\n    }<\/p>\n<p>    response &#061; JsonResponse(data)<\/p>\n<p>    # \u8bbe\u7f6e\u7f13\u5b58\u5934\u90e8<br \/>\n    response[&#039;Cache-Control&#039;] &#061; &#039;public, max-age&#061;300&#039;<br \/>\n    response[&#039;ETag&#039;] &#061; f&#039;&#034;{resource_etag(request, resource_id)}&#034;&#039;<br \/>\n    response[&#039;Last-Modified&#039;] &#061; http_date(resource.modified_at.timestamp())<\/p>\n<p>    return response<\/p>\n<h5>\u54cd\u5e94\u538b\u7f29\u4e0e\u4f18\u5316<\/h5>\n<p>nginx<\/p>\n<p># Nginx\u914d\u7f6e&#xff1a;\u4f18\u5316200\u54cd\u5e94\u7684\u4f20\u8f93\u6548\u7387<br \/>\nhttp {<br \/>\n    # \u542f\u7528gzip\u538b\u7f29<br \/>\n    gzip on;<br \/>\n    gzip_vary on;<br \/>\n    gzip_proxied any;<br \/>\n    gzip_comp_level 6;<br \/>\n    gzip_types<br \/>\n        text\/plain<br \/>\n        text\/css<br \/>\n        text\/xml<br \/>\n        text\/javascript<br \/>\n        application\/json<br \/>\n        application\/javascript<br \/>\n        application\/xml&#043;rss<br \/>\n        application\/atom&#043;xml<br \/>\n        image\/svg&#043;xml;<\/p>\n<p>    # Brotli\u538b\u7f29&#xff08;\u5982\u679c\u652f\u6301&#xff09;<br \/>\n    brotli on;<br \/>\n    brotli_comp_level 6;<br \/>\n    brotli_types<br \/>\n        text\/plain<br \/>\n        text\/css<br \/>\n        text\/xml<br \/>\n        application\/json<br \/>\n        application\/javascript<br \/>\n        application\/xml&#043;rss<br \/>\n        application\/atom&#043;xml;<\/p>\n<p>    # \u9759\u6001\u8d44\u6e90\u4f18\u5316<br \/>\n    server {<br \/>\n        location ~* \\\\.(jpg|jpeg|png|gif|ico|webp)$ {<br \/>\n            # \u56fe\u7247\u8d44\u6e90<br \/>\n            expires 1y;<br \/>\n            add_header Cache-Control &#034;public, immutable&#034;;<\/p>\n<p>            # \u542f\u7528\u56fe\u7247\u4f18\u5316<br \/>\n            image_filter resize 800 600; # \u6839\u636e\u9700\u8981\u8c03\u6574<\/p>\n<p>            # WebP\u81ea\u52a8\u8f6c\u6362<br \/>\n            location ~* \\\\.(jpg|jpeg|png)$ {<br \/>\n                add_header Vary Accept;<\/p>\n<p>                # \u68c0\u67e5\u6d4f\u89c8\u5668\u662f\u5426\u652f\u6301WebP<br \/>\n                if ($http_accept ~* &#034;webp&#034;) {<br \/>\n                    rewrite ^(.*)\\\\.(jpg|jpeg|png)$ $1.webp break;<br \/>\n                }<br \/>\n            }<br \/>\n        }<\/p>\n<p>        location ~* \\\\.(css|js)$ {<br \/>\n            # CSS\u548cJS\u8d44\u6e90<br \/>\n            expires 1y;<br \/>\n            add_header Cache-Control &#034;public, immutable&#034;;<\/p>\n<p>            # \u542f\u7528minify<br \/>\n            location ~* \\\\.js$ {<br \/>\n                js_minify on;<br \/>\n            }<\/p>\n<p>            location ~* \\\\.css$ {<br \/>\n                css_minify on;<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<h4>4.6 \u5b89\u5168\u8003\u8651<\/h4>\n<h5>\u654f\u611f\u4fe1\u606f\u8fc7\u6ee4<\/h5>\n<p>javascript<\/p>\n<p>\/\/ Node.js Express\u793a\u4f8b&#xff1a;\u654f\u611f\u4fe1\u606f\u8fc7\u6ee4<br \/>\nconst express &#061; require(&#039;express&#039;);<br \/>\nconst app &#061; express();<\/p>\n<p>\/\/ \u54cd\u5e94\u8f6c\u6362\u4e2d\u95f4\u4ef6<br \/>\napp.use((req, res, next) &#061;&gt; {<br \/>\n    const originalSend &#061; res.send;<\/p>\n<p>    res.send &#061; function(data) {<br \/>\n        \/\/ \u53ea\u5728200 OK\u65f6\u8fdb\u884c\u8fc7\u6ee4<br \/>\n        if (res.statusCode &#061;&#061;&#061; 200) {<br \/>\n            data &#061; filterSensitiveData(data, req.user);<br \/>\n        }<\/p>\n<p>        originalSend.call(this, data);<br \/>\n    };<\/p>\n<p>    next();<br \/>\n});<\/p>\n<p>function filterSensitiveData(data, user) {<br \/>\n    \/**<br \/>\n     * \u6839\u636e\u7528\u6237\u89d2\u8272\u8fc7\u6ee4\u654f\u611f\u4fe1\u606f<br \/>\n     *\/<\/p>\n<p>    \/\/ \u5982\u679c\u662f\u5b57\u7b26\u4e32&#xff0c;\u5c1d\u8bd5\u89e3\u6790\u4e3aJSON<br \/>\n    let parsedData;<br \/>\n    try {<br \/>\n        parsedData &#061; typeof data &#061;&#061;&#061; &#039;string&#039; ? JSON.parse(data) : data;<br \/>\n    } catch (e) {<br \/>\n        return data; \/\/ \u4e0d\u662fJSON&#xff0c;\u76f4\u63a5\u8fd4\u56de<br \/>\n    }<\/p>\n<p>    \/\/ \u9012\u5f52\u8fc7\u6ee4\u51fd\u6570<br \/>\n    function filterObject(obj, path &#061; &#039;&#039;) {<br \/>\n        if (!obj || typeof obj !&#061;&#061; &#039;object&#039;) {<br \/>\n            return obj;<br \/>\n        }<\/p>\n<p>        if (Array.isArray(obj)) {<br \/>\n            return obj.map((item, index) &#061;&gt;<br \/>\n                filterObject(item, &#096;${path}[${index}]&#096;)<br \/>\n            );<br \/>\n        }<\/p>\n<p>        const filtered &#061; {};<\/p>\n<p>        for (const [key, value] of Object.entries(obj)) {<br \/>\n            const currentPath &#061; path ? &#096;${path}.${key}&#096; : key;<\/p>\n<p>            \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u654f\u611f\u5b57\u6bb5<br \/>\n            if (isSensitiveField(currentPath, key, value)) {<br \/>\n                \/\/ \u6839\u636e\u7528\u6237\u6743\u9650\u51b3\u5b9a\u662f\u5426\u663e\u793a<br \/>\n                if (user &amp;&amp; user.hasPermission(&#039;view_sensitive_data&#039;)) {<br \/>\n                    filtered[key] &#061; value;<br \/>\n                } else {<br \/>\n                    filtered[key] &#061; &#039;[FILTERED]&#039;;<br \/>\n                }<br \/>\n            } else if (typeof value &#061;&#061;&#061; &#039;object&#039; &amp;&amp; value !&#061;&#061; null) {<br \/>\n                filtered[key] &#061; filterObject(value, currentPath);<br \/>\n            } else {<br \/>\n                filtered[key] &#061; value;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return filtered;<br \/>\n    }<\/p>\n<p>    return filterObject(parsedData);<br \/>\n}<\/p>\n<p>function isSensitiveField(path, key, value) {<br \/>\n    const sensitivePatterns &#061; [<br \/>\n        \/password\/i,<br \/>\n        \/token\/i,<br \/>\n        \/secret\/i,<br \/>\n        \/api[_-]?key\/i,<br \/>\n        \/credit[_-]?card\/i,<br \/>\n        \/ssn|social[_-]?security\/i,<br \/>\n        \/phone|mobile\/i,<br \/>\n        \/email\/i,<br \/>\n        \/address\/i<br \/>\n    ];<\/p>\n<p>    \/\/ \u68c0\u67e5\u952e\u540d<br \/>\n    if (sensitivePatterns.some(pattern &#061;&gt; pattern.test(key))) {<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u8def\u5f84<br \/>\n    if (sensitivePatterns.some(pattern &#061;&gt; pattern.test(path))) {<br \/>\n        return true;<br \/>\n    }<\/p>\n<p>    \/\/ \u68c0\u67e5\u503c\u7c7b\u578b&#xff08;\u5982\u770b\u8d77\u6765\u50cf\u90ae\u7bb1&#xff09;<br \/>\n    if (typeof value &#061;&#061;&#061; &#039;string&#039;) {<br \/>\n        const emailRegex &#061; \/^[^\\\\s&#064;]&#043;&#064;[^\\\\s&#064;]&#043;\\\\.[^\\\\s&#064;]&#043;$\/;<br \/>\n        if (emailRegex.test(value) &amp;&amp; value.length &lt; 100) {<br \/>\n            return true;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    return false;<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\napp.get(&#039;\/api\/user\/:id&#039;, (req, res) &#061;&gt; {<br \/>\n    const user &#061; {<br \/>\n        id: 123,<br \/>\n        username: &#039;john_doe&#039;,<br \/>\n        email: &#039;john&#064;example.com&#039;,<br \/>\n        password_hash: &#039;hashed_password_here&#039;,<br \/>\n        phone: &#039;&#043;1234567890&#039;,<br \/>\n        address: {<br \/>\n            street: &#039;123 Main St&#039;,<br \/>\n            city: &#039;Anytown&#039;,<br \/>\n            zip: &#039;12345&#039;<br \/>\n        }<br \/>\n    };<\/p>\n<p>    res.status(200).json(user);<br \/>\n    \/\/ \u975e\u7ba1\u7406\u5458\u7528\u6237\u5c06\u770b\u5230\u8fc7\u6ee4\u540e\u7684\u6570\u636e<br \/>\n});<\/p>\n<h5>\u9632\u6b62\u4fe1\u606f\u6cc4\u9732<\/h5>\n<p>python<\/p>\n<p># Flask\u5b89\u5168\u54cd\u5e94\u914d\u7f6e<br \/>\nfrom flask import Flask, jsonify<br \/>\nfrom flask_talisman import Talisman<br \/>\nimport json<\/p>\n<p>app &#061; Flask(__name__)<\/p>\n<p># \u914d\u7f6e\u5b89\u5168\u5934\u90e8<br \/>\ncsp &#061; {<br \/>\n    &#039;default-src&#039;: &#034;&#039;self&#039;&#034;,<br \/>\n    &#039;script-src&#039;: [&#034;&#039;self&#039;&#034;, &#034;https:\/\/cdn.example.com&#034;],<br \/>\n    &#039;style-src&#039;: [&#034;&#039;self&#039;&#034;, &#034;&#039;unsafe-inline&#039;&#034;],<br \/>\n    &#039;img-src&#039;: [&#034;&#039;self&#039;&#034;, &#034;data:&#034;, &#034;https:\/\/*.example.com&#034;]<br \/>\n}<\/p>\n<p>Talisman(app,<br \/>\n    content_security_policy&#061;csp,<br \/>\n    content_security_policy_nonce_in&#061;[&#039;script-src&#039;],<br \/>\n    force_https&#061;True,<br \/>\n    session_cookie_secure&#061;True,<br \/>\n    session_cookie_http_only&#061;True<br \/>\n)<\/p>\n<p>class SecureJSONEncoder(json.JSONEncoder):<br \/>\n    &#034;&#034;&#034;\u5b89\u5168JSON\u7f16\u7801\u5668&#xff0c;\u9632\u6b62\u654f\u611f\u4fe1\u606f\u6cc4\u9732&#034;&#034;&#034;<\/p>\n<p>    def default(self, obj):<br \/>\n        # \u8fc7\u6ee4\u654f\u611f\u6570\u636e<br \/>\n        if hasattr(obj, &#039;to_secure_dict&#039;):<br \/>\n            return obj.to_secure_dict()<\/p>\n<p>        # \u5904\u7406\u5e38\u89c1\u7684\u654f\u611f\u7c7b\u578b<br \/>\n        if isinstance(obj, dict):<br \/>\n            return self.sanitize_dict(obj)<\/p>\n<p>        return super().default(obj)<\/p>\n<p>    def sanitize_dict(self, d):<br \/>\n        &#034;&#034;&#034;\u9012\u5f52\u8fc7\u6ee4\u5b57\u5178\u4e2d\u7684\u654f\u611f\u6570\u636e&#034;&#034;&#034;<br \/>\n        sanitized &#061; {}<\/p>\n<p>        for key, value in d.items():<br \/>\n            if self.is_sensitive_key(key):<br \/>\n                sanitized[key] &#061; &#039;[FILTERED]&#039;<br \/>\n            elif isinstance(value, dict):<br \/>\n                sanitized[key] &#061; self.sanitize_dict(value)<br \/>\n            elif isinstance(value, list):<br \/>\n                sanitized[key] &#061; [<br \/>\n                    self.sanitize_dict(item) if isinstance(item, dict) else item<br \/>\n                    for item in value<br \/>\n                ]<br \/>\n            else:<br \/>\n                sanitized[key] &#061; value<\/p>\n<p>        return sanitized<\/p>\n<p>    def is_sensitive_key(self, key):<br \/>\n        sensitive_keys &#061; {<br \/>\n            &#039;password&#039;, &#039;token&#039;, &#039;secret&#039;, &#039;api_key&#039;, &#039;credit_card&#039;,<br \/>\n            &#039;ssn&#039;, &#039;social_security&#039;, &#039;phone&#039;, &#039;email&#039;, &#039;address&#039;<br \/>\n        }<\/p>\n<p>        key_lower &#061; key.lower()<br \/>\n        return any(sensitive in key_lower for sensitive in sensitive_keys)<\/p>\n<p>app.json_encoder &#061; SecureJSONEncoder<\/p>\n<p># \u793a\u4f8b\u6570\u636e\u6a21\u578b<br \/>\nclass User:<br \/>\n    def __init__(self, id, username, email, password_hash):<br \/>\n        self.id &#061; id<br \/>\n        self.username &#061; username<br \/>\n        self.email &#061; email<br \/>\n        self.password_hash &#061; password_hash<\/p>\n<p>    def to_secure_dict(self):<br \/>\n        return {<br \/>\n            &#039;id&#039;: self.id,<br \/>\n            &#039;username&#039;: self.username,<br \/>\n            &#039;email&#039;: self.email,<br \/>\n            # password_hash \u4e0d\u4f1a\u88ab\u5305\u542b<br \/>\n        }<\/p>\n<p>&#064;app.route(&#039;\/api\/user\/&lt;int:user_id&gt;&#039;)<br \/>\ndef get_user(user_id):<br \/>\n    # \u6a21\u62df\u4ece\u6570\u636e\u5e93\u83b7\u53d6\u7528\u6237<br \/>\n    user &#061; User(<br \/>\n        id&#061;user_id,<br \/>\n        username&#061;&#039;john_doe&#039;,<br \/>\n        email&#061;&#039;john&#064;example.com&#039;,<br \/>\n        password_hash&#061;&#039;$2b$12$&#8230;&#039;  # \u5b9e\u9645\u4e2d\u4ece\u6570\u636e\u5e93\u83b7\u53d6<br \/>\n    )<\/p>\n<p>    # \u81ea\u52a8\u4f7f\u7528\u5b89\u5168\u7684JSON\u7f16\u7801<br \/>\n    return jsonify(user), 200<\/p>\n<h4>4.7 \u5e38\u89c1\u9519\u8bef\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h4>\n<h5>\u9519\u8bef\u6a21\u5f0f&#xff1a;\u8bef\u7528200 OK<\/h5>\n<p>python<\/p>\n<p># \u9519\u8bef\u7684\u505a\u6cd5&#xff1a;\u7528200 OK\u5305\u88c5\u9519\u8bef<br \/>\n&#064;app.route(&#039;\/api\/resource\/&lt;int:resource_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_resource_wrong(resource_id):<br \/>\n    resource &#061; Resource.query.get(resource_id)<\/p>\n<p>    if not resource:<br \/>\n        # \u9519\u8bef&#xff1a;\u4f7f\u7528200\u8fd4\u56de\u9519\u8bef\u4fe1\u606f<br \/>\n        return jsonify({<br \/>\n            &#034;status&#034;: &#034;error&#034;,<br \/>\n            &#034;message&#034;: &#034;Resource not found&#034;,<br \/>\n            &#034;code&#034;: &#034;NOT_FOUND&#034;<br \/>\n        }), 200  # \u5e94\u8be5\u4f7f\u7528404<\/p>\n<p>    return jsonify(resource.to_dict()), 200<\/p>\n<p># \u6b63\u786e\u7684\u505a\u6cd5&#xff1a;\u4f7f\u7528\u9002\u5f53\u7684\u72b6\u6001\u7801<br \/>\n&#064;app.route(&#039;\/api\/resource\/&lt;int:resource_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_resource_correct(resource_id):<br \/>\n    resource &#061; Resource.query.get(resource_id)<\/p>\n<p>    if not resource:<br \/>\n        # \u6b63\u786e&#xff1a;\u4f7f\u7528404\u72b6\u6001\u7801<br \/>\n        return jsonify({<br \/>\n            &#034;error&#034;: &#034;Resource not found&#034;,<br \/>\n            &#034;code&#034;: &#034;RESOURCE_NOT_FOUND&#034;<br \/>\n        }), 404<\/p>\n<p>    return jsonify(resource.to_dict()), 200<\/p>\n<h5>\u6700\u4f73\u5b9e\u8df5\u6e05\u5355<\/h5>\n<p>yaml<\/p>\n<p># 200 OK\u6700\u4f73\u5b9e\u8df5\u914d\u7f6e<br \/>\nbest_practices:<br \/>\n  response_consistency:<br \/>\n    &#8211; \u6240\u6709\u6210\u529f\u7684GET\u8bf7\u6c42\u8fd4\u56de200 OK<br \/>\n    &#8211; \u6240\u6709\u6210\u529f\u7684PUT\u8bf7\u6c42\u8fd4\u56de200 OK\u6216204 No Content<br \/>\n    &#8211; \u6240\u6709\u6210\u529f\u7684DELETE\u8bf7\u6c42\u8fd4\u56de200 OK\u6216204 No Content<br \/>\n    &#8211; \u6240\u6709\u6210\u529f\u7684POST\u8bf7\u6c42\u8fd4\u56de200 OK\u6216201 Created<\/p>\n<p>  response_body:<br \/>\n    &#8211; 200\u54cd\u5e94\u5e94\u5305\u542b\u5b8c\u6574\u7684\u8d44\u6e90\u8868\u793a<br \/>\n    &#8211; \u5bf9\u4e8e\u96c6\u5408&#xff0c;\u5305\u542b\u5206\u9875\u4fe1\u606f<br \/>\n    &#8211; \u5bf9\u4e8e\u64cd\u4f5c\u7ed3\u679c&#xff0c;\u5305\u542b\u64cd\u4f5c\u72b6\u6001\u548c\u5143\u6570\u636e<br \/>\n    &#8211; \u59cb\u7ec8\u5305\u542b\u81ea\u63cf\u8ff0\u7684\u9519\u8bef\u6d88\u606f&#xff08;\u5373\u4f7f\u662f\u6210\u529f&#xff09;<\/p>\n<p>  headers:<br \/>\n    required:<br \/>\n      &#8211; Content-Type: \u660e\u786e\u6307\u5b9a\u5185\u5bb9\u7c7b\u578b<br \/>\n      &#8211; Content-Length: \u6307\u5b9a\u5185\u5bb9\u957f\u5ea6&#xff08;\u975e\u6d41\u5f0f\u54cd\u5e94&#xff09;<\/p>\n<p>    recommended:<br \/>\n      &#8211; ETag: \u5b9e\u4f53\u6807\u7b7e&#xff0c;\u7528\u4e8e\u7f13\u5b58\u9a8c\u8bc1<br \/>\n      &#8211; Last-Modified: \u6700\u540e\u4fee\u6539\u65f6\u95f4<br \/>\n      &#8211; Cache-Control: \u7f13\u5b58\u63a7\u5236\u7b56\u7565<br \/>\n      &#8211; Vary: \u6307\u5b9a\u54ea\u4e9b\u5934\u90e8\u5f71\u54cd\u7f13\u5b58<\/p>\n<p>    conditional:<br \/>\n      &#8211; \u5982\u679c\u652f\u6301\u6761\u4ef6\u8bf7\u6c42&#xff0c;\u5305\u542bETag\u548cLast-Modified<br \/>\n      &#8211; \u5982\u679c\u8d44\u6e90\u4e0d\u5e38\u53d8\u5316&#xff0c;\u8bbe\u7f6e\u8f83\u957f\u7684max-age<br \/>\n      &#8211; \u5982\u679c\u8d44\u6e90\u4e2a\u6027\u5316&#xff0c;\u8bbe\u7f6eprivate\u7f13\u5b58<\/p>\n<p>  security:<br \/>\n    &#8211; \u8fc7\u6ee4\u654f\u611f\u4fe1\u606f&#xff08;\u5bc6\u7801\u3001\u4ee4\u724c\u7b49&#xff09;<br \/>\n    &#8211; \u5b9e\u65bd\u9002\u5f53\u7684CORS\u7b56\u7565<br \/>\n    &#8211; \u8bbe\u7f6e\u5b89\u5168\u5934\u90e8&#xff08;X-Content-Type-Options\u7b49&#xff09;<br \/>\n    &#8211; \u9a8c\u8bc1\u7528\u6237\u6743\u9650\u540e\u518d\u8fd4\u56de\u6570\u636e<\/p>\n<p>  performance:<br \/>\n    &#8211; \u542f\u7528\u538b\u7f29&#xff08;gzip\u3001brotli&#xff09;<br \/>\n    &#8211; \u4f7f\u7528\u6761\u4ef6\u8bf7\u6c42\u51cf\u5c11\u6570\u636e\u4f20\u8f93<br \/>\n    &#8211; \u5bf9\u4e8e\u5927\u578b\u54cd\u5e94&#xff0c;\u8003\u8651\u5206\u9875\u6216\u6d41\u5f0f\u4f20\u8f93<br \/>\n    &#8211; \u8bbe\u7f6e\u9002\u5f53\u7684\u7f13\u5b58\u7b56\u7565<\/p>\n<p>  api_design:<br \/>\n    &#8211; \u4fdd\u6301\u54cd\u5e94\u7ed3\u6784\u4e00\u81f4<br \/>\n    &#8211; \u4f7f\u7528\u6807\u51c6\u5a92\u4f53\u7c7b\u578b&#xff08;application\/json\u7b49&#xff09;<br \/>\n    &#8211; \u63d0\u4f9b\u8d44\u6e90\u94fe\u63a5&#xff08;HATEOAS&#xff09;<br \/>\n    &#8211; \u5305\u542b\u7248\u672c\u4fe1\u606f<\/p>\n<p>  error_handling:<br \/>\n    &#8211; \u4e0d\u8981\u7528200\u5305\u88c5\u9519\u8bef<br \/>\n    &#8211; \u63d0\u4f9b\u673a\u5668\u53ef\u8bfb\u7684\u9519\u8bef\u4ee3\u7801<br \/>\n    &#8211; \u5305\u542b\u8be6\u7ec6\u7684\u4eba\u7c7b\u53ef\u8bfb\u6d88\u606f<br \/>\n    &#8211; \u63d0\u4f9b\u89e3\u51b3\u9519\u8bef\u7684\u5efa\u8bae<\/p>\n<p>  monitoring:<br \/>\n    &#8211; \u76d1\u63a7200\u54cd\u5e94\u65f6\u95f4<br \/>\n    &#8211; \u8ddf\u8e2a200\u54cd\u5e94\u7684\u5206\u5e03<br \/>\n    &#8211; \u8bbe\u7f6e\u54cd\u5e94\u5927\u5c0f\u7684\u8b66\u62a5<br \/>\n    &#8211; \u8bb0\u5f55\u5f02\u5e38\u5927\u7684200\u54cd\u5e94<\/p>\n<h5>\u5b9e\u6218\u793a\u4f8b&#xff1a;\u5b8c\u6574\u7684API\u7aef\u70b9<\/h5>\n<p>python<\/p>\n<p># \u5b8c\u6574\u7684RESTful API\u7aef\u70b9\u793a\u4f8b<br \/>\nfrom flask import Flask, request, jsonify, url_for<br \/>\nfrom flask_sqlalchemy import SQLAlchemy<br \/>\nfrom datetime import datetime<br \/>\nimport hashlib<\/p>\n<p>app &#061; Flask(__name__)<br \/>\napp.config[&#039;SQLALCHEMY_DATABASE_URI&#039;] &#061; &#039;sqlite:\/\/\/app.db&#039;<br \/>\ndb &#061; SQLAlchemy(app)<\/p>\n<p># \u6570\u636e\u6a21\u578b<br \/>\nclass Article(db.Model):<br \/>\n    id &#061; db.Column(db.Integer, primary_key&#061;True)<br \/>\n    title &#061; db.Column(db.String(200), nullable&#061;False)<br \/>\n    content &#061; db.Column(db.Text, nullable&#061;False)<br \/>\n    author_id &#061; db.Column(db.Integer, nullable&#061;False)<br \/>\n    created_at &#061; db.Column(db.DateTime, default&#061;datetime.utcnow)<br \/>\n    updated_at &#061; db.Column(db.DateTime, default&#061;datetime.utcnow,<br \/>\n                          onupdate&#061;datetime.utcnow)<\/p>\n<p>    def to_dict(self, include_content&#061;True):<br \/>\n        &#034;&#034;&#034;\u8f6c\u6362\u4e3a\u5b57\u5178&#xff0c;\u53ef\u9009\u62e9\u5305\u542b\u5185\u5bb9&#034;&#034;&#034;<br \/>\n        data &#061; {<br \/>\n            &#039;id&#039;: self.id,<br \/>\n            &#039;title&#039;: self.title,<br \/>\n            &#039;author_id&#039;: self.author_id,<br \/>\n            &#039;created_at&#039;: self.created_at.isoformat(),<br \/>\n            &#039;updated_at&#039;: self.updated_at.isoformat(),<br \/>\n            &#039;links&#039;: {<br \/>\n                &#039;self&#039;: url_for(&#039;get_article&#039;, article_id&#061;self.id, _external&#061;True),<br \/>\n                &#039;author&#039;: url_for(&#039;get_user&#039;, user_id&#061;self.author_id, _external&#061;True)<br \/>\n            }<br \/>\n        }<\/p>\n<p>        if include_content:<br \/>\n            data[&#039;content&#039;] &#061; self.content<\/p>\n<p>        return data<\/p>\n<p>    def generate_etag(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; f&#034;{self.id}{self.updated_at.isoformat()}&#034;<br \/>\n        return hashlib.md5(content.encode()).hexdigest()<\/p>\n<p># \u8f85\u52a9\u51fd\u6570<br \/>\ndef check_conditions(article):<br \/>\n    &#034;&#034;&#034;\u68c0\u67e5\u6761\u4ef6\u8bf7\u6c42\u5934\u90e8&#034;&#034;&#034;<br \/>\n    # \u68c0\u67e5If-None-Match<br \/>\n    if_none_match &#061; request.headers.get(&#039;If-None-Match&#039;, &#039;&#039;)<br \/>\n    current_etag &#061; f&#039;&#034;{article.generate_etag()}&#034;&#039;<\/p>\n<p>    if if_none_match and current_etag in if_none_match:<br \/>\n        return &#039;not_modified&#039;<\/p>\n<p>    # \u68c0\u67e5If-Modified-Since<br \/>\n    if_modified_since &#061; request.headers.get(&#039;If-Modified-Since&#039;)<br \/>\n    if if_modified_since:<br \/>\n        try:<br \/>\n            since_date &#061; datetime.strptime(if_modified_since,<br \/>\n                                          &#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<br \/>\n            if article.updated_at &lt;&#061; since_date:<br \/>\n                return &#039;not_modified&#039;<br \/>\n        except ValueError:<br \/>\n            pass<\/p>\n<p>    return &#039;modified&#039;<\/p>\n<p># API\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/api\/articles\/&lt;int:article_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_article(article_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u6587\u7ae0\u8be6\u60c5&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u6587\u7ae0<br \/>\n    article &#061; Article.query.get(article_id)<br \/>\n    if not article:<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;Article not found&#039;,<br \/>\n            &#039;code&#039;: &#039;ARTICLE_NOT_FOUND&#039;<br \/>\n        }), 404<\/p>\n<p>    # \u68c0\u67e5\u6761\u4ef6\u8bf7\u6c42<br \/>\n    condition_status &#061; check_conditions(article)<br \/>\n    if condition_status &#061;&#061; &#039;not_modified&#039;:<br \/>\n        return &#039;&#039;, 304<\/p>\n<p>    # \u68c0\u67e5\u7528\u6237\u6743\u9650<br \/>\n    current_user &#061; get_current_user()  # \u5047\u8bbe\u7684\u51fd\u6570<br \/>\n    if not current_user:<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;Authentication required&#039;,<br \/>\n            &#039;code&#039;: &#039;UNAUTHORIZED&#039;<br \/>\n        }), 401<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94<br \/>\n    include_content &#061; current_user.has_permission(&#039;view_article_content&#039;)<br \/>\n    response_data &#061; article.to_dict(include_content&#061;include_content)<\/p>\n<p>    # \u6dfb\u52a0\u5206\u9875\u4fe1\u606f&#xff08;\u5982\u679c\u6709\u76f8\u5173\u6587\u7ae0&#xff09;<br \/>\n    related_articles &#061; Article.query.filter(<br \/>\n        Article.author_id &#061;&#061; article.author_id,<br \/>\n        Article.id !&#061; article.id<br \/>\n    ).limit(5).all()<\/p>\n<p>    if related_articles:<br \/>\n        response_data[&#039;related_articles&#039;] &#061; [<br \/>\n            {<br \/>\n                &#039;id&#039;: a.id,<br \/>\n                &#039;title&#039;: a.title,<br \/>\n                &#039;link&#039;: url_for(&#039;get_article&#039;, article_id&#061;a.id, _external&#061;True)<br \/>\n            }<br \/>\n            for a in related_articles<br \/>\n        ]<\/p>\n<p>    # \u521b\u5efa\u54cd\u5e94<br \/>\n    response &#061; jsonify(response_data)<\/p>\n<p>    # \u8bbe\u7f6e\u5934\u90e8<br \/>\n    response.headers[&#039;ETag&#039;] &#061; f&#039;&#034;{article.generate_etag()}&#034;&#039;<br \/>\n    response.headers[&#039;Last-Modified&#039;] &#061; article.updated_at.strftime(<br \/>\n        &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n    )<\/p>\n<p>    # \u6839\u636e\u6587\u7ae0\u7c7b\u578b\u8bbe\u7f6e\u7f13\u5b58\u7b56\u7565<br \/>\n    if article.updated_at &lt; datetime.utcnow() &#8211; timedelta(days&#061;7):<br \/>\n        # \u8d85\u8fc77\u5929\u672a\u66f4\u65b0\u7684\u6587\u7ae0&#xff0c;\u7f13\u5b581\u5929<br \/>\n        cache_control &#061; &#039;public, max-age&#061;86400, must-revalidate&#039;<br \/>\n    else:<br \/>\n        # \u8fd1\u671f\u66f4\u65b0\u7684\u6587\u7ae0&#xff0c;\u7f13\u5b581\u5c0f\u65f6<br \/>\n        cache_control &#061; &#039;public, max-age&#061;3600, must-revalidate&#039;<\/p>\n<p>    response.headers[&#039;Cache-Control&#039;] &#061; cache_control<\/p>\n<p>    # \u5b89\u5168\u5934\u90e8<br \/>\n    response.headers[&#039;X-Content-Type-Options&#039;] &#061; &#039;nosniff&#039;<br \/>\n    response.headers[&#039;X-Frame-Options&#039;] &#061; &#039;DENY&#039;<\/p>\n<p>    return response, 200<\/p>\n<p>&#064;app.route(&#039;\/api\/articles&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef create_article():<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u65b0\u6587\u7ae0&#034;&#034;&#034;<\/p>\n<p>    # \u9a8c\u8bc1\u8bf7\u6c42<br \/>\n    data &#061; request.get_json()<br \/>\n    if not data or &#039;title&#039; not in data or &#039;content&#039; not in data:<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;Missing required fields&#039;,<br \/>\n            &#039;required&#039;: [&#039;title&#039;, &#039;content&#039;]<br \/>\n        }), 400<\/p>\n<p>    # \u9a8c\u8bc1\u7528\u6237\u6743\u9650<br \/>\n    current_user &#061; get_current_user()<br \/>\n    if not current_user:<br \/>\n        return jsonify({<br \/>\n            &#039;error&#039;: &#039;Authentication required&#039;,<br \/>\n            &#039;code&#039;: &#039;UNAUTHORIZED&#039;<br \/>\n        }), 401<\/p>\n<p>    # \u521b\u5efa\u6587\u7ae0<br \/>\n    article &#061; Article(<br \/>\n        title&#061;data[&#039;title&#039;],<br \/>\n        content&#061;data[&#039;content&#039;],<br \/>\n        author_id&#061;current_user.id<br \/>\n    )<\/p>\n<p>    db.session.add(article)<br \/>\n    db.session.commit()<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94<br \/>\n    response_data &#061; article.to_dict()<br \/>\n    response_data[&#039;message&#039;] &#061; &#039;Article created successfully&#039;<\/p>\n<p>    # \u4f7f\u7528201 Created&#xff08;\u66f4\u5408\u9002&#xff09;<br \/>\n    response &#061; jsonify(response_data)<br \/>\n    response.headers[&#039;Location&#039;] &#061; url_for(&#039;get_article&#039;,<br \/>\n                                          article_id&#061;article.id,<br \/>\n                                          _external&#061;True)<br \/>\n    return response, 201<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    db.create_all()<br \/>\n    app.run(debug&#061;True)<\/p>\n<p>\u8fd9\u4e2a\u793a\u4f8b\u5c55\u793a\u4e86\u5982\u4f55\u6b63\u786e\u5730\u5b9e\u73b0\u8fd4\u56de200 OK\u7684API\u7aef\u70b9&#xff0c;\u5305\u62ec\u6761\u4ef6\u8bf7\u6c42\u5904\u7406\u3001\u7f13\u5b58\u63a7\u5236\u3001\u5b89\u5168\u5934\u90e8\u3001\u9519\u8bef\u5904\u7406\u7b49\u6700\u4f73\u5b9e\u8df5\u3002<\/p>\n<h3>\u7b2c5\u7ae0&#xff1a;201 Created &#8211; \u8d44\u6e90\u521b\u5efa\u6210\u529f<\/h3>\n<h4>5.1 201 Created\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666f<\/h4>\n<h5>RFC\u6807\u51c6\u5b9a\u4e49<\/h5>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.2\u8282&#xff0c;201 Created\u72b6\u6001\u7801\u7684\u5b9a\u4e49&#xff1a;<\/p>\n<p>201 (Created)\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u5df2\u6210\u529f&#xff0c;\u5e76\u56e0\u6b64\u521b\u5efa\u4e86\u4e00\u4e2a\u65b0\u8d44\u6e90\u3002\u65b0\u521b\u5efa\u7684\u8d44\u6e90\u7684\u4e3b\u8981URI\u5728\u54cd\u5e94\u7684Location\u5934\u90e8\u5b57\u6bb5\u4e2d\u63d0\u4f9b\u3002Origin\u670d\u52a1\u5668\u5e94\u8be5\u5728\u8d44\u6e90\u53ef\u7528\u4e4b\u524d\u521b\u5efa\u8d44\u6e90\u3002\u5982\u679c\u65e0\u6cd5\u7acb\u5373\u6267\u884c\u6b64\u64cd\u4f5c&#xff0c;\u670d\u52a1\u5668\u5e94\u8be5\u4ee5202 (Accepted)\u54cd\u5e94\u3002<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<p>201 Created\u7684\u72ec\u7279\u7279\u5f81&#xff1a;<\/p>\n<li>\n<p>\u8d44\u6e90\u521b\u5efa&#xff1a;\u660e\u786e\u8868\u793a\u65b0\u8d44\u6e90\u5df2\u88ab\u521b\u5efa<\/p>\n<\/li>\n<li>\n<p>\u4f4d\u7f6e\u6307\u793a&#xff1a;\u5fc5\u987b&#xff08;\u6216\u5e94\u8be5&#xff09;\u5305\u542bLocation\u5934\u90e8&#xff0c;\u6307\u5411\u65b0\u8d44\u6e90<\/p>\n<\/li>\n<li>\n<p>\u54cd\u5e94\u4f53\u5185\u5bb9&#xff1a;\u901a\u5e38\u5305\u542b\u65b0\u8d44\u6e90\u7684\u8868\u793a\u6216\u81f3\u5c11\u5305\u542b\u5176\u6807\u8bc6\u4fe1\u606f<\/p>\n<\/li>\n<li>\n<p>\u65f6\u95f4\u6027&#xff1a;\u8d44\u6e90\u5e94\u5728\u54cd\u5e94\u65f6\u5df2\u521b\u5efa&#xff08;\u4e0e202 Accepted\u533a\u522b&#xff09;<\/p>\n<\/li>\n<h5>\u9002\u7528\u573a\u666f\u77e9\u9635<\/h5>\n<p>yaml<\/p>\n<p>scenarios:<br \/>\n  create_with_immediate_availability:<br \/>\n    description: &#034;\u8d44\u6e90\u521b\u5efa\u540e\u7acb\u5373\u53ef\u7528&#034;<br \/>\n    examples:<br \/>\n      &#8211; \u521b\u5efa\u65b0\u7528\u6237\u8d26\u6237<br \/>\n      &#8211; \u53d1\u5e03\u65b0\u6587\u7ae0<br \/>\n      &#8211; \u4e0a\u4f20\u6587\u4ef6<br \/>\n      &#8211; \u521b\u5efa\u8ba2\u5355<br \/>\n    response_requirements:<br \/>\n      status: 201<br \/>\n      location: &#034;\u5fc5\u987b\u5305\u542b\u65b0\u8d44\u6e90URI&#034;<br \/>\n      body: &#034;\u5efa\u8bae\u5305\u542b\u5b8c\u6574\u6216\u90e8\u5206\u8d44\u6e90\u8868\u793a&#034;<\/p>\n<p>  create_with_generated_identifier:<br \/>\n    description: &#034;\u670d\u52a1\u5668\u751f\u6210\u8d44\u6e90\u6807\u8bc6\u7b26&#034;<br \/>\n    examples:<br \/>\n      &#8211; \u6570\u636e\u5e93\u81ea\u52a8\u751f\u6210ID<br \/>\n      &#8211; \u5206\u914d\u552f\u4e00URL<br \/>\n      &#8211; \u751f\u6210API\u5bc6\u94a5<br \/>\n    response_requirements:<br \/>\n      status: 201<br \/>\n      location: &#034;\u5fc5\u987b\u5305\u542b\u65b0\u8d44\u6e90URI&#034;<br \/>\n      body: &#034;\u5fc5\u987b\u5305\u542b\u751f\u6210\u7684\u6807\u8bc6\u7b26&#034;<\/p>\n<p>  batch_create_partial_success:<br \/>\n    description: &#034;\u6279\u91cf\u521b\u5efa&#xff0c;\u90e8\u5206\u6210\u529f&#034;<br \/>\n    examples:<br \/>\n      &#8211; \u6279\u91cf\u5bfc\u5165\u7528\u6237<br \/>\n      &#8211; \u6279\u91cf\u521b\u5efa\u4ea7\u54c1<br \/>\n    response_consideration:<br \/>\n      &#8211; \u5982\u679c\u5168\u90e8\u6210\u529f: 201<br \/>\n      &#8211; \u5982\u679c\u90e8\u5206\u6210\u529f: 207 Multi-Status<br \/>\n      &#8211; \u5982\u679c\u5168\u90e8\u5931\u8d25: 400\/422<\/p>\n<h4>5.2 Location\u5934\u90e8\u7684\u6b63\u786e\u7528\u6cd5<\/h4>\n<h5>Location\u5934\u90e8\u89c4\u8303<\/h5>\n<p>python<\/p>\n<p># Location\u5934\u90e8\u751f\u6210\u5668<br \/>\nclass LocationHeaderBuilder:<br \/>\n    &#034;&#034;&#034;\u6784\u5efa\u7b26\u5408\u89c4\u8303\u7684Location\u5934\u90e8&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, base_url&#061;None):<br \/>\n        self.base_url &#061; base_url or self._detect_base_url()<\/p>\n<p>    def build(self, resource_id, resource_path, absolute&#061;True):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u6784\u5efaLocation\u5934\u90e8<\/p>\n<p>        Args:<br \/>\n            resource_id: \u65b0\u8d44\u6e90\u7684\u6807\u8bc6\u7b26<br \/>\n            resource_path: \u8d44\u6e90\u8def\u5f84\u6a21\u677f&#xff0c;\u5982&#039;\/api\/users\/{id}&#039;<br \/>\n            absolute: \u662f\u5426\u751f\u6210\u7edd\u5bf9URL<\/p>\n<p>        Returns:<br \/>\n            Location\u5934\u90e8\u503c<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u66ff\u6362\u8def\u5f84\u4e2d\u7684\u5360\u4f4d\u7b26<br \/>\n        path &#061; resource_path.replace(&#039;{id}&#039;, str(resource_id))<\/p>\n<p>        if absolute:<br \/>\n            # \u751f\u6210\u7edd\u5bf9URL<br \/>\n            if self.base_url.endswith(&#039;\/&#039;) and path.startswith(&#039;\/&#039;):<br \/>\n                path &#061; path[1:]<\/p>\n<p>            location &#061; f&#034;{self.base_url}{path}&#034;<\/p>\n<p>            # \u9a8c\u8bc1URL\u6709\u6548\u6027<br \/>\n            if not self._is_valid_url(location):<br \/>\n                raise ValueError(f&#034;Invalid location URL: {location}&#034;)<\/p>\n<p>            return location<br \/>\n        else:<br \/>\n            # \u76f8\u5bf9\u8def\u5f84<br \/>\n            if not path.startswith(&#039;\/&#039;):<br \/>\n                path &#061; f&#034;\/{path}&#034;<br \/>\n            return path<\/p>\n<p>    def _detect_base_url(self):<br \/>\n        &#034;&#034;&#034;\u68c0\u6d4b\u57fa\u7840URL&#034;&#034;&#034;<br \/>\n        # \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u8fd9\u901a\u5e38\u6765\u81ea\u914d\u7f6e\u6216\u8bf7\u6c42\u4e0a\u4e0b\u6587<br \/>\n        import os<br \/>\n        return os.environ.get(&#039;API_BASE_URL&#039;, &#039;http:\/\/localhost:5000&#039;)<\/p>\n<p>    def _is_valid_url(self, url):<br \/>\n        &#034;&#034;&#034;\u7b80\u5355\u9a8c\u8bc1URL\u683c\u5f0f&#034;&#034;&#034;<br \/>\n        from urllib.parse import urlparse<br \/>\n        try:<br \/>\n            result &#061; urlparse(url)<br \/>\n            return all([result.scheme, result.netloc])<br \/>\n        except:<br \/>\n            return False<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\nbuilder &#061; LocationHeaderBuilder(&#039;https:\/\/api.example.com\/v1&#039;)<\/p>\n<p># \u521b\u5efa\u7528\u6237\u540e<br \/>\nuser_id &#061; 123<br \/>\nlocation &#061; builder.build(<br \/>\n    resource_id&#061;user_id,<br \/>\n    resource_path&#061;&#039;\/users\/{id}&#039;,<br \/>\n    absolute&#061;True<br \/>\n)<\/p>\n<p>print(f&#034;Location: {location}&#034;)<br \/>\n# \u8f93\u51fa: Location: https:\/\/api.example.com\/v1\/users\/123<\/p>\n<h5>\u7edd\u5bf9URL\u4e0e\u76f8\u5bf9URL\u7684\u9009\u62e9<\/h5>\n<p>python<\/p>\n<p># Django\u793a\u4f8b&#xff1a;\u667a\u80fdLocation\u5934\u90e8\u751f\u6210<br \/>\nfrom django.urls import reverse<br \/>\nfrom django.http import JsonResponse<br \/>\nimport re<\/p>\n<p>class CreatedResponse(JsonResponse):<br \/>\n    &#034;&#034;&#034;201 Created\u54cd\u5e94\u5c01\u88c5\u7c7b&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, data&#061;None, resource_name&#061;None, resource_id&#061;None,<br \/>\n                 absolute_url&#061;True, **kwargs):<\/p>\n<p>        # \u786e\u4fdd\u5305\u542b\u5fc5\u8981\u6570\u636e<br \/>\n        if data is None:<br \/>\n            data &#061; {}<\/p>\n<p>        if resource_id and &#039;id&#039; not in data:<br \/>\n            data[&#039;id&#039;] &#061; resource_id<\/p>\n<p>        super().__init__(data, status&#061;201, **kwargs)<\/p>\n<p>        # \u8bbe\u7f6eLocation\u5934\u90e8<br \/>\n        if resource_name and resource_id:<br \/>\n            location &#061; self._generate_location(<br \/>\n                resource_name, resource_id, absolute_url<br \/>\n            )<br \/>\n            self[&#039;Location&#039;] &#061; location<\/p>\n<p>    def _generate_location(self, resource_name, resource_id, absolute_url):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u751f\u6210Location\u5934\u90e8<\/p>\n<p>        \u7b56\u7565&#xff1a;<br \/>\n        1. \u5c1d\u8bd5\u4f7f\u7528\u547d\u540dURL\u6a21\u5f0f<br \/>\n        2. \u56de\u9000\u5230\u6807\u51c6\u6a21\u5f0f<br \/>\n        3. \u6839\u636e\u8bf7\u6c42\u51b3\u5b9a\u7edd\u5bf9\/\u76f8\u5bf9URL<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u6784\u5efaURL\u6a21\u5f0f\u540d\u79f0<br \/>\n        detail_view_name &#061; f&#039;{resource_name}-detail&#039;<\/p>\n<p>        try:<br \/>\n            # \u5c1d\u8bd5\u4f7f\u7528\u547d\u540dURL<br \/>\n            path &#061; reverse(detail_view_name, args&#061;[resource_id])<br \/>\n        except:<br \/>\n            # \u56de\u9000\u5230\u6807\u51c6\u6a21\u5f0f<br \/>\n            path &#061; f&#039;\/api\/{resource_name}\/{resource_id}\/&#039;<\/p>\n<p>        if absolute_url:<br \/>\n            # \u83b7\u53d6\u8bf7\u6c42\u5bf9\u8c61&#xff08;\u9700\u8981\u5728\u89c6\u56fe\u4e0a\u4e0b\u6587\u4e2d&#xff09;<br \/>\n            from django.http import HttpRequest<br \/>\n            request &#061; HttpRequest()  # \u5b9e\u9645\u4e2d\u4ece\u89c6\u56fe\u83b7\u53d6<\/p>\n<p>            # \u6784\u5efa\u7edd\u5bf9URL<br \/>\n            location &#061; request.build_absolute_uri(path)<br \/>\n        else:<br \/>\n            location &#061; path<\/p>\n<p>        return location<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\nfrom django.views import View<br \/>\nfrom django.utils.decorators import method_decorator<br \/>\nfrom django.views.decorators.csrf import csrf_exempt<\/p>\n<p>&#064;method_decorator(csrf_exempt, name&#061;&#039;dispatch&#039;)<br \/>\nclass UserCreateView(View):<br \/>\n    &#034;&#034;&#034;\u7528\u6237\u521b\u5efa\u89c6\u56fe&#034;&#034;&#034;<\/p>\n<p>    def post(self, request):<br \/>\n        # \u89e3\u6790\u8bf7\u6c42\u6570\u636e<br \/>\n        import json<br \/>\n        data &#061; json.loads(request.body)<\/p>\n<p>        # \u521b\u5efa\u7528\u6237&#xff08;\u6a21\u62df&#xff09;<br \/>\n        user_id &#061; self._create_user(data)<\/p>\n<p>        # \u6784\u5efa\u54cd\u5e94\u6570\u636e<br \/>\n        response_data &#061; {<br \/>\n            &#039;id&#039;: user_id,<br \/>\n            &#039;username&#039;: data.get(&#039;username&#039;),<br \/>\n            &#039;email&#039;: data.get(&#039;email&#039;),<br \/>\n            &#039;created_at&#039;: &#039;2023-01-23T10:30:45Z&#039;,<br \/>\n            &#039;message&#039;: &#039;User created successfully&#039;<br \/>\n        }<\/p>\n<p>        # \u8fd4\u56de201 Created\u54cd\u5e94<br \/>\n        response &#061; CreatedResponse(<br \/>\n            data&#061;response_data,<br \/>\n            resource_name&#061;&#039;users&#039;,<br \/>\n            resource_id&#061;user_id,<br \/>\n            absolute_url&#061;True<br \/>\n        )<\/p>\n<p>        # \u6dfb\u52a0\u5176\u4ed6\u5934\u90e8<br \/>\n        response[&#039;X-RateLimit-Remaining&#039;] &#061; &#039;999&#039;<\/p>\n<p>        return response<\/p>\n<p>    def _create_user(self, data):<br \/>\n        &#034;&#034;&#034;\u6a21\u62df\u7528\u6237\u521b\u5efa&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u8fd9\u91cc\u4f1a\u6709\u6570\u636e\u5e93\u64cd\u4f5c<br \/>\n        import random<br \/>\n        return random.randint(1000, 9999)<\/p>\n<h4>5.3 \u54cd\u5e94\u4f53\u5185\u5bb9\u8bbe\u8ba1<\/h4>\n<h5>\u54cd\u5e94\u4f53\u7ed3\u6784\u6700\u4f73\u5b9e\u8df5<\/h5>\n<p>javascript<\/p>\n<p>\/\/ 201 Created\u54cd\u5e94\u4f53\u8bbe\u8ba1<br \/>\nclass CreatedResponseBuilder {<br \/>\n    constructor(options &#061; {}) {<br \/>\n        this.options &#061; {<br \/>\n            includeFullResource: true,<br \/>\n            includeLinks: true,<br \/>\n            includeMetadata: true,<br \/>\n            timestampFormat: &#039;iso&#039;,<br \/>\n            &#8230;options<br \/>\n        };<br \/>\n    }<\/p>\n<p>    build(resource, resourceType, request) {<br \/>\n        const response &#061; {<br \/>\n            \/\/ \u8d44\u6e90\u6807\u8bc6\u7b26&#xff08;\u5fc5\u987b&#xff09;<br \/>\n            id: resource.id,<\/p>\n<p>            \/\/ \u8d44\u6e90\u7c7b\u578b<br \/>\n            type: resourceType,<\/p>\n<p>            \/\/ \u521b\u5efa\u72b6\u6001<br \/>\n            status: &#039;created&#039;,<\/p>\n<p>            \/\/ \u65f6\u95f4\u6233<br \/>\n            created_at: this._formatTimestamp(resource.created_at),<\/p>\n<p>            \/\/ \u6d88\u606f&#xff08;\u53ef\u9009\u4f46\u63a8\u8350&#xff09;<br \/>\n            message: &#096;${resourceType} created successfully&#096;<br \/>\n        };<\/p>\n<p>        \/\/ \u5305\u542b\u5b8c\u6574\u6216\u90e8\u5206\u8d44\u6e90\u8868\u793a<br \/>\n        if (this.options.includeFullResource) {<br \/>\n            Object.assign(response, resource.toJSON ? resource.toJSON() : resource);<br \/>\n        } else {<br \/>\n            \/\/ \u53ea\u5305\u542b\u5173\u952e\u5b57\u6bb5<br \/>\n            response.identifier &#061; resource.id;<br \/>\n            response.location &#061; this._buildLocation(resource, resourceType, request);<br \/>\n        }<\/p>\n<p>        \/\/ \u5305\u542b\u94fe\u63a5&#xff08;HATEOAS&#xff09;<br \/>\n        if (this.options.includeLinks) {<br \/>\n            response.links &#061; this._buildLinks(resource, resourceType, request);<br \/>\n        }<\/p>\n<p>        \/\/ \u5305\u542b\u5143\u6570\u636e<br \/>\n        if (this.options.includeMetadata) {<br \/>\n            response.metadata &#061; {<br \/>\n                api_version: &#039;v1&#039;,<br \/>\n                request_id: request.headers[&#039;x-request-id&#039;],<br \/>\n                timestamp: new Date().toISOString()<br \/>\n            };<br \/>\n        }<\/p>\n<p>        return response;<br \/>\n    }<\/p>\n<p>    _formatTimestamp(timestamp) {<br \/>\n        if (!timestamp) return new Date().toISOString();<\/p>\n<p>        switch (this.options.timestampFormat) {<br \/>\n            case &#039;iso&#039;:<br \/>\n                return timestamp.toISOString ? timestamp.toISOString() : timestamp;<br \/>\n            case &#039;unix&#039;:<br \/>\n                return Math.floor(new Date(timestamp).getTime() \/ 1000);<br \/>\n            default:<br \/>\n                return timestamp;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    _buildLocation(resource, resourceType, request) {<br \/>\n        \/\/ \u57fa\u4e8e\u8bf7\u6c42\u548c\u8d44\u6e90\u7c7b\u578b\u6784\u5efa\u4f4d\u7f6e<br \/>\n        const baseUrl &#061; this._getBaseUrl(request);<br \/>\n        return &#096;${baseUrl}\/${resourceType}\/${resource.id}&#096;;<br \/>\n    }<\/p>\n<p>    _buildLinks(resource, resourceType, request) {<br \/>\n        const baseUrl &#061; this._getBaseUrl(request);<br \/>\n        const resourceUrl &#061; &#096;${baseUrl}\/${resourceType}\/${resource.id}&#096;;<\/p>\n<p>        return {<br \/>\n            self: {<br \/>\n                href: resourceUrl,<br \/>\n                method: &#039;GET&#039;<br \/>\n            },<br \/>\n            update: {<br \/>\n                href: resourceUrl,<br \/>\n                method: &#039;PUT&#039;<br \/>\n            },<br \/>\n            delete: {<br \/>\n                href: resourceUrl,<br \/>\n                method: &#039;DELETE&#039;<br \/>\n            },<br \/>\n            collection: {<br \/>\n                href: &#096;${baseUrl}\/${resourceType}&#096;,<br \/>\n                method: &#039;GET&#039;<br \/>\n            }<br \/>\n        };<br \/>\n    }<\/p>\n<p>    _getBaseUrl(request) {<br \/>\n        \/\/ \u4ece\u8bf7\u6c42\u4e2d\u83b7\u53d6\u57fa\u7840URL<br \/>\n        if (request &amp;&amp; request.get) {<br \/>\n            return &#096;${request.protocol}:\/\/${request.get(&#039;host&#039;)}\/api&#096;;<br \/>\n        }<br \/>\n        return &#039;\/api&#039;;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nconst builder &#061; new CreatedResponseBuilder({<br \/>\n    includeFullResource: true,<br \/>\n    includeLinks: true<br \/>\n});<\/p>\n<p>const newUser &#061; {<br \/>\n    id: 456,<br \/>\n    username: &#039;john_doe&#039;,<br \/>\n    email: &#039;john&#064;example.com&#039;,<br \/>\n    created_at: new Date()<br \/>\n};<\/p>\n<p>const request &#061; {<br \/>\n    protocol: &#039;https&#039;,<br \/>\n    get: (header) &#061;&gt; {<br \/>\n        if (header &#061;&#061;&#061; &#039;host&#039;) return &#039;api.example.com&#039;;<br \/>\n        return null;<br \/>\n    }<br \/>\n};<\/p>\n<p>const responseBody &#061; builder.build(newUser, &#039;users&#039;, request);<br \/>\nconsole.log(JSON.stringify(responseBody, null, 2));<\/p>\n<h5>\u5f02\u6b65\u521b\u5efa\u573a\u666f\u5904\u7406<\/h5>\n<p>python<\/p>\n<p># \u5f02\u6b65\u8d44\u6e90\u521b\u5efa\u5904\u7406<br \/>\nimport asyncio<br \/>\nfrom datetime import datetime<br \/>\nfrom fastapi import FastAPI, BackgroundTasks, HTTPException<br \/>\nfrom fastapi.responses import JSONResponse<br \/>\nfrom pydantic import BaseModel<br \/>\nimport uuid<\/p>\n<p>app &#061; FastAPI()<\/p>\n<p>class AsyncResourceCreator:<br \/>\n    &#034;&#034;&#034;\u5904\u7406\u5f02\u6b65\u8d44\u6e90\u521b\u5efa&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.pending_resources &#061; {}<br \/>\n        self.completed_resources &#061; {}<\/p>\n<p>    async def create_resource_async(self, resource_data, background_tasks):<br \/>\n        &#034;&#034;&#034;\u5f02\u6b65\u521b\u5efa\u8d44\u6e90&#034;&#034;&#034;<\/p>\n<p>        # \u751f\u6210\u4e34\u65f6\u8d44\u6e90ID<br \/>\n        temp_id &#061; str(uuid.uuid4())<\/p>\n<p>        # \u5b58\u50a8\u521b\u5efa\u72b6\u6001<br \/>\n        self.pending_resources[temp_id] &#061; {<br \/>\n            &#039;status&#039;: &#039;processing&#039;,<br \/>\n            &#039;data&#039;: resource_data,<br \/>\n            &#039;created_at&#039;: datetime.utcnow(),<br \/>\n            &#039;estimated_completion&#039;: 30  # \u9884\u8ba130\u79d2\u5b8c\u6210<br \/>\n        }<\/p>\n<p>        # \u5728\u540e\u53f0\u542f\u52a8\u521b\u5efa\u4efb\u52a1<br \/>\n        background_tasks.add_task(<br \/>\n            self._process_resource_creation,<br \/>\n            temp_id,<br \/>\n            resource_data<br \/>\n        )<\/p>\n<p>        # \u8fd4\u56de202 Accepted&#xff0c;\u5305\u542b\u72b6\u6001\u68c0\u67e5\u7aef\u70b9<br \/>\n        return {<br \/>\n            &#039;status&#039;: &#039;accepted&#039;,<br \/>\n            &#039;message&#039;: &#039;Resource creation in progress&#039;,<br \/>\n            &#039;resource_id&#039;: temp_id,<br \/>\n            &#039;status_url&#039;: f&#039;\/api\/resources\/{temp_id}\/status&#039;,<br \/>\n            &#039;estimated_completion_seconds&#039;: 30,<br \/>\n            &#039;created_at&#039;: datetime.utcnow().isoformat()<br \/>\n        }<\/p>\n<p>    async def _process_resource_creation(self, temp_id, resource_data):<br \/>\n        &#034;&#034;&#034;\u540e\u53f0\u5904\u7406\u8d44\u6e90\u521b\u5efa&#034;&#034;&#034;<\/p>\n<p>        try:<br \/>\n            # \u6a21\u62df\u8017\u65f6\u64cd\u4f5c<br \/>\n            await asyncio.sleep(5)<\/p>\n<p>            # \u521b\u5efa\u5b9e\u9645\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;<br \/>\n            actual_id &#061; self._generate_actual_id()<\/p>\n<p>            # \u5b58\u50a8\u5b8c\u6210\u72b6\u6001<br \/>\n            self.completed_resources[actual_id] &#061; {<br \/>\n                &#039;id&#039;: actual_id,<br \/>\n                **resource_data,<br \/>\n                &#039;created_at&#039;: datetime.utcnow(),<br \/>\n                &#039;status&#039;: &#039;active&#039;<br \/>\n            }<\/p>\n<p>            # \u66f4\u65b0\u5f85\u5904\u7406\u8bb0\u5f55<br \/>\n            self.pending_resources[temp_id].update({<br \/>\n                &#039;status&#039;: &#039;completed&#039;,<br \/>\n                &#039;actual_id&#039;: actual_id,<br \/>\n                &#039;completed_at&#039;: datetime.utcnow()<br \/>\n            })<\/p>\n<p>            # \u8fd9\u91cc\u53ef\u4ee5\u89e6\u53d1\u901a\u77e5\u6216\u5176\u4ed6\u540e\u7eed\u64cd\u4f5c<\/p>\n<p>        except Exception as e:<br \/>\n            # \u8bb0\u5f55\u9519\u8bef<br \/>\n            self.pending_resources[temp_id].update({<br \/>\n                &#039;status&#039;: &#039;failed&#039;,<br \/>\n                &#039;error&#039;: str(e),<br \/>\n                &#039;failed_at&#039;: datetime.utcnow()<br \/>\n            })<\/p>\n<p>    def _generate_actual_id(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210\u5b9e\u9645\u8d44\u6e90ID&#034;&#034;&#034;<br \/>\n        return f&#034;res_{uuid.uuid4().hex[:8]}&#034;<\/p>\n<p>    def get_status(self, resource_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u521b\u5efa\u72b6\u6001&#034;&#034;&#034;<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u5df2\u5b8c\u6210<br \/>\n        if resource_id in self.completed_resources:<br \/>\n            resource &#061; self.completed_resources[resource_id]<br \/>\n            return {<br \/>\n                &#039;status&#039;: &#039;completed&#039;,<br \/>\n                &#039;resource&#039;: resource,<br \/>\n                &#039;links&#039;: {<br \/>\n                    &#039;self&#039;: f&#039;\/api\/resources\/{resource_id}&#039;<br \/>\n                }<br \/>\n            }<\/p>\n<p>        # \u68c0\u67e5\u662f\u5426\u5728\u5904\u7406\u4e2d<br \/>\n        if resource_id in self.pending_resources:<br \/>\n            pending &#061; self.pending_resources[resource_id]<\/p>\n<p>            if pending[&#039;status&#039;] &#061;&#061; &#039;completed&#039;:<br \/>\n                # \u5982\u679c\u5df2\u5b8c\u6210\u4f46\u8fd8\u6ca1\u6709\u79fb\u52a8\u5230\u5b8c\u6210\u5217\u8868<br \/>\n                actual_id &#061; pending.get(&#039;actual_id&#039;)<br \/>\n                if actual_id and actual_id in self.completed_resources:<br \/>\n                    resource &#061; self.completed_resources[actual_id]<br \/>\n                    return {<br \/>\n                        &#039;status&#039;: &#039;completed&#039;,<br \/>\n                        &#039;resource&#039;: resource,<br \/>\n                        &#039;links&#039;: {<br \/>\n                            &#039;self&#039;: f&#039;\/api\/resources\/{actual_id}&#039;<br \/>\n                        }<br \/>\n                    }<\/p>\n<p>            # \u4ecd\u5728\u5904\u7406\u4e2d<br \/>\n            elapsed &#061; (datetime.utcnow() &#8211; pending[&#039;created_at&#039;]).total_seconds()<br \/>\n            progress &#061; min(100, (elapsed \/ pending[&#039;estimated_completion&#039;]) * 100)<\/p>\n<p>            return {<br \/>\n                &#039;status&#039;: pending[&#039;status&#039;],<br \/>\n                &#039;progress&#039;: f&#034;{progress:.1f}%&#034;,<br \/>\n                &#039;estimated_seconds_remaining&#039;: max(0, pending[&#039;estimated_completion&#039;] &#8211; elapsed),<br \/>\n                &#039;created_at&#039;: pending[&#039;created_at&#039;].isoformat(),<br \/>\n                &#039;status_url&#039;: f&#039;\/api\/resources\/{resource_id}\/status&#039;<br \/>\n            }<\/p>\n<p>        # \u672a\u627e\u5230<br \/>\n        return None<\/p>\n<p># \u521b\u5efa\u5b9e\u4f8b<br \/>\ncreator &#061; AsyncResourceCreator()<\/p>\n<p># API\u7aef\u70b9<br \/>\n&#064;app.post(&#034;\/api\/resources&#034;)<br \/>\nasync def create_resource(<br \/>\n    resource_data: dict,<br \/>\n    background_tasks: BackgroundTasks<br \/>\n):<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u521b\u5efa\u65b0\u8d44\u6e90<\/p>\n<p>    \u7b56\u7565&#xff1a;<br \/>\n    1. \u5982\u679c\u8d44\u6e90\u53ef\u4ee5\u7acb\u5373\u521b\u5efa -&gt; \u8fd4\u56de201 Created<br \/>\n    2. \u5982\u679c\u8d44\u6e90\u9700\u8981\u5f02\u6b65\u521b\u5efa -&gt; \u8fd4\u56de202 Accepted<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    # \u68c0\u67e5\u662f\u5426\u53ef\u4ee5\u7acb\u5373\u521b\u5efa<br \/>\n    if self._can_create_immediately(resource_data):<br \/>\n        # \u7acb\u5373\u521b\u5efa\u8d44\u6e90<br \/>\n        resource_id &#061; await self._create_resource_now(resource_data)<\/p>\n<p>        # \u8fd4\u56de201 Created<br \/>\n        response_data &#061; {<br \/>\n            &#039;id&#039;: resource_id,<br \/>\n            **resource_data,<br \/>\n            &#039;created_at&#039;: datetime.utcnow().isoformat(),<br \/>\n            &#039;links&#039;: {<br \/>\n                &#039;self&#039;: f&#039;\/api\/resources\/{resource_id}&#039;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        response &#061; JSONResponse(<br \/>\n            content&#061;response_data,<br \/>\n            status_code&#061;201<br \/>\n        )<br \/>\n        response.headers[&#039;Location&#039;] &#061; f&#039;\/api\/resources\/{resource_id}&#039;<\/p>\n<p>        return response<\/p>\n<p>    else:<br \/>\n        # \u9700\u8981\u5f02\u6b65\u521b\u5efa<br \/>\n        result &#061; await creator.create_resource_async(<br \/>\n            resource_data,<br \/>\n            background_tasks<br \/>\n        )<\/p>\n<p>        # \u8fd4\u56de202 Accepted<br \/>\n        return JSONResponse(<br \/>\n            content&#061;result,<br \/>\n            status_code&#061;202<br \/>\n        )<\/p>\n<p>&#064;app.get(&#034;\/api\/resources\/{resource_id}\/status&#034;)<br \/>\nasync def get_resource_status(resource_id: str):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90\u521b\u5efa\u72b6\u6001&#034;&#034;&#034;<\/p>\n<p>    status &#061; creator.get_status(resource_id)<\/p>\n<p>    if not status:<br \/>\n        raise HTTPException(<br \/>\n            status_code&#061;404,<br \/>\n            detail&#061;f&#034;Resource {resource_id} not found&#034;<br \/>\n        )<\/p>\n<p>    return status<\/p>\n<p>def _can_create_immediately(resource_data):<br \/>\n    &#034;&#034;&#034;\u5224\u65ad\u662f\u5426\u53ef\u4ee5\u7acb\u5373\u521b\u5efa\u8d44\u6e90&#034;&#034;&#034;<br \/>\n    # \u8fd9\u91cc\u53ef\u4ee5\u6839\u636e\u4e1a\u52a1\u903b\u8f91\u5224\u65ad<br \/>\n    # \u4f8b\u5982&#xff1a;\u8d44\u6e90\u5927\u5c0f\u3001\u590d\u6742\u5ea6\u7b49<br \/>\n    return len(resource_data.get(&#039;content&#039;, &#039;&#039;)) &lt; 1000<\/p>\n<p>async def _create_resource_now(resource_data):<br \/>\n    &#034;&#034;&#034;\u7acb\u5373\u521b\u5efa\u8d44\u6e90&#034;&#034;&#034;<br \/>\n    # \u6a21\u62df\u521b\u5efa\u64cd\u4f5c<br \/>\n    return f&#034;res_{uuid.uuid4().hex[:8]}&#034;<\/p>\n<h4>5.4 \u4e0e202 Accepted\u7684\u533a\u522b<\/h4>\n<h5 style=\"background-color:transparent\">\u51b3\u7b56\u6d41\u7a0b\u56fe\u00a0<img loading=\"lazy\" decoding=\"async\" alt=\"\" height=\"1736\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/11\/20261104055657-6aeac9298080e.png\" width=\"1704\" \/><\/h5>\n<h5>\u4ee3\u7801\u793a\u4f8b\u5bf9\u6bd4<\/h5>\n<p>python<\/p>\n<p># \u5bf9\u6bd4201\u548c202\u7684\u4f7f\u7528<br \/>\nfrom enum import Enum<br \/>\nfrom datetime import datetime<\/p>\n<p>class CreationStrategy(Enum):<br \/>\n    &#034;&#034;&#034;\u8d44\u6e90\u521b\u5efa\u7b56\u7565&#034;&#034;&#034;<br \/>\n    IMMEDIATE &#061; &#034;immediate&#034;      # \u8fd4\u56de201<br \/>\n    ASYNC &#061; &#034;async&#034;             # \u8fd4\u56de202<br \/>\n    BATCH &#061; &#034;batch&#034;             # \u53ef\u80fd\u8fd4\u56de207<\/p>\n<p>class ResourceCreator:<br \/>\n    &#034;&#034;&#034;\u8d44\u6e90\u521b\u5efa\u5668&#xff0c;\u652f\u6301\u4e0d\u540c\u7b56\u7565&#034;&#034;&#034;<\/p>\n<p>    def create_resource(self, data, strategy&#061;CreationStrategy.IMMEDIATE):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u521b\u5efa\u8d44\u6e90<\/p>\n<p>        Args:<br \/>\n            data: \u8d44\u6e90\u6570\u636e<br \/>\n            strategy: \u521b\u5efa\u7b56\u7565<\/p>\n<p>        Returns:<br \/>\n            (status_code, response_data, headers)<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        if strategy &#061;&#061; CreationStrategy.IMMEDIATE:<br \/>\n            return self._create_immediate(data)<br \/>\n        elif strategy &#061;&#061; CreationStrategy.ASYNC:<br \/>\n            return self._create_async(data)<br \/>\n        else:<br \/>\n            raise ValueError(f&#034;Unsupported strategy: {strategy}&#034;)<\/p>\n<p>    def _create_immediate(self, data):<br \/>\n        &#034;&#034;&#034;\u7acb\u5373\u521b\u5efa&#xff08;\u8fd4\u56de201&#xff09;&#034;&#034;&#034;<\/p>\n<p>        # \u9a8c\u8bc1\u6570\u636e<br \/>\n        validation_result &#061; self.validate_data(data)<br \/>\n        if not validation_result[&#039;valid&#039;]:<br \/>\n            return (400, {<br \/>\n                &#039;error&#039;: &#039;Validation failed&#039;,<br \/>\n                &#039;details&#039;: validation_result[&#039;errors&#039;]<br \/>\n            }, {})<\/p>\n<p>        try:<br \/>\n            # \u521b\u5efa\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;<br \/>\n            resource_id &#061; self._generate_id()<br \/>\n            created_at &#061; datetime.utcnow()<\/p>\n<p>            # \u5b58\u50a8\u8d44\u6e90<br \/>\n            resource &#061; {<br \/>\n                &#039;id&#039;: resource_id,<br \/>\n                **data,<br \/>\n                &#039;created_at&#039;: created_at,<br \/>\n                &#039;updated_at&#039;: created_at,<br \/>\n                &#039;status&#039;: &#039;active&#039;<br \/>\n            }<\/p>\n<p>            self._store_resource(resource_id, resource)<\/p>\n<p>            # \u6784\u5efa201\u54cd\u5e94<br \/>\n            response_data &#061; {<br \/>\n                &#039;id&#039;: resource_id,<br \/>\n                **{k: v for k, v in data.items() if k !&#061; &#039;password&#039;},<br \/>\n                &#039;created_at&#039;: created_at.isoformat(),<br \/>\n                &#039;message&#039;: &#039;Resource created successfully&#039;<br \/>\n            }<\/p>\n<p>            headers &#061; {<br \/>\n                &#039;Location&#039;: f&#039;\/api\/resources\/{resource_id}&#039;,<br \/>\n                &#039;X-Resource-ID&#039;: resource_id,<br \/>\n                &#039;X-Created-At&#039;: created_at.isoformat()<br \/>\n            }<\/p>\n<p>            return (201, response_data, headers)<\/p>\n<p>        except Exception as e:<br \/>\n            # \u521b\u5efa\u5931\u8d25<br \/>\n            return (500, {<br \/>\n                &#039;error&#039;: &#039;Creation failed&#039;,<br \/>\n                &#039;message&#039;: str(e)<br \/>\n            }, {})<\/p>\n<p>    def _create_async(self, data):<br \/>\n        &#034;&#034;&#034;\u5f02\u6b65\u521b\u5efa&#xff08;\u8fd4\u56de202&#xff09;&#034;&#034;&#034;<\/p>\n<p>        # \u9a8c\u8bc1\u57fa\u672c\u6570\u636e<br \/>\n        if not data.get(&#039;type&#039;):<br \/>\n            return (400, {<br \/>\n                &#039;error&#039;: &#039;Missing resource type&#039;<br \/>\n            }, {})<\/p>\n<p>        # \u751f\u6210\u4efb\u52a1ID<br \/>\n        task_id &#061; self._generate_task_id()<\/p>\n<p>        # \u5b58\u50a8\u4efb\u52a1<br \/>\n        task &#061; {<br \/>\n            &#039;id&#039;: task_id,<br \/>\n            &#039;data&#039;: data,<br \/>\n            &#039;status&#039;: &#039;pending&#039;,<br \/>\n            &#039;created_at&#039;: datetime.utcnow(),<br \/>\n            &#039;estimated_completion&#039;: datetime.utcnow().timestamp() &#043; 300  # 5\u5206\u949f\u540e<br \/>\n        }<\/p>\n<p>        self._store_task(task_id, task)<\/p>\n<p>        # \u542f\u52a8\u540e\u53f0\u4efb\u52a1<br \/>\n        self._start_background_task(task_id, data)<\/p>\n<p>        # \u6784\u5efa202\u54cd\u5e94<br \/>\n        response_data &#061; {<br \/>\n            &#039;task_id&#039;: task_id,<br \/>\n            &#039;status&#039;: &#039;accepted&#039;,<br \/>\n            &#039;message&#039;: &#039;Resource creation accepted and will be processed&#039;,<br \/>\n            &#039;created_at&#039;: task[&#039;created_at&#039;].isoformat(),<br \/>\n            &#039;status_url&#039;: f&#039;\/api\/tasks\/{task_id}&#039;,<br \/>\n            &#039;estimated_completion&#039;: task[&#039;estimated_completion&#039;]<br \/>\n        }<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;X-Task-ID&#039;: task_id,<br \/>\n            &#039;X-Task-Status-URL&#039;: f&#039;\/api\/tasks\/{task_id}&#039;,<br \/>\n            &#039;Retry-After&#039;: &#039;300&#039;  # 5\u5206\u949f\u540e\u68c0\u67e5<br \/>\n        }<\/p>\n<p>        return (202, response_data, headers)<\/p>\n<p>    def _generate_id(self):<br \/>\n        import uuid<br \/>\n        return f&#034;res_{uuid.uuid4().hex[:8]}&#034;<\/p>\n<p>    def _generate_task_id(self):<br \/>\n        import uuid<br \/>\n        return f&#034;task_{uuid.uuid4().hex[:8]}&#034;<\/p>\n<p>    def validate_data(self, data):<br \/>\n        &#034;&#034;&#034;\u9a8c\u8bc1\u8d44\u6e90\u6570\u636e&#034;&#034;&#034;<br \/>\n        errors &#061; []<\/p>\n<p>        if not data.get(&#039;name&#039;):<br \/>\n            errors.append(&#039;name is required&#039;)<\/p>\n<p>        if data.get(&#039;email&#039;) and not self._is_valid_email(data[&#039;email&#039;]):<br \/>\n            errors.append(&#039;invalid email format&#039;)<\/p>\n<p>        return {<br \/>\n            &#039;valid&#039;: len(errors) &#061;&#061; 0,<br \/>\n            &#039;errors&#039;: errors<br \/>\n        }<\/p>\n<p>    def _is_valid_email(self, email):<br \/>\n        import re<br \/>\n        pattern &#061; r&#039;^[a-zA-Z0-9._%&#043;-]&#043;&#064;[a-zA-Z0-9.-]&#043;\\\\.[a-zA-Z]{2,}$&#039;<br \/>\n        return bool(re.match(pattern, email))<\/p>\n<p>    def _store_resource(self, resource_id, resource):<br \/>\n        &#034;&#034;&#034;\u5b58\u50a8\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u5b58\u50a8\u5230\u6570\u636e\u5e93<br \/>\n        pass<\/p>\n<p>    def _store_task(self, task_id, task):<br \/>\n        &#034;&#034;&#034;\u5b58\u50a8\u4efb\u52a1&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u5b58\u50a8\u5230\u6570\u636e\u5e93<br \/>\n        pass<\/p>\n<p>    def _start_background_task(self, task_id, data):<br \/>\n        &#034;&#034;&#034;\u542f\u52a8\u540e\u53f0\u4efb\u52a1&#xff08;\u6a21\u62df&#xff09;&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u4f1a\u4f7f\u7528celery\u3001rq\u7b49<br \/>\n        pass<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\ncreator &#061; ResourceCreator()<\/p>\n<p># \u573a\u666f1&#xff1a;\u7acb\u5373\u521b\u5efa<br \/>\nstatus, data, headers &#061; creator.create_resource(<br \/>\n    {&#039;name&#039;: &#039;Test Resource&#039;, &#039;email&#039;: &#039;test&#064;example.com&#039;},<br \/>\n    CreationStrategy.IMMEDIATE<br \/>\n)<\/p>\n<p>print(f&#034;Status: {status}&#034;)<br \/>\nprint(f&#034;Location Header: {headers.get(&#039;Location&#039;)}&#034;)<br \/>\nprint(f&#034;Response: {data}&#034;)<\/p>\n<p># \u573a\u666f2&#xff1a;\u5f02\u6b65\u521b\u5efa<br \/>\nstatus, data, headers &#061; creator.create_resource(<br \/>\n    {&#039;type&#039;: &#039;report&#039;, &#039;parameters&#039;: {&#039;format&#039;: &#039;pdf&#039;}},<br \/>\n    CreationStrategy.ASYNC<br \/>\n)<\/p>\n<p>print(f&#034;\\\\nStatus: {status}&#034;)<br \/>\nprint(f&#034;Task Status URL: {data.get(&#039;status_url&#039;)}&#034;)<br \/>\nprint(f&#034;Response: {data}&#034;)<\/p>\n<h4>5.5 \u5ba2\u6237\u7aef\u5904\u7406201\u54cd\u5e94<\/h4>\n<h5>\u6d4f\u89c8\u5668JavaScript\u5904\u7406<\/h5>\n<p>javascript<\/p>\n<p>class CreatedResponseHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406201 Created\u54cd\u5e94<br \/>\n     *\/<\/p>\n<p>    async handleCreatedResponse(response, requestData) {<br \/>\n        console.log(&#039;Resource created successfully&#039;);<\/p>\n<p>        \/\/ \u68c0\u67e5Location\u5934\u90e8<br \/>\n        const location &#061; response.headers.get(&#039;Location&#039;);<br \/>\n        if (!location) {<br \/>\n            console.warn(&#039;201\u54cd\u5e94\u7f3a\u5c11Location\u5934\u90e8&#039;);<br \/>\n            \/\/ \u4f46\u4ecd\u7136\u5c1d\u8bd5\u5904\u7406\u54cd\u5e94\u4f53<br \/>\n        }<\/p>\n<p>        \/\/ \u89e3\u6790\u54cd\u5e94\u4f53<br \/>\n        let responseData;<br \/>\n        try {<br \/>\n            responseData &#061; await response.json();<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to parse response:&#039;, error);<br \/>\n            throw new Error(&#039;Invalid response format&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u9a8c\u8bc1\u54cd\u5e94\u6570\u636e<br \/>\n        this.validateResponseData(responseData);<\/p>\n<p>        \/\/ \u5904\u7406\u54cd\u5e94<br \/>\n        return await this.processResponse(responseData, location, requestData);<br \/>\n    }<\/p>\n<p>    validateResponseData(data) {<br \/>\n        \/\/ \u68c0\u67e5\u5fc5\u8981\u5b57\u6bb5<br \/>\n        if (!data.id &amp;&amp; !data.resource_id) {<br \/>\n            console.warn(&#039;Response missing resource identifier&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u72b6\u6001<br \/>\n        if (data.status &amp;&amp; data.status !&#061;&#061; &#039;created&#039;) {<br \/>\n            console.warn(&#096;Unexpected status: ${data.status}&#096;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async processResponse(data, location, originalRequest) {<br \/>\n        const result &#061; {<br \/>\n            success: true,<br \/>\n            resourceId: data.id || data.resource_id,<br \/>\n            data: data,<br \/>\n            location: location,<br \/>\n            timestamp: new Date().toISOString()<br \/>\n        };<\/p>\n<p>        \/\/ \u6839\u636e\u54cd\u5e94\u7c7b\u578b\u6267\u884c\u4e0d\u540c\u64cd\u4f5c<br \/>\n        switch (data.type || &#039;resource&#039;) {<br \/>\n            case &#039;user&#039;:<br \/>\n                await this.handleUserCreation(result, originalRequest);<br \/>\n                break;<br \/>\n            case &#039;order&#039;:<br \/>\n                await this.handleOrderCreation(result, originalRequest);<br \/>\n                break;<br \/>\n            case &#039;document&#039;:<br \/>\n                await this.handleDocumentCreation(result, originalRequest);<br \/>\n                break;<br \/>\n            default:<br \/>\n                await this.handleGenericCreation(result, originalRequest);<br \/>\n        }<\/p>\n<p>        \/\/ \u66f4\u65b0UI<br \/>\n        this.updateUI(result);<\/p>\n<p>        \/\/ \u89e6\u53d1\u4e8b\u4ef6<br \/>\n        this.dispatchCreationEvent(result);<\/p>\n<p>        return result;<br \/>\n    }<\/p>\n<p>    async handleUserCreation(result, requestData) {<br \/>\n        \/\/ \u7528\u6237\u521b\u5efa\u6210\u529f\u540e\u7684\u5904\u7406<br \/>\n        console.log(&#096;User created: ${result.resourceId}&#096;);<\/p>\n<p>        \/\/ \u81ea\u52a8\u767b\u5f55\u65b0\u7528\u6237&#xff08;\u5982\u679c\u9002\u7528&#xff09;<br \/>\n        if (requestData.autoLogin) {<br \/>\n            await this.autoLogin(result.data.email, requestData.password);<br \/>\n        }<\/p>\n<p>        \/\/ \u91cd\u5b9a\u5411\u5230\u7528\u6237\u9875\u9762<br \/>\n        if (result.location) {<br \/>\n            setTimeout(() &#061;&gt; {<br \/>\n                window.location.href &#061; result.location;<br \/>\n            }, 2000);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    async handleOrderCreation(result, requestData) {<br \/>\n        \/\/ \u8ba2\u5355\u521b\u5efa\u6210\u529f\u540e\u7684\u5904\u7406<br \/>\n        console.log(&#096;Order created: ${result.resourceId}&#096;);<\/p>\n<p>        \/\/ \u663e\u793a\u786e\u8ba4\u4fe1\u606f<br \/>\n        this.showOrderConfirmation(result.data);<\/p>\n<p>        \/\/ \u5f00\u59cb\u8ba2\u5355\u72b6\u6001\u8f6e\u8be2<br \/>\n        this.startOrderStatusPolling(result.resourceId);<br \/>\n    }<\/p>\n<p>    updateUI(result) {<br \/>\n        \/\/ \u66f4\u65b0\u7528\u6237\u754c\u9762<br \/>\n        const message &#061; document.getElementById(&#039;creation-message&#039;);<br \/>\n        if (message) {<br \/>\n            message.textContent &#061; &#039;\u521b\u5efa\u6210\u529f&#xff01;&#039;;<br \/>\n            message.className &#061; &#039;alert alert-success&#039;;<\/p>\n<p>            \/\/ \u6dfb\u52a0\u67e5\u770b\u94fe\u63a5<br \/>\n            if (result.location) {<br \/>\n                const link &#061; document.createElement(&#039;a&#039;);<br \/>\n                link.href &#061; result.location;<br \/>\n                link.textContent &#061; &#039;\u67e5\u770b\u65b0\u8d44\u6e90&#039;;<br \/>\n                link.className &#061; &#039;btn btn-primary ml-3&#039;;<br \/>\n                message.appendChild(link);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u66f4\u65b0\u8d44\u6e90\u5217\u8868<br \/>\n        if (window.updateResourceList) {<br \/>\n            window.updateResourceList(result.data);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    dispatchCreationEvent(result) {<br \/>\n        \/\/ \u6d3e\u53d1\u81ea\u5b9a\u4e49\u4e8b\u4ef6<br \/>\n        const event &#061; new CustomEvent(&#039;resource-created&#039;, {<br \/>\n            detail: result,<br \/>\n            bubbles: true<br \/>\n        });<br \/>\n        document.dispatchEvent(event);<br \/>\n    }<\/p>\n<p>    async autoLogin(email, password) {<br \/>\n        \/\/ \u81ea\u52a8\u767b\u5f55\u903b\u8f91<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/auth\/login&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n                },<br \/>\n                body: JSON.stringify({ email, password })<br \/>\n            });<\/p>\n<p>            if (response.ok) {<br \/>\n                console.log(&#039;Auto-login successful&#039;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Auto-login failed:&#039;, error);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showOrderConfirmation(orderData) {<br \/>\n        \/\/ \u663e\u793a\u8ba2\u5355\u786e\u8ba4<br \/>\n        const modal &#061; document.getElementById(&#039;order-confirmation-modal&#039;);<br \/>\n        if (modal) {<br \/>\n            document.getElementById(&#039;order-id&#039;).textContent &#061; orderData.id;<br \/>\n            document.getElementById(&#039;order-total&#039;).textContent &#061;<br \/>\n                &#096;$${orderData.total?.toFixed(2) || &#039;0.00&#039;}&#096;;<\/p>\n<p>            \/\/ \u663e\u793a\u6a21\u6001\u6846<br \/>\n            $(modal).modal(&#039;show&#039;); \/\/ \u5047\u8bbe\u4f7f\u7528Bootstrap<br \/>\n        }<br \/>\n    }<\/p>\n<p>    startOrderStatusPolling(orderId) {<br \/>\n        \/\/ \u5f00\u59cb\u8f6e\u8be2\u8ba2\u5355\u72b6\u6001<br \/>\n        const interval &#061; setInterval(async () &#061;&gt; {<br \/>\n            try {<br \/>\n                const response &#061; await fetch(&#096;\/api\/orders\/${orderId}\/status&#096;);<br \/>\n                const status &#061; await response.json();<\/p>\n<p>                if (status.state &#061;&#061;&#061; &#039;completed&#039; || status.state &#061;&#061;&#061; &#039;failed&#039;) {<br \/>\n                    clearInterval(interval);<br \/>\n                    this.updateOrderStatus(status);<br \/>\n                }<br \/>\n            } catch (error) {<br \/>\n                console.error(&#039;Status polling failed:&#039;, error);<br \/>\n            }<br \/>\n        }, 5000); \/\/ \u6bcf5\u79d2\u8f6e\u8be2\u4e00\u6b21<br \/>\n    }<\/p>\n<p>    updateOrderStatus(status) {<br \/>\n        \/\/ \u66f4\u65b0\u8ba2\u5355\u72b6\u6001\u663e\u793a<br \/>\n        console.log(&#039;Order status updated:&#039;, status);<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.getElementById(&#039;create-form&#039;).addEventListener(&#039;submit&#039;, async (e) &#061;&gt; {<br \/>\n    e.preventDefault();<\/p>\n<p>    const formData &#061; new FormData(e.target);<br \/>\n    const jsonData &#061; Object.fromEntries(formData.entries());<\/p>\n<p>    try {<br \/>\n        const response &#061; await fetch(&#039;\/api\/resources&#039;, {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            headers: {<br \/>\n                &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n                &#039;X-Requested-With&#039;: &#039;XMLHttpRequest&#039;<br \/>\n            },<br \/>\n            body: JSON.stringify(jsonData)<br \/>\n        });<\/p>\n<p>        if (response.status &#061;&#061;&#061; 201) {<br \/>\n            const handler &#061; new CreatedResponseHandler();<br \/>\n            await handler.handleCreatedResponse(response, jsonData);<br \/>\n        } else {<br \/>\n            \/\/ \u5904\u7406\u9519\u8bef<br \/>\n            console.error(&#039;Creation failed:&#039;, response.status);<br \/>\n        }<br \/>\n    } catch (error) {<br \/>\n        console.error(&#039;Request failed:&#039;, error);<br \/>\n    }<br \/>\n});<\/p>\n<h5>\u79fb\u52a8\u7aef\u5904\u7406\u793a\u4f8b<\/h5>\n<p>swift<\/p>\n<p>\/\/ Swift iOS\u793a\u4f8b&#xff1a;\u5904\u7406201 Created\u54cd\u5e94<br \/>\nimport Foundation<\/p>\n<p>struct Resource: Codable {<br \/>\n    let id: String<br \/>\n    let name: String<br \/>\n    let type: String<br \/>\n    let createdAt: Date<br \/>\n    let links: [String: String]?<\/p>\n<p>    enum CodingKeys: String, CodingKey {<br \/>\n        case id, name, type<br \/>\n        case createdAt &#061; &#034;created_at&#034;<br \/>\n        case links<br \/>\n    }<br \/>\n}<\/p>\n<p>struct CreationResponse: Codable {<br \/>\n    let resource: Resource<br \/>\n    let message: String?<br \/>\n    let location: String?<\/p>\n<p>    enum CodingKeys: String, CodingKey {<br \/>\n        case resource, message<br \/>\n        case location &#061; &#034;Location&#034; \/\/ \u6ce8\u610f&#xff1a;\u6765\u81ea\u5934\u90e8<br \/>\n    }<br \/>\n}<\/p>\n<p>class APIClient {<br \/>\n    let baseURL: URL<br \/>\n    let session: URLSession<\/p>\n<p>    init(baseURL: URL, session: URLSession &#061; .shared) {<br \/>\n        self.baseURL &#061; baseURL<br \/>\n        self.session &#061; session<br \/>\n    }<\/p>\n<p>    func createResource&lt;T: Encodable&gt;(<br \/>\n        _ resource: T,<br \/>\n        endpoint: String,<br \/>\n        completion: &#064;escaping (Result&lt;CreationResponse, Error&gt;) -&gt; Void<br \/>\n    ) {<br \/>\n        let url &#061; baseURL.appendingPathComponent(endpoint)<\/p>\n<p>        var request &#061; URLRequest(url: url)<br \/>\n        request.httpMethod &#061; &#034;POST&#034;<br \/>\n        request.setValue(&#034;application\/json&#034;, forHTTPHeaderField: &#034;Content-Type&#034;)<br \/>\n        request.setValue(&#034;application\/json&#034;, forHTTPHeaderField: &#034;Accept&#034;)<\/p>\n<p>        \/\/ \u6dfb\u52a0\u8ba4\u8bc1\u4ee4\u724c&#xff08;\u5982\u679c\u5b58\u5728&#xff09;<br \/>\n        if let token &#061; AuthManager.shared.accessToken {<br \/>\n            request.setValue(&#034;Bearer \\\\(token)&#034;, forHTTPHeaderField: &#034;Authorization&#034;)<br \/>\n        }<\/p>\n<p>        do {<br \/>\n            request.httpBody &#061; try JSONEncoder().encode(resource)<br \/>\n        } catch {<br \/>\n            completion(.failure(error))<br \/>\n            return<br \/>\n        }<\/p>\n<p>        let task &#061; session.dataTask(with: request) { data, response, error in<br \/>\n            \/\/ \u5904\u7406\u7f51\u7edc\u9519\u8bef<br \/>\n            if let error &#061; error {<br \/>\n                DispatchQueue.main.async {<br \/>\n                    completion(.failure(error))<br \/>\n                }<br \/>\n                return<br \/>\n            }<\/p>\n<p>            guard let httpResponse &#061; response as? HTTPURLResponse else {<br \/>\n                DispatchQueue.main.async {<br \/>\n                    completion(.failure(APIError.invalidResponse))<br \/>\n                }<br \/>\n                return<br \/>\n            }<\/p>\n<p>            \/\/ \u5904\u7406201 Created\u54cd\u5e94<br \/>\n            if httpResponse.statusCode &#061;&#061; 201 {<br \/>\n                self.handleCreatedResponse(<br \/>\n                    data: data,<br \/>\n                    response: httpResponse,<br \/>\n                    completion: completion<br \/>\n                )<br \/>\n            } else {<br \/>\n                \/\/ \u5904\u7406\u5176\u4ed6\u72b6\u6001\u7801<br \/>\n                self.handleErrorResponse(<br \/>\n                    data: data,<br \/>\n                    response: httpResponse,<br \/>\n                    completion: completion<br \/>\n                )<br \/>\n            }<br \/>\n        }<\/p>\n<p>        task.resume()<br \/>\n    }<\/p>\n<p>    private func handleCreatedResponse(<br \/>\n        data: Data?,<br \/>\n        response: HTTPURLResponse,<br \/>\n        completion: &#064;escaping (Result&lt;CreationResponse, Error&gt;) -&gt; Void<br \/>\n    ) {<br \/>\n        guard let data &#061; data else {<br \/>\n            DispatchQueue.main.async {<br \/>\n                completion(.failure(APIError.noData))<br \/>\n            }<br \/>\n            return<br \/>\n        }<\/p>\n<p>        do {<br \/>\n            \/\/ \u89e3\u6790\u54cd\u5e94\u4f53<br \/>\n            let decoder &#061; JSONDecoder()<br \/>\n            decoder.dateDecodingStrategy &#061; .iso8601<\/p>\n<p>            var resource: Resource<br \/>\n            if let resourceDict &#061; try JSONSerialization.jsonObject(with: data) as? [String: Any],<br \/>\n               let resourceData &#061; try? JSONSerialization.data(withJSONObject: resourceDict) {<br \/>\n                resource &#061; try decoder.decode(Resource.self, from: resourceData)<br \/>\n            } else {<br \/>\n                \/\/ \u5982\u679c\u54cd\u5e94\u4f53\u4e0d\u5305\u542b\u5b8c\u6574\u8d44\u6e90&#xff0c;\u5c1d\u8bd5\u89e3\u6790\u6700\u5c0f\u6570\u636e<br \/>\n                let partialData &#061; try decoder.decode([String: String].self, from: data)<br \/>\n                guard let id &#061; partialData[&#034;id&#034;] else {<br \/>\n                    throw APIError.invalidResponse<br \/>\n                }<\/p>\n<p>                resource &#061; Resource(<br \/>\n                    id: id,<br \/>\n                    name: partialData[&#034;name&#034;] ?? &#034;&#034;,<br \/>\n                    type: partialData[&#034;type&#034;] ?? &#034;unknown&#034;,<br \/>\n                    createdAt: Date(),<br \/>\n                    links: nil<br \/>\n                )<br \/>\n            }<\/p>\n<p>            \/\/ \u83b7\u53d6Location\u5934\u90e8<br \/>\n            let location &#061; response.allHeaderFields[&#034;Location&#034;] as? String<\/p>\n<p>            \/\/ \u6784\u5efa\u54cd\u5e94\u5bf9\u8c61<br \/>\n            let creationResponse &#061; CreationResponse(<br \/>\n                resource: resource,<br \/>\n                message: &#034;Resource created successfully&#034;,<br \/>\n                location: location<br \/>\n            )<\/p>\n<p>            DispatchQueue.main.async {<br \/>\n                completion(.success(creationResponse))<\/p>\n<p>                \/\/ \u89e6\u53d1\u901a\u77e5<br \/>\n                NotificationCenter.default.post(<br \/>\n                    name: .resourceCreated,<br \/>\n                    object: nil,<br \/>\n                    userInfo: [&#034;resource&#034;: resource]<br \/>\n                )<\/p>\n<p>                \/\/ \u5982\u679c\u5b58\u5728Location&#xff0c;\u53ef\u4ee5\u81ea\u52a8\u5bfc\u822a<br \/>\n                if let location &#061; location, let url &#061; URL(string: location) {<br \/>\n                    DispatchQueue.main.asyncAfter(deadline: .now() &#043; 1) {<br \/>\n                        UIApplication.shared.open(url)<br \/>\n                    }<br \/>\n                }<br \/>\n            }<\/p>\n<p>        } catch {<br \/>\n            DispatchQueue.main.async {<br \/>\n                completion(.failure(error))<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private func handleErrorResponse(<br \/>\n        data: Data?,<br \/>\n        response: HTTPURLResponse,<br \/>\n        completion: &#064;escaping (Result&lt;CreationResponse, Error&gt;) -&gt; Void<br \/>\n    ) {<br \/>\n        let error: APIError<\/p>\n<p>        switch response.statusCode {<br \/>\n        case 400:<br \/>\n            error &#061; .badRequest(self.parseErrorMessage(from: data))<br \/>\n        case 401:<br \/>\n            error &#061; .unauthorized<br \/>\n        case 403:<br \/>\n            error &#061; .forbidden<br \/>\n        case 409:<br \/>\n            error &#061; .conflict(self.parseErrorMessage(from: data))<br \/>\n        case 422:<br \/>\n            error &#061; .validationError(self.parseValidationErrors(from: data))<br \/>\n        case 429:<br \/>\n            error &#061; .rateLimited<br \/>\n        default:<br \/>\n            error &#061; .serverError(response.statusCode)<br \/>\n        }<\/p>\n<p>        DispatchQueue.main.async {<br \/>\n            completion(.failure(error))<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private func parseErrorMessage(from data: Data?) -&gt; String? {<br \/>\n        guard let data &#061; data,<br \/>\n              let json &#061; try? JSONSerialization.jsonObject(with: data) as? [String: Any],<br \/>\n              let message &#061; json[&#034;message&#034;] as? String else {<br \/>\n            return nil<br \/>\n        }<br \/>\n        return message<br \/>\n    }<\/p>\n<p>    private func parseValidationErrors(from data: Data?) -&gt; [String: [String]]? {<br \/>\n        guard let data &#061; data,<br \/>\n              let json &#061; try? JSONSerialization.jsonObject(with: data) as? [String: Any],<br \/>\n              let errors &#061; json[&#034;errors&#034;] as? [String: [String]] else {<br \/>\n            return nil<br \/>\n        }<br \/>\n        return errors<br \/>\n    }<br \/>\n}<\/p>\n<p>enum APIError: Error, LocalizedError {<br \/>\n    case invalidResponse<br \/>\n    case noData<br \/>\n    case badRequest(String?)<br \/>\n    case unauthorized<br \/>\n    case forbidden<br \/>\n    case conflict(String?)<br \/>\n    case validationError([String: [String]]?)<br \/>\n    case rateLimited<br \/>\n    case serverError(Int)<\/p>\n<p>    var errorDescription: String? {<br \/>\n        switch self {<br \/>\n        case .badRequest(let message):<br \/>\n            return message ?? &#034;Bad request&#034;<br \/>\n        case .unauthorized:<br \/>\n            return &#034;Authentication required&#034;<br \/>\n        case .forbidden:<br \/>\n            return &#034;Access forbidden&#034;<br \/>\n        case .conflict(let message):<br \/>\n            return message ?? &#034;Resource conflict&#034;<br \/>\n        case .validationError(let errors):<br \/>\n            return errors?.map { &#034;\\\\($0.key): \\\\($0.value.joined(separator: &#034;, &#034;))&#034; }<br \/>\n                .joined(separator: &#034;; &#034;) ?? &#034;Validation failed&#034;<br \/>\n        case .rateLimited:<br \/>\n            return &#034;Too many requests&#034;<br \/>\n        case .serverError(let code):<br \/>\n            return &#034;Server error (\\\\(code))&#034;<br \/>\n        default:<br \/>\n            return &#034;Unknown error&#034;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u6269\u5c55Notification.Name<br \/>\nextension Notification.Name {<br \/>\n    static let resourceCreated &#061; Notification.Name(&#034;resourceCreated&#034;)<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nclass ResourceCreationViewController: UIViewController {<br \/>\n    let apiClient &#061; APIClient(baseURL: URL(string: &#034;https:\/\/api.example.com&#034;)!)<\/p>\n<p>    &#064;IBAction func createResourceTapped(_ sender: UIButton) {<br \/>\n        let newResource &#061; [<br \/>\n            &#034;name&#034;: &#034;New Resource&#034;,<br \/>\n            &#034;type&#034;: &#034;document&#034;,<br \/>\n            &#034;content&#034;: &#034;Sample content&#034;<br \/>\n        ]<\/p>\n<p>        showLoadingIndicator()<\/p>\n<p>        apiClient.createResource(newResource, endpoint: &#034;resources&#034;) { [weak self] result in<br \/>\n            DispatchQueue.main.async {<br \/>\n                self?.hideLoadingIndicator()<\/p>\n<p>                switch result {<br \/>\n                case .success(let response):<br \/>\n                    self?.handleCreationSuccess(response)<br \/>\n                case .failure(let error):<br \/>\n                    self?.handleCreationFailure(error)<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    private func handleCreationSuccess(_ response: CreationResponse) {<br \/>\n        \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n        let alert &#061; UIAlertController(<br \/>\n            title: &#034;Success&#034;,<br \/>\n            message: response.message ?? &#034;Resource created successfully&#034;,<br \/>\n            preferredStyle: .alert<br \/>\n        )<\/p>\n<p>        alert.addAction(UIAlertAction(title: &#034;View&#034;, style: .default) { _ in<br \/>\n            if let location &#061; response.location,<br \/>\n               let url &#061; URL(string: location) {<br \/>\n                UIApplication.shared.open(url)<br \/>\n            }<br \/>\n        })<\/p>\n<p>        alert.addAction(UIAlertAction(title: &#034;OK&#034;, style: .cancel))<\/p>\n<p>        present(alert, animated: true)<\/p>\n<p>        \/\/ \u66f4\u65b0\u672c\u5730\u6570\u636e<br \/>\n        updateResourceList(with: response.resource)<br \/>\n    }<\/p>\n<p>    private func handleCreationFailure(_ error: Error) {<br \/>\n        \/\/ \u663e\u793a\u9519\u8bef\u6d88\u606f<br \/>\n        let alert &#061; UIAlertController(<br \/>\n            title: &#034;Error&#034;,<br \/>\n            message: error.localizedDescription,<br \/>\n            preferredStyle: .alert<br \/>\n        )<\/p>\n<p>        alert.addAction(UIAlertAction(title: &#034;OK&#034;, style: .default))<\/p>\n<p>        present(alert, animated: true)<br \/>\n    }<\/p>\n<p>    private func updateResourceList(with resource: Resource) {<br \/>\n        \/\/ \u66f4\u65b0UI<br \/>\n        if var resources &#061; UserDefaults.standard.array(forKey: &#034;recentResources&#034;) as? [[String: Any]] {<br \/>\n            let resourceDict: [String: Any] &#061; [<br \/>\n                &#034;id&#034;: resource.id,<br \/>\n                &#034;name&#034;: resource.name,<br \/>\n                &#034;type&#034;: resource.type,<br \/>\n                &#034;created_at&#034;: resource.createdAt.timeIntervalSince1970<br \/>\n            ]<br \/>\n            resources.insert(resourceDict, at: 0)<br \/>\n            UserDefaults.standard.set(resources, forKey: &#034;recentResources&#034;)<br \/>\n        }<\/p>\n<p>        \/\/ \u5237\u65b0\u5217\u8868<br \/>\n        NotificationCenter.default.post(<br \/>\n            name: .resourceCreated,<br \/>\n            object: nil,<br \/>\n            userInfo: [&#034;resource&#034;: resource]<br \/>\n        )<br \/>\n    }<br \/>\n}<\/p>\n<h4>5.6 \u5b89\u5168\u8003\u8651\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h4>\n<h5>\u5b89\u5168\u6027\u589e\u5f3a<\/h5>\n<p>python<\/p>\n<p># \u5b89\u5168\u7684201\u54cd\u5e94\u751f\u6210\u5668<br \/>\nfrom django.http import JsonResponse<br \/>\nfrom django.views.decorators.csrf import csrf_exempt<br \/>\nfrom django.contrib.auth.decorators import login_required<br \/>\nimport json<br \/>\nimport hashlib<br \/>\nfrom datetime import datetime<\/p>\n<p>class SecureCreatedResponse:<br \/>\n    &#034;&#034;&#034;\u5b89\u5168\u7684201 Created\u54cd\u5e94\u751f\u6210\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, request):<br \/>\n        self.request &#061; request<br \/>\n        self.resource &#061; None<br \/>\n        self.resource_type &#061; None<\/p>\n<p>    def create_response(self, resource_data, resource_type, permissions&#061;None):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u521b\u5efa\u5b89\u5168\u7684201\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            resource_data: \u8d44\u6e90\u6570\u636e<br \/>\n            resource_type: \u8d44\u6e90\u7c7b\u578b<br \/>\n            permissions: \u6240\u9700\u6743\u9650\u5217\u8868<\/p>\n<p>        Returns:<br \/>\n            JsonResponse with 201 status<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u9a8c\u8bc1\u6743\u9650<br \/>\n        if permissions:<br \/>\n            self._check_permissions(permissions)<\/p>\n<p>        # \u9a8c\u8bc1\u8d44\u6e90\u6570\u636e<br \/>\n        validated_data &#061; self._validate_and_sanitize(resource_data)<\/p>\n<p>        # \u521b\u5efa\u8d44\u6e90<br \/>\n        self.resource &#061; self._create_resource(validated_data)<br \/>\n        self.resource_type &#061; resource_type<\/p>\n<p>        # \u6784\u5efa\u54cd\u5e94<br \/>\n        response_data &#061; self._build_response_data()<br \/>\n        headers &#061; self._build_headers()<\/p>\n<p>        # \u521b\u5efa\u54cd\u5e94\u5bf9\u8c61<br \/>\n        response &#061; JsonResponse(response_data, status&#061;201)<\/p>\n<p>        for key, value in headers.items():<br \/>\n            response[key] &#061; value<\/p>\n<p>        # \u6dfb\u52a0\u5b89\u5168\u5934\u90e8<br \/>\n        self._add_security_headers(response)<\/p>\n<p>        # \u8bb0\u5f55\u5ba1\u8ba1\u65e5\u5fd7<br \/>\n        self._log_creation()<\/p>\n<p>        return response<\/p>\n<p>    def _check_permissions(self, required_permissions):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u7528\u6237\u6743\u9650&#034;&#034;&#034;<br \/>\n        if not self.request.user.is_authenticated:<br \/>\n            raise PermissionError(&#034;Authentication required&#034;)<\/p>\n<p>        for perm in required_permissions:<br \/>\n            if not self.request.user.has_perm(perm):<br \/>\n                raise PermissionError(f&#034;Missing permission: {perm}&#034;)<\/p>\n<p>    def _validate_and_sanitize(self, data):<br \/>\n        &#034;&#034;&#034;\u9a8c\u8bc1\u548c\u6e05\u7406\u6570\u636e&#034;&#034;&#034;<br \/>\n        validated &#061; {}<\/p>\n<p>        # \u5fc5\u9700\u5b57\u6bb5\u68c0\u67e5<br \/>\n        required_fields &#061; [&#039;name&#039;, &#039;type&#039;]<br \/>\n        for field in required_fields:<br \/>\n            if field not in data:<br \/>\n                raise ValueError(f&#034;Missing required field: {field}&#034;)<br \/>\n            validated[field] &#061; str(data[field]).strip()<\/p>\n<p>        # \u6e05\u7406\u53ef\u9009\u5b57\u6bb5<br \/>\n        optional_fields &#061; [&#039;description&#039;, &#039;tags&#039;, &#039;metadata&#039;]<br \/>\n        for field in optional_fields:<br \/>\n            if field in data:<br \/>\n                validated[field] &#061; self._sanitize_field(data[field], field)<\/p>\n<p>        # \u6e05\u7406\u7528\u6237\u8f93\u5165<br \/>\n        if &#039;content&#039; in data:<br \/>\n            validated[&#039;content&#039;] &#061; self._sanitize_html(data[&#039;content&#039;])<\/p>\n<p>        # \u9632\u6b62\u6ce8\u5165\u653b\u51fb<br \/>\n        self._check_for_injection(validated)<\/p>\n<p>        return validated<\/p>\n<p>    def _sanitize_field(self, value, field_name):<br \/>\n        &#034;&#034;&#034;\u6839\u636e\u5b57\u6bb5\u7c7b\u578b\u6e05\u7406\u6570\u636e&#034;&#034;&#034;<br \/>\n        if isinstance(value, str):<br \/>\n            # \u79fb\u9664\u63a7\u5236\u5b57\u7b26<br \/>\n            import re<br \/>\n            value &#061; re.sub(r&#039;[\\\\x00-\\\\x1F\\\\x7F]&#039;, &#039;&#039;, value)<\/p>\n<p>            # \u5b57\u6bb5\u7279\u5b9a\u6e05\u7406<br \/>\n            if field_name &#061;&#061; &#039;email&#039;:<br \/>\n                if not self._is_valid_email(value):<br \/>\n                    raise ValueError(f&#034;Invalid email: {value}&#034;)<\/p>\n<p>            elif field_name &#061;&#061; &#039;url&#039;:<br \/>\n                if not self._is_valid_url(value):<br \/>\n                    raise ValueError(f&#034;Invalid URL: {value}&#034;)<\/p>\n<p>            # \u957f\u5ea6\u9650\u5236<br \/>\n            max_lengths &#061; {<br \/>\n                &#039;name&#039;: 100,<br \/>\n                &#039;description&#039;: 500,<br \/>\n                &#039;tags&#039;: 200<br \/>\n            }<\/p>\n<p>            if field_name in max_lengths and len(value) &gt; max_lengths[field_name]:<br \/>\n                raise ValueError(<br \/>\n                    f&#034;{field_name} too long (max {max_lengths[field_name]} chars)&#034;<br \/>\n                )<\/p>\n<p>        elif isinstance(value, list):<br \/>\n            # \u6e05\u7406\u5217\u8868\u4e2d\u7684\u6bcf\u4e2a\u5143\u7d20<br \/>\n            return [self._sanitize_field(item, field_name) for item in value]<\/p>\n<p>        elif isinstance(value, dict):<br \/>\n            # \u6e05\u7406\u5b57\u5178<br \/>\n            return {<br \/>\n                k: self._sanitize_field(v, f&#034;{field_name}.{k}&#034;)<br \/>\n                for k, v in value.items()<br \/>\n            }<\/p>\n<p>        return value<\/p>\n<p>    def _sanitize_html(self, html):<br \/>\n        &#034;&#034;&#034;\u6e05\u7406HTML\u5185\u5bb9&#034;&#034;&#034;<br \/>\n        # \u4f7f\u7528bleach\u6216\u5176\u4ed6HTML\u6e05\u7406\u5e93<br \/>\n        try:<br \/>\n            import bleach<br \/>\n            from bleach.sanitizer import ALLOWED_TAGS, ALLOWED_ATTRIBUTES<\/p>\n<p>            allowed_tags &#061; ALLOWED_TAGS &#043; [&#039;p&#039;, &#039;br&#039;, &#039;h1&#039;, &#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;]<br \/>\n            allowed_attrs &#061; {<br \/>\n                **ALLOWED_ATTRIBUTES,<br \/>\n                &#039;a&#039;: [&#039;href&#039;, &#039;title&#039;, &#039;rel&#039;],<br \/>\n                &#039;img&#039;: [&#039;src&#039;, &#039;alt&#039;, &#039;title&#039;]<br \/>\n            }<\/p>\n<p>            # \u6e05\u7406HTML<br \/>\n            cleaned &#061; bleach.clean(<br \/>\n                html,<br \/>\n                tags&#061;allowed_tags,<br \/>\n                attributes&#061;allowed_attrs,<br \/>\n                strip&#061;True<br \/>\n            )<\/p>\n<p>            # \u6dfb\u52a0nofollow\u5230\u5916\u90e8\u94fe\u63a5<br \/>\n            cleaned &#061; bleach.linkify(cleaned, callbacks&#061;[self._nofollow_callback])<\/p>\n<p>            return cleaned<\/p>\n<p>        except ImportError:<br \/>\n            # \u5982\u679cbleach\u4e0d\u53ef\u7528&#xff0c;\u8fdb\u884c\u57fa\u672c\u6e05\u7406<br \/>\n            import re<br \/>\n            # \u79fb\u9664\u6240\u6709HTML\u6807\u7b7e<br \/>\n            return re.sub(r&#039;&lt;[^&gt;]*&gt;&#039;, &#039;&#039;, html)<\/p>\n<p>    def _nofollow_callback(self, attrs, new&#061;False):<br \/>\n        &#034;&#034;&#034;\u4e3a\u5916\u90e8\u94fe\u63a5\u6dfb\u52a0nofollow&#034;&#034;&#034;<br \/>\n        href_key &#061; (None, &#039;href&#039;)<\/p>\n<p>        if href_key in attrs:<br \/>\n            href &#061; attrs[href_key]<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u4e3a\u5916\u90e8\u94fe\u63a5<br \/>\n            if not href.startswith((&#039;\/api\/&#039;, &#039;\/static\/&#039;, self.request.get_host())):<br \/>\n                rel_key &#061; (None, &#039;rel&#039;)<br \/>\n                rel_values &#061; set(attrs.get(rel_key, &#039;&#039;).split())<br \/>\n                rel_values.add(&#039;nofollow&#039;)<br \/>\n                rel_values.add(&#039;noopener&#039;)<br \/>\n                rel_values.add(&#039;noreferrer&#039;)<br \/>\n                attrs[rel_key] &#061; &#039; &#039;.join(sorted(rel_values))<\/p>\n<p>        return attrs<\/p>\n<p>    def _check_for_injection(self, data):<br \/>\n        &#034;&#034;&#034;\u68c0\u67e5\u6ce8\u5165\u653b\u51fb&#034;&#034;&#034;<br \/>\n        injection_patterns &#061; [<br \/>\n            r&#039;&lt;script[^&gt;]*&gt;&#039;,<br \/>\n            r&#039;javascript:&#039;,<br \/>\n            r&#039;on\\\\w&#043;\\\\s*&#061;&#039;,<br \/>\n            r&#039;SELECT.*FROM&#039;,<br \/>\n            r&#039;INSERT.*INTO&#039;,<br \/>\n            r&#039;DELETE.*FROM&#039;,<br \/>\n            r&#039;UPDATE.*SET&#039;,<br \/>\n            r&#039;DROP.*TABLE&#039;<br \/>\n        ]<\/p>\n<p>        def check_value(value):<br \/>\n            if isinstance(value, str):<br \/>\n                for pattern in injection_patterns:<br \/>\n                    import re<br \/>\n                    if re.search(pattern, value, re.IGNORECASE):<br \/>\n                        raise SecurityError(f&#034;Potential injection detected: {pattern}&#034;)<\/p>\n<p>            elif isinstance(value, (list, dict)):<br \/>\n                for item in (value.values() if isinstance(value, dict) else value):<br \/>\n                    check_value(item)<\/p>\n<p>        check_value(data)<\/p>\n<p>    def _create_resource(self, validated_data):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u8d44\u6e90&#034;&#034;&#034;<br \/>\n        # \u5b9e\u9645\u4e2d\u8fd9\u91cc\u4f1a\u6709\u6570\u636e\u5e93\u64cd\u4f5c<br \/>\n        from uuid import uuid4<br \/>\n        from django.utils import timezone<\/p>\n<p>        resource_id &#061; str(uuid4())<\/p>\n<p>        resource &#061; {<br \/>\n            &#039;id&#039;: resource_id,<br \/>\n            **validated_data,<br \/>\n            &#039;created_by&#039;: self.request.user.id if self.request.user.is_authenticated else None,<br \/>\n            &#039;created_at&#039;: timezone.now(),<br \/>\n            &#039;updated_at&#039;: timezone.now(),<br \/>\n            &#039;status&#039;: &#039;active&#039;,<br \/>\n            &#039;version&#039;: 1<br \/>\n        }<\/p>\n<p>        # \u751f\u6210ETag<br \/>\n        resource[&#039;etag&#039;] &#061; self._generate_etag(resource)<\/p>\n<p>        # \u5b58\u50a8\u8d44\u6e90&#xff08;\u6a21\u62df&#xff09;<br \/>\n        # self._store_resource(resource)<\/p>\n<p>        return resource<\/p>\n<p>    def _generate_etag(self, resource):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; f&#034;{resource[&#039;id&#039;]}{resource[&#039;created_at&#039;].isoformat()}{resource[&#039;version&#039;]}&#034;<br \/>\n        return hashlib.md5(content.encode()).hexdigest()<\/p>\n<p>    def _build_response_data(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u54cd\u5e94\u6570\u636e&#034;&#034;&#034;<br \/>\n        # \u8fc7\u6ee4\u654f\u611f\u5b57\u6bb5<br \/>\n        safe_resource &#061; {<br \/>\n            k: v for k, v in self.resource.items()<br \/>\n            if k not in [&#039;created_by&#039;, &#039;version&#039;, &#039;etag&#039;, &#039;password&#039;, &#039;token&#039;]<br \/>\n        }<\/p>\n<p>        response_data &#061; {<br \/>\n            &#039;id&#039;: self.resource[&#039;id&#039;],<br \/>\n            &#039;type&#039;: self.resource_type,<br \/>\n            &#039;attributes&#039;: safe_resource,<br \/>\n            &#039;links&#039;: self._build_links(),<br \/>\n            &#039;meta&#039;: {<br \/>\n                &#039;created_at&#039;: self.resource[&#039;created_at&#039;].isoformat(),<br \/>\n                &#039;request_id&#039;: self.request.META.get(&#039;HTTP_X_REQUEST_ID&#039;, &#039;&#039;),<br \/>\n                &#039;api_version&#039;: &#039;v1&#039;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        return response_data<\/p>\n<p>    def _build_links(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efaHATEOAS\u94fe\u63a5&#034;&#034;&#034;<br \/>\n        base_url &#061; self.request.build_absolute_uri(&#039;\/&#039;)[:-1]<br \/>\n        resource_url &#061; f&#034;{base_url}\/api\/{self.resource_type}s\/{self.resource[&#039;id&#039;]}&#034;<\/p>\n<p>        return {<br \/>\n            &#039;self&#039;: {<br \/>\n                &#039;href&#039;: resource_url,<br \/>\n                &#039;method&#039;: &#039;GET&#039;<br \/>\n            },<br \/>\n            &#039;update&#039;: {<br \/>\n                &#039;href&#039;: resource_url,<br \/>\n                &#039;method&#039;: &#039;PUT&#039;<br \/>\n            },<br \/>\n            &#039;delete&#039;: {<br \/>\n                &#039;href&#039;: resource_url,<br \/>\n                &#039;method&#039;: &#039;DELETE&#039;<br \/>\n            },<br \/>\n            &#039;collection&#039;: {<br \/>\n                &#039;href&#039;: f&#034;{base_url}\/api\/{self.resource_type}s&#034;,<br \/>\n                &#039;method&#039;: &#039;GET&#039;<br \/>\n            }<br \/>\n        }<\/p>\n<p>    def _build_headers(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u54cd\u5e94\u5934\u90e8&#034;&#034;&#034;<br \/>\n        base_url &#061; self.request.build_absolute_uri(&#039;\/&#039;)[:-1]<br \/>\n        location &#061; f&#034;{base_url}\/api\/{self.resource_type}s\/{self.resource[&#039;id&#039;]}&#034;<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Location&#039;: location,<br \/>\n            &#039;ETag&#039;: f&#039;&#034;{self.resource[&#034;etag&#034;]}&#034;&#039;,<br \/>\n            &#039;X-Resource-ID&#039;: self.resource[&#039;id&#039;],<br \/>\n            &#039;X-Resource-Type&#039;: self.resource_type,<br \/>\n            &#039;X-Created-At&#039;: self.resource[&#039;created_at&#039;].isoformat()<br \/>\n        }<\/p>\n<p>        # \u7f13\u5b58\u63a7\u5236<br \/>\n        if self.resource_type in [&#039;document&#039;, &#039;article&#039;]:<br \/>\n            headers[&#039;Cache-Control&#039;] &#061; &#039;public, max-age&#061;300, must-revalidate&#039;<br \/>\n        else:<br \/>\n            headers[&#039;Cache-Control&#039;] &#061; &#039;private, no-cache&#039;<\/p>\n<p>        return headers<\/p>\n<p>    def _add_security_headers(self, response):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u5b89\u5168\u5934\u90e8&#034;&#034;&#034;<br \/>\n        security_headers &#061; {<br \/>\n            &#039;X-Content-Type-Options&#039;: &#039;nosniff&#039;,<br \/>\n            &#039;X-Frame-Options&#039;: &#039;DENY&#039;,<br \/>\n            &#039;X-XSS-Protection&#039;: &#039;1; mode&#061;block&#039;,<br \/>\n            &#039;Referrer-Policy&#039;: &#039;strict-origin-when-cross-origin&#039;,<br \/>\n            &#039;Content-Security-Policy&#039;: &#034;default-src &#039;self&#039;&#034;,<br \/>\n            &#039;Strict-Transport-Security&#039;: &#039;max-age&#061;31536000; includeSubDomains&#039;<br \/>\n        }<\/p>\n<p>        for header, value in security_headers.items():<br \/>\n            response[header] &#061; value<\/p>\n<p>    def _log_creation(self):<br \/>\n        &#034;&#034;&#034;\u8bb0\u5f55\u5ba1\u8ba1\u65e5\u5fd7&#034;&#034;&#034;<br \/>\n        audit_log &#061; {<br \/>\n            &#039;event&#039;: &#039;resource_created&#039;,<br \/>\n            &#039;resource_type&#039;: self.resource_type,<br \/>\n            &#039;resource_id&#039;: self.resource[&#039;id&#039;],<br \/>\n            &#039;user_id&#039;: self.request.user.id if self.request.user.is_authenticated else None,<br \/>\n            &#039;user_agent&#039;: self.request.META.get(&#039;HTTP_USER_AGENT&#039;, &#039;&#039;),<br \/>\n            &#039;ip_address&#039;: self.request.META.get(&#039;REMOTE_ADDR&#039;, &#039;&#039;),<br \/>\n            &#039;timestamp&#039;: datetime.utcnow().isoformat(),<br \/>\n            &#039;metadata&#039;: {<br \/>\n                &#039;method&#039;: self.request.method,<br \/>\n                &#039;path&#039;: self.request.path,<br \/>\n                &#039;content_length&#039;: len(self.request.body) if self.request.body else 0<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # \u8bb0\u5f55\u5230\u5ba1\u8ba1\u65e5\u5fd7&#xff08;\u5b9e\u9645\u4e2d\u4f1a\u5199\u5165\u6570\u636e\u5e93\u6216\u65e5\u5fd7\u6587\u4ef6&#xff09;<br \/>\n        import logging<br \/>\n        logger &#061; logging.getLogger(&#039;audit&#039;)<br \/>\n        logger.info(json.dumps(audit_log))<\/p>\n<p>    def _is_valid_email(self, email):<br \/>\n        import re<br \/>\n        pattern &#061; r&#039;^[a-zA-Z0-9._%&#043;-]&#043;&#064;[a-zA-Z0-9.-]&#043;\\\\.[a-zA-Z]{2,}$&#039;<br \/>\n        return bool(re.match(pattern, email))<\/p>\n<p>    def _is_valid_url(self, url):<br \/>\n        import re<br \/>\n        pattern &#061; r&#039;^https?:\/\/[^\\\\s\/$.?#].[^\\\\s]*$&#039;<br \/>\n        return bool(re.match(pattern, url))<\/p>\n<p>class SecurityError(Exception):<br \/>\n    &#034;&#034;&#034;\u5b89\u5168\u5f02\u5e38&#034;&#034;&#034;<br \/>\n    pass<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\n&#064;csrf_exempt<br \/>\n&#064;login_required<br \/>\ndef create_resource_view(request):<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u8d44\u6e90\u89c6\u56fe&#034;&#034;&#034;<\/p>\n<p>    if request.method !&#061; &#039;POST&#039;:<br \/>\n        return JsonResponse({&#039;error&#039;: &#039;Method not allowed&#039;}, status&#061;405)<\/p>\n<p>    try:<br \/>\n        # \u89e3\u6790\u8bf7\u6c42\u6570\u636e<br \/>\n        data &#061; json.loads(request.body)<\/p>\n<p>        # \u521b\u5efa\u54cd\u5e94<br \/>\n        creator &#061; SecureCreatedResponse(request)<br \/>\n        response &#061; creator.create_response(<br \/>\n            resource_data&#061;data,<br \/>\n            resource_type&#061;&#039;document&#039;,<br \/>\n            permissions&#061;[&#039;app.add_document&#039;]<br \/>\n        )<\/p>\n<p>        return response<\/p>\n<p>    except json.JSONDecodeError:<br \/>\n        return JsonResponse({&#039;error&#039;: &#039;Invalid JSON&#039;}, status&#061;400)<br \/>\n    except ValueError as e:<br \/>\n        return JsonResponse({&#039;error&#039;: str(e)}, status&#061;400)<br \/>\n    except PermissionError as e:<br \/>\n        return JsonResponse({&#039;error&#039;: str(e)}, status&#061;403)<br \/>\n    except SecurityError as e:<br \/>\n        return JsonResponse({&#039;error&#039;: &#039;Security violation&#039;}, status&#061;400)<br \/>\n    except Exception as e:<br \/>\n        # \u8bb0\u5f55\u8be6\u7ec6\u9519\u8bef<br \/>\n        import traceback<br \/>\n        logger.error(f&#034;Creation failed: {e}\\\\n{traceback.format_exc()}&#034;)<br \/>\n        return JsonResponse({&#039;error&#039;: &#039;Internal server error&#039;}, status&#061;500)<\/p>\n<h5>\u6700\u4f73\u5b9e\u8df5\u603b\u7ed3<\/h5>\n<p>yaml<\/p>\n<p># 201 Created\u6700\u4f73\u5b9e\u8df5\u6e05\u5355<br \/>\nbest_practices_201:<\/p>\n<p>  # \u54cd\u5e94\u8981\u6c42<br \/>\n  response_requirements:<br \/>\n    status_code: 201  # \u5fc5\u987b\u4f7f\u7528201&#xff0c;\u4e0d\u662f200<br \/>\n    location_header: &#034;\u5fc5\u987b\u5305\u542b\u65b0\u8d44\u6e90URI&#034;<br \/>\n    response_body: &#034;\u5e94\u8be5\u5305\u542b\u65b0\u8d44\u6e90\u7684\u8868\u793a&#034;<\/p>\n<p>  # Location\u5934\u90e8<br \/>\n  location_header:<br \/>\n    format: &#034;\u7edd\u5bf9URI&#xff08;\u63a8\u8350&#xff09;\u6216\u76f8\u5bf9\u8def\u5f84&#034;<br \/>\n    specificity: &#034;\u6307\u5411\u5177\u4f53\u8d44\u6e90&#xff0c;\u4e0d\u662f\u96c6\u5408&#034;<br \/>\n    stability: &#034;URI\u5e94\u957f\u671f\u6709\u6548&#034;<br \/>\n    example: &#034;https:\/\/api.example.com\/v1\/users\/123&#034;<\/p>\n<p>  # \u54cd\u5e94\u4f53<br \/>\n  response_body:<br \/>\n    must_include:<br \/>\n      &#8211; \u8d44\u6e90\u6807\u8bc6\u7b26&#xff08;id&#xff09;<br \/>\n      &#8211; \u8d44\u6e90\u7c7b\u578b&#xff08;type&#xff09;<br \/>\n      &#8211; \u521b\u5efa\u72b6\u6001&#xff08;status: &#039;created&#039;&#xff09;<\/p>\n<p>    should_include:<br \/>\n      &#8211; \u65b0\u8d44\u6e90\u7684\u5b8c\u6574\u6216\u90e8\u5206\u8868\u793a<br \/>\n      &#8211; \u521b\u5efa\u65f6\u95f4\u6233<br \/>\n      &#8211; \u6210\u529f\u6d88\u606f<\/p>\n<p>    may_include:<br \/>\n      &#8211; \u76f8\u5173\u8d44\u6e90\u94fe\u63a5&#xff08;HATEOAS&#xff09;<br \/>\n      &#8211; \u5143\u6570\u636e&#xff08;API\u7248\u672c\u3001\u8bf7\u6c42ID\u7b49&#xff09;<br \/>\n      &#8211; \u4e0b\u4e00\u6b65\u64cd\u4f5c\u5efa\u8bae<\/p>\n<p>  # \u5b89\u5168\u6027<br \/>\n  security:<br \/>\n    authentication: &#034;\u521b\u5efa\u64cd\u4f5c\u901a\u5e38\u9700\u8981\u8ba4\u8bc1&#034;<br \/>\n    authorization: &#034;\u9a8c\u8bc1\u7528\u6237\u6709\u521b\u5efa\u6743\u9650&#034;<br \/>\n    input_validation: &#034;\u4e25\u683c\u9a8c\u8bc1\u548c\u6e05\u7406\u8f93\u5165&#034;<br \/>\n    output_filtering: &#034;\u8fc7\u6ee4\u54cd\u5e94\u4e2d\u7684\u654f\u611f\u4fe1\u606f&#034;<br \/>\n    audit_logging: &#034;\u8bb0\u5f55\u8d44\u6e90\u521b\u5efa\u4e8b\u4ef6&#034;<\/p>\n<p>  # \u7f13\u5b58\u884c\u4e3a<br \/>\n  caching:<br \/>\n    default: &#034;201\u54cd\u5e94\u901a\u5e38\u4e0d\u7f13\u5b58&#034;<br \/>\n    headers: &#034;\u8bbe\u7f6e\u9002\u5f53\u7684Cache-Control&#034;<br \/>\n    consideration: &#034;Location\u6307\u5411\u7684\u8d44\u6e90\u53ef\u80fd\u53ef\u7f13\u5b58&#034;<\/p>\n<p>  # \u9519\u8bef\u5904\u7406<br \/>\n  error_handling:<br \/>\n    validation_errors: &#034;\u8fd4\u56de400 Bad Request&#034;<br \/>\n    permission_denied: &#034;\u8fd4\u56de403 Forbidden&#034;<br \/>\n    resource_conflict: &#034;\u8fd4\u56de409 Conflict&#034;<br \/>\n    rate_limited: &#034;\u8fd4\u56de429 Too Many Requests&#034;<br \/>\n    server_error: &#034;\u8fd4\u56de500 Internal Server Error&#034;<\/p>\n<p>  # \u5ba2\u6237\u7aef\u5904\u7406<br \/>\n  client_handling:<br \/>\n    check_status: &#034;\u9a8c\u8bc1\u72b6\u6001\u7801\u662f\u5426\u4e3a201&#034;<br \/>\n    parse_location: &#034;\u4eceLocation\u5934\u90e8\u83b7\u53d6\u8d44\u6e90URI&#034;<br \/>\n    follow_links: &#034;\u53ef\u4ee5\u4f7f\u7528Location URI\u8bbf\u95ee\u65b0\u8d44\u6e90&#034;<br \/>\n    handle_errors: &#034;\u51c6\u5907\u5904\u7406\u975e201\u54cd\u5e94&#034;<\/p>\n<p>  # \u7279\u6b8a\u60c5\u51b5<br \/>\n  special_cases:<br \/>\n    batch_creation: &#034;\u8003\u8651\u4f7f\u7528207 Multi-Status&#034;<br \/>\n    async_creation: &#034;\u8003\u8651\u4f7f\u7528202 Accepted&#034;<br \/>\n    duplicate_prevention: &#034;\u4f7f\u7528\u552f\u4e00\u7ea6\u675f\u548c409 Conflict&#034;<\/p>\n<p>  # \u76d1\u63a7\u548c\u65e5\u5fd7<br \/>\n  monitoring:<br \/>\n    success_rate: &#034;\u76d1\u63a7201\u54cd\u5e94\u6bd4\u4f8b&#034;<br \/>\n    response_time: &#034;\u76d1\u63a7\u521b\u5efa\u64cd\u4f5c\u5ef6\u8fdf&#034;<br \/>\n    error_rates: &#034;\u76d1\u63a7\u5404\u79cd\u9519\u8bef\u7684\u6bd4\u4f8b&#034;<br \/>\n    audit_trail: &#034;\u7ef4\u62a4\u5b8c\u6574\u7684\u521b\u5efa\u5ba1\u8ba1\u65e5\u5fd7&#034;<\/p>\n<p>\u901a\u8fc7\u672c\u7ae0\u7684\u8be6\u7ec6\u89e3\u6790&#xff0c;\u6211\u4eec\u6df1\u5165\u4e86\u89e3\u4e86201 Created\u72b6\u6001\u7801\u7684\u6b63\u786e\u7528\u6cd5\u3001\u6700\u4f73\u5b9e\u8df5\u3001\u5b89\u5168\u8003\u8651\u4ee5\u53ca\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u5668\u7684\u5b9e\u73b0\u7ec6\u8282\u3002\u6b63\u786e\u4f7f\u7528201 Created\u53ef\u4ee5\u663e\u8457\u6539\u5584API\u7684\u53ef\u7528\u6027\u3001\u53ef\u53d1\u73b0\u6027\u548c\u7528\u6237\u4f53\u9a8c\u3002<\/p>\n<h2>\u7b2c6\u7ae0&#xff1a;202 Accepted &#8211; \u5f02\u6b65\u5904\u7406\u63a5\u53d7<\/h2>\n<h3>6.1 202 Accepted\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666f<\/h3>\n<h4>RFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.3\u8282&#xff0c;202 Accepted\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5177\u6709\u4ee5\u4e0b\u5173\u952e\u542b\u4e49&#xff1a;<\/p>\n<p>&#034;202 (Accepted)\u72b6\u6001\u7801\u8868\u793a\u8bf7\u6c42\u5df2\u88ab\u63a5\u53d7\u5904\u7406&#xff0c;\u4f46\u5904\u7406\u5c1a\u672a\u5b8c\u6210\u3002\u8bf7\u6c42\u53ef\u80fd\u6700\u7ec8\u4f1a\u88ab\u6267\u884c&#xff0c;\u4e5f\u53ef\u80fd\u4e0d\u4f1a&#xff0c;\u56e0\u4e3a\u5728\u5904\u7406\u8fdb\u884c\u65f6\u53ef\u80fd\u4f1a\u88ab\u62d2\u7edd\u3002\u8fd9\u4e2a\u72b6\u6001\u7801\u6ca1\u6709\u81ea\u52a8\u91cd\u65b0\u53d1\u9001\u72b6\u6001\u7801\u7684\u529f\u80fd\u3002&#034;<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u975e\u627f\u8bfa\u6027\u63a5\u53d7&#xff1a;\u670d\u52a1\u5668\u63a5\u53d7\u8bf7\u6c42\u4f46\u4e0d\u4fdd\u8bc1\u6267\u884c\u5b8c\u6210<\/p>\n<\/li>\n<li>\n<p>\u5f02\u6b65\u5904\u7406&#xff1a;\u64cd\u4f5c\u5c06\u5728\u540e\u53f0\u5f02\u6b65\u6267\u884c<\/p>\n<\/li>\n<li>\n<p>\u65e0\u5373\u65f6\u7ed3\u679c&#xff1a;\u54cd\u5e94\u65f6\u4e0d\u63d0\u4f9b\u6700\u7ec8\u7ed3\u679c<\/p>\n<\/li>\n<li>\n<p>\u72b6\u6001\u8ddf\u8e2a&#xff1a;\u901a\u5e38\u9700\u8981\u63d0\u4f9b\u72b6\u6001\u67e5\u8be2\u673a\u5236<\/p>\n<\/li>\n<h4>\u9002\u7528\u573a\u666f\u77e9\u9635<\/h4>\n<p>python<\/p>\n<p># 202 Accepted\u9002\u7528\u573a\u666f\u5206\u6790\u5668<br \/>\nclass AcceptedUseCaseAnalyzer:<br \/>\n    &#034;&#034;&#034;\u5206\u6790202\u72b6\u6001\u7801\u7684\u9002\u7528\u573a\u666f&#034;&#034;&#034;<\/p>\n<p>    SCENARIOS &#061; {<br \/>\n        &#034;long_running_process&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u957f\u65f6\u95f4\u8fd0\u884c\u7684\u5904\u7406\u4efb\u52a1&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u89c6\u9891\u8f6c\u7801\u548c\u538b\u7f29&#034;,<br \/>\n                &#034;\u5927\u6570\u636e\u5206\u6790\u62a5\u544a\u751f\u6210&#034;,<br \/>\n                &#034;\u673a\u5668\u5b66\u4e60\u6a21\u578b\u8bad\u7ec3&#034;,<br \/>\n                &#034;\u590d\u6742\u6570\u636e\u5bfc\u51fa&#034;<br \/>\n            ],<br \/>\n            &#034;typical_duration&#034;: &#034;\u51e0\u5206\u949f\u5230\u51e0\u5c0f\u65f6&#034;,<br \/>\n            &#034;implementation_pattern&#034;: &#034;\u4efb\u52a1\u961f\u5217&#043;\u72b6\u6001\u8f6e\u8be2&#034;<br \/>\n        },<br \/>\n        &#034;external_dependency&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u4f9d\u8d56\u5916\u90e8\u7cfb\u7edf\u6216\u670d\u52a1\u7684\u64cd\u4f5c&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u652f\u4ed8\u7f51\u5173\u5904\u7406&#034;,<br \/>\n                &#034;\u7b2c\u4e09\u65b9API\u96c6\u6210&#034;,<br \/>\n                &#034;\u533a\u5757\u94fe\u4ea4\u6613\u786e\u8ba4&#034;,<br \/>\n                &#034;\u77ed\u4fe1\/\u90ae\u4ef6\u53d1\u9001&#034;<br \/>\n            ],<br \/>\n            &#034;typical_duration&#034;: &#034;\u51e0\u79d2\u5230\u51e0\u5206\u949f&#034;,<br \/>\n            &#034;implementation_pattern&#034;: &#034;\u56de\u8c03\u6216\u8f6e\u8be2&#034;<br \/>\n        },<br \/>\n        &#034;manual_review&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u9700\u8981\u4eba\u5de5\u5ba1\u6838\u7684\u64cd\u4f5c&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u5185\u5bb9\u5ba1\u6838&#xff08;\u56fe\u7247\u3001\u6587\u672c&#xff09;&#034;,<br \/>\n                &#034;\u7528\u6237\u6ce8\u518c\u5ba1\u6838&#034;,<br \/>\n                &#034;\u63d0\u73b0\u7533\u8bf7\u5ba1\u6279&#034;,<br \/>\n                &#034;\u654f\u611f\u64cd\u4f5c\u786e\u8ba4&#034;<br \/>\n            ],<br \/>\n            &#034;typical_duration&#034;: &#034;\u51e0\u5c0f\u65f6\u5230\u51e0\u5929&#034;,<br \/>\n            &#034;implementation_pattern&#034;: &#034;\u5de5\u4f5c\u6d41\u5f15\u64ce&#043;\u901a\u77e5&#034;<br \/>\n        },<br \/>\n        &#034;batch_processing&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u6279\u91cf\u6570\u636e\u5904\u7406&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u6279\u91cf\u7528\u6237\u5bfc\u5165&#034;,<br \/>\n                &#034;\u6570\u636e\u8fc1\u79fb&#034;,<br \/>\n                &#034;\u62a5\u8868\u751f\u6210&#034;,<br \/>\n                &#034;\u7cfb\u7edf\u5907\u4efd&#034;<br \/>\n            ],<br \/>\n            &#034;typical_duration&#034;: &#034;\u51e0\u5341\u5206\u949f\u5230\u51e0\u5c0f\u65f6&#034;,<br \/>\n            &#034;implementation_pattern&#034;: &#034;\u6279\u5904\u7406\u4efb\u52a1&#043;\u8fdb\u5ea6\u8ddf\u8e2a&#034;<br \/>\n        },<br \/>\n        &#034;resource_intensive&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u8d44\u6e90\u5bc6\u96c6\u578b\u64cd\u4f5c&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;3D\u6e32\u67d3&#034;,<br \/>\n                &#034;\u79d1\u5b66\u8ba1\u7b97&#034;,<br \/>\n                &#034;\u5927\u89c4\u6a21\u56fe\u50cf\u5904\u7406&#034;,<br \/>\n                &#034;\u5bc6\u7801\u5b66\u8fd0\u7b97&#034;<br \/>\n            ],<br \/>\n            &#034;typical_duration&#034;: &#034;\u51e0\u79d2\u5230\u51e0\u5c0f\u65f6&#034;,<br \/>\n            &#034;implementation_pattern&#034;: &#034;\u8ba1\u7b97\u96c6\u7fa4&#043;\u4efb\u52a1\u8c03\u5ea6&#034;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    def is_appropriate(self, request_characteristics):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u5224\u65ad\u662f\u5426\u9002\u5408\u4f7f\u7528202 Accepted<\/p>\n<p>        Args:<br \/>\n            request_characteristics: {<br \/>\n                &#034;estimated_duration&#034;: \u79d2\u6570,<br \/>\n                &#034;requires_external_service&#034;: bool,<br \/>\n                &#034;needs_human_review&#034;: bool,<br \/>\n                &#034;resource_intensity&#034;: &#034;low|medium|high&#034;,<br \/>\n                &#034;can_be_cancelled&#034;: bool<br \/>\n            }<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        criteria &#061; [<br \/>\n            # \u6301\u7eed\u65f6\u95f4\u6807\u51c6<br \/>\n            request_characteristics.get(&#034;estimated_duration&#034;, 0) &gt; 30,<\/p>\n<p>            # \u5916\u90e8\u4f9d\u8d56\u6807\u51c6<br \/>\n            request_characteristics.get(&#034;requires_external_service&#034;, False),<\/p>\n<p>            # \u4eba\u5de5\u5ba1\u6838\u6807\u51c6<br \/>\n            request_characteristics.get(&#034;needs_human_review&#034;, False),<\/p>\n<p>            # \u8d44\u6e90\u5f3a\u5ea6\u6807\u51c6<br \/>\n            request_characteristics.get(&#034;resource_intensity&#034;) in [&#034;medium&#034;, &#034;high&#034;],<\/p>\n<p>            # \u53ef\u53d6\u6d88\u6027\u6807\u51c6&#xff08;\u5f02\u6b65\u4efb\u52a1\u901a\u5e38\u53ef\u53d6\u6d88&#xff09;<br \/>\n            request_characteristics.get(&#034;can_be_cancelled&#034;, False)<br \/>\n        ]<\/p>\n<p>        # \u5982\u679c\u6ee1\u8db3\u4efb\u610f\u4e24\u4e2a\u6761\u4ef6&#xff0c;\u5efa\u8bae\u4f7f\u7528202<br \/>\n        return sum(criteria) &gt;&#061; 2<\/p>\n<h4 style=\"background-color:transparent\">202 vs 201 vs 200 \u51b3\u7b56\u6d41\u7a0b\u56fe<img loading=\"lazy\" decoding=\"async\" alt=\"\" height=\"1936\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/11\/20261104055658-6aeac92aa2363.png\" width=\"2690\" \/><\/h4>\n<h3>6.2 \u54cd\u5e94\u5934\u90e8\u4e0e\u72b6\u6001\u8ddf\u8e2a<\/h3>\n<h4>\u5fc5\u9700\u4e0e\u63a8\u8350\u7684\u5934\u90e8<\/h4>\n<p>http<\/p>\n<p># \u5b8c\u6574\u7684202 Accepted\u54cd\u5e94\u793a\u4f8b<br \/>\nHTTP\/1.1 202 Accepted<br \/>\nDate: Mon, 23 Jan 2023 10:30:45 GMT<br \/>\nContent-Type: application\/json<br \/>\nContent-Length: 423<br \/>\nLocation: https:\/\/api.example.com\/tasks\/550e8400-e29b-41d4-a716-446655440000<br \/>\nRetry-After: 30<br \/>\nX-Request-ID: req_1234567890abcdef<br \/>\nX-Task-ID: task_550e8400-e29b-41d4-a716-446655440000<br \/>\nX-Task-Status-URL: https:\/\/api.example.com\/tasks\/550e8400-e29b-41d4-a716-446655440000\/status<br \/>\nX-Estimated-Completion: Mon, 23 Jan 2023 10:35:45 GMT<br \/>\nCache-Control: no-store, no-cache, must-revalidate<br \/>\nExpires: 0<br \/>\nPragma: no-cache<\/p>\n<p>{<br \/>\n  &#034;task&#034;: {<br \/>\n    &#034;id&#034;: &#034;550e8400-e29b-41d4-a716-446655440000&#034;,<br \/>\n    &#034;status&#034;: &#034;pending&#034;,<br \/>\n    &#034;message&#034;: &#034;Your request has been accepted and is being processed&#034;,<br \/>\n    &#034;created_at&#034;: &#034;2023-01-23T10:30:45Z&#034;,<br \/>\n    &#034;estimated_completion&#034;: &#034;2023-01-23T10:35:45Z&#034;,<br \/>\n    &#034;progress&#034;: 0,<br \/>\n    &#034;links&#034;: {<br \/>\n      &#034;self&#034;: {<br \/>\n        &#034;href&#034;: &#034;https:\/\/api.example.com\/tasks\/550e8400-e29b-41d4-a716-446655440000&#034;,<br \/>\n        &#034;method&#034;: &#034;GET&#034;<br \/>\n      },<br \/>\n      &#034;status&#034;: {<br \/>\n        &#034;href&#034;: &#034;https:\/\/api.example.com\/tasks\/550e8400-e29b-41d4-a716-446655440000\/status&#034;,<br \/>\n        &#034;method&#034;: &#034;GET&#034;<br \/>\n      },<br \/>\n      &#034;cancel&#034;: {<br \/>\n        &#034;href&#034;: &#034;https:\/\/api.example.com\/tasks\/550e8400-e29b-41d4-a716-446655440000\/cancel&#034;,<br \/>\n        &#034;method&#034;: &#034;POST&#034;<br \/>\n      }<br \/>\n    }<br \/>\n  },<br \/>\n  &#034;metadata&#034;: {<br \/>\n    &#034;api_version&#034;: &#034;v1&#034;,<br \/>\n    &#034;request_id&#034;: &#034;req_1234567890abcdef&#034;,<br \/>\n    &#034;timestamp&#034;: &#034;2023-01-23T10:30:45Z&#034;<br \/>\n  }<br \/>\n}<\/p>\n<h4>\u72b6\u6001\u8ddf\u8e2a\u7aef\u70b9\u8bbe\u8ba1<\/h4>\n<p>python<\/p>\n<p># \u5b8c\u6574\u7684\u72b6\u6001\u8ddf\u8e2a\u7cfb\u7edf\u5b9e\u73b0<br \/>\nfrom datetime import datetime, timedelta<br \/>\nfrom enum import Enum<br \/>\nimport uuid<br \/>\nimport json<br \/>\nfrom typing import Dict, Any, Optional, List<br \/>\nfrom dataclasses import dataclass, asdict<br \/>\nfrom abc import ABC, abstractmethod<\/p>\n<p>class TaskStatus(Enum):<br \/>\n    &#034;&#034;&#034;\u4efb\u52a1\u72b6\u6001\u679a\u4e3e&#034;&#034;&#034;<br \/>\n    PENDING &#061; &#034;pending&#034;<br \/>\n    PROCESSING &#061; &#034;processing&#034;<br \/>\n    COMPLETED &#061; &#034;completed&#034;<br \/>\n    FAILED &#061; &#034;failed&#034;<br \/>\n    CANCELLED &#061; &#034;cancelled&#034;<br \/>\n    RETRYING &#061; &#034;retrying&#034;<\/p>\n<p>&#064;dataclass<br \/>\nclass TaskProgress:<br \/>\n    &#034;&#034;&#034;\u4efb\u52a1\u8fdb\u5ea6\u4fe1\u606f&#034;&#034;&#034;<br \/>\n    current: int &#061; 0<br \/>\n    total: int &#061; 100<br \/>\n    percentage: float &#061; 0.0<br \/>\n    phase: str &#061; &#034;initializing&#034;<br \/>\n    message: str &#061; &#034;&#034;<\/p>\n<p>    def update(self, current: int, total: int &#061; 100, phase: str &#061; None, message: str &#061; None):<br \/>\n        self.current &#061; current<br \/>\n        self.total &#061; total<br \/>\n        self.percentage &#061; (current \/ total) * 100 if total &gt; 0 else 0<br \/>\n        if phase:<br \/>\n            self.phase &#061; phase<br \/>\n        if message:<br \/>\n            self.message &#061; message<\/p>\n<p>    def to_dict(self):<br \/>\n        return asdict(self)<\/p>\n<p>&#064;dataclass<br \/>\nclass TaskResult:<br \/>\n    &#034;&#034;&#034;\u4efb\u52a1\u7ed3\u679c&#034;&#034;&#034;<br \/>\n    success: bool &#061; True<br \/>\n    data: Optional[Dict[str, Any]] &#061; None<br \/>\n    error: Optional[str] &#061; None<br \/>\n    error_code: Optional[str] &#061; None<br \/>\n    warnings: List[str] &#061; None<\/p>\n<p>    def __post_init__(self):<br \/>\n        if self.warnings is None:<br \/>\n            self.warnings &#061; []<\/p>\n<p>    def to_dict(self):<br \/>\n        result &#061; asdict(self)<br \/>\n        # \u8fc7\u6ee4None\u503c<br \/>\n        return {k: v for k, v in result.items() if v is not None}<\/p>\n<p>class Task:<br \/>\n    &#034;&#034;&#034;\u5f02\u6b65\u4efb\u52a1\u6a21\u578b&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, task_type: str, input_data: Dict[str, Any], creator: str &#061; None):<br \/>\n        self.id &#061; str(uuid.uuid4())<br \/>\n        self.type &#061; task_type<br \/>\n        self.input_data &#061; input_data<br \/>\n        self.creator &#061; creator<br \/>\n        self.created_at &#061; datetime.utcnow()<br \/>\n        self.updated_at &#061; self.created_at<br \/>\n        self.status &#061; TaskStatus.PENDING<br \/>\n        self.progress &#061; TaskProgress()<br \/>\n        self.result &#061; None<br \/>\n        self.metadata &#061; {<br \/>\n            &#034;retry_count&#034;: 0,<br \/>\n            &#034;max_retries&#034;: 3,<br \/>\n            &#034;priority&#034;: &#034;normal&#034;,<br \/>\n            &#034;timeout&#034;: 3600  # \u9ed8\u8ba41\u5c0f\u65f6\u8d85\u65f6<br \/>\n        }<br \/>\n        self.links &#061; {}<\/p>\n<p>    def update_status(self, status: TaskStatus, message: str &#061; None):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u4efb\u52a1\u72b6\u6001&#034;&#034;&#034;<br \/>\n        self.status &#061; status<br \/>\n        self.updated_at &#061; datetime.utcnow()<br \/>\n        if message and self.progress:<br \/>\n            self.progress.message &#061; message<\/p>\n<p>    def set_progress(self, current: int, total: int &#061; 100, phase: str &#061; None, message: str &#061; None):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8fdb\u5ea6&#034;&#034;&#034;<br \/>\n        self.progress.update(current, total, phase, message)<br \/>\n        self.updated_at &#061; datetime.utcnow()<\/p>\n<p>    def complete(self, result_data: Dict[str, Any] &#061; None):<br \/>\n        &#034;&#034;&#034;\u5b8c\u6210\u4efb\u52a1&#034;&#034;&#034;<br \/>\n        self.result &#061; TaskResult(success&#061;True, data&#061;result_data)<br \/>\n        self.update_status(TaskStatus.COMPLETED, &#034;Task completed successfully&#034;)<br \/>\n        self.set_progress(100, 100, &#034;completed&#034;)<\/p>\n<p>    def fail(self, error: str, error_code: str &#061; None):<br \/>\n        &#034;&#034;&#034;\u4efb\u52a1\u5931\u8d25&#034;&#034;&#034;<br \/>\n        self.result &#061; TaskResult(success&#061;False, error&#061;error, error_code&#061;error_code)<br \/>\n        self.update_status(TaskStatus.FAILED, f&#034;Task failed: {error}&#034;)<\/p>\n<p>    def cancel(self):<br \/>\n        &#034;&#034;&#034;\u53d6\u6d88\u4efb\u52a1&#034;&#034;&#034;<br \/>\n        self.update_status(TaskStatus.CANCELLED, &#034;Task cancelled by user&#034;)<\/p>\n<p>    def to_response_dict(self, request) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u8f6c\u6362\u4e3aAPI\u54cd\u5e94\u683c\u5f0f&#034;&#034;&#034;<br \/>\n        base_url &#061; f&#034;{request.scheme}:\/\/{request.get_host()}&#034;<\/p>\n<p>        # \u6784\u5efa\u94fe\u63a5<br \/>\n        self.links &#061; {<br \/>\n            &#034;self&#034;: {<br \/>\n                &#034;href&#034;: f&#034;{base_url}\/api\/tasks\/{self.id}&#034;,<br \/>\n                &#034;method&#034;: &#034;GET&#034;,<br \/>\n                &#034;description&#034;: &#034;Get task details&#034;<br \/>\n            },<br \/>\n            &#034;status&#034;: {<br \/>\n                &#034;href&#034;: f&#034;{base_url}\/api\/tasks\/{self.id}\/status&#034;,<br \/>\n                &#034;method&#034;: &#034;GET&#034;,<br \/>\n                &#034;description&#034;: &#034;Get task status&#034;<br \/>\n            },<br \/>\n            &#034;cancel&#034;: {<br \/>\n                &#034;href&#034;: f&#034;{base_url}\/api\/tasks\/{self.id}\/cancel&#034;,<br \/>\n                &#034;method&#034;: &#034;POST&#034;,<br \/>\n                &#034;description&#034;: &#034;Cancel the task&#034;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # \u5982\u679c\u5df2\u5b8c\u6210&#xff0c;\u6dfb\u52a0\u7ed3\u679c\u94fe\u63a5<br \/>\n        if self.status &#061;&#061; TaskStatus.COMPLETED and self.result and self.result.data:<br \/>\n            resource_id &#061; self.result.data.get(&#034;id&#034;)<br \/>\n            if resource_id:<br \/>\n                self.links[&#034;result&#034;] &#061; {<br \/>\n                    &#034;href&#034;: f&#034;{base_url}\/api\/resources\/{resource_id}&#034;,<br \/>\n                    &#034;method&#034;: &#034;GET&#034;,<br \/>\n                    &#034;description&#034;: &#034;Get the created resource&#034;<br \/>\n                }<\/p>\n<p>        response &#061; {<br \/>\n            &#034;task&#034;: {<br \/>\n                &#034;id&#034;: self.id,<br \/>\n                &#034;type&#034;: self.type,<br \/>\n                &#034;status&#034;: self.status.value,<br \/>\n                &#034;progress&#034;: self.progress.to_dict(),<br \/>\n                &#034;created_at&#034;: self.created_at.isoformat(),<br \/>\n                &#034;updated_at&#034;: self.updated_at.isoformat(),<br \/>\n                &#034;estimated_completion&#034;: self._estimate_completion(),<br \/>\n                &#034;links&#034;: self.links<br \/>\n            },<br \/>\n            &#034;metadata&#034;: {<br \/>\n                &#034;api_version&#034;: &#034;v1&#034;,<br \/>\n                &#034;request_id&#034;: request.headers.get(&#034;X-Request-ID&#034;, &#034;&#034;),<br \/>\n                &#034;timestamp&#034;: datetime.utcnow().isoformat()<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # \u6dfb\u52a0\u8f93\u5165\u6570\u636e\u6458\u8981&#xff08;\u4e0d\u5305\u542b\u654f\u611f\u4fe1\u606f&#xff09;<br \/>\n        if self.input_data:<br \/>\n            response[&#034;task&#034;][&#034;input_summary&#034;] &#061; self._summarize_input(self.input_data)<\/p>\n<p>        return response<\/p>\n<p>    def _estimate_completion(self) -&gt; str:<br \/>\n        &#034;&#034;&#034;\u4f30\u7b97\u5b8c\u6210\u65f6\u95f4&#034;&#034;&#034;<br \/>\n        if self.status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED]:<br \/>\n            return self.updated_at.isoformat()<\/p>\n<p>        # \u57fa\u4e8e\u8fdb\u5ea6\u4f30\u7b97<br \/>\n        if self.progress.percentage &gt; 0:<br \/>\n            elapsed &#061; (datetime.utcnow() &#8211; self.created_at).total_seconds()<br \/>\n            if self.progress.percentage &lt; 100:<br \/>\n                remaining &#061; elapsed * (100 &#8211; self.progress.percentage) \/ self.progress.percentage<br \/>\n                estimated &#061; datetime.utcnow() &#043; timedelta(seconds&#061;remaining)<br \/>\n                return estimated.isoformat()<\/p>\n<p>        # \u9ed8\u8ba4&#xff1a;\u521b\u5efa\u65f6\u95f4 &#043; \u8d85\u65f6\u65f6\u95f4<br \/>\n        default_estimated &#061; self.created_at &#043; timedelta(seconds&#061;self.metadata[&#034;timeout&#034;])<br \/>\n        return default_estimated.isoformat()<\/p>\n<p>    def _summarize_input(self, input_data: Dict[str, Any]) -&gt; Dict[str, Any]:<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u8f93\u5165\u6570\u636e\u6458\u8981&#xff0c;\u8fc7\u6ee4\u654f\u611f\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        summary &#061; {}<\/p>\n<p>        for key, value in input_data.items():<br \/>\n            if isinstance(value, dict):<br \/>\n                summary[key] &#061; {&#034;type&#034;: &#034;object&#034;, &#034;keys&#034;: list(value.keys())}<br \/>\n            elif isinstance(value, list):<br \/>\n                summary[key] &#061; {&#034;type&#034;: &#034;array&#034;, &#034;count&#034;: len(value)}<br \/>\n            elif isinstance(value, str):<br \/>\n                # \u654f\u611f\u5b57\u6bb5\u5904\u7406<br \/>\n                if any(sensitive in key.lower() for sensitive in [&#034;password&#034;, &#034;token&#034;, &#034;secret&#034;, &#034;key&#034;]):<br \/>\n                    summary[key] &#061; {&#034;type&#034;: &#034;string&#034;, &#034;length&#034;: len(value), &#034;masked&#034;: True}<br \/>\n                else:<br \/>\n                    summary[key] &#061; {&#034;type&#034;: &#034;string&#034;, &#034;length&#034;: len(value)}<br \/>\n            elif isinstance(value, (int, float, bool)):<br \/>\n                summary[key] &#061; {&#034;type&#034;: type(value).__name__, &#034;value&#034;: value}<br \/>\n            else:<br \/>\n                summary[key] &#061; {&#034;type&#034;: str(type(value))}<\/p>\n<p>        return summary<\/p>\n<p>class TaskManager:<br \/>\n    &#034;&#034;&#034;\u4efb\u52a1\u7ba1\u7406\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.tasks: Dict[str, Task] &#061; {}<br \/>\n        self.task_processors &#061; {}<\/p>\n<p>    def create_task(self, task_type: str, input_data: Dict[str, Any], creator: str &#061; None) -&gt; Task:<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u65b0\u4efb\u52a1&#034;&#034;&#034;<br \/>\n        task &#061; Task(task_type, input_data, creator)<br \/>\n        self.tasks[task.id] &#061; task<\/p>\n<p>        # \u6839\u636e\u4efb\u52a1\u7c7b\u578b\u542f\u52a8\u5904\u7406\u5668<br \/>\n        if task_type in self.task_processors:<br \/>\n            processor &#061; self.task_processors[task_type]<br \/>\n            processor.start(task)<\/p>\n<p>        return task<\/p>\n<p>    def get_task(self, task_id: str) -&gt; Optional[Task]:<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u4efb\u52a1&#034;&#034;&#034;<br \/>\n        return self.tasks.get(task_id)<\/p>\n<p>    def register_processor(self, task_type: str, processor):<br \/>\n        &#034;&#034;&#034;\u6ce8\u518c\u4efb\u52a1\u5904\u7406\u5668&#034;&#034;&#034;<br \/>\n        self.task_processors[task_type] &#061; processor<\/p>\n<p>    def cleanup_old_tasks(self, max_age_hours: int &#061; 24):<br \/>\n        &#034;&#034;&#034;\u6e05\u7406\u65e7\u4efb\u52a1&#034;&#034;&#034;<br \/>\n        cutoff_time &#061; datetime.utcnow() &#8211; timedelta(hours&#061;max_age_hours)<\/p>\n<p>        to_delete &#061; []<br \/>\n        for task_id, task in self.tasks.items():<br \/>\n            if task.updated_at &lt; cutoff_time:<br \/>\n                to_delete.append(task_id)<\/p>\n<p>        for task_id in to_delete:<br \/>\n            del self.tasks[task_id]<\/p>\n<p>        return len(to_delete)<\/p>\n<p># \u793a\u4f8b\u4efb\u52a1\u5904\u7406\u5668<br \/>\nclass VideoTranscodingProcessor:<br \/>\n    &#034;&#034;&#034;\u89c6\u9891\u8f6c\u7801\u5904\u7406\u5668&#034;&#034;&#034;<\/p>\n<p>    def start(self, task: Task):<br \/>\n        &#034;&#034;&#034;\u5f00\u59cb\u5904\u7406&#034;&#034;&#034;<br \/>\n        # \u5728\u5b9e\u9645\u5e94\u7528\u4e2d&#xff0c;\u8fd9\u91cc\u4f1a\u5c06\u4efb\u52a1\u653e\u5165\u961f\u5217<br \/>\n        # \u8fd9\u91cc\u4f7f\u7528\u6a21\u62df\u5904\u7406<\/p>\n<p>        import threading<br \/>\n        thread &#061; threading.Thread(target&#061;self._process, args&#061;(task,))<br \/>\n        thread.daemon &#061; True<br \/>\n        thread.start()<\/p>\n<p>    def _process(self, task: Task):<br \/>\n        &#034;&#034;&#034;\u6a21\u62df\u89c6\u9891\u8f6c\u7801\u5904\u7406&#034;&#034;&#034;<br \/>\n        try:<br \/>\n            task.update_status(TaskStatus.PROCESSING, &#034;Starting video transcoding&#034;)<\/p>\n<p>            # \u6a21\u62df\u5904\u7406\u6b65\u9aa4<br \/>\n            steps &#061; [<br \/>\n                (&#034;validating_input&#034;, 10, &#034;Validating input file&#034;),<br \/>\n                (&#034;extracting_metadata&#034;, 20, &#034;Extracting video metadata&#034;),<br \/>\n                (&#034;transcoding_video&#034;, 60, &#034;Transcoding video&#034;),<br \/>\n                (&#034;encoding_audio&#034;, 80, &#034;Encoding audio&#034;),<br \/>\n                (&#034;finalizing&#034;, 100, &#034;Finalizing output&#034;)<br \/>\n            ]<\/p>\n<p>            for phase, progress, message in steps:<br \/>\n                import time<br \/>\n                time.sleep(2)  # \u6a21\u62df\u5904\u7406\u65f6\u95f4<br \/>\n                task.set_progress(progress, 100, phase, message)<\/p>\n<p>            # \u5b8c\u6210\u4efb\u52a1<br \/>\n            output_data &#061; {<br \/>\n                &#034;video_id&#034;: f&#034;vid_{uuid.uuid4().hex[:8]}&#034;,<br \/>\n                &#034;format&#034;: &#034;mp4&#034;,<br \/>\n                &#034;resolution&#034;: &#034;1920&#215;1080&#034;,<br \/>\n                &#034;duration&#034;: 120,<br \/>\n                &#034;size_mb&#034;: 45.7,<br \/>\n                &#034;url&#034;: f&#034;https:\/\/cdn.example.com\/videos\/vid_{uuid.uuid4().hex[:8]}.mp4&#034;<br \/>\n            }<\/p>\n<p>            task.complete(output_data)<\/p>\n<p>        except Exception as e:<br \/>\n            task.fail(f&#034;Transcoding failed: {str(e)}&#034;, &#034;TRANSCODING_ERROR&#034;)<\/p>\n<h3>6.3 \u5b9e\u73b0\u6a21\u5f0f\u4e0e\u67b6\u6784<\/h3>\n<h4>\u5f02\u6b65\u5904\u7406\u67b6\u6784\u6a21\u5f0f<\/h4>\n<p>python<\/p>\n<p># \u5b8c\u6574\u7684\u5f02\u6b65\u5904\u7406\u7cfb\u7edf\u67b6\u6784<br \/>\nfrom flask import Flask, request, jsonify, Blueprint<br \/>\nfrom celery import Celery<br \/>\nfrom datetime import datetime<br \/>\nimport redis<br \/>\nimport json<\/p>\n<p># \u521b\u5efaFlask\u5e94\u7528<br \/>\napp &#061; Flask(__name__)<br \/>\napp.config.update(<br \/>\n    CELERY_BROKER_URL&#061;&#039;redis:\/\/localhost:6379\/0&#039;,<br \/>\n    CELERY_RESULT_BACKEND&#061;&#039;redis:\/\/localhost:6379\/0&#039;,<br \/>\n    TASK_TIMEOUT&#061;3600,<br \/>\n    MAX_RETRIES&#061;3<br \/>\n)<\/p>\n<p># \u521d\u59cb\u5316Celery<br \/>\ndef make_celery(app):<br \/>\n    celery &#061; Celery(<br \/>\n        app.import_name,<br \/>\n        backend&#061;app.config[&#039;CELERY_RESULT_BACKEND&#039;],<br \/>\n        broker&#061;app.config[&#039;CELERY_BROKER_URL&#039;]<br \/>\n    )<br \/>\n    celery.conf.update(app.config)<\/p>\n<p>    class ContextTask(celery.Task):<br \/>\n        def __call__(self, *args, **kwargs):<br \/>\n            with app.app_context():<br \/>\n                return self.run(*args, **kwargs)<\/p>\n<p>    celery.Task &#061; ContextTask<br \/>\n    return celery<\/p>\n<p>celery &#061; make_celery(app)<\/p>\n<p># Redis\u8fde\u63a5<br \/>\nredis_client &#061; redis.Redis(host&#061;&#039;localhost&#039;, port&#061;6379, db&#061;1)<\/p>\n<p># \u4efb\u52a1\u72b6\u6001\u7ba1\u7406<br \/>\nclass TaskStatusManager:<br \/>\n    &#034;&#034;&#034;\u57fa\u4e8eRedis\u7684\u4efb\u52a1\u72b6\u6001\u7ba1\u7406&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, redis_client):<br \/>\n        self.redis &#061; redis_client<br \/>\n        self.status_key_prefix &#061; &#034;task:status:&#034;<br \/>\n        self.result_key_prefix &#061; &#034;task:result:&#034;<\/p>\n<p>    def create_task(self, task_type, input_data, metadata&#061;None):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u65b0\u4efb\u52a1\u8bb0\u5f55&#034;&#034;&#034;<br \/>\n        task_id &#061; f&#034;task_{datetime.utcnow().strftime(&#039;%Y%m%d_%H%M%S&#039;)}_{uuid.uuid4().hex[:8]}&#034;<\/p>\n<p>        task_info &#061; {<br \/>\n            &#034;id&#034;: task_id,<br \/>\n            &#034;type&#034;: task_type,<br \/>\n            &#034;status&#034;: &#034;pending&#034;,<br \/>\n            &#034;input&#034;: input_data,<br \/>\n            &#034;metadata&#034;: metadata or {},<br \/>\n            &#034;created_at&#034;: datetime.utcnow().isoformat(),<br \/>\n            &#034;updated_at&#034;: datetime.utcnow().isoformat(),<br \/>\n            &#034;progress&#034;: {<br \/>\n                &#034;current&#034;: 0,<br \/>\n                &#034;total&#034;: 100,<br \/>\n                &#034;percentage&#034;: 0,<br \/>\n                &#034;message&#034;: &#034;Task created&#034;<br \/>\n            }<br \/>\n        }<\/p>\n<p>        # \u5b58\u50a8\u5230Redis&#xff0c;\u8bbe\u7f6e\u8fc7\u671f\u65f6\u95f4&#xff08;24\u5c0f\u65f6&#xff09;<br \/>\n        key &#061; self.status_key_prefix &#043; task_id<br \/>\n        self.redis.setex(<br \/>\n            key,<br \/>\n            24 * 3600,  # 24\u5c0f\u65f6<br \/>\n            json.dumps(task_info)<br \/>\n        )<\/p>\n<p>        return task_id, task_info<\/p>\n<p>    def update_status(self, task_id, status, progress&#061;None, message&#061;None):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u4efb\u52a1\u72b6\u6001&#034;&#034;&#034;<br \/>\n        key &#061; self.status_key_prefix &#043; task_id<br \/>\n        task_json &#061; self.redis.get(key)<\/p>\n<p>        if not task_json:<br \/>\n            return False<\/p>\n<p>        task_info &#061; json.loads(task_json)<br \/>\n        task_info[&#034;status&#034;] &#061; status<br \/>\n        task_info[&#034;updated_at&#034;] &#061; datetime.utcnow().isoformat()<\/p>\n<p>        if progress:<br \/>\n            task_info[&#034;progress&#034;] &#061; progress<\/p>\n<p>        if message and &#034;progress&#034; in task_info:<br \/>\n            task_info[&#034;progress&#034;][&#034;message&#034;] &#061; message<\/p>\n<p>        # \u66f4\u65b0Redis<br \/>\n        ttl &#061; self.redis.ttl(key)<br \/>\n        self.redis.setex(key, ttl if ttl &gt; 0 else 24*3600, json.dumps(task_info))<\/p>\n<p>        return True<\/p>\n<p>    def store_result(self, task_id, result_data, success&#061;True):<br \/>\n        &#034;&#034;&#034;\u5b58\u50a8\u4efb\u52a1\u7ed3\u679c&#034;&#034;&#034;<br \/>\n        result_info &#061; {<br \/>\n            &#034;task_id&#034;: task_id,<br \/>\n            &#034;success&#034;: success,<br \/>\n            &#034;data&#034;: result_data,<br \/>\n            &#034;completed_at&#034;: datetime.utcnow().isoformat()<br \/>\n        }<\/p>\n<p>        key &#061; self.result_key_prefix &#043; task_id<br \/>\n        self.redis.setex(key, 7 * 24 * 3600, json.dumps(result_info))  # \u4fdd\u75597\u5929<\/p>\n<p>        # \u66f4\u65b0\u72b6\u6001\u4e3a\u5b8c\u6210<br \/>\n        self.update_status(<br \/>\n            task_id,<br \/>\n            &#034;completed&#034; if success else &#034;failed&#034;,<br \/>\n            {&#034;current&#034;: 100, &#034;total&#034;: 100, &#034;percentage&#034;: 100, &#034;message&#034;: &#034;Task completed&#034;}<br \/>\n        )<\/p>\n<p>    def get_task_info(self, task_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u4efb\u52a1\u4fe1\u606f&#034;&#034;&#034;<br \/>\n        key &#061; self.status_key_prefix &#043; task_id<br \/>\n        task_json &#061; self.redis.get(key)<\/p>\n<p>        if not task_json:<br \/>\n            return None<\/p>\n<p>        task_info &#061; json.loads(task_json)<\/p>\n<p>        # \u83b7\u53d6\u7ed3\u679c&#xff08;\u5982\u679c\u5df2\u5b8c\u6210&#xff09;<br \/>\n        if task_info[&#034;status&#034;] in [&#034;completed&#034;, &#034;failed&#034;]:<br \/>\n            result_key &#061; self.result_key_prefix &#043; task_id<br \/>\n            result_json &#061; self.redis.get(result_key)<br \/>\n            if result_json:<br \/>\n                result_info &#061; json.loads(result_json)<br \/>\n                task_info[&#034;result&#034;] &#061; result_info<\/p>\n<p>        return task_info<\/p>\n<p># \u521d\u59cb\u5316\u4efb\u52a1\u7ba1\u7406\u5668<br \/>\ntask_manager &#061; TaskStatusManager(redis_client)<\/p>\n<p># Celery\u4efb\u52a1\u5b9a\u4e49<br \/>\n&#064;celery.task(bind&#061;True, max_retries&#061;3)<br \/>\ndef process_video_task(self, task_id, input_data):<br \/>\n    &#034;&#034;&#034;\u89c6\u9891\u5904\u7406Celery\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    from celery.exceptions import MaxRetriesExceededError<\/p>\n<p>    try:<br \/>\n        # \u66f4\u65b0\u72b6\u6001\u4e3a\u5904\u7406\u4e2d<br \/>\n        task_manager.update_status(<br \/>\n            task_id,<br \/>\n            &#034;processing&#034;,<br \/>\n            {&#034;current&#034;: 10, &#034;total&#034;: 100, &#034;percentage&#034;: 10, &#034;message&#034;: &#034;Starting video processing&#034;}<br \/>\n        )<\/p>\n<p>        # \u6a21\u62df\u5904\u7406\u6b65\u9aa4<br \/>\n        steps &#061; [<br \/>\n            (20, &#034;Validating video format&#034;),<br \/>\n            (40, &#034;Extracting metadata&#034;),<br \/>\n            (60, &#034;Processing video&#034;),<br \/>\n            (80, &#034;Encoding output&#034;),<br \/>\n            (100, &#034;Finalizing&#034;)<br \/>\n        ]<\/p>\n<p>        for progress, message in steps:<br \/>\n            # \u6a21\u62df\u5904\u7406\u65f6\u95f4<br \/>\n            import time<br \/>\n            time.sleep(5)<\/p>\n<p>            # \u66f4\u65b0\u8fdb\u5ea6<br \/>\n            task_manager.update_status(<br \/>\n                task_id,<br \/>\n                &#034;processing&#034;,<br \/>\n                {&#034;current&#034;: progress, &#034;total&#034;: 100, &#034;percentage&#034;: progress, &#034;message&#034;: message}<br \/>\n            )<\/p>\n<p>            # \u68c0\u67e5\u662f\u5426\u88ab\u53d6\u6d88<br \/>\n            current_status &#061; task_manager.get_task_info(task_id)<br \/>\n            if current_status and current_status[&#034;status&#034;] &#061;&#061; &#034;cancelled&#034;:<br \/>\n                return {&#034;status&#034;: &#034;cancelled&#034;, &#034;task_id&#034;: task_id}<\/p>\n<p>        # \u5b8c\u6210\u4efb\u52a1<br \/>\n        result_data &#061; {<br \/>\n            &#034;video_id&#034;: f&#034;vid_{uuid.uuid4().hex[:8]}&#034;,<br \/>\n            &#034;status&#034;: &#034;processed&#034;,<br \/>\n            &#034;download_url&#034;: f&#034;https:\/\/cdn.example.com\/videos\/{uuid.uuid4().hex[:8]}.mp4&#034;<br \/>\n        }<\/p>\n<p>        task_manager.store_result(task_id, result_data, success&#061;True)<\/p>\n<p>        return {&#034;status&#034;: &#034;success&#034;, &#034;result&#034;: result_data}<\/p>\n<p>    except Exception as e:<br \/>\n        # \u8bb0\u5f55\u9519\u8bef<br \/>\n        error_result &#061; {<br \/>\n            &#034;error&#034;: str(e),<br \/>\n            &#034;error_code&#034;: &#034;PROCESSING_ERROR&#034;<br \/>\n        }<\/p>\n<p>        task_manager.store_result(task_id, error_result, success&#061;False)<\/p>\n<p>        # \u91cd\u8bd5\u903b\u8f91<br \/>\n        try:<br \/>\n            self.retry(countdown&#061;60, max_retries&#061;3)  # 60\u79d2\u540e\u91cd\u8bd5<br \/>\n        except MaxRetriesExceededError:<br \/>\n            pass<\/p>\n<p>        raise<\/p>\n<p># API\u84dd\u56fe<br \/>\ntasks_bp &#061; Blueprint(&#039;tasks&#039;, __name__, url_prefix&#061;&#039;\/api\/tasks&#039;)<\/p>\n<p>&#064;tasks_bp.route(&#039;&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef create_task():<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u5f02\u6b65\u4efb\u52a1&#034;&#034;&#034;<\/p>\n<p>    # \u89e3\u6790\u8bf7\u6c42<br \/>\n    data &#061; request.get_json()<br \/>\n    if not data:<br \/>\n        return jsonify({&#034;error&#034;: &#034;No data provided&#034;}), 400<\/p>\n<p>    task_type &#061; data.get(&#039;type&#039;)<br \/>\n    if not task_type:<br \/>\n        return jsonify({&#034;error&#034;: &#034;Task type is required&#034;}), 400<\/p>\n<p>    # \u9a8c\u8bc1\u4efb\u52a1\u7c7b\u578b<br \/>\n    valid_types &#061; [&#039;video_process&#039;, &#039;report_generate&#039;, &#039;data_export&#039;, &#039;image_process&#039;]<br \/>\n    if task_type not in valid_types:<br \/>\n        return jsonify({<br \/>\n            &#034;error&#034;: f&#034;Invalid task type. Must be one of: {&#039;, &#039;.join(valid_types)}&#034;<br \/>\n        }), 400<\/p>\n<p>    # \u521b\u5efa\u4efb\u52a1\u8bb0\u5f55<br \/>\n    task_id, task_info &#061; task_manager.create_task(<br \/>\n        task_type&#061;task_type,<br \/>\n        input_data&#061;data.get(&#039;input&#039;, {}),<br \/>\n        metadata&#061;{<br \/>\n            &#034;source_ip&#034;: request.remote_addr,<br \/>\n            &#034;user_agent&#034;: request.headers.get(&#039;User-Agent&#039;),<br \/>\n            &#034;content_type&#034;: request.headers.get(&#039;Content-Type&#039;)<br \/>\n        }<br \/>\n    )<\/p>\n<p>    # \u6839\u636e\u4efb\u52a1\u7c7b\u578b\u542f\u52a8Celery\u4efb\u52a1<br \/>\n    if task_type &#061;&#061; &#039;video_process&#039;:<br \/>\n        process_video_task.delay(task_id, data.get(&#039;input&#039;, {}))<br \/>\n    # \u5176\u4ed6\u4efb\u52a1\u7c7b\u578b\u7684\u5904\u7406&#8230;<\/p>\n<p>    # \u6784\u5efa202\u54cd\u5e94<br \/>\n    base_url &#061; request.host_url.rstrip(&#039;\/&#039;)<\/p>\n<p>    response_data &#061; {<br \/>\n        &#034;task&#034;: {<br \/>\n            &#034;id&#034;: task_id,<br \/>\n            &#034;type&#034;: task_type,<br \/>\n            &#034;status&#034;: task_info[&#034;status&#034;],<br \/>\n            &#034;created_at&#034;: task_info[&#034;created_at&#034;],<br \/>\n            &#034;estimated_completion&#034;: (datetime.utcnow() &#043; timedelta(minutes&#061;10)).isoformat(),<br \/>\n            &#034;links&#034;: {<br \/>\n                &#034;self&#034;: f&#034;{base_url}\/api\/tasks\/{task_id}&#034;,<br \/>\n                &#034;status&#034;: f&#034;{base_url}\/api\/tasks\/{task_id}\/status&#034;,<br \/>\n                &#034;cancel&#034;: f&#034;{base_url}\/api\/tasks\/{task_id}\/cancel&#034;<br \/>\n            }<br \/>\n        },<br \/>\n        &#034;message&#034;: &#034;Task accepted and will be processed asynchronously&#034;<br \/>\n    }<\/p>\n<p>    response &#061; jsonify(response_data)<br \/>\n    response.status_code &#061; 202<br \/>\n    response.headers[&#039;Location&#039;] &#061; f&#034;{base_url}\/api\/tasks\/{task_id}&#034;<br \/>\n    response.headers[&#039;Retry-After&#039;] &#061; &#039;30&#039;<br \/>\n    response.headers[&#039;X-Task-ID&#039;] &#061; task_id<\/p>\n<p>    return response<\/p>\n<p>&#064;tasks_bp.route(&#039;\/&lt;task_id&gt;&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_task(task_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u4efb\u52a1\u4fe1\u606f&#034;&#034;&#034;<br \/>\n    task_info &#061; task_manager.get_task_info(task_id)<\/p>\n<p>    if not task_info:<br \/>\n        return jsonify({&#034;error&#034;: &#034;Task not found&#034;}), 404<\/p>\n<p>    base_url &#061; request.host_url.rstrip(&#039;\/&#039;)<\/p>\n<p>    # \u6784\u5efa\u54cd\u5e94<br \/>\n    response_data &#061; {<br \/>\n        &#034;task&#034;: {<br \/>\n            &#034;id&#034;: task_info[&#034;id&#034;],<br \/>\n            &#034;type&#034;: task_info[&#034;type&#034;],<br \/>\n            &#034;status&#034;: task_info[&#034;status&#034;],<br \/>\n            &#034;progress&#034;: task_info.get(&#034;progress&#034;, {}),<br \/>\n            &#034;created_at&#034;: task_info[&#034;created_at&#034;],<br \/>\n            &#034;updated_at&#034;: task_info[&#034;updated_at&#034;],<br \/>\n            &#034;links&#034;: {<br \/>\n                &#034;self&#034;: f&#034;{base_url}\/api\/tasks\/{task_id}&#034;,<br \/>\n                &#034;status&#034;: f&#034;{base_url}\/api\/tasks\/{task_id}\/status&#034;<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    # \u5982\u679c\u5df2\u5b8c\u6210&#xff0c;\u5305\u542b\u7ed3\u679c<br \/>\n    if task_info[&#034;status&#034;] in [&#034;completed&#034;, &#034;failed&#034;] and &#034;result&#034; in task_info:<br \/>\n        response_data[&#034;task&#034;][&#034;result&#034;] &#061; task_info[&#034;result&#034;]<\/p>\n<p>        # \u5982\u679c\u6210\u529f\u4e14\u521b\u5efa\u4e86\u8d44\u6e90&#xff0c;\u6dfb\u52a0\u8d44\u6e90\u94fe\u63a5<br \/>\n        if (task_info[&#034;status&#034;] &#061;&#061; &#034;completed&#034; and<br \/>\n            task_info[&#034;result&#034;].get(&#034;success&#034;) and<br \/>\n            task_info[&#034;result&#034;].get(&#034;data&#034;, {}).get(&#034;resource_id&#034;)):<\/p>\n<p>            resource_id &#061; task_info[&#034;result&#034;][&#034;data&#034;][&#034;resource_id&#034;]<br \/>\n            response_data[&#034;task&#034;][&#034;links&#034;][&#034;resource&#034;] &#061; \\\\<br \/>\n                f&#034;{base_url}\/api\/resources\/{resource_id}&#034;<\/p>\n<p>    # \u5982\u679c\u72b6\u6001\u662fpending\u6216processing&#xff0c;\u6dfb\u52a0\u53d6\u6d88\u94fe\u63a5<br \/>\n    if task_info[&#034;status&#034;] in [&#034;pending&#034;, &#034;processing&#034;]:<br \/>\n        response_data[&#034;task&#034;][&#034;links&#034;][&#034;cancel&#034;] &#061; \\\\<br \/>\n            f&#034;{base_url}\/api\/tasks\/{task_id}\/cancel&#034;<\/p>\n<p>    return jsonify(response_data)<\/p>\n<p>&#064;tasks_bp.route(&#039;\/&lt;task_id&gt;\/status&#039;, methods&#061;[&#039;GET&#039;])<br \/>\ndef get_task_status(task_id):<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u4efb\u52a1\u72b6\u6001&#xff08;\u8f7b\u91cf\u7ea7&#xff0c;\u9002\u5408\u8f6e\u8be2&#xff09;&#034;&#034;&#034;<br \/>\n    task_info &#061; task_manager.get_task_info(task_id)<\/p>\n<p>    if not task_info:<br \/>\n        return jsonify({&#034;error&#034;: &#034;Task not found&#034;}), 404<\/p>\n<p>    # \u8f7b\u91cf\u7ea7\u72b6\u6001\u54cd\u5e94<br \/>\n    response_data &#061; {<br \/>\n        &#034;task_id&#034;: task_info[&#034;id&#034;],<br \/>\n        &#034;status&#034;: task_info[&#034;status&#034;],<br \/>\n        &#034;progress&#034;: task_info.get(&#034;progress&#034;, {}).get(&#034;percentage&#034;, 0),<br \/>\n        &#034;message&#034;: task_info.get(&#034;progress&#034;, {}).get(&#034;message&#034;, &#034;&#034;),<br \/>\n        &#034;updated_at&#034;: task_info[&#034;updated_at&#034;]<br \/>\n    }<\/p>\n<p>    # \u6dfb\u52a0\u4e0b\u4e00\u4e2a\u5efa\u8bae\u7684\u8f6e\u8be2\u65f6\u95f4<br \/>\n    if task_info[&#034;status&#034;] in [&#034;pending&#034;, &#034;processing&#034;]:<br \/>\n        response_data[&#034;suggested_poll_interval&#034;] &#061; 10  # 10\u79d2<\/p>\n<p>    return jsonify(response_data)<\/p>\n<p>&#064;tasks_bp.route(&#039;\/&lt;task_id&gt;\/cancel&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef cancel_task(task_id):<br \/>\n    &#034;&#034;&#034;\u53d6\u6d88\u4efb\u52a1&#034;&#034;&#034;<br \/>\n    task_info &#061; task_manager.get_task_info(task_id)<\/p>\n<p>    if not task_info:<br \/>\n        return jsonify({&#034;error&#034;: &#034;Task not found&#034;}), 404<\/p>\n<p>    if task_info[&#034;status&#034;] not in [&#034;pending&#034;, &#034;processing&#034;]:<br \/>\n        return jsonify({<br \/>\n            &#034;error&#034;: f&#034;Cannot cancel task in {task_info[&#039;status&#039;]} state&#034;<br \/>\n        }), 400<\/p>\n<p>    # \u66f4\u65b0\u72b6\u6001<br \/>\n    task_manager.update_status(<br \/>\n        task_id,<br \/>\n        &#034;cancelled&#034;,<br \/>\n        {&#034;current&#034;: 0, &#034;total&#034;: 100, &#034;percentage&#034;: 0, &#034;message&#034;: &#034;Cancelled by user&#034;}<br \/>\n    )<\/p>\n<p>    # \u8fd9\u91cc\u5e94\u8be5\u901a\u77e5Celery\u4efb\u52a1\u505c\u6b62&#xff08;\u5b9e\u9645\u5b9e\u73b0\u4f1a\u66f4\u590d\u6742&#xff09;<\/p>\n<p>    return jsonify({<br \/>\n        &#034;message&#034;: &#034;Task cancellation requested&#034;,<br \/>\n        &#034;task_id&#034;: task_id,<br \/>\n        &#034;status&#034;: &#034;cancelled&#034;<br \/>\n    })<\/p>\n<p># \u6ce8\u518c\u84dd\u56fe<br \/>\napp.register_blueprint(tasks_bp)<\/p>\n<p>if __name__ &#061;&#061; &#039;__main__&#039;:<br \/>\n    app.run(debug&#061;True)<\/p>\n<h3>6.4 \u5ba2\u6237\u7aef\u5904\u7406\u6a21\u5f0f<\/h3>\n<h4>\u667a\u80fd\u8f6e\u8be2\u7b56\u7565<\/h4>\n<p>javascript<\/p>\n<p>\/\/ \u667a\u80fd\u4efb\u52a1\u8f6e\u8be2\u7ba1\u7406\u5668<br \/>\nclass TaskPollingManager {<br \/>\n    constructor(options &#061; {}) {<br \/>\n        this.options &#061; {<br \/>\n            initialDelay: 1000,        \/\/ \u521d\u59cb\u5ef6\u8fdf<br \/>\n            maxDelay: 30000,           \/\/ \u6700\u5927\u5ef6\u8fdf<br \/>\n            backoffFactor: 1.5,        \/\/ \u9000\u907f\u56e0\u5b50<br \/>\n            maxAttempts: 100,          \/\/ \u6700\u5927\u5c1d\u8bd5\u6b21\u6570<br \/>\n            jitter: 0.1,               \/\/ \u968f\u673a\u6296\u52a8\u56e0\u5b50<br \/>\n            &#8230;options<br \/>\n        };<\/p>\n<p>        this.tasks &#061; new Map();        \/\/ \u4efb\u52a1\u6620\u5c04<br \/>\n        this.attempts &#061; new Map();     \/\/ \u5c1d\u8bd5\u6b21\u6570\u8bb0\u5f55<br \/>\n        this.timeouts &#061; new Map();     \/\/ \u8d85\u65f6\u8bb0\u5f55<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u5f00\u59cb\u8f6e\u8be2\u4efb\u52a1<br \/>\n     * &#064;param {string} taskId &#8211; \u4efb\u52a1ID<br \/>\n     * &#064;param {string} statusUrl &#8211; \u72b6\u6001URL<br \/>\n     * &#064;param {Object} callbacks &#8211; \u56de\u8c03\u51fd\u6570<br \/>\n     *\/<br \/>\n    startPolling(taskId, statusUrl, callbacks &#061; {}) {<br \/>\n        \/\/ \u9a8c\u8bc1\u56de\u8c03<br \/>\n        const defaultCallbacks &#061; {<br \/>\n            onProgress: () &#061;&gt; {},<br \/>\n            onSuccess: () &#061;&gt; {},<br \/>\n            onFailure: () &#061;&gt; {},<br \/>\n            onCancelled: () &#061;&gt; {},<br \/>\n            onError: (error) &#061;&gt; console.error(&#039;Polling error:&#039;, error)<br \/>\n        };<\/p>\n<p>        const mergedCallbacks &#061; { &#8230;defaultCallbacks, &#8230;callbacks };<\/p>\n<p>        \/\/ \u521d\u59cb\u5316\u4efb\u52a1\u8bb0\u5f55<br \/>\n        this.tasks.set(taskId, {<br \/>\n            id: taskId,<br \/>\n            url: statusUrl,<br \/>\n            callbacks: mergedCallbacks,<br \/>\n            status: &#039;polling&#039;,<br \/>\n            lastPolled: null,<br \/>\n            delay: this.options.initialDelay<br \/>\n        });<\/p>\n<p>        this.attempts.set(taskId, 0);<\/p>\n<p>        \/\/ \u5f00\u59cb\u8f6e\u8be2<br \/>\n        this._schedulePoll(taskId);<\/p>\n<p>        return {<br \/>\n            cancel: () &#061;&gt; this.cancelPolling(taskId),<br \/>\n            getStatus: () &#061;&gt; this.tasks.get(taskId)?.status<br \/>\n        };<br \/>\n    }<\/p>\n<p>    cancelPolling(taskId) {<br \/>\n        const task &#061; this.tasks.get(taskId);<br \/>\n        if (!task) return false;<\/p>\n<p>        task.status &#061; &#039;cancelled&#039;;<\/p>\n<p>        \/\/ \u6e05\u9664\u8d85\u65f6<br \/>\n        const timeoutId &#061; this.timeouts.get(taskId);<br \/>\n        if (timeoutId) {<br \/>\n            clearTimeout(timeoutId);<br \/>\n            this.timeouts.delete(taskId);<br \/>\n        }<\/p>\n<p>        return true;<br \/>\n    }<\/p>\n<p>    _schedulePoll(taskId) {<br \/>\n        const task &#061; this.tasks.get(taskId);<br \/>\n        if (!task || task.status !&#061;&#061; &#039;polling&#039;) return;<\/p>\n<p>        const attempts &#061; this.attempts.get(taskId) || 0;<br \/>\n        if (attempts &gt;&#061; this.options.maxAttempts) {<br \/>\n            task.callbacks.onError(new Error(&#039;Max polling attempts reached&#039;));<br \/>\n            this.tasks.delete(taskId);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        \/\/ \u8ba1\u7b97\u5ef6\u8fdf&#xff08;\u5e26\u6307\u6570\u9000\u907f\u548c\u6296\u52a8&#xff09;<br \/>\n        let delay &#061; task.delay;<br \/>\n        delay &#061; Math.min(delay, this.options.maxDelay);<\/p>\n<p>        \/\/ \u6dfb\u52a0\u968f\u673a\u6296\u52a8<br \/>\n        const jitter &#061; delay * this.options.jitter;<br \/>\n        delay &#043;&#061; (Math.random() * 2 &#8211; 1) * jitter;<\/p>\n<p>        \/\/ \u786e\u4fdd\u6700\u5c0f\u5ef6\u8fdf<br \/>\n        delay &#061; Math.max(delay, 100);<\/p>\n<p>        const timeoutId &#061; setTimeout(() &#061;&gt; {<br \/>\n            this._pollTask(taskId);<br \/>\n        }, delay);<\/p>\n<p>        this.timeouts.set(taskId, timeoutId);<br \/>\n    }<\/p>\n<p>    async _pollTask(taskId) {<br \/>\n        const task &#061; this.tasks.get(taskId);<br \/>\n        if (!task || task.status !&#061;&#061; &#039;polling&#039;) return;<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(task.url, {<br \/>\n                method: &#039;GET&#039;,<br \/>\n                headers: {<br \/>\n                    &#039;Accept&#039;: &#039;application\/json&#039;,<br \/>\n                    &#039;X-Requested-With&#039;: &#039;XMLHttpRequest&#039;<br \/>\n                }<br \/>\n            });<\/p>\n<p>            if (!response.ok) {<br \/>\n                throw new Error(&#096;HTTP ${response.status}&#096;);<br \/>\n            }<\/p>\n<p>            const data &#061; await response.json();<\/p>\n<p>            \/\/ \u66f4\u65b0\u4efb\u52a1\u72b6\u6001<br \/>\n            task.lastPolled &#061; new Date();<br \/>\n            this.attempts.set(taskId, (this.attempts.get(taskId) || 0) &#043; 1);<\/p>\n<p>            \/\/ \u5904\u7406\u72b6\u6001<br \/>\n            switch (data.status) {<br \/>\n                case &#039;pending&#039;:<br \/>\n                case &#039;processing&#039;:<br \/>\n                    \/\/ \u4ecd\u5728\u5904\u7406\u4e2d&#xff0c;\u62a5\u544a\u8fdb\u5ea6<br \/>\n                    task.callbacks.onProgress({<br \/>\n                        taskId,<br \/>\n                        progress: data.progress || 0,<br \/>\n                        message: data.message || &#039;&#039;,<br \/>\n                        estimatedCompletion: data.estimated_completion<br \/>\n                    });<\/p>\n<p>                    \/\/ \u6839\u636e\u670d\u52a1\u5668\u5efa\u8bae\u8c03\u6574\u8f6e\u8be2\u95f4\u9694<br \/>\n                    if (data.suggested_poll_interval) {<br \/>\n                        task.delay &#061; data.suggested_poll_interval * 1000;<br \/>\n                    } else {<br \/>\n                        \/\/ \u6307\u6570\u9000\u907f<br \/>\n                        task.delay &#061; Math.min(<br \/>\n                            task.delay * this.options.backoffFactor,<br \/>\n                            this.options.maxDelay<br \/>\n                        );<br \/>\n                    }<\/p>\n<p>                    this._schedulePoll(taskId);<br \/>\n                    break;<\/p>\n<p>                case &#039;completed&#039;:<br \/>\n                    task.status &#061; &#039;completed&#039;;<br \/>\n                    task.callbacks.onSuccess({<br \/>\n                        taskId,<br \/>\n                        result: data.result,<br \/>\n                        data: data<br \/>\n                    });<br \/>\n                    this._cleanupTask(taskId);<br \/>\n                    break;<\/p>\n<p>                case &#039;failed&#039;:<br \/>\n                    task.status &#061; &#039;failed&#039;;<br \/>\n                    task.callbacks.onFailure({<br \/>\n                        taskId,<br \/>\n                        error: data.error,<br \/>\n                        errorCode: data.error_code,<br \/>\n                        data: data<br \/>\n                    });<br \/>\n                    this._cleanupTask(taskId);<br \/>\n                    break;<\/p>\n<p>                case &#039;cancelled&#039;:<br \/>\n                    task.status &#061; &#039;cancelled&#039;;<br \/>\n                    task.callbacks.onCancelled({<br \/>\n                        taskId,<br \/>\n                        data: data<br \/>\n                    });<br \/>\n                    this._cleanupTask(taskId);<br \/>\n                    break;<\/p>\n<p>                default:<br \/>\n                    console.warn(&#096;Unknown task status: ${data.status}&#096;);<br \/>\n                    this._schedulePoll(taskId);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#096;Polling task ${taskId} failed:&#096;, error);<\/p>\n<p>            task.callbacks.onError(error);<\/p>\n<p>            \/\/ \u51fa\u9519\u65f6\u589e\u52a0\u5ef6\u8fdf<br \/>\n            task.delay &#061; Math.min(<br \/>\n                task.delay * this.options.backoffFactor * 2,<br \/>\n                this.options.maxDelay * 2<br \/>\n            );<\/p>\n<p>            this._schedulePoll(taskId);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    _cleanupTask(taskId) {<br \/>\n        \/\/ \u6e05\u9664\u8d85\u65f6<br \/>\n        const timeoutId &#061; this.timeouts.get(taskId);<br \/>\n        if (timeoutId) {<br \/>\n            clearTimeout(timeoutId);<br \/>\n            this.timeouts.delete(taskId);<br \/>\n        }<\/p>\n<p>        \/\/ \u6e05\u7406\u8bb0\u5f55<br \/>\n        this.tasks.delete(taskId);<br \/>\n        this.attempts.delete(taskId);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u81ea\u52a8\u5904\u7406202\u54cd\u5e94<br \/>\n     * &#064;param {Response} response &#8211; Fetch API\u54cd\u5e94\u5bf9\u8c61<br \/>\n     * &#064;param {Object} callbacks &#8211; \u56de\u8c03\u51fd\u6570<br \/>\n     *\/<br \/>\n    static async handle202Response(response, callbacks &#061; {}) {<br \/>\n        if (response.status !&#061;&#061; 202) {<br \/>\n            throw new Error(&#096;Expected 202 status, got ${response.status}&#096;);<br \/>\n        }<\/p>\n<p>        \/\/ \u89e3\u6790\u54cd\u5e94<br \/>\n        const data &#061; await response.json();<br \/>\n        const taskId &#061; data.task?.id;<\/p>\n<p>        if (!taskId) {<br \/>\n            throw new Error(&#039;No task ID in 202 response&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u83b7\u53d6\u72b6\u6001URL<br \/>\n        let statusUrl &#061; response.headers.get(&#039;Location&#039;);<br \/>\n        if (!statusUrl &amp;&amp; data.task?.links?.status) {<br \/>\n            statusUrl &#061; data.task.links.status;<br \/>\n        }<\/p>\n<p>        if (!statusUrl) {<br \/>\n            throw new Error(&#039;No status URL in 202 response&#039;);<br \/>\n        }<\/p>\n<p>        \/\/ \u521b\u5efa\u8f6e\u8be2\u7ba1\u7406\u5668\u5b9e\u4f8b&#xff08;\u5355\u4f8b\u6a21\u5f0f&#xff09;<br \/>\n        if (!window.taskPollingManager) {<br \/>\n            window.taskPollingManager &#061; new TaskPollingManager();<br \/>\n        }<\/p>\n<p>        \/\/ \u5f00\u59cb\u8f6e\u8be2<br \/>\n        return window.taskPollingManager.startPolling(<br \/>\n            taskId,<br \/>\n            statusUrl,<br \/>\n            callbacks<br \/>\n        );<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\nclass AsyncTaskHandler {<br \/>\n    constructor() {<br \/>\n        this.pollingManager &#061; new TaskPollingManager({<br \/>\n            initialDelay: 2000,<br \/>\n            maxDelay: 30000,<br \/>\n            backoffFactor: 1.5<br \/>\n        });<\/p>\n<p>        this.setupUI();<br \/>\n    }<\/p>\n<p>    setupUI() {<br \/>\n        \/\/ \u521b\u5efaUI\u5143\u7d20<br \/>\n        const container &#061; document.getElementById(&#039;task-container&#039;);<br \/>\n        if (!container) return;<\/p>\n<p>        container.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;task-controls&#034;&gt;<br \/>\n                &lt;button id&#061;&#034;start-video-process&#034; class&#061;&#034;btn btn-primary&#034;&gt;<br \/>\n                    Process Video<br \/>\n                &lt;\/button&gt;<br \/>\n                &lt;div id&#061;&#034;task-progress&#034; style&#061;&#034;display: none;&#034;&gt;<br \/>\n                    &lt;div class&#061;&#034;progress&#034;&gt;<br \/>\n                        &lt;div class&#061;&#034;progress-bar&#034; role&#061;&#034;progressbar&#034; style&#061;&#034;width: 0%&#034;&gt;&lt;\/div&gt;<br \/>\n                    &lt;\/div&gt;<br \/>\n                    &lt;div class&#061;&#034;task-status&#034;&gt;&lt;\/div&gt;<br \/>\n                    &lt;button id&#061;&#034;cancel-task&#034; class&#061;&#034;btn btn-sm btn-warning&#034;&gt;Cancel&lt;\/button&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div id&#061;&#034;task-result&#034; style&#061;&#034;display: none;&#034;&gt;&lt;\/div&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        \/\/ \u7ed1\u5b9a\u4e8b\u4ef6<br \/>\n        document.getElementById(&#039;start-video-process&#039;).addEventListener(&#039;click&#039;, () &#061;&gt; {<br \/>\n            this.startVideoProcessing();<br \/>\n        });<br \/>\n    }<\/p>\n<p>    async startVideoProcessing() {<br \/>\n        const videoFile &#061; document.getElementById(&#039;video-file&#039;)?.files[0];<br \/>\n        if (!videoFile) {<br \/>\n            alert(&#039;Please select a video file&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        \/\/ \u663e\u793a\u8fdb\u5ea6UI<br \/>\n        document.getElementById(&#039;task-progress&#039;).style.display &#061; &#039;block&#039;;<br \/>\n        document.getElementById(&#039;task-result&#039;).style.display &#061; &#039;none&#039;;<\/p>\n<p>        \/\/ \u51c6\u5907\u8868\u5355\u6570\u636e<br \/>\n        const formData &#061; new FormData();<br \/>\n        formData.append(&#039;video&#039;, videoFile);<br \/>\n        formData.append(&#039;quality&#039;, &#039;high&#039;);<br \/>\n        formData.append(&#039;format&#039;, &#039;mp4&#039;);<\/p>\n<p>        try {<br \/>\n            const response &#061; await fetch(&#039;\/api\/tasks&#039;, {<br \/>\n                method: &#039;POST&#039;,<br \/>\n                body: formData<br \/>\n            });<\/p>\n<p>            if (response.status &#061;&#061;&#061; 202) {<br \/>\n                \/\/ \u4f7f\u7528\u8f6e\u8be2\u7ba1\u7406\u5668\u5904\u7406202\u54cd\u5e94<br \/>\n                const pollingControl &#061; await TaskPollingManager.handle202Response(<br \/>\n                    response,<br \/>\n                    {<br \/>\n                        onProgress: (progress) &#061;&gt; {<br \/>\n                            this.updateProgress(progress);<br \/>\n                        },<br \/>\n                        onSuccess: (result) &#061;&gt; {<br \/>\n                            this.handleSuccess(result);<br \/>\n                        },<br \/>\n                        onFailure: (error) &#061;&gt; {<br \/>\n                            this.handleFailure(error);<br \/>\n                        },<br \/>\n                        onCancelled: () &#061;&gt; {<br \/>\n                            this.handleCancelled();<br \/>\n                        },<br \/>\n                        onError: (error) &#061;&gt; {<br \/>\n                            console.error(&#039;Task error:&#039;, error);<br \/>\n                            this.showError(&#039;Task processing failed&#039;);<br \/>\n                        }<br \/>\n                    }<br \/>\n                );<\/p>\n<p>                \/\/ \u5b58\u50a8\u63a7\u5236\u5f15\u7528\u4ee5\u4fbf\u53d6\u6d88<br \/>\n                this.currentTaskControl &#061; pollingControl;<\/p>\n<p>                \/\/ \u7ed1\u5b9a\u53d6\u6d88\u6309\u94ae<br \/>\n                document.getElementById(&#039;cancel-task&#039;).onclick &#061; () &#061;&gt; {<br \/>\n                    if (this.currentTaskControl) {<br \/>\n                        this.currentTaskControl.cancel();<br \/>\n                        this.cancelTask(pollingControl.taskId);<br \/>\n                    }<br \/>\n                };<\/p>\n<p>            } else {<br \/>\n                const errorData &#061; await response.json();<br \/>\n                this.showError(&#096;Failed to create task: ${errorData.error}&#096;);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Request failed:&#039;, error);<br \/>\n            this.showError(&#039;Network error&#039;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    updateProgress(progress) {<br \/>\n        const progressBar &#061; document.querySelector(&#039;.progress-bar&#039;);<br \/>\n        const statusDiv &#061; document.querySelector(&#039;.task-status&#039;);<\/p>\n<p>        if (progressBar) {<br \/>\n            progressBar.style.width &#061; &#096;${progress.progress}%&#096;;<br \/>\n            progressBar.textContent &#061; &#096;${Math.round(progress.progress)}%&#096;;<br \/>\n        }<\/p>\n<p>        if (statusDiv) {<br \/>\n            statusDiv.textContent &#061; progress.message || &#096;Processing&#8230; ${progress.progress}%&#096;;<\/p>\n<p>            if (progress.estimatedCompletion) {<br \/>\n                const eta &#061; new Date(progress.estimatedCompletion).toLocaleTimeString();<br \/>\n                statusDiv.textContent &#043;&#061; &#096; (Estimated completion: ${eta})&#096;;<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleSuccess(result) {<br \/>\n        document.getElementById(&#039;task-progress&#039;).style.display &#061; &#039;none&#039;;<br \/>\n        document.getElementById(&#039;task-result&#039;).style.display &#061; &#039;block&#039;;<\/p>\n<p>        const resultDiv &#061; document.getElementById(&#039;task-result&#039;);<br \/>\n        resultDiv.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;alert alert-success&#034;&gt;<br \/>\n                &lt;h4&gt;Task Completed Successfully!&lt;\/h4&gt;<br \/>\n                &lt;p&gt;Video processing completed.&lt;\/p&gt;<br \/>\n                ${result.result?.download_url ?<br \/>\n                    &#096;&lt;a href&#061;&#034;${result.result.download_url}&#034; class&#061;&#034;btn btn-success&#034; download&gt;<br \/>\n                        Download Video<br \/>\n                    &lt;\/a&gt;&#096; : &#039;&#039;}<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        this.currentTaskControl &#061; null;<br \/>\n    }<\/p>\n<p>    handleFailure(error) {<br \/>\n        document.getElementById(&#039;task-progress&#039;).style.display &#061; &#039;none&#039;;<\/p>\n<p>        const resultDiv &#061; document.getElementById(&#039;task-result&#039;);<br \/>\n        resultDiv.style.display &#061; &#039;block&#039;;<br \/>\n        resultDiv.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;alert alert-danger&#034;&gt;<br \/>\n                &lt;h4&gt;Task Failed&lt;\/h4&gt;<br \/>\n                &lt;p&gt;${error.error || &#039;Unknown error&#039;}&lt;\/p&gt;<br \/>\n                ${error.errorCode ? &#096;&lt;small&gt;Error code: ${error.errorCode}&lt;\/small&gt;&#096; : &#039;&#039;}<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        this.currentTaskControl &#061; null;<br \/>\n    }<\/p>\n<p>    handleCancelled() {<br \/>\n        document.getElementById(&#039;task-progress&#039;).style.display &#061; &#039;none&#039;;<\/p>\n<p>        const resultDiv &#061; document.getElementById(&#039;task-result&#039;);<br \/>\n        resultDiv.style.display &#061; &#039;block&#039;;<br \/>\n        resultDiv.innerHTML &#061; &#096;<br \/>\n            &lt;div class&#061;&#034;alert alert-warning&#034;&gt;<br \/>\n                &lt;h4&gt;Task Cancelled&lt;\/h4&gt;<br \/>\n                &lt;p&gt;The task was cancelled successfully.&lt;\/p&gt;<br \/>\n            &lt;\/div&gt;<br \/>\n        &#096;;<\/p>\n<p>        this.currentTaskControl &#061; null;<br \/>\n    }<\/p>\n<p>    async cancelTask(taskId) {<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#096;\/api\/tasks\/${taskId}\/cancel&#096;, {<br \/>\n                method: &#039;POST&#039;<br \/>\n            });<\/p>\n<p>            if (!response.ok) {<br \/>\n                console.error(&#039;Failed to cancel task&#039;);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Cancel request failed:&#039;, error);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showError(message) {<br \/>\n        const resultDiv &#061; document.getElementById(&#039;task-result&#039;);<br \/>\n        if (resultDiv) {<br \/>\n            resultDiv.style.display &#061; &#039;block&#039;;<br \/>\n            resultDiv.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;alert alert-danger&#034;&gt;<br \/>\n                    ${message}<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n        } else {<br \/>\n            alert(message);<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u9875\u9762\u52a0\u8f7d\u65f6\u521d\u59cb\u5316<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    window.asyncTaskHandler &#061; new AsyncTaskHandler();<br \/>\n});<\/p>\n<h4>WebSocket\u5b9e\u65f6\u66f4\u65b0<\/h4>\n<p>javascript<\/p>\n<p>\/\/ WebSocket\u4efb\u52a1\u72b6\u6001\u5b9e\u65f6\u66f4\u65b0<br \/>\nclass TaskWebSocketManager {<br \/>\n    constructor(options &#061; {}) {<br \/>\n        this.options &#061; {<br \/>\n            wsEndpoint: &#039;\/ws\/tasks&#039;,<br \/>\n            reconnectInterval: 5000,<br \/>\n            maxReconnectAttempts: 10,<br \/>\n            heartbeatInterval: 30000,<br \/>\n            &#8230;options<br \/>\n        };<\/p>\n<p>        this.ws &#061; null;<br \/>\n        this.reconnectAttempts &#061; 0;<br \/>\n        this.subscriptions &#061; new Map(); \/\/ taskId -&gt; callback<br \/>\n        this.messageQueue &#061; [];<br \/>\n        this.isConnected &#061; false;<\/p>\n<p>        this.connect();<br \/>\n    }<\/p>\n<p>    connect() {<br \/>\n        try {<br \/>\n            const protocol &#061; window.location.protocol &#061;&#061;&#061; &#039;https:&#039; ? &#039;wss:&#039; : &#039;ws:&#039;;<br \/>\n            const wsUrl &#061; &#096;${protocol}\/\/${window.location.host}${this.options.wsEndpoint}&#096;;<\/p>\n<p>            this.ws &#061; new WebSocket(wsUrl);<\/p>\n<p>            this.ws.onopen &#061; () &#061;&gt; {<br \/>\n                console.log(&#039;WebSocket connected&#039;);<br \/>\n                this.isConnected &#061; true;<br \/>\n                this.reconnectAttempts &#061; 0;<\/p>\n<p>                \/\/ \u53d1\u9001\u5fc3\u8df3<br \/>\n                this.startHeartbeat();<\/p>\n<p>                \/\/ \u91cd\u65b0\u8ba2\u9605\u6240\u6709\u4efb\u52a1<br \/>\n                this.resubscribeAll();<\/p>\n<p>                \/\/ \u53d1\u9001\u961f\u5217\u4e2d\u7684\u6d88\u606f<br \/>\n                this.flushMessageQueue();<br \/>\n            };<\/p>\n<p>            this.ws.onmessage &#061; (event) &#061;&gt; {<br \/>\n                this.handleMessage(event);<br \/>\n            };<\/p>\n<p>            this.ws.onclose &#061; (event) &#061;&gt; {<br \/>\n                console.log(&#039;WebSocket disconnected:&#039;, event.code, event.reason);<br \/>\n                this.isConnected &#061; false;<br \/>\n                this.stopHeartbeat();<br \/>\n                this.handleDisconnection();<br \/>\n            };<\/p>\n<p>            this.ws.onerror &#061; (error) &#061;&gt; {<br \/>\n                console.error(&#039;WebSocket error:&#039;, error);<br \/>\n            };<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;WebSocket connection failed:&#039;, error);<br \/>\n            this.scheduleReconnect();<br \/>\n        }<br \/>\n    }<\/p>\n<p>    subscribe(taskId, callback) {<br \/>\n        \/\/ \u5b58\u50a8\u8ba2\u9605<br \/>\n        this.subscriptions.set(taskId, callback);<\/p>\n<p>        \/\/ \u5982\u679c\u5df2\u8fde\u63a5&#xff0c;\u53d1\u9001\u8ba2\u9605\u6d88\u606f<br \/>\n        if (this.isConnected) {<br \/>\n            this.send({<br \/>\n                type: &#039;subscribe&#039;,<br \/>\n                task_id: taskId<br \/>\n            });<br \/>\n        } else {<br \/>\n            \/\/ \u5426\u5219\u52a0\u5165\u961f\u5217<br \/>\n            this.messageQueue.push({<br \/>\n                type: &#039;subscribe&#039;,<br \/>\n                task_id: taskId<br \/>\n            });<br \/>\n        }<\/p>\n<p>        return () &#061;&gt; this.unsubscribe(taskId);<br \/>\n    }<\/p>\n<p>    unsubscribe(taskId) {<br \/>\n        this.subscriptions.delete(taskId);<\/p>\n<p>        if (this.isConnected) {<br \/>\n            this.send({<br \/>\n                type: &#039;unsubscribe&#039;,<br \/>\n                task_id: taskId<br \/>\n            });<br \/>\n        }<br \/>\n    }<\/p>\n<p>    resubscribeAll() {<br \/>\n        for (const [taskId] of this.subscriptions) {<br \/>\n            this.send({<br \/>\n                type: &#039;subscribe&#039;,<br \/>\n                task_id: taskId<br \/>\n            });<br \/>\n        }<br \/>\n    }<\/p>\n<p>    send(message) {<br \/>\n        if (this.isConnected &amp;&amp; this.ws.readyState &#061;&#061;&#061; WebSocket.OPEN) {<br \/>\n            this.ws.send(JSON.stringify(message));<br \/>\n        } else {<br \/>\n            this.messageQueue.push(message);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    flushMessageQueue() {<br \/>\n        while (this.messageQueue.length &gt; 0 &amp;&amp; this.isConnected) {<br \/>\n            const message &#061; this.messageQueue.shift();<br \/>\n            this.ws.send(JSON.stringify(message));<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleMessage(event) {<br \/>\n        try {<br \/>\n            const data &#061; JSON.parse(event.data);<\/p>\n<p>            switch (data.type) {<br \/>\n                case &#039;task_update&#039;:<br \/>\n                    this.handleTaskUpdate(data);<br \/>\n                    break;<\/p>\n<p>                case &#039;task_completed&#039;:<br \/>\n                    this.handleTaskCompleted(data);<br \/>\n                    break;<\/p>\n<p>                case &#039;task_failed&#039;:<br \/>\n                    this.handleTaskFailed(data);<br \/>\n                    break;<\/p>\n<p>                case &#039;heartbeat&#039;:<br \/>\n                    \/\/ \u5fc3\u8df3\u54cd\u5e94&#xff0c;\u66f4\u65b0\u6700\u540e\u6d3b\u52a8\u65f6\u95f4<br \/>\n                    this.lastHeartbeat &#061; Date.now();<br \/>\n                    break;<\/p>\n<p>                case &#039;error&#039;:<br \/>\n                    console.error(&#039;WebSocket error:&#039;, data.message);<br \/>\n                    break;<\/p>\n<p>                default:<br \/>\n                    console.warn(&#039;Unknown message type:&#039;, data.type);<br \/>\n            }<\/p>\n<p>        } catch (error) {<br \/>\n            console.error(&#039;Failed to parse WebSocket message:&#039;, error);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleTaskUpdate(data) {<br \/>\n        const { task_id, progress, status, message } &#061; data;<br \/>\n        const callback &#061; this.subscriptions.get(task_id);<\/p>\n<p>        if (callback) {<br \/>\n            callback({<br \/>\n                type: &#039;update&#039;,<br \/>\n                taskId: task_id,<br \/>\n                progress,<br \/>\n                status,<br \/>\n                message,<br \/>\n                timestamp: new Date().toISOString()<br \/>\n            });<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleTaskCompleted(data) {<br \/>\n        const { task_id, result } &#061; data;<br \/>\n        const callback &#061; this.subscriptions.get(task_id);<\/p>\n<p>        if (callback) {<br \/>\n            callback({<br \/>\n                type: &#039;completed&#039;,<br \/>\n                taskId: task_id,<br \/>\n                result,<br \/>\n                timestamp: new Date().toISOString()<br \/>\n            });<\/p>\n<p>            \/\/ \u5b8c\u6210\u540e\u81ea\u52a8\u53d6\u6d88\u8ba2\u9605<br \/>\n            this.unsubscribe(task_id);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleTaskFailed(data) {<br \/>\n        const { task_id, error, error_code } &#061; data;<br \/>\n        const callback &#061; this.subscriptions.get(task_id);<\/p>\n<p>        if (callback) {<br \/>\n            callback({<br \/>\n                type: &#039;failed&#039;,<br \/>\n                taskId: task_id,<br \/>\n                error,<br \/>\n                errorCode: error_code,<br \/>\n                timestamp: new Date().toISOString()<br \/>\n            });<\/p>\n<p>            this.unsubscribe(task_id);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    startHeartbeat() {<br \/>\n        this.heartbeatInterval &#061; setInterval(() &#061;&gt; {<br \/>\n            if (this.isConnected) {<br \/>\n                this.send({ type: &#039;heartbeat&#039; });<br \/>\n            }<br \/>\n        }, this.options.heartbeatInterval);<br \/>\n    }<\/p>\n<p>    stopHeartbeat() {<br \/>\n        if (this.heartbeatInterval) {<br \/>\n            clearInterval(this.heartbeatInterval);<br \/>\n            this.heartbeatInterval &#061; null;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleDisconnection() {<br \/>\n        \/\/ \u901a\u77e5\u6240\u6709\u8ba2\u9605\u8005\u8fde\u63a5\u65ad\u5f00<br \/>\n        for (const [taskId, callback] of this.subscriptions) {<br \/>\n            callback({<br \/>\n                type: &#039;disconnected&#039;,<br \/>\n                taskId,<br \/>\n                message: &#039;Connection lost&#039;,<br \/>\n                timestamp: new Date().toISOString()<br \/>\n            });<br \/>\n        }<\/p>\n<p>        \/\/ \u5c1d\u8bd5\u91cd\u8fde<br \/>\n        this.scheduleReconnect();<br \/>\n    }<\/p>\n<p>    scheduleReconnect() {<br \/>\n        if (this.reconnectAttempts &gt;&#061; this.options.maxReconnectAttempts) {<br \/>\n            console.error(&#039;Max reconnection attempts reached&#039;);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        this.reconnectAttempts&#043;&#043;;<\/p>\n<p>        const delay &#061; this.options.reconnectInterval * Math.pow(1.5, this.reconnectAttempts &#8211; 1);<\/p>\n<p>        console.log(&#096;Scheduling reconnect in ${delay}ms (attempt ${this.reconnectAttempts})&#096;);<\/p>\n<p>        setTimeout(() &#061;&gt; {<br \/>\n            this.connect();<br \/>\n        }, delay);<br \/>\n    }<\/p>\n<p>    disconnect() {<br \/>\n        this.stopHeartbeat();<\/p>\n<p>        if (this.ws) {<br \/>\n            this.ws.close(1000, &#039;Client disconnected&#039;);<br \/>\n            this.ws &#061; null;<br \/>\n        }<\/p>\n<p>        this.isConnected &#061; false;<br \/>\n        this.subscriptions.clear();<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528WebSocket\u7684\u5f02\u6b65\u4efb\u52a1\u5904\u7406\u5668<br \/>\nclass WebSocketTaskHandler {<br \/>\n    constructor() {<br \/>\n        this.wsManager &#061; new TaskWebSocketManager();<br \/>\n        this.setupUI();<br \/>\n    }<\/p>\n<p>    async createTask(taskType, inputData) {<br \/>\n        const response &#061; await fetch(&#039;\/api\/tasks&#039;, {<br \/>\n            method: &#039;POST&#039;,<br \/>\n            headers: {<br \/>\n                &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n            },<br \/>\n            body: JSON.stringify({<br \/>\n                type: taskType,<br \/>\n                input: inputData<br \/>\n            })<br \/>\n        });<\/p>\n<p>        if (response.status &#061;&#061;&#061; 202) {<br \/>\n            const data &#061; await response.json();<br \/>\n            const taskId &#061; data.task.id;<\/p>\n<p>            \/\/ \u8ba2\u9605WebSocket\u66f4\u65b0<br \/>\n            const unsubscribe &#061; this.wsManager.subscribe(taskId, (update) &#061;&gt; {<br \/>\n                this.handleTaskUpdate(update);<br \/>\n            });<\/p>\n<p>            return {<br \/>\n                taskId,<br \/>\n                unsubscribe,<br \/>\n                data<br \/>\n            };<br \/>\n        } else {<br \/>\n            throw new Error(&#096;Failed to create task: ${response.status}&#096;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    handleTaskUpdate(update) {<br \/>\n        switch (update.type) {<br \/>\n            case &#039;update&#039;:<br \/>\n                this.updateProgressUI(update);<br \/>\n                break;<\/p>\n<p>            case &#039;completed&#039;:<br \/>\n                this.showCompletion(update);<br \/>\n                break;<\/p>\n<p>            case &#039;failed&#039;:<br \/>\n                this.showFailure(update);<br \/>\n                break;<\/p>\n<p>            case &#039;disconnected&#039;:<br \/>\n                this.showDisconnected(update);<br \/>\n                break;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    updateProgressUI(update) {<br \/>\n        const progressElement &#061; document.getElementById(&#039;task-progress&#039;);<br \/>\n        if (progressElement) {<br \/>\n            progressElement.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;progress&#034;&gt;<br \/>\n                    &lt;div class&#061;&#034;progress-bar&#034;<br \/>\n                         style&#061;&#034;width: ${update.progress}%&#034;<br \/>\n                         role&#061;&#034;progressbar&#034;&gt;<br \/>\n                        ${update.progress}%<br \/>\n                    &lt;\/div&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;task-message&#034;&gt;${update.message}&lt;\/div&gt;<br \/>\n                &lt;div class&#061;&#034;task-timestamp&#034;&gt;${new Date(update.timestamp).toLocaleTimeString()}&lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showCompletion(update) {<br \/>\n        const container &#061; document.getElementById(&#039;task-container&#039;);<br \/>\n        if (container) {<br \/>\n            container.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;alert alert-success&#034;&gt;<br \/>\n                    &lt;h4&gt;Task Completed!&lt;\/h4&gt;<br \/>\n                    &lt;p&gt;Task ${update.taskId} completed successfully.&lt;\/p&gt;<br \/>\n                    &lt;pre&gt;${JSON.stringify(update.result, null, 2)}&lt;\/pre&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showFailure(update) {<br \/>\n        const container &#061; document.getElementById(&#039;task-container&#039;);<br \/>\n        if (container) {<br \/>\n            container.innerHTML &#061; &#096;<br \/>\n                &lt;div class&#061;&#034;alert alert-danger&#034;&gt;<br \/>\n                    &lt;h4&gt;Task Failed&lt;\/h4&gt;<br \/>\n                    &lt;p&gt;Task ${update.taskId} failed: ${update.error}&lt;\/p&gt;<br \/>\n                    ${update.errorCode ? &#096;&lt;small&gt;Error code: ${update.errorCode}&lt;\/small&gt;&#096; : &#039;&#039;}<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    showDisconnected(update) {<br \/>\n        const progressElement &#061; document.getElementById(&#039;task-progress&#039;);<br \/>\n        if (progressElement) {<br \/>\n            progressElement.innerHTML &#043;&#061; &#096;<br \/>\n                &lt;div class&#061;&#034;alert alert-warning&#034;&gt;<br \/>\n                    &lt;strong&gt;Connection lost&lt;\/strong&gt;<br \/>\n                    &lt;p&gt;Real-time updates unavailable. Falling back to polling.&lt;\/p&gt;<br \/>\n                &lt;\/div&gt;<br \/>\n            &#096;;<\/p>\n<p>            \/\/ \u56de\u9000\u5230\u8f6e\u8be2<br \/>\n            this.fallbackToPolling(update.taskId);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    fallbackToPolling(taskId) {<br \/>\n        \/\/ \u5b9e\u73b0\u8f6e\u8be2\u56de\u9000\u903b\u8f91<br \/>\n        console.log(&#096;Falling back to polling for task ${taskId}&#096;);<br \/>\n    }<\/p>\n<p>    setupUI() {<br \/>\n        \/\/ UI\u8bbe\u7f6e\u4ee3\u7801&#8230;<br \/>\n    }<br \/>\n}<\/p>\n<h3>6.5 \u6700\u4f73\u5b9e\u8df5\u4e0e\u76d1\u63a7<\/h3>\n<h4>\u76d1\u63a7\u548c\u544a\u8b66\u914d\u7f6e<\/h4>\n<p>yaml<\/p>\n<p># Prometheus\u76d1\u63a7\u914d\u7f6e<br \/>\nscrape_configs:<br \/>\n  &#8211; job_name: &#039;async_tasks&#039;<br \/>\n    static_configs:<br \/>\n      &#8211; targets: [&#039;localhost:9091&#039;]<br \/>\n    metrics_path: &#039;\/metrics\/tasks&#039;<\/p>\n<p># Grafana\u4eea\u8868\u677f\u914d\u7f6e<br \/>\ndashboard:<br \/>\n  panels:<br \/>\n    &#8211; title: &#034;\u5f02\u6b65\u4efb\u52a1\u6982\u89c8&#034;<br \/>\n      type: &#034;stat&#034;<br \/>\n      targets:<br \/>\n        &#8211; expr: &#034;sum(async_tasks_total)&#034;<br \/>\n          legend: &#034;\u603b\u4efb\u52a1\u6570&#034;<br \/>\n        &#8211; expr: &#034;sum(async_tasks_pending)&#034;<br \/>\n          legend: &#034;\u7b49\u5f85\u4e2d&#034;<br \/>\n        &#8211; expr: &#034;sum(async_ta<\/p>\n<h2>\u7b2c7\u7ae0&#xff1a;204 No Content &#8211; \u65e0\u5185\u5bb9\u6210\u529f<\/h2>\n<h3>7.1 204 No Content\u7684\u8bed\u4e49\u4e0e\u4f7f\u7528\u573a\u666f<\/h3>\n<h4>RFC\u6807\u51c6\u5b9a\u4e49\u6df1\u5ea6\u89e3\u6790<\/h4>\n<p>\u6839\u636eRFC 7231\u7b2c6.3.5\u8282&#xff0c;204 No Content\u72b6\u6001\u7801\u7684\u5b9a\u4e49\u5982\u4e0b&#xff1a;<\/p>\n<p>&#034;204 (No Content)\u72b6\u6001\u7801\u8868\u793a\u670d\u52a1\u5668\u5df2\u6210\u529f\u5904\u7406\u8bf7\u6c42&#xff0c;\u5e76\u4e14\u54cd\u5e94\u8d1f\u8f7d\u4e2d\u6ca1\u6709\u8981\u53d1\u9001\u7684\u989d\u5916\u5185\u5bb9\u3002\u54cd\u5e94\u4e2d\u7684\u5143\u6570\u636e\u6307\u7684\u662f\u539f\u59cb\u670d\u52a1\u5668\u4e3a\u8bf7\u6c42\u9009\u62e9\u7684\u8868\u793a&#xff0c;\u8be5\u8868\u793a\u73b0\u5728\u9002\u7528\u4e8e\u76ee\u6807\u8d44\u6e90\u3002&#034;<\/p>\n<h5>\u6838\u5fc3\u8bed\u4e49\u7279\u5f81<\/h5>\n<li>\n<p>\u65e0\u54cd\u5e94\u4f53&#xff1a;\u54cd\u5e94\u6d88\u606f\u4e0d\u80fd\u5305\u542b\u8868\u793a\u4f53<\/p>\n<\/li>\n<li>\n<p>\u6210\u529f\u5b8c\u6210&#xff1a;\u8bf7\u6c42\u5df2\u6210\u529f\u6267\u884c<\/p>\n<\/li>\n<li>\n<p>\u53ef\u9009\u5934\u90e8&#xff1a;\u53ef\u4ee5\u5305\u542b\u66f4\u65b0\u540e\u7684\u5143\u6570\u636e\u5934\u90e8<\/p>\n<\/li>\n<li>\n<p>\u7f13\u5b58\u5f71\u54cd&#xff1a;\u53ef\u80fd\u5f71\u54cd\u7f13\u5b58\u7684\u6709\u6548\u6027<\/p>\n<\/li>\n<h4>\u9002\u7528\u573a\u666f\u77e9\u9635<\/h4>\n<p>python<\/p>\n<p># 204 No Content\u9002\u7528\u573a\u666f\u5206\u6790\u5668<br \/>\nclass NoContentUseCaseAnalyzer:<br \/>\n    &#034;&#034;&#034;\u5206\u6790204\u72b6\u6001\u7801\u7684\u9002\u7528\u573a\u666f&#034;&#034;&#034;<\/p>\n<p>    SCENARIOS &#061; {<br \/>\n        &#034;successful_deletion&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u6210\u529f\u5220\u9664\u8d44\u6e90&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u5220\u9664\u7528\u6237\u8d26\u6237&#034;,<br \/>\n                &#034;\u5220\u9664\u6587\u7ae0&#034;,<br \/>\n                &#034;\u6e05\u9664\u7f13\u5b58\u9879&#034;,<br \/>\n                &#034;\u79fb\u9664\u6743\u9650&#034;<br \/>\n            ],<br \/>\n            &#034;headers&#034;: {<br \/>\n                &#034;required&#034;: [],<br \/>\n                &#034;recommended&#034;: [&#034;X-Deleted-Resource&#034;, &#034;ETag&#034;],<br \/>\n                &#034;optional&#034;: [&#034;Last-Modified&#034;, &#034;Cache-Control&#034;]<br \/>\n            },<br \/>\n            &#034;response_body&#034;: &#034;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#034;<br \/>\n        },<br \/>\n        &#034;successful_update&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u6210\u529f\u66f4\u65b0\u4f46\u65e0\u9700\u8fd4\u56de\u8d44\u6e90&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u66f4\u65b0\u6700\u540e\u8bbf\u95ee\u65f6\u95f4&#034;,<br \/>\n                &#034;\u6807\u8bb0\u4e3a\u5df2\u8bfb&#034;,<br \/>\n                &#034;\u589e\u52a0\u8ba1\u6570\u5668&#034;,<br \/>\n                &#034;\u5207\u6362\u72b6\u6001&#034;<br \/>\n            ],<br \/>\n            &#034;headers&#034;: {<br \/>\n                &#034;required&#034;: [],<br \/>\n                &#034;recommended&#034;: [&#034;ETag&#034;, &#034;Last-Modified&#034;],<br \/>\n                &#034;optional&#034;: [&#034;Location&#034;, &#034;Cache-Control&#034;]<br \/>\n            },<br \/>\n            &#034;response_body&#034;: &#034;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#034;<br \/>\n        },<br \/>\n        &#034;batch_operation&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u6279\u91cf\u64cd\u4f5c\u5b8c\u6210&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u6279\u91cf\u5220\u9664\u6d88\u606f&#034;,<br \/>\n                &#034;\u6279\u91cf\u6279\u51c6\u8bf7\u6c42&#034;,<br \/>\n                &#034;\u6279\u91cf\u6807\u8bb0\u5b8c\u6210&#034;,<br \/>\n                &#034;\u6279\u91cf\u53d6\u6d88\u8ba2\u9605&#034;<br \/>\n            ],<br \/>\n            &#034;headers&#034;: {<br \/>\n                &#034;required&#034;: [],<br \/>\n                &#034;recommended&#034;: [&#034;X-Operations-Count&#034;],<br \/>\n                &#034;optional&#034;: [&#034;X-Successful-Operations&#034;, &#034;X-Failed-Operations&#034;]<br \/>\n            },<br \/>\n            &#034;response_body&#034;: &#034;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#xff0c;\u4f46\u53ef\u80fd\u9700\u8981\u5728\u5934\u90e8\u5305\u542b\u7edf\u8ba1\u4fe1\u606f&#034;<br \/>\n        },<br \/>\n        &#034;noop_operation&#034;: {<br \/>\n            &#034;description&#034;: &#034;\u65e0\u5b9e\u9645\u64cd\u4f5c\u4f46\u6210\u529f&#034;,<br \/>\n            &#034;examples&#034;: [<br \/>\n                &#034;\u53d6\u6d88\u8ba2\u9605\u5df2\u53d6\u6d88\u7684\u8ba2\u9605&#034;,<br \/>\n                &#034;\u5220\u9664\u4e0d\u5b58\u5728\u7684\u8d44\u6e90&#xff08;\u82e5\u7ea6\u5b9a\u5982\u6b64&#xff09;&#034;,<br \/>\n                &#034;\u66f4\u65b0\u4e3a\u76f8\u540c\u503c&#034;,<br \/>\n                &#034;\u7a7a\u64cd\u4f5c&#034;<br \/>\n            ],<br \/>\n            &#034;headers&#034;: {<br \/>\n                &#034;required&#034;: [],<br \/>\n                &#034;recommended&#034;: [],<br \/>\n                &#034;optional&#034;: [&#034;X-No-Op&#034;]<br \/>\n            },<br \/>\n            &#034;response_body&#034;: &#034;\u7981\u6b62\u5305\u542b\u54cd\u5e94\u4f53&#034;<br \/>\n        }<br \/>\n    }<\/p>\n<p>    def is_appropriate(self, request_method, operation_type, needs_response_data):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u5224\u65ad\u662f\u5426\u9002\u5408\u4f7f\u7528204 No Content<\/p>\n<p>        Args:<br \/>\n            request_method: HTTP\u65b9\u6cd5<br \/>\n            operation_type: \u64cd\u4f5c\u7c7b\u578b<br \/>\n            needs_response_data: \u662f\u5426\u9700\u8981\u8fd4\u56de\u6570\u636e<\/p>\n<p>        Returns:<br \/>\n            \u662f\u5426\u9002\u5408\u4f7f\u7528204<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        # \u5982\u679c\u9700\u8981\u54cd\u5e94\u6570\u636e&#xff0c;\u4e0d\u80fd\u4f7f\u7528204<br \/>\n        if needs_response_data:<br \/>\n            return False<\/p>\n<p>        # \u65b9\u6cd5\u7279\u5b9a\u7684\u89c4\u5219<br \/>\n        method_rules &#061; {<br \/>\n            &#039;DELETE&#039;: lambda: operation_type in [&#039;deletion&#039;, &#039;removal&#039;],<br \/>\n            &#039;PUT&#039;: lambda: operation_type in [&#039;update&#039;, &#039;replace&#039;] and not needs_response_data,<br \/>\n            &#039;PATCH&#039;: lambda: operation_type in [&#039;partial_update&#039;] and not needs_response_data,<br \/>\n            &#039;POST&#039;: lambda: operation_type in [&#039;state_change&#039;, &#039;action&#039;] and not needs_response_data<br \/>\n        }<\/p>\n<p>        rule &#061; method_rules.get(request_method)<br \/>\n        if not rule:<br \/>\n            return False<\/p>\n<p>        return rule()<\/p>\n<h3>7.2 \u54cd\u5e94\u5934\u90e8\u8bbe\u8ba1<\/h3>\n<h4>\u5fc5\u9700\u4e0e\u63a8\u8350\u7684\u5934\u90e8<\/h4>\n<p>http<\/p>\n<p># \u5b8c\u6574\u7684204 No Content\u54cd\u5e94\u793a\u4f8b<br \/>\nHTTP\/1.1 204 No Content<br \/>\nDate: Mon, 23 Jan 2023 10:30:45 GMT<br \/>\nServer: Apache\/2.4.41<br \/>\nETag: &#034;abc123def456&#034;<br \/>\nLast-Modified: Mon, 23 Jan 2023 10:30:45 GMT<br \/>\nCache-Control: no-store, no-cache, must-revalidate<br \/>\nPragma: no-cache<br \/>\nExpires: 0<br \/>\nX-Request-ID: req_1234567890abcdef<br \/>\nX-RateLimit-Remaining: 999<br \/>\nX-Deleted-Resource: \/api\/users\/123<br \/>\nX-Operation-Type: delete<\/p>\n<h4>\u5934\u90e8\u8bbe\u8ba1\u539f\u5219<\/h4>\n<p>python<\/p>\n<p># 204\u54cd\u5e94\u5934\u90e8\u751f\u6210\u5668<br \/>\nclass NoContentHeadersBuilder:<br \/>\n    &#034;&#034;&#034;\u6784\u5efa204 No Content\u54cd\u5e94\u7684\u5934\u90e8&#034;&#034;&#034;<\/p>\n<p>    def __init__(self, request):<br \/>\n        self.request &#061; request<br \/>\n        self.headers &#061; {}<\/p>\n<p>        # \u8bbe\u7f6e\u9ed8\u8ba4\u5934\u90e8<br \/>\n        self._set_default_headers()<\/p>\n<p>    def _set_default_headers(self):<br \/>\n        &#034;&#034;&#034;\u8bbe\u7f6e\u9ed8\u8ba4\u5934\u90e8&#034;&#034;&#034;<br \/>\n        self.headers.update({<br \/>\n            &#039;Date&#039;: self._format_date(),<br \/>\n            &#039;Server&#039;: &#039;CustomAPI\/1.0&#039;,<br \/>\n            &#039;X-Request-ID&#039;: self.request.headers.get(&#039;X-Request-ID&#039;, &#039;&#039;),<br \/>\n            &#039;X-Content-Type-Options&#039;: &#039;nosniff&#039;,<br \/>\n            &#039;X-Frame-Options&#039;: &#039;DENY&#039;<br \/>\n        })<\/p>\n<p>    def add_resource_metadata(self, resource_type, resource_id, action):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u8d44\u6e90\u5143\u6570\u636e\u5934\u90e8&#034;&#034;&#034;<br \/>\n        if resource_id:<br \/>\n            self.headers[f&#039;X-{resource_type.capitalize()}-ID&#039;] &#061; str(resource_id)<br \/>\n            self.headers[f&#039;X-{action.capitalize()}-Resource&#039;] &#061; \\\\<br \/>\n                f&#039;\/api\/{resource_type}\/{resource_id}&#039;<br \/>\n        return self<\/p>\n<p>    def add_cache_headers(self, cache_directive&#061;&#039;no-cache&#039;):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u7f13\u5b58\u63a7\u5236\u5934\u90e8&#034;&#034;&#034;<br \/>\n        self.headers[&#039;Cache-Control&#039;] &#061; cache_directive<br \/>\n        self.headers[&#039;Pragma&#039;] &#061; &#039;no-cache&#039;<br \/>\n        self.headers[&#039;Expires&#039;] &#061; &#039;0&#039;<br \/>\n        return self<\/p>\n<p>    def add_etag(self, etag_value):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0ETag\u5934\u90e8&#034;&#034;&#034;<br \/>\n        if etag_value:<br \/>\n            self.headers[&#039;ETag&#039;] &#061; f&#039;&#034;{etag_value}&#034;&#039;<br \/>\n        return self<\/p>\n<p>    def add_last_modified(self, timestamp):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0Last-Modified\u5934\u90e8&#034;&#034;&#034;<br \/>\n        if timestamp:<br \/>\n            self.headers[&#039;Last-Modified&#039;] &#061; self._format_date(timestamp)<br \/>\n        return self<\/p>\n<p>    def add_rate_limit_headers(self, remaining, limit&#061;1000, reset&#061;None):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u901f\u7387\u9650\u5236\u5934\u90e8&#034;&#034;&#034;<br \/>\n        self.headers[&#039;X-RateLimit-Limit&#039;] &#061; str(limit)<br \/>\n        self.headers[&#039;X-RateLimit-Remaining&#039;] &#061; str(remaining)<\/p>\n<p>        if reset:<br \/>\n            self.headers[&#039;X-RateLimit-Reset&#039;] &#061; str(reset)<\/p>\n<p>        return self<\/p>\n<p>    def add_custom_header(self, name, value):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u81ea\u5b9a\u4e49\u5934\u90e8&#034;&#034;&#034;<br \/>\n        self.headers[name] &#061; value<br \/>\n        return self<\/p>\n<p>    def build(self):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa\u5934\u90e8\u5b57\u5178&#034;&#034;&#034;<br \/>\n        return self.headers<\/p>\n<p>    def _format_date(self, dt&#061;None):<br \/>\n        &#034;&#034;&#034;\u683c\u5f0f\u5316\u65e5\u671f\u4e3aHTTP\u65e5\u671f\u683c\u5f0f&#034;&#034;&#034;<br \/>\n        from datetime import datetime<br \/>\n        import email.utils<\/p>\n<p>        if dt is None:<br \/>\n            dt &#061; datetime.utcnow()<br \/>\n        elif isinstance(dt, str):<br \/>\n            dt &#061; datetime.fromisoformat(dt.replace(&#039;Z&#039;, &#039;&#043;00:00&#039;))<\/p>\n<p>        return email.utils.format_datetime(dt, usegmt&#061;True)<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b<br \/>\nfrom flask import request<\/p>\n<p>&#064;app.route(&#039;\/api\/users\/&lt;int:user_id&gt;&#039;, methods&#061;[&#039;DELETE&#039;])<br \/>\ndef delete_user(user_id):<br \/>\n    &#034;&#034;&#034;\u5220\u9664\u7528\u6237\u7aef\u70b9&#034;&#034;&#034;<\/p>\n<p>    # \u9a8c\u8bc1\u7528\u6237\u5b58\u5728\u548c\u6743\u9650<br \/>\n    user &#061; User.query.get(user_id)<br \/>\n    if not user:<br \/>\n        return jsonify({&#039;error&#039;: &#039;User not found&#039;}), 404<\/p>\n<p>    # \u68c0\u67e5\u6743\u9650<br \/>\n    if not current_user.can_delete(user):<br \/>\n        return jsonify({&#039;error&#039;: &#039;Permission denied&#039;}), 403<\/p>\n<p>    # \u6267\u884c\u5220\u9664<br \/>\n    db.session.delete(user)<br \/>\n    db.session.commit()<\/p>\n<p>    # \u6784\u5efa204\u54cd\u5e94<br \/>\n    headers_builder &#061; NoContentHeadersBuilder(request)<\/p>\n<p>    response_headers &#061; (headers_builder<br \/>\n        .add_resource_metadata(&#039;user&#039;, user_id, &#039;deleted&#039;)<br \/>\n        .add_etag(user.generate_etag())<br \/>\n        .add_last_modified(datetime.utcnow())<br \/>\n        .add_cache_headers(&#039;no-store, must-revalidate&#039;)<br \/>\n        .add_rate_limit_headers(remaining&#061;999)<br \/>\n        .add_custom_header(&#039;X-User-Email&#039;, user.email)  # \u4ec5\u7528\u4e8e\u5ba1\u8ba1<br \/>\n        .build())<\/p>\n<p>    # \u8fd4\u56de204 No Content<br \/>\n    return &#039;&#039;, 204, response_headers<\/p>\n<h3>7.3 \u5b9e\u73b0\u6a21\u5f0f\u4e0e\u6700\u4f73\u5b9e\u8df5<\/h3>\n<h4>\u5b8c\u6574\u5b9e\u73b0\u793a\u4f8b<\/h4>\n<p>python<\/p>\n<p># \u5b8c\u6574\u7684204 No Content\u5b9e\u73b0\u6a21\u5f0f<br \/>\nfrom flask import Flask, request, jsonify, Response<br \/>\nfrom flask_sqlalchemy import SQLAlchemy<br \/>\nfrom datetime import datetime<br \/>\nimport hashlib<\/p>\n<p>app &#061; Flask(__name__)<br \/>\napp.config[&#039;SQLALCHEMY_DATABASE_URI&#039;] &#061; &#039;sqlite:\/\/\/app.db&#039;<br \/>\ndb &#061; SQLAlchemy(app)<\/p>\n<p># \u6570\u636e\u6a21\u578b<br \/>\nclass Article(db.Model):<br \/>\n    id &#061; db.Column(db.Integer, primary_key&#061;True)<br \/>\n    title &#061; db.Column(db.String(200), nullable&#061;False)<br \/>\n    content &#061; db.Column(db.Text, nullable&#061;False)<br \/>\n    author_id &#061; db.Column(db.Integer, nullable&#061;False)<br \/>\n    is_published &#061; db.Column(db.Boolean, default&#061;False)<br \/>\n    created_at &#061; db.Column(db.DateTime, default&#061;datetime.utcnow)<br \/>\n    updated_at &#061; db.Column(db.DateTime, default&#061;datetime.utcnow,<br \/>\n                          onupdate&#061;datetime.utcnow)<\/p>\n<p>    def generate_etag(self):<br \/>\n        &#034;&#034;&#034;\u751f\u6210ETag&#034;&#034;&#034;<br \/>\n        content &#061; f&#034;{self.id}{self.updated_at.isoformat()}{self.is_published}&#034;<br \/>\n        return hashlib.md5(content.encode()).hexdigest()<\/p>\n<p>class Notification(db.Model):<br \/>\n    id &#061; db.Column(db.Integer, primary_key&#061;True)<br \/>\n    user_id &#061; db.Column(db.Integer, nullable&#061;False)<br \/>\n    message &#061; db.Column(db.String(500), nullable&#061;False)<br \/>\n    is_read &#061; db.Column(db.Boolean, default&#061;False)<br \/>\n    created_at &#061; db.Column(db.DateTime, default&#061;datetime.utcnow)<br \/>\n    read_at &#061; db.Column(db.DateTime, nullable&#061;True)<\/p>\n<p># \u8f85\u52a9\u51fd\u6570<br \/>\ndef build_204_response(**kwargs):<br \/>\n    &#034;&#034;&#034;<br \/>\n    \u6784\u5efa204 No Content\u54cd\u5e94<\/p>\n<p>    Args:<br \/>\n        **kwargs: \u5934\u90e8\u53c2\u6570<br \/>\n            &#8211; etag: ETag\u503c<br \/>\n            &#8211; last_modified: \u6700\u540e\u4fee\u6539\u65f6\u95f4<br \/>\n            &#8211; resource_type: \u8d44\u6e90\u7c7b\u578b<br \/>\n            &#8211; resource_id: \u8d44\u6e90ID<br \/>\n            &#8211; action: \u6267\u884c\u7684\u64cd\u4f5c<br \/>\n            &#8211; cache_control: \u7f13\u5b58\u63a7\u5236\u6307\u4ee4<br \/>\n            &#8211; rate_limit_remaining: \u901f\u7387\u9650\u5236\u5269\u4f59<br \/>\n    &#034;&#034;&#034;<\/p>\n<p>    headers &#061; {<br \/>\n        &#039;Date&#039;: datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;),<br \/>\n        &#039;Server&#039;: &#039;ArticleAPI\/1.0&#039;<br \/>\n    }<\/p>\n<p>    # \u6dfb\u52a0ETag<br \/>\n    if kwargs.get(&#039;etag&#039;):<br \/>\n        headers[&#039;ETag&#039;] &#061; f&#039;&#034;{kwargs[&#034;etag&#034;]}&#034;&#039;<\/p>\n<p>    # \u6dfb\u52a0Last-Modified<br \/>\n    if kwargs.get(&#039;last_modified&#039;):<br \/>\n        if isinstance(kwargs[&#039;last_modified&#039;], datetime):<br \/>\n            headers[&#039;Last-Modified&#039;] &#061; kwargs[&#039;last_modified&#039;].strftime(<br \/>\n                &#039;%a, %d %b %Y %H:%M:%S GMT&#039;<br \/>\n            )<br \/>\n        else:<br \/>\n            headers[&#039;Last-Modified&#039;] &#061; kwargs[&#039;last_modified&#039;]<\/p>\n<p>    # \u6dfb\u52a0\u8d44\u6e90\u4fe1\u606f<br \/>\n    if kwargs.get(&#039;resource_type&#039;) and kwargs.get(&#039;resource_id&#039;):<br \/>\n        resource_type &#061; kwargs[&#039;resource_type&#039;].capitalize()<br \/>\n        action &#061; kwargs.get(&#039;action&#039;, &#039;updated&#039;).capitalize()<\/p>\n<p>        headers[f&#039;X-{resource_type}-ID&#039;] &#061; str(kwargs[&#039;resource_id&#039;])<br \/>\n        headers[f&#039;X-{action}-Resource&#039;] &#061; \\\\<br \/>\n            f&#039;\/api\/{kwargs[&#034;resource_type&#034;]}\/{kwargs[&#034;resource_id&#034;]}&#039;<\/p>\n<p>    # \u6dfb\u52a0\u7f13\u5b58\u63a7\u5236<br \/>\n    if kwargs.get(&#039;cache_control&#039;):<br \/>\n        headers[&#039;Cache-Control&#039;] &#061; kwargs[&#039;cache_control&#039;]<br \/>\n    else:<br \/>\n        headers[&#039;Cache-Control&#039;] &#061; &#039;no-cache, must-revalidate&#039;<\/p>\n<p>    # \u6dfb\u52a0\u901f\u7387\u9650\u5236<br \/>\n    if kwargs.get(&#039;rate_limit_remaining&#039;):<br \/>\n        headers[&#039;X-RateLimit-Remaining&#039;] &#061; str(kwargs[&#039;rate_limit_remaining&#039;])<\/p>\n<p>    # \u6dfb\u52a0\u8bf7\u6c42ID<br \/>\n    request_id &#061; request.headers.get(&#039;X-Request-ID&#039;)<br \/>\n    if request_id:<br \/>\n        headers[&#039;X-Request-ID&#039;] &#061; request_id<\/p>\n<p>    # \u5b89\u5168\u5934\u90e8<br \/>\n    headers.update({<br \/>\n        &#039;X-Content-Type-Options&#039;: &#039;nosniff&#039;,<br \/>\n        &#039;X-Frame-Options&#039;: &#039;DENY&#039;,<br \/>\n        &#039;X-XSS-Protection&#039;: &#039;1; mode&#061;block&#039;<br \/>\n    })<\/p>\n<p>    return Response(status&#061;204, headers&#061;headers)<\/p>\n<p># API\u7aef\u70b9<br \/>\n&#064;app.route(&#039;\/api\/articles\/&lt;int:article_id&gt;&#039;, methods&#061;[&#039;DELETE&#039;])<br \/>\ndef delete_article(article_id):<br \/>\n    &#034;&#034;&#034;\u5220\u9664\u6587\u7ae0&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u6587\u7ae0<br \/>\n    article &#061; Article.query.get(article_id)<br \/>\n    if not article:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Article not found&#039;}), 404<\/p>\n<p>    # \u68c0\u67e5\u6743\u9650<br \/>\n    current_user &#061; get_current_user()  # \u5047\u8bbe\u7684\u51fd\u6570<br \/>\n    if not current_user or current_user.id !&#061; article.author_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Permission denied&#039;}), 403<\/p>\n<p>    # \u8bb0\u5f55\u5220\u9664\u524d\u7684\u4fe1\u606f&#xff08;\u7528\u4e8e\u5ba1\u8ba1&#xff09;<br \/>\n    deleted_info &#061; {<br \/>\n        &#039;id&#039;: article.id,<br \/>\n        &#039;title&#039;: article.title,<br \/>\n        &#039;author_id&#039;: article.author_id,<br \/>\n        &#039;deleted_at&#039;: datetime.utcnow().isoformat(),<br \/>\n        &#039;deleted_by&#039;: current_user.id<br \/>\n    }<\/p>\n<p>    # \u6267\u884c\u5220\u9664<br \/>\n    db.session.delete(article)<br \/>\n    db.session.commit()<\/p>\n<p>    # \u8bb0\u5f55\u5ba1\u8ba1\u65e5\u5fd7<br \/>\n    log_audit_event(&#039;article_deleted&#039;, deleted_info)<\/p>\n<p>    # \u8fd4\u56de204\u54cd\u5e94<br \/>\n    return build_204_response(<br \/>\n        etag&#061;article.generate_etag(),<br \/>\n        last_modified&#061;datetime.utcnow(),<br \/>\n        resource_type&#061;&#039;article&#039;,<br \/>\n        resource_id&#061;article_id,<br \/>\n        action&#061;&#039;deleted&#039;,<br \/>\n        cache_control&#061;&#039;no-store, must-revalidate&#039;,<br \/>\n        rate_limit_remaining&#061;999<br \/>\n    )<\/p>\n<p>&#064;app.route(&#039;\/api\/articles\/&lt;int:article_id&gt;\/publish&#039;, methods&#061;[&#039;PUT&#039;])<br \/>\ndef publish_article(article_id):<br \/>\n    &#034;&#034;&#034;\u53d1\u5e03\u6587\u7ae0&#xff08;\u66f4\u65b0\u72b6\u6001&#xff09;&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u6587\u7ae0<br \/>\n    article &#061; Article.query.get(article_id)<br \/>\n    if not article:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Article not found&#039;}), 404<\/p>\n<p>    # \u68c0\u67e5\u6743\u9650<br \/>\n    current_user &#061; get_current_user()<br \/>\n    if not current_user or current_user.id !&#061; article.author_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Permission denied&#039;}), 403<\/p>\n<p>    # \u5982\u679c\u5df2\u7ecf\u53d1\u5e03&#xff0c;\u8fd4\u56de204&#xff08;\u5e42\u7b49&#xff09;<br \/>\n    if article.is_published:<br \/>\n        return build_204_response(<br \/>\n            etag&#061;article.generate_etag(),<br \/>\n            last_modified&#061;article.updated_at,<br \/>\n            resource_type&#061;&#039;article&#039;,<br \/>\n            resource_id&#061;article_id,<br \/>\n            action&#061;&#039;already_published&#039;,<br \/>\n            cache_control&#061;&#039;no-cache&#039;<br \/>\n        )<\/p>\n<p>    # \u66f4\u65b0\u6587\u7ae0\u72b6\u6001<br \/>\n    article.is_published &#061; True<br \/>\n    article.updated_at &#061; datetime.utcnow()<\/p>\n<p>    db.session.commit()<\/p>\n<p>    # \u89e6\u53d1\u540e\u7eed\u64cd\u4f5c&#xff08;\u4f8b\u5982\u53d1\u9001\u901a\u77e5&#xff09;<br \/>\n    send_article_published_notification(article)<\/p>\n<p>    # \u8fd4\u56de204\u54cd\u5e94<br \/>\n    return build_204_response(<br \/>\n        etag&#061;article.generate_etag(),<br \/>\n        last_modified&#061;article.updated_at,<br \/>\n        resource_type&#061;&#039;article&#039;,<br \/>\n        resource_id&#061;article_id,<br \/>\n        action&#061;&#039;published&#039;,<br \/>\n        cache_control&#061;&#039;no-cache, must-revalidate&#039;<br \/>\n    )<\/p>\n<p>&#064;app.route(&#039;\/api\/notifications\/&lt;int:notification_id&gt;\/read&#039;, methods&#061;[&#039;PUT&#039;])<br \/>\ndef mark_notification_read(notification_id):<br \/>\n    &#034;&#034;&#034;\u6807\u8bb0\u901a\u77e5\u4e3a\u5df2\u8bfb&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u901a\u77e5<br \/>\n    notification &#061; Notification.query.get(notification_id)<br \/>\n    if not notification:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Notification not found&#039;}), 404<\/p>\n<p>    # \u68c0\u67e5\u6743\u9650<br \/>\n    current_user &#061; get_current_user()<br \/>\n    if not current_user or current_user.id !&#061; notification.user_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Permission denied&#039;}), 403<\/p>\n<p>    # \u5982\u679c\u5df2\u7ecf\u6807\u8bb0\u4e3a\u5df2\u8bfb&#xff0c;\u8fd4\u56de204&#xff08;\u5e42\u7b49&#xff09;<br \/>\n    if notification.is_read:<br \/>\n        return build_204_response(<br \/>\n            resource_type&#061;&#039;notification&#039;,<br \/>\n            resource_id&#061;notification_id,<br \/>\n            action&#061;&#039;already_read&#039;,<br \/>\n            cache_control&#061;&#039;no-cache&#039;<br \/>\n        )<\/p>\n<p>    # \u66f4\u65b0\u901a\u77e5\u72b6\u6001<br \/>\n    notification.is_read &#061; True<br \/>\n    notification.read_at &#061; datetime.utcnow()<\/p>\n<p>    db.session.commit()<\/p>\n<p>    # \u8fd4\u56de204\u54cd\u5e94<br \/>\n    return build_204_response(<br \/>\n        resource_type&#061;&#039;notification&#039;,<br \/>\n        resource_id&#061;notification_id,<br \/>\n        action&#061;&#039;marked_read&#039;,<br \/>\n        cache_control&#061;&#039;no-cache&#039;<br \/>\n    )<\/p>\n<p>&#064;app.route(&#039;\/api\/users\/&lt;int:user_id&gt;\/preferences&#039;, methods&#061;[&#039;PATCH&#039;])<br \/>\ndef update_user_preferences(user_id):<br \/>\n    &#034;&#034;&#034;\u66f4\u65b0\u7528\u6237\u504f\u597d\u8bbe\u7f6e&#034;&#034;&#034;<\/p>\n<p>    # \u83b7\u53d6\u7528\u6237<br \/>\n    user &#061; User.query.get(user_id)<br \/>\n    if not user:<br \/>\n        return jsonify({&#039;error&#039;: &#039;User not found&#039;}), 404<\/p>\n<p>    # \u68c0\u67e5\u6743\u9650<br \/>\n    current_user &#061; get_current_user()<br \/>\n    if not current_user or current_user.id !&#061; user_id:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Permission denied&#039;}), 403<\/p>\n<p>    # \u89e3\u6790\u8bf7\u6c42<br \/>\n    data &#061; request.get_json()<br \/>\n    if not data:<br \/>\n        return jsonify({&#039;error&#039;: &#039;No data provided&#039;}), 400<\/p>\n<p>    # \u9a8c\u8bc1\u548c\u66f4\u65b0\u504f\u597d\u8bbe\u7f6e<br \/>\n    preferences &#061; data.get(&#039;preferences&#039;, {})<\/p>\n<p>    # \u9a8c\u8bc1\u504f\u597d\u8bbe\u7f6e\u683c\u5f0f<br \/>\n    if not isinstance(preferences, dict):<br \/>\n        return jsonify({&#039;error&#039;: &#039;Preferences must be an object&#039;}), 400<\/p>\n<p>    # \u66f4\u65b0\u7528\u6237\u504f\u597d&#xff08;\u5047\u8bbeUser\u6a21\u578b\u6709preferences\u5b57\u6bb5&#xff09;<br \/>\n    user.preferences &#061; {**user.preferences, **preferences}<br \/>\n    user.updated_at &#061; datetime.utcnow()<\/p>\n<p>    db.session.commit()<\/p>\n<p>    # \u5bf9\u4e8e\u504f\u597d\u8bbe\u7f6e\u66f4\u65b0&#xff0c;\u5ba2\u6237\u7aef\u901a\u5e38\u4e0d\u9700\u8981\u5b8c\u6574\u7684\u7528\u6237\u5bf9\u8c61<br \/>\n    # \u8fd4\u56de204\u54cd\u5e94<br \/>\n    return build_204_response(<br \/>\n        etag&#061;user.generate_etag(),<br \/>\n        last_modified&#061;user.updated_at,<br \/>\n        resource_type&#061;&#039;user&#039;,<br \/>\n        resource_id&#061;user_id,<br \/>\n        action&#061;&#039;preferences_updated&#039;,<br \/>\n        cache_control&#061;&#039;private, no-cache&#039;<br \/>\n    )<\/p>\n<p># \u6279\u91cf\u64cd\u4f5c\u793a\u4f8b<br \/>\n&#064;app.route(&#039;\/api\/notifications\/read-all&#039;, methods&#061;[&#039;POST&#039;])<br \/>\ndef mark_all_notifications_read():<br \/>\n    &#034;&#034;&#034;\u6807\u8bb0\u6240\u6709\u901a\u77e5\u4e3a\u5df2\u8bfb&#034;&#034;&#034;<\/p>\n<p>    current_user &#061; get_current_user()<br \/>\n    if not current_user:<br \/>\n        return jsonify({&#039;error&#039;: &#039;Authentication required&#039;}), 401<\/p>\n<p>    # \u83b7\u53d6\u7528\u6237\u7684\u6240\u6709\u672a\u8bfb\u901a\u77e5<br \/>\n    unread_notifications &#061; Notification.query.filter_by(<br \/>\n        user_id&#061;current_user.id,<br \/>\n        is_read&#061;False<br \/>\n    ).all()<\/p>\n<p>    if not unread_notifications:<br \/>\n        # \u6ca1\u6709\u672a\u8bfb\u901a\u77e5&#xff0c;\u8fd4\u56de204&#xff08;\u5e42\u7b49&#xff09;<br \/>\n        return build_204_response(<br \/>\n            action&#061;&#039;no_unread_notifications&#039;,<br \/>\n            cache_control&#061;&#039;no-cache&#039;<br \/>\n        )<\/p>\n<p>    # \u6279\u91cf\u66f4\u65b0<br \/>\n    update_count &#061; 0<br \/>\n    now &#061; datetime.utcnow()<\/p>\n<p>    for notification in unread_notifications:<br \/>\n        notification.is_read &#061; True<br \/>\n        notification.read_at &#061; now<br \/>\n        update_count &#043;&#061; 1<\/p>\n<p>    db.session.commit()<\/p>\n<p>    # \u8fd4\u56de204&#xff0c;\u4f46\u5728\u5934\u90e8\u5305\u542b\u7edf\u8ba1\u4fe1\u606f<br \/>\n    response &#061; build_204_response(<br \/>\n        action&#061;&#039;all_notifications_marked_read&#039;,<br \/>\n        cache_control&#061;&#039;no-cache&#039;<br \/>\n    )<\/p>\n<p>    # \u6dfb\u52a0\u6279\u91cf\u64cd\u4f5c\u7edf\u8ba1<br \/>\n    response.headers[&#039;X-Notifications-Marked-Read&#039;] &#061; str(update_count)<br \/>\n    response.headers[&#039;X-Operation-Timestamp&#039;] &#061; now.isoformat()<\/p>\n<p>    return response<\/p>\n<p># \u8f85\u52a9\u51fd\u6570\u5b9e\u73b0<br \/>\ndef get_current_user():<br \/>\n    &#034;&#034;&#034;\u83b7\u53d6\u5f53\u524d\u7528\u6237&#xff08;\u793a\u4f8b\u5b9e\u73b0&#xff09;&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u4e2d\u4f1a\u4ecesession\u6216token\u4e2d\u83b7\u53d6<br \/>\n    return None<\/p>\n<p>def log_audit_event(event_type, data):<br \/>\n    &#034;&#034;&#034;\u8bb0\u5f55\u5ba1\u8ba1\u65e5\u5fd7&#034;&#034;&#034;<br \/>\n    audit_log &#061; {<br \/>\n        &#039;event&#039;: event_type,<br \/>\n        &#039;data&#039;: data,<br \/>\n        &#039;timestamp&#039;: datetime.utcnow().isoformat(),<br \/>\n        &#039;ip_address&#039;: request.remote_addr,<br \/>\n        &#039;user_agent&#039;: request.headers.get(&#039;User-Agent&#039;)<br \/>\n    }<br \/>\n    print(f&#034;[AUDIT] {audit_log}&#034;)  # \u5b9e\u9645\u4e2d\u4f1a\u5199\u5165\u65e5\u5fd7\u7cfb\u7edf<\/p>\n<p>def send_article_published_notification(article):<br \/>\n    &#034;&#034;&#034;\u53d1\u9001\u6587\u7ae0\u53d1\u5e03\u901a\u77e5&#034;&#034;&#034;<br \/>\n    # \u5b9e\u9645\u5b9e\u73b0\u4f1a\u53d1\u9001\u90ae\u4ef6\u3001\u63a8\u9001\u7b49<br \/>\n    pass<\/p>\n<h3>7.4 \u5ba2\u6237\u7aef\u5904\u7406204\u54cd\u5e94<\/h3>\n<h4>\u6d4f\u89c8\u5668JavaScript\u5904\u7406<\/h4>\n<p>javascript<\/p>\n<p>\/\/ 204 No Content\u54cd\u5e94\u5904\u7406\u5668<br \/>\nclass NoContentResponseHandler {<br \/>\n    \/**<br \/>\n     * \u5904\u7406204 No Content\u54cd\u5e94<br \/>\n     *\/<\/p>\n<p>    constructor() {<br \/>\n        this.eventHandlers &#061; new Map();<br \/>\n        this.setupGlobalHandler();<br \/>\n    }<\/p>\n<p>    setupGlobalHandler() {<br \/>\n        \/\/ \u62e6\u622afetch\u8bf7\u6c42\u4ee5\u5904\u7406204\u54cd\u5e94<br \/>\n        const originalFetch &#061; window.fetch;<\/p>\n<p>        window.fetch &#061; async function(&#8230;args) {<br \/>\n            const response &#061; await originalFetch.apply(this, args);<\/p>\n<p>            \/\/ \u68c0\u67e5\u662f\u5426\u4e3a204\u54cd\u5e94<br \/>\n            if (response.status &#061;&#061;&#061; 204) {<br \/>\n                \/\/ \u89e6\u53d1\u5168\u5c40204\u5904\u7406\u4e8b\u4ef6<br \/>\n                const event &#061; new CustomEvent(&#039;noContentResponse&#039;, {<br \/>\n                    detail: {<br \/>\n                        response,<br \/>\n                        url: response.url,<br \/>\n                        timestamp: new Date().toISOString()<br \/>\n                    }<br \/>\n                });<br \/>\n                window.dispatchEvent(event);<\/p>\n<p>                \/\/ \u5904\u7406\u7279\u6b8a\u5934\u90e8<br \/>\n                await NoContentResponseHandler.handleSpecialHeaders(response);<br \/>\n            }<\/p>\n<p>            return response;<br \/>\n        };<br \/>\n    }<\/p>\n<p>    static async handleSpecialHeaders(response) {<br \/>\n        \/\/ \u5904\u7406\u6709\u7279\u6b8a\u610f\u4e49\u7684\u5934\u90e8<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u6709\u88ab\u5220\u9664\u7684\u8d44\u6e90<br \/>\n        const deletedResource &#061; response.headers.get(&#039;X-Deleted-Resource&#039;);<br \/>\n        if (deletedResource) {<br \/>\n            await this.handleResourceDeletion(deletedResource);<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u6709ETag\u66f4\u65b0<br \/>\n        const etag &#061; response.headers.get(&#039;ETag&#039;);<br \/>\n        if (etag) {<br \/>\n            await this.updateCacheEtag(response.url, etag);<br \/>\n        }<\/p>\n<p>        \/\/ \u68c0\u67e5\u64cd\u4f5c\u7c7b\u578b<br \/>\n        const operationType &#061; response.headers.get(&#039;X-Operation-Type&#039;);<br \/>\n        if (operationType) {<br \/>\n            await this.handleOperationType(operationType, response);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static async handleResourceDeletion(resourceUrl) {<br \/>\n        console.log(&#096;Resource deleted: ${resourceUrl}&#096;);<\/p>\n<p>        \/\/ \u4ece\u672c\u5730\u7f13\u5b58\u4e2d\u79fb\u9664\u8d44\u6e90<br \/>\n        if (&#039;caches&#039; in window) {<br \/>\n            try {<br \/>\n                const cache &#061; await caches.open(&#039;api-cache&#039;);<br \/>\n                await cache.delete(resourceUrl);<\/p>\n<p>                \/\/ \u4e5f\u5220\u9664\u76f8\u5173\u7684URL\u6a21\u5f0f<br \/>\n                await cache.delete(new URL(resourceUrl).pathname);<br \/>\n            } catch (error) {<br \/>\n                console.warn(&#039;Failed to delete from cache:&#039;, error);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        \/\/ \u5982\u679cUI\u4e2d\u663e\u793a\u4e86\u8fd9\u4e2a\u8d44\u6e90&#xff0c;\u66f4\u65b0UI<br \/>\n        await this.updateUIForDeletedResource(resourceUrl);<br \/>\n    }<\/p>\n<p>    static async updateCacheEtag(url, etag) {<br \/>\n        \/\/ \u66f4\u65b0\u672c\u5730ETag\u7f13\u5b58<br \/>\n        const cacheKey &#061; &#096;etag:${url}&#096;;<br \/>\n        localStorage.setItem(cacheKey, etag);<br \/>\n    }<\/p>\n<p>    static async handleOperationType(operationType, response) {<br \/>\n        switch (operationType) {<br \/>\n            case &#039;delete&#039;:<br \/>\n                \/\/ \u5904\u7406\u5220\u9664\u64cd\u4f5c<br \/>\n                await this.handleDeleteOperation(response);<br \/>\n                break;<\/p>\n<p>            case &#039;update&#039;:<br \/>\n                \/\/ \u5904\u7406\u66f4\u65b0\u64cd\u4f5c<br \/>\n                await this.handleUpdateOperation(response);<br \/>\n                break;<\/p>\n<p>            case &#039;publish&#039;:<br \/>\n                \/\/ \u5904\u7406\u53d1\u5e03\u64cd\u4f5c<br \/>\n                await this.handlePublishOperation(response);<br \/>\n                break;<\/p>\n<p>            default:<br \/>\n                console.log(&#096;Unknown operation type: ${operationType}&#096;);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static async handleDeleteOperation(response) {<br \/>\n        \/\/ \u663e\u793a\u5220\u9664\u6210\u529f\u7684\u6d88\u606f<br \/>\n        this.showToast(&#039;Resource deleted successfully&#039;, &#039;success&#039;);<\/p>\n<p>        \/\/ \u5982\u679c\u5f53\u524d\u9875\u9762\u662f\u88ab\u5220\u9664\u7684\u8d44\u6e90&#xff0c;\u91cd\u5b9a\u5411\u5230\u5217\u8868\u9875<br \/>\n        const deletedResource &#061; response.headers.get(&#039;X-Deleted-Resource&#039;);<br \/>\n        if (deletedResource &amp;&amp; window.location.pathname.includes(deletedResource)) {<br \/>\n            setTimeout(() &#061;&gt; {<br \/>\n                const listUrl &#061; deletedResource.substring(0, deletedResource.lastIndexOf(&#039;\/&#039;));<br \/>\n                window.location.href &#061; listUrl;<br \/>\n            }, 1500);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static async handleUpdateOperation(response) {<br \/>\n        \/\/ \u663e\u793a\u66f4\u65b0\u6210\u529f\u7684\u6d88\u606f<br \/>\n        this.showToast(&#039;Resource updated successfully&#039;, &#039;success&#039;);<\/p>\n<p>        \/\/ \u91cd\u65b0\u52a0\u8f7d\u76f8\u5173\u6570\u636e<br \/>\n        const resourceId &#061; response.headers.get(&#039;X-Resource-ID&#039;);<br \/>\n        if (resourceId) {<br \/>\n            await this.refreshResourceData(resourceId);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static async handlePublishOperation(response) {<br \/>\n        \/\/ \u663e\u793a\u53d1\u5e03\u6210\u529f\u7684\u6d88\u606f<br \/>\n        this.showToast(&#039;Resource published successfully&#039;, &#039;success&#039;);<\/p>\n<p>        \/\/ \u66f4\u65b0UI\u72b6\u6001<br \/>\n        const resourceId &#061; response.headers.get(&#039;X-Resource-ID&#039;);<br \/>\n        if (resourceId) {<br \/>\n            await this.updatePublishStatus(resourceId, true);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static showToast(message, type &#061; &#039;info&#039;) {<br \/>\n        \/\/ \u663e\u793a\u6d88\u606f\u901a\u77e5<br \/>\n        const toast &#061; document.createElement(&#039;div&#039;);<br \/>\n        toast.className &#061; &#096;toast toast-${type}&#096;;<br \/>\n        toast.textContent &#061; message;<br \/>\n        toast.style.cssText &#061; &#096;<br \/>\n            position: fixed;<br \/>\n            top: 20px;<br \/>\n            right: 20px;<br \/>\n            padding: 12px 20px;<br \/>\n            background: ${type &#061;&#061;&#061; &#039;success&#039; ? &#039;#28a745&#039; : &#039;#17a2b8&#039;};<br \/>\n            color: white;<br \/>\n            border-radius: 4px;<br \/>\n            z-index: 1000;<br \/>\n            animation: fadeInOut 3s ease-in-out;<br \/>\n        &#096;;<\/p>\n<p>        document.body.appendChild(toast);<\/p>\n<p>        setTimeout(() &#061;&gt; {<br \/>\n            toast.remove();<br \/>\n        }, 3000);<br \/>\n    }<\/p>\n<p>    static async updateUIForDeletedResource(resourceUrl) {<br \/>\n        \/\/ \u67e5\u627e\u5e76\u79fb\u9664\u663e\u793a\u8be5\u8d44\u6e90\u7684UI\u5143\u7d20<br \/>\n        const resourceElements &#061; document.querySelectorAll(&#096;[data-resource-url&#061;&#034;${resourceUrl}&#034;]&#096;);<br \/>\n        resourceElements.forEach(element &#061;&gt; {<br \/>\n            element.style.transition &#061; &#039;opacity 0.3s&#039;;<br \/>\n            element.style.opacity &#061; &#039;0&#039;;<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                element.remove();<br \/>\n            }, 300);<br \/>\n        });<\/p>\n<p>        \/\/ \u5982\u679c\u6ca1\u6709\u66f4\u591a\u8d44\u6e90&#xff0c;\u663e\u793a\u7a7a\u72b6\u6001<br \/>\n        await this.checkEmptyState();<br \/>\n    }<\/p>\n<p>    static async refreshResourceData(resourceId) {<br \/>\n        \/\/ \u91cd\u65b0\u83b7\u53d6\u8d44\u6e90\u6570\u636e\u5e76\u66f4\u65b0UI<br \/>\n        try {<br \/>\n            const response &#061; await fetch(&#096;\/api\/resources\/${resourceId}&#096;);<br \/>\n            if (response.ok) {<br \/>\n                const data &#061; await response.json();<\/p>\n<p>                \/\/ \u89e6\u53d1\u6570\u636e\u66f4\u65b0\u4e8b\u4ef6<br \/>\n                const event &#061; new CustomEvent(&#039;resourceUpdated&#039;, {<br \/>\n                    detail: { resourceId, data }<br \/>\n                });<br \/>\n                window.dispatchEvent(event);<br \/>\n            }<br \/>\n        } catch (error) {<br \/>\n            console.error(&#039;Failed to refresh resource:&#039;, error);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    static async updatePublishStatus(resourceId, isPublished) {<br \/>\n        \/\/ \u66f4\u65b0\u53d1\u5e03\u72b6\u6001<br \/>\n        const publishButtons &#061; document.querySelectorAll(&#096;[data-resource-id&#061;&#034;${resourceId}&#034;] .publish-btn&#096;);<br \/>\n        publishButtons.forEach(button &#061;&gt; {<br \/>\n            if (isPublished) {<br \/>\n                button.textContent &#061; &#039;Published&#039;;<br \/>\n                button.disabled &#061; true;<br \/>\n                button.classList.add(&#039;published&#039;);<br \/>\n            }<br \/>\n        });<br \/>\n    }<\/p>\n<p>    static async checkEmptyState() {<br \/>\n        \/\/ \u68c0\u67e5\u662f\u5426\u4e3a\u7a7a\u72b6\u6001<br \/>\n        const resourceList &#061; document.getElementById(&#039;resource-list&#039;);<br \/>\n        if (resourceList &amp;&amp; resourceList.children.length &#061;&#061;&#061; 0) {<br \/>\n            const emptyState &#061; document.createElement(&#039;div&#039;);<br \/>\n            emptyState.className &#061; &#039;empty-state&#039;;<br \/>\n            emptyState.innerHTML &#061; &#096;<br \/>\n                &lt;h3&gt;No resources found&lt;\/h3&gt;<br \/>\n                &lt;p&gt;You haven&#039;t created any resources yet.&lt;\/p&gt;<br \/>\n                &lt;button class&#061;&#034;btn btn-primary&#034; onclick&#061;&#034;createResource()&#034;&gt;<br \/>\n                    Create Your First Resource<br \/>\n                &lt;\/button&gt;<br \/>\n            &#096;;<br \/>\n            resourceList.appendChild(emptyState);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u6ce8\u518c204\u54cd\u5e94\u4e8b\u4ef6\u5904\u7406\u5668<br \/>\n     *\/<br \/>\n    registerHandler(eventName, handler) {<br \/>\n        if (!this.eventHandlers.has(eventName)) {<br \/>\n            this.eventHandlers.set(eventName, []);<br \/>\n        }<br \/>\n        this.eventHandlers.get(eventName).push(handler);<br \/>\n    }<\/p>\n<p>    \/**<br \/>\n     * \u901a\u7528\u7684204\u54cd\u5e94\u5904\u7406\u51fd\u6570<br \/>\n     *\/<br \/>\n    async handle204Response(response, originalRequest) {<br \/>\n        const responseInfo &#061; {<br \/>\n            status: response.status,<br \/>\n            statusText: response.statusText,<br \/>\n            url: response.url,<br \/>\n            headers: Object.fromEntries(response.headers.entries()),<br \/>\n            timestamp: new Date().toISOString(),<br \/>\n            originalRequest: {<br \/>\n                method: originalRequest.method,<br \/>\n                url: originalRequest.url<br \/>\n            }<br \/>\n        };<\/p>\n<p>        console.log(&#039;204 No Content Response:&#039;, responseInfo);<\/p>\n<p>        \/\/ \u6839\u636e\u8bf7\u6c42\u65b9\u6cd5\u6267\u884c\u4e0d\u540c\u7684\u5904\u7406<br \/>\n        switch (originalRequest.method) {<br \/>\n            case &#039;DELETE&#039;:<br \/>\n                await this.handleDelete204(response, originalRequest);<br \/>\n                break;<\/p>\n<p>            case &#039;PUT&#039;:<br \/>\n                await this.handlePut204(response, originalRequest);<br \/>\n                break;<\/p>\n<p>            case &#039;PATCH&#039;:<br \/>\n                await this.handlePatch204(response, originalRequest);<br \/>\n                break;<\/p>\n<p>            case &#039;POST&#039;:<br \/>\n                await this.handlePost204(response, originalRequest);<br \/>\n                break;<\/p>\n<p>            default:<br \/>\n                console.warn(&#096;Unhandled 204 for method: ${originalRequest.method}&#096;);<br \/>\n        }<\/p>\n<p>        \/\/ \u89e6\u53d1\u6ce8\u518c\u7684\u4e8b\u4ef6\u5904\u7406\u5668<br \/>\n        this.triggerEventHandlers(&#039;204Response&#039;, responseInfo);<\/p>\n<p>        \/\/ \u8fd4\u56de\u5904\u7406\u540e\u7684\u54cd\u5e94\u5bf9\u8c61<br \/>\n        return this.createEnhancedResponse(response, responseInfo);<br \/>\n    }<\/p>\n<p>    async handleDelete204(response, request) {<br \/>\n        \/\/ \u5220\u9664\u64cd\u4f5c\u7684\u7279\u6b8a\u5904\u7406<\/p>\n<p>        \/\/ \u4eceURL\u4e2d\u63d0\u53d6\u8d44\u6e90ID<br \/>\n        const resourceId &#061; this.extractResourceId(request.url);<br \/>\n        if (resourceId) {<br \/>\n            \/\/ \u4ece\u672c\u5730\u5b58\u50a8\u4e2d\u79fb\u9664<br \/>\n            this.removeFromLocalStorage(resourceId);<\/p>\n<p>            \/\/ \u66f4\u65b0UI<br \/>\n            await this.updateUIAfterDelete(resourceId);<br \/>\n        }<\/p>\n<p>        \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n        this.showSuccessMessage(&#039;Resource deleted successfully&#039;);<br \/>\n    }<\/p>\n<p>    async handlePut204(response, request) {<br \/>\n        \/\/ PUT\u64cd\u4f5c\u7684\u7279\u6b8a\u5904\u7406<\/p>\n<p>        \/\/ \u68c0\u67e5\u662f\u5426\u6709ETag\u66f4\u65b0<br \/>\n        const etag &#061; response.headers.get(&#039;ETag&#039;);<br \/>\n        if (etag) {<br \/>\n            \/\/ \u66f4\u65b0\u672c\u5730ETag<br \/>\n            await this.updateLocalEtag(request.url, etag);<br \/>\n        }<\/p>\n<p>        \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n        this.showSuccessMessage(&#039;Resource updated successfully&#039;);<br \/>\n    }<\/p>\n<p>    extractResourceId(url) {<br \/>\n        \/\/ \u4eceURL\u4e2d\u63d0\u53d6\u8d44\u6e90ID<br \/>\n        const match &#061; url.match(\/\\\\\/(\\\\d&#043;)(\\\\\/|$)\/);<br \/>\n        return match ? match[1] : null;<br \/>\n    }<\/p>\n<p>    removeFromLocalStorage(resourceId) {<br \/>\n        \/\/ \u4ecelocalStorage\u4e2d\u79fb\u9664\u8d44\u6e90<br \/>\n        const keysToRemove &#061; [];<br \/>\n        for (let i &#061; 0; i &lt; localStorage.length; i&#043;&#043;) {<br \/>\n            const key &#061; localStorage.key(i);<br \/>\n            if (key.includes(resourceId)) {<br \/>\n                keysToRemove.push(key);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        keysToRemove.forEach(key &#061;&gt; localStorage.removeItem(key));<br \/>\n    }<\/p>\n<p>    async updateUIAfterDelete(resourceId) {<br \/>\n        \/\/ \u66f4\u65b0UI<br \/>\n        const elements &#061; document.querySelectorAll(&#096;[data-resource-id&#061;&#034;${resourceId}&#034;]&#096;);<br \/>\n        elements.forEach(element &#061;&gt; {<br \/>\n            element.classList.add(&#039;deleting&#039;);<br \/>\n            setTimeout(() &#061;&gt; {<br \/>\n                element.remove();<br \/>\n                this.checkEmptyState();<br \/>\n            }, 300);<br \/>\n        });<br \/>\n    }<\/p>\n<p>    showSuccessMessage(message) {<br \/>\n        \/\/ \u663e\u793a\u6210\u529f\u6d88\u606f<br \/>\n        const messageDiv &#061; document.getElementById(&#039;message-container&#039;);<br \/>\n        if (messageDiv) {<br \/>\n            const alert &#061; document.createElement(&#039;div&#039;);<br \/>\n            alert.className &#061; &#039;alert alert-success&#039;;<br \/>\n            alert.textContent &#061; message;<br \/>\n            messageDiv.appendChild(alert);<\/p>\n<p>            setTimeout(() &#061;&gt; {<br \/>\n                alert.remove();<br \/>\n            }, 5000);<br \/>\n        }<br \/>\n    }<\/p>\n<p>    updateLocalEtag(url, etag) {<br \/>\n        \/\/ \u66f4\u65b0\u672c\u5730ETag\u5b58\u50a8<br \/>\n        const key &#061; &#096;etag_${btoa(url)}&#096;;<br \/>\n        localStorage.setItem(key, etag);<br \/>\n    }<\/p>\n<p>    triggerEventHandlers(eventName, data) {<br \/>\n        const handlers &#061; this.eventHandlers.get(eventName);<br \/>\n        if (handlers) {<br \/>\n            handlers.forEach(handler &#061;&gt; handler(data));<br \/>\n        }<br \/>\n    }<\/p>\n<p>    createEnhancedResponse(originalResponse, responseInfo) {<br \/>\n        \/\/ \u521b\u5efa\u589e\u5f3a\u7684\u54cd\u5e94\u5bf9\u8c61<br \/>\n        const enhancedResponse &#061; {<br \/>\n            &#8230;originalResponse,<br \/>\n            _enhanced: true,<br \/>\n            _responseInfo: responseInfo,<\/p>\n<p>            \/\/ \u6dfb\u52a0\u8f85\u52a9\u65b9\u6cd5<br \/>\n            getDeletedResourceId: () &#061;&gt; {<br \/>\n                const header &#061; originalResponse.headers.get(&#039;X-Deleted-Resource&#039;);<br \/>\n                return header ? this.extractResourceId(header) : null;<br \/>\n            },<\/p>\n<p>            getOperationType: () &#061;&gt; {<br \/>\n                return originalResponse.headers.get(&#039;X-Operation-Type&#039;);<br \/>\n            },<\/p>\n<p>            getResourceType: () &#061;&gt; {<br \/>\n                return originalResponse.headers.get(&#039;X-Resource-Type&#039;);<br \/>\n            }<br \/>\n        };<\/p>\n<p>        return enhancedResponse;<br \/>\n    }<br \/>\n}<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b<br \/>\ndocument.addEventListener(&#039;DOMContentLoaded&#039;, () &#061;&gt; {<br \/>\n    const handler &#061; new NoContentResponseHandler();<\/p>\n<p>    \/\/ \u6ce8\u518c\u81ea\u5b9a\u4e49\u4e8b\u4ef6\u5904\u7406\u5668<br \/>\n    handler.registerHandler(&#039;204Response&#039;, (data) &#061;&gt; {<br \/>\n        console.log(&#039;Custom 204 handler:&#039;, data);<\/p>\n<p>        \/\/ \u66f4\u65b0\u9875\u9762\u6807\u9898&#xff08;\u5982\u679c\u6709\u64cd\u4f5c\u7c7b\u578b&#xff09;<br \/>\n        const operationType &#061; data.headers[&#039;X-Operation-Type&#039;];<br \/>\n        if (operationType) {<br \/>\n            document.title &#061; &#096;${operationType.charAt(0).toUpperCase() &#043; operationType.slice(1)} &#8211; My App&#096;;<br \/>\n        }<br \/>\n    });<\/p>\n<p>    \/\/ \u8bbe\u7f6e\u5220\u9664\u6309\u94ae<br \/>\n    document.querySelectorAll(&#039;.delete-btn&#039;).forEach(button &#061;&gt; {<br \/>\n        button.addEventListener(&#039;click&#039;, async (e) &#061;&gt; {<br \/>\n            e.preventDefault();<\/p>\n<p>            const resourceId &#061; button.dataset.resourceId;<br \/>\n            const resourceType &#061; button.dataset.resourceType;<\/p>\n<p>            if (!confirm(&#096;Are you sure you want to delete this ${resourceType}?&#096;)) {<br \/>\n                return;<br \/>\n            }<\/p>\n<p>            try {<br \/>\n                const response &#061; await fetch(&#096;\/api\/${resourceType}\/${resourceId}&#096;, {<br \/>\n                    method: &#039;DELETE&#039;,<br \/>\n                    headers: {<br \/>\n                        &#039;X-Requested-With&#039;: &#039;XMLHttpRequest&#039;<br \/>\n                    }<br \/>\n                });<\/p>\n<p>                \/\/ \u4f7f\u7528\u5904\u7406\u5668\u5904\u7406\u54cd\u5e94<br \/>\n                const enhancedResponse &#061; await handler.handle204Response(response, {<br \/>\n                    method: &#039;DELETE&#039;,<br \/>\n                    url: &#096;\/api\/${resourceType}\/${resourceId}&#096;<br \/>\n                });<\/p>\n<p>                console.log(&#039;Enhanced response:&#039;, enhancedResponse);<\/p>\n<p>            } catch (error) {<br \/>\n                console.error(&#039;Delete failed:&#039;, error);<br \/>\n                alert(&#039;Failed to delete resource&#039;);<br \/>\n            }<br \/>\n        });<br \/>\n    });<\/p>\n<p>    \/\/ \u8bbe\u7f6e\u6807\u8bb0\u4e3a\u5df2\u8bfb\u6309\u94ae<br \/>\n    document.querySelectorAll(&#039;.mark-read-btn&#039;).forEach(button &#061;&gt; {<br \/>\n        button.addEventListener(&#039;click&#039;, async (e) &#061;&gt; {<br \/>\n            e.preventDefault();<\/p>\n<p>            const notificationId &#061; button.dataset.notificationId;<\/p>\n<p>            try {<br \/>\n                const response &#061; await fetch(&#096;\/api\/notifications\/${notificationId}\/read&#096;, {<br \/>\n                    method: &#039;PUT&#039;<br \/>\n                });<\/p>\n<p>                if (response.status &#061;&#061;&#061; 204) {<br \/>\n                    \/\/ \u9690\u85cf\u901a\u77e5<br \/>\n                    button.closest(&#039;.notification-item&#039;).style.opacity &#061; &#039;0&#039;;<br \/>\n                    setTimeout(() &#061;&gt; {<br \/>\n                        button.closest(&#039;.notification-item&#039;).remove();<br \/>\n                    }, 300);<br \/>\n                }<br \/>\n            } catch (error) {<br \/>\n                console.error(&#039;Mark as read failed:&#039;, error);<br \/>\n            }<br \/>\n        });<br \/>\n    });<br \/>\n});<\/p>\n<h4>React\u7ec4\u4ef6\u4e2d\u7684204\u5904\u7406<\/h4>\n<p>jsx<\/p>\n<p>\/\/ React\u7ec4\u4ef6\u4e2d\u7684204\u54cd\u5e94\u5904\u7406<br \/>\nimport React, { useState, useEffect } from &#039;react&#039;;<br \/>\nimport { useFetch } from &#039;.\/hooks\/useFetch&#039;;<\/p>\n<p>const NoContentHandler &#061; {<br \/>\n  \/**<br \/>\n   * \u5904\u7406204\u54cd\u5e94\u7684React\u94a9\u5b50<br \/>\n   *\/<br \/>\n  useNoContentHandler() {<br \/>\n    const [deletedResources, setDeletedResources] &#061; useState([]);<br \/>\n    const [updatedResources, setUpdatedResources] &#061; useState(new Set());<\/p>\n<p>    const handle204Response &#061; async (response, requestInfo) &#061;&gt; {<br \/>\n      \/\/ \u68c0\u67e5\u64cd\u4f5c\u7c7b\u578b<br \/>\n      const operationType &#061; response.headers.get(&#039;X-Operation-Type&#039;);<br \/>\n      const resourceId &#061; response.headers.get(&#039;X-Resource-ID&#039;);<br \/>\n      const resourceType &#061; response.headers.get(&#039;X-Resource-Type&#039;);<\/p>\n<p>      switch (operationType) {<br \/>\n        case &#039;delete&#039;:<br \/>\n          \/\/ \u8bb0\u5f55\u88ab\u5220\u9664\u7684\u8d44\u6e90<br \/>\n          setDeletedResources(prev &#061;&gt; [&#8230;prev, {<br \/>\n            id: resourceId,<br \/>\n            type: resourceType,<br \/>\n            timestamp: new Date().toISOString()<br \/>\n          }]);<\/p>\n<p>          \/\/ \u663e\u793a\u5220\u9664\u6210\u529f\u7684\u6d88\u606f<br \/>\n          this.showNotification(&#096;${resourceType} deleted successfully&#096;, &#039;success&#039;);<br \/>\n          break;<\/p>\n<p>        case &#039;update&#039;:<br \/>\n          \/\/ \u8bb0\u5f55\u66f4\u65b0\u7684\u8d44\u6e90<br \/>\n          setUpdatedResources(prev &#061;&gt; {<br \/>\n            const next &#061; new Set(prev);<br \/>\n            next.add(resourceId);<br \/>\n            return next;<br \/>\n          });<\/p>\n<p>          \/\/ \u663e\u793a\u66f4\u65b0\u6210\u529f\u7684\u6d88\u606f<br \/>\n          this.showNotification(&#096;${resourceType} updated successfully&#096;, &#039;success&#039;);<br \/>\n          break;<\/p>\n<p>        default:<br \/>\n          \/\/ \u901a\u7528\u5904\u7406<br \/>\n          this.showNotification(&#039;Operation completed successfully&#039;, &#039;info&#039;);<br \/>\n      }<\/p>\n<p>      \/\/ \u68c0\u67e5\u662f\u5426\u6709ETag\u66f4\u65b0<br \/>\n      const etag &#061; response.headers.get(&#039;ETag&#039;);<br \/>\n      if (etag &amp;&amp; resourceId) {<br \/>\n        this.updateCacheEtag(resourceId, etag);<br \/>\n      }<br \/>\n    };<\/p>\n<p>    const showNotification &#061; (message, type) &#061;&gt; {<br \/>\n      \/\/ \u4f7f\u7528React\u72b6\u6001\u6216Context\u6765\u663e\u793a\u901a\u77e5<br \/>\n      console.log(&#096;[${type.toUpperCase()}] ${message}&#096;);<br \/>\n    };<\/p>\n<p>    const updateCacheEtag &#061; (resourceId, etag) &#061;&gt; {<br \/>\n      \/\/ \u66f4\u65b0ETag\u7f13\u5b58<br \/>\n      localStorage.setItem(&#096;etag_${resourceId}&#096;, etag);<br \/>\n    };<\/p>\n<p>    return {<br \/>\n      handle204Response,<br \/>\n      deletedResources,<br \/>\n      updatedResources,<br \/>\n      clearDeletedResources: () &#061;&gt; setDeletedResources([]),<br \/>\n      clearUpdatedResources: () &#061;&gt; setUpdatedResources(new Set())<br \/>\n    };<br \/>\n  }<br \/>\n};<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b&#xff1a;\u8d44\u6e90\u5220\u9664\u7ec4\u4ef6<br \/>\nconst ResourceDeleteButton &#061; ({ resourceId, resourceType, onDelete }) &#061;&gt; {<br \/>\n  const [isDeleting, setIsDeleting] &#061; useState(false);<br \/>\n  const { handle204Response } &#061; NoContentHandler.useNoContentHandler();<\/p>\n<p>  const handleDelete &#061; async () &#061;&gt; {<br \/>\n    if (!window.confirm(&#096;Are you sure you want to delete this ${resourceType}?&#096;)) {<br \/>\n      return;<br \/>\n    }<\/p>\n<p>    setIsDeleting(true);<\/p>\n<p>    try {<br \/>\n      const response &#061; await fetch(&#096;\/api\/${resourceType}s\/${resourceId}&#096;, {<br \/>\n        method: &#039;DELETE&#039;,<br \/>\n        headers: {<br \/>\n          &#039;Content-Type&#039;: &#039;application\/json&#039;<br \/>\n        }<br \/>\n      });<\/p>\n<p>      if (response.status &#061;&#061;&#061; 204) {<br \/>\n        \/\/ \u5904\u7406204\u54cd\u5e94<br \/>\n        await handle204Response(response, {<br \/>\n          method: &#039;DELETE&#039;,<br \/>\n          resourceId,<br \/>\n          resourceType<br \/>\n        });<\/p>\n<p>        \/\/ \u8c03\u7528\u7236\u7ec4\u4ef6\u7684\u56de\u8c03<br \/>\n        if (onDelete) {<br \/>\n          onDelete(resourceId);<br \/>\n        }<br \/>\n      } else if (response.status &#061;&#061;&#061; 404) {<br \/>\n        alert(&#096;${resourceType} not found&#096;);<br \/>\n      } else {<br \/>\n        const error &#061; await response.json();<br \/>\n        alert(&#096;Delete failed: ${error.message}&#096;);<br \/>\n      }<br \/>\n    } catch (error) {<br \/>\n      console.error(&#039;Delete error:&#039;, error);<br \/>\n      alert(&#039;Network error&#039;);<br \/>\n    } finally {<br \/>\n      setIsDeleting(false);<br \/>\n    }<br \/>\n  };<\/p>\n<p>  return (<br \/>\n    &lt;button<br \/>\n      onClick&#061;{handleDelete}<br \/>\n      disabled&#061;{isDeleting}<br \/>\n      className&#061;{&#096;btn btn-danger ${isDeleting ? &#039;disabled&#039; : &#039;&#039;}&#096;}<br \/>\n    &gt;<br \/>\n      {isDeleting ? (<br \/>\n        &lt;&gt;<br \/>\n          &lt;span className&#061;&#034;spinner-border spinner-border-sm mr-2&#034; role&#061;&#034;status&#034; \/&gt;<br \/>\n          Deleting&#8230;<br \/>\n        &lt;\/&gt;<br \/>\n      ) : (<br \/>\n        &#096;Delete ${resourceType}&#096;<br \/>\n      )}<br \/>\n    &lt;\/button&gt;<br \/>\n  );<br \/>\n};<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b&#xff1a;\u6279\u91cf\u64cd\u4f5c\u7ec4\u4ef6<br \/>\nconst BatchOperations &#061; ({ resourceType, selectedIds, onComplete }) &#061;&gt; {<br \/>\n  const [isProcessing, setIsProcessing] &#061; useState(false);<br \/>\n  const [progress, setProgress] &#061; useState(0);<br \/>\n  const { handle204Response } &#061; NoContentHandler.useNoContentHandler();<\/p>\n<p>  const handleBatchDelete &#061; async () &#061;&gt; {<br \/>\n    if (selectedIds.length &#061;&#061;&#061; 0) {<br \/>\n      alert(&#039;No items selected&#039;);<br \/>\n      return;<br \/>\n    }<\/p>\n<p>    if (!window.confirm(&#096;Delete ${selectedIds.length} ${resourceType}(s)?&#096;)) {<br \/>\n      return;<br \/>\n    }<\/p>\n<p>    setIsProcessing(true);<br \/>\n    setProgress(0);<\/p>\n<p>    try {<br \/>\n      \/\/ \u6279\u91cf\u5220\u9664<br \/>\n      for (let i &#061; 0; i &lt; selectedIds.length; i&#043;&#043;) {<br \/>\n        const resourceId &#061; selectedIds[i];<\/p>\n<p>        const response &#061; await fetch(&#096;\/api\/${resourceType}s\/${resourceId}&#096;, {<br \/>\n          method: &#039;DELETE&#039;<br \/>\n        });<\/p>\n<p>        if (response.status &#061;&#061;&#061; 204) {<br \/>\n          \/\/ \u5904\u7406204\u54cd\u5e94<br \/>\n          await handle204Response(response, {<br \/>\n            method: &#039;DELETE&#039;,<br \/>\n            resourceId,<br \/>\n            resourceType,<br \/>\n            batchIndex: i<br \/>\n          });<br \/>\n        }<\/p>\n<p>        \/\/ \u66f4\u65b0\u8fdb\u5ea6<br \/>\n        setProgress(((i &#043; 1) \/ selectedIds.length) * 100);<br \/>\n      }<\/p>\n<p>      \/\/ \u5b8c\u6210<br \/>\n      if (onComplete) {<br \/>\n        onComplete();<br \/>\n      }<\/p>\n<p>      alert(&#096;Successfully deleted ${selectedIds.length} ${resourceType}(s)&#096;);<\/p>\n<p>    } catch (error) {<br \/>\n      console.error(&#039;Batch delete error:&#039;, error);<br \/>\n      alert(&#039;Some deletions failed&#039;);<br \/>\n    } finally {<br \/>\n      setIsProcessing(false);<br \/>\n      setProgress(0);<br \/>\n    }<br \/>\n  };<\/p>\n<p>  const handleBatchUpdate &#061; async (updates) &#061;&gt; {<br \/>\n    \/\/ \u7c7b\u4f3c\u7684\u6279\u91cf\u66f4\u65b0\u903b\u8f91<br \/>\n  };<\/p>\n<p>  return (<br \/>\n    &lt;div className&#061;&#034;batch-operations&#034;&gt;<br \/>\n      &lt;h3&gt;Batch Operations&lt;\/h3&gt;<br \/>\n      &lt;p&gt;{selectedIds.length} items selected&lt;\/p&gt;<\/p>\n<p>      {isProcessing &amp;&amp; (<br \/>\n        &lt;div className&#061;&#034;progress mb-3&#034;&gt;<br \/>\n          &lt;div<br \/>\n            className&#061;&#034;progress-bar&#034;<br \/>\n            role&#061;&#034;progressbar&#034;<br \/>\n            style&#061;{{ width: &#096;${progress}%&#096; }}<br \/>\n          &gt;<br \/>\n            {Math.round(progress)}%<br \/>\n          &lt;\/div&gt;<br \/>\n        &lt;\/div&gt;<br \/>\n      )}<\/p>\n<p>      &lt;div className&#061;&#034;btn-group&#034;&gt;<br \/>\n        &lt;button<br \/>\n          onClick&#061;{handleBatchDelete}<br \/>\n          disabled&#061;{isProcessing || selectedIds.length &#061;&#061;&#061; 0}<br \/>\n          className&#061;&#034;btn btn-danger&#034;<br \/>\n        &gt;<br \/>\n          Delete Selected<br \/>\n        &lt;\/button&gt;<\/p>\n<p>        &lt;button<br \/>\n          onClick&#061;{() &#061;&gt; handleBatchUpdate({ status: &#039;archived&#039; })}<br \/>\n          disabled&#061;{isProcessing || selectedIds.length &#061;&#061;&#061; 0}<br \/>\n          className&#061;&#034;btn btn-secondary&#034;<br \/>\n        &gt;<br \/>\n          Archive Selected<br \/>\n        &lt;\/button&gt;<br \/>\n      &lt;\/div&gt;<br \/>\n    &lt;\/div&gt;<br \/>\n  );<br \/>\n};<\/p>\n<p>\/\/ \u4f7f\u7528\u793a\u4f8b&#xff1a;\u4e3b\u5e94\u7528\u7ec4\u4ef6<br \/>\nconst App &#061; () &#061;&gt; {<br \/>\n  const [resources, setResources] &#061; useState([]);<br \/>\n  const [selectedResources, setSelectedResources] &#061; useState([]);<br \/>\n  const { deletedResources, clearDeletedResources } &#061; NoContentHandler.useNoContentHandler();<\/p>\n<p>  \/\/ \u83b7\u53d6\u8d44\u6e90\u5217\u8868<br \/>\n  useEffect(() &#061;&gt; {<br \/>\n    fetchResources();<br \/>\n  }, []);<\/p>\n<p>  \/\/ \u76d1\u542c\u5220\u9664\u4e8b\u4ef6<br \/>\n  useEffect(() &#061;&gt; {<br \/>\n    if (deletedResources.length &gt; 0) {<br \/>\n      \/\/ \u4ece\u5217\u8868\u4e2d\u79fb\u9664\u88ab\u5220\u9664\u7684\u8d44\u6e90<br \/>\n      setResources(prev &#061;&gt; prev.filter(<br \/>\n        resource &#061;&gt; !deletedResources.some(<br \/>\n          deleted &#061;&gt; deleted.id &#061;&#061;&#061; resource.id &amp;&amp; deleted.type &#061;&#061;&#061; resource.type<br \/>\n        )<br \/>\n      ));<\/p>\n<p>      \/\/ \u6e05\u9664\u9009\u62e9<br \/>\n      setSelectedResources([]);<\/p>\n<p>      \/\/ \u53ef\u9009&#xff1a;\u6e05\u9664\u5220\u9664\u8bb0\u5f55<br \/>\n      clearDeletedResources();<br \/>\n    }<br \/>\n  }, [deletedResources, clearDeletedResources]);<\/p>\n<p>  const fetchResources &#061; async () &#061;&gt; {<br \/>\n    try {<br \/>\n      const response &#061; await fetch(&#039;\/api\/resources&#039;);<br \/>\n      const data &#061; await response.json();<br \/>\n      setResources(data);<br \/>\n    } catch (error) {<br \/>\n      console.error(&#039;Failed to fetch resources:&#039;, error);<br \/>\n    }<br \/>\n  };<\/p>\n<p>  const handleResourceDelete &#061; (deletedId) &#061;&gt; {<br \/>\n    \/\/ \u4ece\u5217\u8868\u4e2d\u79fb\u9664<br \/>\n    setResources(prev &#061;&gt; prev.filter(resource &#061;&gt; resource.id !&#061;&#061; deletedId));<\/p>\n<p>    \/\/ \u4ece\u9009\u62e9\u4e2d\u79fb\u9664<br \/>\n    setSelectedResources(prev &#061;&gt; prev.filter(id &#061;&gt; id !&#061;&#061; deletedId));<br \/>\n  };<\/p>\n<p>  const handleSelectResource &#061; (resourceId) &#061;&gt; {<br \/>\n    setSelectedResources(prev &#061;&gt; {<br \/>\n      if (prev.includes(resourceId)) {<br \/>\n        return prev.filter(id &#061;&gt; id !&#061;&#061; resourceId);<br \/>\n      } else {<br \/>\n        return [&#8230;prev, resourceId];<br \/>\n      }<br \/>\n    });<br \/>\n  };<\/p>\n<p>  const handleSelectAll &#061; () &#061;&gt; {<br \/>\n    if (selectedResources.length &#061;&#061;&#061; resources.length) {<br \/>\n      setSelectedResources([]);<br \/>\n    } else {<br \/>\n      setSelectedResources(resources.map(r &#061;&gt; r.id));<br \/>\n    }<br \/>\n  };<\/p>\n<p>  return (<br \/>\n    &lt;div className&#061;&#034;container&#034;&gt;<br \/>\n      &lt;h1&gt;Resource Management&lt;\/h1&gt;<\/p>\n<p>      &lt;BatchOperations<br \/>\n        resourceType&#061;&#034;resource&#034;<br \/>\n        selectedIds&#061;{selectedResources}<br \/>\n        onComplete&#061;{fetchResources}<br \/>\n      \/&gt;<\/p>\n<p>      &lt;div className&#061;&#034;resource-list&#034;&gt;<br \/>\n        &lt;table className&#061;&#034;table&#034;&gt;<br \/>\n          &lt;thead&gt;<br \/>\n            &lt;tr&gt;<br \/>\n              &lt;th&gt;<br \/>\n                &lt;input<br \/>\n                  type&#061;&#034;checkbox&#034;<br \/>\n                  checked&#061;{selectedResources.length &#061;&#061;&#061; resources.length &amp;&amp; resources.length &gt; 0}<br \/>\n                  onChange&#061;{handleSelectAll}<br \/>\n                \/&gt;<br \/>\n              &lt;\/th&gt;<br \/>\n              &lt;th&gt;Name&lt;\/th&gt;<br \/>\n              &lt;th&gt;Type&lt;\/th&gt;<br \/>\n              &lt;th&gt;Created&lt;\/th&gt;<br \/>\n              &lt;th&gt;Actions&lt;\/th&gt;<br \/>\n            &lt;\/tr&gt;<br \/>\n          &lt;\/thead&gt;<br \/>\n          &lt;tbody&gt;<br \/>\n            {resources.map(resource &#061;&gt; (<br \/>\n              &lt;tr key&#061;{resource.id}&gt;<br \/>\n                &lt;td&gt;<br \/>\n                  &lt;input<br \/>\n                    type&#061;&#034;checkbox&#034;<br \/>\n                    checked&#061;{selectedResources.includes(resource.id)}<br \/>\n                    onChange&#061;{() &#061;&gt; handleSelectResource(resource.id)}<br \/>\n                  \/&gt;<br \/>\n                &lt;\/td&gt;<br \/>\n                &lt;td&gt;{resource.name}&lt;\/td&gt;<br \/>\n                &lt;td&gt;{resource.type}&lt;\/td&gt;<br \/>\n                &lt;td&gt;{new Date(resource.created_at).toLocaleDateString()}&lt;\/td&gt;<br \/>\n                &lt;td&gt;<br \/>\n                  &lt;ResourceDeleteButton<br \/>\n                    resourceId&#061;{resource.id}<br \/>\n                    resourceType&#061;{resource.type}<br \/>\n                    onDelete&#061;{handleResourceDelete}<br \/>\n                  \/&gt;<br \/>\n                &lt;\/td&gt;<br \/>\n              &lt;\/tr&gt;<br \/>\n            ))}<br \/>\n          &lt;\/tbody&gt;<br \/>\n        &lt;\/table&gt;<br \/>\n      &lt;\/div&gt;<br \/>\n    &lt;\/div&gt;<br \/>\n  );<br \/>\n};<\/p>\n<p>export default App;<\/p>\n<h3>7.5 \u4e0e200 OK\u7684\u533a\u522b\u548c\u9009\u62e9<\/h3>\n<h4 style=\"background-color:transparent\">\u51b3\u7b56\u6d41\u7a0b\u56fe<img loading=\"lazy\" decoding=\"async\" alt=\"\" height=\"1742\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/11\/20261104055701-6aeac92db918a.png\" width=\"3052\" \/><\/h4>\n<h4>\u4ee3\u7801\u793a\u4f8b\u5bf9\u6bd4<\/h4>\n<p>python<\/p>\n<p># \u5bf9\u6bd4200\u548c204\u7684\u4f7f\u7528\u9009\u62e9<br \/>\nfrom enum import Enum<\/p>\n<p>class ResponseStrategy(Enum):<br \/>\n    &#034;&#034;&#034;\u54cd\u5e94\u7b56\u7565&#034;&#034;&#034;<br \/>\n    FULL_200 &#061; &#034;full_200&#034;      # 200 &#043; \u5b8c\u6574\u8d44\u6e90<br \/>\n    PARTIAL_200 &#061; &#034;partial_200&#034; # 200 &#043; \u90e8\u5206\u8d44\u6e90<br \/>\n    NO_CONTENT_204 &#061; &#034;no_content_204&#034;  # 204 No Content<br \/>\n    CREATED_201 &#061; &#034;created_201&#034;  # 201 Created<\/p>\n<p>class ResponseBuilder:<br \/>\n    &#034;&#034;&#034;\u54cd\u5e94\u6784\u5efa\u5668&#xff0c;\u6839\u636e\u7b56\u7565\u8fd4\u56de\u9002\u5f53\u7684\u54cd\u5e94&#034;&#034;&#034;<\/p>\n<p>    def build_response(self, strategy, **kwargs):<br \/>\n        &#034;&#034;&#034;<br \/>\n        \u6839\u636e\u7b56\u7565\u6784\u5efa\u54cd\u5e94<\/p>\n<p>        Args:<br \/>\n            strategy: ResponseStrategy \u679a\u4e3e<br \/>\n            **kwargs: \u53c2\u6570<br \/>\n                &#8211; resource: \u8d44\u6e90\u5bf9\u8c61<br \/>\n                &#8211; action: \u6267\u884c\u7684\u64cd\u4f5c<br \/>\n                &#8211; partial_data: \u90e8\u5206\u6570\u636e<br \/>\n                &#8211; location: \u4f4d\u7f6e&#xff08;\u7528\u4e8e201&#xff09;<\/p>\n<p>        Returns:<br \/>\n            (status_code, data, headers)<br \/>\n        &#034;&#034;&#034;<\/p>\n<p>        if strategy &#061;&#061; ResponseStrategy.FULL_200:<br \/>\n            return self._build_full_200(**kwargs)<br \/>\n        elif strategy &#061;&#061; ResponseStrategy.PARTIAL_200:<br \/>\n            return self._build_partial_200(**kwargs)<br \/>\n        elif strategy &#061;&#061; ResponseStrategy.NO_CONTENT_204:<br \/>\n            return self._build_204(**kwargs)<br \/>\n        elif strategy &#061;&#061; ResponseStrategy.CREATED_201:<br \/>\n            return self._build_201(**kwargs)<br \/>\n        else:<br \/>\n            raise ValueError(f&#034;Unknown strategy: {strategy}&#034;)<\/p>\n<p>    def _build_full_200(self, resource, action&#061;None, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa200 OK\u54cd\u5e94&#xff08;\u5b8c\u6574\u8d44\u6e90&#xff09;&#034;&#034;&#034;<br \/>\n        data &#061; resource.to_dict()  # \u5047\u8bbe\u8d44\u6e90\u6709to_dict\u65b9\u6cd5<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n            &#039;ETag&#039;: f&#039;&#034;{resource.generate_etag()}&#034;&#039;,<br \/>\n            &#039;Last-Modified&#039;: resource.updated_at.strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<br \/>\n        }<\/p>\n<p>        if action:<br \/>\n            headers[f&#039;X-{action.capitalize()}-Resource&#039;] &#061; f&#039;\/api\/resources\/{resource.id}&#039;<\/p>\n<p>        return 200, data, headers<\/p>\n<p>    def _build_partial_200(self, resource, partial_data, action&#061;None, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa200 OK\u54cd\u5e94&#xff08;\u90e8\u5206\u8d44\u6e90&#xff09;&#034;&#034;&#034;<br \/>\n        data &#061; {<br \/>\n            &#039;id&#039;: resource.id,<br \/>\n            &#039;type&#039;: resource.__class__.__name__.lower(),<br \/>\n            &#039;updated_at&#039;: resource.updated_at.isoformat(),<br \/>\n            **partial_data<br \/>\n        }<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n            &#039;ETag&#039;: f&#039;&#034;{resource.generate_etag()}&#034;&#039;,<br \/>\n            &#039;X-Partial-Response&#039;: &#039;true&#039;<br \/>\n        }<\/p>\n<p>        if action:<br \/>\n            headers[f&#039;X-{action.capitalize()}-Resource&#039;] &#061; f&#039;\/api\/resources\/{resource.id}&#039;<\/p>\n<p>        return 200, data, headers<\/p>\n<p>    def _build_204(self, resource&#061;None, action&#061;None, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa204 No Content\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        headers &#061; {<br \/>\n            &#039;Cache-Control&#039;: &#039;no-cache, must-revalidate&#039;,<br \/>\n            &#039;Pragma&#039;: &#039;no-cache&#039;,<br \/>\n            &#039;Expires&#039;: &#039;0&#039;<br \/>\n        }<\/p>\n<p>        if resource:<br \/>\n            headers[&#039;ETag&#039;] &#061; f&#039;&#034;{resource.generate_etag()}&#034;&#039;<br \/>\n            headers[&#039;Last-Modified&#039;] &#061; datetime.utcnow().strftime(&#039;%a, %d %b %Y %H:%M:%S GMT&#039;)<\/p>\n<p>            if action:<br \/>\n                headers[f&#039;X-{action.capitalize()}-Resource&#039;] &#061; f&#039;\/api\/resources\/{resource.id}&#039;<br \/>\n                headers[f&#039;X-{resource.__class__.__name__.capitalize()}-ID&#039;] &#061; str(resource.id)<\/p>\n<p>        elif action:<br \/>\n            headers[f&#039;X-{action.capitalize()}-Operation&#039;] &#061; &#039;completed&#039;<\/p>\n<p>        return 204, None, headers<\/p>\n<p>    def _build_201(self, resource, location&#061;None, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6784\u5efa201 Created\u54cd\u5e94&#034;&#034;&#034;<br \/>\n        data &#061; resource.to_dict()<\/p>\n<p>        headers &#061; {<br \/>\n            &#039;Content-Type&#039;: &#039;application\/json&#039;,<br \/>\n            &#039;Location&#039;: location or f&#039;\/api\/resources\/{resource.id}&#039;<br \/>\n        }<\/p>\n<p>        return 201, data, headers<\/p>\n<p># \u4f7f\u7528\u793a\u4f8b&#xff1a;\u4e0d\u540c\u7684\u64cd\u4f5c\u7c7b\u578b<br \/>\nclass ResourceController:<br \/>\n    &#034;&#034;&#034;\u8d44\u6e90\u63a7\u5236\u5668&#xff0c;\u5c55\u793a\u4e0d\u540c\u7b56\u7565\u7684\u4f7f\u7528&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.response_builder &#061; ResponseBuilder()<\/p>\n<p>    def get_resource(self, resource_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u8d44\u6e90 &#8211; \u603b\u662f\u8fd4\u56de200 &#043; \u5b8c\u6574\u8d44\u6e90&#034;&#034;&#034;<br \/>\n        resource &#061; self._get_resource_by_id(resource_id)<br \/>\n        if not resource:<br \/>\n            return None, 404<\/p>\n<p>        return self.response_builder.build_response(<br \/>\n            ResponseStrategy.FULL_200,<br \/>\n            resource&#061;resource<br \/>\n        )<\/p>\n<p>    def update_resource(self, resource_id, update_data):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u8d44\u6e90 &#8211; \u6839\u636e\u60c5\u51b5\u9009\u62e9\u7b56\u7565&#034;&#034;&#034;<br \/>\n        resource &#061; self._get_resource_by_id(resource_id)<br \/>\n        if not resource:<br \/>\n            return None, 404<\/p>\n<p>        # \u6267\u884c\u66f4\u65b0<br \/>\n        self._apply_update(resource, update_data)<\/p>\n<p>        # \u51b3\u5b9a\u54cd\u5e94\u7b56\u7565<br \/>\n        strategy &#061; self._choose_update_strategy(update_data, resource)<\/p>\n<p>        if strategy &#061;&#061; ResponseStrategy.FULL_200:<br \/>\n            # \u5ba2\u6237\u7aef\u53ef\u80fd\u9700\u8981\u66f4\u65b0\u540e\u7684\u5b8c\u6574\u8d44\u6e90<br \/>\n            return self.response_builder.build_response(<br \/>\n                ResponseStrategy.FULL_200,<br \/>\n                resource&#061;resource,<br \/>\n                action&#061;&#039;updated&#039;<br \/>\n            )<\/p>\n<p>        elif strategy &#061;&#061; ResponseStrategy.PARTIAL_200:<br \/>\n            # \u53ea\u8fd4\u56de\u53d8\u5316\u7684\u90e8\u5206<br \/>\n            partial_data &#061; {k: update_data[k] for k in update_data.keys()}<br \/>\n            return self.response_builder.build_response(<br \/>\n                ResponseStrategy.PARTIAL_200,<br \/>\n                resource&#061;resource,<br \/>\n                partial_data&#061;partial_data,<br \/>\n                action&#061;&#039;updated&#039;<br \/>\n            )<\/p>\n<p>        elif strategy &#061;&#061; ResponseStrategy.NO_CONTENT_204:<br \/>\n            # \u5ba2\u6237\u7aef\u5df2\u7ecf\u6709\u6570\u636e&#xff0c;\u53ea\u9700\u786e\u8ba4\u66f4\u65b0\u6210\u529f<br \/>\n            return self.response_builder.build_response(<br \/>\n                ResponseStrategy.NO_CONTENT_204,<br \/>\n                resource&#061;resource,<br \/>\n                action&#061;&#039;updated&#039;<br \/>\n            )<\/p>\n<p>    def delete_resource(self, resource_id):<br \/>\n        &#034;&#034;&#034;\u5220\u9664\u8d44\u6e90 &#8211; \u603b\u662f\u8fd4\u56de204&#034;&#034;&#034;<br \/>\n        resource &#061; self._get_resource_by_id(resource_id)<br \/>\n        if not resource:<br \/>\n            # \u6839\u636eAPI\u8bbe\u8ba1&#xff0c;\u5220\u9664\u4e0d\u5b58\u5728\u7684\u8d44\u6e90\u53ef\u4ee5\u8fd4\u56de204\u6216404<br \/>\n            # \u8fd9\u91cc\u8fd4\u56de204\u8868\u793a&#034;\u5df2\u7ecf\u662f\u671f\u671b\u7684\u72b6\u6001&#034;<br \/>\n            return self.response_builder.build_response(<br \/>\n                ResponseStrategy.NO_CONTENT_204,<br \/>\n                action&#061;&#039;already_deleted&#039;<br \/>\n            )<\/p>\n<p>        # \u6267\u884c\u5220\u9664<br \/>\n        self._perform_delete(resource)<\/p>\n<p>        return self.response_builder.build_response(<br \/>\n            ResponseStrategy.NO_CONTENT_204,<br \/>\n            resource&#061;resource,<br \/>\n            action&#061;&#039;deleted&#039;<br \/>\n        )<\/p>\n<p>    def create_resource(self, resource_data):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u8d44\u6e90 &#8211; \u603b\u662f\u8fd4\u56de201&#034;&#034;&#034;<br \/>\n        resource &#061; self._create_resource(resource_data)<\/p>\n<p>        return self.response_builder.build_response(<br \/>\n            ResponseStrategy.CREATED_201,<br \/>\n            resource&#061;resource,<br \/>\n            location&#061;f&#039;\/api\/resources\/{resource.id}&#039;<br \/>\n        )<\/p>\n<p>    def _choose_update_strategy(self, update_data, resource):<br \/>\n        &#034;&#034;&#034;\u9009\u62e9\u66f4\u65b0\u64cd\u4f5c\u7684\u54cd\u5e94\u7b56\u7565&#034;&#034;&#034;<\/p>\n<p>        # \u89c4\u52191&#xff1a;\u5982\u679c\u66f4\u65b0\u4e86\u654f\u611f\u5b57\u6bb5&#xff0c;\u8fd4\u56de\u5b8c\u6574\u8d44\u6e90\u4ee5\u4fbf\u5ba2\u6237\u7aef\u5237\u65b0<br \/>\n        sensitive_fields &#061; [&#039;email&#039;, &#039;password&#039;, &#039;api_key&#039;]<br \/>\n        if any(field in update_data for field in sensitive_fields):<br \/>\n            return ResponseStrategy.FULL_200<\/p>\n<p>        # \u89c4\u52192&#xff1a;\u5982\u679c\u662f\u7b80\u5355\u7684\u72b6\u6001\u5207\u6362&#xff0c;\u8fd4\u56de204<br \/>\n        simple_state_changes &#061; [&#039;is_active&#039;, &#039;is_published&#039;, &#039;is_verified&#039;]<br \/>\n        if len(update_data) &#061;&#061; 1 and list(update_data.keys())[0] in simple_state_changes:<br \/>\n            return ResponseStrategy.NO_CONTENT_204<\/p>\n<p>        # \u89c4\u52193&#xff1a;\u5982\u679c\u66f4\u65b0\u4e86\u5f88\u591a\u5b57\u6bb5&#xff0c;\u8fd4\u56de\u5b8c\u6574\u8d44\u6e90<br \/>\n        if len(update_data) &gt; 3:<br \/>\n            return ResponseStrategy.FULL_200<\/p>\n<p>        # \u89c4\u52194&#xff1a;\u9ed8\u8ba4\u8fd4\u56de\u90e8\u5206\u8d44\u6e90<br \/>\n        return ResponseStrategy.PARTIAL_200<\/p>\n<p>    # \u4ee5\u4e0b\u4e3a\u5047\u8bbe\u7684\u65b9\u6cd5\u5b9e\u73b0<br \/>\n    def _get_resource_by_id(self, resource_id):<br \/>\n        # \u83b7\u53d6\u8d44\u6e90<br \/>\n        pass<\/p>\n<p>    def _apply_update(self, resource, update_data):<br \/>\n        # \u5e94\u7528\u66f4\u65b0<br \/>\n        pass<\/p>\n<p>    def _perform_delete(self, resource):<br \/>\n        # \u6267\u884c\u5220\u9664<br \/>\n        pass<\/p>\n<p>    def _create_resource(self, resource_data):<br \/>\n        # \u521b\u5efa\u8d44\u6e90<br \/>\n        pass<\/p>\n<h3>7.6 \u6700\u4f73\u5b9e\u8df5\u603b\u7ed3<\/h3>\n<p>yaml<\/p>\n<p># 204 No Content\u6700\u4f73\u5b9e\u8df5\u6e05\u5355<br \/>\nbest_practices_204:<\/p>\n<p>  # \u6838\u5fc3\u539f\u5219<br \/>\n  core_principles:<br \/>\n    &#8211; \u54cd\u5e94\u4f53\u5fc5\u987b\u4e3a\u7a7a<br \/>\n    &#8211; \u8868\u793a\u6210\u529f\u6267\u884c<br \/>\n    &#8211; \u53ef\u5305\u542b\u66f4\u65b0\u7684\u5934\u90e8\u4fe1\u606f<br \/>\n    &#8211; \u5ba2\u6237\u7aef\u5e94\u57fa\u4e8e\u5934\u90e8\u66f4\u65b0\u72b6\u6001<\/p>\n<p>  # \u9002\u7528\u573a\u666f<br \/>\n  appropriate_scenarios:<br \/>\n    &#8211; \u6210\u529f\u5220\u9664\u8d44\u6e90<br \/>\n    &#8211; \u6210\u529f\u66f4\u65b0\u4f46\u5ba2\u6237\u7aef\u5df2\u6709\u6570\u636e<br \/>\n    &#8211; \u72b6\u6001\u5207\u6362\u64cd\u4f5c&#xff08;\u53d1\u5e03\/\u53d6\u6d88\u53d1\u5e03\u3001\u6fc0\u6d3b\/\u505c\u7528&#xff09;<br \/>\n    &#8211; \u6807\u8bb0\u4e3a\u5df2\u8bfb\/\u5df2\u5b8c\u6210<br \/>\n    &#8211; \u8ba1\u6570\u5668\u589e\u52a0<br \/>\n    &#8211; \u7a7a\u64cd\u4f5c\u6216\u5e42\u7b49\u64cd\u4f5c<\/p>\n<p>  # \u4e0d\u9002\u7528\u573a\u666f<br \/>\n  inappropriate_scenarios:<br \/>\n    &#8211; \u9700\u8981\u8fd4\u56de\u6570\u636e\u7684\u64cd\u4f5c<br \/>\n    &#8211; \u521b\u5efa\u65b0\u8d44\u6e90&#xff08;\u5e94\u4f7f\u7528201&#xff09;<br \/>\n    &#8211; \u590d\u6742\u64cd\u4f5c\u9700\u8981\u8fd4\u56de\u7ed3\u679c<br \/>\n    &#8211; \u5ba2\u6237\u7aef\u9700\u8981\u66f4\u65b0\u540e\u7684\u5b8c\u6574\u8d44\u6e90<\/p>\n<p>  # \u5934\u90e8\u8bbe\u8ba1<br \/>\n  headers_design:<br \/>\n    required:<br \/>\n      &#8211; Date: \u54cd\u5e94\u65f6\u95f4<br \/>\n      &#8211; Cache-Control: \u901a\u5e38\u8bbe\u7f6e\u4e3a&#034;no-cache, must-revalidate&#034;<\/p>\n<p>    recommended:<br \/>\n      &#8211; ETag: \u5982\u679c\u8d44\u6e90\u6709\u66f4\u65b0<br \/>\n      &#8211; Last-Modified: \u6700\u540e\u4fee\u6539\u65f6\u95f4<br \/>\n      &#8211; X-Resource-ID: \u64cd\u4f5c\u7684\u8d44\u6e90ID<br \/>\n      &#8211; X-Operation-Type: \u64cd\u4f5c\u7c7b\u578b<\/p>\n<p>    optional:<br \/>\n      &#8211; Location: \u5982\u679c\u64cd\u4f5c\u6d89\u53ca\u8d44\u6e90\u4f4d\u7f6e\u53d8\u5316<br \/>\n      &#8211; X-RateLimit-Remaining: \u901f\u7387\u9650\u5236\u4fe1\u606f<br \/>\n      &#8211; X-Deleted-Resource: \u88ab\u5220\u9664\u7684\u8d44\u6e90URL<\/p>\n<p>  # \u5ba2\u6237\u7aef\u5904\u7406<br \/>\n  client_handling:<br \/>\n    &#8211; \u4e0d\u8981\u671f\u671b\u54cd\u5e94\u4f53<br \/>\n    &#8211; \u68c0\u67e5\u72b6\u6001\u7801\u662f\u5426\u4e3a204<br \/>\n    &#8211; \u89e3\u6790\u5934\u90e8\u4ee5\u83b7\u53d6\u64cd\u4f5c\u4fe1\u606f<br \/>\n    &#8211; \u57fa\u4e8e\u5934\u90e8\u66f4\u65b0\u672c\u5730\u72b6\u6001\u548c\u7f13\u5b58<br \/>\n    &#8211; \u5bf9\u4e8e\u5220\u9664\u64cd\u4f5c&#xff0c;\u4ece\u672c\u5730\u79fb\u9664\u8d44\u6e90<br \/>\n    &#8211; \u5bf9\u4e8e\u66f4\u65b0\u64cd\u4f5c&#xff0c;\u66f4\u65b0\u672c\u5730ETag<\/p>\n<p>  # \u670d\u52a1\u5668\u5b9e\u73b0<br \/>\n  server_implementation:<br \/>\n    &#8211; \u786e\u4fdd\u54cd\u5e94\u4f53\u4e3a\u7a7a<br \/>\n    &#8211; \u8bbe\u7f6e\u9002\u5f53\u7684\u7f13\u5b58\u5934\u90e8<br \/>\n    &#8211; \u5305\u542b\u6709\u7528\u7684\u64cd\u4f5c\u5143\u6570\u636e<br \/>\n    &#8211; \u5bf9\u4e8e\u5220\u9664\u64cd\u4f5c&#xff0c;\u5305\u542b\u88ab\u5220\u9664\u8d44\u6e90\u7684\u6807\u8bc6<br \/>\n    &#8211; \u5bf9\u4e8e\u66f4\u65b0\u64cd\u4f5c&#xff0c;\u5305\u542b\u65b0\u7684ETag<br \/>\n    &#8211; \u4fdd\u6301\u5e42\u7b49\u6027<\/p>\n<p>  # \u7f13\u5b58\u884c\u4e3a<br \/>\n  caching:<br \/>\n    &#8211; 204\u54cd\u5e94\u901a\u5e38\u4e0d\u5e94\u7f13\u5b58<br \/>\n    &#8211; \u8bbe\u7f6eCache-Control\u4e3a&#034;no-cache&#034;\u6216&#034;no-store&#034;<br \/>\n    &#8211; \u5982\u679c\u64cd\u4f5c\u5f71\u54cd\u5176\u4ed6\u8d44\u6e90&#xff0c;\u4f7f\u76f8\u5173\u7f13\u5b58\u5931\u6548<\/p>\n<p>  # \u9519\u8bef\u5904\u7406<br \/>\n  error_handling:<br \/>\n    &#8211; \u5982\u679c\u64cd\u4f5c\u5931\u8d25&#xff0c;\u4e0d\u8981\u4f7f\u7528204<br \/>\n    &#8211; \u4f7f\u7528\u9002\u5f53\u7684\u9519\u8bef\u72b6\u6001\u7801&#xff08;4xx\u62165xx&#xff09;<br \/>\n    &#8211; \u5bf9\u4e8e\u6743\u9650\u9519\u8bef\u8fd4\u56de403<br \/>\n    &#8211; \u5bf9\u4e8e\u8d44\u6e90\u4e0d\u5b58\u5728&#xff0c;\u6839\u636eAPI\u8bbe\u8ba1\u8fd4\u56de404\u6216204<\/p>\n<p>  # \u5e42\u7b49\u6027\u8003\u8651<br \/>\n  idempotency:<br \/>\n    &#8211; \u5220\u9664\u4e0d\u5b58\u5728\u7684\u8d44\u6e90\u53ef\u8fd4\u56de204<br \/>\n    &#8211; \u66f4\u65b0\u4e3a\u76f8\u540c\u503c\u53ef\u8fd4\u56de204<br \/>\n    &#8211; \u91cd\u590d\u7684\u72b6\u6001\u5207\u6362\u53ef\u8fd4\u56de204<br \/>\n    &#8211; \u786e\u4fdd\u64cd\u4f5c\u662f\u5e42\u7b49\u7684<\/p>\n<p>  # \u76d1\u63a7\u548c\u65e5\u5fd7<br \/>\n  monitoring:<br \/>\n    &#8211; \u8bb0\u5f55204\u54cd\u5e94\u7684\u64cd\u4f5c\u7c7b\u578b<br \/>\n    &#8211; \u76d1\u63a7204\u54cd\u5e94\u7387<br \/>\n    &#8211; \u5ba1\u8ba1\u654f\u611f\u64cd\u4f5c&#xff08;\u5220\u9664\u3001\u6743\u9650\u53d8\u66f4&#xff09;<br \/>\n    &#8211; \u8bb0\u5f55\u64cd\u4f5c\u65f6\u95f4\u548c\u6267\u884c\u8005<\/p>\n<p>  # \u5b89\u5168\u8003\u8651<br \/>\n  security:<br \/>\n    &#8211; \u9a8c\u8bc1\u64cd\u4f5c\u6743\u9650<br \/>\n    &#8211; \u8bb0\u5f55\u654f\u611f\u64cd\u4f5c<br \/>\n    &#8211; \u9632\u6b62CSRF\u653b\u51fb<br \/>\n    &#8211; \u9a8c\u8bc1\u8f93\u5165\u6570\u636e<br \/>\n    &#8211; \u9650\u5236\u64cd\u4f5c\u9891\u7387<\/p>\n<p>\u901a\u8fc7\u672c\u7ae0\u7684\u8be6\u7ec6\u89e3\u6790&#xff0c;\u6211\u4eec\u6df1\u5165\u4e86\u89e3\u4e86204 No Content\u72b6\u6001\u7801\u7684\u6b63\u786e\u7528\u6cd5\u3001\u6700\u4f73\u5b9e\u8df5\u3001\u4ee5\u53ca\u4e0e200 OK\u7684\u533a\u522b\u3002204\u72b6\u6001\u7801\u5728RESTful API\u8bbe\u8ba1\u4e2d\u626e\u6f14\u7740\u91cd\u8981\u89d2\u8272&#xff0c;\u7279\u522b\u662f\u5728\u4e0d\u9700\u8981\u8fd4\u56de\u6570\u636e\u7684\u6210\u529f\u64cd\u4f5c\u573a\u666f\u4e2d\u3002<\/p>\n<p>\u6b63\u786e\u4f7f\u7528204 No Content\u53ef\u4ee5\u63d0\u9ad8API\u7684\u7b80\u6d01\u6027\u548c\u6548\u7387&#xff0c;\u51cf\u5c11\u4e0d\u5fc5\u8981\u7684\u6570\u636e\u4f20\u8f93&#xff0c;\u540c\u65f6\u4fdd\u6301\u5ba2\u6237\u7aef\u72b6\u6001\u7684\u4e00\u81f4\u6027\u3002<\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\/* MD \/ \u5bcc\u6587\u672c\u4e2d\u7684 .toc\uff08\u542b\u535a\u5ba2\u56ed\u642c\u5bb6\u7b49\u5d4c\u5957\u7ed3\u6784\uff09\uff1b.toc-box \u5728\u4fa7\u680f\uff0c\u4e0d\u53d7\u5f71\u54cd *\/#content_views .toc,\/* \u7f16\u8f91\u5668\u5e38\u5728\u76ee\u5f55\u524d\u540e\u63d2\u5165\u7a7a p\uff08:empty \u4ecd\u5360 20px\uff09\uff0c\u4e00\u5e76\u53bb\u6389\u907f\u514d\u9876\u7a7a\u9699 *\/#content_views.markdown_views &gt; p:empty:has(+ .toc),#content_views.markdown_views &gt; .toc + p:empty,\/* \u5bcc\u6587\u672c\u65e7\u7248\u76ee\u5f55\u6807\u8bb0 *\/#content<\/p>\n","protected":false},"author":2,"featured_media":122267,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[43],"topic":[],"class_list":["post-122270","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-server","tag-43"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/122270.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\/* MD \/ \u5bcc\u6587\u672c\u4e2d\u7684 .toc\uff08\u542b\u535a\u5ba2\u56ed\u642c\u5bb6\u7b49\u5d4c\u5957\u7ed3\u6784\uff09\uff1b.toc-box \u5728\u4fa7\u680f\uff0c\u4e0d\u53d7\u5f71\u54cd *\/#content_views .toc,\/* \u7f16\u8f91\u5668\u5e38\u5728\u76ee\u5f55\u524d\u540e\u63d2\u5165\u7a7a p\uff08:empty \u4ecd\u5360 20px\uff09\uff0c\u4e00\u5e76\u53bb\u6389\u907f\u514d\u9876\u7a7a\u9699 *\/#content_views.markdown_views &gt; p:empty:has(+ .toc),#content_views.markdown_views &gt; .toc + p:empty,\/* \u5bcc\u6587\u672c\u65e7\u7248\u76ee\u5f55\u6807\u8bb0 *\/#content\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/122270.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-11-04T05:57:05+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/11\/20261104055657-6aeac9298080e.png\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"79 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/122270.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/122270.html\",\"name\":\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-11-04T05:57:05+00:00\",\"dateModified\":\"2026-11-04T05:57:05+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/122270.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/122270.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/122270.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/122270.html","og_locale":"zh_CN","og_type":"article","og_title":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\/* MD \/ \u5bcc\u6587\u672c\u4e2d\u7684 .toc\uff08\u542b\u535a\u5ba2\u56ed\u642c\u5bb6\u7b49\u5d4c\u5957\u7ed3\u6784\uff09\uff1b.toc-box \u5728\u4fa7\u680f\uff0c\u4e0d\u53d7\u5f71\u54cd *\/#content_views .toc,\/* \u7f16\u8f91\u5668\u5e38\u5728\u76ee\u5f55\u524d\u540e\u63d2\u5165\u7a7a p\uff08:empty \u4ecd\u5360 20px\uff09\uff0c\u4e00\u5e76\u53bb\u6389\u907f\u514d\u9876\u7a7a\u9699 *\/#content_views.markdown_views &gt; p:empty:has(+ .toc),#content_views.markdown_views &gt; .toc + p:empty,\/* \u5bcc\u6587\u672c\u65e7\u7248\u76ee\u5f55\u6807\u8bb0 *\/#content","og_url":"https:\/\/www.wsisp.com\/helps\/122270.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-11-04T05:57:05+00:00","og_image":[{"url":"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/11\/20261104055657-6aeac9298080e.png"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"79 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/122270.html","url":"https:\/\/www.wsisp.com\/helps\/122270.html","name":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-11-04T05:57:05+00:00","dateModified":"2026-11-04T05:57:05+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/122270.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/122270.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/122270.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"HTTP \u72b6\u6001\u7801\uff1a\u5ba2\u6237\u7aef\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u8bed\u8a00\u2014\u2014\u7b2c\u4e8c\u90e8\u5206\uff1a\u6210\u529f\u7c7b\u72b6\u6001\u7801\uff082xx\uff09\u6df1\u5ea6\u89e3\u6790\uff08\u4e00\uff09"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/122270","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=122270"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/122270\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media\/122267"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=122270"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=122270"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=122270"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=122270"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}