前言

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

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

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

这个项目是前后端分离,后端为了快,依然用我的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. 查看docker程序使用的内存脚本

    #!/bin/bash # 找出所有运行的容器 idNames=`docker ps --format "{{.ID}}|{{.Names}},"` # 按,号分隔 OLD_IFS ...

  2. 搞透 IOC,Spring IOC 看这篇就够了!

    IOC与AOP属于Spring的核心内容,如果想掌握好Spring你肯定需要对IOC有足够的了解 @mikechen IOC的定义 IOC是Inversion of Control的缩写,多数书籍翻译 ...

  3. 插件化编程之WebAPI统一返回模型

    WebApi返回数据我们一般包裹在一个公共的模型下面的,而不是直接返回最终数据,在返回参数中,显示出当前请求的时间戳,是否请求成功,如果错误那么错误的消息是什么,状态码(根据业务定义的值)等等.我们常 ...

  4. Docker | 容器数据卷详解

    什么是容器数据卷 从docker的理念说起,docker将应用和环境打包成一个镜像,运行镜像(生成容器)就可以访问服务了. 如果数据都存在容器中,那么删除容器,数据就会丢失!需求:数据可以持久化 My ...

  5. python基础-较复杂数据类型预览

    1.初识列表   列表就是队列:   列表是一种有序的,且内容可重复的数据类型:   用list代表列表,也可以用list()定义一个列表,同时定义列表可以直接使用 [ ]:   python中列表是 ...

  6. 一篇文章带你了解热门版本控制系统——Git

    一篇文章带你了解热门版本控制系统--Git 这篇文章会介绍到关于版本控制的相关知识以及版本控制神器Git 我们可能在生活中经常会使用GitHub网页去查询一些开源的资源或者项目,GitHub就是基于G ...

  7. Application保存作用域

    Application保存作用域,作用范围:一次应用程序范围有效.Application属性范围值,只要设置一次,则所有的网页窗口都可以取得数据. ServletContext在服务器启动时创建,在服 ...

  8. 一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 41.spring的事务传播机制 42 .spring事务什么时候会失效 43 .什么的是bean的自动装配.有哪些方式? ...

  9. 齐博x1 二次开发的灵魂fun函数

    X1最强大之处,体现在灵活,扩展性强,在使用过程中,你会发现灵活之处非常之多. 现在跟大家讲一下,灵魂函数 fun() X1的核心函数文件是 application/common.php 随着模块频道 ...

  10. RabbitMQ安装说明文档(超详细版本)

    RabbitMQ安装说明文档(超详细版本) 1. 安装依赖环境 在线安装依赖环境: yum install build-essential openssl openssl-devel unixODBC ...