前言

最近有个项目到一段落,做个小结记录。

内容可能会多次补充,在博客上实时更新哈~

如果是在公众号阅读这篇文章,可以点击「查看原文」访问最新版本~

这个项目是前后端分离,后端为了快,依然用我的DjangoStarter框架。前端一开始是小程序,后面突然换成公众号H5的形式,还好我用了Taro,大差不差。

不过Taro目前没啥好用成熟的组件库,前一个项目本来用着Taroify,不过用了一半项目还没做完,Taroify的作者就跑路不维护了~ 虽然但是,还是能用,把旧项目的一些代码复用一下,也不是不行。

总体的开发体验就是很一般,虽说React写前端舒服多了,但组件库实在是拉胯… 如果下个项目依然要用Taro的话,估计得试试新出的NutUI-React了。

说回正题,这次我从Web开发、部署这几方面对这个项目做个小总结。

后端

后端用DjangoStarter模板,自从我上次升级了v2版本之后,还没实战过,这次项目上使用了,还是稳得一批,(所以点star的同学可以放心用哈~)

之前oauth部分只有企业微信,微信登录还是todo,这次因为接入公众号,我顺手也把微信登录做了,其实跟企业微信基本没啥区别。

"双标"的ModelViewSet

drf的ModelViewSet,可以快速生成crud接口,不过默认权限控制很粗糙,只能选择三种:

  • 已登录可访问
  • 管理员可访问
  • 任何人可访问

但正常的场景是,假设有个文章接口,用户只能管理自己写的文章,管理员可以访问全部文章。也就是要对不同角色的用户区别对待~

要实现的话可以这样,重写 ModelViewSetget_queryset 方法,根据用户的身份来生成对应 queryset

def get_queryset(self):
user: User = self.request.user
if user.is_superuser:
return super().get_queryset()
else:
return super().get_queryset().filter(user=user)

然后再添加和更新的时候也要修改一下

比如重写一下 create 方法,最前面加上当前用户的id

def create(self, request: Request, *args, **kwargs):
request.data['user'] = request.user.id
# 后面代码就省略了

Model Field 扩展

本次用了两个扩展

  • tinymce 的 HTMLField : 用于富文本编辑,也就是前面说的文章功能
  • MultiSelectField :用于多选字段(虽然Django+PgSql可以保存列表数据,但跟这好像俩回事)

tinymce之前的文章有介绍过,我还封装了一个 contrib 包,后面有空集成到DjangoStarter里面

MultiSelectField使用也简单,可以把一个 Choices 作为字段的值,在Django Admin里面,表现为一个多选列表,编辑和使用都比较方便~

一些架构设计的问题

本来在做DjangoStarter v2版本的时候,我把相关的代码都放在 django_starter 包里,就是为了开发者不需要去修改这部分代码,这样在DjangoStarter版本有更新的时候,可以直接覆盖升级。

但我又把 oauth 和 UserProfile (用户信息)所在的 auth 这两个app都放在了 django_starter/contrib 包里面。

但往往一个项目中,难免会对用户信息做一些扩展,这样就得修改到这个 django_starter 下面的代码,这不符合设计规范~

这部分也是我在v2版本设计的问题,看来可能要把这个用户信息相关的代码都放回到 apps 下面,让用户(开发者)自行决定这部分代码是否使用。

前端

前端使用React+TypeScript,开发体验还可以,尽管之前经常吐槽TypeScript,但熟悉之后还是能愉快使用的,毕竟和C#同一个作者,质量有保障~

虽说Taro坑很多,组件库质量也不高

但… 没有的组件,就自己造轮子!

好吧,在造轮子这件事上,我把自己坑了一下… 我自己做了个日历组件,不得不说,日历组件确实有点小复杂,项目开发过程中,这组件就出了两次坑爹的bug,花了我不少时间折腾~

说回来,就用Taro提供的最基本的 view 组件,再配合scss,可以说组件库里缺什么,自己造什么,虽然我不是专门做前端的,样式写得很菜,但… 勉强能看吧

我感觉React用上手了比vue舒服一些(非引战),可能跟我之前经常用Flutter习惯了声明式UI有关~

