Welcome to my blog. You can post whatever you like!

The blog post is using markdown, and here is a simple guide. Markdown Guide

LOGIN TO POST A BLOG

盒马的2020届校招~(之后会迁移到git上面)

hxs2580

Author: hxs2580

Posted: 2019-07-16 10:15:28

Category: 招聘 校招 盒马 部门

Views: 1094 Comments: 0

**写在前面:这个网址是一个亦师亦友的大神同学的个人博客,我就偷偷把广告挂在里面啦,csdn不能发广告来着~这篇博客里有他从零开始搭建网站的一些心得经验,有兴趣的可以逛一逛,或者关注一下satori的git,会有很多好玩的小玩意让你开心的~** ### 一:部门介绍 阿里巴巴盒马事业群,用技术和数据驱动新零售,为消费者打造社区化的一站式新零售体验中心。用科技打造新零售的商业操作系统,带给人们“鲜美生活”,欢迎加入盒马,和你一起码天下~ ### 二:个人团队介绍 盒马供应链团队招java岗,小组20个人,组内的大家都很nice,如果对盒马供应链小组有兴趣的java大佬,欢迎投递。当然希望大家加入微信群聊一下具体要求和目标,其他岗位其他地点其他部门的内推也是可以的,不过不是我们小组罢了,可能没办法直接秒速跟进状态,只能帮忙在你面完以后帮忙看一下系统内推面试的结果。 **下面放几个部门的生活照~** 1.部门outing ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715234557670.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715233022489.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) **周末一起相约出去玩** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715233200432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) **有新人,项目结项,发钱了等等乱七八糟原因的大大小小聚餐** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715234519544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) **有人入职周年,生日时候的生日蛋糕以及大家的祝福** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715234434526.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715234246818.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) **组内大家一起互相鼓励,互相表扬,互相帮助,以及一起分享生活的点点滴滴** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715234158296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715233727649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI2OTU0NjI1,size_16,color_FFFFFF,t_70) ### 三:招聘岗位及要求(还有一些个人学习建议) **JAVA研发工程师** 1. 希望你是2020年应届毕业生(校招) 2. 扎实的java编程基础(java语法原理,java容器原理,jvm原理(jvm调优加分),java并发(越深越好)),熟悉java开发体系,如果能有一定的项目开发经验那就更好了。 3. 表达问题思路清晰,良好的沟通能力与技术学习能力 **(后面项我列举了最好了解一下的知识)** 4. 有过用mysql等数据库使用经验 5. 了解http,操作系统,计网等基础知识(我另一篇推荐知识的博客有这些,大概简单的勉强够用) 6. 了解一些简单的设计模式(常见的那些最好看过一些源码,实现过一些,项目中用到过并有自己的理解) 7. 了解SSM,SpringBoot 等框架加分(建议初学以spring,spring boot,mybatis框架开始,这是现在很多公司的主流架构) 8. 了解WEB开发相关技术,如HTML,CSS,JavaScript,ajax 等加分(优先度不高,安心安心) 9. 了解分布式架构加分(缓存建议学一下redis,消息队列可以学一下kafka,rocketmq,分布式事务了解一些,分布式锁可以学一下zookeeper,rpc可以看dubbo) 10. 了解高可用架构加分(比如看看Hystrix,优先级不高~ ) 11. 了解微服务加分(公司项目现在设计的很轻量级,一般都是微服务架构的,了解一下有好处,这是未来所有公司架构的升级方向) **我的csdn个人博客(名字搜littleAsuna或者访问https://blog.csdn.net/baidu_26954625) 也有一些收集到的学习网站和自己的总结,也会慢慢将没来得及整理的那些整理出来,希望能给大家提供一些帮助** **【部门地点】** 杭州阿里巴巴西溪园区 **【面试形式】** 内推无需笔试,直接电话面试 **【面试时间】** 2020的校招秋招,(社招当然是要的,社招3-4人,8月15号之前)校招大约2-3人。 直推主管老大,进度推进快,随时跟进,越早投递机会越大,也能早早拿到正式offer! 【简历命名格式】 秋招内推+学校+姓名+电话 社招内推+姓名+电话 【投递邮箱】 pinenut.hxs@alibaba-inc.com 【交流群】 不放了还是加我微信我慢慢拉把 收到简历以后会尽快回复!2020校招的群二维码可能会挂掉,有什么问题可以邮件或者加微信hxs2580,我手动拉进群~ 欢迎各位大家加入阿里巴巴盒马大家庭!! -- [[Monkey 20.gif]]最后感谢我葫芦娃翀神的个人网站[[Monkey 20.gif]]

