-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 124 KB
/
content.json
1
[{"title":"Docker + MySQL 主从配置以实现读写分离","date":"2023-04-13T14:06:20.000Z","path":"2023/04/13/diary-230413-01/","text":"【什么是 MySQL 主从备份】 引用一段最近很火的 ChatGPT(微软NewBing)给出的解释 MySQL的主从备份是为了确保数据安全,避免一台机器硬盘损坏导致数据永久丢失。主从备份的实现方式是将主数据库的数据同步到从数据库上,当主数据库出现故障时,可以快速切换到从数据库上,保证业务的正常运行。 在MySQL中,主从备份有两种实现方式:基于二进制日志的复制和基于GTID的复制。基于二进制日志的复制是MySQL自带的一种复制方式,它通过记录二进制日志来实现主从备份。而基于GTID的复制是MySQL 5.6版本之后引入的一种新的复制方式,它通过记录全局事务ID来实现主从备份。 这里我们不深究实现方式,以下介绍一种使用 docker-compose 配置主从备份实现读写分离的做法。 1. 首先,在服务器上创建一个存放 docker-compose 文件的目录,如:mysql-master-slave ps:这里我假设你的服务器上已经安装好了 docker 以及 docker-compose。如果没有,请先参照有关 docker 安装配置的教程进行安装。 2. 在目录下方新建一个 docker-compose.yaml 文件,内容如下 ps:networks 节点中的 CIDR 和 IP 可自定义,后续会用到。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647version: '3.1'services: db-master: image: mysql:8.0.29-oracle restart: always container_name: mysql-writeread ports: - 33061:3306 volumes: - ./master/config:/etc/mysql/conf.d - ./master/data:/var/lib/mysql - ./master/log:/var/log/mysql environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: asdf@123 command: --default-authentication-plugin=mysql_native_password --lower_case_table_names=1 networks: default: ipv4_address: 172.132.1.10 db-slave: image: mysql:8.0.29-oracle restart: always container_name: mysql-readonly ports: - 33062:3306 volumes: - ./slave/config:/etc/mysql/conf.d - ./slave/data:/var/lib/mysql - ./slave/log:/var/log/mysql environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: asdf@123 command: --default-authentication-plugin=mysql_native_password --lower_case_table_names=1 networks: default: ipv4_address: 172.132.1.11networks: default: ipam: driver: default config: - subnet: 172.132.1.0/24 3. 在 mysql-master-slave 目录下新建目录 master、slave 4. 在 mysql-master-slave/master 和 mysql-master-slave 目录下方分别创建config目录 5. 在 master 和 slave 各自的 config 目录下方创建 docker-my.cnf 配置文件 mysql-master-slave/master/config/docker-my.cnf 123456789101112131415161718192021222324252627282930313233343536373839404142[mysqld]sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTIONlower_case_table_names=1max_connections=10240max_user_connections=1024max_allowed_packet = 16Mlog-error=/var/log/mysql/error.log# 主从配置 - 主库default_authentication_plugin=mysql_native_passwordcollation-server=utf8mb4_0900_ai_cicharacter-set-server=utf8mb4init-connect='SET NAMES utf8'skip-host-cacheskip-name-resolveserver-id=1 #此id在主从库不能相同log-bin=mysql-bin #开启二进制日记文件relay-log= mysql-relay-binbinlog-format=ROWread-only=0 #可读可写log-slave-updates=1expire_logs_days = 7binlog_cache_size=32mmax_binlog_cache_size=512mmax_binlog_size=512mreplicate-do-db=target-database-name #需要同步的数据库,有多个时添加多行binlog-ignore-db=mysql #不同步哪些数据库binlog-ignore-db=sysbinlog-ignore-db=information_schemabinlog-ignore-db=performance_schemaperformance_schema_max_table_instances=4096table_definition_cache=4096performance_schema=offtable_open_cache=2048innodb_buffer_pool_chunk_size=2048Minnodb_buffer_pool_size=2048Mmysqlx_max_connections=3000[mysqldump]quickmax_allowed_packet = 16M mysql-master-slave/slave/config/docker-my.cnf 123456789101112131415161718192021222324252627282930313233343536373839404142[mysqld]sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTIONlower_case_table_names=1max_connections=10240max_user_connections=1024max_allowed_packet = 16Mlog-error=/var/log/mysql/error.log# 主从配置 - 从库default_authentication_plugin=mysql_native_passwordcollation-server=utf8mb4_0900_ai_cicharacter-set-server=utf8mb4init-connect='SET NAMES utf8'skip-host-cacheskip-name-resolveserver-id=2 #此id在主从库不能相同log-bin=mysql-bin #开启二进制日记文件relay-log= mysql-relay-binbinlog-format=ROWread-only=1 #可读可写log-slave-updates=1expire_logs_days = 7binlog_cache_size=32mmax_binlog_cache_size=512mmax_binlog_size=512mreplicate-do-db=target-database-name #需要同步的数据库,有多个时添加多行binlog-ignore-db=mysql #不同步哪些数据库binlog-ignore-db=sysbinlog-ignore-db=information_schemabinlog-ignore-db=performance_schemaperformance_schema_max_table_instances=4096table_definition_cache=4096performance_schema=offtable_open_cache=2048innodb_buffer_pool_chunk_size=2048Minnodb_buffer_pool_size=2048Mmysqlx_max_connections=3000[mysqldump]quickmax_allowed_packet = 16M 6. 执行 docker-compose up -d 运行两个MySQL主从容器 7. 使用root用户连接主库,创建用于同步数据的用户 repl ,并授予所需的权限(Replication) 123CREATE USER `repl`@`[从库所在容器的IP]` IDENTIFIED WITH mysql_native_password BY '123456';GRANT Replication Slave ON *.* TO `repl`@`[从库所在容器的IP]`; 其中 [从库所在容器的IP] 还可以通过 docker inspect mysql-readonly | grep IPAddress 查出 8. 连接主库,执行 show master status; 查询主库的状态信息,记录 File 和 Position 字段的值,下一步需要用到 查询结果示例: 12File Position Binlog_Do_DB Binlog_Ignore_DB Executed_Gtid_Setmysql-bin.000002 1710 mysql,sys,information_schema,performance_schema 9. 连接从库,执行以下命令以设置主库信息 需要将 MASTER_LOG_FILE 的值替换为上一步中 File字段 的值; 将 MASTER_LOG_POS 的值替换为上一步中 Position字段 的值; 1CHANGE MASTER TO MASTER_HOST='[主库所在容器的IP]', MASTER_USER='repl', MASTER_PASSWORD='123456', MASTER_PORT=3306, MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=1710, MASTER_CONNECT_RETRY=10; 10. 使用 Navicat 连接主库,备份需要同步的数据库,在从库中创建同名数据库,并从备份文件中还原 11. 连接从库,执行 start slave; 启动从库同步 12. 通过命令行形式连接从库,执行 show slave status\\G; 查询从库的状态信息,确保 Slave_IO_Running 和 Slave_SQL_Running 的状态都为 Yes 如果不确定从库是否能正常连上主库,可以执行 docker exec -it mysql-readonly /bin/bash 进入到从库容器, 然后执行 mysql -h mysql-writeread -urepl -p,输入密码看是否能连上 13. 验证同步是否成功 ① 在主库中设置了主从同步的表中创建一个 test 表,在从库中可以看到 test 表被同步过来 ② 在主库中设置了主从同步的表中插入/修改/删除一条数据,在从库中可以看到数据修改后的结果 注意,同步可能会存在些许延迟","tags":[{"name":"Docker","slug":"Docker","permalink":"//blog.solutionx.top/tags/Docker/"},{"name":"2023-04","slug":"2023-04","permalink":"//blog.solutionx.top/tags/2023-04/"},{"name":"MySQL","slug":"MySQL","permalink":"//blog.solutionx.top/tags/MySQL/"}]},{"title":"WPF TreeViewItem中某一元素的属性变化时更改其他元素的属性","date":"2023-01-07T14:57:40.000Z","path":"2023/01/07/diary-230107-01/","text":"最近用WPF写一个小工具的时候用到了TreeView,想实现如下效果,当鼠标进入到当前节点时,自动显示用于展开下拉菜单的按钮。 废话不多说,直接上代码: 12345678910111213141516171819202122232425262728<!--注意,此处省略了部分与效果实现无关的属性,只需参考DataTrigger部分的代码即可--><TreeView> <TreeView.ItemTemplate> <HierarchicalDataTemplate> <Grid> <!--此处省略其他UI元素--> <Button Content=\"···\"> <Button.Resources> <Style TargetType=\"Button\" BasedOn=\"{StaticResource {x:Type Button}}\"> <Setter Property=\"Background\" Value=\"Transparent\" /> <Setter Property=\"Foreground\" Value=\"Transparent\" /> <Style.Triggers> <DataTrigger Binding=\"{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=1,AncestorType=Grid},Path=IsMouseOver}\" Value=\"True\"> <Setter Property=\"Foreground\" Value=\"Black\" /> </DataTrigger> </Style.Triggers> </Style> </Button.Resources> </Button> </Grid> </HierarchicalDataTemplate> </TreeView.ItemTemplate></TreeView> 代码解读: 以上Button元素绑定了一个 DataTrigger,用于监听上一层(通过AncestorLevel=1设置)Grid 元素的 IsMouseOver 属性。当 IsMouseOver 为 True,也就是鼠标移动到 Grid 上方时,设置 Button 的 Foreground 属性值为 Black,否则用默认值 Transparent。 最终实现的效果也就类似于上方动图啦。","tags":[{"name":"WPF","slug":"WPF","permalink":"//blog.solutionx.top/tags/WPF/"},{"name":"2023-01","slug":"2023-01","permalink":"//blog.solutionx.top/tags/2023-01/"},{"name":"TreeViewItem","slug":"TreeViewItem","permalink":"//blog.solutionx.top/tags/TreeViewItem/"},{"name":"DataTrigger","slug":"DataTrigger","permalink":"//blog.solutionx.top/tags/DataTrigger/"}]},{"title":"在Hexo中使用emoji表情","date":"2022-09-15T11:07:03.000Z","path":"2022/09/15/diary-220915-03/","text":"Hexo 默认使用 hexo-renderer-marked 作为 Markdown 引擎。 由于其不支持渲染 emoji 表情(即不支持类似于 :smile: 的写法),想要使用 :smile: 来显示表情 😄 ,则需要更换一个支持该功能的插件。 这里,我们用到的是 hexo-renderer-markdown-it 和 markdown-it-emoji。 首先,在博客根目录下运行命令行工具,执行以下命令卸载默认的 Markdown 引擎。 1npm uninstall hexo-renderer-marked --save 然后,我们安装需要的插件。 12npm install hexo-renderer-markdown-it --savenpm install markdown-it-emoji --save 插件安装完成后,需要更改博客根目录下的站点配置文件 _config.yml 如果之前没有添加过相关配置项,则在末尾添加如下配置(如果有,则可以参考以下配置值): 123456789101112131415161718192021## markdown 渲染引擎配置,默认是hexo-renderer-marked,这个插件渲染速度更快,且有新特性markdown: render: html: true # 不转义 HTML 内容,即允许使用 HTML 标签 xhtmlOut: false breaks: true linkify: true typographer: true quotes: '“”‘’' plugins: - markdown-it-footnote - markdown-it-sup - markdown-it-sub - markdown-it-abbr - markdown-it-emoji anchors: level: 2 collisionSuffix: 'v' permalink: false permalinkClass: header-anchor permalinkSymbol: ¶ 关于Markdown表情代号,可参考 👉 这里","tags":[{"name":"Hexo","slug":"Hexo","permalink":"//blog.solutionx.top/tags/Hexo/"},{"name":"Markdown","slug":"Markdown","permalink":"//blog.solutionx.top/tags/Markdown/"},{"name":"2022-09","slug":"2022-09","permalink":"//blog.solutionx.top/tags/2022-09/"}]},{"title":"Markdown表情代号大全","date":"2022-09-15T10:08:03.000Z","path":"2022/09/15/diary-220915-02/","text":"由于国内网络问题,时常访问不到 Github Gist 等站点,故将其做一个备份放到自己的个人博客。 Markdown表情代号源地址点 👉 这里 另外,如果你的 hexo 博客无法正常显示emoji表情,那多半是废了。(开玩笑哈,这是因为默认的Markdown引擎不支持将 Github emoji 渲染到 html 页面,需要更换一个 emoji 引擎,参考 👉 这里即可进行配置) People :bowtie: :bowtie: 😄 :smile: 😆 :laughing: 😊 :blush: 😃 :smiley: ☺️ :relaxed: 😏 :smirk: 😍 :heart_eyes: 😘 :kissing_heart: 😚 :kissing_closed_eyes: 😳 :flushed: 😌 :relieved: 😆 :satisfied: 😁 :grin: 😉 :wink: 😜 :stuck_out_tongue_winking_eye: 😝 :stuck_out_tongue_closed_eyes: 😀 :grinning: 😗 :kissing: 😙 :kissing_smiling_eyes: 😛 :stuck_out_tongue: 😴 :sleeping: 😟 :worried: 😦 :frowning: 😧 :anguished: 😮 :open_mouth: 😬 :grimacing: 😕 :confused: 😯 :hushed: 😑 :expressionless: 😒 :unamused: 😅 :sweat_smile: 😓 :sweat: 😥 :disappointed_relieved: 😩 :weary: 😔 :pensive: 😞 :disappointed: 😖 :confounded: 😨 :fearful: 😰 :cold_sweat: 😣 :persevere: 😢 :cry: 😭 :sob: 😂 :joy: 😲 :astonished: 😱 :scream: :neckbeard: :neckbeard: 😫 :tired_face: 😠 :angry: 😡 :rage: 😤 :triumph: 😪 :sleepy: 😋 :yum: 😷 :mask: 😎 :sunglasses: 😵 :dizzy_face: 👿 :imp: 😈 :smiling_imp: 😐 :neutral_face: 😶 :no_mouth: 😇 :innocent: 👽 :alien: 💛 :yellow_heart: 💙 :blue_heart: 💜 :purple_heart: ❤️ :heart: 💚 :green_heart: 💔 :broken_heart: 💓 :heartbeat: 💗 :heartpulse: 💕 :two_hearts: 💞 :revolving_hearts: 💘 :cupid: 💖 :sparkling_heart: ✨ :sparkles: ⭐ :star: 🌟 :star2: 💫 :dizzy: 💥 :boom: 💥 :collision: 💢 :anger: ❗ :exclamation: ❓ :question: ❕ :grey_exclamation: ❔ :grey_question: 💤 :zzz: 💨 :dash: 💦 :sweat_drops: 🎶 :notes: 🎵 :musical_note: 🔥 :fire: 💩 :hankey: 💩 :poop: 💩 :shit: 👍 :+1: 👍 :thumbsup: 👎 :-1: 👎 :thumbsdown: 👌 :ok_hand: 👊 :punch: 👊 :facepunch: ✊ :fist: ✌️ :v: 👋 :wave: ✋ :hand: ✋ :raised_hand: 👐 :open_hands: ☝️ :point_up: 👇 :point_down: 👈 :point_left: 👉 :point_right: 🙌 :raised_hands: 🙏 :pray: 👆 :point_up_2: 👏 :clap: 💪 :muscle: 🤘 :metal: 🖕 :fu: 🚶 :walking: 🏃 :runner: 🏃 :running: 👫 :couple: 👪 :family: 👬 :two_men_holding_hands: 👭 :two_women_holding_hands: 💃 :dancer: 👯 :dancers: 🙆♀️ :ok_woman: 🙅 :no_good: 💁 :information_desk_person: 🙋 :raising_hand: 👰♀️ :bride_with_veil: :person_with_pouting_face: :person_with_pouting_face: :person_frowning: :person_frowning: 🙇 :bow: 💏 :couplekiss: 💑 :couple_with_heart: 💆 :massage: 💇 :haircut: 💅 :nail_care: 👦 :boy: 👧 :girl: 👩 :woman: 👨 :man: 👶 :baby: 👵 :older_woman: 👴 :older_man: :person_with_blond_hair: :person_with_blond_hair: 👲 :man_with_gua_pi_mao: 👳♂️ :man_with_turban: 👷 :construction_worker: 👮 :cop: 👼 :angel: 👸 :princess: 😺 :smiley_cat: 😸 :smile_cat: 😻 :heart_eyes_cat: 😽 :kissing_cat: 😼 :smirk_cat: 🙀 :scream_cat: 😿 :crying_cat_face: 😹 :joy_cat: 😾 :pouting_cat: 👹 :japanese_ogre: 👺 :japanese_goblin: 🙈 :see_no_evil: 🙉 :hear_no_evil: 🙊 :speak_no_evil: 💂♂️ :guardsman: 💀 :skull: 🐾 :feet: 👄 :lips: 💋 :kiss: 💧 :droplet: 👂 :ear: 👀 :eyes: 👃 :nose: 👅 :tongue: 💌 :love_letter: 👤 :bust_in_silhouette: 👥 :busts_in_silhouette: 💬 :speech_balloon: 💭 :thought_balloon: :feelsgood: :feelsgood: :finnadie: :finnadie: :goberserk: :goberserk: :godmode: :godmode: :hurtrealbad: :hurtrealbad: :rage1: :rage1: :rage2: :rage2: :rage3: :rage3: :rage4: :rage4: :suspect: :suspect: :trollface: :trollface: Nature ☀️ :sunny: ☔ :umbrella: ☁️ :cloud: ❄️ :snowflake: ⛄ :snowman: ⚡ :zap: 🌀 :cyclone: 🌁 :foggy: 🌊 :ocean: 🐱 :cat: 🐶 :dog: 🐭 :mouse: 🐹 :hamster: 🐰 :rabbit: 🐺 :wolf: 🐸 :frog: 🐯 :tiger: 🐨 :koala: 🐻 :bear: 🐷 :pig: 🐽 :pig_nose: 🐮 :cow: 🐗 :boar: 🐵 :monkey_face: 🐒 :monkey: 🐴 :horse: 🐎 :racehorse: 🐫 :camel: 🐑 :sheep: 🐘 :elephant: 🐼 :panda_face: 🐍 :snake: 🐦 :bird: 🐤 :baby_chick: 🐥 :hatched_chick: 🐣 :hatching_chick: 🐔 :chicken: 🐧 :penguin: 🐢 :turtle: 🐛 :bug: 🐝 :honeybee: 🐜 :ant: 🪲 :beetle: 🐌 :snail: 🐙 :octopus: 🐠 :tropical_fish: 🐟 :fish: 🐳 :whale: 🐋 :whale2: 🐬 :dolphin: 🐄 :cow2: 🐏 :ram: 🐀 :rat: 🐃 :water_buffalo: 🐅 :tiger2: 🐇 :rabbit2: 🐉 :dragon: 🐐 :goat: 🐓 :rooster: 🐕 :dog2: 🐖 :pig2: 🐁 :mouse2: 🐂 :ox: 🐲 :dragon_face: 🐡 :blowfish: 🐊 :crocodile: 🐪 :dromedary_camel: 🐆 :leopard: 🐈 :cat2: 🐩 :poodle: 🐾 :paw_prints: 💐 :bouquet: 🌸 :cherry_blossom: 🌷 :tulip: 🍀 :four_leaf_clover: 🌹 :rose: 🌻 :sunflower: 🌺 :hibiscus: 🍁 :maple_leaf: 🍃 :leaves: 🍂 :fallen_leaf: 🌿 :herb: 🍄 :mushroom: 🌵 :cactus: 🌴 :palm_tree: 🌲 :evergreen_tree: 🌳 :deciduous_tree: 🌰 :chestnut: 🌱 :seedling: 🌼 :blossom: 🌾 :ear_of_rice: 🐚 :shell: 🌐 :globe_with_meridians: 🌞 :sun_with_face: 🌝 :full_moon_with_face: 🌚 :new_moon_with_face: 🌑 :new_moon: 🌒 :waxing_crescent_moon: 🌓 :first_quarter_moon: 🌔 :waxing_gibbous_moon: 🌕 :full_moon: 🌖 :waning_gibbous_moon: 🌗 :last_quarter_moon: 🌘 :waning_crescent_moon: 🌜 :last_quarter_moon_with_face: 🌛 :first_quarter_moon_with_face: 🌔 :moon: 🌍 :earth_africa: 🌎 :earth_americas: 🌏 :earth_asia: 🌋 :volcano: 🌌 :milky_way: ⛅ :partly_sunny: :octocat: :octocat: :squirrel: :squirrel: Objects 🎍 :bamboo: 💝 :gift_heart: 🎎 :dolls: 🎒 :school_satchel: 🎓 :mortar_board: 🎏 :flags: 🎆 :fireworks: 🎇 :sparkler: 🎐 :wind_chime: 🎑 :rice_scene: 🎃 :jack_o_lantern: 👻 :ghost: 🎅 :santa: 🎄 :christmas_tree: 🎁 :gift: 🔔 :bell: 🔕 :no_bell: 🎋 :tanabata_tree: 🎉 :tada: 🎊 :confetti_ball: 🎈 :balloon: 🔮 :crystal_ball: 💿 :cd: 📀 :dvd: 💾 :floppy_disk: 📷 :camera: 📹 :video_camera: 🎥 :movie_camera: 💻 :computer: 📺 :tv: 📱 :iphone: ☎️ :phone: ☎️ :telephone: 📞 :telephone_receiver: 📟 :pager: 📠 :fax: 💽 :minidisc: 📼 :vhs: 🔉 :sound: 🔈 :speaker: 🔇 :mute: 📢 :loudspeaker: 📣 :mega: ⌛ :hourglass: ⏳ :hourglass_flowing_sand: ⏰ :alarm_clock: ⌚ :watch: 📻 :radio: 📡 :satellite: ➿ :loop: 🔍 :mag: 🔎 :mag_right: 🔓 :unlock: 🔒 :lock: 🔏 :lock_with_ink_pen: 🔐 :closed_lock_with_key: 🔑 :key: 💡 :bulb: 🔦 :flashlight: 🔆 :high_brightness: 🔅 :low_brightness: 🔌 :electric_plug: 🔋 :battery: 📲 :calling: 📧 :email: 📫 :mailbox: 📮 :postbox: 🛀 :bath: 🛁 :bathtub: 🚿 :shower: 🚽 :toilet: 🔧 :wrench: 🔩 :nut_and_bolt: 🔨 :hammer: 💺 :seat: 💰 :moneybag: 💴 :yen: 💵 :dollar: 💷 :pound: 💶 :euro: 💳 :credit_card: 💸 :money_with_wings: 📧 :e-mail: 📥 :inbox_tray: 📤 :outbox_tray: ✉️ :envelope: 📨 :incoming_envelope: 📯 :postal_horn: 📪 :mailbox_closed: 📬 :mailbox_with_mail: 📭 :mailbox_with_no_mail: 🚪 :door: 🚬 :smoking: 💣 :bomb: 🔫 :gun: 🔪 :hocho: 💊 :pill: 💉 :syringe: 📄 :page_facing_up: 📃 :page_with_curl: 📑 :bookmark_tabs: 📊 :bar_chart: 📈 :chart_with_upwards_trend: 📉 :chart_with_downwards_trend: 📜 :scroll: 📋 :clipboard: 📆 :calendar: 📅 :date: 📇 :card_index: 📁 :file_folder: 📂 :open_file_folder: ✂️ :scissors: 📌 :pushpin: 📎 :paperclip: ✒️ :black_nib: ✏️ :pencil2: 📏 :straight_ruler: 📐 :triangular_ruler: 📕 :closed_book: 📗 :green_book: 📘 :blue_book: 📙 :orange_book: 📓 :notebook: 📔 :notebook_with_decorative_cover: 📒 :ledger: 📚 :books: 🔖 :bookmark: 📛 :name_badge: 🔬 :microscope: 🔭 :telescope: 📰 :newspaper: 🏈 :football: 🏀 :basketball: ⚽ :soccer: ⚾ :baseball: 🎾 :tennis: 🎱 :8ball: 🏉 :rugby_football: 🎳 :bowling: ⛳ :golf: 🚵 :mountain_bicyclist: 🚴 :bicyclist: 🏇 :horse_racing: 🏂 :snowboarder: 🏊 :swimmer: 🏄 :surfer: 🎿 :ski: ♠️ :spades: ♥️ :hearts: ♣️ :clubs: ♦️ :diamonds: 💎 :gem: 💍 :ring: 🏆 :trophy: 🎼 :musical_score: 🎹 :musical_keyboard: 🎻 :violin: 👾 :space_invader: 🎮 :video_game: 🃏 :black_joker: 🎴 :flower_playing_cards: 🎲 :game_die: 🎯 :dart: 🀄 :mahjong: 🎬 :clapper: 📝 :memo: 📝 :pencil: 📖 :book: 🎨 :art: 🎤 :microphone: 🎧 :headphones: 🎺 :trumpet: 🎷 :saxophone: 🎸 :guitar: 👞 :shoe: 👡 :sandal: 👠 :high_heel: 💄 :lipstick: 👢 :boot: 👕 :shirt: 👕 :tshirt: 👔 :necktie: 👚 :womans_clothes: 👗 :dress: 🎽 :running_shirt_with_sash: 👖 :jeans: 👘 :kimono: 👙 :bikini: 🎀 :ribbon: 🎩 :tophat: 👑 :crown: 👒 :womans_hat: 👞 :mans_shoe: 🌂 :closed_umbrella: 💼 :briefcase: 👜 :handbag: 👝 :pouch: 👛 :purse: 👓 :eyeglasses: 🎣 :fishing_pole_and_fish: ☕ :coffee: 🍵 :tea: 🍶 :sake: 🍼 :baby_bottle: 🍺 :beer: 🍻 :beers: 🍸 :cocktail: 🍹 :tropical_drink: 🍷 :wine_glass: 🍴 :fork_and_knife: 🍕 :pizza: 🍔 :hamburger: 🍟 :fries: 🍗 :poultry_leg: 🍖 :meat_on_bone: 🍝 :spaghetti: 🍛 :curry: 🍤 :fried_shrimp: 🍱 :bento: 🍣 :sushi: 🍥 :fish_cake: 🍙 :rice_ball: 🍘 :rice_cracker: 🍚 :rice: 🍜 :ramen: 🍲 :stew: 🍢 :oden: 🍡 :dango: 🥚 :egg: 🍞 :bread: 🍩 :doughnut: 🍮 :custard: 🍦 :icecream: 🍨 :ice_cream: 🍧 :shaved_ice: 🎂 :birthday: 🍰 :cake: 🍪 :cookie: 🍫 :chocolate_bar: 🍬 :candy: 🍭 :lollipop: 🍯 :honey_pot: 🍎 :apple: 🍏 :green_apple: 🍊 :tangerine: 🍋 :lemon: 🍒 :cherries: 🍇 :grapes: 🍉 :watermelon: 🍓 :strawberry: 🍑 :peach: 🍈 :melon: 🍌 :banana: 🍐 :pear: 🍍 :pineapple: 🍠 :sweet_potato: 🍆 :eggplant: 🍅 :tomato: 🌽 :corn: Places 🏠 :house: 🏡 :house_with_garden: 🏫 :school: 🏢 :office: 🏣 :post_office: 🏥 :hospital: 🏦 :bank: 🏪 :convenience_store: 🏩 :love_hotel: 🏨 :hotel: 💒 :wedding: ⛪ :church: 🏬 :department_store: 🏤 :european_post_office: 🌇 :city_sunrise: 🌆 :city_sunset: 🏯 :japanese_castle: 🏰 :european_castle: ⛺ :tent: 🏭 :factory: 🗼 :tokyo_tower: 🗾 :japan: 🗻 :mount_fuji: 🌄 :sunrise_over_mountains: 🌅 :sunrise: 🌠 :stars: 🗽 :statue_of_liberty: 🌉 :bridge_at_night: 🎠 :carousel_horse: 🌈 :rainbow: 🎡 :ferris_wheel: ⛲ :fountain: 🎢 :roller_coaster: 🚢 :ship: 🚤 :speedboat: ⛵ :boat: ⛵ :sailboat: 🚣 :rowboat: ⚓ :anchor: 🚀 :rocket: ✈️ :airplane: 🚁 :helicopter: 🚂 :steam_locomotive: 🚊 :tram: 🚞 :mountain_railway: 🚲 :bike: 🚡 :aerial_tramway: 🚟 :suspension_railway: 🚠 :mountain_cableway: 🚜 :tractor: 🚙 :blue_car: 🚘 :oncoming_automobile: 🚗 :car: 🚗 :red_car: 🚕 :taxi: 🚖 :oncoming_taxi: 🚛 :articulated_lorry: 🚌 :bus: 🚍 :oncoming_bus: 🚨 :rotating_light: 🚓 :police_car: 🚔 :oncoming_police_car: 🚒 :fire_engine: 🚑 :ambulance: 🚐 :minibus: 🚚 :truck: 🚋 :train: 🚉 :station: 🚆 :train2: 🚅 :bullettrain_front: 🚄 :bullettrain_side: 🚈 :light_rail: 🚝 :monorail: 🚃 :railway_car: 🚎 :trolleybus: 🎫 :ticket: ⛽ :fuelpump: 🚦 :vertical_traffic_light: 🚥 :traffic_light: ⚠️ :warning: 🚧 :construction: 🔰 :beginner: 🏧 :atm: 🎰 :slot_machine: 🚏 :busstop: 💈 :barber: ♨️ :hotsprings: 🏁 :checkered_flag: 🎌 :crossed_flags: 🏮 :izakaya_lantern: 🗿 :moyai: 🎪 :circus_tent: 🎭 :performing_arts: 📍 :round_pushpin: 🚩 :triangular_flag_on_post: 🇯🇵 :jp: 🇰🇷 :kr: 🇨🇳 :cn: 🇺🇸 :us: 🇫🇷 :fr: 🇪🇸 :es: 🇮🇹 :it: 🇷🇺 :ru: 🇬🇧 :gb: 🇬🇧 :uk: 🇩🇪 :de: Symbols 1️⃣ :one: 2️⃣ :two: 3️⃣ :three: 4️⃣ :four: 5️⃣ :five: 6️⃣ :six: 7️⃣ :seven: 8️⃣ :eight: 9️⃣ :nine: 🔟 :keycap_ten: 🔢 :1234: 0️⃣ :zero: #️⃣ :hash: 🔣 :symbols: ◀️ :arrow_backward: ⬇️ :arrow_down: ▶️ :arrow_forward: ⬅️ :arrow_left: 🔠 :capital_abcd: 🔡 :abcd: 🔤 :abc: ↙️ :arrow_lower_left: ↘️ :arrow_lower_right: ➡️ :arrow_right: ⬆️ :arrow_up: ↖️ :arrow_upper_left: ↗️ :arrow_upper_right: ⏬ :arrow_double_down: ⏫ :arrow_double_up: 🔽 :arrow_down_small: ⤵️ :arrow_heading_down: ⤴️ :arrow_heading_up: ↩️ :leftwards_arrow_with_hook: ↪️ :arrow_right_hook: ↔️ :left_right_arrow: ↕️ :arrow_up_down: 🔼 :arrow_up_small: 🔃 :arrows_clockwise: 🔄 :arrows_counterclockwise: ⏪ :rewind: ⏩ :fast_forward: ℹ️ :information_source: 🆗 :ok: 🔀 :twisted_rightwards_arrows: 🔁 :repeat: 🔂 :repeat_one: 🆕 :new: 🔝 :top: 🆙 :up: 🆒 :cool: 🆓 :free: 🆖 :ng: 🎦 :cinema: 🈁 :koko: 📶 :signal_strength: :u5272: :u5272: :u5408: :u5408: :u55b6: :u55b6: :u6307: :u6307: :u6708: :u6708: :u6709: :u6709: 🈵 :u6e80: :u7121: :u7121: :u7533: :u7533: :u7a7a: :u7a7a: :u7981: :u7981: 🈂️ :sa: 🚻 :restroom: 🚹 :mens: 🚺 :womens: 🚼 :baby_symbol: 🚭 :no_smoking: 🅿️ :parking: ♿ :wheelchair: 🚇 :metro: 🛄 :baggage_claim: 🉑 :accept: 🚾 :wc: 🚰 :potable_water: 🚮 :put_litter_in_its_place: ㊙️ :secret: ㊗️ :congratulations: Ⓜ️ :m: 🛂 :passport_control: 🛅 :left_luggage: 🛃 :customs: 🉐 :ideograph_advantage: 🆑 :cl: 🆘 :sos: 🆔 :id: 🚫 :no_entry_sign: 🔞 :underage: 📵 :no_mobile_phones: 🚯 :do_not_litter: 🚱 :non-potable_water: 🚳 :no_bicycles: 🚷 :no_pedestrians: 🚸 :children_crossing: ⛔ :no_entry: ✳️ :eight_spoked_asterisk: ✴️ :eight_pointed_black_star: 💟 :heart_decoration: 🆚 :vs: 📳 :vibration_mode: 📴 :mobile_phone_off: 💹 :chart: 💱 :currency_exchange: ♈ :aries: ♉ :taurus: ♊ :gemini: ♋ :cancer: ♌ :leo: ♍ :virgo: ♎ :libra: ♏ :scorpius: ♐ :sagittarius: ♑ :capricorn: ♒ :aquarius: ♓ :pisces: ⛎ :ophiuchus: 🔯 :six_pointed_star: ❎ :negative_squared_cross_mark: 🅰️ :a: 🅱️ :b: 🆎 :ab: 🅾️ :o2: 💠 :diamond_shape_with_a_dot_inside: ♻️ :recycle: 🔚 :end: 🔛 :on: 🔜 :soon: 🕐 :clock1: 🕜 :clock130: 🕙 :clock10: 🕥 :clock1030: 🕚 :clock11: 🕦 :clock1130: 🕛 :clock12: 🕧 :clock1230: 🕑 :clock2: 🕝 :clock230: 🕒 :clock3: 🕞 :clock330: 🕓 :clock4: 🕟 :clock430: 🕔 :clock5: 🕠 :clock530: 🕕 :clock6: 🕡 :clock630: 🕖 :clock7: 🕢 :clock730: 🕗 :clock8: 🕣 :clock830: 🕘 :clock9: 🕤 :clock930: 💲 :heavy_dollar_sign: ©️ :copyright: ®️ :registered: ™️ :tm: ❌ :x: ❗ :heavy_exclamation_mark: ‼️ :bangbang: ⁉️ :interrobang: ⭕ :o: ✖️ :heavy_multiplication_x: ➕ :heavy_plus_sign: ➖ :heavy_minus_sign: ➗ :heavy_division_sign: 💮 :white_flower: 💯 :100: ✔️ :heavy_check_mark: ☑️ :ballot_box_with_check: 🔘 :radio_button: 🔗 :link: ➰ :curly_loop: 〰️ :wavy_dash: 〽️ :part_alternation_mark: 🔱 :trident: :black_square: :black_square: :white_square: :white_square: ✅ :white_check_mark: 🔲 :black_square_button: 🔳 :white_square_button: ⚫ :black_circle: ⚪ :white_circle: 🔴 :red_circle: 🔵 :large_blue_circle: 🔷 :large_blue_diamond: 🔶 :large_orange_diamond: 🔹 :small_blue_diamond: 🔸 :small_orange_diamond: 🔺 :small_red_triangle: 🔻 :small_red_triangle_down: :shipit: :shipit:","tags":[{"name":"Markdown","slug":"Markdown","permalink":"//blog.solutionx.top/tags/Markdown/"},{"name":"2022-09","slug":"2022-09","permalink":"//blog.solutionx.top/tags/2022-09/"}]},{"title":"certbot自动签发SSL通配符证书","date":"2022-09-15T08:28:42.000Z","path":"2022/09/15/diary-220915-01/","text":"【概述】 ① 使用 certbot 签发免费的 SSL 通配符证书; ② 证书有效期为三个月,非通配符证书到期后会自动重签,但是通配符证书由于需要设置 DNS TXT 记录,故需要借助各云服务商的CLI工具自动设置(本文以阿里云CLI为例); ③ 编写用于转换证书格式的脚本(本文主要介绍用于 .net 程序的证书格式 .pfx); ④ 编写用于将证书复制到远程服务器对应目录的脚本(此脚本需要用到 expect 工具自动补全 ssh 密码,存在一定风险,介意的朋友也可以考虑更安全的方式,也欢迎在评论区分享出来!); ⑤ 往 crontab 中添定时任务,定时执行证书刷新命令以及上述两个脚本。 在开始之前,先来说说 certbot 签发证书的大致流程 ① 执行 certbot 命令,然后根据提示输入必要的信息; ② certbot 向 SSL 通配符证书自动签发平台 Let’s Encrypt 发送签发请求; ③ 输出 已成功签发的证书文件。 0. 准备 ① 进行通配符证书签发的服务器需要有公网IP; ② 在服务器上安装 certbot 工具,本文以 Linux 平台为例,大家可以结合自身需求以及系统版本参考 certbot官网 给出的教程进行安装; ℹ️ 需要特别说明的是,Oracle Linux Server 参考 CentOS 的教程借助 snapd 进行安装的时候会提示 Segmentation fault 这一类错误信息,如果你的系统恰巧也是 Oracle Linux Server,可参考以下命令进行安装: 12345$ sudo su - # 建议先切换到root用户$ yum install certbot -y$ yum install python3-certbot-nginx -y# 参考 https://github.com/certbot/certbot/issues/9138#issuecomment-1059061866 ③ 检查服务器上的443端口是否被占用,如被占用,则请先关闭对应的服务进程,否则后续执行 certbot 可能会报错。 1. 签发通配符证书 使用 certbot 签发证书很简单,只需要一条命令即可。 1$ certbot certonly -d \"*.yourdomain.com\" --manual --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory 参数说明: certonly 表示插件,Certbot 有很多插件。不同的插件都可以申请证书,用户可以根据需要自行选择; -d 为哪些主机申请证书。如果是通配符,输入 *.xxx.com (根据实际情况替换为你自己的域名); –preferred-challenges dns-01,使用 DNS 方式校验域名所有权;(通配符证书只能用 dns-01) –server,Let’s Encrypt ACME v2 版本使用的服务器不同于 v1 版本,需要显示指定。 执行完上述命令后,会提示 输入邮箱、是否同意相关协议、是否愿意将邮箱地址共享给基金会、对IP被访问是否OK 等输入确认项,挨个输入确认即可,当执行到最后一步的时候先不要按回车: 123456789- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Please deploy a DNS TXT record under the name_acme-challenge.yourdomain.com with the following value:fxTlRPDBTFGUXCp2qDrEZRfasdKoPGuShUVYk2jIf-9Before continuing, verify the record is deployed.- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Press Enter to Continue 将提示中的 DNS TXT 记录添加到域名的 DNS 解析中。(示例: 记录名:_acme-challenge;记录值:fxTlRPDBTFGUXCp2qDrEZRfasdKoPGuShUVYk2jIf-9) 回车确认是否成功,若成功,则输出如下内容: 12345678910111213IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/yourdomain.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/yourdomain.com/privkey.pem Your cert will expire on 2022-12-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot-auto again. To non-interactively renew *all* of your certificates, run \"certbot-auto renew\" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le 证书和密钥保存在目录 /etc/letsencrypt/live/yourdomain.com/ 下方。 以上步骤即完成了通配符证书的签发。 2. 配置用于自动创建 DNS TXT 记录的脚本 Github上有很多写的特别好的教程,此处放一个传送门。 大致步骤如下:(详细步骤还请参考原文,如有不懂可留言) ① 在阿里云控制台中创建一个 Access Key(记得记下来); ② 安装 aliyun cli 工具,执行 aliyun configure 配置 Access Key; ③ 安装 certbot-dns-aliyun 插件(一个用于 certbot 钩子的脚本); ④ 执行 certbot renew 命令对证书进行延期操作时,加上钩子参数 --manual-auth-hook 和 --manual-cleanup-hook 指定需要运行的命令(即第③步中安装的插件) 3. 编写用于转换证书格式的脚本(可无) 创建存放脚本的目录(根据个人习惯自定义) 12$ mkdir -p /root/SSL/scripts/$ cd /root/SSL/scripts/ 创建脚本 $ nano transform-to-pfx.sh,内容如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859#!/bin/bash# Define variables.domain=yourdomain.com # 此处填写你的域名password=123456 # 此处填写用于证书格式转换时的密码outputDir=/root/SSL/yourdomain.com # 此处填写用于存放证书文件的目录outputFile=yourdomain.com.pfx # 此处填写转换后的证书文件名称# Build the params with the variables.pemOutputDir=/etc/letsencrypt/live/${domain}pfxOutputPath=${outputDir}/${outputFile}# Output execute time.echo [$(date +\"%Y-%m-%d %H:%M:%S\")]# Check if expect is installed.command -v expect >/dev/null 2>&1 || { echo >&2 \"[expect] is not installed. Trying to install it...\"; yum install expect -y;}# Output the infos.echo \"------------------------------------\"echo \"[Source file]\"echo \" ${pemOutputDir}/fullchain.pem\"echo \" ${pemOutputDir}/privkey.pem\"echo \"[Target file]\"echo \" ${pfxOutputPath}\"echo \"------------------------------------\"echo# Copy source file to target directory.mkdir -p ${outputDir}cp -L ${pemOutputDir}/* ${outputDir}# Use expect to auto fill the input params.echo \"Executing transform command...\"echoexpect << EOF # Execute transform command. spawn openssl pkcs12 -export -in ${pemOutputDir}/fullchain.pem -inkey ${pemOutputDir}/privkey.pem -out ${pfxOutputPath} sleep 1 expect { \"assword:\" { send \"$password\\r\"; exp_continue } \"assword:\" { send \"$password\\r\" } }EOFechoechoecho \"[finished]\"echo \"------------------------------------\" 给脚本授予可执行权限: 1$ chmod +x transform-to-pfx.sh 4. 编写用于拷贝证书文件到对应服务器的脚本(可无) 创建脚本 $ nano copy-result-to-private-server.sh,内容如下: 1234567891011121314151617181920212223242526272829303132#!/bin/bash# Define variablessourceDir=/root/SSL/yourdomain.com/ # 此处填写证书文件所在目录targetHost=10.10.10.10 # 此处填写远程服务器的地址targetPort=22 # 此处填写远程服务器SSH端口号targetDir=/home/user/SSL/letsencrypt/ # 此处填写远程服务器证书放置目录(需提前创建)uname=user # 此处填写远程服务器的登录用户名upwd=P@ssw0rd # 此处填写远程服务器的登录密码# Output execute time.echo [$(date +\"%Y-%m-%d %H:%M:%S\")]# Check if expect is installed.command -v expect >/dev/null 2>&1 || { echo >&2 \"[expect] is not installed. Trying to install it...\"; yum install expect -y;}# Use expect to auto fill the input params with variables.expect -c \" spawn scp -r -P ${targetPort} ${sourceDir} ${uname}@${targetHost}:${targetDir} expect { \\\"Are you sure you want to continue connecting (yes/no)?\\\" {send \\\"yes\\r\\\"; exp_continue;} \\\"*assword\\\" {send \\\"${upwd}\\r\\\";} }expect eof\"echo 给脚本授予可执行权限: 1$ chmod +x copy-result-to-private-server.sh 5. 配置 crontab 每周周日0点执行一次 12345678# .---------------- 分钟,取值范围为 0-59# | .------------- 小时,取值范围为 0-23# | | .---------- 日,取值范围为 1-31# | | | .------- 月,取值范围为 1-12# | | | | .---- 星期,取值范围为 0-7,0 和 7 都表示星期日# | | | | | .-- 要执行的命令# | | | | | | 0 0 * * 7 root certbot renew --manual --preferred-challenges dns --manual-public-ip-logging-ok --manual-auth-hook \"alidns\" --manual-cleanup-hook \"alidns clean\" --deploy-hook \"/root/SSL/scripts/transform-to-pfx.sh 2>&1 >/root/SSL/scripts/transform-to-pfx.log && /root/SSL/scripts/copy-result-to-private-server.sh 2>&1 >/root/SSL/scripts/copy-result-to-private-server.log\"","tags":[{"name":"2022-09","slug":"2022-09","permalink":"//blog.solutionx.top/tags/2022-09/"},{"name":"Https","slug":"Https","permalink":"//blog.solutionx.top/tags/Https/"},{"name":"SSL","slug":"SSL","permalink":"//blog.solutionx.top/tags/SSL/"},{"name":"Certbot","slug":"Certbot","permalink":"//blog.solutionx.top/tags/Certbot/"},{"name":"Shell","slug":"Shell","permalink":"//blog.solutionx.top/tags/Shell/"}]},{"title":"C# 你对枚举有多了解?","date":"2022-08-10T14:47:11.000Z","path":"2022/08/10/diary-220810-01/","text":"那些你可能没有关注过的枚举知识。 【组合枚举(Flags Enum)】 可以对枚举的成员进行组合,主要是改变ToString()方法的返回值,让可组合枚举调用ToString()时返回枚举成员名。 【Flags特性】 1、按约定,如果枚举成员可组合,falgs特性必须被添加到枚举类型上 注意:如果声明了这样的枚举却没有使用flags属性,你仍然可以组合枚举的成员,但是调用枚举实例的ToString()方法时,输出的将是一个数值,而不是一组名称。 2、按约定,可组合枚举的名称应该是复数的(比如:BorderSides,名称结尾带 s)。 【注意点】 为了避免歧义,枚举成员需要显示的赋值。典型的就是使用2的乘幂。 12[Flags]public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 } 【运算】 可以使用位操作符,例如 | 和 & 1234567891011121314BorderSides leftRight = BorderSides.Left | BorderSides.Right;if ((leftRight & BorderSides.Left) != 0) Console.WriteLine(\"Includes Left\");string formatted = leftRight.ToString(0); // \"Left, Right\"(当枚举的定义没加 Flags 特性时,此处打印 3)BorderSides s = BorderSides.Left;s |= BorderSides.Right;Console.WriteLine(s == leftRight); // Trues ^= BorderSides.Right; // 按位亦或Console.WriteLine(s); // Left(当枚举的定义没加 Flags 特性时,此处打印 1) 【运算原理】 枚举值转为二进制后按位操作 【枚举支持的操作符】 操作符 描述 = 赋值 == 判断是否相等 != 判断是否不相等 <= 判断是否小于等于 >= 判断是否大于等于 < 左移 > 右移 & 按位与 | 按位或 ^ 按位异或 + 加 - 减 ~ 按位取反 ++ 自增 – 自减 += 加 -= 减 sizeof 获取枚举的大小 【类型安全问题】 检查枚举值的合理性:Enum.IsDefined() 静态方法。 12BorderSide side = (BorderSide) 2333;Console.WriteLine(Enum.IsDefined(typeof(BorderSide), side)); // False 以上知识整理自B站大佬的视频:真会C# … 3.7 3.8 枚举和嵌套类型","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2022-08","slug":"2022-08","permalink":"//blog.solutionx.top/tags/2022-08/"}]},{"title":"IdentityServer4 - 入门","date":"2022-08-04T13:35:06.000Z","path":"2022/08/04/diary-220804-03/","text":"【Identity Server 4】 官网:https://identityserver4.readthedocs.io/en/latest/index.html [概述] 是一个 OpenID Connect provider → 它实现了 OpenID Connect 和 OAuth 2.0 两个协议 主要职责 1、保护资源 2、认证用户(可使用本地账户或外部的 Identity Provider,如使用微信、github登录) 3、可提供会话管理和单点登录(SSO) 4、管理和认证客户端 5、给客户端发行 identity 和 access token 6、验证 tokens 【实际应用举例】 1、保护客户端(值守) OAuth 2.0 Client Credentials 当应用请求access token访问它们所拥有的资源时使用,不代表用户。 2、保护WPF客户端(用户登录) OAuth 2.0 Password Grant 【启动一个将数据存到内存的模板】(仅适用于测试或对安全性要求不高的项目) 1、安装模板 1PS> dotnet new -i identityserver4.templates 2、查看已安装的模板 1PS> dotnet new --list IdentityServer4提供的模板有如下几类 123456IdentityServer4 Empty is4empty [C#] Web/IdentityServer4IdentityServer4 Quickstart UI (UI assets only) is4ui [C#] Web/IdentityServer4IdentityServer4 with AdminUI is4admin [C#] Web/IdentityServer4IdentityServer4 with ASP.NET Core Identity is4aspid [C#] Web/IdentityServer4IdentityServer4 with Entity Framework Stores is4ef [C#] Web/IdentityServer4IdentityServer4 with In-Memory Stores and Test Users is4inmem [C#] Web/IdentityServer4 3、启动一个在内存中存储数据且包含测试用户的模板 1PS> dotnet new is4inmem --name IdentityServer.MemoryTemplate.Test","tags":[{"name":"2022-08","slug":"2022-08","permalink":"//blog.solutionx.top/tags/2022-08/"},{"name":"IdentityServer4","slug":"IdentityServer4","permalink":"//blog.solutionx.top/tags/IdentityServer4/"},{"name":"ASP.NET Core","slug":"ASP-NET-Core","permalink":"//blog.solutionx.top/tags/ASP-NET-Core/"}]},{"title":"OpenID Connect 协议","date":"2022-08-04T13:00:00.000Z","path":"2022/08/04/diary-220804-02/","text":"【OpenID Connect 协议】 是一种身份认证协议(Authentication),基于OAuth2.0构建 官方定义:OpenID Connect 是建立在 OAuthorization2.0 协议上的一个简单的身份标识层,OpenID Connect 兼容 OAuth2.0 注意:(以下二者单词不是同一个) 身份认证 Authentication 授权 Authorization 【ID Token】 包括用户身份认证信息 【UserInfo端点】 通过该端点可获取用户信息 【提供一组标识身份的 scopes 和 claims】 1、profile 2、email 3、address 4、phone 【抽象流程图】 12345678910111213141516171819202122+----------+ +----------+| | | || |--(A)------- Authorization Grant ---------> | || | | || | +------------+ | || | | | | || | | | | || | | End-User |<---(2) AuthN & AuthZ --->| || | | | | || RP | | | | OP ||(Relying) | | | | || | +------------+ | || | | || | | || |<------------(3) AuthN Response ------------| || | | || |-------------(4) UserInfo Request --------->| || | | || |<------------(5) UserInfo Response----------| || | | |+----------+ +----------+ 依赖方 OpenID Provider 【三个流程】 1、Authorization Code Flow 2、Implicit Flow 3、Hybrid Flow","tags":[{"name":"2022-08","slug":"2022-08","permalink":"//blog.solutionx.top/tags/2022-08/"},{"name":"OpenID Connect","slug":"OpenID-Connect","permalink":"//blog.solutionx.top/tags/OpenID-Connect/"},{"name":"IdentityServer4","slug":"IdentityServer4","permalink":"//blog.solutionx.top/tags/IdentityServer4/"}]},{"title":"OAuth 2.0 协议","date":"2022-08-04T12:00:00.000Z","path":"2022/08/04/diary-220804-01/","text":"【OAuth2.0】 是一个委托协议,它可以让那些控制资源的人允许某个应用以代表他们来访问他们控制的资源,注意是“代表这些人”,而不是假冒或者模仿这些人。 这个应用从资源的所有者那里获得到授权(Authorization)和 AccessToken,随后就可以使用这个 Access Token 来访问资源。 【结构】 [用户] ←→ [客户端应用] ←→ [受保护资源] 资源所有者 如:WPF、Angular (SPA)、ASP.NET Core MVC 如:ASP.NET Core Web API 可委派权限 代表资源所有者访问受保护的资源(资源消费者) 一般指Web API、文件资源等 *注:SPA → Single Page Application,即单页面应用 123456789101112131415161718+---------+ +---------------+| |--(A)- Authorization Request -> | Resource || | | Owner || |<-(B)-- Authorization Grant --- | || | +---------------+| || | +---------------+| Client |--(C)-- Authorization Grant --> | Authorization || | | Server || |<-(D)------ Access Token ------ | || | +---------------+| || | +---------------+| |--(E)------ Access Token -----> | Resource || | | Server || |<-(F)--- Protected Resource --- | || | +---------------++---------+ 【授权Authorization】 只能用于授权,即 → “你能干什么” OpenId Connect 身份认证Authentication 协议 (相当于在OAuth2.0上加了一层) 用于身份认证,即 → “你是谁” 授权类型 Authorization Grant Types 1、★ Authorization Code (采用授权服务器作为中介,而不是从资源所有者获得) 2、★ Implicit (减少了来回往返的次数,可提高浏览器应用的相应速度,存在一定风险) 3、★ Resource Owner Password Credentials (直接使用资源所有者的密码验证,当资源所有者和客户端之间高度信任并且其他授权方式不 可、的时候才可以使用,凭据只用于一次请求) 4、★ Client Credentials (资源或资源服务器不属于某个用户时使用) 5、Device Code 6、Refresh Token (用于刷新、获取Access Token) 【端点】 1、Authorization Endpoint 授权端点(在浏览器中处理,和资源所有者进行交互) 2、Token Endpoint Token端点 (客户端应用软件与服务器进行交互,获得 Access Token) 【Scope 范围】 代表资源所有者在被保护资源那里的一些权限 【Access Token】 1、有时候就叫Token 2、用来访问被保护资源的凭据 3、代表了给客户端颁发的授权,也就是委托给客户端的权限 4、描述出Scope,有效期 【Refresh Token】 1、用来获取 Access Token 的凭据 2、由 Authorization Server 颁发给客户端应用的 3、可选(根据情况选择只返回 Access Token 还是将 Access Token 和新的 Refresh Token 一起返回) 4、具备让客户端应用逐渐降低访问权限的能力 12345678910111213141516171819202122+---------+ +---------------+| |--(A)------- Authorization Grant -----------> | || | | || |<-(B)--------------Access Token ------------- | || | & Refresh Token | || | | || | +------------+ | || |--(C)---- Access Token ----> | | | || | | | | || Client |<-(D)- Protected Resource -- | Resource | | Authorization || | | Server | | Server || |--(E)---- Access Token ----> | | | || | | | | || |<-(F)- Ivalid Token Error -- | | | || | +------------+ | || | | || | | || |--(G)------------- Refresh Token -----------> | || | | || |<-(H)----------- Access Token --------------- | || | & Optional Refresh Token +---------------++---------+ 【发生错误时】 1、error 2、error_description 3、error_uri 4、state 【错误的类型】 1、invalid_request 2、invalid_client (401) 3、invalid_grant 4、unauthorized_client 5、unsupported_grant_type 6、invalid_scope","tags":[{"name":"2022-08","slug":"2022-08","permalink":"//blog.solutionx.top/tags/2022-08/"},{"name":"IdentityServer4","slug":"IdentityServer4","permalink":"//blog.solutionx.top/tags/IdentityServer4/"},{"name":"OAuth 2.0","slug":"OAuth-2-0","permalink":"//blog.solutionx.top/tags/OAuth-2-0/"}]},{"title":"在Docker中运行Wiki.js","date":"2022-07-31T12:00:00.000Z","path":"2022/07/31/diary-220731-01/","text":"本教程介绍编写docker-compose.yaml以在Docker中运行Wiki.js 换不多说,直接上docker-compose.yaml的内容 123456789101112131415161718192021222324252627282930313233343536version: \"3\"services: wiki-db: image: postgres:11-alpine environment: TZ: Asia/Shanghai POSTGRES_DB: wiki POSTGRES_PASSWORD: wikijsrocks POSTGRES_USER: wikijs# logging:# driver: \"none\" # 如不需要输出日志,可将此两行取消注释 restart: unless-stopped volumes: - db-data:/var/lib/postgresql/data# ports:# - \"5432:5432\" # 如需要在Navicat等客户端中连接该数据库,可将此两行取消注释 wiki: image: ghcr.io/requarks/wiki:2 depends_on: - wiki-db environment: TZ: Asia/Shanghai DB_TYPE: postgres DB_HOST: wiki-db DB_PORT: 5432 DB_USER: wikijs DB_PASS: wikijsrocks DB_NAME: wiki restart: unless-stopped ports: - \"8080:3000\" # 可将8080改为自己想要的端口,用于后续的访问volumes: db-data: 【启动容器】 在docker-compose.yaml所在目录下执行以下命令: 1$ docker-compose up -d 【停止容器】 在docker-compose.yaml所在目录下执行以下命令: 1$ docker-compose stop 【停止并删除容器】 在docker-compose.yaml所在目录下执行以下命令: 1$ docker-compose down","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2022-07","slug":"2022-07","permalink":"//blog.solutionx.top/tags/2022-07/"},{"name":"Wiki.js","slug":"Wiki-js","permalink":"//blog.solutionx.top/tags/Wiki-js/"}]},{"title":"Windows下安装配置MinIO","date":"2022-05-30T16:00:00.000Z","path":"2022/05/31/diary-220531-01/","text":"本教程介绍在Windows下安装配置MinIO。(包括直接运行+配置成服务) 【手动运行MinIO】 1、准备 创建放置minio可执行文件的目录(例:E:\\MinIO) 2、运行Powershell,下载安装包、配置环境变量、手动运行MinIO ● 执行以下命令下载minio安装包到上一步创建的文件夹中(也可以访问https://dl.min.io/server/minio/release/windows-amd64/minio.exe直接下载) 1PS> Invoke-WebRequest -Uri \"https://dl.min.io/server/minio/release/windows-amd64/minio.exe\" -OutFile \"E:\\MinIO\\minio.exe\" ● 执行以下命令设置环境用户名和密码的环境变量 12PS> setx MINIO_ROOT_USER minioadminPS> setx MINIO_ROOT_PASSWORD asdf@123 ● 执行以下命令手动运行MinIO 1PS> E:\\MinIO\\minio.exe server E:\\MinIO\\data --address=:12000 --console-address=:12001 参数说明: –address 定义api监听的地址(默认端口为9000) –console-address 定义网页控制台监听的地址(默认端口为9001) 【配置成服务】 借助winsw将MinIO配置成服务,其他程序也可以参考此方法 1、下载winsw(https://github.com/winsw/winsw/releases) 2、将WinSW-x64.exe复制到放置minio.exe的目录E:\\MinIO,将WinSW-x64.exe重命名为MinioService.exe 3、在目录E:\\MinIO中创建MinioService.xml文件,编辑内容如下 12345678910111213141516171819202122232425262728293031<service> <!-- 设置服务Id --> <id>MinioService</id> <!-- 设置服务名称 --> <name>MinioService</name> <!-- 设置服务描述 --> <description>MinioService</description> <!-- 设置环境变量 --> <env name=\"HOME\" value=\"%BASE%\"/> <!-- 设置执行对象 --> <executable>%BASE%\\minio.exe</executable> <!-- 设置执行参数 --> <arguments>server \"%BASE%\\data\" --address=:12000 --console-address=:12001</arguments> <!-- 设置日志模式 --> <logmode>rotate</logmode> <!-- 设置日志路径 --> <logpath>%BASE%\\logs</logpath> <!-- 设置日志参数 --> <log mode=\"roll-by-size-time\"> <!-- 设置切割阈值 --> <sizeThreshold>10240</sizeThreshold> <!-- 设置日期格式 --> <pattern>yyyyMMdd</pattern> <!-- 设置切割时间 --> <autoRollAtTime>00:00:00</autoRollAtTime> <!-- 设置生命周期 --> <zipOlderThanNumDays>5</zipOlderThanNumDays> <!-- 设置压缩格式 --> <zipDateFormat>yyyyMMdd</zipDateFormat> </log></service> 4、在.exe文件所在目录下运行CMD或PowerShell,执行以下命令完成 [安装/卸载服务] 的操作 1234567891011# 安装服务.\\MinioService.exe install# 卸载服务.\\MinioService.exe uninstall# 启动服务net start MinioService# 停止服务net stop MinioService 5、修改用户名或密码(默认均为minioadmin,手动安装也可以通过以下方式修改) ① 服务安装完成后,在E:\\MinIO\\data目录下有一个.minio.sys目录,进到这个目录,编辑config.json文件 ② 搜索 access_key 和 secret_key 并将其修改为自定义的值 ③ Win+R> 运行 services.msc,找到MinioService,右键重启服务 参考文档: 1、MinIO注册成服务 https://blog.csdn.net/weixin_41137835/article/details/114888202 2、MinIO修改密码 https://blog.csdn.net/weixin_44981485/article/details/106809902","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"Windows","slug":"Windows","permalink":"//blog.solutionx.top/tags/Windows/"},{"name":"2022-05","slug":"2022-05","permalink":"//blog.solutionx.top/tags/2022-05/"},{"name":"分布式对象存储","slug":"分布式对象存储","permalink":"//blog.solutionx.top/tags/分布式对象存储/"},{"name":"MinIO","slug":"MinIO","permalink":"//blog.solutionx.top/tags/MinIO/"}]},{"title":"使用Nginx配置一个简单的反向代理","date":"2022-02-21T16:00:00.000Z","path":"2022/02/22/diary-220222-01/","text":"本教程介绍使用Nginx配置一个简单的反向代理,或者你也可以使用在线网站 DigitalOcean 直接生成想要的配置 【nginx配置文件的目录结构】 123456└──conf ├── nginx.conf ... └── conf.d ├── proxies.conf └── proxy.conf 1、在 nginx.conf 文件的 http节点 中添加如下配置 1234567891011121314151617181920212223242526# Connection header for WebSocket reverse proxymap $http_upgrade $connection_upgrade { default upgrade; "" close;}map $remote_addr $proxy_forwarded_elem { # IPv4 addresses can be sent as-is ~^[0-9.]+$ "for=$remote_addr"; # IPv6 addresses need to be bracketed and quoted ~^[0-9A-Fa-f:.]+$ "for=\\"[$remote_addr]\\""; # Unix domain socket names cannot be represented in RFC 7239 syntax default "for=unknown";}map $http_forwarded $proxy_add_forwarded { # If the incoming Forwarded header is syntactically valid, append to it "~^(,[ \\\\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\\"([\\\\t \\\\x21\\\\x23-\\\\x5B\\\\x5D-\\\\x7E\\\\x80-\\\\xFF]|\\\\\\\\[\\\\t \\\\x21-\\\\x7E\\\\x80-\\\\xFF])*\\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\\"([\\\\t \\\\x21\\\\x23-\\\\x5B\\\\x5D-\\\\x7E\\\\x80-\\\\xFF]|\\\\\\\\[\\\\t \\\\x21-\\\\x7E\\\\x80-\\\\xFF])*\\"))?)*([ \\\\t]*,([ \\\\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\\"([\\\\t \\\\x21\\\\x23-\\\\x5B\\\\x5D-\\\\x7E\\\\x80-\\\\xFF]|\\\\\\\\[\\\\t \\\\x21-\\\\x7E\\\\x80-\\\\xFF])*\\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\\"([\\\\t \\\\x21\\\\x23-\\\\x5B\\\\x5D-\\\\x7E\\\\x80-\\\\xFF]|\\\\\\\\[\\\\t \\\\x21-\\\\x7E\\\\x80-\\\\xFF])*\\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem"; # Otherwise, replace it default "$proxy_forwarded_elem";} 2、在 conf.d 目录下创建 proxy.conf 配置文件,内容如下 123456789101112131415161718proxy_http_version 1.1;proxy_cache_bypass $http_upgrade;# Proxy headersproxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header Forwarded $proxy_add_forwarded;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;# Proxy timeoutsproxy_connect_timeout 60s;proxy_send_timeout 60s;proxy_read_timeout 60s; 3、在 conf.d 目录下创建反向代理配置项 proxies.conf 123456789server { listen 55555; server_name localhost; location / { proxy_pass http://localhost:8080/; include conf.d/proxy.conf; }}","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2022-02","slug":"2022-02","permalink":"//blog.solutionx.top/tags/2022-02/"},{"name":"Nginx","slug":"Nginx","permalink":"//blog.solutionx.top/tags/Nginx/"}]},{"title":"Windows路径太长无法删除的问题","date":"2022-02-12T16:00:00.000Z","path":"2022/02/13/diary-220213-01/","text":"[工具] Windows自带远程复制工具robocopy(无需安装) [示例] 包含长路径的文件夹:targetDir 1、在targetDir同一层级下创建一个名为test的目录 2、在targetDir目录下以管理员权限运行命令提示符或Powershell 3、执行命令 robocopy test targetDir /purge 4、等待目标文件夹被覆盖,覆盖完成后targetDir下的子目录则会消失 参考: https://jingyan.baidu.com/article/17bd8e525081be85ab2bb88c.html","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"Windows","slug":"Windows","permalink":"//blog.solutionx.top/tags/Windows/"},{"name":"2022-02","slug":"2022-02","permalink":"//blog.solutionx.top/tags/2022-02/"},{"name":"疑难杂症","slug":"疑难杂症","permalink":"//blog.solutionx.top/tags/疑难杂症/"}]},{"title":"Windows配置端口转发","date":"2022-01-11T16:00:00.000Z","path":"2022/01/12/diary-220112-01/","text":"本教程介绍Windows下使用自带工具配置端口转发(映射)。 【创建端口转发】 1PS> netsh interface portproxy add v4tov4 listenaddress=192.168.1.100 listenport=33890 connectaddress=192.168.1.101 connectport=3389 参数 说明 listenaddress 监听地址,如果本机有多个地址,且需要监听所有地址,此参数可以省略。 listenport 监听端口,用于监听网络请求的端口。 connectaddress 连接地址,用于转发网络请求的地址。 connectport 连接端口,用于转发网络请求的端口。 12# 上述命令可以简写为:PS> netsh interface portproxy add v4tov4 listenport=33890 connectaddress=192.168.1.101 connectport=3389 说明:以上命令设置本地监听端口 33890,转发到 192.168.1.101:3389 【查看端口转发规则】 1234567891011121314# 显示所有转发规则PS> netsh interface portproxy show all# 仅显示 IPv4ToIPv4 的转发规则PS> netsh interface portproxy show v4tov4# 仅显示 IPv6ToIPv6 的转发规则PS> netsh interface portproxy show v6tov6# 仅显示 IPv4ToIPv6 的转发规则PS> netsh interface portproxy show v4tov6# 仅显示 IPv6ToIPv4 的转发规则PS> netsh interface portproxy show v6tov4 【删除端口转发规则】 12345# 如果在创建端口转发规则的时候有 listenaddress ,在删除的时候需要加上 listenaddressPS> netsh interface portproxy delete v4tov4 listenaddress=192.168.1.100 listenport=33890# 如果在创建端口转发规则的时候没有 listenaddress ,在删除的时候不用加 listenaddressPS> netsh interface portproxy delete v4tov4 listenport=33890","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2022-01","slug":"2022-01","permalink":"//blog.solutionx.top/tags/2022-01/"},{"name":"Windows","slug":"Windows","permalink":"//blog.solutionx.top/tags/Windows/"},{"name":"网络","slug":"网络","permalink":"//blog.solutionx.top/tags/网络/"}]},{"title":"Consul安装配置","date":"2021-12-31T16:00:00.000Z","path":"2022/01/01/diary-220101-01/","text":"本教程介绍在Windows、CentOS以及Docker下安装配置Consul。 【Windows下安装】 1、下载安装包 https://www.consul.io/downloads.html 2、复制consul.exe到指定目录,如:D:\\Consul\\consul.exe 3、以管理员身份启动CMD,执行以下命令 1sc.exe create \"Consul\" binPath=\"D:\\Consul\\consul.exe agent -server -ui -bootstrap-expect 1 -client=0.0.0.0 -config-dir=D:\\Consul\\config -data-dir=D:\\Consul\\data -bind=127.0.0.1\" 或以管理员身份启动Powershell,执行以下命令 1PS> New-Service -Name \"ConsulService\" -DisplayName \"Consul Service\" -BinaryPathName \"D:\\Consul\\consul.exe agent -server -ui -bootstrap-expect 1 -client=0.0.0.0 -config-dir=D:\\Consul\\config -data-dir=D:\\Consul\\data -bind=127.0.0.1\" 【CentOS下安装】 123$ sudo yum install -y yum-utils$ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo$ sudo yum -y install consul 【Docker启动】 12345678# 默认拉取latest$ docker pull consul# 拉取指定版本$ docker pull consul:1.6.1# 安装并运行$ docker run -d -p 8500:8500 --restart=always --name=consul consul:latest agent -server -bootstrap -ui -node=1 -client='0.0.0.0' 【参数说明】 ● agent: 表示启动 Agent 进程。 ● server:表示启动 Consul Server 模式 ● client:表示启动 Consul Cilent 模式。 ● bootstrap:表示这个节点是 Server-Leader ,每个数据中心只能运行一台服务器。技术角度上讲 Leader 是通过 Raft 算法选举的,但是集群第一次启动时需要一个引导 Leader,在引导群集后,建议不要使用此标志。 ● ui:表示启动 Web UI 管理器,默认开放端口 8500,所以上面使用 Docker 命令把 8500 端口对外开放。 ● node:节点的名称,集群中必须是唯一的,默认是该节点的主机名。 ● client:consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果你要对外提供服务改成0.0.0.0 ● join:表示加入到某一个集群中去。 如:-json=192.168.0.11。 【查看版本】 1$ consul -v 【运行agent】 consul安装之后,代理必须运行。代理可以在服务器或客户端模式下运行。 1234567891011# 下面以开发模式启动consul代理$ consul agent -dev# 通过members命令查看集群成员$ consul members #members命令输出基于gossip协议,并最终一致。(在任何时候,当地代理所看到的可能与服务器上的状态不完全一致)# 通过HTTP API查看集群成员$ curl localhost:8500/v1/catalog/nodes# 通过DNS接口查询节点$ dig @127.0.0.1 -p 8600 localhost.localdomain.node.consul. 【注册服务】 1、服务定义 将consul的配置文件放到一个文件夹 1$ mkdir /etc/consul.d 定义一个名为web的服务在端口80上运行,给一个标签,可以作为查询服务的附加方式 1$ echo '{\"service\": {\"name\": \"web\", \"tags\": [\"rails\"], \"port\": 80}}' | sudo tee /etc/consul.d/web.json 指定配置目录重启代理程序 1$ consul agent -dev -config-dir=/etc/consul.d 以上命令执行后可以在日志中看到类似 [DEBUG] agent: Service “web” in sync 的项, 证明代理程序从配置文件加载了服务定义,并成功将其注册到服务目录中。 2、查询服务 代理启动后,可用DNS或HTTP API查询服务 对于DNS API,服务的DNS名称为 NAME.service.consul。(NAME为服务的名称,所有DNS名称始终在consul命名空间中) 1$ dig @127.0.0.1 -p 8600 web.service.consul; 使用DNS API检索整个地址/端口对作为SRV记录 1$ dig @127.0.0.1 -p 8600 web.service.consul SRV; SRV记录表示web服务正在80端口运行,并且存在于节点localhost.localdomain.node.dc1.consul 除此之外,还能用DNS API按标签查询。格式为:TAG.NAME.service.consul 1$ dig @127.0.0.1 -p 8600 rails.web.service.consul; 对于HTTP API,可以使用curl来查询 1$ curl http://localhost:8500/v1/catalog/service/web 目录API提供了托管给定服务器的所有节点 以下命令只查找健康的实例 1$ curl 'http://localhost:8500/v1/health/service/web?passing' 3、更新服务 服务定义可以通过更改配置文件并向代理发送SIGHUP来更新。(此操作不会出现任何停机或无法提供服务查询的情况) 除了改配置文件外,还可通过HTTP API动态添加、删除和修改服务 【consul集群】 1、启用代理 -dev参数可以快速设置一个开发服务器,但不足以在集群环境中使用。 集群装哪个每个节点都必须有唯一的名称。(consul默认使用机器的主机名,可用-node参数手动覆盖) -bind参数可用于侦听地址,该地址可被集群中所有节点访问。 第一个节点将作为这个集群的唯一服务器,可以用-server参数来指定。 -bootstrap-expect参数用于向consul服务器提示我们期望加入的其他服务器节点数。用于延迟复制日志的引导,直至预期数量的服务器成功加入。 -enable_script_checks参数设置为true时,可以启用执行外部脚本的运行状况检查 参考: 1、https://blog.csdn.net/qq_38565742/article/details/82015744 2、https://zhuanlan.zhihu.com/p/361869606 3、https://www.cnblogs.com/summerday152/p/14013439.html","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2022-01","slug":"2022-01","permalink":"//blog.solutionx.top/tags/2022-01/"},{"name":"Windows","slug":"Windows","permalink":"//blog.solutionx.top/tags/Windows/"},{"name":"CentOS","slug":"CentOS","permalink":"//blog.solutionx.top/tags/CentOS/"},{"name":"Consul","slug":"Consul","permalink":"//blog.solutionx.top/tags/Consul/"}]},{"title":"Linux下离线安装docker及docker-compose","date":"2021-11-30T16:00:00.000Z","path":"2021/12/01/diary-211201-01/","text":"本教程介绍在Linux下离线安装docker及docker-compose。 【Linux下离线安装docker】 1、准备docker离线包 下载地址:https://download.docker.com/linux/static/stable/x86_64/ 2、准备docker.service文件,内容如下 123456789101112131415161718192021222324252627282930313233[Unit]Description=Docker Application Container EngineDocumentation=https://docs.docker.comAfter=network-online.target firewalld.serviceWants=network-online.target[Service]Type=notify# the default is not to use systemd for cgroups because the delegate issues still# exists and systemd currently does not support the cgroup feature set required# for containers run by dockerExecStart=/usr/bin/dockerdExecReload=/bin/kill -s HUP $MAINPID# Having non-zero Limit*s causes performance problems due to accounting overhead# in the kernel. We recommend using cgroups to do container-local accounting.LimitNOFILE=infinityLimitNPROC=infinityLimitCORE=infinity# Uncomment TasksMax if your systemd version supports it.# Only systemd 226 and above support this version.#TasksMax=infinityTimeoutStartSec=0# set delegate yes so that systemd does not reset the cgroups of docker containersDelegate=yes# kill only the docker process, not all processes in the cgroupKillMode=process# restart the docker process if it exits prematurelyRestart=on-failureStartLimitBurst=3StartLimitInterval=60s[Install]WantedBy=multi-user.target 3、准备安装脚本(install.sh),内容如下: 1234567891011121314151617#!/bin/shecho '解压tar包...'tar -xvf $1echo '将docker目录移到/usr/bin目录下...'cp docker/* /usr/bin/echo '将docker.service 移到/etc/systemd/system/ 目录...'cp docker.service /etc/systemd/system/echo '添加文件权限...'chmod +x /etc/systemd/system/docker.serviceecho '重新加载配置文件...'systemctl daemon-reloadecho '启动docker...'systemctl start dockerecho '设置开机自启...'systemctl enable docker.serviceecho 'docker安装成功...'docker -v 4、准备卸载脚本(uninstall.sh),内容如下: 12345678#!/bin/shecho '删除docker.service...'rm -f /etc/systemd/system/docker.serviceecho '删除docker文件...'rm -rf /usr/bin/docker*echo '重新加载配置文件'systemctl daemon-reloadecho '卸载成功...' 5、安装 1234# 将tgz文件、docker.service、install.sh、uninstall.sh放到同一个目录# 给脚本赋予可执行权限chmod +x *.sh 6、执行安装脚本进行 1./install.sh docker-***.tgz 7、验证 1docker -v 8、卸载 12# 直接执行 uninstall.sh 即可./uninstall.sh 参考: https://www.jb51.net/article/167103.htm 【将当前用户加入docker组】 1、新建一个docker组,如果已存在则不需要新建 1sudo groupadd docker 查看已存在的组 1cat /etc/group 2、将当前用户加入docker组 1sudo gpasswd -a ${USER} docker 3、重启docker 1sudo systemctl restart docker 4、切换当前会话到新的group 1newgrp - docker 【Linux下离线安装docker-compose】 1、到docker-compose的GitHub发布页面下载对应版本的docker-compose(二进制文件) 下载地址:https://github.com/docker/compose/releases 2、将可执行重命名为 docker-compose 3、将可执行文件移动到指定目录 1sudo mv docker-compose /usr/local/bin 4、赋予可执行权限 1sudo chmod +x /usr/local/bin/docker-compose 5、创建软链接 1sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose 6、测试安装是否成功 1docker-compose --version 注意: 对于alpine需要以下依赖包: py-pip,python-dev,libffi-dev,openssl-dev,gcc,libc-dev,和 make 参考: https://blog.csdn.net/qq_22041375/article/details/106922563","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2021-12","slug":"2021-12","permalink":"//blog.solutionx.top/tags/2021-12/"},{"name":"Linux","slug":"Linux","permalink":"//blog.solutionx.top/tags/Linux/"},{"name":"Docker","slug":"Docker","permalink":"//blog.solutionx.top/tags/Docker/"},{"name":"Docker-Compose","slug":"Docker-Compose","permalink":"//blog.solutionx.top/tags/Docker-Compose/"}]},{"title":"新起点,新征程","date":"2021-11-07T16:00:00.000Z","path":"2021/11/08/diary-211108-01/","text":"新的起点,新的征程。 作为一个“老”.NET人,自接触这一开发框架至今已有五六个年头。 从婴儿学步般的控制台程序到有头有脸的Winform窗体程序,再到稍稍高级一点的WPF窗体程序,直至最近,我把当前阶段的落脚点放在了基于ASP.NET Core的WebAPI开发上。 或许起点不是新的,但是征程确是崭新的。 这对于我来说是一个新的挑战,是我很久以前就想着要进入却一直没有机会进入的一个未知领域。 为此我自学了ASP.NET Core,还学习了很多微服务相关的知识。并且也有了自己的小项目。 这一切,或许源自我对.NET的兴趣,又或许不单单只是.NET,更是对编程开发的热爱。 或许大多数人都觉得我不应该拘泥于.NET,Java、Python的生态也大有用武之地。但是,有些工具用上手了就没有办法舍弃。或许,这就是我的执着吧。 (VS YYDS!)","tags":[{"name":"随笔","slug":"随笔","permalink":"//blog.solutionx.top/tags/随笔/"},{"name":"2021-11","slug":"2021-11","permalink":"//blog.solutionx.top/tags/2021-11/"},{"name":".NET","slug":"NET","permalink":"//blog.solutionx.top/tags/NET/"},{"name":"Work","slug":"Work","permalink":"//blog.solutionx.top/tags/Work/"}]},{"title":"ASP.NET 6 应用Docker部署","date":"2021-09-16T16:00:00.000Z","path":"2021/09/17/diary-210917-01/","text":"Linux环境简单部署ASP.NET 6应用到Docker容器 首先创建一个用于存放源码的文件夹。 12$ mkdir test$ cd test 然后在当前文件夹中初始化一个ASP.NET 6的测试项目。(如果不知道关键字,可通过输出dotnet new -l查询所有可以创建的项目的类型) 1$ dotnet new web 接下来执行dotnet run查看效果 1$ dotnet run helloworld应用创建完成后,开始为其制作Dockerfile。 该程序的运行环境为ASP.NET Core 6,本教程采用微软官方的alpine3.14镜像,其Dockerfile的内容如下所示。如有其它需求,也可以到Docker Hub 微软官方发布的镜像库中获取。 123456789101112131415161718ARG REPO=mcr.microsoft.com/dotnet/runtimeFROM $REPO:6.0.0-rc.2-alpine3.14-arm32v7# .NET globalization APIs will use invariant mode by default because DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true is set# by the base runtime-deps image. See https://aka.ms/dotnet-globalization-alpine-containers for more information.ENV \\ # ASP.NET Core version ASPNET_VERSION=6.0.0-rc.2.21480.10 \\ # Set the default console formatter to JSON Logging__Console__FormatterName=Json# Install ASP.NET CoreRUN wget -O aspnetcore.tar.gz https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$ASPNET_VERSION/aspnetcore-runtime-$ASPNET_VERSION-linux-musl-arm.tar.gz \\ && aspnetcore_sha512='8fde5df285a852cfa37bc026983357cac8fdc77c5f9e2fdb242f1c967375e43bac9cc6f9ac6caa684a9bb3aab94c0f98ff93e8e9650399b44917c9e51d8e545c' \\ && echo \"$aspnetcore_sha512 aspnetcore.tar.gz\" | sha512sum -c - \\ && tar -ozxf aspnetcore.tar.gz -C /usr/share/dotnet ./shared/Microsoft.AspNetCore.App \\ && rm aspnetcore.tar.gz 本演示项目对原始Dockerfile文件做了些许修改,完整Dockerfile内容如下: 1234567891011121314151617181920212223242526272829303132ARG REPO=mcr.microsoft.com/dotnet/runtimeFROM $REPO:6.0.0-rc.1-alpine3.14-amd64# .NET globalization APIs will use invariant mode by default because DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true is set# by the base runtime-deps image. See https://aka.ms/dotnet-globalization-alpine-containers for more information.ENV \\ # ASP.NET Core version ASPNET_VERSION=6.0.0-rc.1.21452.15 \\ # Set the default console formatter to JSON Logging__Console__FormatterName=Json# Install ASP.NET CoreRUN wget -O aspnetcore.tar.gz https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$ASPNET_VERSION/aspnetcore-runtime-$ASPNET_VERSION-linux-musl-x64.tar.gz \\ && aspnetcore_sha512='c34e939169faafd9ffc2189695f7e5e134170b131850606c781b80801aab3f8b73a6c4bdb0dbe9b104b065e0585339deec97da367662ed0cf1f0e7dcd009cee1' \\ && echo \"$aspnetcore_sha512 aspnetcore.tar.gz\" | sha512sum -c - \\ && tar -ozxf aspnetcore.tar.gz -C /usr/share/dotnet ./shared/Microsoft.AspNetCore.App \\ && rm aspnetcore.tar.gz \\ && mkdir /lib64 \\ && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2# Work dirWORKDIR /app# Copy project files to work dirCOPY ./ ./# Set expose portEXPOSE 80/tcp# Application entry pointENTRYPOINT [\"dotnet\", \"webhello.dll\"] 演示项目在调试过程中,提示某个依赖项不存在,因此本人在原始Dockerfile的基础上增加了一个指向替代依赖项的软链接。 除此之外,我还设定了程序的工作路径,并且追加了将源码复制到docker镜像中的命令,设置了容器的监听端口(80),程序的入口(ENTRYPOINT)。 完整的Dockerfile内容如下所示,仅供参考: 1234567891011121314151617181920212223242526272829303132ARG REPO=mcr.microsoft.com/dotnet/runtimeFROM $REPO:6.0.0-rc.1-alpine3.14-amd64# .NET globalization APIs will use invariant mode by default because DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true is set# by the base runtime-deps image. See https://aka.ms/dotnet-globalization-alpine-containers for more information.ENV \\ # ASP.NET Core version ASPNET_VERSION=6.0.0-rc.1.21452.15 \\ # Set the default console formatter to JSON Logging__Console__FormatterName=Json# Install ASP.NET CoreRUN wget -O aspnetcore.tar.gz https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$ASPNET_VERSION/aspnetcore-runtime-$ASPNET_VERSION-linux-musl-x64.tar.gz \\ && aspnetcore_sha512='c34e939169faafd9ffc2189695f7e5e134170b131850606c781b80801aab3f8b73a6c4bdb0dbe9b104b065e0585339deec97da367662ed0cf1f0e7dcd009cee1' \\ && echo \"$aspnetcore_sha512 aspnetcore.tar.gz\" | sha512sum -c - \\ && tar -ozxf aspnetcore.tar.gz -C /usr/share/dotnet ./shared/Microsoft.AspNetCore.App \\ && rm aspnetcore.tar.gz \\ && mkdir /lib64 \\ && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2# Work dirWORKDIR /app# Copy project files to work dirCOPY ./ ./# Set expose portEXPOSE 80/tcp# Application entry pointENTRYPOINT [\"dotnet\", \"webhello.dll\"] Dockerfile配置完成后就可以进行镜像打包和发布了。Docker镜像打包发布参考请其他教程。 以上演示项目已上传到Github:https://github.com/XVCoder/webhello ,Docker镜像(xv132/dothello)也已上传到Docker Hub,有需要者自取。","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"Docker","slug":"Docker","permalink":"//blog.solutionx.top/tags/Docker/"},{"name":"2021-09","slug":"2021-09","permalink":"//blog.solutionx.top/tags/2021-09/"},{"name":"ASP.NET 6","slug":"ASP-NET-6","permalink":"//blog.solutionx.top/tags/ASP-NET-6/"}]},{"title":"WPF触发器Trigger的使用","date":"2020-06-10T16:00:00.000Z","path":"2020/06/11/diary-200611-01/","text":"用于监听一个或多个属性的值,当该属性值发生变化时,执行指定的动作。 单条件触发 [示例] [XAML示例代码] 123456789101112131415161718192021<!--关闭按钮--><Label Content=\"×\" Cursor=\"Hand\" FontSize=\"20\" FontWeight=\"Bold\" HorizontalAlignment=\"Left\" Margin=\"450,1,0,0\" VerticalAlignment=\"Top\"> <!--鼠标移入时更改前景色--> <Label.Resources> <Style TargetType=\"{x:Type Label}\"> <Setter Property=\"Foreground\" Value=\"#00000000\"/> <Style.Triggers> <Trigger Property=\"IsMouseOver\" Value=\"True\"> <Setter Property=\"Foreground\" Value=\"#c8ffffff\"/> </Trigger> </Style.Triggers> </Style> </Label.Resources></Label> 多条件触发 [XAML示例代码] 123456789<MultiTrigger> <MultiTrigger.Conditions> <Condition Property=\"Text\" SourceName=\"test\" Value=\"1\"/> <Condition Property=\"IsSelected\" Value=\"True\"/> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property=\"Background\" Value=\"Yellow\"/> </MultiTrigger.Setters></MultiTrigger>","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2020-06","slug":"2020-06","permalink":"//blog.solutionx.top/tags/2020-06/"},{"name":"WPF","slug":"WPF","permalink":"//blog.solutionx.top/tags/WPF/"},{"name":"Trigger","slug":"Trigger","permalink":"//blog.solutionx.top/tags/Trigger/"}]},{"title":"C#实现简单的程序日志输出","date":"2020-04-06T16:00:00.000Z","path":"2020/04/07/diary-200407-01/","text":"程序调试的时候好好的,发布之后老是出问题,原因还找不到?那得好好考虑将异常报错信息输出到日志文件了! 话不多说,直接上代码 ↓↓↓ 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889using System;using System.IO;namespace [命名空间]{ public class LogHelper { //定义日志输出位置 private readonly static string rootDir = Environment.CurrentDirectory.ToString() + \"\\\\SystemLogs\"; /// <summary> /// 输出自定义日志 /// </summary> /// <param name=\"msg\"></param> public static void WriteLog(string msg) { var logDir = rootDir + $\"\\\\{DateTime.Now:yyyyMM}\"; if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); string filePath = logDir + $\"\\\\syslog_{DateTime.Now:yyyyMMdd}.log\"; StreamWriter tw = null; try { if (!File.Exists(filePath)) { File.Create(filePath).Close(); } using (tw = File.AppendText(filePath)) {#if DEBUG tw.WriteLine($\"【{DateTime.Now:yyyy/MM/dd HH:mm:ss}】 (Debug) >> {msg}\");#else tw.WriteLine($\"【{DateTime.Now:yyyy/MM/dd HH:mm:ss}】 >> \" + msg);#endif tw.Flush(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { if (tw != null) tw.Close(); } } /// <summary> /// 输出异常信息 /// </summary> /// <param name=\"exMsg\">异常信息</param> public static void WriteLog(Exception exMsg) { var logDir = rootDir + $\"\\\\{DateTime.Now:yyyyMM}\"; if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); string filePath = logDir + $\"\\\\syslog_{DateTime.Now:yyyyMMdd}.log\"; StreamWriter tw = null; try { if (!File.Exists(filePath)) { File.Create(filePath).Close(); } using (tw = File.AppendText(filePath)) {#if DEBUG tw.WriteLine($\"【{DateTime.Now:yyyy/MM/dd HH:mm:ss}】 (Debug) >> {exMsg.Message}\\n{exMsg.InnerException}\");#else tw.WriteLine($\"【{DateTime.Now:yyyy/MM/dd HH:mm:ss}】 >> \" + msg);#endif tw.Flush(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { if (tw != null) tw.Close(); } } }} 其中「#if DEBUG"」用于标志调试模式输出的日志。 整个方法逻辑很简单,就是将需要输出的日志或者异常信息按照自己定义的格式追加到指定的文件中。","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2020-04","slug":"2020-04","permalink":"//blog.solutionx.top/tags/2020-04/"},{"name":"日志输出","slug":"日志输出","permalink":"//blog.solutionx.top/tags/日志输出/"}]},{"title":"C#避免输出伪随机数","date":"2020-03-28T16:00:00.000Z","path":"2020/03/29/diary-200329-01/","text":"如下所示,C#的Random是用于生成随机数的类,但是程序在每次执行时输出的随机数都是一样的,也称之为“伪随机数”。 123456789101112131415161718static void Main(string[] args){ Random random = new Random(); for (int i = 0; i < 10; i++) { Console.Write(random.Next(0, 10) + ""); } Console.WriteLine("\\n"); Random newRandom = new Random(); for (int i = 0; i < 10; i++) { Console.Write(newRandom.Next(0, 10) + ""); } Console.ReadKey();} 执行结果如下: Random还有一个构造函数可以传入种子值用于计算下一个随机数的值,不过这个值最好也是随机变化的,这个种子值我们可以使用GUID的哈希值作为参数来传入。这样生成的随机数也就是真随机数了。 123456789101112131415161718static void Main(string[] args){ Random random = new Random(Guid.NewGuid().GetHashCode()); //使用Guid的哈希值做种子 for (int i = 0; i < 10; i++) { Console.Write(random.Next(0, 10) + ""); } Console.WriteLine("\\n"); Random newRandom = new Random(Guid.NewGuid().GetHashCode()); //使用Guid的哈希值做种子 for (int i = 0; i < 10; i++) { Console.Write(newRandom.Next(0, 10) + ""); } Console.ReadKey();} 执行结果如下:","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2020-03","slug":"2020-03","permalink":"//blog.solutionx.top/tags/2020-03/"},{"name":"随机数生成","slug":"随机数生成","permalink":"//blog.solutionx.top/tags/随机数生成/"}]},{"title":"【骚年制杖否】","date":"2019-08-12T16:00:00.000Z","path":"2019/08/13/diary-190812-01/","text":"壁纸分享","tags":[{"name":"2019-08","slug":"2019-08","permalink":"//blog.solutionx.top/tags/2019-08/"},{"name":"壁纸分享","slug":"壁纸分享","permalink":"//blog.solutionx.top/tags/壁纸分享/"},{"name":"手机壁纸","slug":"手机壁纸","permalink":"//blog.solutionx.top/tags/手机壁纸/"}]},{"title":"Linq取实体类集合中的某个属性转化为数组","date":"2018-11-09T02:00:00.000Z","path":"2018/11/09/diary-181109-01/","text":"123456789101112class Demo{ public int Id { get; set; } public string Name { get; set; }}static void Main(string[] args){ List<Demo> data = new List<Demo>(); string[] names = (from x in data select x.Name).ToArray(); //或者 string[] names = data.Select(x=>x.Name).ToArray();}","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-11","slug":"2018-11","permalink":"//blog.solutionx.top/tags/2018-11/"},{"name":"Linq","slug":"Linq","permalink":"//blog.solutionx.top/tags/Linq/"}]},{"title":"修改 Visual Studio 2013 C#类的模板文件","date":"2018-09-16T05:59:00.000Z","path":"2018/09/16/diary-180916-01/","text":"【说明:本文参考并引用了rootmn的博客文章】 通过修改 Visual Studio 2013 C#类的模板文件可实现在创建类的同时给类添加类说明以及版权信息等注释文本(其他语言同样适用) 更改如下目录下的模板文件即可实现: C:\\Program Files\\Microsoft Visual Studio 12.0\\Common7\\IDE\\ItemTemplates\\CSharp\\Code\\2052\\Class\\Class.cs (默认路径) 如VS安装路径并非上述路径,可尝试:[安装目录]\\Common7\\IDE\\ItemTemplates\\CSharp\\Code\\2052\\Class\\Class.cs 将模板文件 “Class.cs” 用记事本打开 在需要添加注释的位置加上自己的文本即可,以下代码供参考: 12345678910111213141516171819202122232425//=====================================================//Copyright (C) 2013-$year$ rootmn LI//All rights reserved//CLR Version: $clrversion$//Item Name: $itemname$//Machine Name: $machinename$//namespace: $rootnamespace$//CS File Name: $safeitemname$//User Name: $username$//Create Year: $year$//Create Time: $time$//Author's Blog: http://www.solutionx.top //======================================================using System;using System.Collections.Generic;$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;$endif$using System.Text;$if$ ($targetframeworkversion$ >= 4.5)using System.Threading.Tasks;$endif$namespace $rootnamespace${ class $safeitemrootname$ { }}","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-9","slug":"2018-9","permalink":"//blog.solutionx.top/tags/2018-9/"},{"name":"Visual Studio","slug":"Visual-Studio","permalink":"//blog.solutionx.top/tags/Visual-Studio/"}]},{"title":"同一个Winform程序在同一时间只允许运行一个","date":"2018-09-08T01:59:00.000Z","path":"2018/09/08/diary-180908-01/","text":"在program类的main函数中添加以下代码 12345678bool CreateNew;System.Threading.Mutex mutex = new System.Threading.Mutex(false, \"程序的名称\", out CreateNew);if (!CreateNew){ MessageBox.Show(\"程序正在运行中!\", \"提示\"); Application.Exit(); return;}","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"WinForm","slug":"WinForm","permalink":"//blog.solutionx.top/tags/WinForm/"},{"name":"2018-9","slug":"2018-9","permalink":"//blog.solutionx.top/tags/2018-9/"}]},{"title":"Winform解决界面重绘闪烁的问题","date":"2018-08-16T08:55:00.000Z","path":"2018/08/16/diary-180816-01/","text":"在窗体或用户控件中重写CreateParams 123456789protected override CreateParams CreateParams{ get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x02000000; return cp; }}","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-8","slug":"2018-8","permalink":"//blog.solutionx.top/tags/2018-8/"},{"name":"WinForm","slug":"WinForm","permalink":"//blog.solutionx.top/tags/WinForm/"}]},{"title":"SQL中JOIN的作用","date":"2018-06-25T01:35:00.000Z","path":"2018/06/25/diary-180625-01/","text":"首先简单介绍一下JOIN语法 table1:左表;table2:右表。 JOIN 按照功能大致分为如下三类: 1、INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录。 2、LEFT JOIN(左连接):取得左表(table1)完全记录,即使右表(table2)并无对应匹配记录。 3、RIGHT JOIN(右连接):与 LEFT JOIN 相反,取得右表(table2)完全记录,即使左表(table1)并无匹配对应记录。 (注意:mysql不支持Full join,不过可以通过UNION 关键字来合并 LEFT JOIN 与 RIGHT JOIN来模拟FULL join.) 接下来给出一个列子用于解释下面几种分类。如下两个表(A,B) 12345678910mysql> select A.id,A.name,B.name from A,B where A.id=B.id;+----+-----------+-------------+| id | name | name |+----+-----------+-------------+| 1 | Pirate | Rutabaga || 2 | Monkey | Pirate || 3 | Ninja | Darth Vader || 4 | Spaghetti | Ninja |+----+-----------+-------------+4 rows in set (0.00 sec) INNER JOIN 内连接,也叫等值连接,inner join产生同时符合A和B的一组数据。 1234567mysql> select * from A inner join B on A.name = B.name;+----+--------+----+--------+| id | name | id | name |+----+--------+----+--------+| 1 | Pirate | 2 | Pirate || 3 | Ninja | 4 | Ninja |+----+--------+----+--------+ LEFT JOIN 123456789101112mysql> select * from A left join B on A.name = B.name;#或者:select * from A left outer join B on A.name = B.name;+----+-----------+------+--------+| id | name | id | name |+----+-----------+------+--------+| 1 | Pirate | 2 | Pirate || 2 | Monkey | NULL | NULL || 3 | Ninja | 4 | Ninja || 4 | Spaghetti | NULL | NULL |+----+-----------+------+--------+4 rows in set (0.00 sec) left join,(或left outer join:在Mysql中两者等价,推荐使用left join.)左连接从左表(A)产生一套完整的记录,与匹配的记录(右表(B)) .如果没有匹配,右侧将包含null。 如果想只从左表(A)中产生一套记录,但不包含右表(B)的记录,可以通过设置where语句来执行,如下: 12345678mysql> select * from A left join B on A.name=B.name where A.id is null or B.id is null;+----+-----------+------+------+| id | name | id | name |+----+-----------+------+------+| 2 | Monkey | NULL | NULL || 4 | Spaghetti | NULL | NULL |+----+-----------+------+------+2 rows in set (0.00 sec) 同理,还可以模拟inner join. 如下: 12345678mysql> select * from A left join B on A.name=B.name where A.id is not null and B.id is not null;+----+--------+------+--------+| id | name | id | name |+----+--------+------+--------+| 1 | Pirate | 2 | Pirate || 3 | Ninja | 4 | Ninja |+----+--------+------+--------+2 rows in set (0.00 sec) 求差集: 根据上面的例子可以求差集,如下: 1234567891011121314SELECT * FROM A LEFT JOIN B ON A.name = B.nameWHERE B.id IS NULLunionSELECT * FROM A right JOIN B ON A.name = B.nameWHERE A.id IS NULL;# 结果+------+-----------+------+-------------+| id | name | id | name |+------+-----------+------+-------------+| 2 | Monkey | NULL | NULL || 4 | Spaghetti | NULL | NULL || NULL | NULL | 1 | Rutabaga || NULL | NULL | 3 | Darth Vader |+------+-----------+------+-------------+ 再举个简单的栗子: 有两表:a,b 其中a表中的字段为a_id,b中的字段为b_id,b_no;记录如下: 1234567891011121314151617a:+------+| a_id |+------+| 2 || 3 || 5 |+------+b:+------+------+| b_id | b_no |+------+------+| 1 | 3 || 2 | 5 || 3 | 6 |+------+------+ 执行语句:select a.a_id,b.b_no from a left join b on (a.a_id=b.b_id and b.b_id>5) 效果如下: 1234567+------+------+| a_id | b_no |+------+------+| 5 | 6 || 2 | null || 3 | null |+------+------+ Right join 12345678910mysql> select * from A right join B on A.name = B.name;+------+--------+----+-------------+| id | name | id | name |+------+--------+----+-------------+| NULL | NULL | 1 | Rutabaga || 1 | Pirate | 2 | Pirate || NULL | NULL | 3 | Darth Vader || 3 | Ninja | 4 | Ninja |+------+--------+----+-------------+4 rows in set (0.00 sec) 同left join。 Cross join cross join:交叉连接,得到的结果是两个表的乘积,即笛卡尔积 笛卡尔(Descartes)乘积又叫直积。假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。可以扩展到多个集合的情况。类似的例子有,如果A表示某学校学生的集合,B表示该学校所有课程的集合,则A与B的笛卡尔积表示所有可能的选课情况。 1234567891011121314151617181920212223242526mysql> select * from A cross join B;+----+-----------+----+-------------+| id | name | id | name |+----+-----------+----+-------------+| 1 | Pirate | 1 | Rutabaga || 2 | Monkey | 1 | Rutabaga || 3 | Ninja | 1 | Rutabaga || 4 | Spaghetti | 1 | Rutabaga || 1 | Pirate | 2 | Pirate || 2 | Monkey | 2 | Pirate || 3 | Ninja | 2 | Pirate || 4 | Spaghetti | 2 | Pirate || 1 | Pirate | 3 | Darth Vader || 2 | Monkey | 3 | Darth Vader || 3 | Ninja | 3 | Darth Vader || 4 | Spaghetti | 3 | Darth Vader || 1 | Pirate | 4 | Ninja || 2 | Monkey | 4 | Ninja || 3 | Ninja | 4 | Ninja || 4 | Spaghetti | 4 | Ninja |+----+-----------+----+-------------+16 rows in set (0.00 sec)#再执行:mysql> select * from A inner join B; 试一试#在执行mysql> select * from A cross join B on A.name = B.name; 试一试 实际上,在 MySQL 中(仅限于 MySQL) CROSS JOIN 与 INNER JOIN 的表现是一样的,在不指定 ON 条件得到的结果都是笛卡尔积,反之取得两个表完全匹配的结果。 INNER JOIN 与 CROSS JOIN 可以省略 INNER 或 CROSS 关键字,因此下面的 SQL 效果是一样的: 123... FROM table1 INNER JOIN table2... FROM table1 CROSS JOIN table2... FROM table1 JOIN table2 Full join 1234567891011121314mysql> select * from A left join B on B.name = A.name -> union -> select * from A right join B on B.name = A.name;+------+-----------+------+-------------+| id | name | id | name |+------+-----------+------+-------------+| 1 | Pirate | 2 | Pirate || 2 | Monkey | NULL | NULL || 3 | Ninja | 4 | Ninja || 4 | Spaghetti | NULL | NULL || NULL | NULL | 1 | Rutabaga || NULL | NULL | 3 | Darth Vader |+------+-----------+------+-------------+6 rows in set (0.00 sec) 全连接产生的所有记录(双方匹配记录)在表A和表B。如果没有匹配,则对面将包含null。 性能分析等详细内容参考 → Mysql Join语法解析与性能分析 - BeginMan - 博客园","tags":[{"name":"2018-6","slug":"2018-6","permalink":"//blog.solutionx.top/tags/2018-6/"},{"name":"SQL","slug":"SQL","permalink":"//blog.solutionx.top/tags/SQL/"}]},{"title":"数据契约(DataContract)","date":"2018-06-12T03:35:00.000Z","path":"2018/06/12/diary-180612-01/","text":"服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。 一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送,如下所示。 123456[DataContract]public class UserInfo{ //….} 只有声明为DataContract的类型的对象可以被传送,且只有成员属性会被传递,成员方法不会被传递。WCF对声明为DataContract的类型提供更加细节的控制,可以把一个成员排除在序列化范围以外,也就是说,客户端程序不会获得被排除在外的成员的任何信息,包括定义和数据。默认情况下,所有的成员属性都被排除在外,因此需要把每一个要传送的成员声明为DataMember,如下所示。 123456789101112131415161718192021222324252627[DataContract]public class UserInfo{ [DataMember] public string UserName { get; set; } [DataMember] public int Age { get; set; } [DataMember] public string Location { get; set; } public string Zodiac { get; set; }} 上面这段代码把UserInfo类声明为DataContract,将UserName、Age、Location这3个属性声明为DataMember(数据成员)。Zodiac成员没有被声明为DataMember,因此在交换数据时,不会传输Zodiac的任何信息。 DataContract也支持Name/Namespace属性,如同ServiceContract,Name和Namespace可以自定义名称和命名空间,客户端将使用自定义的名称和命名空间对DataContract类型进行访问。 声明为DataMember的成员也可以自定义客户端可见的名称,例如: 123456789101112[DataMember(Name=\"Name\")]public string UserName{ get; set;}[DataMember(Name=\"Age\")]public int UserAge{ get; set;} 除了Name和Namespace以外,DataMember还有以下参数,它们的含义分别如下。 (1)IsRequired:值为true时,要求序列化引擎检查对象是否存在该值;若无,则会有异常抛出。 (2)Order:bool类型值,值为true时,序列化和反序列化过程将会按成员定义的顺序进行,这对依赖于成员位置的反序列化过程无比重要。 (3)EmitDefaultvalue:为成员属性设置一个默认值。 一般情况下,将类型声明为DataContract就可以满足传送的需求了,不过特殊情况是难以避免的,这时就需要对要传送的SOAP消息进行更加精确的控制,MessageContract可以满足这种需求。 把一个类型声明为MessageContract,意味着它可以被序列化为SOAP消息,可以声明类型的成员为SOAP消息的各个部分,如Header、Body等,如下所示。 123456789101112131415161718192021222324[MessageContract]public class UserMessage{ private string user = String.Empty; private string authKey = String.Empty; [MessageBodyMember( Name = \"UserName\", Namespace = \"http://www.wcf.com\")] public string User { get { return user; } set { user = value; } } [MessageHeader( Name = \"AuthKey\", Namespace = \"http://www.wcf.com\", MustUnderstand = true )] public string AuthKey { get { return authKey; } set { this.authKey = value; } }} User成员被声明为MessageBody(消息体)的一个成员,AuthKey被声明为消息头(MessageHeader)的一个成员。这个类将可以生成如下的SOAP消息。 1234567891011<s:Envelope> <s:Header> <a:Action s:mustUnderstand=\"1\">http://UserMessage/Action</a:Action> <h:AuthKey s:mustUnderstand=\"1\" xmlns:h=\"http://www.wcf.com\">xxxx</h:AuthKey> </s:Header> <s:Body> <UserMessage xmlns=\"Microsoft.WCF.Documentation\"> <User xmlns=\"http://www.wcf.com\">abcd</User> </UserMessage> </s:Body> </s:Envelope> MessageHeader中,MustUnderstand参数表示读取该头的程序必须能够识别头的内容,否则不能继续处理。Name/Namespace的作用与前面的元素相同。另有Relay参数,若为true,头的内容被接收到以后会在响应消息中回发给消息发送端。 转载自 数据契约(DataContract) - 子夜. - 博客园","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-6","slug":"2018-6","permalink":"//blog.solutionx.top/tags/2018-6/"}]},{"title":"几个值得收藏的.NET开源项目","date":"2018-05-29T03:08:00.000Z","path":"2018/05/29/diary-180509-01/","text":"① OpenAuth.NET “最好用的.NET权限工作流框架” 基于经典领域驱动设计的权限管理及快速开发框架,基于Martin Fowler企业级应用开发思想及全新技术组合(Asp.Net MVC、EF、AutoFac、WebAPI、Swagger、Json.Net等),核心模块包括:组织机构、角色用户、权限授权、表单设计、工作流等。它的架构精良易于扩展,是中小企业的首选。","tags":[{"name":"2018-5","slug":"2018-5","permalink":"//blog.solutionx.top/tags/2018-5/"}]},{"title":"WinForm图片拼接","date":"2018-02-20T12:08:00.000Z","path":"2018/02/20/diary-180220-01/","text":"基本思路 在设定好的最终图像框中根据预设坐标值逐个绘制要拼接的图片 举个简单的例子 1、首先,创建一个Bitmap对象img,同时设置好最终图片的大小 2、然后,创建一个Graphics对象g,用于在Bitmap对象中绘制将要拼接的图片 3、根据设定的坐标,在Bitmap对象中相应位置绘制对应的图片,达到图片拼接的效果 4、将最终得到的Bitmap显示在PictureBox1中 关键代码: 1234567Bitmap img = new Bitmap(40, 40);Graphics g = Graphics.FromImage(img);g.DrawImage(Image1, new Point(0, 0));g.DrawImage(Image2, new Point(20, 0));g.DrawImage(Image3, new Point(0, 20));g.DrawImage(Image4, new Point(20, 20));this.pictureBox1.Image = img; 待拼接图片: 拼接后:","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-2","slug":"2018-2","permalink":"//blog.solutionx.top/tags/2018-2/"},{"name":"C#学习笔记","slug":"C-学习笔记","permalink":"//blog.solutionx.top/tags/C-学习笔记/"}]},{"title":"Visual Studio在使用过程中遇到的一些问题","date":"2018-02-19T07:45:00.000Z","path":"2018/02/19/diary-180219-01/","text":"用于记录个人遇到的一些有关Visual Studio使用方面的小问题 自定义控件相关 在创建好自定义控件并运行时,弹出“未能找到程序集“D:\\C#%FBA···%A%B%C3.dll”。请确保路径正确。”异常的解决方法 通常,出现该异常的原因是项目路径名中包含了’C#'字样,将其重命名或是移到其他路径即可解决","tags":[{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"},{"name":"2018-2","slug":"2018-2","permalink":"//blog.solutionx.top/tags/2018-2/"},{"name":"C#学习笔记","slug":"C-学习笔记","permalink":"//blog.solutionx.top/tags/C-学习笔记/"}]},{"title":"【网页书签】","date":"2017-09-05T05:00:00.000Z","path":"2017/09/05/diary-170817-01/","text":"C# 线程间不能调用剪切板的问题 - 编程之家 - CSDN博客 C# 以MDF文件链接数据库 - 隔壁老王的博客 - CSDN博客 Markdown-入门指南及网站整合 - 努力在追梦的路上 - CSDN博客 Untitled - 马克飞象 - 专为印象笔记打造的Markdown编辑器 C# 自定义控件VS用户控件 - 野狼谷 - 博客园 【WinForm】创建自定义控件 - bomo - 博客园 C# 颜色Color与16进制互转 - Net-Spider - 博客园 C# MeasureString测量字符串函数的使用方法 C#教程 脚本之家 C# byte数组与Image的相互转换 - 阿凡卢 - 博客园 C# 将文件转化成字节数组 - 沙耶 - 博客园 C#的自定义滚动条 - 今木。非昔木 - 博客园 NAT路由器“打洞”技术,即P2P通信实现原理(非常详细) - CSDN博客 C# 使用ffmpeg.exe进行音频转换完整demo-xtyga330024 .net 调用 ffmpeg 转换音频文件amr - mp3 - mingxiu_wzdx的日志 - 网易博客 通过改hosts访问wikipedia - CSDN博客 winform实例(3)-利用摄像头进行拍照 - 智博的日常 - 博客园 GDI+ 中发生一般性错误 - 绿水青山 - 博客园","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2017-9","slug":"2017-9","permalink":"//blog.solutionx.top/tags/2017-9/"},{"name":"书签","slug":"书签","permalink":"//blog.solutionx.top/tags/书签/"}]},{"title":"来点纯音提提神吧","date":"2017-08-06T23:47:00.000Z","path":"2017/08/07/diary-170807-01/","text":"提神醒脑的一剂良药 😆 🎵🎶 点击打开网易云歌单","tags":[{"name":"音乐","slug":"音乐","permalink":"//blog.solutionx.top/tags/音乐/"},{"name":"2017-8","slug":"2017-8","permalink":"//blog.solutionx.top/tags/2017-8/"}]},{"title":"【日常随笔】 17-07-25","date":"2017-07-25T15:30:00.000Z","path":"2017/07/25/diary-170725-01/","text":"最近“闲”的时候干了这些事 👇👇👇 1、 云中转聊天程序的登录界面有所优化 用户可以点击登录界面左下角的小按钮通过用户名搜索自己的账号 用以解决忘记账号而无法登录的尴尬 虽然没什么人在用我这个所谓的“云中转聊天程序”聊天就是了😅 2、 由于网易云跟帖前段时间也宣布下线,本站的评论系统改为友言(目前已换成 来必力) 说实话,未备案域名无法使用畅言就已经让人很不爽了 结果稍微差那么一丢丢但还看的过去的网易云跟帖居然宣布下线!? 简直是屋漏偏逢连夜雨,于是只好选用了与主题极不搭调的友言 由于技术不到位,布局不会调,新开的友言评论框简直没法看 体验了一段时间之后,决定放弃友言 现在已更换为来必力 ->自学过程中编写的小程序<-","tags":[{"name":"随笔","slug":"随笔","permalink":"//blog.solutionx.top/tags/随笔/"},{"name":"2017-7","slug":"2017-7","permalink":"//blog.solutionx.top/tags/2017-7/"}]},{"title":"C#创建非矩形窗体","date":"2017-07-24T04:30:00.000Z","path":"2017/07/24/diary-170724-01/","text":"简 介 示例: 1、 C#根据含透明色的图片创建非矩形窗体 2、 鼠标左键控制该窗体的移动 (转载请注明出处) 具体操作 1、 在新建立的窗体中拖入PictureBox控件 2、 设置好PictureBox的图片来源 3、 其属性SizeMode设置为StretchImage(拉伸) 4、 设置窗体背景色透明,在窗体加载事件中添加如下代码: 123this.TransparencyKey = Color.White; //设置当前窗体的默认透明色 this.BackColor = this.TransparencyKey; //设置当前窗体的背景色为透明 this.FormBorderStyle = FormBorderStyle.None; //设置窗体无边框 5、 让鼠标控制窗体的移动(详细代码见后文) 首先创建一个Point对象downPoint(用于记录鼠标移动前的坐标) 然后在PictureBox的MouseDown事件中将鼠标移动前的坐标e.Location赋值给downPoint 最后,在pictureBox的MouseMove事件中先判断当前按键是否为鼠标左键,若是,则通过鼠标移动前的坐标和移动后的坐标生成新的坐标赋值给当前的窗体 代 码 1234567891011121314151617181920212223242526272829303132333435363738394041using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Runtime.InteropServices;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;namespace MainFrm{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { this.TransparencyKey = Color.White; //设置当前窗体的默认透明色 this.BackColor = this.TransparencyKey; //设置当前窗体的背景色为透明 this.FormBorderStyle = FormBorderStyle.None; //设置窗体无边框 } Point downPoint; //用于记录鼠标移动前的位置 private void pictureBox1_MouseDown(object sender, MouseEventArgs e) {//将鼠标移动前的位置赋值给downPoint downPoint = e.Location; } private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { if (e.Button==MouseButtons.Left) {//通过鼠标的移动前和移动后的坐标生成新的坐标 Location = new Point(Location.X + e.X - downPoint.X, Location.Y + e.Y - downPoint.Y); } } }} *(本页地址:http://solutionx.top/2017/07/24/diary-170724-1/ ) *","tags":[{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"2017-7","slug":"2017-7","permalink":"//blog.solutionx.top/tags/2017-7/"},{"name":"C#","slug":"C","permalink":"//blog.solutionx.top/tags/C/"}]},{"title":"【C#小程序】 云中转聊天","date":"2017-07-13T14:45:00.000Z","path":"2017/07/13/diary-170713-01/","text":"概 述 C#小程序第二波 - - “云中转聊天”(EX-Chat) 基本功能完成于前些日子,更多功能还在探索中 缘 由 “为什么想做这么个项目呢?” 本人在学习.NET的时候对使用Socket编程实现通信遇到的一些问题深有感触。当时懂的东西特少,以为只要在我的电脑连接网络开启Socket服务端,其他能连上网的电脑上的客户端就能连接得到。直到后来与同学在两个不同内网中测试自己写出来的程序的可用性的时候发现根本连不上,当时我还以为是自己将代码写错了才会导致这种情况,后来证明是当时的自己图样图森破。原来,Socket服务端要想让所有客户端都能连接到,必须得将其运行在连接了外网的服务器(或是电脑)上,或者还有一种方案就是对自己的电脑进行NAT穿透(也就是所谓的内网穿透吧)。苦于本人对NAT并不是特别了解,虽然知道有这么个方案,由于也没有很多空闲时间去学,于是就一直没能将其实现。没有连接到外网的电脑也不会用NAT怎么办呢?那么接下来这个项目就是介绍一种无服务器的简单通信方案。 思 路 为了解决以上所说的那些问题,我的思路大致是这样的: 首先,准备工作 由于本人想的这个方案由于比较简单,所以要求也比较低,总的来说一个能用的云数据库则足以(云数据库可以上服务供应商官网购买,如果只是感兴趣想学学,你也可以上论坛找找看有没有免费试用的) 有了云数据库之后,我们先在云数据库上面建立一张初始表用于存储用户的数据,表中的相关字段根据自己的需要自行设置,以本人项目为例,我创建的用户数据表主要包含了七个字段:用户账号、用户名、用户密码、注册时间、在线状态、最后登录时间、用户类型。 接下来 就是使用VS进行用户界面的搭建了,主要包含登录、注册、好友列表、聊天界面四个窗体。因为跟本文主题不相关,这里不进行细说,每个人都有自己的想法,只要能实现基本的功能即可。 用户界面搭建好了之后,最关键的就是注册和聊天功能了。 注册时,用户填写相关信息,点击“注册”,客户端随机生成一个长度为5的数字作为账号,并将其与用户填写的信息一起写入到用户数据表中,同时建立一张专门用于存储该用户聊天记录的表,这也就完成了聊天功能(仅文字信息)的核心部分,最后就是如何实现客户端之间的通信了。 那么如何实现通信呢?既然有说到是数据库作数据中转,那么学过数据库的同学应该就能猜到是个什么想法了吧。 其实很简单,我利用的只不过就是数据库的增删查改功能。 这里简单的叙述一下消息的发送过程:本地用户在给对方发消息的时候,本地客户端在对方的聊天记录表上插入一条记录,而对方客户端在接收这条消息时则通过Timer控件定时检查对应账号的聊天记录表,如果有新的消息,则将其打印出来。反过来,对方给本地用户发消息也是同一个道理。 其他的比如清空消息记录之类功能,原理都大同小异,只要你会SQL脚本,相信这些实现起来都不难。 具体实现 前阵子上阿里云申(ceng)请(xiao)域(pian)名(yi)的时候,顺便加价买了个共享虚拟主机普惠版。 由于廉价的一匹,得来的配置是这样的:200M的网页空间和20M的数据库空间,不过就目前来说,本人拿来学习和测试足以。 云虚拟主机被某帅比搁置了一段时间之后,终于在毫无特殊可言的某天下午迎来了它的使命。 由于是基于云数据库作数据中转的聊天程序,于是本人将项目名暂且取作“云中转聊天”(经过几个版本的迭代更新之后已更改为EX-Chat) 根据以上所述思路操作,经过多次修改,花费了好几天时间之后总算是写出了成品。 外观较为简陋,暂时只实现了基本的通信功能,并且安全性较低,只作学习和测试用。 下面放上几个主要窗体的相关截图用以参考,更多功能留作以后探索,更新日志见[此处][1],这里暂不详述 [1]: http://software.solutionx.top/云中转聊天程序/pages/02/ 点击此处下载程序(出于一些个人原因,下载入口暂时已关闭) – end 更新于:2017.11.20 end – 新版本的部分截图(1.0.4.8) 更新日志见[此处][2] [2]: http://software.solutionx.top/云中转聊天程序/pages/04/ – end 更新于:2018.4.17 end –","tags":[{"name":"2017-7","slug":"2017-7","permalink":"//blog.solutionx.top/tags/2017-7/"},{"name":"C#小程序","slug":"C-小程序","permalink":"//blog.solutionx.top/tags/C-小程序/"}]},{"title":"【C#小程序】 ColorCreator","date":"2017-07-12T10:23:00.000Z","path":"2017/07/12/diary-170712-01/","text":"简 介 ColorCreator是本渣于前段时间自学HTML和CSS时依照自己的需求用C#编写的 小小程序,并无太多的功能,简而言之就是 - - 展示十六进制颜色代码所对应的颜色 细 究 当时在写HTML标签时遇到了一个问题,本渣不太清楚颜色的十六进制代码对应什么样的颜色 (汗!) 虽然这么串十六进制代码有它的规律,但是本渣并没有深究 原因很简单,懒 ~ 可能有人会说,“写到HTML的某个标签里上浏览器跑一下不就好了?” 的确,使用这种方式来查看颜色代码对应着什么样的颜色再方便不过 可是本渣就是喜欢折腾更加方便的method,(> _ <) 还是一个字,懒! 我都懒得写到HTML里面了 直接复制粘贴,点一下生成按钮就可以看到结果多方便啊 基于这么个想法,本渣打开了vs 咔咔咔地撸了几行代码之后,一个小巧的颜色生成器就出来啦 实际结合TakeColor使用起来还确实挺方便的 (TakeColor没有集成“通过十六进制代码生成颜色”这项功能) 不过吧,本渣并没有因此而感到满意 要是还能通过调节TakeColor上面那种小滑块来获得自己想要的颜色岂不美哉 想是这样想,不过距离上次这样想已经过去大概有三个月了吧 可是本渣至今没有行动,还是那个字、、 呵呵哒 、、以后再说以后再说 哈哈 截图奉上: 点击此处下载程序 点击此处查看源码 下面放上“生成”按钮的代码: 1234567891011121314151617181920212223242526272829//点击生成按钮private void btnCreate_Click(object sender, EventArgs e){ if (!string.IsNullOrEmpty(this.labelMsg.Text)) { this.labelMsg.Text = \"\"; } if (txtInput.Text.Length == 7) { Bitmap bmp = new Bitmap(200, 150); Graphics g = Graphics.FromImage(bmp); string hex = this.txtInput.Text; try { Color _color = System.Drawing.ColorTranslator.FromHtml(hex); Brush b = new SolidBrush(_color); g.FillRectangle(b, new Rectangle(0, 0, 200, 150)); this.picColorBoard.Image = bmp; } catch { labelMsg.Text = \"**请输入有效的十六进制码**\"; } } else { labelMsg.Text = \"**请输入有效的十六进制码**\"; }} 后 记 此为本渣第一篇用于 - - 记录【在自学.NET的道路上积累的经验】 并且 【收录个人鼓捣出的作品】 的文章 另外,本渣自己鼓捣出来的小程序的下载地址 看这里 仅用于学习和测试 仅用于学习和测试 仅用于学习和测试","tags":[{"name":"2017-7","slug":"2017-7","permalink":"//blog.solutionx.top/tags/2017-7/"},{"name":"C#小程序","slug":"C-小程序","permalink":"//blog.solutionx.top/tags/C-小程序/"}]},{"title":"【音乐分享】 アイロニ","date":"2017-06-24T04:23:00.000Z","path":"2017/06/24/diary-170624-01/","text":"人生って何なのって? 少し歩き疲れたんだ 有些走累了啊 少し歩き疲れたんだ 有些走累了呢 月並みな表現(ひょうげん)だけど 雖然以那麼平凡的表現 人生(じんせい)とかいう長い道を 來形容人生漫長的道路 少し休みたいんだ 想稍稍休息下呢 少し休みたいんだけど 虽然想稍稍休息一下 時間は刻一刻(こくいっこく)残酷と 可是時間每時每刻都這樣殘酷 私を 引っぱっていくんだ 將我 緊拖著前行 Yeah—— Yeah——Yeah—— うまくいきそうなんだけど 雖然看似順利地進行著 うまくいかないことばかりで 但其實全是不順利的事 迂闊(うかつ)にも泣いてしまいそうになる 因此一塌糊塗地哭起來 情(なさ)けない本当にな 真是丟人呢 惨(みじ)めな気持なんか 這樣悲慘的感受 嫌というほど味わってきたし 已經不想再次體驗到了 とっくに悔しさなんてものは 明明應該將悔恨之類的 捨ててきたはずなのに 早早丟棄掉 絶望抱くほど 悪いわけじゃないけど 雖然也不是感到絕望般 那樣差勁 欲しいものは いつも少し手には届かない 但是想要的東西 卻永遠得不到手 そんな半端(はんぱ)だとね 對這樣沒用的傢伙 なんか期待してしまうから 為何會有所期待呢 それならもういっそのこと 既然如此不如乾脆 ドン底まで突き落としてよ 就將它推入穀底吧 答えなんて言われたって 即使要說出些什么答案 人によってすり替(か)わってって 因人不同也會有所改變 だから絶対(ぜったい)なんて絶対 所以絕對之類的絕對 信じらんないよ ねぇ 是不能相信的 對吧 苦しみって誰にもあるって 誰都會有苦楚 そんなのわかってるから何だって 說著那些什么都明白 なら笑って済ませばいいの? 那就笑著应对過去就好了吧? もうわかんないよ バカ! 我不知道該怎麼辦啦 笨蛋! 散々言われてきたくせに 明明是被狠狠說了一番 なんだ まんざらでもないんだ 但卻未必就是這樣 簡単に考えたら楽なことも 將簡單思考起來很容易的事 難関(なんかん)に考えてたんだ 也當做難題考慮了 段々(だんだん)と色々めんどくなって 種種事都越發麻煩 もう淡々と終わらせちゃおうか 讓一切都淡泊結束吧 「病んだ?」とかもう 嫌になったから 「病了嗎?」之類的已經受夠了 やんわりと終わればもういいじゃんか 能溫和地結束不就好了嗎 夢だとか希望だとか 生きてる意味とか 夢也好希望也好 亦或是生存意義 別にそんなものはさして 必要ないから 那些東西也並不是 沒有必要存在 具体的(ぐたいてき)でわかりやすい 請給我具體易懂的 機会(きかい)をください 這樣的機會啊 泣き場所探すうちに 在尋找哭泣的地方時 もう泣き疲れちゃったよ 就已經哭累了啊 きれいごとって嫌い だって 討厭華而不實的話 期待しちゃっても形になんなくて 期待著卻捉不到蛛絲馬跡 「星が僕ら見守って」って 要說「星星守護著我們」 夜しかいないじゃん ねぇ 那也就只有晚上 對吧 君のその優しいとこ 你的那份溫柔 不覚(ふかく)にも求めちゃうから 我在不知不覺中尋求著 この心やらかいとこ 這顆心的柔軟 もう触んないで ヤダ! 請别再觸碰了 不要了啊! もうほっといて 不要再管我了啊 もう置いてって 就这样丟下我吧 汚れきったこの道は 弄髒了的這條路 もう変わんないよ嗚呼 已無法改變了啊 疲れちゃって弱気(よわき)になって 因疲倦而變得懦弱了 逃げ出したって無駄なんだって 想要逃也是白費力氣 だから内面(ないめん)耳塞いで 所以內心捂著耳朵 もう最低(さいてい)だって泣いて 已是最糟了而哭著 人生って何なのって 人生又是什麼呢 わかんなくても生きてるだけで 只是不明不白地活著 幸せって思えばいいの? 認為這即是幸福就可以了嗎? もうわかんないよ バカ! 我不明白了啊 笨蛋!","tags":[{"name":"2017-6","slug":"2017-6","permalink":"//blog.solutionx.top/tags/2017-6/"},{"name":"音乐","slug":"音乐","permalink":"//blog.solutionx.top/tags/音乐/"}]},{"title":"【音乐分享】 一笑倾城","date":"2017-06-12T09:46:00.000Z","path":"2017/06/12/diary-170612-02/","text":"还有什么事情是一首音乐解决不了的? 😃","tags":[{"name":"2017-6","slug":"2017-6","permalink":"//blog.solutionx.top/tags/2017-6/"},{"name":"音乐","slug":"音乐","permalink":"//blog.solutionx.top/tags/音乐/"}]},{"title":"MarkDown的使用教程","date":"2017-06-12T02:55:00.000Z","path":"2017/06/12/diary-170612-01/","text":"个人总结比较常用的几个 【标题】 123456# 标题1## 标题2### 标题3#### 标题4##### 标题5###### 标题6 【显示效果】 标题1 标题2 标题3 标题4 标题5 标题6 【字体加粗】 1**目标文本** 【显示效果】 目标文本 【删除线】 1~~要删除的文本~~ 【显示效果】 要删除的文本 【字体倾斜】 1* 目标文本*(“ * ” 后方无空格) 【显示效果】 目标文本 【文字引用】 1> 目标文本 【显示效果】 目标文本 【无序列表】(*、-、+ 均可) 将括号内的符号加到行的前面即可 通过空格缩进实现次级行显示 【有序列表】 在需要添加序号的行的前面添加“1. ”、“2. ”、“3. ”之类文本即可 注意“.”后方有空格 【分割线】(“-”大于三个即可) 1------------ 【显示效果】 【直接链接】 1[百度](http://www.baidu.com \"百度\") 【显示效果】 百度 【引用链接】 12[百度][1][1]: http://www.baidu.com \"标题\" 【显示效果】 [百度][1] [1]: http://www.baidu.com “标题” 【表格】 12345|左对齐|居中|右对齐||:---|:---:| ---:|| 1| 2| 3|| a| b| c|| ,| .| 、| 【显示效果】 左对齐 居中 右对齐 1 2 3 a b c , . 、 【github 特有的特性】 复选框列表 在列表符号后面加上[]或者[x]代表选中或者未选中情况 1234567[x] C[x] C++[x] Java[x] Qt[x] Android[ ] C#[ ] .NET 【显示效果】 【表情显示】 1:blush: 【显示效果】 😊 更多表情请点击 这里 暂时就这么多吧 网上教程很多,这里只列举某帅比觉得还不错的几个 其他教程如下: Markdown——入门指南【荐】 –>传送门<–(转自 Te_Lee的博客) Markdown 11中基本语法 –>传送门<–(转自 秋水Leo的博客) Markdown 语法 示例 字体 字号 颜色 –>传送门<–(转自 ABC-ruifeng的博客) Markdown 语法说明 (简体中文版) –>传送门<–(转自“小众软件”) Markdown: Basics (快速入门) (简体中文版) –>传送门<–(转自“小众软件”) Markdown 官方文档 1、创始人 John Gruber 的 Markdown 语法说明 2、Markdown 语法说明(简体中文版).pdf 3、Markdown 官方教程 编辑器推荐 【Atom】 软件截图:","tags":[{"name":"2017-6","slug":"2017-6","permalink":"//blog.solutionx.top/tags/2017-6/"},{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"},{"name":"Markdown","slug":"Markdown","permalink":"//blog.solutionx.top/tags/Markdown/"}]},{"title":"沉迷二次元无法自拔😆","date":"2017-06-11T14:51:00.000Z","path":"2017/06/11/diary-170611-02/","text":"分享一组二次元美图 ⚠️⚠️⚠️前方多图预警(about 5~6 MB)⚠️⚠️⚠️","tags":[{"name":"2017-6","slug":"2017-6","permalink":"//blog.solutionx.top/tags/2017-6/"},{"name":"二次元","slug":"二次元","permalink":"//blog.solutionx.top/tags/二次元/"}]},{"title":"Starting point 2017.6.11","date":"2017-06-11T14:47:00.000Z","path":"2017/06/11/diary-170611-01/","text":"某只闲的蛋疼的帅比折腾了好几天 浏览了无数博主发布的教程结合实际操作 在经历了无数次失败,积累了不少经验之后 终于找到了适合自己的方法搭建属于自己的博客 (博客搭建的相关教程请访问本文末尾的跳转链接) 说 明 搭建方式:Hexo+Github,博客采用Litten大佬提供的主题 用 途:个人博客,记录和分享本人在程序猿这条不归路上的坎坷经历 体 会 使用Hexo搭建个人博客相对其他方法来说很方便,在发表博文时用户只需掌握少量Git指令以及Markdown语法即可快速实现 Hexo支持使用Markdown语法编辑博文,相较于直接写HTML标签和代码来说要方便不少,并且易于掌握 Markdown:一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。( 百度百科传送门: Markdown ) 以下简要介绍使用Markdown在代码显示上的运用: bash代码(创建新的博文) 1$ hexo new \"我的第一篇博文\" C#代码 1234void ShowMsg(){ MessageBox.Show(\"博主是个大帅比!\");} js代码 1alert(\"JavaScript表示赞同!\"); 搭建经验及教程 本人使用Hexo+Github搭建个人博客的想法来自一个学前端开发的学长 起初是通过访问他的博客正式开始接触“个人博客”这一领域。当时就是觉得拥有一个个人博客是一件挺酷炫的事情,所有知道我博客地址的人都能访问我的网站,浏览我写的博客,这是一件让人觉得很有成就感的事情。 写博客不但可以记录生活中发生的有意义的事情,还能提升自己的写作能力,那么我想这件事本身也很有意义吧。 想归想,要获得成就还是得有付出的。经过一番对于个人博客的初步了解之后,本渣渣总算是步入了个人博客搭建之路。 通过分析那位学长的网站以及他发过的博文,得知有Hexo这么一个框架。 [Hexo][2]是一个开源的静态博客生成器,用[node.js][3]开发,作者是台湾大学生tommy351。 [2]: https://hexo.io/zh-cn/ [3]: http://nodejs.cn/ 而他使用的博客主题是由Litten设计开发的,能开发出这么简洁大气的博客主题,想必他也是很厉害的一个人吧! 此处送上大佬的博客主页:Litten的博客 使用这种方法搭建个人博客对于我来说的难点是如何使用Hexo。一开始我对Hexo有个误解,一直以为Hexo的主题本身就是一个博客模板,只要把主题里面所有的文件放到github仓库就可以直接访问。 后来经历过多次失败后才发现事情并没有那么简单。 一番疯狂的学习和实践之后,本站总算是基本搭建完成。折腾了很久,切实验证了一个真理 - - “成功是需要多次尝试并且经历多次失败才能得来的”、、、 期间访问了很多个人博客,收藏了很多教程,按照这些教程一步一步操作,遇到不懂的就百度,相信大家也能很快拥有属于自己的博客 教 程 使用 Hexo 生成一套静态博客网页(主要) 参考: 1、史上最详细“截图”搭建Hexo博客并部署到Githu 2、Hexo+GitHub Pages搭建属于自己的blog 3、windows环境下使用hexo搭建blog平台 4、hexo常用命令笔记 5、github写博客 6、hexo你的博客 7、Hexo 主题制作指南 8、使用AES算法加密hexo文章 9、Hexo官方文档 本文到此就先告一段落、吧、、、","tags":[{"name":"杂谈","slug":"杂谈","permalink":"//blog.solutionx.top/tags/杂谈/"},{"name":"随笔","slug":"随笔","permalink":"//blog.solutionx.top/tags/随笔/"},{"name":"2017-6","slug":"2017-6","permalink":"//blog.solutionx.top/tags/2017-6/"},{"name":"Github","slug":"Github","permalink":"//blog.solutionx.top/tags/Github/"},{"name":"Hexo","slug":"Hexo","permalink":"//blog.solutionx.top/tags/Hexo/"},{"name":"Blog","slug":"Blog","permalink":"//blog.solutionx.top/tags/Blog/"},{"name":"教程","slug":"教程","permalink":"//blog.solutionx.top/tags/教程/"}]}]