掏出几个常用的hook(useEffect / useRef / useRouter),开发体验很丝滑。

这次我还多学了一个 useLayoutEffect,用来解决页面闪烁。

全局状态管理没用redux,改用轻量级mobx,舒服~ 不过除了用户管理,其他的基本上可以用路由传参解决,全局状态用得很少。

路由管理

好消息,这次我终于没有手写路由地址了

终于搞了个 RouterMap

export const RouterMap = {
announcementDetail: 'pages/announcement/detail',
announcementList: 'pages/announcement/index',
home: '/pages/index/index',
feedback: 'pages/user/feedback',
login: '/pages/user/login',
order: 'pages/user/order'
}

需要跳转的时候就

Taro.navigateTo({url: RouterMap.login})

不过在必须登录才可以访问的页面上,我还是用最原始的判断跳转,很不优雅

useEffect(() => {
if (!myUserStore.isLogin) {
Taro.redirectTo({url: RouterMap.login})
return
}
}, [])

看了「前端带师」的 remax-router,对路由做了hack,直接在框架路由处做拦截,真羡慕啊,等会学会这个操作我也要这样做。

多写组件

虽然我自己造轮子埋了不少坑,但还是鼓励多用组件,现代前端就是组件化开发嘛,都写在一个页面也太丑了,都给我拆成组件!

于是,我的src目录下就有俩放组件的目录,一个是 components ,一个是 ui

前者放只在本项目内用的组件,后者放通用组件,可复用的那种,以后有空做成NPM包的那种。

组件间的通信很方便,父组件向子组件传递,直接props传值;子到父,直接在props里定义个事件就行了。

比如我这个日历组件

export interface CalendarSmallProps extends ViewProps {
days: Array<DayPlan>
value?: Date onChange?(value: DoctorDayPlan): void
}

父组件使用的时候

<CalendarSmall days={days} value={currentDate} onChange={handleDayChange}/>

日历组件向父组件传值,也就是触发事件

function setDay(item: DoctorDayPlan) {
setValue(item.date)
props.onChange?.(item)
}

咱就是说,这个 xxx?.() 的语法真是妙 (连「前端带师(coppy)」都赞不绝口,能不妙吗?)

然后每个组件建立个目录,比如这个日历组件,我放在 ui/calendar_small 下。俩个文件:

  • index.tsx :主要代码
  • index.scss :样式

然后在 ui 目录下再来个 index.ts

里面导出一下

export * from './calendar_small'

这样在使用的时候只要 import {CalendarSmall} from "@/ui"; 即可,方便得很啊!

部署

前面写了那么多,我都差点忘了部署才是本次项目重点想记录的。

前段时间我买了个新域名 dealiaxy.com,新项目也搞了个新的服务器,这次部署想实现的效果是 *.dealiaxy.com 泛域名解析,且全部走HTTPS。

之前看同学博客的时候发现有个叫swag的镜像,把 Let's Encrypt 都折腾配置好了,开箱即用,这次来试试看。

使用swag配置HTTPS

因为这是我第一次用swag镜像部署 Let's Encrypt 的泛域名HTTPS,遇到挺多坑的,也查了很多资料,最终完美搞定~

很多时候虽然文档很齐备,但因为各种条件不一致,很难一下子搞起来。

首先在域名控制台添加A记录的解析,把 @* 都指向这台服务器,然后准备个空目录来部署swag容器。

docker 部署

继续用docker-compose,有几个关键配置。

  • Let's Encrypt 有多种验证方式,因为我要用泛域名证书,所以配置 VALIDATION 为 dns 方式
  • 时区 TZ 设置为 Asia/Shanghai
  • 子域名 SUBDOMAINS 设置为 wildcard (通配符)
  • DNSPLUGIN 是DNS提供商,是配置重点,后面说
  • 挂载一下 /config 目录,后面swag跑起来之后需要在里面配置域名和网站信息
version: "2"
services:
swag:
image: linuxserver/swag
container_name: swag
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- URL=dealiaxy.com
- SUBDOMAINS=wildcard
- VALIDATION=dns
- DNSPLUGIN=cloudflare
volumes:
- ./config:/config
ports:
- 443:443
- 80:80
restart: unless-stopped
networks:
default:
name: swag

