刘帅
2026-09-22 39fd035073756bf45c6189c79b6943005a549666
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package com.dsh.guns.modular.system.controller.system;
 
import com.dsh.course.feignClient.account.AppUserClient;
import com.dsh.course.feignClient.account.CoachClient;
import com.dsh.course.feignClient.account.RefereeClient;
import com.dsh.course.feignClient.account.model.CoachSerchVO;
import com.dsh.course.feignClient.account.model.RefereeList;
import com.dsh.guns.config.UserExt;
import com.dsh.guns.core.common.exception.BizExceptionEnum;
import com.dsh.guns.core.exception.GunsException;
import com.dsh.guns.modular.system.audit.ManagementAuditWriter;
import com.dsh.guns.modular.system.model.SiteBooking;
import com.dsh.guns.modular.system.model.TOperator;
import com.dsh.guns.modular.system.model.CoachQuery;
import com.dsh.guns.modular.system.service.ISiteBookingService;
import com.dsh.guns.modular.system.service.TOperatorService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.dsh.guns.modular.system.model.User;
import com.dsh.guns.modular.system.service.IUserService;
 
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
 
/** Deliberately narrow, auditable unmasking capability for the admin account. */
@RestController
@RequestMapping("/system/sensitive")
public class SensitiveDataController {
    @Resource private AppUserClient appUserClient;
    @Resource private ManagementAuditWriter auditWriter;
    @Resource private IUserService userService;
    @Resource private ISiteBookingService siteBookingService;
    @Resource private CoachClient coachClient;
    @Resource private RefereeClient refereeClient;
    @Resource private TOperatorService operatorService;
 
