[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"index-recommend":3},{"topics":4,"articles":127},{"data":5,"title":126},[6,22,36,48,61,74,86,99,113,120],{"id":7,"createdAt":8,"status":9,"user":10,"title":17,"content":18,"images":19,"viewCount":20,"commentCount":21},10,"2024-03-22T06:45:15.218Z",1,{"phone":11,"userId":12,"nickName":13,"vipType":9,"avatar":14,"sign":15,"createdAt":16},"16100000018",526827823890501,"小崔别催我","https://image.xinwei.ltd/images/avatar-default.png","","2024-03-18T10:07:56.193Z","小道消息，某厂又准备裁员了，今年形势极其严峻啊","大家一起来聊聊",[],455,4,{"id":23,"createdAt":24,"status":9,"user":25,"title":31,"content":32,"images":33,"viewCount":34,"commentCount":35},5,"2024-03-16T04:21:52.909Z",{"phone":26,"userId":27,"nickName":28,"vipType":9,"avatar":29,"sign":15,"createdAt":30},"16100000010",525978917961797,"江舵主","https://image.xinwei.ltd/9ef776012b1677a481b13b43d43567ee1710561773766.jpeg","2024-03-16T00:33:43.769Z","offer怎么选？大家给点意见哈","最近面试了一个月，收到一个大厂的offer，还有一个中厂的offer，都差不多40k的，大厂负责的事情多，中厂只负责一个项目，有点为难",[],420,6,{"id":9,"createdAt":37,"status":9,"user":38,"title":44,"content":15,"images":45,"viewCount":46,"commentCount":47},"2024-01-01T16:20:45.561Z",{"phone":39,"userId":40,"nickName":41,"vipType":9,"avatar":42,"sign":15,"createdAt":43},"16100000001",521588467585093,"一路向北","https://image.xinwei.ltd/c598b22c492a000aa301910dcca147c61710561327592.jpg","2024-03-03T14:48:56.469Z","2023年过去了，过的一地鸡毛，不知道大家过的咋样？",[],371,3,{"id":49,"createdAt":50,"status":9,"user":51,"title":57,"content":58,"images":59,"viewCount":60,"commentCount":9},15,"2024-06-15T05:28:36.213Z",{"phone":52,"userId":53,"nickName":54,"vipType":9,"avatar":55,"sign":15,"createdAt":56},"16100000002",525978103783493,"老夫子","https://image.xinwei.ltd/4c436d4c286aa453f6cdba4d7e9cc4f61710561660956.jpg","2024-03-16T00:30:24.994Z","华为od在你们眼中是什么存在","我是面试了很多，但要么是给的太低，要么就是公司太小，反正感觉和大厂无缘了，现在通过外包进入了华为od，大佬们不要瞧不起我哈",[],370,{"id":62,"createdAt":63,"status":9,"user":64,"title":69,"content":70,"images":71,"viewCount":73,"commentCount":47},9,"2024-03-22T06:39:17.934Z",{"phone":65,"userId":66,"nickName":67,"vipType":9,"avatar":14,"sign":15,"createdAt":68},"16100000016",526827645988933,"一个搞java的实习生","2024-03-18T10:07:12.759Z","杭州3月份完全不行啊，很多岗位都不怎么回复啊","友友们，你们也是这样吗？我投了很多，但是没有几个回复的，而且工资压得很低，你们是怎么处理的？我刚从上海来这边",[72],"https://image.xinwei.ltd/8b468c0cdd21c04eeaaba5cfdf0b25f41711089555003.jpg",293,{"id":35,"createdAt":75,"status":9,"user":76,"title":82,"content":15,"images":83,"viewCount":84,"commentCount":85},"2024-03-16T04:34:51.817Z",{"phone":77,"userId":78,"nickName":79,"vipType":9,"avatar":80,"sign":15,"createdAt":81},"16100000005",525978440544325,"小哆啦C梦","https://image.xinwei.ltd/1c9e4db43041f1e2ae2f0edfb41e02381710561422999.jpg","2024-03-16T00:31:47.211Z","这里有内推的渠道吗？求个内推，客户端",[],241,2,{"id":87,"createdAt":88,"status":9,"user":89,"title":95,"content":96,"images":97,"viewCount":98,"commentCount":23},8,"2024-03-17T08:48:36.4Z",{"phone":90,"userId":91,"nickName":92,"vipType":9,"avatar":93,"sign":15,"createdAt":94},"16100000009",525978830176325,"长江8号","https://image.xinwei.ltd/64541af99b605bad698547c61f78fc631710561753588.jpeg","2024-03-16T00:33:22.337Z","如果你有一个程序员的对象，现在的你是什么感受？","tip：我和我对象都是程序员😁，哈哈",[],232,{"id":100,"createdAt":101,"status":9,"user":102,"title":108,"content":109,"images":110,"viewCount":111,"commentCount":112},11,"2024-03-31T06:51:42.645Z",{"phone":103,"userId":104,"nickName":105,"vipType":9,"avatar":106,"sign":15,"createdAt":107},"16100000020",526827970003013,"何家妮子","https://image.xinwei.ltd/994f957df30b45e8d3862dfbaa7e12e21711107061407.jpeg","2024-03-18T10:08:31.864Z","欢迎大家报考软考啊，如果有想5月份突击的话，可以联系我哈","软考是做到亚洲中日韩互认的计算机证书，在事业单位有这样的证书可以有非常大的便利哦，详情可以联系我哈",[],183,0,{"id":21,"createdAt":114,"status":9,"user":115,"title":116,"content":15,"images":117,"viewCount":118,"commentCount":119},"2024-01-17T08:34:58.704Z",{"phone":39,"userId":40,"nickName":41,"vipType":9,"avatar":42,"sign":15,"createdAt":43},"94年的程序媛，今年回家又要被催婚了，肿么办",[],100,7,{"id":85,"createdAt":121,"status":9,"user":122,"title":123,"content":15,"images":124,"viewCount":125,"commentCount":47},"2024-01-04T06:39:19.18Z",{"phone":39,"userId":40,"nickName":41,"vipType":9,"avatar":42,"sign":15,"createdAt":43},"马上快过年了，问问大家今年挣钱了吗？",[],97,"话题",{"data":128,"title":2292},[129,150,164,177,192,207,220,233,248,263,278,291,304,318,331,344,357,370,383,395,410,425,438,450,463,476,488,501,514,527,539,552,566,578,590,610,622,635,650,667,682,695,708,720,735,748,760,773,792,805,817,830,842,854,867,880,892,905,917,929,944,955,968,980,993,1006,1018,1031,1043,1054,1067,1080,1093,1106,1117,1130,1143,1156,1170,1183,1194,1205,1217,1228,1240,1252,1265,1276,1287,1298,1310,1321,1333,1344,1356,1368,1380,1392,1404,1414,1426,1439,1451,1462,1475,1487,1497,1508,1519,1531,1542,1555,1565,1577,1588,1599,1610,1620,1632,1642,1654,1666,1679,1690,1701,1713,1724,1736,1747,1759,1769,1780,1791,1805,1816,1827,1839,1849,1859,1870,1880,1890,1901,1912,1924,1934,1944,1954,1964,1975,1986,1998,2010,2021,2031,2043,2053,2064,2074,2087,2097,2109,2120,2132,2142,2153,2164,2175,2185,2195,2207,2219,2231,2242,2254,2266,2280],{"id":130,"createdAt":131,"title":132,"content":133,"summary":134,"image":135,"uid":136,"user":137,"categoryId":85,"category":142,"subCategoryId":144,"subCategory":145,"comments":147,"status":9,"reason":15,"notice":15,"visitCount":148,"commentCount":9,"keywords":149},123,"2024-08-18T18:08:17.125Z","web网页集成Apple Sign in苹果授权登录","## 一、概要\n现在越来越多的应用都集成了第三方授权登录了，而且由于苹果的iOS app上架审核规则，即如果有第三方授权登录，那么必须接入苹果Apple授权登录。那么针对苹果账号，网页端的苹果授权登录自然而然也变得很重要。\n\n## 二、Apple Sign In官方文档\n[https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/)\n\n## 三、步骤\n### 3.1 web接入Apple Sign In所需的资料\n- 1. 一个Apple 开发者账号\n- 2. 开发者账号下配置一个App Id\n- 3. 开发者账号下配置一个Service ID\n- 4. 开发者账号下给Service ID的sign in Apple编辑中配置domains和website\n> 针对一个Service ID，公司账号可以注册上限100个的网站，个人账号上限10个\n- 5. 开发者账号下给新建一个key（可以下载导出p8格式文件，服务端会需要到）\n- 6. 如果是公司账号要给服务端team id（就是右上角账号名字中的一组字符串）.\n\n针对上述所需的资料，创建App Id的流程很简单，基本和Service ID类似，所以这里不赘述。\n\n### 3.2 创建Service ID\n在Identifier下，选择Service Ids类型，然后点击Identifiers旁边的“+”号\n![创建Service ID - 1](https://image.xinwei.ltd/11724001528643.jpg)\n\n输入service id的描述，identifier（就是website网页上配置的**client ID**），要记得勾选 `Sign in with Apple`.\n![创建Service ID - 2](https://image.xinwei.ltd/image1724002021317.png)\n\n配置`Sign in with Apple`，点击`Configure`\n![点击Configure](https://image.xinwei.ltd/21724002446590.jpg)\n- 选择一个App ID\n- 配置网站的域名（我这里是测试和正式环境都配置了，localhost配置不了）\n- 配置Return URLs（这里是需要提供一个服务端的接口，因为Apple会给这里面的url发送post请求）\n- 注意点：domains和return urls都是用英文逗号`,`隔开的。domains中不要加http或者https，return urls中是要加http和https。\n![配置域名、网址](https://image.xinwei.ltd/31724002623205.jpg)\n\n点击Next，点击Done就ok了。\n![确认website urls](https://image.xinwei.ltd/41724002974138.jpg)\n\n### 3.3 新建一个key\n![新建一个key](https://image.xinwei.ltd/image1724003234060.png)\n\n可以下载p8文件出来，给服务端\n![download key](https://image.xinwei.ltd/71724003479138.jpg)\n\n\n### 3.4 网页的html中配置\n如果要使用service id中的return urls，那就需要服务端提供post接口。如果不用服务端，只用前端介入的话，可以使用pop up的弹出窗口模式。\n> 如果使用pop up方式，那么在Facebook等第三方app中显示网页时，授权弹窗会被阻止，导致流程中断，切记\n\n在html中配置script脚本\n```html\n\u003Cscript type=\"text/javascript\" src=\"https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js\">\u003C/script>\n```\n\n使用js或者ts脚本\n```js\nAppleID.auth.init({\n      clientId : '[CLIENT_ID]',\n      scope : '[SCOPES]', // 比如[ 'name', 'email' ]\n      redirectURI : '[REDIRECT_URI]',\n      state : '[STATE]',\n      nonce : '[NONCE]',\n      usePopup : true // 这里如果是true，那么就是使用弹窗授权, return urls就用不到\n});\n\n// 直接使用promise的方式，当然也有使用document监听的\nAppleID.auth.signIn()\n.then(res => {\n  \n})\n.catch(err => {\n  \n});\n\n// 可选，document监听\n// Listen for authorization success.\ndocument.addEventListener('AppleIDSignInOnSuccess', (event) => {\n    // Handle successful response.\n    console.log(event.detail.data);\n});\n\n\n// Listen for authorization failures.\ndocument.addEventListener('AppleIDSignInOnFailure', (event) => {\n     // Handle error.\n     console.log(event.detail.error);\n});\n```\n\n使用signin方法得到的数据就是包括code、id_token参数的res，将这2个参数传给自己公司的接口，让服务端去从Apple端获取用户信息。\n\n\n## 四、服务端所需的资料\n- Apple开发者账号的team id\n- Apple开发者账号的service id\n- Apple开发者账号的key文件（p8文件）\n\n以上大概就是这些了，希望对大家有帮助。\n","现在越来越多的应用都集成了第三方授权登录了，而且由于苹果的iOS app上架审核规则，即如果有第三方授权登录，那么必须接入苹果Apple Sign in授权登录。那么针对苹果账号，网页端的苹果授权登录自然而然也变得很重要。","https://image.xinwei.ltd/11724001528643.jpg",499668042977349,{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},"13121171998","全栈老韩","https://image.xinwei.ltd/images/IMG_5430.JPG","2024-01-01T16:14:30.305Z",{"id":85,"name":143},"IT技术",32,{"id":144,"name":146,"parentId":85},"前端",[],3421,"web apple sign in,apple sign in,apple web sign,apple sign in web,apple登录,apple登录网页集成,apple集成",{"id":151,"createdAt":152,"title":153,"content":154,"summary":155,"image":156,"uid":136,"user":157,"categoryId":85,"category":158,"subCategoryId":100,"subCategory":159,"comments":161,"status":9,"reason":15,"notice":15,"visitCount":162,"commentCount":112,"keywords":163},148,"2025-02-13T04:40:30.945Z","flutter Notification Service Extension errors(Error output from CocoaPods,unknown ISA PBXFileSystemSynchronized)...","## 一、背景\n在flutter工程中为iOS添加Notification Service Extension时，遇到了一系列的编译和运行问题，在此做一个记录。\n\n本地环境`environment`:\n- Xcode: 16.2\n- Simulator: iOS 17.5\n- Flutter 3.27.0-0.1.pre • channel beta • https://github.com/flutter/flutter.git\nFramework • revision 2e2c358c9b (4 months ago) • 2024-10-22 11:02:13 -0400\nEngine • revision af0f0d559c\nTools • Dart 3.6.0 (build 3.6.0-334.3.beta) • DevTools 2.40.1\n- ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-darwin23]\n- CocoaPods : 1.15.2\n\n## 二、问题\n### 问题1: \n> Error output from CocoaPods:\n↳\n    Searching for inspections failed: undefined method `map' for nil\nError running pod install\nError launching application on iPhone SE (3rd generation).\n\n![issue-1 pod error](https://image.xinwei.ltd/image1739418777754.png)\n\n### 问题2:\n> Launching lib/main.dart on iPhone SE (3rd generation) in debug mode...\nRunning pod install...\nRunning Xcode build...\nXcode build done.                                           304.5s\nFailed to build iOS app\nError (Xcode): Cycle inside Runner; building could produce unreliable results.\nCycle details:\n→ That command depends on command in Target 'Runner': script phase “[CP] Copy Pods Resources”\n○ That command depends on command in Target 'Runner': script phase “[CP] Embed Pods Frameworks”\n○ That command depends on command in Target 'Runner': script phase “Thin Binary”\n○ Target 'Runner' has process command with output '/Users/edy/Desktop/xxxproject/build/ios/Debug-iphonesimulator/Runner.app/Info.plist'\n○ Target 'Runner' has copy command from '/Users/edy/Desktop/xxxproject/build/ios/Debug-iphonesimulator/xxxPushExtension.appex' to '/Users/edy/Desktop/xxxproject/build/ios/Debug-iphonesimulator/Runner.app/PlugIns/xxxPushExtension.appex'\n\n![issue-2 cycle inside runner](https://image.xinwei.ltd/image1739419782879.png)\n\n\n### 问题3（可能会遇到这样的问题，我也是在这个过程中碰到过）:\n> RuntimeError - `PBXGroup` attempted to initialize an object with unknown ISA `PBXFileSystemSynchronizedRootGroup`\n\n这个问题需要删除掉Notification Service Extension，然后重新新建.\n\n\n\n## 三、步骤：\n### 3.1 给iOS工程添加Notification Service Extension\n添加Notification Service Extension\n![add Notification Service Extension](https://image.xinwei.ltd/image1739418168922.png)\n\n![choose Notification Service Extension](https://image.xinwei.ltd/image1739418252399.png)\n\n添加完之后的状态folder结构\n![](https://image.xinwei.ltd/image1739418361790.png)\n\n### 3.2 编译flutter运行，出现报错：\n```run flutter error\nLaunching lib/main.dart on iPhone SE (3rd generation) in debug mode...\nUpdating project for Xcode compatibility.\nUpgrading project.pbxproj\nUpgrading Runner.xcscheme\nRunning pod install...\nCocoaPods' output:\n↳\n      Preparing\n\n    Analyzing dependencies\n\n    Inspecting targets to integrate\n      CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local because checking is only performed in repo update\n\n    ――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n\n    ### Command\n\n    ```\n    /Users/edy/.rvm/rubies/ruby-3.3.5/bin/pod install --verbose\n    ```\n\n    ### Report\n\n    * What did you do?\n\n    * What did you expect to happen?\n\n    * What happened instead?\n\n\n    ### Stack\n\n    ```\n       CocoaPods : 1.15.2\n            Ruby : ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-darwin23]\n        RubyGems : 3.5.16\n            Host : macOS 14.6.1 (23G93)\n           Xcode : 16.2 (16C5032a)\n             Git : git version 2.39.5 (Apple Git-154)\n    Ruby lib dir : /Users/edy/.rvm/rubies/ruby-3.3.5/lib\n    Repositories : cocoapods - git - https://github.com/CocoaPods/Specs.git @ 3574d4d220177427a4fa77c221b01e55996aa84d\n\n                   trunk - CDN - https://cdn.cocoapods.org/\n    ```\n\n    ### Plugins\n\n    ```\n    cocoapods-deintegrate : 1.0.5\n    cocoapods-plugins     : 1.0.0\n    cocoapods-search      : 1.0.1\n    cocoapods-trunk       : 1.6.0\n    cocoapods-try         : 1.2.0\n    ```\n\n    ### Podfile\n\n    ```ruby\n    # Uncomment this line to define a global platform for your project\n    platform :ios, '13.0'\n\n    # CocoaPods analytics sends network stats synchronously affecting flutter build latency.\n    ENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\n    project 'Runner', {\n      'Debug' => :debug,\n      'Profile' => :release,\n      'Release' => :release,\n    }\n\n    def flutter_root\n      generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n      unless File.exist?(generated_xcode_build_settings_path)\n        raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n      end\n\n      File.foreach(generated_xcode_build_settings_path) do |line|\n        matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n        return matches[1].strip if matches\n      end\n      raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\n    end\n\n    require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\n    flutter_ios_podfile_setup\n\n    target 'Runner' do\n      use_frameworks!\n      use_modular_headers!\n\n      pod 'KeychainAccess'\n      pod 'FBAudienceNetwork','= 6.15.0'\n\n      flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n      target 'RunnerTests' do\n        inherit! :search_paths\n      end\n    end\n\n    post_install do |installer|\n      installer.pods_project.targets.each do |target|\n        flutter_additional_ios_build_settings(target)\n        target.build_configurations.each do |config|\n          config.build_settings['SWIFT_VERSION'] = '5.0'  # required by simple_permission\n          config.build_settings['ENABLE_BITCODE'] = 'NO'\n        end\n      end\n    end\n    ```\n\n    ### Error\n\n    ```\n    RuntimeError - `PBXGroup` attempted to initialize an object with unknown ISA `PBXFileSystemSynchronizedRootGroup` from attributes: `{\"isa\"=>\"PBXFileSystemSynchronizedRootGroup\", \"exceptions\"=>[\"0453440D2D5DA235003E9BF2\"], \"explicitFileTypes\"=>{}, \"explicitFolders\"=>[], \"path\"=>\"xxxPushExtension\", \"sourceTree\"=>\"\u003Cgroup>\"}`\n    If this ISA was generated by Xcode please file an issue: https://github.com/CocoaPods/Xcodeproj/issues/new\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:359:in `rescue in object_with_uuid'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:349:in `object_with_uuid'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:300:in `block (2 levels) in configure_with_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:299:in `each'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:299:in `block in configure_with_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:296:in `each'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:296:in `configure_with_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project.rb:272:in `new_from_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:350:in `object_with_uuid'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:290:in `block in configure_with_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:287:in `each'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project/object.rb:287:in `configure_with_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project.rb:272:in `new_from_plist'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project.rb:213:in `initialize_from_file'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.1/lib/xcodeproj/project.rb:113:in `open'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1194:in `block (2 levels) in inspect_targets_to_integrate'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1193:in `each'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1193:in `block in inspect_targets_to_integrate'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/user_interface.rb:64:in `section'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1188:in `inspect_targets_to_integrate'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:107:in `analyze'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:422:in `analyze'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:244:in `block in resolve_dependencies'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/user_interface.rb:64:in `section'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:243:in `resolve_dependencies'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:162:in `install!'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/command/install.rb:52:in `run'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/claide-1.1.0/lib/claide/command.rb:334:in `run'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/command.rb:52:in `run'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/bin/pod:55:in `\u003Ctop (required)>'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/bin/pod:25:in `load'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/bin/pod:25:in `\u003Cmain>'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/bin/ruby_executable_hooks:22:in `eval'\n    /Users/edy/.rvm/rubies/ruby-3.3.5/bin/ruby_executable_hooks:22:in `\u003Cmain>'\n    ```\n\n    ――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n\n    [!] Oh no, an error occurred.\n\n    Search for existing GitHub issues similar to yours:\n    https://github.com/CocoaPods/CocoaPods/search?q=%60PBXGroup%60+attempted+to+initialize+an+object+with+unknown+ISA+%60PBXFileSystemSynchronizedRootGroup%60+from+attributes%3A+%60%7B%22isa%22%3D%3E%22PBXFileSystemSynchronizedRootGroup%22%2C+%22exceptions%22%3D%3E%5B%220453440D2D5DA235003E9BF2%22%5D%2C+%22explicitFileTypes%22%3D%3E%7B%7D%2C+%22explicitFolders%22%3D%3E%5B%5D%2C+%22path%22%3D%3E%22xxxPushExtension%22%2C+%22sourceTree%22%3D%3E%22%3Cgroup%3E%22%7D%60%0AIf+this+ISA+was+generated+by+Xcode+please+file+an+issue%3A+https%3A%2F%2Fgithub.com%2FCocoaPods%2FXcodeproj%2Fissues%2Fnew&type=Issues\n\n    If none exists, create a ticket, with the template displayed above, on:\n    https://github.com/CocoaPods/CocoaPods/issues/new\n\n    Be sure to first read the contributing guide for details on how to properly submit a ticket:\n    https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md\n\n    Don't forget to anonymize any private data!\n\n    Looking for related issues on cocoapods/cocoapods...\n\nError output from CocoaPods:\n↳\n    Searching for inspections failed: undefined method `map' for nil\n\nError running pod install\nError launching application on iPhone SE (3rd generation).\n```\n\n### 3.3 解决pod install失败的问题\n从问题3.1的folder结构中可以看出，文件夹的icon标识不是灰色gray的，而是淡蓝色blue的icon，这个应该是Xcode的bug，需要将新建的Extension文件夹改成group。\n\n右键Extension文件夹，选择`Convert to Group`\n![convert to Group](https://image.xinwei.ltd/image1739419003315.png)\n\n这个解决方法，感谢链接：[https://github.com/CocoaPods/CocoaPods/issues/12456](https://github.com/CocoaPods/CocoaPods/issues/12456)\n![github fix](https://image.xinwei.ltd/image1739419140817.png)\n\n### 3.4 接着运行代码，遇到build的报错\n![issue-2 cycle inside runner](https://image.xinwei.ltd/image1739419782879.png)\n这个问题有点奇怪，我也查了好久，才从网上找到一个解决办法：\n- 选择主target，进入到Build Phrases的设置下\n![build phrases setting](https://image.xinwei.ltd/image1739420069053.png)\n- 将Embed Foundation Extensions这项，移动到CopyBundle Resources这项正下面.\n![move position result](https://image.xinwei.ltd/image1739420184527.png)\n- 然后运行，没有报错。\n![run success](https://image.xinwei.ltd/image1739420382663.png)\n\n参考链接：[https://stackoverflow.com/questions/77138968/handling-cycle-inside-runner-building-could-produce-unreliable-results-after-up](https://stackoverflow.com/questions/77138968/handling-cycle-inside-runner-building-could-produce-unreliable-results-after-up)\n\n## 四、添加即时通知 - time sensitive notifications\n### 4.1 设置push notification capability\n按照下面截图中的步骤，添加push notifications\n![push notifications](https://image.xinwei.ltd/image1739420598289.png)\n\n会生成entitlements文件和capability\n![push notifications result](https://image.xinwei.ltd/image1739420695587.png)\n\n### 4.2 配置ios deployment target\n因为Xcode默认会配置ios deployment target，所以我们要检查适配版本，修改为我们想要的版本.\n截图是默认的18.2，但现阶段不可能配置到这个版本。\n![default ios deployment target](https://image.xinwei.ltd/image1739420828256.png)\n修改为15.0\n![set to ios15.0](https://image.xinwei.ltd/image1739420981066.png)\n\n### 4.3 配置NotificationService方法\n配置如下：\n```NotificationService.swift\n//\n//  NotificationService.swift\n//  xxxPushExtension\n//\n//  Created by Hamry on 2/13/25.\n//\nimport UserNotifications\nimport UIKit\nimport Intents\n\nclass NotificationService: UNNotificationServiceExtension {\n\n    var contentHandler: ((UNNotificationContent) -> Void)?\n    var bestAttemptContent: UNMutableNotificationContent?\n    \n    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {\n            self.contentHandler = contentHandler\n            bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)\n            \n            if let bestAttemptContent = bestAttemptContent {\n                // Modify the notification content here...\n                // 获取通信消息\n                if let options = bestAttemptContent.userInfo[\"fcm_options\"] as? [String: Any], let senderImageURLString = options[\"image\"] as? String\n                {\n                    // Here you need to download the image data from the URL.\n                    self.getMediaAttachment(for: senderImageURLString) { image in\n                        guard let groupIcon = image else {\n                            contentHandler(bestAttemptContent)\n                            return\n                        }\n                        let avatar = INImage(imageData: groupIcon.pngData()!)\n                        // 消息发送方\n                        let messageSender = INPerson(\n                            personHandle: INPersonHandle(value: nil, type: .unknown),\n                            nameComponents: try? PersonNameComponents(bestAttemptContent.title),\n                            displayName: bestAttemptContent.title,\n                            image: avatar,\n                            contactIdentifier: nil,\n                            customIdentifier: nil,\n                            isMe: false,\n                            suggestionType: .none\n                        )\n                        \n                        // 消息接收方\n                        let mePerson = INPerson(\n                            personHandle: INPersonHandle(value: \"\", type: .unknown),\n                            nameComponents: nil,\n                            displayName: nil,\n                            image: nil,\n                            contactIdentifier: nil,\n                            customIdentifier: nil,\n                            isMe: true,\n                            suggestionType: .none\n                        )\n                        \n                        let intent = INSendMessageIntent(recipients: [mePerson],\n                                                         outgoingMessageType: .outgoingMessageText,\n                                                         content: bestAttemptContent.body,\n                                                         speakableGroupName: INSpeakableString(spokenPhrase: bestAttemptContent.title),\n                                                         conversationIdentifier: nil,\n                                                         serviceName: nil,\n                                                         sender: messageSender,\n                                                         attachments: nil)\n//                        sender\n                        intent.setImage(avatar, forParameterNamed: \\.speakableGroupName)\n\n                        let interaction = INInteraction(intent: intent, response: nil)\n                        interaction.direction = .incoming\n                        interaction.donate(completion: nil)\n                        do {\n                            let messageContent = try request.content.updating(from: intent)\n                            contentHandler(messageContent)\n                        } catch {\n                            print(error.localizedDescription)\n                            contentHandler(bestAttemptContent)\n                        }\n                    }\n                }else {\n                    contentHandler(bestAttemptContent)\n                }\n            }\n        }\n    \n    override func serviceExtensionTimeWillExpire() {\n        // Called just before the extension will be terminated by the system.\n        // Use this as an opportunity to deliver your \"best attempt\" at modified content, otherwise the original push payload will be used.\n        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {\n            contentHandler(bestAttemptContent)\n        }\n    }\n\n}\n\nextension NotificationService {\n\n    //  通过本地 url 地址 获取图片资源\n    private func getMediaAttachment(for urlString: String, completion: @escaping (UIImage?) -> Void) {\n        // 1\n        guard let url = URL(string: urlString) else {\n            completion(nil)\n            return\n        }\n        \n        // 2 通过远程图片URL下载图片\n        downloadImage(forURL: url) { result in\n            // 3\n            guard let image = try? result.get() else {\n                completion(nil)\n                return\n            }\n            \n            // 4\n            completion(image)\n        }\n    }\n    \n    // 通过远程图片url地址 下载图片文件\n    public enum DownloadError: Error {\n        case emptyData\n        case invalidImage\n    }\n\n    private func downloadImage(forURL url: URL, completion: @escaping (Result\u003CUIImage, Error>) -> Void) {\n        let task = URLSession.shared.dataTask(with: url) { data, response, error in\n            if let error = error {\n                completion(.failure(error))\n                return\n            }\n            \n            guard let data = data else {\n                completion(.failure(DownloadError.emptyData))\n                return\n            }\n            \n            guard let image = UIImage(data: data) else {\n                completion(.failure(DownloadError.invalidImage))\n                return\n            }\n            \n            completion(.success(image))\n        }\n        \n        task.resume()\n    }\n}\n\n```\n\n### 4.4 运行，没有问题\n注意`NotificationService.swift`中的`INPerson`等是iOS 15.0才开始使用的特性，所以需要4.2种的ios deployment target配置。\n\n## 五、总结\n其实也反反复复遇到过其他问题，但是解决了，这里篇幅有限，暂时记录这些，希望对有的同学有帮助.\n\n","记录一下在flutter工程中添加iOS的Notification Service Extension遇到的编译以及运行问题，希望对大家有所帮助。","https://image.xinwei.ltd/image1739418777754.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},"Flutter",[],3175,"Notification Service Extension,flutter Notification Service Extension,unknown ISA PBXFileSystemSynchronizedRootGroup,Error output from CocoaPods",{"id":165,"createdAt":166,"title":167,"content":168,"summary":169,"image":170,"uid":136,"user":171,"categoryId":85,"category":172,"subCategoryId":144,"subCategory":173,"comments":174,"status":9,"reason":15,"notice":15,"visitCount":175,"commentCount":112,"keywords":176},118,"2024-07-20T11:14:14.489Z","web开发-从facebook的in-app browser浏览器中网页，唤起Safari 或者 谷歌chrome浏览器","## 一、概要\n安卓Android系统是可以实现从Facebook中的网页，唤起默认的浏览器。\n但是iOS最多能实现打开Safari，但是不能指定具体的网址。\n\n## 二、在安装了facebook的iPhone中，通过网页唤起Safari\n### 2.1 【不成功】：直接使用https 或者 http链接\n```js\nwindow.location.href = 'https://www.baidu.com'\n```\n\n没有Facebook的“跳转外部浏览器”的弹窗出现，依然还是在Facebook的browser中刷新\n\n### 2.2 【不成功】：通过ftp协议使用中转用的index.html\nfacebook中打开的网页\n```js\nwindow.location.href = `ftp://43.xxx.xxx.xxx/index.html`\n```\n中转网页中\n```js\nwindow.open(”https://www.baidu.com”, “_self”);\n```\nSafari已经不支持ftp协议。\n![safari的ftp不支持.png](https://image.xinwei.ltd/11721472897038.png)\n\n能弹出Facebook的“跳转外部浏览器”的弹窗，点“确定”后可以唤起Safari，但是Safari中的中转index.html不能解析，Safari的白色提示页面提示“ftp url is blocked”\n\n### 2.3 【半成功】：通过x-web-search://协议\n```js\nconst currentLink = location.href\nconst link = currentLink.replace('https://', '').replace('http://', '').replace('www.', '')\nwindow.location.href = `x-web-search://${link}`\n```\n能弹出Facebook的“跳转外部浏览器”的弹窗，点“确定”后可以唤起Safari，但是进入的是Safari的默认搜素引擎的搜索界面，搜索输入框中是link的参数部分\n\n如果使用以下的方式，那么只会出现一个网址是空的Safari界面\n```js\nwindow.location.href = `x-web-search://`\n```\n## 三、在iOS上唤起iOS版谷歌浏览器\n```js\nwindow.location = `googlechrome://${link}`// ios to chrome\n```\n\n## 四、在安装了Facebook的Android手机中唤起chrome\n```js\nconst currentLink = location.href\nconst link = currentLink.replace('https://', '').replace('http://', '').replace('www.', '')\n\nif (ua.isAndroid()) {\n    window.location.href = `intent://${link}#Intent;scheme=https;end`// android\n}\n```\n\n或者使用：\n```js\n\u003Cscript>\n    function isFacebookApp() {\n        var ua = navigator.userAgent || navigator.vendor || window.opera;\n        return (ua.indexOf(\"FBAV\") > -1) || (ua.indexOf(\"FBAN\") > -1);\n    }\nif (isFacebookApp()) {\n    var currentLink = location.href;\n    if (currentLink.indexOf('https') > -1) {\n        var currentLink = currentLink.replace('https://', '');\n        currentLink = currentLink.replace('www.', '');\n        var chromeLink = \"intent://\" + currentLink + \"#Intent;scheme=https;package=com.android.chrome;end\";\n        window.location.href = chromeLink;\n    }\n    if (currentLink.indexOf('http') > -1) {\n        var currentLink = currentLink.replace('http://', '');\n        currentLink = currentLink.replace('www.', '');\n        var chromeLink = \"intent://\" + currentLink + \"#Intent;scheme=http;package=com.android.chrome;end\";\n        window.location.href = chromeLink;\n    }\n} \n\u003C/script> \n```\n\n## 五、 以下是一些测试过跳转的不成功代码逻辑\n```js\n// tryOpenDefault(() => {\n        //     window.open(url, '_blank');\n        // }, 1000)\n        \n        // tryOpenDefault(() => {\n        //     window.location.href = url;\n        // }, 2000)\n        \n        // tryOpenDefault(() => {\n        //     window.open(url, '_system');\n        // }, 3000)\n        \n        // tryOpenDefault(() => {\n        //     window.location.href = 'intent://' + url + '#Intent;' + 'scheme=https;end';\n        // }, 4000)\n        \n        // 会弹出跳转box，但是又快速退出回到帖子页\n        // tryOpenDefault(() => {\n        //     var a = document.createElement('a');\n        //     a.setAttribute('href', url);\n        //     a.setAttribute('target', '_blank'); // Ensures it opens in a new tab/window\n        //     a.click();\n        // }, 5000)\n  \n  \n  \n  // window.location.href = `prefs://${link}`\n                \n                // window.location.href = `x-safari-https://${link}` // box but not jump\n                \n                // window.location.href = `site://${link}` // not work\n                \n                // not work\n                // var a = document.createElement('a');\n                // a.setAttribute('href', currentLink);\n                // a.setAttribute('target', '_blank'); // Ensures it opens in a new tab/window\n                // a.click();\n                \n                // not work again\n                // var a = document.createElement('a');\n                // a.setAttribute('href', currentLink);\n                // a.setAttribute('target', '_blank'); // Ensures it opens in a new tab/window\n                // var dispatch = document.createEvent(\"HTMLEvents\");\n                // dispatch.initEvent(\"click\", true, true);\n                // a.dispatchEvent(dispatch);\n                    \n                // window.open(location.href, '_blank') // not work\n                // window.location.href = location.href // not work\n                // window.location.href = `safari://${currentLink}` // can prompt box, but can not jump still\n                // window.location.href = `safari://${link}`// can prompt box, but can not jump\n                // window.location.href = `googlechrome://${link}`// can open chrome\n```\n\n## 六、总结\n目前经过各种尝试发现，在安卓上确实是可以通过intent的方式唤起系统的浏览器，但是iOS的Safari浏览器，并没有合适的方法唤起浏览器并打开对应的网址。\n\n所以如果在iOS上的Facebook或者是其他app的内置浏览器（即in-app browser）上，想仅仅只通过web中来实现是做不到的。除非这个in-app浏览器所在的app是可以内置我们自己的代码的。\n\n因为在iOS系统中，app打开Safari的方式都是通过iOS的系统API：\n```js\n[[UIApplication sharedInstance] openUrl:@\"https://xxx.xxx.xxx\"]\n```\n这样的方式来实现跳转Safari的。所以除非web和app有通信机制，调用iOS原生代码的这个API。\n\n而且即使通过在Mac上的应用程序右键Safari浏览器，点击“查看内容”，打开Safari应用的`info.plist`，查看Safari的`URL Scheme`，也就只有有限的`http`、`https`、`ftp`等深链接。\n我在Mac上测试时，发现是可以通过以下代码：（有点忘了是不是safari开头，应该还有一个`x-safari-http`的scheme头，还是`webkit:`这个）\n```js\nwindow.location.href = `safari://43.xxx.xxx.xxx/index.html`\n```\n在Mac上是可以从谷歌Chrome浏览器跳转打开Safari的，但是在移动端是不行的。\n\n\n所以在iOS的第三方app的内置浏览器中，想打开系统Safari浏览器，最好还是要做一个引导的浮层，指向右上角的三个点，引导用户主动点击Facebook等第三方app的“打开外部浏览器”选项。\n\n\n\n","在一些面向海外的项目中，有时候经常绕不开Google和Facebook这2个巨大的平台。最常见的就是Google登录和Facebook登录，但是在一些项目推广和广告相关的需求，难免会遇到打开mobile的默认浏览器，以提高web项目的用户体验或者避免Google和Facebook的内置浏览器的限制。","https://image.xinwei.ltd/11721472897038.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],3077,"web facebook,facebook web,facebook integrate,facebook开发,facebook集成",{"id":178,"createdAt":179,"title":180,"content":181,"summary":182,"image":183,"uid":136,"user":184,"categoryId":85,"category":185,"subCategoryId":186,"subCategory":187,"comments":189,"status":9,"reason":15,"notice":15,"visitCount":190,"commentCount":9,"keywords":191},87,"2024-02-23T08:00:20.265Z","ruby - Your Ruby version is 2.6.3, but your Gemfile specified >= 2.6.10，或者 Error running '__rvm_make install'","当运行iOS工程时，遇到gemfile文件，对ruby的版本是有要求的。\n\n或者你因为其他原因导致需要安装或者升级ruby版本。\n\n有时候会遇到以下报错：\n\n报错：\n1. ruby版本过低\n> Your Ruby version is 2.6.3, but your Gemfile specified >= 2.6.10\n\n![ruby version fail](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708673908208.png)\n\n2. ruby安装失败\n> Error running '__rvm_make install',\nplease read /Users/hanweixing/.rvm/log/1708659865_ruby-2.7.5/install.log\nThere has been an error while running make install. Halting the installation.\n\n![ruby install fail](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708673957020.png)\n\n当遇到这样的错误时，你可以按照以下步骤进行解决：\n1. 查看本地ruby版本情况：\n```\nruby -v\n```\n\n2. 清理本地rvm下载安装的缓存\n```\nrvm cleanup all\n```\n\n3. 获取rvm的稳定版本\n```\nrvm get stable --verbose\n```\n\n4. 如果你需要重新安装rvm\n```\ncurl -sSL https://get.rvm.io | bash -s stable --ruby\n```\n\n5. 尝试安装ruby\n```\nrvm install ruby --verbose\n```\n\n6. 如果安装失败，可以使用以下命令获取可用的ruby版本\n```\nrvm list known\n```\n\n7. 指定ruby版本安装\n```\nrvm install ruby-3.2.2 --verbose\n```\n或者\n```\nrvm install 3.2.2 -C --with-openssl-dir=/opt/homebrew/opt/openssl@3.1 --verbose\n```\n这2个命令基本可以让安装ruby成功\n![ruby install success](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708674829430.png)\n\n8. 通过`ruby -v`命令查看ruby对应版本是否是刚安装的那个版本。\n\n9. 查看本地安装的ruby版本\n```\nrvm list\n```\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708675069734.png)\n\n10. 如果不是，可以使用rvm的use命令来切换\n![ruby切换](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708674940438.png)\n\n```\nrvm use 3.2.2\n```","这篇文章针对ruby的安装或者报错给出对应的解决方案，当运行iOS工程时，遇到gemfile文件，对ruby的版本是有要求的。","https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-23%20151708673908208.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},18,{"id":186,"name":188,"parentId":85},"Linux",[],2772,"ruby,ruby install,ruby error,ruby开发,ruby issue,ruby编码",{"id":193,"createdAt":194,"title":195,"content":196,"summary":197,"image":198,"uid":136,"user":199,"categoryId":85,"category":200,"subCategoryId":201,"subCategory":202,"comments":204,"status":9,"reason":15,"notice":15,"visitCount":205,"commentCount":112,"keywords":206},111,"2024-05-17T01:32:02.657Z","npm报错：node: --openssl-legacy-provider is not allowed in NODE_OPTIONS","## 出现的问题\n问题1:\n> ERROR  Failed to compile with 1 error                                        \n error  in ./src/modules/stu/views/member/MemberModelTest.vue?vue&type=style&index=0&id=7b0ab969&scoped=true&lang=scss\nSyntax Error: Error: Node Sass version 9.0.0 is incompatible with ^4.0.0.\n @ ./node_modules/vue-style-loader??ref--8-oneOf-1-0!./node_modules/css-loader??ref--8-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--8-oneOf-1-2!./node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/modules/stu/views/member/MemberModelTest.vue?vue&type=style&index=0&id=7b0ab969&scoped=true&lang=scss 4:14-520 15:3-20:5 16:22-528\n @ ./src/modules/stu/views/member/MemberModelTest.vue?vue&type=style&index=0&id=7b0ab969&scoped=true&lang=scss\n @ ./src/modules/stu/views/member/MemberModelTest.vue\n @ ./src/modules/stu/router/practise.js\n @ ./src/modules/stu/router sync nonrecursive .js$\n @ ./src/modules/stu/router/index.js\n @ ./src/modules/stu/main.js\n @ multi (webpack)-dev-server/client?http://xxx.xxx.xxx.13:8080/sockjs-node (webpack)/hot/dev-server.js ./src/modules/stu/main.js\n\n问题2\n> node: --openssl-legacy-provider is not allowed in NODE_OPTIONS\n\n问题3\n> Error: spawn vue-cli-service ENOENT\n\n## 问题分析\n### 问题1\n这个问题往往出现在当你的电脑中安装了vue3的环境，而vue2的环境比如vue-cli-service等被移除，并且你运行vue2工程的时候。为什么这么说呢，你可以看报错信息中，node-sass这个sass库，通常是用于vue2的，并且这个node-sass已经被废弃，官方建议是使用dart-sass即（sass)来替代node-sass。\n详细线索可以查看：[https://www.npmjs.com/package/node-sass](https://www.npmjs.com/package/node-sass)\n而node-sass依赖的环境如下：\n![node-sass.png](https://image.xinwei.ltd/image1715906803789.png)\n\n你可以查看你电脑中现有的node版本：\n```terminal\nnode -v\n```\n由于我的node版本是20以上，而且使用的是vue3开发所以，报错了问题1.\n\n### 问题2\n出现问题2的原因很简单，那就是我在将本地的node版本降级到node 14.18.1的时候，终端报错了，并且由于我之前开发时对react-native做优化，在bash配置中增加了`--openssl-legacy-provider`的环境配置，这个原因造成的。\n\n### 问题3\n这个问题更简单，就是我喜欢使用vue3来开发，从而删除了本地的vue2中的`vue-cli-service`，导致本地没有这个bash命令。\n\n## 解决方案\n其实第1个和第2个问题是相辅相成的，在某些特殊场景下（比如你的电脑是Mac，并且系统相匹配的openssl版本是3.x）。所以有时候，你解决了第1个问题，还需要继续解决第2个问题。\n### 问题1\n针对vue2中配置了node-sass，而本地node环境比较高（比如14以上），那么有以下几个方案：\n- 使用nvm来使用本地的node，可以自由切换node（推荐）\n- 降级电脑上本地的node版本\n- 使用虚拟机或者docker技术来配置一个另类的\n\n这里的3个方法都可以配置一个低版本的node环境，我个人推荐使用第一个，因为比较简单。\n所以这里我解释一下第一个怎么做：\n1. 安装nvm\n```terminal\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash\n```\n\n2. 查看本地的nvm\n```terminal\nnvm ls\n```\n![nvm ls命令.png](https://image.xinwei.ltd/image1715908341860.png)\n\n3. 如果你需要安装某个node版本\n```terminal\nnvm install 14.18.1 --verbose\n```\n\n4. 如果你想要切换某一个node版本\n```terminal\nnvm use 14.18.1\n```\n\n### 问题2\n这个问题在你的mac电脑系统内置的openssl命令在3.x，而node环境配置了`--openssl-legacy-provider`的情况。\n1. 查看本地openssl版本\n```terminal\nopenssl -v\n```\n![](https://image.xinwei.ltd/image1715908806020.png)\n\n2. 检查本地的bash环境配置中的`NODE_OPTIONS`，如果有就修改一下\n有的同学是直接在终端中执行以下命令，但是如果bash文件中明确配置了这个项，其实是无效的\n```terminal\nset NODE_OPTIONS=\n```\n所以还需要检查在Mac上的资源库目录下.bashrc和.zshrc（如果安装的是item2的终端）这2个文件\n![node_options的配置.png](https://image.xinwei.ltd/21715909356941.png)\n如图所示，如果有`NODE_OPTIONS`这个配置，那么就设为空值。\n```.zshrc\nexport NODE_OPTIONS=\n```\n\n### 问题3\n这个问题比较简单，直接重新安装vue-cli就好。详细说明可以参考：[vue-cli官网](https://cli.vuejs.org/zh/#%E8%B5%B7%E6%AD%A5)\n```terminal\nnpm install -g @vue/cli\n```\n\n更多信息，大家可以关注我的公众号“新卫网络科技”。\n\n\n\n\n\n\n\n\n\n","很多时候，我们在开发前端项目中，会遇到node版本和项目中依赖不匹配的问题，正如标题上所写的那样，在执行node -v相关的node命令时，可能出现如此报错，这篇文章就来探讨一下如何解决这个问题。","https://image.xinwei.ltd/image1715906803789.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},17,{"id":201,"name":203,"parentId":85},"Node",[],2247,"node,npm,node版本,node issue,npm问题,npm nvm",{"id":208,"createdAt":209,"title":210,"content":211,"summary":212,"image":213,"uid":136,"user":214,"categoryId":85,"category":215,"subCategoryId":100,"subCategory":216,"comments":217,"status":9,"reason":15,"notice":15,"visitCount":218,"commentCount":112,"keywords":219},131,"2024-10-19T06:11:42.227Z","flutter pub get时报错pod install error: Error in the HTTP2 framing layer","在flutter工程中，flutter pub get时iOS工程的pod install报错问题。\n\n> Downloading dependencies\n-> Installing Adjust (4.38.4)\n > Git download\n > Git download\n     $ /usr/bin/git clone https://github.com/adjust/ios_sdk.git /var/folders/kx/zj85bxgx4td2tw0137gxb9vr0000gn/T/d20241019-31559-79f4ta --template= --single-branch --depth 1 --branch v4.38.4\n     Cloning into '/var/folders/kx/zj85bxgx4td2tw0137gxb9vr0000gn/T/d20241019-31559-79f4ta'...\n     fatal: unable to access 'https://github.com/adjust/ios_sdk.git/': Error in the HTTP2 framing layer\n[!] Error installing Adjust\n[!] /usr/bin/git clone https://github.com/adjust/ios_sdk.git /var/folders/kx/zj85bxgx4td2tw0137gxb9vr0000gn/T/d20241019-31559-79f4ta --template= --single-branch --depth 1 --branch v4.38.4\nCloning into '/var/folders/kx/zj85bxgx4td2tw0137gxb9vr0000gn/T/d20241019-31559-79f4ta'...\nfatal: unable to access 'https://github.com/adjust/ios_sdk.git/': Error in the HTTP2 framing layer\n\n\n截图：\n![flutter pub get error](https://image.xinwei.ltd/image1729318210433.png)\n\n\n解决办法：\n将git的http version切换成1.1:\n```terminal\ngit config --global http.version HTTP/1.1\n```\n","在flutter工程中，flutter pub get时iOS工程的pod install报错问题。","https://image.xinwei.ltd/image1729318210433.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],1981,"flutter pod install error,pod install error,adjust error,flutter",{"id":221,"createdAt":222,"title":223,"content":224,"summary":225,"image":226,"uid":136,"user":227,"categoryId":85,"category":228,"subCategoryId":144,"subCategory":229,"comments":230,"status":9,"reason":15,"notice":15,"visitCount":231,"commentCount":112,"keywords":232},141,"2024-11-28T07:00:21.332Z","web中实现Google登录和firebase登录以及google token转firebase token","## 一、前言\n在web的前端开发中，实现第三方授权登录的场景和平台很多，最常见的比如Google登录、Apple登录、Facebook登录，国内常见的有比如微信登录、支付宝登录，这些都是需要依赖他们这些第三方平台的授权流程才能拿到登录用户的身份id或者access token的。\n\n在web开发中，实现第三方登录的形式，我的经验告诉我大概可以分为3种：\n- 1. 引入第三方平台的库，在vue或者react或者原生div等按钮组件中，加一些标签属性或者配置。当点击后，第三方平台的函数会去执行授权流程。\n\n这种场景比如，使用谷歌的统一登录按钮：\n```示例\n\u003Cdiv id=\"g_id_onload\"\n     data-client_id=\"YOUR_GOOGLE_CLIENT_ID\"\n     data-login_uri=\"https://your.domain/your_login_endpoint\"\n     data-your_own_param_1_to_login=\"any_value\"\n     data-your_own_param_2_to_login=\"any_value\">\n\u003C/div>\n```\n![谷歌登录按钮](https://image.xinwei.ltd/11732766626888.png)\n\n具体解释可自行参考：[https://developers.google.com/identity/gsi/web/tools/configurator?hl=zh-cn](https://developers.google.com/identity/gsi/web/tools/configurator?hl=zh-cn)\n\n- 2. 引入第三方平台的库，使用javascript方法去调用第三方库的js api实现授权流程。\n\n还是拿Google登录举例：\n```示例\n\u003Cscript src=\"https://accounts.google.com/gsi/client\" async>\u003C/script>\n```\n```示例脚本\n\u003Cscript>\n  window.onload = function () {\n    google.accounts.id.initialize({\n      client_id: 'YOUR_GOOGLE_CLIENT_ID',\n      callback: handleCredentialResponse\n    });\n    google.accounts.id.prompt((notification) => {\n        if (notification.isNotDisplayed() || notification.isSkippedMoment()) {\n            // try next provider if OneTap is not displayed or skipped\n        }\n    });\n  }\n\u003C/script>\n```\n具体可自行查看：[https://developers.google.com/identity/gsi/web/guides/use-one-tap-js-api?hl=zh-cn](https://developers.google.com/identity/gsi/web/guides/use-one-tap-js-api?hl=zh-cn)\n\n- 3. 使用重定向的方式，在web当前域名网页下，跳转第三方平台的授权页，用户登录同意授权后，第三方平台的授权页会重定向回web的域名网页，重定向回来的web网页中会携带第三方平台加上的授权结果参数。前端开发同学，需要自己从网址参数读取参数值，并且将参数值通过接口请求发送给自己的后端同学进行处理。\n\n还是拿Google登录举例，通过重定向授权流程，最后重定向到的网址是：\n```示例授权返回后的网址\nhttps://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600\n```\n如上所示，会有一些参数拼接在前端同学的授权返回网址上。\n\n以上大概是目前在web前端开发中遇到的几种授权登录场景。\n\n> 提示：显示网页的设备环境是多种多样的，对于在网页授权过程中，如果是弹出式授权窗口(iframe)的形式，经常会被一些设备或者一些app的webkit给拦截掉，从而导致授权失败。\n\n\n## 二、前端web开发中实现Google登录的方法\n根据前言中所提到的一些信息，相信大家对授权登录的集成方式有了大概的理解，在这一节，我们聊一下如何优雅的实现一个不会被webkit拦截的授权登录方式。所以我选择重定向的方式。\n\nGoogle对OAuth 2.0 的授权流程是比较支持的，当然你也可以继续使用js API的方式，这里我选择OAuth 2.0的标准。\n\n1. 需要在Google console中创建项目，得到一个client id；\n2. 需要在Google console的项目中配置重定向网址；\n3. web前端中实现重定向的代码。\n\n官方有给出文档：[https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=zh-cn](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow?hl=zh-cn)\n\n### 2.1 实现Google登录必要的配置\n需要在Google的console控制台，新建一个项目，然后配置web的一些授权网域。\nGoogle的console网址：[https://console.cloud.google.com/](https://console.cloud.google.com/)\n\n![创建Google项目](https://image.xinwei.ltd/image1732767629549.png)\n\n在Google console中创建一个项目后，需要配置apis & services\n![apis & services](https://image.xinwei.ltd/image1732767724800.png)\n\n刚创建的项目是没有oauth client id的，所以需要创建一个新的：\n![OAuth client ID](https://image.xinwei.ltd/image1732767839169.png)\n\n创建完成后会得到client Id（我截图中有firebase自动创建的，这个在下一节会提到，大家可以忽略）\n![Client ID位置](https://image.xinwei.ltd/image1732768187572.png)\n\n配置授权回调网址，需要进入到OAuth consent Screen，然后点击进入编辑 \n![进入编辑](https://image.xinwei.ltd/image1732772143251.png)\n填写授权域名，这里可以填写重定向回调的网址域名，而且只能是顶级域名，当重定向成功后，会跳转到这个顶级域名下的网页（重定向的uri必须是这个顶级域名下的）\n![authorized domains](https://image.xinwei.ltd/image1732772314649.png)\n\n到这里，配置上的事情就结束了。\n\n### 2.2 发起重定向以及解析授权参数\n```google.ts\nfunction generateNonce() {\n  return [...Array(30)]\n    .map(() => Math.floor(Math.random() * 36).toString(36))\n    .join('');\n}\n// 实例化谷歌登录\nexport const googleSignAction = (callback: (success: boolean, msg?: string, res?: any) => void) => {\n            const clientId = \"xxx-xxx.apps.googleusercontent.com\";\n            const url = location.protocol.replaceAll(\":\", \"\") + \"://\" + location.host;\n\t    const redirectUri = encodeURIComponent(url);\n\t    const scope = encodeURIComponent(\"email profile openid\");\n\t    const responseType = \"id_token\"; // code、access_token\n      const nonce = generateNonce(); // id_token 必须要传此字段，且随机\n\t\t\n\t    const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=${responseType}&state=google&nonce=${nonce}`;\n\t    window.location.href = authUrl;\n}\n```\n> 提示：这里的ClientId，就是OAuth 2.0 Client IDs中的值\n\n![client id](https://image.xinwei.ltd/image1732772707047.png)\n\n接下来，当重定向成功后，解析我们自己网页中的参数：\n```google.ts\n/**\n * 当Google授权登录成功之后，读取path，从path中获取code，然后发送post请求，获取access token以及id token。\n * - id token用来和firebase进行授权，获取firebase的token。\n * - success：返回从firebase获取到的id token\n */\nexport const HandleGoogleAuthRedirect = async (path: string, success: (token: string) => void, fail: (err: any) => void) => {\n\tlet searchParams = path\n\tlet searchList = path.split(\"/#/\")\n\tif (searchList.length > 0) {\n\t\tsearchParams = searchList[searchList.length - 1]\n\t}\n\tsearchList = searchParams.split(\"?\")\n\tif (searchList.length > 0) {\n\t\tsearchParams = searchList[searchList.length - 1]\n\t}\n\tconst params = new URLSearchParams(searchParams)\n\tconst idToken = params.get('id_token')\n  ...\n}\n```\n这里的path参考前言一节中的重定向部分，网址携带参数的示例。\n\n这里的idToken就是Google的身份验证得到的标识，然后可以调用后端接口让服务端同学去处理了。\n\n## 三、前端web开发中实现firebase的Google登录的方法\n众所周知，firebase是Google的一个大一统集成平台，firebase上可以集成Crashly、firestore、login等一系列的功能，使用firebase可以很清晰的查看各种统计数据，各个功能的数据可以同步进行对比分析。\n\n所以firebase自然提供了Google登录，当然他还支持Facebook、Apple、Twitter登录等平台。\n\nfirebase的web登录同样是会得到一个id token，但是这个id token和Google得到的id token不是同一个字符串，是只针对firebase平台的。\n\n使用firebase登录需要在firebase的console平台上创建项目并且进行配置。\nfirebase console的链接是：[https://console.firebase.google.com/](https://console.firebase.google.com/)\n\n### 3.1 创建firebase项目并且配置\n进入控制台，创建一个新项目\n![创建新项目](https://image.xinwei.ltd/image1732773565975.png)\n\n创建完项目后，给这个项目添加web平台\n![添加应用](https://image.xinwei.ltd/image1732773746221.png)\n\n选择添加web平台\n![firebase web](https://image.xinwei.ltd/image1732773709899.png)\n\n根据以下截图，按照序号顺序，选择使用google作为身份提供方\n![firebase google](https://image.xinwei.ltd/image1732773858431.png)\n\n添加完后，进行编辑，一定要选择启用Google\n![firebase google](https://image.xinwei.ltd/image1732773945666.png)\n\n添加授权网域，需要把我们的网页域名添加到这里的“已获授权的网域”\n![auth domains](https://image.xinwei.ltd/image1732774056910.png)\n\n至此，前端相关的配置到这里就结束了，另外提一句，后端需要的文件也在这个控制台里，以下是截图：\n![server side usage](https://image.xinwei.ltd/image1732774393582.png)\n\n### 3.2 代码使用firebase\n查看前端的firebase集成代码，还是在刚刚的“项目设置”页面下，在“常规”这一项下，滑动到最底部，查看web的集成代码\n![project setting](https://image.xinwei.ltd/image1732774591497.png)\n\n滚动到最底部的地方，就有集成方式介绍\n![npm firebase](https://image.xinwei.ltd/image1732774714790.png)\n\n集成后查看package.json文件：\n```package.json\n{\n  \"dependencies\": {\n    ...\n    \"firebase\": \"^10.12.3\",\n    ...\n  },\n  ...\n}\n```\n\n我先把firebase的示例代码贴到这里：\n```firebase.ts\n// Import the functions you need from the SDKs you need\nimport { initializeApp } from \"firebase/app\";\nimport { getAnalytics } from \"firebase/analytics\";\n// TODO: Add SDKs for Firebase products that you want to use\n// https://firebase.google.com/docs/web/setup#available-libraries\n\n// Your web app's Firebase configuration\n// For Firebase JS SDK v7.20.0 and later, measurementId is optional\nconst firebaseConfig = {\n  apiKey: \"AxxxxQk\",\n  authDomain: \"xxx.firebaseapp.com\",\n  projectId: \"xxxx\",\n  storageBucket: \"xxx.firebasestorage.app\",\n  messagingSenderId: \"xxxx\",\n  appId: \"1:xxxx:web:xxxx\",\n  measurementId: \"G-xxx\"\n};\n\n// Initialize Firebase\nconst app = initializeApp(firebaseConfig);\nconst analytics = getAnalytics(app);\n```\n\n我们这里由于使用到了Google的身份验证，所以其实需要引入firebase的Google提供API，所以我贴一下我自己的代码：（`GetCurrentFirebase`表示的就是上面示例中的`firebaseConfig`）\n```示例.ts\nimport { initializeApp } from \"firebase/app\";\nimport { getAuth, signInWithPopup, GoogleAuthProvider } from \"firebase/auth\";\nimport { GetCurrentFirebase } from \"../../../env/firebase\";\n\nconst firebaseConfig = GetCurrentFirebase()\n\nconst app = initializeApp(firebaseConfig);\nconst auth = getAuth(app);\nconst provider = new GoogleAuthProvider();\n\nexport { auth, provider, signInWithPopup, GoogleAuthProvider };\n```\n\n然后我们在点击按钮后，要唤起授权弹窗：\n```示例.ts\n...\nimport { auth, provider, signInWithPopup, GoogleAuthProvider  } from \"./firebase\";\n...\n\nconst onClickGoogleBtn = (callback: (success: boolean, msg?: string, res?: any) => void) => {\n  signInWithPopup(auth, provider)\n\t  .then(async (result) => {\n\t\t  console.log(\"Google授权登录获取到的值是：\", res);\n\t\t  if (result && result.user) {\n\t\t\t  const user = result.user;\n\t\t\t  user.getIdToken(true).then((id_token) => {\n                              callback(true, id_token, res)\n\t\t\t  }).catch(error => {\n                              callback(false, typeof error == 'string' ? error : (error.message || error.msg));\n\t\t\t  });\n\t\t  } else {\n\t\t\t  callback(false, TXT_PleaseTryAgain);\n\t\t  }\n\t  })\n\t  .catch((error) => {\n\t\tconsole.log(\"Google授权登录失败是：\", error);\n\t\tcallback(false, typeof error == 'string' ? error : (error.message || error.msg));\n\t  });\n}\n```\n\n这样就能获取到firebase的id token了，但是这种方式就是通过弹窗授权的方式，而弹窗授权在一些设备的浏览器或者某些app的webview中显示时，弹窗会被禁止，从而导致弹窗授权失败。\n\n所以我们必须得使用一个不使用弹窗的授权方式，保证授权登录能成功。\n\n我看了firebase的很多文档，他们虽然提供了fireabse的重定向登录方式，但是对于fireabse的验证有诸多限制，是不容易实现的。\n\nfirebase的官方集成文档的几种方式：[https://firebase.google.com/docs/auth/web/redirect-best-practices?hl=zh-cn](https://firebase.google.com/docs/auth/web/redirect-best-practices?hl=zh-cn)\n\n总结来说，firebase的重定向方式有以下几种：\n![firebase授权示例](https://image.xinwei.ltd/image1732776357150.png)\n\n其实就一句话，那就是如果只使用firebase的方式进行Google授权，而且使用重定向的方式，那么服务端要么使用firebase Hosting，要么自行托管firebase授权文件。\n\n所以仅仅靠firebase实现Google的授权重定向登录，是不够的，下一节我们会说到。\n\n## 四、结合Google授权登录、firebase身份转换达到获取firebase的id token\n从这一节的标题看，相信大家已经知道如何去优雅的实现一个firebase的Google重定向登录。而且只需要使用第二、三种的代码，稍加改进就可以实现。\n\n总的流程就是：\n1. 使用Google的重定向获取Google的id token；\n2. 使用firebase的身份credential将Google的id token转成firebase的id token。\n\n第一步，通过第二节的代码，就可以得到，所以这里不再赘述。\n\n第二步，将firebase的授权登录代码，改造一下：\n```firebase.ts\n...\nimport { signInWithCredential } from \"firebase/auth\";\nimport { auth, GoogleAuthProvider  } from \"./firebase\";\n...\n\n        const params = new URLSearchParams(searchParams)\n\tconst idToken = params.get('id_token')\n\tif (idToken != null && idToken.length > 0) {\n\t\tconst credential = GoogleAuthProvider.credential(idToken);\n\t\tconst result = await signInWithCredential(auth, credential);\n\t\tconst user = result.user;\n\t\tuser.getIdToken(true).then((token) => {\n\t\t\t// 这里获取到的就是firebase 的 id token\n\t\t\tsuccess(token);\n\t\t}).catch(error => {\n\t\t\t// 这里就是出错了\n\t\t\tfail(error)\n\t\t});\n\t}\n...\n```\n上面的这个代码，大家应该能看懂吧，`auth`和`GoogleAuthProvider`，是在第三章的代码中`export`的，这里从`firebase`库中引入了`signInWithCredential`了。\n\n\n\n全篇就写到这里，希望对大家有帮助。\n\n\n\n\n\n\n\n\n\n","今天简单记录一下如何在web前端开发中实现Google登录和firebase登录，重点在如何使用谷歌登录后，转成firebase的登录方法。","https://image.xinwei.ltd/11732766626888.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],1789,"google login,firebase google login,firebase login,firebase google,firebase,google,google login token,web firebase,web google,web谷歌登录,web firebase登录,谷歌登录,firebase登录",{"id":234,"createdAt":235,"title":236,"content":237,"summary":238,"image":239,"uid":136,"user":240,"categoryId":85,"category":241,"subCategoryId":242,"subCategory":243,"comments":245,"status":9,"reason":15,"notice":15,"visitCount":246,"commentCount":112,"keywords":247},145,"2025-01-24T04:52:55.324Z","git Authentication failed - remote: Https克隆账号或密码错误，如何查看克隆账号或密码","## 问题\n> Pushing to https://codeup.aliyun.com/66f152d5c/h5/xxx.git\nremote: Https克隆账号或密码错误，如何查看克隆账号或密码：https://help.aliyun.com/document_detail/158880.html\nfatal: Authentication failed for 'https://codeup.aliyun.com/66f152d5c/h5/xxx.git/'\n\n问题截图：\n![git error截图](https://image.xinwei.ltd/31737693420722.png)\n\n## 简要描述一下背景\n本地新开发了一个web项目，当开发完之后，需要上传到git仓库，于是我选择了阿里云的codeup平台，在codeup上新建了一个仓库目录，然后通过`git init`、`git remote add`等操作将代码推送到codeup上，但是出现了`git push`的报错，如上面的问题和截图所示。\n\n## 问题所在\n即是本地的`git config`中username和password需要和阿里的codeup上的https进行验证，但是本地的username和password在验证的时候输入不正确，所以出现如此报错。\n\n## 解决问题\n### 1. 确认username和password\n去阿里的codeup平台上的【个人设置】中，找到【HTTPS密码】设置的地方\n![codeup location](https://image.xinwei.ltd/image1737693779286.png)\n\n确保你是知道克隆账号username和密码password的，如果已经忘记了，可以点击编辑图标修改对应的username和password。\n\n修改的过程中，会需要提供邮箱收到的验证码。\n![codeup_修改密码](https://image.xinwei.ltd/image1737694118357.png)\n\n\n所以需要你确保你的邮箱已经在codeup上验证过了。\n![codeup email](https://image.xinwei.ltd/image1737693948700.png)\n\n\n### 2. 执行git push\n已经知道了https的username和password后，那么操作git时遇到验证输入你就可以解决了。\n![git push username](https://image.xinwei.ltd/21737694191169.png)\n如图所示，我在vs code的终端中对项目git push，在顶部出现了username的输入框，输入username后，紧接着弹出password输入框，输入password，那么验证就通过了。\n![git push success](https://image.xinwei.ltd/image1737694310587.png)\n\n以上就是这么多了。\n\n","简单记录一个git操作失败的问题吧。喜欢记录，喜欢分享，希望对需要的同学有所帮助。","https://image.xinwei.ltd/31737693420722.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},31,{"id":242,"name":244,"parentId":85},"git&svn",[],1681,"git,git Authentication,git Authentication failed,git https,git https error",{"id":249,"createdAt":250,"title":251,"content":252,"summary":253,"image":254,"uid":136,"user":255,"categoryId":85,"category":256,"subCategoryId":257,"subCategory":258,"comments":260,"status":9,"reason":15,"notice":15,"visitCount":261,"commentCount":9,"keywords":262},88,"2024-02-24T08:44:18.075Z","curl error: RPC failed; curl 18 HTTP/2 stream 5 was reset8.00 KiB/s","不管在什么类型的项目中，当使用git相关的pull或者clone时，有时候会遇到以下错误：\n> error: RPC failed; curl 18 HTTP/2 stream 5 was reset8.00 KiB/s\n> Early EOF\n> Expect 5032 Kib\n> \n>  或者\n>\n> error time out\n\n忘记给错误截图了，但是问题基本是那个类似是上面的报错。\n\n从上面的报错来看，其实是在curl的时候，网络disconnect或者其他类似的io出错。\n\n这个问题的出现其实就是由于网络超时导致的package没有传输完成，或者是由于缓冲区小了导致的io error。\n\n面对这样的问题，有2种思路是可以思考的。\n1. 增加git clone的时间，防止由于time out而导致的终止链接，即增大buffer。\n2. 升级curl到8.2.1以上。【**推荐先尝试这个方式**】\n\n## 1. 升级curl\n我们先来说升级curl的方式，其实在curl的github上（[https://github.com/curl/curl/issues/11353](https://github.com/curl/curl/issues/11353)）就已经有相关的issue提出来，而且作者也说了8.2.1之前的版本确实会复现这样的问题，所以很简单，直接查看本地的curl版本，如果低于8.2.1版本，那么直接去升级就好了。\n\n\n查看curl的版本：\n```\ncurl --version\n```\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-24%20161708763014958.png)\n\n升级本地的curl版本：\n```\nbrew install curl\n```\n注意：\n在Mac上通过brew安装的curl，有可能没有自动配置环境变量，甚至安装之后查看curl的版本，还是之前的版本。所以需要进一步配置一下环境变量。\n\n通过brew info查看刚才安装的curl：\n```\nbrew info curl\n```\n![](https://image.xinwei.ltd/Snipaste_2024-02-24_16-28-061708763303402.png)\n\n然后，按照截图的蓝色框框中的内容，配置一下环境变量：\n```终端命令\necho 'export PATH=\"/usr/local/opt/curl/bin:$PATH\"' >> ~/.zshrc\n```\n然后再检查curl的版本：\n![](https://image.xinwei.ltd/Snipaste_2024-02-24_16-30-081708763434718.png)\n\n确认版本正确之后，再去执行git相关的操作，验证是否可行。\n我自己的问题是在更新curl后得到解决。\n\n## 2. 增加git clone的时间\n很容易理解，增加clone时间其实就是希望哪怕网速再慢，也可以慢慢clone下来，而不会导致time out而链接中断。\n那么常用的设置命令是：\n```终端命令\ngit config --global http.postBuffer 157286400\n```\n这里是把缓冲区设置为150M大小，git默认的是1M。\n\n其实很多程序员对此存在争议，认为这个设置是没有必要的，但仍然有程序员通过这个设置解决了自己的问题。\n\n对于这个设置，由于我自己通过这个命令没有解决我遇到的问题，所以暂时不敢苟同。\n\n## 3. 另外还有一种解法，就是使用http/1.1而不是http/2来进行传输\n这个方式我没尝试过，但是有一定的道理，http/2的协议比http/1.1确实多了很多限制，那么更改成http/1.1，或许可以解决transfer中遇到的问题，仅作参考吧。\n\n这种方式的解法是：\n先设置：\n```\ngit config --global http.version HTTP/1.1\n```\n当执行完git相关的操作后，再设置：\n```\ngit config --global http.version HTTP/2\n```\n\n","这个问题是在使用git相关的库clone时较容易遇到的问题，给大家一个解决问题的思路。","https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-02-24%20161708763014958.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},39,{"id":257,"name":259,"parentId":85},"软件",[],1645,"git,git clone,git error,git issue,git使用,git克隆,git使用",{"id":264,"createdAt":265,"title":266,"content":267,"summary":268,"image":269,"uid":136,"user":270,"categoryId":85,"category":271,"subCategoryId":272,"subCategory":273,"comments":275,"status":9,"reason":15,"notice":15,"visitCount":276,"commentCount":112,"keywords":277},104,"2024-03-25T17:17:22.421Z","微信游戏小程序制作工具","\n官网：[https://gamemaker.weixin.qq.com/](https://gamemaker.weixin.qq.com/)\n## 概述\n微信提供了一个无须安装且无须写代码，只需要拖拽和参数设置的可视化小游戏在线开发工具。网站地址就是上面记录的官网地址。\n\n官网中已经提供了很多demo游戏，可以查看了解具体应该如何去制作一个游戏。\n\n这篇文章会具体从游戏中的基本概念和基础操作让大家对制作小游戏有一个基本的了解。\n\n![微信游戏小程序.jpg](https://image.xinwei.ltd/17113845921191711384625793.jpg)\n\n\n### 1.1  场景scene\n游戏中的一个页面。一个游戏至少要有一个场景，否则游戏就是一个静态的黑色界面，什么也不存在。\n在一个场景中，可以定义很多精灵，后面会解释。\n在场景中可以给精灵设置多种不同的动画、背景色、坐标、循环执行、碰撞、事件监听等。可以给物体设置坐标，当然直接拖动到某一个位置也是可以的。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/17113846040351711384658046.jpg)\n\n### 1.2 精灵sprite\n这是游戏中的通常叫法，指的是一个场景中包含的所有元素（主要是图片，比如背景色图片、子弹图片、飞机图片等等），都被叫精灵。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/17113846780451711384691666.jpg)\n\n### 1.3 动画animation\n可以给精灵设置动画。包括水平/垂直平移、面向某个精灵移动、移动的步数、旋转、循环、透明度等等。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/41711384741588.jpg)\n\n### 1.4 素材asset\n主要指的是构成一个游戏的所有图片、音频。素材可以通过导入平台上的素材库或者导入自己的图片、音频文件。导入到资源管理器之后，就可以进行使用。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/41711384773732.jpg)\n\n### 1.5 坐标系统\n打开官网的编辑器的时候，可以看到屏幕的中心点是（0，0）的位置，所以学过数学中象限概念的人就可以知道一个点在屏幕中的坐标可以如何设置正数和负数。当然用户在屏幕上的鼠标移动位置，也会显示在屏幕标尺的上方，也可以通过这个知道任何位置的具体坐标。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/51711384808664.jpg)\n\n### 1.6 造型\n对于游戏中的精灵来说，可以有好几个造型。造型可以理解成一张图片，在不同条件下，可以展示成其他状态的图片或者形态。比如一个敌机正常状态下是一个正常的飞机图片，在被子弹击中的时候，可以切换成爆炸状态的图片。\n在制作工具界面，可以通过点击外观，就可以看到切换造型的事件。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/61711384858794.jpg)\n\n### 1.7 事件和通知\n指的是给精灵添加的在某些时刻执行什么动作的钩子。\n对于程序员来说，这是很容易理解的。也可以从通俗的话来描述，比如当进入当前场景时、被点击时、发生碰撞时、收到通知时、屏幕被点击时等等很多时刻。\n添加了事件后，就可以在这个事件包裹的框框内，拖入其他的比如运动、动画等逻辑操作。\n比如可以设置添加一个事件“当场景启动时”，然后拖入一个动画“《自己》在《1》秒内滑行到X:《0》Y《0》”。尖括号内的时可以下拉选择精灵，或者输入数字的。那么预览场景的时候，就可以看到，当页面被加载出来时，精灵滑行到了你设置的(x, y)坐标位置。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/71711384899412.jpg)\n\n创建通知之后，那么肯定有地方会接收这个通知做某些事情。接收通知时间在“添加事件”的位置。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/71711384929416.jpg)\n\n这样就可以在通知的包裹块里，设置某些其他动作。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/71711384964652.jpg)\n\n### 1.8  逻辑\n非常重要，这里包含循环和条件循环的一些设置，相当于编程中的do-white和if-else以及sleep等操作。精灵的动画效果依托这些条件进行设置时，体验会更加的丰富。\n\n从拖拽的动作堆叠面板中可以看出。逻辑块一般都是在某个条件下，包含一组操作。\n\n跟平时我们画流程图一样，制作工具中拖动出来的一些操作都是使用不同形状来表示。常见的比如三角内包含的就是判断条件，长方形表示具体执行等。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/81711385029797.jpg)\n\n\n### 1.9 其他\n游戏中还可以设置局部或者全局变量。\n变量可以通过\"运算\"中的数据部分，设置变量的加减，然后给某一个数字图片精灵，通过“外观”的“修改《自己》的值为《》”，拖入“数据”中的“变量《》”，就可以实现外观上显示数字。\n\n整个操作界面中有很多元素，大家不妨自己动手试一试。自己动手才能更深入的理解每一个设置的作用。\n![小游戏变量设置.png](https://image.xinwei.ltd/image1711387026687.png)\n\n\n### 2.0 预览和发布\n![小游戏基础.png](https://image.xinwei.ltd/image1711386876600.png)\n\n\n在左上角的“作品设置”中可以进行作品设置和上传以及下载源码的操作。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/211711385075658.jpg)\n\n预览场景或者整个项目，也是在左上角的位置。\n![微信游戏小程序.jpg](https://image.xinwei.ltd/221711385101919.jpg)\n\n\n\n\n","这篇文章介绍如何开发一个微信上的游戏小程序，主要是利用了微信的在线开发制作工具，无须安装和代码编写，通过素材和逻辑堆砌，良好的设计出一个微信的游戏小程序，并且可以发布到微信进行审核。","https://image.xinwei.ltd/17113845921191711384625793.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},13,{"id":272,"name":274,"parentId":85},"小程序",[],1632,"wechat miniprogram,wechat game,wechat game play,game play,mini programe,mini game develop,game develop,小程序游戏,小程序游戏开发",{"id":279,"createdAt":280,"title":281,"content":282,"summary":283,"image":284,"uid":136,"user":285,"categoryId":85,"category":286,"subCategoryId":144,"subCategory":287,"comments":288,"status":9,"reason":15,"notice":15,"visitCount":289,"commentCount":112,"keywords":290},139,"2024-11-27T11:10:01.438Z","element plus使用范围为一周的日期选择组件(el-date-picker), 周一到周日 和 周日到周六","在引入element-plus组件库的项目中，使用date picker组件选择周范围的日期时，如何设置周范围从周日到周六。\n\n在默认情况下，尤其是中文时区的项目中，默认的周范围选择是周一到周日的7天，但是在出海业务中，经常会遇到要使用周日到周六的7天范围。\n\nelment-plus中有一个非常方便的组件`el-date-picker`\n\n解释一下，我们使用的周选择器效果图：\n![周一到周日](https://image.xinwei.ltd/11732704674655.png)\n这是默认情况下的的情况，如图所示，选择的是周一到周日的时间范围，只要点击其中某一个日期，那么范围会廊括这个日期在内的一周。\n代码是：\n```vue\n...\n\u003Cel-date-picker\n    v-model=\"dates\"\n    type=\"week\"\n    :format=\"`${searchData.st} - ${searchData.et}`\"\n    placeholder=\"Pick a week\"\n    @change=\"handleWeekChange\"\n/>\n...\n\nimport {dayjs, ElMessage} from 'element-plus';\n...\nconst dates = ref();\nconst searchData = reactive({\n    ...\n    st: '',\n    et: '',\n    ...\n})\n...\n\nfunction handleWeekChange(value){\n    if (value) {\n        getTime(value)\n    }\n}\n\nfunction getTime(value){\n    const start = dayjs(value).startOf('week').format('YYYY-MM-DD');\n    const end = dayjs(value).endOf('week').format('YYYY-MM-DD');\n    searchData.st = start;\n    searchData.et = end;\n}\n```\n\n那么如何将上面截图中的“周一到周日”的范围，改成“周日到周六”的范围呢？\n\n其实elment plus在官网中已经说明了：[https://element-plus.org/zh-CN/guide/i18n.html](https://element-plus.org/zh-CN/guide/i18n.html)\n\n![element-plus datepicker](https://image.xinwei.ltd/21732705335452.png)\n\n那么我们需要做的操作就是将dayjs的的locale进行修改，其他的组件使用完全不用动。\n\n在`main.ts`中直接`import` `dayjs`的locale文件即可：\n```main.ts\nimport 'dayjs/locale/en'\n```\n\n当然你还可以直接从element-plus中export出dayjs，然后调用方法也行：\n```main.ts\n...\nimport ElementPlus, { dayjs } from 'element-plus';\n...\n\ndayjs.locale('en');\n...\n```\n\n得到的效果如下：\n![周日到周六](https://image.xinwei.ltd/31732705632008.png)\n\n\n欢迎大家多多关注公众号\"新卫网络科技\".\n\n\n","在引入element-plus组件库的项目中，使用date picker组件选择周范围的日期时，如何设置周范围从周日到周六。","https://image.xinwei.ltd/11732704674655.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],1594,"el-date-picker,element plus date picker,week type date picker,vue date picker,vue week picker,element-plus",{"id":292,"createdAt":293,"title":294,"content":295,"summary":296,"image":297,"uid":136,"user":298,"categoryId":85,"category":299,"subCategoryId":144,"subCategory":300,"comments":301,"status":9,"reason":15,"notice":15,"visitCount":302,"commentCount":112,"keywords":303},120,"2024-07-31T03:45:41.753Z","uni-app中的输入框input: uni-easyinput，自动填充账号密码的背景色","## 背景\n在前端开发中，输入框input标签，在Safari或者Chrome浏览器上显示网页时，经常会出现keychain或者Google密码的自动填充提示。当使用这个填充提示时，浏览器会把账号和密码自动填充到输入框中，并且改变输入框的背景色，但是背景色就容易和现有的网页的背景色不协调。所以需要去解决这个问题。\n\n## 示例\n未输入状态：\n![未输入状态](https://image.xinwei.ltd/image1722396490031.png)\n\n在Safari中准备输入时，Safari自动在输入框右侧显示了账号/密码填充按钮：\n![Safari的账号密码填充](https://image.xinwei.ltd/image1722396537516.png)\n\n选择自动填充：\n![Safari账号密码填充](https://image.xinwei.ltd/image1722396597898.png)\n\n填充之后的样式，发现被Safari修改了：\n![Safari自动填充后样式被修改了](https://image.xinwei.ltd/image1722396642478.png)\n\n这种UI显示极其不协调，所以需要去修改。但是在检查html元素时，你会发现，直接在css中对input标签设置背景色也解决不了。\n![safari查看input标签](https://image.xinwei.ltd/1%20%281%291722397264339.png)\n\n\n## 解决方案\n在网上查找一些解决方案时，很多是直接在input标签设置auto-fill相关的样式，比如以下：\n```css\ninput:-webkit-autofill,\ninput:-webkit-autofill:hover, \ninput:-webkit-autofill:focus, \ninput:-webkit-autofill:active  {\n    transition: background-color 5000s;\n    -webkit-text-fill-color: #fff !important;\n}\n```\n\n但是我当前是使用的uni-app这个框架，直接设置input的标签并不会直接生效，所以采用以下的方法：\n```scss\n:deep(.uni-input-input) {\n  color: white !important;\n  -webkit-text-fill-color: white !important;\n  -webkit-box-shadow: 0 0 0px 1000px transparent inset !important;\n  background-color:transparent !important;\n  background-image: none !important;\n  transition: background-color 50000s ease-in-out 0s !important; //背景色透明 生效时长 过渡效果 启用时延迟的时间\n}\n```\n\n我在uni-app框架下使用的是`uni-easyinput`输入框:\n```uni-app, xxx.vue\n\u003Cuni-easyinput\n  class=\"input\"\n  :class=\"{ 'input-empty' : email.trim().length \u003C= 0, 'input-error' : emailError.trim().length > 0 }\"\n  placeholder=\"Email\"\n  placeholder-style=\"color:#8C8C8C;font-family:'gilroy-medium';font-size:32rpx;text-align:left\"\n  style=\"color: white;\"\n  :clearable=\"false\"\n  v-model=\"email\"\n/>\n```\n\n效果：\n![输入框input修改后](https://image.xinwei.ltd/image1722397520758.png)\n\n\n希望对大家有帮助。\n\n\n\n\n\n\n","在前端开发中，输入框input标签，在Safari或者Chrome浏览器上显示网页时，经常会出现keychain或者Google密码的自动填充提示。当使用这个填充提示时，浏览器会把账号和密码自动填充到输入框中，并且改变输入框的背景色，但是背景色就容易和现有的网页的背景色不协调。所以需要去解决这个问题。","https://image.xinwei.ltd/image1722396490031.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],1574,"input tag,input color,input tag web,web input,input颜色,输入框修改颜色",{"id":186,"createdAt":305,"title":306,"content":307,"summary":308,"image":309,"uid":136,"user":310,"categoryId":85,"category":311,"subCategoryId":312,"subCategory":313,"comments":315,"status":9,"reason":15,"notice":15,"visitCount":316,"commentCount":112,"keywords":317},"2024-01-22T15:01:27.42Z","Mac上操作，使用yml文件制作docker compose nginx","docker nginx image site: https://hub.docker.com/_/nginx  \n\n本文是在Mac OS上使用docker的桌面版来操作docker，以Mac OS模拟宿主机. \n\n### 一、在宿主机上自定义目录作为挂载目录\n比如在centos上可以使用home目录，大家也可以使用docker拉取centos镜像运行容器，在centos中操作，也可以在实际的云服务器上操作。  \n\n本文直接使用Mac OS模拟宿主机，所以在desktop新建了我自己的目录： \n```terminal\n/Users/hanweixing/Desktop/docker_projects/pro_2\n```\n![docker目录](https://image.xinwei.ltd/11705932893134.png)\n1.目录下的nginx空文件夹，将来存放挂载nginx容器中的目录/文件;\n2.docker-compose.yml文件后面会提到;\n3.所以自己准备一个空文件夹备用即可，这里新建nginx文件夹为例。\n### 二、在宿主机上部署nginx的注意点 \n1.当在宿主机上挂载nginx容器中的目录和配置文件时，宿主机需要提供一个nginx.conf文件，否则会报错： \n> pro_2-nginx0101-1  | nginx: [emerg] open() \"/etc/nginx/nginx.conf\" failed (2: No such file or directory)\n\n![](https://image.xinwei.ltd/21705932985538.png)  \n### 三、如何获取一个默认的nginx.conf文件\n当然你可以在网上搜索一个文件。 \n也可以使用临时容器获取&拷贝出来： \n```terminal \n$ docker container create --name tmpNginx nginx\n$ mkdir -p /Users/hanweixing/Desktop/docker_projects/pro_2/nginx/conf/\n$ docker cp tmpNginx:/etc/nginx/conf.d/default.conf /Users/hanweixing/Desktop/docker_projects/pro_2/nginx/nginx.conf\n$ docker rm tmpNginx\n```\n注：这里使用创建容器然后删除的做法，其实还有一个就是如下的用法，即不会保存的容器，这里不单独讲了。 \n```language\ndocker run --rm\n```\n这样就可以在我们的目录下得到一个nginx.conf文件。 \n```查看nginx.conf\nhanweixing@192  ~/Desktop/docker_projects/pro_2  cat nginx/nginx.conf\n```   \n```nginx.conf\nuser  nginx;\nworker_processes  auto;\n\nerror_log  /var/log/nginx/error.log notice;\npid        /var/run/nginx.pid;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       /etc/nginx/mime.types;\n    default_type  application/octet-stream;\n\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  /var/log/nginx/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    keepalive_timeout  60;\n\n    gzip  on;\n    \n    include /etc/nginx/conf.d/*.conf;\n\n    server{\n       listen 80;\n       charset utf-8;\n\n       location / {\n          root   /usr/share/nginx/html;\n          try_files $uri $uri/ =404;\n          index  index.html index.htm;\n       }\n\n       #error_page  404              /404.html;\n\n       # redirect server error pages to the static page /50x.html\n       #\n       error_page   500 502 503 504  /50x.html;\n       location = /50x.html {\n           root   html;\n       }\n    }\n}\n\n```\n### 四、编写docker compose yml文件\ndocker hub上的示例是：\n![](https://image.xinwei.ltd/31705934403764.png)\n仿照写一份完整的nginx配置：\n```terminal\n hanweixing@192  ~/Desktop/docker_projects/pro_2  vi docker-compose.yml\n```\n```docker-compose.yml\nversion: '1.0'\n\nservices:\n  nginx:\n    restart: always\n    container_name: blog_nginx\n    image: nginx\n    ports:\n      - 80:80 # expose outside\n      - 443:443 # ssl\n    volumes:\n      - ./nginx/nginx.conf/:/etc/nginx/nginx.conf\n      - ./nginx/conf.d:/etc/nginx/conf.d\n      - ./nginx/html:/usr/share/nginx/html\n      - ./nginx/www:/var/www\n      - ./nginx/logs:/var/log/nginx\n      - ./nginx/etc/cert:/etc/nginx/cert\n    environment:\n      - NGINX_PORT=80\n      - TZ=Asia/Shanghai # 时区\n    privileged: true\n\n```\n### 五、运行docker compose.\n```terminal\n hanweixing@192  ~/Desktop/docker_projects/pro_2  docker compose up -d\n```\n![](https://image.xinwei.ltd/41705934536583.png)\n挂载目录下自动生成对应容器的目录：\n![](https://image.xinwei.ltd/51705934561761.png)\n\n### 六、查看容器状态\n```terminal\ndocker container ls // 这是查看running状态的容器\n```\n建议使用\n```terminal\ndocker ps -a // check all\n```\n桌面版直接看docker\n![](https://image.xinwei.ltd/61705935467755.png)\n\n### 七、访问localhost:80\n![](https://image.xinwei.ltd/71705935497791.png)\n因为我们的nginx中的conf配置中挂载目录下没有提供html或php网页:\n![](https://image.xinwei.ltd/81705935533640.png)\n\n### 八、提供一个html文件测试\n随便编写一个html文件：index.html\n```index.html\n\u003Chtml>\n\u003Cbody>\n\u003Ch1>\n测试标题\n\u003C/h1>\n\u003Cdiv>\n测试\n\u003Cdiv>\n\u003C/body>\n\u003C/html>\n```\n![](https://image.xinwei.ltd/91705935578009.png)\n\n### 九、测试网页\n访问成功\n![](https://image.xinwei.ltd/101705935673279.png)\n\n\n\n\n\n\n\n\n\n","docker nginx image site: https://hub.docker.com/_/nginx  本文是在Mac OS上使用docker的桌面版来操作docker，以Mac OS模拟宿主机.  一、在宿主机上自定义目录作为挂载目录 比如在centos上可以使用home目录，大家也可以使用docker拉取centos镜像运行容器，在centos中操作，也可以在实际的云服务器上操作。 ","https://image.xinwei.ltd/11705932893134.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},36,{"id":312,"name":314,"parentId":85},"Docker",[],1401,"docker,docker yml,mac上yml,docker compose,docker compose nginx,nginx",{"id":319,"createdAt":320,"title":321,"content":322,"summary":323,"image":15,"uid":136,"user":324,"categoryId":85,"category":325,"subCategoryId":7,"subCategory":326,"comments":328,"status":9,"reason":15,"notice":15,"visitCount":329,"commentCount":112,"keywords":330},126,"2024-10-05T16:48:19.276Z","com.google.android.recaptcha的sdk附注警告","## 一、概要\n当在Android应用中引入了recaptcha的sdk时，如果版本号过低，在提审到Android Google play的时候，会出现sdk的警告。警告中提出要更新sdk，为了应对Google play的审核，这个问题还是需要解决的，防止引起不必要的审核失败问题。\n\n## 二、问题截图\n![recaptcha警告](https://image.xinwei.ltd/img_v3_02f1_9fe17a9c-2f3e-4fd4-b957-e5cd52509e3g1728145995035.JPG)\n\n> A critical security vulnerability was discovered in reCAPTCHA Enterprise for Mobile. The vulnerability has been patched in the latest SDK release. Customers will need to update their Android application with the reCAPTCHA Enterprise for Mobile SDK, version 18.4.0 or above. We strongly recommend you update to the latest version as soon as possible.\n\n## 三、分析\n从截图中分析，这个问题是由recaptcha库的版本出现严重的安全漏洞所致。只要将版本号升至18.4.0及以上即可解决。\n\n那么在有明确引入dependency的gradle中修改版本号，更新一下即可。\n\n```\n// Add the following build rule to the dependencies section of your app-level build.gradle file:\n\nimplementation 'com.google.android.recaptcha:recaptcha:18.4.0'\n\n// Make sure to use reCAPTCHA SDK version 18.4.0 or later.\n```\n\n而我的问题，确实并没有直接引入`recaptcha`，但是我的项目中引入了`firebase-bom`，而`firebase-bom`中就包含了`recaptcha`，所以这种情况，就需要升级`firebase-bom`的版本了。\n\n## 四、解决\n我的项目中`firebase-bom`的版本是\n```gradle\nimplementation platform('com.google.firebase:firebase-bom:32.3.1')\n```\n改为：\n```gradle\nimplementation platform('com.google.firebase:firebase-bom:32.8.0')\n```\n\n以上解决了我的问题，经过上传到google playd的内测轨道，没有`recaptcha`的警告了。\n","当在Android应用中引入了recaptcha的sdk时，如果版本号过低，在提审到Android Google play的时候，会出现sdk的警告。警告中提出要更新sdk，为了应对Google play的审核，这个问题还是需要解决的，防止引起不必要的审核失败问题。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":7,"name":327,"parentId":85},"Android",[],1278,"recaptcha,google play,android",{"id":332,"createdAt":333,"title":334,"content":335,"summary":336,"image":15,"uid":136,"user":337,"categoryId":85,"category":338,"subCategoryId":47,"subCategory":339,"comments":341,"status":9,"reason":15,"notice":15,"visitCount":342,"commentCount":112,"keywords":343},89,"2024-03-01T09:46:34.56Z","CDN: trunk URL couldn't be downloaded: https://cdn.jsdelivr.net/cocoa/Specs/0/9/9/libevent/2.1.12/libevent.podspec.json","不管是react native还是单独的iOS中，当使用到iOS的cocoapods管理库时，有时候会遇到\"CDN: trunk URL couldn't be downloaded\"的错误，这大概是由于jsdelivr的安全策略导致的网络download fail\n\n错误类似如下：\n![CDN: trunk URL couldn't be downloaded](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-01%20171709286240361.png)\n\n这个时候的解决办法是，修改pod的source，指定从cocoapods的官方仓库进行install。\n\n具体如下：\n```Podfile\nsource 'https://github.com/CocoaPods/Specs.git'\n```\n\n截图如下：\n![source 'https://github.com/CocoaPods/Specs.git'](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-01%20171709286356627.png)\n\n","不管是react native还是单独的iOS中，当使用到iOS的cocoapods管理库时，有时候会遇到\"CDN: trunk URL couldn't be downloaded\"的错误，这大概是由于jsdelivr的安全策略导致的网络download fail。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},"iOS",[],1271,"react native,react native ios,react native库,react native开发",{"id":345,"createdAt":346,"title":347,"content":348,"summary":349,"image":350,"uid":136,"user":351,"categoryId":85,"category":352,"subCategoryId":144,"subCategory":353,"comments":354,"status":9,"reason":15,"notice":15,"visitCount":355,"commentCount":112,"keywords":356},140,"2024-11-27T11:41:32.455Z","前端开发-如何生成并导出xlsx或者csv文件","在前端开发中，尤其是一些管理后台的开发项目中，经常有需要生成并导出表格的场景。最常见的表格格式莫过于xlsx和csv这两种格式的excel文件。\n\n这种场景有两种实践：\n1. 服务端生成了excel表格，将文件uri网络地址通过接口传给前端，前端使用`window.location.href`或者`window.open`等方法使用跳转下载这个uri对应的表格文件。\n2. 前端根据已有的数据，自行生成excel表格，实现自动下载文件。\n\n第1个实践对于前端来说是比较方便的，今天我们介绍第2个实践。介绍的包含2种格式（xlsx、csv）的文件生成和下载，推荐`xlsx`格式的excel。\n\n## 一、xlsx格式excel文件生成和下载\n这种方式通过使用前端库`xlsx`来实现：\n```package.json\n...\n\"dependencies\": {\n  ...\n  \"xlsx\": \"^0.18.5\",\n  ...\n}\n...\n```\n封装代码：\n```xlsxFileHandle.ts\nimport * as XLSX from 'xlsx';\n\nexport interface excelType {\n  json: object;\n  name: string;\n  titleArr: string[];\n  sheetName: string;\n}\n\n/**\n * 导出成xlsx文件\n * @param params 传入的数据格式\n */\nexport const exportExcel = (params: excelType) => {\n    const keyArray = [];\n    let data = [];\n    const getLength = function (obj: object) {\n        let count = 0;\n        for (const i in obj) {\n          if (Object.prototype.hasOwnProperty.call(obj, i)) {\n            count++;\n          }\n        }\n        return count;\n    };\n\t\n    for (const key1 in params.json) {\n      if (Object.prototype.hasOwnProperty.call(params.json, key1)) {\n        const element = (params.json as { [key: string]: object })[key1];\n        const rowDataArray = [];\n        for (const key2 in element) {\n          if (Object.prototype.hasOwnProperty.call(element, key2)) {\n            const element2 = (element as { [key: string]: object })[key2];\n            rowDataArray.push(element2);\n            if (keyArray.length \u003C getLength(element)) {\n              keyArray.push(key2);\n            }\n          }\n        }\n        data.push(rowDataArray);\n      }\n    }\n    data.splice(0, 0, params.titleArr as any);\n    data = data.filter(e => e.length > 0)\n\n    console.log(data);\n    let ws = XLSX.utils.aoa_to_sheet(data);\n    let wb = XLSX.utils.book_new();\n    // 隐藏英文字段表头\n    const wsrows = [{ hidden: false }];\n      /* 设置worksheet每列的最大宽度 */\n      const colWidth = data.filter(e => e.length > 0).map((row) =>\n        row.map((val) => {\n          /* 先判断是否为null/undefined */\n          if (val == null) {\n            return {\n              wch: 20,\n            };\n          } else if (val.toString().charCodeAt(0) > 255) {\n            /* 再判断是否为中文 */\n            return {\n              wch: val.toString().length * 2,\n            };\n          } else {\n            return {\n              wch: val.toString().length*2,\n            };\n          }\n        })\n      );\n      /* 以第一行为初始值 */\n      const result = colWidth[1];\n      for (let i = 1; i \u003C colWidth.length; i++) {\n        for (let j = 0; j \u003C colWidth[i].length; j++) {\n          if (result[j].wch \u003C colWidth[i][j].wch) {\n            result[j].wch = colWidth[i][j].wch;\n          }\n        }\n      }\n      ws['!cols'] = result;\n      ws['!rows'] = wsrows; // ws - worksheet\n      XLSX.utils.book_append_sheet(wb, ws, params.sheetName);\n      /* generate file and send to client */\n      XLSX.writeFile(wb, `${params.name}.xlsx`);\n};\n\n```\n使用实例代码：\n```payExport.ts\nimport { exportExcel } from \"../xlsxFileHandle\"\n\n/**\n * 导出成xlsx文件\n */\nexport const ExportForXlsx = (batch_id: number, valueList: Array\u003CxxxListItem>) => {\n    const titleList = [\n        \"ID\",\n        \"订单ID\",\n        \"姓\",\n        \"名\",\n        \"收款人名字\",\n        \"电话\",\n        \"账号号码\",\n        \"银行名字\",\n        \"账号名字\",\n        \"收款金额\",\n        \"选填，附言\",\n        \"扣款金额\",\n        \"汇率\",\n        \"手续费$\",\n        \"创建时间\",\n        \"完成时间\",\n        \"订单状态\",\n    ]\n    const getStatusDesc = (status: number) => {\n        if (status == 1) {\n            return '待付款'\n        }\n        if (status == 2) {\n            return '失败'\n        }\n        if (status == 3) {\n            return '成功'\n        }\n        if (status == 4) {\n            return '处理中'\n        }\n        if (status == 5) {\n            return '失败-系统'\n        }\n        return '未知'\n    }\n    const newValueList = valueList.map(e => {\n        return {\n            \"ID\" : e.payee_id + '',\n            \"订单ID\": e.order_id + '',\n            \"姓\": e.last_name + '',\n            \"名\": e.first_name + '',\n            \"收款人名字\": e.payee_name + '',\n            \"电话\": e.phone_number + '',\n            \"账号号码\": e.account_id + '',\n            \"银行名字\": e.back_name + '',\n            \"账号名字\": e.bank_account_number + '',\n            \"收款金额\": e.receive_amount + '',\n            \"选填，附言\": e.remark + '',\n            \"扣款金额\": e.buckle_amount + '',\n            \"汇率\": e.exchange_rate + '',\n            \"手续费$\": e.handling_fee + '',\n            \"创建时间\": e.create_time + '',\n            \"完成时间\": (e.complete_time > 0 ? moment(e.complete_time).format('YYYY-MM-DD HH:mm:ss') : e.complete_time) + '',\n            \"订单状态\": getStatusDesc(e.status) + ''\n        }\n    })\n    const tableName = '支付-' + batch_id //表名\n    exportExcel({\n        json: newValueList,//数据\n        name: tableName, // 表名\n        titleArr: titleList,//表头\n        sheetName: batch_id + '',//页签\n    })\n}\n```\n以上方法得到的效果：\n![导出excel](https://image.xinwei.ltd/41732707449352.png)\n\n## 二、csv格式excel文件生成和下载\n不使用任何库，而是将数据合成字符串。\n封装代码：\n```csvFile.ts\n/**\n * 导出成.csv格式的表格\n * @param titleList 比如：['ID', '姓名', '年龄']\n * @param valueList 比如：[ [1, '张三', 28], [2, '李四', 22] ]\n */\nexport const extractCsvFileFromData = (titleList: Array\u003Cstring>, valueList: Array\u003Cany>) => {\n    const totalList = [titleList, ...valueList]\n    // 将数据转换为CSV格式的字符串  \n    let csvContent = \"data:text/csv;charset=utf-8,\";  \n    totalList.forEach((row, index) => {  \n      if (index > 0) {  \n        csvContent += \"\\n\";  \n      }  \n      csvContent += row.join(\",\");  \n    });  \n\n    // 创建一个可下载的链接  \n    const encodedUri = encodeURI(csvContent);  \n    const link = document.createElement(\"a\");  \n    link.setAttribute(\"href\", encodedUri);  \n    link.setAttribute(\"download\", \"数据.csv\");  \n    document.body.appendChild(link); // 需要添加到body  \n    link.click();\n}\n```\n使用示例代码：\n```pay.ts\nimport moment from \"moment\"\n\n/**\n * 将明细列表，生成csv文件\n * @param valueList 明细列表数据\n */\nexport const GenerateCSVFileFrom = (valueList: Array\u003CxxxListItem>) => {\n    const titleList = [\n        \"ID\",\n        \"订单ID\",\n        \"姓\",\n        \"名\",\n        \"收款人名字\",\n        \"电话\",\n        \"账号号码\",\n        \"银行名字\",\n        \"账号名字\",\n        \"收款金额\",\n        \"选填，附言\",\n        \"扣款金额\",\n        \"汇率\",\n        \"手续费$\",\n        \"创建时间\",\n        \"完成时间\",\n        \"订单状态\",\n    ]\n\n    const getStatusDesc = (status: number) => {\n        if (status == 1) {\n            return '待付款'\n        }\n        if (status == 2) {\n            return '失败'\n        }\n        if (status == 3) {\n            return '成功'\n        }\n        if (status == 4) {\n            return '处理中'\n        }\n        if (status == 5) {\n            return '失败-系统'\n        }\n        return '未知'\n    }\n    \n    const newValueList = valueList.map(e => {\n        return [\n            e.payee_id + '', e.order_id + '', e.last_name + '', e.first_name + '',\n            e.payee_name + '', e.phone_number + '', e.account_id + '', e.back_name + '',\n            e.bank_account_number + '', e.receive_amount + '', e.remark + '', e.buckle_amount + '',\n            e.exchange_rate + '', e.handling_fee + '', e.create_time + '', (e.complete_time > 0 ? moment(e.complete_time).utcOffset(-5).format('YYYY-MM-DD HH:mm:ss') : e.complete_time) + '',\n            getStatusDesc(e.status) + ''\n        ]\n    })\n   \n    extractCsvFileFromData(titleList, newValueList)\n}\n```\n\n以上就是在前端开发中，生成和导出xlsx和csv格式的excel文件的简单实践，希望对大家有帮助。\n","今天介绍一下如何在前端开发中，实现生成xlsx或者csv文件的方法和步骤，算是记录一下，供大家参考。","https://image.xinwei.ltd/41732707449352.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],1220,"xlsx,vue xlsx,csv xlsx,vue csv,vue生成csv,vue生成xlsx,导出xlsx,下载excel,下载xlsx,下载csv",{"id":358,"createdAt":359,"title":360,"content":361,"summary":362,"image":363,"uid":136,"user":364,"categoryId":85,"category":365,"subCategoryId":100,"subCategory":366,"comments":367,"status":9,"reason":15,"notice":15,"visitCount":368,"commentCount":9,"keywords":369},133,"2024-11-04T02:17:31.538Z","flutter_slidable的报错问题记录","## 问题\n工程引入了flutter_slidable库，但是随着工程升级，flutter_slider的新版本遇到编译问题，如下截图：\n![flutter报错截图](https://image.xinwei.ltd/image1730685616014.png)\n\n> Error (Xcode): Invaid depfile: /Users/edy/Desktop/xxxx/.dart_tool/flutter_build/1c028f2d1b282d28c0bd6c81c174182a/kernel_snapshot_program.d\n> \n> Could not build the application for the simulator.\n> Error launching application on iPhone XR.\n\n从截图中看不出具体，所以需要使用Xcode跑一下iOS工程，以便查处问题所在：\n![flutter ios工程编译](https://image.xinwei.ltd/image1730685745563.png)\n\n从xcode中可以看出是flutter_slider这个库出现了问题。\n\n## 解决方式\n首先通过代码的git变动记录，可以看出flutter_slidable这个库的版本在编译报错前后是出现了版本号变化的，如果你从`pubspec.yaml`中看不出来，可以查看`pubspec.lock`中的变化。\n\n通过在GitHub上：[https://github.com/letsar/flutter_slidable](https://github.com/letsar/flutter_slidable)，在issue中可以看到也有人反馈了相同问题：\n[https://github.com/letsar/flutter_slidable/issues/488](https://github.com/letsar/flutter_slidable/issues/488)\n\n问题出在，flutter_slidable的3.0.1到最新版本3.1.1，有变量未被定义，无法识别，引入tag的版本无法解决。但是master分支上已经有修复了，所以需要修改flutter_slidable的引入指向问题。\n所以做以下改动\n\n```pubspec.yaml\n  provider: ^6.1.2\n#  flutter_slidable: ^3.0.1\n  flutter_slidable:\n      git:\n        url: https://github.com/letsar/flutter_slidable.git\n        ref: master\n  pull_to_refresh: ^2.0.0\n```\n然后执行`flutter pub get`更新依赖。\n\n问题解决。\n\n## 其他\n如果存在缓存问题，可以clean一下工程\n```terminal\nflutter clean\n```\n","记录一个flutter工程中库报错的问题，原因是flutter_slider的master分支上有undefined的变量。","https://image.xinwei.ltd/image1730685616014.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],1153,"flutter_slidable,flutter,slider,flutter ios,xcode,ios",{"id":371,"createdAt":372,"title":373,"content":374,"summary":375,"image":376,"uid":136,"user":377,"categoryId":85,"category":378,"subCategoryId":47,"subCategory":379,"comments":380,"status":9,"reason":15,"notice":15,"visitCount":381,"commentCount":112,"keywords":382},99,"2024-03-21T17:20:17.357Z","Swift知识一览（一）","## 一、概览\nSwift是苹果主推的Apple系列产品的开发编程语言，相较于早期的Objective-C，Swift是一门类型安全的且具有可扩展性的编程语言。\n\nSwift是开源的，apple在github上的开源Swift地址：[https://github.com/apple/swift](https://github.com/apple/swift)。\n\nSwift主要是使用C++编写。\n\nswift的官网：[https://www.swift.org/](https://www.swift.org/)\n\n如官网所说，Swift是一个对入门者友好，也对资深专家强有力支持的具有多用途的编程语言。它快捷、现代、安全且便于编写。\n\n使用领域：\n1. Apple系列平台：比如iOS、macOS、ipadOS等其他平台；\n2. 跨平台命令行：因其语法简洁、安全且轻量；\n3. 服务端和网络：因其内存占用小，启动快、高性能。\n\n目前由于Swift的ABI（Application Binary Interface，应用程序二进制接口）稳定，所以近期并没有新版本频繁更新。\n\n目前最新的稳定版本是5.10。\n\n解释：\nABI是应用程序与操作系统之间的底层接口，相关内容是目标文件格式、数据类型的大小/布局/对齐、函数调用约定等。\nAPI是应用程序编程接口。\n\n## 二、安装\n### 2.1 MacOS系统\n直接安装新版的Xcode，Xcode内置了新版的Swift。\n\n### 2.2 Linux系统\n使用Docker容器就可以获取到官方的Swift镜像。\nDocker容器的基本知识可以在官网学习，也可以看我的博客：https://www.xinwei.ltd/article/83 查看基本命令。\n\n### 2.3 Windows系统\n使用包管理器，比如WinGet。\n\n当在电脑中安装了Swift环境后，就可以在终端通过以下类似指令执行swift的操作。\n![swift -help.jpg](https://image.xinwei.ltd/17110381485881711038168386.jpg)\n\n\n## 三、如何针对不同领域方向学习\n### 3.1 命令行\n可以参考官方教程：[https://www.swift.org/getting-started/cli-swiftpm/](https://www.swift.org/getting-started/cli-swiftpm/)\n### 3.2 跨平台库\n可以参考官方教程：[https://www.swift.org/getting-started/library-swiftpm/](https://www.swift.org/getting-started/library-swiftpm/)\n### 3.3 Web服务\n可以参考官方教程：[https://www.swift.org/getting-started/vapor-web-server/](https://www.swift.org/getting-started/vapor-web-server/)\n### 3.4 iOS和macOS\n可以参考官方教程：[https://www.swift.org/getting-started/swiftui/](https://www.swift.org/getting-started/swiftui/)\n\n## 四、Swift的编译过程\n可以通过在Xcode的Swift代码断点，查看汇编代码，Swift的编译前端是swiftc。以下图片可以揭示编译流程和编译前后端的概况。（以下图片来自于网络）\n![swift编译流程1](https://image.xinwei.ltd/v2-c2ac0057a8b6e26b5dbd8d573253c317_r1711038218911.png)\n![swift编译流程2](https://image.xinwei.ltd/202007252211019341711038244403.png)\n\n\n从图中可以看出，代码编译的过程主要分为以下几步：\n1.生成语法树：`swiftc -dump-ast main.swift`\n2.生成简洁的SIL代码：`swiftc -emit-sil main.swift`\n3.生成LLVM IR代码：`swiftc -emit-ir main.swift -o main.ll`\n4.生成汇编代码：`swiftc -emit-assembly main.swift -9 main.s`\n\n## 五、基本语法和约定\n### 5.1 类型声明\n- let 声明不可变变量\n- var 声明可变变量\n#### 5.1.1 常量\n只能赋值一次，不要求在编译时期确定，但是使用之前必须赋值一次。\n```swift\nlet num = 1\n\nlet num1: Int\nnum1 = 20\n\n// 以下编译会报错\nlet a: Int\nprint(a)\n```\n\n#### 5.1.2 标识符\n可以使用任意字符作为常量名、变量名、函数名，但是不能以数字开头，也不能包含空白符、制表符、箭头等特殊字符。\n```swift\nlet 🤭 = \"emoj1\"\n```\n\n\n### 5.2 数据类型\n在Swift中有2种常见的数据类型\n- 值类型\n- 引用类型\n#### 5.2.1 值类型\n包括以下：\n- 枚举enum、可选类型（本身也是枚举）\n- 结构体struct，包括常见的String、Array、Dictionary、Int、Float、Double、Bool、Character、Set\n\n整型说明：\nInt在32位平台上等价于Int32，在64位平台上等价于Int64。\n\n浮点数说明：\nFloat有32位，精度只有6位\nDouble有64位，精度至少有16位\n\n#### 5.2.2 引用类型\nclass类型\n\n#### 5.2.3  字面量\n其实就是直接确定的值\n比如：\n```swift\nlet num = 1\nlet str = \"hello world\"\nlet decimal = 0x01 // 16进制\nlet hex = 0o02 // 8进制\nlet binary = 0b03 // 2进制\nlet doubleHex = 0xFp2 // 16进制，表示15 * 2(^2)，十进制的60\nlet doubleHex = 0xFp-2 // 16进制，表示15 * 2(^-2)，十进制的3.75\n\n// 数组\nlet arr = [1, 2, 3]\n// 字典\nlet dic = [\"name\" : \"小明\", \"age\" : 10]\n```\n\n\n#### 5.2.4 类型转换\n```swift\nlet num: Int = 1\nlet num1: UInt8 = 2\nlet sum = num + Int(num1)\n\n// 下面也是可以的，数字字面量本身没有明确类型\nlet sum = 1 + 3.14\n```\n\n\n#### 5.2.5 元组（Tuple)\n```swift\nlet response = (200, \"success\")\nprint(\"code is \\(response.0)，message is \\(response.1))\")\n\n// 类似于前端的析构\nlet (code, msg) = response\n\nlet response1 = (code: 200, msg: \"success\")\nprint(\"code is \\(response1.code)，message is \\(response1.msg))\")\n```\n\n### 5.3 流程控制相关\n#### 5.3.1 if-else语句\nif中的语句，只能是bool类型，而不能是像OC中1之类的数字\n```swift\nlet num = 10\nif num > 10 {\n    // ...\n} else if num \u003C 10 {\n    // ...\n} else {\n    \n}\n```\n\n#### 5.3.2 while语句\n```swift\nvar num = 1\nwhite num \u003C 5 {\n    num += 1\n}\n\nvar age = 10\nrepeat { // 相当于do-while\n    age += 1\n} while age \u003C 100\n```\n\n\n#### 5.3.3 for循环\nswift中的for循环，可以有for-in的写法，并且配合区间运算符，轻巧简洁\n```swift\nlet nums = [1, 2, 3, 4, 5]\nfor num in 0...3 {\n    print(\"num is \\(nums[num])\")\n}\n\n// 需要注意以下的循环\nfor var i in 0...3 {\n    i += 1\n    print(i)\n} // 打印的是：1，2，3，4\n\n// 可以省略迭代变量\nfor _ in 0...3 {\n    // do something 4 times\n    print(\"*\")\n}\n```\n\n#### 5.3.4 区间运算符\n- 闭区间：\n0...3：相当于 >= 0，并且 \u003C= 3之间的区间\n区间的类型是：`ClosedRange\u003CT>`\n- 半开区间：\n0..\u003C3：相当于 >= 0，并且 \u003C 3之间的区间\n区间的类型是：`Range\u003CT>`\n- 单侧区间\n```swift\nlet minRange = ...10 // \u003C=10以下的区间范围\nlet maxRange = 10... // >=10以上的区间范围\n\nprint(minRange.contains(5)) // true\nprint(minRange.contains(11)) // false\n\nprint(maxRange.contains(-1)) // false\n```\n\n区间的类型是：`PartialRangeThrough\u003CT>`\n\n这里解释以下类型中的T，后面会解释Swift中的这种泛型。\n\n区间运算符也可以使用字符串，通过ASCII来判断值\n```swift\nlet range = \"a\"...\"f\" // ClosedRange\u003CString>\nrange.contains(\"c\") // true\nrange.contains(\"z\") // false\n```\n\n这里扩充一下，有一个函数，表示有间隔的区间（类似于步进器）：\n```swift\nlet start = 0\nlet interval = 2\nlet max = 100\nfor num in stride(from: start, through: max, by: interval) {\n    print(\"100以内的正偶数:\\(num)\")\n}// 0, 2, 4, 6, ..., 100\n```\n\n\n#### 5.3.5 switch语句 & where\n```swift\nlet num = 5\nswitch num {\n    case 1: // 不允许写{}\n        print(\"是1\")\n        break // 可以不写，也不会贯穿case\n    case 2:\n        print(\"是2\")\n        fallthrough // fallthrough可以实现条件贯穿\n    default: // 不允许写{}\n        print(\"非1非2\")\n}\n```\n\n1. 针对switch中的表达式，需要case枚举尽，或者使用default。\n   default在case穷举完之后，可以不使用default。\n2. case和default后面至少需要一条语句，或者break一下。\n3. case支持复合条件，或者case叠加\n```swift\nlet char = \"a\"\nswitch char {\n    case \"a\", \"A\":\n        print(\"都是a\")\n    default:\n        print(\"非a\")\n}\n\nswitch char {\n    case \"a\":\n    case \"A\":\n        print(\"都是a\")\n    default:\n        print(\"非a\")        \n}\n```\n\n4. 可以使用区间或者元组\n```swift\nlet num = 10\nswitch num {\n    case 1...5:\n        print(\"1\u003C=x\u003C=5以内\")\n    case 5\u003C..10:\n        print(\"5\u003Cx\u003C=10以内\")\n    default:\n        print(\"unknown\")\n}\n\nlet pt = (0, 10)\nswitch pt {\n    case (0, 0):\n        print(\"1\")\n    case (_, 10): // 下划线代表忽略对应位置的值\n        print(\"纵轴是10的点\")\n    case (0, _):\n        print(\"横轴是0的点\")\n    case (-1..\u003C0, 10...20):\n        print(\"在阴影区\")                    \n    default:\n        print(\"unknown\")\n}\n\n// 值绑定\nlet pt2 = (0, 10)\nswitch pt2 {\n    case (let x, 0):\n        print(\"横轴坐标\\(x)\")\n    case (0, let y):\n        print(\"纵轴坐标\\(y)\")\n    case let (x, y):\n        print(\"unknown point (\\(x), \\(y))\")\n}\n```\n\n5. 配合where使用\n```swift\nlet turple = (0, 10)\nswitch turple {\n    case (let x, 10): // 绑定到x\n        print(\"x is \\(x), y is 10\")\n    case (0, let y): // 绑定到y\n        print(\"y is \\(y), x is 0\")\n    default:\n        print(\"anything\")\n}\n\nlet turple1 = (0, 10)\nswitch turple1 {\n    case let (x, y) where x == y: // where条件\n        print(\"x is equal to y\")\n    default:\n        print(\"anything\")\n}\n\nlet nums = [1, 2, 3, 4]\nfor num in nums where num > 2 { // 类似于continue\n    print(\"num is \\(num)\")\n}\n```\n\n\n#### 5.3.6 标签语句\n```swift\nupper: for i in 0...3 {\n    for j in 0...3 {\n        if j > 3 {\n            continue upper        \n        }\n        if i == 3 {\n            break upper        \n        }\n        print(\"i is \\(i), j is \\(j)\")\n    }\n}\n```\n\n\n### 5.4 函数\n#### 5.4.1 定义&声明\n```swift\nfunc dosomthing() {\n    // code\n}\n// 等价于\nfunc dosomething() -> Void {\n    // code\n}\n// 等价于\nfunc dosomething() -> () {\n    \n}\n\n// 带参数\nfunc dosomething(param1: Int, param2: Int) -> Int {\n    return param1 + param2\n}\n\n// 隐式返回\n// 如果整个函数体是一个单一表达式，那么函数会隐式返回这个表达式\nfunc dosomething(a: Int, b: Int) -> Int {\n    a + b\n}\ndosomthing(a: 1, b: 2) // 3\n\n// 返回元组\nfunc getValues(a: Int, b: Int) -> (sum: Int, average: Int) {\n    let sum = a + b\n    return (sum, sum >> 1)\n}\nlet result = getValues(a: 10, b: 2)\nprint(result.sum) // 12\nprint(result.average) // 6\n\n// 函数参数标签\nfunc eat(thing food: String) {\n    print(\"eat \\(food)\")\n}\neat(thing: \"苹果\")\n\n// 省略参数\nfunc eat(_ food: String) {\n    print(\"eat \\(food)\")\n}\neat(\"苹果\")\n```\n\n\n#### 5.4.2 参数相关\n```swift\n// 默认参数值\nfunc addUser(name: String = \"小明\", age: Int, score: Int = 100) {\n    print(\"姓名：\\(name)，年纪：\\(age)，成绩：\\(score)\")\n}\naddUser(name: \"Tom\", age: 10, score: 80)\naddUser(name: \"Tom\", age: 10)\naddUser(age: 10, score: 80)\naddUser(age: 10)\n\n// 如果省略参数标签，此时应注意若，语义模糊，编译器编译时或运行时报错\nfunc addUser(_ name: String = \"小明\", age: Int, _ score: Int = 100) {\n    \n}\n\n// 可变参数，以print为例就是一个可变参数的函数\n// 一个函数最多只能有1个可变参数\n// 紧跟在可变参数后面的参数不可省略参数标签\nfunc addNums(numbers: Int...) -> Int { // 此处的Int...表示一组Int参数\n    var sum = 0\n    for num in numbers {\n        sum += num    \n    }\n    return sum\n}\nsum(numbers: 1, 2, 3, 4)\n\nfunc sumTest(_ a: Int..., name: String, _ age: Int) {\n    \n}\n\n// print函数的定义:\n/// - Parameters:\n/// - items: Zero or more items to print.\n/// - separator: A string to print between each item. The default is a single space (`\" \"`).\n/// - terminator: The string to print after all items have been printed. The\n/// default is a newline (`\"\\n\"`).\npublic func print(_ items: Any..., separator: String = \" \", terminator: String = \"\\n\")\n```\n\n\n#### 5.4.3 inout标记的参数地址传递\n使用inout定义一个输入输出参数，可以在函数内部修改外部实参的值。\n```swift\nfunc swapValue(x: inout Int, y: inout Int) {\n    // 第一种方式\n    let temp = x\n    x = y\n    y = temp\n    // 第二种方式，使用元组\n    (x, y) = (y, x)\n}\n\nvar x = 10, y = 20\nswapValue(&x, &y)\n```\n\n注意：\n- 可变参数（比如：Int...）不能标记为inout\n- inout参数不能有默认值\n- inout参数只能传入可以被多次赋值的\n- inout参数的本质是地址传递（引用传递）\n\n#### 5.4.4 函数重载\n定义：函数名相同，但参数个数不同，或者参数类型不同，或者参数标签不同。\n```swift\nfunc setPosition(x: Float, y: Float) -> (Float, Float) { }\nfunc setPosition(x: Float, y: Float, z: Float) -> (Float, Float) { }\n\nfunc setPosition(x: Float, y: Float) -> (Float, Float) { }\nfunc setPosition(x: Int, y: Float) -> (Float, Float) { }\n\nfunc setPosition(_ x: Float, _ y: Float) -> (Float, Float) { }\nfunc setPosition(a: Int, b: Float) -> (Float, Float) { }\n```\n\n注意以下的代码编译器会报错（非重载，因为返回值的类型和函数重载无关）：\n![非重载情况jpg](https://image.xinwei.ltd/17110391266611711039148415.jpg)\n\n\n默认参数值和函数重载一起使用产生二义性时，编译器不会报错\n```swift\nfunc test(a: Int, b: Int) -> Int {\n    a + b\n}\nfunc test(a: Int, b: Int, c: Int = 3) {\n    a + b + c\n}\ntest(a: 1, b: 2) // 调用的是第一个\n```\n\n注意如果可变参数、省略参数标签、函数重载一起使用产生二义性时，编译器可能会报错。\n\n#### 5.4.5 内联函数 inline function\n开启编译器优化（Release模式下默认开启），某些函数会被编译器编译为内联函数，也就是将函数的调用体展开执行。\n\n（注意：如果函数体比较长，或者含有递归调用，或者含有动态派发，一般不会被自动内联）\n\n一般情况下，Swift不需要手动处理内联条件。但是Swift也提供了手动处理内联函数的语义。\n```swift\n// 这种不会被内联，即使开启编译器优化\n@inline(never) func test() {\n    print(\"test\")\n}\n\n// 开启编译器优化后，代码很长的情况，也会被内联。\n// 但是如果有递归调用、动态派发，还是不会被内联。\n@inline(__always) func test() {\n    print(\"test\")\n}\n```\n\n\n#### 5.4.6 函数类型\n函数是有类型的，由形参类型、返回值类型组成\n```swift\nfunc dosomething() {} // 函数类型是：() -> Void 或者 () -> ()\n// 另外提一句，Void是元组()的类型别名，点击Void进去或者看Swift源码可证\n\nfunc add(a: Int, b: Int) -> Int {} //函数类型是：(Int, Int) -> Int\n\n// 声明\nvar fn: (Int, Int) -> Int = add\nfn(1, 2) // 3 这个时候不需要a或者b形参，因为函数类型不包括形参\n```\n\n\n#### 5.4.7 函数作参数，或者作返回值\n```swift\nfunc add(a: Int, b: Int) -> Int {\n    a + b\n}\nfunc minus(a: Int, b: Int) -> Int {\n    a - b\n}\nfunc decideToHandle(_ fn: (Int, Int) -> Int, a: Int, b: Int) {\n    let result = fn(a, b)\n    print(\"result is \\(result)\")\n}\n\n// 作加法\ndecideHandle(add, a: 1, b: 2)\n// 作减法\ndecideHandle(minus, a: 1, b: 2)\n```\n\n\n返回值如果是函数类型，这种函数叫作高阶函数（higher-order function）\n```swift\nfunc add(a: Int, b: Int) -> Int {\n    a + b\n}\nfunc minus(a: Int, b: Int) -> Int {\n    a - b\n}\nfunc handle(_ useAdd: Bool) -> (Int, Int) -> Int {\n    useAdd ? add : minus\n}\n\n// 作加法\nhandle(true)(1, 2) // 3\n// 作减法\nhandle(false)(1, 2) // -1\n```\n\n\n### 5.5 类型别名typealias\n作用是用来给类型取别名的。\n比如：\n```swift\ntypealias MyInt = Int8\nlet num: MyInt = 4\n\ntypealias CustomFn = (Int, Int) -> Int\nfunc add(a: Int, b: Int) -> Int {\n    a + b\n}\nCustomFn = add\nCustomFn(1, 2) // 3\n```\n\n\n### 5.6 嵌套函数\n指的是在函数内部再定义函数。\n如：\n```swift\nfunc handle(_ useAdd: Bool) -> (Int, Int) -> Int {\n    func add(a: Int, b: Int) -> Int {\n        a + b    \n    }\n    func minus(a: Int, b: Int) -> Int {\n        a - b    \n    }\n    return useAdd ? add : minus\n}\nhandle(true)(1, 2) // 3\nhandle(false)(1, 2) // -1\n```\n\n\n这是《Swift知识一览》系列的第一篇文章，若感兴趣，请关注后续发表的文章。\n\n","这篇文章准备做成一个系列，供所有想学习Swift的开发者阅读，也可以作为资深Swift开发者的查阅手册。文章力求简洁易懂，争取条理清晰，也借阅了官网以及网上的一些资料。该篇文章主要介绍Swift的背景领域、安装、学习方法、编译过程以及部分基本语法约定。","https://image.xinwei.ltd/17110381485881711038168386.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],1096,"swift,ios swift,ios develop,swift develop,swift开发,ios开发",{"id":384,"createdAt":385,"title":386,"content":387,"summary":388,"image":15,"uid":136,"user":389,"categoryId":85,"category":390,"subCategoryId":47,"subCategory":391,"comments":392,"status":9,"reason":15,"notice":15,"visitCount":393,"commentCount":112,"keywords":394},28,"2024-01-29T14:27:48.349Z","使用YYLabel来实现收起和展开，嵌入#话题并允许点击","总结：这里实现的方式使用了numberOfLines属性，配合YYText的富文本增加点击事件，以及Masory布局来实现展开和收起，以及点击话题。\n如果需要控制折叠状态下的文本字数，可以直接截取富文本的某一段就可以了，这里不赘述。\n\n# 一、依赖 YYText\n```podfile\npod 'YYText'\n```\n\n# 二、实例化YYLabel作为文本组件\n注意：要设置一下preferredMaxLayoutWidth，控制文本的宽度\n（以下代码中的颜色和字体可根据自己需要自行设置，这里是使用我们自己封装的字体和颜色）\n```implemention.m\n- (YYLabel *)descLabel {\n    if (!_descLabel) {\n        _descLabel = [[YYLabel alloc] init];\n        _descLabel.font = [DZFontStyle pingFangFontOfSize:14];\n        _descLabel.textColor = UIColorWithHex(@\"#E0E1E6\");\n        _descLabel.textAlignment = NSTextAlignmentLeft;\n        _descLabel.numberOfLines = 2;\n        _descLabel.lineBreakMode = NSLineBreakByTruncatingTail;\n        _descLabel.preferredMaxLayoutWidth = CONTAINER_WIDTH;\n    }\n    return _descLabel;\n}\n```\n\n# 三、获取富文本\n1.第一步就是将整个的普通字符串，添加字体、颜色属性成富文本，再链接append上话题的富文本，形成了一个完整的富文本；\n2.这一步需要设置展开状态下的富文本：\n普通字符串富文本 + #话题#富文本及点击事件 + 省略号...(可选，这里我使用了空格) + “收起”image及点击事件\n3.折叠状态下的富文本，就是完整的富文本；（折叠状态下显示“展开”的操作在下一节）\n4.避免重复计算展开和折叠状态下的富文本，所以使用2个变量接收第一次算出来的富文本\n5.在label所在的代码作用域，实现“收起”的点击事件（下面代码是用block交给外层）\n```implemention.m\n/// 处理图片描述富文本\n- (NSAttributedString *)videoLayerDescAttStrWithDescWidth:(CGFloat)descWidth {\n    // 普通字符串\n    NSString *allStr = DZRealString(self.videoInfoVO.des);\n    // 富文本\n    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:allStr];\n    NSRange range = NSMakeRange(0, attStr.length);\n    if (range.location != NSNotFound) {\n        [attStr addAttribute:NSFontAttributeName value:Font(14) range:range];\n        [attStr addAttribute:NSForegroundColorAttributeName value:UIColorFromRGB(0xE0E1E6) range:range];\n    }\n    //话题，如果存在，添加点击方法（这里使用block交给外部实现），就append起来\n    NSString *topicStr = nil;\n    NSMutableAttributedString *topicAttStr = nil;\n    if (!IsEmpty(self.topicInfoVO)) {\n        topicStr = [NSString stringWithFormat:@\"#%@#\", DZRealString(self.topicInfoVO.title)];\n        topicAttStr = [[NSMutableAttributedString alloc] initWithString:topicStr attributes:@{NSFontAttributeName:[DZFontStyle pingFangMediumFontOfSize:14], NSForegroundColorAttributeName:UIColorFromRGB(0xFFFFFF)}];\n        @weakify_dzx(self);\n        [topicAttStr yy_setTextHighlightRange:NSMakeRange(0, topicAttStr.length) color:UIColorFromRGB(0xFFFFFF) backgroundColor:[UIColor clearColor] tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {\n            @strongify_dzx(self);\n            if (self.topicClick) {\n                self.topicClick();\n            }\n        }];\n        [attStr appendAttributedString:topicAttStr];\n    }\n    // 这里是2行的宽度，减去\"收起\"图片的宽度，就是展示整个富文本的总宽度\n    CGFloat descTwoLineMaxWidth = descWidth * 2.0 - 35;\n    // 富文本需要展示完全的宽度\n    CGRect fullStrRectInOneLine = [attStr boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) context:nil];\n    \n    if (fullStrRectInOneLine.size.width > descTwoLineMaxWidth) { // 超出2行 折叠起来了\n        if (self.isExpand) {\n            if (self.expandDescAttri) {\n                return self.expandDescAttri;\n            } else {\n                // 这里控制的是，让\"展开\"状态下的富文本最多字数为100，\n                if (attStr.length > 100) {\n                    attStr = [[NSMutableAttributedString alloc] initWithAttributedString:[attStr attributedSubstringFromRange:NSMakeRange(0, 100)]];\n                }\n                // append 空格\n                [attStr appendAttributedString:[[NSAttributedString alloc] initWithString:@\" \" attributes:@{NSForegroundColorAttributeName : [UIColor whiteColor], NSFontAttributeName : Font(14)}]];\n                // append \"收起\"图片的attachment\n                NSAttributedString *imgAttri = [NSAttributedString yy_attachmentStringWithContent:[UIImage imageNamed:@\"immersion_video_desc_collapse\"] contentMode:UIViewContentModeCenter attachmentSize:CGSizeMake(35, 16) alignToFont:[DZFontStyle pingFangFontOfSize:16] alignment:(YYTextVerticalAlignmentCenter)];\n                [attStr appendAttributedString:imgAttri];\n                // 点击\"收起\"的事件\n                @weakify_dzx(self);\n                NSRange attachmentRange = NSMakeRange(attStr.length - 1, 1);\n                [attStr yy_setTextHighlightRange:attachmentRange color:nil backgroundColor:nil tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {\n                    @strongify_dzx(self);\n                    if (self.videoCollapseClick) {\n                        self.videoCollapseClick();\n                    }\n                }];\n                // 拼接成最终的富文本\n                attStr = [[NSMutableAttributedString alloc] initWithAttributedString:[[self class] addParagraphForAttri:attStr]];\n                self.expandDescAttri = attStr;\n                return attStr;\n            }\n        } else {\n            if (self.foldDescAttri) {\n                return self.foldDescAttri;\n            } else {\n                // 完整的富文本\n                attStr = [[NSMutableAttributedString alloc] initWithAttributedString:[[self class] addParagraphForAttri:attStr]];\n                self.foldDescAttri = attStr;\n                return attStr;\n            }\n        }\n    } else { // 未超出2行\n        return attStr;\n    }\n    return attStr;\n}\n// 这里加了富文本的行间距属性\n+ (NSAttributedString *)addParagraphForAttri:(NSAttributedString *)attStr {\n    NSRange attRange = NSMakeRange(0, attStr.length);\n    if (attRange.location != NSNotFound) {\n        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];\n        paragraphStyle.lineSpacing = 5;\n        paragraphStyle.paragraphSpacing = 0;\n        paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;\n        NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithAttributedString:attStr];\n        [attributedStr addAttributes:@{ NSParagraphStyleAttributeName : paragraphStyle} range:attRange];\n        return attributedStr;\n    }\n    return attStr;\n}\n```\n6. 收起事件的实现\n```implemention.m\n// \"收起\"\n    infoVo.videoCollapseClick = ^{\n        weakSelf.infoVo.isExpand = NO;\n        weakSelf.descLabel.numberOfLines = 2;\n        weakSelf.descLabel.attributedText = [weakSelf.infoVo videoLayerDescAttStrWithDescWidth:CONTAINER_WIDTH];\n    };\n```\n\n# 四、折叠状态下的“展开”+ 点击展开事件\n1. 使用YYLabel的truncationToken，来显示“展开”。\n```implemention.m\n// 这里使用变量属性truncationToken，后面会赋值给YYLabel\n- (NSAttributedString *)truncationToken{\n    if (!_truncationToken) {\n        // 展开图片的size\n        CGSize imageSize = CGSizeMake(57, 27);\n        // 容器view，子视图是省略号+展开image\n        UIView *trailingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, imageSize.width, imageSize.height)];\n        // 给容器视图加点击事件，点击后就显示展开状态下的富文本\n        @weakify_dzx(self);\n        [trailingView addTapGestureActionWithBlock:^(UITapGestureRecognizer * _Nonnull tapAction) {\n            @strongify_dzx(self);\n            [self tapExpandAction];\n        }];\n        // 省略号\n        UILabel *dotLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 6, 22, 17)];\n        dotLabel.font = [DZFontStyle pingFangFontOfSize:14];\n        dotLabel.textColor = [UIColor whiteColor];\n        dotLabel.text = @\"...\";\n        // 展开image\n        UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@\"immersion_video_desc_expand\"]];\n        imageView.frame = CGRectMake(22, 6, 35, 16);\n        // 添加进容器\n        [trailingView addSubview:dotLabel];\n        [trailingView addSubview:imageView];\n        \n        _truncationToken = [NSAttributedString yy_attachmentStringWithContent:trailingView contentMode:UIViewContentModeCenter attachmentSize:imageSize alignToFont:[DZFontStyle pingFangFontOfSize:16] alignment:(YYTextVerticalAlignmentCenter)];\n    }\n    return _truncationToken;\n}\n```\n2. 赋值truncationToken\n```implemention.m\nself.descLabel.truncationToken = self.truncationToken;\n```\n3. 展开的实现\n```implemention.m\n/// 点击展开\n- (void)tapExpandAction {\n    self.infoVo.isExpand = YES;\n    self.descLabel.numberOfLines = 0; // 不限行\n    self.descLabel.attributedText = [self.infoVo videoLayerDescAttStrWithDescWidth:CONTAINER_WIDTH];\n}\n```\n\n# 五、给YYLabel赋值\n```implemention.m\n// 描述\n    self.descLabel.attributedText = [infoVo videoLayerDescAttStrWithDescWidth:CONTAINER_WIDTH];\n```","在objective-c中，实现文本的折叠和展开。这里实现的方式使用了numberOfLines属性，配合YYText的富文本增加点击事件，以及Masory布局来实现展开和收起，以及点击话题。如果需要控制折叠状态下的文本字数，可以直接截取富文本的某一段就可以了。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],1079,"ios,label,ios label,iOS文本,iOS文本折叠",{"id":396,"createdAt":397,"title":398,"content":399,"summary":400,"image":401,"uid":136,"user":402,"categoryId":85,"category":403,"subCategoryId":404,"subCategory":405,"comments":407,"status":9,"reason":15,"notice":15,"visitCount":408,"commentCount":112,"keywords":409},21,"2024-01-26T13:49:03.139Z","如何在nuxt3中上传FormData文件","nuxt3中进行网络请求时，往往使用的是useFetch，当然其他使用方式如$fetch的。\n\n使用useFetch时，有和axios类似的网络interceptor，可以使用Promise在interceptor中自行决定返回某种格式的response，这里不赘述，只简单记一些可能出错的点。\n\n下面是一个简单的封装上传FormData的例子。\n\n```typescript\n// path example：/api/upload\nfunction upload\u003CT>(path: string, file: File) {\n  const formData = new FormData();\n  formData.append('file', file);\n  return useFetch\u003CT>(path, {\n    baseURL: 'https://www.xxxx.com', \n    key: path,\n    method: 'POST',\n    body: FormData,\n  })\n};\n```\n在这个例子中，将file文件挂载在名为“file”的formData中，然后传给useFetch的option中的body。\n\n服务端就可以通过post类型的该上传接口路由从MultipartForm中取出名为file的文件，然后进行上传处理，然后组装返回给客户端的response。\n\n有时候可能遇到的问题：no multipart boundary param in Content-Type\n如下截图：\n![报错截图](https://image.xinwei.ltd/11706276565695.png)\n\n这个问题目前我遇到的就是由于从Vue3的前端项目，重构为nuxt3项目时，老代码中的错误。\n\n错误的起因是在headers中多了一个内容类型Content-Type的声明（因为nuxt3在服务端渲染时，会自动添加含有boundary=的Content-Type）：\n```typescript\nreturn useFetch\u003CT>(path, {\n    baseURL: 'https://www.xxxx.com', \n    key: path,\n    method: 'POST',\n    body: FormData,\n    headers: { // 这里不要加Content-Type\n        \"Content-Type\": \"multiple/form-data\"  \n    }\n  })\n```\n截图为：\n![代码报错的地方截图](https://image.xinwei.ltd/21706276648780.png)\n\n对比一下在headers中添加Content-Type和不添加的效果对比：\n1. 添加Content-Type，如下图红框所示：\n![没有添加content-type的抓包](https://image.xinwei.ltd/31706276697489.png)\n\n2. 不添加Content-Type，nuxt3自动添加的是如下图所示：\n![](https://image.xinwei.ltd/41706276741721.png)\n\n从这2个对比的截图，我们可以看出，nuxt3在使用FormData类型的值给body字段时，会自动在Content-Type中加boundary信息。\n\n\n\n","在nuxt3中该如何使用网络请求API以FormData方式上传文件呢，这里会给大家提供演示的代码，以做参考。","https://image.xinwei.ltd/11706276565695.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},37,{"id":404,"name":406,"parentId":85},"全栈",[],1039,"nuxt,nuxt3,nuxt3 formdata,nuxt上传,nuxt知识,formdata",{"id":411,"createdAt":412,"title":413,"content":414,"summary":415,"image":416,"uid":136,"user":417,"categoryId":85,"category":418,"subCategoryId":419,"subCategory":420,"comments":422,"status":9,"reason":15,"notice":15,"visitCount":423,"commentCount":112,"keywords":424},92,"2024-03-04T04:42:47.834Z","React-Native \"Library not loaded: @rpath/hermes.framework/hermes\" or \"dyld image not found hermes.framework\"","**前言：**\n在开发react native的过程中，有可能遇到hermes.framework没有load的问题，经过我大范围的查找资料和fix尝试，我自己项目出现的编译问题始终没有得到正确的解决。为此，我不得不寻求一个不会出现该问题的版本，经过多次的尝试，得到一个解决方案，以供大家参考。\n\n**react-native的github上的issue现状：**\nissue部分同样出现过很多这种上报的案例，但是在issue部分，往往官方团队并不能复现这种特有的情况，基本情况是不了了之，然后告诉开发者去升级最新版本。也有通过自己手动添加link library的方式解决该问题的，但是仍然还有一些开发者在issue和stackoverflow上反馈了该种类型的报错。\n\n## 具体报错：\n> Library not loaded: @rpath/hermes.framework/hermes\n\n或者：\n> dyld image not found hermes.framework... found in /var/.../hermes.framework reference in /var/....\n\n诸如此类的报错，都是hermes引擎库没有正确的被链接，或者是hermes的引擎库版本出现了兼容性的问题。因为从node_modules下的hermes-engine中可以看到hermes是被正常install得。\n\n解决这种问题的思路，经过我查阅资料和代码尝试，我做了一些解决该问题的方案总结，每个开发者可能会使用其中的某一个方法就可以解决掉。\n\n## 解决bug的前提：\n1. 如果你是将项目从低版本升级到高版本，那么你应该按照react-native的upgrade方案，修改你工程中的配置文件和代码，版本升级指南你可以进入以下网址查看：\n[https://react-native-community.github.io/upgrade-helper/](https://react-native-community.github.io/upgrade-helper/)\n或者你可以在react-native的工程目录下，运行命令`react-native upgrade`命令，同样会给出这个链接的。\n\n2. 你应该确保在你自己的电脑上，新建任意版本的react native工程是既可以运行在模拟器上，也可以运行在真实设备上。\n新建react native工程：\n```\nnpx react-native@latest init RnTestProj --verbose\n```\n跑一下模拟器（同样可以usb连接iphone进行再跑这个命令）\n```\nyarn ios --verbose\n```\n或者\n```\nreact-native run-ios --verbose\n```\n3. 在任意react native工程目录下，执行终端命令：\n```\nreact-native doctor\n```\n检测当前电脑环境中的react native运行环境都是ok的。\n\n## 解决方案\n### 方案一\n确保工程中的ruby、bundle、cocoapods、gem是最新且一致的：\n- 更新gem\n```\ngem update --system --verbose\n```\n- 更新rvm\n```\nrvm cleanup all // clean\nrvm get stable --verbose\n```\n- 安装最新ruby\n```\n// 可以查看目前所有可以安装的ruby版本\nrvm list remote\n// 安装最新\nrvm install ruby --verbose\n// 或者指定版本安装\nrvm install ruby-3.2.2 --verbose\n```\n可以查看本地已安装好的ruby版本:\n```\nrvm list known\n```\n可以使用其中的一个ruby版本作为默认版本：\n```\nrvm --default use 3.2.2\n```\n- 安装新版的cocoapods（1.15及以上除外，因为react-native官方团队说1.15以上会有集成问题，所以最好选择安装1.14.3）\n```\ngem install cocoapods -v 1.14.3 --verbose\n```\n如果按照以上步骤，那么你的Gemfile文件中的配置应该手动修改成类似如下：\n```Gemfile\nsource 'https://rubygems.org'\n\n# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version\nruby '3.2.2'\n\ngem 'cocoapods', '1.14.3'\ngem 'activesupport', '>= 6.1.7.3', '\u003C 7.1.0'\n```\n> 切记修改Gemfile后，要删除掉Gemfile.lock文件\n\n做完上面的工作，你要删除掉node_modules、yarn.lock、ios/Pods、ios/build、ios/Podfile.lock这些文件或者文件夹。\n依次执行：\n```\nyarn install --verbose\nbundle install --verbose\n// 进入ios文件夹后执行\nbundle exec pod install --verbose\n```\n然后再尝试运行在模拟器和真实设备上，查看是否解决。\n\n### 方案二\n这个方案就是将hermes.xcframework直接手动链接到工程中。\nxcode > build phases > Link binary with Libraries\n![](https://image.xinwei.ltd/11709526298940.png)\n\n这种方式有的人成功了，但不适用于我的工程。\n\n### 方案三\n在ios的文件夹目录下，通过pod命令直接升级hermes-engine\n```\npod update hermes-engine --no-repo-update\n```\n![](https://image.xinwei.ltd/21709526493150.png)\n\n### 方案四\n就是设置是否启用hermes。先将hermes禁用，安装好依赖，尝试编译运行。如果正常，那么再启用hermes，再安装依赖，尝试编译运行，看是否可以解决。\n\n启用true/禁用false\n```Podfile\n:hermes_enabled => false,\n```\n\n基本上大家的共识是，希望使用hermes，而不是禁用它。由于hermes出色的性能，能很好的优化启动速度和降低包大小，所以最终目的不是禁用它，而是看切换过程中，是否能得到某些编译方面的启示。\n\n### 最终方案\n以上的四种方案，是可以在整个互联网上找到的解决方案，可但是，对于我的项目是无效的。\n所以我自己历经辛苦，终于找到一个稳定版本，不会出现该错误。\n简介：我的项目是一个0.61.5的老项目，对于当前0.73.5的最新稳定版来讲，已经不能再正常运行了。\n步骤如下：\n1. 0.61.5 升级到 0.71.15；\n2. 使用方案一的方式，确保最新的环境；\n3. 重新安装Xcode（我的电脑目前只安装了Xcode14.2）；\n4. 重新安装所有依赖，并尝试运行到模拟器和真实设备；\n5. 最后模拟器和真实设备都运行成功了。\n最后附上我的环境配置：\n```\nreact-native info\n```\n```terminal\n ✘ hanweixing@Pirate-Hamry  ~/Desktop/AwesomeProjectII   main  react-native info\nwarn Package react-native-vector-icons contains invalid configuration: \"dependency.assets\" is not allowed. Please verify it's properly linked using \"npx react-native config\" command and contact the package maintainers about this.\ninfo Fetching system and libraries information...\nwarn Multiple Podfiles were found: ios/Podfile,vendor/bundle/ruby/3.2.0/gems/cocoapods-core-1.14.3/lib/cocoapods-core/Podfile. Choosing ios/Podfile automatically. If you would like to select a different one, you can configure it via \"project.ios.sourceDir\". You can learn more about it here: https://github.com/react-native-community/cli/blob/master/docs/configuration.md\nSystem:\n  OS: macOS 12.7.3\n  CPU: (4) x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz\n  Memory: 33.92 MB / 8.00 GB\n  Shell:\n    version: 5.8.1\n    path: /bin/zsh\nBinaries:\n  Node:\n    version: 18.16.0\n    path: /usr/local/bin/node\n  Yarn:\n    version: 1.22.21\n    path: /usr/local/bin/yarn\n  npm:\n    version: 9.5.1\n    path: /usr/local/bin/npm\n  Watchman:\n    version: 2024.01.22.00\n    path: /usr/local/bin/watchman\nManagers:\n  CocoaPods:\n    version: 1.14.3\n    path: /Users/hanweixing/.rvm/gems/ruby-3.2.2/bin/pod\nSDKs:\n  iOS SDK:\n    Platforms:\n      - DriverKit 22.2\n      - iOS 16.2\n      - macOS 13.1\n      - tvOS 16.1\n      - watchOS 9.1\n  Android SDK: Not Found\nIDEs:\n  Android Studio: 4.2 AI-202.7660.26.42.7351085\n  Xcode:\n    version: 14.2/14C18\n    path: /usr/bin/xcodebuild\nLanguages:\n  Java:\n    version: 10.0.2\n    path: /usr/bin/javac\n  Ruby:\n    version: 3.2.2\n    path: /Users/hanweixing/.rvm/rubies/ruby-3.2.2/bin/ruby\nnpmPackages:\n  \"@react-native-community/cli\": Not Found\n  react:\n    installed: 18.2.0\n    wanted: 18.2.0\n  react-native:\n    installed: 0.71.15\n    wanted: 0.71.15\n  react-native-macos: Not Found\nnpmGlobalPackages:\n  \"*react-native*\": Not Found\nAndroid:\n  hermesEnabled: true\n  newArchEnabled: false\niOS:\n  hermesEnabled: true\n  newArchEnabled: false\n\ninfo React Native v0.73.5 is now available (your project is running on v0.71.15).\ninfo Changelog: https://github.com/facebook/react-native/releases/tag/v0.73.5\ninfo Diff: https://react-native-community.github.io/upgrade-helper/?from=0.73.5\ninfo For more info, check out \"https://reactnative.dev/docs/upgrading?os=macos\".\n```\n\n希望对大家有所帮助。\n\n\n\n","在开发react native的过程中，有可能遇到hermes.framework没有load的问题，经过我大范围的查找资料和fix尝试，我自己项目出现的编译问题始终没有得到正确的解决。为此，我不得不寻求一个不会出现该问题的版本，经过多次的尝试，得到一个解决方案，以供大家参考。","https://image.xinwei.ltd/11709526298940.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},12,{"id":419,"name":421,"parentId":85},"React Native",[],1007,"react native,react native herme,react native herme库",{"id":426,"createdAt":427,"title":428,"content":429,"summary":430,"image":431,"uid":136,"user":432,"categoryId":85,"category":433,"subCategoryId":47,"subCategory":434,"comments":435,"status":9,"reason":15,"notice":15,"visitCount":436,"commentCount":112,"keywords":437},91,"2024-03-02T08:42:52.504Z","iOS 海康威视sdk的集成","# 海康威视的设备网络iOS sdk 集成记录\n\n下载地址（海康提供的硬件产品下载地址）：[https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10&id=5cda5902f47ae80dd41a54b7](https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10&id=5cda5902f47ae80dd41a54b7)\n\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709366837263.png)\n\n如图所示，直接点击“立即下载”的按钮可以下载sdk，查看开发指南也是下载的pdf版使用手册。\n\n下载下来的文件结构：\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709366940703.png)\n\n## 接入步骤\n1. 首先在`SimpleDemo`中，用Xcode运行一下，确保demo是可以正常运行的。正常情况下，demo是可以正常run起来的。至少我写这篇文章时的版本是正常运行的。\n2. 需要注意demo中的几个配置的点：\n- 当前版本的海康威视sdk的demo，使用的是`mrc`，而不是`arc`。通过在build setetings中搜索`counting`来搜索引用计数的配置项：\n![海康威视iOS demo 截图](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709367254416.png)\n从截图中可以看出，配置项`Objective-C Automatic Reference Counting`是`NO`。\n\n- SimpleDemoViewController文件是`.mm`文件\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709367434377.png)\n所以这里面使用一些C、C++的代码。\n那么当你集成海康威视的sdk时，切记在使用C、C++代码的时候，需要使用.mm的扩展文件名。\n3. 海康威视的开发文档中，并没有明确说明在引入他们的静态库和文件时，需要link那些library，但是在demo中，是可以从`build phrases`中的`link binary with libraries`选项中得到需要add的一些库。\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709367866210.png)\n切记：在导入sdk的静态库、动态库、文件到项目中后，一定要在`link binary with libraries`选项中添加demo中所示的library，否则编译肯定会各种报错。\n4. 按照上述的步骤，是可以正常集成海康的sdk的。\n\n接下来会说明异常情况。\n\n## 其他场景\n1. 当下基本大多数的iOS工程都是`arc`的，而不是`mrc`，如何在`arc`工程中集成`mrc`的第三方库？\n\n在arc出现之前，至少在我的记忆中，iOS7出现之前，接触的基本都是mrc的工程。\n\n把sdk中的静态库、动态库、文件引入工程后，也引入了link的library后，可以先尝试进行编译，看具体报错。\n\n出现错误大概就是`'release' is unavailable: not available in automatic reference counting mode`\n\n查看编译报错的所在文件，在`build prases`配置中的`Compile Sources`下，找到报这个错误的文件，双击配置一下`-fno-objc-arc`，如下截图：\n![-fno-objc-arc](https://image.xinwei.ltd/Snipaste_2024-03-02_16-38-431709368745019.png)\n通过这种方式，使编译该文件时使用手动引用计数。\n\n如果有其他异常情况，欢迎一起讨论。\n","这篇文章记录一下我使用海康威视sdk的经历，通过简单的铺陈介绍，希望给在集成这个sdk时可能遇到问题的同学们一点借鉴。","https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-02%20161709366837263.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],979,"海康威视,hk vision,hk vision sdk,海康威视sdk,hk ios sdk",{"id":119,"createdAt":439,"title":440,"content":441,"summary":442,"image":443,"uid":136,"user":444,"categoryId":85,"category":445,"subCategoryId":47,"subCategory":446,"comments":447,"status":9,"reason":15,"notice":15,"visitCount":448,"commentCount":112,"keywords":449},"2024-01-08T15:54:37.173Z","fastlane match - Passphrase for Match storage - forget","### 一、背景\n使用fastlane来管理iOS的证书时，有时候由于人事交接不完全，或者首次生成证书的那个电脑由于某种原因丢失fastlane match密码。造成在使用fastlane来打包时，需要输入密码，否则打包进行不下去。\n\n### 二、问题\nPassphrase for Match storage:******\nType passphrase again:****\nCouldn't decrypt the repo, please make sure you enter the right password!\n\n### 三、问题截图\n![](https://image.xinwei.ltd/image_11704728706442.png)\n\n\n### 四、问题分析：\n1.fastlane管理证书流程：\n- 需要使用公司的一个gitlab或者github仓库来存储证书，在iOS工程的fastlane文件夹下的Matchfile文件中进行配置。\n![](https://image.xinwei.ltd/image_21704728742915.png)\n\n- 在fastlane文件夹下的Appfile中配置bundle id、Apple账号等\n![](https://image.xinwei.ltd/image_31704728768119.png)\n\n2. fastlane match时会将初次设置的密码存在钥匙串中。\n![](https://image.xinwei.ltd/image_41704728793974.png)\n\n3. fastlane match --help命令可以查看fastlane match后面的指令参数。其中有一个change_password的指令，表示可以更改加密证书的密码。\n![](https://image.xinwei.ltd/image_51704728814106.png)\n\n4. 如果清空证书git仓库中的证书，是可以重新执行fastlane match来设置加密/解密证书的密码的。\n5. 如果Apple证书中心上的发布证书已经有2个，或者开发证书已经有4个（针对同一个bundle id），那么fastlane去生成证书时就会报错，表示证书已经达到创建上限，无法由fastlane再次生成。\n\n### 五、解决办法：\n解决密码问题的方法在于2种：\n第一，就是按照2中的步骤，去钥匙串中查找match开头的那个密码，尝试输入，测试是否可以解决。\n第二，要么就是需要清空证书git仓库下的所有证书（要删除Apple证书中心无用的一些证书，让fastlane可以创建新证书，否则数量超发，新建不了）。然后在iOS工程的fastlane文件夹所在的目录下，重新执行fastlane match (development、distribution)，等待fastlane通过Apple API去Apple证书中心生成新证书和profile文件，等在Apple证书中心生成新证书后，会自动下载到本地并安装到钥匙串中。\n\n删除Apple证书中心的证书的命令：\n```language\nfastlane match nuke (develop、distribution)\n```\n\n可以用来删除证书和profile，执行这个命令时，会让你选择删除哪些证书。\n","一、背景 使用fastlane来管理iOS的证书时，有时候由于人事交接不完全，或者首次生成证书的那个电脑由于某种原因丢失fastlane match密码。造成在使用fastlane来打包时，需要输入密码，否则打包进行不下去。 二、问题 Passphrase for Match storage: Type passphrase again: Couldn't decrypt the repo, pl","https://image.xinwei.ltd/image_11704728706442.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],965,"fastlane,fastlane match,Passphrase for Match storage,ios fastlane",{"id":451,"createdAt":452,"title":453,"content":454,"summary":455,"image":456,"uid":136,"user":457,"categoryId":85,"category":458,"subCategoryId":100,"subCategory":459,"comments":460,"status":9,"reason":15,"notice":15,"visitCount":461,"commentCount":112,"keywords":462},127,"2024-10-14T06:05:26.414Z","为flutter项目中配置firebase cli问题 - This tool has encountered an error.","## 一、背景\n在flutter项目中要引入firebase时，需要安装一下firebase cli，这样就可以在flutter项目中进行firebase的初始化。\n\n## 二、问题\n在Mac上安装firebase cli时，终端提示错误：\n> -- firebase-tools@This tool has encountered an error. Please file a bug on Github (https://github.com/firebase/firebase-tools/) and include firepit-log.txt is now installed\n\n![firebase cli error](https://image.xinwei.ltd/image1728885706405.png)\n\n\n## 三、安装\nfirebase提供了3种在Mac上的安装方式：\n### 3.1 终端命令\n在Mac上安装firebase cli\n```terminal\ncurl -sL firebase.tools | upgrade=true bash --verbose\n```\n\n### 3.2 下载二进制文件\n下载链接：[https://firebase.tools/bin/macos/latest](https://firebase.tools/bin/macos/latest)\n下载下来后是一个文件，需要使用chmod命令改成可执行文件。\n（下面命令中的`firebase_tools`改成你下载的文件名，建议下载的文件名改成firebase）\n```terminal\nchmod +x ./firebase_tools\n```\n\n### 3.3 使用npm安装\n```terminal\nnpm install -g firebase-tools\n```\n\n## 四、解决办法\n使用任何一种方式进行安装，但是在我安装的过程中出现如上的报错，解决办法是需要先卸载已经安装的firebase cli\n### 4.1 卸载\n```terminal\ncurl -sL firebase.tools | uninstall=true bash\n```\n\n### 4.2 安装\n```terminal\nnpm i -g firebase-tools\n```\n\n","在Mac上安装firebase cli时，终端提示错误\"This tool has encountered an error. Please file a bug on Github (https://github.com/firebase/firebase-tools/) and include firepit-log.txt\"","https://image.xinwei.ltd/image1728885706405.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],964,"flutter,flutter firebase,flutter firebase install,firebase install",{"id":464,"createdAt":465,"title":466,"content":467,"summary":468,"image":469,"uid":136,"user":470,"categoryId":85,"category":471,"subCategoryId":419,"subCategory":472,"comments":473,"status":9,"reason":15,"notice":15,"visitCount":474,"commentCount":112,"keywords":475},86,"2024-02-22T05:34:48.298Z","react native error: Multiple commands produce '/Users/hanweixing/Library/Developer/Xcode/DerivedData/AwesomeProject-dkfg","当在运行react native工程时碰到问题：\n> Multiple commands produce '/Users/hanweixing/Library/Developer/Xcode/DerivedData/AwesomeProject-dkfg\n\n可以在运行的命令行中（如果执行的是：yarn ios)，看到有哪些文件是被多次声称的，都有报错的红色显示，我这里忘记截图红色提示部分的错误。\n\n当然你还可以尝试直接使用Xcode运行iOS工程，查看报错信息。\n\n我工程出现的问题是react native的脚本和Xcode，都去使用了我的.ttf字体文件，所以需要去删除一下Xcode编译时copy的字体文件即可。\n\n这里主要就是需要用Xcode打开iOS工程，去build phrases的选项下，找到Copy Bundle Resources，删除报错中提示到的字体文件就好。\n\n![copy bundle resources](https://image.xinwei.ltd/11708580057871.png)\n","简要介绍一下如何解决在运行react native工程时碰到的multiple commands produce的问题。","https://image.xinwei.ltd/11708580057871.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],946,"react native, react native runtime,react native run error,react native运行,react native开发",{"id":477,"createdAt":478,"title":479,"content":480,"summary":481,"image":15,"uid":136,"user":482,"categoryId":85,"category":483,"subCategoryId":186,"subCategory":484,"comments":485,"status":9,"reason":15,"notice":15,"visitCount":486,"commentCount":112,"keywords":487},94,"2024-03-06T16:37:23.852Z","一些在Mac电脑上常用的终端命令或Linux命令","# Mac上常见的操作命令\n**前言**\n这篇文章介绍在Mac电脑上常用的一些终端命令或者Linux命令，比如mkdir创建文件/文件夹、mv移动文件/文件夹、cp拷贝文件/文件夹，包括scp远程拷贝文件/文件夹的一些命令。作为参考手册。\n\n1. 查看当前终端所在的路径\n```\npwd\n```\n\n2. 显示当前路径下的文件\n```\nls // 查看非隐藏的所有文件/文件夹\nls -a // 查看所有文件/文件夹（隐藏的文件也可以查看）\nll // 查看文件/文件夹的详细信息（读写权限、创建日期等）\n```\n\n3. 创建文件\n```\ntouch test.txt\n```\n\n4. 进入编辑文件\n```\nvi test.txt\n// 或者\nvim test.txt\n```\n进入终端编辑文件之后，通常按i键进入编辑模式，当编辑文件结束后，按ESC键退出编辑模式，然后点击：，输入wq进行保存退出。如果遇到保存退出失败的情况，需要在wq后面加!号，再enter键确认退出\n\n5. 查看文件内容（同样适合查看一些密钥证书的内容，比如rsa的公钥文件内容）\n```\ncat test.txt\n```\n\n6. 以文本方式查看文件\n```\nopen -e test.txt\n```\n\n7. 创建文件夹\n```\nmkdir myProject\n```\n\n8. 进入某一个子文件夹内部\n```\ncd myProject\n```\n\n9. 返回当前文件夹的上一层\n```\ncd ..\n```\n\n10. 移动文件或文件夹\n将一个文件/文件夹，从一个路径移到另一个路径下\n```\nmv test.txt myProject/test.txt\n```\n\n11. 移动文件并改名\n```\nmv test.txt myProject/newTest.txt\n```\n\n12. 拷贝文件/文件夹到另一个文件夹下（或者在新路径下生成新文件夹）\n```\ncp -R myProject /Users/xxx/Desktop/myProject\n```\n这种方式是直接将myProject文件夹(包括其中的文件)，整个文件夹拷贝到Desktop下，当然可以改新文件夹的名字\n\n13. 拷贝文件夹内的所有文件（不包括文件夹）到新文件夹下\n```\ncp -R myProject/* /Users/xxx/Desktop/newProject/\n```\n\n14. 远程拷贝文件或文件夹\n需要使用scp命令，scp 使用同 ssh 一样的底层协议。\n```\nscp -E myProject/test.txt username@remoteserver.com:/Users/username/root/test.txt\n```\n-E 表示保留扩展属性等信息。\n\n15. 远程拷贝文件夹内所有文件（不包括文件夹）\n```\nscp -r myProject/* username@remoteserver.com:/Users/username/root/\n```\n\n还可以使用-q参数，表示不输出拷贝进度的信息\n还可以使用-i参数，指定密钥验证文件（可以避免手动输入密码）\n这里的详细解释可以参考我之前的文章：[远程ssh到服务器，以及本地电脑和服务端之间的文件传输scp相关操作](https://www.xinwei.ltd/article/93)\n\n16. mac上可以使用Homebrew来管理一些常见的第三方软件，比如mysql、postgresql、redis、php、python、watchman、nginx等。\n如果你想安装某一个第三方的软件包，可以尝试搜索\n```\nbrew search nginx\n```\n\n17. 使用brew安装第三方软件包\n```\nbrew install nginx\n```\n\n18. 查看当前终端中使用过哪些历史命令\n```\nhistory\n```\n\n19. 使用grep模糊查找之前使用的历史命令\n```\n history | grep npm\n```\n\n20. 安装iOS的cocoadpods是需要使用gem安装的\n```\ngem install cocoapods\n```\n\n21. 删除文件/文件夹\n```\nrm -rf test.txt\n```\n\n22. 执行某个bash脚本\n```\n./test.sh // 会被脚本中的路径变化影响\nbash test.sh // 不会更改脚本执行完后的路径\n```\n\n目前先总结这些吧，还有很多其他命令，比如在安装node之后会有npm全局命令，当然这些都是环境变量配置（手动/安装包自动）之后可以使用的命令。\n\n\n","这篇文章介绍在Mac电脑上常用的一些终端命令或者Linux命令，比如mkdir创建文件/文件夹、mv移动文件/文件夹、cp拷贝文件/文件夹，包括scp远程拷贝文件/文件夹的一些命令。作为参考手册。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":186,"name":188,"parentId":85},[],895,"mac commands,mac terminal,mac命令,terminal命令",{"id":489,"createdAt":490,"title":491,"content":492,"summary":493,"image":494,"uid":136,"user":495,"categoryId":85,"category":496,"subCategoryId":144,"subCategory":497,"comments":498,"status":9,"reason":15,"notice":15,"visitCount":499,"commentCount":112,"keywords":500},113,"2024-05-26T07:33:46.438Z","如何在nuxt3项目中设置ESLint和Pretierr代码对齐","## 一、背景 \n正如我们在开发的代码项目中需要进行代码对齐或者代码纠错等开发环境特性，在前端项目中也是如此，诸如vue、react这样的项目，众所周知使用的代码对齐方案是配合使用vs code来进行。这里我们来介绍如何在nuxt项目中使用eslint和prettierr。  \n\n## 二、使用到的必备条件\n- Visual Studio Code（我们的编辑器）\n- 需要用到的扩展（ESLint、Prettier - Code formatter)\n- 一个nuxt3的项目\n\n## 三、步骤 \n### 1. 本地电脑上需要安装 VS Code \n下载地址：[vs code下载官网](https://visualstudio.microsoft.com/)  \n### 2. 本地创建一个nuxt3的项目 \n一般我们使用npx来新建一个nuxt3的项目，当然你还可以使用其他包管理器 \n```terminal \nnpx nuxi@latest init \u003Cproject-name>\n```\n### 3. 建议电脑中全局安装eslint \n```terminal \nnpm install -g eslint\n```\n这样子我们可以使用eslint的终端命令，来检测本地项目的eslint config配置  \n\n### 4. 用vs code打开项目，安装必要插件 \n#### 4.1 安装ESLint插件 \n![安装ESLint.png](https://image.xinwei.ltd/image1716705973646.png)  \n\n#### 4.2 安装Prettier - Code formatter \n![安装Prettier.png](https://image.xinwei.ltd/image1716706053560.png)  \n\n#### 4.3 配置vs code的settings.json文件 \n![vs code的settings.json文件.png](https://image.xinwei.ltd/image1716706632371.png) \n如果项目中没有明确的`.prettierrc`配置文件存在，那么vs code的setting.json中的配置就会生效，所以为了保底，settings.json文件中需要配置一下： \n```settings.json \n{   \n  \"prettier.configPath\": \".prettierrc\", // 如果项目中有.prettierrc文件，那么可以在这里设置一下   \n  \"editor.formatOnPaste\": false,   \n  \"editor.formatOnType\": false,   \n  \"scss.lint.unknownAtRules\": \"ignore\",   \n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",   \n  \"eslint.format.enable\": true,   \n  \"prettier.useEditorConfig\": false \n} \n```\n如果你要设置`.prettierrc`文件，以下配置可以供参考：\n```.prettierrc\n{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"trailingComma\": \"none\"\n}\n```\n#### 4.4 引入nuxt3所需的eslint模块\n根据nuxt3官网中的解释，我们需要执行以下的命令，就可以在nuxt的config文件中加入module\n```terminal\nnpx nuxi module add eslint\n```\n当执行以下命令启动项目时，会自动生成`eslint.config.mjs`这个配置文件，自动生成的内容如下：\n```eslint.config.mjs\n// @ts-check\nimport withNuxt from './.nuxt/eslint.config.mjs'\n\nexport default withNuxt()\n```\n\n### 5. 使用eslint命令来检测配置文件\n在项目的根目录下执行以下命令（可以在vs code中新建终端来执行命令）\n```terminal\neslint --inspect-config\n```\n![执行eslint命令.png](https://image.xinwei.ltd/image1716708276362.png)\n\n会自动打开默认浏览器，浏览器中的内容如下：\n![eslint打开的浏览器.png](https://image.xinwei.ltd/image1716708369887.png)\n\n### 6. 验证一下\n输入一个未对齐的代码\n![测试代码.png](https://image.xinwei.ltd/image1716708461299.png)\n然后保存一下（Mac上command + s是保存，根据自己实际情况操作）\n![验证代码.png](https://image.xinwei.ltd/image1716708544316.png)\n发现代码可以自动对齐了。\n\n## 四、总结\n经过上面的配置就可以得到一个代码自动对齐的效果。如果有任何问题，可以查看vs code的输出，看看eslint或者哪里有没有报错。\n![查看output.png](https://image.xinwei.ltd/image1716708657283.png)\n\n\n\n","正如我们在开发的代码项目中需要进行代码对齐或者代码纠错等开发环境特性，在前端项目中也是如此，诸如vue、react这样的项目，众所周知使用的代码对齐方案是配合使用vs code来进行。这里我们来介绍如何在nuxt项目中使用eslint和prettierr。","https://image.xinwei.ltd/image1716705973646.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],853,"html,代码对其,eslint,js eslint,html eslint",{"id":502,"createdAt":503,"title":504,"content":505,"summary":506,"image":507,"uid":136,"user":508,"categoryId":85,"category":509,"subCategoryId":144,"subCategory":510,"comments":511,"status":9,"reason":15,"notice":15,"visitCount":512,"commentCount":112,"keywords":513},121,"2024-08-12T02:42:47.033Z","在Google上做seo，如何更新robots.txt文件","## 摘要\n在我做过的很多web项目的seo中，特别喜欢Google的seo，因为真的是速度快而且人性化。最讨厌国内的seo，简直反人类，各种要参与搜索引擎的某些营销计划才能提交爬虫相关，速度慢，要求还贼多。饭馆Google的爬虫，没有那么多要求，还没有付费排名，真的是web的福音。\n\n这篇文章简单的说一下如何更新Google上的robots.txt文件。\n\n## 步骤\n1. 直接进入谷歌搜索，登录谷歌账号；\n2. 要么输入`site:baidu.com`（将baidu.com替换成你自己的网站域名），谷歌搜索一下，在搜索结果页，就可以看到谷歌的提示\n![Google的site搜索](https://image.xinwei.ltd/image1723430135036.png)\n点击就可以进入Google搜索管理控制台\n3. 或者你直接进入网址：[Google Search Console](https://search.google.com/)\n4. 更新robots.txt的地方在Setting（也就是设置）中。\n![Google的robots.txt位置](https://image.xinwei.ltd/image1723430306863.png)\n5. 点击`OPEN REPORT`进入\n![Google的robots.txt文件](https://image.xinwei.ltd/image1723430400014.png)\n6. 点击文件右侧的更多图标\n![请求爬取robots.txt](https://image.xinwei.ltd/image1723430471685.png)\n7. 直接点击“Request a recrawl”重新爬取即可。\n\n## 附录\n附上Google的robots.txt相关操作官方记录：[https://support.google.com/webmasters/answer/6062598?hl=zh-Hans](https://support.google.com/webmasters/answer/6062598?hl=zh-Hans)\n\n","在我做过的很多web项目的seo中，特别喜欢Google的seo，因为真的是速度快而且人性化。最讨厌国内的seo，简直反人类，各种要参与百度的某些计划才能提交爬虫相关，速度慢，要求还贼多。","https://image.xinwei.ltd/image1723430135036.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],778,"google seo,seo,goole seo develop,google seo optimise,google web seo,goole seo优化",{"id":515,"createdAt":516,"title":517,"content":518,"summary":519,"image":520,"uid":136,"user":521,"categoryId":85,"category":522,"subCategoryId":100,"subCategory":523,"comments":524,"status":9,"reason":15,"notice":15,"visitCount":525,"commentCount":112,"keywords":526},135,"2024-11-09T09:53:38.596Z","flutter中如何实现Apple登录","## 一、现有的Apple登录第三方库\n众所周知，当你想要快速实现某一个功能的时候，最快的方法还是要看一下有没有便捷的第三方库，可以极大的提高效率，而flutter第三方库查找的地方就在：[https://pub.dev/](https://pub.dev/)，这个是flutter官方支持库网址。\n\n目前来说，flutter的生态已经非常完善，而且支持的平台也很多，所以大多数项目所需的功能，都是可以找到现成的库来解决的。\n\n对于苹果的授权登录，最流行的库当属：`sign_in_with_apple`，连接是：[https://pub.dev/packages/sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple)，这个库是aboutyou团队开发和维护的库。\n\n## 二、集成Apple login\n1. 在flutter的pubspec.yaml中手动引入，或者通过命令引入：\n- 手动引入：\n```pubspec.yaml\nsign_in_with_apple: ^6.1.0\n```\n![flutter apple登录](https://image.xinwei.ltd/image1731144292470.png)\n- 终端命令集成\n```terminal\nflutter pub add sign_in_with_apple    \n```\n\n2. 自定义你的登录按钮，点击事件中添加以下代码：\n```dart\nimport 'package:sign_in_with_apple/sign_in_with_apple.dart';\n\nclass LoginPage extends StatefulWidget {\n  const LoginPage({super.key});\n\n  @override\n  State\u003CLoginPage> createState() => _LoginPageState();\n}\n\nclass _LoginPageState extends State\u003CLoginPage> {\n  ...\n\n  /// Apple 登录，解析这个credential\n  void login(AuthorizationCredentialAppleID appleID) async {\n    BaseViewModel.showLoadingDialog();\n    DefaultRepository repository = DefaultRepository();\n    var result = await repository.loginApple(\n        appleID.authorizationCode, appleID.identityToken ?? \"\", appleID.userIdentifier ?? \"\");\n    await BaseViewModel.hideLoadingDialog();\n    if (result.isSucc) {\n      handleLoginResult(result);\n    }\n  }\n\n  ...\n  // 登录按钮\n  GestureDetector(\n              onTap: () async {\n                final credential = await SignInWithApple.getAppleIDCredential(\n                  scopes: [\n                    AppleIDAuthorizationScopes.email,\n                    AppleIDAuthorizationScopes.fullName,\n                  ],\n                );\n                login(credential);\n              },\n              child: Container(\n                decoration: const BoxDecoration(\n                  color: Colours.btnEnableColor,\n                  borderRadius: BorderRadius.all(Radius.circular(28)),\n                ),\n                constraints: BoxConstraints.expand(width: sWidth - 64, height: 56),\n                child: const Center(\n                  child: Text(\"Continue with Apple\", style: TextStyle(\n                    color: Colors.black,\n                    fontFamily: 'SF',\n                    fontWeight: FontWeight.w700,\n                  ),),\n                ),\n              ),\n            )\n  ...\n}\n\n```\n3. 将苹果登录获取到的credential通过接口，发送给后端服务器去调用Apple服务。\n4. Apple开发者账号的证书中心网站中，需要配置开发证书和发布证书支持Sign In with Apple的功能。这个直接打开证书配置中心网站操作即可：[https://developer.apple.com/account/resources/](https://developer.apple.com/account/resources/)\n5. 如果想要同时支持flutter打出web、Android的包，那么需要这个证书中心配置service id，这个id用来做clientid。这个在我之前的uni-app项目中有提到，可以参考：[https://www.xinwei.ltd/article/123](https://www.xinwei.ltd/article/123)\n6. 在证书中心需要配置一个key，配置key的操作，也可以参考我之前的文章：[https://www.xinwei.ltd/article/123](https://www.xinwei.ltd/article/123)，key创建成功后，需要下载下来给后端，后端服务需要用到这个文件。\n7. 使用Xcode打开flutter中的iOS工程，选中Runner的target，添加Sign In with Apple功能。\n![sign in with apple](https://image.xinwei.ltd/image1731145116775.png)\n8.完整的代码就是上面贴出来的代码，步骤也很简单，集成进去，然后测试，一切应该都OK的。\n\n到此就结束了，祝集成顺利。\n","本文介绍如何在flutter中实现Apple的第三方登录，非常容易实现，这里简单记录一下。","https://image.xinwei.ltd/image1731144292470.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],720,"flutter apple login,apple login,flutter apple,flutter ios,flutter苹果登录,flutter",{"id":272,"createdAt":528,"title":529,"content":530,"summary":531,"image":532,"uid":136,"user":533,"categoryId":85,"category":534,"subCategoryId":272,"subCategory":535,"comments":536,"status":9,"reason":15,"notice":15,"visitCount":537,"commentCount":112,"keywords":538},"2024-01-13T14:34:43.134Z","小程序 Taro react Error Minified React error #321 白屏报错","### 一、出现的场景：\n1.Taro的编译正常，也能监听代码修改，并且没有报错；\n2.用小程序ide引入并打开dist文件夹，报了如下截图中的错误\n### 二、报错截图：\nError: Minified React error #321\n![](https://image.xinwei.ltd/image_011705156281359.png)\n### 三、问题分析\n这种情况出现的原因可能有很多，每个人遇到的情况可能不一样，主要是要从报错推测问题来正面解决一下。\n\n从截图中的报错来看，是React相关的报错，然后看其中的堆栈报错信息，说是react.production.min.js中的useMemo导出相关有问题，可以看到底部的报错信息是从@taro-之类的某个库报出来的。\n\n那么做个推论，react导出某个函数（截图中指出是useMemo），taro并不识别，可能是taro在使用react框架的过程中没有识别到。但是react框架肯定没问题，taro框架往往也不会有问题，那么不识别的问题，就很有可能是taro版本和react的版本，没有做好版本依赖关系导致的。\n\n所以思路往react和taro的版本依赖上去考虑。\n### 四、尝试和解决\n1.首先检查react和react-dom的版本是否匹配，如果不匹配，那么修改成一致，重新npm install；\n\n2.检查Taro版本，查看package.json中的@tarojs/taro和@tarojs/cli版本是否一致，\n并且检查当前终端中使用的Taro版本:\n```terminal\ntaro --version\n```\n需要保持这3个的版本一致，如果不一致，那么使用以下的2个命令，让项目和终端使用的taro版本保持一致。\n```terminal\ntaro update self [version] // 更新 Taro 开发工具 taro-cli 到指定版本或 Taro3 的最新版本\ntaro update project [version] // 更新项目所有 Taro 相关依赖到指定版本或 Taro3 的最新版本\n```\n并且需要重新npm install。\n\n3.如果仍然不行，那么检查package-lock.json中的taro相关库的版本，是否和package.json中的版本保持一致。\n一个重要需要注意的点就是，taro相关库版本号前面的版本控制符号：\n```\n\"@tarojs/taro\": \"3.5.12\",\n\"@tarojs/cli\": \"^3.5.12\", // 需要去掉前面的^号，保持和tarojs/taro一致\n```\n比如这个cli库前有^这样的大版本控制符号，在npm install的时候，如果3.x系列库有高于3.5的库（比如：3.6.14），那么安装的也就是3.6.14，而不是看到的3.5.12，这样实际上导致taro和cli的版本不一致。\n\n改成一致后，都需要npm install一下。\n\n### 五、小结\n我截图中出现的问题，正是由于方案3中的版本号问题造成的，导致实际install的tarojs/cli是3.6.14（在package-lock.json中可以看到），而和tarojs/taro中3.5.12版本不匹配。\n","一、出现的场景： 1.Taro的编译正常，也能监听代码修改，并且没有报错； 2.用小程序ide引入并打开dist文件夹，报了如下截图中的错误 二、报错截图： Error: Minified React error 321 (https://image.xinwei.ltd/image_011705156281359.png) 三、问题分析 这种情况出现的原因可能有很多，每个人遇到的情况可能不一样，","https://image.xinwei.ltd/image_011705156281359.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":272,"name":274,"parentId":85},[],717,"小程序,taro,minified react error,taro小程序",{"id":540,"createdAt":541,"title":542,"content":543,"summary":544,"image":545,"uid":136,"user":546,"categoryId":85,"category":547,"subCategoryId":186,"subCategory":548,"comments":549,"status":9,"reason":15,"notice":15,"visitCount":550,"commentCount":112,"keywords":551},93,"2024-03-05T17:26:55.36Z","远程ssh到服务器，以及本地电脑和服务端之间的文件传输scp相关操作","**前言**\n这篇文章介绍一下如何在本地电脑上ssh登录到远程服务器，并且实现本地和远程服务器之间的scp文件传输。写一个shell脚本文件，如何处理本地代码产物和远程服务器上的部署。\n\n**准备工作**\n1. 一台云服务器，使用阿里云或者腾讯云等其他公司的云服务器，云服务器我使用的是centos7系列的。\n2. 本地一台Mac电脑，我是使用的Mac电脑，所以和windows电脑上操作可能不太一样，但是如果都在本地安装了ssh和scp环境，那就没啥事。\n3. 云服务器上使用密钥，一般云服务器都可以在服务器的管理台模块找到密钥配置部分。需要生成密钥对，并且绑定到云主机。如下图\n![云服务器密钥](https://image.xinwei.ltd/61709658040939.png)\n4. 生成的私钥会下载到我们的电脑中，需要妥善保管，放在某一个文件夹下面，后面会用到这个私钥文件。\n5. 需要云服务器的ip地址，后面流程会用到。\n6. 有的云服务器在绑定密钥后，会自动屏蔽掉密码登录。如果你需要保留密码登录，那么需要额外设置，下面是流程：\n- 通过ssh方式使用密钥文件登录主机（下面的ssh登录流程有），就可以在终端进入服务器的目录进行操作；\n- 在终端编辑sshd_config文件\n```\nvi /etc/ssh/sshd_config \n```\n![](https://image.xinwei.ltd/11709658750408.png)\n将`PasswordAuthentication`设置为yes（仅仅需要将这个配置项前的#号去掉）\n- 重启主机的ssh服务\n```\nsystemctl restart sshd\n```\n![](https://image.xinwei.ltd/21709658841094.png)\n\n\n## 使用SSH登录远程主机\n打开终端，在终端窗口中输入以下命令:\n1. 方式一：会提示需要密码输入，需要手动输入密码\n// root是主机登录的username，124.xxx.xxx.xxx需要填写你自己的主机ip地址\n```terminal\nssh root@124.xxx.xxx.xxx\n```\n如截图\n![](https://image.xinwei.ltd/51709658391238.png)\n需要手动输入密码\n![](https://image.xinwei.ltd/41709658436271.png)\n\n2. 方式二：使用私钥配合ssh进行免密码登录云主机\n```\nssh -i /Users/hanweixing/xxx.pem root@124.xxx.xxx.xxx\n```\n其中的\"/Users/hanweixing/xxx.pem\"是你在云服务器生成的密钥对私钥文件所在的路径，root是主机登录名，124.xxx.xxx.xxx是主机的ip地址\n\n## 使用scp免输密码的方式将本地文件或文件夹上传到云服务器\n使用scp可以将本地文件/文件夹上传到云服务器，或者从下载云服务器上的文件或者文件夹到本地的某个路径下。\n```\nscp -i /Users/hanweixing/xxx.pem -r output root@124.xxx.xxx.xxx:/root/xxx/output\n```\n- -i 指定私钥文件\n- /Users/hanweixing/xxx.pem 私钥文件路径\n- -r 递归上传指定文件夹下的所有内容\n- output 将要上传到服务器的文件夹\n- :/root/xxx/output 云主机上的某一个路径\n\n## 本地电脑上写一个脚本，上传文件到云服务器主机后，继续让剩余脚本在云主机上执行\n1. 方式一：使用EOF的方式\n```\nssh -i /Users/hanweixing/xxx.pem root@124.xxx.xxx.xxx \u003C\u003C EOF\necho \"当前目录：\"\npwd\necho \"列出所有docker容器：\"\ndocker ps -a\nEOF\n```\n这种方式会在登录主机后，会继续在主机环境下执行EOF包裹的脚本。\n\n2. 方式二：使用单引号括住要继续执行的脚本\n```\nssh -i /Users/hanweixing/xxx.pem root@124.xxx.xxx.xxx 'echo \"当前目录：\"; pwd; docker ps -a'\n```\n这种方式同样也会在登录云主机后，继续执行单引号内部包裹的脚本。\n\n3. 方式三：当然是在云服务器上创建一个bash脚本，然后通过ssh登录主机去执行这个脚本。\n\nssh登录云服务器和scp操作本地和云服务器之间的文件传输就介绍这么多，希望对大家有所帮助。\n\n\n","这篇文章介绍一下如何在本地电脑上ssh登录到远程服务器，并且实现本地和远程服务器之间的scp文件传输。写一个shell脚本文件，如何处理本地代码产物和远程服务器上的部署。","https://image.xinwei.ltd/61709658040939.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":186,"name":188,"parentId":85},[],709,"mac ssh,ssh terminal,ssh command,ssh login,ssh命令,ssh指令",{"id":553,"createdAt":554,"title":555,"content":556,"summary":557,"image":15,"uid":136,"user":558,"categoryId":85,"category":559,"subCategoryId":560,"subCategory":561,"comments":563,"status":9,"reason":15,"notice":15,"visitCount":564,"commentCount":112,"keywords":565},102,"2024-03-22T12:37:31.112Z","Mysql的报错：Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\xA4\\xAD =...' for column 'content' at row 1","## 问题\n当往mysql数据库中插入emoj等表情数据时，有时候会出现以下的报错，这里简要跟大家分享一下我的解决方案。\n> Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\xA4\\xAD =...' for column 'content' at row 1\n\n这里的`\\xF0\\x9F\\xA4\\xAD`其实指的是我传入的emoj表情符号。\n\n这个问题通常是因为数据库的列 `content` 没有被配置为接受 `UTF-8` 编码的字符集。因为utf8一般使用3个字节存储，但是utf8mb4采用4哥字节存储。那么解决这个方案，就需要更改字符集。\n解决这个错误的方法通常包括以下几个步骤。\n\n## 一、检查字符集和校对规则：\n确保数据库、表以及 `content` 列都使用了 `utf8mb4` 字符集。`utf8mb4` 字符集能够支持更多的 `Unicode` 字符，包括 emoji 表情符号。\n```mysql\nSHOW CREATE DATABASE your_database_name;  \nSHOW CREATE TABLE your_table_name;  \nSHOW FULL COLUMNS FROM your_table_name;\n```\n查看它们是不是utf8mb4，如果不是，那么就更改它们。\n\n## 二、修改数据库和表的字符集（任何修改请确保先在本地debug环境操作成功后再在生产环境进行修改，防止发生故障）：\n修改表的字符集:\n```mysql\nALTER DATABASE your_database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;  \nALTER TABLE your_table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n```\n\n## 三、修改列的字符集：\n检查列content 列的字符集不是 utf8mb4，你还需要修改它。\n\n```mysql\nALTER TABLE your_table_name MODIFY content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n```\nTEXT 为你列的实际数据类型。\n\n## 四、检查连接字符集：\n确保后台接口程序连接到数据库时，也使用了 utf8mb4 字符集。在连接字符串中可能需要指定字符集，如：\n\n```mysql\njdbc:mysql://your_host:your_port/your_database?useUnicode=true&characterEncoding=UTF-8&useSSL=false\n```\n\n或者在你的 MySQL 配置文件中设置：\n```mysqld\ncharacter-set-server=utf8mb4  \ncollation-server=utf8mb4_unicode_ci\n```\n\n和/或在 client 部分设置： \n```mysqld\ndefault-character-set=utf8mb4\n```\n\n## 五、重启数据库服务：\n有时候需要重启 MySQL 服务来确保所有的变更都生效。\n\n按照这些步骤操作后，就不会出现emoj字符集插入失败的情况了。其他情况，欢迎交流。\n","当往mysql数据库中插入emoj等表情数据时，有时候会出现这样的报错：Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\xA4\\xAD =...' for column 'content' at row 1。这里简要跟大家分享一下我的解决方案。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},38,{"id":560,"name":562,"parentId":85},"数据库",[],694,"mysql,mysql emoji,mysql manual,mysql handle,mysql操作,mysql表情",{"id":9,"createdAt":567,"title":568,"content":569,"summary":570,"image":15,"uid":136,"user":571,"categoryId":85,"category":572,"subCategoryId":9,"subCategory":573,"comments":575,"status":9,"reason":15,"notice":15,"visitCount":576,"commentCount":47,"keywords":577},"2024-01-02T15:54:06.348Z","浏览器跨域-Request header field userid is not allowed ...","# 一、问题背景\n部署后端项目，在支持浏览器的接口请求时，由于浏览器的同源策略，特别容易遇到跨域问题。\n比如浏览器访问的网页的域名是：`https://www.baidu.com`，但是在这个网页中通过网络框架请求一个接口：`https://api.baidu.com/api/recommend`时，由于**同源策略**（这里不作特别解释，网上解释很多），可以知道网页域名和接口域名不同源，那么这个接口在请求时，浏览器会报**cors error**（跨域错误）。\n# 二、问题描述：\n`Access to XMLHttpRequest at 'https://api.xxx.com/api/recommend/query' from origin 'https://www.xxx.com' has been blocked by CORS policy: Request header field userid is not allowed by Access-Control-Allow-Headers in preflight response。`\n# 三、知识点\n1. 浏览器有同源策略，不会允许非同源请求通过（但可以解决）。\n2. 像是postman或者原生app中发起请求，不会有同源策略的问题。\n3. 如果网页域名和网页中的接口请求的域名非同源，解决跨域问题，需要后端接口设置允许跨域，那么浏览器也是会让服务器允许跨域的。\n4. 浏览器的网页在发起跨域请求时，一般首先会发一个options的预检preflight请求，检查服务端接口是否允许跨域，如果服务端接口设置允许跨域，那么浏览器会再次发一个正常的请求，得到正确的响应。\n# 四、问题解决\n如二的问题描述，是在预检请求的时候，发现`request header`中有一个`userId`的字段，服务端接口并没有为这个`userId`字段设置：`Access-Control-Allow-Headers`，而且这个字段不是约定俗成的`header` 字段，所以浏览器将这次请求定义为跨域失败。\n所以如果前端`userId`字段非传不可的话，那么就让后端接口在跨域时设置一下：\n```language\n\"Access-Control-Allow-Headers\", \"userId\"\n```\n\n\n\n\n\n","部署后端项目，在支持浏览器的接口请求时，由于浏览器的同源策略，特别容易遇到跨域问题。\n比如浏览器访问的网页的域名是：https://www.baidu.com，但是在这个网页中通过网络框架请求一个接口：https://api.baidu.com/api/recommend时，由于同源策略（这里不作特别解释，网上解释很多），可以知道网页域名和接口域名不同源，那么这个接口在请求时，浏览器会报cors ",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":9,"name":574,"parentId":85},"浏览器",[],648,"前端,跨域,浏览器跨域,request header,CORS policy",{"id":579,"createdAt":580,"title":581,"content":582,"summary":583,"image":15,"uid":136,"user":584,"categoryId":85,"category":585,"subCategoryId":419,"subCategory":586,"comments":587,"status":9,"reason":15,"notice":15,"visitCount":588,"commentCount":9,"keywords":589},84,"2024-02-21T14:33:44.408Z"," error An unexpected error occurred: \"https://registry.npmjs.org/@react-native-community%2 masked-view: ETIMEDOUT\".","在开发react native项目时，使用\n```\nyarn install\n```\n或者\n```\nnpm install\n```\n去下载安装依赖时，出现报错：\n>  error An unexpected error occurred: \"https://registry.npmjs.org/@react-native-community%2 masked-view: ETIMEDOUT\".\n\n更改淘宝的镜像源也依然无济于事。\n\n可以尝试使用以下的命令，去除npm相关的代理配置：\n```\nnpm config delete proxy\nnpm config delete http-proxy\nnpm config delete https-proxy\n```\n\n以上的几行命令是可以解决我当前的问题的，诸君可以试一下。","当在开发react native时，有时候使用yarn install或者npm install会出现time out的报错，比如：error An unexpected error occurred: \"https://registry.npmjs.org/@react-native-community%2 masked-view: ETIMEDOUT\".",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],620,"react native,react native issue,react native开发,react native yarn install,react native问题",{"id":591,"createdAt":592,"title":593,"content":594,"summary":595,"image":596,"uid":597,"user":598,"categoryId":9,"category":603,"subCategoryId":87,"subCategory":605,"comments":607,"status":9,"reason":15,"notice":15,"visitCount":608,"commentCount":9,"keywords":609},96,"2024-03-17T08:24:51.255Z","我认为的人生意义","在漫长的人生旅途中，我们时常会陷入对人生的思考：究竟什么是人生的意义？我们应该追求什么？这些问题困扰着每一个人，让我们在迷茫中寻找答案。\n![](https://image.xinwei.ltd/images%20%285%291710663795081.jpeg)\n\n人生如同一场旅程，每个人都在寻找属于自己的目的地。在这个过程中，我们会经历欢笑与泪水，成功与失败，得到与失去。正是这些经历，让我们逐渐认识到人生的多样性和复杂性，也让我们更加珍惜每一个瞬间。\n![](https://image.xinwei.ltd/images%20%283%291710663819543.jpeg)\n\n对于人生的意义，每个人都有自己的理解。有人认为，人生的意义在于追求幸福，让自己和周围的人都能过上美好的生活。有人认为，人生的意义在于实现自己的价值，为社会做出贡献，让后人铭记。还有人认为，人生的意义在于体验，感受世间的美好与痛苦，让自己的生命变得丰富多彩。\n\n然而，无论我们如何定义人生的意义，都离不开一个核心：追求。追求是人生的动力，它推动着我们不断前行，去探索未知的领域，去实现自己的梦想。没有追求的人生如同一潭死水，失去了活力和生机。\n\n在追求的过程中，我们需要学会平衡。平衡好物质与精神的需求，平衡好个人与集体的利益，平衡好现实与理想的差距。只有找到适合自己的平衡点，我们才能在追求的过程中保持内心的宁静与满足。\n\n![](https://image.xinwei.ltd/images%20%284%291710663876781.jpeg)\n\n此外，我们还需要学会珍惜。珍惜身边的人，珍惜每一次机会，珍惜每一个瞬间。因为生命中的每一个瞬间都是独一无二的，错过了就再也无法找回。当我们学会珍惜时，就会发现生活中的美好无处不在。\n\n总之，人生是一场充满挑战和机遇的旅程。在追求人生意义的过程中，我们需要保持坚定的信念和勇气去面对一切困难与挫折。同时，我们也要学会平衡和珍惜，让自己的生命变得更加充实和有意义。最终，我们会发现，人生的意义其实就在于这个追求的过程本身，它让我们的生命焕发出无尽的光彩。","在漫长的人生旅途中，我们时常会陷入对人生的思考：究竟什么是人生的意义？我们应该追求什么？这些问题困扰着每一个人，让我们在迷茫中寻找答案。 (https://image.xinwei.ltd/images%20%285%291710663795081.jpeg) 人生如同一场旅程，每个人都在寻找属于自己的目的地。在这个过程中，我们会经历欢笑与泪水，成功与失败，得到与失去。正是这些经历，让我们逐渐认识","https://image.xinwei.ltd/images%20%285%291710663795081.jpeg",525978185445445,{"phone":599,"userId":597,"nickName":600,"vipType":9,"avatar":601,"sign":15,"createdAt":602},"16100000003","上班墨鱼","https://image.xinwei.ltd/bbd06233d9b5c67c9f8ddd35b63cb81f1710561355517.jpeg","2024-03-16T00:30:44.932Z",{"id":9,"name":604},"文学",{"id":87,"name":606,"parentId":9},"随笔",[],553,"人生,人生感悟,迷茫",{"id":611,"createdAt":612,"title":613,"content":614,"summary":615,"image":15,"uid":136,"user":616,"categoryId":85,"category":617,"subCategoryId":144,"subCategory":618,"comments":619,"status":9,"reason":15,"notice":15,"visitCount":620,"commentCount":112,"keywords":621},125,"2024-08-24T09:23:15.721Z","git cz不生效的问题如何解决","## 一、背景\n出于电脑系统升级，或者清理缓存，或者重新安装npm时，会遇到git cz命令失败的情况，而且git cz命令失效后，也不会在终端有任何输出。\n\n## 二、解决方案\n一般都是因为commitizen不存在或者被删除的问题导致的。所以需要根据步骤，一步一步去排查一下。\n\n### 2.1 确认是否已经全局安装了commitizen：\n\n```terminal\nnpm install -g commitizen\n```\n\n### 2.2 确认是否已经在项目中安装了cz-conventional-changelog：\n\n```terminal\nnpm install --save-dev cz-conventional-changelog\n```\n\n### 2.3 确认package.json中是否有以下配置：\n\n```package.json\n\"config\": {\n  \"commitizen\": {\n    \"path\": \"node_modules/cz-conventional-changelog\"\n  }\n}\n```\n\n### 2.4 确认是否在命令行中正确使用`git cz`。\n\n如果以上步骤都正确无误，尝试删除`node_modules`文件夹和`package-lock.json`文件，然后重新运行`npm install`来重新安装依赖。\n\n如果以上步骤仍然无法解决问题，可能需要检查是否有全局路径问题或者环境变量配置问题。\n\n### 2.5 尝试使用npx\n可以尝试在项目目录中直接使用npx来调用commitizen：\n\n```terminal\nnpx commitizen init cz-conventional-changelog --save-dev --save-exact\n```\n这条命令会初始化`commitizen`并且安装`cz-conventional-changelog`作为默认的提交规范。\n\n使用`npx`可以直接运行项目中的`node_modules`中的二进制文件，而不用全局安装。\n\n但是建议全局安装吧。\n\n希望对大家有帮助。","针对项目的git管理，在前端项目中，我们经常会使用commitizen工具来进行管理，一个是可以配合使用lerna来管理多个web项目，另外也可以清晰的提交git记录，十分方便。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],520,"git cz,git commit cz,git cz command,git cz命令,git cz配置,git cz config",{"id":623,"createdAt":624,"title":625,"content":626,"summary":627,"image":628,"uid":136,"user":629,"categoryId":85,"category":630,"subCategoryId":201,"subCategory":631,"comments":632,"status":9,"reason":15,"notice":15,"visitCount":633,"commentCount":112,"keywords":634},132,"2024-11-03T15:43:25.935Z","Koa后端小项目编程记录 - 项目搭建","## 前言\n对于小型it项目，没必要使用市面上成熟的后端框架来做。之前使用node开发后端项目时，使用了阿里的Midwayjs来做过项目，使用上确实很流畅丝滑，但是我现在项目不大，就从头开始写一个，成册记录。\n\n本篇介绍koa工程的基础搭建。\n\n## 一、前期准备\n1. 电脑上安装node环境；\n2. 使用vscode或者其他软件作为编辑器（本项目使用vscode）；\n3. 包管理器命令随意，既可以npm，也可以选择使用yarn、cnpm都行；\n4. 掌握typescript知识也要熟悉typescript的语法；\n5. 知道koa如何使用；\n6. 要有一点shell脚本知识；\n7. 本项目以Mac电脑环境做演示说明。\n\n## 二、项目搭建\n1. 创建文件夹，工程代码都放在这个文件中；\n2. 打开终端，使用`cd`命令进入到刚创建的空文件路径下，使用以下命令创建一个`package.json`文件 ：\n```terminal\nnpm init\n```\n![npm init项目](https://image.xinwei.ltd/image1730646296209.png)\n如上图所示，会生成包管理文件`package.json`文件，可以一步步设置项目的基本描述信息。\n\n## 三、入口文件\n创建一个`app.ts`文件，用作项目的入口文件，后面项目启动运行都从这个入口文件开始。\n\n## 四、引入typescript支持\n引入`typesript`依赖，以及`tsc`编译器，`tsc`是将`ts`文件编译成`js`的编译器，`ts-node`依赖库可以直接编译ts文件：\n```terminal\nnpm install --save-dev typescript ts-node @types/node\n```\n\n\n## 五、引入eslint代码格式库\n`eslint`支持对typescript语法的lint规则，具体配置可以参考eslint官网：\n[https://typescript-eslint.io/getting-started](https://typescript-eslint.io/getting-started)\n```terminal\nnpm install --save-dev eslint @eslint/js @types/eslint__js typescript typescript-eslint\n```\n\n## 六、创建tsconfig.json文件\n1. `tsconfig.json`文件可以定义ts编译的一些规则，可以通过以下终端命令来生成：\n```terminal\nnpx tsc --init\n```\n（如果电脑中没有安装`npx`工具的话，可以通过npm安装一下）\n全局安装`npx`的终端命令：\n```terminal\nnpm install -g npx\n```\n2. 修改`tsconfig.json`文件，设置一些基本属性：\n![tsconfig.json文件](https://image.xinwei.ltd/image1730647679245.png)\n`rootDir`设置ts文件所在的最外层目录；\n其他如有任何需要，可以逐步修改文件中的配置。\n\n## 七、安装koa以及koa-router\n```terminal\nnpm install koa koa-router \n```\n\n## 八、定义script脚本执行命令\n使用编辑器打开项目文件夹，点击并且编辑`package.json`文件：\n```package.json\n...\n\"scripts\": {\n    \"dev\": \"ts-node app.ts\",\n    \"build\": \"tsc\"\n  },\n...\n```\n![package.json文件摘要](https://image.xinwei.ltd/image1730648232747.png)\n在\"scripts\"配置项中，写入如图所示的配置，那么执行文件时，就可以直接执行：\n`npm run dev`来启动项目。\n\n在根目录下创建一个src的空文件夹，后续所有的文件将在src文件夹下创建开始。\n\n## 九、工程目录\n![koa工程目录](https://image.xinwei.ltd/image1730648445149.png)\n\n至此，基础的工程搭建算是完成。\n\n## 十、测试运行\n测试工程时，可以在`app.ts`中打印语句进行测试。\n```app.ts\nconsole.log(\"Hello world!\");\n```\n然后在终端执行脚本命令：\n```terminal\nnpm run dev\n```\n如果在终端看到输出`Hello world!`，那么即表示工程的基础搭建已经完成。\n\n第一章到此。\n\n","最近开发一个小型it项目，打算使用koa框架做服务端，准备做成一个实战记录，后期可能会整理成册，希望对于想要学习或者参考的同学有帮助。","https://image.xinwei.ltd/image1730646296209.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":201,"name":203,"parentId":85},[],502,"koa,node koa,koa后端,koa服务端开发,服务端开发,node开发,node服务端,node项目,koa项目",{"id":636,"createdAt":637,"title":638,"content":639,"summary":640,"image":641,"uid":78,"user":642,"categoryId":9,"category":643,"subCategoryId":644,"subCategory":645,"comments":647,"status":9,"reason":15,"notice":15,"visitCount":648,"commentCount":112,"keywords":649},95,"2024-03-17T08:18:27.663Z","我的故乡","岁月如流，人生如梦。漫步于故土之上，不禁让我陷入无尽的遐想。这是一片充满生机与希望的土地，它孕育了我，也承载了我所有的记忆和情感。\n\n![](https://image.xinwei.ltd/images%20%281%291710663385331.jpeg)\n\n初夏的微风轻拂过田野，金黄的麦浪翻滚着，仿佛是大地的欢笑。我沿着熟悉的小径，踏过青石板，穿过杨柳依依的小河，来到了童年的乐园——村头的老槐树下。这里曾是我们嬉戏的天堂，那时候的我们，无忧无虑，心怀梦想，渴望着远方的世界。\n\n槐树依旧屹立在那里，只是岁月已经在它的身上留下了痕迹。我抚摸着粗糙的树干，感受着岁月的沧桑。它见证了我们的成长，也见证了村庄的变迁。如今，儿时的伙伴们都已离去，追寻着自己的梦想，只有这棵老槐树，依旧默默地守护着这片土地。\n\n漫步在乡间的小道上，我看到了那些熟悉的景象：绿油油的稻田，袅袅炊烟，还有那些忙碌的身影。农民们挥舞着锄头，汗水浸湿了衣衫，但他们的脸上却洋溢着丰收的喜悦。这是他们辛勤劳作的成果，也是大地对他们的馈赠。\n\n![](https://image.xinwei.ltd/images%20%282%291710663474822.jpeg)\n\n\n走过田野，我来到了村尾的古井旁。井水清澈见底，井壁上长满了青苔。我想起了小时候，每当夏日炎炎，我们都会来到这里，打上一桶水，畅饮一番。那时候的井水，是那么的甘甜，仿佛能消除所有的疲惫和烦恼。\n\n夕阳西下，我站在高处，俯瞰着整个村庄。那些错落有致的房屋，那些蜿蜒曲折的小道，还有那些郁郁葱葱的树木，都构成了一幅美丽的画卷。这是我的家乡，是我永远的牵挂和眷恋。无论我走到哪里，这片土地都会在我心中占据着最重要的位置。\n![](https://image.xinwei.ltd/images1710663426493.jpeg)\n\n\n岁月流转，故乡依旧。漫步在故土上，我感受到了岁月的沉淀和生命的延续。这里的一切都让我感到亲切和温暖，仿佛回到了母亲的怀抱。我知道，无论未来会面临怎样的挑战和困难，只要我心中有故乡，就有无穷的力量和勇气去面对一切。","岁月如流，人生如梦。漫步于故土之上，不禁让我陷入无尽的遐想。这是一片充满生机与希望的土地，它孕育了我，也承载了我所有的记忆和情感。","https://image.xinwei.ltd/images%20%281%291710663385331.jpeg",{"phone":77,"userId":78,"nickName":79,"vipType":9,"avatar":80,"sign":15,"createdAt":81},{"id":9,"name":604},33,{"id":644,"name":646,"parentId":9},"情感",[],479,"散文,随笔,文章,30岁,人生感悟",{"id":118,"createdAt":651,"title":652,"content":653,"summary":654,"image":655,"uid":656,"user":657,"categoryId":9,"category":662,"subCategoryId":87,"subCategory":663,"comments":664,"status":9,"reason":15,"notice":15,"visitCount":665,"commentCount":112,"keywords":666},"2024-03-22T07:14:57.823Z","我的2024","2024是伤心的开始，从年前裁员开始，到年后找工作，一路心酸，背着房贷、车贷，还有孩子教育，老人养老，桩桩件件有压力的事情落到头上。整个人变得焦虑，每天在网上寻找一个差不多的工作，却总是没有结果。这样的情况已经从年后就开始了，也不知道这个世界怎么了，为什么对一个普通上班人的恶意这么大呢？\n\n![悲伤.jpg](https://image.xinwei.ltd/52d135537bbedfdd0f9918944af901f31711090325107.jpg)\n\n这两年看很多人在自媒体上挣了钱，感觉他们挣钱是真的好容易，动不动就是几十上百万，有的直接都买楼办公，心里真的很羡慕。想想我自己从小上学，长大毕业进入社会，找了一个普通的工作，工资从月薪3200开始，一个普通人的一生就开始了。\n\n![难过.jpg](https://image.xinwei.ltd/c2614919eceaa794ea66fcb4024a97091711090531944.jpeg)\n\n\n后面自己也经历过很多小公司，都是做一些基础的工作，生活也就是一滩平静的水，没有激起任何浪花。\n![总有不如意.jpg](https://image.xinwei.ltd/f9db8beddc550cc187b383501eab5aa01711090669685.jpg)\n\n后面也在父母的帮助下，在小县城买了房，也跟现在的老婆结了婚，普通平淡的生活也增添了很多色彩，在这里感谢我老婆为我们生了一个可爱的女儿，怀孕生产真的是很辛苦的，大着肚子的夏天，生孩子时候的痛苦，所以我对我老婆充满了感激。\n![爱老婆.jpg](https://image.xinwei.ltd/4cc091feb6fac2b91ada6de9a0d1a7821711090915428.jpg)\n\n本以为生活会继续这样平淡的过下去，做着普通的工作，有县城的房子，有贤惠的老婆有可爱的女儿，不求大富大贵，但求一家人生活无忧，平平安安就好。可是去年，公司的盈利不好，说是要节约人力成本，一开始就有2个关系要好的同事就被裁员了，从那开始，我就有了被裁员的担心，后来果不其然，在1个月之内，我也被约谈了。让我做好交接工作，年前让我主动辞职。尽管我不情愿，跟主管聊了好几次，但是仍然拗不过公司的决定。最后的结果，还是没有改变，于是在1月上旬，我就从公司难过的走了出去。\n![顶不住.jpg](https://image.xinwei.ltd/54eecb79d9e153cd37fbf877674b050e1711091243650.jpeg)\n\n被裁员还没有补偿，这大概是多数中年人在职场上的常态吧。那个时候我都不敢跟媳妇说这些事情，怕对不起她，让她担心，所以我也积极在网上和朋友之间打听新的工作机会。但是现实总是那么残酷，即使我如何积极的找工作，依然没有找到和被裁之前的薪资。面对着每个月的开销用度，心里既着急也无赖。可能有的人觉得有个活就应该去做，但是你们能理解一个月就几千块工资的人，再降薪是多么可怕的一件事吗？但是我又无可奈何。\n![无话可说.jpg](https://image.xinwei.ltd/168717e2939a765bafa1607410010c641711091598337.jpeg)\n\n所以今天来这里发泄一下，如果接下来的这个小公司还是给不了之前的薪资，我也会接了，因为真的已经熬不住了。。。\n\n![祝福.jpg](https://image.xinwei.ltd/77b0deef9ebbc2514b216e253eb2658f1711091669889.jpg)\n\n\n\n","有感而发，来来来，已经被生活所伤的兄弟姐妹们都来集合了，看看能不嫩抱团取暖","https://image.xinwei.ltd/52d135537bbedfdd0f9918944af901f31711090325107.jpg",526827888050245,{"phone":658,"userId":656,"nickName":659,"vipType":9,"avatar":660,"sign":15,"createdAt":661},"16100000019","陌上人如玉","https://image.xinwei.ltd/77b0deef9ebbc2514b216e253eb2658f1711091669889.jpg","2024-03-18T10:08:11.857Z",{"id":9,"name":604},{"id":87,"name":606,"parentId":9},[],478,"生活,努力生活,生活感悟,沟通",{"id":668,"createdAt":669,"title":670,"content":671,"summary":672,"image":673,"uid":136,"user":674,"categoryId":85,"category":675,"subCategoryId":676,"subCategory":677,"comments":679,"status":9,"reason":15,"notice":15,"visitCount":680,"commentCount":112,"keywords":681},14,"2024-01-14T14:35:13.333Z","go-micro - 安装及可能遇到的问题","### 一、安装\n#### 1. 安装micro和go-micro的command line工具\n以用于终端操作\nmicro的github：[链接](https://micro.dev/getting-started#install)\ngo-micro的github：[链接](https://github.com/go-micro/cli)\n终端操作：\n```terminal\n// micro\ngo install github.com/micro/micro/v3@latest\n\n// go-micro\ngo install github.com/go-micro/cli/cmd/go-micro@latest\n```\n#### 2. 现有的go项目中引入安装\n```terminal\n# 安装go-micro\ngo get -u -v github.com/micro/go-micro\n\n# 安装工具\ngo get -u -v github.com/micro/micro\n\n# 安装protobuf\ngo get -u google.golang.org/protobuf\ngo get -u github.com/micro/protoc-gen-micro\n```\n#### 3. docker中安装micro\n根据个人习惯吧，如果适应了在docker的容器进行操作，可以使用docker去pull micro的镜像，在容器中玩一下。\n我个人目前习惯于在服务器上使用docker去部署应用，本地使用docker桌面版来开发和测试。\n对于本地进行go开发，建议直接使用1中的方式安装cli。\n具体安装步骤，直接看docker hub的官网：\nmicro: *https://hub.docker.com/r/micro/micro*\ngo-micro: *https://hub.docker.com/r/yam8511/go-micro* （这个是star比较多的一个，暂无officail版的）\n\n### 二、检查micro和go-micro\n#### 1. 终端检查micro和go-micro命令是否成功\n```terminal\n// 检查micro，直接输入micro\nmicro\n\n// 检查go-micro，直接输入go-micro\ngo-micro\n```\n\n正常情况下显示：\n![](https://image.xinwei.ltd/image_11705242749456.png)\n\n\n#### 2. 可能出现的问题：\n1. zsh: command not found: micro\n![](https://image.xinwei.ltd/image_21705242798268.png)\n\n解决方案：（需要把micro和go-micro的命令文件暴露给zsh终端）\n- 进入自己的GOBIN配置目录，检查是否已经有了micro和go-micro的可执行文件，一般情况下都会有的\n```terminal\n// 获取GOBIN目录或者GOPATH目录，GOPATH中的bin文件夹一般设置为GOBIN\ngo env GOBIN \n// 或者在GOPATH下的bin目录中找\ngo env GOPATH\n```\n![](https://image.xinwei.ltd/image_31705242840035.png)\n\n- 既然已经有了这2个可执行文件，那么需要暴露一下可执行文件的路径，由于我使用的是zsh终端，那么配置文件就是我当前电脑用户下的.zshrc这个隐藏文件中。那么添加可执行文件的路径：\n```terminal\nexport PATH=${PATH}:$(go env GOPATH)/bin\n```\n\n截图\n![](https://image.xinwei.ltd/image_41705242875948.png)\n\n- 更新.zshrc文件, 之后再重担执行一下micro和go-micro命令验证一下即可\n```terminal\n// 终端执行一下命令，使配置生效\nsource .zshrc\n```\n\n后续再补充.\n","一、安装 1. 安装micro和go-micro的command line工具 以用于终端操作 micro的github：[链接](https://micro.dev/getting-startedinstall) go-micro的github：[链接](https://github.com/go-micro/cli) 终端操作： terminal // micro go install git","https://image.xinwei.ltd/image_11705242749456.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},16,{"id":676,"name":678,"parentId":85},"Golang",[],477,"go-micro,golang,go-micro安装,micro和go-micro,go微服务,微服务",{"id":683,"createdAt":684,"title":685,"content":686,"summary":687,"image":688,"uid":136,"user":689,"categoryId":85,"category":690,"subCategoryId":47,"subCategory":691,"comments":692,"status":9,"reason":15,"notice":15,"visitCount":693,"commentCount":112,"keywords":694},156,"2025-03-16T05:26:33.898Z","在iOS中使用Bugly上传DSYM文件","如何使用Bugly的方法，在Bugly的官网其实已经有记录和说明：[https://bugly.tds.qq.com/docs/tutorial/symbol/tool/#4-使用工具上传](https://bugly.tds.qq.com/docs/tutorial/symbol/tool/#4-使用工具上传)，这里根据我的使用经验，做一个简单的总结。\n\n## 一、获取App的AppId、AppKey\n入口\n![appid entry](https://image.xinwei.ltd/image1742088780180.png)\n\nApp ID、App Key\n![app key](https://image.xinwei.ltd/image1742088831676.png)\n\n后面会用到。\n\n## 二、下载bugly的dsym上传工具包\n印象中早期的dsym是不需要手动上传的，后来改成了必须要用工具上传，不过也简单，直接去下载：[https://bugly.qq.com/v2/downloads](https://bugly.qq.com/v2/downloads)\n\n![bugly symbol tool download](https://image.xinwei.ltd/image1742089244050.png)\n\n解压Zip包得到：\n![unzip tool](https://image.xinwei.ltd/image1742089306684.png)\n\n\n\n## 三、检查本地的java环境\n终端运行以下命令：\n```terminal\njava --version\n```\n检查是否有以下类似输出：\n![java version](https://image.xinwei.ltd/image1742089653781.png)\n\n如果没有，那么就要在本地安装java环境，大家可以直接去oracle下载安装包：[https://www.oracle.com/hk/java/technologies/downloads/](https://www.oracle.com/hk/java/technologies/downloads/)\n\n安装的Java路径一般为：\n```terminal\nwhereis java\n```\n![java path](https://image.xinwei.ltd/image1742090044108.png)\n\n\n## 四、使用jar包和java命令上传dsym\n将dsym文件和bugly的jar包可以放在同一个目录下面，然后通过终端，cd进入到jar包所在的目录，执行以下java命令：\n```terminal\njava -jar buglyqq-upload-symbol.jar -appid 4xxx -appkey 4xxx -bundleid com.xxx  -version 1.0.0 -platform IOS -inputSymbol xxx-AppStore.app.dSYM\n```\n所需的appid、appkey在第一步就获取到了，\nbundle id和version就是你iOS包中的设置；\ndsym就是在同一个目录下的文件。\n\n执行完成后，可以从终端得到上传的结构：\n```terminal\n##[info]retCode: 200 response message: {\"statusCode\":0,\"msg\":\"success\",\"uploadReqID\":\"a53fd62f6e-ab4b2b2a-b074-4ecd-b798-4d1d9458ba36\"}\n```\n\n或者你可以到bugly的符号表管理中看刚上传的文件：\n![symbol manage](https://image.xinwei.ltd/image1742089003834.png)\n\n![dsym symbol list](https://image.xinwei.ltd/image1742102741483.png)\n\n以上就是这些了。\n\n","这篇文章介绍了如果在iOS开发中使用了腾讯的Bugly工具库，那么如何上传我们的app的dsym文件的方法。","https://image.xinwei.ltd/image1742088780180.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],473,"ios dsym,ios bugly,bugly dsym,upload dsym,dsym,bugly",{"id":696,"createdAt":697,"title":698,"content":699,"summary":700,"image":701,"uid":136,"user":702,"categoryId":85,"category":703,"subCategoryId":47,"subCategory":704,"comments":705,"status":9,"reason":15,"notice":15,"visitCount":706,"commentCount":112,"keywords":707},106,"2024-04-02T16:49:01.483Z","Objective-c对象&类&元类&内存对齐等的知识点","## 一、概览\n### 1.1 类的本质\n是结构体。\n\nobjc2.0源码之前，使用的是objc_class结构体：\n![老objc_class.png](https://image.xinwei.ltd/image1712075563167.png)\nobjc2.0源码新使用的objc_class结构体:\n![新objc_class.png](https://image.xinwei.ltd/image1712075678950.png)\n\n\n- clang\n将代码编译成c++的代码使用clang：\n```clang\nclang -rewrite-obj main.m -o main.cpp\n```\n\n\n- xcrun\n使用xcrun指定sdk进行输出c++代码\n```xcrun\nxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx源文件 -o 输出的c++文件名\n```\n\n\n### 1.2 获取对象的内存大小\n在runtime的`\u003Cobjc/runtime.h>`中，定一个了一个`class_getInstanceSize`的函数，可以获取类的实例的大小：\n![class_getInstanceSize.jpg](https://image.xinwei.ltd/11712074108958.jpg)\n\n![class_getInstanceSize使用示例.jpg](https://image.xinwei.ltd/21712074148927.jpg)\n\n\n从图中可以看出，系统给对象分配了***16***个字节，但是对象只占了***8***个字节。\n- `class_getInstanceSize`获取的是内存对齐后的成员变量相加后所占用的大小\n- `malloc_size`获取的是系统给对象开辟内存的大小\n\n\n> oc的runtime源码中要求所有的对象开辟空间至少是16字节。\n> \n![oc->instanceSize.jpg](https://image.xinwei.ltd/31712074260364.jpg)\n\n\n### 1.3 实例对象探究\n结构体的成员在内存中是连续存储的。\n![对象内存结构.jpg](https://image.xinwei.ltd/41712074471516.jpg)\n如图所示，示例对象中存储了isa指针和成员变量的值。\n\n### 1.4 内存对齐\noc中规定对象最小开辟空间是16个字节。\n\n即使没有这个约束，那么还有一个内存对齐。\n\n**内存对齐**：结构体的大小必须是最大成员大小的倍数。\n![内存对齐.jpg](https://image.xinwei.ltd/51712074608060.jpg)\n\n针对stu，其实是这样理解：isa是8个字节，int占4个字节，加起来是12，但是由于内存对齐规则，所以需要是8的倍数，那么接近12的8的倍数，就是16。\n\niOS系统的要求是，malloc分配出来的内存空间，一定是16（buket size的最大值是256）的倍数。\n### 1.5 对象实例中的存储\n实例中存储isa指针和成员变量的值，没有方法。\n\n### 1.6 sizeof\n会在编译器确定传入的值是什么类型，比如，如果是指针类型，直接替换成8，如果是int类型，那么替换成4.\n\n## 二、示例解读\n### 2.1 如下示例\n![内存大小探究.jpg](https://image.xinwei.ltd/61712074692488.jpg)\n\n这个示例中，结构体的class_getInstanceSize大小是24，但是malloc_size是32。\n![内存大小探究2.jpg](https://image.xinwei.ltd/71712074740173.jpg)\n\n解释：iOS系统的要求是，malloc分配出来的内存空间，一定是16（buket size的最大值是256）的倍数。instanceSize只需要24个字节就可以存储，但是分配内存时又要是16的倍数。\n\n## 三、类对象\n### 3.1 获取类对象\n```oc.m\nNSObject *obj = [[NSOject alloc] init];\n\nClass objCls = [obj class];\n\nClass objCls1 = [NSObject class];\n\nClass objCls2 = object_getClass(obj);\n```\n\n每个类在内存中有且只有一个class对象。\n\n### 3.2 类对象中存储的信息\n- isa指针\n- superclass指针\n- 类的属性信息、类的对象方法信息\n- 类的协议信息、类的成员变量信息\n\n\n## 四、元类对象\n### 4.1 获取元类对象\n```oc.m\nClass metaCls = object_getClass([NSObject class]);\n\nClass metaCls1 = object_getClass([obj class]);\n```\n\n每个类在内存中有且只有一个meta-class对象。\n### 4.2 元类中存储的数据\n- isa指针\n- superclass指针\n- 类的类方法信息\n\n### 4.3 判断是否是元类对象\n```oc.m\nclass_isMetaClass([NSObject class]);\n```\n\n\n### 4.4 Objective-c中为什么设计元类\n主要是为了复用消息机制。参考下面五中的指向图。\n\n\n\n## 五、isa & superclass\n实例对象、类对象、元类对象都有isa指针，有一个指向图如下：\n![isa指向图.jpg](https://image.xinwei.ltd/81712074886786.jpg)\n\nisa和superclass的指向都如图所示。\n\n### 5.1 经典案例\n```oc.m\n// 新增一个NSObject+Test分类\n@interface NSObject (Test)\n\n+ (void)test;\n\n@end\n\n@implementation NSObject (Test)\n\n+ (void)test {\n    NSLog(@\"NSObject中的+test\", self);\n}\n\n- (void)test {\n    NSLog(@\"NSObject中的-test\", self);\n}\n\n@end\n\n\n// 一个oc类Person\n@interface Person:  NSObject\n\n+ (void)test;\n\n@end\n\n@implementation Person\n\n+ (void)test {\n    NSLog(@\"Person中的+test\", self);\n}\n\n@end\n\n\n// main.m中的main函数\nint main(int argc, const char *argv[]) {\n    NSLog(@\"Person中的类adr: %p\", [Person class]);\n    NSLog(@\"NSObject中的类adr: %p\", [NSObject class]);\n    \n    [Person test];\n    [NSObject test];\n}\n```\n\n- 第一种情况：NSObject分类Test中声明了类方法test以及实现，Person类也声明和实现了类方法。\n\n会各自调用自己的类方法，打印各自类方法中的NSLog；\n\n打印的self，也是各自的类对象地址。各自的self都是各自类对象地址。\n\n- 第二种情况：NSObject分类Test中声明了类方法test以及实现，Person类只声明，但不实现类方法。\n\nNSObject分类会调用自己的类方法，打印自几类方法中的NSLog；\n\nPerson调用NSObject中的类方法test。\n\n输出的self也是各自的类对象地址。\n\n- 第三种情况：NSObject分类Test中声明了类方法+test，但只实现实例方法-test，Person类只声明+test，但不实现类方法。\n\n输出的self也是各自的类对象地址。因为调用+test方法的接收者都是各自的类对象。\n\nNSObject分类和Person都会调用分类的实例方法。\n\n> 因为当NSObject元类中没有类+test方法时，会从superclass找到父类NSObject，父类NSObject中有方法名test【是不会辨别类方法和实例方法的，只关注方法名】，所以尽管main函数中是调用了类方法，但是最终会走到NSObject的相同方法名的实例方法中去。\n\n### 5.2 示例[self class]和[super class]\n```oc.m\n@interface Person: NSObject\n@end\n\n@implementation Person\n\n- (instancetype)init {\n    self = [super init];\n    if (self) {\n        NSLog(@\"self class: %@；super class: %@\", [self class], [super class])\n    }\n    return self;\n}\n\n@end\n```\n\n以上代码均打印Person对象地址。\n\n解释：\n[self class]，对象的class，直接就是对象的isa指向，所以是Person类。\n\n[super class]，表示从当前类的父类进行查找方法，所以以上代码需要从Person的父类NSObject中去找，NSObject实现了class方法，那么读取方法接收者的isa指针，这里的方法接收者还是self，所以self的isa指针指向的还是Person类。\n\n\n使用clang将oc转为c++：\n（下面这张图是偷懒从网上找的）\n![oc转c++.jpg](https://image.xinwei.ltd/91712075033158.jpg)\n\n按照苹果的文档，super是一个指向objc_super结构体的指针。\n![objc_super.jpg](https://image.xinwei.ltd/101712075065185.jpg)\n\n[super class]的消息接收者即receiver是self。所以当最后找isa指针时，还是找的self的isa指向的类Person。\n\n### 5.3 isa & isa_mask才能是类的地址值\n![isa_mask逻辑.jgp](https://image.xinwei.ltd/111712075115672.jpg)\n\nisa是一个共同体，如下：\n![isa结构体.png](https://image.xinwei.ltd/image1712076334468.png)\n\n\n示例（控制台中通过lldb调试，验证class的isa是需要&isa_mask的值，就是元类的地址）：\n![isa_mask的lldb调试.jpg](https://image.xinwei.ltd/121712075142935.jpg)\n\n上图中，因为objc2.0，将isa设置为private，所以在oc中使用lldb调试时，使用personClass->isa是拿不到isa的，所以上图中的mj_objc_class这个结构体模拟的就是objc2.0中object_class这个结构体，目的是为了给oc暴露isa这个指针来查看isa指向的地址值\n![objc_object中的private私有isa.png](https://image.xinwei.ltd/image1712076197032.png)\n\nsuperclass没有使用mask，所以是直接指向的父类的地址。\n![superclass的lldb调试.jpg](https://image.xinwei.ltd/131712075197419.jpg)\n\n\n## 六、objc_class的探索\n新的objc源码中是使用了objc_class，继承自objc_object【c++语法】。\n![objc_class源码.jpg](https://image.xinwei.ltd/141712075246203.jpg)\n\n\n### 6.1 objc_class的数据结构\n![objc_class结构.jpg](https://image.xinwei.ltd/151712075306105.jpg)\n\n\n\n### 6.2 小结\n![ios面试题.jpg](https://image.xinwei.ltd/161712075365343.jpg)\n\n\n## 七、分类\n### 7.1 分类的方法和类方法\n- 分类的方法是和主类的方法是存在一起的，都在元类的method_list_t\n- 分类的方法不会覆盖主类的方法\n- 分类如果有和类同名的方法，分类的方法排在主类的前面\n- 分类的加载在主类之后\n如果要确保只使用主类中的方法：\n![响应本类方法.jpg](https://image.xinwei.ltd/171712075418800.jpg)\n\n\n文章太长，其他部分下篇再附上。\n\n\n","这篇文章描述了oc中的对象、类、元类、内存对齐和isa指针等内容。这篇文章是多年前在学习iOS的过程中做的笔记，如今整理了一下发到这里，供大家参阅交流，后面也会陆陆续续将之前的文章分享出来。","https://image.xinwei.ltd/image1712075563167.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],418,"oc,oc class,oc meta class,ios memory,ios对象,ios类,ios元类,ios内存",{"id":87,"createdAt":709,"title":710,"content":711,"summary":712,"image":713,"uid":136,"user":714,"categoryId":85,"category":715,"subCategoryId":144,"subCategory":716,"comments":717,"status":9,"reason":15,"notice":15,"visitCount":718,"commentCount":112,"keywords":719},"2024-01-09T15:25:56.498Z","nuxt3 服务端渲染ssr - Error: Failed to download template from registry: fetch failed","### 一、nuxt3 简介\n当你搜索看到这篇文章的时候，那么对于nuxt3的基本概念你应该是清楚的了吧。\n如果你还没对nuxt3有了解，那么你可以通过官网：[nuxt3官网](https://nuxt.com/docs/getting-started/introduction)进行了解。\n\n简单来说，nuxt是一种以Vue框架为模版的服务端渲染方案（俗称ssr：server side rendering），对seo极其友好，性能强大。\n\n### 二、nuxt3解决的问题\n对于程序员们开发的网站，很多通过前端框架如Vue、React、Uniapp直接开发出来，使用上是没有问题的，但是当涉及到SEO的时候，仅仅使用这些框架是不够的。seo指的是可以被搜索引擎如百度、谷歌等直接爬虫搜索到。\n\n因为像Vue、React这样的单页面框架（只有一个html，通过客户端js加载渲染数据），对于seo是不友好的。百度这样的搜索引擎爬虫只能爬到没有实际数据的html页面。因为渲染dom结构的时候，在获取数据之前。\n\n所以nuxt3主要是用于解决Vue这种单页面框架的seo问题，当然还做了很多性能优化的工作。\n（React对应的ssr方案有Next，名字很像吧）\n\n### 三、版本\nnuxt有个针对Vue版本的版本分水岭，那就是nuxt2和nuxt3。\n\n其中nuxt2使用的是Vue2.x版本，\nnuxt3使用的是Vue3.x版本。\n\n对于经常使用Vue开发的朋友们知道，Vue3.x这种组合式Api开发方式，比传统的Vue2.x的选项式开发方式，无论从包大小还是性能上来说是绝对领先的。\n\n所以当下使用nuxt进行服务端渲染的小伙伴来说，使用nuxt3当然是首选。\n\n下面从官网截了一张图，可以看到不同版本之间的直观对比。\n\n![](https://image.xinwei.ltd/image1704812263060.png)\n\n### 四、万事开头难\n1. 创建nuxt工程（nuxt3 & vue3.x)\n```terminal\nnpx nuxi@latest init my-app\n```\n2. 问题\n刚开始接触nuxt开发的小伙伴，在创建模版工程的时候，经常遇到一个问题（当然国内外都有出现，详情可见：[nuxt github issue](https://github.com/nuxt/nuxt/issues/21853)），就是Error: Failed to download template from registry: fetch failed。\n\n问题截图：\n![](https://image.xinwei.ltd/image1704812908132.png)\n\n\n文本描述：\n ERROR  Error: Failed to download template from registry: fetch failed\n\n这种问题很鸡肋，非常影响初学者的学习进度。\n\n3. 解决方案\n这个问题出现的原因就是在于，在下载nuxt的模版代码到本地时，网络无法访问：`raw.githubusercontent.com`，具体原因有很多，不便解释。\n\n如何解决这个网络问题，网上也有很多方案。\n方案一：\n大多数就是修改hosts文件：通过ip/域名查询（网上有很多，可以自行搜索）`raw.githubusercontent.com`的ip地址，然后在hosts文件中加上一句（以mac中hosts举例）：\n```hosts文件\n# nuxt\n185.199.108.133 raw.githubusercontent.com\n```\n很多同学能解决掉，当然也有不成功的。\n\n方案二：\n如果方案一没有解决，那么大家不妨试试如下方案：\n- [x] 目的在于修改dns的配置。\n- [x] 以Mac为例，打开网络偏好设置，打开高级，找到dns设置，添加ip：8.8.8.8，然后点击确定->应用:\n![](https://image.xinwei.ltd/image1704813820922.png)\n这个是将dns首选指到谷歌，这种方式是我自用的有效方式，大家不妨试试。\n\n以上。\n\n\n\n","一、nuxt3 简介 当你搜索看到这篇文章的时候，那么对于nuxt3的基本概念你应该是清楚的了吧。 如果你还没对nuxt3有了解，那么你可以通过官网：[nuxt3官网](https://nuxt.com/docs/getting-started/introduction)进行了解。 简单来说，nuxt是一种以Vue框架为模版的服务端渲染方案（俗称ssr：server side rendering）","https://image.xinwei.ltd/image1704812263060.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],403,"nuxt,nuxt3,nuxt服务端渲染,nuxt error,failed to download template",{"id":721,"createdAt":722,"title":723,"content":724,"summary":725,"image":726,"uid":104,"user":727,"categoryId":9,"category":728,"subCategoryId":729,"subCategory":730,"comments":732,"status":9,"reason":15,"notice":15,"visitCount":733,"commentCount":112,"keywords":734},101,"2024-03-22T11:06:53.252Z","2024年软考报名开始了，你们报名了吗？","2024年软考很多省份已经陆续开通了报名渠道，不知道软考是什么的小伙伴，建议大家及早去熟悉哦，对自己的职业和生活很有益处的，所以在这里给大家科普一下哦。\n\n这里附上浙江省软考的政策通知文件哦。\n\n报名入口：[https://bm.ruankao.org.cn/sign/welcome](https://bm.ruankao.org.cn/sign/welcome)\n\n另外提一句，浙江的**报名时间是3月20日到3月26日**哦，过了这个时间，就不能报名今年的5月份考试了。\n\n还有就是，报名估计需要1天的审核时间，所以越早报名，就不会错过时间了，希望大家谨记哦。\n\n另外，需要准备照片哦，这个有时候容易审核不过，我就是去照相馆拍的，按照要求是：**上传的照片必须为报考人员本人近期正面免冠白底彩色证件照，务必保证照片清晰、可辨认、无遮挡，其他如生活照、视频捕捉、摄像头所摄等照片一律不予审核通过**\n\n## 报名条件：\n- 具有浙江省内身份证以及居住证人员\n- 在浙江近一年内有连续6个月社保\n- 在校学生需要提供学生证等\n\n## 浙江省软件考试实施中心文件\n浙软考发〔2024〕1 号\n关于 2024 年上半年全国计算机技术与软件 专业技术资格(水平)考试有关事项的通知\n各市人事考试机构，各有关单位，广大考生: 根据人力资源和社会保障部办公厅《关于 2024 年度专业技术\n人 员 职 业 资 格 考 试 计 划 及 有 关 事 项 的 通 知 》( 人 社 厅 发 〔 2 0 2 4 〕 1 号 ) 和全国计算机专业技术资格考试办公室《关于 2024 年度计算机技术 与软件专业技术资格(水平)考试工作安排及有关事项的通知》(计 考办〔2024〕1 号)要求，现就 2024 年上半年全国计算机技术与软 件专业技术资格(水平)考试(以下简称“全国计算机软件资格考 试”)浙江考区有关事项通知如下:\n### 一、考试形式、时间、考点设置及开考资格\n2024 年全国计算机软件资格考试采取计算机化考试(机考) 方式，上半年考试时间为 5 月 25 日至 28 日。浙江考区考点设在省 直(杭州市)和各设区市，开考级别与专业资格名称、考试科目详 见《2024 年上半年全国计算机软件资格考试开考计划》(附件 1)，\n \n各级别、各资格的考试时间待报名结束后确定，具体以准考证为准。\n### 二、报考对象及条件\n按照《计算机技术与软件专业技术资格(水平)考试暂行规定》， 凡遵守中华人民共和国宪法和各项法律，恪守职业道德，具有一定 计算机技术应用能力的人员，均可根据本人情况，报名参加相应专 业类别、级别的考试。\n  根据报考属地化管理原则，报考人员须是在浙江省居住、工作\n或上学的人员。\n### 三、报考流程\n#### (一)网上报名时间和要求\n1.报名时间:2024 年 3 月 20 日 9:00 至 3 月 26 日 16:00。 2.相关要求:2024 年上半年全国计算机软件资格考试采取网上\n报名的办法，报名与审核同步进行，报名时间截止后，系统即关闭， 不再开设补报名通道。因审核需要时间，请各位考生及早、提前完 成报名信息提交。\n报考人员请登录中国计算机技术职业资格网“报名入口”栏目 (https://bm.ruankao.org.cn/sign/welcome)，进入“全国计算机 技术与软件专业技术资格(水平)考试网上报名平台”，机构名称选 择“浙江”，按报名系统的要求，如实完整输入本人信息、上传电子 照片和在浙江省居住、工作或上学的相关证明材料。\n  相关证明材料仅限于以下三项中任意一项:\n(1)浙江省内的居住证明:有效期内浙江省内身份证或户口簿\n(含户口簿首页和本人页)或由浙江省内公安机关签发的“浙江省 居住证”。\n(2)浙江省内的工作证明:2023 年 6 月至 2024 年 2 月期间连 续六个月的浙江省内社保证明(登录支付宝“浙里办”-社保服务- 社保证明打印进行下载，以 PDF 格式上传)。\n(3)浙江省内的上学证明(仅限在读学生):有效期内的教育 部学籍在线验证报告(登录学信网-学籍查询栏进行下载)。\n报考人员应严格按照规定和要求，准确、完整、真实填报相关 信息、上传相关材料。报名时间截止后，报考人员提交的个人信息 和材料等均不可修改或重新上传。如因报考人员未按要求提供相关 证明材料或错填个人信息(如通讯地址、手机号)、错选报考信息(如 考试级别、考区)、错传照片等原因，导致在报名截止时间内审核未 通过的或造成参加考试、通信联络、考试成绩、证书制发与使用等 受影响的，责任由报考人员自行承担。如因报考人员提供虚假材料 或未按要求办理报考相关事项的，按考试报名无效或考试成绩无效 处理，已缴费用不予退还。\n  报考人员请及时登录报名系统查看审核结果，考试机构不再另\n行通知考生。\n#### (二)缴费\n1.缴费时间:2024 年 3 月 21 日 9:00 至 3 月 28 日 16:00。 \n2.收费标准:考试(机考)收费标准按浙价费〔2010〕258 号\n文件规定，初、中级资格考试每人 200 元，高级资格考试每人 300\n元。 3.相关要求:报名审核通过后，报考人员登录报名系统在规定\n时间内进行缴费，已通过审核但未在规定时间内进行网上缴费的视 作自动放弃报名资格。网上缴费成功后报考人员不得修改报考信息。 缴费成功即完成报名所有手续，不再受理退费申请。\n4.缴费电子发票:报考人员缴费时须在支付平台维护好个人手 机号码，缴费成功后电子发票将以短信形式发送给个人，请及时下 载打印。\n#### (三)打印准考证\n1.打印时间:2024 年 5 月 20 日 10:00 至 5 月 24 日 16:00。 2.相关要求:缴费成功的报考人员通过登录报名系统在规定的\n时间内下载并打印准考证，逾期未打印准考证视为放弃考试。报考\n人员按准考证规定的时间、地点和要求参加考试。\n  关于网上报名、网上缴费、网上打印准考证的操作流程及具体\n要求，详见报名系统中相关说明。\n### 四、考试相关规定\n(一)报考人员须持本人纸质准考证和有效证件原件进入考场 (两证缺一不可，有效证件包括居民身份证、社保卡、外籍人士护 照、外国人永久居留证、港澳台居民身份证明)。如考前发生影响考 试正常举行的突发情况，相关要求将根据实际情况进行调整，请报 考人员务必及时关注并遵照执行，若因不符合要求而不能参加考试\n的，责任自负。\n\n(二)考试违纪违规人员信息和处理意见于考试结束后 10 个工 作日内在浙江省科技宣传教育中心网站“软件考试”栏目公告。报考人员报名时应对网上提交的信息和相关材料负责。不得以提交不实信息、伪造报名材料等不正当手段来获取考试资格。\n报考人员严禁将手机以及其他有收发和存储式功能的电子设备、通讯工具、计算器和提包、资料等物品带至座位;严禁将试题、答题信息带出考场。\n考试结束后将采用技术手段进行雷同卷的甄别和认定，被认定为雷同答卷的，按规定给予成绩无效处理，涉及违纪的按相关条款追加处理。报考人员有义务妥善保护好自己的答题信息不被他人抄袭，若有答卷雷同，一旦认定，双方均取消考试成绩。\n报考人员有以上行为或有其他违反《考场规则》(与准考证一同 打印)的，将按《专业技术人员资格考试违纪违规行为处理规定》 (人力资源和社会保障部令第 31 号)，给予取消考试成绩、记入考 试诚信档案库、向社会公布等处理，由此造成的一切后果责任自负。\n\n(三)职业资格考试严格执行考培分离政策，国家未指定任何培训机构开展职业资格考试培训和代报名工作，请报考人员切勿轻信培训机构虚假宣传，并通过官方渠道进行报名。\n### 五、成绩公布和证书制发\n考试成绩由工业和信息化部教育与考试中心统一公布，可通过\n浙江省科技宣传教育中心网站“软件考试”栏目查询。\n  报考人员一次性通过规定科目考试，获国家专业技术人员职业资格(计算机技术与软件专业技术资格)相应级别证书。在省人力社保厅印发合格人员文件后，考试合格人员可登录浙江政务服务网“高级职称评审与专技考试”栏目，自主下载打印专业技术人员资格考试电子合格证明，该证明在省内与纸质证书具有同等效用。在\n人社部下发资格证书后，纸质证书的领取方式在浙江省科技宣传教\n育中心网站“软件考试”栏目另行通知。\n### 六、其他有关事项\n计算机软件资格考试社会影响大、涉及面广、政策性强，各市 考试实施机构要高度重视，加强与有关部门之间的协调配合，认真 做好各个环节的考务工作，严格执行《专业技术人员资格考试违纪 违规行为处理规定》(人社部令第 31 号)，严肃考风考纪，确保考试 工作安全顺利进行。\n根据《计算机技术与软件专业技术资格(水平)考试暂行规定》 (国人部发〔2003〕39 号):通过考试取得初级资格可聘任技术员 或助理工程师职务，取得中级资格可聘任工程师职务，取得高级资 格可聘任高级工程师职务。用人单位可根据《工程技术人员职务试 行条例》《关于推进工程领域职称社会化评价改革的意见》(浙人社 发〔2018〕128 号)等有关规定及工作需要，从获得证书的人员中 择优聘任相应专业技术职务。\n  有关考试大纲、教程和辅导用书信息，可在浙江省科技宣传教\n育中心网站“软件考试”栏目查询。\n### 七、联系方式\n![浙江软考联系方式.png](https://image.xinwei.ltd/image1711105462083.png)\n\n![浙江软考科目.png](https://image.xinwei.ltd/image1711105522046.png)\n\n\n抄送:省人力社保厅、科技厅、经信厅，各市人力社保局。 浙江省软件考试实施中心 2024 年 3 月 15 日印发\n  \n\n","2024年软考很多省份已经陆续开通了报名渠道，不知道软考是什么的小伙伴，建议大家及早去熟悉哦，对自己的职业和生活很有益处的，所以在这里给大家科普一下哦。","https://image.xinwei.ltd/image1711105462083.png",{"phone":103,"userId":104,"nickName":105,"vipType":9,"avatar":106,"sign":15,"createdAt":107},{"id":9,"name":604},34,{"id":729,"name":731,"parentId":9},"社会人文",[],395,"软考,2024软考,软考报名",{"id":736,"createdAt":737,"title":738,"content":739,"summary":740,"image":741,"uid":136,"user":742,"categoryId":85,"category":743,"subCategoryId":144,"subCategory":744,"comments":745,"status":9,"reason":15,"notice":15,"visitCount":746,"commentCount":112,"keywords":747},81,"2024-02-05T02:58:30.12Z","midwayjs中当有HasMany时，可能出现的错误：Error 74008 ReferenceError: Cannot access 'xxx' before initialization","### 问题\n> Error 74008 ReferenceError: Cannot access 'xxx' before initialization ...\n\n### 问题截图\n![midwayjs错误截图1](https://image.xinwei.ltd/11707101748955.png)\n\n![midwayjs错误截图2](https://image.xinwei.ltd/21707101769607.png)\n\n这里有一些已有的issue，大家可以参考。\n\n当有HasMany时，导致截图中的错误: [https://github.com/sequelize/sequelize-typescript/issues/825](https://github.com/sequelize/sequelize-typescript/issues/825)\n\n### 解决办法\n在上面的截图中，定义的child的model中，可以对所属的model使用ReturnType的写法\n```\nparent: ReturnType\u003C() => JobCategory>; // Awaited\u003CJobCategory>;\n```","midwayjs中当使用HasMany时，两个相关联的model可能出现initialize前后的错误：Error 74008 ReferenceError: Cannot access 'xxx' before initialization。","https://image.xinwei.ltd/11707101748955.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],388,"midwayjs,midwayjs hasMany,midwayjs数据库,midwayjs一对多,midwayjs表关联",{"id":749,"createdAt":750,"title":751,"content":752,"summary":753,"image":15,"uid":136,"user":754,"categoryId":85,"category":755,"subCategoryId":144,"subCategory":756,"comments":757,"status":9,"reason":15,"notice":15,"visitCount":758,"commentCount":112,"keywords":759},122,"2024-08-18T09:04:47.117Z","网页外观主题色-如何监听系统appearance主题色的改变","自从Apple公司推出系统外观深色、浅色主题色的切换后，大多数的pc、mobile的app也都支持主题色的修改，进而发展到浏览器的主题色也可以进行修改，网页也就可以随着主题色颜色的切换变得更加丰富。\n\n\n那么我们怎么在js中监听系统主题色的改变呢？\n\n```js\nconst listenForColorSchemeChanges = () => {\n\t  darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n\t  darkModeMediaQuery.addEventListener('change', event => {\n\t        if (event.matches) {\n\t           console.log(\"Switched to dark mode (black style).\");\n\t        } else {\n\t           console.log(\"Switched to light mode (white style).\");\n\t        }\n\t  });\n}\n```","自从Apple公司推出系统外观深色、浅色主题色的切换后，大多数的pc、mobile的app也都支持主题色的修改，进而发展到浏览器的主题色也可以进行修改，网页也就可以随着主题色颜色的切换变得更加丰富。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],383,"apple web,apple web color,apple appearance,apple web appearance,apple website,website color,ios web color",{"id":761,"createdAt":762,"title":763,"content":764,"summary":765,"image":766,"uid":136,"user":767,"categoryId":85,"category":768,"subCategoryId":100,"subCategory":769,"comments":770,"status":9,"reason":15,"notice":15,"visitCount":771,"commentCount":112,"keywords":772},144,"2024-12-24T11:51:16.415Z","flutter动画 (一) 从loading的转圈动画说起 - Ticker","## 引言\nflutter中的动画一个重要且必不可少的机制就是`Ticker`，所以要理解flutter的动画，必须先要对Ticker有所理解。\n\n对于Ticker这个概念，可以将其理解为时钟或者刷新信号。意味着，在UI IT开发领域共识中，每秒60帧刷新频率下，Ticker会发出信号去执行动画，并且在这个频率之下，Ticker在每一帧都提供了回调给到开发者去进行处理。\n\n## 常见的`SingleTickerProviderStateMixin`\n以上是对Ticker的简单介绍，下面介绍一个轻量且高效的tiker provider `SingleTickerProviderStateMixin`，说到provider，你应该就知道`SingleTickerProviderStateMixin`是一个提供了`Ticker`的对象，而从其命名中就可以看出，这是一个混入`mixin`对象，而且通常用于处理单独的某一种动画时常用的ticker提供对象【使用多动画则考虑使用`TickerProviderStateMixin`】，咱们看看它的声明：\n```ticker_provider.dart\n/// Provides a single [Ticker] that is configured to only tick while the current\n/// tree is enabled, as defined by [TickerMode].\n///\n/// To create the [AnimationController] in a [State] that only uses a single\n/// [AnimationController], mix in this class, then pass `vsync: this`\n/// to the animation controller constructor.\n///\n/// This mixin only supports vending a single ticker. If you might have multiple\n/// [AnimationController] objects over the lifetime of the [State], use a full\n/// [TickerProviderStateMixin] instead.\n@optionalTypeArgs\nmixin SingleTickerProviderStateMixin\u003CT extends StatefulWidget> on State\u003CT> implements TickerProvider {\n  Ticker? _ticker;\n...\n}\n```\n\n所以它可以被状态组件`StatefulWidget`的`State`进行混入处理。而且在`SingleTickerProviderStateMixin`中已经包含`Ticker`对象，在dispose中也会处理`removeListener`,\n```ticker_provider.dart\n...\n@override\n  void dispose() {\n    assert(() {\n      if (_ticker == null || !_ticker!.isActive) {\n        return true;\n      }\n      throw FlutterError.fromParts(\u003CDiagnosticsNode>[\n        ErrorSummary('$this was disposed with an active Ticker.'),\n        ErrorDescription(\n          '$runtimeType created a Ticker via its SingleTickerProviderStateMixin, but at the time '\n          'dispose() was called on the mixin, that Ticker was still active. The Ticker must '\n          'be disposed before calling super.dispose().',\n        ),\n        ErrorHint(\n          'Tickers used by AnimationControllers '\n          'should be disposed by calling dispose() on the AnimationController itself. '\n          'Otherwise, the ticker will leak.',\n        ),\n        _ticker!.describeForError('The offending ticker was'),\n      ]);\n    }());\n    _tickerModeNotifier?.removeListener(_updateTicker);\n    _tickerModeNotifier = null;\n    super.dispose();\n  }\n...\n```\n所以在使用`SingleTickerProviderStateMixin`时，你不必担心`Ticker`的回收内存泄漏问题。\n\n## loading动画案例\n这里我们写一个简单的转圈动画，代码如下：\n```loading.dart\nimport 'package:flutter/cupertino.dart';\n\nclass LoadingWidget extends StatefulWidget {\n\n  late final Widget? loadingWidget;\n\n  LoadingWidget({ super.key, this.loadingWidget });\n\n  @override\n  _LoadingWidgetWidgetState createState() => _LoadingWidgetWidgetState();\n}\n\nclass _LoadingWidgetWidgetState extends State\u003CLoadingWidget>\n    with SingleTickerProviderStateMixin {\n  late AnimationController _controller;\n\n  @override\n  void initState() {\n    super.initState();\n    _controller = AnimationController(\n      duration: const Duration(seconds: 1),\n      vsync: this,\n    )..repeat(); // 持续动画\n  }\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return AnimatedBuilder(\n      animation: _controller,\n      builder: (context, child) {\n        return Transform.rotate(\n          angle: _controller.value * 2.0 * 3.141592653589793,\n          child: child,\n        );\n      },\n      child: widget.loadingWidget != null ? widget.loadingWidget : Image.asset(\n        'assets/images/loading.png', // 转圈图片\n        width: 24,\n        height: 24,\n      ),\n    );\n  }\n}\n```\n示例图片：\n![loading flutter](https://image.xinwei.ltd/private_media_unlock_loading1735040143331.png)\n\n上面一段代码通过使用`AnimationController`动画控制器，在参数`vsync`传入了当前的state作为ticker provider，动画时长duration为1秒，在`AnimatedBuilder`动画生成器中builder下传入了转圈动画`Transform.rotate`，角度是当前动画进度的值，乘以一圈的弧度 2 pi，那么1秒钟一圈的动画，持续不断的repeat重复执行，就组成了loading转圈的效果。\n\n以上就是对loading动画的一个简单解释说明，在这个基础上，如果想进行其他动画，比如：translate、scale、flip等就都可以用这个思路实现。\n\n希望对大家有帮助，也请大家多多关注“新卫网络科技”微信公众号。\n\n\n\n","逐渐分享一些在flutter中展示的动画以及UI效果，供大家参考，希望对大家学习和了解flutter有一些帮助。","https://image.xinwei.ltd/private_media_unlock_loading1735040143331.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],365,"SingleTickerProviderStateMixin,ticker,flutter loading,flutter animation,flutter animate,flutter动画,flutter progress,flutter circle,loading,animation,animate,动画,flutter",{"id":125,"createdAt":774,"title":775,"content":776,"summary":777,"image":778,"uid":779,"user":780,"categoryId":9,"category":785,"subCategoryId":786,"subCategory":787,"comments":789,"status":9,"reason":15,"notice":15,"visitCount":790,"commentCount":112,"keywords":791},"2024-03-17T08:32:46.413Z","我的古城之旅","沐浴着初夏的暖阳，我踏上了一段寻觅岁月痕迹的旅程。目的地，那座承载着千年历史的古城。\n\n![](https://image.xinwei.ltd/%E4%B8%8B%E8%BD%BD1710664258144.jpeg)\n\n穿越喧嚣的都市，我来到了古城的门前。巍峨的城墙矗立在眼前，仿佛诉说着曾经的辉煌与沧桑。我轻抚着粗糙的城墙，感受着岁月的流转。这里，曾是古代将士们坚守的阵地，如今却成为了一处静谧的历史遗迹。\n\n漫步在古城的街巷间，我仿佛穿越了时空。青石板铺就的街道两旁，古朴的民居依山而建，错落有致。檐角上翘的屋檐，精美的雕花窗棂，无不透露出浓郁的古典韵味。这里的居民似乎还保留着古时的生活习俗，悠闲地在街头巷尾聊着家常，让人感受到了古城的宁静与恬淡。\n\n沿着蜿蜒的小巷，我来到了古城的一处名胜——古寺。寺庙的门前，一棵参天的古树矗立着，枝叶繁茂，为这片古老的土地增添了一抹生机。走进寺庙，香火缭绕，佛音袅袅。我双手合十，虔诚地许下心愿。在这里，我仿佛找到了内心的宁静与安宁。\n\n夕阳西下，我站在古城的最高处，俯瞰着整个城市。那些错落有致的古建筑，在夕阳的映衬下，散发出金色的光芒。远处的山峦和村庄，如诗如画般展现在眼前。这一刻，我感受到了古城的魅力与韵味，仿佛与它融为一体。\n\n漫步古城的旅程即将结束，但我的心却留在了这里。这座承载着千年历史的古城，让我感受到了岁月的痕迹和文化的底蕴。在这里，我找到了心灵的归宿，也收获了无尽的思考与感悟。在未来的日子里，我将带着这些宝贵的记忆，继续探索这个世界的美好与奇迹。","穿越喧嚣的都市，我来到了古城的门前。巍峨的城墙矗立在眼前，仿佛诉说着曾经的辉煌与沧桑。我轻抚着粗糙的城墙，感受着岁月的流转。这里，曾是古代将士们坚守的阵地，如今却成为了一处静谧的历史遗迹。","https://image.xinwei.ltd/%E4%B8%8B%E8%BD%BD1710664258144.jpeg",525978507886661,{"phone":781,"userId":779,"nickName":782,"vipType":9,"avatar":783,"sign":15,"createdAt":784},"16100000006","你是佩奇吧","https://image.xinwei.ltd/f4d32b46463a7e0579c33959ab104fbd1710561691303.jpg","2024-03-16T00:32:03.652Z",{"id":9,"name":604},19,{"id":786,"name":788,"parentId":9},"游记",[],362,"旅游,古城,古城旅游",{"id":793,"createdAt":794,"title":795,"content":796,"summary":797,"image":798,"uid":136,"user":799,"categoryId":85,"category":800,"subCategoryId":144,"subCategory":801,"comments":802,"status":9,"reason":15,"notice":15,"visitCount":803,"commentCount":112,"keywords":804},154,"2025-03-05T08:05:55.961Z","在Visual Studio Code中给Vue 或者 React项目设置ESLint和Prettier","## 一、背景\n在代码开发项目中，代码格式提醒以及代码对齐是提高代码编写效率和代码阅读体验的一个规范化要求，所以每个程序员都应该有良好的代码编写习惯，熟练掌握常用代码编辑器的插件配置。\n\n这里我记录一下我在配置Vue项目在Visual Studio Code这个编辑器下的代码插件配置过程，做一下记录，也希望对有需要的同学有所帮助。\n\n## 二、介绍\nweb前端项目中重要的2个通用代码插件当属于：\n- ESLint （vue eslint官网： [https://eslint.vuejs.org/](https://eslint.vuejs.org/)）\n这里贴的是vue官方的eslint配置网址，其他的配置，可在eslint的官方网站上查阅：[https://eslint.org/](https://eslint.org/)\n> Official ESLint plugin for Vue.js.\n\nreact的eslint插件可以参阅：[https://www.npmjs.com/package/eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react)\n\n- Prettier （官网： [https://prettier.io/](https://prettier.io/)）\n> An opinionated code formatter\n\n从官网的使用说明，其实也可以逐步掌握配置vs code的各项步骤，我这里结合我自己的配置简单说明一下。\n\n## 三、步骤\n### 3.1 本地node环境下，全局安装eslint\n```terminal\nnpm install -g eslint\n```\n安装了全局的命令后，后续可以使用终端命令生成配置文件。\n\n### 3.2 vue项目配置package.json\n```terminal\nnpm install --save-dev eslint eslint-config-prettier eslint-plugin-vue globals typescript-eslint\n```\n上面的这条命令，安装了eslint、eslint的prettier插件、eslint的vue插件、typescript的eslint插件。\n\n这个时候，如果vue项目中还没有生成eslint的配置文件，我们可以通过终端命令生成配置文件。\n我们可以看一下eslint的终端命令说明：\n```terminal\neslint --help\n```\n![eslint help](https://image.xinwei.ltd/image1741159886345.png)\n可以看到截图中有一个命令是可以生成config文件的。\n\n### 3.3 生成eslint.config.js\n执行命令`eslint --init`可以进行终端会话选择配置\n```terminal\neslint --init\n```\n![eslint --init](https://image.xinwei.ltd/image1741160107848.png)\n\n从截图中看到终端会话最后一条“Successfully created /Users/xxxx/eslint.config.js file.”，就是在当前项目的根目录下自动生成了一个`eslint.config.js`文件。\n![eslint.config.js](https://image.xinwei.ltd/image1741160226531.png)\n（注意：红框中的内容是后面配置的）\n\n### 3.4 配置prettier\n1. 先install\n```terminal\nnpm install --save-dev --save-exact prettier\n```\n`--save-exact`会锁定prettier版本，因为prettier每次的release都可能有不同的针对性。\n2. 生成.prettierrc文件\n执行node语句：\n```terminal\nnode --eval \"fs.writeFileSync('.prettierrc','{}\\n')\"\n```\n生成的.prettierrc文件是一个只包含大括号{}的文件，我们可以在这个大括号中加入配置：\n```.prettierrc\n{\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 4,\n  \"semi\": false,\n  \"singleQuote\": false\n}\n```\n文件中已经加入了行尾逗号、tab键宽度、顿号、单引号的使用规则，目前是足够的，不够可以按照prettier的文档继续添加。\n3. 生成.prettierignore文件\n这个文件是代码format的忽略文件，这些文件都不会format。\n```terminal\nnode --eval \"fs.writeFileSync('.prettierignore','# Ignore artifacts:\\nbuild\\ncoverage\\n')\"\n```\n> Prettier will follow rules specified in .gitignore if it exists in the same directory from which it is run. You can also base your .prettierignore on .eslintignore (if you have one).\n\n这里有提示，就是说该文件也会使用.gitignore这个git忽略文件中的配置，所以这一步你可以省略。\n\n### 3.5 ESLint和Prettier的配置冲突问题\nprettier文档明确说了以下一段话：\n> If you use ESLint, install eslint-config-prettier to make ESLint and Prettier play nice with each other. It turns off all ESLint rules that are unnecessary or might conflict with Prettier. There’s a similar config for Stylelint: stylelint-config-prettier\n\n意思是，如果你同时安装了ESLint和Prettier，那么要安装eslint-config-prettier这个插件来避免这2个插件的配置冲突。\n\n所以参考：[https://github.com/prettier/eslint-config-prettier#installation](https://github.com/prettier/eslint-config-prettier#installation) 这里面的说明，修改一下eslint.config.js中的配置。\n\n【我们在3.2中安装过了eslint-config-prettier，所以不需要再次安装】\n![eslint-config-prettier config](https://image.xinwei.ltd/image1741161409889.png)\n【我们在3.3中的截图中有红色框，指的就是当前这步】\n\n### 3.6 配置Visual Studio Code\n链接：[https://prettier.io/docs/editors](https://prettier.io/docs/editors) 中有各个编辑器的配置，我着重记录一下vs code的配置。\n1. 首先打开vs code 的setting配置\n![open vs code setting](https://image.xinwei.ltd/image1741161641808.png)\n2. 以json配置的形式查看setting配置\n![setting json entry](https://image.xinwei.ltd/image1741161693172.png)\n3. 配置json\n![config prettier](https://image.xinwei.ltd/image1741161767635.png)\n```settings.json\n{\n  \"dart.flutterSdkPath\": \"/Users/xxx/flutter/flutter\",\n  \"diffEditor.ignoreTrimWhitespace\": true,\n  \"explorer.confirmDelete\": false,\n  \"dart.openDevTools\": \"flutter\",\n  \"lldb.library\": \"/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB\",\n  \"lldb.launch.expressions\": \"native\",\n  \"lldb.suppressUpdateNotifications\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.formatOnSave\": true,\n  \"eslint.validate\": [\"javascript\", \"typescript\", \"vue\"]\n}\n```\n这样配置，就可以使用prettier作为vs code的默认format插件，并且对js、ts、vue进行format格式化显示，在onSave（Command + s 保存）的时候可以进行format。\n\n以上。\n\n上面就是一个简单的配置，更复杂的配置大家可以参考2个插件的集成文档，希望对有帮助。\n\n\n\n\n\n\n\n","最近又在写新项目，又需要给项目配置一下代码lint和pretty format，所以做一个简单的记录，方便以后直接使用。","https://image.xinwei.ltd/image1741159886345.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],356,"ESLint,Prettier,visual studio code setting,vue eslint,vue prettier,react eslint,react prettier",{"id":806,"createdAt":807,"title":808,"content":809,"summary":810,"image":15,"uid":27,"user":811,"categoryId":9,"category":812,"subCategoryId":729,"subCategory":813,"comments":814,"status":9,"reason":15,"notice":15,"visitCount":815,"commentCount":9,"keywords":816},98,"2024-03-17T09:12:18.933Z","数字鸿沟下的社会分化：技术进步带来的新挑战","随着科技的飞速发展，信息技术已经渗透到生活的方方面面，从智能手机、互联网到人工智能、大数据等，它们正在重塑我们的社会结构和生活方式。然而，这种技术进步并非普惠的，它在带来便利的同时，也加剧了社会的不平等，形成了一道难以逾越的“数字鸿沟”。\n\n本文将探讨数字鸿沟现象及其背后的社会问题，并提出可能的解决之道。\n\n## 数字鸿沟的定义与表现\n数字鸿沟通常指的是在信息技术接入、使用及能力上的差异，这种差异在不同地区、不同收入群体、不同教育水平的人群中尤为显著。例如，城市居民可能享受到高速稳定的网络服务，而偏远地区的居民则可能面临网络覆盖不足、网速慢等问题；富裕阶层可以轻松购买最新的智能设备，而低收入家庭则可能连基本的电脑和手机都难以负担。\n\n## 数字鸿沟带来的社会问题\n数字鸿沟不仅是一个技术问题，更是一个深刻的社会问题。它加剧了信息的不对称，使得一部分人能够充分利用信息技术提升生活质量和工作效率，而另一部分人则被排斥在数字化的世界之外。这种排斥不仅影响个体的机会公平，也阻碍了社会的整体进步。\n\n在教育领域，数字鸿沟导致教育资源的不均衡分配。线上教育的兴起本应为更多人提供学习机会，但缺乏必要设备或网络连接的学生却被排除在外。在医疗领域，远程医疗和电子健康记录本可提升医疗服务的质量和效率，但对于那些无法接入这些服务的群体来说，他们可能面临更严重的健康风险。\n\n## 解决数字鸿沟的途径\n解决数字鸿沟需要政府、企业和社会的共同努力。政府应制定相关政策，确保基础信息设施的普遍覆盖，并通过补贴、贷款等方式帮助低收入家庭接入信息技术。教育部门应推动数字教育的普及，特别关注弱势群体，确保他们不会因技术障碍而失去学习机会。\n\n企业方面，科技公司有责任确保其产品和服务能够惠及更广泛的人群。这可以通过开发适合不同收入水平和教育背景的用户的产品，以及提供价格合理、易于使用的服务来实现。\n\n社会组织和志愿者团体也可以在缩小数字鸿沟中发挥重要作用。他们可以通过开展信息技术培训、提供技术支持等方式，帮助那些在技术接入和使用上遇到困难的人群。\n\n## 结论\n数字鸿沟是技术进步带来的新挑战，它要求我们在享受技术带来的便利的同时，不忘关注那些被排斥在数字化世界之外的人群。通过政府、企业和社会的共同努力，我们有望缩小这一鸿沟，实现信息技术的普惠和共享，从而推动社会的整体进步和繁荣。","随着科技的飞速发展，信息技术已经渗透到生活的方方面面，从智能手机、互联网到人工智能、大数据等，它们正在重塑我们的社会结构和生活方式。然而，这种技术进步并非普惠的，它在带来便利的同时，也加剧了社会的不平等，形成了一道难以逾越的“数字鸿沟”。本文将探讨数字鸿沟现象及其背后的社会问题，并提出可能的解决之道。",{"phone":26,"userId":27,"nickName":28,"vipType":9,"avatar":29,"sign":15,"createdAt":30},{"id":9,"name":604},{"id":729,"name":731,"parentId":9},[],339,"科技,信息技术,it感悟",{"id":818,"createdAt":819,"title":820,"content":821,"summary":822,"image":823,"uid":136,"user":824,"categoryId":85,"category":825,"subCategoryId":144,"subCategory":826,"comments":827,"status":9,"reason":15,"notice":15,"visitCount":828,"commentCount":112,"keywords":829},110,"2024-04-23T08:37:05.061Z","jQuery实现放大镜效果","## 实现一个放大镜效果\n放大镜效果往往是电商平台上出现的比较多的一种效果，用于对商品图的放大查看。\n\n其原理是用一张适当大小的图来显示原图，但是使用一张更高清的大图来用作放大镜中看到的图。\n\n这里简短使用2张图的标签来做演示，一个代表缩略图，一个代表高清大图。高清大图在缩略图的右侧，悬浮显示，父标签和缩略图的父标签大小一致。\n\n### 1 利用jQuery实现\n#### 1.1 css:声明缩略图和高清图的样式\n```language\n/* 缩略图父标签class */\n.bigImage {\n  box-shadow: 0 0 3px rgba(0,0,0,0.15);\n  position: relative;\n  width: 400px;\n  height: 400px;\n}\n/* 缩略图img标签 */\n.bigImage>img {\n  position: relative;\n\n  width: 400px;\n  height: auto;\n  min-height: 400px;\n  max-height: 500px;\n  display: none;\n}\n\n/* 高清图父标签class */\n.magnifier {  \n  position: absolute;\n  z-index: 299; \n  border: 1px solid #ccc;\n  left: 420px;\n  width: 400px;\n  height: 400px;\n  background: rgba(255, 255, 255, 0.5);  \n  display: none;\n  overflow: hidden;\n}  \n/* 高清图img标签 */\n.magnifier-image {  \n  width: 1000px;\n  height: auto;\n  position: absolute;  \n  left: -300px; \n  top: -300px;\n}\n```\n\n通过position的relative和absolute属性，可以设置放大镜效果图absolute在原缩略图右侧，并且横向间隔20px。\n高清图的img设置为更宽更高，超出父标签的视口。视口里面的图，通过设置left和top可以移动高清图。\n\n#### 1.2 html: 摘取部分代码，主要是缩略图和高清大图的标签\n```language\n\u003C!-- 商品图区 -->\n        \u003Cdiv>\n          \u003C!-- 缩略图 -->\n          \u003Cdiv class=\"bigImage f-c-c\">\n            \u003Cimg />\n            \u003C!-- 放大镜的高清图 -->\n            \u003Cdiv class=\"magnifier\">\n              \u003Cimg class=\"magnifier-image\" alt=\"Magnified Image\">\n            \u003C/div>\n          \u003C/div>\n         \u003C/div>\n```\n\n\n#### 1.3 js:使用jQuery监听标签hover和鼠标移动事件\n当鼠标落在缩略图上时，显示放大镜标签：\n```language\n// 监听缩略图的hover\n  $('.bigImage').hover(function () {\n      $('.magnifier').css('display', 'block');\n    }, function () {\n      $('.magnifier').css('display', 'none');\n    }\n  );\n```\n\n\n当鼠标在缩略图上移动时，需要监听，并且改变放大镜区域中高清图的位置：\n```language\n  let largeImage = $('.bigImage');  \n  let magnifier = $('.magnifier');  \n  let magnifierImage = $('.magnifier-image');\n\n  let magnifierWidth = magnifier.width();  \n  let magnifierHeight = magnifier.height();\n\n  let bigImageOffset = largeImage.offset();\n\n  largeImage.on(\"mousemove\", function( e ) {\n    let posX = e.pageX + 1 - bigImageOffset.left;  \n    let posY = e.pageY + 1 - bigImageOffset.top;\n\n    let previewImgWidth = magnifierImage.width();  \n    let previewImgHeight = magnifierImage.height();\n    \n    // 计算放大镜中显示的大图位置 \n    let magnifierLeft = posX * (previewImgWidth / largeImage.width()) - magnifierWidth/2.0; \n    let magnifierTop = posY * (previewImgHeight / largeImage.height()) - magnifierHeight/2.0;\n\n    // 确保放大镜在图片范围内  \n    if (magnifierLeft \u003C 0) {\n      magnifierLeft = 0;\n    }\n    if (magnifierTop \u003C 0) {\n      magnifierTop = 0;\n    }\n    if (magnifierLeft + magnifierWidth > previewImgWidth) {\n      magnifierLeft = previewImgWidth - magnifierWidth;\n    }\n    if (magnifierTop + magnifierHeight > previewImgHeight) {\n      magnifierTop = previewImgHeight - magnifierHeight;\n    }\n\n    magnifierImage.css('left', -magnifierLeft);\n    magnifierImage.css('top', -magnifierTop);\n  });\n```\n\n\n- 获取鼠标在原缩略图中时，相对缩略图的x和y坐标，是鼠标的相对位置\n- 通过原缩略图和高清图的宽高比例，按照比例计算出鼠标应该落在高清图的哪一个位置\n- 我们要达到的效果是，鼠标坐标所在的点，处于放大镜标签的中间位置，并且高清大图的边缘不能显现在放大镜中\n- 边缘条件需要做判断，确保放大镜中永远显示的都是高清大图的一部分\n\n#### 1.4 标签的frame等解释\n##### 1.4.1 offset\n是标签相对于document的位置\n![jQuery的offset.png](https://image.xinwei.ltd/101713861213518.png)\n\n##### 1.4.2 event.pageX和event.pageY\n是相对于document左边缘的鼠标的位置，iframe中相对于iframe的坐标：\n![jQuery的pageX.png](https://image.xinwei.ltd/111713861301868.png)\n\n##### 1.4.3 width和height\n标签元素的宽和高。\n这里截图中说的是.css(\"width\"\")返回的是带px或者其他单位的，建议是在数学计算情况下使用.width()。\n![jQuery的width和height.png](https://image.xinwei.ltd/121713861351965.png)\n\n\n\n\n\n\n\n","因为最近的项目中又要做这个效果需求，所以简单记录一下使用到的jQuery方式实现，供大家参考。","https://image.xinwei.ltd/101713861213518.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],329,"jquery,jquery effective,jquery开发,jquery放大镜",{"id":831,"createdAt":832,"title":833,"content":834,"summary":835,"image":15,"uid":136,"user":836,"categoryId":85,"category":837,"subCategoryId":419,"subCategory":838,"comments":839,"status":9,"reason":15,"notice":15,"visitCount":840,"commentCount":112,"keywords":841},44,"2024-01-30T11:42:39.516Z","React native开发中可能遇到的问题","1. android项目集成react native后，真机调试，如果进入react native界面崩溃，可以尝试以下命令：\n```\ncurl \"http://localhost:8081/index.android.bundle?platform=android\" -o \"android/app/src/main/assets/index.android.bundle\"\n```\n\n2. 真机调试\n将\n```\nNSURL *jsCodeLocation = [NSURL URLWithString:@\"localhost/index.ios.bundle?platform=ios\"];\n```\n改成：\n```\nNSURL *jsCodeLocation = [NSURL URLWithString:@\"http://192.168.199.111:8081/index.ios.bundle?platform=ios\"];\n```\n并开启dev server。\n\n3. 离线包\n在根目录下面执行（入口文件路径和名字根据实际情况修改）：\n```\nreact-native bundle --entry-file ReactComponent/index.js  --bundle-output index.ios.jsbundle --platform ios --dev false --assets-dest ./\n```\n将生成的main.jsbundle和assets加入到Xcode中（添加引用）\n在代码中改成：\n```\nNSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:@\"main\" withExtension:@\"jsbundle\"];\n```\n\n```\nreact-native bundle --entry-file index.ios.js  --bundle-output main.jsbundle --platform ios --dev false --assets-dest ./\n```\n\n4. 如果执行了react-native link后，运行ios提示`native module can not be null`\n检查一下cocoapods中的react native库的引用\n```\n#'node_modules'目录一般位于根目录中\n# 但是如果你的结构不同，那你就要根据实际路径修改下面的`:path`\npod 'React', :path => '../node_modules/react-native', :subspecs => [\n    'Core',\n    'RCTText',\n    'RCTImage',\n    'RCTLinkingIOS',\n    'RCTSettings',\n    'RCTVibration',\n    'RCTActionSheet',\n    'RCTNetwork',\n    'RCTWebSocket', # 这个模块是用于调试功能的\n    # 在这里继续添加你所需要的模块\n  ]\n  # 如果你的RN版本 >= 0.42.0，请加入下面这行\n  pod \"Yoga\", :path => \"../node_modules/react-native/ReactCommon/yoga\"\n```\n\n5. Unrecognized font family ionicons\nCannot read property 'UIAppFonts' of null\n参考：\n> https://www.npmjs.com/package/react-native-vector-icons\n\n按照链接里面的步骤，采用cocoapods来进行修改\n第一步：在cocoapods中加入RNVecorIcons的引用；\n第二部：修改info.plist，加入字体的声明\n\n6. FAILURE: Build failed with an exception.\n> * What went wrong:\nA problem occurred configuring project ':app'.\n> Could not resolve all dependencies for configuration ':app:_debugApk'.\n   > A problem occurred configuring project ':react-native-vector-icons'.\n      > The SDK Build Tools revision (23.0.1) is too low for project ':react-native-vector-icons'. Minimum required is 25.0.0\n\n修改android/build.gradle中的\n```\ndependencies {\n    classpath 'com.android.tools.build:gradle:2.3.1'\n    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'\n\n    // NOTE: Do not place your application dependencies here; they belong\n    // in the individual module build.gradle files\n}\n```\n为\n```\ndependencies {\n    classpath 'com.android.tools.build:gradle:2.2.3'\n    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'\n\n    // NOTE: Do not place your application dependencies here; they belong\n    // in the individual module build.gradle files\n}\n```\n\n7. Error:The SDK Build Tools revision (23.0.3) is too low for project ':app'. Minimum required is 25.0.0\n在android／build.gradle中进行设置\n（参考：\nhttps://github.com/oblador/react-native-vector-icons/issues/428、\nhttp://stackoverflow.com/questions/41890659/errorthe-sdk-build-tools-revision-23-0-3-is-too-low-for-project-app-minim）\n\n```\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.2.3'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n//        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n    }\n}\n```\n\n如果不行的话，试试清除缓存。\n\n8. 向原生工程中引入react-native-vector-icons库的步骤\n参考：https://www.npmjs.com/package/react-native-vector-icons\n\n9. 警告：\n>  Animated: 'useNativeDriver' is not supported because the native animated module is missing. Failing back to JS-based animation. .....\n\n这个问题和iOS端相关，需要在cocoapods中，加入RCTAnimation的引用，类似于下：\n```\n# 'node_modules'目录一般位于根目录中\n  # 但是如果你的结构不同，那你就要根据实际路径修改下面的`:path`\n  pod 'React', :path => '../node_modules/react-native', :subspecs => [\n    'Core',\n    'RCTAnimation',\n    'RCTText',\n    'RCTImage',\n    'RCTLinkingIOS',\n    'RCTSettings',\n    'RCTVibration',\n    'RCTActionSheet',\n    'RCTNetwork',\n    'RCTWebSocket', # 这个模块是用于调试功能的\n    # 在这里继续添加你所需要的模块\n  ]\n  # 如果你的RN版本 >= 0.42.0，请加入下面这行\n  pod \"Yoga\", :path => \"../node_modules/react-native/ReactCommon/yoga\"\n\npod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'\n```\n\n10. Deviceinfo native modules is not installed correctly\n\n检查iOS工程，0.42的react native中有RCTConvert+Map这个文件，而0.44的版本中有这个文件，如果iOS工程中是用Cocoapods来管理第三方库的话，那么需要执行 pod update更新一下。\n\n11. [iOS][cocoapods] 'RCTAnimation/RCTValueAnimatedNode.h' file not found\n\n参考链接：https://github.com/facebook/react-native/issues/13198\n需要在报错的文件中，修改RCTValueAnimatedNode.h的引用方式，从尖括号改成双引号：\n#import \"RCTValueAnimatedNode.h\"\n\n12. TaskQueue: Error with task : No item for index 0\n\n解决方法：\n在使用数组的map方法时，要给每条数据加一个唯一key的键，并且要return一下，正确写法如下：\n```\nif(json.data.list &&\n   json.data.list.length > 0){\n\n    if(this._requestGradeList_para.pagesize === 1){\n        this.teacherGradeListStore.initialList(json.data.list.map( (item)=>{\n            item[\"key\"] = item.student_id;\n            return item;\n        }));\n    }else{\n        this.teacherGradeListStore.concatList(json.data.list.map( (item)=>{\n            item[\"key\"] = item.student_id;\n            return item;\n     }));\n```\n\n13. 集成图标第三方库“react-native-charts-wrapper”的步骤和方法\n\n解决方法：参考链接：http://blog.csdn.net/sinat_17775997/article/details/67635156\n\n14. Warning: In next release empty section headers will be rendered. In this release you can use 'enableEmptySections' flag to render empty section headers.\n\n解决方法：\n这个错误出现在ListView中，在以后的版本中才会实现空的section headers作为默认值，当前版本并没有支持。\n如果我们不需要使用headers的话，可以禁止EmptySections。\n解决方法：\n```\n \u003CListView\n      style={styles.listView}\n      dataSource={this.state.dataSource}\n      enableEmptySections={true}\n      />\n```\n","react native在安装或者开发过程中可能遇到的问题，以及对应的解决方法，供大家作为参考。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],324,"react native,react native项目,react native issue",{"id":62,"createdAt":843,"title":844,"content":845,"summary":846,"image":847,"uid":136,"user":848,"categoryId":85,"category":849,"subCategoryId":47,"subCategory":850,"comments":851,"status":9,"reason":15,"notice":15,"visitCount":852,"commentCount":112,"keywords":853},"2024-01-10T13:39:17.341Z","fastlane: No code signing identity found and can not create a new one","### 问题\nNo code signing identity found and can not create a new one because you enabled `readonly`\n\n### 背景：\n在iOS的app的持续构建中使用了fastlane来管理证书，并且配置脚本命令上传Testflight、Appstore。\n在打包过程中，fastlane报了如下截图中错误.\n\n截图：\n![](https://image.xinwei.ltd/image_11704893729190.png)\n\n\n### 分析问题步骤：\n1. 在打包机上通过Xcode检查签名配置（或者在钥匙串中查看本地证书是否都有效）。\n首先在执行打包的电脑机器上，通过Xcode打开打包的工程，查看对应iOS target的下的签名配置Signing & Capabilities，检查Debug and Profile、Adhoc、Release环境下，选择的profile证书是否匹配（若不匹配，profile下拉选择框下会有黄色三角）。\n![](https://image.xinwei.ltd/image_21704893844998.png)\n\n2. 检查target下的build settings中的signing配置。\n![](https://image.xinwei.ltd/image_31704893867481.png)\n\n确保Adhoc、Debug、Profile、Release对应的配置正确。\n3. 问题定位。\n第2步中的很好确定，很可能出现问题的就是第1步中的配置。\n一般在cer证书没有问题的情况下，出现本文中问题的情况，都是由于profile文件和cer不匹配导致的，我的情况就是如问题截图中一样的。\n导致不匹配的一个原因是：打包机上下载的profile，这个profile中的Certificates选择不正确，这是因为原先iOS项目中已经有一个发布证书，但是我用fastlane match重新生成了一个证书，但是fastlane match在生成profile文件时，使用了原先的发布证书。而我打包机上的发布证书，只有fastlane match生成的那个。\n4. 总结\n发布证书有2个，只有1个是fastlane match通过Apple的API生成的。但是fastlane match命令生成的profile使用的是之前的发布证书。\n\n### 解决办法：\n#### 方法一：\n登录Apple certificate证书中心，在profile下，找到在步骤1所示的黄色警告⚠️的profile文件，进入profile详情，点击Edit，确保Certificates选择的是打包机钥匙串中最新的那个发布证书。保存修改后，download这个profile到本地，并双击安装这个profile。\n#### 方法二：\n（比较有风险，风险点就是需要删除所有的发布证书。如果之前的发布证书或者其p12证书，被使用在其他第三方的平台，则需要更新其他平台上使用的这个证书）使用fastlane match nuke删除所有的证书和profile（或者fastlane match nuke distribution删除发布相关的证书），切记尤其删除了推送证书，那么需要及时更新所有的其他推送平台上的证书。\n","问题 No code signing identity found and can not create a new one because you enabled readonly 背景： 在iOS的app的持续构建中使用了fastlane来管理证书，并且配置脚本命令上传Testflight、Appstore。 在打包过程中，fastlane报了如下截图中错误. 截图： (https://ima","https://image.xinwei.ltd/image_11704893729190.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],320,"fastlane,no code signing identitiy,code sign",{"id":855,"createdAt":856,"title":857,"content":858,"summary":859,"image":860,"uid":136,"user":861,"categoryId":85,"category":862,"subCategoryId":47,"subCategory":863,"comments":864,"status":9,"reason":15,"notice":15,"visitCount":865,"commentCount":112,"keywords":866},103,"2024-03-23T05:52:23.185Z","Swift知识一览（二）","## 一、枚举\n### 1.1 定义如下：\n```swift\nenum Season {\n    case spring\n    case summer\n    case autumn\n    case winter\n}\n或者写成\nenum Season {\n    case spring, summer, autumn, winter\n}\n\nvar current = Season.spring\ncurrent = .summer\nprint(current) // summer\n```\n\n\n### 1.2 枚举关联值(associate value)\n就是在枚举的声明中，关联某一个类型，配合switch使用枚举时，就可以从枚举中解析出关联值。\n```swift\nenum Direction {\n    case angle(Float)\n    case name(String)\n}\nvar dir = Direction.angle(45)\ndir = .name(\"东北方向\")\n\n// 从枚举中取关联值\nswitch dir {\n    case .angle(let pt):\n        print(\"当前方向角度：\\(pt)\")\n    case .name(let name):\n        print(\"当前方向是：\\(name)\")\n}\n```\n\n\n### 1.3 枚举的原始值(raw value)\n指的是在定义枚举的时候，可以给enum指定一个类型，然后可以给case中的枚举成员指定一个该类型的默认值，这个默认值就是枚举成员的关联值。\n```swift\nenum Direction : String { // 定义原始值的类型\n    case east = \"东\" // \"东\"就是原始值，依此类推\n    case south = \"南\"\n    case west = \"西\"\n    case north = \"北\"\n}\nvar dir = Direction.east\nprint(dir) // east\nprint(dir.rawValue) // 东\nprint(Direction.east.rawValue) // 东\n\n// 有一种直接创建枚举变量的方式\nlet dir = Direction(rawValue: \"南\")\n// 如果不包括在枚举成员的所有原始值中，那么此时的dir就是一个Direction?可选类型\n// 可能是nil，比如: Direction(rawValue: \"南北\")\n// 可选类型在后面会写到\n```\n\n还有隐式原始值的概念，如下：\n【如果枚举的原始值类型为Int、String，那么Swift会给枚举成员分配原始值。】\n```swift\nenum Direction : String {\n    case east, south, west, north\n}\n// 那么枚举成员的原始值就是对应的成员名称字符串\nprint(Direction.east) // east\nprint(Direction.north.rawValue) // east\n\nenum Direction: Int {\n    case east, south, west, north\n}\nprint(Direction.east.rawValue) // 0，后面成员的原始值依次加1\n\n// 如果给case其中的某几个case赋原始值，那么没赋原始值的枚举成员，\n// 它的原始值是紧靠它的前一个枚举成员原始值加1\n// 比如\nenum Direction: Int {\n    case east = 1, south, west = 8, winter\n}\nprint(Direction.east.rawValue) // 1\nprint(Direction.south.rawValue) // 2\nprint(Direction.west.rawValue) // 8\nprint(Direction.north.rawValue) // 9\n```\n\n### 1.4 枚举的递归(recursive enumeration)\n也就是指枚举的case枚举成员，如果有关联值，且其关联值类型就是当前枚举，这种情况就是递归枚举。\n递归枚举在定义时一定要在enum前加`indirect`修饰，或者在递归的枚举成员case前加`indirect`修饰。\n```swift\nindirect enum Direction {\n    case angle(Float)\n    case twoDirection(Direction, Direction)\n}\n\n// 或者\nenum Direction {\n    case angle(Float)\n    indirect case twoDirection(Direction, Direction)\n}\n```\n\n\n## 二、MemoryLayout获取数据类型占用的内存大小\n如果在Objective-C中，使用的是sizeof来获取类型占用的内存大小，Swift中使用的是MemoryLayout。\n### 2.1 具体使用\n```swift\nMemoryLayout\u003CInt>.size // 64位系统输出8，32位系统输出4\n\n// 针对变量获取内存大小\nlet num = 8;\nMemoryLayout.size(ofValue:num) // 8\n2.2 其他使用\n`MemoryLayout\u003CInt>.stride`和`MemoryLayout\u003CInt>.alignment`\n- stride 是指系统分配的内存大小\n- alignment是内存对齐的字节数\n- size是指在分配的内存中实际占用了多少字节\n// 和上面一样，都可以获取类型或者变量的类型的内存大小\nMemoryLayout\u003CInt>.stride\nMemoryLayout\u003CInt>.alignment\n\nlet num = 8;\nMemoryLayout.stride(ofValue:num)\nMemoryLayout.alignment(ofValue:num)\n```\n\n \n### 2.3 关联值和原始值在enum中的内存存储\n- 关联值存储在枚举变量类型中，所以需要能够存储所有大小的关联值的类型，也就是说，枚举类型的内存大小是所有关联值类型内存大小的和。\n举例如下：\n```swift\nenum Direction {\n    case ange(Int, Int)\n    case name // 这个地方如果没有指定关联类型，那么使用1个字节就可以存储\n}\n// 那么会是以下输出\nprint(MemoryLayout\u003CDirection>.alignment) // 8\nprint(MemoryLayout\u003CDirection>.size) // 17 解释：2 * 8 + 1 = 17\nprint(MemoryLayout\u003CDirection>.stride) // 24 解释：，内存对齐是8，所以取8的倍数24\n```\n\n\n- 原始值，比较特殊，不会占用到枚举类型的内存中去，具体的原始值存储在其他地方。\n```swift\nenum Direction : String {\n    case east, south, west, north\n}\n\nenum Direction: Int {\n    case east = 1, south, west, north\n}\n\n// 上面2种情况，枚举类型占用的内存都是1个字节。\n// 因为只用一个字节就可以表示出不同的枚举。\n// 可以想象使用以下方式，表示上面的默认1的原始值写法，直接返回某个具体的值就可以，没有必要存储原始值的类型。\nenum Direction: Int {\n    case east, south, west, north\n    \n    func rawValue() -> Int {\n        if self == 0 return 1\n        if self == 1 return 2\n        ...\n    }\n}\n```\n\n\n## 三、可选项（optional）\n也就是常说的可选类型，指一个变量的值可以是nil，或者是有确定的值。\n### 3.1 定义\n通过在类型名称后加一个？号，表示这个变量是可选类型\n```swift\nvar name: String? = nil // 等同于 var name: String?，因为可选类型未赋值时，默认就是nil\nname = \"小明\"\n\n// 输出的是Optional包裹的值\nprint(name) // Optional(\"小明\")\n```\n\n\n### 3.2 强制解包 & 可选项绑定\n切记，对值为nil的可选变量强制解包，会runtime error\n\n使用可选项时，需要判断是否为nil，或者复制给临时变量。这种方式叫可选项绑定。\n\n```swift\nvar name: String? = \"小明\"\nprint(name!) // \"小明\" 强制解包出来的就是直接值，不会有Optional包裹\n\nlet name1: String = name!\n\n// 使用可选项时，需要进行判断是否为nil，或者进行赋值\n// 这就叫做可选项绑定\nif name != nil {\n    // do something with name!\n}\n// 或者\nif let sureName = name { // sureName的作用域仅在这个大括号内\n    // do something with sureName\n}\n```\n\n\n这里还有一个概念，叫隐式解包，也就是直接使用感叹号!，例子如下：\n```swift\nvar a: Int! = 1 // 这个叫隐式解包的可选项\n// 这种方式可以给a置为nil，当然在使用之前必须赋值非nil，否则解包crash\na = nil\n\n// 这种有时候用在跟assert的情况\n```\n\n### 3.3 空合运算符??(nil-coalescing operator)\n指的是，用在可选类型变量后面，加一个??，紧跟一个同类型的变量，表示如果??前的可选项的值为nil，那么使用??后面的变量，否则使用??之前的变量。\n```swift\nlet a: String?\nlet b = a ?? \"未知名字\" // 因为b的值为\"未知名字\"，所以b的类型不是可选项类型\nprint(\"\\(a)\") // nil\nprint(\"\\(b)\") // \"未知名字\"\n\nlet a: String?\nlet b: String? = \"1\"\nlet c = a ?? b // 此时的c是Optional(\"1\")\n\nlet num: Int? = 1\nlet num1: Int = 2\nlet result = num ?? num1 // 此时的result是Int，非可选项\n```\n\n小结一下：\n- ??前的变量必须是可选项；\n- 如果??前后的2个变量都是可选类型，不管这2个可选类型是否有值，那么被赋值变量也是可选项；\n- 如果??前的可选项有值，??后的变量为非可选项，那么返回左边变量的时候就会对左边的可选项进行强制解包；\n总之一句话，对于??这样的空合运算符，左侧的被赋值变量的类型，取决于??后面的变量类型。\n\n另外，空合运算符可以联合使用，如下：\n```swift\nlet a: Int? = 1\nlet b: Int? = 2\nlet c = a ?? b ?? 3 // 那么c是Int，非可选项\n// 这种连续使用，左侧的c取决于最右侧的最后那个变量或者值\n```\n\n**特别注意的地方**：\n- 如果从字典中通过key取值，被赋值的变量是可选项（如果没有对应的key，那么返回的是nil）\n- 如果从数组中通过index取值，被赋值的变量是非可选项（如果有越界，程序直接crash）\n```swift\nlet dic = [\"name\": \"小明\", \"age\": 1]\nlet name = dic[\"name\"] // Optional(\"小明\")\nlet gender = dic[\"gender\"] // nil\n\nlet arr = [1, 2, 3]\nlet a = arr[0] // 1\nlet b = arr[3] // crash\n```\n\n\n### 3.4 guard\n和if正好相反，if是true才去执行大括号内的语句，guard是false才去执行紧接着的大括号内语句。\n在guard的else语句中，一定要写退出当前作用域(return、throw error、break、continue等），否则会报错\n```swift\nif condition {\n    // 如果condition为true，进入这个大括号\n}\n\nguard condition else {\n    // 如果condition为false，进入这个大括号\n    return\n}\n// 如果condition为true，会执行下面的代码\n\n\nvar name: String?\n...\nguard let sureName = name else {\n    print(\"name 不存在\")\n    return\n}\nprint(\"name is: \\(name)\")\n```\n\n\n### 3.5 多重可选项\n指大的是使用2个??来声明可选项\n```swift\nlet a: Int? = 1\nlet b: Int?? = a\nlet c: Int?? = 1\n\n// 在这种情况下，a 和 c 是等价的\nprint(b == c) // true\n\nlet a: Int? = nil\nlet b: Int?? = a\nlet c: Int?? = nil // 表示c中存储的是nil，而不是Optional(nil)\n// 在这种情况下，a 和 c 是不等价的\nprint(b == 3) // false\n```\n\n千万留心下面的这种情况：\n```swift\nlet a: Int? = nil\nlet b: Int?? = a\nlet c: Int?? = nil\n\nprint((b ?? 1) ?? 2) // 2，解释：首先b中存储了a这样一个可选项，那么b第一次解包出来的是a，然后再解1次包是nil\nprint((c ?? 1) ?? 2) // 1，解释：首先c中存储了nil，那么第一次解包出来就是nil，所以将1返回。\n```\n\n\n### 3.6 对多重可选项，可以使用断点调试来探究其构成\n断点使用llvm调试命令：`frame variable -R xxx`，xxx是变量\n```xcode-llvm\n(lldb) frame variable -R xxx\n// 或者简写\n(lldb) fr v -R xxx\n```\n![llvm指令.jpg](https://image.xinwei.ltd/17111730313711711173051270.jpg)\n可以看到每一个可选变量，它们的stack frame结构。\n双重可选变量内部是包裹的一个可选变量。\n![llvm指令1.jpg](https://image.xinwei.ltd/17111730698921711173097908.jpg)\n\n\n这是《Swift知识一览》系列的第二篇文章，若感兴趣，请关注后续发表的文章。","这篇文章是《Swift知识一览》的第二篇文章。该篇文章主要介绍Swift的枚举（关联值、原始值、递归枚举）、MemoryLayout、可选项等内容。根据案例来分析枚举、可选项的用法还有注意点。","https://image.xinwei.ltd/17111730313711711173051270.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],289,"swift,swift develop,swift knowledge,swift知识,swift开发",{"id":868,"createdAt":869,"title":870,"content":871,"summary":872,"image":873,"uid":136,"user":874,"categoryId":85,"category":875,"subCategoryId":144,"subCategory":876,"comments":877,"status":9,"reason":15,"notice":15,"visitCount":878,"commentCount":112,"keywords":879},155,"2025-03-15T06:54:52.907Z","web中Google登录或者firebase登录遇到的access blocked的问题","之前我在别的文章中已经写过关于在web中实现Google/firebase登录的几种方式和如何优雅实现重定向的步骤，这篇文章是我对之前文章没有明确问题的补充。\n\n问题：\n> Access blocked: project-xxxx's request does not comply with Google's policies\n\n或者\n> Error 403:disallowed_useragent\n\n上述问题出现的原因：在mobile移动端的系统浏览器（比如Safari、Google），以及其他App（比如Facebook、Tiktok、Twitter、Snapchat等）的内置webview中，当使用Google的web auth登录集成时，如果是弹出式pop up的授权弹窗，那么基本上会被浏览器或者webview所阻止，导致产品的注册/登录成功率低。\n\n以下是在上面提到的平台platform上授权登录弹窗被阻止的截图：\n![web auth login fail 1.jpg](https://image.xinwei.ltd/IMG_3137%20%E5%B0%8F1742021242267.jpeg)\n\n![web auth login fail 2.jpg](https://image.xinwei.ltd/IMG_3136%20%E5%B0%8F1742021242059.jpeg)\n\n![web auth login fail 3.png](https://image.xinwei.ltd/IMG_31391742021207505.PNG)\n\n![web auth login fail 4.png](https://image.xinwei.ltd/image1742021399233.png)\n\n解决方案：\n我之前在其他的文章中有提到过，大家可以参考那片文章：[https://www.xinwei.ltd/article/141](https://www.xinwei.ltd/article/141)\n\n![web auth reference.png](https://image.xinwei.ltd/image1742021537792.png)\n\n这篇文章就是为了将报错“access blocked”指向我的那篇没有贴出报错截图的文章，防止大家在搜索问题答案的时候不能找到对应的解决办法。\n\n以上。\n\n","这篇文章是对之前的一片文章的补充，补充一些遇到报错的截图，详细的解决办法在另外一篇文章中已经给出，这里是报错的一些截图。","https://image.xinwei.ltd/IMG_3137%20%E5%B0%8F1742021242267.jpeg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],288,"web auth login,google login,firebase login,web google,web firebase,web access blocked",{"id":881,"createdAt":882,"title":883,"content":884,"summary":885,"image":15,"uid":136,"user":886,"categoryId":85,"category":887,"subCategoryId":419,"subCategory":888,"comments":889,"status":9,"reason":15,"notice":15,"visitCount":890,"commentCount":112,"keywords":891},46,"2024-01-30T14:21:04.528Z","iOS和Android原生工程集成React Native","我早年记录的笔记，仅供参考哈。\n# 一、iOS原生集成react-native\n1. mkdir ssa_manage_app\n2. cd ssa_manage_app\n3. mkdir ios\n4. cd ios\n5. 将iOS原生工程拖入iOS文件夹目录\n6. cd ..\n7. touch package.json 或者 npm init\n8.  vim package.json，输入\nhttp://reactnative.cn/docs/0.42/integration-with-existing-apps.html#content\n的package.json的内容，要求：\nreact-native : 0.42.0\nreact : 15.4.1\n9. cd ios， 修改cocoapods的podfile，引入react和yoga，参照\nhttp://reactnative.cn/docs/0.42/integration-with-existing-apps.html#content\n的podfile的内容，保存，并 pod update\n10. cd ..  ， 执行 npm install\n（如果没有安装react， npm install –g react@15.4.1，\n如果没有安装react-native， npm install –g react-native@0.42.0）\n11. react-native run-ios，如无异常，运行成功。\n\n异常问题：\n1. jschelpers/JavaScriptCore.h file not found\n\n解决方法：（链接：https://github.com/facebook/react-native/issues/13010）\nJust in case this help anyone, my version of cocoapods was 1.1.1 and not 1.2.0, I had to uninstall cocoapods and reinstall it again:\n```\nsudo gem uninstall cocoapods\nsudo gem install cocoapods\npod install\nbuild and enjoy\n```\n\n# 二、Android原生集成react-native\n1. cd ssa_manage_app；\n2. mkdir android\n3. 拷贝原生安卓代码（根目录下所有代码）到android目录下面\n4. 参照\n（http://reactnative.cn/docs/0.42/integration-with-existing-apps.html#content）\n修改，在android/build.gradle中，加入\n```\nmaven {\n            // All of React Native (JS, Android binaries) is installed from npm\n            url \"$rootDir/../node_modules/react-native/android\"\n        }\n```\n在android/app/build.gradle中，加入依赖\ncompile \"com.facebook.react:react-native:0.42.0\" // From node_modules.\n \n在AndroidManifest.xml中加入网络访问权限和dev配置\n \n5. 修改android/app/build.gradle中的applicationId为”com.ymq.badminton”；\n6. 使node.js脚本的启动和安装从原生工程的实际路径执行\n修改node_modules/react-natvie/local_cli/ runAndroid/ runAndroid.js，\n在function buildAndRun(args)中，加入applicationId变量获取，\n```\nonst applicationId = fs.readFileSync(\n       'app/build.gradle',\n       'utf8'\n    ).match(/applicationId \"(.+?)\"/)[1];\n```\n然后修改runOnSpecificDevice(args, cmd, packageName, adbPath);\n为runOnSpecificDevice(args, cmd, applicationId, adbPath);\n，\n修改runOnAllDevices(args, cmd, packageName, adbPath);\n为runOnAllDevices(args, cmd, applicationId, adbPath);\n\n在`function tryLaunchAppOnDevice(device, packageName, adbPath, mainActivity)`\n方法中，修改`const adbArgs = ['-s', device, 'shell', 'am', 'start', '-n', packageName + '/. ' + mainActivity];`\n为`const adbArgs = ['-s', device, 'shell', 'am', 'start', '-n', packageName + '/.activity.' + mainActivity];`\n\n在f`unction runOnAllDevices(args, cmd, packageName, adbPath)`\n方法中，修改\n`const fallbackAdbArgs = [\n          'shell', 'am', 'start', '-n', packageName + '/.MainActivity'\n        ];`\n为\n`const fallbackAdbArgs = [\n          'shell', 'am', 'start', '-n', packageName + '/.activity.MainActivity'\n        ];`\n\n7. cd ssa_manage_app\n8. react-native run-android\n\n异常问题：\n1. 检查android sdk的环境变量配置\necho $ANDROID_HOME\n查看输出的对应路径下是否含有sdk\n\n查看设备的连接情况\nadb devices\n\n2.\n`Scanning 432 folders for symlinks in /Users/hanweixing/Desktop/ssa_manage_app_1/node_modules (5ms)\nStarting JS server...\nBuilding and installing the app on the device (cd android && ./gradlew installDebug)...\nCould not install the app on the device, read the error above for details.\nMake sure you have an Android emulator running or a device connected and have set up your Android development environment:`\n\n[https://facebook.github.io/react-native/docs/android-setup.html](https://facebook.github.io/react-native/docs/android-setup.html)\n解决办法：（链接：[https://github.com/facebook/react-native/issues/8868](https://github.com/facebook/react-native/issues/8868)）\n\n> you might check the permissions on android/gradlew\nthey should be 755 not 644\nrun chmod 755 android/gradlew inside your app root folder\nthen run react-native run-android\nand it should work again.\n\n在 android/app/ 目录下面执行 chmod 755 gradlew\n\n3.\nFAILURE: Build failed with an exception.\n```\n* What went wrong:\nExecution failed for task ':app:transformClassesWithJarMergingForDebug'.\n> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: okhttp3/internal/ws/RealWebSocket$1.class\n \n* Try:\nRun with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.\n \nBUILD FAILED\n \nTotal time: 9.079 secs\nCould not install the app on the device, read the error above for details.\nMake sure you have an Android emulator running or a device connected and have\nset up your Android development environment:\n```\n[https://facebook.github.io/react-native/docs/android-setup.html](https://facebook.github.io/react-native/docs/android-setup.html)\n解决方案：\n（https://github.com/facebook/react-native/issues/12646、\nhttps://github.com/square/okhttp/issues/3040#issuecomment-271364687）\n\n在android/app/build.gradle中，加入\n```\nconfigurations.all {     // OkHttp 3.5.0+ includes the websockets API, so we need this to prevent a conflict     exclude module: 'okhttp-ws' }\n```\n\n4.\nFAILURE: Build failed with an exception.\n```\n* What went wrong:\nExecution failed for task ':app:installDebug'.\n> com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: Failed to finalize session : INSTALL_FAILED_CONFLICTING_PROVIDER: Package couldn't be installed in /data/app/com.ymq.min-1: Can't install because provider name com.ymq.min.AUTH_XGPUSH (in package com.ymq.min) is already used by com.ymq.badminton\n \n* Try:\nRun with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.\n \nBUILD FAILED\n \nTotal time: 1 mins 23.928 secs\nCould not install the app on the device, read the error above for details.\nMake sure you have an Android emulator running or a device connected and have\nset up your Android development environment:\nhttps://facebook.github.io/react-native/docs/android-setup.html\n```\n解决方法：\n在app/build.gradle中，修改applicationId为com.ymq.badminton\n\n5.\nBUILD SUCCESSFUL\n```\nTotal time: 1 mins 9.594 secs\nRunning /Users/hanweixing/Library/android-sdk-macosx/platform-tools/adb -s emulator-5554 reverse tcp:8081 tcp:8081\nStarting the app on emulator-5554 (/Users/hanweixing/Library/android-sdk-macosx/platform-tools/adb -s emulator-5554 shell am start -n com.ymq.min/.MainActivity)...\nStarting: Intent { cmp=com.ymq.min/.MainActivity }\nError type 3\nError: Activity class {com.ymq.min/com.ymq.min.MainActivity} does not exist.\n```\n解决方法：\n修改run-android.js，指定从applicationId来读取mainActivity\n （参考链接：https://github.com/facebook/react-native/issues/5546）\n\n注意：\n安卓中如果集成了react native界面，需要的配置还要有：\n1. \n```\n\u003Cactivity\nandroid:name=\"com.ymq.badminton.activity.MainActivity\"\n             android:label=\"@string/app_name\"\n-            android:screenOrientation=\"portrait\" />\n+            android:screenOrientation=\"portrait\" \n+            android:exported=\"true\" />\n```\n\n2. android／app／build.gradle\n```\napply plugin: 'com.android.application'\napply plugin: 'android-apt'\n+ apply from: \"../../node_modules/react-native/react.gradle\"\n+ def enableSeparateBuildPerCPUArchitecture = false\n+ def enableProguardInReleaseBuilds = false\nandroid {\n    signingConfigs {\n        config_release {\n\n \n         signingConfig signingConfigs.config_release\n         multiDexEnabled true\n-\n+\tndk {\n+            abiFilters \"armeabi-v7a\", \"x86\"\n+        }\n+\t\n+\tpackagingOptions {\n+            exclude \"lib/arm64-v8a/librealm-jni.so\"\n+        }\n     }\n+\t\n+    splits {\n+         abi {//多平台支持\n+             reset()\n+             enable enableSeparateBuildPerCPUArchitecture\n+             universalApk false  // If true, also generate a universal APK\n+             include \"armeabi-v7a\", \"x86\"\n+         }\n+     }   \n+    \n     dexOptions {\n         jumboMode true\n     }\n\n \n dependencies {\n \n-    compile \"com.facebook.react:react-native:0.42.0\" // From node_modules.\n+    compile \"com.facebook.react:react-native:+\" // From node_modules.\n \n-    compile fileTree(include: ['*.jar'], dir: 'libs')\n+    compile fileTree(dir: \"libs\", include: [\"*.jar\"])\n     //testCompile 'junit:junit:4.12'\n \n     // custom alert\n\n```\n\n3. android/app/gradle.properties\n```\norg.gradle.jvmargs=-Xmx2048m -XX\\:MaxPermSize\\=512m -XX\\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\\=UTF-8\n   org.gradle.daemon=true\n   org.gradle.parallel=true\n+ android.useDeprecatedNdk=true\n   systemProp.http.proxyPort=8080\n   versCode=158\n   versName=1.2.7\n   No newline at end of file\n```\n\n4. android/app/src/main/AndroidManifest.xml\n```\n\u003Cuses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\" />\n      \u003Cuses-permission android:name=\"android.permission.READ_LOGS\" />\n+    \u003Cuses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n      \u003Cuses-permission android:name=\"android.permission.SYSTEM_OVERLAY_WINDOW\" />\n      \u003Cuses-permission android:name=\"android.permission.WAKE_LOCK\" />\n      \u003Cuses-permission android:name=\"android.permission.USE_CREDENTIALS\" />\n      \u003Cuses-permission android:name=\"android.permission.MANAGE_ACCOUNTS\" />\n```\n\n5. android/build.gradle\n```\nallprojects {\n    repositories {\n        mavenCentral()\n+\t\tmavenLocal()\n        jcenter()\n        maven {\n            url \"https://jitpack.io\"\n```\n\n6. android/app/build.gradle\n```\n}\n \n configurations.all {\n-    // OkHttp 3.5.0+ includes the websockets API, so we need this to prevent a conflict\n-    exclude module: 'okhttp-ws'\n+    resolutionStrategy.force 'com.squareup.okhttp3:okhttp:3.4.1'\n }\n```\n\n7. 其他的具体看工程配置。\n","在iOS和Android中如何集成react native，这里以我的工程做一个记录。这是一篇比较老的笔记，很早记录的，现在翻出来以供大家御览。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],279,"react native,react native工程,react native集成,react native ios android,react native app",{"id":893,"createdAt":894,"title":895,"content":896,"summary":897,"image":898,"uid":136,"user":899,"categoryId":85,"category":900,"subCategoryId":257,"subCategory":901,"comments":902,"status":9,"reason":15,"notice":15,"visitCount":903,"commentCount":112,"keywords":904},107,"2024-04-16T16:43:26.601Z","iPad换电池","## 一、整个过程所需的工具\n![我的ipad.jpeg](https://image.xinwei.ltd/IMG_2743%20%E5%B0%8F1713280931334.jpeg)\n![拆iPad工具.jpeg](https://image.xinwei.ltd/IMG_2744%20%E5%B0%8F1713280984877.jpeg)\n一个需要换电池的iPad\n一个iPad电池\n拆机工具：橡胶吸盘、密封胶、黄色铲子、深蓝色三角小铲、黑色塑料双杆钳子、一个红尾黑杆的梅花起子。\n（照片中还有一个黑色的梅花起子和蓝色的撬杆，这2个我没咋使用到，这个纯黑色的起子可能是其他型号iPad的螺丝刀。）\n我这里还使用了刀片。\n\n拆机工具是在电商平台上购买电池时附带的工具，一般都很齐全，大家要看准iPad背面金属壳上的型号（比如A1474）来购买电池。\n\n## 二、iPad的结构\niPad是没有外部螺丝的，结构是分为3层。\n第一层，是透明的触摸板，这个触摸板是接收手指交互的电容屏，所有的交互都是通过电容屏接收，然后通过管线反馈给中间屏幕层。\n第二层，是黑色（关机状态下）的屏幕层，屏幕层不能接收手指交互，单纯的作为显示而已。\n第三层，是主板和电池层，这一层有一个长条的主板还有排线，以及占据近80%比例的锂电池。\n\n![ipad机器结构.jpeg](https://image.xinwei.ltd/IMG_2734%20%E4%B8%AD1713282169348.jpeg)\n\n如图所示，左侧透明是第一层的电容屏，竖直起来的是黑色的屏幕，底部是主板和电池。上面的2层，都是通过home键区域右角上的主板相连接。\n\niPad的每一层都是使用胶水粘合的。（主板部分当然会有螺丝）\n\n> 切记：第一层有3个窄的排线，第二层有一个窄的排线，都处于home键一侧的右边角部分，贴嵌在底层的主板上。所以拆解的时候，不要强拉硬扯屏幕，防止把排线扯坏了。\n\n## 三、拆机步骤\n### 3.1 去掉钢化膜\n如果你的iPad有贴膜的话，首先最好去掉这个贴膜，否则不容易打开屏幕壳子。\n\n### 3.2 拆卸iPad第一层的电容屏\n因为iPad是密封起来的，外部没有暴露螺丝，所以拆机都是需要把屏幕第一层吸附起来。\n![拆屏幕.png](https://image.xinwei.ltd/image1713282542825.png)\n\n这一步，因为密封胶水黏性很强，所以比较难吸起屏幕。可以尝试使用吹风机来回吹白色边缘，让胶水发热，方便吸盘吸起屏幕。\n专业的维修店会有平铺的加热板，会把屏幕侧贴在加热板上，让胶水容易脱落。\n\n多次用吸盘拉扯屏幕的一角，发现屏幕有微微起缝的时候，可以用刀片卡住细缝，然后使用深蓝色三角撬片沿着细缝慢慢切割，沿着白色边缘屏幕将胶水划开。\n\n当四周的胶水都起开后，第一层的透明电容屏，就可以轻轻的翻起来。\n\n![第一层电容屏.png](https://image.xinwei.ltd/image1713283418122.png)\n\n后续将主板上的螺丝取下来后，可以将电容屏单独取出来，如下：\n![拆下来的电容屏.png](https://image.xinwei.ltd/IMG_2725%20%E4%B8%AD1713283752662.jpeg)\n\n\n\n### 3.3 拆卸iPad第二层的屏幕层\n第二层的屏幕层。\n四个角上有螺丝，然后使用了胶水粘贴的。\n所以先找到屏幕四个角落处的螺丝，使用梅花起子将螺丝取出。然后使用黄色的撬板，将屏幕撬起来。\n如下图的照片，可以看到四个角上都有螺丝洞。\n\n主板上取下螺丝后，可以将屏幕取下来：\n![ipad屏幕.jpeg](https://image.xinwei.ltd/IMG_2727%20%E4%B8%AD1713283883549.jpeg)\n\n### 3.4 取主板上的螺丝下来，让电容屏和屏幕分离出来\n如图所示，主板上有一个金属片，盖着主板的排线。按照截图，取下3个螺丝\n![要取下的金属盖.png](https://image.xinwei.ltd/image1713284262136.png)\n取下金属盖之后，需要使用塑料镊子，将排线连接处夹起排线：\n![取下金属盖.png](https://image.xinwei.ltd/image1713284195174.png)\n![主板.jpeg](https://image.xinwei.ltd/IMG_2730%20%E4%B8%AD1713284410511.jpeg)\n\n单独取下电容屏和屏幕后，可以看到主板和电池\n![iPad内部.jpeg](https://image.xinwei.ltd/IMG_2728%20%E4%B8%AD1713284487491.jpeg)\n\n### 3.5 拆卸电池\n如图所示，是电池的电源片和主板的连接处，用螺丝固定起来了。所以需要使用起子取出螺丝。\n![电池螺丝.png](https://image.xinwei.ltd/image1713284637960.png)\n\n取出螺丝后，用黄色撬片，从螺丝处以外的任意一边，把电池撬起来，同样的，电池都用胶水粘在金属壳子上，比较难弄，慢慢撬。\n\n注意不要把电池强扯下来，而是用撬板把胶水都撬断。\n\n电池的螺丝连接处，我忘记给电源连接片拍照了，但你可以看新买的那个锂电池，锂电池的连接电源片是环形的，金属壳上有螺丝圆柱，锂电池的连接片是套在这个圆柱上的。\n\n所以当你把电池粘贴的胶水都撬断后，还要继续撬电池连接处的主板部分，将这部分的主板撬起一些角度来，方便将旧电池连接处的环形电源片从圆柱上取出来，旧电池才能完全分离出来。\n\n整个拆卸步骤都了解之后，那么还原也是相当简单的。\n\n### 3.6 组装回来\n- 使用胶水在原胶水位置涂抹，套好电池的环形电源片后，将电池平铺到涂有胶水的金属壳上。\n- 将第二屏幕的排线插在主板上，将第一层的3个排线插在主板上，盖好金属盖，上好螺丝。\n- 放下第二层屏幕，拧好四个角上的螺丝，清理屏幕上的灰尘。\n- 在金属框四周原胶水位置涂抹胶水，清理电容屏上的灰尘，放下第一层电容屏，贴合好。\n\n完工。\n\n\n","记录一下iPad换电池的经历，供所有想自己动手换电池的同学们参考，可以根据图文教程，提前了解如何拆卸以及iPad内部打开后的样貌。欢迎经验交流。","https://image.xinwei.ltd/IMG_2743%20%E5%B0%8F1713280931334.jpeg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":257,"name":259,"parentId":85},[],264,"ipad battery,ipad fix,ipad换电池,ipad维修",{"id":906,"createdAt":907,"title":908,"content":909,"summary":910,"image":15,"uid":136,"user":911,"categoryId":85,"category":912,"subCategoryId":312,"subCategory":913,"comments":914,"status":9,"reason":15,"notice":15,"visitCount":915,"commentCount":112,"keywords":916},83,"2024-02-19T11:45:18.702Z","docker的日常操作命令一览表","## docker的常见操作命令\n### 1. 拉取镜像到本地\n```\ndocker pull nginx // 默认nginx的最新版本\n```\n\n### 2. 查看本地的镜像\n```\ndocker image ls\n\ndocker image list\n\ndocker images\n```\n以上三种都是等价的。\n\n### 3. 删除本地容器\n```\ndocker image rm imageId\n\ndocker image remove imageId\n\ndocker rmi imageId\n```\n### 4. 创建并运行容器\n```\ndocker run --name testName -d nginx:latest\n```\n- -name 指定后面的参数为容器名\n- -d 后台运行容器\n- nginx是镜像名\n- :latest是最新版本\n### 5. 查看所有状态下的容器\n```\ndocker ps -a\n```\n### 6. 重启容器\n```\ndocker restart containerId\n```\n> docker restart 和 docker container restart 等价\n### 7. 停止容器\n```\ndocker stop containerId\n```\n### 8. 删除容器\n```\ndocker rm containerId\n```\n### 9. 编译本地的Dockerfile文件为镜像\n```\ndocker build -t test:latest .\n```\n将当前命令行所在的目录下的Dockerfile文件编译成image，容器命名为:test，版本是：latest\n### 10.  创建和运行容器时挂载和暴露端口\n```\ndocker run --name containerName -p 80:8080 -v /root/content:/content -d nginx:alpine\n```\n- --name 制定后面的参数为容器名\n- -p 将容器的8080端口映射到宿主机的80端口\n- -v 将宿主机的/root/content 挂载到容器的/content目录\n- -d 后台运行并打印containerId\n- nginx:alpine 指定镜像名和版本\n","这里介绍日常在使用docker容器开发时常用的命令，以作手册参考.",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":312,"name":314,"parentId":85},[],259,"docker,docker container,docker容器,docker开发",{"id":918,"createdAt":919,"title":920,"content":921,"summary":922,"image":15,"uid":136,"user":923,"categoryId":85,"category":924,"subCategoryId":47,"subCategory":925,"comments":926,"status":9,"reason":15,"notice":15,"visitCount":927,"commentCount":112,"keywords":928},77,"2024-02-02T11:28:06.163Z","iOS中FMDB数据库框架使用示例","iOS中如何方便快捷的使用数据库存储数据，FMDB是一个不错的选择，这里给出具体使用方法。\n\n具体的数据库操作语句如下：\n\n```\n// 建表\n\n// 数据库路径\nNSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:dbExtention];\n// 建数据库\nFMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:path];\n    \n [dbQueue inDatabase:^(FMDatabase *db) {\n        // 建表\n        NSString * sql = [NSString stringWithFormat:@\"CREATE TABLE '%@' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'fileName' text, 'timeStamp' INTEGER, 'duration' INTEGER, 'noNum' INTEGER, 'score' text, 'hasPlayed' INTEGER)\", tableName];\n\n        BOOL res = [db executeUpdate:sql];\n        NSLog(@\"建表%@\", (res ? @\"成功\" : @\"失败\"));\n    }];\n\n// 删表    \n    [dbQueue inDatabase:^(FMDatabase *db) {\n        \n        NSString * sql = [NSString stringWithFormat:@\"DROP TABLE '%@'\", tableName];\n        BOOL res = [db executeUpdate:sql];\n        \n        NSLog(@\"删表%@\", (res ? @\"成功\" : @\"失败\"));\n    }];\n\n// 判断表是否存在\n[dbQueue inDatabase:^(FMDatabase *db) {\n        \n        NSString *existsSql = [NSString stringWithFormat:@\"select count(name) as countNum from sqlite_master where type = 'table' and name = '%@'\", tableName];\n        FMResultSet *res = [db executeQuery:existsSql];\n        \n        NSLog(@\"表存在%@\", (res ? @\"成功\" : @\"失败\"));\n    }];\n\n// 插入数据\n[dbQueue inDatabase:^(FMDatabase *db) {\n        \n        NSString *fileName = record.fileName;\n        NSInteger timeStamp = record.timeStamp;\n        NSInteger duration = record.duration;\n        NSInteger noNum = record.noNum;\n        NSString *score = record.score;\n        NSInteger hasPlayed = record.hasPlayed;\n        \n        NSString * sql = [NSString stringWithFormat:@\"INSERT INTO '%@' ('fileName', 'timeStamp', 'duration', 'noNum', 'score', 'hasPlayed') VALUES ('%@', '%ld', '%ld', '%ld', '%@', '%ld')\", tableName, fileName, timeStamp, duration, noNum, score, hasPlayed];\n        \n        BOOL res = [db executeUpdate:sql];\n        NSLog(@\"插入%@\", (res ? @\"成功\" : @\"失败\"));\n        \n        if(callBack) {\n            \n            callBack(res);\n        }\n    }];\n\n// 更新数据\nNSString * sql1 = [NSString stringWithFormat:@\"update '%@' set 'noNum' = '%ld' where timeStamp = %ld\", tableName, noNum, timeStamp];\nNSString * sql2 = [NSString stringWithFormat:@\"update '%@' set 'score' = '%@' where timeStamp = %ld\", tableName, score, timeStamp];\nNSString * sql3 = [NSString stringWithFormat:@\"update '%@' set 'hasPlayed' = '%ld' where timeStamp = %ld\", tableName, hasPlayed, timeStamp];\n        \n        BOOL res1 = [db executeUpdate:sql1];\n        BOOL res2 = [db executeUpdate:sql2];\n        BOOL res3 = [db executeUpdate:sql3];\n        \n        NSLog(@\"插入%@\", ((res1 && res2 && res3) ? @\"成功\" : @\"失败\"));\n\n// 查询\nNSMutableArray *recordArr = [NSMutableArray array];\nNSString * sql = [NSString stringWithFormat:@\"SELECT * FROM '%@' ORDER BY timeStamp ASC\", tableName];\nFMResultSet * rs = [db executeQuery:sql];\n\n        while ([rs next]) {\n            \n            NSString *filePath = [rs stringForColumn:@\"fileName\"];\n            NSInteger timeStamp = [rs longForColumn:@\"timeStamp\"];\n            NSInteger duration = [rs intForColumn:@\"duration\"];\n            NSInteger noNum = [rs intForColumn:@\"noNum\"];\n            NSString *score = [rs stringForColumn:@\"score\"];\n            NSInteger hasPlayed = [rs intForColumn:@\"hasPlayed\"];\n            \n            CYUserRecord *record = [[CYUserRecord alloc] init];\n            record.fileName = filePath;\n            record.timeStamp = timeStamp;\n            record.duration = duration;\n            record.noNum = noNum;\n            record.score = score;\n            record.hasPlayed = hasPlayed;\n            \n            [recordArr addObject:record];\n        }\n\nNSString * sql1 = [NSString stringWithFormat:@\"SELECT * FROM '%@' WHERE patched = 0 ORDER BY timeStamp ASC\", tableName];\n\n删除某一条数据：\nNSString * sqlDelete = [NSString stringWithFormat:@\"DELETE FROM '%@' WHERE fileName = '%@'\", tableName, fileName];\n[db executeUpdate:sqlDelete];\n\n从一个表直接往另一个表添加数据：\ninsert into table1 select  * from table2\n```","iOS中如何方便快捷的使用数据库存储数据，FMDB是一个不错的选择，这里给出具体使用方法。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],258,"ios,ios fmdb,ios sqlite,ios mysql,ios数据库,数据库",{"id":930,"createdAt":931,"title":932,"content":933,"summary":934,"image":935,"uid":136,"user":936,"categoryId":85,"category":937,"subCategoryId":938,"subCategory":939,"comments":941,"status":9,"reason":15,"notice":15,"visitCount":942,"commentCount":112,"keywords":943},138,"2024-11-24T07:10:08.316Z","mongodb数据库的mongoose操作","## 一、背景\n在开发koa后端项目中使用mongodb数据库，并且安装mongoose操作数据库，在连接数据库，并且save document时，遇到连接报错以及数据操作报错问题。\n\n## 二、问题\n### 2.1 问题1\n> MongoDB connection error: MongoServerError: Authentication failed.\n    at Connection.sendCommand (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/connection.ts:525:17)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at async Connection.command (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/connection.ts:597:22)\n    at async executeScram (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/auth/scram.ts:113:20)\n    at async ScramSHA256.auth (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/auth/scram.ts:60:12)\n    at async performInitialHandshake (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/connect.ts:163:7)\n    at async connect (/Users/hanweixing/Desktop/xinwei/lottery/backend/lottery-backend/node_modules/mongodb/src/cmap/connect.ts:43:5) {\n  errorResponse: {\n    ok: 0,\n    errmsg: 'Authentication failed.',\n    code: 18,\n    codeName: 'AuthenticationFailed'\n  },\n  ok: 0,\n  code: 18,\n  codeName: 'AuthenticationFailed',\n  connectionGeneration: 0,\n  [Symbol(errorLabels)]: Set(2) { 'HandshakeError', 'ResetPool' }\n}\n\n![mongodb error](https://image.xinwei.ltd/11732431368761.png)\n\n\n### 2.2 问题2\n> MongoParseError: credentials must be an object with 'username' and 'password' properties\n\n## 三、解决问题\n以上2个问题，其实是一个问题，主要原因在于使用mongoose连接数据库时，数据库的user、password没有正确设置的问题。\n\nmongoose正确使用的官方网址是：[https://mongoosejs.com/](https://mongoosejs.com/)\n\n但是官方网站上给出的示例，极其简单，并没有针对mongoosede的不同版本说明对应的配置，导致很多人容易设置错误，stack overflow上也有特别多的人提出相同的issue。\n\n以下是官方给出的示例：\n```js\nconst mongoose = require('mongoose');\nmongoose.connect('mongodb://127.0.0.1:27017/test');\n\nconst Cat = mongoose.model('Cat', { name: String });\n\nconst kitty = new Cat({ name: 'Zildjian' });\nkitty.save().then(() => console.log('meow'));\n```\n在实际的开发中，mongodb和其他数据库一样，都有用户名和密码。所以一定会有authority的配置。\n\n下面我给出我项目中对应版本的代码，供大家参考。\n- mongoose版本：\n```package.json\n\"mongoose\": \"^8.8.1\"\n```\n- 代码：\n```js\nconst url = `mongodb://@127.0.0.1:27017/dbName`; // dbName是你新的mongodb database名称\n  mongoose\n    .connect(url, {\n      authSource: \"admin\",\n      user: Env_Mongodb_Username, // 这里设置mongodb的username\n      pass: Env_Mongodb_Password, // 这里设置mongodb的password\n    })\n    .then(async () => {\n      console.log(\"Connected to MongoDB\");\n    })\n    .catch((err) => {\n      console.error(\"MongoDB connection error:\", err);\n      process.exit(1);\n    });\n```\n\n希望对大家有帮助。koa项目分享，目前已经分享了两篇内容，后续我会继续进行分享。\n\n欢迎大家关注“新卫网络科技”微信公众号。\n\n\n","koa项目，使用mongodb数据库，正确的使用mongoose进行验证连接的操作。","https://image.xinwei.ltd/11732431368761.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},29,{"id":938,"name":940,"parentId":85},"后端",[],245,"koa mongodb,koa mongoose,mongodb mongoose,mongodb database,mongoose connect, MongoServerError,Authentication failed,mongodb数据库,mongoose数据库连接",{"id":945,"createdAt":946,"title":947,"content":948,"summary":949,"image":15,"uid":136,"user":950,"categoryId":85,"category":951,"subCategoryId":144,"subCategory":952,"comments":953,"status":9,"reason":15,"notice":15,"visitCount":942,"commentCount":9,"keywords":954},114,"2024-06-08T11:22:17.245Z","一些常用的CSS效果","## 一、文本相关\n### 1.1 一行缩略\n```css\n  .name {\n        margin-left: 4px;\n        font-size: 10px;\n    \n        max-width: 80px;\n\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        overflow: hidden;\n      }\n```\n\n### 1.2 多行缩略\n```css\n    .des {\n      margin-top: 20px;\n      font-size: 12px;\n\n      max-width: 50%;\n\n      display: -webkit-box;\n      -webkit-box-orient: vertical;\n      -webkit-line-clamp: 2;\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n```\n\n## 二、动画相关\n###2.1 平移或者缩放\n```css\ntransform: translateY(20px);\n\ntransform: rotate(45deg);\n```\n\n### 2.2 应用动画\n```css\n.card-normal {\n  opacity: 0;\n  transform: rotate(45deg);\n}\n\n.card-addAnimation {\n  opacity: 1;\n  transition: opacity transform .3s;\n\n  // 或者应用所有的变化\n  // transition: all 0.3s;\n}\n```\n\n### 2.3 关键帧动画\n```css\n\t.loading {\n\t\tanimation: loading-android 1s 0s linear infinite;\n\t}\n  \n\t@keyframes loading-android {\n\t\t0% {\n\t\t\ttransform: rotate(0deg);\n\t\t}\n\n\t\t50% {\n\t\t\ttransform: rotate(180deg);\n\t\t}\n\n\t\t100% {\n\t\t\ttransform: rotate(360deg);\n\t\t}\n\t}\n```\n\n## 三、高斯模糊\n> 这个需要对标签`\u003Cimage src=\"\" />`这样的或者`\u003Cvideo src=\"\" />`这样的标签才会生效，普通div不会应用高斯模糊效果。\n```css\n.avatar {\n  filter: blur(16px);\n}\n```\n\n## 四、hover动画\n```css\n.name-normal {\n  font-size: 16px;\n  color: rgba(0, 0, 0, 0.5);\n}\n\n.name-normal:hover {\n  font-size: 22px;\n  color: rgba(0, 0, 0, 1);\n}\n```\n\n## 五、scss相关\n### 5.1 如果是在vue中引入`xxx.scss`样式文件\n```vue\n\u003Cstyle lang=\"scss\" scoped>\n@import '../../static/style/global.scss'\n\u003C/style>\n```\n\n### 5.2 常用的“/”除法现在很多scss会warning\n由于斜线/的除法，会让人很困惑，所以`dart-scss`就引入了新的写法：\n```global.scss\n  @use \"scss:math\";\n  $card-ratio: math.div(218, 390);\n```\n然后就可以在vue的`style`模块进行引入了\n```xxx.vue\n\u003Cstyle lang=\"scss\" scoped>\n@import '../../static/style/global.scss'\n\n.image {\n  height: calc(100% * $card-ratio);\n}\n\u003C/style>\n```\n\n\n","这篇文章把我常用的一些css效果的相关代码贴出来，方便自行取用，免得再到处搜索了。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"html css,js css,html css,css,css效果",{"id":956,"createdAt":957,"title":958,"content":959,"summary":960,"image":961,"uid":136,"user":962,"categoryId":85,"category":963,"subCategoryId":144,"subCategory":964,"comments":965,"status":9,"reason":15,"notice":15,"visitCount":966,"commentCount":112,"keywords":967},129,"2024-10-14T12:24:50.032Z","前端上传文件那些事的记录（一）","记录一下常用的几个小代码片段，之前在nuxt的file上传那篇文章中记录过，今天又使用到了，就随手记录一下。\n\n## 一、背景\n服务端定义了一个`post`类型的上传接口，并且告知`key`是`file`，前端需要调用接口，并使用组件上传。前端目前使用的是element-plus框架中的el-uploader组件。\n\n\n## 二、前端展示\n### 2.1 上传组件\n```vue\n\u003Cel-upload\n      class=\"upload-demo\"\n      drag\n      ref=\"uploadRef\"\n      action=\"#\"\n      accept=\".xlsx,.csv\"\n      :auto-upload=\"false\"\n      :limit=\"1\"\n      :http-request=\"requestUpload\"\n      :on-success=\"successPicture\"\n      :on-error=\"errorPicture\"\n      :on-change=\"uploadChange\"\n      :on-exceed=\"handleExceed\"\n      multiple\n>\n    \u003Ci-ep-uploadFilled class=\"el-icon--upload\" />\n       \u003Cdiv class=\"el-upload__text\">\n            将文件拖到此处 \u003Cem>或点击上传\u003C/em>\n       \u003C/div>\n       \u003Ctemplate #tip>\n            \u003Cdiv class=\"el-upload__tip\">\n                 ⚠️支持“.xlsx和.csv文件”\n            \u003C/div>\n       \u003C/template>\n\u003C/el-upload>\n```\n这个组件中赋值了`http-request`，所以使用到的是自定义上传接口，并不是直接通过action上传。\n\n### 2.2 调用接口\n```setup中\n//自定义上传\nconst requestUpload = (option)=>{\n    const file = option.file;\n    awardPunishUploadApi(file)\n}\n```\n\n### 2.3 封装的接口中\n```ts\nexport function awardPunishUploadApi(file: File) {\n    return request({\n        url: '/admin/reward/upload',\n        method: 'post',\n        data: {\n            'file': file\n        },\n        headers: { \n            'Content-Type': 'multipart/form-data',\n        },\n    });\n}\n```\n(request中封装了axios，详细代码不再贴上，大家应该都会写的）\n\n其实这样就能调用成功。\n![web upload file](https://image.xinwei.ltd/image1728908538200.png)\n\n文章到此为止了，简短吧。\n\n","记录一下常用的几个小代码片段，之前在nuxt的file上传那篇文章中记录过，今天又使用到了，就随手记录一下。","https://image.xinwei.ltd/image1728908538200.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],239,"web file upload,formdata,file post,vue",{"id":969,"createdAt":970,"title":971,"content":972,"summary":973,"image":15,"uid":136,"user":974,"categoryId":85,"category":975,"subCategoryId":7,"subCategory":976,"comments":977,"status":9,"reason":15,"notice":15,"visitCount":978,"commentCount":112,"keywords":979},82,"2024-02-07T07:04:32.408Z","Android签名的那些命令","# 安卓签名\n## 1. 生成keystore文件\n```\nkeytool -genkey -alias user-AndroidKey -keyalg RSA -validity 400000 -keystore user.keystore\n```\n输入密钥库口令: user#1001\n您的名字与姓氏是什么? user\n您的组织单位名称是什么? xxx科技有限公司\n您的组织名称是什么?xxx科技有限公司\n您所在的城市或区域名称是什么?杭州\n您所在的省/市/自治区名称是什么?杭州\n您的组织名称是什么?该单位的双字母国家/地区代码是什么?CN\n\nCN=xxx, OU=xxx科技有限公司, O=xxx科技有限公司, L=杭州, ST=杭州, C=ch是否正确?\n\n## 2. 对apk进行签名\n```\njarsigner -verbose -keystore user.keystore -signedjar usersigned.apk to-sign-app.apk user-AndroidKey -digestalg SHA1 -sigalg MD5withRSA\n```\n\n```\n输入密钥库的密码短语: user#1001\n\nkey alias: user-AndroidKey\n\nkey password: user#1001\n```\n\n## 3. 查看应用签名\n```\nkeytool -list -keystore user.keystore -alias user-AndroidKey -v\n```\n","这里介绍Android的apk签名的一些命令，包括签名和查看签名。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":7,"name":327,"parentId":85},[],235,"android,android apk,android sign,android sign apk,android签名,android开发",{"id":981,"createdAt":982,"title":983,"content":984,"summary":985,"image":986,"uid":136,"user":987,"categoryId":85,"category":988,"subCategoryId":47,"subCategory":989,"comments":990,"status":9,"reason":15,"notice":15,"visitCount":991,"commentCount":112,"keywords":992},105,"2024-03-27T14:44:09.847Z","Swift知识一览（三）","## 一、枚举的内存布局\n### 1.1 内存分配\n从上一章内容《Swift知识一览（二）》[https://www.xinwei.ltd/article/103](https://www.xinwei.ltd/article/103) 中，我们知道可以通过`MemoryLayout`来查看枚举类型所占的字节数。如下例子：\n```swift\nenum Direction {\n    case east, south, west, north\n}\n// 打印该枚举类型所占内存\nprint(MemoryLayout.size(ofValue: Direction.east)) // 1，解释：1个字节可以存储该枚举类型\n\n// 上述枚举类型，在实际的内存存储中，从east开始存储0，1，2，3\n```\n\n- 原始值不影响枚举类型的内存存储大小的。\n\n每一个枚举成员都共用1个字节，其实针对上面的枚举案例或者有原始值的枚举，针对每一个case的枚举变量，存储的都是0、1、2、3这样的排序数字。原始值类型枚举不影响枚举成员内存大小。\n\n- 关联值，内存大小是最长的那个关联case长度 + 1字节的case识别。其他case枚举成员都共用关联类型最长的那个枚举成员的内存空间。\n\n举例如下：\n```swift\nenum CustomNumber {\n    case one(Int, Int, Int)\n    case two(Int, Int)\n    case three(Int)\n    case four(Bool)\n    case other\n}\n\n// 类似上面这种枚举，内存是：\nsize: 3 * 8 + 1 = 25 // 因为最大关联类型的参数最多有3个，足够存储其他关联类型。1是用来表示存储第几个case\n// 查看内存分布的时候，可以看到1字节中存储的是从对应于枚举成员排序的0、1、2、3、4这几个值\nalignment: 8\nstride: 32\n\n// 只有1个关联类型参数的情况\nenum CustomEnum {\n    case one(Int)\n}\n// size、stride、aligment应该都是8（64位平台）。无须存储识别case枚举成员的那1个字节。\n```\n\n\n### 1.2 Swift中获取变量内存地址\n使用`withUnsafePointer`来获取变量内存地址。\n函数定义：\n```swift\n/// Invokes the given closure with a pointer to the given argument.\n///\n/// The `withUnsafePointer(to:_:)` function is useful for calling Objective-C\n/// APIs that take in parameters by const pointer.\n///\n/// The pointer argument to `body` is valid only during the execution of\n/// `withUnsafePointer(to:_:)`. Do not store or return the pointer for later\n/// use.\n///\n/// - Parameters:\n///   - value: An instance to temporarily use via pointer. Note that the `inout`\n///     exclusivity rules mean that, like any other `inout` argument, `value`\n///     cannot be directly accessed by other code for the duration of `body`.\n///     Access must only occur through the pointer argument to `body` until\n///     `body` returns.\n///   - body: A closure that takes a pointer to `value` as its sole argument. If\n///     the closure has a return value, that value is also used as the return\n///     value of the `withUnsafePointer(to:_:)` function. The pointer argument\n///     is valid only for the duration of the function's execution.\n///     It is undefined behavior to try to mutate through the pointer argument\n///     by converting it to `UnsafeMutablePointer` or any other mutable pointer\n///     type. If you need to mutate the argument through the pointer, use\n///     `withUnsafeMutablePointer(to:_:)` instead.\n/// - Returns: The return value, if any, of the `body` closure.\n@inlinable public func withUnsafePointer\u003CT, Result>(to value: inout T, _ body: (UnsafePointer\u003CT>) throws -> Result) rethrows -> Result\n```\n\n使用：\n```swift\nvar dir = Direction.east\n\nlet result = withUnsafePointer(to: &dir) { address in\n    return address\n}\nprint(\"--0--dir内存地址--\\(result)\")\n```\n\n### 1.3 枚举内存分配汇编举证\n- 第一步，如下图所示，红色框内是枚举，按照图中设置断点。通过`withUnsafePointer`获取变量dir的内存地址，然后右键控制台左侧的dir变量，点击View Memory。\n![查看swift中枚举内存1.jpg](https://image.xinwei.ltd/11711550417432.jpg)\n\n- 第二步，如下图所示，会显示内存查看界面，但是在第1步点击后进来时，Address输入框是空值，Xcode没有允许我们访问枚举变量的内存地址。所以我们需要输入控制台打印出来的内存地址，才能查看内存。\n![查看swift中枚举内存2.jpg](https://image.xinwei.ltd/21711550448468.jpg)\n\n- 第三步，进入下一个断点，查看给dir赋值.south这个枚举成员后，dir会存储什么值。（按照1.1中的解释，应该存储1）\n![查看swift中枚举内存3.jpg](https://image.xinwei.ltd/31711550485510.jpg)\n\n同样跟第二步一样，查看View Memory。这一步可以验证，dir此时存储的是1.\n![查看swift中枚举内存4.jpg](https://image.xinwei.ltd/41711550512792.jpg)\n\n- 第四步，重复第三步的操作，进入到下一个断点。这个时候，dir被赋值了枚举成员west。按照1.1中的解释，此时dir中应该存储的是2.\n![查看swift中枚举内存5.jpg](https://image.xinwei.ltd/51711550552985.jpg)\n![查看swift中枚举内存6.jpg](https://image.xinwei.ltd/61711550571597.jpg)\n\n这是《Swift知识一览》系列的第三篇文章，若感兴趣，请关注后续发表的文章。\n\n\n\n\n\n\n","这篇文章是《Swift知识一览》的第三篇文章。该篇文章主要介绍Swift的枚举的内存分配，通过LLDB和汇编的相关知识去分析枚举的内存情况，附上操作截图，可以让大家自行动手去了解枚举的内存知识。最近有点忙，更新的有点慢。","https://image.xinwei.ltd/11711550417432.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],227,"swift,swift开发,swift article,swift ios",{"id":994,"createdAt":995,"title":996,"content":997,"summary":998,"image":999,"uid":136,"user":1000,"categoryId":85,"category":1001,"subCategoryId":47,"subCategory":1002,"comments":1003,"status":9,"reason":15,"notice":15,"visitCount":1004,"commentCount":47,"keywords":1005},130,"2024-10-15T05:57:26.4Z","cocoapods: Unable to add a source with url `https://github.com/CocoaPods/Specs.git` named `cocoapods`","## 一、背景\n新电脑上运行早期的一个iOS工程，尽管重新配置过flutter的环境，但是对于iOS工程的pod库的install还是碰到一个如标题所示的报错，随手记录一下。\n\n## 二、问题\n在工程根目录pod文件下，执行`pod install --no-repo-update --verbose`时报错：\n> [!] Unable to add a source with url `https://github.com/CocoaPods/Specs.git` named `cocoapods`.\n![pod install错误](https://image.xinwei.ltd/image1728971422371.png)\n\n## 三、解决方法\n其实很简单，需要重新构建本地的cocoapods的repo，你可以打开`~/.cocoapods`文件夹\n```terminal\nopen ~/.cocoapods\n```\n可以看到\n![cocoapods文件夹](https://image.xinwei.ltd/image1728971586954.png)\n这里面是什么也没有的，这是不正常的。\n\n因为pod文件中引入的库，应该是可以通过pod的repo导入podspec文件索引的，正常导入的库是可以在这里找到对应的trunk，并且找到对应的podspec的。\n\n所以你需要执行\n```terminal\npod repo update --verbose\n```\n或者你在`pod install`的时候不要加后缀指令`--no-repo-update`，直接\n```terminal\npod install --verbose\n```\n（--verbose你理解为打印详细的指令执行过程中的信息就好，加了更好看指令执行进度和详细情况）\n","在新电脑上运行早期的一个iOS工程，尽管重新配置过flutter的环境，但是对于iOS工程的pod库的install还是碰到一个如标题所示的报错，随手记录一下。","https://image.xinwei.ltd/image1728971422371.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],226,"pod install,pod repo,ios pod",{"id":1007,"createdAt":1008,"title":1009,"content":1010,"summary":1011,"image":15,"uid":136,"user":1012,"categoryId":85,"category":1013,"subCategoryId":100,"subCategory":1014,"comments":1015,"status":9,"reason":15,"notice":15,"visitCount":1016,"commentCount":112,"keywords":1017},136,"2024-11-09T10:31:31.533Z","当你在flutter中遇到输入框响应但输入框没有显示在键盘上时","先给出一段示例代码，键盘输入框写在modal弹窗中的情况，一些代码做了简化处理，所以不能直接运行，目的是让大家只看重要的部分，知道基本的布局。其中还使用了一些自己封装的类，所以不要直接运行。\n```dart\nvoid _showCommentActionSheet(BuildContext currentContext, HomeListModel cardModel) {\n    showCupertinoModalPopup(\n      barrierColor: Colors.black.withOpacity(0.8),\n      context: currentContext,\n      builder: (BuildContext context) {\n        return SafelyClickWidget(\n          onTap: () {\n            KeyboardUtils.closeKeyboard(context);\n          },\n          child: TextField(\n            autofocus: false,\n            style: TextStyles.mediumText(16, Colors.white),\n            focusNode: viewModel.focusNode,\n            decoration: InputDecoration(\n              contentPadding: const EdgeInsets.symmetric(horizontal: 0),\n              hintText: \"Add a comment\",\n              hintStyle: TextStyles.mediumText(16, Colours.FF8C8C8C),\n              border: InputBorder.none,\n              filled: true,\n              fillColor: Colors.transparent,\n              enabledBorder: OutlineInputBorder(\n                borderSide: BorderSide.none,\n                borderRadius: BorderRadius.circular(0),\n              ),\n              focusedBorder: OutlineInputBorder(\n                borderSide: BorderSide.none,\n                borderRadius: BorderRadius.circular(0),\n              ),\n            ),\n            textInputAction: TextInputAction.send,\n            onSubmitted: (value) async {\n              \n            },\n            onChanged: (value) {\n              \n            },\n          ),\n        );\n      },\n    );\n  }\n```\n\n类似这种布局，当在一个页面widget展示时，如果出现了键盘遮挡了输入框的情况，那么可以将输入框以及你想布局在安全区SafeArea中的组件可以显示在键盘之上，你可以加一个`AnimatedPadding`或者`AnimatedContainer`组件包裹他们。\n\n当设置了对应的padding时，这2个组件可以动态的调整自组件的位置，以此可以满足不被输入框遮挡的问题。\n\n示例如下：\n```dart\nvoid _showCommentActionSheet(BuildContext currentContext, HomeListModel cardModel) {\n    showCupertinoModalPopup(\n      barrierColor: Colors.black.withOpacity(0.8),\n      context: currentContext,\n      builder: (BuildContext context) {\n        return SafelyClickWidget(\n          onTap: () {\n            KeyboardUtils.closeKeyboard(context);\n          },\n          child: AnimatedPadding(\n            padding: MediaQuery.of(context).viewInsets, // 划重点\n            duration: const Duration(milliseconds: 100),\n            child: TextField(\n              autofocus: false,\n              style: TextStyles.mediumText(16, Colors.white),\n              focusNode: viewModel.focusNode,\n              decoration: InputDecoration(\n                contentPadding: const EdgeInsets.symmetric(horizontal: 0),\n                hintText: \"Add a comment\",\n                hintStyle: TextStyles.mediumText(16, Colours.FF8C8C8C),\n                border: InputBorder.none,\n                filled: true,\n                fillColor: Colors.transparent,\n                enabledBorder: OutlineInputBorder(\n                  borderSide: BorderSide.none,\n                  borderRadius: BorderRadius.circular(0),\n                ),\n                focusedBorder: OutlineInputBorder(\n                  borderSide: BorderSide.none,\n                  borderRadius: BorderRadius.circular(0),\n                ),\n              ),\n              textInputAction: TextInputAction.send,\n              onSubmitted: (value) async {\n\n              },\n              onChanged: (value) {\n\n              },\n            ),\n          ),\n        );\n      },\n    );\n  }\n```\n\n如上述代码所示，当使用组件`AnimatedPadding`，并且设置`padding`为`MediaQuery.of(context).viewInsets`时，组件中包裹的子组件就可以限制在键盘的上方，因为`viewInsets`就是在键盘出现时，`viewInsets.bottom`就会响应，而且就是键盘的顶部位置。\n\n加上设置了duration动画时间，就可以实现一个动态丝滑的向上移动动画，也不会被键盘遮挡。\n\n本文到此结束。\n\n","不知大家有没有遇到一种情况，有时候明明将输入框被SafeArea包裹，但是输入框响应时，输入框依然被键盘遮挡住了的情况。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],212,"flutter keyboard,flutter ios input,flutter input,flutter ios keyboard,flutter app input,input keyboard",{"id":1019,"createdAt":1020,"title":1021,"content":1022,"summary":1023,"image":1024,"uid":136,"user":1025,"categoryId":85,"category":1026,"subCategoryId":144,"subCategory":1027,"comments":1028,"status":9,"reason":15,"notice":15,"visitCount":1029,"commentCount":112,"keywords":1030},128,"2024-10-14T09:58:19.277Z","在web项目中实现一个当前页下载xlsx等文件功能","## 一、背景\n在网页上点击某一个按钮，实现下载文件的操作。\n\n## 二、常见方案\n### 2.1 服务端给一个文件对应的uri\n在前端网页上，使用`a`标签，标签href中设置下载文件对应的链接，一般就可以实现跳转下载文件。\nexample:\n```html\n\u003Ca href=\"https://www.xxx.com/xxx.txt\">下载\u003C/a>\n```\n情况往往没有设定文件下载权限，文件链接暴露的话，任何人都可以下载这个文件。\n如果是`同源`的情况下（网页域名和下载域名符合同源策略），那么一般还比较容易做到鉴权，但实际开发过程中，web服务器和文件服务器是做分离的，所以这种情况是难以做到鉴权的。\n\n### 2.2 使用接口获取文件流方式\n这种方式需要服务端做好配置，读取文件后，并且要设置`response header`，前端请求文件流接口，接口统一封装了对应的token信息。\n#### 2.2.1 前端请求接口\n不管是vue还是react项目，哪怕使用fetch，都是很容易在`request header`中加入对应的authorization信息，服务端就可以使用jwt来判断是否允许下载。\n\n对于使用axios框架来说，更是非常简单的，这里不赘述，咱们重点放在请求后的处理上。\n\n#### 2.2.2 后端读取文件\n这种方式需要服务端做好配置，不仅返回的是文件流，而且要设置对应的`response header`，咱们拿一个xlsx文件类型举例：\n![文件response header](https://image.xinwei.ltd/image1728899316449.png)\n如图所示，服务端要设置一下返回的内容类型：\n```\ncontent-disposition:\nattachment; filename=data_20241014040517.xlsx\n\ncontent-type:\napplication/octet-stream\n\n```\n\n#### 2.2.3 前端读取文件流为blob\n直接上代码。主要逻辑就是使用fetch的`response.blob`方法将流转成blob对象，然后新建一个a标签，模拟点击，实现在当前页面就可以下载文件下来。\n```js\n    const userStore = useUserStoreHook();\n    const token = userStore.accessToken\n    fetch(`${import.meta.env.Fascin8_APP_BASE_URL}/admin/billing/export?` + downloadParam({...param}).substring(1), {\n        method: 'GET',\n        headers: {\n          'Authorization': token,\n        },\n    })\n    .then(response => {\n        console.log(\"请求结果是：\", response)\n        if (!response.ok) {\n          throw new Error('Network response was not ok');\n        }\n        // 从header的Content-Disposition中获取文件名\n        const contentDisposition = response.headers.get('Content-Disposition');\n        let filename = ''\n        if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) {\n            const filenameRegex = /filename[^;=\\n]*=((['\"]).*?\\2|[^;\\n]*)/;\n            const matches = filenameRegex.exec(contentDisposition);\n            if (matches != null && matches[1]) {\n                filename = matches[1].replace(/['\"]/g, ''); // Remove extra quotes\n            }\n        } else {\n          // 自定义一个文件名\n            filename = 'data_' + moment().format(\"YYYYMMDDHHmmss\") + '.xlsx'\n        }\n\n        return response.blob().then(blob => ({ blob, filename }));\n    })\n    .then(({ blob, filename }) => {\n        // 创建a标签，并且触发下载\n        const url = window.URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = filename;\n        document.body.appendChild(a);\n        a.click();\n        window.URL.revokeObjectURL(url);\n    })\n```\n\n\n\n\n\n\n","我们经常在网页上有下载文件的功能，会有常见的几种方式，这篇文章来和大家分享讨论一下。","https://image.xinwei.ltd/image1728899316449.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],196,"web dowload,web下载文件,vue xlsx,vue下载表格,vue",{"id":100,"createdAt":1032,"title":1033,"content":1034,"summary":1035,"image":1036,"uid":136,"user":1037,"categoryId":85,"category":1038,"subCategoryId":144,"subCategory":1039,"comments":1040,"status":9,"reason":15,"notice":15,"visitCount":1041,"commentCount":112,"keywords":1042},"2024-01-11T14:50:23.553Z","midwayjs - 相关基础知识","[midwayjs官网](https://midwayjs.org/docs/intro)\n\n### 一、开始\n初始化项目（将项目创建在你所想的文件夹下）\n```terminal\nnpm init midway\n```\n![](https://image.xinwei.ltd/image_11704984101233.png)\n![](https://image.xinwei.ltd/image_21704984114259.png)\n\n### 二、目录结构\n![](https://image.xinwei.ltd/image_31704984162591.png)\nconfiguration：组件配置等\ninterface：参数约束等\nconfig：缓存、数据库等\ncontroller：写请求响应api\nfilter：过滤器，比如异常、404等\nmiddleware：中间件，请求的日志记录、安全拦截等\nservice：写业务逻辑\ntest: 单元测试\n\n### 三、数据库typeorm\n具体步骤参考：[midwayjs-orm](https://midwayjs.org/docs/extensions/orm)\n#### 3.1、安装\n```terminal\nnpm i @midwayjs/typeorm@3 typeorm --save\n```\n#### 3.2、配置configuration\n```configuration.ts\nimport { Configuration } from '@midwayjs/core';\nimport * as orm from '@midwayjs/typeorm';\nimport { join } from 'path';\n\n@Configuration({\n  imports: [\n    // ...\n    orm                                                         // 加载 typeorm 组件\n  ],\n  importConfigs: [\n    join(__dirname, './config')\n  ]\n})\nexport class MainConfiguration {\n\n}\n```\n\n#### 3.3、安装数据库 Driver\n```language\n# for MySQL or MariaDB，也可以使用 mysql2 替代\nnpm install mysql --save\n```\n\n#### 3.4、配置config.default.ts\nps：需要改成你在自己电脑上的mysql对应的ip、port、用户名、密码\n```config.default.ts\n// orm配置\n  typeorm: {\n    dataSource: {\n      default: {\n        type: 'mysql',\n        host: '127.0.0.1',\n        port: 3306,\n        username: 'root',\n        password: '123456',\n        database: 'midway_boot',\n        synchronize: true,\n        logging: true,\n      },\n    },\n  },\n```\n![](https://image.xinwei.ltd/image_41704984485475.png)\n\n\n","[midwayjs官网](https://midwayjs.org/docs/intro) 一、开始 初始化项目（将项目创建在你所想的文件夹下） terminal npm init midway (https://image.xinwei.ltd/image_11704984101233.png) (https://image.xinwei.ltd/image_21704984114259.png","https://image.xinwei.ltd/image_11704984101233.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],194,"midwayjs,midwayjs基础,midwayjs官网",{"id":242,"createdAt":1044,"title":1045,"content":1046,"summary":1047,"image":15,"uid":136,"user":1048,"categoryId":85,"category":1049,"subCategoryId":47,"subCategory":1050,"comments":1051,"status":9,"reason":15,"notice":15,"visitCount":1052,"commentCount":112,"keywords":1053},"2024-01-29T14:35:11.373Z","mac下查看.mobileprovision文件","在mac中查看一个mobileprovision文件的信息，直接使用security命令。\n```terminal\nsecurity cms -D -i XXX.mobileprovision\n```","在mac中查看一个mobileprovision文件的信息，直接使用security命令。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],188,"mac,ios开发,ios mobileprovision,mobileprovision",{"id":1055,"createdAt":1056,"title":1057,"content":1058,"summary":1059,"image":1060,"uid":136,"user":1061,"categoryId":85,"category":1062,"subCategoryId":144,"subCategory":1063,"comments":1064,"status":9,"reason":15,"notice":15,"visitCount":1065,"commentCount":112,"keywords":1066},117,"2024-07-07T04:03:15.539Z","DOM和CSSOM","DOM（Document Object Model）树和CSS（Cascading Style Sheets）树是Web开发中两个重要的概念，它们在浏览器渲染网页时起着至关重要的作用。下面我将分别解释它们是什么，如何理解它们，以及它们是如何工作的。\n\n## DOM树\n### DOM树是什么？\n\nDOM树是文档对象模型的树形表示。它是HTML或XML文档的编程接口，它定义了文档的结构和内容，并允许开发人员通过编程方式（如JavaScript）来访问和修改文档的结构、样式和内容。\n\n## 如何理解DOM树？\n\nDOM树把文档中的每个元素都表示为一个节点（Node）。这些节点之间通过父子、兄弟等关系连接起来，形成一棵树形结构。例如，\u003Chtml>元素是根节点，\u003Chead>和\u003Cbody>是\u003Chtml>的子节点，而`\u003Cbody>`的子节点可能是`\u003Cdiv>`、`\u003Cp>`、`\u003Ca>`等元素。\n\n## DOM树是如何工作的？\n\n当浏览器加载HTML文档时，它会解析文档并创建一个DOM树。这个DOM树是动态的，也就是说，它可以随着JavaScript代码的执行而发生变化。例如，你可以使用JavaScript来添加、删除或修改DOM树中的节点，从而改变网页的结构和内容。\n![Dom树](https://image.xinwei.ltd/11720324831696.png)\n\n\n## CSS树\n### CSS树是什么？\n\nCSS树（更准确地说是CSSOM树，即CSS Object Model）是CSS对象模型的树形表示。它描述了文档中各个元素应该如何显示，包括颜色、字体、布局等样式信息。\n\n### 如何理解CSS树？\n\nCSS树也是由节点组成的，但这些节点主要表示CSS规则。每个CSS规则由选择器和一组声明组成。选择器用于指定哪些元素应该应用该规则，而声明则指定了这些元素应该如何显示。浏览器会解析CSS代码并创建一个CSSOM树，其中每个节点都对应一个CSS规则。\n\n### CSS树是如何工作的？\n\n当浏览器加载CSS文件或`\u003Cstyle>`标签中的样式时，它会解析这些样式并创建一个CSSOM树。然后，浏览器会将DOM树和CSSOM树合并成一个渲染树（Render Tree）。渲染树中的每个节点都对应着屏幕上的一个矩形区域，并包含了如何绘制该区域的信息。浏览器会遍历这个渲染树，并使用GPU来绘制网页上的每个元素。\n![css树.png](https://image.xinwei.ltd/image1720324900186.png)\n\n\n## 总结\nDOM树描述了文档的结构和内容，而CSS树描述了文档中各个元素应该如何显示。当浏览器加载HTML和CSS时，它会分别创建DOM树和CSS树，并将它们合并成一个渲染树来绘制网页。JavaScript可以通过编程方式访问和修改DOM树来改变网页的结构、样式和内容。\n\n","DOM（Document Object Model）树和CSS（Cascading Style Sheets）树是Web开发中两个重要的概念，它们在浏览器渲染网页时起着至关重要的作用。下面我将分别解释它们是什么，如何理解它们，以及它们是如何工作的。","https://image.xinwei.ltd/11720324831696.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],187,"dom,cssom,dom cssom,dom cssom knowledge,dom知识",{"id":1068,"createdAt":1069,"title":1070,"content":1071,"summary":1072,"image":1073,"uid":136,"user":1074,"categoryId":85,"category":1075,"subCategoryId":201,"subCategory":1076,"comments":1077,"status":9,"reason":15,"notice":15,"visitCount":1078,"commentCount":112,"keywords":1079},143,"2024-12-15T08:43:30.907Z","koa中当你使用不正确使用next时会造成的404 Not Found问题.【附带koa源码解释】","因为koa是一个以中间件为基础的网络框架，所以经常有许多和中间件相关的操作，一个中间件处理完事务，就会交由另一个中间件继续处理。但是当处理next不当时，经常会有异常的问题出现，导致花费比较多的时间。\n\n## 一、问题\n> 404 Not Found\n\n![koa 404](https://image.xinwei.ltd/image1734173809644.png)\n\n如上图所示，是一个get接口请求得到404的截图。\n\n## 二、代码审查\n代码截图：\n![代码截图](https://image.xinwei.ltd/image1734173960494.png)\n```user.ts\nconst userRouter = new Router({\n  prefix: \"/user\",\n});\n\nuserRouter.get(\"/profile\", ValidTokenMiddleware, async (ctx: any) => {\n  const user_id = MiddlewareGetHeaderUserId(ctx);\n  console.log(\"user id: \", user_id);\n  const user = await UserModel.findById(user_id);\n  console.log(\"user: \", user);\n  ctx.body = new LTNetResponse(LTNetErrorCode.Success, user, \"\");\n});\n```\n如上代码所示，接口`/user/profile`通过get请求，经过中间件`ValidTokenMiddleware`，然后交由`async (ctx:any) => {}`中间件处理请求结果。\n\n从代码逻辑上是没有问题的，但是为什么会得到一个`404 Not Found`的结果呢。\n\n首先，代码中的数据库处理是没有问题的，也是经得起验证的。\n其次，直接去掉`await`和`async`相关的代码，直接`ctx.body=\"123\"`，是不会`404 Not Found`的。\n\n所以，`404 Not Found`是和异步调用有关产生的问题，那么继续审查代码可以发现，其中的中间件`ValidTokenMiddleware`应该是导致这个问题的关键。\n\n查看`ValidTokenMiddleware`中间件的内容：\n![中间件_1.png](https://image.xinwei.ltd/image1734174633015.png)\n![中间件_2.png](https://image.xinwei.ltd/image1734174734732.png)\n\n其实能找到关键代码：\n```ValidTokenMiddleware_file.ts\n...\n    const user_id_of_header = decoded.userId;\n    if (userId != user_id_of_header + \"\") {\n      ctx.body = new LTNetResponse(\n        LTNetErrorCode.ParamError,\n        null,\n        String_Token_Error\n      );\n      return;\n    }\n    next();\n...\n```\n我们可以看到`next`的中间件传递处理，但是这里没有使用`await next()`。问题找到了，应该在`next()`时使用`await`关键字。\n\n## 三、正确认识next\n正如koa的核心概念，koa使用的是一个以洋葱模型进行网络请求的入和出的数据流动逻辑。\n我们看看koajs对next的解释：\n```koa example\n// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,\n// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.\n\napp.use((ctx, next) => {\n  const start = Date.now();\n  return next().then(() => {\n    const ms = Date.now() - start;\n    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);\n  });\n});\n```\n> next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.\n\n所以`next`是一个返回`Promise`对象的异步函数，koajs也在官网给出了next的代码示例：\n![koa cascading](https://image.xinwei.ltd/image1734175931768.png)\n> When a middleware invokes next() the function suspends and passes control to the next middleware defined. After there are no more middleware to execute downstream, the stack will unwind and each middleware is resumed to perform its upstream behaviour.\n\n对于中间件这样的一个栈结构，在某一个中间件(叫做中间件A)中，如果不用`await`修饰`next()`，那么下一个中间件(叫做中间件B）其中的async异步操作完成的时候，中间件B中的控制权无法返回到中间件A，导致`response`无法完成，也就是形成`404 Not Found`。\n\n## 四、代码解释\n### 4.1 koa构造的时候，做了什么\n![koa app source codes](https://image.xinwei.ltd/image1734232243694.png)\n从截图中可以看到这里有一个中间件数组。\n在koa初始化后，会使用use加入很多中间件：\n![koa use middleware](https://image.xinwei.ltd/image1734232332285.png)\n这里的`use`就是往中间件数组中加入函数的。\n\n### 4.2 koa监听的时候做了什么\n```koa Application.js\n/**\n   * Shorthand for:\n   *\n   *    http.createServer(app.callback()).listen(...)\n   *\n   * @param {Mixed} ...\n   * @return {Server}\n   * @api public\n   */\n\n  listen(...args) {\n    debug('listen');\n    const server = http.createServer(this.callback());\n    return server.listen(...args);\n  }\n```\n将一个`callback`传递给`node.http`，`callback`后面说，对于`createServer`的定义是这样的:\n```\nimport http from 'node:http';\n\n// Create a local server to receive data from\nconst server = http.createServer((req, res) => {\n  res.writeHead(200, { 'Content-Type': 'application/json' });\n  res.end(JSON.stringify({\n    data: 'Hello World!',\n  }));\n});\n```\n所以现在记住，`req`和`res`都会被`callback`处理。\n### 4.3 callback做了什么\n首先看`callback`的源码:\n```\n/**\n   * Return a request handler callback\n   * for node's native http server.\n   *\n   * @return {Function}\n   * @api public\n   */\n\n  callback() {\n    const fn = compose(this.middleware);\n\n    if (!this.listenerCount('error')) this.on('error', this.onerror);\n\n    const handleRequest = (req, res) => {\n      const ctx = this.createContext(req, res);\n      if (!this.ctxStorage) {\n        return this.handleRequest(ctx, fn);\n      }\n      return this.ctxStorage.run(ctx, async() => {\n        return await this.handleRequest(ctx, fn);\n      });\n    };\n\n    return handleRequest;\n  }\n```\n当`req`和`res`从`node.http`原生请求中过来后，向`compose`传递了middleware数组得到一个fn函数，并且对于这次请求的req和res创建了一个上下文context，\n```compose的fn\n/**\n * Compose `middleware` returning\n * a fully valid middleware comprised\n * of all those which are passed.\n *\n * @param {Array} middleware\n * @return {Function}\n * @api public\n */\n\nfunction compose (middleware) {\n  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')\n  for (const fn of middleware) {\n    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')\n  }\n\n  /**\n   * @param {Object} context\n   * @return {Promise}\n   * @api public\n   */\n\n  return function (context, next) {\n    // last called middleware #\n    let index = -1\n    return dispatch(0)\n    function dispatch (i) {\n      if (i \u003C= index) return Promise.reject(new Error('next() called multiple times'))\n      index = i\n      let fn = middleware[i]\n      if (i === middleware.length) fn = next\n      if (!fn) return Promise.resolve()\n      try {\n        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));\n      } catch (err) {\n        return Promise.reject(err)\n      }\n    }\n  }\n}\n```\n`compose`就是在中间件数组中，从序号0开始，依次读取中间件，通过`i+1`的`dispatch.bind`操作读取数组中的下一个中间件，依次处理`context`，也就是说，`next`指的是下一个中间件【next返回值是Promise】，这样子形成了一个`Promise`的链式调用。\n\n其实就一句话，一个中间件就是一个函数，第一个参数是`context`，第二个参数`next`是下一个中间件函数，`context`在中间件之间传递。\n\n当中间件A中调用`next()`，不加`await`，那么中间件B的resolve状态将不会返回，中间件B中即使对`ctx.body`的修改将不会按照顺序链的调用方式，因此本篇文章示例代码中`ctx.body`将不会正确处理，导致404 Not Found.\n","因为koa是一个以中间件为基础的网络框架，所以经常有许多和中间件相关的操作，一个中间件处理完事务，就会交由另一个中间件继续处理。但是当处理next不当时，经常会有异常的问题出现，导致花费比较多的时间。","https://image.xinwei.ltd/image1734173809644.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":201,"name":203,"parentId":85},[],179,"404 Not Found,koa 404 Not Found,koa,koajs,koa bodyparser,koa,404,Not Found,koa中间件,koa源码",{"id":1081,"createdAt":1082,"title":1083,"content":1084,"summary":1085,"image":1086,"uid":136,"user":1087,"categoryId":85,"category":1088,"subCategoryId":201,"subCategory":1089,"comments":1090,"status":9,"reason":15,"notice":15,"visitCount":1091,"commentCount":112,"keywords":1092},137,"2024-11-11T17:14:50.497Z","Koa后端小项目编程记录（二）get、post请求","## 一、前言\n在上一章“项目搭建”：[https://www.xinwei.ltd/article/132](https://www.xinwei.ltd/article/132)中，我们手动创建了一个ts的后端koa项目，实现了ts编译，并且引入了关键的koa库。\n\n这一章我们来引入其他库，实现常见的post、get请求。\n\n## 二、实现监听\nkoa作为后端框架，可以实现对端口port的监听，所有通过该端口的请求，都会被监听到，那么koa的监听如何实现呢，这里一个简单的举例：\n```app.ts\nconst Koa = require('koa');\nconst app = new Koa();\n\napp.use(async ctx => {\n  ctx.body = 'Hello World';\n});\n\napp.listen(3000);\n```\n我们在app.ts中写入上面的代码，我们再来看一下package.json中的执行脚本：\n```package.json\n...\n\"scripts\": {\n    \"dev\": \"ts-node app.ts\",\n    \"build\": \"tsc\"\n  },\n...\n```\n从这里的脚本命令，我们知道，当我们在根目录的终端中执行npm命令：\n```terminal\nnpm run dev\n```\n时，ts-node就会去编译app.ts文件，当编译时，实例化了koa，并且listen了3000端口，那么这个时候，程序并不会直接退出exit，而是会处于pending状态，监听着来自端口3000的消息。\n\n上面的app.ts文件中，app.use方法是一个中间件写法，也就是说请求和响应都会经过这个中间件处理，而ctx.body被赋值'Hello World'这个字符串时，就表示任何经过3000端口的请求，都会收到'Hello World'这个字符串响应。\n\n## 三、引入router库，实现一个简易get请求\n在后端项目中，和前端的vue以及react都很类似，甚至在flutter中也有对应的路由概念router，哪怕在iOS的组件化之中，都是可以应用到router这样的概念。所以如果对这几个方向有过开发经验的同学，应该是可以理解router的概念的。\n\nrouter这个概念在后端中也很普及，golang的项目同样也是有应用到的，所以有时候经验积累的足够多的时候，很多原理都是相通的，与诸君共勉。\n\n我们讲正文吧，koa的router，其实就是请求的path，如何从端口监听到对应的path请求，并且对这个path请求做处理，也就是返回对应的接口响应。当你不使用router时，那么需要自己处理请求上下文中的path以及携带的请求参数。\n\n这里我使用的是\"@koa/router\"这个npm包处理koa的路由请求，大家可以在npm包网站：[https://www.npmjs.com/package/koa-router](https://www.npmjs.com/package/koa-router)查看这个package。\n\n我们在终端执行：\n```terminal\nnpm install @koa/router\n```\n并且安装对应的type支持包：\n```terminal\nnpm install --save-dev @types/koa__router\n```\n这样子，我们就可以做一下后端path路由，修改app.ts为：\n```app.ts\nvar Koa = require('koa');\nvar Router = require('koa-router');\n\nvar app = new Koa();\nvar router = new Router();\n\nrouter.get('/', (ctx, next) => {\n  // 处理类似http://127.0.0.1:3000的get请求\n  ctx.body = \"Hello World\";\n});\n\napp\n  .use(router.routes())\n  .use(router.allowedMethods());\n```\n\n正如这个代码注释中提到的，重新运行`npm run dev`时，在浏览器中输入`http://127.0.0.1:3000`，就可以在网页上显示字符串`Hello World`，那么一个简单的get请求就实现了。\n\n## 四、写一个post请求\n我们在上面的步骤中写了一个简单的router的get请求，这里我们发散一下思维，封装一个用户登录信息模块。\n\n在写登录信息模块之前，我们需要定义一下响应的类型结构，用来规范我们的响应数据格式。\n1. 定义响应码code的枚举：\n![error枚举](https://image.xinwei.ltd/image1731343743899.png)\n```error.ts\n/**\n * 接口返回的错误码\n */\nenum NetErrorCode {\n  NotLogin = 401,\n  ParamError = 403,\n  NotFound = 404,\n  Success = 200,\n}\n\nexport default NetErrorCode;\n```\n2. 定义响应返回格式：\n![reponse类](https://image.xinwei.ltd/image1731343834179.png)\n```response.ts\nimport NetErrorCode from \"./error\";\n\n/**\n * 网络请求的返回\n */\nexport default class NetResponse\u003CT> {\n  code: NetErrorCode;\n  msg?: string;\n  data?: T;\n\n  constructor(code: NetErrorCode, data: T, msg?: string) {\n    this.code = code;\n    this.data = data;\n    this.msg = msg;\n  }\n}\n```\n这里使用了泛型，可以返回对应类型的数据data.\n3. 创建一个router文件夹，用来存放对不同router的请求处理：\n- 登录router:\n![登录的router](https://image.xinwei.ltd/image1731344053980.png)\n```router.ts\nimport NetErrorCode from \"../dto/error\";\nimport NetResponse from \"../dto/response\";\n\nconst Router = require(\"@koa/router\");\n\nconst router = new Router();\n\nrouter.get(\"/\", (ctx: any, next: any) => {\n  console.log(\"请求了:\", ctx);\n  next();\n});\n\nrouter.post(\"/login\", (ctx: any, next: any) => {\n  console.log(\"登录信息是：\", ctx.request.body);\n  console.log(\"登录信息是--headers：\", ctx.request.headers);\n  ctx.body = new NetResponse\u003Cany>(NetErrorCode.Success, null, \"\");\n});\n\nexport default router;\n```\n- 用户router：\n![user router](https://image.xinwei.ltd/image1731344142227.png)\n```user.ts\nconst Router = require(\"@koa/router\");\n\nconst userRouter = new Router({\n  prefix: \"/user\",\n});\n\nuserRouter.get(\"/profile\", (ctx: any, next: any) => {\n  console.log(\"用户信息是--headers：\", typeof next);\n  ctx.body = ctx.request.body;\n});\n\nexport default userRouter;\n\n```\n这里解释一下，user router的实例化函数中，可以创建一个前缀，就像该例子中，我们定义了一个`/user`前缀，然后使用这个router实现一个get请求，那么这个user router就可以处理`http://127.0.0.1:3000/user/profile`这样的接口请求。\n4. 修改app.ts如下：\n```app.ts\nconst Koa = require(\"koa\");\n\nimport router from \"./src/router/router\";\nimport userRouter from \"./src/router/user\";\n\nconst app = new Koa();\n\n// 根请求、login请求\napp.use(router.routes()).use(router.allowedMethods());\n// 用户模块\napp.use(userRouter.routes()).use(userRouter.allowedMethods());\n\napp.listen(3000);\n\n```\n5. 本来到这一步，一切应该是可以正常接收get、post请求的，但。。。\n\n当你通过postman这个软件，模拟一个请求：`http://127.0.0.1:3000/login`\n![](https://image.xinwei.ltd/image1731344535982.png)\npostman的response中为空，vscode的终端输出结果是`undefined`，这是为何呢，原因是尽管router对koa的请求做了路由处理，但是没有对ctx做解析处理，所以我们从`ctx.request.body`中得到的是undefined.\n\n那么我们开始下一步。\n\n## 五、引入bodyparser库\n这个库就是用来做body解析的，当然还有其他的一些库，比如koa-body，这里我们使用bodyparser库，地址是：[https://www.npmjs.com/search?q=body-parser](https://www.npmjs.com/search?q=body-parser)\n\n执行终端命令：\n```terminal\n$ npm i @koa/bodyparser --save\n```\n\n修改app.ts为：\n```app.ts\nconst Koa = require(\"koa\");\nconst { bodyParser } = require(\"@koa/bodyparser\");\n\nimport router from \"./src/router/router\";\nimport userRouter from \"./src/router/user\";\n\nconst app = new Koa();\napp.use(bodyParser());\n\n// 根请求、login请求\napp.use(router.routes()).use(router.allowedMethods());\n// 用户模块\napp.use(userRouter.routes()).use(userRouter.allowedMethods());\n\napp.listen(3000);\n```\n如代码所示，我们引入bodyParser，并且以中间件的方式进行使用，那么在对端口3000发送的任何请求，都会同步解析到ctx.request之中，这样子就能获取post携带的参数。\n\n## 六、测试post携带参数请求\n![测试post请求](https://image.xinwei.ltd/image1731345214358.png)\n![终端输出](https://image.xinwei.ltd/image1731345246035.png)\n\n至此，post请求和get请求的案例就实现了。\n\n本章到此结束，下一章继续。\n","小型it项目koa后端开发记录，整理成册，希望对于想要学习或者参考的同学有帮助。","https://image.xinwei.ltd/image1731343743899.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":201,"name":203,"parentId":85},[],176,"koa get,koa post,get post,koa,node koa,koa后端,koa服务端开发,服务端开发,node开发,node服务端,node项目,koa项目",{"id":1094,"createdAt":1095,"title":1096,"content":1097,"summary":1098,"image":1099,"uid":136,"user":1100,"categoryId":85,"category":1101,"subCategoryId":560,"subCategory":1102,"comments":1103,"status":9,"reason":15,"notice":15,"visitCount":1104,"commentCount":112,"keywords":1105},180,"2026-02-06T10:06:39.606Z","云mysql数据库迁移和docker容器访问的一些事","\n最近云数据库快到期了，接下来我不想继续使用云数据库了，因为直接在我的`centos docker`云服务器上直接创建一个mysql容器，然后再使用`MySQLWorkbench`官方的mysql可视化软件，就可以直接操作数据库了，不必要额外的开销。当然如果是公司要求或者需要优化数据库的访问等要求，云数据库还是必要的。\n\n## 一、云数据库数据迁移\n我之前已经在文章[https://www.xinwei.ltd/article/153](https://www.xinwei.ltd/article/153)已经明确的写过了如何在数据库之间迁移数据的做法，大家可以参考那篇文章进行数据库的数据导出，导出的一些勾选项，大家参照那篇文章的截图即可。\n这篇文章不做详细的解释了。\n\n## 二、创建docker数据库容器\n根据我的习惯，我依然喜欢使用`docker-compose.yml`文件来创建容器，因为真的很方便，这里我展示一下我的`docker-compose.yml`中mysql部分的内容：\n```docker-compose.yml\n...\nmysql:\n    image: mysql:5.7\n    container_name: xxx_mysql\n    restart: always\n    ports:\n      - xxx:3306\n    volumes:\n      - ./mysql/data:/var/lib/mysql\n      - ./mysql/conf/my.cnf:/etc/my.cnf\n    environment:\n      MYSQL_ROOT_PASSWORD: \"xxx\"\n...\n```\n- 其中`xxx`部分改成你自己的`端口port`和`数据库密码`，端口需要在云服务器的防火墙的地方进行端口访问配置，保证外部可以访问到那个端口号。\n- `volumes`目录挂载，必须做一个目录挂载，保证数据库数据不会因为容器的异常或者销毁，从而丢失数据。**【非常重要、非常重要、非常重要！】**\n- 这里要注意`container_name`的配置，定义一个你想要的容器名，后面会用到。\n\n## 三、创建并启动mysql容器\n如果你的`docker-compose.yml`中已经有创建或者启动过其他容器，现在`只`新增或者修改了一下mysql这个容器，那么不要直接执行`docker compose up`(老版本命令) 或者`docker-compose up`，这样会重建之前的容器，当然如果你不在乎之前容器的重建、重启等造成的服务暂时中断，那么直接执行这个命令也是可以的。\n\n推荐在云服务器的docker环境中执行单独创建/重启mysql容器：\n```docker terminal\ndocker compose up -d mysql\n```\n这里的mysql就是你在`docker-compose.yml`中配置的`service id`，就是上面文件中的那个mysql。\n\n执行上面的命令后，就可以让mysql跑起来了。\n\n### 3.1 mysql的密码不对\n如果你之前在当前docker环境下，已经创建过mysql容器，密码不是你现在yml文件中配置的这个，会导致你`docker-compose.yml`文件中的mysql容器重建/重启后，密码不统一的问题，原因是mysql会缓存之前的密码到你的挂载目录中，即使mysql容器重新修改了`environment`中的数据库密码，但还是不会生效的。\n\n这个场景可能不会出现，但是如果出现，你可以使用docker命令`docker exec -it xxx bash`进入到你的mysql容器，执行mysql命令修改密码：\n```mysql docker 内部terminal\n# 登录MySQL（用旧密码123456）\nmysql -uroot -p123456\n\n# 修改root密码为新值（abcd123）\nALTER USER 'root'@'%' IDENTIFIED BY 'abcd123';\n```\n\n或者，你清空挂载目录`./mysql/data`下的所有文件，重新`docker restart xxxmysqlContainerId`，这个操作前提需要你确保之前的mysql可以完全重建，且数据不重要的情况下。\n\n## 四、使用MySQLWorkbench\n不管市面上数据库可视化软件多么眼花缭乱，mysql官方的数据库可视化软件`MySQLWorkbench`依然是我熟练使用且信赖的。当然大家可以使用自己顺手的可视化软件。\n\n![mysqlworkbench新增链接](https://image.xinwei.ltd/image1770368577541.png)\n\n创建一个数据库，用于将sql文件的执行数据导入到这个数据库。\n（数据库的名字最好和云数据库中的数据库名字统一，避免代码改动的太多）\n![创建数据库](https://image.xinwei.ltd/mysqlworkbench_2.jpeg)\n\n如上图，红色框部分就是你需要创建的数据库名字。\n\n## 五、导入数据\n鼠标点击选中新建的那个数据库，然后点击左上角的导入sql文件按钮，选择在步骤`一`中你导出的那个sql文件。\n![导入数据sql](https://image.xinwei.ltd/sql_file.png)\n\n导入sql文件后，点击闪电⚡️按钮执行sql文件就好：\n![执行sql文件](https://image.xinwei.ltd/exe_sql.png)\n\n接下来你右键并刷新你的数据库，就可以看到所有的table表都存在了，所有的数据记录也都在里面。\n\n## 六、代码中直接访问mysql docker\n以前我们是云数据库的情况下，和数据库建立连接，都要通过外网或者内网的数据库ip地址，去创建数据库连接，现在我们是在`centos docker`云服务器环境下，有docker环境，那么在这同一个主机的docker环境中，一个容器访问另一个容器，是可以直接通过容器名字代替ip访问的。\n\n即你可以将容器名字当作ip使用。\n\n如果你熟练docker的相关知识的话，你应该知道可以这么使用。\n\n比如我之前配置的数据库地址，修改为docker容器间的访问时，文件内容如下：\n```db.go\npackage config\n\nconst (\n\t// mysqlIp string = \"127.0.0.1\" // 本地docker数据库\n\n\t// 线上\n\tmysqlIp string = \"xxx_mysql\"\n\n)\n\nconst (\n\t// mysql的host\n\tMysqlIp string = mysqlIp + \":3306\"\n\n\t// mysql的root\n\tMysql_Root string = \"root\"\n\n\t// mysql的密码\n\t// Mysql_Pwd string = \"123456\" // 本地docker mysql测试密码\n\tMysql_Pwd string = \"abcd123\" // 线上密码\n\n\t// 数据库名\n\tMysql_Db_Name string = \"xxx_db\"\n)\n\n```\n\n上面的`mysqlIp`变量的值就是，就可以直接写上`docker-compose.yml`中你定义的那个`container_name`的值。\n\n以上就是这些，文章写长了确实有点累，请大家多多关注。\n\n\n\n\n\n","这篇文章介绍一下我今天迁移云mysql数据库到docker数据库的一些事，希望对有的同学有帮助。","https://image.xinwei.ltd/image1770368577541.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":560,"name":562,"parentId":85},[],170,"docker mysql, docker,mysqlworkbench, centos docker,数据库迁移,docker visit,docker的容器名访问, sql文件,golang mysql",{"id":1107,"createdAt":1108,"title":1109,"content":1110,"summary":1111,"image":15,"uid":136,"user":1112,"categoryId":85,"category":1113,"subCategoryId":9,"subCategory":1114,"comments":1115,"status":9,"reason":15,"notice":15,"visitCount":1116,"commentCount":9,"keywords":562},147,"2025-02-10T17:10:14.043Z","测试一下数据库","# 测试内容哈","这是测试摘要",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":9,"name":574,"parentId":85},[],167,{"id":1118,"createdAt":1119,"title":1120,"content":1121,"summary":1122,"image":1123,"uid":136,"user":1124,"categoryId":85,"category":1125,"subCategoryId":312,"subCategory":1126,"comments":1127,"status":9,"reason":15,"notice":15,"visitCount":1128,"commentCount":112,"keywords":1129},20,"2024-01-26T10:42:25.116Z","docker bind for port is already allocated","Error response from daemon: driver failed programming external connectivity on endpoint blog_redis (27b921fbdad1c8a40929a3c16ce61c0623939f12e4c6eddaaac0e9d3ce2c298b): Bind for 0.0.0.0:6379 failed: port is already allocated\n![](https://image.xinwei.ltd/11706265565281.png)\n从问题报错来看，端口6379已经被其他程序占用了，所以容器的端口绑定失败了，所以解决问题的办法，那就是找出当前占用6379端口的程序，并且kill掉这个进程，让docker可以正常进行端口映射。\n### 一、查看端口号对应的pid\n```terminal\nlsof -i tcp:6379\n```\n![](https://image.xinwei.ltd/21706265628446.png)\n从截图中看，确实有进程509占用了端口6379，而且是之前启动的redis-server在占用，说明docker之前使用后并没有释放掉这个端口监听。\n### 二、kill进程\n```terminal\nkill -9 507\n```\n执行上述命令后，重新docker restart容器，看是否能解决问题。\n### 三、其他情况\n1.如果是桌面端的docker软件，完全可以重新启动来解决。\n2.如果是使用docker-compose.yml的场景下，可以先关闭掉所有容器，再使用up试试\n```terminal\ndocker-compose down\n```\n3. 如果是容器之间的端口互斥，可以查看所有容器绑定的端口号是否有冲突的\n```terminal\ndocker ps -a\n```\n\n4. 如果是运行laravel环境，可以尝试以下命令\n```terminal\nvalet stop\n```\n","docker在创建或者运行容器时，碰到的端口映射已经被占用的问题：docker bind for port is already allocated.","https://image.xinwei.ltd/11706265565281.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":312,"name":314,"parentId":85},[],163,"docker,docker bind,docker端口,port,allocated",{"id":1131,"createdAt":1132,"title":1133,"content":1134,"summary":1135,"image":1136,"uid":136,"user":1137,"categoryId":85,"category":1138,"subCategoryId":144,"subCategory":1139,"comments":1140,"status":9,"reason":15,"notice":15,"visitCount":1141,"commentCount":112,"keywords":1142},119,"2024-07-30T03:15:24.417Z","前端-记录一下常用到的css效果代码-高斯模糊","## 一、文字换行\n### 1.1 word级别的换行\n```scss\n&-title {\n  font-family: \"gilroy-bold\";\n  font-size: 26rpx;\n  color: #BFBFBF;\n  word-wrap: break-word; // 这种是完整word的break\n}\n```\n![break-word的css样式](https://image.xinwei.ltd/image1722307469498.png)\n\n### 1.2 all任意位置换行\n```scss\n&-title {\n   font-family: \"gilroy-bold\";\n   font-size: 26rpx;\n   color: #BFBFBF;\n   word-break: break-all; // 任意位置break\n}\n```\n![break-all的css样式](https://image.xinwei.ltd/image1722307698985.png)\n\n\n## 二、高斯模糊\n### 2.1 给div后面的image或者video加模糊效果\n```scss\n.blur-effect {\n  -webkit-backdrop-filter: blur(32rpx); /* Add this line first, it fixes blur for Safari*/\n  backdrop-filter: blur(32rpx);  /* This line activates blur*/\n  transform: translateZ(0); /*启动GPU优化*/\n}\n```\n![image或video加背景高斯模糊](https://image.xinwei.ltd/image1722308902278.png)\n\n\n### 2.2 给image或者video直接加模糊效果\n```scss\n.blur-effect-filter {\n  filter: blur(32rpx); /*直接加在image、video上*/\n  transform: translateZ(0); /*启动GPU优化*/\n}\n```\n![image或video直接加高斯模糊](https://image.xinwei.ltd/image1722309036060.png)\n\n\n","记录一下日常用到的css效果代码，方便以后随时取用，像是文本换行、高斯模糊效果。word-wrap、word-break、backdrop-filter、filter。","https://image.xinwei.ltd/image1722307469498.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],161,"css effective,css codes,css效果,css record",{"id":1144,"createdAt":1145,"title":1146,"content":1147,"summary":1148,"image":1149,"uid":136,"user":1150,"categoryId":85,"category":1151,"subCategoryId":144,"subCategory":1152,"comments":1153,"status":9,"reason":15,"notice":15,"visitCount":1154,"commentCount":112,"keywords":1155},149,"2025-02-24T11:19:15.509Z","在前端中获取视频video的md5 - SparkMD5","在有些场景中，会遇到需要获取文件，比如图片、视频的md5，那么在前端中如何做呢，这里和大家分享一下。\n\n## 背景\n在我的项目开发过程中，需要获取视频的md5，用以做视频校验以及安全性相关的策略。\n\n## md5策略\n我选择使用spark-md5这个库来做md5，原因是这个库相对来说是性能比较良好的md5算法库，具体的介绍，大家可以通过npm package中去查看：[https://www.npmjs.com/package/spark-md5](https://www.npmjs.com/package/spark-md5)\n\n![spark-md5 介绍](https://image.xinwei.ltd/image1740394988120.png)\n\n\n## md5的具体用法\npackage主页已经介绍了一些具体用法，我这里简要介绍一下：\n```js\n// 常用字符串整体md5\nvar hexHash = SparkMD5.hash('Hi there');        // hex hash\nvar rawHash = SparkMD5.hash('Hi there', true);  // OR raw hash (binary string)\n\n// 动态字符串md5\nvar spark = new SparkMD5();\nspark.append('Hi');\nspark.append(' there');\nvar hexHash = spark.end();                      // hex hash\nvar rawHash = spark.end(true);                  // OR raw hash (binary string)\n```\n\n官方给出了一个file的md5示例：\n```js\ndocument.getElementById('file').addEventListener('change', function () {\n    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n        file = this.files[0],\n        chunkSize = 2097152,                             // Read in chunks of 2MB\n        chunks = Math.ceil(file.size / chunkSize),\n        currentChunk = 0,\n        spark = new SparkMD5.ArrayBuffer(),\n        fileReader = new FileReader();\n\n    fileReader.onload = function (e) {\n        console.log('read chunk nr', currentChunk + 1, 'of', chunks);\n        spark.append(e.target.result);                   // Append array buffer\n        currentChunk++;\n\n        if (currentChunk \u003C chunks) {\n            loadNext();\n        } else {\n            console.log('finished loading');\n            console.info('computed hash', spark.end());  // Compute hash\n        }\n    };\n\n    fileReader.onerror = function () {\n        console.warn('oops, something went wrong.');\n    };\n\n    function loadNext() {\n        var start = currentChunk * chunkSize,\n            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;\n\n        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));\n    }\n\n    loadNext();\n});\n```\n\n但其实我们往往是在vue或者react的框架下做文件选取（图片、视频选取），所以我的用法很简单，下面是我的使用代码，仅供参考：\n```js\nimport SparkMD5 from 'spark-md5'\n\nconst readMp4Md5 = (file, callback) => {\n    var fileReader = new FileReader();\n    var spark = new SparkMD5.ArrayBuffer();\n    fileReader.readAsArrayBuffer(file);\n    fileReader.onload = (e) => {\n        spark.append(e.target.result);\n        var md5 = spark.end();\n        callback(md5);\n    };\n}\n```\n\n## 文件上传部分\n我直接使用了elemnt-plus中的`el-upload`组件，这里我贴出部分代码，用于做记录：\n```vue\n\u003Cel-upload\n   v-loading=\"appMp4Loading\"\n   v-model:file-list=\"row.appMp4List\"\n   :data=\"{index: 3}\"\n   :http-request=\"requestUpload\"\n   accept=\".mp4\"\n   :on-success=\"handleRowUploadChange(row, 3)\"\n   :show-file-list=\"false\"\n>\n    \u003Cdiv v-if=\"row.data.mp4.trim().length > 0\">\n        \u003Cvideo\n           :src=\"row.data.mp4\"\n           controls\n           controlslist=\"nodownload noplaybackrate\"\n           disablePictureInPicture \n           preload=\"metadata\"\n           width=\"200\"\n           height=\"200\"\n       >\n       \u003C/video>\n       \u003Cdiv style=\"font-size: 16px; color: #409eff;\">替换\u003C/div>\n   \u003C/div>\n   \u003Cel-button v-else type=\"primary\" link>+上传mp4视频\u003C/el-button>\n\u003C/el-upload>\n\n...\nconst requestUpload = (option)=>{\n      const { index } = option.data;\n      let param = {\n        course_id: 0,\n        file_name: option.file.name,\n      }\n      showUploadLoading(true, index);\n      return uploadCourseInfoApi(param).then((rst) => {\n            request({\n                url: rst.data.url,\n                method: 'put',\n                timeout: 360000,\n                data: option.file,\n                headers: { 'Content-Type': option.file.type },\n            }).then(res => {\n                if(res?.status>=200&&res?.status\u003C=300) {\n                    //上传成功 调用onSuccess方法，否则没有完成图标\n                    option.onSuccess(rst)\n                    ElMessage.success('上传成功');\n                }\n                showUploadLoading(false, index);\n            }).catch(err=>{\n                ElMessage.error('上传文件失败');\n                deleteUploadFailFielist(index);\n                showUploadLoading(false, index);\n            })\n        })\n        .catch(err=>{\n            ElMessage.error('获取上传地址失败');\n            deleteUploadFailFielist(index);\n            showUploadLoading(false, index);\n            option.onError()\n        })\n}\n\nconst handleRowUploadChange = (row, index) => {\n    let fileList = [];\n    if (index == 0) {\n        fileList = row.pngList || [];\n    } else if (index == 1) {\n        fileList = row.appGifList || [];\n    } else if (index == 2) {\n        fileList = row.keepGifList || [];\n    } else if (index == 3) {\n        fileList = row.appMp4List || [];\n    }\n    if (fileList.length == 0) return;\n    const firstResponse = fileList[fileList.length - 1].response;\n    if (!firstResponse) return;\n    const { code, data, msg } = firstResponse;\n    if (code == 0 && data && data.cdn_url) {\n        if (index == 0) {\n            row.data.png = data.cdn_url;\n        } else if (index == 1) {\n            row.data.app_gif = data.cdn_url;\n        } else if (index == 2) {\n            row.data.tool_gif = data.cdn_url;\n        } else if (index == 3) {\n            row.data.mp4 = data.cdn_url;\n            if (fileList.length > 0 && fileList[fileList.length - 1].raw) {\n                readMp4Md5(fileList[fileList.length - 1].raw, (md5) => {\n                    row.data.mp4_md5 = md5;\n                });\n            }\n        }\n    }\n}\n...\n```\n\n实用很简单，希望对大家有帮助。\n\n也希望大家多多关注公众号“新卫网络科技”。","在有些场景中，会遇到需要获取文件，比如图片、视频的md5，那么在前端中如何做呢，这里和大家分享一下。","https://image.xinwei.ltd/image1740394988120.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],152,"md5,spark-md5,video md5,vue md5,npm md5,video upload,el-upload",{"id":85,"createdAt":1157,"title":1158,"content":1159,"summary":1160,"image":1161,"uid":136,"user":1162,"categoryId":85,"category":1163,"subCategoryId":1164,"subCategory":1165,"comments":1167,"status":9,"reason":15,"notice":15,"visitCount":1168,"commentCount":9,"keywords":1169},"2024-01-03T15:56:50.016Z","部署Nginx-一个ip，多个域名，部署多个项目","# 一、背景\n无论是在centos、ubuntu、iis等云服务器，还是在个人的电脑上，当部署Nginx时，基本上都会遇到想用一台服务器（只有一个IP）映射到多个域名上，以供访问多个不同的前端项目的情况（这里以部署多个前端项目为例）。\n\n# 二、知识点\n1.一台云服务器对应于一个ip；\n2.ip可以配合很多端口号进行监听活动；\n3.网页访问通用端口号是80；\n4.通过域名访问某个网页（比如百度：www.baidu.com）时，是需要将域名通过网路中dns解析服务器解析成对应服务器的ip的；\n5.在类unix机上，通常使用hosts文件来配置本地的ip和通用域名，一个hosts文件类似如下：\n![](https://image.xinwei.ltd/image1704297239923.png)\n\n\n6.云服务器上备案了的域名解析，当然在云服务器控制台的域名解析配置项下。\n# 三、实现的目标\n1.电脑上有2个网页，代表2个网站\n2.不想要通过监听不同端口号来访问这2个网站，而是我分别给这2个网站起个域名，对应域名访问对应的网站\n3.例如：\nwww.abc.com，就可以访问网站1\nwww.123.com，就可以访问网站2\n\n# 四、具体操作（在Mac电脑中演示）\n1.准备2个前端项目，或者只是2个html文件就好，类似如下，需要放在nginx配置目录文件下的位置，此处以html文件夹作为nginx显示前端项目的目录。\n![](https://image.xinwei.ltd/image11704297275326.png)\n\n\n2. 修改nginx的配置文件nginx.conf，给80端口加上2个server的配置，如下：\n![](https://image.xinwei.ltd/image21704297300227.png)\n\n\n修改后记得重启一下nginx：nginx -s reload，让配置生效\n3. 修改mac电脑上的hosts文件，位置一般在：\n![](https://image.xinwei.ltd/image31704297325210.png)\n\n修改之前先备份（切记要备份，基本操作哈），由于权限限制，不能直接修改hosts文件，所以先将原始hosts文件先拷贝出2份，一份用作备份，一份用来进行修改，修改后的文件再去etc下替换原文件。\n修改文件为：\n\n4. 浏览器中输入：www.abc.com和www.123.com，测试一下：\n![](https://image.xinwei.ltd/image41704297375937.png)\n\n![](https://image.xinwei.ltd/image51704297395300.png)\n\n\n\n6. 测试结果是成功的。\n\n","一、背景 无论是在centos、ubuntu、iis等云服务器，还是在个人的电脑上，当部署Nginx时，基本上都会遇到想用一台服务器（只有一个IP）映射到多个域名上，以供访问多个不同的前端项目的情况（这里以部署多个前端项目为例）。 二、知识点 1.一台云服务器对应于一个ip； 2.ip可以配合很多端口号进行监听活动； 3.网页访问通用端口号是80； 4.通过域名访问某个网页（比如百度：www.ba","https://image.xinwei.ltd/image1704297239923.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},30,{"id":1164,"name":1166,"parentId":85},"Nginx",[],150,"nginx,域名,多个域名,nginx部署,一个ip",{"id":1171,"createdAt":1172,"title":1173,"content":1174,"summary":1175,"image":1176,"uid":136,"user":1177,"categoryId":85,"category":1178,"subCategoryId":419,"subCategory":1179,"comments":1180,"status":9,"reason":15,"notice":15,"visitCount":1181,"commentCount":112,"keywords":1182},90,"2024-03-01T10:53:28.114Z","react native ios dyld: Symbol not found: _OBJC_CLASS_$_NSUnitInformationStorage Referenced from:","在react native中，版本0.73.4，新建一个react native的工程，可以发现最低的iOS适配版本是13.4，如果切换成以下的其他版本，那么就会报错。\n> dyld: Symbol not found: _OBJC_CLASS_$_NSUnitInformationStorage\n  Referenced from: /Users/hanweixing/Library/Developer/CoreSimulator/Devices/6FB156D2-56AE-4192-8B39-50307DFC11FC/data/Containers/Bundle/Application/D25E91AA-D245-484B-9197-9D328AAAF4A7/ShuYuanVer.app/Frameworks/hermes.framework/hermes (which was built for iOS 13.4)\n  Expected in: /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation\n in /Users/hanweixing/Library/Developer/CoreSimulator/Devices/6FB156D2-56AE-4192-8B39-50307DFC11FC/data/Containers/Bundle/Application/D25E91AA-D245-484B-9197-9D328AAAF4A7/ShuYuanVer.app/Frameworks/hermes.framework/hermes\ndyld: launch, loading dependent libraries\nDYLD_FRAMEWORK_PATH=/Users/hanweixing/Library/Developer/Xcode/DerivedData/ShuYuanVer-cjidiiifatjytufqpysibozvnmcc/Build/Products/Debug-iphonesimulator\nDYLD_FALLBACK_LIBRARY_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib\nDYLD_ROOT_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot\nDYLD_FALLBACK_FRAMEWORK_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks\nDYLD_INSERT_LIBRARIES=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libBacktraceRecording.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libMainThreadChecker.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/Developer/Library/Pr\n...\n\n截图：\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-01%20181709290063513.png)\n\n除此之外，有一个实验性的更新hermes的问题，这里提一下。\n\n如果你的react-native工程不是最新稳定版本，按照官方的建议，是不再支持not active version的修复和issue回答的，所以尽量安装最新的稳定版本。\n\n另外，当你遇到hermes的 `dyld library not loaded hermes.xcframework`这样的错误时，如果你正好升级到最新版本，但是升级后报了hermes的这样的错误，你可以尝试升级hermes:\n```terminal\npod update hermes-engine --no-repo-update\n```\n\n这样可能会解决你遇到的问题。\n","在react native中，版本0.73.4，新建一个react native的工程，可以发现最低的iOS适配版本是13.4，如果切换成以下的其他版本，那么就会报错。","https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-03-01%20181709290063513.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],146,"react native,react native 0.73.4,react native开发",{"id":1184,"createdAt":1185,"title":1186,"content":1187,"summary":1188,"image":15,"uid":136,"user":1189,"categoryId":85,"category":1190,"subCategoryId":47,"subCategory":1191,"comments":1192,"status":9,"reason":15,"notice":15,"visitCount":956,"commentCount":112,"keywords":1193},66,"2024-02-02T10:12:01.239Z","iOS 友盟错误分析","找到编译archive时候的包：一般是这样~/Library/Developer/XCode/Archives/YYYY-MM-DD/***** DD-MM-YY 下午8.51.xcarchive\n\n就是你上传appstore的文件包\n\n显示包内容：\n找到dSYMs文件夹下的：***.app.dSYM\n找到Products/Applications/文件夹下的***.app\n\n把这两个文件放在同一目录下.\n\n然后在命令行中输入如下命令:\n```\natos -arch i386 -o *****.app/*****  0x000235f3\n```\n\n```\natos -arch armv7 -o *****.app/***** 0x000a7943\n```\n\n就会解析地址得到代码信息，\n（地址一般是友盟错误信息中可以点击的地址。）\n\n注意：在上面的命令中我用的是i386，也可以根据你生成.App情况来选择使用armv6或者armv7\n\n以下是三者区别:\n- Armv6 binaries are needed if you're targeting iPhone3G.\n- Armv7 binaries are desirable if you're targeting iPhone3GS/4 or iPad.\n- i386 binares are needed if you intent to run your code at the Simulator.\n\n","如何分析友盟中的报错呢，将报错的上传包下载下来后，通过命令来查看友盟报错的函数。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"友盟,umeng,crash,crash log,ios dysm,ios开发",{"id":1195,"createdAt":1196,"title":1197,"content":1198,"summary":1199,"image":15,"uid":136,"user":1200,"categoryId":85,"category":1201,"subCategoryId":47,"subCategory":1202,"comments":1203,"status":9,"reason":15,"notice":15,"visitCount":130,"commentCount":112,"keywords":1204},47,"2024-01-30T14:25:35.11Z","iOS中对图片进行高斯模糊处理","使用CIImage进行高斯模糊\n```\n- (UIImage *)GaussianBlurFrom:(UIImage *):image {\n      CIContext *context = [CIContext contextWithOptions:nil];\n      CIImage *image = [CIImage imageWithCGImage:image.CGImage];\n      CIFilter *filter = [CIFilter filterWithName:@\"CIGaussianBlur\"];\n      [filter setValue:image forKey:kCIInputImageKey];\n      [filter setValue:@20.0f forKey:kCIInputRadiusKey];\n      CIImage *result = [filter valueForKey:kCIOutputImageKey];\n      CGRect extent = [result extent];\n      return [UIImage imageWithCGImage:[context createCGImage:result fromRect:extent]];\n    }\n```\n\n","这里给出一段示例代码，大家可以试试。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios高斯模糊,ios动画",{"id":1206,"createdAt":1207,"title":1208,"content":1209,"summary":1210,"image":1211,"uid":136,"user":1212,"categoryId":85,"category":1213,"subCategoryId":100,"subCategory":1214,"comments":1215,"status":9,"reason":15,"notice":15,"visitCount":749,"commentCount":112,"keywords":1216},151,"2025-02-25T10:02:54.466Z","ITMS-90683: Missing purpose string in info.plist. NSAppleMusicUsageDescription - flutter - permission_handler","## 背景\n在开发flutter的过程中没有出现异常，但是在提交iOS app到审核的时候，出现了报错，提示缺少在info.plist中的权限描述。问题如下截图所示：\n\n![ITMS-90683 issue](https://image.xinwei.ltd/20250225-0427501740475825483.jpeg)\n\n> ITMS-90683: Missing purpose string in info.plist\n\n\n## 问题分析\n从截图中可以看出，Apple提示我的iOS工程中缺少`NSAppleMusicUsageDescription`和`NSLocationAlwaysWhenInUseUsageDescription`这2个权限的描述。\n\n开发过iOS的小伙伴肯定知道，如果我们在代码中有对隐私权限的调用（相册、相机、定位、麦克风、电话簿等），如果没有在主target中的info.plist添加目的描述，Apple是不允许的，并且Apple还会让开发者在appconnect提审网站上对app使用到的一些隐私、加密等内容做勾选。\n\n如果是在iOS纯原生代码开发中，我们是比较快的定位问题的，但是在flutter开发的时候，由于有时候加的flutter package太多，导致我们容易遗忘有哪些权限描述需要及时在info.plist中进行填写，纯属是一个细心问题。\n\n这样的问题，我也在网上找到过有人也犯过同样的错误：[https://github.com/Baseflow/flutter-permission-handler/issues/302](https://github.com/Baseflow/flutter-permission-handler/issues/302)\n\n## 问题解决\n在flutter中，我们通常使用`permission_handler`这个库来检查和请求业务功能所需的权限。\n\n说到这个库，有一个问题需要提到，就是，如果有同学没有按照文档的`ios setup`部分将iOS工程进行配置的话，会出现`权限虽然获取到了，但是check权限状态时却是foreverDenied`的问题。\n![permission-handler ios setup](https://image.xinwei.ltd/image1740477251312.png)\n\n\n这个问题一定要切记。\n\n`permission-handler`这个pub的链接是：[https://pub.dev/packages/permission_handler](https://pub.dev/packages/permission_handler)\n\n那么，当你使用flutter开发的时候，如果是遇到类似`Missing purpose string in info.plist`这样的报错，你就应该首先检查`permission_handler`这个库的配置是否正确。\n\n其中最不容易发现的是，问题不在代码，而是在`Podfile`文件中，我这里贴出我的`Podfile`文件，供大家参考：\n```Podfile\n# Uncomment this line to define a global platform for your project\nplatform :ios, '13.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n  \n  pod 'KeychainAccess'\n  pod 'FBAudienceNetwork','= 6.15.0'\n  \n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n    target.build_configurations.each do |config|\n      config.build_settings['SWIFT_VERSION'] = '5.0'  # required by simple_permission\n      config.build_settings['ENABLE_BITCODE'] = 'NO'\n      # You can remove unused permissions here\n      # for more information: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h\n      # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'\n      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [\n        '$(inherited)',\n\n        ## dart: PermissionGroup.calendar\n        #'PERMISSION_EVENTS=1',\n        \n        ## dart: PermissionGroup.calendarFullAccess\n        #'PERMISSION_EVENTS_FULL_ACCESS=1',\n\n        ## dart: PermissionGroup.reminders\n        #'PERMISSION_REMINDERS=1',\n\n        ## dart: PermissionGroup.contacts\n        #'PERMISSION_CONTACTS=1',\n\n        ## dart: PermissionGroup.camera\n        'PERMISSION_CAMERA=1',\n\n        ## dart: PermissionGroup.microphone\n        'PERMISSION_MICROPHONE=1',\n\n        ## dart: PermissionGroup.speech\n        #'PERMISSION_SPEECH_RECOGNIZER=1',\n\n        ## dart: PermissionGroup.photos\n        'PERMISSION_PHOTOS=1',\n\n        ## The 'PERMISSION_LOCATION' macro enables the `locationWhenInUse` and `locationAlways` permission. If\n        ## the application only requires `locationWhenInUse`, only specify the `PERMISSION_LOCATION_WHENINUSE`\n        ## macro.\n        ##\n        ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]\n        'PERMISSION_LOCATION=1',\n        'PERMISSION_LOCATION_WHENINUSE=0',\n\n        ## dart: PermissionGroup.notification\n        'PERMISSION_NOTIFICATIONS=1',\n\n        ## dart: PermissionGroup.mediaLibrary\n        #'PERMISSION_MEDIA_LIBRARY=1',\n\n        ## dart: PermissionGroup.sensors\n        #'PERMISSION_SENSORS=1',\n\n        ## dart: PermissionGroup.bluetooth\n        #'PERMISSION_BLUETOOTH=1',\n\n        ## dart: PermissionGroup.appTrackingTransparency\n        'PERMISSION_APP_TRACKING_TRANSPARENCY=1',\n\n        ## dart: PermissionGroup.criticalAlerts\n        #'PERMISSION_CRITICAL_ALERTS=1',\n\n        ## dart: PermissionGroup.criticalAlerts\n        #'PERMISSION_ASSISTANT=1',\n      ]\n    end\n  end\nend\n```\n\n那么对照问题截图中的权限：`NSAppleMusicUsageDescription`和`NSLocationAlwaysWhenInUseUsageDescription`\n然后对照`permission_handler`库的权限枚举值：\n![permission_handler podfile macro](https://image.xinwei.ltd/image1740477661568.png)\n\n从红色框中的可以在`Podfile`中对对应的枚举值进行注释（添加#号），没有用到的权限切记要么删除，要么#号注释掉即可。\n\n\n以上。","这里记录一下我在flutter开发过程中遇到的一个问题，希望对有需要的同学有帮助。","https://image.xinwei.ltd/20250225-0427501740475825483.jpeg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"ITMS-90683,NSAppleMusicUsageDescription,ios ITMS-90683,ios NSAppleMusicUsageDescription,flutter NSAppleMusicUsageDescription,permission_handler",{"id":1218,"createdAt":1219,"title":1220,"content":1221,"summary":1222,"image":15,"uid":136,"user":1223,"categoryId":85,"category":1224,"subCategoryId":419,"subCategory":1225,"comments":1226,"status":9,"reason":15,"notice":15,"visitCount":749,"commentCount":112,"keywords":1227},85,"2024-02-22T05:17:35.138Z","React Native: Build failed Cannot initialize a parameter of type ‘NSArray> ’ with an rvalue of type ‘NSArray ’...","当使用react native的某些早期版本时，单独运行iOS工程，可能会遇到如下问题：\n> Build failed Cannot initialize a parameter of type ‘NSArray> ’ with an rvalue of type ‘NSArray ’...\n\n这个问题，我在react-native 0.61.5 版本时碰到了。\n\n这个问题也可能会因为react-native的版本升级时遇到，暂时不确定是否和pod的模块化有关，但是仍然需要在podfile中做一个配置，使桥接模块bridge类名查找更广。\n\n具体的操作如下，在podfile中添加如下的ruby语句：\n```\npost_install do |installer|\n    find_and_replace(\"../node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm\",\n    \"_initializeModules:(NSArray\u003Cid\u003CRCTBridgeModule>> *)modules\", \"_initializeModules:(NSArray\u003CClass> *)modules\")\n    find_and_replace(\"../node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm\",\n        \"RCTBridgeModuleNameForClass(module))\", \"RCTBridgeModuleNameForClass(Class(module)))\")\nend\n\ndef find_and_replace(dir, findstr, replacestr)\n  Dir[dir].each do |name|\n      text = File.read(name)\n      replace = text.gsub(findstr,replacestr)\n      if text != replace\n          puts \"Fix: \" + name\n          File.open(name, \"w\") { |file| file.puts replace }\n          STDOUT.flush\n      end\n  end\n  Dir[dir + '*/'].each(&method(:find_and_replace))\nend\n```\n添加完这2个ruby函数之后，重新运行一下：\n```\npod install --verbose\n```\n\n我当前的问题是这样解决的。","当使用react native的某些早起版本时，单独运行iOS工程，可能会遇到如标题所示的问题。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],"react native,react native issue,react native开发,react native问题",{"id":1229,"createdAt":1230,"title":1231,"content":1232,"summary":1233,"image":1234,"uid":136,"user":1235,"categoryId":85,"category":1236,"subCategoryId":560,"subCategory":1237,"comments":1238,"status":9,"reason":15,"notice":15,"visitCount":1055,"commentCount":9,"keywords":1239},153,"2025-03-02T08:48:23.253Z","数据库database迁移及迁移过程可能出现的issue","这篇文章记录一下我在某云平台上迁移数据库的过程，虽然是一个小的不太正式的数据迁移，但是也可能对一些同学有参考价值，与众位共享。\n\n## 一、背景\n我在某云服务器平台上部署了服务器和mysql轻量数据库，但是面临数据库即将到期，而暂时没有找到一个优惠套餐的情况下，我决定直接重新购买一个便宜的云数据库来迁移migrate我的轻量数据库，所以有了这次的过程记录。\n\n虽然云平台上有很多数据库续费、升级、数据迁移服务，但是我为了省钱所以选择了最直接的方案，就是直接购买一个便宜的云数据库，然后直接把数据迁移过去就好了。当然是在我的数据量比较小，而且用户pv不多的情况下。如果是数据量大，pv/uv大的情况下，就必须考虑其他方案，会更稳妥安全，避免实时数据丢失。\n\n## 二、过程\n首先我们登录该云平台的数据库管理，在数据库管理页面，可以看到有迁移数据库数据的一些选项。\n![database export choice](https://image.xinwei.ltd/db_11740902887894.png)\n\n如上截图所示，在“数据管理”选项下有“数据导出”选项，我们点击后：\n\n![data export](https://image.xinwei.ltd/db_21740902950092.png)\n\n在该截图中，\n- 我们选择要导出的数据库，\n- 然后选择最大的行数，也就是100000行，\n- 文件类型是SQL，\n- 编码我们最好选择utf8mb4，因为这个是兼容emoji表情符号的，\n- 导出内容自然是要“数据和结构”，\n- 导出对象选择全选，\n- “其他选项”我使用“在CREATE语句前生成DROP语句”，这样子在生成表的时候，会最先drop，然后再create，防止sql语句create报错，\n- 数据选项，这2个选项都勾选，其中要注意，“是否禁止外键约束”这个选项要勾选，否则sql导出语句在执行时，就有可能导致外键创建时，而对应的table却没有create，导致sql执行失败。\n- 这样子，我们可以得到一个sql文件，这个sql文件包含数据库中的所有表table，以及sql的create、drop等数据结构。\n\n## 三、本地测试一下sql\n在真实的云数据库迁移入数据前，我们要在本地数据库中进行测试一下sql文件是否能真正实现我们想要的数据迁移。\n\n本地测试需要本地安装并启动mysql，最快的方式是使用docker，这是我喜欢的一种高效方案，如何在本地电脑上通过docker安装和启动mysql，docker中使用mysql的方案，大家可以去docker网站上看一下，我还没写文章记录，不过之前我记录过在Mac上安装mysql的记录，大家可以参考我之前的文章：[https://www.xinwei.ltd/article/24](https://www.xinwei.ltd/article/24)\n\n另外，我们同时也需要mysql的可视化查看软件，自从之前使用navida的软件被提醒要收费之后，我就在使用mysql workbench这个官方软件，大家可以自行下载，下面的截图是mysql workbench。\n\n我们可以在mysql workbench中导入和执行mysql文件。\n\n## 四、可能出现的问题\n### 4.1 外键问题\n> mysql execute Error Code: 1215. Cannot add foreign key constraint\t0.055 sec\n\n这个问题就是在生成sql文件的时候，如果没有勾选“是否禁止外键约束”，就有可能导致这个问题，有的table在create的时候，含有外键，但是外键对应的table并没有create，所以必须在sql中先禁止外键约束。\n\n### 4.2 database没有生成的问题\n![mysql workbench 1046](https://image.xinwei.ltd/db_31740903469038.png)\n> 23:58:35\tDROP TABLE IF EXISTS `article`\tError Code: 1046. No database selected Select the default DB to be used by double-clicking its name in the SCHEMAS list in the sidebar.\t0.018 sec\n\n本地启动mysql服务后，通过mysql workbench连接了mysql，但是我们查看mysql的数据库，却并没有我们在之前数据库中的database，截图左侧只有默认的数据库。那么我们要新增一个和之前数据库同名的数据库。\n\n![create database](https://image.xinwei.ltd/db_41740904616878.png)\n\n点击新增的数据库，然后导入并执行sql文件：\n![execute sql](https://image.xinwei.ltd/db_51740904653741.png)\n\n以上截图执行完sql后，数据库中已经有了各种表table了。\n\n说明sql文件没有问题了。\n\n## 五、云数据库导入\n我购买了一台云数据库后，登入到该数据库的云平台管理界面，和之前的管理界面类似。\n旧\n![old cloud database page](https://image.xinwei.ltd/db_61740904799470.png)\n新\n![new cloud database page](https://image.xinwei.ltd/db_71740904834512.png)\n\n在“导入导出”选项中导入之前生成的sql文件，并且执行后就会和我们之前在本地测试的结果一样，各种table和数据都创建好了。\n\n当我们在服务器的代码连接mysql处，修改旧的数据库内网ip，换成新的云数据库内网ip，确认数据库的连接username和password后，重新更新服务器的部署。\n\n接下来我们在线上进行测试，直接新写一篇文章，看服务器和数据库的交互是否有问题。\n\n## 六、线上测试\n在网站平台上随便写一个文章\n![test mysql and server](https://image.xinwei.ltd/db_91740905121881.png)\n测试发布和显示都没有问题。\n\n云数据库中进行检索\n![cloud mysql check](https://image.xinwei.ltd/db_81740904906194.png)\n\n\n发现数据也是正常的。\n\n那么这次的数据库迁移工作算是完成了，这只是一个最基础的不安全的数据库迁移操作，不管怎样，希望对有需要的同学有借鉴意义就行。\n\n也请大家多多关注公众号“新卫网络科技”。\n\n\n\n\n\n\n\n\n\n\n\n","这篇文章记录一下我在某云平台上迁移数据库的过程，虽然是一个小的不太正式的数据迁移，但是也可能对一些同学有参考价值，与众位共享。","https://image.xinwei.ltd/db_11740902887894.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":560,"name":562,"parentId":85},[],"database migrate,mysql migrate,database dump file,mysql dump file",{"id":1241,"createdAt":1242,"title":1243,"content":1244,"summary":1245,"image":1246,"uid":136,"user":1247,"categoryId":85,"category":1248,"subCategoryId":144,"subCategory":1249,"comments":1250,"status":9,"reason":15,"notice":15,"visitCount":1055,"commentCount":112,"keywords":1251},109,"2024-04-23T07:37:12.745Z","jQuery实现Max Mega Menu效果","\n## 实现一个Max Mega Menu的css效果\n### 1. Max Mega Menu样式说明\nmax mega menu是给WordPress提供的一个样式插件。主要效果是在鼠标hover在普通menu时，弹窗由不可见状态，透明度和y轴方向位移有一个transform动画。\n官网：[https://www.megamenu.com/](https://www.megamenu.com/)\n可以在官网上查看demo效果。\n以下使用jQuery实现一个简易的max mega效果。\n\n### 2 利用jQuery实现\n#### 2.1 css:声明max mega的弹窗normal和hover样式\n```language\n// style.css 全局\n\n.mega-menu {\n  visibility: hidden;\n  opacity: 0;\n  transform: translate(0, 10px);\n  transition: opacity 200ms ease-in, transform 200ms ease-in, visibility 200ms ease-in;\n}\n\n.mega-menu-hover {\n  visibility: visible;\n  opacity: 1;\n  transform: translate(0, 0);\n}\n```\n\n\n#### 2.2 html:声明其中一个menu的弹窗样式\n![max mega menu示例.jpg](https://image.xinwei.ltd/91713857714684.jpg)\n\n主要是先应用一个动画过渡时的css样式:\n```language\n\u003Cdiv class=\"product-list mega-menu\">\n```\n\n\n#### 2.3 js:使用jQuery监听弹窗的父标签hover\n```language\n// 监听“产品”的hover\n  $('.product-nav').hover(\n    function() {\n      // 当鼠标进入元素时执行的函数\n      // $('.product-list').show(); // 显示弹框\n      $('.product-list').addClass('mega-menu-hover');\n      $('.product-nav>a>img').attr('src', '/static/image/menu/menu_normal_arrow_up.png')\n    },\n    function() {\n      // 当鼠标离开元素时执行的函数  \n      // $('.product-list').hide(); // 隐藏弹框\n      $('.product-list').removeClass('mega-menu-hover');\n      $('.product-nav>a>img').attr('src', '/static/image/menu/menu_normal_arrow_down.png')\n    }\n  );\n```\n\n主要操作是在hover时添加`mega-menu-hover`样式，在hover结束时，移除`mega-menu-hover`样式。\n\n\n\n\n\n\n\n\n\n\n","Max Mega Menu是wordpress配套的menu动效插件，在wordpress上使用这个插件是十分常见的，这里使用jQuery和css来实现这个效果。","https://image.xinwei.ltd/91713857714684.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"jquery,max mega,wordpress max mega,wordpress animation,wordpress开发,wordpress网站",{"id":1253,"createdAt":1254,"title":1255,"content":1256,"summary":1257,"image":1258,"uid":136,"user":1259,"categoryId":85,"category":1260,"subCategoryId":100,"subCategory":1261,"comments":1262,"status":9,"reason":15,"notice":15,"visitCount":1263,"commentCount":112,"keywords":1264},157,"2025-03-19T07:42:22.47Z","Unhandled Exception: MissingPluginException(No implementation found for method data on channel plugins.justsoft.xyz/....","## 问题\n> Unhandled Exception: MissingPluginException(No implementation found for method data on channel plugins.justsoft.xyz/video_thumbnail)\n\n在flutter项目中使用了video_thumbnail来获取视频第一帧图片作为显示的图片，但是在控制台出现了如上的报错。\n\n更详细的信息被hot refresh掉了，所以这里没能及时截图。\n\n## 问题排查\n从控制台的信息中，说是method channel调用“data”这个方法出错，所以一开始怀疑是video_thumbnail这个库的问题，但是根据 [https://pub.dev/packages/video_thumbnail](https://pub.dev/packages/video_thumbnail) 这里面的版本号，我本地的版本号已经是最新的`0.5.3`.\n\n虽然我从GitHub上有找到类似的issue：[https://github.com/justsoft/video_thumbnail/issues/77](https://github.com/justsoft/video_thumbnail/issues/77)，但是报错的method channel不一样，所以我直接去检查是不是plugin中确实没有实现data这个method。\n\n直接去Android studio中的Flutter Plugins中去查找一下\n![Flutter Plugins Directory](https://image.xinwei.ltd/image1742369517757.png)\n\n找到`video_thumbnail`\n![video_thumbnail data method](https://image.xinwei.ltd/image1742369675546.png)\n\n从上面的截图中就可以看出，这个`data`的method channel是存在的，那么初步判断`video_thumbnail`是没有问题的。\n\n所以有理由怀疑是pub cache出了问题，先清理一下pub cache：\n```terminal\n flutter pub cache clean\n```\n然后\n```terminal\nflutter pub get\n```\n\n重新运行，选择视频，进行app内的发送逻辑，查看是否视频的第一帧显示是否有问题，以及控制台是否还有报错。\n\n经过运行，发现问题已经没有了。\n\n在这种情况下，我分析我的问题出现在，我之前一直运行flutter跑的是iOS的模拟器下，是没有问题的，但是我运行到Android模拟器上时，虽然视频第一帧图片显示没有问题，但是控制台就是会有这样的报错，那么有可能是之前在跑iOS的情况下，video_thumbnail pub cache中没有Android的method channel。\n\n以上。","记录一个flutter中的video_thumbnail的控制台报错。","https://image.xinwei.ltd/image1742369517757.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],116,"video_thumbnail,flutter video_thumbnail,MissingPluginException,No implementation found,video_thumbnail plugin",{"id":1266,"createdAt":1267,"title":1268,"content":1269,"summary":1270,"image":15,"uid":136,"user":1271,"categoryId":85,"category":1272,"subCategoryId":47,"subCategory":1273,"comments":1274,"status":9,"reason":15,"notice":15,"visitCount":264,"commentCount":112,"keywords":1275},75,"2024-02-02T11:20:04.644Z","iOS中获取音频文件的时长","```\n// 音频文件路径\nNSString *path = manager.recorder.url.path;\nNSURL *assetUrl = [NSURL fileURLWithPath:path];\n// 封装成多媒体资源对象\nAVURLAsset* audioAsset =[AVURLAsset URLAssetWithURL:assetUrl options:nil];\nCMTime audioDuration = audioAsset.duration;\nfloat audioDurationSeconds = CMTimeGetSeconds(audioDuration);\n\n```","iOS中获取音频文件的时长的简单代码。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios audio,ios audio duration,ios音频,ios音频时长,ios开发",{"id":1277,"createdAt":1278,"title":1279,"content":1280,"summary":1281,"image":15,"uid":136,"user":1282,"categoryId":85,"category":1283,"subCategoryId":144,"subCategory":1284,"comments":1285,"status":9,"reason":15,"notice":15,"visitCount":371,"commentCount":112,"keywords":1286},79,"2024-02-05T02:40:00.561Z","midwayjs报错：Model not initialized: Member \\\"getTableName\\\" cannot be called. \\\"User\\\" needs to be added to a Sequelize instance","### 问题\n>Model not initialized: Member \\\"getTableName\\\" cannot be called. \\\"User\\\" needs to be added to a Sequelize instance\n\n### 出现错误的地方：\n```\nasync getAllPosts(body: any) {\n    const { pageSize, pageIndex, userId } = body;\n    let query: Object = {};\n    query[\"offset\"] = (pageIndex !== undefined && pageIndex !== null) ? ((pageIndex - 1) * (pageSize || 10)) : 0;\n    query[\"limit\"] = (pageSize !== undefined && pageSize !== null) ? pageSize : 10;\n    if (userId !== undefined && userId !== null) {\n      query[\"where\"] = {\n        user_id : userId\n      };\n    }\n\n    query[\"include\"] = [User]; \n\n    let result: { rows: HuntingPostForm[]; count: number };\n    try {\n      result = await this.huntingPostModel.findAndCountAll(query)\n    } catch (error) {\n      throw new CommonException(StatusCode.SYS_ERROR, error.message);\n    }\n}\n```\n\n### 解决方法：\n[https://github.com/sequelize/sequelize-typescript/issues/686](https://github.com/sequelize/sequelize-typescript/issues/686) 只能参考\n\n[https://github.com/sequelize/sequelize-typescript#how-to-use-associations-with-repository-mode](https://github.com/sequelize/sequelize-typescript#how-to-use-associations-with-repository-mode) 可以解决\n就是Repository方式的include中，必须还是Repository类型\n\n`query[\"include\"] = [this.huntingRoleModel]; // [this.userModel, this.huntingRoleModel]`","在midwayjs中使用Sequelize的时候，定义model有时候容易出现的问题，这里大家需要注意。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"midwayjs,midwayjs sequelize,midwayjs develop,midwayjs前端,midwayjs后端,midwayjs开发",{"id":786,"createdAt":1288,"title":1289,"content":1290,"summary":1291,"image":1292,"uid":136,"user":1293,"categoryId":85,"category":1294,"subCategoryId":1164,"subCategory":1295,"comments":1296,"status":9,"reason":15,"notice":15,"visitCount":591,"commentCount":112,"keywords":1297},"2024-01-23T07:36:10.978Z","在Mac上原生玩一把Nginx","## 流程\n### 1. 本地创建文件夹\n```terminal\nmkdir -p backEnd\ncd backEnd\n```\n\n### 2. 下载nginx\n```terminal\nwget http://nginx.org/download/nginx-1.17.10.tar.gz\n```\n\n如果没有wget工具，那么下载wget:\n- centOS上使用：\n```terminal\nyum install -y wget\n```\n- MacOS上使用：\n```terminal\nbrew install wget\n```\n\n### 3. 解压缩\n```terminal\ntar -zxvf nginx-1.17.10.tar.gz\n```\n \n### 4. 查看最近一次命令是否执行成功\n```terminal\necho $?\n```\n\n### 5. 编译nginx\n```terminal\ncd nginx-1.17.10\n./configure && make && make install\n# 如果提示需要perl环境，那么执行以下命令\nbrew install -y pcre pcre-devel\n# 如果需要指定将nginx编译到某一个文件夹，可以执行以下命令\nmkdir nginx // 这里的nginx指的是当前目录下的文件夹\n./configure --prefix=../nginx\n```\n### 6. 查看nginx是否被设置了环境变量\n```terminal\nwhich nginx\n```\n\n### 7. 查看当前目录下的文件及文件夹的详细描述\n```terminal\nll\n```\n\n### 8. 查看当前运行的nginx进程\n```terminal\nps -ef | grep nginx\n# 查看mysql进程\nps -ef | grep mysql\n```\n\n### 9. centOS中设置nginx命令\n```terminal\n// linux系统的环境变量一般都在profile文件中，例如Mac的.bash_profile文件等\n// centos中存放在/etc/profile文件中\n\n// 1.进入etc目录，打开profile并编辑\n[root@VM-4-14-centos etc]# vi profile\n\n// 2. 在profile文件中的export 语句后面新增一条语句\n# 这里的\"nginx_pkg/nginx-d/sbin/\"需要替换成你自己nginx编译安装之后的nginx可执行文件所在的目录\nexport PATH=$PATH:/root/nginx_pkg/nginx-d/sbin/\n// 3. 编辑profile完成后，点esc后输入:wq保存退出；\n// 4. 更新profile使生效\n[root@VM-4-14-centos etc]source profile\n// 5. 测试nginx是否配置成功\n[root@VM-4-14-centos etc]nginx -t\n```\n\n## 可能遇到的问题\n> nginx: [alert] could not open error log file: open() \"../nginx-d/logs/error.log\" failed (2: No such file or directory)\n\n类似下图：\n![nginx错误截图](https://image.xinwei.ltd/11705994858459.png)\n\n解决方法：\n1.检查截图所指路径下是否有error.log文件，若无则在所指的目录(../nginx-d/logs/)下，新建error.log文件\n```terminal\ntouch error.log\n```\n\n2. 修改logs文件夹的权限\n```terminal\nchmod 700 logs\n```\n\n3. 测试是否解决，下图显示已经成功。\n![nginx测试结果](https://image.xinwei.ltd/21705994960210.png)\n\n\n\n\n","流程 1. 本地创建文件夹 terminal mkdir -p backEnd cd backEnd  2. 下载nginx terminal wget http://nginx.org/download/nginx-1.17.10.tar.gz 如果没有wget工具，那么下载wget: - centOS上使用： terminal yum install -y wget - MacOS上使用： t","https://image.xinwei.ltd/11705994858459.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":1164,"name":1166,"parentId":85},[],"mac,mac上nginx,nginx,制作nginx,mac上的nginx",{"id":1299,"createdAt":1300,"title":1301,"content":1302,"summary":1303,"image":1304,"uid":136,"user":1305,"categoryId":85,"category":1306,"subCategoryId":47,"subCategory":1307,"comments":1308,"status":9,"reason":15,"notice":15,"visitCount":540,"commentCount":112,"keywords":1309},182,"2026-02-28T03:10:39.681Z","Invalid MinimumOSVersion. App that only support 64-bit devices must specify a deployment target of 8.0 or later","最近在提交iOS app审核时，在最后上传`App Connect`时，给了我一个报错，提示我的部署target版本无效，但同样的环境，两周前还能正常提交，现在报错了。  \n\n在此记录一下。  \n\n## 问题 \n\n> Upload failed with errors:\n> The following issues occurred while distributing your application.\n> Validation failed\n> Invalid MinimumOSVersion. App that only support 64-bit devices must specify a deployment target of 8.0 or later. MinimumOSVersion in 'xxx.app/Frameworks/App.framework' is \".(ID: ac595c50-225c-4599-8378-5bd5daece418)\n\n## 问题截图 \n![ios issue png](https://image.xinwei.ltd/image1772245805345.png) \n\n首先说明一下我的工程是`Flutter`工程，然后编译为iOS进行的发布。  \n\n## 修复问题 \n\n1. 这个问题，首先确认一下自己的iOS工程中的target的deploy配置是否是正常的，有没有可能由于某些原因发生过变化。\n   `XCode -> target -> General -> deployment -> version`\n\n其实这一步查过了没有什么问题。  \n\n2. 右键你刚刚上传的Archive，显示包内容，在frameworks文件夹下查找`App.framework`，继续右键查看包内容，找到`info.plist`文件，查找是否有target deploy的配置。\n\n这一步可能也没有发现什么问题，如果没有找到，那就是证明了这个issue的存在。 \n\n3. 修改Podfile文件 上面的2步只是基础检查，可能并不会有什么帮助，所以我们直接修改Podfile文件，将每一个target都设置deployment target version.\n\n首先，确保顶部的版本设置。 \n```Podfile \n# Uncomment this line to define a global platform for your project\nplatform :ios, '13.0'\n\n```  \n然后在target下针对性的使用module \n```Podfile \ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!  这句\n  \n  pod 'SwiftyStoreKit'\n  \n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n```  \n\n最后针对每一个target进行配置deployment target:\n```terminal\n...\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n    \n    target.build_configurations.each do |config|\n          config.build_settings['SWIFT_VERSION'] = '5.0'\n          config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'  # 这句\n...\n```\n\n4. 删除Podfile.lock文件，方便后续重新pod install\n\n5. 清理工程\n\n首先clean flutter：\n```terminal\nflutter clean\n```\n\n6. 升级flutter到stable最新版本，防止flutter的版本变化影响。\n\n查看当前flutter的channel：\n```terminal\nflutter --version\n```\n\n如果需要切换到stable：\n```terminal\nflutter channel stable\n```\n\n升级到stable最新版本\n```terminal\nflutter upgrade\n```\n\n然后pub get：\n```terminal\nflutter clean\nflutter pub get\n```\n\n7. 编译为iOS\n```terminal\nflutter build ios\n```\n\n8. 重新archive\n通过Xcode打开iOS工程，重新archive。\n\n9. 上传到App Connect\n\n10. 无报错。\n\n至此，我的问题已经解决了，欢迎大家一起讨论，请关注我。","最近在提交iOS app审核时，在最后上传App Connect时，给了我一个报错，提示我的部署target版本无效，但同样的环境，两周前还能正常提交，现在报错了。","https://image.xinwei.ltd/image1772245805345.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"Invalid MinimumOSVersion, 64-bit devices, deployment target of 8.0 or later, ios deploy, ios app deployment target, Validation failed, flutter",{"id":1168,"createdAt":1311,"title":1312,"content":1313,"summary":1314,"image":1315,"uid":136,"user":1316,"categoryId":85,"category":1317,"subCategoryId":257,"subCategory":1318,"comments":1319,"status":9,"reason":15,"notice":15,"visitCount":249,"commentCount":112,"keywords":1320},"2025-02-25T07:59:07.729Z","Visual Studio Code + Copilot AI，让你的编程代码起飞","今天介绍一下visual studio的新版本中增加的AI辅助代码编写功能，经过一段时间的尝试，发现确实能够提高代码编写效率，大家可以试试。\n\n## 一、背景\nvisual studio在2024年11月份的新版本中增加了Copilot的AI代码编辑功能，这个功能是将Github的copilot集成到Visiual Studio中，帮助程序员高效的进行代码编写.\n![visual studio ai version](https://image.xinwei.ltd/71740468487514.png)\n\n## 二、介绍\n我们可以打开vs code的release说明，点击进入到Copilot的介绍网页\n![visual studio ai introduction](https://image.xinwei.ltd/51740468671983.png)\n\n根据网页的介绍，当前的vs code copilot是面向所有使用者免费使用的，但是有代码补齐的频率限制，如下图所示：\n![copilot free plan](https://image.xinwei.ltd/image1740468838161.png)\n> With GitHub Copilot Free you get 2000 code completions/month. That's about 80 per working day - which is a lot. You also get 50 chat requests/month, as well as access to both GPT-4o and Claude 3.5 Sonnet models.\n\n也就是说使用者每天大概有80次的代码补齐使用频率，而且使用了GPT-4o和Claude 3.5 Sonnet大模型，使用者可以在这2种模型之前切换使用。\n\n除了代码补齐，visual studio code还允许使用者打开Copilot的聊天会话框，在会话框中输入想问的答案，copilot会基于当前默认或者使用者选择的大模型进行回复。\n\n就我这段时间的使用经验来说，和直接使用GPT几乎是一样的效果，所以非常推荐大家尝试这个功能。\n\n## 三、使用\n在下载/更新新版的vs code后，打开vs code，在正中间的搜索栏右侧，会出现一个Copilot的图标，点击这个图标就会出现Copilot的配置框。\n![vs code copilot entry](https://image.xinwei.ltd/21740469121037.png)\n\n提示：当你第一次点击这个图标时，会提示，要你授权github的授权登录，当你同意授权登录后，会自动打开浏览器，并且显示github的授权页，你需要确认授权登录，当同意并且授权通过，visual studio code就会登录你的GitHub账号，启用copilot了。\n\n如截图所示，点击出现的面板中有我们可以选择的功能模块项。\n当我们选择“打开聊天”，会进入到对话框页面：\n![vs code ai conversation](https://image.xinwei.ltd/31740469441087.png)\n\n从截图的右下角，你也可以看到当前使用的AI 模型时GPT-4o，当然你也可以选择其他大模型。\n\n\n### 3.1 代码补齐\n当你能够进行上述的步骤之后，在你编写代码的时候，当你的光标停滞时，copilot就会根据代码的上下文，自动提示你后续可能会要输入的代码，如下图所示：\n![code complition](https://image.xinwei.ltd/11740469684875.png)\n\n从截图中可以看出，我的光标所在行是copilot自动联想出来的code 代码，当我需要它提示的这些联想代码时，我点击“Tab”键，就会在光标位置直接填充了联想出来的代码。\n\n经过我一段时间的使用，联想出来的代码，正确率是非常高的。\n\n### 3.2 代码位置打开聊天框\n你也可以直接在光标位置直接打开copilot的聊天框，快捷键是【Command + i】，在悬浮的聊天框中输入你想问的问题，然后可以选择将答案应用到光标位置。\n![open copilot chat](https://image.xinwei.ltd/41740470024407.png)\n\n这个悬浮的聊天框还有\"开启语音聊天\"的功能（GPT-4o左侧的那个麦克风图标），虽然我还没有体验过，但是大家可以试一试。\n\n### 3.3 手动更新copilot扩展\n你也可以直接在扩展中搜索copilot，即时更新最新的copilot插件，如下图：\n![update copilot](https://image.xinwei.ltd/61740470226484.png)\n\n\n还有更多的使用技巧，篇幅有限，这里简单的给出基本使用的流程，大家可以自己多多探索，在我们编写代码中是十分有帮助的，希望能帮到一些同学。\n","今天介绍一下visual studio的新版本中增加的AI辅助代码编写功能，经过一段时间的尝试，发现确实能够提高代码编写效率，大家可以试试。","https://image.xinwei.ltd/71740468487514.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":257,"name":259,"parentId":85},[],"copilot,copilot ai,visual studio code copilot,visual studio GPT,visual studio ai,ai code,vs ai,vs code ai",{"id":1322,"createdAt":1323,"title":1324,"content":1325,"summary":1326,"image":1327,"uid":136,"user":1328,"categoryId":85,"category":1329,"subCategoryId":100,"subCategory":1330,"comments":1331,"status":9,"reason":15,"notice":15,"visitCount":464,"commentCount":112,"keywords":1332},160,"2025-04-28T10:30:15.06Z","flutter error: intl is pinned to version 0.20.2 by flutter_localizations from the flutter SDK...","本文记录一下在flutter切换channel后的工程编译报错问题。\n\n## 本地flutter环境\n```terminal\nFlutter 3.32.0-0.1.pre • channel beta • https://github.com/flutter/flutter.git\nFramework • revision eeb81b9a8a (2 weeks ago) • 2025-04-11 12:11:52 -0700\nEngine • revision 72ee26e314 (3 weeks ago) • 2025-04-09 15:57:49 -0700\nTools • Dart 3.8.0 (build 3.8.0-278.1.beta) • DevTools 2.45.0\n```\n\n![flutter version](https://image.xinwei.ltd/image1745835466070.png)\n\n\n## 问题1\n> Resolving dependencies...\nNote: intl is pinned to version 0.20.2 by flutter_localizations from the flutter SDK.\nSee https://dart.dev/go/sdk-version-pinning for details.\n>\n> Because no versions of flutter_styled_toast match >2.2.1 \u003C3.0.0 and flutter_styled_toast 2.2.1 depends on flutter_localizations from sdk, flutter_styled_toast ^2.2.1 requires flutter_localizations from sdk.\nAnd because every version of flutter_localizations from sdk depends on intl 0.20.2, flutter_styled_toast ^2.2.1 requires intl 0.20.2.\nSo, because xxx_app depends on both intl ^0.19.0 and flutter_styled_toast ^2.2.1, version solving failed.\nFailed to update packages.\n\n报错截图：\n![intl error](https://image.xinwei.ltd/image1745835281510.png)\n\n这个问题指的是，`flutter_styled_toast`依赖的`intl`的库是`0.20.2`，但是当前工程依赖的`intl`版本是`^0.19.0`，所以面对这样的报错，升级`intl`的库就好。\n\n解决方式：\n```pubspec.yaml\n...\nintl: ^0.20.2 # 之前是^0.19.0\n...\n```\n![intl png](https://image.xinwei.ltd/image1745835532477.png)\n\n\n## 问题2\n> Resolving dependencies...\nBecause scroll_datetime_picker 0.2.1 depends on intl ^0.19.0 and no versions of scroll_datetime_picker match >0.2.1 \u003C0.3.0, scroll_datetime_picker ^0.2.1 requires intl ^0.19.0.\nSo, because mustche_app depends on both intl ^0.20.2 and scroll_datetime_picker ^0.2.1, version solving failed.\nFailed to update packages.\n\n问题截图：\n![scroll_datetime_picker version image](https://image.xinwei.ltd/image1745835665615.png)\n\n问题分析：\n在问题1中，修改`intl`的版本为`^0.20.2`后，但是另外一个package `scroll_datetime_picker`依赖的是`^0.19.0`，所以又出现了版本冲突。\n\n那么有2种解决方式：\n- 去查询`scroll_datetime_picker ^0.2.1`这个package是否有支持`intl: ^0.20.2`的版本，有的话，就更新。但是这种方式只能解决当前这个package的版本依赖，并不能确保其他package也能支持`intl: ^0.20.2`。\n(当前`0.2.1`版本的`scroll_datetime_picker`没有支持`intl: ^0.20.2`)\n[https://pub.dev/packages/scroll_datetime_picker](https://pub.dev/packages/scroll_datetime_picker)\n\n![scroll_datetime_picker version image](https://image.xinwei.ltd/image1745836063881.png)\n\n\n- 覆盖所有的intl版本依赖。\n\n那么我们使用第二种方式，将所有依赖的`intl` package都覆盖掉。\n\n### 解决方式\n![dependency_overrides image](https://image.xinwei.ltd/image1745836116006.png)\n如上截图，添加`dependency_overrides`:\n```pubspec.yaml\n...\ndependency_overrides:\n  win32: ^5.5.4\n  intl: ^0.20.2\n...\n```\n\n这样修改后，以上的2个报错都解决了，工程正常运行。\n\n欢迎交流。\n\n\n\n\n\n","记一次flutter更改channel后的工程编译报错问题，内容是intl和flutter_styled_toast的版本冲突。","https://image.xinwei.ltd/image1745835466070.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"intl,flutter_localizations,flutter_styled_toast,version solving failed,flutter error,flutter Resolving dependencies,flutter intl",{"id":47,"createdAt":1334,"title":1335,"content":1336,"summary":1337,"image":1338,"uid":136,"user":1339,"categoryId":85,"category":1340,"subCategoryId":676,"subCategory":1341,"comments":1342,"status":9,"reason":15,"notice":15,"visitCount":1218,"commentCount":112,"keywords":1343},"2024-01-06T14:15:30.146Z","gorm [rows 2]INSERT INTO user_info xxx ON DUPLICATE KEY UPDATE uid=VALUES(uid)","# 问题描述\n在使用gorm插入数据时，有2张表是关联的，两张表分别是User和UserInfo表，而User和UserInfo是HasOne关联关系。\n\n在插入User一条数据时，会关联生成一条UserInfo记录。\n\n在插入第一条User记录时，会关联创建一条UserInfo记录，在第一条数据时没有问题。\n\n当插入第二条User记录时，发现User的新记录创建是成功的，但是UserInfo表中仍然只有一条数据，而且第二条UserInfo记录覆盖了第一条。\n\n2023/11/26 21:04:57 /Users/xxx/controller/user/user.go:88\n[27.498ms] [rows:2] INSERT INTO user_info (created_at,updated_at,deleted_at,uid,name,token,sign,birthday,is_admin,is_ban,ban_message,is_robot,last_login_time) VALUES ('2023-11-26 21:04:57.274','2023-11-26 21:04:57.274',NULL,486881260xxxxxx,'','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwaG9uZSI6IjEzMTIxMTcxOTk4IiwiZXhwIjo0ODU0NjAzODk3LCJuYmYiOjE3MDEwMDM4OTcsImlhdCI6MTcwMTAwMzg5N30.UprJeW_hr8GNgYl8YhbvRlcIdE9zyc0hzmZqtnZ3dmI','',NULL,0,0,'',0,NULL) ON DUPLICATE KEY UPDATE uid=VALUES(uid)\n\n2023/11/26 21:04:57 /Users/xxx/controller/user/user.go:88\n[55.255ms] [rows:1] INSERT INTO user (created_at,updated_at,deleted_at,user_id,phone,nick_name,avatar,vip_type) VALUES ('2023-11-26 21:04:57.247','2023-11-26 21:04:57.247',NULL,486881260xxxxxx,'1312117xxxx','用户486881260527685','',1)\n\n# 问题截图\n![](https://image.xinwei.ltd/image_11704550303976.png)\n\n当使用Deug()来查看gorm输出，从截图中可以看出，在插入第2条UserInfo记录的时候，影响到了2条记录。在用Workbench中查看数据库时，发现只有一条记录。\n\n# 问题思路分析\n1. 首先检查insert语句，分析user记录关联的userinfo记录，比第一次插入时的userinfo记录的区别，结果没有任何发现，都是正常的。\n![](https://image.xinwei.ltd/image_21704550352210.png)\n\n2. 其次在insert的时候使用Omit掉userInfo字段，不让user记录来关联插入UserInfo记录，在insert了user记录后，再insert一下UserInfo记录，看是否userInfo记录还会被覆盖掉。\n![](https://image.xinwei.ltd/image_31704550381963.png)\n结果是userinfo还是继续被后面的第二条数据覆盖。\n\n3. 那么可以排除不是插入语句的问题，因为单独插入也会覆盖，那就只有可能就是表定义的时候，有的字段定义的问题，查看表中的字段。找出可能使UserInfo前后插入的2条记录被mysql认为是相同数据的关键字段。\n![](https://image.xinwei.ltd/image_41704550437729.png)\n\n找到问题了，每次生成User记录的时候，关联生成的UserInfo记录中，只赋值了Token字段，保证了Token是唯一的，但是定义为unique列属性的字段Name却都是无值的，后续的新UserInfo记录就被认为和已经存在数据库中的第一条记录是同一个，那么就不会执行插入，而是更新。\n\n问题解决\n根据以上的第3步，发现定义Name的时候，确实冗余增加了unique列属性，导致插入记录覆盖，所以将列属性gorm:\"unique\"去掉就好了。\n\n\n","问题描述 在使用gorm插入数据时，有2张表是关联的，两张表分别是User和UserInfo表，而User和UserInfo是HasOne关联关系。 在插入User一条数据时，会关联生成一条UserInfo记录。 在插入第一条User记录时，会关联创建一条UserInfo记录，在第一条数据时没有问题。 当插入第二条User记录时，发现User的新记录创建是成功的，但是UserInfo表中仍然只有一","https://image.xinwei.ltd/image_11704550303976.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":676,"name":678,"parentId":85},[],"golang,gorm,insert error,gorm表插入,sql错误,DUPLICATE KEY UPDATE",{"id":1345,"createdAt":1346,"title":1347,"content":1348,"summary":1349,"image":1350,"uid":136,"user":1351,"categoryId":85,"category":1352,"subCategoryId":560,"subCategory":1353,"comments":1354,"status":9,"reason":15,"notice":15,"visitCount":736,"commentCount":112,"keywords":1355},24,"2024-01-26T15:07:27.114Z","mysql数据库的下载安装还有启动等常见操作","### 1. 下载mysql\n```terminal\n# MacOS上的下载\nbrew install mysql\n\n# 查看mysql信息\nbrew info mysql\n```\n\n### 2. 查看mysql的环境变量\n```terminal\nwhich mysql\n```\n\n### 3. mysql设置初始密码\n```mysql\nmysql_secure_installation\n```\n\n### 4. 用户进入mysql\n```terminal\nmysql -u root -p // 会被要求输入密码\n```\n\n### 5. 查看mysql临时密码\n```mysql\ngrep \"password\" /var/log/mysqld.log\n```\n\n### 6. mysql启动\n```terminal\nmysql.server start\n```\n\n### 7. mysql停止运行\n```terminal\nmysql.server stop\n```\n\n### 8. mysql查看端口号\n```mysql\nshow global variables like 'port'; // mysql启动后的mysql输入端输入改命令\n```\n![mysql查看端口](https://image.xinwei.ltd/11706281608716.png)\n\n\n\n","这里有mysql数据库在Mac系统上的下载安装还有配置指令操作。","https://image.xinwei.ltd/11706281608716.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":560,"name":562,"parentId":85},[],"mysql,mac mysql",{"id":1104,"createdAt":1357,"title":1358,"content":1359,"summary":1360,"image":1361,"uid":136,"user":1362,"categoryId":85,"category":1363,"subCategoryId":312,"subCategory":1364,"comments":1365,"status":9,"reason":15,"notice":15,"visitCount":1366,"commentCount":112,"keywords":1367},"2025-10-26T01:59:59.522Z","mongodb docker error: ERROR: child process failed, exited with 1","## 背景\n这个问题出现的背景是我使用docker compose yml文件来部署mongodb数据库等服务，在自己电脑上使用docker以及yml文件都没有问题，但是部署到云服务器上容器就一直在restart，链接数据库的后端服务也没有启动成功。\n\n## 问题\n> mongodb | {\"t\":{\"$date\":\"2025-10-26T00:01:41.215+00:00\"},\"s\":\"I\",  \"c\":\"CONTROL\",  \"id\":8423404, \"ctx\":\"initandlisten\",\"msg\":\"mongod shutdown complete\",\"attr\":{\"Summary of time elapsed\":{\"Statistics\":{\"enterTerminalShutdownMillis\":0,\"stepDownReplCoordMillis\":0,\"quiesceModeMillis\":0,\"stopFLECrudMillis\":0,\"shutDownMirrorMaestroMillis\":0,\"shutDownWaitForMajorityServiceMillis\":0,\"shutDownGlobalConnectionPoolMillis\":0,\"shutDownSearchTaskExecutorsMillis\":0,\"shutDownFlowControlTicketHolderMillis\":0,\"shutDownReplicaSetMonitorMillis\":0,\"shutDownTransportLayerMillis\":0,\"shutDownTTLMonitorMillis\":0,\"shutDownExpiredDocumentRemoverMillis\":0,\"shutDownOtelMetricsMillis\":0,\"shutDownFTDCMillis\":0,\"shutDownOCSPMillis\":0,\"shutdownTaskTotalMillis\":0}}}}\nmongodb | {\"t\":{\"$date\":\"2025-10-26T00:01:41.215+00:00\"},\"s\":\"I\",  \"c\":\"CONTROL\",  \"id\":23138,   \"ctx\":\"initandlisten\",\"msg\":\"Shutting down\",\"attr\":{\"exitCode\":100}}\nmongodb | ERROR: child process failed, exited with 1\nmongodb | To see additional information in this output, start without the \"--fork\" option.\nmongodb | Take a look at your mongod configuration to see if something is wrong.\nmongodb | Could not init database.\nmongodb | ['/usr/bin/mongod', '--bind_ip', '127.0.0.1', '--port', '27017', '--logappend', '--tlsMode', 'disabled', '--logpath', '/proc/1/fd/1', '--fork']\nmongodb | Subprocess failed with errorcode 1\n\n截图：\n![mongodb docker error](https://image.xinwei.ltd/image1761443050983.png)\n\n我的docker compose yml中关于mongodb的配置部分：\n```docker-compose.yml\n...\nservices:\n  db: \n    image: mongodb/mongodb-community-server\n    container_name: mongodb\n    restart: always\n    ports:\n      - 27017:27017\n    volumes:\n      - ./data:/data/db\n    environment:\n      - MONGO_INITDB_ROOT_USERNAME=root\n      - MONGO_INITDB_ROOT_PASSWORD=xxx\n...\n```\n这部分的mongodb配置是没有问题的，在自己的电脑上docker文件也没有问题，后面部署到服务器上也是可以的。\n\n## 解决方法\n\n根本原因是文件的权限问题，是哪个文件的问题呢？除了上面bind部分的data文件夹，其实还有yml文件的权限问题。\n\n### 1. 首先查看服务器上文件的权限\n```docker-centos-server\n[root@VM-4-14-centos docker]# ll\ntotal 8\ndrwxr-xr-x 2 root root 4096 Oct 26 08:01 data\n-rw-r--r-- 1 root root 1174 Oct 26 07:54 docker-compose.yml\n```\n\n### 2. 修改文件夹和文件的权限\n从上面看的出，挂载的data文件夹以及yml文件，权限并没有完全给予所有group user读写，那么我们需要改写它们的权限。\n```docker-centos-server\n[root@VM-4-14-centos docker]# chmod 777 *\n[root@VM-4-14-centos docker]# ll\ntotal 8\ndrwxrwxrwx 2 root root 4096 Oct 26 08:01 data\n-rwxrwxrwx 1 root root 1174 Oct 26 07:54 docker-compose.yml\n```\n如上所示，使用chmod来给目录下所有文件修改为所有角色都有读写权限。\n\n### 3. 在服务器上执行docker compose命令验证一下\n```docker-centos-server\n[root@VM-4-14-centos docker]# docker-compose up\nCreating mongodb ... done\nAttaching to mongodb\n...\nmongodb | {\"t\":{\"$date\":\"2025-10-26T00:07:47.565+00:00\"},\"s\":\"I\",  \"c\":\"WTCHKPT\",  \"id\":22430,   \"ctx\":\"Checkpointer\",\"msg\":\"WiredTiger message\",\"attr\":{\"message\":{\"ts_sec\":1761437267,\"ts_usec\":565072,\"thread\":\"117:0x7f69ed371640\",\"session_name\":\"WT_SESSION.checkpoint\",\"category\":\"WT_VERB_CHECKPOINT_PROGRESS\",\"log_id\":1000000,\"category_id\":7,\"verbose_level\":\"INFO\",\"verbose_level_id\":0,\"msg\":\"saving checkpoint snapshot min: 3, snapshot max: 3 snapshot count: 0, oldest timestamp: (0, 0) , meta checkpoint timestamp: (0, 0) base write gen: 7\"}}}\n...\n```\n没有任何报错，至此我们知道了，需要在自动部署脚本中需要加入修改文件权限的脚本。\n\n我截取部分自己写的部署脚本给一个示例：\n```deploy-shell-script.sh\n...\necho \"执行docker-compose命令，启动容器\"\nssh -i /Users/xxx/ssh_cert/ssh_xxx_visit.pem root@xxx.xxx.xxx.xxx '\necho \"当前目录：\";\npwd;\ncd /root/xxx/docker;\nchmod 777 *;\ndocker-compose up;\n'\n...\n```\n\n好了，至此问题解决了，希望对大家有帮助。\n\n","记录一下使用mongodb的docker镜像遇到的问题，一开始还以为我的yml文件配置问题，还有docker版本问题，后面捣鼓半天。","https://image.xinwei.ltd/image1761443050983.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":312,"name":314,"parentId":85},[],76,"mongodb,mongoose,mongodb docker,mongo,mongodb error,docker error",{"id":1091,"createdAt":1369,"title":1370,"content":1371,"summary":1372,"image":1373,"uid":136,"user":1374,"categoryId":85,"category":1375,"subCategoryId":100,"subCategory":1376,"comments":1377,"status":9,"reason":15,"notice":15,"visitCount":1378,"commentCount":112,"keywords":1379},"2026-01-13T10:00:44.931Z","flutter中老生常谈的json序列化操作json_serializable和json_annotation以及插件使用","相信大家在移动端的开发中，不管是iOS还是Android中都有json序列化的操作，比如MJ的json序列化，那么flutter中同样也是有对应的json序列化和反序列化操作。\n\n这里做一个回顾介绍，温故知新一下。\n\n## 一、flutter中的json serializable\n在flutter中需要依赖2个库去实现：\n- json_serializable\n- json_annotation\n\n这2个package需要一起使用，具体的使用可以在对应的pub.dev上看到：\n[https://pub.dev/packages/json_serializable](https://pub.dev/packages/json_serializable)\n[https://pub.dev/packages/json_annotation](https://pub.dev/packages/json_annotation)\n\n具体安装方法：\n```terminal\ndart pub add dev:json_serializable\n```\n\n```terminal\ndart pub add json_annotation\n```\n\n## 二、具体使用\n当我们在请求接口的时候，可以把json数据转成对应的class对象使用。\n比如：\n```user.json\n{\n  \"user_id\" : \"1234\",\n  \"nick_name\" : \"Tom\",\n  \"age\": 1,\n  \"right\": {\n    \"vip\" : 1\n  }\n}\n```\n这里我们使用了嵌套的json结构，即map中还有map结构，所以我们需要定义2个class，然后在user的class中包含right的class，这个是常见的结构形式。\n\n### 2.1 定义Right的class\n```right.dart\nimport 'package:json_annotation/json_annotation.dart';\n\n// 1. 一定要加一个文件引入，这个part引入的.g.dart文件是在后面的dart命令中生成的，而且和class在同一个目录中。\npart 'right.g.dart';\n\n// 2. 需要添加 @JsonSerializable()\n@JsonSerializable()\nclass Right {\n  final int? vip;\n\n  Right({\n    this.vip,\n  });\n\n  // 3. 下面的2个方法需要手动写上，_$RightFromJson和_$RightToJson是在后面的dart命令中生成的，暂时的语法报错，先不要管。（前缀都是默认你的class名字的首字母大写驼峰写法：Right， 后缀统一为fromJson和toJson）\n  factory Right.fromJson(Map\u003CString, dynamic> json) =>\n      _$RightFromJson(json);\n\n  Map\u003CString, dynamic> toJson() => _$RightToJson(this);\n}\n```\n\n### 2.2 定义User的class\n```user.dart\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:xxx/modules/app/beans/right.dart';\n\n// 1. 声明后续dart命令会生成的文件\npart 'user.g.dart';\n\n// 2. 需要添加 @JsonSerializable()\n@JsonSerializable()\nclass User {\n  // 3. 使用别名，那么要使用@JsonKey去声明原始json中的字段\n  @JsonKey(name: \"user_id\")\n  final String? userId;\n\n  @JsonKey(name: \"nick_name\")\n  final String? nickName;\n\n  final int? age;\n  // 4. 权益class\n  final Right? right;\n\n  User({\n    this.userId,\n    this.nickName,\n    this.age,\n    this.right,\n  });\n\n  // 5. fromJson和toJson方法\n  factory User.fromJson(Map\u003CString, dynamic> json) => _$UserFromJson(json);\n\n  Map\u003CString, dynamic> toJson() => _$UserToJson(this);\n}\n```\n\n### 2.3 json转class\n比如从接口获取到了json数据，然后我们就可以直接使用user class去转化。\n```xxx.dart\n...\nfinal res = response as Map\u003CString, dynamic>?;\nif (res == null) return null;\nfinal user = User.fromJson(res);\n...\n```\n\n### 2.4 执行dart生成命令\n\n以上2个class声明之后，就可以执行dart命令，去生成对应的xxx.g.dart文件。\n\n（终端命令，在根目录下执行，会自动查找所有有@JsonSerializable()的class，然后生成对一个的.g.dart文件）\n```terminal\ndart run build_runner build\n```\n\n以上的基础结构其实已经满足了简单的json序列化操作。\n\n## 三、进阶操作\n### 3.1 在class中声明一个非json字段\n还是用上述的user.dart举例，意思是指，如果在user中增加一个自定义的字段（不在原始json数据中），比如想增加一个是否选中的bool字段：isSelected，那么这个属性就不需要从json中转化。\n\n可以这么做：\n```user.dart\n...\n// 1. 在JsonKey中设置这2个属性为false，表示这个字段不需要从json数据中转，也不需要从class中转的Map中。\n// 2. 这里不用final声明，表示user这个class的isSelected字段可以在使用时进行修改。\n@JsonKey(includeFromJson: false, includeToJson: false)\nbool isSelected = true;\n...\n```\n\n### 3.2 一个class extends 另一个序列化class\n依然拿上面的user.dart举例，现在有一个class，拥有比user class更多的信息，也就是多了一个或者多个字段，然后是在另外一个接口中请求到。\n\n我来详细的举例，比如上面的user，代表的是通用的用户信息，但是现在有一个场景，代表访客，那么这个访客可以有一个访问次数的信息，当然这个访客也拥有通用的用户信息。直接举例：\n```visitor.json\n{\n  \"user_id\" : \"1234\",\n  \"nick_name\" : \"Tom\",\n  \"age\": 1,\n  \"right\": {\n    \"vip\" : 1\n  },\n  # 这里比user信息多一个字段\n  \"visit_count\" : 3\n}\n```\n\n那么visitor应该extendsuser，看下面的代码，我在代码中进行注释。\n```visitor.dart\n// 1. 必须引入user class\nimport 'package:xxx/modules/app/beans/user.dart';\n// 2. 这个文件虽然没有用到right class，但是也必须引入。\n// 否则dart命令将找不到嵌套的right的class声明。\nimport 'package:xxx/modules/app/beans/right.dart';\n\n// 3. 同样需要引入后续命令生成的.g.dart\npart 'visitor.g.dart';\n\n// 4. 这里需要extends user\n@JsonSerializable()\nclass Visitor extends User {\n  @JsonKey(name: \"visit_count\")\n  final int? visitCount;\n\n  Visitor(this.visitCount);\n\n  // 5. 依然需要from json 和 to json方法\n  factory Visitor.fromJson(Map\u003CString, dynamic> json) =>\n      _$VisitorFromJson(json);\n\n  @override\n  Map\u003CString, dynamic> toJson() => _$VisitorToJson(this);\n}\n```\n\n上面的代码解释应该很清晰吧，每次新增一个JsonSerializable的class，都需要执行2.4的dart命令。\n\n### 3.3 使用Android Studio插件\n上面的基本使用大家应该都知道了，但是每次都要写非常多的样式代码，实在是比较费劲，所以有大佬写了Androi Studio插件，提高开发效率。\n![android studio flutter 插件](https://image.xinwei.ltd/image1768298153028.png)\n上面截图中就是快速的json到class的插件，大家可以根据红色框去安装到Android studio中。\n之后，就可以在创建dart文件时，从鼠标菜单中找到它：\n![json to dart menu](https://image.xinwei.ltd/image1768298271244.png)\n\n从下面的截图中，你应该可以知道如何把json数据粘贴到json框中，声明class名称，然后点击生成。\n![json handle png](https://image.xinwei.ltd/image1768298328202.png)\n\n这个插件确实提升了开发效率，大家可以试试。\n\n以上，希望对大家有所帮助。\n","这篇文章介绍一下如何在flutter中做json序列化和反序列化的方案和流程，提高开发效率。","https://image.xinwei.ltd/image1768298153028.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],74,"flutter json_serializable, flutter json_annotation,flutter json, flutter serializable, flutter json annotation, from json, to json, flutter",{"id":1381,"createdAt":1382,"title":1383,"content":1384,"summary":1385,"image":1386,"uid":136,"user":1387,"categoryId":85,"category":1388,"subCategoryId":144,"subCategory":1389,"comments":1390,"status":9,"reason":15,"notice":15,"visitCount":1378,"commentCount":112,"keywords":1391},108,"2024-04-23T06:17:33.801Z","jQuery知识一览","## 一、概览\njQuery官网：[https://jquery.com/](https://jquery.com/)\njQuery是一个高效、轻量并且功能丰富的js库。\n核心在于查询query。\njQuery是一个优秀的js函数库，是React/Vue/Angular框架之外中大型项目的首选。\njQuery的主旨是write less, do more。\n\n### 1.1 jQuery的功能\n- html元素的选取\n- 操作html元素\n- css操作\n- html事件处理\n- 实现js动画效果\n- 能够链式调用\n- 容易扩展插件\n- 封装了ajax\n\n### 1.2 引入jQuery库\n引入jQuery的方式有2种，一种是项目中直接引入jQuery的min.js文件，一种是使用服务器端jQuery文件（使用cdn）脚本标签方式引入。\n#### 1.2.1 本地项目引入\n在官网的：[https://jquery.com/download/](https://jquery.com/download/) 链接下可以下载到完整的代码，放到项目文件的js文件夹下。\n```js\n\u003Cscript src=\"static/js/jquery-3.7.1.min.js\">\u003C/script>\n```\n\n#### 1.2.2 cdn方式引入\n在网站：[https://www.bootcdn.cn/](https://www.bootcdn.cn/) 可以获得稳定、快速、免费的cdn加速服务。\n```js\n\u003Cscript src=\"https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js\">\u003C/script>\n```\n\n\n#### 1.2.3 版本兼容\n- 1.x 版本兼容老版本的IE，文件比较大\n- 2.x 版本文件比较小，支持IE8+\n- 3.x 版本引入部分新API，提供多个分包的版本，支持IE9+\n\n#### 1.2.4 开发的正确姿势\n开发过程中一般使用非min.js文件方便调试，生产环境部署上线时才使用min.js这种压缩文件。\n\n## 二、jQuery源码浅析\n### 2.1 匿名函数调用\n![jQuery源码1.jpg](https://image.xinwei.ltd/11713852422127.jpg)\n![jQuery源码2.jpg](https://image.xinwei.ltd/21713852443999.jpg)\n\n从源码中可以看出，jQuery的整体逻辑可以用以下简单的结构进行描述：\n```js\n( function( global, factory ) {\n    // 判断有无window环境的一堆逻辑代码\n})( typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n    // 构造jQuery的一些逻辑代码\n    return jQuery\n});\n```\n\n### 2.2 jQuery是一个函数\n![jQuery源码3.jpg](https://image.xinwei.ltd/31713852508969.jpg)\n\n从源码中可以看出，jQuery被定义为一个函数，函数中返回了一个实例对象（看new关键字）。\n\n继续跟踪源码 `new jQuery.fn.init( selector, context)`，这个函数中调用了`makeArray`，当然在其他if判断语句中也有返回伪数组对象（比如，定义了length字段，还有[0]的操作），这里拿`makeArray`作为演示。\n![jQuery源码4.jpg](https://image.xinwei.ltd/41713852546533.jpg)\n\n查看`makeArray`函数：\n![jQuery源码5.jpg](https://image.xinwei.ltd/51713852575184.jpg)\n\n所以这个返回实例对象，是一个伪数组。\n```js\n$('#menu-trigger') instanceof Array // false\n$('#menu-trigger') instanceof Object // true\n```\n\n### 2.3 jQuery挂载在window上\n![jQuery源码6.jpg](https://image.xinwei.ltd/61713852625552.jpg)\n\n从源码中可以看出，将jQuery函数和window.$ 以及window.jQuery绑定赋值，所以使用jQuery和$ 标识符就可以直接使用jQuery。通常在项目中直接使用$标识符，快捷简省。\n\n### 2.4 jQuery验证\n所以在引入jQuery的项目中：\n```language\nconsole.log(typeof $); // function\nconsole.log($ === jQuery); // true\nconsole.log($() instanceof Object); // true\n```\n![jQuery源码7.jpg](https://image.xinwei.ltd/71713852699828.jpg)\n\n\n\n## 三、jQuery常见用法\n### 3.1 函数形式调用\n通常形式为：$(param)\n- param为函数：dom加载完成后，执行该回调函数\n- param为选择器字符串：查找与该选择器匹配的所有标签，并封装成jQuery对象\n- param为dom对象：将该dom对象封装成jQuery对象\n- param为标签字符串：创建标签对象并封装成jQuery对象\n```language\n$(function() {\n    console.log(\"dom finished and execute this\");\n})\n\n$('#btn').click(function () {\n    // 这里的this是id为#btn的dom元素\n    console.log(this.innerHTML)\n    \n    console.log($(this).html())\n})\n\n$('\u003Cinput type=\"number\">\u003C/input>').appendTo('div')\n```\n\n\n### 3.2 点语法调用函数\n```language\nlet list = [1, 2, 3]\n$.each(list, function(i, ele) {\n    console.log(i, ele)\n})\n\n$.trim(' hello world ')\n```\n\n\n### 3.3 用法浅析\n- jQuery函数返回的是一个伪数组（Object对象），可以使用length和下标。\n```language\n// class中名为btn的dom元素有多少\n$('.btn').length\n\n$('.btn')[0]\n\n$('.btn').get(0)\n\n$('.btn').index()\n\n// 设置名为btn的class对应的dom标签的文本内容\n$('.btn').text('自定义文本内容')\n```\n\n通过$(param)传入的是selector、element、标签情况下，返回的是包含1个或者多个dom元素对象的伪数组。\n![jQuery源码8.jpg](https://image.xinwei.ltd/81713852789593.jpg)\n\n\n### 3.4 获取一组dom元素的常见用法\n```language\n// 基础标签和class\n// 选择了所有的div和span标签\n$('div, span')\n\n// 选择所有具有某个class的标签\n$('div.container')\n\n// 层次选择器\n$('ul span') // ul标签下的所有span元素\n$('ul>span') // ul标签下的所有子span元素\n$('.container+li') // class为container的元素后的下一个li元素\n$('ul .item~*') // class为item的元素后面所有兄弟元素\n\n// 过滤选择器\n$('div:first') // 选择第一个div\n$('div:last') // 最后一个div\n$('div:not(.container)') // class不为container的所有div\n$('div:lt(3):gt(0)') // 所有div元素中的大于0小于3的div元素，表示1和2索引处的dom元素\n$('div:containers(\"hello world\")') // 内容为hellow world的div元素\n$('div:hidden') // style中display: none的div元素\n$('div[data]') // 有data属性的div元素, example: \u003Cdiv data=\"\">\u003C/div>\n$('div[data=\"123\"]') // 有data属性且值为123的div元素, example: \u003Cdiv data=\"123\">\u003C/div>\n\n// 示例，使table表格的奇数行背景样式设置\n$('table>tbody>tr:odd')\n\n// form表单中\n$(':text') // 所有单行输入框\n$(':text:disabled') // 所有disabled的input输入框\n$(':checkbox') // 所有checkbox\n$(':checkbox:checked') // 所有选中的checkbox\n$('select').val() // select标签选中的option的value值\n```\n\n\n### 3.5 修改css\n直接修改css属性（如果其dom标签存在这个css属性）\n```language\n$('#container').css('background', 'red');\n\n$('#container').css({ 'background' : 'red', 'color': 'blue' }) // 一组属性\n```\n\n清空某标签下的所有dom：\n```language\n$('.carousel-inner').empty();\n```\n\n给某标签下添加dom标签：\n```language\n$('.carousel-inner').append(domStr);\n```\n\n移除、添加class：\n```language\n$('.carousel-indicators li').removeClass('active');\n$('.carousel-indicators li:first').addClass('active');\n```\n\n\n### 3.6 获取属性\n获取dom标签上的属性：\n```language\n $('.about-img-1>img').attr('src');\n```\n\n设置标签的属性：\n```language\n $('.about-img-1>img').attr('src', (data && data['image']) ? data['image'] : '');\n```\n\n\n### 3.7 一些dom事件\n点击：\n```language\n$('.category-product-page-ul>li').click(function(e) {\n    e.preventDefault();\n    console.log('this is:', this); // 打印对应的dom标签\n});\n```\n\nhover：\n```language\n$('#container').hover(  \n    function() {  \n        // 当鼠标进入元素时执行的函数\n    },\n    function() {  \n        // 当鼠标离开元素时执行的函数\n    }  \n  );\n```\n\n监听事件：\n```language\n$('.bigImage').on(\"mousemove\", function( e ) {\n    // do something\n});\n```\n\n\n### 3.8 发起ajax请求\n```language\nconst json = '/static/js/data/xxx.json';\n $.ajax({\n    url: json,  \n    dataType: 'json',  \n    success: function(data) {\n      // do something\n    },\n    error: function(jqXHR, textStatus, errorThrown) {  \n      console.error('Fail to read json:', textStatus, errorThrown, json);\n    }  \n  });\n```\n\npost请求：\n```language\n$.post(\"url\", data,\n    function (data, textStatus, jqXHR) {\n      \n    },\n    \"dataType\"\n  );\n```\n\n","这篇文章将我对jQuery的一系列知识点做一个总结，记录一下日常经常用到一些方法，其中的代码片段也可以作为手册来温故知新。","https://image.xinwei.ltd/11713852422127.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"jquery,jquery knowledge,jquery知识,jquery开发",{"id":1181,"createdAt":1393,"title":1394,"content":1395,"summary":1396,"image":1397,"uid":136,"user":1398,"categoryId":85,"category":1399,"subCategoryId":1164,"subCategory":1400,"comments":1401,"status":9,"reason":15,"notice":15,"visitCount":1402,"commentCount":112,"keywords":1403},"2025-02-02T15:09:41.368Z","给nginx申请、配置和更新ssl证书","这篇文章简要介绍一下如何配置ssl证书，拿腾讯云上的ssl配置进行举例，让大家了解这个操作流程。\n\n## 申请并验证ssl证书\n腾讯云上的ssl证书主页：[https://console.cloud.tencent.com/ssl](https://console.cloud.tencent.com/ssl)\n\n如截图所示：\n![腾讯云ssl中心](https://image.xinwei.ltd/image1738507039421.png)\n\n你可以直接购买一个ssl证书，针对泛域名或者特定域名，当然也可以申请一个免费的ssl证书，但是当前时刻免费的ssl证书只有3个月90天的有效期，过期后ssl证书就不能使用了。\n\n![ssl证书申请流程](https://image.xinwei.ltd/image1738507194864.png)\n\n如上图所示，整个的ssl证书申请流程，就是简单的3步。\n- [x] 第一步，要在云平台上购买/申请ssl证书，绑定相应的域名；\n- [x] 第二步，选择dns验证方式，并且完成dns验证。有截图中所示的3种验证方式，我一般使用手动，你也可以使用其他验证方式。然后点击确定后，会生成一个dns记录，会提示让你将这条记录添加到云解析中。\n主页：[https://console.cloud.tencent.com/cns](https://console.cloud.tencent.com/cns)\n![域名云解析](https://image.xinwei.ltd/image1738507745925.png)\n\n- [x] 第三步，云解析中添加完dns记录后，回到刚刚的dns验证页面，点击“验证dns”，现在基本几分钟就可以完成dns记录。\n\n下载ssl证书：\n![ssl证书下载](https://image.xinwei.ltd/image1738507881646.png)\n\n## 配置nginx的ssl\n找到你的nginx的nginx.conf文件，对于ssl证书配置，最终目的是要实现http传输时通过443端口进行ssl验证加密。\n那么你需要在nginx.conf文件中配置443端口，需要分为2步：\n- [ ] 需要将你刚刚下载的ssl证书解压得到以下几个文件：\n![ssl证书截图](https://image.xinwei.ltd/image1738508174460.png)\n如截图所示，会有4种格式的证书对应不同平台的ssl证书配置需要，在nginx.conf配置中，我们只需要使用到`.key`、`.crt`这2个文件。\n\n- [ ] 将上面的2个文件上传到你的服务器中某一个位置。如果是使用docker的话，会比较方便，我自己也是配置的nginx的docker容器来进行配置，通过bind进行目录绑定。\n\n- [ ] 配置nginx的443端口\n```nginx.conf\n...\nserver {\n        listen 443 ssl;\n        listen [::]:443 ssl;\n\n        #请填写绑定证书的域名\n        server_name www.abc.com; \n        #请填写证书文件的相对路径或绝对路径\n        ssl_certificate ./cert/xxx/2025-02-02/xxx.crt; \n        #请填写私钥文件的相对路径或绝对路径\n        ssl_certificate_key ./cert/xxx/2025-02-02/xxx.key; \n        ssl_session_timeout 5m;\n        #请按照以下协议配置\n        ssl_protocols TLSv1.2 TLSv1.3; \n        #请按照以下套件配置，配置加密套件，写法遵循 openssl 标准。\n        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; \n        ssl_prefer_server_ciphers on;\n\n        charset utf-8;\n\n        location / {\n            root   /usr/share/nginx/html/xxx;\n            index  index.html index.htm;\n\n            # 此处一定要改成nginx容器中的目录地址，宿主机上的地址容器访问不到\n            # 命令必须用 root, 不能用 alias\n            \n            #try_files $uri $uri/ /index.html;\n\n            proxy_pass http://xxx.xxx.xxx.xxx:1234; # ip端口转发\n            proxy_set_header Host $host;  \n            proxy_set_header X-Real-IP $remote_addr;  \n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  \n            proxy_set_header X-NginX-Proxy true;  \n            #add_header 'Access-Control-Allow-Origin' '*';\n        }\n\n        error_page  404              /404.html;\n\n        # redirect server error pages to the static page /50x.html\n        #\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n    }\n...\n```\n上面的配置就是针对域名`www.abc.com`配置443访问端口，配置对应的ssl证书。\n这里的配置中，我有代理转发的配置，大家可以忽略，或者仅仅作为参考，我们的目的仅在于其中的ssl证书配置部分。\n- [ ] 给nginx的ssl配置完成后，需要对nginx进行重载，那么你可以在nginx的环境下，执行以下命令，重载nginx的配置.\n```terminal\n# 检测nginx.conf中的语法是否正确\nnginx -t\n\n# nginx重载\nnginx -s reload\n```\n\n结束。\n\n以上应该差不多讲清了这个ssl证书流程了吧。\n\n希望对大家有帮助。","这篇文章简要介绍一下如何配置ssl证书，拿腾讯云上的ssl配置进行举例，让大家了解这个操作流程。","https://image.xinwei.ltd/image1738507039421.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":1164,"name":1166,"parentId":85},[],73,"ssl,dns ssl,https,nginx ssl,https ssl,nginx,server ssl,ssl certificate,nginx https",{"id":729,"createdAt":1405,"title":1406,"content":1407,"summary":1408,"image":15,"uid":136,"user":1409,"categoryId":85,"category":1410,"subCategoryId":47,"subCategory":1411,"comments":1412,"status":9,"reason":15,"notice":15,"visitCount":1402,"commentCount":112,"keywords":1413},"2024-01-29T15:10:42.462Z","Runtime底层原理分析","官方：\\\nurl: \u003Chttps://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048>\n\n# OC对象、方法\n1.  OC对象的本质是结构体；\n2.  方法的本质是消息发送；\n3.  消息的组成：\n```main.m\nPerson *p = [[Person alloc] init];\n[p run];\n```\n本质：\n```main.mm\nPerson *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(\"Person\"), sel_registerName(\"new\"));\n((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName(\"run\"))\n```\n\n*   消息接收者：p\n*   方法：`sel_registerName(\"run\")` -> 方法编号 -- 字符串name \\\n    通过sel 找到函数实现的指针.\\\n    函数实现类似于：\n```\nvoid runIMP(id self, SEL _cmd) {\n    ...\n}\n```\n\n# Runtime调用的3种方式\n1.  runtime api\n2.  NSObject api isKindOf, isMemberOf\n3.  编译器提供的oc上层方法 @selector\n4.  向父类发消息\n\n- 对象方法:\n```\nstruct objc_super mySuper;\nmySuper.receiver = s;\nmySuper.super_class = class_getSuperclass([s class]);\nobjc_msgSendSuper(&mySuper, @selector(run));\n```\n\n- 类方法:\n```\nstruct objc_super myClassSuper;\nmyClassSuper.receiver = [s class];\nmyClassSuper.super_class = class_getSuperclass(object_getClass([s class));  // 元类\nobjc_msgSendSuper(&myClassSuper, sel_registerName(\"walk\"));\n```\n*   类方法存在元类之中，以实例方法的姿态形式存在\n*   对象在类中是一个实例，类在元类中也是一个实例\n\n# objc_msgSend\n1. 快速\n- 缓存找 汇编 -> cache_t 找imp通过hash表\n- cache在类结构体中\n\n2. 慢速\n直接通过C, C++, 汇编来找. 找到后存缓存\n*   [x] objc\\_msgSend用汇编来写\n*   [x] 如果用C函数，不能写一个函数，跳转到任意的指针\n*   [x] C还要往下层编译，汇编快\n*   [x] 汇编有寄存器, x0 - x31\n\n# 源码分析\n`tagged pointer`特殊的数据类型\n1.  \\_objc\\_msgSend\n2.  LNilOrTagged\n3.  LGetIsaDone isa处理完毕\n4.  CacheLoopup NORMAL\n\n*   call imp\n\n*   objc\\_msgSend\\_uncached\n\n*   CacheHit\n\n*   CheckMiss -> \\_objc\\_msgSend\\_uncached\n\n*   add\n\nadditional: bl汇编指令就是跳转.\n\nMethodTableLookup\n\n## C、C++:\n1.  lookUpImpOrForward\n2.  relizeClass(cls)\n3.  \\_class\\_initialize\n4.  漫长的过程 -> 找方法 -> 找自己 -> 找父类 -> NSObject\n\n*   getMethodNoSuper\\_nolock 递归查找方法列表中的方法\n\n*   log\\_and\\_fill\\_cache\n\n*   [x] remap(cls)重映射\n\n1.  resolver动态解析 \\\n    如果方法没有实现，走resolver方法. \\\n    动态解析一次. \\\n    流程：\n\n*   lookup imp\n*   如果没有，走\\_class\\_resolveMethod（只走一次）\n*   如果不是元类，走\\_class\\_resolveInstanceMethod\n*   如果是元类，走\\_class\\_resolveClassMethod，再\\_class\\_resolveInstanceMethod\n\n1.  消息转发forwardingTargetForSelector\n2.  方法签名\n3.  无法处理doesNotRecognize\n\n\n## 查看所有的objc_msgSend信息的函数\n```\nextern void instrumentobjcMessageSends(BOOL);\n```\n可以在private -> tmp -> msgSends-进程号\n\n### 总结\n\nruntime就是可以做到\n\n1.  动态添加注册类\n2.  查询/添加属性、方法\n3.  等\n","iOS的runtime的底层概念和调用方式。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios开发,ios runtime",{"id":1415,"createdAt":1416,"title":1417,"content":1418,"summary":1419,"image":15,"uid":136,"user":1420,"categoryId":85,"category":1421,"subCategoryId":47,"subCategory":1422,"comments":1423,"status":9,"reason":15,"notice":15,"visitCount":1424,"commentCount":112,"keywords":1425},50,"2024-01-30T14:41:54.605Z","在 iOS 应用中直接跳转到 AppStore 的方法","比如微信在Mac的App Store中的链接是：\n`https://apps.apple.com/cn/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=12`\n\n其中`%E5%BE%AE%E4%BF%A1`是“微信”的url encoded编码字符串。\n\n找到你想要的应用程序的描述链接，比如：\n> http://itunes.apple.com/gb/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=8\n\n然后将 http:// 替换为 itms:// 或者 `itms-apps://：\n itms://itunes.apple.com/gb/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=8`\n `itms-apps:// itunes.apple.com/gb/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=8 ` \n\n然后打开这个链接地址：\n```\n[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@\"itms://itunes.apple.com/gb/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=8\"]];\n```\n```\n[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@\"itms-apps ://itunes.apple.com/gb/app/%E5%BE%AE%E4%BF%A1/id836500024?mt=8\"]];\n```\n\n在真机上面进行测试","这是很早之前的记录，现在不一定适用，可以作为对之前的记忆参考。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],71,"ios跳转,ios深链接",{"id":1427,"createdAt":1428,"title":1429,"content":1430,"summary":1431,"image":1432,"uid":136,"user":1433,"categoryId":85,"category":1434,"subCategoryId":100,"subCategory":1435,"comments":1436,"status":9,"reason":15,"notice":15,"visitCount":1437,"commentCount":112,"keywords":1438},159,"2025-04-25T02:23:30.481Z","Parameter specified as non-null is null: method com.flutter.gradle.VersionUtils.mostRecentSemanticVersion, parameter....","记一次flutter编译报错的问题.\n\n我的`pubspec.yaml`中引入了`better_player_plus`和`flutter_vlc_player`，配置是：\n\n```pubspec.yaml\n...\ndependencies:\n  flutter:\n    sdk: flutter\n\n  # The following adds the Cupertino Icons font to your application.\n  # Use with the CupertinoIcons class for iOS style icons.\n  cupertino_icons: ^1.0.8\n  better_player_plus: ^1.0.8\n  flutter_vlc_player: ^7.4.3\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n...\n```\n\n## 问题1\n> Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...\nRunning Gradle task 'assembleDebug'...\nWarning: Flutter support for your project's Android Gradle Plugin version (Android Gradle Plugin version 8.1.0) will soon be dropped. Please upgrade your Android Gradle Plugin version to a version of at least Android Gradle Plugin version 8.3.0 soon.\nAlternatively, use the flag \"--android-skip-build-dependency-validation\" to bypass this check.\n>\n> Potential fix: Your project's AGP version is typically defined in the plugins block of the `settings.gradle` file (/Users/edy/Desktop/test/android/settings.gradle), by a plugin with the id of com.android.application. \nIf you don't see a plugins block, your project was likely created with an older template version. In this case it is most likely defined in the top-level build.gradle file (/Users/edy/Desktop/test/android/build.gradle) by the following line in the dependencies block of the buildscript: \"classpath 'com.android.tools.build:gradle:\u003Cversion>'\".\n>\n>\n> FAILURE: Build failed with an exception.\n>\n> * What went wrong:\nA problem occurred configuring project ':better_player_plus'.\n> Parameter specified as non-null is null: method com.flutter.gradle.VersionUtils.mostRecentSemanticVersion, parameter version1\n>\n> * Try:\n> Run with --stacktrace option to get the stack trace.\n> Run with --info or --debug option to get more log output.\n> Run with --scan to get full insights.\n> Get more help at https://help.gradle.org.\n>\n> BUILD FAILED in 1m 15s\nError: Gradle task assembleDebug failed with exit code 1\n\n\n报错截图：\n![fllutter gradle error](https://image.xinwei.ltd/Pasted%20Graphic1745546641474.png)\n\n这个问题出现在我使用了flutter的库：`flutter_vlc_player: ^7.4.3`，但我本地的flutter sdk环境是`3.27.0-0.1.pre`的问题.\n\n在运行Android的时候，我Android studio中默认的gradle默认是`8.1.0`的，所以也会有警告.\n\n### 解决办法\n- 1. 升级本地的flutter sdk\n```terminal\nflutter channel beta # 切换到最新的beta channel\n```\n- 2. 更新dart版本\n之前是\n```pubspec.yaml\n...\nenvironment:\n  sdk: ^3.6.0-216.1.beta\n...\n```\n升级后：\n```pubspec.yaml\n...\nenvironment:\n  sdk: ^3.7.0\n...\n```\n- 3. 配置android/settings.gradle\n之前：\n```settings.gradle\n...\nplugins {\n    id \"dev.flutter.flutter-plugin-loader\" version \"1.0.0\"\n    id \"com.android.application\" version \"8.1.0\" apply false\n    id \"org.jetbrains.kotlin.android\" version \"1.8.22\" apply false\n}\n\ninclude \":app\"\n```\n配置后：\n```settings.gradle\n...\nplugins {\n    id \"dev.flutter.flutter-plugin-loader\" version \"1.0.0\"\n    id \"com.android.application\" version \"8.3.0\" apply false // 升级这里\n    id \"org.jetbrains.kotlin.android\" version \"1.8.22\" apply false\n}\n\ninclude \":app\"\n```\n\n到这一步，再编译会有新的报错，错误在下面贴出来，并解决。\n\n## 问题2\n> Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...\nRunning Gradle task 'assembleDebug'...\n>\n> FAILURE: Build failed with an exception.\n>\n>* Where:\nBuild file '/Users/edy/Desktop/test/android/app/build.gradle' line: 2\n>\n> * What went wrong:\nAn exception occurred applying plugin request [id: 'com.android.application']\n> Failed to apply plugin 'com.android.internal.version-check'.\n   > Minimum supported Gradle version is 8.4. Current version is 8.3. If using the gradle wrapper, try editing the distributionUrl in /Users/edy/Desktop/test/android/gradle/wrapper/gradle-wrapper.properties to gradle-8.4-all.zip\n>\n> * Try:\n> Run with --stacktrace option to get the stack trace.\n> Run with --info or --debug option to get more log output.\n> Run with --scan to get full insights.\n> Get more help at https://help.gradle.org.\n>\n> BUILD FAILED in 27s\nError: Gradle task assembleDebug failed with exit code 1\n\n问题截图：\n![flutter gradle 8.4](https://image.xinwei.ltd/Pasted%20Graphic%2011745547492901.png)\n\n从截图中已经看出解决办法了，提示我们要修改`gradle-wrapper.properties`的版本\n\n### 解决办法\n修改`android/gradle/wrapper/gradle-wrapper.properties`配置\n之前配置是：\n```gradle-wrapper.properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.3-all.zip\n```\n更新配置后：\n```gradle-wrapper.properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.4-all.zip\n```\n\n再次运行，问题解决了。\n\n希望对大家有借鉴意义。\n\n\n\n","记一次flutter编译报错的问题.","https://image.xinwei.ltd/Pasted%20Graphic1745546641474.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],70,"gradle version,gradle error,flutter gradle,gradle,android gradle,better_player_plus,flutter_vlc_player",{"id":1440,"createdAt":1441,"title":1442,"content":1443,"summary":1444,"image":1445,"uid":136,"user":1446,"categoryId":85,"category":1447,"subCategoryId":676,"subCategory":1448,"comments":1449,"status":9,"reason":15,"notice":15,"visitCount":1437,"commentCount":112,"keywords":1450},22,"2024-01-26T14:19:32.414Z","学习go的安装环境和基本配置还有基础知识","# 一、go安装和环境配置\n## 1. 安装\n方式一：软件包安装\n进入官网：https://go.dev/dl/，选择自己的电脑系统，下载软件包安装\n方式二： Mac电脑中使用brew\n```terminal\nbrew install go\n```\nwindows略\n\n## 2. 查看和配置环境\n```terminal\n// 查看go的环境配置变量，主要看GOPATH、GOROOT、GOBIN\ngo env\n\n// 如果不想使用默认的GOPATH配置路径，可自行设置GOPATH\ngo env -w GOPATH=xxxx  // xxxx为路径，比如/usr/local/go\n\n// GOBIN也需要设置一下，使用GOPATH路径下的bin目录即可\n```\n1. src // 这里面存放我们自己编写的go项目，往往以域名为文件夹名，然后以项目名作为二级文件夹名\n比如：src/baidu.com/baidu_cloud（baidu.com为域名，下面有一个baidu_cloud的工程项目）\n2. pkg // 见后面介绍\n3. bin // 见后面介绍\n\n## 3. 修改go镜像\n由于墙，国内推荐使用七牛云的镜像服务，查看其官网指南：https://goproxy.cn/\n![go镜像服务截图](https://image.xinwei.ltd/11706277776025.png)\n\n## 4. 代码编辑器IDE\n对于go，官方IDE是goland，可以到官网下载：[官网IDE下载](https://www.jetbrains.com/zh-cn/go/)\n我个人推荐使用vscode，在vscode中安装使用几个好用的扩展：\n![我推荐的开发go的vs插件](https://image.xinwei.ltd/21706277868160.png)\n\n# 二、go程序基本框架\n## 1. GOPATH解释\n1.src文件夹：存放源码；\n2.bin文件夹：存编译出的产物；\n3.pkg文件夹：存缓存的库。\n\n- 应该放在GOPATH指向的目录\n```terminal\n// 查看GOPATH指向的目录位置\ngo env | grep GOPATH\n```\n- 补充\nGOROOT：go编译源码根目录；\nGOPATH： go工程的根目录；GOPATH下应该要有src、bin、pkg这3个文件夹(没有的话可以新建这几个文件夹)，编写的工程代码需放在src中，比如go项目工程go_test，目录应该如: $GOPATH/src/go_test。\nGOBIN:  go工程的bin目录，存放二进制可执行文件；\n\n- 每一个go文件，都以go为扩展名；\n- go文件中都有一个包名，作为命名空间：\n```test.go\npacakage main\n\nfunc main() {\n    \n}\n```\n\n## 2. 代码工程 & mod\n1. 程序的入口文件都是main.go\n2. 然后可以新建其他的文件夹，比如controller、model、services、utils等\n3. 初始化mod，mod中有我们自己项目中所需引入的库，类似于依赖配置文件。\n打开命令行终端，进入在main.go同级目录，执行以下命令：\n```terminal\ngo mod init baidu_cloud //这里以baidu_cloud为工程名字作为示例\n```\n4. 生成mod文件后，之后基本不用管mod\n\n## 3. 引入包\n```xxx.go\nimport \"fmt\" // 在go文件中引入标准库\n```\n当需要引入其他框架时，比如gorm框架，需要在项目根目录，即mod所在目录下，执行终端命令：\n```terminal\ngo get -u gorm.io/gorm\n```\n执行命令成功后，mod文件中会自动引入这个库版本等信息，工程中就可以直接引用使用。\n其他go框架，参照其对应的github使用方式即可。\n\n## 4. go常用命令\n```terminal\ngo run xxx.go // 编译运行某个文件\n\ngo run *.go // 所有go文件\n\ngo build -o xxx.exe xxx.go // 编译成windows上的程序\n\ngo env // 查看go的环境配置信息\n\ngo env -w GOPATH=/usr/local/go // 修改env中某个配置变量的值，这里以GOPATH为示例\n\n```\n\n# 三、基本语法\n1. 变量声明：\n```xxx.go\n// 自动类型推断\nname := \"自动类型推断，所有语句不需要分号结尾\"\nage := 14\n\nlist := [10]int{1, 2, 3}; // 固定长度数组\narr  := []int{}; // 可变长度数组\n\n// 先声明，后赋值\nvar nickname: string\nnickname = \"可以声明了再赋值\"\n\n// 声明多个并赋值\nname, age := \"姓名\", 14\n\n// 可以不用加小括号\n// 大括号的 { 要紧跟在代码行右侧，再换行写大括号中的代码\ncon := 4\nif con > 3 {\n    fmt.println(\"true\")\n}\n\n// 数字变量，只有后置运算符，没有c语言中的前置运算符\niterator := 1 // 声明了一个1\niterator++ // 这行必须单独一行，不能放在if或者其他代码行中\n++iterator // 会报错\n```\n\n2. 数组\n```xxx.go\nlist := []str{\"元素1\", \"元素2\"}\n\n// 获取数组长度\nfmt.printf(\"数组长度：%d\", len(list))\n\n// 第一种常规循环\nfor i:=0; i\u003Clen(); i++ {\n    \n}\n\n// 第二种循环 for - range\nfor key, value := range list {\n    fmt.printf(\"数组长度：%c\", key, \", 值是：%c\", value)\n}\n\n// 如果要忽略其中某一个值，使用_下划线代替即可\nfor _, value := range list {\n    fmt.printf(\"值是：%c\", value)\n}\n\n```\n\n3. 数组的length和capacity（长度len和容量cap）\n```xxx.go\nlist := []int{1, 2}\n\nprintln(\"长度是:\", len(list)) // 2\nprintln(\"容量是:\", cap(list)) // 2\n\n// 数组添加\nlist = append(list, [3, 4, 5])\n\nfmt.println(\"长度是:\", len(list)) // 4\nfmt.println(\"容量是:\", cap(list)) // 8\n```\n当数组新增加的数量，超过初始容量时，可变数组的容量会增加为初始容量的2倍，当容量越来越大时，可能到不了2倍\n\n4. 数组切片\n```xxx.go\nlist := []int{1, 2, 3, 4}\n\nsliceList = list[0:2] // 取list的第0位到第2位的元素，半闭合数组 0\u003C= slice \u003C2\n\nsliceList2 = list[:4] // 默认为 0 \u003C= ... \u003C4\n\nsliceList2 = list[2:] // 默认为 2 \u003C= ... \n\n// warning：当修改切片中的某一个数据时，会影响到原数组。\n// 想要切片独立于原数组，那么需要使用copy函数\n\nwelString := \"hello world\"[6:]\nfor i:=0; i\u003Clen(welString); i++ {\n    \n}\n```\ncopy操作:\n```xxx.go\nlist := []int{1, 2, 3, 4}\n\nlist_copy := make([]int, len(list))\n\ncopy(list_copy, list[:]) // 表示把list全长的切片，拷贝到list_copy，\n// 因为copy函数第二位要求是切片类型\n```\nmake创建指定length和capacity的切片:\n```xxx.go\nstr := make([]string, 10, 15) // 创建一个默认长度为10，容量为15的切片, \n// 15所代表的第3个形参不是必须填写的，如果没写cap，默认和len一样\n```\n\n5. 函数声明\n```xxx.go\n// 不带返回值\nfunc main() {\n    \n}\n\n// 带返回值\nfunc doSomething() string {\n    return \"返回值是xxxxx\"\n}\n```\n\n6. 指针\n```xxx.go\n// 指针操作\nname := \"Li lei\"\nnamePtr = &name\n\nfmt.println(\"指针指向的内容是：\", *namePtr)\n\n// 不同于c语言，go还可以取到栈空间的指针\nfunc test() *int {\n    num := 1\n    return &num // 一般情况下，程序执行完毕，栈空间中的变量会被释放掉，\n    // 但是go会判断如果是需要使用到的栈变量，会把变量存到堆上\n}\n\nfmt.println(\"指针的内容是：\", *test()) // 1\n\n```\n\n7. map字典\n```xxx.go\n// 推断式声明 - 推荐使用\ndict := make(map(int)string)\n// 或者\ndict := make(map(int)string, 10) // 给定长度\n\n// 先声明，再赋值\nvar dict map(int)string\ndict = make(map(int)string)\n\n// 赋值\ndict[0] = \"first\"\ndict[1] = \"second\"\n\n// 读取\nfmt.println(\"值是：\", dict[0]) // first\nfmt.println(\"超过容量的值是：\", dict[100]) // 空，当读取的key不在容量内时，返回空，不报错\n\n// 循环\nfor key, value := range dict {\n    fmt.println(\"key是：\", key, \", value是：\", value)\n}\n\n// 判断map中是否有这个key存在（由于读取不存在的key也不报错）\nvalue, ok := dict[0]\nif ok {\n    fmt.println(\"value是：\", value) // first\n} else {\n    fmt.println(\"value是：\", value) // 空\n}\n\n// 删除某一个值\ndelete(dict, 0)\ndelete(dict, 100) // 删除不存在的，也不会报错\nfmt.Println(\"删除后：\", dict) // map[1 : second]\n\n// 并发任务处理的时候，需要对map上锁\n```\n\n","这里告诉大家如何安装golang的开发环境，包含对环境目录的解释，编辑器和好用的插件，以及还有一些语法及代码使用的基本知识。介绍了变量、切片、函数、指针、map等概念和基本使用。","https://image.xinwei.ltd/11706277776025.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":676,"name":678,"parentId":85},[],"golang,mac golang",{"id":676,"createdAt":1452,"title":1453,"content":1454,"summary":1455,"image":1456,"uid":136,"user":1457,"categoryId":85,"category":1458,"subCategoryId":242,"subCategory":1459,"comments":1460,"status":9,"reason":15,"notice":15,"visitCount":1437,"commentCount":112,"keywords":1461},"2024-01-19T15:18:03.329Z","git操作时的Operation timed out","问题描述：\n当在一个git仓库中手动操作git命令时，比如pull、push等时，或者在ssh -T git@github.com时，终端报错：ssh: connect to host github.com port 22: Operation timed out\n截图：\n![](https://image.xinwei.ltd/11705677326461.png)\n\n\n问题排查：\n1. 在Mac电脑中ssh文件夹的位置在：/Users/xxx/.ssh\n2. 查看其中的文件，截图如下：\n![](https://image.xinwei.ltd/21705677338850.png)\n\n3. 通过在github网站的ssh配置中，查看公钥都存在，而且known_hosts中也存在对应github的ssh公钥信息。\n4. known_hosts文件：known_hosts是系统生成的，在一些需要ssh操作中，通过ssh访问的主机的端口和公钥信息都会保存在这里。主要是保存你ssh访问某些主机时的身份信息到本地，防止中间人攻击。\n5. 然后就是因为: ssh -T git@github.com 测试访问时，提示端口22操作超时，如上面的问题截图所示。\n6. 那么这些文件配置没问题，那么最后确定就是端口22访问的问题，通过查找很多资料后发现，可以尝试将端口号22修改成443的https访问。\n\n解决办法：\n1.在ssh文件夹下需要一个config文件，在文件中配置github的host主机和端口，那么在进行ssh访问时，会从config文件中读取配置。\n```config\nHost github.com\n  Hostname ssh.github.com\n  Port 443\n```\n2.我们可以直接通过一行终端命令搞定。\n3.终端进入到ssh文件夹下:\n```terminal\ncd ~/.ssh\n```\n![](https://image.xinwei.ltd/31705677413273.png)\n\n4.那么在.ssh文件夹下没有config文件对吧，我们通过命令写入一下，然后会生成config文件（直接复制以下命令，回车就好）：\n```terminal\necho 'Host github.com\n  Hostname ssh.github.com\n  Port 443' >> config\n```\n![](https://image.xinwei.ltd/41705677448440.png)\n\n那么config文件自动就生成了，重新尝试链接github.com。\n5.看结果，成功被allowed了：\n![](https://image.xinwei.ltd/51705677461947.png)\n\n6.然后可以查看一下know_hosts文件，连接成功的端口是443了:\n![](https://image.xinwei.ltd/61705677474766.png)\n\n以上。\n","问题描述： 当在一个git仓库中手动操作git命令时，比如pull、push等时，或者在ssh -T git@github.com时，终端报错：ssh: connect to host github.com port 22: Operation timed out 截图： (https://image.xinwei.ltd/11705677326461.png) 问题排查： 1. 在Mac电脑中s","https://image.xinwei.ltd/11705677326461.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":242,"name":244,"parentId":85},[],"git,git error,operation timed out,git操作",{"id":1463,"createdAt":1464,"title":1465,"content":1466,"summary":1467,"image":1468,"uid":136,"user":1469,"categoryId":85,"category":1470,"subCategoryId":186,"subCategory":1471,"comments":1472,"status":9,"reason":15,"notice":15,"visitCount":1473,"commentCount":112,"keywords":1474},169,"2025-09-20T11:18:11.125Z","rvm install ruby-3.2.2 failed: Error running 'requirements_osx_brew_update_system ruby-3.2.2'","最近换了电脑，所以又需要配置一下电脑的环境，但是在遇到ruby环境的升级时又遇到了新的问题，所以在此记录一下。\n\n在我之前的文章：[https://www.xinwei.ltd/article/87](https://www.xinwei.ltd/article/87)中，已经描述了如何在mac中安装rvm来管理ruby版本的流程和步骤。\n\n但是一些异常情况仍然可能会出现，因为电脑系统版本等问题，这里出现的就是其中一种。\n\n## 问题\n> ➜  ~ sudo rvm install ruby-3.2.2 --verbose\nSearching for binary rubies, this might take some time.\nNo binary rubies available for: osx/26.0/x86_64/ruby-3.2.2.\nContinuing with compilation. Please read 'rvm help mount' to get more information on binary rubies.\nChecking requirements for osx.\nInstalling requirements for osx.\nUpdating system..........Failed to update Homebrew, follow instructions at\n.\n    https://docs.brew.sh/Common-Issues\n>\n> and make sure `brew update` works before continuing.\n.\nError running 'requirements_osx_brew_update_system ruby-3.2.2',\nplease read /Users/hamry/.rvm/log/1758364524_ruby-3.2.2/update_system.log\nRequirements installation failed with status: 1.\n\n\n截图：\n![ruby error](https://image.xinwei.ltd/image1758365862537.png)\n\n正如报错中所示，说没有更新Homebrew，但是当我执行`brew update`命令时，提示当前的Homebrew已经是最新版本。\n![brew version](https://image.xinwei.ltd/image1758366123908.png)\n\n所以肯定是由其他问题导致的，那我们打开报错中的log文件，看能不能找到具体问题：\n```update_system.log\n[2025-09-20 18:35:24] requirements_osx_brew_update_system\nrequirements_osx_brew_update_system () \n{ \n    if __rvm_version_compare \"${_system_version}\" -ge 10.7; then\n        __rvm_detect_xcode_version_at_least 4.6.2 || __CLT_version_at_least 4.6.0 || { \n            \\typeset ret=$?;\n            rvm_error \"\nXcode version older than 4.6.2 installed, download and install newer version from:\n\n    http://connect.apple.com\n\nAfter installation open Xcode, go to Downloads and install Command Line Tools.\n\";\n            return $ret\n        };\n    fi;\n    brew update || { \n        \\typeset ret=$?;\n        rvm_error \"Failed to update Homebrew, follow instructions at\n\n    https://docs.brew.sh/Common-Issues\n\nand make sure \\`brew update\\` works before continuing.\";\n        return $ret\n    }\n}\ncurrent path: /Users/hamry\nPATH=/Users/hamry/.nvm/versions/node/v22.19.0/bin:/Users/hamry/env_packages/flutter/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Users/hamry/.rvm/bin\ncommand(2): requirements_osx_brew_update_system ruby-3.2.2\n++ __rvm_version_compare 26.0 -ge 10.7\n++ typeset first\n+++ command printf %b '26.0\\n10.7\\n'\n+++ printf %b '26.0\\n10.7\\n'\n+++ __rvm_version_sort\n+++ command head -n1\n+++ head -n1\n+++ command awk '-F[.-]' -v OFS=. '{                   # split on \".\" and \"-\", merge back with \".\"\n    original=$0                                        # save original to preserve it before the line is changed\n    for (n=1; n\u003C10; n++) {                             # iterate through max 9 components of version\n      $n=tolower($n)                                   # ignore case for sorting\n      if ($n == \"\")                 $n=\"0\"             # treat non existing parts as 0\n      if ($n ~ /^p[0-9]/)           $n=substr($n, 2)   # old ruby -p notation\n      if ($n ~ /^[0-9](rc|b)/)      $n=substr($n, 1, 1)\". \"substr($n, 2)   # old jruby 0RC1 notation\n      if (n == 1 && $n ~ /^[0-9]/)  $n=\"zzz.\"$n        # first group must be a string\n      if (n > 1 && $n ~ /^[a-z]/)   $n=\" \"$n           # names go before numbers thanks to space\n    }\n    print $0\"\\t\"original                               # print the transformed version and original separated by \\t\n                                                       # so we can extract original after sorting\n  }'\n+++ awk '-F[.-]' -v OFS=. '{                   # split on \".\" and \"-\", merge back with \".\"\n    original=$0                                        # save original to preserve it before the line is changed\n    for (n=1; n\u003C10; n++) {                             # iterate through max 9 components of version\n      $n=tolower($n)                                   # ignore case for sorting\n      if ($n == \"\")                 $n=\"0\"             # treat non existing parts as 0\n      if ($n ~ /^p[0-9]/)           $n=substr($n, 2)   # old ruby -p notation\n      if ($n ~ /^[0-9](rc|b)/)      $n=substr($n, 1, 1)\". \"substr($n, 2)   # old jruby 0RC1 notation\n      if (n == 1 && $n ~ /^[0-9]/)  $n=\"zzz.\"$n        # first group must be a string\n      if (n > 1 && $n ~ /^[a-z]/)   $n=\" \"$n           # names go before numbers thanks to space\n    }\n    print $0\"\\t\"original                               # print the transformed version and original separated by \\t\n                                                       # so we can extract original after sorting\n  }'\n+++ LC_ALL=C\n+++ sort -t. -k 1,1d -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n\n+++ awk '-F\\t' '{print $2}'\n++ first=10.7\n++ case \"$2\" in\n++ [[ 10.7 == \\h\\e\\a\\d ]]\n++ [[ 10.7 == \\1\\0\\.\\7 ]]\n++ return 0\n++ __rvm_detect_xcode_version_at_least 4.6.2\n+++ __rvm_detect_xcode_version\n+++ typeset version_file\n+++ for version_file in /Applications/Xcode.app/Contents/version.plist /Developer/Applications/Xcode.app/Contents/version.plist\n+++ [[ -f /Applications/Xcode.app/Contents/version.plist ]]\n+++ [[ -x /usr/libexec/PlistBuddy ]]\n+++ /usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' /Applications/Xcode.app/Contents/version.plist\n+++ return 0\n++ typeset __xcode_version=26.0\n++ [[ -n 26.0 ]]\n++ __rvm_version_compare 26.0 -ge 4.6.2\n++ typeset first\n+++ command printf %b '26.0\\n4.6.2\\n'\n+++ printf %b '26.0\\n4.6.2\\n'\n+++ __rvm_version_sort\n+++ command head -n1\n+++ head -n1\n+++ command awk '-F[.-]' -v OFS=. '{                   # split on \".\" and \"-\", merge back with \".\"\n    original=$0                                        # save original to preserve it before the line is changed\n    for (n=1; n\u003C10; n++) {                             # iterate through max 9 components of version\n      $n=tolower($n)                                   # ignore case for sorting\n      if ($n == \"\")                 $n=\"0\"             # treat non existing parts as 0\n      if ($n ~ /^p[0-9]/)           $n=substr($n, 2)   # old ruby -p notation\n      if ($n ~ /^[0-9](rc|b)/)      $n=substr($n, 1, 1)\". \"substr($n, 2)   # old jruby 0RC1 notation\n      if (n == 1 && $n ~ /^[0-9]/)  $n=\"zzz.\"$n        # first group must be a string\n      if (n > 1 && $n ~ /^[a-z]/)   $n=\" \"$n           # names go before numbers thanks to space\n    }\n    print $0\"\\t\"original                               # print the transformed version and original separated by \\t\n                                                       # so we can extract original after sorting\n  }'\n+++ awk '-F[.-]' -v OFS=. '{                   # split on \".\" and \"-\", merge back with \".\"\n    original=$0                                        # save original to preserve it before the line is changed\n    for (n=1; n\u003C10; n++) {                             # iterate through max 9 components of version\n      $n=tolower($n)                                   # ignore case for sorting\n      if ($n == \"\")                 $n=\"0\"             # treat non existing parts as 0\n      if ($n ~ /^p[0-9]/)           $n=substr($n, 2)   # old ruby -p notation\n      if ($n ~ /^[0-9](rc|b)/)      $n=substr($n, 1, 1)\". \"substr($n, 2)   # old jruby 0RC1 notation\n      if (n == 1 && $n ~ /^[0-9]/)  $n=\"zzz.\"$n        # first group must be a string\n      if (n > 1 && $n ~ /^[a-z]/)   $n=\" \"$n           # names go before numbers thanks to space\n    }\n    print $0\"\\t\"original                               # print the transformed version and original separated by \\t\n                                                       # so we can extract original after sorting\n  }'\n+++ LC_ALL=C\n+++ sort -t. -k 1,1d -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n\n+++ awk '-F\\t' '{print $2}'\n++ first=4.6.2\n++ case \"$2\" in\n++ [[ 4.6.2 == \\h\\e\\a\\d ]]\n++ [[ 4.6.2 == \\4\\.\\6\\.\\2 ]]\n++ return 0\n++ true\n++ brew update\nError: Running Homebrew as root is extremely dangerous and no longer supported.\nAs Homebrew does not drop privileges on installation you would be giving all\nbuild scripts full access to your system.\n++ typeset ret=1\n++ rvm_error 'Failed to update Homebrew, follow instructions at\n\n    https://docs.brew.sh/Common-Issues\n\nand make sure `brew update` works before continuing.'\n++ rvm_pretty_print stderr\n++ case \"${rvm_pretty_print_flag:=auto}\" in\n++ case \"${TERM:-dumb}\" in\n++ case \"$1\" in\n++ [[ -t 2 ]]\n++ return 1\n++ printf %b 'Failed to update Homebrew, follow instructions at\n\n    https://docs.brew.sh/Common-Issues\n\nand make sure `brew update` works before continuing.\\n'\n++ return 1\n\n```\n\nlog里面说：\n> Error: Running Homebrew as root is extremely dangerous and no longer supported.\nAs Homebrew does not drop privileges on installation you would be giving all\nbuild scripts full access to your system.\n++ typeset ret=1\n> ++ rvm_error 'Failed to update Homebrew, follow instructions at\n>\n>    https://docs.brew.sh/Common-Issues\n>\n> and make sure `brew update` works before continuing.'\n\n所以依然提到是Homebrew的问题，但我没有发现Homebrew哪里有问题，brew update也已经执行过了，brew命令也能正常执行，所以要考虑其他问题。\n\n恰巧我以前在配置电脑环境中也遇到过openssl版本的问题，所以我怀疑是不是跟openssl版本有关系，这样想的原因是印象中有要把openssl升级到1版本的需要，然后在网上查到确实也有人提出要升级openssl的解决办法，索性尝试一下。\n\n![brew install openssl](https://image.xinwei.ltd/image1758366811778.png)\n从截图中可以看出，我电脑上的openssl已经是最新的，有点奇怪。\n\n那我在执行命令时，尝试直接指定openssl的位置，试一下，结果成功了。\n\n最终解决方案（ruby最新版本是3.4.6）：\n```terminal\nrvm install ruby-3.4.6 --with-openssl-dir=$(brew --prefix openssl@1.1)\n```\n![rvm install](https://image.xinwei.ltd/image1758367026139.png)\n\n查看是否生效：\n```terminal\n➜  ~ rvm docs generate-ri\nGenerating ri documentation.....................................\n➜  ~ rvm list\n=* ruby-3.4.6 [ x86_64 ]\n\n# => - current\n# =* - current && default\n#  * - default\n➜  ~ \n```\n\n这样就可以了。\n\n\n\n\n\n\n","最近换了电脑，所以又需要配置一下电脑的环境，但是在遇到ruby环境的升级时又遇到了新的问题，所以在此记录一下。","https://image.xinwei.ltd/image1758365862537.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":186,"name":188,"parentId":85},[],68,"rvm update,ruby install,install ruby,ruby update,ruby,rvm",{"id":1476,"createdAt":1477,"title":1478,"content":1479,"summary":1480,"image":15,"uid":136,"user":1481,"categoryId":85,"category":1482,"subCategoryId":85,"subCategory":1483,"comments":1485,"status":9,"reason":15,"notice":15,"visitCount":1473,"commentCount":112,"keywords":1486},78,"2024-02-05T02:29:46.543Z","浅谈Typescript","# 微软官方\n\n官网: \u003Chttps://www.typescriptlang.org/>\n中文网：\u003Chttps://www.tslang.cn/>\n\n# 概念\n\n*   JavaScript的超集。\n*   TypeScript是JavaScript类型的超集，它可以编译成纯JavaScript。\n*   TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行，并且是开源的。\n\n# 为什么选择\n\n1.  javascript的超集\n2.  ts静态类型语言，js动态类型语言。\n\n*   类型安全，类型检查，静态类型编译时就可以发现错误，动态只有运行时才能发现\n\n1.  从es6到es10，esnext的支持；\n2.  任何浏览器，各种系统，服务器都支持。\n\n# 个人角度\n\n1.程序更容易理解\n\n*   函数或者方法的参数类型\n*   动态类型语言需要手动调试\n\n1.  效率高，vs code，代码自动补全\n2.  完全支持javascript，兼容第三方库，.d.ts库\n3.  更少的错误\n\n# 环境\n\n1.  node环境：去nodejs的官网下载\n2.  typeScript\n    `npm install -g TypeScript`\n3.  VS Code IDE\n\n# 编码\n\n1.  typescript的编译器是tsc\n2.  ts -> js\n    `tsc xxx.ts`\n    会生成同名的js文件\n3.  执行js\n    `node xxx.js`\n\n# 基本类型\n\n## js的8种类型\n\n*   7种原始类型\n\n1.  Boolean\n2.  Null\n3.  Undefined\n4.  Number\n5.  BigInt\n6.  String\n7.  Symbol\n\n*   Object对象类型\n\n*   [x] 扩展\n\n*   除Object以外的所有类型都是不可变的（值本身无法被改变）\n\n*   null\n\n*   undefined\n\n*   null和undefined是所有类型的子类型\n\n*   any任意类型\n\n","这里介绍Typescript的基本概念，为什么要选择typescript，如何开始使用typescript。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":85,"name":1484,"parentId":85},"JavaScript&HTML&CSS",[],"typescript,typescript knowlege,typescript知识,typescript概念,typescript开发,前端",{"id":144,"createdAt":1488,"title":1489,"content":1490,"summary":1491,"image":15,"uid":136,"user":1492,"categoryId":85,"category":1493,"subCategoryId":144,"subCategory":1494,"comments":1495,"status":9,"reason":15,"notice":15,"visitCount":1473,"commentCount":112,"keywords":1496},"2024-01-29T14:38:19.581Z","ant-design-vue中文化","自定义的ant-design-vue-import.js中:\n```ant-design-vue-import.js\nimport Vue from 'vue';\nimport {\n    ConfigProvider,\n} from 'ant-design-vue';\n\nVue.use(ConfigProvider);\n```\n\nApp.vue中:\n```App.vue\n\u003Ctemplate>\n    \u003Ca-config-provider :locale=\"zhCN\">\n        \u003Cdiv id=\"app\">\n            \u003Ckeep-alive>\n                \u003Crouter-view v-if=\"$route.meta.keepAlive\" />\n            \u003C/keep-alive>\n            \u003Crouter-view v-if=\"!$route.meta.keepAlive\" />\n        \u003C/div>\n    \u003C/a-config-provider>\n\u003C/template>\n\n\u003Cscript>\nimport zhCN from 'ant-design-vue/es/locale/zh_CN';\nimport moment from 'moment';\nimport 'moment/locale/zh-cn';\nmoment.locale('zh-cn');\nexport default {\n    name: \"App\",\n    data() {\n        return {\n          zhCN,\n        };\n    },\n    created() {},\n    mounted() {},\n    methods: {},\n};\n\u003C/script>\n\n\u003Cstyle lang=\"less\">\nhtml,\nbody,\n#app {\n    margin: 0;\n    height: 100%;\n}\n\u003C/style>\n```\n","给vue项目中引入ant-design后，配置一下vue项目的中文化，以便组件中的提示或者默认文字是中文的。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"vue,vue ui,ant-design,antd",{"id":419,"createdAt":1498,"title":1499,"content":1500,"summary":1501,"image":15,"uid":136,"user":1502,"categoryId":85,"category":1503,"subCategoryId":144,"subCategory":1504,"comments":1505,"status":9,"reason":15,"notice":15,"visitCount":1506,"commentCount":112,"keywords":1507},"2024-01-12T14:49:56.402Z","何为URL安全的base64编码","### 一、常见使用场景\n腾讯万象中的图片处理中，在url上追加参数，比如文字水印、图片水印等。\n\n### 二、什么是 URL 安全的 BASE64 编码？\n1. 将普通 BASE64 编码结果中的加号（+）替换成连接号（-）；\n2. 将编码结果中的正斜线（/）替换成下划线（_）；\n3. 将编码结果中的“=”去掉。\n\n三、js中如何使用\n使用库\"js-base64\"\n```language\nimport { Base64 } from 'js-base64'\n\n// 参数中第二个使用true，即为安全的url base64编码\nconst encodedColor = Base64.toBase64('#FFFFFF', true)\n```\n","一、常见使用场景 腾讯万象中的图片处理中，在url上追加参数，比如文字水印、图片水印等。 二、什么是 URL 安全的 BASE64 编码？ 1. 将普通 BASE64 编码结果中的加号（+）替换成连接号（-）； 2. 将编码结果中的正斜线（/）替换成下划线（_）； 3. 将编码结果中的“=”去掉。 三、js中如何使用 使用库\"js-base64\" import { Base64 } from 'j",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],67,"url,base64,url安全的base64,base64编码,url编码",{"id":1509,"createdAt":1510,"title":1511,"content":1512,"summary":1513,"image":15,"uid":136,"user":1514,"categoryId":85,"category":1515,"subCategoryId":47,"subCategory":1516,"comments":1517,"status":9,"reason":15,"notice":15,"visitCount":1184,"commentCount":112,"keywords":1518},60,"2024-02-01T05:52:07.251Z","iOS的内存管理和runloop","# 内存管理 & Runloop\n## 一、内存管理\n1.  自动引用计数Related Count会自动给对象加retain，release.\n2.  函数是`objc_retain`\n```\nobjc_retain(id _Nullable objc)\n{\n    if (!obj) return obj;\n    if (obj->isTaggedPointer()) return obj;\n    return obj->retain();\n}\n\n```\n*   传进需要引用计数的对象\n*   isa指针是不是 TaggedPointer\n*   TaggedPointer针对小类型，当我们存储，比如NSNumber、NSString，字符串的长度小于固定长度时，我们使用TaggedPointer来优化它们的isa指针.目的是，存储的更高效.\n*   并不是所有对象的isa指针类型都是一样的\n*   所以对于NSNumber、NSString不用进行内存引用计数加1，内存管理语义是不一样的\n*   isa指针，64位，如果都用来存储地址，是不明智的。还存储了别的信息.\n\n- [ ]  retain函数\n```\nobjc_object::retain()\n{\n  assert(!isTaggedPointer());\n  if (fastpath(!ISA()->hasCustomRR())) {\n      return rootRetain();\n  }\n  \n  return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);\n}\n```\n- [ ] rootRetain()\n```\nobjc_object::rootRetain(bool tryRetain, bool handleOverflow)\n{\n....\n}\n```\n*   操作了sideTable: \\\n    是HashTable，存储的是我们当前对象的引用计数，弱引用表，锁\n\n*   执行了函数\n```\nid objc_object::sidetable_retain()\n{\n...\n}\n```\n\nSideTable的定义\n```\n*   // Template parameters.\n    enum HaveOld { DontHaveOld = false, DoHaveOld = true };\n    enum HaveNew { DontHaveNew = false, DoHaveNew = true };\n\n    struct SideTable {\n      spinlock\\_t slock;\n      // 引用计数，也是一个hash表\n      RefcontMap refcnts;\n      // 弱引用表，hash表\n      weak\\_table\\_t weak\\_table;\n      SideTable() {\n          memset(&weak_table, 0, sizeof(weak_table));\n      }\n      \n      ~SideTable() {\n          _objc_fatal(\"Do not delete SideTabel.\");\n      }\n      \n      void lock() { slock.lock(); }\n      void unlock() { slock.unlock(); }\n      void forceReset() { slock.forceReset(); }\n      \n      // Address-ordered lock discipline for a pair of side tables.\n      \n      template\u003CHaveOld, HaveNew>\n      static void lockTwo(SideTabel *lock1, SideTabel *lock2);\n      template\u003CHaveOld, HaveNew>\n      static void unlockTwo(SideTabel *lock1, SideTabel *lock2);\n}\n```\n\n*   引用计数加1\n\n    // 位移运算\n    refcntStorage += SIDE\\_TABLE\\_RC\\_ONE;\n\n这里的1，是经过位移后的1.\n\n\nweak：\n*   Runtime维护了一张全局的弱引用表\n*   调用了`id objc_initWeak(id *location, id newObj)`函数，location是地址，然后其中调用了\n    `storeWeak`方法来存储\n*   根据传进来的newobj来操作SideTabel来操作.\n*   在弱引用表中插入的是`weak_entry_t`，找到表起始地址，while循环找到我们存储的对象.\n*   有性能情况，不断的通过hash运算来查询、插入、删除等.\n*   总之，weak就是通过hash运算找到弱引用表，插入或者删除\n*   dealloc: 自动清理弱引用表.\n\n## Runloop\n1. 使用NSRunLoop currentLoop，是对CFRunLoop的封装.\n```\nstruct __CFRunLoop {\n  ...\n  // 唤醒\n  // mach_port内核通讯\n  __CFPort _wakeUpPort; // used for CFRunLoopWakeUp\n  ...\n  pthread_t _pthread;\n  ...\n  CFMutableSetRef _commonModes;\n  // timer，source， observer\n  CFMutableSetRef _commonModeItems;\n  CFRunLoopModeRef _currentMode;\n  ...\n}\n```\n\n2. 线程和RunLoop一一对应.\n3. CommonMode并不是mode类型，而是一个特殊标识\n```\nstruct __CFRunLoopMode {\n    ...\n    CFMutableSetRef _source0; // 不具备主动唤醒线程能力，手动唤醒线程。可创建并添加到当前的runloop中的\n    CFMutableSetRef _source1; // 基于port端口事件，可主动唤醒线程\n    ...\n}\n```\n\n### 示例解说\n```\nDispatch\\_async(dispatch\\_get\\_main\\_queue(), ^{\n  NSLog(@\"1\");\n  [self performSelector:@selector(test) withObject\\:nil afterDelay:0];\n  NSLog(@\"3\");\n})\n\n(void)test {\n  NSLog(\"current Thread -- %@\", \\[NSThread currentThread]);\n  NSLog(@\"2\");\n}\n\n```\n结果输出：\n```\n1 \n3 \ncurrent thread -- \u003CNSThread: ..>{number = 1, name = main}\n2\n```\n*   查看performSelector的注释，就是往当前线程中的RunLoop中注册了一个NSTimer.\n*   performSelector要求, 运行在default mode中，并且当前的runloop是执行的\n*   主线程默认runloop开启\n*   当runloop回来的时候，timer时间到了，再去执行selector，也就是performselector就是注册了一个timer，然后等待timer时间到了，回到default mode中时，再执行selector.\n\n注：如果是在dispatch_get_global_queue中时，输出: \n```\n1 \n3\n```\n原因：\nglobal队列的runloop默认不开启.\n\n","如何理解iOS的内存管理，什么是自动引用计数，sidetable又是什么，本文从源码的角度一步步和大家一起来进行探讨。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios memory,ios auto reference,ios自动引用计数",{"id":1520,"createdAt":1521,"title":1522,"content":1523,"summary":1524,"image":1525,"uid":136,"user":1526,"categoryId":85,"category":1527,"subCategoryId":1164,"subCategory":1528,"comments":1529,"status":9,"reason":15,"notice":15,"visitCount":1184,"commentCount":112,"keywords":1530},26,"2024-01-29T14:14:06.03Z","在nginx上部署vue项目时该如何配置nginx.conf文件","# 一、配置nginx环境\n## 选择一：本地安装nginx\n可以在本地安装nginx环境进行模拟，windows上可以安装虚拟机环境，Mac由于是类unix系统，可以直接安装nginx，平台上有Mac上原生安装nginx的教程，大家可以参考[在Mac上原生玩一把Nginx](https://www.xinwei.ltd/article/19)。\n\n## 选择二：直接使用云服务器\n如果购买了云服务器，当然可以在centos类似的系统中进行安装nginx环境，阿里云还有腾讯云上，都可以搜索到对应的安装方法，这里不赘述了。\n\n## 选择三：使用docker【推荐】\ndocker这样的容器技术，让环境配置省去了极其多的时间，一键命令构建环境，更是避免了原生安装过程中可能出现的一系列问题。\n具体的步骤可以参考本平台上的文章：[Mac上操作，使用yml文件制作docker compose nginx](https://www.xinwei.ltd/article/18)\n\n# 二、端口\n对于前端网页项目，往往直接通过80端口进行访问，这样子就可以不用在网页url中包含其他的非80端口号来使用。\n（如果一个云服务器上配置多个前端网页项目，都通过80端口访问，当然也是可以的，后面有时间我再出一个文章）\n## 2.1 本地环境\n本地的nginx环境下，可以自行定义使用哪个端口来访问前端项目。\n\n## 2.2 云服务器\n需要防火墙放开80端口访问，一般默认开启了，但还是检查一下\n\n## 2.3 docker环境\n参考前面给出的链接文章中的端口绑定，可以自行指定宿主机哪个端口号和容器中的80端口进行绑定。当然宿主机最好也是使用80端口。\n\n# 三、vue项目处理\n## 3.1 vue项目中的publicPath\n一般默认为“/”，表示直接从根域名直接访问，比如:https://www.baidu.com/。\n\n如果是包含路径的访问，比如：https://www.baidu.com/business，那么publicPath应该配置为:\"/business\"。\n\n如果被设置为空字符串 ('') 或 ('./')，那么所有的产物都会使用相对路径，打出来的包可以被部署在任意路径上。\n\n## 3.2 打包vue项目\nvue项目或者react项目，最终build出来的包，都是js、css、html或者有图片及其他文件的一个文件夹。一般不指定生成目录名的情况下，vue项目的产物文件夹名字为dist。\n\n## 3.3 产物在nginx中的位置\nnginx的根目录，即nginx.conf这个文件所在的目录，这里会有一些文件夹：etc、logs、conf.d、html等，当然可以自己再创建一些文件夹。\n![本地环境为例](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-01-29%20221706536986833.png)\n\n你可以放在你想放的文件夹下，在nginx.conf中指定的路径会以这个根目录为相对路径。\n\n以下图为例：\n![](https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-01-29%20221706537189380.png)\n这里以official项目为例，其中放的就是dist文件夹中的所有内容。那么我们在nginx中会配置一个路径。\n\n# 四、nginx配置\n一个具体简单的配置如下（后面会有官方的写法）：\n```nginx.conf\n... // 省略以上内容\ngzip  on;\n\n    server {\n        listen       80;\n        server_name  localhost;\n\n        #charset koi8-r;\n    charset utf-8;\n\n        #access_log  logs/host.access.log  main;\n\n        location /  {\n            root   html/official;\n            index  index.html index.htm;\n        try_files $uri $uri/ @routers; // 这里比较重要\n        }\n    location @routers { // 这里是为vue的路由跳转做配置\n        rewrite ^.*$ /index.html last;\n     }\n\n        #error_page  404              /404.html;\n        ... // 省略后面的其他\n```\n\n上面的routers写法，不一定适用于所有人的项目，如果上面大家的配置不可行，可以尝试下面的配置：\n```nginx.conf\nserver {\n    listen       80;\n    server_name  localhost;\n    location / {\n      root   /app;\n      index  index.html;\n      try_files $uri $uri/ /index.html;\n    }\n    error_page   500 502 503 504  /50x.html;\n    location = /50x.html {\n      root   /usr/share/nginx/html;\n    }\n  }\n```\n\n以上。\n\n\n\n","vue项目经过build出来的dist文件夹，如何部署到云服务器，成功让浏览器可以访问到这个vue项目。这里给出已有的配置供大家参考。","https://image.xinwei.ltd/%E6%88%AA%E5%B1%8F2024-01-29%20221706536986833.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":1164,"name":1166,"parentId":85},[],"vue,vue dist,vue build",{"id":21,"createdAt":1532,"title":1533,"content":1534,"summary":1535,"image":1536,"uid":136,"user":1537,"categoryId":85,"category":1538,"subCategoryId":242,"subCategory":1539,"comments":1540,"status":9,"reason":15,"notice":15,"visitCount":1184,"commentCount":112,"keywords":1541},"2024-01-07T07:14:45.659Z","git和SSH keys associated with your account","### 一、简介\ngit操作基本上是每个程序员必备技能。\n不知道svn情有可原，但不知道github、gitlab等git平台的程序员绝对不是一个合格的程序员。\n今天简单的列出github的ssh生成和操作步骤，供大家复制粘贴使用（如果有程序员没有安装zsh这样的终端，就不易从历史命令中找到命令，那么每次都需要手动重新敲命令）\n\n### 二、常见git操作和解释\n对于常见的git仓库操作命令，无非以下几种：\n1.git init  // 实例化git仓库\n2.git branch // 分支查看或者在当前提交状态下生成新分支\n3.git clone // 克隆一个git仓库\n4.git fetch // 从一个git仓库下载文件和引用\n5.git pull // 从仓库拉取更新\n6.git add // 将改动暂存\n7.git commit // 写提交信息\n8.git push // 推送改动到git仓库\n\n上面的基本操作，在日常使用中，基本够用。再复杂点的git操作也就是分支的操作上，比如：\n1.git diff  // 在提交之间或者工作区中，显示修改了哪些代码\n2.git checkout // 切换分支\n3.git merge // 分支合并操作\n4.git cherry-pick // 将其他提交commit，应用到当前分支\n5.git rebase // 将提交置于其他分支的最上面，所谓变基\n6.git reset // 重置提交，将当前分支回退到某一次的历史提交，慎用\n7.git revert // 回退某次提交，即取消某次commit，并生成一条回退提交记录\n8.git stash // 暂存当前的修改，可以用git stash apply将暂存列表的某一条记录恢复\n9.git tag add // 添加标签\n10.git commit amend // 修改未push的commit信息\n\n### 三、鉴权问题\n那么我们常常在终端操作的时候，当执行git操作，git仓库的鉴权就会用到ssh。没有私钥文件，那么访问就会被deny掉。如下：\n![](https://image.xinwei.ltd/image_11704611382739.png)\n\n那么通常我们需要生成一对公钥和私钥，将公钥存放在git平台，然后本地存储了私钥，用于git仓库鉴权。\n\ngithub上的ssh设置在profile->setting中：\n![](https://image.xinwei.ltd/image_21704611395421.png)\n\n\ngithub平台也提示了让你去生成公钥私钥的文档：[github ssh生成文档](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)\n\n### 四、具体操作\n那么简单的操作如下：\n1.本地生成公钥私钥（生成的文件一般在终端所在的当前文件夹，所有需要终端cd到ssh文件夹中）：\n```language\ncd ~/.ssh\n```\n\n![](https://image.xinwei.ltd/image_31704611449553.png)\n\n2. 使用Mac电脑自带的ssh命令即可：\n```language\nssh-keygen -t rsa -b 4096 -C \"xxx\" // xxx是你的邮箱\n```\n\n![](https://image.xinwei.ltd/image_41704611457879.png)\n\n这样子就会在ssh文件下生成了2个密钥文件，公钥以.pub为扩展名\n3. 将私钥添加到ssh中\n```language\nssh-add xxx // xxx为你的私钥文件名\n```\n![](https://image.xinwei.ltd/image_51704611525216.png)\n\n\n4. 查看公钥内容，并拷贝粘贴到github的 new ssh key处\n```language\ncat  // 你的公钥全名\n```\n![](https://image.xinwei.ltd/image_61704611546404.png)\n![](https://image.xinwei.ltd/image_71704611638838.png)\n\n\n\n5. 本地测试是否被github鉴权成功！成功完事。\n```language\nssh -T git@github.com\n```\n![](https://image.xinwei.ltd/image_81704611659272.png)\n\n\n6. 接下来就可以正常的git仓库操作了！\n","一、简介 git操作基本上是每个程序员必备技能。 不知道svn情有可原，但不知道github、gitlab等git平台的程序员绝对不是一个合格的程序员。 今天简单的列出github的ssh生成和操作步骤，供大家复制粘贴使用（如果有程序员没有安装zsh这样的终端，就不易从历史命令中找到命令，那么每次都需要手动重新敲命令） 二、常见git操作和解释 对于常见的git仓库操作命令，无非以下几种： 1.g","https://image.xinwei.ltd/image_11704611382739.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":242,"name":244,"parentId":85},[],"git,ssh,git技能,ssh keys,git操作",{"id":1543,"createdAt":1544,"title":1545,"content":1546,"summary":1547,"image":1548,"uid":136,"user":1549,"categoryId":85,"category":1550,"subCategoryId":144,"subCategory":1551,"comments":1552,"status":9,"reason":15,"notice":15,"visitCount":1553,"commentCount":112,"keywords":1554},124,"2024-08-24T09:17:44.08Z","npm命令失败：request to https://registry.npm.taobao.org/lerna failed, reason: certificate has expired","## 一、背景\n大家应该都遇到过npm命令执行失败的情况，或者当你升级老web项目时会遇到：request to https://registry.npm.taobao.org/lerna failed, reason: certificate has expired。\n\n## 二、问题\n> npm verb type system\nnpm verb stack FetchError: request to https://registry.npm.taobao.org/lerna failed, reason: certificate has expired\nnpm verb stack     at ClientRequest.\u003Canonymous> (/Users/hanweixing/.nvm/versions/node/v18.20.2/lib/node_modules/npm/node_modules/minipass-fetch/lib/index.js:130:14)\nnpm verb stack     at ClientRequest.emit (node:events:517:28)\nnpm verb stack     at _destroy (node:_http_client:882:13)\nnpm verb stack     at onSocketNT (node:_http_client:902:5)\nnpm verb stack     at process.processTicksAndRejections (node:internal/process/task_queues:83:21)\n\n问题截图：\n![npm verb stack FetchError](https://image.xinwei.ltd/image1724490371361.png)\n\n## 三、解决方案\n这个问题是因为taobao的npm镜像过期了，你需要去更新npm的镜像源。\n\n### 3.1 查看web工程中是否有`.npmrc`文件\n![.npmrc文件](https://image.xinwei.ltd/image1724490535035.png)\n\n如果是在lerna项目管理中，npm命令应该在lerna项目的根目录package.json相同级别目录下执行，那么`.npmrc`文件应该在根目录下，当然如果packages目录下的项目中有`.npmrc`文件，可能是单独创建工程遗留的。没有冲突问题。\n\nlerna项目中之需要修改根目录下的.npmrc文件，或者不存在这个文件的话就创建一个就好了。\n\n### 3.2 修改npm镜像\n![修改npm镜像](https://image.xinwei.ltd/image1724490799943.png)\n\n修改的链接如下：\n```.npmrc\n# registry=https://registry.npm.taobao.org\nregistry=https://registry.npmmirror.com/\n```\n\n### 3.3 清除npm cache\n```terminal\nnpm cache clean --force\n```\n\n### 3.4 验证\n```terminal\nnpm install --verbose\n```\n\n希望对大家有帮助\n\n","大家应该都遇到过npm命令执行失败的情况，或者当你升级老web项目时会遇到：request to https://registry.npm.taobao.org/lerna failed, reason: certificate has expired。","https://image.xinwei.ltd/image1724490371361.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],64,"npm registry,npm registry error,npm change registry,npm镜像",{"id":312,"createdAt":1556,"title":1557,"content":1558,"summary":1559,"image":15,"uid":136,"user":1560,"categoryId":85,"category":1561,"subCategoryId":47,"subCategory":1562,"comments":1563,"status":9,"reason":15,"notice":15,"visitCount":1553,"commentCount":112,"keywords":1564},"2024-01-29T15:18:53.727Z","Runloop底层原理分析","# Runloop\n\n*   Runloop一个运行循环，是一个对象.\n*   包含一个do while循环\n*   提供一个入口函数，程序可以进入一个do while循环\n*   消息机制处理模式\n\nC: 运行->计算->完成->退出\n\n## 作用：\n\n*   保持程序的持续进行\n*   处理app中的各种事件（触摸、定时器、performSelecor）\n*   节省cpu资源、提供程序的性能：该做事就做事，该休息就休息\n\n## 处理\n\n*   block应用: `__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__`\n\n*   调用timer用: `__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__`\n\n*   响应source0: `__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__`\n\n*   响应source1: `__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__`\n\n*   GCD主队列: `__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__`\n\n*   observer源: `__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__`\n\n*   [x] 线程和runloop通过key-value的形式一一对应.\n\n*   [x] 子线程的runloop默认不开启\n\n*   [x] 基于线程创建runloop\n\n*   [x] 通过变量 -- 线程 -- runloop -- timer\n\n*   [x] timer依赖于runloop\n\n\u003C!---->\n\n    NSThread *thread = [[NSThread alloc] initWithBlock:^{\n        NSLog(@\"%@\", [NSThread currentThread]);\n        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^{\n            NSLog(@\"子线程timer\");\n            if (self.isStopping) {\n                [NSThread exit];\n            }\n        }];\n        [[NSRunloop currentRunLoop] run];\n    }];\n    thread.name = @\"xxxxx\";\n    [thread start];\n\n1.  Timer为什么不准？ \\\\\n\n*   [x] 在default和tracking mode切换.\n\n1.  一个runloop对应多个mode，一个mode对应于多个timer，source，observers.\n2.  timer是如何加入runloop？\n    runLoopRun \\\n    runLoopDoBlocks \\\n    while循环执行item并执行timer等 \\\n    timer加入的mode和我们现在runloop的mode相等\\\n    curr->\\_mode = KCFRunLoopCommonModes相等.\n\n*   timer一定要加入到相应的runloop的mode， timer加到items\n*   runloopRun -> doBlock -> while\n*   [x] item->next\n*   [x] doit\n*   [x] block调用\n\n\u003C!---->\n\n    -(void)cfTimerDemo {\n        CFRunLoopTimerContext context = {\n            0,\n            ((__bridge void*)self),\n            NULL,\n            NULL,\n            NULL\n        };\n        CFRunLoopRef rlp = CFRunLoopGetCurrent();\n        /**\n        参数一：用于分配对象的内存\n        参数二：在什么时候触发（距离现在）\n        参数三：每个多少时间出发一次\n        参数四：未来参数\n        参数五：CFRunLoopObserver的优先级 当在RunLoop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0\n        参数六：回调，比如触发事件，我就会来到这里\n        参数七：上下文记录信息\n        */\n        CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context);\n        CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);\n    }\n    void lgRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info) {\n        NSLog(@\"%@---%@\", timer, info);\n    }\n\n1.  observer\n\n*   监听runloop的状态.\n*   其demo和timer类似.\n\n1.  source\n\n*   source0，回调函数指针\n\n*   [x] 一个signal 待处理\n\n*   [x] wake up唤醒runloop处理事件\n\n*   [x] 作用：处理app内部事件\n\n*   [x] 作用：app自己负责管理的事务, 触摸事件等 UIEvent、CFSocket\n\n*   source1 mach\\_port & 回调函数指针\n\n*   [x] runloop和内核管理\n\n*   [x] port\n\n*   [x] 线程之间通讯\n\n*   实用技巧：\n\n*   [x] 卡顿检测\n\n*   [x] FPS\n\n","这里是runloop原理相关的介绍，还有runloop的运行机制，及其中的一些细节解释。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios开发,runloop,ios runloop",{"id":111,"createdAt":1566,"title":1567,"content":1568,"summary":1569,"image":1570,"uid":597,"user":1571,"categoryId":85,"category":1572,"subCategoryId":47,"subCategory":1573,"comments":1574,"status":9,"reason":15,"notice":15,"visitCount":1575,"commentCount":112,"keywords":1576},"2026-03-03T04:38:22.115Z","iOS 高频面试题（10 道）","# iOS 高频面试题（10 道）\n## 1. OC 中 Category（分类）和 Extension（扩展）的区别？Category 为什么能给类添加属性却不能直接生成成员变量？\n![面试题png](https://image.xinwei.ltd/image1772510914934.png)\n\n(2）Category 不能直接生成成员变量的原因\n- OC 的类结构（struct objc_class）在编译期已确定内存布局，Category 是运行时动态加载的，无法修改原类的内存布局，因此不能直接添加成员变量；\n\n- Category 中用 @property 声明属性时，编译器只会生成 getter/setter 方法的声明，不会自动生成实现和成员变量；\n\n- 解决方法：通过 关联对象（Associated Object） 间接实现属性效果：\n\n```oc\n// Category 中添加属性\n@interface UIView (Extension)\n@property (nonatomic, copy) NSString *identifier;\n@end\n\n@implementation UIView (Extension)\n// 关联对象的 key\nstatic const void *kIdentifierKey = &kIdentifierKey;\n- (void)setIdentifier:(NSString *)identifier {\n    objc_setAssociatedObject(self, kIdentifierKey, identifier, OBJC_ASSOCIATION_COPY_NONATOMIC);\n}\n- (NSString *)identifier {\n    return objc_getAssociatedObject(self, kIdentifierKey);\n}\n@end\n```\n\n## 2. iOS 中的内存管理方式是什么？ARC 原理是什么？MRC 下的引用计数规则？\n（1）核心内存管理方式\niOS 基于引用计数（Reference Counting） 管理内存，分为 MRC（手动引用计数）和 ARC（自动引用计数），核心原则：谁创建谁释放，谁持有谁释放。\n\n（2）ARC 原理\nARC 是编译器特性（非运行时），编译器在编译期自动插入 retain/release/autorelease 代码，无需手动管理，但底层仍基于引用计数：\n- 编译期分析代码上下文，在合适的位置插入内存管理方法；\n- ARC 遵循 “强引用持有对象，弱引用不持有” 的规则；\n- 限制：不能手动调用 retain/release/autorelease，不能使用 NSAutoreleasePool（改用 @autoreleasepool）。\n\n（3）MRC 引用计数规则\n![mrc png](https://image.xinwei.ltd/image1772510914934.png)\n示例（MRC）：\n```oc\nNSString *str = [[NSString alloc] initWithString:@\"test\"]; // 引用计数=1\n[str retain]; // 引用计数=2\n[str release]; // 引用计数=1\n[str release]; // 引用计数=0，对象销毁\n```\n\n## 3. UIKit 中 RunLoop 的作用是什么？RunLoop 与线程的关系？常用的 RunLoop Mode 有哪些？\n（1）RunLoop 核心作用\nRunLoop 是 “事件循环器”，核心功能是让线程在有任务时处理任务，无任务时休眠（避免占用 CPU），主要职责：\n- 处理事件（触摸、定时器、网络请求）；\n- 管理线程生命周期（如主线程 RunLoop 一直运行，子线程默认无 RunLoop）；\n- 处理 UI 刷新（CATransaction 提交后触发 RunLoop 刷新 UI）；\n- 延迟执行（performSelector:withObject:afterDelay: 依赖 RunLoop）。\n\n（2）RunLoop 与线程的关系\n- 一一对应：一个线程对应一个 RunLoop，RunLoop 存储在全局字典中，线程为 key，RunLoop 为 value；\n- 懒加载：RunLoop 不会随线程创建而创建，调用 [NSRunLoop currentRunLoop] 时才创建；\n- 主线程 RunLoop 自动创建且一直运行，子线程 RunLoop 需要手动启动，且运行后需手动停止。\n\n（3）常用 RunLoop Mode\nRunLoop Mode 是 “事件过滤器”，同一时间 RunLoop 只能处于一个 Mode，仅处理该 Mode 下的事件：\n- kCFRunLoopDefaultMode：默认模式，处理 UI 事件、定时器、常规任务（主线程默认处于该 Mode）；\n- UITrackingRunLoopMode：跟踪模式，处理滑动 / 滚动事件（如 UIScrollView 滑动时，RunLoop 切换到该 Mode，默认 Mode 事件暂停）；\n- kCFRunLoopCommonModes：伪模式，是 Default + Tracking 的集合（设置该 Mode 后，两种场景都能处理事件）。\n\n实战场景：UIScrollView 滑动时定时器暂停，可将定时器加入 CommonModes 解决：\n```oc\nNSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {\n    NSLog(@\"定时器执行\");\n}];\n[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];\n```\n\n## 4. Swift 中值类型（Struct/Enum）和引用类型（Class）的区别？各自的适用场景？\n（1）核心区别\n![](https://image.xinwei.ltd/struct_enum.png)\n（2）适用场景\n- 值类型：\n适合存储独立、不可共享的数据（如模型数据、基础类型、工具类）：\n基础数据结构：Int/String/Array/Dictionary（Swift 中均为 Struct）；\n业务模型：如 UserModel（存储用户 ID、姓名等，避免多地方修改导致数据不一致）；\n轻量工具：如 Size（存储宽高）、Point（存储坐标）。\n- 引用类型：\n适合需要共享状态、继承、生命周期管理的场景：\n- [x] UI 组件：UIViewController/UIView（需继承、共享实例）；\n- [x] 业务管理器：如 NetworkManager（单例，全局共享网络请求逻辑）；\n- [x] 需析构的对象：如 FileManager（需在 deinit 中关闭文件句柄）。\n\n\n## 5. iOS 中 tableView/collectionView 的性能优化方式有哪些？\n### 5.1 复用机制优化：\n- 必须使用 dequeueReusableCell(withIdentifier:) 复用单元格，避免重复创建；\n- 提前注册单元格（register(_:forCellWithReuseIdentifier:)），避免运行时查找 nib 文件；\n- 自定义单元格时，将子视图封装为属性，避免重复调用 viewWithTag:（耗时）。\n\n### 5.2 布局计算优化：\n- 提前计算并缓存单元格高度（如在模型中存储 cellHeight，避免 heightForRowAt 中重复计算）；\n- iOS 8+ 启用 estimatedRowHeight + automaticDimension 自动计算高度时，需固定约束，减少布局遍历；\n- 避免在 cellForRowAt/willDisplayCell 中执行复杂计算（如数据转换、正则匹配）。\n\n### 5.3 渲染优化：\n- 图片优化：使用异步加载（SDWebImage）、缓存图片、压缩图片尺寸（匹配单元格大小）；\n- 减少离屏渲染：避免设置 cornerRadius+masksToBounds（改用贝塞尔曲线绘制）、避免阴影（shadowPath 优化）；\n\n### 5.4 数据加载优化：\n- 分页加载：滚动到底部时加载下一页数据，避免一次性加载大量数据；\n- 预加载：提前加载下一页数据（如滚动到倒数 10 行时）；\n- 异步处理：数据解析、模型转换放在子线程，主线程仅负责渲染。\n\n### 5.5 其他优化：\n- 关闭不必要的动画：如 cell.selectionStyle = .none（无选中样式）；\n- 使用 UITableViewCell 的 prepareForReuse 方法重置单元格状态（避免数据残留）；\n- 大列表使用 UICollectionView 替代 UITableView（布局更灵活，性能更优）；\n- 启用 shouldRasterize（光栅化）：对复杂单元格，将其渲染为位图缓存，减少重复绘制（注意：仅适合静态单元格）。\n\n## 6. iOS 中的代理（Delegate）和通知（Notification）的区别？各自的适用场景？\n（1）核心区别\n![delegate png](https://image.xinwei.ltd/delegate.png)\n（2）适用场景\n- Delegate：\n  适合需要双向通信、实时响应、类型安全的场景：\n- [x] 控件交互：UITableViewDelegate（点击单元格、返回高度）、UIScrollViewDelegate（滚动监听）；\n- [x] 页面传值：子控制器通过代理向父控制器传递数据（如修改后的数据）；\n- [x] 回调结果：网络请求类通过代理返回请求结果（同步处理）。\n- Notification：\n适合跨层级、解耦、一对多的通信场景：\n- [x] 全局状态变更：如用户登录 / 退出、网络状态变化（通知所有相关页面刷新）；\n- [x] 跨组件通信：如首页、个人中心同时监听购物车数量变化；\n- [x] 系统事件监听：如键盘弹出 / 收起（UIKeyboardWillShowNotification）。\n\n注意：通知必须在 deinit 中注销，避免崩溃：\n```swift\n// Swift 注销通知\ndeinit {\n    NotificationCenter.default.removeObserver(self)\n}\n```\n\n## 7. iOS 中 Block 的原理是什么？Block 的循环引用如何解决？\n（1）Block 原理\nBlock 是 OC 中的 “闭包”，本质是封装了函数调用和函数环境的 OC 对象（底层是 struct __block_impl 结构体）：\n- Block 存储在栈 / 堆 / 全局区：\n全局 Block：捕获不到外部变量，存储在全局区（如 ^{NSLog(@\"test\");}）；\n栈 Block：捕获外部变量，存储在栈区（生命周期由系统管理，易销毁）；\n堆 Block：栈 Block 调用 copy 后，转移到堆区（手动管理生命周期）。\n- Block 捕获外部变量：\n值捕获：基本数据类型（int/NSString）捕获值，Block 内修改不影响外部；\n引用捕获：对象类型捕获指针，需注意循环引用；\n__block 修饰：让变量变为可修改（栈变量转为堆变量）。\n\n（2）Block 循环引用原因及解决\n- 原因：Block 会强引用捕获的对象，若对象同时强引用 Block（如控制器持有 Block，Block 捕获 self），则形成循环引用，导致对象无法释放。\n- 解决方法：\n1. 弱引用 + 强引用（常用）：\n```oc\n// OC\n__weak typeof(self) weakSelf = self;\nself.block = ^{\n    __strong typeof(weakSelf) strongSelf = weakSelf; // 避免self提前释放\n    if (strongSelf) {\n        [strongSelf doSomething];\n    }\n};\n```\n```swift\n// Swift\nweak var weakSelf = self\nself.block = { [weak self] in\n    guard let strongSelf = self else { return }\n    strongSelf.doSomething()\n}\n```\n2. 使用 __unsafe_unretained：类似 weak，但不会自动置空（慎用，可能野指针）；\n3. 打破循环：使用完成后将 Block 置为 nil（如网络请求完成后，self.block = nil）；\n4. 捕获局部变量：若 Block 仅在方法内使用，捕获局部变量（非 self 属性），避免循环。\n\n## 8. iOS 中的多线程方案有哪些？GCD 的核心概念和常用方法？\n（1）iOS 多线程方案对比\n![thread png](https://image.xinwei.ltd/thread.png)\n\n（2）GCD 核心概念\n- 队列（Queue）：存储任务的容器，分为：\n串行队列（Serial）：任务按顺序执行，只有一个线程；\n并发队列（Concurrent）：任务同时执行，多个线程（由系统管理）；\n主队列（Main Queue）：串行队列，运行在主线程，处理 UI 操作。\n\n- 任务（Task）：分为同步（sync）和异步（async）：\n同步任务：阻塞当前线程，任务执行完才返回；\n异步任务：不阻塞当前线程，任务后台执行。\n\n（3）GCD 常用方法\n1.异步并发（常用，非 UI 任务）：\n```swift\n// Swift\nlet concurrentQueue = DispatchQueue(label: \"com.example.concurrent\", attributes: .concurrent)\nconcurrentQueue.async {\n    // 子线程执行耗时任务（如网络请求、数据解析）\n    print(\"任务1：\\(Thread.current)\")\n}\nconcurrentQueue.async {\n    print(\"任务2：\\(Thread.current)\")\n}\n```\n\n2. 异步串行（顺序执行任务）：\n```swift\nlet serialQueue = DispatchQueue(label: \"com.example.serial\")\nserialQueue.async { print(\"任务1\") }\nserialQueue.async { print(\"任务2\") } // 任务1执行完才执行\n```\n\n3. 主线程更新 UI：\n```swift\nDispatchQueue.main.async {\n    self.label.text = \"更新UI\" // 必须在主线程执行\n}\n```\n\n4. 延时执行：\n```swift\nDispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n    print(\"2秒后执行\")\n}\n```\n\n5. 栅栏函数（控制并发队列的任务顺序）：\n```swift\nconcurrentQueue.async { print(\"任务1\") }\nconcurrentQueue.async { print(\"任务2\") }\n// 栅栏：等待前面任务执行完，执行自己，再执行后面任务\nconcurrentQueue.async(flags: .barrier) { print(\"栅栏任务\") }\nconcurrentQueue.async { print(\"任务3\") }\n```\n\n## 9. iOS 中 App 的启动流程是什么？如何优化 App 启动速度？\n（1）App 启动流程（冷启动）\n冷启动是 App 从完全关闭到打开的过程，分为 3 个阶段：\n- dyld 阶段（动态链接器）：\n加载可执行文件（Mach-O）；\n加载动态库（系统库如 UIKit、自定义库）；\n重定位符号，绑定依赖。\n- runtime 阶段：\n初始化 OC 运行时（objc_init）；\n加载类、分类、方法（+load 方法执行）；\n初始化全局变量、静态变量。\n- 主线程初始化阶段：\n执行 UIApplicationMain；\n加载 AppDelegate，执行 application:didFinishLaunchingWithOptions:；\n加载根控制器，渲染首屏 UI。\n\n（2）启动速度优化\n核心思路：“减少加载项、延迟执行、异步处理”：\n- dyld 阶段优化：\n减少动态库数量（合并静态库，移除无用动态库）；\n启用 bitcode（编译器优化二进制）；\n减少符号表（strip 无用符号）。\n\n- runtime 阶段优化：\n避免在 +load 方法中执行耗时操作（改用 +initialize 或延迟执行）；\n减少类 / 分类数量（合并无用分类）；\n优化全局变量 / 静态变量（避免初始化耗时）。\n\n\n- 主线程初始化阶段优化：\n延迟加载非首屏资源（如广告、统计、第三方 SDK）；\n异步初始化第三方 SDK（如埋点、推送，放在子线程）；\n首屏 UI 简化：使用骨架屏替代复杂视图，首屏只加载核心控件；\n懒加载：控制器的子视图、数据请求等延迟到 viewDidAppear 执行。\n\n\n- 其他优化：\n启用启动优化（Xcode 的 Build Settings 中开启 Optimization Level）；\n压缩资源（图片、字体，减少加载时间）；\n使用 Instruments 的 Time Profiler 分析启动耗时，定位瓶颈。\n\n\n## 10. iOS 中常见的崩溃类型有哪些？如何捕获和排查崩溃？\n（1）常见崩溃类型\n- 野指针崩溃（EXC_BAD_ACCESS）：\n访问已释放的对象（如通知未注销、Block 循环引用导致对象提前释放）；\n访问空指针（如 nil 对象调用不存在的方法）。\n\n- 数组越界（NSRangeException）：\n访问数组索引超出范围（如 array[10]，但数组只有 5 个元素）。\n\n\n- 字典键值异常：\n字典设置 nil 键 / 值（OC 中字典键值不能为 nil，Swift 可）；\n访问不存在的键（虽不崩溃，但返回 nil，可能导致后续逻辑异常）。\n\n\n- 死锁（Deadlock）：\n主线程同步等待子线程，子线程又等待主线程（如 GCD 同步任务入主队列）。\n\n\n- 内存溢出（OOM）：\n加载大量图片 / 数据，内存占用超过系统限制，被系统杀死。\n\n\n- 类型转换失败（NSInvalidArgumentException）：\n如将 NSString 强制转为 NSNumber。\n\n\n（2）崩溃捕获与排查\n- 开发阶段排查：\nXcode 控制台：直接查看崩溃日志（含崩溃类型、调用栈）；\nInstruments：Zombies 工具检测野指针，Leaks 工具检测内存泄漏；\n开启 Zombie Objects（Xcode -> Edit Scheme -> Diagnostics -> Enable Zombie Objects），定位野指针。\n\n- 线上崩溃捕获：\n第三方工具：集成 Crashlytics（Fabric）、Bugly、友盟统计，自动捕获崩溃日志；\n自定义崩溃捕获：通过 NSSetUncaughtExceptionHandler 捕获 OC 异常，signal 捕获信号量崩溃：\n```oc\n// 捕获 OC 异常\nNSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);\nvoid UncaughtExceptionHandler(NSException *exception) {\n    // 记录崩溃信息：调用栈、异常原因\n    NSArray *callStack = [exception callStackSymbols];\n    NSString *reason = [exception reason];\n    // 上传到服务器\n}\n```\n\n- 崩溃排查步骤：\n\n查看崩溃日志的 Exception Type（崩溃类型）和 Exception Message（原因）；\n\n分析调用栈（Thread 0 Crashed 是崩溃线程），定位崩溃代码行；\n\n复现崩溃场景（如特定操作、特定设备），逐步排查；\n\n针对野指针：检查内存管理（ARC 引用、代理是否为 weak）；\n\n针对逻辑崩溃：添加断言（NSAssert）、边界检查（数组 / 字典访问前判断范围）。\n\n以上。\n\n\n\n\n","这里总结一下iOS的高频面试题，希望对正在面试的iOS开发者有所帮助，不一定全对，可自行判读哈。","https://image.xinwei.ltd/image1772510914934.png",{"phone":599,"userId":597,"nickName":600,"vipType":9,"avatar":601,"sign":15,"createdAt":602},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],57,"ios面试题, iOS swift面试题,iOS高频面试题,面试题,ios面试题",{"id":1578,"createdAt":1579,"title":1580,"content":1581,"summary":1582,"image":15,"uid":136,"user":1583,"categoryId":85,"category":1584,"subCategoryId":47,"subCategory":1585,"comments":1586,"status":9,"reason":15,"notice":15,"visitCount":1578,"commentCount":112,"keywords":1587},56,"2024-01-31T01:19:42.753Z","UIWebView和WKWebView和原生的交互操作","这里以UIWebview为主的演示，当然当今在iOS中依然废弃了UIWebview，普遍使用的是WKWebview。因为是我很早之前的笔记，所以仅供记录。\n\n# OC和JS的交互方式：\n## 1. oc调用js：\n(1）通过拦截URL的方式，js在web端src或者href时，通过截取sheme/ URL/ Path/ NavigationType等来决定是否执行某一个事件\n\nUIWebView在`shouldStart`的代理方法中拦截；\n\nWKWebView在`decidePolicyForNavigation`中进行拦截；\n\n（2）\nUIWebView:\n```\nNSString *jsStr = [NSString stringWithFormat:@\"showAlert('%@')\",@\"这里是JS中alert弹出的message\"];\n[_webView stringByEvaluatingJavaScriptFromString:jsStr];\n```\n注意：该方法会同步返回一个字符串，因此是一个同步方法，可能会阻塞UI。\n\nWKWebView:\nWKWebView的`evaluateJavaScript:completionHandler:`\n\n(3）\n使用JavaScriptCore库来做JS交互。\n```\nJSContext *context = [self.webView valueForKeyPath:@\"documentView.webView.mainFrame.javaScriptContext\"];\nNSString *textJS = @\"showAlert('这里是JS中alert弹出的message')\";\n[context evaluateScript:textJS];\n```\n\n## 2. JS调用OC\n(1)首先导入JavaScriptCore库, 然后在OC中获取JS的上下文\n```\nJSContext *context = [self.webView valueForKeyPath:@\"documentView.webView.mainFrame.javaScriptContext\"];\n```\n\n再然后定义好JS需要调用的方法，例如JS要调用share方法，则可以在UIWebView加载url完成后，在其代理方法中添加要调用的share方法：\n```\n- (void)webViewDidFinishLoad:(UIWebView *)webView\n{\n    JSContext *context = [self.webView valueForKeyPath:@\"documentView.webView.mainFrame.javaScriptContext\"];\n    //定义好JS要调用的方法, share就是调用的share方法名\n    context[@\"share\"] = ^() {\n        NSLog(@\"+++++++Begin Log+++++++\");\n        NSArray *args = [JSContext currentArguments];\n\n        dispatch_async(dispatch_get_main_queue(), ^{\n            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@\"方式二\" message:@\"这是OC原生的弹出窗\" delegate:self cancelButtonTitle:@\"收到\" otherButtonTitles:nil];\n            [alertView show];\n        });\n\n        for (JSValue *jsVal in args) {\n            NSLog(@\"%@\", jsVal.toString);\n        }\n\n        NSLog(@\"-------End Log-------\");\n    };\n}\n\n```\n\nhtml中的演示（部分代码）：\n```html\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n        \u003Cscript type=\"text/javascript\">\n\n            function secondClick() {\n                share('分享的标题','分享的内容','图片地址');\n            }\n        \u003C/script>\n    \u003C/head>\n\u003C/html>\n\n```\n","UIWebView和WKWebView和iOS的objective-c原生代码之间的交互示例，一个简单的演示。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,uiwebview,wkwebview,objective-c webview,ios开发",{"id":1378,"createdAt":1589,"title":1590,"content":1591,"summary":1592,"image":15,"uid":136,"user":1593,"categoryId":85,"category":1594,"subCategoryId":47,"subCategory":1595,"comments":1596,"status":9,"reason":15,"notice":15,"visitCount":1597,"commentCount":112,"keywords":1598},"2024-02-02T11:18:28.84Z","iOS中录制音频的简单示例","直接看代码吧\n```\n\n// 音频文件目录\nNSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@\"Documents\"];\n\nNSString *folder = [path stringByAppendingString:@\"/Audio\"];\n    \nNSFileManager *fileManager = [NSFileManager defaultManager];\n    \nif(![fileManager fileExistsAtPath:folder]){ // 如果不存在，那么建立这个文件夹\n        \n        [fileManager createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:nil];\n }\n// 时间戳\nNSDate *date = [NSDate date];\nNSInteger timeInterval = [date timeIntervalSince1970]*1000;    \nNSString *timeStamp = [NSString stringWithFormat:@\"%ld\", timeInterval];\n\n// 音频文件完整路径\nNSString *fileFullName = [NSString stringWithFormat:@\"%@.aac\", timeStamp];\nNSString *storePath = [path stringByAppendingPathComponent:fileFullName];\n\nNSError *error = nil;\n// 初始化录音机\nNSDictionary *setting = [NSDictionary\n                                                  dictionaryWithObjectsAndKeys:\n                                                  [NSNumber numberWithInt:kAudioFormatMPEG4AAC],AVFormatIDKey,\n                                                  [NSNumber numberWithInt:44100],AVSampleRateKey,\n                                                  [NSNumber numberWithInt:1],AVNumberOfChannelsKey,\n                                                  [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,\n                                                  [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,\n                                                  [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,\n                                                  nil];\n\n AVAudioRecorder *recorder = [[AVAudioRecorder alloc]\n                 initWithURL:[NSURL URLWithString:path]\n                 settings:setting\n                 error:&error];\n   \n  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error: nil];\n    [[AVAudioSession sharedInstance] setActive: YES error: nil];\n    \n    AVAudioSession *session = [AVAudioSession sharedInstance];\n    NSError *setCategoryError = nil;\n    \n    if (![session setCategory:AVAudioSessionCategoryPlayAndRecord\n                  withOptions:AVAudioSessionCategoryOptionMixWithOthers\n                        error:&setCategoryError]) {\n        // handle error\n        NSLog(@\"Failed to setup audio session %@\", setCategoryError.description);\n        \n        if(callBack) {\n            \n            callBack(NO, 4, @\"录音链接建立失败\");\n        }\n    } else {\n        \n        if(callBack) {\n            \n            callBack(YES, 1, @\"录音链接建立成功\");\n        }\n        \n        [recorder prepareToRecord];\n        [recorder recordForDuration:audioMaxDuration];\n    }\n\n\n\n```","在iOS中如何手写一个录制音频的代码，一个简单的示例。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],55,"ios,ios record,ios audio,ios音频,ios录制音频,ios开发",{"id":1575,"createdAt":1600,"title":1601,"content":1602,"summary":1603,"image":15,"uid":136,"user":1604,"categoryId":85,"category":1605,"subCategoryId":47,"subCategory":1606,"comments":1607,"status":9,"reason":15,"notice":15,"visitCount":1608,"commentCount":112,"keywords":1609},"2024-01-31T02:44:48.503Z","iOS的hook介绍，fishhook的调试理解","# HOOK\n## Method Swizzle\n利用OC的runtime特性，动态改变`SEL`（方法编号）和`IMP`（方法实现）的对应关系，达到OC方法调用流程改变的目的。主要用于OC方法。\n\n## fishhook\n它是facebook提供的一个动态修改链接`mach-O`文件的工具。利用`MachO`文件加载原理，通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。\n### hook C函数\n> 引入`facebook`的`fishhook.h`和`fishhook.c`\n```\n@implementation ViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    // HOOK 交换\n    /**\n     struct rebinding {\n     const char *name; // 需要HOOK的函数名称，C字符串\n     void *replacement; // 新函数的地址\n     void **replaced; // 用来保存原始函数地址的指针\n     };\n     */\n    struct rebinding nslog;\n    nslog.name = \"NSLog\";\n    nslog.replaced = (void *) &sys_nslog; // 保存系统函数的地址的指针的指针\n    nslog.replacement = myNSlog; // 新函数的地址\n    \n    struct rebinding rebs[1] = {nslog};\n    /**\n     用于重新绑定符号\n     一次性交换多个函数，\n     arg1:存放rebinding结构体的数组\n     arg2:数组的长度\n     */\n    rebind_symbols(rebs, 1);\n}\n\n// ----更改系统的NSLog函数调用\n// 函数指针，用来保存原始的函数地址!\nstatic void(*sys_nslog)(NSString *str, ...);\n\n\n// 定义一个新的函数\nvoid myNSlog(NSString *str, ...) {\n    str = [str stringByAppendingString:@\"\\n钩上了！\"];\n    // 由于系统的内部实现不知道\n    sys_nslog(str);\n}\n\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    NSLog(@\"点击了屏幕!\");\n}\n```\n\nhook 自定义的C函数 （勾不住）\n```\n@implementation ViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    [self funcDemo];\n}\n\n// 交换自定义函数\n- (void)funcDemo {\n    rebind_symbols((struct rebinding[1]){{\"func\", newFunc, (void *)&funcP}}, 1);\n}\n// 保存原始函数的指针\nstatic void(*funcP)(const char *);\n\n// 新函数\nvoid newFunc(const char * str) {\n    NSLog(@\"勾住了\");\n    funcP(str);\n}\n// 自定义的函数\nvoid func(const char * str) {\n    NSLog(@\"%s\", str);\n}\n\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    func(\"点击了屏幕\");\n}\n```\n\n### 底层汇编解释\n- C函数是静态的\n- PIC 位置独立代码\n- DYLD 将可执行文件加载到内存中，将NSLog的地址绑定到符号symbol，一开始符号symbol都是0x0，当APP被DYLD加载到内存中运行的时候，DYLD会把symbol中的指针指向内存中的UIKit, UIFoundation中的函数地址。\n\n在NSLog函数的hook代码中，在rebind操作时打一个断点，然后在xCode终端看底层汇编的内容:\n\n1.  命令`image list`可以查看fishhook.app的exec可执行文件在内存中的地址：\n```\nimage list\n[  0] DC3F7324-1654-3F1B-8A0A-2E8E9B228968 0x0000000108bf5000 /Users/hanweixing/Library/Developer/Xcode/DerivedData/FishHook-egipaudxwxzvacdyvhhoisbqvjio/Build/Products/Debug-iphonesimulator/FishHook.app/FishHook \n[  1] CE635DB2-D47E-3C05-A0A3-6BD982E7E750 0x000000010bbd1000 /usr/lib/dyld \n[  2] 528E1F55-F655-3533-99B9-7EAE1DAE5D07 0x0000000108c01000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim \n...\n```\n得到可执行文件的内存地址：`0x0000000108bf5000`\n\n2\\. 通过MachOView软件，打开fishhook.app的可执行文件exec，在`__la_symbol_ptr`的 `Lazy Symbol Pointers`中可以看到`NSLog`的内存偏移：`00004028`\n- 在Xcode中读取`NSLog`对应的`symbol`地址：\n```\n(lldb) x 0x0000000108bf5000+0x4028\n0x108bf9028: a0 79 bf 08 01 00 00 00 89 b4 f7 08 01 00 00 00  .y..............\n0x108bf9038: 36 0f d8 0c 01 00 00 00 e6 79 bf 08 01 00 00 00  6........y......\n```\n- 取前面的8个字节，然后倒序拼接，就可以将`symbol`中的值转换成汇编代码：（可以看到这是系统Foundation中的NSLog）\n```\n(lldb) dis -s 0x0108bf79a0\nFoundation`NSLog`:\n    0x108bf79a0: pushq  $0x0\n    0x108bf79a5: jmp    0x108bf7990\n    0x108bf79aa: pushq  $0xd\n    0x108bf79af: jmp    0x108bf7990\n    0x108bf79b4: pushq  $0x118                    ; imm = 0x118 \n    0x108bf79b9: jmp    0x108bf7990\n```\n- 将断点跳一步，继续在Xcode中进行调试，读取最新的`symbol`中的值，可以发现被替换成了我们替换的函数实现。\n```\n(lldb) x 0x0000000108bf5000+0x4028\n0x108bf9028: e0 6d bf 08 01 00 00 00 89 b4 f7 08 01 00 00 00  .m..............\n0x108bf9038: 36 0f d8 0c 01 00 00 00 31 71 91 0b 01 00 00 00  6.......1q......\n(lldb) dis -s 0x0108bf6de0\nFishHook`myNSlog:\n    0x108bf6de0 \u003C+0>:  pushq  %rbp\n    0x108bf6de1 \u003C+1>:  movq   %rsp, %rbp\n    0x108bf6de4 \u003C+4>:  subq   $0x30, %rsp\n    0x108bf6de8 \u003C+8>:  movq   $0x0, -0x8(%rbp)\n    0x108bf6df0 \u003C+16>: leaq   -0x8(%rbp), %rax\n    0x108bf6df4 \u003C+20>: movq   %rdi, -0x10(%rbp)\n    0x108bf6df8 \u003C+24>: movq   %rax, %rdi\n    0x108bf6dfb \u003C+27>: movq   -0x10(%rbp), %rsi\n(lldb)\n```\n\n## Cydia Substrate （主要用在逆向）\nCydia Substrate原名为Mobile Substrate,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。当然它不仅仅是针对iOS而设计的，安卓一样可以用。\n官方地址：\u003Chttp://www.cydiasubstrate.com/>\n\n","iOS中的hook操作有3种主流的方式，主要介绍fishhook的调试步骤，用于理解hook的原理。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],53,"ios,ios hook,ios fishhook,ios开发",{"id":560,"createdAt":1611,"title":1612,"content":1613,"summary":1614,"image":15,"uid":136,"user":1615,"categoryId":85,"category":1616,"subCategoryId":47,"subCategory":1617,"comments":1618,"status":9,"reason":15,"notice":15,"visitCount":1608,"commentCount":112,"keywords":1619},"2024-01-30T03:58:16.359Z","runtime-对象和方法","1.  OC对象的本质 -> 结构体\n\noc文件->.cpp文件:\n\n    clang -rewrite-objc main.m -o main.cpp\n\n*   完整命令：\n\n\u003C!---->\n\n    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.c++\n\n1.  方法的本质 -> 消息发送\n    objc:\n\n\u003C!---->\n\n    - (void)run { }\n\n对应底层C:\n\n    void run(id self, SEL _cmd) { }\n\n1.  方法： 对象方法和类方法.\n\n2.  runtime的调用方式：三种：\n\n*   runtime api: class\\_get...\n*   NSObject api: isKindOf isMemberOf\n*   oc上层方法: @selector\n\n1.  对象方法的调用：\n\n\u003C!---->\n\n    Object *obj = [Object new];\n    [obj run];\n\n*   [x] 方法调用的底层编译\n*   [x] 方法的本质： 消息： 消息接受者 消息编号 ...参数（消息体）\n\n\u003C!---->\n\n    objc_msgSend(obj, sel_registerName(\"run\"));\n\n1.  类方法编译底层\n\n\u003C!---->\n\n    id cls = [ObjectClass class];\n    void *pointA = &cls;\n    [(__bridge id)pointA walk];\n\n\\===\n\n    objc_msgSend(objc_getClass(\"ObjectClass\"), sel_registerName(\"walk\"));\n\n*   向父类发消息（对象方法）:\n\n\u003C!---->\n\n    struct objc_super mySuper;\n    mySuper.receiver = obj;\n    mySuper.super_class = class_getSuperclass([obj class);\n    objc_msgSendSuper(&mySuper, @selector(run));\n\n*   向父类发消息（类方法）：\n\n\u003C!---->\n\n    struct objc_super myClassSuper;\n    myClassSuper.receiver = [obj class];\n    myClassSuper.super_class = class_getSuperClass(object_getClass([obj class])); // 元类\n    objc_msgSendSuper(&myClassSuper, sel_registerName(\"walk\"));\n\n1.  对象方法存在类中\n\n2.  类方法存在元类中\n\n3.  类方法在元类中，以什么样的姿态存在？\\\n    ：以实例方法存在.\n\n4.  对象在类中是以实例存在.\n\n5.  类在元类中也是以实例形式存在.\n\n6.  objc\\_msgSend\n    两种方式：\n\n*   快速 by汇编 缓存找 cache\\_t imp 哈希表\n*   慢速 c, c++一起完成，找到存缓存\n\n否则 经过复杂的过程 \\\n为什么使用汇编写？\n\n*   c 写一个函数，不可能保留未知的参数，跳转到任意的指针.\n*   块，c还要向下编译\n*   汇编可以保存，by 寄存器 x0 x31\n\n1.  源码分析流程\n\n*   汇编部分\n*   c,c++部分 \\\n    具体查看 opensource.apple.com -> objc4 \\\n    分析源码的文件是objc4中arm64相关的.s汇编文件，然后汇编中有`__class_lookupMethodAndLoadCache3`一个宏调用，要在工程中使用`_class_lookupMethodAndLoadCache3`来查找，因为在.mm文件里面.\n\n大概的过程：\n\n*   消息发送调用后，通过汇编，从汇编的缓存中查找\n*   如果找到，那么就直接调用，否则查找方法实现的函数指针，找到实现函数指针，就返回函数指针\n*   如果在缓存中找不到，也找不到函数指针，那么通过c、c++函数去找，先是自己的类中找，然后递归从父类中查找。\n*   如果找到，那么就fill cache并且返回这个函数指针。找不到的话，那么就要开始走动态解析了。\n\n\\-- Warning:\n在c、c++的查找过程中，会再次判断一下缓存中是否有函数实现，尽管汇编过程中已经判断过缓存。这个原因是：oc语言的动态性，并发，有可能使缓存中有值了。\n\n1.  动态方法解析.\n    类的方法解析：（动态方法解析的函数名一样，只是前缀是-，而不是+）\n\n\u003C!---->\n\n    + (BOOL)resolveInstanceMethod:(SEL)sel {\n        return [super resolveinstanceMethod: sel];\n    }\n\n\u003C!---->\n\n    + (BOOL)resolveClassMethod:(SEL)sel {\n        return [super resolveClassMethod: sel];\n    }\n\n","runtime中关于对象和方法的本质，以及如何去验证和理解其中的原理。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"objective-c,ios runtime,swit runtime,oc runtime",{"id":1141,"createdAt":1621,"title":1622,"content":1623,"summary":1624,"image":1625,"uid":136,"user":1626,"categoryId":85,"category":1627,"subCategoryId":100,"subCategory":1628,"comments":1629,"status":9,"reason":15,"notice":15,"visitCount":1630,"commentCount":112,"keywords":1631},"2025-05-30T04:40:15.124Z","flutter: 改造in_app_purchase以支持在purchase failed时获取Apple pay的实际StoreKit error code.","## 背景\napp最近一直有一些用户反馈支付失败的问题，根据早期的埋点数据，无法正确定位到用户购买失败的真正原因，所以需要进一步分析用户在支付失败情况下的真实原因。\n\n当前的app是使用flutter开发的，并且使用了flutter的`in_app_purchase`这个库来实现的，但是在监听StoreKit的购买状态时候，error这个状态太模糊了，flutter在platform层hook支付结果后暴露出的error，和iOS StoreKit的error，形成了一对多的情况。当flutter监听到error状态时，可能指的是StoreKit众多失败原因中某一个。\n\n`in_app_purchase`的flutter package地址：[https://pub.dev/packages/in_app_purchase](https://pub.dev/packages/in_app_purchase)\n\n## 目的\n将iOS的`StoreKit` error错误code值，原原本本的反馈到flutter的error信息中。\n\n我们先来看看`SKError`的error code枚举值：[https://developer.apple.com/documentation/storekit/skerror](https://developer.apple.com/documentation/storekit/skerror)\n\n![SKError-1.png](https://image.xinwei.ltd/image1748576472033.png)\n\n```SKError enum\nError codes\n\nenum Code\n\nError codes for StoreKit errors.\n\nstatic var unknown: SKError.Code\nError code indicating that an unknown or unexpected error occurred.\n\nstatic var clientInvalid: SKError.Code\nError code indicating that the client is not allowed to perform the attempted action.\n\nstatic var paymentCancelled: SKError.Code\nError code indicating that the user canceled a payment request.\n\nstatic var paymentInvalid: SKError.Code\nError code indicating that one of the payment parameters was not recognized by the App Store.\n\nstatic var paymentNotAllowed: SKError.Code\nError code indicating that the user is not allowed to authorize payments.\n\nstatic var storeProductNotAvailable: SKError.Code\nError code indicating that the requested product is not available in the store.\n\nstatic var cloudServicePermissionDenied: SKError.Code\nError code indicating that the user has not allowed access to Cloud service information.\n\nstatic var cloudServiceNetworkConnectionFailed: SKError.Code\nError code indicating that the device could not connect to the network.\n\nstatic var cloudServiceRevoked: SKError.Code\nError code indicating that the user has revoked permission to use this cloud service.\n\nstatic var privacyAcknowledgementRequired: SKError.Code\nError code indicating that the user has not yet acknowledged Apple’s privacy policy for Apple Music.\n\nstatic var unauthorizedRequestData: SKError.Code\nError code indicating that the app is attempting to use a property for which it does not have the required entitlement.\n\nstatic var invalidOfferIdentifier: SKError.Code\nError code indicating that the offer identifier cannot be found or is not active.\n\nstatic var invalidOfferPrice: SKError.Code\nError code indicating that the price you specified in App Store Connect is no longer valid.\n\nstatic var invalidSignature: SKError.Code\nError code indicating that the signature in a payment discount is not valid.\n\nstatic var missingOfferParams: SKError.Code\nError code indicating that parameters are missing in a payment discount.\n\nstatic var ineligibleForOffer: SKError.Code\nAn error code that indicates the user is ineligible for the subscription offer.\n\nstatic var overlayCancelled: SKError.Code\nAn error code that indicates the cancellation of an overlay.\n\nstatic var overlayInvalidConfiguration: SKError.Code\nAn error code that indicates the overlay’s configuration is invalid.\n\nstatic var overlayPresentedInBackgroundScene: SKError.Code\n\nstatic var overlayTimeout: SKError.Code\nAn error code that indicates the timing out of an overlay.\n\nstatic var unsupportedPlatform: SKError.Code\nAn error code that indicates the current platform doesn’t support overlays.\n\n```\n\nflutter的`in_app_purchase`的支付状态枚举：\n```purchase_status.dart\n// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n/// Status for a [PurchaseDetails].\n///\n/// This is the type for [PurchaseDetails.status].\nenum PurchaseStatus {\n  /// The purchase process is pending.\n  ///\n  /// You can update UI to let your users know the purchase is pending.\n  pending,\n\n  /// The purchase is finished and successful.\n  ///\n  /// Update your UI to indicate the purchase is finished and deliver the product.\n  purchased,\n\n  /// Some error occurred in the purchase. The purchasing process if aborted.\n  error,\n\n  /// The purchase has been restored to the device.\n  ///\n  /// You should validate the purchase and if valid deliver the content. Once the\n  /// content has been delivered or if the receipt is invalid you should finish\n  /// the purchase by calling the `completePurchase` method. More information on\n  /// verifying purchases can be found [here](https://pub.dev/packages/in_app_purchase#restoring-previous-purchases).\n  restored,\n\n  /// The purchase has been canceled.\n  ///\n  /// Update your UI to indicate the purchase is canceled.\n  canceled,\n}\n```\n\n很明显，这里flutter的error值明显对应了iOS的众多error，导致埋点中无法细分具体error状态。\n\n## 解决办法\n思路是要git clone `in_app_purchase` fork到本地，修改`in_app_purchase`中的错误返回。\n\n## 梳理逻辑\n### 1.flutter中purchase error的业务代码中获取error code\n\n在in_app_purchase的purchase stream listen中监听购买状态（具体使用方法请参考：[https://pub.dev/packages/in_app_purchase](https://pub.dev/packages/in_app_purchase)）:\n```example.dart\nvoid _listenToPurchaseUpdated(List\u003CPurchaseDetails> purchaseDetailsList) {\n  purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {\n    if (purchaseDetails.status == PurchaseStatus.pending) {\n      _showPendingUI();\n    } else {\n      if (purchaseDetails.status == PurchaseStatus.error) {\n        _handleError(purchaseDetails.error!);\n      } else if (purchaseDetails.status == PurchaseStatus.purchased ||\n                 purchaseDetails.status == PurchaseStatus.restored) {\n        bool valid = await _verifyPurchase(purchaseDetails);\n        if (valid) {\n          _deliverProduct(purchaseDetails);\n        } else {\n          _handleInvalidPurchase(purchaseDetails);\n        }\n      }\n      if (purchaseDetails.pendingCompletePurchase) {\n        await InAppPurchase.instance\n            .completePurchase(purchaseDetails);\n      }\n    }\n  });\n}\n```\n\n对于error状态中error code获取：\n```example.dart\n if (purchaseDetails.status == PurchaseStatus.error) {\n ...\n          var errorCode = -1;\n          if (purchaseDetails.error != null && purchaseDetails.error.code != null) {\n              errorCode = purchaseDetails.error.code;\n          }\n          PayTraceUtil.doPayTrace(payPageEventFrom!, PaySceneSourceType.ScenceDialogPayResult, errorCode);\n...\n}\n```\n在iOS平台下，上面监听purchase status示例代码中的`PurchaseDetails`的实例类型是`AppStorePurchaseDetails`\n![AppStorePurchaseDetails.png](https://image.xinwei.ltd/image1748577582712.png)\n\n所以从iOS platform中传递过来的purchase购买实例，最终会包装为`AppStorePurchaseDetails`，那么我们就查一下这个class的构造函数：\n```app_store_purhase_details.dart\n...\n/// Generate a [AppStorePurchaseDetails] object based on an iOS\n  /// [SKPaymentTransactionWrapper] object.\n  factory AppStorePurchaseDetails.fromSKTransaction(\n    SKPaymentTransactionWrapper transaction,\n    String base64EncodedReceipt,\n  ) {\n    final AppStorePurchaseDetails purchaseDetails = AppStorePurchaseDetails(\n      productID: transaction.payment.productIdentifier,\n      purchaseID: transaction.transactionIdentifier,\n      skPaymentTransaction: transaction,\n      status: const SKTransactionStatusConverter()\n          .toPurchaseStatus(transaction.transactionState, transaction.error),\n      transactionDate: transaction.transactionTimeStamp != null\n          ? (transaction.transactionTimeStamp! * 1000).toInt().toString()\n          : null,\n      verificationData: PurchaseVerificationData(\n          localVerificationData: base64EncodedReceipt,\n          serverVerificationData: base64EncodedReceipt,\n          source: kIAPSource),\n    );\n    if (purchaseDetails.status == PurchaseStatus.error ||\n        purchaseDetails.status == PurchaseStatus.canceled) {\n      purchaseDetails.error = IAPError(\n        source: kIAPSource,\n        code: kPurchaseErrorCode\",\n        message: transaction.error?.domain ?? '',\n        details: transaction.error?.userInfo,\n      );\n    }\n...\n```\n\n重点要看purchaseDetails.error的赋值，可以看到赋值的`IAPError`的`code`是`kPurchaseErrorCode`，查看这个常量值为：\n![kPurchaseErrorCode.png](https://image.xinwei.ltd/image1748577943055.png)\n\n如你所见，这里的code将永远是`purchase_error`。可以佐证，flutter的购买失败状态下，error code的值，针对任何iOS的失败枚举都只返回了这个常量值。\n\n### 2. 如何传递正确的error code\n还是在上面1中的构造函数中，我们可以看到有一个`transaction`，这个其实就是对应于`StoreKit`中的购买trasaction，它的类型是`SKPaymentTransactionWrapper`，我们来看看它的声明：\n![SKPaymentTransactionWrapper.png](https://image.xinwei.ltd/image1748578310932.png)\n\n这里面有error，看看error的声明：\n![flutter SKError](https://image.xinwei.ltd/image1748578348063.png)\n\n看到这个命名，大家也应该知道这个正好是对应于原生iOS的`SKError`。\n\n所以我们需要将`transaction`的error（SKError）中的error code传递给`IAPError`的code，那么就可以达成我们的目的。\n\n## 解决步骤\n### 1. fork flutter的in_app_purchase\n找到in_app_purchase的github地址：[https://github.com/flutter/packages/tree/main/packages/in_app_purchase](https://github.com/flutter/packages/tree/main/packages/in_app_purchase)\n\n\ngit仓库结构是：\n![in_app_purchase git.png](https://image.xinwei.ltd/image1748578544885.png)\n\n可以看到这里在flutter的整个package仓库中，无法单独fork，那我们直接就克隆整个package到本地。\n\n我们需要找一个in_app_purchase的某一个版本，这里我使用的是3.2.0版本\n![in_app_purchase 3.2.0](https://image.xinwei.ltd/image1748578801008.png)\n\n那么我们在某一个文件夹下，克隆到这个版本的所有flutter package\n```terminal\ngit clone -b in_app_purchase-v3.2.0 --single-branch git@github.com:flutter/packages.git\n```\n\n克隆后找到`in_app_purchase`:\n![in_app_purchase fork folder](https://image.xinwei.ltd/image1748578942058.png)\n\n### 2. 修改in_app_purchase_storekit库\n首先明确以下几个事情：\n1. 从上面的梳理思路中可以看到，我们需要修改的代码在`in_app_purchase_storekit`这个package中。\n2. `in_app_purchase`这个package是内部依赖`in_app_purchase_android`、`in_app_purchase_platform_interface`、`in_app_purchase_storekit`这3个平台库的。\n3. 修改`in_app_purchase_storekit`后依赖，那么必须要指定`in_app_purchase`去依赖我们本地修改后的。\n\n看看`in_app_purchase`的pubspec.yaml:\n![in_app_purchase pubspec.yarml](https://image.xinwei.ltd/image1748579384384.png)\n\n修改`in_app_purchase_storekit`，路径在`/Users/edy/Desktop/purchase/packages/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart`:\n![修改`in_app_purchase_storekit`](https://image.xinwei.ltd/image1748579456449.png)\n\n```app_store_purchase_details.dart\n...\nif (purchaseDetails.status == PurchaseStatus.error ||\n    purchaseDetails.status == PurchaseStatus.canceled) {\n      purchaseDetails.error = IAPError(\n        source: kIAPSource,\n        code: \"${transaction.error?.code ?? kPurchaseErrorCode}\", // ,\n        message: transaction.error?.domain ?? '',\n        details: transaction.error?.userInfo,\n      );\n}\n...\n```\n\n4. 将修改后的`in_app_purchase_storekit`引入到flutter工程中\n我们可以在flutter中新建一个文件夹，将修改的库拷贝到这里。\n![plugin folder](https://image.xinwei.ltd/image1748579673091.png)\n\n我新建一个plugin文件夹，将修改后的库放在这里.\n\n5. 让`in_app_purchase`使用修改后的`in_app_purchase_storekit`\n首先引入`in_app_purchase`是必要的\n![pubspec.yaml](https://image.xinwei.ltd/image1748579790181.png)\n```pubspec.yaml\n...\nin_app_purchase: ^3.2.0\n...\n```\n\n要让`in_app_purchase`引用我们plugin文件夹下的`in_app_purchase_storekit`，那么我们需要知道必须要使用override的方式，同样需要在pubspec.yaml文件中引入：\n```pubspec.yaml\n...\ndependency_overrides:\n  in_app_purchase_storekit:\n    path: plugin/in_app_purchase_storekit\n...\n```\n\n6. 运行flutter到iOS上，断点调试，成功。\n\n\n以上就是所有的流程了，大家如果有类似的需要，不妨试试。\n\n\n\n\n\n\n\n","这篇文章记录一下我在flutter开发中如何改造in_app_purchase这个flutter的支付package，以便能上报用户在苹果支付中实际遇到的问题。","https://image.xinwei.ltd/image1748576472033.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],51,"in_app_purchase,StoreKit error,flutter StoreKit error,flutter in_app_purchase,apple pay,flutter plugin,error code",{"id":1437,"createdAt":1633,"title":1634,"content":1635,"summary":1636,"image":15,"uid":136,"user":1637,"categoryId":85,"category":1638,"subCategoryId":47,"subCategory":1639,"comments":1640,"status":9,"reason":15,"notice":15,"visitCount":1630,"commentCount":112,"keywords":1641},"2024-02-02T10:45:02.317Z","浅谈iOS中的base64加密解密","# 密码学\n\n## base64\n\n字符其实有65种，可以将任意二进制数据进行编码，编码成为有65中字符组成的文本文件。\n0-9, a-z A-Z, + / =\n\n## Example\n*   加密\n\n1.  touch 123.txt\n2.  base64 123.txt -o abc.txt\n\n# \n*   解密\n\n1.  base64 abc.txt -o 234.txt -D\n\n> 图片也可以，但是修改图片的名称、后缀名，都不能改变加密的结果，因为其二进制数据没有发生变化。\n\n### 字符串的加解密\n```\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    NSLog(@\"%@\", [self base64Endcode:@\"A\"]);\n    NSLog(@\"%@\", [self base64Decode:@\"QQ==\"]);\n}\n\n- (NSString *)base64Endcode:(NSString *)string {\n    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];\n    return [data base64EncodedStringWithOptions:0];\n}\n\n- (NSString *)base64Decode:(NSString *)string {\n    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];\n    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n}\n```\n\n*   [x] 对称加密 \\\n    算法：DES, 3DES, AES(高级密码标准，美国国家安全局使用的)\n\n*   [x] 非对称加密 \\\n    算法：RSA\n\n*   [x] 数学算法：\n\n*   哈希（散列）函数: \\\n    算法公开 \\\n    对相同的数据加密，结果是一样的 \\\n    对不同的数据，MD5得到的结果都是32个字符 \\\n    不能反算 \\\n    数据摘要，数据的一个部分（信息指纹）\n\n","这篇文章聊聊iOS中的base64，如何实现一个简单的base64加密和解密。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"base64,ios,ios base64,ios encode,ios decode,encode,ios加密",{"id":1643,"createdAt":1644,"title":1645,"content":1646,"summary":1647,"image":1648,"uid":136,"user":1649,"categoryId":85,"category":1650,"subCategoryId":144,"subCategory":1651,"comments":1652,"status":9,"reason":15,"notice":15,"visitCount":1415,"commentCount":112,"keywords":1653},177,"2026-02-03T04:47:58.856Z","nextjs项目的打包、docker部署、脚本等","这篇文章记录一下我最近写的`NextJs`项目的简单打包、部署以及一些脚本，留作记录以及供大家参考。\n\n最近我写了一个彩票类的小程序：`好彩管家`(支付宝小程序)/`彩号管家`(微信小程序)，小程序使用的taro用作跨平台的小程序开发，h5方面为了seo友好，我选择了使用`NextJs`进行改写并丰富功能，使用nextjs开发的体验还是很不错的，毕竟这么多AI公司都选择了NextJs做前端。所以我觉得广大的开发者还是应该去体验一下NextJs，还是很不错的。\n\n话不多说，我来介绍一下我的工程。\n\n## 一、工程的性能情况介绍\n网页是：[https://lottery.xinwei.ltd](https://lottery.xinwei.ltd)\n\n![lottery lighthouse测试](https://image.xinwei.ltd/image1770091084545.png)\n\n从这张图可以看出，我这个工程性能还是可以的，在SEO上基本比较优秀了，当然在渲染上还是有优化的空间，这里不做赘述，大家关注点可以放在如何打包部署上。\n\n\n## 二、工程打包\n下面是我的NextJs的脚本命令配置。\n\n![nextjs工程的脚本](https://image.xinwei.ltd/image1770091403089.png)\n\n截图中已经给出了一些解释，我稍微列一下：\n- dev 脚本命令就是本地开发的\n- -p 是可选的命令，nextJs默认使用3000端口，使用-p xxx端口号，可以自定义监听的端口号\n- start 是生产环境下，执行这个命令就可以监听，同样可以使用-p指定端口号\n- deploy 是我增加的脚本命令，我在工程中写了一个bash脚本，通过npm命令就可以执行这个bash脚本，这个bash脚本就是用来打包，并上传内容到云服务器上，进行docker的相关操作。\n\n## 三、我的docker-compose文件\n我个人比较喜欢使用docker-compose文件来部署项目，因为文件结构清晰，配置一一对应设置就好，我这里留一下我的docker-compose文件内容。\n\n![docker-compose文件位置](https://image.xinwei.ltd/image1770091909884.png)\n\n直接在项目`根目录`下创建这个文件。\n```docker-compose.yml\nversion: '2.2'\n\nservices:\n  lottery:\n    image: 'node:latest'\n    container_name: xxx\n    working_dir: /app\n    environment:\n      - NODE_ENV=production\n    volumes:\n      - ./dist:/app\n    ports:\n      - 3000:3000 # 根据自己暴露的主机防火墙端口来设置\n    command: >\n      sh -c \"npm install && NODE_ENV=production npm run start\"\n```\n\n这个文件比较简单，就指定了一个容器的配置，使用node镜像，然后容器启动时，会执行command脚本中的命令。\n\n## 四、bash脚本\nbash脚本的作用就是用来执行npm相关命令删除旧打包产物、生成新产物，然后通过云服务器的scp授权证书远程ssh访问，操作云服务器文件、文件夹、docker等。\n\nbash脚本内容：\n```web.sh\n#bin/sh\n\necho \"当前路径：\"\necho \"\"\npwd\n\necho \"删除旧产物\"\nrm -rf .next\n\necho \"打包项目，生成新产物\"\nnpm run build\n\necho \"加权限\"\nchmod 777 .next\n\n# 这是在云服务器厂商管理后台生成的ssh操作授权证书，可以放在本地电脑上，执行命令时带入即可操作云服务器资源\nSSH_KEY=\"/Users/hamry/xxx/ssh_visit.pem\"\n# 这是主机访问的username和IP，这里不是我的实际IP，你需要改成你自己云服务器的ip地址\nSERVER=\"root@1.2.3.4\"\n\necho \"连接服务器&删除旧产物\"\nssh -i $SSH_KEY $SERVER '\necho \"当前目录：\";\npwd;\necho \"删除旧产物\";\nrm -rf /root/lottery_nextjs/dist/*;\nrm -rf /root/lottery_nextjs/dist/.[^.]*;\n'\n\necho \"拷贝产物到服务器\"\necho \"拷贝 .next、package、package-lock、public 到服务器\"\nscp -q -i $SSH_KEY -r .next package.json package-lock.json public $SERVER:/root/lottery_nextjs/dist/\necho \"拷贝 docker-compose 到服务器\"\nscp -q -i $SSH_KEY docker-compose.yml $SERVER:/root/lottery_nextjs/\necho \"重启docker\"\nssh -i $SSH_KEY $SERVER '\npwd;\ncd /root/lottery_nextjs/dist;\npwd;\n# docker-compose up  # 这里是第一次执行脚本时，会创建docker容器，第二次不用再次执行，不然每次都覆盖生成新容器。\ndocker restart xxx  # 和上面的命令不要同时执行，第一次生成容器时不要执行这个命令，第二次后就可以注释上面的命令，使用这条命令来重启第一次生成的那个容器ID\n'\n\necho \"容器重启成功\"\n```\n\n脚本的相关内容，我都在上面做了解释，大家如果要参考，要仔细阅读清楚，避免执行出错。\n\n另外，这里要说明一下，我们`不需要把整个NextJs工程拷贝到云服务器`，很占据云服务器空间的，所以我们只需要把重要关键的文件传输到云服务器。关键的文件在上面的脚本中已经有处理，我罗列在这里：\n- .next文件夹，这个是dev或者build时都会生成的隐藏文件夹，产物都放在这里了\n- package.json & package-lock.json，这2个文件也必不可少，最终执行命令也要通过`npm run start`执行package.json中的start命令。\n- public 文件夹，这里存放的是工程的静态资源文件，也是必不可少的，否则静态资源的访问就会出现404.\n\n这里要说一句为什么没有`node_modules`文件夹呢，是不是很奇怪，那说明你可能没看懂docker-compose.yml脚本中的那行命令：\n```docker-compose.yml\n...\ncommand: >\n      sh -c \"npm install && NODE_ENV=production npm run start\"\n...\n```\n这次看清楚了吧，这里有`npm install`命令，因为是node镜像，然后有`package.json`，那么在云服务器上安装依赖就很容易。如果要自己上传node_modules文件夹，一个是上传时间会特别久，另一个原因是有可能终端命令行执行由于文件太多导致EOF问题失败。\n\n## 五、云服务器上的文件内容\n### 5.1 整体文件夹\n![云服务器部署根目录](https://image.xinwei.ltd/image1770093298286.png)\n这里我在云服务器上创建了一个文件夹：lottery_nextjs，我所有的项目都会在云服务器的根路径/root目录下，lottery_nextjs这个文件夹就是我用来部署这个项目的专属文件夹。\n\n这个文件夹下面，放了2个东西：\n- dist：用于存放nextjs工程的打包产物，bash脚本中上传的内容都是到dist文件夹下；\n- docker-compose.yml: docker-compose文件，这个是用来创建docker容器，并让docker启动nextJs服务的，内容在上面已经给大家显示出来了。\n\n### 5.2 dist文件夹\n\n![dist文件夹内容](https://image.xinwei.ltd/image1770093595218.png)\n\n大家看到了吧，上面提到的4个文件/文件夹，就是上传到这里，如果你没看到.next文件夹，因为那是隐藏文件夹，你要点击截图中的红色框部分，可以查看隐藏内容。\n\n这里的node_modules就是通过`npm install`生成的。\n\n\n## 六、图片\n另外我展示一下next.config.ts中的图片配置，因为网络图片访问是需要进行域名配置的，在dev环境下访问没有问题，但是生产环境的安全策略必须要配置图片访问域名，如下：\n![图片配置](https://image.xinwei.ltd/image1770093931735.png)\n\n```next.config.ts\nimport type { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https', // 协议：http/https\n        hostname: 'www.baidu.com', // 域名\n        port: '', // 端口（无则留空）\n      },\n    ],\n  },\n  output: 'standalone',\n};\n\nexport default nextConfig;\n\n```\n这里建议设置：`output: 'standalone',`，是优化docker部署的，大家可以试试。\n\n## 七、其他\n大家正常开发完项目后，通过我上述的操作，你可以很快的把nextjs项目部署到自己的服务器上，我这里是为了快速上线，所以怎么快怎么来。如果要更正式点，那么就需要使用工具做一些持续集成了，大家自己可以去摸索一下。\n\n以上。\n\n求关注。\n\n\n\n\n","这篇文章记录一下我最近写的nextjs项目的简单打包、部署以及一些脚本，留作记录以及供大家参考。","https://image.xinwei.ltd/image1770091084545.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"nextjs docker, nextjs server, nextjs lottery, nextjs react, nextjs seo, nextjs docker-compose, nextjs,好彩管家,彩号管家,云服务器操作",{"id":1655,"createdAt":1656,"title":1657,"content":1658,"summary":1659,"image":15,"uid":136,"user":1660,"categoryId":85,"category":1661,"subCategoryId":47,"subCategory":1662,"comments":1663,"status":9,"reason":15,"notice":15,"visitCount":1664,"commentCount":112,"keywords":1665},58,"2024-01-31T04:03:11.44Z","iOS的KVO底层实现","# 概述：\n\nKVO属于观察者模式，在很多地方都会用到，比如响应式编程。\n\n1.  KVO只是观察属性的set方法，属性是对成员变量及其set、get方法的封装。\n```\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    \n//    _p.dog.age = a++;\n//    _p.dog.level = a++;\n\n    // 成员变量这样子不能被监听到\n    _p->_name = [NSString stringWithFormat:@\"%d\", a++]; \n}\n```\n\n2. 添加观察\n```\n/*\nNSKeyValueObservingOptionNew        返回新值\nNSKeyValueObservingOptionOld        返回旧值\nNSKeyValueObservingOptionInitial    注册的时候就会发一次通知，改变的时候也会发通知\nNSKeyValueObservingOptionPrior      改变之前发一次，改变之后发一次\n*/\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    _p = [[Person alloc] init];\n    // 添加观察者\n    [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n}\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n```\n观察者方法中有一个`change`，这个里面有一个`kind`的key，有以下几种：\n```\ntypedef NS_ENUM(NSUInteger, NSKeyValueChange) {\n    NSKeyValueChangeSetting = 1,\n    NSKeyValueChangeInsertion = 2,\n    NSKeyValueChangeRemoval -= 3,\n    NSKeyValueChangeReplacement = 4,\n};\n```\n\n# 概述：\n\nKVO属于观察者模式，在很多地方都会用到，比如响应式编程。\n\n## 1.  KVO只是观察属性的set方法，属性是对成员变量及其set、get方法的封装。\n\n    - (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n        static int a = 0;\n        \n    //    _p.dog.age = a++;\n    //    _p.dog.level = a++;\n\n        // 成员变量这样子不能被监听到\n        _p->_name = [NSString stringWithFormat:@\"%d\", a++]; \n    }\n\n## 2. 添加观察者：\n\n    /*\n    NSKeyValueObservingOptionNew        返回新值\n    NSKeyValueObservingOptionOld        返回旧值\n    NSKeyValueObservingOptionInitial    注册的时候就会发一次通知，改变的时候也会发通知\n    NSKeyValueObservingOptionPrior      改变之前发一次，改变之后发一次\n    */\n    - (void)viewDidLoad {\n        [super viewDidLoad];\n        \n        _p = [[Person alloc] init];\n        // 添加观察者\n        [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n    }\n    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n        NSLog(@\"%@\", change);\n    }\n\n观察者方法中有一个`change`，这个里面有一个`kind`的key，有以下几种：\n\n    typedef NS_ENUM(NSUInteger, NSKeyValueChange) {\n        NSKeyValueChangeSetting = 1,\n        NSKeyValueChangeInsertion = 2,\n        NSKeyValueChangeRemoval -= 3,\n        NSKeyValueChangeReplacement = 4,\n    };\n\n## 3. KVO有两种方式触发\n*   自动\\\n    通过`@interface NSObject(NSKeyValueObservingCustomization)`中的\n```\n/* Return YES if the key-value observing machinery should automatically invoke -willChangeValueForKey:/-didChangeValueForKey:, -willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey:, or -willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: whenever instances of the class receive key-value coding messages for the key, or mutating key-value coding-compliant methods for the key are invoked. Return NO otherwise. Starting in Mac OS 10.5, the default implementation of this method searches the receiving class for a method whose name matches the pattern +automaticallyNotifiesObserversOf\u003CKey>, and returns the result of invoking that method if it is found. So, any such method must return BOOL too. If no such method is found YES is returned.\n*/\n+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;\n```\n默认`YES`来触发。\n\n*   手动\\\n    即通过如下设置，手动触发\n```\n// KVO两种模式：1. 自动， 2. 手动\n// 默认是自动模式\n+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {\n    return NO;\n}\n```\n```\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    // 手动触发KVO\n    [_p willChangeValueForKey:@\"name\"];\n    _p.name = [NSString stringWithFormat:@\"%d\", a++];\n    [_p didChangeValueForKey:@\"name\"];\n}\n```\n## 4. 属性的依赖（依然可以observe）\n```\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    _p = [[Person alloc] init];\n    // 添加观察者\n    [_p addObserver:self forKeyPath:@\"dog.age\" options:NSKeyValueObservingOptionNew context:nil];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n    static int a = 0;\n    _p.dog.age = a++;\n}\n```\n## 5. `KVO`监听的是`set`方法，对于像是容器来的比如`NSMutableArray`，则需要结合`KVC`。\n监听容器类属性的方法有：\n### 5.1. 例如新建`NSMutableArray`，然后通过`set`方法赋值给属性;（low的做法）\n### 5.2. Apple提供了一个途径：\n```\n// 通过KVO观察容器属性的变化，利用KVC方法\n    NSMutableArray *tempArr = [_p mutableArrayValueForKey:@\"arr\"];\n    [tempArr addObject:@\"objc\"];\n//    [_p.arr addObject:@\"1\"];\n```\n这样就可以通过`addObserver`方法观察得到了，苹果的这种做法，通过断点，可以得知，`tempArr`其实并不是`NSMutableArray`类型，而是`NSKeyValueNotifyingMutableArray`，是`NSMutableArray`的子类，通过重写`addObject`方法来手动`willChange`和`didChange`。\n\n### 5.3. 监听`NSMutableArray`的`count`\n*   通过KVO `addObserver`crash, 观察`\"@count\"`也会crash\n*   通过KVC `id count = [arr valueForKey:@\"count\"]`会crash\n*   通过集合运算符 `[arr valueForKey:@\"@count\"]`可以成功获取到count\n\n## 6. 如果要观察Person类中的Dog属性的age和level属性，\n```\n    // 添加观察者\n    //    [_p addObserver:self forKeyPath:@\"dog.age\" options:NSKeyValueObservingOptionNew context:nil];\n    //    [_p addObserver:self forKeyPath:@\"dog.level\" options:NSKeyValueObservingOptionNew context:nil];\n        // 如果观察dog的属性的话，可以直接观察dog，然后在person类中做修改\n        [_p addObserver:self forKeyPath:@\"dog\" options:NSKeyValueObservingOptionNew context:nil];\n        \n    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n        NSLog(@\"%@\", change);\n    }\n\n    - (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event {\n        static int a = 0;\n        \n        _p.dog.age = a++;\n        _p.dog.level = a++;\n    }\n```\n在`Person.m`文件里：\n```\n// 返回一个容器，里面放字符串类型\n+ (NSSet\u003CNSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {\n//    NSLog(@\"%@\", key);\n    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];\n    if ([key isEqualToString:@\"dog\"]) {\n        NSArray *arr = @[@\"_dog.level\", @\"_dog.age\"];\n        keyPaths = [keyPaths setByAddingObjectsFromArray:arr];\n    }\n        \n    return keyPaths;\n}\n```\n## 7. 观察的实现原理\n通过\n```\n// 添加观察者\n    [_p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n```\n会创建一个继承自`Person`的子类`NSKVONotifying_Person`，记住以下：\n*   方法的调用和类型有关\n*   成员变量和对象有关系\n\n`_p`的类型从Person变成了子类`NSKVONotifying_Person`，即`_p`的`isa`指针指向了`NSKVONotifying_Person`\n\n## 8. 消息发送\n消息发送时，首先找类，然后找这个类`Class`中的方法`SEL`编号，对应着方法的实现，然后就能找到代码区。如果找不到，那么就会根据`isa`指针，找到父类，找父类的方法。\n\n## 9. 子类继承父类\n一个类继承父类的情况下，子类不会有父类的方法，如果子类调用的方法在子类中没有，那么就会通过`isa`指针去找父类的方法。子类重写父类的方法，实质上就是添加方法。\n\n## 10 使用`runtime`重新实现系统的`KVO`\n### 10.1 新建分类：`NSObject+CSKV`\n```\n@interface NSObject (CSKVO)\n\n- (void)CS_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;\n\n@end\n\n#import \"NSObject+CSKVO.h\"\n#import \u003Cobjc/runtime.h>\n\n@implementation NSObject (CSKVO)\n\n- (void)CS_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {\n    // 1. 自定义一个NSKVONotifying_Person子类\n    NSString *oldClassName = NSStringFromClass(self.class);\n    NSString *newClassName = [@\"CS_\" stringByAppendingString:oldClassName];\n    // 创建一个类\n    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);\n    // 注册该类\n    objc_registerClassPair(myClass);\n    // 2. 动态修改\n    object_setClass(self, myClass);\n    // 3. 添加setName方法！\n    class_addMethod(myClass, @selector(setName:), (IMP)haha, \"v@:@\"); // “v@:@”：v表示返回值void, @表示第一个参数是OC类型，:表示方法编号SEL，@表示最后一个参数是oc类型\n}\n\n// OC方法的调用里面有两个隐藏参数：id self, SEL _cmd\nvoid haha(id self, SEL _cmd, NSString *newName) { // 这个地方如果只给参数：NSString *newName, 那么newName的值就知识CS_Person对象\n    NSLog(@\"子类setname %@\", newName);\n    // 修改name属性\n    // 通知外界willChange, didChange.\n}\n\n@end\n```\n\n### 10.2 添加观察者\n```\n[_p CS_addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"%@\", change);\n}\n```\n\n### 10.3 修改属性\n```\n//    _p.name = [NSString stringWithFormat:@\"%d\", a++];\n    // 上面等价于下面的消息机制\n    objc_msgSend(_p, @selector(setName:), [NSString stringWithFormat:@\"%d\", a++]);\n```\n\n## 11 数组`NSMutableArray`\n\n`NSMutableArray *arr = [NSMutableArray arrayWithCapacity:1];`\n\n*   关于容量，数组在内存中，如果内存不够用了，容量会成倍(\\*2)的增加！(1, 2, 4, 8)\n*   使用这种方式时，一般往往是认为大多数情况下，存储的容量只有1个。\n*   `[NSMutableArray array]`更节约内存\n*   `capacity`：数组容量 -> 内存中的大小\n\n## 12 使用runtime证明KVO中生成了NSKVONotifying_Person\n```\n@implementation ViewController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    Person *p = [[Person alloc] init];\n    [p addObserver:self forKeyPath:@\"name\" options:NSKeyValueObservingOptionNew context:nil];\n    p.name = @\"Hamry\";\n    NSLog(@\"======%@\", [ViewController findSubClass:[p class]]);\n}\n\n+ (NSArray *)findSubClass:(Class)defaultClass {\n    // 注册类的总数\n    int count = objc_getClassList(NULL, 0);\n    // 创建一个数组，其中包含给定对象\n    NSMutableArray *array = [NSMutableArray arrayWithObject:defaultClass];\n    // 获取所有已注册的类\n    Class *classes = (Class *)malloc(sizeof(Class)*count);\n    objc_getClassList(classes, count);\n    // 遍历\n    for (int i = 0; i \u003C count; i++) {\n        if (defaultClass == class_getSuperclass(classes[i])) {\n            [array addObject:classes[i]];\n        }\n    }\n    free(classes);\n    return array;\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary\u003CNSKeyValueChangeKey,id> *)change context:(void *)context {\n    NSLog(@\"change: %@\", change);\n}\n```\n\n```\n2019-07-23 11:56:06.303501+0800 MethodForwarding[66501:2118253] change: {\n    kind = 1;\n    new = Hamry;\n}\n2019-07-23 11:56:06.362926+0800 MethodForwarding[66501:2118253] ======(\n    Person,\n    \"NSKVONotifying_Person\"\n)\n\n```","iOS的kvo的底层实现原理，通过代码调试一步步来实现对kvo原理的探索。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],49,"ios,ios kvo,ios调试,ios debug",{"id":1667,"createdAt":1668,"title":1669,"content":1670,"summary":1671,"image":1672,"uid":136,"user":1673,"categoryId":85,"category":1674,"subCategoryId":144,"subCategory":1675,"comments":1676,"status":9,"reason":15,"notice":15,"visitCount":1677,"commentCount":112,"keywords":1678},178,"2026-02-04T07:12:40.458Z","将Nuxt工程从3.x升级到最新的4.3.0","这篇文章记录一下我如何将我的Nuxt工程从3.x升级到最新的4.x版本，其中遇到一些坑，希望对大家有帮助。\n\n我之前已经写过如何去打包和部署`NextJs`工程，这次我回过头来去处理我的另外一个项目，是使用`Nuxt`开发的前端项目，众所周知，如果要做一个`SEO`良好的项目，必不可少要用到服务端渲染，那么针对`React`语言，比较好的实践是使用`NextJs`，对于`Vue`项目，比较好的是选择`Nuxt`，我目前的项目都有使用到这2种类型的开发框架。\n\n之前使用`Nuxt`是比较早的时候，那个时候`Nuxt`还是`3.x`版本，但是过了这么久，`Nuxt`做了非常多的版本升级，对于性能上的优化还是不少的，所以升级到最新版本是一个必不可少的步骤。这里我来介绍一下我工程中遇到的升级问题和流程。\n\n## 一、升级Nuxt版本\n升级前我的nuxt版本如下：\n```package.json\n...\n\"devDependencies\": {\n    ...\n    \"nuxt\": \"^3.9.0\",\n    ...\n},\n...\n```\n\n最新的Nuxt版本是：`4.3.0`\n![nuxt最新版本4.3.0](https://image.xinwei.ltd/image1770188114730.png)\n\n升级的话，我们可以直接在官网中查询到：[https://nuxt.com/docs/4.x/getting-started/upgrade](https://nuxt.com/docs/4.x/getting-started/upgrade)\n\n需要使用的终端命令是：\n```terminal\nnpx nuxt upgrade\n```\n\n但是直接去执行这个命令，版本号也只能限定到3.x版本系列的最高版本，而不是4.x版本。\n\n比如我直接执行上面的终端命令，只能将我的`^3.9.0`升级到`3.20.1`。\n\n所以我们要先修改`nuxt`的版本号为：\n```package.json\n...\n\"devDependencies\": {\n    ...\n    \"nuxt\": \"^4.3.0\",\n    ...\n},\n...\n```\n然后执行`npm install`终端命令，先安装`4.3.0`的版本.\n\n最后再执行\n```terminal\nnpx nuxt upgrade\n```\n\n终端会提示你是否要`re-create node_modules和pakcage-lock.json`，选择它，然后enter继续。\n\n之后命令会将工程中的所有依赖和`.nuxt`升级到版本`4.x`的依赖。\n\n\n## 二、scss的预处理配置\n在以前的Nuxt 3.x版本中，我引入全局`scss`文件的时候，引入是下面这样的：\n```nuxt.config.ts\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: '2026-02-04',\n  ...\n  css: ['~/assets/css/style.css'],\n  ...\n  vite: {\n    css: {\n      preprocessorOptions: {\n        scss: {\n          additionalData: '@import \"~/assets/css/global.scss\";',\n        },\n      },\n    },\n  },\n  ...\n});\n\n```\n\n但是升级到4.x版本的时候这种预处理配置会报错:\n```terminal error\nℹ Error: [sass] expected \";\".\n  ╷\n1 │ @import \"~/assets/css/global.scss\";\n  │                                       ^\n  ╵\n  app.vue 1:39  root stylesheet\n\n ⁃ (app.vue 1:39  root stylesheet:undefined:undefined:undefined:undefined)\n```\n\n必须要改成以下的形式：\n```nuxt.config.ts\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: '2026-02-01',\n  ...\n  css: ['~/assets/css/style.css'],\n  ...\n  vite: {\n    css: {\n      preprocessorOptions: {\n        scss: {\n          additionalData: '@use \"~/assets/css/global.scss\" as *;',\n        },\n      },\n    },\n  },\n  ...\n});\n\n```\n\n另外我提一句，在4.x版本中可以添加以下的描述：\n```nuxt.config.ts\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: '2026-02-01',\n...\n});\n```\n看看它的介绍：\n![compatibilityDate说明](https://image.xinwei.ltd/image1770188869709.png)\n\n这个配置会影响到一些nitro服务端的预设值等，所以建议最好配置一下。\n\n\n## 三、页面动态id修改\n在之前的3.x版本中，我有一些页面使用了`[:id].vue`这样的配置，但是在`4.x`版本中已经不允许这么做了，所以如果你的项目中有这样的动态路由，也是一定要改写一下。\n\n从`[:id].vue`改写为`[id].vue`，不要id前面的`:`冒号了。\n\n\n以上就是我暂时遇到的一些升级问题，这样修改后，项目是能正常运行，且编译速度和渲染也有700ms左右的提升。\n\n\n这里简单给大家做参考，希望对大家有帮助。\n\n希望大家多多点赞、关注支持一下，您的点赞/关注是我更新的动力，谢谢。\n\n\n\n","这篇文章记录一下我如何将我的Nuxt工程从3.x升级到最新的4.x版本，其中遇到一些坑，希望对大家有帮助。","https://image.xinwei.ltd/image1770188114730.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],48,"Nuxt, Nuxt js, Nuxt Vue, nuxt scss, nuxt preprocessor,Nuxt version, Nuxt config,Nuxt版本升级, Nuxt SEO",{"id":1680,"createdAt":1681,"title":1682,"content":1683,"summary":1684,"image":15,"uid":136,"user":1685,"categoryId":85,"category":1686,"subCategoryId":144,"subCategory":1687,"comments":1688,"status":9,"reason":15,"notice":15,"visitCount":1677,"commentCount":112,"keywords":1689},43,"2024-01-30T10:41:50.184Z","phonegap工程中修改app的名字","在phonegap工程中，当添加了iOS和android平台或多个平台后，工程进行了开发，然后觉得app的名字想修改一下（比如在手机上显示的app名字，或者通过ipa导入安装或者apk包点击安装时提示的名字），那么该怎么做？\n\n原生app中修改app名字的做法：\n- iOS：通过Xcode打开或者直接打开info.plist文件，修改app的display name即可。\n\n- android：到res/values/string.xml中，修改\n\n\n在phonegap工程中，根据phonegap包含的支持平台分为2种情况。\n- 情况一\nphonegap工程中就只有一个iOS平台或者安卓平台，那么直接去修改config.xml中的\u003Cname>标签即可，或者通过原生的方法修改也行。\n- 情况二\nphonegap中包含了多个平台（比如同时支持iOS、android），如果直接去修改config.xml中的\u003Cname>标签，编译的时候就会报错，提示工程名字不匹配等错误。这个时候的做法是：\n1. iOS工程是可以直接通过xcode工程打开，然后修改info.plist中显示的app名字即可。\n2. android的app名字，如果仅仅通过原生的方法进行修改是不成功的，string.xml中的app_name是会被覆盖的。\n\n但是如果在phonegap的config.xml中进行全局修改。编译iOS的时候就会出现错误，提示不能动态修改config.xml中的\u003Cname>的值。\n意味着，config.xml中的\u003Cname>的值修改后，表示会修改phonegap中所包含的所有平台的名字。\n\n那么正确的修改方式是：\n在phonegap的config.xml中，在对应的平台标签下，修改全局\u003Cname>标签的值。\n\n所以其他的平台也是可以同样这么做的。","如何在phonegap中修改app的名字，不管是iOS或者安卓的，修改方法。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"phonegap,phonegap工程,phonegap移动端,phonegap名字",{"id":257,"createdAt":1691,"title":1692,"content":1693,"summary":1694,"image":1695,"uid":136,"user":1696,"categoryId":85,"category":1697,"subCategoryId":47,"subCategory":1698,"comments":1699,"status":9,"reason":15,"notice":15,"visitCount":881,"commentCount":112,"keywords":1700},"2024-01-30T04:54:08.277Z","xib中tableviewcell rowheight的高度自适应","\n\n用xib实现高度自适应所应该具有的条件：\n1. 每一个cell中，内容文字显示有多有少，使得每一个cell的高度不能完全一致；\n2. 自定义的cell的xib中，控件的布局从左到右，从上到下进行布局；\n3. xib中最靠近cell Content的控件，要设置相对于content的bottom约束。\n4. 内容文字控件UILabel的xib需要设置lineNumber = 0; 并且Preferred width 要勾选Explicit。\n![xib截图](https://image.xinwei.ltd/11706590380707.png)\n\n5. 需要在对应的控制器中，设置\n```\nUITableView，\n        self.tableView.rowHeight =  UITableViewAutomaticDimension;\n     self.tableView.estimatedRowHeight = 365;\n```\n6. 需要在对应控制器，设置一个cell的实例demoHeightCell的全局cell类实例, \n\n// demo height cell\n    CYCourseDetailTableViewCell *demoHeightCell;\n然后通过nib的loadNib方式实例化\n```\ndemoHeightCell = [[[NSBundle mainBundle] loadNibNamed:@\"CYCourseDetailTableViewCell\" owner:nil options:nil] firstObject];\n```\n然后如下：\n```\n- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{\n    [demoHeightCell  valueWithModel:courseArr[indexPath.row]];\n    CGSize demoSize = [demoHeightCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];\n    return demoSize.height;\n}\n```\n返回计算后的高度，即为理想宽度。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","在iOS中，如何使用xib来让tableviewCell的row height高度自适应","https://image.xinwei.ltd/11706590380707.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios xib,ios tableview,xib tableview",{"id":1702,"createdAt":1703,"title":1704,"content":1705,"summary":1706,"image":15,"uid":136,"user":1707,"categoryId":85,"category":1708,"subCategoryId":47,"subCategory":1709,"comments":1710,"status":9,"reason":15,"notice":15,"visitCount":1711,"commentCount":112,"keywords":1712},72,"2024-02-02T11:04:31.568Z","iOS中当适配iPad的时候使用UIAlertController注意的点","\n使用`UIAlertController`时，要设置其`popoverPresentationController.sourceView`和`popoverPresentationController.sourceRec`这2个属性t，否则会导致适配iOS和iPad的app发生crash。\n\n例如：\n```\nlet alertController = UIAlertController(title: \"My first app\", message: \"Hello World\", preferredStyle: .ActionSheet)\n        //ipad使用，不加ipad上会崩溃\n        if let popoverController = alertController.popoverPresentationController {\n            popoverController.sourceView = sender\n            popoverController.sourceRect = sender.bounds\n        }\n```\n\nsourceView可以是UIButton、self.view等，也可以是barButtonItem;\n\n例如：\n```\nif let popoverController = alertController.popoverPresentationController {\n    popoverController.barButtonItem = sender\n}\nself.presentViewController(alertController, animated: true, completion: nil)\n```","开发的iOS应用，有时候要同时适配iPad版本，当在使用UIAlertController时，需要注意一个点，否则会导致crash。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],45,"ios,ios app,ios ipad,ios开发",{"id":1597,"createdAt":1714,"title":1715,"content":1716,"summary":1717,"image":1718,"uid":136,"user":1719,"categoryId":85,"category":1720,"subCategoryId":47,"subCategory":1721,"comments":1722,"status":9,"reason":15,"notice":15,"visitCount":1711,"commentCount":112,"keywords":1723},"2024-01-30T15:55:44.326Z","使用Xcode9出现的错误","1. 在使用“七牛”的库“PLMediaStreamingKit\"时，会附带下载了一个\"QNNetDiag\"的库，在Xcode9下编译时，报错如下：\n![报错截图](https://image.xinwei.ltd/11706629744612.jpg)\n原因：七牛的库在Xcode9下编译不通过的问题。\n解决方案：下载最新的QNNetDiag库，替换掉工程中的库；\n参考：\nhttp://www.cocoachina.com/bbs/read.php?tid-1726549.html\nhttps://github.com/qiniu/iOS-netdiag/issues/9\n\n2. 导航栏\n（1）如果通过UIBarButtonItem的initCustomView:方法来实例化，这个自定义的视图如果是UIButton或者UIImageView的话，frame会被拉伸；但是如果自定义视图是UIView的话，就不会被拉伸。\n\n（2）导航栏的titleView：\n在iOS11上，自定义title view，会被拉伸，需要给自定义的titleView的.m文件内部覆盖一个读取方法，将自己需要的尺寸大小设置进去。如下：\n![代码截图](https://image.xinwei.ltd/21706629838358.jpg)\n\n参考：\n[https://stackoverflow.com/questions/44932084/ios-11-navigationitem-titleview-width-not-set](https://stackoverflow.com/questions/44932084/ios-11-navigationitem-titleview-width-not-set)\n[http://www.jianshu.com/p/9391bea190e6](http://www.jianshu.com/p/9391bea190e6)\n\n3. 安全区域（SafeArea）\n1）竖屏:\n![](https://image.xinwei.ltd/31706629976836.png)\n解决方法：\n取“状态栏”和“导航栏”的高度：\n```\n// 状态栏(statusbar)\nCGRect statusRect = [[UIApplication sharedApplication] statusBarFrame];\n//标题栏\nCGRect navRect = self.navigationController.navigationBar.frame;\n```\n底部的home indicator的高度为34（竖屏的情况下）；\n\n（2）横屏：\n![](https://image.xinwei.ltd/41706630044504.png)\n解决方法：\n按照上图，进行安全区域的显示。\n\n4. 使用xcode 9，在有cocoapods的工程中，会出现AppIcons不显示的情况。\n解决方法：\n打开工程目录下:[工程名]/Pods/Target Support Files/Pods-[工程名]/Pods-[工程名]-resources.sh这个文件,替换最后一段代码:\n修改前:\n```\nprintf \"%s\\0\" \"${XCASSET_FILES[@]}\" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform \"${PLATFORM_NAME}\" --minimum-deployment-target \"${!DEPLOYMENT_TARGET_SETTING_NAME}\" ${TARGET_DEVICE_ARGS} --compress-pngs --compile \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\nfi\n```\n修改后:\n```\nprintf \"%s\\0\" \"${XCASSET_FILES[@]}\" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform \"${PLATFORM_NAME}\" --minimum-deployment-target \"${!DEPLOYMENT_TARGET_SETTING_NAME}\" ${TARGET_DEVICE_ARGS} --compress-pngs --compile \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" --app-icon \"${ASSETCATALOG_COMPILER_APPICON_NAME}\" --output-partial-info-plist \"${BUILD_DIR}/assetcatalog_generated_info.plist\"\nfi\n```\n\n然后重新运行工程即可。\n\n\n","这里是我使用Xcode9时遇到一些问题，以及对应解决办法。","https://image.xinwei.ltd/11706629744612.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios开发,xcode,ios xcode,xcode9,ios",{"id":1725,"createdAt":1726,"title":1727,"content":1728,"summary":1729,"image":1730,"uid":136,"user":1731,"categoryId":85,"category":1732,"subCategoryId":560,"subCategory":1733,"comments":1734,"status":9,"reason":15,"notice":15,"visitCount":1711,"commentCount":112,"keywords":1735},23,"2024-01-26T15:01:10.993Z","mysql数据库常用命令","### 1. 查看mysql中的数据库\n```terminal\nmysql> show databases;\n```\n![mysql命令](https://image.xinwei.ltd/11706280926072.png)\n\n### 2. 新增数据库\n```terminal\nmysql> create database XXX; // XXX是数据库名\n```\n![mysql命令](https://image.xinwei.ltd/21706280979343.png)\n\n### 3. 删除数据库\n```terminal\nmysql> drop database test;\n```\n![mysql命令](https://image.xinwei.ltd/31706281042000.png)\n\n### 4. 使用哪一个数据库\n```terminal\nmysql> use wordpress;\n```\n![mysql命令](https://image.xinwei.ltd/41706281090282.png)\n\n### 5. 显示当前数据库下的表\n```terminal\nmysql> show tables;\n```\n![mysql命令](https://image.xinwei.ltd/51706281128267.png)\n\n### 6. 创建表&查看表字段信息\n```terminal\nmysql> create table user(id INT, name VARCHAR(10)); // 创建表\nmysql> desc user; // 查看表信息\n```\n![mysql命令](https://image.xinwei.ltd/61706281189169.png)\n\n### 7. 删除表\n```terminal\nmysql> drop table account;\n```\n![mysql命令](https://image.xinwei.ltd/71706281234343.png)\n\n\n","这里介绍mysql数据库的基本指令，以及对应的指令截图，供大家作为参考。","https://image.xinwei.ltd/11706280926072.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":560,"name":562,"parentId":85},[],"mysql",{"id":1737,"createdAt":1738,"title":1739,"content":1740,"summary":1741,"image":15,"uid":136,"user":1742,"categoryId":85,"category":1743,"subCategoryId":47,"subCategory":1744,"comments":1745,"status":9,"reason":15,"notice":15,"visitCount":831,"commentCount":112,"keywords":1746},40,"2024-01-30T04:57:45.62Z","UIView animation动画、AvAudioPlayer声效、定时器timer","声效：\n```\nNSString *mp3String=[[NSBundle mainBundle]pathForResource:@\"beauty_ok2\" ofType:@\"mp3\"];\n    NSURL *url=[NSURL fileURLWithPath:mp3String];\n    NSData *data=[[NSData alloc]initWithContentsOfURL:url];\n    _audioPlayer=[[AVAudioPlayer alloc]initWithData:data error:nil];\n```\nUIView animation动画:\n```\n[UIView animateWithDuration:1.2 animations:^{\n        for(int i=0; i\u003C12; i++)\n        {\n            self.starImageView = (UIImageView *)[self.view viewWithTag:STARIMAGEVIEWTAG+i];\n            self.starImageView.transform = CGAffineTransformMakeRotation(3.0);\n            self.starImageView.alpha = 0;\n            [_audioPlayer prepareToPlay];\n            [_audioPlayer play];\n            \n        }\n    } completion:^(BOOL finished) {\n        for(int i=0; i\u003C12; i++)\n        {\n            self.starImageView = (UIImageView *)[self.view viewWithTag:STARIMAGEVIEWTAG+i];\n            [self.starImageView removeFromSuperview];\n        }\n    }];\n```\n\n定时器timer:\n```\nstarsAnimationTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doStarsAnimation) userInfo:nil repeats:NO];\n[starsAnimationTimer invalidate];\n    starsAnimationTimer = nil;\n```","一段播放生效、简单动画和定时器的代码，仅供参考。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios animation,ios动画,ios定时器",{"id":1748,"createdAt":1749,"title":1750,"content":1751,"summary":1752,"image":1753,"uid":597,"user":1754,"categoryId":85,"category":1755,"subCategoryId":100,"subCategory":1756,"comments":1757,"status":9,"reason":15,"notice":15,"visitCount":1680,"commentCount":112,"keywords":1758},184,"2026-03-08T09:49:17.464Z","Flutter高频面试题（15道）","这篇文章介绍一下Flutter面试题，方便大家在金三银四的求职季作为参考，具体内容大家可以自行判断哈。\n\n## 1. Flutter 的核心架构分为哪几层？\n解答：Flutter 架构从下到上分为三层：\n- 引擎层（Engine）：由 C/C++ 实现，包含 Skia 图形引擎、Dart 运行时、文本渲染等核心能力；\n- 框架层（Framework）：由 Dart 实现，包含 Widget、RenderObject、Element 等核心组件，是开发者直接接触的层；\n- 应用层（App）：开发者基于框架层编写的业务代码。\n\n## 2. Widget、Element、RenderObject 的关系是什么？\n解答：\n- Widget：是配置信息的不可变描述（蓝图），描述 UI 长什么样，本身不参与渲染；\n- Element：是 Widget 的实例化对象，连接 Widget 和 RenderObject，保存状态并管理生命周期；\n- RenderObject：负责实际的布局、绘制和事件处理，是渲染树的核心。\n关系：Widget 生成 Element，Element 关联对应的 RenderObject，一个 Widget 可以对应多个 Element（如复用场景）。\n\n## 3. Flutter 中 StatefulWidget 和 StatelessWidget 的区别？\n解答：\n- StatelessWidget：无状态组件，内部无可变状态，数据由父组件传入或固定，构建后不可变，适合静态 UI（如文本、图标）；\n- StatefulWidget：有状态组件，包含 State 类存储可变状态，状态变化时调用 setState() 触发重建，适合动态 UI（如输入框、列表）。\n\n## 4. setState 的原理是什么？\n解答：调用 setState() 时，Flutter 会标记当前 State 为 “脏”（dirty），并将其加入待更新队列；\n\n在下一帧（Frame）时，Flutter 遍历脏状态列表，调用 State 的 build() 方法重建 Widget 树，最终更新 RenderObject 完成 UI 刷新；\n\n注意：setState() 必须在 State 挂载后调用（mounted = true），否则会报错。\n\n## 5. Flutter 中 Key 的作用是什么？常用的 Key 有哪些？\n解答：\n作用：帮助 Flutter 在 Widget 树重建时识别新旧 Widget 的对应关系，避免错误复用状态；\n常用 Key：\n- ValueKey：基于字符串 / 数字等值标识（如 ValueKey(\"user1\")）；\n- ObjectKey：基于对象实例标识；\n- UniqueKey：生成唯一标识，适合临时 Widget；\n- GlobalKey：跨 Widget 访问状态 / 节点（如获取输入框焦点）。\n\n## 6. Flutter 中的生命周期有哪些关键阶段？\n解答：StatefulWidget 的 State 生命周期：\n- 创建：createState() → initState()（初始化状态，只调用一次）；\n- 构建：didChangeDependencies()（依赖变化时调用）→ build()（构建 UI）；\n- 更新：didUpdateWidget()（父组件传参变化时调用）→ build()；\n- 销毁：deactivate()（组件被移除）→ dispose()（释放资源，如定时器、网络连接）。\n\n## 7. Flutter 中如何实现页面路由跳转？\n- 基本跳转（无返回值）：\n```dart\nNavigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));\n```\n\n- 跳转并接收返回值：\n```dart\nNavigator.push\u003CString>(context, MaterialPageRoute(builder: (context) => SecondPage()))\n  .then((result) {\n    // 处理返回值\n  });\n```\n\n- 返回上一页：\n```dart\nNavigator.pop(context, \"返回值\"); // 可携带返回值\n```\n\n- 命名路由：提前在 MaterialApp 配置 routes，通过 Navigator.pushNamed(context, \"/second\") 跳转。\n\n## 8. Flutter 中的 InheritedWidget 作用是什么？\n解答：\n`InheritedWidget` 是 Flutter 中实现跨组件状态共享的核心组件（如 Theme、MediaQuery 底层都是它）；\n\n子组件可通过 of() 方法向上查找最近的 InheritedWidget 实例，获取共享数据；\n\n当 InheritedWidget 数据变化时，依赖它的子组件会自动重建，实现高效的状态传递（比 setState 跨组件传递更优）。\n\n## 9. 常用的状态管理方案有哪些？各自的优缺点？\n解答：\n![](https://image.xinwei.ltd/image1772962801247.png)\n\n## 10. Flutter 中 ListView 的优化方式有哪些？\n解答：\n- 使用 ListView.builder（懒加载），而非直接 ListView(children: [...])（一次性构建所有子项）；\n- 设置 itemExtent（固定子项高度），减少布局计算；\n- 避免在 itemBuilder 中创建新对象（如 TextStyle），抽离为常量；\n- 长列表使用 AutomaticKeepAliveClientMixin 保持列表项状态；\n- 超大列表可使用 flutter_list_view 等第三方库优化滑动性能。\n\n## 11. Flutter 中如何处理网络请求？常用库有哪些？\n解答：\n- 核心库：dio（最常用）、http（官方轻量库）；\n- 示例（dio）：\n```dart\nfinal dio = Dio();\ntry {\n  final response = await dio.get(\"https://api.example.com/data\");\n  if (response.statusCode == 200) {\n    print(response.data);\n  }\n} catch (e) {\n  print(\"请求失败：$e\");\n}\n```\n- 优化：封装请求拦截器（添加 token、日志）、响应拦截器（统一处理错误）、取消请求等。\n\n## 12. Flutter 中如何实现本地存储？\n解答：\n- 轻量存储（键值对）：shared_preferences，适合存储用户配置、token 等；\n- 本地数据库：sqflite（SQLite），适合结构化数据（如本地列表、缓存）；\n- 文件存储：path_provider 获取本地路径 + Dart IO 操作，适合存储大文件（如图片、视频）。\n\n## 13. Flutter 与原生（Android/iOS）如何交互？\n解答：\n- Flutter 调用原生：通过 MethodChannel 传递方法名和参数，原生端实现对应方法；\n- 原生调用 Flutter：同样通过 MethodChannel 主动向 Flutter 发送消息；\n- 示例（Flutter 调原生）：\n```dart\nfinal channel = MethodChannel(\"com.example/channel\");\nString result = await channel.invokeMethod(\"getDeviceInfo\");\n```\n\n## 14. Flutter 中热重载（Hot Reload）和热重启（Hot Restart）的区别？\n解答：\n- 热重载：仅重新编译修改的 Dart 代码，保留当前应用状态（如输入框内容、页面跳转状态），速度快，适合调试 UI / 逻辑；\n- 热重启：重新编译所有 Dart 代码，重置应用状态（回到初始页面），速度稍慢，适合修改了全局状态、路由配置等场景；\n- 注意：原生代码（Android/iOS）修改后，无法通过热重载 / 重启生效，需重新运行。\n\n## 15. Flutter 中如何处理屏幕适配？\n解答：\n基于屏幕尺寸 / 像素比封装适配工具类（如按设计稿宽度等比缩放）；\n使用 MediaQuery 获取屏幕宽高、像素比：\n```dart\nfinal screenWidth = MediaQuery.of(context).size.width;\nfinal screenHeight = MediaQuery.of(context).size.height;\n```\n- 第三方库：flutter_screenutil（主流）、getx 内置的适配方法。\n\n\n以上暂时先总结这么多。\n\n","这篇文章介绍一下Flutter面试题，方便大家在金三银四的求职季作为参考，具体内容大家可以自行判断哈。","https://image.xinwei.ltd/image1772962801247.png",{"phone":599,"userId":597,"nickName":600,"vipType":9,"avatar":601,"sign":15,"createdAt":602},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"flutter面试题, flutter高频面试, flutter面试, flutter开发, 面试题",{"id":1711,"createdAt":1760,"title":1761,"content":1762,"summary":1763,"image":15,"uid":136,"user":1764,"categoryId":85,"category":1765,"subCategoryId":419,"subCategory":1766,"comments":1767,"status":9,"reason":15,"notice":15,"visitCount":1680,"commentCount":112,"keywords":1768},"2024-01-30T11:46:20.178Z","react-native 相关的命令","1. React Native Clear Cache: [https://gist.github.com/jmoses89/c2e4786fd342b3444f3bc6beff32098d#file-react-native-clear-cache](https://gist.github.com/jmoses89/c2e4786fd342b3444f3bc6beff32098d#file-react-native-clear-cache)\n\n```\nwatchman watch-del-all \nrm -rf node_modules\nnpm cache clean \nnpm install\nnpm start --reset-cache\n\n```\n\n2. 在0.42的react native中加入0.43的新list\n参考：[https://hackernoon.com/react-native-new-flatlist-component-30db558c7a5b](https://hackernoon.com/react-native-new-flatlist-component-30db558c7a5b)\n\n> The only caveat here is that you need to actually get the source code for the new React Native components in order to use them. They currently exist only on master, so here’s a quick script that will download them straight into your project (assuming you’ve already installed react-native using npm or Yarn). Copy this whole command into your terminal:\n\n```\nmkdir -p node_modules/react-native/Libraries/Lists/ && \\ for file in 'FlatList' 'MetroListView' 'SectionList' 'VirtualizedList' 'VirtualizedSectionList' 'ViewabilityHelper' 'VirtualizeUtils'; \\ do curl https://raw.githubusercontent.com/facebook/react-native/master/Libraries/Lists/${file}.js > node_modules/react-native/Libraries/Lists/${file}.js; \\ done\n```","React Native使用过程中可能使用到的一些命令，作为记录。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":419,"name":421,"parentId":85},[],"react native,react native命令,react native经验",{"id":1128,"createdAt":1770,"title":1771,"content":1772,"summary":1773,"image":15,"uid":136,"user":1774,"categoryId":85,"category":1775,"subCategoryId":242,"subCategory":1776,"comments":1777,"status":9,"reason":15,"notice":15,"visitCount":1778,"commentCount":112,"keywords":1779},"2025-06-06T09:53:36.648Z","git仓库迁移操作-git mirror","我们在开发过程中，有时候会遇到要迁移代码仓库的场景，比如代码之前托管在github上，但是想要迁移到其他的托管平台（像阿里的云效、腾讯的e-coding、公司的gitlab等），或者从一个仓库迁移到同托管平台的另一个仓库repository，并且要保持将所有的提交记录、标签等内容都同步过去，是可以通过git自带的命令实现的。\n\n这篇文章简要描述一下如何开始做代码的迁移。\n\n1. 已有某一个git仓库，比如我们把这个仓库叫仓库A，它的git clone地址假设为：\n```\nhttps形式：https://github.com/A.git\nssh形式：git@github.com:XXX/A.git\n```\n\n需要将仓库A迁移到仓库B.\n\n2. 新建仓库B\n在你想要迁移到的托管平台上，新建一个仓库，切记以下内容：\n> 新建仓库时不要选择添加 README、.gitignore 或许可协议，确保仓库为空\n\n仓库B的地址假设为：\n```\nhttps形式：https://github.com/B.git\nssh形式：git@github.com:XXX/B.git\n```\n\n3. 克隆仓库A的裸代码\n裸仓库bare repository中包含了所有git对象和历史记录，但是没有工程文件。\n\n选择一个文件夹位置，比如在桌面上新建一个文件夹:git_dir\n```terminal\ncd ~/Desktop # 进入到桌面\nmkdir git_dir # 创建文件夹git_dir\n```\n\n然后终端进入到这个文件夹中，执行克隆裸仓库.\n\n```terminal\ngit clone --bare git@github.com:XXX/A.git # 这里的git地址是仓库A的git地址\n```\n\n生成的裸仓库就是一个.git为扩展名的文件夹.\n\n4. 进入.git的裸仓库文件夹中\n```terminal\ncd A.git # 进入生成的以.git扩展名的文件夹中\n```\n\n5. 推送裸仓库到仓库B\n```terminal\ngit push --mirror git@github.com:XXX/B.git # 这里的git地址就是你需要的新仓库git地址\n```\n\n> --mirror参数可以将所有分支、标签和远程引用全部推送到目标仓库\n\n6. 结束\n上面的几个步骤执行完成后，就可以登录到仓库B，看仓库B中的代码、标签、分支、引用等都完整的从仓库A中拷贝过来了。\n\n希望对有的同学有帮助，到此结束。\n\n\n","这篇文章简要介绍一下如何进行git仓库的迁移，比如从github上完全克隆到gitlab上，指的是如何从一个托管平台，迁移到另一个托管平台。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":242,"name":244,"parentId":85},[],42,"git mirror,git clone --bare,bare repository,git bare,git clone,git仓库,git迁移",{"id":1781,"createdAt":1782,"title":1783,"content":1784,"summary":1785,"image":15,"uid":136,"user":1786,"categoryId":85,"category":1787,"subCategoryId":47,"subCategory":1788,"comments":1789,"status":9,"reason":15,"notice":15,"visitCount":1737,"commentCount":112,"keywords":1790},52,"2024-01-30T15:08:00.821Z","addChildViewController后 Childvc viewWillAppear 不调用的问题","设置\n```\n[childVC beginAppearanceTransition:YES animated:YES]\n```\n即可\n\n移除的时候要这样 ：\n```\n[childController beginAppearanceTransition:NO animated:YES];\n[childController.view removeFromSuperview];\n[childController endAppearanceTransition];\n```\n\nviewWillDisappear和viewDidDisappear才会被调用","iOS中当在父控制器中添加子控制器时，子控制器的viewWillAppear并没有被调用的问题。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios控制器,添加子控制器,ios父子控制器",{"id":7,"createdAt":1792,"title":1793,"content":1794,"summary":1795,"image":15,"uid":1796,"user":1797,"categoryId":9,"category":1801,"subCategoryId":644,"subCategory":1802,"comments":1803,"status":9,"reason":15,"notice":15,"visitCount":1737,"commentCount":112,"keywords":1804},"2024-01-10T14:33:36.361Z","30岁大龄女青年的人生焦虑","心之所向，情之所思，神之所往，向之本型，思之可能，往之不能，叶生叶落，花开花谢，有果之结果，有子之播籽，根叶茎果皆得妙用，此谓天生其材皆有其道，亦有其造化。\n\n成林者成林，成木者成木，秀花者修花，结果者得果，望籽者蓄子。蓄子欲得果，得果思修花，修花望成木，成木念成林，故生怨念，怼记恨。子怪其无果，果愤其花羞，花感其叶弱，叶究其木棉，木责其根惰。相合乃得相生，相责怎得相合？\n\n星月晦明，四时更替，有春而发生者，有春之泯落者，有夏之盛发者，有夏之始发者，有秋之广收者，有秋之始花者，有冬之冰藏者，有冬之吐芳者，冬者继，春者替，后又为之。尊规守道，蓄力得时即可发也，无规乱道，倍力错时而不知也。各司其职，各守其时，方大道不乱，规大运也。\n\n吾生贫地乃无名之草，春见少壮，夏遇秀花，秋贺丰收，冬则枯槁根寂于地下，此一世也。念少壮之陪伴，观修花之容颜，闻秋收之喜悦，感沃土之不弃，人生四得也。冬之不僵乃恩于树木之师友温护，造就二世三世也。继而春发也！\n\n法天、法地、寻道、问理，世界之大俯仰之间，所遇所见皆是造化。而今已近而立之年，足下无一立锥之地，业界无一波澜之声，踌躇、彷徨、茕茕孑立、踽踽独行，恐误入歧途。\n\n虽自知己身愚钝无能法天地之外像，亦无外乎先贤俊才之理法，只得一则察观天地之宗法意象，二则力效先贤之理法心源，为此生立言，立行，立身之所托。虽诸多纰漏，若恐一丝效用，乃天地德才眷顾，当拜天地滋养，谢先贤教化，克己复行，不敢自持。\n\n亦当翘首以勤思贤之心，悟习六法之要，望尽绵薄之一二，当不妄此世之虚行。愧未有自心之体悟，粗笔一篇，聊以自娱。\n","心之所向，情之所思，神之所往，向之本型，思之可能，往之不能，叶生叶落，花开花谢，有果之结果，有子之播籽，根叶茎果皆得妙用，此谓天生其材皆有其道，亦有其造化。 成林者成林，成木者成木，秀花者修花，结果者得果，望籽者蓄子。蓄子欲得果，得果思修花，修花望成木，成木念成林，故生怨念，怼记恨。子怪其无果，果愤其花羞，花感其叶弱，叶究其木棉，木责其根惰。相合乃得相生，相责怎得相合？ 星月晦明，四时更替，有春而",502827247546437,{"phone":1798,"userId":1796,"nickName":1799,"vipType":9,"avatar":14,"sign":15,"createdAt":1800},"15132429769","棉花糖","2024-01-10T14:29:20.484Z",{"id":9,"name":604},{"id":644,"name":646,"parentId":9},[],"30岁,大龄女青年,人生焦虑",{"id":1806,"createdAt":1807,"title":1808,"content":1809,"summary":1810,"image":15,"uid":136,"user":1811,"categoryId":85,"category":1812,"subCategoryId":85,"subCategory":1813,"comments":1814,"status":9,"reason":15,"notice":15,"visitCount":257,"commentCount":112,"keywords":1815},25,"2024-01-26T15:29:22.58Z","Javascript进阶知识","# 变量、内存、箭头函数\n## 1. let、var、const - 变量提升\nvar：有变量提升；\nlet：1. 没有变量提升；2.同一个作用域内不能重复定义同一个名称；3.有着严格的作用域。\nconst：声明只读的常量。1: 声明的时候就要赋值【const name; // 报错 】；2. 同样没有变量提升，也有严格的作用域；3. 在声明数组或者Object这样的引用对象时，只能保证指针是固定的\n```xxx.js\n// 1. 变量提升\nconsole.log(x)\nvar x = 10;\n\n// 其解析过程为：\nvar x;\nconsole.log(x); // 所以此处x为undefined\nx = 10;\n\n\n// 如果是let\nconsole.log(y); // 会报错，找不到变量y\nlet y = 10;\n\n// 2. 作用域\nfunction demo_var() {\n    var num = 10;\n    if (true) {\n        var num = 20;    \n    }\n    console.log(num); // 为20\n}\n\nfunction demo_let() {\n    let num = 10;\n    if (true) {\n        let num = 20;    \n    }\n    console.log(num); // 为10\n}\n\n// 3. 在循环中的表现\n// let 局部作用域\nvar arr_let = [];\nfor(let i = 0; i \u003C 5; i++) {\n    arr_let[i] = function() {\n        console.log(i)    \n    }\n}\narr_let[2](); // 打印2\n\n// var 变量提升\nvar arr_var = [];\nfor(var i = 0; i \u003C 5; i++) {\n    arr_var[i] = function() {\n        console.log(i)    \n    }\n}\narr_var[3](); // 打印5\n\n```\n\n## 2. 栈内存和堆内存\n栈内存：javascript中的5种基本类型：Undefined、Null、Boolean、Number和String，都是直接存储在栈中的。\n\n堆内存：javascript中的其他几种类型：对象Object、数组Array、函数Function等引用类型，都是通过拷贝或者new出来的，这样的数据存储在堆内存中。但引用类型的数据的地址是存储在栈中的。\n\n在访问引用类型数据时，先从栈中获取对象的地址指针，再通过地址指针找到堆内存中的数据。\n```js\nvar obj = {\n    id: 1\n};\nvar obj1 = obj;\nobj1.id = 2; // obj中的id变为2\n\n```\n\n## 3. 箭头函数\n```js\n// 变量声明和使用\nfunction fn1(v) {\n    console.log(v);\n}\nfn1(1234);\n\n// 变量表达式\nvar fn2 = function(v) {\n    console.log(v);\n}\nfn2(1234);\n\n// 简化成箭头函数\nfn = v => console.log(v);\n\n// 写法\nvar arr = [1,2,3,4,5,6];\nconst changedArr = arr.map(function(item, index){\n    return item + 1;\n});\n// 改成如下\nconst changedArr = arr.map((item, index) => {\n    return item + 1;\n});\n// 或者\nconst changedArr = arr.map(item => item + 1);\n\n// Additions\nconst fn2 = (x, y, z) => ({id: x, name: y, age: z}); // 当箭头函数返回的是一个对象时，需要加（）括起来\n\n```\n\n注意点：\n1.箭头函数不能作为构造函数，不可以使用new；\nes5中没有class类的概念，是通过函数实现的，es6才有。\n构造函数可以理解为是用来生成模版的函数。\n箭头函数中没有this.\n```js\nfunction Person(name, age) {\n    this.name = name;\n    this.age = age;\n}\nvar xiaoming = new Person('xiaoming', 10);\n\n// 箭头函数\nvar Person = (name) => {\n    this.name = name;\n}\nvar xiaogang = new Person('xiaogang'); // Person is not a constructor.\n\n```\n\n2.箭头函数没有原型对象\n构造函数自身的属性和方法是没有办法进行共享的，原型对象的属性和方法是可以被所有实例对象共享的。\n\n原型对象可以理解为是一个父对象，意味着其属性和方法可以被子类继承。\n```js\nfunction Person(name, age) {\n    this.name = name;\n    this.age = age;\n}\nPerson.prototype.sayHello = function() {\n    console.log('sayHello');\n}\nvar xiaoming = new Person('xiaoming', 10);\nxiaoming.sayHello();\n\n```\n\n3. 箭头函数中不可以使用arguments对象，该对象在函数体内不存在，可以使用rest\n```js\nvar fun = () => {\n    console.log(arguments[1]); // arguments is not defined.\n}\nfun(1, 2, 3);\n```\narguments是参数集合，但不等同于数组.\n\n```js\nvar fun = (...args) => { // 这里的...和【...扩展运算符】是不同的概念，正好相反\n    return args.length; // 这里的args就是数组\n}\nfun(0, 1, 2, 3); // 4\n```\n\n4. this指向，由于箭头函数不绑定this，所以它会捕获其所在上下文的this的值，作为自己的this值。\n\n\n\n\n","Javascript中的变量提升概念以及作用域，如何通过文中案例进行理解，堆内存和栈内存的知识以及变量存储说明，如何理解箭头函数，还有this指向问题。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":85,"name":1484,"parentId":85},[],"javascript,js,js var",{"id":1817,"createdAt":1818,"title":1819,"content":1820,"summary":1821,"image":15,"uid":136,"user":1822,"categoryId":85,"category":1823,"subCategoryId":144,"subCategory":1824,"comments":1825,"status":9,"reason":15,"notice":15,"visitCount":560,"commentCount":112,"keywords":1826},41,"2024-01-30T10:33:42.28Z","在cordova加壳只给链接的情况下上传文件问题","在页面中，点击上传文件的按钮，提示“没有应用可以来打开”；\n\n那么就检查cordova项目中是否有file_upload插件，直接给项目加入该插件；\n\n```\ncordova plugin add cordova-plugin-file-transfer\n```\n\n如果还不行，那就检查config.xml文件中的设置。","有时候在cordova的项目中，上传按钮点击会没有反应，可能是上传插件的配置有问题。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"cordova,hybrid项目,cordova项目",{"id":1828,"createdAt":1829,"title":1830,"content":1831,"summary":1832,"image":1833,"uid":136,"user":1834,"categoryId":85,"category":1835,"subCategoryId":85,"subCategory":1836,"comments":1837,"status":9,"reason":15,"notice":15,"visitCount":404,"commentCount":9,"keywords":1838},158,"2025-04-23T10:57:30.572Z","css效果记录-渐变边框，内容是透明或者高斯模糊","这篇文章用来记录我在开发中需要实现的一种css效果，效果是一个div，有渐变颜色的边框，但是中间内容区域是透明或者高斯模糊效果。作为记录，也可以供大家参考。\n\n## 最终效果\n首先我们来看看最后实现的效果图：\n![css gradient border with blur image](https://image.xinwei.ltd/image1745404358859.png)\n\n上面的效果，需要我们设置相关的css属性，所以我会将我尝试的效果思路列举一下，每一个尝试也是一种类似的css效果，也作为记录。\n\n> 最终效果的实现，在最后一步。\n\n## 尝试: 将background分为border box和padding-box\n这种方式的思路就是一个div，可以将background分为border和padding的2种box，当然还有content box，那么针对每一个box可以设置颜色渐变。\n\n效果是：\n![css gradient border](https://image.xinwei.ltd/image1745404759607.png)\n\n> 缺陷是padding-box不能设置为透明transparent，因为这种渐变是border包裹范围内background颜色渐变了，`border-box`包裹范围内的渐变像是盖在上面。所以如果`padding-box`是透明或者blur高斯模糊，那么就会漏出全部的`border-box`的渐变效果。\n\n这种效果的测试代码是：\n```test_1.html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" />\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    \u003Ctitle>test-1\u003C/title>\n  \u003C/head>\n  \u003Cbody>\n    \u003Cdiv class=\"gradient-border\">\u003C/div>\n  \u003C/body>\n  \u003Cstyle>\n    body {\n      background: rgb(33, 213, 168);\n    }\n    .gradient-border {\n      border-radius: 20px;\n      border: 10px solid transparent; // border渐变，必须要设置border宽度和颜色transparent.\n\n      width: 400px;\n      height: 200px;\n\n      background: linear-gradient(orange) padding-box,\n        linear-gradient(blue, green) border-box;\n\n      overflow: hidden;\n      /* clip: rect(0px, 400px, 200px, 0px); */\n    }\n  \u003C/style>\n\u003C/html>\n```\n\n所以这种方式，只能用在`padding-box`区域中，不显示底下内容的场景。\n\n\n## 高斯模糊效果\n这里简单实现一个高斯模糊效果，因为在我们的最终效果中，需要有高斯模糊，那么我们可以先看看如何实现高斯模糊效果，根据代码来发散思维。\n\n高斯模糊效果是：\n![css blur effect image](https://image.xinwei.ltd/image1745405113368.png)\n\n测试代码是：\n```test_2.html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" />\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    \u003Ctitle>Document\u003C/title>\n  \u003C/head>\n  \u003Cbody>\n    \u003Cdiv class=\"background\" id=\"divB\">\n      \u003C!-- This is Div B -->\n      \u003Ch1>This is Div B\u003C/h1>\n    \u003C/div>\n\n    \u003Cdiv class=\"overlay\" id=\"divA\">\n      \u003C!-- This is Div A -->\n      \u003Cp>This is Div A content\u003C/p>\n    \u003C/div>\n  \u003C/body>\n  \u003Cstyle>\n    body {\n      margin: 0;\n      padding: 0;\n      height: 100vh;\n      overflow: hidden;\n    }\n\n    .background {\n      position: absolute;\n      width: 100%;\n      height: 100%;\n      background: url(\"https://picsum.photos/800/600\") no-repeat center/cover; // 使用一个随机底图\n      z-index: 0;\n    }\n\n    .overlay {\n      position: absolute;\n      top: 100px;\n      left: 100px;\n      width: 300px;\n      height: 200px;\n      padding: 20px;\n      z-index: 1;\n\n      border-radius: 15px;\n\n      // 高斯模糊效果\n      backdrop-filter: blur(10px);\n      -webkit-backdrop-filter: blur(10px);\n\n      color: white;\n      border: 10px solid rgba(255, 255, 255, 0.3);\n    }\n  \u003C/style>\n\u003C/html>\n```\n\n从这里面可以看到高斯模糊效果是`backdrop-filter`来决定的，所以之前的`border-box`和`padding-box`尝试是无法应用到这种css属性设置。\n\n那么可以考虑使用`:before`和`:after`这2个伪标签，分别设置不同的渐变，当然这确实是可以的。\n\n\n## 最终实现代码\n```test.html\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" />\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    \u003Ctitle>Document\u003C/title>\n  \u003C/head>\n  \u003Cbody>\n    \u003Cdiv class=\"background\" id=\"divB\">\n      \u003C!-- This is Div B -->\n      \u003Ch1>This is Div B\u003C/h1>\n    \u003C/div>\n\n    \u003Cdiv class=\"glass-border\" id=\"divA\">\n      \u003C!-- This is Div A -->\n      \u003Cp>This is Div A content\u003C/p>\n    \u003C/div>\n  \u003C/body>\n  \u003Cstyle>\n    body {\n      margin: 0;\n      padding: 0;\n      height: 100vh;\n      overflow: hidden;\n    }\n\n    .background {\n      position: absolute;\n      width: 100%;\n      height: 100%;\n      background: url(\"https://picsum.photos/800/600\") no-repeat center/cover; // 也用一个随机图做底色\n      z-index: 0;\n    }\n\n    .glass-border {\n      position: absolute;\n      top: 100px;\n      left: 100px;\n      width: 300px;\n      height: 200px;\n      padding: 20px;\n      z-index: 1;\n\n      background: transparent;\n\n      color: white;\n\n      border-radius: 15px;\n      position: relative;\n    }\n\n    .glass-border::before { // border渐变色的处理\n      content: \"\";\n      position: absolute;\n      top: -2px; // 内容是2px的border宽度\n      left: -2px;\n      right: -2px;\n      bottom: -2px;\n      z-index: -1; // 中间层\n      border-radius: inherit;\n      padding: 2px;\n\n      background: linear-gradient(135deg, #ff00cc, #3333ff); // 作为border的渐变颜色\n      mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); // 使用mask将叠加效果的中间部分给透明掉\n      -webkit-mask: linear-gradient(#fff 0 0) content-box,\n        linear-gradient(#fff 0 0);\n      mask-composite: exclude;\n      -webkit-mask-composite: destination-out;\n    }\n\n    .glass-border::after { // 高斯模糊的处理\n      content: \"\";\n      position: absolute;\n      top: 0; // 直接是div A的区域设置为高斯模糊\n      left: 0;\n      right: 0;\n      bottom: 0;\n      border-radius: inherit;\n      backdrop-filter: blur(10px);\n      -webkit-backdrop-filter: blur(10px);\n      z-index: -2; // 层级置于底部\n    }\n  \u003C/style>\n\u003C/html>\n```\n\n用于记录，也希望对大家有帮助。\n\n\n\n","这篇文章用来记录我在开发中需要实现的一种css效果，效果是一个div，有渐变颜色的边框，但是中间内容区域是透明或者高斯模糊效果。作为记录，也可以供大家参考。","https://image.xinwei.ltd/image1745404358859.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":85,"name":1484,"parentId":85},[],"css gradient border,css blur,gradient border,blur effect,blur,渐变边框,高斯模糊,CSS效果",{"id":1677,"createdAt":1840,"title":1841,"content":1842,"summary":1843,"image":15,"uid":136,"user":1844,"categoryId":85,"category":1845,"subCategoryId":47,"subCategory":1846,"comments":1847,"status":9,"reason":15,"notice":15,"visitCount":404,"commentCount":112,"keywords":1848},"2024-01-30T14:29:02.459Z","修改iOS工程所有target的版本号或者build version.","使用agvtool命令，可以对target的version和build号进行修改。\n```terminal\n//更新build version使用\nagvtool new-version -all xxxx\n// 更新version使用\nagvtool new-marketing-version x.x.x\n```","修改iOS原生工程的target版本号或者build号，通过agvtool命令可以实现对所有target得修改。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios工程,ios版本号,ios build,ios target",{"id":1664,"createdAt":1850,"title":1851,"content":1852,"summary":1853,"image":15,"uid":136,"user":1854,"categoryId":85,"category":1855,"subCategoryId":47,"subCategory":1856,"comments":1857,"status":9,"reason":15,"notice":15,"visitCount":312,"commentCount":112,"keywords":1858},"2024-01-30T14:33:57.186Z","UITabController中的TabbarItem的配置","很早之前的代码，摘取出来作为记录。\n\n```WKTabBarController.m\n@implementation WKTabBarController\n\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n    [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:\n                                                       UIColorFromRGBA(0x828282, 1), NSForegroundColorAttributeName,\n                                                       nil] forState:UIControlStateNormal];\n    [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:\n                                                       UIColorFromRGBA(0x23a478, 1), NSForegroundColorAttributeName,\n                                                       nil] forState:UIControlStateSelected];\n    \n    //待接单\n    WKPendingOrdersViewController *pendingOrders = [[WKPendingOrdersViewController alloc] init];\n    pendingOrders.tabBarItem.title = @\"待接单\";\n    pendingOrders.tabBarItem.imageInsets = edgeInsets;\n    pendingOrders.tabBarItem.image = [UIImage imageNamed:@\"pending_order_noSelect\"];\n    UIImage *pendingOrdersSelectedImg = [UIImage imageNamed:@\"pending_order_selected\"];\n    pendingOrders.tabBarItem.selectedImage = [pendingOrdersSelectedImg imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];\n    WKBaseNavigationController *pendingOrdersNav = [[WKBaseNavigationController alloc] initWithRootViewController:pendingOrders];\n    \n    //待配送\n    WKPendingDeliverViewController *pendingDeliver = [[WKPendingDeliverViewController alloc] init];\n    pendingDeliver.tabBarItem.title = @\"待配送\";\n    pendingDeliver.tabBarItem.imageInsets = edgeInsets;\n    pendingDeliver.tabBarItem.image = [UIImage imageNamed:@\"pending_deliver_noSelect\"];\n    UIImage *pendingDeliverSelectedImg = [UIImage imageNamed:@\"pending_deliver_selected\"];\n    pendingDeliver.tabBarItem.selectedImage = [pendingDeliverSelectedImg imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];\n    WKBaseNavigationController *pendingDeliverNav = [[WKBaseNavigationController alloc] initWithRootViewController:pendingDeliver];\n    \n    //已处理\n    WKFinishedViewController *finished = [[WKFinishedViewController alloc] init];\n    finished.tabBarItem.title = @\"已配送\";\n    finished.tabBarItem.imageInsets = edgeInsets;\n    finished.tabBarItem.image = [UIImage imageNamed:@\"finished_noSelect\"];\n    UIImage *finishedSelectedImg = [UIImage imageNamed:@\"finished_selected\"];\n    finished.tabBarItem.selectedImage = [finishedSelectedImg imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];\n    WKBaseNavigationController *finishedNav = [[WKBaseNavigationController alloc] initWithRootViewController:finished];\n    \n    //管理\n    WKManageViewController *manage = [[WKManageViewController alloc] init];\n    manage.tabBarItem.title = @\"管理\";\n    manage.tabBarItem.imageInsets = edgeInsets;\n    manage.tabBarItem.image = [UIImage imageNamed:@\"manage_noSelect\"];\n    UIImage *manageSelectedImg = [UIImage imageNamed:@\"manage_selected\"];\n    manage.tabBarItem.selectedImage = [manageSelectedImg imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];\n    WKBaseNavigationController *manageNav = [[WKBaseNavigationController alloc] initWithRootViewController:manage];\n    \n    self.viewControllers = @[pendingOrdersNav, pendingDeliverNav, finishedNav, manageNav];\n}\n\n- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item{\n    NSLog(@\"item title is %@\", item.title);\n}\n```","在iOS的UITabController中的TabbarItem，进行选中和非选中时，设置不同的文字颜色代码。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios tab,ios tabcontroller,ios tabbaritem,tabbaritem",{"id":1860,"createdAt":1861,"title":1862,"content":1863,"summary":1864,"image":15,"uid":136,"user":1865,"categoryId":85,"category":1866,"subCategoryId":47,"subCategory":1867,"comments":1868,"status":9,"reason":15,"notice":15,"visitCount":1860,"commentCount":112,"keywords":1869},35,"2024-01-29T15:16:09.144Z","iOS内存相关知识还有二进制重排的介绍","## 二进制重排\n\n*   大型项目用的比较多\n\n1.  iOS内存\n\n*   物理内存：没有办法直接访问物理内存，即内存条的真实地址\n\n*   虚拟内存：LLDB调试如打印内存地址或者真实的内存地址，都是虚拟内存\n\n*   早期的内存都是物理内存，内存不够时，给出内存不够的弹窗\n\n缺点： (所以引入虚拟内存)\n\n*   软件一次性加载到内存中，对内存消耗太大\n*   外挂，检索内存，修改数据，如修改金币数量，跨进程访问数据\n*   每一个进程，都有对应的映射表，映射到物理内存\n\n1.  访问虚拟内存\n2.  虚拟内存映射到物理内存\n3.  访问数据，映射，然后cpu，通过硬件MMU，和操作系统去从虚拟内存到物理内存.\n4.  一个进程对应于一个虚拟页表\n5.  一个虚拟页表有好多段\n6.  进程并不是整个的被载入到内存，只有用户数据正在活跃的数据载入。\n7.  分段载入时，段的大小，涉及到分页，有哪些页正在活跃，就载入到内存\n8.  不可以字节为单位，否则映射表就会比较大，应以页为单位\n9.  页的地址+偏移地址，找到某一个数据\n10. 页的大小，MacOS，Linux，页是4K的大小，iOS一页是16K.\n11. 环境变量PAGESIZE，可以看出页的多少，通过终端.\n12. 以上也是虚拟内存产生的原因\n\n*   用户点开一个app，是一页一页加载\n\n*   进程访问数据的时候，访问的是虚拟页表，如果这个数据通过页表查询，不在物理内存上，那么产生阻塞，阻塞当前进程，叫缺页异常。\n\n*   由操作系统触发。\n\n*   然后到磁盘上查找这一页，加载到物理内存，加载完毕，进程继续。访问进程数据，cpu通过mmu进行地址翻译，得知这一页数据已经加载，有映射关系，通过虚拟内存找到物理内存真实数据，再加载。\n\n*   虚拟内存势必消耗时间，毫秒级别\n\n*   但是速度会很快，用户基本感知不到缺页异常，可以几乎忽略不计\n\n*   概念：页面置换\n\n*   [x] 用户点开某一页，加载到物理内存时，如果物理内存满了，将物理内存中不活跃的页，进行替换.\n\n## 问题：什么样的时刻，缺页异常会同时大量的出现？\n\napp启动的时候：数据加载、方法调用，一定会出现耗时\n\n*   技术层面\n    降低缺页异常次数 -> 启动优化\n\n*   优化方案？\n\n*   合并动态库是一个动态库加载时间减少的方案\n\n*   不加载不必要的页\n\n*   Xcode工程的build settings中有一个设置项：Write Link Map file\n\n*   [x] 编译工程得到product后，可以show in finder去找到LinkMap-normal-arm64.txt文件\n\n*   [x] 文件里面有Symbols的执行顺序，这个顺序跟compile source files的添加顺序有关。\n\n*   [x] 而单个文件中的方法执行顺序，跟代码书写顺序相同。\n\n*   假设：\n\n*   如果启动时，有500个缺页异常，因为启动时刻需要调用的方法分别分布在了这500页数据里面。\n\n*   但是这500页里的数据里，不全都是启动时需要调用的方法.\n\n*   如果，让app链接的顺序，按照启动调用顺序来进行排列，那么启动时必要的方法都会优先执行\n","这里介绍iOS中内存相关的知识，还有二进制重排的原理和逻辑。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios内存,ios底层知识,二进制重排",{"id":1630,"createdAt":1871,"title":1872,"content":1873,"summary":1874,"image":15,"uid":136,"user":1875,"categoryId":85,"category":1876,"subCategoryId":47,"subCategory":1877,"comments":1878,"status":9,"reason":15,"notice":15,"visitCount":729,"commentCount":112,"keywords":1879},"2024-01-30T15:05:16.666Z","iOS Library not loaded And Reason- image not found","> dyld: Library not loaded: @rpath/RxCocoa.framework/RxCocoa\n  Referenced from: /Users/hanweixing/Library/Developer/CoreSimulator/Devices/81C2B877-2B99-4E1D-8CE6-8B58D0FF3B55/data/Containers/Bundle/Application/9A8E6CAB-A04B-4804-BB2F-CA08FF70144C/TapatalkFree.app/TapatalkFree\n  Reason: image not found\n\n可以从stackoverflow上找到类似的issue，发现一个类似的：\n[https://stackoverflow.com/questions/24333981/ios-app-with-framework-crashed-on-device-dyld-library-not-loaded-xcode-6-beta](https://stackoverflow.com/questions/24333981/ios-app-with-framework-crashed-on-device-dyld-library-not-loaded-xcode-6-beta)\n\n摘取其中的一句：\n> In the target's General tab, there is an Embedded Binaries field. When you add the framework there the crash is resolved.\n\n但是这个回答并不适用于所有类似的情况，我的工程中就是由于第三方库在`pod install`的时候，并没有完全download下来，而由于项目中集成的库太多，导致在终端并没有留意其中的提示。\n\n所以我的项目中，由于编译成静态.a库的时候，库并没有编译完整，检查网络环境，确保`pod install`成功，编译完整之后就成功了。","iOS工程集成了n多的第三方库，在编译时，framework在link时发生错误，出现了image not found的报错。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios第三方库,ios编译问题,ios framework,framework",{"id":1778,"createdAt":1881,"title":1882,"content":1883,"summary":1884,"image":15,"uid":136,"user":1885,"categoryId":85,"category":1886,"subCategoryId":47,"subCategory":1887,"comments":1888,"status":9,"reason":15,"notice":15,"visitCount":729,"commentCount":112,"keywords":1889},"2024-01-30T10:36:18.135Z","iOS键盘上的toolBar中_Done_字体改成中文“完成”","这个只需要到iOS工程之中，target—>info.plist中，将\n`Localization native development region`的值English改成zhCN","iOS的工程新建出来后，默认都是English的，所以有的第三方库尤其国外作者的库，当有一些空间标题的显示时，会判断语言环境。所以如果我们需要设置成某一个语言环境，需要在Xcode中进行配置。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios工程,ios国际化,ios english,ios中文",{"id":1154,"createdAt":1891,"title":1892,"content":1893,"summary":1894,"image":1895,"uid":136,"user":1896,"categoryId":85,"category":1897,"subCategoryId":144,"subCategory":1898,"comments":1899,"status":9,"reason":15,"notice":15,"visitCount":644,"commentCount":112,"keywords":1900},"2025-02-28T09:15:46.001Z","css的代码记录 - 文字颜色渐变","记录一下使用到的一些css属性，这次是文字颜色渐变.\n\n文字渐变效果：\n![gradient text color png](https://image.xinwei.ltd/image1740734023088.png)\n\ncss示例代码\n![scss codes](https://image.xinwei.ltd/image1740734012558.png)\n\n```css\n&-title {\n        background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%); // 背景进行渐变\n        -webkit-background-clip: text;  // 背景应用到文字上\n        color: transparent;  // 文字颜色设为透明\n        font-family: Onest;\n        font-size: 50px;\n        font-weight: 900;\n        line-height: 63.75px;\n        text-align: center;\n    }\n```\n\n仅做记录，方便取用代码。","记录一下使用到的一些css属性，这次是文字颜色渐变.","https://image.xinwei.ltd/image1740734023088.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"text gradient color,css text gradient color,css gradient,gradient color",{"id":1902,"createdAt":1903,"title":1904,"content":1905,"summary":1906,"image":15,"uid":136,"user":1907,"categoryId":85,"category":1908,"subCategoryId":47,"subCategory":1909,"comments":1910,"status":9,"reason":15,"notice":15,"visitCount":644,"commentCount":112,"keywords":1911},54,"2024-01-30T15:29:23.804Z","UINavigationBar设置全局导航条风格和颜色","这是一段历史悠久的代码...\n```\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {\n    \n    _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];\n    \n    _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];\n\n    //设置全局导航条风格和颜色\n    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];\n    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];\n    \n    KCFriendViewController *friendController=[[KCFriendViewController alloc]init];\n    UINavigationController *navigationController=[[UINavigationController alloc]initWithRootViewController:friendController];\n    \n    _window.rootViewController=navigationController;\n    \n    [_window makeKeyAndVisible];\n    \n    return YES;\n}\n```","设置iOS的全局导航条风格和颜色的示例代码。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios开发,ios,ios nav,ios color,ios颜色",{"id":1913,"createdAt":1914,"title":1915,"content":1916,"summary":1917,"image":1918,"uid":136,"user":1919,"categoryId":85,"category":1920,"subCategoryId":144,"subCategory":1921,"comments":1922,"status":9,"reason":15,"notice":15,"visitCount":144,"commentCount":112,"keywords":1923},142,"2024-12-02T03:02:55.368Z","FirebaseError: Firebase: Invalid Idp Response: the Google id_token is not allowed to be used with this application. ..","## 一、前言\n在集成firebase来实现Google的授权登录时，有一种场景可能会导致Google授权失败，这篇文章来分享一下我遇到的这个问题。\n\n问题：\n> FirebaseError: Firebase: Invalid Idp Response: the Google id_token is not allowed to be used with this application. Its audience (OAuth 2.0 client ID) is 99914531xxxx-bbbvcr84fckm6is00it6igep9e0vkk57.apps.googleusercontent.com, which is not authorized to be used in the project with project_number: 48135841xxxx. (auth/invalid-credential).\n\n在我的上一篇文章 [https://www.xinwei.ltd/article/141](https://www.xinwei.ltd/article/141) 中，我已经分享了集成firebase的google授权登录的方法，但是可能会有人遇到上面的这个问题。\n\n出现问题的原因，是在于firebase是否自动在开发者的Google console中创建了client ID。另外还有一种情况是，如果开发者自己在Google console平台上新建了多个平台的app，然后这多个平台的app使用同一个firebase的情况下，会出现上面的这个问题。\n\n解决这个问题的方法很简单，就是需要在firebase的Google授权登录配置的地方，新增一个client ID，我截图给大家说明一下。\n\n## 二、解决方案\n### 2.1 firebase console中配置\n如下截图所示，firebase的控制台中已经提供了可以额外添加client ID的地方，这个地方是允许我们加入Google console平台上的client id的，在截图所载位置的地方加上client ID，并且保存后，Google console平台上的app就可以使用这个firebase项目的授权登录了。\n![firebase console](https://image.xinwei.ltd/image1733108254550.png)\n\n如果大家对这个步骤比较迷惑，建议阅读我之前的文章：[https://www.xinwei.ltd/article/141](https://www.xinwei.ltd/article/141)\n\n### 2.2 google console中的client ID\n![google console client id](https://image.xinwei.ltd/image1733108482069.png)\n如截图所示，这里的client id，就是需要复制粘贴到firebase console中的值，按照这个序号操作，你就可以看到这个client id，然后根据我上面给出的文章链接，就可以解决这个问题。\n\n希望对大家有帮助。\n\n\n","在集成firebase来实现Google的授权登录时，有一种场景可能会导致Google授权失败，这篇文章来分享一下我遇到的这个问题。","https://image.xinwei.ltd/image1733108254550.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"FirebaseError,google id_token,Invalid Idp Response,Google id_token is not allowed to be used,OAuth 2.0 client ID,googleusercontent.com,firebase集成,google集成,谷歌授权登录",{"id":1473,"createdAt":1925,"title":1926,"content":1927,"summary":1928,"image":15,"uid":136,"user":1929,"categoryId":85,"category":1930,"subCategoryId":47,"subCategory":1931,"comments":1932,"status":9,"reason":15,"notice":15,"visitCount":144,"commentCount":112,"keywords":1933},"2024-02-02T10:22:17.591Z","再谈iOS中的内存","## 内存\n1.  内存条里面，堆和栈的划分，通过弹性的扩展，一个从高位往地位进行存储，一个从地位往高位进行存储，直到堆和栈相交，形成了堆栈溢出。\n2.  对象存储在堆区\n3.  指针存储在栈区\n4.  oc的对象本质是指针，指向一个结构体\n\n```\nNSObject *objc = [[NSObject alloc] init];\n```\n\noc的方法其实就是调用了c函数，上面的objc是一个临时变量，32位系统上占4个字节，64位系统上占8个字节\n\n> 栈平衡：栈内存释放\n\n> 内存泄漏：指针释放了，但是对应的存储区域没有被释放，那么系统就不会使用对应的这个存储区域，即造成内存泄漏。\n\n> 野指针: 如果指针指向的内存区域free释放掉了，而指针在当前的函数中没有释放，并且又被执行到了，那么这个指针就变成了野指针。\n\n> dealloc: 当我们实例化一些非ARC管理的一些对象时，需要在dealloc中进行释放。\n\n> 栈地址从高往低走，堆地址从低往高走。\n\n1.  C里的create new copy默认都会开辟堆空间(malloc)，返回一个指针\n\n2.  有时候苹果中常见的代码，如C结构体指针释放，不仅要free，还要执行release.\n\n```\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    \n    CFRunLoopObserverRef observer;\n    // 结构体里面，有可能再次开辟堆空间\n//    CFRunLoopObserverCreate(\u003C#CFAllocatorRef allocator#>, \u003C#CFOptionFlags activities#>, \u003C#Boolean repeats#>, \u003C#CFIndex order#>, \u003C#CFRunLoopObserverCallBack callout#>, \u003C#CFRunLoopObserverContext *context#>)\n    // 这个结构体的释放，如何释放；\n    free(observer); // 意味着结构体指针对应的内存空间释放了\n    CFRelease(observer); // 意味着结构体中开辟的空间被释放\n}\n```\n\n> 叶子函数不会有栈空间，即最后一个函数，没有再往下调用.","iOS中的内存相关知识，堆和栈的关系，以及oc是如何处理内存的。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios memory,ios stack,ios heap,ios堆,ios栈,堆栈",{"id":1164,"createdAt":1935,"title":1936,"content":1937,"summary":1938,"image":15,"uid":136,"user":1939,"categoryId":85,"category":1940,"subCategoryId":47,"subCategory":1941,"comments":1942,"status":9,"reason":15,"notice":15,"visitCount":144,"commentCount":112,"keywords":1943},"2024-01-29T14:33:42.44Z","Masonry约束中使用动画","### 使用CGAffineTransform\n```xxx.m\n// 库存bar、title\n    self.stockAnimateView.alpha = 0;\n    [self.stockAnimateView mas_remakeConstraints:^(MASConstraintMaker *make) {\n        make.leading.mas_equalTo(0);\n        make.top.mas_equalTo(10);\n    }];\n    if (stock \u003C= 2) {\n        self.stockprogressLabel.text = [NSString stringWithFormat:@\"库存仅剩x%ld\", stock];\n        @weakify_dzx(self);\n        self.stockAnimateView.transform = CGAffineTransformIdentity;\n        [UIView animateWithDuration:0.5 animations:^{\n            @strongify_dzx(self);\n            self.stockAnimateView.alpha = 1;\n            self.stockAnimateView.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -4, -10);\n        }];\n    }\n```","在控件使用masonry进行布局时，给组件加一个动画。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,masonry,ios masonry",{"id":1402,"createdAt":1945,"title":1946,"content":1947,"summary":1948,"image":15,"uid":136,"user":1949,"categoryId":85,"category":1950,"subCategoryId":47,"subCategory":1951,"comments":1952,"status":9,"reason":15,"notice":15,"visitCount":242,"commentCount":112,"keywords":1953},"2024-02-02T11:15:54.55Z","iOS中播放音频的简单示例","```\nNSString *recordFilePath = record.fileName;\n    \nNSString *urlStr = [[[self getRecorRootPath] stringByAppendingString:[NSString stringWithFormat:@\"/%@\", recordFilePath]] stringByAppendingString:@\".aac\"];\n    \nNSError* error = nil;\n\nAVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:urlStr] error:&error];\n\n```\n\n// 除了此处设置音量外，还应该设置播放的模式：从听筒播放、扬声器播放。\n参考本文件夹目录下的文章：《选择和声音通道听筒、扬声器切换》\nplayer.volume = 1.0f;\nplayer.numberOfLoops = 0;        \n[player prepareToPlay];\n[player play];","iOS中播放音频的简单示例，show you code.",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios audio,ios sound,ios音频,ios多媒体,ios开发",{"id":1424,"createdAt":1955,"title":1956,"content":1957,"summary":1958,"image":15,"uid":136,"user":1959,"categoryId":85,"category":1960,"subCategoryId":47,"subCategory":1961,"comments":1962,"status":9,"reason":15,"notice":15,"visitCount":242,"commentCount":112,"keywords":1963},"2024-02-02T10:55:04.49Z","汇编的简单知识","# 汇编\n\n1.  汇编语言与机器语言一一对应，每一条机器指令都有与之对应的汇编指令；\n2.  汇编语言可以通过编译得到机器语言，机器语言可以通过反汇编得到汇编语言\n3.  高级语言可以通过编译得到汇编语言/机器语言，但汇编语言/机器语言几乎不可能换成成高级语言\n4.  修改pc通用寄存器的指令：\n\n    register write pc 0x...\n\n> CPU从何处执行指令是由pc中的内容决定的，我们可以通过改变pc的内容来控制CPU执行目标指令\n\n> ARM64提供了一个mov指令（传送指令），可以用来修改大部分寄存器的值，比如：\\\n> move x0, #10、mov x1,#20\n\n> 但是mov指令不能用于设置pc的值，ARM64没有提供这样的功能\n\n> ARM64提供了另外的指令来修改pc的值，这些指令通称为转移指令，最简单的是bl指令\n\n## Example\n```\n// 1. 新建assembly file: asm.s\n\n.text\n.global _A,_B\n\n_A:\n    mov x0, #0xa0\n    mov x1, #0x00\n    add x1,x0,#0x14\n    bl _B\n    mov x0,#0x0\n    ret\n    \n_B:\n    add x0,x0,#0x10\n    ret\n    \n// 2. 在main.m中\nvoid A();\nint main(int argc, char * argv[]) {\n    A();\n}\n```\n> 上述代码会造成死循环，由于ret是在lr(x30)寄存器中告诉cpu下一条指令的地址，所以会造成在A和B之间重复调用。\n\n修改代码：\n```\n// 1. 新建assembly file: asm.s\n\n.text\n.global _A,_B\n\n_A:\n    str x30,[sp #-0x10]! // 感叹号的作用是先执行数据的存放，再进行减\n    bl _B\n    mov x0, #0xa0\n    ldr x30, [sp], #0x10\n    ret\n    \n_B:\n    add x0,x0,#0x10\n    ret\n    \n// 2. 在main.m中\nvoid A();\nint main(int argc, char * argv[]) {\n    A();\n}\n```\n\n函数本质\n\n*   栈 \\\n    LIFO后进先出","什么是汇编，汇编的一些基础知识，这篇文章简单梳理一下。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"汇编,ios,ios汇编,ios c,c++,ios开发,ios汇编语言",{"id":1965,"createdAt":1966,"title":1967,"content":1968,"summary":1969,"image":15,"uid":136,"user":1970,"categoryId":85,"category":1971,"subCategoryId":47,"subCategory":1972,"comments":1973,"status":9,"reason":15,"notice":15,"visitCount":242,"commentCount":112,"keywords":1974},69,"2024-02-02T10:36:21.708Z","什么是socket，iOS中的Socket如何实现","# Socket\n网络以数据包的形式进行传播\n\n高低电平\n\nOSI网络模型：从7层模型，合并成4层模型\n\n*   OSI的7层模型：\n\n1.  应用层\n2.  表示层\n3.  会话层\n4.  传输层\n5.  网络层\n6.  数据链路层\n7.  物理层\n\n# \n\n*   4层模型\n1.  应用层\n2.  传输层 （tcp， udp）\n3.  网络互联层 （ip）\n4.  网络接口层\n\n# \n*   TCP\n1.  握手，建立传输数据的通道\n2.  可靠，数据大小不受限制，可以进行大数据传输\n3.  安全，可靠协议，安全送达\n4.  应用：数据下载\n# \n*   UDP\n1.  不可靠，只管发送，不确认对方是否收到\n2.  效率高，不需要建立连接\n3.  数据包大小受限制，大小限制在64k之内\n4.  应用：直播、游戏\n\n## socket\n\n每一个端都有socket，通过ip地址进行连接，通过端口号进行通信。\n\n过程：创建socket，建立连接，数据读取，关闭连接\n\n## Example\n\n### 引入头文件\n```\n#import \u003Csys/socket.h>\n#import \u003Cnetinet/in.h>\n#import \u003Carpa/inet.h>\n```\n\n### 模拟本地socket：\n```\n- (void)socketDemo {\n    // 1. 创建socket\n    /**\n     domain:    协议域， AF_INET -> ipv4\n     type:      socket类型，SOCK_STREAM\n     protocol:  IPPROTO_TCP\n     */\n    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);\n    // 2. 连接服务器\n    /**\n    1 > 客户端socket\n    2 > 服务器ip地址结构体的指针\n    3 > 结构体数据的长度\n    返回值：0表示成功\n    */\n    struct sockaddr_in serverAddr;\n    serverAddr.sin_family = AF_INET;\n    serverAddr.sin_port = htons(12345);\n    serverAddr.sin_addr.s_addr = inet_addr(\"127.0.0.1\");\n    \n    \n    int connResult = connect(clientSocket, (const struct sockaddr *)&serverAddr, sizeof(serverAddr));\n    if (connResult == 0) { // Success\n        NSLog(@\"连接成功\");\n    } else { // fail\n        NSLog(@\"失败码 %d\", connResult);\n    }\n    // 3. 发送数据\n    /**\n    1. 客户端socket\n    2. 发送内容的地址\n    3. 发送内容长度\n    4. 发送方式标志，一般为0\n    返回值：成功后发送的字节数，失效则返回SOCKET_ERROR\n    */\n    NSString *sendMsg = @\"Hello\";\n    ssize_t sendLen = send(clientSocket, sendMsg.UTF8String, strlen(sendMsg.UTF8String), 0);\n    NSLog(@\"发送了 %ld 个字节\", sendLen);\n    \n    // 4. 从服务器接收数据\n    /**\n    1. 客户端socket\n    2. 接收内容的缓冲区地址\n    3. 接收内容缓存区长度\n    4. 接收方式，0表示阻塞，必须等待服务器返回数据\n    返回值：如果成功，则返回读入的字节数，失效则返回SOCKET_ERROR\n    */\n    uint8_t buffer[1024]; // 准备空间\n    ssize_t recvLen = recv(clientSocket, buffer, sizeof(buffer), 0);\n    NSLog(@\"接收了 %ld 个字节\", recvLen);\n    \n    NSData *data = [NSData dataWithBytes:buffer length:recvLen];\n    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n    NSLog(@\"%@\", str);\n    \n    // 5. close\n    close(clientSocket);\n}\n```\n\n> 可以在terminal终端执行命令：`nc -lk 端口号`，始终监听本地计算机“端口号”的数据。Netcat是终端下用于调试和检查网络的工具包，可用于创建TCP/IP连接。\n\n### 连接和发送/接收消息\n```\n@property (nonatomic, assign) int clientSocket;\n```\n```\n- (void)viewDidLoad {\n    [super viewDidLoad];\n    // Do any additional setup after loading the view.\n    \n//    [self socketDemo];\n    \n    [self doConnectionWithPort:80 addr:\"61.135.169.121\"];\n//    [self sendMsg:@\"我要奋斗!!\"];\n    NSString *request = @\"GET / HTTP/1.1\\n\"\n    \"Host: www.baidu.com\\n\\n\";\n    [self sendMsg:request];\n}\n\n- (void)doConnectionWithPort:(int)port addr:(const char *)addr {\n    _clientSocket = socket(AF_INET, SOCK_STREAM, 0);\n    struct sockaddr_in serverAddr;\n    serverAddr.sin_family = AF_INET;\n    serverAddr.sin_port = htons(port);\n    serverAddr.sin_addr.s_addr = inet_addr(addr);\n    \n    \n    int connResult = connect(_clientSocket, (const struct sockaddr *)&serverAddr, sizeof(serverAddr));\n    if (connResult == 0) { // Success\n        NSLog(@\"连接成功\");\n    } else { // fail\n        NSLog(@\"失败码 %d\", connResult);\n    }\n    \n}\n\n- (void)sendMsg:(NSString *)sendMsg {\n    // 3. 发送数据\n    ssize_t sendLen = send(_clientSocket, sendMsg.UTF8String, strlen(sendMsg.UTF8String), 0);\n    NSLog(@\"发送了 %ld 个字节\", sendLen);\n    \n    \n    // 4. 从服务器接收数据\n    \n    uint8_t buffer[1024]; // 准备空间\n    ssize_t recvLen = recv(_clientSocket, buffer, sizeof(buffer), 0);\n    NSLog(@\"接收了 %ld 个字节\", recvLen);\n    \n    NSData *data = [NSData dataWithBytes:buffer length:recvLen];\n    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];\n    NSLog(@\"%@\", str);\n    \n    \n}\n\n- (void)closeSocket {\n    // 5. close\n    close(_clientSocket);\n}\n```\n\n## 网络\n苹果的connection有一些地方做的不是很好，比如异步下载，数据回调不来，runloop默认不开启。\n线程有任务才有可能不释放。\n\nhttps协议：http + ssl (RSA加密)\n\n> https不会对客户端进行验证\n\n> RSA非对称加密 \\\n> 公钥，私钥 == 长度大概在200位的一串数字 \\\n> 明文公钥加密，私密解密 \\\n> 私钥加密，公钥解密！\n\n> 对称加密 \\\n> 明文密钥加密 -> 密文 \\\n> 密文密钥解密 -> 明文\n\n> 加盐\n\n1.  一个字符串，通过拼接\n2.  可以使用随机盐，一个客户端一个盐，保证安全\n3.  也可以再加上时间戳（服务器给的），来保证安全，服务器验证当前时间或者前1分钟内的数据，那么hacker就只有在2分钟之内进行破解，增加安全性\n\n\n","什么是socket，iOS中的Socket如何实现，tcp和udp在socket中起到哪些作用，咱们在这里一起探讨一下。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"socket,ios,ios socket,ios tcp,ios udp,ios开发",{"id":644,"createdAt":1976,"title":1977,"content":1978,"summary":1979,"image":1980,"uid":136,"user":1981,"categoryId":85,"category":1982,"subCategoryId":144,"subCategory":1983,"comments":1984,"status":9,"reason":15,"notice":15,"visitCount":242,"commentCount":112,"keywords":1985},"2024-01-29T14:58:41.433Z","webpack的概念，每一个配置的作用以及具体的操作实践","# 一、webpack概念\n## 1.1 webpack 是什么    \nwebpack 是一种前端资源构建工具，一个静态模块打包器(module bundler)。\n在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。\n它将根据模块的依赖关系进行静态分析，打包生成对应的静态资源(bundle)。\n![webpack打包全图](https://image.xinwei.ltd/11706539329583.png)\n\n## 1.2 webpack 五个核心概念\n### 1.2.1 Entry        \n入口(Entry)指示 webpack 以哪个文件为入口起点开始打包，分析构建内部依赖图。 \n\n### 1.2.2 Output\n输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去，以及如何命名。\n\n### 1.2.3 Loader             \nLoader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解JavaScript)  \n\n### 1.2.4 Plugins\n插件(Plugins)可以用于执行范围更广的任务。插件的范围包括，从打包优化和压缩，一直到重新定义环境中的变量等。\n\n### 1.2.5 Mode               \n模式(Mode)指示 webpack 使用相应模式的配置。\n![打包模式](https://image.xinwei.ltd/21706539462160.png)\n\n# 二、webpack配置\n## 2.1 初始化配置\n1. 初始化 package.json输入指令:                    \n```terminal\nnpm init\n```\n2. 下载并安装 webpack输入指令:\n```terminal\n//【全局安装】\nnpm install webpack webpack-cli -g\n//【本地安装\nnpm install webpack webpack-cli -D\n```\n## 2.2 编译打包应用\n1. 创建文件\n2. 运行指令\n开发环境指令:\n```terminal\nwebpack src/js/index.js -o build/js/built.js --mode=development\n```\n功能: webpack 能够编译打包 js 和 json 文件，并且能将 es6 的模块化语法转换成浏览器能识别的语法。 \n\n生产环境指令:\n```terminal\nwebpack src/js/index.js -o build/js/built.js --mode=production\n```\n功能:在开发配置功能上多一个功能，压缩代码。\n\n3. 结论\nwebpack 能够编译打包 js 和 json 文件。\n能将 es6 的模块化语法转换成浏览器能识别的语法。\n能压缩代码。\n\n4. 问题\n不能编译打包 css、img 等文件。\n不能将 js 的 es6 基本语法转化为 es5 以下语法。\n\n# 三、webpack 开发环境的基本配置\n## 3.1 创建配置文件\n1. 创建文件 webpack.config.js\n2. 配置内容如下\n```js\nconst { resolve } = require('path'); // node 内置核心模块，用来处理路径问题。                                    \nmodule.exports = {\n  entry: './src/js/index.js', // 入口文件\n  output: { // 输出配置\n  filename: './built.js',\n  path: resolve(__dirname, 'build/js')\n  },                        \n  mode: 'development' //开发环境\n};        \n```\n3. 运行指令\n```terminal\nwebpack\n```\n\n## 3.2 打包样式资源\n1.创建文件\n![](https://image.xinwei.ltd/31706539813801.png)\n2. 下载安装 loader 包\n```terminal\nnpm i css-loader style-loader less-loader less -D   \n```\n3. 修改配置文件\n```js\n// resolve 用来拼接绝对路径的方法\nconst { resolve } = require('path');                        \nmodule.exports = {\n// webpack 配置// 入口起点\nentry: './src/index.js',\n// 输出                        \noutput: {\n    // 输出文件名\n    filename: 'built.js',\n    // 输出路径// __dirname nodejs 的变量，代表当前文件的目录绝对路径\n    path: resolve(__dirname, 'build')                                                                        \n},                        \n// loader 的配置\nmodule: {                        \n    rules: [// 详细 loader 配置// 不同文件必须配置不同 loader 处理\n    {                        \n    // 匹配哪些文件\n    test: /\\.css$/,\n    // 使用哪些 loader 进行处理\n    use: [                        \n    // use 数组中 loader 执行顺序:从右到左，从下到上 依次执行\n    // 创建 style 标签，将 js 中的样式资源插入进行，添加到 head 中生效\n    'style-loader',\n    // 将 css 文件变成 commonjs 模块加载 js 中，里面内容是样式字符串\n    'css-loader'                        \n    ]\n    },                        \n        {\n            test: /\\.less$/,\n            use: [                    \n    'style-loader',\n    'css-loader',\n    // 将 less 文件编译成 css 文件// 需要下载 less-loader 和 less\n    'less-loader'                        \n    ]\n    }                        \n    ]\n},                        \n// plugins 的配置\nplugins: [                        \n// 详细 plugins 的配置\n],                        \n// 模式\nmode: 'development', // 开发模式// mode: 'production'                        \n}                                                        \n```\n4. 运行指令\n```terminal\nwebpack\n```\n\n## 3.3 打包 HTML 资源\n1. 创建文件\n![](https://image.xinwei.ltd/41706539904725.png)\n2. 下载安装 plugin 包\n```terminal\nnpm install --save-dev html-webpack-plugin\n```\n3. 修改配置文件\n```js\n                                                                        \nconst { resolve } = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');                        \nmodule.exports = {\n  entry: './src/index.js',\n  output: {                        \n    filename: 'built.js',                        \n    path: resolve(__dirname, 'build')\n  },                    \n  module: {\n    rules: [                        \n    // loader 的配置\n    ]                        \n  },\n  plugins: [                        \n// plugins 的配置\n// html-webpack-plugin\n// 功能:默认会创建一个空的 HTML，自动引入打包输出的所有资源(JS/CSS)\n// 需求:需要有结构的 HTML 文件\nnew HtmlWebpackPlugin({                        \n// 复制 './src/index.html' 文件，并自动引入打包输出的所有资源(JS/CSS)                        \n      template: './src/index.html'\n    })                    \n],                        \n  mode: 'development'\n};                                                        \n```\n4.运行指令: \n```terminal\nwebpack\n```\n\n## 3.4 打包图片资源    \n1.创建文件    \n![](https://image.xinwei.ltd/51706540000340.png)\n\n2.下载安装 loader 包\n```terminal\nnpm install --save-dev html-loader url-loader file-loader\n```\n3.修改配置文件\n```js\n                                                                    const { resolve } = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');                        \nmodule.exports = {\n  entry: './src/index.js',\n  output: {                        \n    filename: 'built.js',                    \n    path: resolve(__dirname, 'build')\n  },                        \n  module: {\n    rules: [                    \n{test: /\\.less$/,// 要使用多个 loader 处理用 use\nuse: ['style-loader', 'css-loader', 'less-loader']                        \n},{                                                                                                \n// 问题:默认处理不了 html 中 img 图片\n// 处理图片资源\ntest: /\\.(jpg|png|gif)$/,\n// 使用一个 loader                        \n// 下载 url-loader file-loader\nloader: 'url-loader',\noptions: {\n                                                                        \n// 图片大小小于8kb，就会被base64处理\n// 优点: 减少请求数量(减轻服务器压力)\n// 缺点:图片体积会更大(文件请求速度更慢)\nlimit: 8 * 1024,\n// 问题:因为url-loader默认使用es6模块化解析，而html-loader引入图片是commonjs\n// 解析时会出问题:[object Module]\n// 解决:关闭 url-loader 的 es6 模块化，使用 commonjs 解析\nesModule: false,\n// 给图片进行重命名\n// [hash:10]取图片的 hash 的前 10 位\n// [ext]取文件原来扩展名\nname: '[hash:10].[ext]'                        \n}},                        \n{\ntest: /\\.html$/,// 处理 html 文件的 img 图片(负责引入 img，从而能被 url-loader 进行处理)\nloader: 'html-loader'                        \n}]                        \n},\nplugins: [                        \n    new HtmlWebpackPlugin({\n      template: './src/index.html'                    \n})],                        \n  mode: 'development'\n};                      \n```\n4. 运行指令: webpack\n\n## 3.5 打包其他资源    \n1. 创建文件\n![](https://image.xinwei.ltd/61706540088957.png)\n2. 修改配置文件\n```js\n                                                                        \nconst { resolve } = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');                        \nmodule.exports = {\n  entry: './src/index.js',\n  output: {                        \n    filename: 'built.js',                        \n    path: resolve(__dirname, 'build')\n  },                        \n  module: {\n    rules: [                        \n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader']                        \n},                        \n// 打包其他资源(除了html/js/css资源以外的资源)\n{                        \n// 排除 css/js/html 资源\nexclude: /\\.(css|js|html|less)$/,\nloader: 'file-loader',\noptions: {                        \n          name: '[hash:10].[ext]'\n        }                        \n}]                        \n},\nplugins: [                        \n    new HtmlWebpackPlugin({\n      template: './src/index.html'                        \n    })                                                                            \n],                        \n  mode: 'development'\n}; \n```\n4. 运行指令: webpack\n\n## 3.6 devserver\n1. 创建文件\n![](https://image.xinwei.ltd/71706540144948.png)\n2. 修改配置文件\n```js\n                                                                        \nconst { resolve } = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');                        \nmodule.exports = {\n  entry: './src/index.js',\n  output: {                        \n    filename: 'built.js',                        \n    path: resolve(__dirname, 'build')\n  },                        \n  module: {\n    rules: [                        \n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader']                        \n        },                        \n// 打包其他资源(除了html/js/css资源以外的资源)\n{                        \n// 排除 css/js/html 资源\nexclude: /\\.(css|js|html|less)$/,                                                                        loader: 'file-loader',\n        options: {                        \n          name: '[hash:10].[ext]'\n        }                        \n}]                        \n},\nplugins: [                        \n    new HtmlWebpackPlugin({\n      template: './src/index.html'                        \n    })\n],                        \nmode: 'development',\ndevServer: {                        \n// 项目构建后路径\ncontentBase: resolve(__dirname, 'build'),\n// 启动 gzip 压缩\ncompress: true,\n// 端口号\nport: 3000,\n// 自动打开浏览器\nopen: true                        \n}}; \n```\n4. 运行指令:\n```terminal\nnpx webpack-dev-server\n```\n\n## 3.7 开发环境配置  \n1.创建文件    \n![](https://image.xinwei.ltd/81706540210677.png)\n\n2. 修改配置文件\n```js\n                                                                        const { resolve } = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');                                                                        \nmodule.exports = {\n  entry: './src/js/index.js',\n  output: {                        \n    filename: 'js/built.js',\n    path: resolve(__dirname, 'build')\n  },                        \n  module: {\n    rules: [                        \n// loader 的配置\n{                        \n// 处理 less 资源\ntest: /\\.less$/,\nuse: ['style-loader', 'css-loader', 'less-loader']                        \n},{\n// 处理css资源\ntest: /\\.css$/,\nuse: ['style-loader', 'css-loader']                        \n},{\n// 处理图片资源\ntest: /\\.(jpg|png|gif)$/,\nloader: 'url-loader',\noptions: {                        \nlimit: 8 * 1024,\nname: '[hash:10].[ext]',\n// 关闭es6模块化\nesModule: false,\noutputPath: 'imgs'                                                                                                \n}},                        \n{\n// 处理html中img资源\ntest: /\\.html$/,\nloader: 'html-loader'                            \n},\n{                                                                                                                                                                                                                                                                                                                                                                // 处理其他资源\nexclude: /\\.(html|js|css|less|jpg|png|gif)/,\nloader: 'file-loader',\noptions: {                                                                        \nname: '[hash:10].[ext]',\noutputPath: 'media'\n  }                        \n}\n]                        \n},\nplugins: [                        \n// plugins 的配置new HtmlWebpackPlugin({                        \n      template: './src/index.html'\n    })                        \n  ],\n  mode: 'development',\n  devServer: {                        \n    contentBase: resolve(__dirname, 'build'),\n    compress: true,\n    port: 3000,\n    open: true                        \n}};   \n```\n3. 运行指令:\n```terminal\nnpx webpack-dev-server\n```\n暂时写到这里吧。","介绍webpack的概念、基本配置及每一项配置的含义和作用，然后是具体的操作步骤和演示。","https://image.xinwei.ltd/11706539329583.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"vue,webpack,webpack概念",{"id":1987,"createdAt":1988,"title":1989,"content":1990,"summary":1991,"image":1992,"uid":136,"user":1993,"categoryId":85,"category":1994,"subCategoryId":272,"subCategory":1995,"comments":1996,"status":9,"reason":15,"notice":15,"visitCount":1164,"commentCount":112,"keywords":1997},173,"2025-11-21T03:26:49.909Z","taro中将vite构建修改为webpack构建","这篇文章记录我在开发小程序过程中遇到的问题和解决方案，希望对一些同学有帮助。问题是taro的4.1.7或4.1.8版本编译为支付宝小程序时遇到的问题。\n\n## 问题\n> node_modules/lottie-web/build/player/lottie.js (14422:32) Use of eval in \"node_modules/lottie-web/build/player/lottie.js\" is strongly discouraged as it poses security risks and may cause issues with minification.\nnode_modules/@nutui/lottie-miniprogram/miniprogram_dist/index.js (9:157644) Use of eval in \"node_modules/@nutui/lottie-miniprogram/miniprogram_dist/index.js\" is strongly discouraged as it poses security risks and may cause issues with minification.\nnode_modules/@nutui/lottie-miniprogram/miniprogram_dist/index.js (9:157715) Use of eval in \"node_modules/@nutui/lottie-miniprogram/miniprogram_dist/index.js\" is strongly discouraged as it poses security risks and may cause issues with minification.\n✓ 1126 modules transformed.\n/Users/xxx/node_modules/@tarojs/vite-runner/dist/mini/emit.js:188\n                                    if (chunk.type === 'asset') {\n                                              ^\n> TypeError: Cannot read properties of undefined (reading 'type')\n    at Object.set (/Users/xxx/node_modules/@tarojs/vite-runner/src/mini/emit.ts:191:25)\n    at Object.fn (/Users/xxx/node_modules/@tarojs/plugin-platform-alipay/src/program.ts:121:32)\n    at Kernel.\u003Canonymous> (/Users/xxx/node_modules/@tarojs/service/src/Kernel.ts:307:34)\n    at Generator.next (\u003Canonymous>)\n    at /Users/xxx/node_modules/@tarojs/service/dist/Kernel.js:8:71\n    at new Promise (\u003Canonymous>)\n    at __awaiter (/Users/xxx/node_modules/@tarojs/service/dist/Kernel.js:4:12)\n    at /Users/xxx/node_modules/@tarojs/service/src/Kernel.ts:306:24\n    at _next0 (eval at create (/Users/xxx/node_modules/tapable/lib/HookCodeFactory.js:71:10), \u003Canonymous>:17:17)\n    at eval (eval at create (/Users/xxx/node_modules/tapable/lib/HookCodeFactory.js:71:10), \u003Canonymous>:41:1)\nNode.js v22.19.0\n\n问题截图：\n![taro vite error](https://image.xinwei.ltd/image1763629138230.png)\n\n这个问题其实很多人都有在碰到，主要是由于taro新版本出的bug，那我们要做的其实无外乎2种方式，要么回退taro的cli和依赖版本到之前的一个正常版本，或者将vite改为webpack的编译配置。\n\n## 解决方案\n我倾向于保持taro cli的最新版本，然后将vite构建改为webpack构建，所以接下来我要记录的是如何将我的工程改为webpack构建的过程。\n\n我当前的taro环境：\n```terminal\n👽 Taro v4.1.7\n\n  Taro CLI 4.1.7 environment info:\n    System:\n      OS: macOS 26.1\n      Shell: 5.9 - /bin/zsh\n    Binaries:\n      Node: 22.19.0 - /Users/xxx/.nvm/versions/node/v22.19.0/bin/node\n      Yarn: 1.22.22 - /Users/xxx/.nvm/versions/node/v22.19.0/bin/yarn\n      npm: 10.9.3 - /Users/xxx/.nvm/versions/node/v22.19.0/bin/npm\n    npmPackages:\n      @tarojs/cli: 4.1.7 => 4.1.7 \n      @tarojs/components: 4.1.7 => 4.1.7 \n      @tarojs/helper: 4.1.7 => 4.1.7 \n      @tarojs/plugin-framework-react: 4.1.7 => 4.1.7 \n      @tarojs/plugin-html: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-alipay: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-h5: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-harmony-hybrid: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-jd: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-qq: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-swan: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-tt: 4.1.7 => 4.1.7 \n      @tarojs/plugin-platform-weapp: 4.1.7 => 4.1.7 \n      @tarojs/react: 4.1.7 => 4.1.7 \n      @tarojs/runtime: 4.1.7 => 4.1.7 \n      @tarojs/shared: 4.1.7 => 4.1.7 \n      @tarojs/taro: 4.1.7 => 4.1.7 \n      @tarojs/vite-runner: 4.1.7 => 4.1.7 \n      babel-preset-taro: 4.1.7 => 4.1.7 \n      eslint-config-taro: 4.1.7 => 4.1.7 \n      react: ^18.0.0 => 18.3.1 \n```\n\n### 1. 升级taro cli到最新版本\n使用以下命令\n```terminal\ntaro update self 4.1.8\n```\n\n### 2. 升级工程project的taro配置\n使用以下终端命令\n```terminal\ntaro update project 4.1.8\n```\n我在升级的时候遇到问题：\n```terminal error\n⚠ npm error code ERESOLVE\n⚠ npm error ERESOLVE could not resolve\nnpm error\nnpm error While resolving: xxx-mini@1.0.0\nnpm error Found: @tarojs/plugin-framework-react@4.1.7\nnpm error node_modules/@tarojs/plugin-framework-react\nnpm error   @tarojs/plugin-framework-react@\"4.1.8\" from the root project\nnpm error\nnpm error Could not resolve dependency:\nnpm error @tarojs/plugin-framework-react@\"4.1.8\" from the root project\nnpm error\nnpm error Conflicting peer dependency: @tarojs/shared@4.1.8\nnpm error node_modules/@tarojs/shared\nnpm error   peer @tarojs/shared@\"4.1.8\" from @tarojs/plugin-framework-react@4.1.8\nnpm error   node_modules/@tarojs/plugin-framework-react\nnpm error     @tarojs/plugin-framework-react@\"4.1.8\" from the root project\nnpm error\nnpm error Fix the upstream dependency conflict, or retry\nnpm error this command with --force or --legacy-peer-deps\nnpm error to accept an incorrect (and potentially broken) dependency resolution.\nnpm error\nnpm error\nnpm error For a full report see:\nnpm error /Users/xxx/.npm/_logs/2025-11-20T09_07_03_753Z-eresolve-report.txt\n⚠ npm error A complete log of this run can be found in: /Users/xxx/.npm/_logs/2025-11-20T09_07_03_753Z-debug-0.log\n```\n\n从上面的报错中看出错误在：`@tarojs/plugin-framework-react@4.1.7`和当前project配置（taro 4.1.8）不匹配，所以检查一下`package.json`中`@tarojs/plugin-framework-react`是否是4.1.8，发现`package.json`中的配置均以被修改为4.1.8了，那么问题就是出在没有删掉的`package-lock.json`和`node_modules`。\n\n所以删除`package-lock.json`和`node_modules`后，重新`npm install`。\n\n### 3. 将vite修改为webpack编译配置\n首先要将`package.json`文件中的一些vite等依赖去掉，\n比如（如果有的话）：\n```依赖项\n...\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"@tarojs/vite-runner\": \"4.1.8\",\n    \"vite\": \"^4.2.0\",\n    \"vite-plugin-importer\": \"^0.2.5\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n...\n```\n\n然后确保引入依赖：\n```新依赖\n@tarojs/webpack5-runner\n```\n\n### 4. 将config/index.ts中的vite构建改为webpack\n将`defineConfig`的配置从vite改为webpack5。\n比如：\n```config/index.ts\n...\nexport default defineConfig\u003C\"vite\">(async (merge, { command, mode }) => {\n  const baseConfig: UserConfigExport\u003C\"vite\"> = {\n...\n     compiler: {\n        type: \"vite\",\n...\n```\n改为:\n```config/index.ts\n...\nexport default defineConfig\u003C\"webpack5\">(async (merge, { command, mode }) => {\n  const baseConfig: UserConfigExport\u003C\"webpack5\"> = {\n...\n     compiler: {\n        type: \"webpack5\",\n...\n```\n\n### 5. 修改babel.config.js中的构建配置\n修改前：\n```babel.config.js\n// babel-preset-taro 更多选项和默认值：\n// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md\nmodule.exports = {\n  presets: [\n    [\n      \"taro\",\n      {\n        framework: \"react\",\n        ts: true,\n        compiler: \"vite\",\n      },\n    ],\n  ],\n  // plugins: [], // 彻底清空插件，避免任何 Babel 导入插件干扰\n};\n\n```\n修改后：\n```babel.config.js\n// babel-preset-taro 更多选项和默认值：\n// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md\nmodule.exports = {\n  presets: [\n    [\n      \"taro\",\n      {\n        framework: \"react\",\n        ts: true,\n        compiler: \"webpack5\",\n      },\n    ],\n  ],\n  // plugins: [], // 彻底清空插件，避免任何 Babel 导入插件干扰\n};\n\n```\n\n然后，同样删除掉`package-lock.json`和`node_modules`，然后`npm install`。\n\n### 6. 运行npm run dev:weapp报错\n```部分报错\n...\n Webpack\n  Compiled with some errors in 7.41s\n\n✖ Errors: \n\nresolve '@/utils/dl_util' in '/Users/xxx/src/pages/dt_play'\n  Parsed request is a module\n  using description file: /Users/xxx/package.json (relative path: ./src/pages/dt_play)\n    Field 'browser' doesn't contain a valid alias configuration\n    resolve as module\n      /Users/xxx/src/pages/dt_play/node_modules doesn't exist or is not a directory\n      /Users/xxx/src/pages/node_modules doesn't exist or is not a directory\n      /Users/xxx/src/node_modules doesn't exist or is not a directory\n      looking for modules in /Users/xxx/node_modules\n        /Users/xxx/node_modules/@/utils doesn't exist\n...\n```\n\n从报错可以看出，是由于我之前在vite构建那个时候配置过路径别名，但是改为webpack后别名没有识别。\n\n### 7. 解决别名问题\n- 需要引入dev依赖：`tsconfig-paths-webpack-plugin`，安装这个依赖。\n- 回到config/index.ts配置文件，去使用这个插件\n```config/index.ts\n...\nimport TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'\n...\nmini {\n...\n      webpackChain(chain) {\n        // 这里定义 webpack 配置\n        chain.resolve.plugin(\"tsconfig-paths\").use(TsconfigPathsPlugin);\n      },\n...\n},\n...\nh5: {\n...\n      webpackChain(chain) {\n        chain.resolve.plugin(\"tsconfig-paths\").use(TsconfigPathsPlugin);\n      },\n...\n}\n...\n```\n\n### 8. 重新运行\n运行就OK了。\n\n\n\n以上就是所有了，欢迎大家一起讨论细节.\n\n","这篇文章记录我在开发小程序过程中遇到的问题和解决方案，希望对一些同学有帮助。问题是taro的4.1.7或4.1.8版本编译为支付宝小程序时遇到的问题。","https://image.xinwei.ltd/image1763629138230.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":272,"name":274,"parentId":85},[],"taro alipay, taro vite, taro, vite, vite-runner, chunk.type, vite-runner/dist/mini/emit.js:188",{"id":1999,"createdAt":2000,"title":2001,"content":2002,"summary":2003,"image":2004,"uid":136,"user":2005,"categoryId":85,"category":2006,"subCategoryId":100,"subCategory":2007,"comments":2008,"status":9,"reason":15,"notice":15,"visitCount":1164,"commentCount":112,"keywords":2009},165,"2025-08-15T08:34:21.937Z","flutter compileDebugKotlin的报错：Unresolved reference 'Registrar'","## 报错信息\n\n> Launching lib/main.dart on sdk gphone64 arm64 in debug mode...\nRunning Gradle task 'assembleDebug'...\nWarning: Flutter support for your project's Android Gradle Plugin version (Android Gradle Plugin version 8.1.0) will soon be dropped. Please upgrade your Android Gradle Plugin version to a version of at least Android Gradle Plugin version 8.3.0 soon.\nAlternatively, use the flag \"--android-skip-build-dependency-validation\" to bypass this check.\nPotential fix: Your project's AGP version is typically defined in the plugins block of the `settings.gradle` file (/Users/xxx/android/settings.gradle), by a plugin with the id of com.android.application. \nIf you don't see a plugins block, your project was likely created with an older template version. In this case it is most likely defined in the top-level build.gradle file (/Users/xxx/android/build.gradle) by the following line in the dependencies block of the buildscript: \"classpath 'com.android.tools.build:gradle:\u003Cversion>'\".\nwarning: [options] source value 8 is obsolete and will be removed in a future release\nwarning: [options] target value 8 is obsolete and will be removed in a future release\nwarning: [options] To suppress warnings about obsolete options, use -Xlint:-options.\n3 warnings\ne: file:///Users/han/.pub-cache/hosted/pub.dev/image_gallery_saver-2.0.1/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt:18:48 Unresolved reference 'Registrar'.\ne: file:///Users/xxx/plugin/FlutterToast/android/src/main/kotlin/io/github/ponnamkarthik/toast/fluttertoast/FlutterToastPlugin.kt:9:48 Unresolved reference 'Registrar'.\nFAILURE: Build completed with 2 failures.\n1: Task failed with an exception.\n> * What went wrong:\nExecution failed for task ':image_gallery_saver:compileDebugKotlin'.\n> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction\n   > Compilation error. See log for more details\n> * Try:\n> Run with --stacktrace option to get the stack trace.\n> Run with --info or --debug option to get more log output.\n> Run with --scan to get full insights.\n> Get more help at https://help.gradle.org.\n> ==============================================================================\n> 2: Task failed with an exception.\n> -----------\n> * What went wrong:\nExecution failed for task ':fluttertoast:compileDebugKotlin'.\n> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction\n   > Compilation error. See log for more details\n> * Try:\n> Run with --stacktrace option to get the stack trace.\n> Run with --info or --debug option to get more log output.\n> Run with --scan to get full insights.\n> Get more help at https://help.gradle.org.\n==============================================================================\nBUILD FAILED in 29s\nError: Gradle task assembleDebug failed with exit code 1\n>\n\n## 报错截图：\n![](https://image.xinwei.ltd/12231755241266073.png)\n![](https://image.xinwei.ltd/image1755241307976.png)\n![](https://image.xinwei.ltd/image1755241336036.png)\n\n\n## 分析\n从上述的编译信息可以看出，报错在于：fluttertoast:compileDebugKotlin 和 image_gallery_saver:compileDebugKotlin，导致问题出现的真正原因在于`e: file://`这2个报错语句中，关键信息是：`Unresolved reference 'Registrar'`。\n\n这个`Registrar`类，在早期flutter版本更新中已经提到过，可以查看flutter官方解释：[https://docs.flutter.dev/release/breaking-changes/plugin-api-migration](https://docs.flutter.dev/release/breaking-changes/plugin-api-migration)\n![](https://image.xinwei.ltd/image1755241744337.png)\n\n那么我们根据报错信息中去看对应的file文件，可以看到(以imagegallerysaver为例):\n![](https://image.xinwei.ltd/image1755241840637.png)\n\n总而言之一句话，flutter已经让大多数的插件发布者发布新的不含`Registrar`的新版本了，开发者可以去官网: pub.dev去查看是否有新版本或者plus的插件。\n![](https://image.xinwei.ltd/image1755242083655.png)\n\n那么问题也就好办了，办法有三：\n- 1. 去pub.dev看看报错的库是否发布了新版本，且新版本中已经去掉`Registrar`；\n- 2. 如果新版本中还是没有去掉`Registrar`依赖，那么寻找其他新库替代；\n- 3. 如果也找不到新库，直接fork原来的库，在库里面去掉`Registrar`的依赖，并且按照下面引用话中使用基于`FlutterPlugin`的引用，然后改造成本地依赖的plugin，也是可以的。\n> but we encourage you to migrate to the new APIs based on FlutterPlugin.\n\n那么针对我这里的报错，对于`fluttertoast`这个库，是我本地的plugin库，我直接在本地的plugin中删掉`Registrar`的引用即可；对于`image_gallery_saver`，查了一下，最新的版本也没有解决这个问题，那么使用了另一个库替换它，新库是`image_gallery_saver_plus: ^4.0.1`，然后在使用的地方改成新库的API即可。\n\n至此，编译报错可以解决了。\n\n\n另外编译信息中提到了gradle版本的问题，最好是要升级一下，下一篇我们来详细描述如何去做。\n\n","这篇文章记录我的flutter工程，在升级flutter版本时遇到的Android编译报错问题。","https://image.xinwei.ltd/12231755241266073.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"fluttertoast,compileDebugKotlin,Unresolved reference 'Registrar',image_gallery_saver,flutter android",{"id":2011,"createdAt":2012,"title":2013,"content":2014,"summary":2015,"image":15,"uid":136,"user":2016,"categoryId":85,"category":2017,"subCategoryId":100,"subCategory":2018,"comments":2019,"status":9,"reason":15,"notice":15,"visitCount":1164,"commentCount":112,"keywords":2020},134,"2024-11-09T09:03:58.848Z","Flutter中如何使用base64加密和解密","以下是如何在flutter中使用base64加密和解密的示例，创建一个flutter的class，声明static方法调用进行调用，借助dart的convert库可以很简单的实现。\n```dart\nimport 'dart:convert';\nimport 'dart:typed_data';\n\nclass CryptoBase64 {\n  // 使用Base64编码字符串\n  static String encode(String plainText) {\n    // 字符串转换为字节数组\n    final Uint8List bytes = Uint8List.fromList(plainText.codeUnits);\n    return base64Encode(bytes);\n  }\n\n  // 使用Base64解码字符串\n  static String decode(String encodedText) {\n    // 将Base64编码的字符串解码为字节数组，然后转换为字符串\n    final Uint8List bytes = Uint8List.fromList(base64Decode(encodedText));\n    return String.fromCharCodes(bytes);\n  }\n}\n\n```","以下是如何在flutter中使用base64加密和解密的示例，创建一个flutter的class，声明static方法调用进行调用，借助dart的convert库可以很简单的实现。 dart import 'dart:convert'; import 'dart:typed_data'; class CryptoBase64 { // 使用Base64编码字符串 static String enc",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"flutter base64,base64 flutter,flutter,flutter crypto",{"id":1263,"createdAt":2022,"title":2023,"content":2024,"summary":2025,"image":15,"uid":136,"user":2026,"categoryId":85,"category":2027,"subCategoryId":144,"subCategory":2028,"comments":2029,"status":9,"reason":15,"notice":15,"visitCount":938,"commentCount":112,"keywords":2030},"2024-07-06T10:55:22.848Z","分享第二张web前端面试题","忘记拍照了，凭印象将以下几道web前端的面试题分享给大家。希望对大家有所帮助。\n\n面试题是需要在电脑上作答的，而且要可以成功运行，大家可以自行尝试一下，为每次的前端面试做准备。\n\n## 1. 使用Flex或者Grid实现3列布局，当在小屏幕上时，显示成从上到下的布局。\n下面给出一个示例，可以仅供参考，大家尽量自己试试之后再看示例。\n\n```html\n/*完整的html文件代码，大家自行编写一下*/\n\u003Cdiv class=\"flex-container\">  \n  \u003Cdiv class=\"flex-item\">1\u003C/div>  \n  \u003Cdiv class=\"flex-item\">2\u003C/div>  \n  \u003Cdiv class=\"flex-item\">3\u003C/div>  \n\u003C/div>\n.flex-container {  \n  display: flex;  \n  flex-wrap: wrap; /* 允许换行 */  \n  justify-content: space-between; /* 水平方向上的对齐方式，可按需调整 */  \n}  \n  \n.flex-item {  \n  flex: 1 0 100%; /* 在小屏幕上，flex-basis设为100%以确保每个项目占满一行 */  \n  max-width: calc(33.33% - 10px); /* 在大屏幕上的最大宽度，减去的值是为了处理间距 */  \n  margin-bottom: 10px; /* 垂直间距 */  \n}  \n  \n/* 媒体查询用于在小屏幕上改变布局 */  \n@media (max-width: 600px) { /* 假设600px为小屏幕的分界点 */  \n  .flex-item {  \n    flex-basis: 100%; /* 在小屏幕上每个项目占满一行 */  \n    margin-right: 0; /* 移除水平间距 */  \n  }  \n}\n```\n\n\n## 2. 实现一个deep clone的方法\n示例参考如下：\n```language\nfunction deepClone(obj) {  \n    // 处理null和undefined  \n    if (obj == null) {  \n        return obj;  \n    }  \n  \n    // 处理非对象类型（number, string, boolean, null, undefined）  \n    if (typeof obj !== 'object' || obj instanceof Date || obj instanceof RegExp) {  \n        return obj;  \n    }  \n  \n    // 处理数组  \n    if (Array.isArray(obj)) {  \n        return obj.map(item => deepClone(item));  \n    }  \n  \n    // 处理对象  \n    const newObj = {};  \n    for (let key in obj) {  \n        if (obj.hasOwnProperty(key)) {  \n            newObj[key] = deepClone(obj[key]);  \n        }  \n    }  \n    return newObj; \n  \n    // 注意：这里我们没有使用JSON.stringify和JSON.parse，因为这会忽略上述限制  \n} \n```\n\n\n## 3.实现一个react(或vue)中的常见的hook方法\n示例，比如模拟一个useState:\n\n```language\n// 自定义Hook: useCustomState  \nfunction useCustomState(initialState) {  \n  // 使用useReducer来模拟useState的行为  \n  // 注意：这里的reducer非常简单，只是返回新的状态  \n  const [state, dispatch] = useReducer((prevState, newState) => newState, initialState);  \n  \n  // 返回一个状态更新函数  \n  function setState(newState) {  \n    dispatch(newState);  \n  }  \n  \n  // 返回状态和状态更新函数  \n  return [state, setState];  \n}  \n```\n\n## 4. 如何防止网站中上传文件对web服务器的xss攻击\n示例：\n\n在服务器端实施严格的输入验证和过滤机制，确保上传的文件不包含任何恶意代码或脚本\n\n限制允许上传的文件类型，并验证文件的MIME类型是否与期望的相符\n\n在显示用户上传的文件内容之前，始终进行输出编码和转义\n\n等等\n\n## 5.至少说出3种前端中的性能优化策略\n示例：\n\nwebpack相关的代码压缩和分割\n\n图片优化的懒加载和压缩以及cdn\n\n浏览器的缓存策略等\n\n\n\n这里只给出一些示例，大家可以根据这些线索进行联想和扩展，这是现场的真实面试题，希望对大家有所帮助。","忘记拍照了，凭印象将以下几道web前端的面试题分享给大家。希望对大家有所帮助。面试题是需要在电脑上作答的，而且要可以成功运行，大家可以自行尝试一下，为每次的前端面试做准备。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"web前端,web面试题,web开发题,js面试题",{"id":2032,"createdAt":2033,"title":2034,"content":2035,"summary":2036,"image":2037,"uid":136,"user":2038,"categoryId":85,"category":2039,"subCategoryId":144,"subCategory":2040,"comments":2041,"status":9,"reason":15,"notice":15,"visitCount":938,"commentCount":112,"keywords":2042},115,"2024-07-06T10:51:21.656Z","分享一张web前端面试题","如图所示，这是某公司出的3道面试手写题，手写解答部分有2处错误，请大家忽略。\n\n针对图中的3道题，各位同学看看自己是否可以写出比较满意的答案呢？\n\n![前端面试题](https://image.xinwei.ltd/image1720263013493.png)\n\n\n 1.实现四舍五入保留n位小数位方法（满足大数场景）\n这个问题类属于一道算法题了，也就是需要面试者实现一个四舍五入的方法，可能大家思路上都很清晰，希望大家自己手动写一下，防止面试时遇到卡壳或者手写时间过长。\n\n\n\n 2.实现防抖和节流函数\n这个也是大家耳熟能详的前端性能优化部分的知识点。但是否能当场手写出来，一部分同学可能也是需要花点时间的。\n\n温馨提示：\n\n防抖应用的场景，一般常见于按钮的重复点击；\n\n节流应用的场景，一般常见于页面滚动mousemove事件监听。\n\n\n\n3.Pomise对象池\n请你编写一个异步函数 promisePool，它接收一个异步函数组functions和池限制n（假设异步任务都成功，仅考虑任务的执行不用关注任务的返回值）\n\n```js\n/**\n* @param {Function[]} functions - 异步任务执行队列\n* @param {number} n - 可执行任务数\n* @return {void}\n*/\nvar promisePool = async function(functions, n) { } ;\n```\n\n这道题考异步，那么大家心中应该也明晰前端中处理异步的方式方法。\n\n可能面试官还会问，有没有更优化的方案，大家不妨多想想（比如，一条任务完成后，可以立即再加入任务）。\n","如图所示，这是某公司出的3道面试手写题，手写解答部分有2处错误，请大家忽略。针对图中的3道题，各位同学看看自己是否可以写出比较满意的答案呢？","https://image.xinwei.ltd/image1720263013493.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"面试题,js 面试题,面试,手写面试题,web面试题",{"id":1506,"createdAt":2044,"title":2045,"content":2046,"summary":2047,"image":15,"uid":136,"user":2048,"categoryId":85,"category":2049,"subCategoryId":47,"subCategory":2050,"comments":2051,"status":9,"reason":15,"notice":15,"visitCount":938,"commentCount":112,"keywords":2052},"2024-02-02T10:15:27.668Z","iOS的单元测试","# 单元测试\n\n*   逻辑测试\n*   性能测试\n*   异步测试\n*   UI测试\n*   自动化测试\n    驱动：TDD测试驱动代码\n\n## Example 1 Normal Test\n```\n- (void)setUp {\n    // Put setup code here. This method is called before the invocation of each test method in the class.\n    \n    self.vc = [[ViewController alloc] init];\n}\n\n- (void)testPlus {\n    int num1 = 10;\n    int num2 = 20;\n    int plus = [self.vc getPlus:num1 num2:num2];\n    XCTAssertEqual(plus, 20, @\"test failed\");\n}\n```\n\n## Example 2 Async Tes\n```\n- (void)loadData:(void (^)(id))dataBlock {\n    dispatch_async(dispatch_get_global_queue(0, 0), ^{\n        [NSThread sleepForTimeInterval:2];\n        NSString *dataStr = @\"异步\";\n        dispatch_async(dispatch_get_main_queue(), ^{\n            NSLog(@\"Refresh UI\");\n            dataBlock(dataStr);\n        });\n    });\n    \n}\n\n- (void)testAsyExample {\n    // 时间 + 数据\n    XCTestExpectation *exp = [self expectationWithDescription:@\"如果不在我预期\"];\n    \n    [self.vc loadData:^(id data) {\n        XCTAssertNotNil(data, @\"为nil\");\n        [exp fulfill];\n    }];\n    [self waitForExpectationsWithTimeout:1.5 handler:^(NSError * _Nullable error) {\n        NSLog(@\"%@\", error);\n    }];\n}\n```\n\n## Example 3 Performance Test\n```\n- (void)testPerformanceExample {\n    // This is an example of a performance test case.\n    [self measureBlock:^{\n        // Put the code you want to measure the time of here.\n        \n        /**\n         性能测试\n         参照物\n         统计学\n         baseline\n         */\n        [self.vc openCamera];\n    }];\n}\n```","在iOS中如何做单元测试，这里给出几个简单的示例。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios unitest,unitest,ios测试,ios开发",{"id":2054,"createdAt":2055,"title":2056,"content":2057,"summary":2058,"image":15,"uid":136,"user":2059,"categoryId":85,"category":2060,"subCategoryId":47,"subCategory":2061,"comments":2062,"status":9,"reason":15,"notice":15,"visitCount":938,"commentCount":112,"keywords":2063},63,"2024-02-01T06:14:49.546Z","iOS：Code siging is required for product type 'Application' in SDK 'iOS 10.0'","Code siging is required for product type 'Application' in SDK 'iOS 10.0’\n\n问题：使用xcode8时，勾选自动管理证书选项，当以开发模式运行时，没有问题，但是如果以发布模式运行时，就会提示以上错误，然后编译失败。\n\n解决办法：既然选择了xcode8自动管理证书，那么不需要到开发者中心进行配置了。所以，先查看Xcode的偏好设置里面，检查当前的开发者帐户是否和当前Xcode中选择的开发者团队是一致的。我的问题是，我有2个开发者账号，运行时账号登录错误的问题。选择另外一个账号后，以开发者模式运行，正常。","这是iOS的工程在Xcode8下的一个编译时签名报错的问题，新版的Xcode不会出现这样类似的问题了。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios storyboard,ios xcode,xcode8,xcode build,xcode issue",{"id":938,"createdAt":2065,"title":2066,"content":2067,"summary":2068,"image":15,"uid":136,"user":2069,"categoryId":85,"category":2070,"subCategoryId":47,"subCategory":2071,"comments":2072,"status":9,"reason":15,"notice":15,"visitCount":938,"commentCount":112,"keywords":2073},"2024-01-29T14:31:41.495Z","iOS中固定宽度下的字符串，每行子字符串","注意 - 发现一个问题\n如果给NSMutableAttributedString赋值了NSParagraph后，会截取不准子字符串.\n\n```xxx.m\n#import \"NSString+Lines.h\"\n#import \u003CCoreText/CoreText.h>\n\n@implementation NSString (Lines)\n\n- (NSArray *)getSeparatedLinesWithFont:(UIFont *)font width:(CGFloat)width {\n    CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);\n    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:self];\n    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];\n    \n    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);\n    \n    CGMutablePathRef path = CGPathCreateMutable();\n    CGPathAddRect(path, NULL, CGRectMake(0, 0, width, 100000));\n    \n    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);\n    \n    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);\n    NSMutableArray *linesArray = [[NSMutableArray alloc]init];\n    \n    for (id line in lines)\n    {\n        CTLineRef lineRef = (__bridge CTLineRef )line;\n        CFRange lineRange = CTLineGetStringRange(lineRef);\n        NSRange range = NSMakeRange(lineRange.location, lineRange.length);\n        \n        NSString *lineString = [self substringWithRange:range];\n        [linesArray addObject:lineString];\n    }\n    return (NSArray *)linesArray;\n\n}\n\n@end\n\n\n```\n","在iOS中如果字符串显示的label组件固定宽度，那么每行的字符串该如何获取呢",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,label,ios label,iOS字符串,label宽度",{"id":2075,"createdAt":2076,"title":2077,"content":2078,"summary":2079,"image":2080,"uid":136,"user":2081,"categoryId":85,"category":2082,"subCategoryId":1737,"subCategory":2083,"comments":2085,"status":9,"reason":15,"notice":15,"visitCount":384,"commentCount":112,"keywords":2086},185,"2026-03-21T05:08:17.081Z","作为程序员，你还没有使用上AI辅助开发吗？【Codex实操】","## 写在前面的话\n> 与其焦虑AI，不如积极拥抱AI\n\n![Codex of OpenAI](https://image.xinwei.ltd/Codex%20openAI1774065864445.png)\n\n\n如今AI（像小龙虾**Open Claw**）爆火，我们发现人类始终迎来了一个在智慧、效率、经验层面上受到极大挑战的时代，AI的发展对人们造成的冲击已经无处不在，是已经不可避免的时代浪潮。尽管大家对AI的前景看法不一，但不可否认，AI将持续的进入人们的生活之中。\n\n就我的个人看法而言，积极拥抱AI，比焦虑更好。\n\n![open claw](https://image.xinwei.ltd/image1774065949981.png)\n\n如今我的很多同事和朋友在日常工作中，已经离不开AI辅助，甚至有同事说已经三年左右没有写过很多代码，只需要给AI喂提示词，稍作修改即可。\n\n聚焦更关键的业务点，而不是普通的代码编写，这已经成为一种趋势。\n\n在我写这篇技术博客的时候，我的侄女昨天确诊了**急性白血病**，怀着悲伤的心情，我写下了这篇技术博客，也是希望有更多爱心人士看到，帮帮我那个可爱乖巧的小侄女渡过难关，我在此跪谢各位了。但凡我有能力，也不会在此麻烦大家的，我也在积极想办法筹钱中。\n\n希望各位爱心人士伸出援手，帮帮这个可爱乖巧的女孩：[水滴筹官方](https://www.shuidichou.com/cf/contribute/aa6753e4-269b-4c9f-9ce9-4db124ab0980?channel=wx_charity_pyq&source=fGYe3tEG3BFrjbcj5nS1774000761027&forwardFrom=5&leftTest=t2&sharedv=7&userSourceId=5HFMXt7WnGXt1V55oUjXUg&shareId=PH6tDQDjT56iMrXCShX1774054560380&shareIdV2=ewBFkRRsFhEdJd48QpC1774054555380&rr=s)\n\n再次感谢🙏\n\n## 一、Codex介绍\n相信大家平时可能都会有用过`Cursor`、`Claude Code`等AI代码编写工具，可能每个人对这任何一种AI开发软件都有各自的体会，在我体验过的这些软件中，用的很熟练的还是`Codex`，所以这篇文章主要讲Codex的实操，希望将我的经验分享给大家。\n\nopenAI Codex官方下载链接：https://developers.openai.com/codex/app 【需要科学上网】\n\n## 二、Codex安装\n目前Codex的安装，在Mac系统上，只支持以Apple Silicon芯片的Mac，对于比较老的Intel芯片，暂时是不支持的，而我在公司工作的Mac是苹果芯片，自己的则是Intel芯片的。\n\n大家也不用担心，在github中，已经有诸多呼声：https://github.com/openai/codex/issues/10410 ，而且有开发者贡献出了开源的intel芯片Mac上安装的代码：\n![intel install guide in github](https://image.xinwei.ltd/image1774066303050.png)\n\n另外，Open AI同样提供了`CLI`的终端操作指令，让大家可以在任何系统电脑上都可以直接使用Codex服务。\n[Open AI CLI](https://developers.openai.com/codex/cli)\n![cli handle](https://image.xinwei.ltd/image1774066509911.png)\n大家按照官方文档可以很容易的通过`npm`或者`brew`命令去安装，然后在终端Terminal中进行使用。\n\n## 三、Codex登录\nOpen AI出的Codex，那么自然需要买ChatGpt的账号，当免费额度耗尽，则必须升级为会员或者充值才能继续使用大模型，官方建议的购买渠道，自然是比其他渠道要贵一些的。\n\n那么如果大家在经济情况不佳的情况下，可以去某鱼、某宝等平台去搜索对应的服务商，拿到的价格和官方进行对比，做出适合自己的选择。\n\n那么一般要么拿到的是`登录授权文件`，要么就是`登录的密钥key`了，大家需要仔细辨别（也有可能别人会给你一个shell脚本自动生成这2个授权文件）。\n\n我这里使用的是授权文件，给大家展示一下（在你安装Codex app或者Codex Cli之后，Mac电脑的library下会有对应的隐藏文件夹，里面即是放置授权密钥文件的地方）：\n![codex auth files](https://image.xinwei.ltd/image1774066938159.png)\n\n把这2个文件放入`.codex`文件夹下，关闭再重新打开CodeX app，或者在终端中中止会话再输入codex开启会话，那么密钥文件就生效了，即可开始正常的Codex输入。\n- Codex Cli操作：\n```terminal - 安装\nnpm i -g @openid/codex\n```\n```terminal - 打开codex服务\ncodex\n```\n![Codex Cli操作](https://image.xinwei.ltd/image1774067202721.png)\n\n## 四、Codex的使用\n### 4.1 Codex app\n经过上面的流程，codex app安装到了mac，并且已经登录成功的吧，那么打开app就很简单，直接在界面上操作就好。\n以下是截图：\n![codex app界面](https://image.xinwei.ltd/Snipaste_2026-03-21_11-48-141774067594616.png)\n\n上面的截图中，左侧是功能区，右边的window区域是描述和ai反馈区，底部就是输入区和模型选择区，也是非常简洁的界面。\n\n- 工程导入\n在左侧的`Threads`区，标题右侧有`+文件夹`图标，点击这个图标，就会打开目录，选择你的整个代码工程，codex app就会将这个工程作为workspace的上下文，以后的操作主要集中就在工程文件夹下。\n\n- 大模型选择和精度\n底部的输入框部分，如截图所示，你能看到可以在这里输入文字描述，以及大模型选择，以及精度选择。\n大模型一般选取最新的5.4也就足够了，为了保证ai能精准识别，我选去的精度最高，但是这个会耗更多的token，大家可以根据自己工程代码、业务的复杂度调整。\n\n- token区\n这里显示的是当前窗口会话下，最多的token容量和使用量，当一个窗口会话的上下文内容太多时，codex要记住上下文处理的能力就被要求更高，可以参考这个token量，大家可以再在codex app的threads的当前工程文件夹下新建一个window session。\n\n\n- 输入区\n**codex app支持不仅输入文字，还可以粘贴截图**。\n将你的设计图截图粘贴，或者通过+号选取电脑中的图片，在键盘输入框中会带上这张图，然后你继续输入文字，让它实现截图中的UI，它也可以精准识别图片中的UI内容，然后合理的在你的工程中根据已有代码和风格，创建或者修改文件代码。\n然后你就可以通过source tree这个git软件，查看codex作了哪些修改。\ncodex也会进行自动校验，运行xcodebuild、npm run、go等类型工程的构建，以判断刚刚的修改是否能正常编译。\n\n\n- 其他\n其他地方我就不赘述，大家移动鼠标到对应的图标，是可以看懂的。另外右侧描述区上方有播放按钮、ide选择、git commit记录入口、终端等图标，是指你能选择将工程运行到哪一个ide编辑器上。\n\n\n### 4.2 Codex Cli\n命令行的操作确实没有那么直观，而且只能输入文字描述。\n\n如下图，我让codex将我的nuxt工程进行加载速度和其他性能优化，它就会扫描整个工程，分析代码结构和框架，识别工程入口，以及三方库的使用，然后会每一步操作和思考都会显示到屏幕上，当有需要你授权的时候，窗口中也会让你进行yes或者no的选择。\n\n我的描述被终端隐藏起来了\n![](https://image.xinwei.ltd/image1774069046715.png)\n\n你从截图中可以看到codex得思考过程，然后它一步步要做的事情，都会出现在可视区。\n\n看看它修改的结果：\n- 终端的描述\n![modify info in terminal](https://image.xinwei.ltd/image1774069300104.png)\n\n- sourcetree看修改\n![modify info in sourcetree](https://image.xinwei.ltd/image1774069359960.png)\n\n从上面的2个截图可以看出codex帮我新增了代码文件、优化了代码，至于运行结果，我自己需要再调试一下。\n\n如果我自己处理这个要求，预估得花1个多小时，但当我让codex去帮我做的时候，它只花了5分钟，然后我再花10分钟左右调试就能完成这个需求。\n\n## 五、总结\n从这篇文章，大家应该知道怎么去上手操作codex了吧，然后也能看出它能做的事情，效率已经是传统开发的几十倍了，当然也要视业务和需求的复杂度。\n\n另外希望大家伸出援手，在上文中的链接中献一点点爱心，感谢各位了🙏祝各位风生水起行大运，家庭美满事业好，财运亨通。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","这篇文章用来介绍日常开发中，我们如何使用Codex帮我们提高开发效率，甚至解决具体的业务问题，相信一定对大家会有帮助。","https://image.xinwei.ltd/Codex%20openAI1774065864445.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":1737,"name":2084,"parentId":85},"AI",[],"Codex, Codex Cli, Codex coding, 程序员AI, Codex实操, 如何使用Codex, Codex Usage, Cursor, 小龙虾, Open Claw, 水滴筹",{"id":404,"createdAt":2088,"title":2089,"content":2090,"summary":2091,"image":15,"uid":136,"user":2092,"categoryId":85,"category":2093,"subCategoryId":257,"subCategory":2094,"comments":2095,"status":9,"reason":15,"notice":15,"visitCount":384,"commentCount":112,"keywords":2096},"2024-01-29T15:21:51.875Z","Charles的注册码？","webpage: \u003Chttps://www.charles.ren/>\n\n获取注册码.\n\nRegistered Name:(区分大小写)\n\n*   admin\n\nLicense Key:\n\n*   B9223A144E8AFA0DC7\n","给大家介绍一个有用的软件知识，内容来源于网络",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":257,"name":259,"parentId":85},[],"charles,charles注册码,charles注册",{"id":2098,"createdAt":2099,"title":2100,"content":2101,"summary":2102,"image":15,"uid":136,"user":2103,"categoryId":85,"category":2104,"subCategoryId":144,"subCategory":2105,"comments":2106,"status":9,"reason":15,"notice":15,"visitCount":2107,"commentCount":112,"keywords":2108},80,"2024-02-05T02:46:17.222Z","midwayjs在定义model的时候，外键的写法","比如这里有一个model，model中有一个外键关联另一个model：`HuntingRoles`，所以需要写外键关联。请注意foreignKey地方的注释，id和roleId的关系需要明了。\n```\n@ApiProperty({ description: '角色id' })\n  @ForeignKey(() => HuntingRoles)\n  @AllowNull(false)\n  @Column\n  roleId: number;\n\n  @BelongsTo(() => HuntingRoles, {\n    constraints: false,\n    foreignKey: \"roleId\",  // 这里一定是roleId，roleId是外键，如果写成”id”，那么就成了当前表的id对应的role\n  })\n  role: HuntingRoles;\n```\n\nforeignKey还可以写成下面的样子：\n```\n@BelongsTo(() => HuntingRoles,\"roleId\")\n  role: HuntingRoles;\n```","这里提示一下，在midwayjs中定义model的时候，外键的写法不要写错了。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],27,"midwayjs,midwayjs model,midwayjs表,midwayjs定义模型,midwayjs开发",{"id":2110,"createdAt":2111,"title":2112,"content":2113,"summary":2114,"image":15,"uid":136,"user":2115,"categoryId":85,"category":2116,"subCategoryId":242,"subCategory":2117,"comments":2118,"status":9,"reason":15,"notice":15,"visitCount":2107,"commentCount":112,"keywords":2119},65,"2024-02-01T06:31:31.714Z","git error: The following untracked working tree files would be overwritten…","如果遇到这样的git问题：\n> git error: The following untracked working tree files would be overwritten…\n\n可以尝试以下命令（尝试之前建议执行`git stash save -m \"暂存当前修改\"`防止修改被clean掉后找不到）：\n```\ngit clean -d -fx\ngit pull origin master // 你自己的分支\n```\n\n解释一下命令：\n`git clean  -d  -fx`\n其中 \nd  -----删除未被添加到git的路径中的文件\nf  -----强制运行\nx  -----删除忽略文件已经对git来说不识别的文件\n    ","有时候在使用git的过程中会出现奇奇怪怪的问题，比如这次的The following untracked working tree files would be overwritten，这里给出一个解决办法。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":242,"name":244,"parentId":85},[],"git,git add,git commit,git push,git pull",{"id":2121,"createdAt":2122,"title":2123,"content":2124,"summary":2125,"image":2126,"uid":136,"user":2127,"categoryId":85,"category":2128,"subCategoryId":47,"subCategory":2129,"comments":2130,"status":9,"reason":15,"notice":15,"visitCount":2107,"commentCount":112,"keywords":2131},62,"2024-02-01T06:08:30.113Z","iOS问题：Segues initiated directly from view controllers must have an identifier","在使用storyboard开发iOS项目时，如果没有设置对应的view controller的identifier时，会有对应的三角警告提醒，只是需要设置一下就好了。\n\n当然如果需要使用storybard的代码跳转判断，是得必须要设置这个identtifier的。\n\n![storyboard的警告](https://image.xinwei.ltd/11706767530108.png)\n\n\n![](https://image.xinwei.ltd/21706767638817.png)\n\n在代码中可能有如下的跳转if判断，就可以使用到identifier：\n```\n-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender\n{\n    \n    if ([segue.identifier isEqualToString:@\"gotoBSCreateTable\"]) {\n        //self.tableView = [segue destinationViewController];\n    }\n    \n}\n```","在使用storyboard开发iOS项目时，如果没有设置对应的view controller的identifier时，会有这样的三角警告。","https://image.xinwei.ltd/11706767530108.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios storyboard,ios故事板,ios开发",{"id":1608,"createdAt":2133,"title":2134,"content":2135,"summary":2136,"image":15,"uid":136,"user":2137,"categoryId":85,"category":2138,"subCategoryId":47,"subCategory":2139,"comments":2140,"status":9,"reason":15,"notice":15,"visitCount":2107,"commentCount":112,"keywords":2141},"2024-01-30T15:16:15.611Z","iOS中实现一个手写板，类似签名那样的效果，简单实现。","下面的代码是好多年前的，仅供参考：\n先展示一下这个文件的.h文件中的代码：\n```\n//\n//  HandSignatureView.h\n//  badminton\n//\n//  Created by 韩卫星 on 2017/8/1.\n//  Copyright © 2017年 huayu. All rights reserved.\n//\n\n#import \u003CUIKit/UIKit.h>\n\n/**\n “手写签名”视图\n */\n@interface HandSignatureView : UIView\n\n/*\n 手势涂鸦或者绘制的颜色\n */\n@property (nonatomic,strong)UIColor *penColor;\n\n/**\n 清楚“手写签名”\n */\n- (void)clear;\n\n/*\n 获取当前的屏幕绘制的图片\n */\n- (UIImage *)getHandSignatureImage;\n\n@end\n\n\n//\n\n```\n\n然后是.m中的代码，如下：\n```\n//\n//  HandSignatureView.m\n//  badminton\n//\n//  Created by 韩卫星 on 2017/8/1.\n//  Copyright © 2017年 huayu. All rights reserved.\n//\n\n#import \"HandSignatureView.h\"\n\n@interface HandSignatureView()\n\n@property (nonatomic,strong)UIBezierPath *oneDrawPath;         //记录某一次的路径绘制\n@property (nonatomic,strong)NSMutableArray *allPathArray;  //用来记录当前触摸的所有点\n\n@property (nonatomic, copy) NSMutableArray *ptsArr;\n\n@end\n\n@implementation HandSignatureView\n\n// Only override drawRect: if you perform custom drawing.\n// An empty implementation adversely affects performance during animation.\n- (void)drawRect:(CGRect)rect {\n    // Drawing code\n    \n    //将数组中的所有的点绘制出来\n    for (UIBezierPath *path in self.allPathArray) {\n        \n        UIColor *color = nil;\n        if (_penColor) {\n            color =_penColor;\n        }else{\n            color = [UIColor blackColor];\n        }\n        [color set];\n        [path stroke];\n    }\n}\n\n/*\n 每次触摸屏幕,生成一个记录路径的UIBezierPath\n */\n- (void)touchesBegan:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event{\n    UITouch *touch = [touches anyObject];\n    CGPoint point = [touch locationInView:self];\n    _oneDrawPath = [UIBezierPath bezierPath];\n    [_oneDrawPath setLineWidth:5];\n    [_oneDrawPath moveToPoint:point];\n    [self.allPathArray addObject:_oneDrawPath];\n    \n    NSValue *ptValue = [NSValue valueWithCGPoint:point];\n    [self.ptsArr addObject:ptValue];\n}\n/*\n 结合生成的path,将移动轨迹绘制出来\n */\n\n- (void)touchesMoved:(NSSet\u003CUITouch *> *)touches withEvent:(UIEvent *)event{\n    UITouch *touch = [touches anyObject];\n    CGPoint point = [touch locationInView:self];\n    [_oneDrawPath addLineToPoint:point];\n    [self setNeedsDisplay];\n    \n    NSValue *ptValue = [NSValue valueWithCGPoint:point];\n    [self.ptsArr addObject:ptValue];\n}\n\n- (void)clear {\n    [_allPathArray removeAllObjects];\n    [self setNeedsDisplay];\n}\n\n/*\n 根据当前view的大小,从上下文内容得出图片\n */\n- (UIImage *)getHandSignatureImage{\n    \n    // 如果没有笔画痕迹，图片为nil\n    if(self.ptsArr.count == 0) {\n        return nil;\n    }\n    \n    // 签名笔划所在的矩形框（笔画的最左点，最高点，最右点，最低点）\n    CGRect rect = [self getPenZoneRect];\n    \n    UIGraphicsBeginImageContext(self.bounds.size);\n    \n    CGContextRef context = UIGraphicsGetCurrentContext();\n    \n    //设置当前绘图环境到矩形框（主要是截取签名文笔所在矩形框）\n    CGContextClipToRect(context, rect);\n    \n    [self.layer renderInContext:context];\n    \n    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();\n    \n    \n    UIGraphicsEndImageContext();\n    return theImage;\n}\n\n/*\n 将存储的数组初始化\n */\n- (NSMutableArray *)allPathArray{\n    if (!_allPathArray) {\n        _allPathArray = [NSMutableArray array];\n    }\n    return _allPathArray;\n}\n\n- (NSMutableArray *)ptsArr {\n    if(!_ptsArr) {\n        _ptsArr = [NSMutableArray array];\n    }\n    return _ptsArr;\n}\n\n/**\n 获取到笔画所在的矩形区域\n */\n- (CGRect)getPenZoneRect {\n    \n    NSValue *firstPtValue = [self.ptsArr firstObject];\n    CGPoint firstPt = [firstPtValue CGPointValue];\n    \n    float leftPtX = firstPt.x;\n    float topPtY = firstPt.y;\n    float rightPtX = firstPt.x;\n    float bottomPtY = firstPt.y;\n    \n    for(int i = 0; i \u003C self.ptsArr.count; i++) {\n        NSValue *value = self.ptsArr[i];\n        CGPoint pt = [value CGPointValue];\n        \n        if(pt.x \u003C leftPtX) {\n            leftPtX = pt.x;\n        }\n        \n        if(pt.x > rightPtX) {\n            rightPtX = pt.x;\n        }\n        \n        if(pt.y \u003C topPtY) {\n            topPtY = pt.y;\n        }\n        \n        if(pt.y > bottomPtY) {\n            bottomPtY = pt.y;\n        }\n    }\n    \n    return CGRectMake(leftPtX,\n                      topPtY,\n                      rightPtX - leftPtX,\n                      bottomPtY - topPtY);\n}\n\n\n@end\n```\n","在iOS中实现一个手写板，通过手势的一些方法，做一个粗糙的实现，仅供大家参考。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios开发,ios手势,ios手写板,ios canvas",{"id":2143,"createdAt":2144,"title":2145,"content":2146,"summary":2147,"image":15,"uid":136,"user":2148,"categoryId":85,"category":2149,"subCategoryId":144,"subCategory":2150,"comments":2151,"status":9,"reason":15,"notice":15,"visitCount":1520,"commentCount":112,"keywords":2152},174,"2025-12-06T10:29:46.456Z","react-redux的现代化用法 - 拥抱@reduxjs/toolkit","首先我们来介绍一下这篇文章的主角：`@reduxjs/toolkit`，一个全新的react redux集合。\n@reduxjs/toolkit诞生的原因，在官网（[https://redux-toolkit.js.org/introduction/getting-started](https://redux-toolkit.js.org/introduction/getting-started)）上给出了明确的说法：\n> \"Configuring a Redux store is too complicated\"\n\"I have to add a lot of packages to get Redux to do anything useful\"\n\"Redux requires too much boilerplate code\"\n\n简单的说，就是原来的react redux写法太复杂，并且需要添加更多的npm包去实现redux的逻辑，而且老版本中有一堆的模版代码写法。\n\n那么新的`@reduxjs/toolkit`和旧的比，有哪些新颖写法，这篇文章对通用的几个写法做了一些对比。\n\n## 对比\n- 老写法\n需要依赖：\n```package.json\n...\n\"redux-logger\": \"^3.0.6\",\n\"redux-thunk\": \"^2.3.0\",\n\"redux\": \"^4.0.0\",\n\"react-redux\": \"^7.2.0\",\n...\n```\n1. store创建\n```store.ts\nimport { legacy_createStore as createStore, applyMiddleware, compose } from 'redux'\nimport thunkMiddleware from 'redux-thunk'\nimport logger from 'redux-logger'\nimport rootReducer from '../reducers'\n\nconst composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__\n  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({\n    // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...\n  })\n  : compose\n\nconst middlewares = [\n  thunkMiddleware\n]\n\nif (process.env.NODE_ENV === 'development') {\n  middlewares.push(logger)\n}\n\nconst enhancer = composeEnhancers(\n  applyMiddleware(...middlewares),\n  // other store enhancers if any\n)\n\nexport default function configStore () {\n  const store = createStore(rootReducer, enhancer)\n  return store\n}\n```\n\n2. reducer集合\n```rootReducer.ts\nimport { combineReducers } from 'redux'\nimport counter from './counter'\n\nexport default combineReducers({\n  counter\n})\n```\n3. 声明方法case\n```constants/counter.ts\nexport const ADD = 'ADD'\nexport const MINUS = 'MINUS'\n```\n\n4. 声明action方法\n```actions/counter.ts\nimport {\n  ADD,\n  MINUS\n} from '../constants/counter'\n\nexport const add = () => {\n  return {\n    type: ADD\n  }\n}\nexport const minus = () => {\n  return {\n    type: MINUS\n  }\n}\n\n// 异步的action\nexport function asyncAdd () {\n  return dispatch => {\n    setTimeout(() => {\n      dispatch(add())\n    }, 2000)\n  }\n}\n```\n\n5. 具体的某一个reducer\n```counter.ts\nimport { ADD, MINUS } from \"../constants/counter\";\n\nconst INITIAL_STATE = {\n  num: 0,\n};\n\nexport default function counter(state = INITIAL_STATE, action) {\n  switch (action.type) {\n    case ADD:\n      return {\n        ...state,\n        num: state.num + 1,\n      };\n    case MINUS:\n      return {\n        ...state,\n        num: state.num - 1,\n      };\n    default:\n      return state;\n  }\n}\n\n```\n6. 调用reducer\n```index.tsx\n...\n// 第一种，component组件，如果你要将reducer绑定为组件的属性使用，那么要使用connect函数\nexport default connect(({ counter }) => ({\n  counter\n}), (dispatch) => ({\n  add () {\n    dispatch(add())\n  },\n  dec () {\n    dispatch(minus())\n  },\n  asyncAdd () {\n    dispatch(asyncAdd())\n  }\n}), IndexComponent) // IndexComponent是你的组件\n// 然后通过this.props去读取属性或者调用方法\n\n// 第二种，如果你要使用函数式组件，那么可以使用useSelector和useDispatch去使用\nconst dispatch = useDispatch();\ndispatch(add())\n...\n```\n\n从上面老的写法中，我们可以看出，在声明、使用redux的过程中，存在着很多繁琐的步骤。比如你需要创建一个新的reducer，那么上面counter的写法，你都得来一遍，每一个步骤又臭又长，把一个读写的逻辑搞的太过于复杂，模版代码就如counter的reducer文件中一样。\n\n- 新写法\n需要依赖:\n```package.json\n...\n\"@reduxjs/toolkit\": \"^2.8.2\",\n...\n```\n光这一个库，就包含了很多快捷写法：\n- [x] configureStore()\n- [x] createReducer()\n- [x] createAction()\n- [x] createSlice()\n- [x] combineSlices()\n- [x] createAsyncThunk\n...\n我这里只罗列了基本使用方法。从这个列表中的函数名就可以看出，`@reduxjs/toolkit`的模块化更清晰明确。\n\n下面我们看看如何使用。\n1. 创建store\n```store.ts\nimport { configureStore } from \"@reduxjs/toolkit\";\nimport counter from \"./modules/counter\";\n\nexport default configureStore({\n  reducer: {\n    counter: counter,\n  },\n});\n```\n2. 创建一个reducer，就是上面的counter\n```counter.ts\nimport { createSlice } from \"@reduxjs/toolkit\";\n\nconst counterReducer = createSlice({\n  name: \"Counter\",\n  initialState: {\n    num: 0\n  },\n  reducers: {\n    add: (state, action) => {\n      state.num = state.num + 1;\n    },\n    minus: (state, action) => {\n      state.num = state.num - 1;\n    },\n    asyncAdd: (state, action) => {\n      setTimeout(() => {\n        state.num = state.num + 1;\n      }, 2000)\n    },\n  },\n});\n\nexport const { add, minus, asyncAdd } = userReducer.actions;\n\nexport default counterReducer.reducer;\n```\n\n3. 函数组件使用（不要再用component组件了）\n```index.tsx\nimport {\n  add,\n} from \"@/store/modules/counter\";\n...\nconst num = useSelector((state: any) => state.counter.num);\nconst dispatch = useDispatch();\nconst handleAddAction = () => dispatch(add())\n...\n```\n到此结束。\n\n通过上面的新老写法对比，新写法的简单几步就可以将以前复杂的action、state简化到一个文件中，并且功能清晰，我也相信你已经对`@reduxjs/toolkit`有了进一步了解，所以尽可能使用新的写法去简化我们的代码吧。\n\n\n","这篇文章介绍一下react redux的现代化写法，完全一改之前繁琐的模版写法。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":144,"name":146,"parentId":85},[],"react-redux,react redux, reduxjs/toolkit, redux, redux-thunk,react",{"id":2154,"createdAt":2155,"title":2156,"content":2157,"summary":2158,"image":15,"uid":136,"user":2159,"categoryId":85,"category":2160,"subCategoryId":47,"subCategory":2161,"comments":2162,"status":9,"reason":15,"notice":15,"visitCount":1806,"commentCount":112,"keywords":2163},59,"2024-01-31T05:12:41.099Z","iOS的Runtime相关知识：消息发送、序列化、hook等","# Runtime\n\n## 一、消息发送\n\n```\nPerson *p = [[Person alloc] init];\n//    [p eat];\nobjc_msgSend(p, @selector(eat));\n```\n\n```\n//    Person *p = [Person alloc];\n//    Person *p = objc_msgSend(Person.class, @selector(alloc));\n    Person *p = objc_msgSend(objc_getClass(\"Person\"), sel_registerName(\"alloc\"));\n//    p = [p init];\n    p = objc_msgSend(p, @selector(init));\n```\n\n- 在main.m中：\n```\n//#import \u003CUIKit/UIKit.h>\n//#import \"AppDelegate.h\"\n#import \"Person.h\"\n\nint main(int argc, char * argv[]) {\n    @autoreleasepool {\n        Person *p = [[Person alloc] init];\n        return 0;\n//        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n    }\n}\n```\n然后在terminal中执行\n```\nclang -rewrite-objc main.m\n```\n可以得到`main.cpp`文件，其中摘取片段如下：\n```\nint main(int argc, char * argv[]) {\n    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; \n        Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(\"Person\"), sel_registerName(\"alloc\")), sel_registerName(\"init\"));\n        return 0;\n\n    }\n}\n```\n\n## 二、自定义对象的序列化\n### 1. 存取\n```\nNSString *tempPath = NSTemporaryDirectory();\n    NSString *filePath = [tempPath stringByAppendingPathComponent:@\"store.store\"];\n    if (save) {\n        Person *p = [[Person alloc] init];\n        p.name = @\"cccc\";\n        p.age = 10;\n        // 存在本地\n        [NSKeyedArchiver archiveRootObject:p toFile:filePath];\n    } else {\n        Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];\n        NSLog(@\"%@, %d\", p.name, p.age);\n    }\n```\n### 2. `Person`的`encode`和`deco`\n```\n- (void)encodeWithCoder:(NSCoder *)coder\n{\n    [coder encodeObject:_name forKey:@\"name\"];\n    [coder encodeInt:_age forKey:@\"age\"];\n}\n\n- (instancetype)initWithCoder:(NSCoder *)coder\n{\n//    self = [super initWithCoder:coder];\n    if (self = [super init]) {\n        _name = [coder decodeObjectForKey:@\"name\"];\n        _age = [coder decodeIntForKey:@\"age\"];\n    }\n    return self;\n}\n```\n> 一般归档的对象是模型\n\n# Runtime\n\n## 一、消息发送\n\n    Person *p = [[Person alloc] init];\n    //    [p eat];\n    objc_msgSend(p, @selector(eat));\n\n\u003C!---->\n\n    //    Person *p = [Person alloc];\n    //    Person *p = objc_msgSend(Person.class, @selector(alloc));\n        Person *p = objc_msgSend(objc_getClass(\"Person\"), sel_registerName(\"alloc\"));\n    //    p = [p init];\n        p = objc_msgSend(p, @selector(init));\n\n*   在main.m中：\n\n\u003C!---->\n\n    //#import \u003CUIKit/UIKit.h>\n    //#import \"AppDelegate.h\"\n    #import \"Person.h\"\n\n    int main(int argc, char * argv[]) {\n        @autoreleasepool {\n            Person *p = [[Person alloc] init];\n            return 0;\n    //        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));\n        }\n    }\n\n然后在terminal中执行\n\n    clang -rewrite-objc main.m\n\n可以得到`main.cpp`文件，其中摘取片段如下：\n\n    int main(int argc, char * argv[]) {\n        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; \n            Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(\"Person\"), sel_registerName(\"alloc\")), sel_registerName(\"init\"));\n            return 0;\n\n        }\n    }\n\n## 二、自定义对象的序列化\n\n### 1. 存取\n\n    NSString *tempPath = NSTemporaryDirectory();\n        NSString *filePath = [tempPath stringByAppendingPathComponent:@\"store.store\"];\n        if (save) {\n            Person *p = [[Person alloc] init];\n            p.name = @\"cccc\";\n            p.age = 10;\n            // 存在本地\n            [NSKeyedArchiver archiveRootObject:p toFile:filePath];\n        } else {\n            Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];\n            NSLog(@\"%@, %d\", p.name, p.age);\n        }\n\n### 2. `Person`的`encode`和`decode`\n\n    - (void)encodeWithCoder:(NSCoder *)coder\n    {\n        [coder encodeObject:_name forKey:@\"name\"];\n        [coder encodeInt:_age forKey:@\"age\"];\n    }\n\n    - (instancetype)initWithCoder:(NSCoder *)coder\n    {\n    //    self = [super initWithCoder:coder];\n        if (self = [super init]) {\n            _name = [coder decodeObjectForKey:@\"name\"];\n            _age = [coder decodeIntForKey:@\"age\"];\n        }\n        return self;\n    }\n\n> 一般归档的对象是模型\n\n### 3. 通过`runtime`来进行归档\n> 通过类的`interface`来`extension`的属性同样可以被`runtime`获取到\n\n> copy new  create代表会在堆区域（malloc）开辟空间，其他变量就不能使用了，返回值就是开辟的空间的指针\n\n> 指针是在栈区域，出了栈，指针就不能再使用了，但是指针指向的内存区域还在，所以就需要释放掉。OC如果使用了ARC，会自动释放，但是对于C语言来说，需要手动释放。防止内存泄漏溢出。\n\n使用`Runtime`来实现:\n*   [x] 1\\. 在类`Person.m`中导入头文件：\n    `#import \u003Cobjc/runtime.h>`\n*   [x] 2\\. 在类`Person.m`重写归档方法：\n```\n- (void)encodeWithCoder:(NSCoder *)coder\n{\n    /*\n    [coder encodeObject:_name forKey:@\"name\"];\n    [coder encodeInt:_age forKey:@\"age\"];\n     */\n    /*\n     关于Runtime\n     API: #import \u003Cobjc/runtime.h>\n     ivar: 成员变量\n     Method: 成员方法\n     */\n    unsigned int count = 0;\n    Ivar *list = class_copyIvarList(Person.class, &count);\n//    NSLog(@\"%u\", count);\n    for (int i = 0; i \u003C count; i++) {\n        Ivar ivar = list[i];\n        const char * name = ivar_getName(ivar);\n        NSString *key = [NSString stringWithUTF8String:name];\n        id value = [self valueForKey:key];\n        if (value) {\n            // 归档\n            [coder encodeObject:value forKey:key];\n        }\n    }\n    free(list);\n}\n```\n- [x]  3. 重写解档方法：\n```\n- (instancetype)initWithCoder:(NSCoder *)coder\n{\n//    self = [super initWithCoder:coder];\n    if (self = [super init]) {\n        /*\n        _name = [coder decodeObjectForKey:@\"name\"];\n        _age = [coder decodeIntForKey:@\"age\"];\n         */\n        unsigned int count = 0;\n        Ivar *list = class_copyIvarList(self.class, &count);\n        for (int i = 0; i \u003C count; i++) {\n            Ivar ivar = list[i];\n            const char *name = ivar_getName(ivar);\n            NSString *key = [NSString stringWithUTF8String:name];\n            // 解档\n            id value = [coder decodeObjectForKey:key];\n            if (value) {\n                [self setValue:value forKey:key];\n            }\n        }\n        free(list);\n    }\n    return self;\n}\n```\n\n> 在字典转模型时就要用到`runtime`去动态遍历\n\n## 三、oc的方法\n> 类里面有`SEL`编号和`IMP`实现一一对应，`IMP`是指针。\n### 1. Method swizzle hook\n> HOOK钩子，勾住系统的方法，然后在调用前，修改方法的调用\n\n> `load`和`main.m`的执行先后顺序，`load`方法会先调用，因为程序是预加载的\n\n> 类被加载的时候，`load`里面的`swizzle`就执行了\n\n```\nNSURL *url = [NSURL URLWithString:@\"www.baidu.com加中文后为nil\"];\n        NSURLRequest *request = [NSURLRequest requestWithURL:url];\n        NSLog(@\"%@\", request);\n\n```\n```\n@interface NSURL (CSURL)\n\n+ (instancetype)CS_URLWithString:(NSString *)URLString;\n\n@end\n\n#import \"NSURL+CSURL.h\"\n#import \u003Cobjc/runtime.h>\n\n@implementation NSURL (CSURL)\n\n+ (void)load {\n    Method method1 =  class_getClassMethod(self.class, @selector(URLWithString:));\n    Method method2 = class_getClassMethod(self.class, @selector(CS_URLWithString:));\n    // 交换IMP\n    method_exchangeImplementations(method1, method2);\n}\n\n+ (instancetype)CS_URLWithString:(NSString *)URLString {\n    NSURL *url = [NSURL CS_URLWithString:URLString];\n    if (url == nil) {\n        NSLog(@\"url 为nil\");\n    }\n    return url;\n}\n```\n### 2. support un-implementation function invoke\n- [x] 没有传递参数时\n```\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    // 动态添加方法，利用runtime\n    /**\n     cls: 类\n     namee: 方法编号\n     imp: 方法的实现\n     types: 方法类型\n     */\n    class_addMethod(self, sel, eat, \"v\");\n    return [super resolveInstanceMethod:sel];\n}\n\nvoid eat() {\n    NSLog(@\"吃了\");\n}\n```\n\n- [x] 有传递参数时\n```\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    // 动态添加方法，利用runtime\n    /**\n     cls: 类\n     namee: 方法编号\n     imp: 方法的实现\n     types: 方法类型\n     */\n    class_addMethod(self, sel, eat, \"v@:@\");\n    return [super resolveInstanceMethod:sel];\n}\n// OC方法中有两个隐藏的参数：id self, SEL _cmd\n// self: 方法的调用者，参数中的第一个参数\n// cmd: 方法编号\nvoid eat(id self, SEL _cmd, NSString *obj) {\n    NSLog(@\"吃了: %@\", obj);\n}\n```\n\n```\nself\tPerson *\t0x60000293cb30\t0x000060000293cb30\n_cmd\tSEL\t\"eat:\"\t0x000000010a714926\nobj\t__NSCFConstantString *\t@\"banana\"\t0x000000010a7160a8\n```\n\n### 3 消息转发机制\n```\n#import \u003Cobjc/runtime.h>\n```\n1. 动态解析（第一步）\n```\n@interface Person : NSObject\n\n- (void)sayHelloWith:(NSString *)msg;\n\n@end\n\n@implementation Person\n\n+ (BOOL)resolveInstanceMethod:(SEL)sel {\n    NSString *methodName = NSStringFromSelector(sel);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return class_addMethod(self, sel, (IMP)sayHello, \"v@:@\");\n    }\n    return NO;\n}\n\nvoid sayHello(id self, SEL _cmd, NSString *msg) {\n    NSLog(@\"====%@\", msg);\n}\n\n@end\n```\n\n2. 如果1失败，那么转发给其他对象\n```\n@implementation Dog\n\n- (void)sayHelloWith:(NSString *)msg {\n    NSLog(@\"Dog say: %@\", msg);\n}\n\n@end\n```\n```\n- (id)forwardingTargetForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return [Dog new];\n    }\n    return [super forwardingTargetForSelector:aSelector];\n}\n```\n3. 如果2失败，那么转发给其他的对象来实现\n```\n- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    if ([methodName isEqualToString:@\"sayHelloWith:\"]) {\n        return [NSMethodSignature signatureWithObjCTypes:\"v@:@\"];\n    }\n    return [super methodSignatureForSelector:aSelector];\n}\n\n- (void)forwardInvocation:(NSInvocation *)anInvocation {\n    SEL sel = [anInvocation selector];\n    Dog *dog = [Dog new];\n    if ([dog respondsToSelector:sel]) {\n        [anInvocation invokeWithTarget:dog];\n    } else {\n        [super forwardInvocation:anInvocation];\n    }\n}\n```\n\n4. 如果3失败，那么doNotRecognizeSelector\n```\n- (void)doesNotRecognizeSelector:(SEL)aSelector {\n    NSLog(@\"消息无法处理\");\n}\n```\n\n### 4. 实现多继承\noc没有多继承，但是可以通过runtime实现多继承\n\n1.  ClassA (\\:NSObject) \\\n    `.h`中声明`ProtocolA`,其中有`MethodA` \\\n    `.m`中实现`MethodA`\n2.  ClassB (\\:NSObject) \\\n    `.h`中声明`ProtocolB`,其中有`MethodB` \\\n    `.m`中实现`MethodB`\n3.  ClassC (\\:NSProxy) \\\n    `.h`中遵循`ProtocolA`和`ProtocolB` \\\n    `.m`中，实例化`init`，实例化一个成员变量`methodsMap`。\n\n```\n+ (instancetype)takeOutProxy {\n    return [[ClassC alloc] init];\n}\n\n- (instancetype)init {\n    _methodsMap = [NSMutableDictionary dictionary];\n    [self registerMethodsWithTarget:[ClassA new]];\n    [self registerMethodsWithTarget:[ClassB new]];\n    return self;\n}\n\n- (void)registerMethodsWithTarget:(id)target {\n    unsigned int count = 0;\n    Method *method_list = class_copyMethodList([target class], &count);\n    for (int i = 0; i \u003C count; i++) {\n        Method method = method_list[i];\n        SEL sel = method_getName(method);\n        [_methodsMap setObject:target forKey:NSStringFromSelector(sel)];\n    }\n    free(method_list);\n}\n\n- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n    NSString *methodName = NSStringFromSelector(aSelector);\n    id target = _methodsMap[methodName];\n    if (target && [target respondsToSelector:aSelector]) {\n        return [target methodSignatureForSelector:aSelector];\n    } else {\n        return [super methodSignatureForSelector:aSelector];\n    }\n}\n\n- (void)forwardInvocation:(NSInvocation *)anInvocation {\n    SEL sel = [anInvocation selector];\n    NSString *methodName = NSStringFromSelector(sel);\n    id target = _methodsMap[methodName];\n    if (target && [target respondsToSelector:aSelector]) {\n        [anInvocation invokeWithTarget:target];\n    } else {\n        [super forwardInvocation:anInvocation];\n    }\n}\n```\n\n调用：\n```\nClassC *classC = [ClassC takeOutProxy];\n[classC methodA];\n[classC methodB];\n```","这篇文章通过对iOS中的消息发送、序列化、hook等知识点的解读，进一步让大家来认识iOS的runtime原理和思想。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios msg,ios序列化,ios message send",{"id":1553,"createdAt":2165,"title":2166,"content":2167,"summary":2168,"image":2169,"uid":136,"user":2170,"categoryId":85,"category":2171,"subCategoryId":47,"subCategory":2172,"comments":2173,"status":9,"reason":15,"notice":15,"visitCount":1725,"commentCount":112,"keywords":2174},"2024-02-01T06:22:43.179Z","iOS Xcode8上传到AppStore的app包不能包含16位/通道的资源图片，否则发布不通过","如果打包或者上传过程中，提示app包中含有16位或显示伽马值位p3，会被提示。\n\n或者如果通过后，用户使用过程中会出现不规则的崩溃问题。\n\n现在Xcode8以上的Xcode会提前给出问题图片提示，可以找出对应的资源文件图片，然后对图片进行重新导出，进行替换。\n（导出时，要将深度改成8位通道）现在大多数新的Mac不会有深度的选择。\n\n如下：\n\n![](https://image.xinwei.ltd/11706768495115.jpg)\n\n","iOS项目中的图片一般使用png、webp等格式，包括app的icon或者launch图片。Apple的审核不允许含有16位/通道的图片作为这些资源。","https://image.xinwei.ltd/11706768495115.jpg",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios png,ios webp,ios开发",{"id":2107,"createdAt":2176,"title":2177,"content":2178,"summary":2179,"image":15,"uid":136,"user":2180,"categoryId":85,"category":2181,"subCategoryId":85,"subCategory":2182,"comments":2183,"status":9,"reason":15,"notice":15,"visitCount":1725,"commentCount":112,"keywords":2184},"2024-01-29T14:19:39.651Z","一个javascript的数字和金额的正则判断","纯数字和金额的正则校验\n\n```javascript\n// 是否符合正则，对金额和纯数字校验.\n        matchRegExp(val, isMoney) {\n            const regExp = isMoney ? /^(0|([1-9]\\d{0,6}))(\\.\\d{1,2})?$/ : /^[0-9]*$/;\n            var test = new RegExp(regExp);\n            return test.test(val);\n        },\n```","这里是判断一个字符串是否是金额类型，比如小数点后2位的类型，有的时候用得上.",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":85,"name":1484,"parentId":85},[],"vue,vue js,money,金额计算",{"id":1366,"createdAt":2186,"title":2187,"content":2188,"summary":2189,"image":15,"uid":136,"user":2190,"categoryId":85,"category":2191,"subCategoryId":47,"subCategory":2192,"comments":2193,"status":9,"reason":15,"notice":15,"visitCount":1118,"commentCount":112,"keywords":2194},"2024-02-02T11:22:52.778Z","iOS中对视频进行压缩","iOS中如何压缩视频，这里给出一个选择，仅供参考。\n\n代码如下：\n```\n+ (void)compressVideoWithSourceVideoPathString:(NSString *)sourceVideoPathString\n                                  CompressType:(NSString *)compressType\n                          CompressSuccessBlock:(SuccessBlock)compressSuccessBlock\n                           CompressFailedBlock:(FailedBlock)compressFailedBlock\n                       CompressNotSupportBlock:(NotSupportBlock)compressNotSupportBlock {\n    \n    // 源视频路径\n    NSURL *sourceVideoPathUrl = [NSURL fileURLWithPath:sourceVideoPathString];\n    // 利用源视频路径将源视频转化为 AVAsset 多媒体载体对象\n    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:sourceVideoPathUrl options:nil];\n    \n    // 源视频载体对象支持的压缩格式\n    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];\n    // 源视频载体对象支持的压缩格式中是否包含我们选择的压缩格式\n    if ([compatiblePresets containsObject:compressType]) {\n        \n        // 存放压缩视频的文件夹\n        NSFileManager *fileManager = [NSFileManager defaultManager];\n        NSString *compressVideoFolder = [NSHomeDirectory() stringByAppendingPathComponent:@\"/Documents/compressVideoFolder\"];\n        if (![fileManager fileExistsAtPath:compressVideoFolder]) {\n            \n            [fileManager createDirectoryAtPath:compressVideoFolder withIntermediateDirectories:YES attributes:nil error:nil];\n        }\n        // 用当前系统时间给文件命名, 避免因名字重复而覆盖存储\n        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];\n        [formatter setDateFormat:@\"yyyy-MM-dd-HH:mm:ss\"];\n        NSString *currentDateString = [formatter stringFromDate:[NSDate date]];\n        \n        /**\n         *  第一个参数 : 要压缩的 AVAsset 对象\n            第二个参数 : 我们选择的压缩方式\n         */\n        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:compressType];\n        // 压缩视频的输出路径\n        NSString *compressVideoPathString = [compressVideoFolder stringByAppendingPathComponent:[NSString stringWithFormat:@\"compressVideo-%@.mp4\", currentDateString]];\n        NSURL *compressFilePathUrl = [NSURL fileURLWithPath:compressVideoPathString];\n        exportSession.outputURL = compressFilePathUrl;\n        // 压缩文件的输出格式\n        exportSession.outputFileType = AVFileTypeMPEG4;\n        // 压缩文件应保证优化网络使用\n        exportSession.shouldOptimizeForNetworkUse = YES;\n        // 开始压缩\n        [exportSession exportAsynchronouslyWithCompletionHandler:^(void) {\n            \n            if (exportSession.status == AVAssetExportSessionStatusCompleted) {\n                \n                compressSuccessBlock(compressVideoPathString);\n            }else {\n                \n                compressFailedBlock();\n            }\n        }];\n    }else {\n        \n        compressNotSupportBlock();\n    }\n}\n```","iOS中如何压缩视频，这里给出一个选择，仅供参考。",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios compress,ios video,ios compress video,ios压缩视频,ios视频",{"id":2196,"createdAt":2197,"title":2198,"content":2199,"summary":2200,"image":2201,"uid":136,"user":2202,"categoryId":85,"category":2203,"subCategoryId":1737,"subCategory":2204,"comments":2205,"status":9,"reason":15,"notice":15,"visitCount":186,"commentCount":112,"keywords":2206},175,"2025-12-07T08:08:55.157Z","使用工具构建一个简单的AI coding代码编程助手","这篇文章介绍一下如何在visual studio code中去构建一个简单的ai代码辅助编写工作流，通过免费赠送的额度就可以体验一下ai coding。\n\n这篇文章旨在利用工具，并非从头到位开发一套AI agent，重在使用工具上。\n\n\n## 必备基础\n- 1. Visual Studio Code：咱们通用的代码编辑神器，本篇文章依赖该编辑器，请下载安装好。\n- 2. 注册硅基流动账号\n\n## 一、硅基流动部分\n硅基流动是一个整合了众多大模型的API调用服务平台，在这个平台上，你可以通过充值的方式，选择某一个模型，去处理对应领域的AI需求。\n\n之所以我选择这个平台，只是因为它对于新用户提供了14元人民币的免费额度，我就是想免费白嫖体验一把，所以目前的情况下，新注册用户确实可以获得14元这个免费额度，来体验这篇文章中的流程。\n\n官网地址：[https://www.siliconflow.cn/](https://www.siliconflow.cn/)\n\n### 1.1 注册成功后可以在 账户管理-> 余额充值的地方看到当前的余额\n![硅基流动余额](https://image.xinwei.ltd/image1765092901992.png)\n\n### 1.2 然后我们可以在模型广场，去找一个便宜的大模型使用一下：\n![deepseek大模型](https://image.xinwei.ltd/image1765093036081.png)\n\n### 1.3 如上图所示，我选择了红色框的大模型，点击\n![硅基流动模型](https://image.xinwei.ltd/image1765093138567.png)\n\n> 上面截图中的大模型名字右侧有一个复制图标，点击可以复制大模型名字，后面会用到。\n\n### 1.4点击“API文档”，打开了API调用接口目录，注意下面截图中的红色框中部分\n![硅基流动大模型API文档](https://image.xinwei.ltd/image1765093263910.png)\n\n### 1.5 创建一个API key\n在目录 账户管理 -> API密钥部分，你需要新建一个密钥，这个是我们调用相关API接口的凭证。\n![硅基流动的密钥](https://image.xinwei.ltd/image1765093965110.png)\n\n生成成功后，可以点击复制密钥，后面会用到。\n\n### 1.6 总结\n从这个部分，我们获取到2个信息：\n- 1. 大模型名称：\n```大模型名称\ndeepseek-ai/DeepSeek-V3.1-Terminus\n```\n- 2. 大模型API base url\n```api base url\nhttps://api.siliconflow.cn\n```\n- 3. API 密钥\n```密钥字符串\nsk-dmxxxxxxxxxxxxxxxxxxxxxmo\n```\n\n## 二、Visual Studio Code部分\n我们要借助一个第三方的扩展，相信对于经常使用vs code的小伙伴，在vs code中添加扩展是很简单的。\n\n这里我们需要安装`Cline`扩展，这个扩展有着强大的集成功能，方便我们配置大模型的调用，很容易直接使用。\n\n### 2.1 下载安装Cline扩展\n扩展中搜索`Cline`，直接安装：\n![vs code cline](https://image.xinwei.ltd/image1765093722481.png)\n\n安装之后，左侧功能区就会出现Cline的机器人图标入口。\n\n### 2.2 配置Cline\n点击左侧的Cline进入到扩展的配置区\n![cline配置](https://image.xinwei.ltd/image1765094138143.png)\n截图中已经有明显几处我已经配好的部分，你们可以根据第一部分获取到的3个信息，就可以完成选择或者填写。\n\n配置你们应该不会有问题吧，如果有问题，可以评论区留言。\n\n### 2.3 开始让AI工作\n![cline ai工作](https://image.xinwei.ltd/image1765094399264.png)\n\n从上面的截图，你可以在对话框中，输入你想让ai做的事情，当你把详细的功能告诉它之后，它可以在工作区中新建文件、写入代码，当然在新建文件的时候，会话框会弹出确认按钮，当你确认之后，ai会继续在创建的文件中写入代码。\n\nai也会根据你的输入，读取你工作区的代码文件，理解上下文后，开始新代码逻辑的编写。\n\n当然整个过程可能会花个几分钟的时间，但是对于让AI理解你的代码逻辑是十分有益的。\n\n当新的业务逻辑代码被AI创建后，你可以运行工程或者代码文件，检查是否有问题或者符合你的要求。\n\n### 2.4 题外话\n当你的硅基流动的免费额度用完之后，你可以选择充值，或者直接去deepseek、chatgpt等平台去申请密钥，然后不通过硅基流动也是可以的，具体选择取决于你。\n\n至此，文章结束，希望对有的小伙伴有帮助。\n\n\n\n\n","这篇文章介绍一下如何在visual studio code中去构建一个简单的ai代码辅助编写工作流，通过免费赠送的额度就可以体验一下ai coding。","https://image.xinwei.ltd/image1765092901992.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":1737,"name":2084,"parentId":85},[],"ai coding, ai cline, ai vscode, ai siliconflow, deepseek ai, siliconflow, vscode extension,Ai 代码编写,Ai 代码工具, AI编程工作流,硅基流动",{"id":2208,"createdAt":2209,"title":2210,"content":2211,"summary":2212,"image":2213,"uid":136,"user":2214,"categoryId":85,"category":2215,"subCategoryId":272,"subCategory":2216,"comments":2217,"status":9,"reason":15,"notice":15,"visitCount":201,"commentCount":9,"keywords":2218},171,"2025-10-31T09:05:04.806Z","taro react eslint的issue记录：Unexpected usage of doublequote. eslint(jsx-quotes)","在使用taro框架的react来编写小程序时，遇到一个小的代码eslint问题，在这里记录一下。\n\n问题截图：\n![taro eslint](https://image.xinwei.ltd/image1761900766038.png)\n\n问题描述：\n> Unexpected usage of doublequote. eslint(jsx-quotes)\n\n从上面的问题截图中可以看出，自从使用`taro init`出的项目，自带了`eslint`的配置，在我编写的一小段代码中，classname右侧使用了双引号，但是eslint提示，不希望使用双引号的写法。\n\n那么只要使用单引号包裹classname，那么这个提示就会消失的，但是我的visual studio code中已经对我其他的项目都配置是使用单引号的写法，所以我要单独配置当前的小程序eslint规则（taro工程中有一个.eslintrc文件）。\n\n![taro工程结构](https://image.xinwei.ltd/image1761901319205.png)\n\n\n我们看一下taro react小程序工程中的eslint规则：\n```.eslintrc\n{\n  \"extends\": [\"taro/react\"],\n  \"rules\": {\n    \"react/jsx-uses-react\": \"off\",\n    \"react/react-in-jsx-scope\": \"off\"\n  }\n}\n```\n\n那么我想要eslint的双引号的提示消失，就需要在这个文件中进行配置，只需要增加一个`jsx-quotes`的配置，如下：\n```.eslintrc\n{\n  \"extends\": [\"taro/react\"],\n  \"rules\": {\n    \"react/jsx-uses-react\": \"off\",\n    \"react/react-in-jsx-scope\": \"off\",\n    \"jsx-quotes\": \"off\"\n  }\n}\n```\n\n再来看看刚刚的代码eslint提示：\n![eslint修改后的代码提示](https://image.xinwei.ltd/image1761901466330.png)\n\n至此记录。\n\n","记录一下在taro react小程序中遇到一些小问题和解决方式，仅做记录。","https://image.xinwei.ltd/image1761900766038.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":272,"name":274,"parentId":85},[],"taro eslint, taro react, react eslint, taro, eslint, jsx-quote",{"id":2220,"createdAt":2221,"title":2222,"content":2223,"summary":2224,"image":2225,"uid":136,"user":2226,"categoryId":85,"category":2227,"subCategoryId":242,"subCategory":2228,"comments":2229,"status":9,"reason":15,"notice":15,"visitCount":201,"commentCount":112,"keywords":2230},168,"2025-09-09T10:18:10.105Z","ssh -T git@github.com  Connection closed by 127.0.0.1 port 7897","这篇文章，简要介绍当git操作时，git命令提示could not read from remote repository的问题，且终端命令执行ssh -T时，connection被terminated。\n\n## 一、问题：\n在`git pull`时：\n```console\ngit pull\nConnection closed by 127.0.0.1 port 7897\nfatal: Could not read from remote repository.\n\nPlease make sure you have the correct access rights\nand the repository exists.\n\n```\n在`ssh -T git@github.com`时：\n```terminal\nssh -T git@github.com\nConnection closed by 127.0.0.1 port 789\n```\n\n### 1.1 截图：\n![git pull error](https://image.xinwei.ltd/image1757411010326.png)\n\n![ssh error](https://image.xinwei.ltd/image1757411065611.png)\n\n\n## 二、原因\n从上面的问题中可以看出，是自己的电脑127.0.0.1:7897中断了链接，那么问题出现在自己的电脑中。这种情况通常容易出现在科学上网的场景下，需要我们配置本地的代理配置上。\n\n大家应该了解电脑的hosts文件，是允许自定义配置电脑对某些域名、ip访问的文件。\n\n然后对于github而言，都是要求https进行安全访问，那么就涉及到端口号443的配置。\n\n另外github仓库的ssh访问，身份校验也是需要在本地电脑和github上配置访问的ssh 公开/私密 密钥的，并且ssh也是有一个配置文件的。\n\n\n## 三、解决方法\n通过上面的问题分析，我们可以推导出合适的解决方案：我们在ssh的配置文件中，配置好对github的443访问，那么GitHub ssh的身份鉴权问题就能解决，从而git pull等指令和github进行交互时，GitHub就能知道git指令是来自通过了身份鉴权的安全电脑环境，就不会有access right问题。\n\n## 四、操作步骤\n下面的操作是在mac上进行的，目录和文件仅限mac环境。\n\n### 4.1 找到ssh config文件\n- mac上的ssh配置文件，都是在`.ssh`这个文件夹中；\n- `.ssh`文件夹处于mac的资源库目录下，是一个隐藏文件夹(显示隐藏文件及文件夹的操作，大家可以自行搜索一下)；\n- 如果没有config文件，那么就新建一个。\n\n示例：\n![.ssh目录](https://image.xinwei.ltd/image1757412513960.png)\n\n如果没有`config`文件可以新建一个\n```terminal\ntouch config\n```\n\n### 4.2 配置ssh config文件\n```config文件\nHost github.com\n  Hostname ssh.github.com\n  Port 443\n  User git\n  ProxyCommand nc -X 5 -x 127.0.0.1:7897 %h %p\n```\n\n### 4.3 重新验证ssh\n```terminal\nssh -T git@github.com\n```\n![add knowlist](https://image.xinwei.ltd/image1757412925954.png)\n\n执行这步时，就会提示443配置将加到同目录下的`known_hosts`文件中。\n最后那行表示已经成功通过github的ssh身份鉴权，可以使用git相关指令了。\n\n\n至此结束，希望对大家有帮助。\n","这篇文章，简要介绍当git操作时，git命令提示could not read from remote repository的问题，且终端命令ssh -T时，connection被terminated。","https://image.xinwei.ltd/image1757411010326.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":242,"name":244,"parentId":85},[],"git connection,git ssh,ssh -T,ssh git,github,git,ssh connect",{"id":2232,"createdAt":2233,"title":2234,"content":2235,"summary":2236,"image":15,"uid":136,"user":2237,"categoryId":85,"category":2238,"subCategoryId":47,"subCategory":2239,"comments":2240,"status":9,"reason":15,"notice":15,"visitCount":201,"commentCount":112,"keywords":2241},61,"2024-02-01T05:58:03.446Z","iOS问题: Attribute Unavailable_ Automatic Preferred Max Layout Width before iOS 8.0 ","这个是在项目设置支持iOS7以上的时候，出现的问题，因为是老项目中出现的问题，对现在支持iOS11以上或者iOS14以上的项目并不适用。\n\n这里仅作记录。\n\n一般来讲，是因为UILabel控件在iOS8之后，在我目前没有深入研究的理解下，不能让其宽度自动适应（一般情况下是可以只设置x和y，不设置宽高的），需要在属性检查器中，勾选prefix width选项。","在开发iOS项目过程中，使用了xib或storyboard，出现的一个报错问题：Attribute Unavailable_ Automatic Preferred Max Layout Width before iOS 8.0 ",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":47,"name":340,"parentId":85},[],"ios,ios xib,ios storyboard,ios故事板,ios开发",{"id":2243,"createdAt":2244,"title":2245,"content":2246,"summary":2247,"image":2248,"uid":136,"user":2249,"categoryId":85,"category":2250,"subCategoryId":257,"subCategory":2251,"comments":2252,"status":9,"reason":15,"notice":15,"visitCount":668,"commentCount":112,"keywords":2253},162,"2025-05-30T09:09:07.174Z","M1电脑上如何找到钥匙串Keychain Access.app","在iOS app的开发中，经常需要配置Apple的开发者账号，那么在配置证书的时候，必不可少的就是需要使用到打包电脑上的钥匙串Keychain Access app，但是在Apple芯片的电脑上，比如我的打包电脑是Apple M1，在Application应用程序中是找不到钥匙串的。\n\nApple芯片的钥匙串现在已经默认是被隐藏了，要找到Keychain Access.app，你需要到\n`/System/Library/CoreServices/Application`\n这个目录下去找。\n\n你可以直接通过终端命令去打开:\n\n```terminal\nopen /System/Library/CoreServices/Application/KeyChain\\ Access.app\n```\n\n当然如果你的Mac电脑使用的不是英文，那么名字可能就不是“Keychain Access.app”，而是\"钥匙串访问\"，这时终端命令是不能直接打开的。\n\n那你直接终端打开文件夹，再点击钥匙串应用也行:\n\n```terminal\nopen /System/Library/CoreServices/Application\n```\n\n实在不行，你直接按照路径去找也行：\n![Keychain Access.app](https://image.xinwei.ltd/Xnip2025-05-27_12-22-091748596121829.png)\n\n仅作记录。\n\n","这篇文章简要介绍一下如何在m1的Apple Mac上找到钥匙串，供大家参考。","https://image.xinwei.ltd/Xnip2025-05-27_12-22-091748596121829.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":257,"name":259,"parentId":85},[],"M1 mac,M1 Keychain Accss app,mac Keychain Access,Keychain Access,钥匙串,Keychain,Keychain certificate",{"id":2255,"createdAt":2256,"title":2257,"content":2258,"summary":2259,"image":2260,"uid":40,"user":2261,"categoryId":85,"category":2262,"subCategoryId":100,"subCategory":2263,"comments":2264,"status":9,"reason":15,"notice":15,"visitCount":62,"commentCount":112,"keywords":2265},164,"2025-07-21T02:52:00.682Z","记一次flutter 报错:CocoaPods could not find compatible versions for pod","> [!] CocoaPods could not find compatible versions for pod \"Firebase/Auth\":\n  In snapshot (Podfile.lock):\n    Firebase/Auth (= 11.8.0)\n>\n>  In Podfile:\n    firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) was resolved to 5.6.2, which depends on\n      Firebase/Auth (= 11.15.0)\n>\n> Specs satisfying the `Firebase/Auth (= 11.8.0), Firebase/Auth (= 11.15.0)` dependency were found, but they required a higher minimum deployment target.\n\n以上的问题是在flutter for ios的工程中，加入flutter crashlytics这个库时出现的报错，原因是当前本地的cocoapods中没有Firebase/Auth的11.15.0版本的spec，所以需要我们升级。\n\n这个问题在`flutter pub get`或者`flutter add xxx`时没有出现，但是在编译到iOS设备中时会出现。\n\n所以一般我会在flutter pub get后，再cd到iOS目录中执行`pod install`\n![](https://image.xinwei.ltd/image1753066023489.png)\n\n解决上面截图中的办法就是要更新本地cocoapods spec：\n```terminal\npod install --repo-update --verbose\n```\n\n这样就可以了\n","记一次简单的编译报错记录。","https://image.xinwei.ltd/image1753066023489.png",{"phone":39,"userId":40,"nickName":41,"vipType":9,"avatar":42,"sign":15,"createdAt":43},{"id":85,"name":143},{"id":100,"name":160,"parentId":85},[],"flutter,flutter pod,flutter ios,CocoaPods could not find compatible versions for pod",{"id":2267,"createdAt":2268,"title":2269,"content":2270,"summary":2271,"image":2272,"uid":53,"user":2273,"categoryId":21,"category":2274,"subCategoryId":1817,"subCategory":2276,"comments":2278,"status":9,"reason":15,"notice":15,"visitCount":87,"commentCount":112,"keywords":2279},186,"2026-03-21T05:54:11.339Z","11岁患急性白血病小女孩需要大家援助救命","水滴筹官方链接：[](https://www.shuidichou.com/cf/contribute/aa6753e4-269b-4c9f-9ce9-4db124ab0980)\n\n官方网站链接截图：\n![水滴筹截图](https://image.xinwei.ltd/image1774071745980.png)\n\n求求大家停下几分钟，看一下文章内容，帮帮这个小女孩，向所有的爱心人士致敬，情况紧急，万不得已了，跪谢各位了。\n\n## 求助人的故事\n\nSOS: 生命赛跑，让爱心接力，若不是人逢绝处，万不敢在此打扰大家！走出医生办公室，我整个人都是懵的，试问谁能理解一个父母听到自己孩子得了“白血病”俗称【血癌】，那种绝望和恐惧吗？突患重疾！让我们家庭陷入黑暗深渊！被迫加入艰难的扛白之路！\n生活不易，可此时此刻，女儿我想对你说：爸爸妈妈毕其一生，只想护你周全！就算倾尽所有，一命换一命，我们也愿意！只求上苍垂怜，让你度过此难，余生安然无恙，只要你能顺利康复，所有灾厄我愿承担…\n\n很抱歉打扰到大家！我叫韩涛是孩子的爸爸，孩子的妈妈叫李迪。我们的女儿叫韩思琪。今年11岁。我们来自湖北孝感大悟县！\n\nSOS病情说明：\n2026年3月18日发现孩子有发烧症状随即来到大悟县人民医院检查！经过血常规检查报告异常。血小板减少，白细胞高。红细胞低。重度贫血。要求转入上级医院明确诊断。\n2026年3月18日下午6点来到武汉儿童医院！由于孩子病情突发严重，各项指标异常！经医生会诊后要求转入重症监护室住院留观！\n初步诊断：\n1.白血病？\n2.重度贫血\n入院后由于孩子血项低，前期一直依靠输血维持血项增长！\n2026年3月19日下午经血液科医生会诊后要求转入其科室。明确诊断及治疗方案。\n由于孩子血项一直不见好转，入科后也一直进行输血。医生告知要求家属尽快去血站献血后期给孩子使用。（由于血库用血紧张）\n2026年3月20日进行骨髓穿刺及完善各项检查。\n2026年3月20日下午骨髓细胞学报告显示\n诊断：\n1.急性白血病\n\n![](https://image.xinwei.ltd/image1774071854398.png)\n\n\n然而最煎熬的时候是等待结果的这个时间，就像头顶上有把剑迟迟不能落下。在焦急的等待中，结果出来了，医生把我们叫到办公室，告诉我们孩子被确诊【急性白血病】当这把剑砸下来的时候，真的是万念俱灰，女儿不幸在11岁确诊白血病，怎么不幸都落在了我们身上。哭后还是要积极面对，孩子特别懂事，不管检查多痛多难受，懂事孩子也是咬牙坚持，积极配合医生。想到这里我就忍不住泪流满面，多希望得病的是我，放过我的孩子，让我替孩子承担这一切的痛苦。\n\n医生说白血病不是一般的疾病，尽管治疗过程漫长，但是有很大治愈希望。就是治疗费用昂贵，在背负经济压力的同时，还要承受与病魔作斗争的巨大身心煎熬。医生告诉我们后期的费用最起码在几十万，听到这个天文数字的治疗费用，不是我们普通家庭难以承担的，本想自己能扛下这所有，可在面对高额的治疗费让我们恐惧，害怕我们这个小家庭坚持不住。希望能够借这个水滴筹平台聚集社会好心人，愿您伸出援手，帮助我的女儿渡过这次难关。\n\n——我在此郑重承诺：\n我承诺所有善款全部用于女儿治病，希望所有人可以直接捐款到水滴筹平台，这样才能让筹款项目快速带动起来，并加速传播扩散，有助于快速筹集资金！本次筹集的所有善款将全部用于我女儿【韩思琪】的治疗！我们别无他想，只想让我的女儿度过此难，感谢各位的善举，谢谢大家！！！！\n\n【请大家帮帮我们，帮忙【转发】！您的每一次【证实】，每一次【转发】，每一次【捐款】，对我们来说都是莫大的帮助，爱心不分大小，一元两元都是爱，万分感谢，这样才能让我的筹款项目快速带动起来并加速传播，捐助时请备注您的实名，每一笔捐赠，我这边也都会尽量的一笔一笔去记着，往后还清，您对我们的恩情我们此生铭记！】\n\n![病情照片](https://image.xinwei.ltd/image1774071607119.png)","求求大家停下几分钟，看一下文章内容，帮帮这个得急性白血病小女孩，向所有的爱心人士致敬，情况紧急，万不得已，跪谢各位了，祝各位富贵长寿。","https://image.xinwei.ltd/image1774071745980.png",{"phone":52,"userId":53,"nickName":54,"vipType":9,"avatar":55,"sign":15,"createdAt":56},{"id":21,"name":2275},"生活",{"id":1817,"name":2277,"parentId":21},"爱心",[],"急性白血病, 爱心, 小女孩血癌, 帮助小女孩抗病, 爱心天使, 水滴筹, 血癌, 治疗费用高昂, 捐献, 做一个好心人, 福报",{"id":2281,"createdAt":2282,"title":2283,"content":2284,"summary":2285,"image":2286,"uid":136,"user":2287,"categoryId":85,"category":2288,"subCategoryId":272,"subCategory":2289,"comments":2290,"status":9,"reason":15,"notice":15,"visitCount":87,"commentCount":112,"keywords":2291},172,"2025-11-07T07:45:11.219Z","[vite]: Rollup failed to resolve import \"@/utils/xxx\" from \"/Users/xx/my-projects/xxx","本篇文章依然是记录一下在使用taro开发小程序中解决遇到问题的一个小案例，希望对大家有帮助。\n\n## 问题\n![taro vite plugin error](https://image.xinwei.ltd/image1762499717791.png)\n\n> [vite]: Rollup failed to resolve import \"@/utils/xxx_util\" from \"/Users/xxx/Documents/my-projects/mini/xxx/front-mini/xxx-mini/src/pages/xxx_play/index.tsx\".\nThis is most likely unintended because it can break your application at runtime.\nIf you do want to externalize this module explicitly add it to\n`build.rollupOptions.external`\n\n## 问题描述\n从上面的问题描述中可以看出，在我的`tsx`文件中使用了某个`import xxx from \"@/utils/xxx_util\"`这种的引入，但是vite编译时rollup无法解析这样的路径。\n\n所以这个问题就是路径或者别名`alias`的编译配置出了问题。\n\n## 解决方法\n我的小程序工程使用了vite来构建，而且是taro的typescript类型语法，这是我小程序工程的配置，我摘取部分配置出来：\n![vite 构建配置](https://image.xinwei.ltd/image1762500358565.png)\n\n然后因为是typescript来编写的，所以自然也包含tsconfig的配置，下面也是截取部分展示：\n![tsconfig.json内容](https://image.xinwei.ltd/image1762500476948.png)\n\n从上面的基础条件中，我们可以得知的是，tsconfig中已经配置了`paths`，但是为什么还是报错呢？原因有2个方面，一个是tsconfig的paths并没有配置完整，另一个是我们并没有在vite的构建中去使用这个`paths`的配置，那么针对这2个方面，我们逐步解决。\n\n### 1. 完善tsconfig.json中的paths配置\n之所以这么说，我们可以从tsconfig官网（网址：[https://www.typescriptlang.org/zh/tsconfig/?#paths](https://www.typescriptlang.org/zh/tsconfig/?#paths)）中得到答案:\n\n![paths 解释1](https://image.xinwei.ltd/image1762500730810.png)\n\n![paths 解释2](https://image.xinwei.ltd/image1762500784528.png)\n\n从上面的截图中你应该明白如何配置`paths`了吧，并且如果配置`paths`，那么必须配置`baseUrl`.\n所以完善我们的配置：\n```tsconfig.json\n{\n  \"compilerOptions\": {\n    ...\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"./src\", \"./types\", \"./config\"],\n  \"compileOnSave\": false\n}\n...\n```\n\n### 2. 让vite能解析paths\n这里需要使用到插件来解决这个问题，有一个插件能解决我们的问题：`vite-tsconfig-paths`\n（npmjs链接：[https://www.npmjs.com/package/vite-tsconfig-paths](https://www.npmjs.com/package/vite-tsconfig-paths)）\n\n我们可以直接看到这个package的描述：\n> Give vite the ability to resolve imports using TypeScript's path mapping.\n\n所以我们根据文档中的用法安装，并在`config/index.ts`文件中进行配置：\n```config/index.ts\n...\nimport tsconfigPaths from \"vite-tsconfig-paths\"; // \u003C- 引入插件\n...\n\nexport default defineConfig\u003C\"vite\">(async (merge, { command, mode }) => {\n  const baseConfig: UserConfigExport\u003C\"vite\"> = {\n    ...\n    compiler: {\n      type: \"vite\",\n      prebundle: {\n        exclude: [\"@nutui/nutui-react-taro\", \"@nutui/icons-react-taro\"],\n      },\n      vitePlugins: [\n        tsconfigPaths(), // \u003C- 使用插件\n      ],\n    },\n    ...\n  }\n  ...\n}\n...\n```\n\n至此，问题解决了，希望对大家有帮助。\n","本篇文章依然是记录一下在使用taro开发小程序中解决遇到问题的一个小案例，希望对大家有帮助。","https://image.xinwei.ltd/image1762499717791.png",{"phone":138,"userId":136,"nickName":139,"vipType":9,"avatar":140,"sign":15,"createdAt":141},{"id":85,"name":143},{"id":272,"name":274,"parentId":85},[],"taro vite, taro vite plugin, vite plugin, vite rollup, vite alias, tsconfig alias, tsconfig paths, rollup import, import @, tsconfig, taro config/index","文章"]