DNSPLUGIN 配置

swag支持很多DNS提供商,比如阿里云、腾讯云、cloudflare这些。具体的可以看 config/dns-conf 里面的配置。

我这个域名是国外买的,恰好那家服务商也没有在swag的支持列表里面,一开始还有点晕头转向不知道咋办,后面看到swag支持阿里和腾讯的dnspod,于是我在阿里DNS上看了一下,可以配置解析,瞬间悟了,域名在哪买的不重要,域名的DNS提供商可以随便换的。

根据阿里DNS的指引,在域名控制台里面把Name Server改成阿里的 ns1.alidns.comns2.alidns.com 就行了。

然后在阿里云的控制台里生成一下 access_key 和 secret,编辑 config/dns-conf/aliyun.ini 放进去,再启动swag容器就行了。

tips:阿里云DNS需要域名有备案才提供解析,未备案的话慎用~ 可以试试Cloudflare,据说很好用。

用其他的DNS提供商同理,操作很类似。

docker 网络配置

docker容器直接默认是不能直接连接的,所以反向代理也就无从说起。

swag和后端是俩不同的docker容器,要能互相连接,得先加入同一docker网络才行。

推荐portainer这个工具,可以很方便管理docker~

使用docker-compose启动swag,会自动生成一个swag_default的网络,拿这个来用就行了,我先把它改名成swag,方便记忆。

然后再修改一下后端的docker-compose配置,增加网络配置

networks:
swag:
external:
name: swag

然后,我这个docker-compose里有redis和django两个容器,只有django需要加入swag,所以在django下面配置一下网络

web:
networks:
- swag
- default

这样就行了~ (当然我后面还要再改一下,这样写只是方便理解)

反向代理配置

泛域名证书配置搞定了,接下来可以配置网站

静态文件放在 config/www 里面

后端需要做反向代理,配置在 config/nginx/proxy-confs 里面

这里面有个比较难受的地方,swag默认提供了一堆反向代理的模板(文件名 .example 结尾),这个目录一打开里面一堆文件,很影响我找到我已经配置好的,解决办法是 ls 的时候用正则匹配一下文件名。

ll | grep .conf$

这样就只显示以 .conf 结尾的文件了。

假设我的应用域名是 app1.dealiaxy.com ,那配置文件名就是 app1.subdomain.conf

附上我的反向代理配置:

server {
listen 443 ssl;
listen [::]:443 ssl; server_name app1.*; include /config/nginx/ssl.conf; client_max_body_size 0; # enable for ldap auth, fill in ldap details in ldap.conf
#include /config/nginx/ldap.conf; # enable for Authelia
#include /config/nginx/authelia-server.conf; location / {
# enable the next two lines for http auth
#auth_basic "Restricted";
#auth_basic_user_file /config/nginx/.htpasswd; # enable the next two lines for ldap auth
#auth_request /auth;
#error_page 401 =200 /ldaplogin; # enable for Authelia
#include /config/nginx/authelia-location.conf; include /config/nginx/proxy.conf;
resolver 127.0.0.11 valid=30s;
set $upstream_app app1_nginx;
set $upstream_port 8001;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port; }
}

主要就看这几行:

set $upstream_app app1_nginx; # 容器名称
set $upstream_port 8001; # 容器端口,容器里面开启的端口,不是通过 ports 映射的
set $upstream_proto http; # 协议,还有其他比如 uwsgi, https 之类的

再看看接下来的Django部署,就会一目了然了~

Django部署

Django部署依然是用之前很熟悉的docker部署,不过这次我又做了一些修改。

之前是一个nginx服务直接装在系统上,若干个docker容器跑服务,这种情况下每个容器只需要提供web应用功能,不用管静态文件,直接在nginx里面配置静态文件就行了。

但是现在,nginx也装进了docker(swag),那就没办法随意访问到整个系统的文件,如果每增加一个应用,都去挂载一个新的volume到swag里,那也太折腾了。

所以我选择在Django的docker-compose里集成nginx。

docker-compose.yaml

