投资就像在种树

突然觉得自己的账户就像是精心维护的果园,每一个标的就是一株树,建仓就是埋下种子,凡是要将树种高种大,必须有一套方法,加以耐心,毅力和时间,此后经受无数次风雨,依然坚持不懈,最终才能将树养大,养大之后便可以坐享果实了,正所谓前人栽树,后人乘凉。

对应投资一样,在建仓之后,悉心呵护等待其脱离成本区,有时候会接受大跌的洗礼,但是坚持投资策略,不断的给它输送养分,最终慢慢长大,积累利润垫,等积累足够的利润垫,市值也越来越大的时候,每年分红就够了,没有分红的就需要自己把握高点和低点,高点卖出,低点买入,类似于分红了,这时候就是乘凉的时候。

播种和收获都非常容易,但是用科学的方法呵护并陪伴其长大是一个漫长的过程,这不仅是一个技术活,也是一个考验耐心的体力活,期间不能着急,否则树折腾来折腾去,肯定就折腾死了。

立下种树这个观念,在投资中就不会急躁,不会被各种诱惑吸引,只要老老实实按自己方法种自己的树就可以了,保持耐心,相信将来的某一天终将可以品尝到收获的果实。把投资当成种树,也就是接受投资需要比较长的一段时间来发育,而不是一夜暴富,建立了这个心态,就不会一惊一乍,就会宠辱偕忘,波澜不惊。

我的果园现在有十三棵树,现在都在慢慢长大。树呢分为两类,一种是不太用打理的,它会自己慢慢长大;另一种是短期经常打理的,当然长期的也需要短期打理,但是总体上是省心的。现在大部分都还处于发芽期,而且很多处在风雨飘摇,因为自从种下之后经常都是暴风雨。希望以后情况能好一点,让我的树苗们茁壮成长。目前的目标是树苗们都可以长到10%的利润垫。

在 Hexo 博客里插入图表

用到了 Chartjs 的插件:hexo-tag-chart

用法总结如下:

首先在 hexo 博客的目录里运行:npm install hexo-tag-chart --save

然后在文章中就可以使用 chart 的 tag 了

1
2
3
{% chart 90% 300 %}
\\TODO option goes here
{% endchart %}

其中 chart 是标签名,endchart 是结束标签,不需要更改,90% 是图表容器的相对宽度,默认是 100%,300 是图表容器的高度,默认是按正常比例缩放的,你可以通过设置 options 里面的 aspectRatio 属性来调整宽高比例,另外还有许多属性可以自定义,你可以查看 官方文档。在标签之间的部分,需要自己填充的图表数据和属性。

我自己使用图表的页面:FIRE 基金

参考:

构建基于 Python 的单元测试

这篇文章记录了如何基于 Python 构建一个 Web 系统的单元测试,涉及一些基本和高级用法。

测试分类

  • 单元测试:单个模块的测试
  • 集成测试:多个模块的测试
  • 功能测试:项目的功能测试

其实就是范围不同,单元测试仅是系统特定一部分的测试,功能测试是将系统作为整体进行测试,集成测试介于两者之间。

单元测试库

最常用的是 unittest 和 pytest

  • 继承 unittest 的 TestCase 类来组织单元测试
  • assert 语句用来检测是否符合预期,而 pytest 提供了一些更强大的 assert 方法
  • pytest 用来运行测试,它可以使用加强版的 assert,并且它完全支持 unittest

一个简单的单元测试

1
2
3
4
5
6
7
8
9
import unittest
from fizzbuzz import fizzbuzz


class TestFizzBuzz(unittest.TestCase):
def test_fizz(self):
for i in [3, 6, 9, 18]:
print('testing', i)
assert fizzbuzz(i) == 'Fizz'

运行:

1
2
3
4
5
6
7
8
9
(venv) $ pytest
========================== test session starts ===========================
platform darwin -- Python 3.8.6, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /Users/miguel/testing
collected 1 items