Read More

A nice item2 configuration

Satori

Author: Satori

Posted: 2019-06-19 15:17:25

Views: 622 Comments: 0

https://medium.com/@Clovis_app/configuration-of-a-beautiful-efficient-terminal-and-prompt-on-osx-in-7-minutes-827c29391961

Read More

关于您的网站模板的installation guide

nocdoggo

Author: nocdoggo

Posted: 2019-04-11 02:50:35

Category: question

Views: 900 Comments: 1

您好, 请问您是否能够说明一下如何使用您的网站模板呢? 我在[这里][1]看到了您的代码,我在想除了Python 3.5+外,还需要什么环境吗? 谢谢啦! [1]: https://github.com/KomeijiSatori/mysite

Read More

登录系统的设计

Satori

Author: Satori

Posted: 2018-09-19 20:11:19

Category: Django Nuxt SSR 技术

Views: 944 Comments: 1

## 一、背景 登录系统的设计可以说是网站的基本功能之一。之前在搭网站的时候用的是Django相关的一个包,django-registrition-redux实现注册功能和登录功能。但是自从我准备把网站重构后,这部分功能就需要自己去实现了。因此我准备自己写一套登录系统。(注册系统还没有实现) ## 二、总体方案 总体方案是请求任何api的时候都带上cookie,然后服务端解析cookie得到用户信息。 所以根据以上方案,只要解决以下几个问题就可以完成。 第一个是cookie是什么时候生成的,第二个是cookie是怎么生成的,解析的,第三个是cookie怎么销毁的。 ## 三、具体实现 ### 1. 用户登录 cookie一定是在用户完成用户名密码校验之后返回响应带给客户端的。首先我们需要校验用户的用户名密码是否正确。这部分我使用了django内置的User模块(这部分在`django.contrib.auth`中)。它提供一个authenticate方法校验用户是否验证成功。如果成功了,就构造一个cookie,在response一起返回给客户端,并设置max age过期时间。如果失败,则返回验证失败。这部分的代码如下所示。 ``` class LoginView(BaseView): def __init__(self): super(LoginView, self).__init__() self.http_method_names = ['post'] self.attrs = { "user_name": { "type": str, "required": True, }, "user_password": { "type": str, "required": True, } } def get_clean_params(self, request): try: params = post_params(request) clean_params = trans_params(params, self.attrs) except Exception as e: logging.error(e) return None, e return clean_params, None def post(self, request, *args, **kwargs): params, err = self.get_clean_params(request) if err is not None: resp = build_response(ReturnCode.ParamError, message=str(err)) return resp name = params.get("user_name") password = params.get("user_password") user = authenticate(username=name, password=password) if user is not None: handler = UserHandler() user_id = user.id token = handler.get_token_str_for_current_user(user_id) # return response code = ReturnCode.Success message = "Login Success" data = {"user_name": user.username, "user_id": user.id} resp = build_response(code, message=message, data=data) # set cookie max_age = settings.LOGIN_TOKEN_EXPIRE_DAYS * 86400 resp.set_cookie(USER_TOKEN_COOKIE_NAME, token, max_age=max_age) return resp else: # the user fail the authentication code = ReturnCode.AuthenticationFail resp = build_response(code) return resp ``` ### 2. cookie的生成与存储 在验证成功之后, 需要生成cookie返回客户端,下次客户端好带上这个cookie来判断用户是谁。 传统的方式是使用一个session id来区分。但是我设计的前后分离的做法使我想到一些其他的做法。首先cookie不能直接暴露用户的信息。比如不能直接把用户的user id塞到cookie里面。否则其他用户可以轻易伪造这个cookie来做一些不可告人的事情。所以cookie需要加密。 我们先设计一下Cookie的结构,首先在服务端生成一个token,然后加上用户id,组成一个字典,然后将这个数据进行jwt编码。jwt编码采用HS256,这时候需要指定一个密钥来进行HS256加密,这个密钥放在django的settings文件比较合适。加密之后得到jwt字符串,就把这个字符串作为cookie值给客户端。 HS256加密的代码如下,非常简单 ``` def jwt_encode(payload): """ Encode jwt by payload :param payload: type: dict, the data to be encoded :return: type: str, the result """ try: key = settings.LOGIN_SECRET encoded_bytes = jwt.encode(payload, key, algorithm='HS256') encoded = bytes.decode(encoded_bytes) except Exception: logging.error("jwt encode error, for payload={}".format(payload)) return None return encoded ``` 同时还附上解码,后期从cookie识别用户也会用到。 ``` def jwt_decode(encoded_data): """ Decode the data of encoded_data :param encoded_data: str, the encoded string :return: dict, the data """ try: key = settings.LOGIN_SECRET payload = jwt.decode(encoded_data, key) except Exception: logging.error("jwt decode error, for encoded_data={}".format(encoded_data)) return None return payload ``` 我们jwt encode的payload结构就是之前提到的字典,包括两部分,一个是用户id,一个是自动生成的token。先说token生成方法,首先先规定token长度,然后在一个字符集合里面随机取字符组成字符串。代码如下,很简单。 ``` def generate_token(): """ Generate a login token :param :return: token: str """ char_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890" length = settings.LOGIN_TOKEN_LENGTH token = "" for _ in range(length): ind = random.randrange(0, len(char_set)) token += char_set[ind] return token ``` 然后就是将这个token和用户id进行打包,然后进行jwt encode,最后发送给用户,这部分代码如下。 ``` def get_jwt_token_from_login_info(self, user_id, user_token): """ Encode jwt token from login info :param user_id: int :param user_token: str :param expire_time: float :return: """ user_token_info = {} user_token_info[USER_TOKEN_USER_ID_NAME] = user_id user_token_info[USER_TOKEN_VALID_TOKEN_NAME] = user_token user_token_str = jwt_encode(user_token_info) return user_token_str ``` 自此cookie生成已经结束了。但是还有一个工作没有做,那就是需要找一个地方把用户的token信息存下来,后期验证用户需要。我选择把这部分信息存到redis里面。并设置一个过期时间,用来避免用户能永久使用一个cookie登陆,这个后期验证用户会用到。这部嗯代码如下。 ``` def set_login_info_to_redis(self, user_id, token, expire_time): """ Save token to redis :param user_id: int :param token: str :param expire_time: float :return: """ redis_token_info = {} redis_token_info[REDIS_TOKEN_VALID_TOKEN_NAME] = token redis_token_info[REDIS_TOKEN_EXPIRE_TIME_NAME] = expire_time redis_token_info_str = json.dumps(redis_token_info) redis_hset(USER_TOKEN_HSET_NAME, user_id, redis_token_info_str) ``` ### 3. 用户验证 前面第二部分已经提到用户验证的前期工作了,现在把用户验证这块进行详细说明。用户在登陆得到cookie后,之后发送的ajax请求都会带上这个cookie,解析这个cookie就能得知是哪个用户。 首先获得用户的cookie,然后对这个cookie进行jwt decode,从cookie字串得到一个字典,包含用户id还有cookie。当然jwt decode可能失败,因为用户那个字串是可能随便瞎搞的,相当于反作弊,直接就认为用户未登录。得到用户token以及id之后,去redis找那个用户当时存下的token,判断两个token是否一样,一样就认为是真的这个用户登陆,否则就认为作弊,也会认为用户未登录。如果token是匹配的,然后再从redis取得过期时间,如果当前时间超过过期时间,认为用户登陆过期了,需要重新登陆。 从redis取用户登陆信息代码如下。 ``` def get_login_info_from_redis(self, user_id): """ Get a user's login info from redis :param user_id: int :return: {token, expire_time} """ redis_token_info_byte = redis_hget(USER_TOKEN_HSET_NAME, user_id) try: redis_token_info_str = bytes.decode(redis_token_info_byte) redis_token_info = json.loads(redis_token_info_str) redis_token = redis_token_info[REDIS_TOKEN_VALID_TOKEN_NAME] expire_time = redis_token_info[REDIS_TOKEN_EXPIRE_TIME_NAME] return redis_token, expire_time except Exception: return None, None ``` 判断用户是否登陆的代码如下。 ``` def _check_user_login(self, user_id, user_token): """ Check the user with user_id to be login according to the token :param user_id: int :param user_token: dict :return: bool """ # check the valid token redis_token, expire_time = self.user_service.get_login_info_from_redis(user_id) if redis_token is None or expire_time is None: return False if redis_token != user_token: return False current_timestamp = get_current_timestamp() if current_timestamp > expire_time: return False return True ``` ## 四、集成BaseView 现在已经有了这些工具之后,可以把这部分集成进公共的baseview里面,在dispatch的时候,调用一个公共的方法获得当前用户。入口是用户的cookie,返回就是当前的用户。这部分代码如下。 ``` def get_current_user_from_token_str(self, user_token_str): """ Get the current user from the request :param request: HttpRequest, the request :return: User, the user """ user_id, user_token = self.user_service.get_login_info_from_jwt_token(user_token_str) if user_id is None or user_token is None: return None if self._check_user_login(user_id, user_token): user = self.user_service.get_user_by_id(user_id) return user else: return None ``` 总体来说实现方案就是这样,过程还是比较繁琐,但是反作弊必须要加的,不然就可能冒充用户。只要用户把cookie保护好,就不会造成问题。