version: "3"
services:
redis:
image: redis
container_name: app1_redis
restart: always
nginx:
image: nginx:stable-alpine
container_name: app1_nginx
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./media:/code/media:ro
- ./static_collected:/code/static_collected:ro
depends_on:
- web
networks:
- default
- swag
web:
build: .
container_name: app1_web
restart: always
environment:
- ENVIRONMENT=docker
- URL_PREFIX=
- DEBUG=false
command: uwsgi uwsgi.ini
volumes:
- .:/code
depends_on:
- redis
networks:
- default networks:
swag:
external:
name: swag

就是在DjangoStarter原有docker-compose配置的基础上增加了nginx的配置,使用官方的nginx镜像: https://hub.docker.com/_/nginx

nginx.conf

上面的uwsgi.ini没贴出来,也没啥好说的,里面开放的端口是8000,所以nginx配置里面 upstream 写的端口要对应 8000。

upstream django {
ip_hash;
server web:8000; # Docker-compose web服务端口 (也就是uwsgi的端口)
} server {
listen 8001; # 监听8001端口
server_name localhost; # 可以是nginx容器所在ip地址或127.0.0.1,不能写宿主机外网ip地址 charset utf-8;
client_max_body_size 100M; # 限制用户上传文件大小 location /static {
alias /code/static_collected; # 静态资源路径
} location /media {
alias /code/media; # 媒体资源,用户上传文件路径
} location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass django;
uwsgi_read_timeout 600;
uwsgi_connect_timeout 600;
uwsgi_send_timeout 600; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
}
} access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn; server_tokens off;

可以看到这个django应用内嵌的nginx配置开启的8001端口

再回看上面的swag反向代理配置

set $upstream_app app1_nginx;
set $upstream_port 8001;
set $upstream_proto http;

就对应上了

这样配置之后,docker compose up 启动swag,再访问 app1.dealiaxy.com 就可以了~

全站HTTPS太舒服了,浏览器再也不太提示不安全了~

权限问题

可以留意到swag的docker-compose配置里面有俩环境变量,PUIDPGID,swag内部都配置好了,指定了这俩,容器启动的时候就会以指定的用户和用户组运行,而不是默认以root运行,这样会安全一些,而且挂载了 volume 出来的文件,也不是root权限,当前登录用户不用 sudo 就能修改。

在 django 的docker里加入nginx的时候我有尝试改成不用root运行,根据官方指引使用了 nginxinc/nginx-unprivileged 这个镜像,也测试了在docker-compose配置里传入 user 参数,好像都没什么效果。

折腾了半天只好暂时放弃,后续有进展再继续更新。

小结

这次项目说实在的没啥技术含量,CRUD罢了,收获的话就一点点:

  • 又熟悉了一些react的写法
  • 把swag配好了,以后其他服务器可以依样画葫芦,极大提高生产力

参考资料