test_fizzbuzz.py . [100%]

=========================== 1 passed in 0.03s ============================

pytest命令比较智能,它会自动识别单元测试,它假定以这样的名字:test_[something].py 或者 [something]_test.py 命名的模块都包含单元测试。同时它也会搜索子目录。

一般来说,单元测试统一放到 tests 目录下,和应用目录隔离开。

测试覆盖率

安装:pip install pytest-cov

运行 pytest --cov=fizzbuzz,可以针对 fizzbuzz 模块运行单元测试以及覆盖率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(venv) $ pytest --cov=fizzbuzz
========================== test session starts ===========================
platform darwin -- Python 3.8.6, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/miguel/testing
plugins: cov-2.11.1
collected 3 items

test_fizzbuzz.py ... [100%]

---------- coverage: platform darwin, python 3.8.6-final-0 -----------
Name Stmts Miss Cover
---------------------------------
fizzbuzz.py 13 4 69%
---------------------------------
TOTAL 13 4 69%


=========================== 3 passed in 0.07s ============================

还有以下参数:

  • --cov-branch 针对分支处理,有多少个分支就统计多少次
  • --cov-report=term-missing 表示以何种方式展示报告,term-missing表示在terminal上展示,并且会额外加上缺少测试覆盖的代码行数,另外一个常用选项是html 在html上展示报告,很清晰,常用。

可以添加注释 pragma: no cover 来跳过该块代码的覆盖率检测

测试参数化

使用库 parameterized: pip install parameterized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from parameterized import parameterized

# ...

class TestLife(unittest.TestCase):
# ...

@parameterized.expand([('pattern1.txt',), ('pattern2.txt',)])
def test_load(self, pattern):
life = Life()
life.load(pattern)
assert life.survival == [2, 3]
assert life.birth == [3]
assert set(life.living_cells()) == {
(10, 10), (11, 11), (15, 10), (17, 10)}
assert life.bounding_box() == (10, 10, 17, 11)

也可以使用列表推导式:

1
2
3
4
5
class TestLife(unittest.TestCase):
# ...

@parameterized.expand([(n,) for n in range(9)])
def test_advance_cell(self, num_neighbors):

支持多参数:

1
2
3
4
5
6
7
import itertools

class TestLife(unittest.TestCase):
# ...

@parameterized.expand(itertools.product([True, False], range(9)))
def test_advance_cell(self, alive, num_neighbors):

测试异常

1
2
3
4
5
6
7
8
9
10
11
import pytest

# ...

class TestLife(unittest.TestCase):
# ...

def test_load_invalid(self):
life = Life()
with pytest.raises(RuntimeError):
life.load('pattern4.txt')

Mocking

mocking 就是劫持函数或者功能,可以控制返回值或者其他东西的一种功能。在测试中如果对某个函数已经有了详尽的测试,那么在这个函数被调用的地方,就可以用mocking功能,节约资源。

unittest 里的 mock 模块,可以使用 mock.patch_object() 来替换函数或者方法

1
2
3
4
5
6
7
8
9
from unittest import mock

class TestLife(unittest.TestCase):
# ...

@mock.patch.object(Life, '_advance_cell')
def test_advance_false(self, mock_advance_cell):
mock_advance_cell.return_value = False
# ...

测试 Web 应用

最好将测试归集到一个继承 unittest.TestCase 的类里,这样可以公用 setUp 和 tearDown 方法,会有更好的性能,以及更方便。

WSGI 和 ASGI 都有特定的规则用于服务器如何传递到应用的请求。所以我们可以注入假的请求到应用上来模拟,而不用启动真正的服务器。这些 Web 框架都有所谓的测试客户端(test clients)来帮助实现单元测试,不需要任何网络,会向应用传递假的请求。如果 Web 框架没有提供的话,WSGI 应用可以使用 Werkzeug 库,ASGI 应用可以使用 async-asgi-testclient