Read More

使用Django和Nuxt实现前后端分离

Satori

Author: Satori

Posted: 2018-09-19 20:10:24

Category: Django Nuxt 技术

Views: 2194 Comments: 0

## 一、背景 在我搭建这个网站到后期的时候,明显感觉前端的代码维护难度太高。因为Django自带模板渲染是基于Jinja渲染的,最大的特点是模板中的数据从服务端一次取出,然后再渲染页面。而数据改变需要手动去维护依赖该数据的html段落。而且研发到后期发现很多前端模块都是可以抽象成一个组件,但是通过模板的include会带来各种问题。在有机会接触到一些前端的mvvm框架后我就起手打算改造网站,实现前后端分离。 ## 二、整体思路 前端页面部分使用Nuxt,它是一个基于Vue的Server-Side-Render的解决方案,自动集成了vue, vue-router, vuex等等组件,能让前端的开发效率明显增高。同时后端使用Django作为服务的提供者。在服务端渲染的时候,如果需要异步数据,则先发请求给Django,然后的得到数据后再渲染成页面返回给浏览器。之后客户端如果有任何异步数据,都通过ajax的方式向Django发送请求来获得数据。 ## 三、实现细节 1. 请求的发送 前端使用axios来发送请求。我的做法是封装一个api的基础client,这个client被各个业务线的其他client继承。这个基础的client主要负责封装请求的发送,以及异常的处理。 ```js import axios from 'axios'; class ApiClient { get_axios_client () { let axios_client = axios.create(this.client_config); axios_client.interceptors.response.use(function (response) { if (response.status !== 200) { return Promise.reject(response.data); } return response.data; }, function (error) { error = error.response || error; console.log('error', error); if (!error.status) { return Promise.reject({ code: 999, message: 'Server Error!', }); } var message = ''; if (error.status >= 400 && error.status < 500) { message = 'request error, message: ' + error.status + ': ' + error.statusText + '. ' + error.data.message; } else if (error.status === 502 || error.status === 504) { message = 'The server is busy or restarting'; } else { message = 'Internal Server Error'; } return Promise.reject({ code: 999, message: message, }); }); return axios_client; } constructor (config) { this.client_config = config; this.client = this.get_axios_client(); } get (url, params, config) { let mergedConfig = Object.assign({}, config, this.client_config, {url: url, params: params, method: 'get'}); return this.request(mergedConfig); } post (url, data, config) { let mergedConfig = Object.assign({}, config, this.client_config, {url: url, data: data, method: 'post'}); return this.request(mergedConfig); } request (config) { return this.client.request(config) .then(response => { return response; }) .catch(response => { console.log('Error Occurs. Response: ' + response); return Promise.reject(response); }); } } export default ApiClient; ``` 这里主要封装了两个方法,一个是get方法,一个是post方法,都返回一个promise,并且增加一个拦截器,拦截response,在返回正常的情况下把data部分返回,在有问题的时候返回reject,并给出错误码和信息。 同时config里面可以设置请求一些参数,比如超时时间等等。 接着封装一个带有默认配置的BaseClient,设置了请求的基准url以及超时时间,代码如下。 ``` import ApiClient from '@/utils/api_client'; const client_config = { baseURL: '/api/', timeout: 20000, }; class BaseApiClient { constructor (sub_url) { const baseURL = client_config.baseURL + sub_url; this.client = new ApiClient({...client_config, baseURL}); } } export default BaseApiClient; ``` 之后所有业务都可以继承这个BaseClient即可实现请求的发送。 2. 请求的处理 请求处理这边就使用Django,通过cookie来识别用户,这部分的技术细节会在接下来的文章里提到。然后Django把请求处理完之后返回给前端数据,最后前端拿到数据做渲染。无论是服务器渲染还是客户端发的请求,都能够处理。 3. 前后端连接 前面都在讲前端如何发请求,后端怎么处理请求上面,最关键的地方还没有讲到,那就是怎么把前后端连接起来。 按照我现在的方案,node的端口在3000,Django的端口在8000(后期当然可以使用linux socket方式,不通过http来进行通信),那么从node的请求都会走3000这个端口,那Django怎么接收这个请求呢?答案就是nginx。nginx可以作为一个强大的反向代理服务器,我们可以这么操作。 先让nginx去监听某一个端口(比如80),然后根据url前缀进行请求分发。如果是前端的请求(页面请求),那么就代理到node开启的3000端口,其他的都丢给Django。 区分前端路由以及后端请求的方法很简单。对于api请求,我都在路由前面加一个`/api`,这个所有以这个请求开头的都是后端请求,其他都是前端请求。这样做也避免了跨域的问题。不过我这里想使用Django的Admin界面进行数据库的一些管理。因此我需要特别匹配一下`admin`这个路由到Django,同时Admin页面也会依赖一些静态资源,路由以`/static`开头,因此也需要匹配路由到Django。因此,nginx的配置文件大概是下面这样。 ``` server { listen 80; server_name 127.0.0.1; charset utf-8; location /admin { proxy_pass http://127.0.0.1:8000; } location /static { alias /home/satori/new_mysite/new_mysite/backend/static; # This should be changed to your own static file folder } location /api { proxy_pass http://127.0.0.1:8000; } location / { proxy_pass http://127.0.0.1:3000; } } ``` 这样配置所有的请求都能做正确的分发,因此前后端请求就连接上了,也就能通信了。