    @GetMapping("/app-user/{id}")
    public Map<String, Object> appUser(@PathVariable Integer id) {
        assertAdmin();
        Map<String, Object> result = appUserClient.sensitiveAppUser(id);
        writeAudit("app-user", id, userName(result), result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    @GetMapping("/employee/{id}")
    public Map<String, Object> employee(@PathVariable Integer id) {
        assertAdmin();
        User employee = userService.getById(id);
        Map<String, Object> result = null;
        if (employee != null) {
            result = new HashMap<String, Object>();
            result.put("id", employee.getId());
            result.put("name", employee.getName());
            result.put("phone", employee.getPhone());
            result.put("idCard", employee.getIdCard());
        }
        writeAudit("employee", id, employee == null ? null : employee.getName(), result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    @GetMapping("/student/{id}")
    public Map<String, Object> student(@PathVariable Integer id) {
        assertAdmin();
        Map<String, Object> result = appUserClient.sensitiveStudent(id);
        writeAudit("student", id, userName(result), result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    /**
     * 预约管理查看关联预约人的敏感信息。
     *
     * <p>仅通过预约记录定位用户,避免前端以任意用户 ID 查询;该接口保留在
     * /system/sensitive 路径下,以使用全局的脱敏豁免和网关放行策略。</p>
     */
    @GetMapping("/booking/{bookingId}")
    public Map<String, Object> booking(@PathVariable Integer bookingId) {
        assertAdmin();
        SiteBooking booking = siteBookingService.getById(bookingId);
        if (booking == null || booking.getAppUserId() == null) {
            writeBookingAudit(bookingId, booking, null, "NOT_FOUND");
            return null;
        }
 
        Map<String, Object> result = appUserClient.sensitiveAppUser(booking.getAppUserId());
        writeBookingAudit(bookingId, booking, result, result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    @GetMapping("/coach/{id}")
    public Map<String, Object> coach(@PathVariable Integer id) {
        assertAdmin();
        // 与教练管理页 /tCoach/list 使用同一个查询入口,确保返回字段和数据权限一致。
        CoachQuery query = new CoachQuery();
        query.setId(id);
        List<CoachSerchVO> coaches = coachClient.listAll(query);
        CoachSerchVO coach = coaches == null || coaches.isEmpty() ? null : coaches.get(0);
        Map<String, Object> result = null;
        if (coach != null) {
            result = new HashMap<String, Object>();
            result.put("id", coach.getId());
            result.put("name", coach.getName());
            result.put("phone", coach.getPhone());
            result.put("idCard", coach.getIdcard());
        }
        writeAudit("coach", id, coach == null ? null : coach.getName(), result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    @GetMapping("/referee/{id}")
    public Map<String, Object> referee(@PathVariable Integer id) {
        assertAdmin();
        // 使用裁判管理页 /referee/listAll 对应的列表查询接口精确查询该裁判。
        RefereeList query = new RefereeList();
        query.setId(id);
        query.setOffset(0);
        query.setLimit(1);
        Map<String, Object> listResult = refereeClient.getRefereeList(query);
        List<Map<String, Object>> referees = listResult == null ? null
                : (List<Map<String, Object>>) listResult.get("rows");
        Map<String, Object> referee = referees == null || referees.isEmpty() ? null : referees.get(0);
        Map<String, Object> result = null;
        if (referee != null) {
            result = new HashMap<String, Object>();
            result.put("id", referee.get("id"));
            result.put("name", referee.get("name"));
            result.put("phone", referee.get("phone"));
        }
        writeAudit("referee", id, referee == null ? null : String.valueOf(referee.get("name")),
                result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    @GetMapping("/operator/{id}")
    public Map<String, Object> operator(@PathVariable Integer id) {
        assertAdmin();
        TOperator operator = operatorService.getById(id);
        User employee = operator == null ? null : userService.getById(operator.getUserId());
        Map<String, Object> result = null;
        if (employee != null) {
            result = new HashMap<String, Object>();
            result.put("id", employee.getId());
            result.put("name", employee.getName());
            result.put("phone", employee.getPhone());
            result.put("idCard", employee.getIdCard());
        }
        writeAudit("operator", id, operator == null ? null : operator.getName(), result == null ? "NOT_FOUND" : "SUCCESS");
        return result;
    }
 
    private void assertAdmin() {
        if (UserExt.getUser() == null || !"admin".equalsIgnoreCase(UserExt.getUser().getAccount())) {
            throw new GunsException(BizExceptionEnum.NO_PERMITION);
        }
    }
 
    private void writeAudit(String resource, Integer targetUserId, String targetUserName, String result) {
        Map<String, Object> audit = new HashMap<String, Object>();
        audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
        audit.put("sourceService", "mb-cloud-management");
        audit.put("sourceType", "HTTP");
        audit.put("operationType", "VIEW_SENSITIVE");
        audit.put("riskLevel", "SENSITIVE");
        audit.put("operationName", "超级管理员查看敏感信息");
        audit.put("requestMethod", "GET");
        audit.put("requestUri", "/system/sensitive/" + resource + "/" + targetUserId);
        // 敏感信息访问需同时记录对象名称,避免日志中只有无法识别的 ID。
        audit.put("requestParams", "targetUserId=" + targetUserId + ", targetUserName="
                + (targetUserName == null ? "-" : targetUserName));
        audit.put("userId", UserExt.getUser().getId());
        audit.put("userAccount", "admin");
        audit.put("result", result);
        audit.put("createdAt", new Date());
        auditWriter.append(audit);
    }
 
    private void writeBookingAudit(Integer bookingId, SiteBooking booking,
                                   Map<String, Object> user, String result) {
        Map<String, Object> audit = new HashMap<String, Object>();
        audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
        audit.put("sourceService", "mb-cloud-management");
        audit.put("sourceType", "HTTP");
        audit.put("operationType", "VIEW_SENSITIVE");
        audit.put("riskLevel", "SENSITIVE");
        audit.put("operationName", "超级管理员从预约管理查看敏感信息");
        audit.put("requestMethod", "GET");
        audit.put("requestUri", "/system/sensitive/booking/" + bookingId);
        // 审计记录只存预约、用户标识和查询结果,绝不写入手机号、身份证号等明文。
        audit.put("requestParams", "bookingId=" + bookingId + ", appUserId="
                + (booking == null || booking.getAppUserId() == null ? "-" : booking.getAppUserId())
                + ", targetUserName=" + (userName(user) == null ? "-" : userName(user)));
        audit.put("userId", UserExt.getUser().getId());
        audit.put("userAccount", "admin");
        audit.put("result", result);
        audit.put("createdAt", new Date());
        auditWriter.append(audit);
    }
 
    private String userName(Map<String, Object> user) {
        if (user == null || user.get("name") == null) return null;
        return String.valueOf(user.get("name"));
    }
}