比如,Flask 框架可以直接使用自带的 test client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TestWebApp(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.appctx = self.app.app_context()
self.appctx.push()
db.create_all()
self.client = self.app.test_client()

def tearDown(self):
db.drop_all()
self.appctx.pop()
self.app = None
self.appctx = None
self.client = None

Tornado 框架可以继承 HTTPTestCase or AsyncHTTPTestCase 类来实现,其中它自带了 HTTPClient 和 AsyncHTTPClient,可以直接使用:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

class BaseTestCase(AsyncHTTPTestCase):
def setUp(self):
super(BaseTestCase, self).setUp()
self.db_session = test_session
self.db_session.commit()
self.cookie = SimpleCookie()

def get_app(self):
test_app = Application()
return test_app

def get_new_ioloop(self):
return IOLoop.current()

def get_url(self, path):
full_path = super(BaseTestCase, self).get_url('/api/v1{}'.format(path))
return full_path

def _update_cookies(self, headers):
try:
cookies = escape.native_str(headers['Set-Cookie'])
self.cookie.update(SimpleCookie(cookies))
except KeyError:
return

def make_response(self, req, resp):
response = Response()
response.status_code = getattr(resp, 'code', None)
response.headers = {k: v for k, v in list(resp.headers.items())}
response.encoding = get_encoding_from_headers(response.headers)
response.raw = resp
response.reason = response.raw.reason
response._content = resp.body

if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8')
else:
response.url = req.url
extract_cookies_to_jar(response.cookies, req, resp)
response.request = req
return response

def send(self, url, method='GET', data=None, json_data=None, files=None, headers=None, **kwargs):
if 'follow_redirects' not in kwargs:
kwargs['follow_redirects'] = False
request = Request(url=self.get_url(url), files=files, data=data, json=json_data)
request_data = request.prepare()
if headers is None:
headers = {}
headers.update(request_data.headers)
cookie_sting = '; '.join([f'{key}={morsel.value}' for key, morsel in self.cookie.items()])
if cookie_sting != '':
headers.update({'Cookie': cookie_sting})
resp = self.fetch(url, method=method, headers=headers, body=request_data.body, allow_nonstandard_methods=True, **kwargs)
self._update_cookies(resp.headers)
response = self.make_response(request, resp)
self.db_session.rollback()
return response

def get(self, url, **kwargs):
response = self.send(url, method='GET', **kwargs)
return response

def patch(self, url, files=None, data=None, json_data=None):
response = self.send(url, method='PATCH', files=files, data=data, json_data=json_data)
return response

def post(self, url, files=None, data=None, json_data=None, **kwargs):
response = self.send(url, method='POST', files=files, data=data, json_data=json_data, **kwargs)
return response

def put(self, url, files=None, data=None, json_data=None):
response = self.send(url, method='PUT', files=files, data=data, json_data=json_data)
return response

测试 html 内容

没必要全部 match 去做测试,而是可以检查一部分内容是否存在,比如提交按钮是否存在于 html 中,而忽略其顺序等无关信息。

1
2
3
4
5
6
7
8
9
10
11
def test_registration_form(self):
response = self.client.get('/auth/register')
assert response.status_code == 200
html = response.get_data(as_text=True)

# make sure all the fields are included
assert 'name="username"' in html
assert 'name="email"' in html
assert 'name="password"' in html
assert 'name="password2"' in html
assert 'name="submit"' in html

这样的方式也适合于其他数据量比较大的测试,只需要测试关键部分即可。

提交表单

主要问题在于 CSRF token 怎么处理,可以先发一个 GET 请求,然后拿到 token,再去提交表单,这是一种方法。另一种方法就是在测试中禁掉 CSRF 的保护。

1
2
3
4
5
6
7
def setUp(self):
self.app = create_app()
self.app.config['WTF_CSRF_ENABLED'] = False # no CSRF during tests
self.appctx = self.app.app_context()
self.appctx.push()
db.create_all()
self.client = self.app.test_client()

测试表单验证

根据表单验证失败返回的语句进行判断

1
2
3
4
5
6
7
8
9
10
def test_register_user_mismatched_passwords(self):
response = self.client.post('/auth/register', data={
'username': 'alice',
'email': 'alice@example.com',
'password': 'foo',
'password2': 'bar',
})
assert response.status_code == 200
html = response.get_data(as_text=True)
assert 'Field must be equal to password.' in html

测试需要登陆验证的页面

有以下几点:

  1. setUp 方法初始化用户
  2. login 方法
  3. 完成对应测试

Example:

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
36
37
38
39

# ...
import re

class TestWebApp(unittest.TestCase):
# ...

def setUp(self):
self.app = create_app()
self.app.config['WTF_CSRF_ENABLED'] = False # no CSRF during tests
self.appctx = self.app.app_context()
self.appctx.push()
db.create_all()
self.populate_db()
self.client = self.app.test_client()

def populate_db(self):
user = User(username='susan', email='susan@example.com')
user.set_password('foo')
db.session.add(user)
db.session.commit()

def login(self):
self.client.post('/auth/login', data={
'username': 'susan',
'password': 'foo',
})

def test_write_post(self):
self.login()
response = self.client.post('/', data={'post': 'Hello, world!'},
follow_redirects=True)
assert response.status_code == 200
html = response.get_data(as_text=True)
assert 'Your post is now live!' in html
assert 'Hello, world!' in html
assert re.search(r'<span class="user_popup">\s*'
r'<a href="/user/susan">\s*'
r'susan\s*</a>\s*</span>\s*said', html) is not None

测试 API 服务器

比较简单,因为 API 接口第一涉及范围小,第二返回基本上都是 JSON,容易解析。

1
2
3
4
5
6
7
8
9
10
11
12
def test_api_register_user(self):
response = self.client.post('/api/users', json={
'username': 'bob',
'email': 'bob@example.com',
'password': 'bar'
})
assert response.status_code == 201

# make sure the user is in the database
user = User.query.filter_by(username='bob').first()
assert user is not None
assert user.email == 'bob@example.com'

参考

构建你的投资体系

作为一个普通人,最开始的时候需要通过工作不断的积累本金,应付结婚买房生子等等诸多事情,而这些事情都是相当耗费钱的。一旦度过这些所谓的“坎”,工作上也有了不断进步,收入逐渐提高,财富也慢慢的积累起来的时候,就是时候考虑如何用这些积累的财富去创造价值了。

使用资本去创造额外价值就是一种投资,但是投资是有巨大风险的,在没有清楚认识到这些风险之前,不要投资!你要做的就是构建属于你的投资体系!

这篇文章我先介绍一下自己的投资体系,至于我是怎么一步一步的构建出来的,以后再说。

建立在股票基金上

投资体系这件事情范围很大,涉及整个家庭的资金分配,最为人熟知的就是,标准普尔家庭资产象限图

image.png

我这里指的是狭义上的投资体系:股票基金这种高风险投资标的上建立的投资体系

基本原则

首先我的投资体系里有几个原则:

  1. 不买股票,只买基金。其实对于我来说,我的投资体系是建立在基金上的。
  2. 建立自己的交易系统并且坚定执行。

体系结构

我的投资体系分为 趋势梭哈长期持有 两个部分,但是这两个部分共同点是基于一套交易系统。

这个交易系统是我花了一年时间打造的量化决策交易系统。

趋势梭哈

趋势梭哈部分的主要理念就是利用交易系统的信号进行高抛低吸

分为 梭哈标的打野标的

梭哈标的目前就是创业板相关基金,创成长,创业板50,双创,在买入的时候全仓买入。

打野标的就是其他一些宽基和行业基金:上证50,红利,深红利,科创50,能源,新能车,银行,农业,军工,旅游,有色金属。买入的时候只买20%比例。

如何做呢?就是在交易系统提示弱转强的时候买入,在提示强转弱的时候卖出,或者是根据交易系统的止盈条件进行卖出。

长期持有

长期持有部分就是长期持有一部分宽基,在低位加仓,在高位减仓,其实总体上就是一个大波段的高抛低吸。

目前持有标的:上证50,红利,深红利,创成长

另外长期持有的一个目标就是积累足够的份额,足够的利润垫,每年赚取分红,红利ETF的分红大概在3%-5%之间,还是可以的。

总结

我的投资体系大概就是如此,不必盯盘,因为有量化交易系统。不必焦虑,因为有投资体系。靠时间的力量,慢慢积累。

投资切记不要贪婪

曾经的贪婪

以我自身来说,很早就定下来了决定要一以贯之的投资策略,但是中间随着时间的发展,来自人天生的原始力量——贪婪,针对这最初的投资策略,做了好几次优化。

但是结果呢,每次优化每次坑,最终还是回到了之前的投资策略上,这中间的资金成本,时间成本,没法算了。

第一次,想能多上几次车,修改了策略的上车和下车条件。巧的是,修改之后第一次执行就遇到亏损情况,这次影响最为深远,亏损幅度也是最大。

第二次,想着能快速的盈利,将不要买股票这条原则忘的一干二净,买了不少股票,而且还是地产银行相关的,随后的事情——保交楼事件,让银行地产相关的股票大幅下挫,但是还好动用的仓位不多,这次亏损幅度较小。

第三次,再一次的修改了策略的上车,下车条件,按道理这次改变其实不算很大,但是恰巧的是,刚一改就遇到不相容的情况,造成了损失,亏损幅度也较小。

综上,三次都是为了多挣点钱,不满足于既定策略的收益情况,做了一些贪婪的决策,最终导致了亏钱,亏时间的事情发生。

这三次比较巧的事情是,每次都是刚修改完,立马就会步入坑中,其实这三个修改,也是多多少少做了些回测的,但是很奇怪,每次修改完,不相容的情况立马就会出现,立马就会造成亏损。

还好的是,每次跳坑之后,切换到原始策略上,很快就又都能回血,但是失去的时间已经一去不复返了。

还有一种贪婪就是不按策略既定的止盈条件来操作,自己觉得还要涨,所以就不卖了,导致本来还能盈利的,后来给亏损了。这种情况的贪婪伤害幅度虽然不大,但是侮辱性极强,很气人。

总结

首先如果真的要做优化,最好还是:

第一,使用模拟盘或者小仓位去测试

第二,要经历一个跨度比较长的时间

第三,要严格做出回测

否则,脑袋一拍,就动用大仓位去试错,后果可想而知。

其次严格按策略的止盈止损来操作,既然已经有策略,不按策略来,反而去拍脑袋决策,孰优孰劣可想而知。

最后投资要记的一点:慢就是快,能不断的滚雪球,就是最好的方式。切记不要贪婪

pip 最佳使用方法

image.png

在激活的虚拟环境中使用 pip install 或者 python -m pip install 效果是完全相同的,但是有些场景下就有问题了,而 python -m pip 确保了想要安装的包会和当前解释器是一个环境。

可以设置个别名接着用:

1
❯ echo 'alias pip="python -m pip"' >> ~/.aliasrc

Tornado 实现的 WebSocket 简单例子

Server 部分,主要就是继承 WebSocketHandler 实现了个 WebSocket Handler

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
36
37
38
39
40
41
import logging
import tornado.web
import tornado.websocket
import tornado.ioloop
import tornado.options

from tornado.options import define, options

define("port", default=3000, help="run on the given port", type=int)


class Application(tornado.web.Application):
def __init__(self):
handlers = [(r"/", MainHandler)]
settings = dict(debug=True)
tornado.web.Application.__init__(self, handlers, **settings)


class MainHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True

def open(self):
logging.info("A client connected.")

def on_close(self):
logging.info("A client disconnected")

def on_message(self, message):
logging.info("message: {}".format(message))


def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
main()

Client 部分

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
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.ioloop import IOLoop, PeriodicCallback
from tornado import gen
from tornado.websocket import websocket_connect


class Client(object):
def __init__(self, url, timeout):
self.url = url
self.timeout = timeout
self.ioloop = IOLoop.instance()
self.ws = None
self.connect()
PeriodicCallback(self.keep_alive, 20000).start()
self.ioloop.start()

@gen.coroutine
def connect(self):
print("trying to connect")
try:
self.ws = yield websocket_connect(self.url)
except Exception as e:
print("connection error")
else:
print("connected")
self.run()

@gen.coroutine
def run(self):
while True:
msg = yield self.ws.read_message()
if msg is None:
print("connection closed")
self.ws = None
break

def keep_alive(self):
if self.ws is None:
self.connect()
else:
self.ws.write_message("keep alive")


if __name__ == "__main__":
client = Client("ws://localhost:3000", 5)

运行 Server 和 Clinet 之后输出如下:

  • Server 部分
1
2
3
4
5
[I 220805 10:49:12 server:27] A client connected.
[I 220805 10:49:32 server:33] message: keep alive
[I 220805 10:49:52 server:33] message: keep alive
[I 220805 10:50:12 server:33] message: keep alive
[I 220805 10:50:13 server:30] A client disconnected
  • Client 部分
1
2
trying to connect
connected

初识 Casdoor

最近工作需要研究了下Casdoor:

Casdoor is a UI-first Identity Access Management (IAM) / Single-Sign-On (SSO) platform based on OAuth 2.0, OIDC, SAML and CAS.

简介

Casdoor 是一个开源的单点登录系统,单点登录系统的好处就是集中管理用户,使的我们开发的应用只需要关心业务逻辑而不用每个应用都是去实现一套用户系统。

Casdoor 使用了 OAuth2 的方式来完成单点认证,大概逻辑如下图:

image.png

总结一下就是:

  • 第一步 获取code
  • 第二步 用code获取access_token
  • 第三步 用access_token获取所需要的资源

安装

服务安装文档

文档说的很明白,我这里是下载源码使用的,因为我用的PostgreSQL,所以需要改一下app.confadapter.go

然后后端启动:go run main.go

后端启动:

1
2
3
cd web
yarn install
yarn start

非常简单

界面配置

这里我是用最小的改动来完成对接demo的,首先需要添加一个组织,组织就是一堆资源,应用的的集合:

image.png

后面的用户,角色,权限,模型,提供商都可以先不管,需要了解的可以看文档

然后需要添加一个新的应用,主要就是下图这些

image.png

客户端ID和客户端密钥,都是后面对接时候需要的

还有一个就是证书,这个不需要改,只需要点编辑进入将公钥拿到

image.png

上面就是在界面上需要完成的事情和完成一个对接demo所需要的数据了

web 对接

下面是和 Casdoor 对接的 web 接口代码,主要有两部分,第一部分是获取 code,第二部分是通过 code 获取 access_token,而这个 access_token 已经包含了用户信息,用对应的公钥进行解密,即可得到一个用户的 json 数据。

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
36
37
38
39
40
41
42
43
44
45
import logging
from urllib.parse import urljoin

from casdoor import CasdoorSDK


@route(r'/casdoor/sso-login')
class CASDoorSSOLoginHandler(BaseHandler):

def save_user(self, user_data):
user = User.make_user(uid=user_data['id'], ext_uname=user_data['name'], username=user_data['name'], _from='casdoor')
return user

def get(self, *args, **kwargs):
code = self.get_argument('code', None)
target_uri = self.get_argument('target_uri')

subpath = config.get_config("webif.redirect_subpath", '')
trident_base = urljoin(self.origin_host, subpath.lstrip('/'))

endpoint = config.get_config('casdoor_auth.endpoint')
client_id = config.get_config('casdoor_auth.client_id')
client_secret = config.get_config('casdoor_auth.secret')
org_name = config.get_config('casdoor_auth.org_name')
certificate = config.get_config('casdoor_auth.cert')

sdk = CasdoorSDK(
endpoint,
client_id,
client_secret,
certificate,
org_name,
)

if code:
access_token = sdk.get_oauth_token(code)
user_data = sdk.parse_jwt_token(access_token)
user = self.save_user(user_data)
self.session['proxy_user_id'] = str(user.id)
redirect_url = urljoin(trident_base, target_uri)
else:
origin_url = '{}/api/v1/casdoor/sso-login?target_uri={}'.format(trident_base, target_uri)
redirect_url = sdk.get_auth_link(origin_url, state='casdoor')
logging.info('redirect to %s', redirect_url)
return self.redirect(redirect_url)

配置也比较简单:

image.png

这里需要特别注意的是,endpoint 配的是 Casdoor 访问的首页地址,也就是这里是前端地址,而不是后端地址。前端地址是:http://localhost:7001/,后端地址是:http://localhost:8000/,这里很有迷惑性,让我折腾了半天。

能否武统

不管今晚怎样,反正白天先吃了一记闷棍——亏损2% 😨

目前看到各路消息,似乎一副马上开战的样子

但是我觉得吧,打应该打不起来,那么就有两个分支:

  1. 佩洛西不敢去台湾,那双方都当无事发生,不过这也说明美国影响力在下降,但是最开始美国没说要访问台湾,只是台湾媒体渲染起来的,那最终输家就是台湾。
  2. 佩洛西前往台湾,我国战机伴飞,飞跃台湾本土,这是另一个突破,输家也在台湾。

总之,我们都是赢了。

那么明天能不能阳包阴,来个大涨!

Switch铁人三项

Switch铁人三项就是:健身环大冒险,有氧拳击,舞力全开,这三个我都有,聊聊体验吧。

接触Switch就是从健身环大冒险开始的,而这一切又都是从疫情开始的,在疫情之前从来没有想过还会在家里健身,潜意识里都是健身房,而疫情改变了这一切,改变了人的认知。

健身环大冒险因为疫情爆火,而我也在2020年12月开始Switch铁人三项之旅。

首先是健身环大冒险,做的挺不错,也挺科学的,有氧运动+无氧运动结合,搭配闯关剧情,很容易让人坚持下来,也有趣味性。刚开始的时候因为在家办公,所以我经常玩,后来去办公室上班后,每天下班之后就感觉累成狗,基本上工作日晚上就没玩过几次了,基本上都是周末玩一会。

买了健身环大冒险一个多月后,出了舞力全开这款游戏,当时就是为了尝试体验一下,后来发现这个游戏打开的机会屈指可数,主要还是我没有节奏感,不会跳舞,玩这个游戏就是群魔乱舞,基本上毫无体验感和成就感可言。所以一般玩家可以跳过这个游戏,除非喜欢跳舞。

有氧拳击是最近买的一款游戏,主要特点就是专于有氧运动,一天的运动量在40分钟左右,消耗能量300大卡左右,平均心率在130左右,每次练完一身汗,非常舒服。这款游戏从强度来说没有健身环累,主要是健身环有更多的无氧运动,比如有时候持续深蹲,就非常累,而有氧拳击全程有氧运动,心率很均匀,所以不容易疲劳。

跑步也是一项持续的有氧运动,但是跑步这件事情对于大体重的选手来说非常容易损伤膝盖,所以有氧拳击就是一项非常好的替代品。

后面的健身计划:

第一步,继续有氧拳击,争取每天都能玩一次,加强心肺功能,提高基础代谢水平,然后让体重降下来。

第二步,逐渐开始健身环大冒险,适应一下更多的无氧运动,肌肉训练。

第三步,开始逐步恢复囚徒健身,增大肌肉训练。

至于囚徒健身,之前练过一段时间,后来有所懈怠,后面会再写篇文章介绍一下。