Read More

重构网站进度一览

Satori

Author: Satori

Posted: 2018-07-16 19:51:48

Views: 797 Comments: 2

重构代码都在[new_mysite][1]这个仓库里面 总体时间进度如下: 1. 2018.7.13 搭建基础view以及基础request和response处理模块 2. 2018.7.16 完成用户登陆认证模块 3. 2018.9.17 完成前端登录和登出 [1]: https://github.com/KomeijiSatori/new_mysite/

Read More

关于重构网站

Satori

Author: Satori

Posted: 2018-05-08 20:16:26

Category: 史诗巨坑

Views: 816 Comments: 0

终于到这一天了。终于到了重构整个前端以及大部分后端代码了。 问题的本质原因还是因为现在的前端代码已经**不可维护**了。现有前端代码依托django内置的jinja模板渲染,非常鸡肋。数据只能做一次的绑定。数据变化带来的dom变化都是自己写代码改,直接操作dom会带来很大的风险。而且很多js调用比较混乱。 所以现在就开了一个大坑,就是重构前端代码。但是,通过一些分析来看,这次的改动其实约等于全部重写。 现在的技术解决方案准备采用前后端分离的方法。前端采用基于vue的服务端渲染(ssr)框架nuxt,它结合了vuex,vue-router等并进行了适度封装,是一个很强大的工具。后端数据采用Django Restful Framework来实现。 重构最大的难点在于以前许多基于Django的前端渲染相关的模块就不能用了,比如登陆模块,图片上传模块,国际化模块等等,这些都需要换一个框架或者重新造一个轮子,所需很大勇气去开坑。 初步的想法是新的网站可以先快速搭建,功能逐步完善,新老网站同时运行。不过这个坑需要很长的时间去填,先预计到2019年前看能不能搞定,空闲时间的确太少了。