项目完成小结 - Django-React-Docker-Swag部署配置的更多相关文章

  1. 项目完成小结 - Django3.x版本 - 开发部署小结 (2)

    前言 好久没更新博客了,最近依然是在做之前博客说的这个项目:项目完成 - 基于Django3.x版本 - 开发部署小结 这项目因为前期工作出了问题,需求没确定好,导致了现在要做很多麻烦的工作,搞得大家 ...

  2. docker中部署django项目~~Dockfile方式和compose方式

    1.  背景:   本机win10上,后端django框架代码与前端vue框架代码联调通过. 2.  目的:   在centos7系统服务器上使用docker容器部署该项目. 3.  方案一:仅使用基 ...

  3. django中的setting最佳配置小结

    Django settings详解 1.基础 DJANGO_SETTING_MODULE环境变量:让settings模块被包含到python可以找到的目录下,开发情况下不需要,我们通常会在当前文件夹运 ...

  4. Docker如何部署Python项目

    Docker 部署Python项目 作者:白宁超 2019年5月24日09:09:00 导读: 软件开发最大的麻烦事之一就是环境配置,操作系统设置,各种库和组件的安装.只有它们都正确,软件才能运行.如 ...

  5. 在Docker下部署Nginx

    在Docker下部署Nginx 在Docker下部署Nginx,包括: 部署一个最简单的Nginx,可以通过80端口访问默认的网站 设置记录访问和错误日志的路径 设置静态网站的路径 通过proxy_p ...

  6. 使用docker安装部署Spark集群来训练CNN(含Python实例)

    使用docker安装部署Spark集群来训练CNN(含Python实例) http://blog.csdn.net/cyh_24/article/details/49683221 实验室有4台神服务器 ...

  7. Ubuntu Docker 安装和配置 GitLab CI 持续集成

    相关文章: Ubuntu Docker 简单安装 GitLab 劈荆斩棘:Gitlab 部署 CI 持续集成 目的:在 Ubuntu 服务器上,使用 Docker 安装和配置 GitLab Runne ...

  8. 基于docker 如何部署surging分布式微服务引擎

    1.前言 转眼间surging 开源已经有1年了,经过1年的打磨,surging已从最初在window 部署的分布式微服务框架,到现在的可以在docker部署利用rancher 进行服务编排的分布式微 ...

  9. 从头认识一下docker-附带asp.net core程序的docker化部署

    从头认识一下docker-附带asp.net core程序的docker化部署 简介 在计算机技术日新月异的今天, Docker 在国内发展的如火如荼,特别是在一线互联网公司, Docker 的使用是 ...

  10. Django Windows环境下部署

    环境准备 本文将介绍如何在Windows系统上部署Django web项目,本次部署基于下面的架构: Windows10 64位+Python3.6+Django1.11+Apache2.4+mod_ ...

随机推荐

  1. k8s更换网络插件:从flannel更换成calico

    卸载flannel 查看已安装的flannel的信息 # 查看CNI插件,可以得知使用的是flannel # cat /etc/cni/net.d/10-flannel.conflist { &quo ...

  2. Logstash:在 Docker 中部署 Logstash

    文章转载自:https://elasticstack.blog.csdn.net/article/details/116516923 创建一个目录 docker-logstash.在该目录下,有如下的 ...

  3. [题解] Atcoder ABC 225 H Social Distance 2 生成函数,分治FFT

    题目 首先还没有安排座位的\(m-k\)个人之间是有顺序的,所以先把答案乘上\((m-k)!\),就可以把这些人看作不可区分的. 已经确定的k个人把所有座位分成了k+1段.对于第i段,如果我们能求出这 ...

  4. python+request+pymysql+pytest数据驱动

    一.pymysql简单使用 1.安装mysql 下载地址:https://www.mysql.com/,安装教程这里不做介绍了,网上一大推. 2.安装pymysql库 在Terminal终端输入:pi ...

  5. 详解ROMA Connect API 流控实现技术

    摘要:本文将详细描述API Gateway流控实现,揭开高性能秒级流控的技术细节. 1.概述 ROMA平台的核心系统ROMA Connect源自华为流程IT的集成平台,在华为内部有超过15年的企业业务 ...

  6. Mysql单表访问方法,索引合并,多表连接原理,基于规则的优化,子查询优化

    参考书籍<mysql是怎样运行的> 非常推荐这本书,通俗易懂,但是没有讲mysql主从等内容 书中还讲解了本文没有提到的子查询优化内容, 本文只总结了常见的子查询是如何优化的 系列文章目录 ...

  7. 图解 | 聊聊 MyBatis 缓存

    首发公众号-悟空聊架构:图解 | 聊聊 MyBatis 缓存 你好,我是悟空. 本文主要内容如下: 一.MyBatis 缓存中的常用概念 MyBatis 缓存:它用来优化 SQL 数据库查询的,但是可 ...

  8. 常用RE对照表——敬请期待!!!

    .* #任意长度任意字符

  9. MasaFramework -- 缓存入门与设计

    概念 什么是缓存,在项目中,为了提高数据的读取速度,我们会对不经常变更但访问频繁的数据做缓存处理,我们常用的缓存有: 本地缓存 内存缓存:IMemoryCache 分布式缓存 Redis: Stack ...

  10. Win环境安装Protobuf 2.0 版本

    转载请注明出处: 安装步骤 下载 protobuf-2.5.0.zip 与 protoc-2.5.0-win32.zip 下载链接 : https://github.com/protocolbuffe ...