feat: Iteration 1 — 审计日志IP记录、文件上传、医护端API、小程序角色切换
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

Iteration 1 六项任务全部完成:

1. 审计日志IP记录 — task_local RequestInfo 自动注入 IP/user_agent
2. 文件上传服务 — multipart 上传 + ServeDir 静态文件服务
3. 医护端后端API — 医生工作台仪表盘 + 患者标签CRUD + 会话已读
4. 小程序角色切换 — 登录后根据角色跳转医护台/患者首页
5. 小程序安全加固 — secure-storage 开发模式警告
6. 讨论记录归档 — docs/discussions/
This commit is contained in:
iven
2026-04-26 13:13:25 +08:00
parent 1326b3e504
commit a0b72b0f73
21 changed files with 679 additions and 12 deletions

View File

@@ -12,7 +12,8 @@ interface BindPhoneResp {
interface AuthState {
token: string | null;
refreshToken: string | null;
user: { id: string; username: string; display_name?: string; phone?: string } | null;
user: { id: string; username: string; display_name?: string; phone?: string; tenant_id?: string } | null;
roles: string[];
currentPatient: authApi.PatientInfo | null;
patients: authApi.PatientInfo[];
loading: boolean;
@@ -23,22 +24,30 @@ interface AuthState {
loadPatients: () => Promise<void>;
logout: () => void;
restore: () => void;
isMedicalStaff: () => boolean;
}
export const useAuthStore = create<AuthState>((set, get) => ({
token: null,
refreshToken: null,
user: null,
roles: [],
currentPatient: null,
patients: [],
loading: false,
isMedicalStaff: () => {
const { roles } = get();
return roles.some((r) => r === 'doctor' || r === 'nurse' || r === 'admin');
},
restore: () => {
const token = secureGet('access_token') || null;
const refreshToken = secureGet('refresh_token') || null;
const user = Taro.getStorageSync('user') || null;
const roles = Taro.getStorageSync('user_roles') || [];
const currentPatient = Taro.getStorageSync('current_patient') || null;
set({ token, refreshToken, user, currentPatient });
set({ token, refreshToken, user, roles, currentPatient });
},
login: async (code: string) => {
@@ -47,11 +56,13 @@ export const useAuthStore = create<AuthState>((set, get) => ({
const resp = await authApi.wechatLogin(code);
if (resp.bound && resp.token) {
const { access_token, refresh_token, user } = resp.token;
const roles = (resp as any).roles?.map((r: any) => r.code || r.name || r) || [];
secureSet('access_token', access_token);
secureSet('refresh_token', refresh_token);
Taro.setStorageSync('user', user);
Taro.setStorageSync('user_roles', roles);
Taro.setStorageSync('tenant_id', (user as any).tenant_id || '');
set({ token: access_token, refreshToken: refresh_token, user, loading: false });
set({ token: access_token, refreshToken: refresh_token, user, roles, loading: false });
return true;
}
Taro.setStorageSync('wechat_openid', resp.openid);
@@ -71,14 +82,16 @@ export const useAuthStore = create<AuthState>((set, get) => ({
set({ loading: false });
return false;
}
const resp = await authApi.wechatBindPhone(openid, encryptedData, iv) as BindPhoneResp;
const resp = await authApi.wechatBindPhone(openid, encryptedData, iv) as any;
const { access_token, refresh_token, user } = resp;
const roles = resp.roles?.map((r: any) => r.code || r.name || r) || [];
secureSet('access_token', access_token);
secureSet('refresh_token', refresh_token);
Taro.setStorageSync('user', user);
Taro.setStorageSync('user_roles', roles);
Taro.setStorageSync('tenant_id', user.tenant_id || '');
Taro.removeStorageSync('wechat_openid');
set({ token: access_token, refreshToken: refresh_token, user, loading: false });
set({ token: access_token, refreshToken: refresh_token, user, roles, loading: false });
return true;
} catch {
set({ loading: false });
@@ -108,9 +121,10 @@ export const useAuthStore = create<AuthState>((set, get) => ({
secureRemove('access_token');
secureRemove('refresh_token');
Taro.removeStorageSync('user');
Taro.removeStorageSync('user_roles');
Taro.removeStorageSync('current_patient');
Taro.removeStorageSync('current_patient_id');
set({ token: null, refreshToken: null, user: null, currentPatient: null, patients: [] });
set({ token: null, refreshToken: null, user: null, roles: [], currentPatient: null, patients: [] });
Taro.redirectTo({ url: '/pages/login/index' });
},
}));