Read More

基于Django国际化的多语言实现

Satori

Author: Satori

Posted: 2018-02-14 21:32:57

Category: Django HTML JavaScript 技术

Views: 4401 Comments: 9

## 一、背景 做国际化是非常重要的事情。之前网站做出来的时候母亲表示全是英语看不懂。当时我就被触动了一下,虽然现阶段网站根本没有什么流量,但是大部分用户还是国内的,因此不做中文化翻译其他人很难去使用,而且做出国际化之后真的是让自己的网站一下子高大上起来。因此我决定还是做一做国际化。 ## 二、实现方法 国际化的困难程度在我做之前就有估计,不过真实做的时候发现真的比较复杂。复杂不是在于实现难度,而是在细碎程度。因为**HTML文本**,**Javascript代码**,**Django 模型表单标签**都需要进行汉化工作。要做到疏而不漏很困难。还有就是歧义问题,同一个词在不同地方意思不同,还需要自己去做区分。 具体的实现方法参考[官方文档][1],主题思路就是通过在需要翻译的地方进行标注,然后使用Django命令将要翻译的地方生成一个翻译文件,接着补全翻译文件,最后再用Django命令生成翻译的二进制文件即可。 另外一点是需要切换语言,这可以用内置的方法往某个地址post数据即可更改。 ## 三、具体步骤 ### 1. 修改setting.py使之支持国际化 在`MIDDLEWARE`中加一个`'django.middleware.locale.LocaleMiddleware',`,这个中间件负责国际化的语言转化等等。 然后需要加一个`LANGUAGE_CODE = 'zh-hans'`,默认使用中文,再添加语言种类,这里我就添加英语和中文,如下。 ``` LANGUAGES = [ ('en', _('English')), ('zh-hans', _('Chinese')), ] ``` 最后需要定义翻译的文件放在哪里,我们直接放在locale文件夹中,代码如下。 ``` LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale'), ) ``` ### 2. HTML文本的国际化 HTML文本的国际化相对比较简单,有两种方式来标记需要翻译的部分,第一种是直接将要翻译的文本放到`{% trans "xxx" %}`中,比如,`{% trans "language" %}`。 第二种方式适合大段文本的翻译,将要翻译的内容包含在`{% blocktrans %}`和`{% endblocktrans %}`中,比如。 ``` {% blocktrans %} Hello everyone {% endblocktrans %} ``` 但是需要注意的是当要翻译的文本中有变量之类的时候,只能用第二种方式,而且需要给变量一个别名,然后在翻译的文本中去引用,比如有原文是 ``` title: {{ blog.title }}, author: {{ blog.author }} ``` 需要声明一个变量储存,代码如下。 ``` {% blocktrans with title=blog.title author=blog.author %} title: {{ title }}, author: {{ author }} {% endblocktrans %} ``` 多个变量就往后加就行了,如上例。 其次遇到url之类的也需要提前存储,比如原文是 ``` <a href="{% url 'auth_login' %}">Login</a> ``` 需要有一个别名代替url,代码如下。 ``` {% url 'auth_login' as login_url %} {% blocktrans %} <a href="{{ login_url }}">Login</a> {% endblocktrans %} ``` 最后需要注意歧义问题,对于template的歧义,加上context字段,例子如下。 ``` {% blocktrans context "month" %} He will leave on May 2nd. {% endblocktrans %} ``` ### 3. Django源文件的国际化 这部分国际化也比较简单,使用内置的translation中的gettext一系列方法即可获取文本。首先在python文件头部进行声明ugettext以及ugettext_lazy等方法,然后在需要翻译的地方用该方法包装即可。但是需要注意的是`ugettext_lazy`方法只能用于Model以及Form字段的翻译,不能直接作为HttpResponse的返回,这时候需要`ugettext`方法。例子如下。 ``` from django.utils.translation import ugettext as _ def index(request): context = {} blog_list = Blog.objects.all().order_by('-publish_time') page = request.GET.get('page') blogs, page_list = BlogService.get_paginated_items(blog_list, page) context['blogs'] = blogs context['page_list'] = page_list context['title'] = _("Blogs") return render(request, "blogs/index.html", context) ``` 这样就能返回多语言结果了。对于`ugettext_lazy`方法的例子如下。 ``` from django.utils.translation import ugettext_lazy as _ class ProfileForm(ModelForm): class Meta: model = Profile fields = ['gender', 'birthday', 'mobile', 'residence', 'website', 'microblog', 'qq', 'wechat', 'introduction'] labels = { 'gender': _('gender'), 'birthday': _('birthday'), 'mobile': _('mobile'), 'residence': _('residence'), 'website': _('website'), 'microblog': _('microblog'), 'qq': _('QQ'), 'wechat': _('wechat'), 'introduction': _('introduction'), } ``` 对于有上下文才能翻译的词句,需要用`pgettext`方法,例子如下。 ``` from django.utils.translation import pgettext month = pgettext("month name", "May") ``` 这表示"May"这个词的上下文是"month",也就是月份,之后就好分辩怎么翻译,同样也不会和其他may文字冲突。 最后再把template tag中返回给前端的数据做国际化即可。 ### 4. JavaScript的国际化 Javascript的国际化本身比较麻烦,因为它不能直接读翻译文件,也没有直接的翻译函数。还好Django帮我们做了这些事情。首先在根urls.py中引入i18n的url,代码如下。 ``` from django.views.i18n import JavaScriptCatalog # other code urlpatterns += [ url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'), ] ``` 之后只需在引入javascript的html中引入django内置的一个javascript文件,将它引入到其他javascript文件之前即可。代码如下。 ``` <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script> ``` 然后我们就可以在之后的javascript代码直接使用和之前类似python中的`ugettext`,`ugettext_lazy`, `pgettext`函数了。业务代码如下。 ``` document.write(ugettext('this is to be translated')); ``` ### 5. 生成翻译文件以及编译翻译文件 在前面的步骤当中已经将python代码和HTML代码和Javascript代码中需要翻译的地方全部标记了,下一步就是生成翻译文件并进行翻译。首先需要建一个locale文件夹,直接`mkdir locale`即可。下面执行得到翻译文件的命令。加上`-l`标签表示需要翻译的目标语言,比如得到中文翻译文件。 ``` django-admin makemessages -l zh-Hans ``` 对于javascript文件还需要单独执行一下命令。 ``` django-admin makemessages -d djangojs -l zh_Hans ``` 执行之后,就会得到两个翻译文件,`django.po`和`djangojs.po`。每个文件的条目大致如下。 ``` #: accounts/forms.py:11 msgid "gender" msgstr "性别" ``` 第一行注释是出现的位置,第二行是msgid,也就是原文,这个不能修改,第三行是msgstr,就是需要翻译的文本,如果有上下文,就会有`msgctxt`字段,这表示这段文字的上下文。当全部翻译完成之后,再执行命令得到翻译后的二进制文件。命令如下。 ``` django-admin compilemessages ``` 然后就会得到两个.mo文件,这就是翻译后的文件 。 ### 6. 切换语言 对于语言的切换,需要创建一个表单,然后向Django内置的setlang的url发带有`language`字段的数据即可,采用 POST方法。示例代码如下。 ``` <div class="lang-div dropdown-menu" role="menu"> {% get_current_language as LANGUAGE_CODE %} {% get_available_languages as LANGUAGES %} {% get_language_info_list for LANGUAGES as languages %} {% for language in languages %} <a class="lang-item btn {% if language.code == LANGUAGE_CODE %}btn-primary{% else %}btn-default{% endif %}" onclick="$('#lang_type').val('{{ language.code }}'); $('#lang_form').submit();"> {{ language.name_local }} </a> {% endfor %} </div> <form action="{% url 'set_language' %}" method="post" id="lang_form">{% csrf_token %} <input name="language" id="lang_type" type="hidden"> <input name="next" type="hidden" value="{{ redirect_to }}" /> </form> ``` 这种方法通过按钮触发向input元素传入值,再发送即可实现语言切换。 ## 四、总结 总之做国际化还是非常繁琐了,需要各种找,各种翻译,需要几天才能真正开发完。 [1]: https://docs.djangoproject.com/en/1.10/topics/i18n/translation/

