初始化
This commit is contained in:
commit
36f8ef7da9
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Local development
|
||||
.env
|
||||
.env.local
|
||||
*.db
|
||||
241
.venv/bin/Activate.ps1
Normal file
241
.venv/bin/Activate.ps1
Normal file
@ -0,0 +1,241 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
66
.venv/bin/activate
Normal file
66
.venv/bin/activate
Normal file
@ -0,0 +1,66 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="(.venv) ${PS1:-}"
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
25
.venv/bin/activate.csh
Normal file
25
.venv/bin/activate.csh
Normal file
@ -0,0 +1,25 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "(.venv) $prompt"
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
64
.venv/bin/activate.fish
Normal file
64
.venv/bin/activate.fish
Normal file
@ -0,0 +1,64 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/); you cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/Users/aaron/Desktop/code/deliveryman/.venv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) "(.venv) " (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
8
.venv/bin/pip
Executable file
8
.venv/bin/pip
Executable file
@ -0,0 +1,8 @@
|
||||
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
.venv/bin/pip3
Executable file
8
.venv/bin/pip3
Executable file
@ -0,0 +1,8 @@
|
||||
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
.venv/bin/pip3.9
Executable file
8
.venv/bin/pip3.9
Executable file
@ -0,0 +1,8 @@
|
||||
#!/Users/aaron/Desktop/code/deliveryman/.venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
1
.venv/bin/python
Symbolic link
1
.venv/bin/python
Symbolic link
@ -0,0 +1 @@
|
||||
python3
|
||||
1
.venv/bin/python3
Symbolic link
1
.venv/bin/python3
Symbolic link
@ -0,0 +1 @@
|
||||
/Library/Developer/CommandLineTools/usr/bin/python3
|
||||
1
.venv/bin/python3.9
Symbolic link
1
.venv/bin/python3.9
Symbolic link
@ -0,0 +1 @@
|
||||
python3
|
||||
3
.venv/pyvenv.cfg
Normal file
3
.venv/pyvenv.cfg
Normal file
@ -0,0 +1,3 @@
|
||||
home = /Library/Developer/CommandLineTools/usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.9.6
|
||||
13
app/api/deps.py
Normal file
13
app/api/deps.py
Normal file
@ -0,0 +1,13 @@
|
||||
from fastapi import Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.database import get_db
|
||||
from app.models.user import UserDB
|
||||
|
||||
async def get_current_user(
|
||||
phone: str,
|
||||
db: Session = Depends(get_db)
|
||||
) -> UserDB:
|
||||
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="用户未登录")
|
||||
return user
|
||||
132
app/api/endpoints/address.py
Normal file
132
app/api/endpoints/address.py
Normal file
@ -0,0 +1,132 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
from typing import List
|
||||
from app.models.address import AddressDB, AddressCreate, AddressUpdate, AddressInfo
|
||||
from app.models.database import get_db
|
||||
from app.api.deps import get_current_user
|
||||
from app.models.user import UserDB
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=AddressInfo)
|
||||
async def create_address(
|
||||
address: AddressCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""创建配送地址"""
|
||||
# 如果设置为默认地址,先将其他地址的默认状态取消
|
||||
if address.is_default:
|
||||
db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.user_id == current_user.userid,
|
||||
AddressDB.is_default == True
|
||||
)
|
||||
).update({"is_default": False})
|
||||
|
||||
db_address = AddressDB(
|
||||
user_id=current_user.userid,
|
||||
**address.model_dump()
|
||||
)
|
||||
db.add(db_address)
|
||||
db.commit()
|
||||
db.refresh(db_address)
|
||||
return db_address
|
||||
|
||||
@router.get("/", response_model=List[AddressInfo])
|
||||
async def get_addresses(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""获取用户的所有配送地址"""
|
||||
addresses = db.query(AddressDB).filter(
|
||||
AddressDB.user_id == current_user.userid
|
||||
).all()
|
||||
return addresses
|
||||
|
||||
@router.put("/{address_id}", response_model=AddressInfo)
|
||||
async def update_address(
|
||||
address_id: int,
|
||||
address: AddressUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""更新配送地址"""
|
||||
db_address = db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.id == address_id,
|
||||
AddressDB.user_id == current_user.userid
|
||||
)
|
||||
).first()
|
||||
|
||||
if not db_address:
|
||||
raise HTTPException(status_code=404, detail="地址不存在")
|
||||
|
||||
# 如果设置为默认地址,先将其他地址的默认状态取消
|
||||
update_data = address.model_dump(exclude_unset=True)
|
||||
if update_data.get("is_default"):
|
||||
db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.user_id == current_user.userid,
|
||||
AddressDB.is_default == True
|
||||
)
|
||||
).update({"is_default": False})
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(db_address, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_address)
|
||||
return db_address
|
||||
|
||||
@router.delete("/{address_id}")
|
||||
async def delete_address(
|
||||
address_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""删除配送地址"""
|
||||
result = db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.id == address_id,
|
||||
AddressDB.user_id == current_user.userid
|
||||
)
|
||||
).delete()
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="地址不存在")
|
||||
|
||||
db.commit()
|
||||
return {"message": "地址已删除"}
|
||||
|
||||
@router.post("/{address_id}/set-default", response_model=AddressInfo)
|
||||
async def set_default_address(
|
||||
address_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""设置默认地址"""
|
||||
# 取消其他默认地址
|
||||
db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.user_id == current_user.userid,
|
||||
AddressDB.is_default == True
|
||||
)
|
||||
).update({"is_default": False})
|
||||
|
||||
# 设置新的默认地址
|
||||
db_address = db.query(AddressDB).filter(
|
||||
and_(
|
||||
AddressDB.id == address_id,
|
||||
AddressDB.user_id == current_user.userid
|
||||
)
|
||||
).first()
|
||||
|
||||
if not db_address:
|
||||
raise HTTPException(status_code=404, detail="地址不存在")
|
||||
|
||||
db_address.is_default = True
|
||||
db.commit()
|
||||
db.refresh(db_address)
|
||||
return db_address
|
||||
72
app/api/endpoints/community.py
Normal file
72
app/api/endpoints/community.py
Normal file
@ -0,0 +1,72 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from app.models.community import CommunityDB, CommunityCreate, CommunityUpdate, CommunityInfo
|
||||
from app.models.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=CommunityInfo)
|
||||
async def create_community(
|
||||
community: CommunityCreate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建社区"""
|
||||
db_community = CommunityDB(**community.model_dump())
|
||||
db.add(db_community)
|
||||
db.commit()
|
||||
db.refresh(db_community)
|
||||
return db_community
|
||||
|
||||
@router.get("/", response_model=List[CommunityInfo])
|
||||
async def get_communities(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取社区列表"""
|
||||
communities = db.query(CommunityDB).offset(skip).limit(limit).all()
|
||||
return communities
|
||||
|
||||
@router.get("/{community_id}", response_model=CommunityInfo)
|
||||
async def get_community(
|
||||
community_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取社区详情"""
|
||||
community = db.query(CommunityDB).filter(CommunityDB.id == community_id).first()
|
||||
if not community:
|
||||
raise HTTPException(status_code=404, detail="社区不存在")
|
||||
return community
|
||||
|
||||
@router.put("/{community_id}", response_model=CommunityInfo)
|
||||
async def update_community(
|
||||
community_id: int,
|
||||
community: CommunityUpdate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新社区信息"""
|
||||
db_community = db.query(CommunityDB).filter(CommunityDB.id == community_id).first()
|
||||
if not db_community:
|
||||
raise HTTPException(status_code=404, detail="社区不存在")
|
||||
|
||||
update_data = community.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(db_community, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_community)
|
||||
return db_community
|
||||
|
||||
@router.delete("/{community_id}")
|
||||
async def delete_community(
|
||||
community_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除社区"""
|
||||
result = db.query(CommunityDB).filter(CommunityDB.id == community_id).delete()
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="社区不存在")
|
||||
|
||||
db.commit()
|
||||
return {"message": "社区已删除"}
|
||||
92
app/api/endpoints/user.py
Normal file
92
app/api/endpoints/user.py
Normal file
@ -0,0 +1,92 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.user import UserLogin, UserInfo, VerifyCodeRequest, UserDB
|
||||
from app.models.database import get_db
|
||||
import random
|
||||
import string
|
||||
import redis
|
||||
from app.core.config import settings
|
||||
from unisdk.sms import UniSMS
|
||||
from unisdk.exception import UniException
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Redis 连接
|
||||
redis_client = redis.Redis(
|
||||
host=settings.REDIS_HOST,
|
||||
port=settings.REDIS_PORT,
|
||||
db=settings.REDIS_DB,
|
||||
password=settings.REDIS_PASSWORD,
|
||||
decode_responses=True
|
||||
)
|
||||
|
||||
# 初始化短信客户端
|
||||
client = UniSMS(settings.UNI_APP_ID)
|
||||
|
||||
@router.post("/send-code")
|
||||
async def send_verify_code(request: VerifyCodeRequest):
|
||||
"""发送验证码"""
|
||||
phone = request.phone
|
||||
code = ''.join(random.choices(string.digits, k=6))
|
||||
|
||||
try:
|
||||
# 发送短信
|
||||
res = client.send({
|
||||
"to": phone,
|
||||
"signature": settings.UNI_SMS_SIGN,
|
||||
"templateId": settings.UNI_SMS_TEMPLATE_ID,
|
||||
"templateData": {
|
||||
"code": code
|
||||
}
|
||||
})
|
||||
|
||||
if res.code != "0": # 0 表示发送成功
|
||||
raise HTTPException(status_code=500, detail=f"短信发送失败: {res.message}")
|
||||
|
||||
# 存储验证码到 Redis
|
||||
redis_client.setex(
|
||||
f"verify_code:{phone}",
|
||||
settings.VERIFICATION_CODE_EXPIRE_SECONDS,
|
||||
code
|
||||
)
|
||||
|
||||
return {"message": "验证码已发送"}
|
||||
|
||||
except UniException as e:
|
||||
raise HTTPException(status_code=500, detail=f"发送验证码失败: {str(e)}")
|
||||
|
||||
@router.post("/login")
|
||||
async def login(user_login: UserLogin, db: Session = Depends(get_db)):
|
||||
"""用户登录"""
|
||||
phone = user_login.phone
|
||||
verify_code = user_login.verify_code
|
||||
|
||||
# 验证验证码
|
||||
stored_code = redis_client.get(f"verify_code:{phone}")
|
||||
if not stored_code or stored_code != verify_code:
|
||||
raise HTTPException(status_code=400, detail="验证码错误或已过期")
|
||||
|
||||
redis_client.delete(f"verify_code:{phone}")
|
||||
|
||||
# 查找或创建用户
|
||||
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||
if not user:
|
||||
# 创建新用户
|
||||
user = UserDB(
|
||||
username=f"user_{phone[-4:]}",
|
||||
phone=phone
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
return {"message": "登录成功", "user": UserInfo.model_validate(user)}
|
||||
|
||||
@router.get("/info")
|
||||
async def get_user_info(phone: str, db: Session = Depends(get_db)):
|
||||
"""获取用户信息"""
|
||||
user = db.query(UserDB).filter(UserDB.phone == phone).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
return UserInfo.model_validate(user)
|
||||
39
app/core/config.py
Normal file
39
app/core/config.py
Normal file
@ -0,0 +1,39 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
class Settings(BaseSettings):
|
||||
API_V1_STR: str = "/api/v1"
|
||||
PROJECT_NAME: str = "FastAPI 项目"
|
||||
|
||||
# 数据库配置
|
||||
DATABASE_URL: str = "sqlite:///./sql_app.db"
|
||||
|
||||
# JWT 配置
|
||||
SECRET_KEY: str = "your-secret-key-here"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
|
||||
REDIS_HOST: str = "101.36.120.145"
|
||||
REDIS_PORT: int = 6379
|
||||
REDIS_DB: int = 0
|
||||
REDIS_PASSWORD: str = "redis_rJRMHr"
|
||||
VERIFICATION_CODE_EXPIRE_SECONDS: int = 300 # 验证码5分钟后过期
|
||||
|
||||
# 短信配置
|
||||
UNI_APP_ID: str = "kFb5kA5EDXpnzUReadaRNpDTFf6rNmXEc45jwS2C1Mvh9Erj2"
|
||||
UNI_SMS_TEMPLATE_ID: str = "pub_verif_basic" # 验证码短信模板ID
|
||||
UNI_SMS_SIGN: str = "BAISIJI" # 短信签名
|
||||
|
||||
# MySQL配置
|
||||
MYSQL_HOST: str = "101.36.120.145"
|
||||
MYSQL_PORT: int = 3306
|
||||
MYSQL_USER: str = "root"
|
||||
MYSQL_PASSWORD: str = "mariadb_4rMwpT"
|
||||
MYSQL_DB: str = "deliveryman"
|
||||
|
||||
@property
|
||||
def SQLALCHEMY_DATABASE_URL(self) -> str:
|
||||
return f"mysql+pymysql://{self.MYSQL_USER}:{self.MYSQL_PASSWORD}@{self.MYSQL_HOST}:{self.MYSQL_PORT}/{self.MYSQL_DB}?charset=utf8mb4"
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
settings = Settings()
|
||||
35
app/main.py
Normal file
35
app/main.py
Normal file
@ -0,0 +1,35 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api.endpoints import user, address, community
|
||||
from app.models.database import Base, engine
|
||||
|
||||
# 创建数据库表
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(
|
||||
title="闪兔到家",
|
||||
description="API 文档",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# 配置 CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 添加用户路由
|
||||
app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
||||
app.include_router(address.router, prefix="/api/address", tags=["配送地址"])
|
||||
app.include_router(community.router, prefix="/api/community", tags=["社区"])
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "欢迎使用 FastAPI!"}
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy"}
|
||||
45
app/models/address.py
Normal file
45
app/models/address.py
Normal file
@ -0,0 +1,45 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
|
||||
# 数据库模型
|
||||
class AddressDB(Base):
|
||||
__tablename__ = "delivery_addresses"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.userid"), index=True)
|
||||
community_id = Column(Integer, index=True)
|
||||
address_detail = Column(String(200))
|
||||
name = Column(String(50))
|
||||
phone = Column(String(11))
|
||||
is_default = Column(Boolean, default=False)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class AddressCreate(BaseModel):
|
||||
community_id: int
|
||||
address_detail: str = Field(..., max_length=200)
|
||||
name: str = Field(..., max_length=50)
|
||||
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||
is_default: bool = False
|
||||
|
||||
class AddressUpdate(BaseModel):
|
||||
community_id: Optional[int] = None
|
||||
address_detail: Optional[str] = Field(None, max_length=200)
|
||||
name: Optional[str] = Field(None, max_length=50)
|
||||
phone: Optional[str] = Field(None, pattern="^1[3-9]\d{9}$")
|
||||
is_default: Optional[bool] = None
|
||||
|
||||
class AddressInfo(BaseModel):
|
||||
id: int
|
||||
community_id: int
|
||||
address_detail: str
|
||||
name: str
|
||||
phone: str
|
||||
is_default: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
40
app/models/community.py
Normal file
40
app/models/community.py
Normal file
@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
|
||||
# 数据库模型
|
||||
class CommunityDB(Base):
|
||||
__tablename__ = "communities"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
address = Column(String(200), nullable=False)
|
||||
longitude = Column(Float, nullable=False) # 经度
|
||||
latitude = Column(Float, nullable=False) # 纬度
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class CommunityCreate(BaseModel):
|
||||
name: str = Field(..., max_length=100)
|
||||
address: str = Field(..., max_length=200)
|
||||
longitude: float = Field(..., ge=-180, le=180)
|
||||
latitude: float = Field(..., ge=-90, le=90)
|
||||
|
||||
class CommunityUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, max_length=100)
|
||||
address: Optional[str] = Field(None, max_length=200)
|
||||
longitude: Optional[float] = Field(None, ge=-180, le=180)
|
||||
latitude: Optional[float] = Field(None, ge=-90, le=90)
|
||||
|
||||
class CommunityInfo(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
address: str
|
||||
longitude: float
|
||||
latitude: float
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
25
app/models/database.py
Normal file
25
app/models/database.py
Normal file
@ -0,0 +1,25 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
import pymysql
|
||||
|
||||
# 注册 MySQL Python SQL Driver
|
||||
pymysql.install_as_MySQLdb()
|
||||
|
||||
engine = create_engine(
|
||||
settings.SQLALCHEMY_DATABASE_URL,
|
||||
pool_pre_ping=True, # 自动处理断开的连接
|
||||
pool_recycle=3600, # 连接回收时间
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
# 依赖项
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
30
app/models/user.py
Normal file
30
app/models/user.py
Normal file
@ -0,0 +1,30 @@
|
||||
from sqlalchemy import Column, String, DateTime,Integer
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
|
||||
# 数据库模型
|
||||
class UserDB(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
userid = Column(Integer, primary_key=True,autoincrement=True, index=True)
|
||||
username = Column(String(50))
|
||||
phone = Column(String(11), unique=True, index=True)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class UserLogin(BaseModel):
|
||||
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||
verify_code: str = Field(..., min_length=6, max_length=6)
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
userid: int
|
||||
username: str
|
||||
phone: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class VerifyCodeRequest(BaseModel):
|
||||
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||
15
project_structure
Normal file
15
project_structure
Normal file
@ -0,0 +1,15 @@
|
||||
my_fastapi_project/
|
||||
├── app/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── endpoints/
|
||||
│ │ └── __init__.py
|
||||
│ ├── core/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── config.py
|
||||
│ └── models/
|
||||
│ └── __init__.py
|
||||
├── requirements.txt
|
||||
└── README.md
|
||||
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@ -0,0 +1,12 @@
|
||||
fastapi>=0.68.0
|
||||
uvicorn>=0.15.0
|
||||
pydantic>=2.0.0
|
||||
pydantic-settings>=2.0.0
|
||||
python-jose>=3.3.0
|
||||
passlib>=1.7.4
|
||||
python-multipart>=0.0.5
|
||||
sqlalchemy>=1.4.23
|
||||
redis==5.0.1
|
||||
pymysql==1.1.0
|
||||
SQLAlchemy==2.0.27
|
||||
unisms
|
||||
Loading…
Reference in New Issue
Block a user