Read More

和谐图片浏览以及实现方法

Satori

Author: Satori

Posted: 2018-02-11 22:33:02

Category: CSS Django HTML JavaScript 技术

Views: 2011 Comments: 0

## 一、开启关闭方法 在非文本框区域依次按键盘 **`左`** **`下`** **`右`** **`上`** 即可开启。开启后,再按同样按键退出。 ## 二、背景 这次的设计主要想法还是来自[忧郁的弟弟][1]网站的设计。他的设计方法是修改用户右键的点击来实现和谐模式的开启。然后和谐模式开启之后,鼠标的范围就会出现一个圈,和谐图片就只显示在圈内,然后通过移动鼠标来实现看图片的不同部分,然后再按鼠标即可退出。 ## 三、实现方法 这次的实现方法还是比较简单。首先后端传和谐图片相关的html代码,中间就包含和谐图片链接。具体实现方案还是按照Django的template tag方式实现,跟之前的背景图片相似。然后前端监听触发和谐模式开启按键,然后再监听用户鼠标移动事件来调整圈的位置以及圈内图片的位置。 ## 四、具体步骤 1. 后端返回和谐模块html 这个部分依然使用template tag实现,后端直接返回html代码。先调用系统接口获取和谐图片的个数以及种类,任意挑一个,把它的url塞到html里img的src中即可。这部分代码拷贝之前的,具体如下。 ``` @register.simple_tag def load_hidden_background(): image_show_count = 1 curdir = os.path.join(settings.MEDIA_ROOT, "background", "H") if os.path.exists(curdir): items = os.listdir(curdir) if len(items) >= image_show_count: selected = [items[i] for i in random.sample(range(len(items)), image_show_count)] urls = [settings.MEDIA_URL + "background/H/{}".format(x) for x in selected] return mark_safe(""" <div class="hidden-bg"> <div class="circle" style="background: url({}) no-repeat"></div> </div> """.format(*urls)) ``` 然后之后的template只需在中间加一个tag就行, ``` {% load_hidden_background %} ``` 这里还是聊一下返回的html,先有一个大的div叫hidden-bg,它填充整个页面,并置于页面最顶层,背景是灰色,目的是让之后的图片更醒目,初始状态不显示。hidden-div中嵌套新的div叫circle,这个就是圈,图片就在圈里面显示,图片显示方式是通过backgroud属性设置。圈通过border-radius使方块变圈。 同时附上这两个div的css。 ``` @media (min-width: 767px) { .hidden-bg { cursor: url(/static/mysite/images/mouse_none.cur), url(/static/mysite/images/mouse_none.cur), auto; } } .circle { width: 200px; height: 200px; position: fixed; border-radius: 100%; box-shadow: 0 0 0 3px rgba(255,255,255,0.85), 0 0 7px 7px rgba(0,0,0,0.25), inset 0 0 40px 2px rgba(0,0,0,0.25); } .hidden-bg { position: fixed; width: 100%; height: 100%; max-width: 100%; max-height: 100%; left: 0px; top: 0px; display: none; z-index: 1000001; opacity: 1; background: rgba(0, 0, 0, 0.8); } ``` 2. 前端监听按键触发 这个部分和之前讲过的完全一样,绑定一个按键组合即可。 ``` key_listener.sequence_combo("left down right up", toggle_hidden_background, true); ``` 3. 前端处理鼠标移动 这个部分是核心部分。要明确鼠标移动后要干两件事情,第一个是把圈的位置改动,第二个是确定图片的偏移。 圈的位置怎么动呢,应该是鼠标在哪里,圈的圆心就应该在哪里,同时鼠标移动我们能获得当前鼠标的位置。由于块的固定定位的目标在左上角,因此整个块的左偏移`left`属性应该是圆心位置减去半径长度,同理上偏移`top`也是一样。 图片怎么偏移呢,物理上讲运动是相对的。我们应该考虑,鼠标往左动相当于图片往右动,同理鼠标往下动就是图片往上动。由于要模拟看图片的不同位置,因此鼠标移动后的位置和图片的位置应该是相反数。这样,图片也能移动到正确的位置。 圈移动的位置和图片移动的位置配合好之后,就真的模拟出以孔窥图的效果了。这部分的代码如下。 ``` function toggle_hidden_background() { if ($(".hidden-bg").is(":visible")) { $(".hidden-bg").hide(); $(".hidden-bg").unbind('mousemove'); } else { $(".hidden-bg").bind('mousemove' ,function(e) { $(".circle").css({'left': (e.clientX - 100) + 'px'}); $(".circle").css({'top': (e.clientY - 100) + 'px'}); $(".circle").css({'background-position': -(e.clientX - 100) + 'px ' + -(e.clientY - 100) + 'px'}); }); $(".hidden-bg").show(); } } ``` 不过弟弟的网站实现和我不太一样,具体的逻辑没有摸透,它图片的偏移和圈的偏移还不完全是相反数,有一定偏差,不过我这种方案效果还可以,也就不深究了。 [1]: https://mygalgame.com

Read More

域名转让啦

Satori

Author: Satori

Posted: 2018-02-11 21:41:56

Category: 杂谈

Views: 833 Comments: 0

因为国内域名不备案的话不能弄转发记录,这就很坑爹,比如想弄一下子域名blog.chongliu.me都不能弄,非常不方便,因此准备把域名转让到GoDaddy上。转让域名最重要的是要向之前的服务商索要**域名转移密码**,之前看网上各种转让例子,发现有些运营商给转让增加难度,千方百计不让用户得到域名转移密码。这点腾讯做的还不错,进入域名管理之后就可以直接申请转出了,比较方便。 等转让成功了就可以试试子域名了[[Monkey 3.gif]]

Read More