/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.aliyun.control.dto.ApiResponse * com.aliyun.control.exception.BusinessException * com.wyrs.admin.config.ApiDocCatalog * com.wyrs.admin.config.WyrsAppRegistry * com.wyrs.admin.controller.AdminManageController * com.wyrs.admin.controller.AdminManageController$DashboardCache * com.wyrs.admin.entity.AdminOperationLog * com.wyrs.admin.entity.AppAdPlacement * com.wyrs.admin.entity.AppApkDownload * com.wyrs.admin.entity.AppBanner * com.wyrs.admin.entity.AppClientFeature * com.wyrs.admin.entity.AppSiteContent * com.wyrs.admin.entity.AppUser * com.wyrs.admin.entity.AppVersion * com.wyrs.admin.entity.AppWhitelistEntry * com.wyrs.admin.entity.ClientLog * com.wyrs.admin.entity.DailyActiveStat * com.wyrs.admin.entity.DeveloperApp * com.wyrs.admin.entity.MembershipPrice * com.wyrs.admin.entity.PaymentChannelConfig * com.wyrs.admin.entity.PaymentRecord * com.wyrs.admin.entity.UserFeedback * com.wyrs.admin.repository.AppUserRepository * com.wyrs.admin.repository.ClientLogRepository * com.wyrs.admin.repository.DailyActiveStatRepository * com.wyrs.admin.repository.PaymentRecordRepository * com.wyrs.admin.repository.UserFeedbackRepository * com.wyrs.admin.security.AdminSiteScope * com.wyrs.admin.security.AdminSiteScope$Kind * com.wyrs.admin.service.AdminStatsService * com.wyrs.admin.service.AdminStatsService$DateRange * com.wyrs.admin.service.AdminUserIpEnricher * com.wyrs.admin.service.ApkUploadService * com.wyrs.admin.service.AppClientService * com.wyrs.admin.service.AppUserLookupService * com.wyrs.admin.service.BannerImageService * com.wyrs.admin.service.BannerStateService * com.wyrs.admin.service.CapacityPlanService * com.wyrs.admin.service.DauCleanupService * com.wyrs.admin.service.DeveloperAppIconService * com.wyrs.admin.service.JavaPageMetricsService * com.wyrs.admin.service.OnlineDeviceTracker * com.wyrs.admin.service.OperationLogService * com.wyrs.admin.service.ServerMonitorService * com.wyrs.admin.service.config.AdPlacementsConfigService * com.wyrs.admin.service.config.AdminConfigDbMirrorService * com.wyrs.admin.service.config.ApkDownloadsConfigService * com.wyrs.admin.service.config.AppClientFeatureConfigService * com.wyrs.admin.service.config.AppSiteContentConfigService * com.wyrs.admin.service.config.AppWhitelistConfigService * com.wyrs.admin.service.config.BannersConfigService * com.wyrs.admin.service.config.DeveloperAppsConfigService * com.wyrs.admin.service.config.PaymentChannelConfigService * com.wyrs.admin.service.config.PricesConfigService * com.wyrs.admin.service.config.VersionsConfigService * com.wyrs.admin.util.AppChannelNames * com.wyrs.offerwall.config.OfferwallPackages * org.springframework.data.domain.Page * org.springframework.data.domain.PageImpl * org.springframework.data.domain.PageRequest * org.springframework.data.domain.Pageable * org.springframework.data.domain.Sort * org.springframework.data.domain.Sort$Direction * org.springframework.http.HttpStatus * org.springframework.http.MediaType * org.springframework.http.ResponseEntity * org.springframework.http.ResponseEntity$BodyBuilder * org.springframework.web.bind.annotation.DeleteMapping * org.springframework.web.bind.annotation.GetMapping * org.springframework.web.bind.annotation.PatchMapping * org.springframework.web.bind.annotation.PathVariable * org.springframework.web.bind.annotation.PostMapping * org.springframework.web.bind.annotation.PutMapping * org.springframework.web.bind.annotation.RequestBody * org.springframework.web.bind.annotation.RequestMapping * org.springframework.web.bind.annotation.RequestParam * org.springframework.web.bind.annotation.RestController * org.springframework.web.multipart.MultipartFile */ package com.wyrs.admin.controller; import com.aliyun.control.dto.ApiResponse; import com.aliyun.control.exception.BusinessException; import com.wyrs.admin.config.ApiDocCatalog; import com.wyrs.admin.config.WyrsAppRegistry; import com.wyrs.admin.controller.AdminManageController; import com.wyrs.admin.entity.AdminOperationLog; import com.wyrs.admin.entity.AppAdPlacement; import com.wyrs.admin.entity.AppApkDownload; import com.wyrs.admin.entity.AppBanner; import com.wyrs.admin.entity.AppClientFeature; import com.wyrs.admin.entity.AppSiteContent; import com.wyrs.admin.entity.AppUser; import com.wyrs.admin.entity.AppVersion; import com.wyrs.admin.entity.AppWhitelistEntry; import com.wyrs.admin.entity.ClientLog; import com.wyrs.admin.entity.DailyActiveStat; import com.wyrs.admin.entity.DeveloperApp; import com.wyrs.admin.entity.MembershipPrice; import com.wyrs.admin.entity.PaymentChannelConfig; import com.wyrs.admin.entity.PaymentRecord; import com.wyrs.admin.entity.UserFeedback; import com.wyrs.admin.repository.AppUserRepository; import com.wyrs.admin.repository.ClientLogRepository; import com.wyrs.admin.repository.DailyActiveStatRepository; import com.wyrs.admin.repository.PaymentRecordRepository; import com.wyrs.admin.repository.UserFeedbackRepository; import com.wyrs.admin.security.AdminSiteScope; import com.wyrs.admin.service.AdminStatsService; import com.wyrs.admin.service.AdminUserIpEnricher; import com.wyrs.admin.service.ApkUploadService; import com.wyrs.admin.service.AppClientService; import com.wyrs.admin.service.AppUserLookupService; import com.wyrs.admin.service.BannerImageService; import com.wyrs.admin.service.BannerStateService; import com.wyrs.admin.service.CapacityPlanService; import com.wyrs.admin.service.DauCleanupService; import com.wyrs.admin.service.DeveloperAppIconService; import com.wyrs.admin.service.JavaPageMetricsService; import com.wyrs.admin.service.OnlineDeviceTracker; import com.wyrs.admin.service.OperationLogService; import com.wyrs.admin.service.ServerMonitorService; import com.wyrs.admin.service.config.AdPlacementsConfigService; import com.wyrs.admin.service.config.AdminConfigDbMirrorService; import com.wyrs.admin.service.config.ApkDownloadsConfigService; import com.wyrs.admin.service.config.AppClientFeatureConfigService; import com.wyrs.admin.service.config.AppSiteContentConfigService; import com.wyrs.admin.service.config.AppWhitelistConfigService; import com.wyrs.admin.service.config.BannersConfigService; import com.wyrs.admin.service.config.DeveloperAppsConfigService; import com.wyrs.admin.service.config.PaymentChannelConfigService; import com.wyrs.admin.service.config.PricesConfigService; import com.wyrs.admin.service.config.VersionsConfigService; import com.wyrs.admin.util.AppChannelNames; import com.wyrs.offerwall.config.OfferwallPackages; import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /* * Exception performing whole class analysis ignored. */ @RestController @RequestMapping(value={"/api/admin"}) public class AdminManageController { private final AppUserRepository appUserRepository; private final ClientLogRepository clientLogRepository; private final UserFeedbackRepository userFeedbackRepository; private final PaymentRecordRepository paymentRecordRepository; private final DailyActiveStatRepository dailyActiveStatRepository; private final OperationLogService operationLogService; private final AdminStatsService adminStatsService; private final ServerMonitorService serverMonitorService; private final JavaPageMetricsService javaPageMetricsService; private final CapacityPlanService capacityPlanService; private final DauCleanupService dauCleanupService; private final BannerImageService bannerImageService; private final BannerStateService bannerStateService; private final BannersConfigService bannersConfigService; private final VersionsConfigService versionsConfigService; private final PricesConfigService pricesConfigService; private final PaymentChannelConfigService paymentChannelConfigService; private final DeveloperAppsConfigService developerAppsConfigService; private final AdPlacementsConfigService adPlacementsConfigService; private final ApkDownloadsConfigService apkDownloadsConfigService; private final AppSiteContentConfigService siteContentConfigService; private final AppWhitelistConfigService appWhitelistConfigService; private final AppClientFeatureConfigService appClientFeatureConfigService; private final ApkUploadService apkUploadService; private final DeveloperAppIconService developerAppIconService; private final AdminConfigDbMirrorService configDbMirrorService; private final AppUserLookupService appUserLookupService; private final AppClientService appClientService; private final OnlineDeviceTracker onlineDeviceTracker; private final AdminUserIpEnricher adminUserIpEnricher; private volatile DashboardCache dashboardCache; private static final long DASHBOARD_CACHE_MS = 60000L; @GetMapping(value={"/site-scope"}) public ApiResponse> siteScope() { return ApiResponse.ok((Object)AdminSiteScope.current().toPublicMap()); } @GetMapping(value={"/apps"}) public ApiResponse>> apps() { AdminSiteScope scope = AdminSiteScope.current(); if (scope.kind() == AdminSiteScope.Kind.ADADMIN) { ArrayList rows = new ArrayList(); for (Map opt : OfferwallPackages.adminOptions()) { LinkedHashMap row = new LinkedHashMap(); row.put("code", "offerwall"); row.put("name", opt.get("label")); row.put("packageName", opt.get("packageName")); row.put("packageNames", List.of((String)opt.get("packageName"))); row.put("siteRealm", "ADADMIN"); rows.add(row); } return ApiResponse.ok(rows); } return ApiResponse.ok((Object)WyrsAppRegistry.toAdminList((Collection)(scope.isFull() ? null : scope.appCodes()))); } @GetMapping(value={"/api-doc"}) public ApiResponse> apiDoc() { Map doc = ApiDocCatalog.toApiDoc(); AdminSiteScope scope = AdminSiteScope.current(); doc.put("siteScope", scope.toPublicMap()); if (!scope.isFull()) { if (scope.kind() == AdminSiteScope.Kind.ADADMIN) { ArrayList rows = new ArrayList(); for (Map opt : OfferwallPackages.adminOptions()) { LinkedHashMap row = new LinkedHashMap(); row.put("code", "offerwall"); row.put("name", opt.get("label")); row.put("packageName", opt.get("packageName")); row.put("packageNames", List.of((String)opt.get("packageName"))); rows.add(row); } doc.put("apps", rows); } else { doc.put("apps", WyrsAppRegistry.toAdminList((Collection)scope.appCodes())); } doc.put("baseUrl", "https://" + (scope.host().isBlank() ? "admin.wyrs365.com" : scope.host())); } return ApiResponse.ok((Object)doc); } @GetMapping(value={"/channels"}) public ApiResponse>> channels() { return ApiResponse.ok((Object)AppChannelNames.toAdminList()); } @GetMapping(value={"/developer-app-channels"}) public ApiResponse>> developerAppChannels() { return ApiResponse.ok((Object)AppChannelNames.toDeveloperAppAdminList()); } /* * WARNING - Removed try catching itself - possible behaviour change. */ @GetMapping(value={"/dashboard"}) public ApiResponse> dashboard() { AdminSiteScope scope = AdminSiteScope.current(); if (!scope.isFull()) { return ApiResponse.ok((Object)this.buildDashboard()); } long now = System.currentTimeMillis(); DashboardCache cached = this.dashboardCache; if (cached != null && now - cached.atMs < 60000L) { return ApiResponse.ok((Object)cached.data); } AdminManageController adminManageController = this; synchronized (adminManageController) { cached = this.dashboardCache; if (cached != null && now - cached.atMs < 60000L) { return ApiResponse.ok((Object)cached.data); } Map data = this.buildDashboard(); this.dashboardCache = new DashboardCache(new LinkedHashMap(data), now); return ApiResponse.ok((Object)data); } } private Map buildDashboard() { ArrayList codes; LocalDate today = AdminStatsService.todayInChina(); AdminSiteScope scope = AdminSiteScope.current(); Map dauSummary = this.scopedDauSummary(scope); LinkedHashMap data = new LinkedHashMap(); ArrayList arrayList = codes = scope.isFull() ? List.of() : new ArrayList(scope.appCodes()); if (codes.isEmpty()) { data.put("totalUsers", this.appUserRepository.count()); } else { long total = 0L; for (String c : codes) { total += this.appUserRepository.countByAppCode(c); } data.put("totalUsers", total); } data.put("todayActive", dauSummary.get("todayTotal")); if (codes.isEmpty()) { data.put("pendingFeedback", this.userFeedbackRepository.findByStatus("pending", (Pageable)PageRequest.of((int)0, (int)1)).getTotalElements()); data.put("totalPayments", this.paymentRecordRepository.count()); } else { long pending = 0L; long payments = 0L; for (String c : codes) { pending += this.userFeedbackRepository.countByStatusAndAppCode("pending", c); payments += this.paymentRecordRepository.countByAppCode(c); } data.put("pendingFeedback", pending); data.put("totalPayments", payments); } LinkedHashMap byApp = this.adminStatsService.dashboardByApps(); if (!scope.isFull()) { LinkedHashMap filtered = new LinkedHashMap(); for (String c : scope.appCodes()) { if (!byApp.containsKey(c)) continue; filtered.put(c, byApp.get(c)); } byApp = filtered; } data.put("byApp", byApp); data.put("dauSummary", dauSummary); LinkedHashMap country = this.adminStatsService.countryDauByApps(today); if (!scope.isFull() && country != null) { LinkedHashMap filteredCountry = new LinkedHashMap(); for (String c : scope.appCodes()) { if (!country.containsKey(c)) continue; filteredCountry.put(c, country.get(c)); } country = filteredCountry; } data.put("countryDauByApp", country); data.put("capacityPlan", this.capacityPlanService.plan()); data.put("siteScope", scope.toPublicMap()); return data; } @GetMapping(value={"/dau/summary"}) public ApiResponse> dauSummary() { return ApiResponse.ok((Object)this.scopedDauSummary(AdminSiteScope.current())); } @GetMapping(value={"/retention/summary"}) public ApiResponse> retentionSummary(@RequestParam(required=false) String appCode, @RequestParam(defaultValue="14") int days) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); if (code != null && !code.isBlank()) { WyrsAppRegistry.require((String)code); return ApiResponse.ok((Object)this.adminStatsService.retentionSummary(code, days)); } Map summary = this.adminStatsService.retentionSummary(null, days); if (!scope.isFull()) { summary = AdminManageController.filterRetentionSummary((Map)summary, (Set)scope.appCodes()); } return ApiResponse.ok((Object)summary); } @GetMapping(value={"/stats/hourly-24h"}) public ApiResponse> hourly24h(@RequestParam(required=false) String appCode, @RequestParam(required=false, defaultValue="yesterday") String compare) { AdminSiteScope scope = AdminSiteScope.current(); String code = null; if (appCode != null && !appCode.isBlank()) { code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); WyrsAppRegistry.require((String)code); } else if (!scope.isFull()) { Map full = this.adminStatsService.hourly24h(null); return ApiResponse.ok((Object)AdminManageController.filterHourly24h((Map)full, (Set)scope.appCodes())); } if (compare == null || compare.isBlank() || "yesterday".equalsIgnoreCase(compare.trim()) || !"none".equalsIgnoreCase(compare.trim())) { // empty if block } return ApiResponse.ok((Object)this.adminStatsService.hourly24h(code)); } private static Map filterHourly24h(Map summary, Set allowed) { if (summary == null || allowed == null || allowed.isEmpty()) { return summary; } LinkedHashMap out = new LinkedHashMap(summary); ArrayList filtered = new ArrayList(); long[] newTotal = null; long[] activeTotal = null; long[] yNewTotal = null; long[] yActiveTotal = null; Object appsObj = summary.get("apps"); if (appsObj instanceof List) { List list = (List)appsObj; for (Object o : list) { Map m; Object c; if (!(o instanceof Map) || (c = (m = (Map)o).get("appCode")) == null || !allowed.contains(String.valueOf(c))) continue; filtered.add(m); Object pts = m.get("points"); if (!(pts instanceof List)) continue; List points = (List)pts; if (newTotal == null) { newTotal = new long[points.size()]; activeTotal = new long[points.size()]; yNewTotal = new long[points.size()]; yActiveTotal = new long[points.size()]; } for (int i = 0; i < points.size(); ++i) { Object e = points.get(i); if (!(e instanceof Map)) continue; Map p = (Map)e; Object nu = p.get("newUser"); Object au = p.get("activeUser"); Object ynu = p.get("yesterdayNewUser"); Object yau = p.get("yesterdayActiveUser"); if (nu instanceof Number) { Number n = (Number)nu; int n2 = i; newTotal[n2] = newTotal[n2] + n.longValue(); } if (au instanceof Number) { Number a = (Number)au; int n = i; activeTotal[n] = activeTotal[n] + a.longValue(); } if (ynu instanceof Number) { Number yn = (Number)ynu; int n = i; yNewTotal[n] = yNewTotal[n] + yn.longValue(); } if (!(yau instanceof Number)) continue; Number ya = (Number)yau; int n = i; yActiveTotal[n] = yActiveTotal[n] + ya.longValue(); } } } out.put("apps", filtered); LinkedHashMap total = new LinkedHashMap(); total.put("appCode", null); total.put("name", "\u603b\u670d\u52a1\u5668"); if (newTotal != null && !filtered.isEmpty()) { Object samplePts = ((Map)filtered.get(0)).get("points"); ArrayList points = new ArrayList(); if (samplePts instanceof List) { List sp = (List)samplePts; for (int i = 0; i < sp.size(); ++i) { LinkedHashMap p = new LinkedHashMap(); Object e = sp.get(i); if (e instanceof Map) { Map src = (Map)e; p.put("ts", src.get("ts")); p.put("time", src.get("time")); p.put("label", src.get("label")); p.put("yesterdayTs", src.get("yesterdayTs")); p.put("yesterdayTime", src.get("yesterdayTime")); } p.put("newUser", newTotal[i]); p.put("activeUser", activeTotal[i]); p.put("yesterdayNewUser", yNewTotal[i]); p.put("yesterdayActiveUser", yActiveTotal[i]); points.add(p); } } total.put("points", points); } else { total.put("points", List.of()); } out.put("total", total); return out; } private Map scopedDauSummary(AdminSiteScope scope) { Map summary = this.adminStatsService.dauSummary(); if (scope == null || scope.isFull()) { return summary; } Set allowed = scope.appCodes(); List apps = (List)summary.get("apps"); ArrayList filtered = new ArrayList(); long todayTotal = 0L; long yesterdayTotal = 0L; long todayNewTotal = 0L; long yesterdayNewTotal = 0L; long todayIos = 0L; long todayAndroid = 0L; long yesterdayIos = 0L; long yesterdayAndroid = 0L; long totalUsers = 0L; if (apps != null) { for (Map row : apps) { Object code = row.get("appCode"); if (code == null || !allowed.contains(String.valueOf(code))) continue; filtered.add(row); todayTotal += AdminManageController.toLong(row.get("todayDau")); yesterdayTotal += AdminManageController.toLong(row.get("yesterdayDau")); todayNewTotal += AdminManageController.toLong(row.get("todayNewDau")); yesterdayNewTotal += AdminManageController.toLong(row.get("yesterdayNewDau")); todayIos += AdminManageController.toLong(row.get("todayIos")); todayAndroid += AdminManageController.toLong(row.get("todayAndroid")); yesterdayIos += AdminManageController.toLong(row.get("yesterdayIos")); yesterdayAndroid += AdminManageController.toLong(row.get("yesterdayAndroid")); totalUsers += AdminManageController.toLong(row.get("totalUsers")); } } LinkedHashMap out = new LinkedHashMap(summary); out.put("apps", filtered); out.put("todayTotal", todayTotal); out.put("yesterdayTotal", yesterdayTotal); out.put("todayNewTotal", todayNewTotal); out.put("yesterdayNewTotal", yesterdayNewTotal); out.put("todayIos", todayIos); out.put("todayAndroid", todayAndroid); out.put("yesterdayIos", yesterdayIos); out.put("yesterdayAndroid", yesterdayAndroid); out.put("totalUsers", totalUsers); out.put("siteScope", scope.toPublicMap()); return out; } private static Map filterRetentionSummary(Map summary, Set allowed) { if (summary == null || allowed == null || allowed.isEmpty()) { return summary; } LinkedHashMap out = new LinkedHashMap(summary); Object appsObj = summary.get("apps"); if (appsObj instanceof List) { List list = (List)appsObj; ArrayList filtered = new ArrayList(); for (Object o : list) { Map m; Object code; if (!(o instanceof Map) || (code = (m = (Map)o).get("appCode")) == null || !allowed.contains(String.valueOf(code))) continue; filtered.add(m); } out.put("apps", filtered); } return out; } private static long toLong(Object v) { if (v instanceof Number) { Number n = (Number)v; return n.longValue(); } if (v == null) { return 0L; } try { return Long.parseLong(String.valueOf(v)); } catch (Exception e) { return 0L; } } @GetMapping(value={"/monitor"}) public ApiResponse> monitor() { return ApiResponse.ok((Object)this.serverMonitorService.snapshot()); } @GetMapping(value={"/monitor/java-breakdown"}) public ApiResponse> javaBreakdown() { Map snap = this.serverMonitorService.snapshot(); double javaCpu = AdminManageController.extractJavaCpuPercent((Map)snap); return ApiResponse.ok((Object)this.javaPageMetricsService.breakdown(javaCpu)); } @GetMapping(value={"/monitor/full"}) public ApiResponse> monitorFull() { Map snap = this.serverMonitorService.snapshot(); double javaCpu = AdminManageController.extractJavaCpuPercent((Map)snap); LinkedHashMap data = new LinkedHashMap(snap); data.put("javaBreakdown", this.javaPageMetricsService.breakdown(javaCpu)); data.put("onlineDevices", this.onlineDeviceTracker.series24h()); return ApiResponse.ok(data); } @GetMapping(value={"/online-devices/series"}) public ApiResponse> onlineDevicesSeries() { return ApiResponse.ok((Object)this.onlineDeviceTracker.series24h()); } private static double extractJavaCpuPercent(Map snap) { double d; Object rows = snap.get("cpuBreakdown"); if (!(rows instanceof List)) { return 0.0; } List list = (List)rows; for (Object item : list) { Object cpu; Map row; if (!(item instanceof Map) || !"java".equals(String.valueOf((row = (Map)item).get("key"))) || !((cpu = row.get("cpuPercent")) instanceof Number)) continue; Number n = (Number)cpu; return n.doubleValue(); } Object cpu = snap.get("cpuPercent"); if (cpu instanceof Number) { Number n = (Number)cpu; d = n.doubleValue(); } else { d = 0.0; } return d; } @GetMapping(value={"/capacity-plan"}) public ApiResponse> capacityPlan() { return ApiResponse.ok((Object)this.capacityPlanService.plan()); } @GetMapping(value={"/users"}) public ApiResponse>> users(@RequestParam(name="appCode", required=false) String appCode, @RequestParam(name="phone", required=false) String phone, @RequestParam(name="email", required=false) String email, @RequestParam(name="id", required=false) Long id, @RequestParam(name="page", defaultValue="0") int page, @RequestParam(name="size", defaultValue="20") int size) { AdminSiteScope scope = AdminSiteScope.current(); PageRequest pr = PageRequest.of((int)page, (int)size, (Sort)Sort.by((Sort.Direction)Sort.Direction.DESC, (String[])new String[]{"id"})); List scopeCodes = scope.queryAppCodes(appCode); if (id != null) { return this.appUserRepository.findById((Object)id).map(u -> { if (!scope.allows(u.getAppCode())) { return ApiResponse.ok((Object)Page.empty((Pageable)pr)); } if (!(appCode == null || appCode.isBlank() || appCode.equalsIgnoreCase(u.getAppCode()) || ("spark".equalsIgnoreCase(appCode) || "pop".equalsIgnoreCase(appCode)) && ("spark".equalsIgnoreCase(u.getAppCode()) || "pop".equalsIgnoreCase(u.getAppCode())))) { return ApiResponse.ok((Object)Page.empty((Pageable)pr)); } return ApiResponse.ok((Object)new PageImpl(List.of(this.adminUserIpEnricher.toListRow(u)), (Pageable)pr, 1L)); }).orElseGet(() -> ApiResponse.ok((Object)Page.empty((Pageable)pr))); } if (email != null && !email.isBlank()) { String e = email.trim().toLowerCase(Locale.ROOT); List matched = this.appUserRepository.findByEmail(e); if (!scopeCodes.isEmpty()) { matched = matched.stream().filter(u -> scopeCodes.contains(u.getAppCode() == null ? "" : u.getAppCode().toLowerCase(Locale.ROOT))).toList(); } else if (!scope.isFull()) { matched = matched.stream().filter(u -> scope.allows(u.getAppCode())).toList(); } return ApiResponse.ok((Object)this.enrichUserPage(matched, pr, (long)matched.size())); } if (phone != null && !phone.isBlank()) { Page p = this.appUserRepository.findByPhoneContaining(phone, (Pageable)pr); if (scope.isFull() && scopeCodes.isEmpty()) { return ApiResponse.ok((Object)this.enrichUserPage(p)); } List filtered = p.getContent().stream().filter(u -> scope.allows(u.getAppCode())).toList(); return ApiResponse.ok((Object)this.enrichUserPage(filtered, pr, (long)filtered.size())); } if (!scopeCodes.isEmpty()) { if (scopeCodes.size() == 1) { return ApiResponse.ok((Object)this.enrichUserPage(this.appUserRepository.findByAppCode((String)scopeCodes.get(0), (Pageable)pr))); } return ApiResponse.ok((Object)this.enrichUserPage(this.appUserRepository.findByAppCodeIn((Collection)scopeCodes, (Pageable)pr))); } if (!scope.isFull()) { return ApiResponse.ok((Object)this.enrichUserPage(this.appUserRepository.findByAppCodeIn(new ArrayList(scope.appCodes()), (Pageable)pr))); } return ApiResponse.ok((Object)this.enrichUserPage(this.appUserRepository.findAll((Pageable)pr))); } private Page> enrichUserPage(Page page) { List rows = page.getContent().stream().map(arg_0 -> ((AdminUserIpEnricher)this.adminUserIpEnricher).toListRow(arg_0)).toList(); return new PageImpl(rows, page.getPageable(), page.getTotalElements()); } private Page> enrichUserPage(List users, PageRequest pr, long total) { List rows = users.stream().map(arg_0 -> ((AdminUserIpEnricher)this.adminUserIpEnricher).toListRow(arg_0)).toList(); return new PageImpl(rows, (Pageable)pr, total); } @GetMapping(value={"/users/by-email"}) public ApiResponse> userByEmail(@RequestParam String email, @RequestParam(required=false) String appCode) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); Map data = this.appUserLookupService.lookupByEmail(email, code); if (!scope.isFull() && scope.kind() == AdminSiteScope.Kind.POPADMIN && (appCode == null || appCode.isBlank())) { List list; List list2; Map xz = this.appUserLookupService.lookupByEmail(email, "xiaoxiaozhou"); Object v = data.get("appUsers"); if (v instanceof List) { List l = (List)v; list2 = (List)data.get("appUsers"); } else { list2 = List.of(); } List sparkUsers = list2; Object v2 = xz.get("appUsers"); if (v2 instanceof List) { List l = (List)v2; list = (List)xz.get("appUsers"); } else { list = List.of(); } List xzUsers = list; ArrayList merged = new ArrayList(sparkUsers); merged.addAll(xzUsers); data.put("appUsers", merged); data.put("appUserCount", merged.size()); } if (!scope.isFull()) { AdminManageController.filterScopedUserLookup((Map)data, (AdminSiteScope)scope); } data.put("siteScope", scope.toPublicMap()); return ApiResponse.ok((Object)data); } @GetMapping(value={"/users/by-nickname"}) public ApiResponse> userByNickname(@RequestParam String nickname, @RequestParam(required=false) String appCode) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); Map data = this.appUserLookupService.lookupByNickname(nickname, code); data.put("siteScope", scope.toPublicMap()); return ApiResponse.ok((Object)data); } @GetMapping(value={"/users/by-device"}) public ApiResponse> userByDevice(@RequestParam String deviceId, @RequestParam(required=false) String appCode) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); Map data = this.appUserLookupService.lookupByDeviceId(deviceId, code); data.put("siteScope", scope.toPublicMap()); return ApiResponse.ok((Object)data); } @GetMapping(value={"/users/by-ip"}) public ApiResponse> userByIp(@RequestParam String ip, @RequestParam(required=false) String appCode) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); Map data = this.appUserLookupService.lookupByIp(ip, code); data.put("siteScope", scope.toPublicMap()); return ApiResponse.ok((Object)data); } @GetMapping(value={"/users/export-zip"}) public ResponseEntity exportUserZip(@RequestParam String email, @RequestParam(required=false) String appCode) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)appCode); if (code == null || code.isBlank()) { code = "spark"; } byte[] zip = this.appUserLookupService.exportUserZip(email, code); String filename = "user-" + email.replaceAll("[^a-zA-Z0-9@._-]", "_") + ".zip"; return ((ResponseEntity.BodyBuilder)ResponseEntity.ok().header("Content-Disposition", new String[]{"attachment; filename=\"" + filename + "\""})).contentType(MediaType.parseMediaType((String)"application/zip")).body((Object)zip); } @PostMapping(value={"/users/by-email/reset-avatar"}) public ApiResponse> resetAvatarByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); return ApiResponse.ok((Object)this.appUserLookupService.resetAvatar(AdminManageController.str(body, (String)"email"), code)); } @PostMapping(value={"/users/by-email/reset-nickname"}) public ApiResponse> resetNicknameByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); return ApiResponse.ok((Object)this.appUserLookupService.resetNickname(AdminManageController.str(body, (String)"email"), code)); } @PostMapping(value={"/users/by-email/mark-abnormal"}) public ApiResponse> markAbnormalByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); boolean abnormal = body.get("abnormal") == null || Boolean.TRUE.equals(body.get("abnormal")); return ApiResponse.ok((Object)this.appUserLookupService.markAbnormal(AdminManageController.str(body, (String)"email"), code, abnormal, AdminManageController.str(body, (String)"reason"))); } @PostMapping(value={"/users/by-email/ban"}) public ApiResponse> banUserByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); boolean banned = body.get("banned") == null || Boolean.TRUE.equals(body.get("banned")); return ApiResponse.ok((Object)this.appUserLookupService.banUser(AdminManageController.str(body, (String)"email"), code, banned, AdminManageController.str(body, (String)"reason"))); } @PostMapping(value={"/users/by-email/whitelist"}) public ApiResponse> whitelistByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); if (code == null || code.isBlank()) { throw new BusinessException("\u8bf7\u6307\u5b9a appCode", HttpStatus.BAD_REQUEST); } return ApiResponse.ok((Object)this.appUserLookupService.addWhitelist(AdminManageController.str(body, (String)"email"), code, AdminManageController.str(body, (String)"note"))); } @PostMapping(value={"/users/by-email/reset-password"}) public ApiResponse> resetPasswordByEmail(@RequestBody Map body) { AdminSiteScope scope = AdminSiteScope.current(); String code = AdminManageController.resolveScopedAppCode((AdminSiteScope)scope, (String)AdminManageController.str(body, (String)"appCode")); String newPassword = AdminManageController.str(body, (String)"newPassword"); if (newPassword == null || newPassword.isBlank()) { newPassword = AdminManageController.str(body, (String)"password"); } return ApiResponse.ok((Object)this.appUserLookupService.resetPassword(AdminManageController.str(body, (String)"email"), code, newPassword)); } private static String str(Map body, String key) { Object v = body == null ? null : body.get(key); return v == null ? null : String.valueOf(v); } private static String resolveScopedAppCode(AdminSiteScope scope, String appCode) { if (appCode != null && !appCode.isBlank()) { scope.assertAllowed(appCode); return appCode.trim().toLowerCase(Locale.ROOT); } String def = scope.defaultAppCodeOrNull(); if (def != null) { return def; } if (scope.kind() == AdminSiteScope.Kind.POPADMIN) { return "spark"; } return null; } private static void filterScopedUserLookup(Map data, AdminSiteScope scope) { Object sameDevice; Object linked = data.get("linkedApps"); if (linked instanceof List) { List list = (List)linked; ArrayList filtered = new ArrayList(); for (Object o : list) { Map m; Object code; if (!(o instanceof Map) || (code = (m = (Map)o).get("appCode")) == null || !scope.allows(String.valueOf(code))) continue; filtered.add(m); } data.put("linkedApps", filtered); data.put("linkedAppCount", filtered.size()); } if ((sameDevice = data.get("sameDeviceApps")) instanceof List) { List list = (List)sameDevice; ArrayList filtered = new ArrayList(); for (Object o : list) { Map m; Object code; if (!(o instanceof Map) || (code = (m = (Map)o).get("appCode")) == null || !scope.allows(String.valueOf(code))) continue; filtered.add(m); } data.put("sameDeviceApps", filtered); data.put("sameDeviceAppCount", filtered.size()); } } @GetMapping(value={"/users/{id}"}) public ApiResponse> userDetail(@PathVariable Long id) { AppUser u; AdminSiteScope scope = AdminSiteScope.current(); if (!scope.allows((u = (AppUser)this.appUserRepository.findById((Object)id).orElseThrow(() -> new BusinessException("\u7528\u6237\u4e0d\u5b58\u5728", HttpStatus.NOT_FOUND))).getAppCode())) { throw new BusinessException("\u5f53\u524d\u7ad9\u70b9\u65e0\u6743\u8bbf\u95ee\u8be5\u7528\u6237", HttpStatus.FORBIDDEN); } return ApiResponse.ok((Object)this.adminUserIpEnricher.toDetailRow(u)); } @GetMapping(value={"/logs"}) public ApiResponse> logs(@RequestParam(required=false) Long userId, @RequestParam(required=false) String appCode, @RequestParam(required=false) String email, @RequestParam(required=false) String deviceId, @RequestParam(required=false) String level, @RequestParam(required=false, defaultValue="false") boolean crashOnly, @RequestParam(defaultValue="0") int page, @RequestParam(defaultValue="20") int size) { PageRequest pr = PageRequest.of((int)page, (int)size, (Sort)Sort.by((Sort.Direction)Sort.Direction.DESC, (String[])new String[]{"id"})); return ApiResponse.ok((Object)this.clientLogRepository.search(appCode == null || appCode.isBlank() ? null : appCode.trim(), userId, email == null || email.isBlank() ? null : email.trim().toLowerCase(), deviceId == null || deviceId.isBlank() ? null : deviceId.trim(), level == null || level.isBlank() ? null : level.trim(), crashOnly, (Pageable)pr)); } @PostMapping(value={"/logs"}) public ApiResponse createLog(@RequestBody ClientLog log) { if (log.getAppCode() == null || log.getAppCode().isBlank()) { throw new BusinessException("appCode \u4e0d\u80fd\u4e3a\u7a7a"); } return ApiResponse.ok((Object)this.appClientService.reportLog(log.getAppCode(), log)); } @GetMapping(value={"/versions"}) public ApiResponse> versions(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok((Object)this.versionsConfigService.findByAppCodeOrderByVersionCodeDesc(appCode)); } return ApiResponse.ok(this.versionsConfigService.findAll().stream().sorted(Comparator.comparing(AppVersion::getId, Comparator.nullsLast(Long::compareTo)).reversed()).toList()); } @PostMapping(value={"/versions"}) public ApiResponse saveVersion(@RequestBody AppVersion v) { WyrsAppRegistry.require((String)v.getAppCode()); AppVersion saved = this.configDbMirrorService.syncVersion(v); this.operationLogService.log("VERSION_SAVE", "\u4fdd\u5b58\u7248\u672c " + v.getAppCode() + " " + v.getVersionName()); return ApiResponse.ok((Object)saved); } @DeleteMapping(value={"/versions/{id}"}) public ApiResponse deleteVersion(@PathVariable Long id) { this.configDbMirrorService.deleteVersion(id); this.operationLogService.log("VERSION_DELETE", "\u5220\u9664\u7248\u672c id=" + id); return ApiResponse.ok(null); } @GetMapping(value={"/feedbacks"}) public ApiResponse> feedbacks(@RequestParam(required=false) String appCode, @RequestParam(required=false) String status, @RequestParam(defaultValue="0") int page, @RequestParam(defaultValue="20") int size) { PageRequest pr = PageRequest.of((int)page, (int)size, (Sort)Sort.by((Sort.Direction)Sort.Direction.DESC, (String[])new String[]{"id"})); if (status != null && !status.isBlank()) { return ApiResponse.ok((Object)this.userFeedbackRepository.findByStatus(status, (Pageable)pr)); } if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok((Object)this.userFeedbackRepository.findByAppCode(appCode, (Pageable)pr)); } return ApiResponse.ok((Object)this.userFeedbackRepository.findAll((Pageable)pr)); } @PutMapping(value={"/feedbacks/{id}"}) public ApiResponse updateFeedback(@PathVariable Long id, @RequestBody UserFeedback body) { UserFeedback fb = (UserFeedback)this.userFeedbackRepository.findById((Object)id).orElseThrow(); if (body.getStatus() != null) { fb.setStatus(body.getStatus()); } if (body.getReply() != null) { fb.setReply(body.getReply()); } this.operationLogService.log("FEEDBACK_REPLY", "\u5904\u7406\u53cd\u9988 id=" + id); return ApiResponse.ok((Object)((UserFeedback)this.userFeedbackRepository.save((Object)fb))); } @GetMapping(value={"/banners"}) public ApiResponse> banners(@RequestParam(required=false) String appCode) { List list = appCode != null && !appCode.isBlank() ? this.bannersConfigService.findByAppCodeOrderBySortOrderAsc(appCode) : this.bannersConfigService.findAll().stream().sorted(Comparator.comparing(AppBanner::getSortOrder, Comparator.nullsLast(Integer::compareTo))).toList(); this.bannerStateService.applyEnabledTo(list); return ApiResponse.ok(list); } @PostMapping(value={"/banners"}) public ApiResponse saveBanner(@RequestBody AppBanner b) { AppBanner saved; boolean isCreate; WyrsAppRegistry.require((String)b.getAppCode()); Boolean requestedEnabled = b.getEnabled(); boolean bl = isCreate = b.getId() == null; if (!isCreate) { AppBanner existing = (AppBanner)this.bannersConfigService.findById(b.getId()).orElseThrow(); existing.setTitle(b.getTitle()); existing.setImageUrl(b.getImageUrl()); existing.setLinkUrl(b.getLinkUrl()); existing.setSortOrder(b.getSortOrder()); existing.setStartTime(b.getStartTime()); existing.setEndTime(b.getEndTime()); if (requestedEnabled != null) { existing.setEnabled(requestedEnabled); } saved = this.configDbMirrorService.syncBannerToFile(existing); } else { if (requestedEnabled == null) { b.setEnabled(Boolean.valueOf(false)); } saved = this.configDbMirrorService.syncBannerToFile(b); } if (saved.getId() != null && (isCreate || requestedEnabled != null)) { boolean enabled = requestedEnabled != null ? requestedEnabled.booleanValue() : Boolean.TRUE.equals(saved.getEnabled()); this.bannerStateService.setEnabled(saved.getAppCode(), saved.getId().longValue(), enabled); } this.bannerStateService.applyEnabledTo(List.of(saved)); this.operationLogService.log("BANNER_SAVE", "\u4fdd\u5b58Banner " + saved.getTitle()); return ApiResponse.ok((Object)saved); } @PatchMapping(value={"/banners/{id}/enabled"}) public ApiResponse setBannerEnabled(@PathVariable Long id, @RequestBody Map body) { AppBanner banner = (AppBanner)this.bannersConfigService.findById(id).orElseThrow(); Boolean enabled = body.get("enabled"); if (enabled == null) { throw new BusinessException("enabled \u4e0d\u80fd\u4e3a\u7a7a"); } this.bannerStateService.setEnabled(banner.getAppCode(), id.longValue(), enabled.booleanValue()); banner.setEnabled(enabled); this.configDbMirrorService.syncBannerToFile(banner); this.operationLogService.log("BANNER_TOGGLE", "Banner id=" + id + " enabled=" + enabled); return ApiResponse.ok((Object)banner); } @DeleteMapping(value={"/banners/{id}"}) public ApiResponse deleteBanner(@PathVariable Long id) { this.configDbMirrorService.deleteBanner(id); this.operationLogService.log("BANNER_DELETE", "\u5220\u9664Banner id=" + id); return ApiResponse.ok(null); } @PostMapping(value={"/banners/{id}/image"}) public ApiResponse> uploadBannerImage(@PathVariable Long id, @RequestParam(value="file") MultipartFile file) throws IOException { return ApiResponse.ok((Object)this.bannerImageService.uploadImage(id, file)); } @GetMapping(value={"/prices"}) public ApiResponse> prices(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok((Object)this.pricesConfigService.findByAppCode(appCode)); } return ApiResponse.ok((Object)this.pricesConfigService.findAll()); } @PostMapping(value={"/prices"}) public ApiResponse savePrice(@RequestBody MembershipPrice p) { WyrsAppRegistry.require((String)p.getAppCode()); MembershipPrice saved = this.configDbMirrorService.syncPrice(p); this.operationLogService.log("PRICE_SAVE", "\u4fdd\u5b58\u4f1a\u5458\u4ef7\u683c " + p.getName()); return ApiResponse.ok((Object)saved); } @DeleteMapping(value={"/prices/{id}"}) public ApiResponse deletePrice(@PathVariable Long id) { this.configDbMirrorService.deletePrice(id); return ApiResponse.ok(null); } @GetMapping(value={"/payment-channels/catalog"}) public ApiResponse> paymentChannelCatalog() { return ApiResponse.ok((Object)this.paymentChannelConfigService.channelCatalog()); } @GetMapping(value={"/payment-channels"}) public ApiResponse>> paymentChannels(@RequestParam(required=false) String appCode) { String scoped = this.resolveScopedAppCode(appCode); return ApiResponse.ok((Object)this.paymentChannelConfigService.listMasked(scoped)); } @PostMapping(value={"/payment-channels"}) public ApiResponse> savePaymentChannel(@RequestBody PaymentChannelConfig body) { if (body.getAppCode() == null || body.getAppCode().isBlank()) { body.setAppCode(this.resolveScopedAppCode(null)); } WyrsAppRegistry.require((String)body.getAppCode()); PaymentChannelConfig saved = this.paymentChannelConfigService.saveChannel(body); this.operationLogService.log("PAYMENT_CHANNEL_SAVE", "\u4fdd\u5b58\u652f\u4ed8\u6e20\u9053 " + saved.getAppCode() + "/" + saved.getChannel()); return ApiResponse.ok((Object)this.paymentChannelConfigService.toMaskedMap(saved)); } @DeleteMapping(value={"/payment-channels/{id}"}) public ApiResponse deletePaymentChannel(@PathVariable Long id) { this.paymentChannelConfigService.deleteChannel(id); this.operationLogService.log("PAYMENT_CHANNEL_DELETE", "\u5220\u9664\u652f\u4ed8\u6e20\u9053 id=" + id); return ApiResponse.ok(null); } private String resolveScopedAppCode(String appCode) { if (appCode != null && !appCode.isBlank()) { return appCode.trim(); } AdminSiteScope scope = AdminSiteScope.current(); if (scope != null && scope.kind() == AdminSiteScope.Kind.POPADMIN) { return "spark"; } return null; } @GetMapping(value={"/developer-apps"}) public ApiResponse> developerApps(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok((Object)this.developerAppsConfigService.findBySourceAppCodeOrderBySortOrder(appCode)); } return ApiResponse.ok((Object)this.developerAppsConfigService.findAll()); } @PostMapping(value={"/developer-apps"}) public ApiResponse saveDeveloperApp(@RequestBody DeveloperApp app) { WyrsAppRegistry.require((String)app.getSourceAppCode()); DeveloperApp saved = this.developerAppsConfigService.save(app); this.operationLogService.log("DEV_APP_SAVE", "\u4fdd\u5b58\u540c\u5f00\u53d1\u8005\u5e94\u7528 " + saved.getTargetAppName()); return ApiResponse.ok((Object)saved); } @PatchMapping(value={"/developer-apps/{id}/enabled"}) public ApiResponse setDeveloperAppEnabled(@PathVariable Long id, @RequestBody Map body) { DeveloperApp app = (DeveloperApp)this.developerAppsConfigService.findById(id).orElseThrow(); Boolean enabled = body.get("enabled"); if (enabled == null) { throw new BusinessException("enabled \u4e0d\u80fd\u4e3a\u7a7a"); } app.setEnabled(enabled); DeveloperApp saved = this.developerAppsConfigService.save(app); this.operationLogService.log("DEV_APP_TOGGLE", "\u540c\u5f00\u53d1\u8005\u5e94\u7528 id=" + id + " enabled=" + enabled); return ApiResponse.ok((Object)saved); } @DeleteMapping(value={"/developer-apps/{id}"}) public ApiResponse deleteDeveloperApp(@PathVariable Long id) { this.developerAppsConfigService.deleteById(id); this.operationLogService.log("DEV_APP_DELETE", "\u5220\u9664\u540c\u5f00\u53d1\u8005\u5e94\u7528 id=" + id); return ApiResponse.ok(null); } @PostMapping(value={"/developer-apps/sync-website-icons"}) public ApiResponse> syncDeveloperAppWebsiteIcons() { int updated = this.developerAppsConfigService.syncAllWebsiteIcons(); this.operationLogService.log("DEV_APP_ICON_SYNC", "\u540c\u6b65\u5b98\u7f51\u56fe\u6807 updated=" + updated); return ApiResponse.ok(Map.of("updated", updated)); } @GetMapping(value={"/ad-placements"}) public ApiResponse> adPlacements(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok(this.adPlacementsConfigService.findOneByAppCode(appCode).map(List::of).orElse(List.of())); } return ApiResponse.ok(this.adPlacementsConfigService.findAll().stream().sorted(Comparator.comparing(AppAdPlacement::getAppCode)).toList()); } @PutMapping(value={"/ad-placements/{appCode}"}) public ApiResponse saveAdPlacements(@PathVariable String appCode, @RequestBody AppAdPlacement body) { WyrsAppRegistry.require((String)appCode); body.setAppCode(appCode); AppAdPlacement saved = this.adPlacementsConfigService.upsertByAppCode(body); this.operationLogService.log("AD_PLACEMENT_SAVE", "\u4fdd\u5b58\u5e7f\u544a\u4f4d\u914d\u7f6e " + appCode); return ApiResponse.ok((Object)saved); } @GetMapping(value={"/apk-downloads"}) public ApiResponse> apkDownloads(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok(this.apkDownloadsConfigService.findOneByAppCode(appCode).map(List::of).orElse(List.of())); } return ApiResponse.ok(this.apkDownloadsConfigService.findAll().stream().sorted(Comparator.comparing(AppApkDownload::getAppCode)).toList()); } @PutMapping(value={"/apk-downloads/{appCode}"}) public ApiResponse saveApkDownload(@PathVariable String appCode, @RequestBody AppApkDownload body) { WyrsAppRegistry.require((String)appCode); body.setAppCode(appCode); AppApkDownload saved = this.apkDownloadsConfigService.upsertByAppCode(body); this.operationLogService.log("APK_DOWNLOAD_SAVE", "\u4fdd\u5b58 APK/\u5e94\u7528\u5e02\u573a\u914d\u7f6e " + appCode); return ApiResponse.ok((Object)saved); } @PostMapping(value={"/apk-downloads/{appCode}/apk"}) public ApiResponse> uploadReleaseApk(@PathVariable String appCode, @RequestParam(value="file") MultipartFile file) throws IOException { WyrsAppRegistry.require((String)appCode); return ApiResponse.ok((Object)this.apkUploadService.uploadReleaseApk(appCode, file)); } @PostMapping(value={"/apk-downloads/{appCode}/windows"}) public ApiResponse> uploadWindowsPackage(@PathVariable String appCode, @RequestParam(value="file") MultipartFile file) throws IOException { WyrsAppRegistry.require((String)appCode); return ApiResponse.ok((Object)this.apkUploadService.uploadWindowsPackage(appCode, file)); } @GetMapping(value={"/site-content"}) public ApiResponse> siteContentList(@RequestParam(required=false) String appCode) { if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok(List.of(this.siteContentConfigService.getOrDefault(appCode))); } return ApiResponse.ok((Object)this.siteContentConfigService.listAll()); } @PutMapping(value={"/site-content/{appCode}"}) public ApiResponse saveSiteContent(@PathVariable String appCode, @RequestBody AppSiteContent body) { WyrsAppRegistry.require((String)appCode); AppSiteContent saved = this.siteContentConfigService.save(appCode, body); AppApkDownload apk = this.apkDownloadsConfigService.findOneByAppCode(appCode).orElseGet(AppApkDownload::new); apk.setAppCode(appCode); if (body.getDownloadUrl() != null) { apk.setWindowsUrl(body.getDownloadUrl()); } if (body.getDownloadOnShelf() != null) { apk.setWindowsOnShelf(body.getDownloadOnShelf()); } this.apkDownloadsConfigService.upsertByAppCode(apk); this.operationLogService.log("SITE_CONTENT_SAVE", "\u4fdd\u5b58\u5b98\u7f51\u5185\u5bb9 " + appCode); return ApiResponse.ok((Object)saved); } @PostMapping(value={"/site-content/{appCode}/logo"}) public ApiResponse uploadSiteLogo(@PathVariable String appCode, @RequestParam(value="file") MultipartFile file) throws IOException { WyrsAppRegistry.require((String)appCode); AppSiteContent saved = this.siteContentConfigService.saveLogo(appCode, file); this.operationLogService.log("SITE_CONTENT_LOGO", "\u4e0a\u4f20\u5b98\u7f51 Logo " + appCode); return ApiResponse.ok((Object)saved); } @GetMapping(value={"/whitelist"}) public ApiResponse> whitelist(@RequestParam(required=false) String appCode, @RequestParam(required=false) String type) { return ApiResponse.ok((Object)this.appWhitelistConfigService.list(appCode, type)); } @PostMapping(value={"/whitelist"}) public ApiResponse createWhitelist(@RequestBody AppWhitelistEntry body) { AppWhitelistEntry saved = this.appWhitelistConfigService.create(body); this.operationLogService.log("WHITELIST_ADD", "\u6dfb\u52a0\u767d\u540d\u5355 " + saved.getAppCode() + " " + saved.getType() + "=" + saved.getValue()); return ApiResponse.ok((Object)saved); } @PutMapping(value={"/whitelist/{id}"}) public ApiResponse updateWhitelist(@PathVariable Long id, @RequestBody AppWhitelistEntry body) { AppWhitelistEntry saved = this.appWhitelistConfigService.update(id, body); this.operationLogService.log("WHITELIST_UPDATE", "\u66f4\u65b0\u767d\u540d\u5355 id=" + id + " " + saved.getType() + "=" + saved.getValue()); return ApiResponse.ok((Object)saved); } @PatchMapping(value={"/whitelist/{id}/enabled"}) public ApiResponse setWhitelistEnabled(@PathVariable Long id, @RequestBody Map body) { Boolean enabled = body.get("enabled"); if (enabled == null) { throw new BusinessException("enabled \u4e0d\u80fd\u4e3a\u7a7a"); } AppWhitelistEntry saved = this.appWhitelistConfigService.setEnabled(id, enabled.booleanValue()); this.operationLogService.log("WHITELIST_TOGGLE", "\u767d\u540d\u5355 id=" + id + " enabled=" + enabled); return ApiResponse.ok((Object)saved); } @DeleteMapping(value={"/whitelist/{id}"}) public ApiResponse deleteWhitelist(@PathVariable Long id) { this.appWhitelistConfigService.delete(id); this.operationLogService.log("WHITELIST_DELETE", "\u5220\u9664\u767d\u540d\u5355 id=" + id); return ApiResponse.ok(null); } @GetMapping(value={"/app-features/{appCode}"}) public ApiResponse getAppFeatures(@PathVariable String appCode) { return ApiResponse.ok((Object)this.appClientFeatureConfigService.getOrDefault(appCode)); } @PutMapping(value={"/app-features/{appCode}"}) public ApiResponse saveAppFeatures(@PathVariable String appCode, @RequestBody Map body) { boolean bl; Object raw = body.get("familySharingEnabled"); if (raw == null) { throw new BusinessException("familySharingEnabled \u4e0d\u80fd\u4e3a\u7a7a"); } if (raw instanceof Boolean) { Boolean b = (Boolean)raw; bl = b; } else { bl = Boolean.parseBoolean(String.valueOf(raw)); } boolean enabled = bl; AppClientFeature saved = this.appClientFeatureConfigService.upsert(appCode, Boolean.valueOf(enabled)); this.operationLogService.log("APP_FEATURE_SAVE", "\u4fdd\u5b58\u529f\u80fd\u5f00\u5173 " + appCode + " familySharingEnabled=" + enabled); return ApiResponse.ok((Object)saved); } @PostMapping(value={"/developer-apps/{id}/icon"}) public ApiResponse> uploadDeveloperAppIcon(@PathVariable Long id, @RequestParam(value="file") MultipartFile file) throws IOException { return ApiResponse.ok((Object)this.developerAppIconService.uploadIcon(id, file)); } @GetMapping(value={"/stats/dau"}) public ApiResponse> dau(@RequestParam(required=false) String appCode, @RequestParam(required=false) String start, @RequestParam(required=false) String end) { LocalDate e; LocalDate s = start != null ? LocalDate.parse(start) : AdminStatsService.todayInChina().minusDays(30L); LocalDate localDate = e = end != null ? LocalDate.parse(end) : AdminStatsService.todayInChina(); if (appCode != null && !appCode.isBlank()) { return ApiResponse.ok((Object)this.dailyActiveStatRepository.findByAppCodeAndStatDateBetweenOrderByStatDateAsc(appCode, s, e)); } return ApiResponse.ok((Object)this.dailyActiveStatRepository.findByStatDateBetweenOrderByStatDateDesc(s, e)); } @GetMapping(value={"/stats/dau/channels"}) public ApiResponse>> channelDau(@RequestParam String appCode, @RequestParam(required=false) String start, @RequestParam(required=false) String end) { WyrsAppRegistry.require((String)appCode); LocalDate s = start != null ? LocalDate.parse(start) : AdminStatsService.todayInChina().minusDays(7L); LocalDate e = end != null ? LocalDate.parse(end) : AdminStatsService.todayInChina(); return ApiResponse.ok((Object)this.adminStatsService.channelDau(appCode, s, e)); } @GetMapping(value={"/stats/dau/platforms"}) public ApiResponse>> platformDau(@RequestParam String appCode, @RequestParam(required=false) String start, @RequestParam(required=false) String end) { WyrsAppRegistry.require((String)appCode); LocalDate s = start != null ? LocalDate.parse(start) : AdminStatsService.todayInChina().minusDays(7L); LocalDate e = end != null ? LocalDate.parse(end) : AdminStatsService.todayInChina(); return ApiResponse.ok((Object)this.adminStatsService.platformDau(appCode, s, e)); } @GetMapping(value={"/stats/dau/countries"}) public ApiResponse>> countryDau(@RequestParam String appCode, @RequestParam(required=false) String date) { WyrsAppRegistry.require((String)appCode); LocalDate d = date != null ? LocalDate.parse(date) : AdminStatsService.todayInChina(); return ApiResponse.ok((Object)this.adminStatsService.countryDau(appCode, d)); } @GetMapping(value={"/stats/retention"}) public ApiResponse>> retention(@RequestParam String appCode, @RequestParam(required=false) String channel, @RequestParam(defaultValue="7") int cohortDays) { WyrsAppRegistry.require((String)appCode); return ApiResponse.ok((Object)this.adminStatsService.retentionStats(appCode, channel, cohortDays)); } @GetMapping(value={"/stats/share"}) public ApiResponse> shareStats(@RequestParam String appCode, @RequestParam(required=false) String start, @RequestParam(required=false) String end) { WyrsAppRegistry.require((String)appCode); LocalDate s = start != null ? LocalDate.parse(start) : null; LocalDate e = end != null ? LocalDate.parse(end) : null; return ApiResponse.ok((Object)this.adminStatsService.shareStats(appCode, s, e)); } @PostMapping(value={"/stats/dau"}) public ApiResponse saveDau(@RequestBody DailyActiveStat stat) { return ApiResponse.ok((Object)((DailyActiveStat)this.dailyActiveStatRepository.save((Object)stat))); } @PostMapping(value={"/dau/purge-mock"}) public ApiResponse> purgeMockDau(@RequestParam(required=false) String from, @RequestParam(required=false) String to) { LocalDate fromDate = from != null ? LocalDate.parse(from) : null; LocalDate toDate = to != null ? LocalDate.parse(to) : null; Map result = this.dauCleanupService.purgeMockUsersAndRecalculate(fromDate, toDate); this.operationLogService.log("DAU_PURGE_MOCK", "\u6e05\u7406\u6a21\u62df\u65e5\u6d3b deleted=" + String.valueOf(result.get("deletedCount")) + " todayDau=" + String.valueOf(result.get("todayDauByApp"))); return ApiResponse.ok((Object)result); } @PostMapping(value={"/dau/recalculate"}) public ApiResponse> recalculateDau(@RequestParam(required=false) String from, @RequestParam(required=false) String to) { LocalDate fromDate = from != null ? LocalDate.parse(from) : null; LocalDate toDate = to != null ? LocalDate.parse(to) : null; Map result = this.dauCleanupService.recalculateDau(fromDate, toDate); this.operationLogService.log("DAU_RECALCULATE", "\u91cd\u7b97\u65e5\u6d3b from=" + String.valueOf(result.get("recalcFrom")) + " to=" + String.valueOf(result.get("recalcTo")) + " todayDau=" + String.valueOf(result.get("todayDauByApp"))); return ApiResponse.ok((Object)result); } @GetMapping(value={"/payments"}) public ApiResponse> payments(@RequestParam(required=false) String appCode, @RequestParam(required=false) Long userId, @RequestParam(required=false) String period, @RequestParam(required=false) String start, @RequestParam(required=false) String end, @RequestParam(defaultValue="0") int page, @RequestParam(defaultValue="20") int size) { PageRequest pr = PageRequest.of((int)page, (int)size); AdminStatsService.DateRange range = this.adminStatsService.resolveRange(period, start != null ? LocalDate.parse(start) : null, end != null ? LocalDate.parse(end) : null); LocalDateTime s = range.start().atStartOfDay(); LocalDateTime e = range.endExclusive().atStartOfDay(); String code = appCode == null || appCode.isBlank() ? null : appCode; return ApiResponse.ok((Object)this.paymentRecordRepository.findByFilters(code, userId, s, e, (Pageable)pr)); } @GetMapping(value={"/payments/summary"}) public ApiResponse> paymentSummary(@RequestParam(required=false) String appCode, @RequestParam(defaultValue="month") String period, @RequestParam(required=false) String start, @RequestParam(required=false) String end) { LocalDate s = start != null ? LocalDate.parse(start) : null; LocalDate e = end != null ? LocalDate.parse(end) : null; String code = appCode == null || appCode.isBlank() ? null : appCode; return ApiResponse.ok((Object)this.adminStatsService.paymentSummary(code, period, s, e)); } @GetMapping(value={"/operation-logs"}) public ApiResponse> operationLogs(@RequestParam(required=false) String action, @RequestParam(defaultValue="0") int page, @RequestParam(defaultValue="20") int size) { return ApiResponse.ok((Object)this.operationLogService.list(action, (Pageable)PageRequest.of((int)page, (int)size, (Sort)Sort.by((Sort.Direction)Sort.Direction.DESC, (String[])new String[]{"id"})))); } public AdminManageController(AppUserRepository appUserRepository, ClientLogRepository clientLogRepository, UserFeedbackRepository userFeedbackRepository, PaymentRecordRepository paymentRecordRepository, DailyActiveStatRepository dailyActiveStatRepository, OperationLogService operationLogService, AdminStatsService adminStatsService, ServerMonitorService serverMonitorService, JavaPageMetricsService javaPageMetricsService, CapacityPlanService capacityPlanService, DauCleanupService dauCleanupService, BannerImageService bannerImageService, BannerStateService bannerStateService, BannersConfigService bannersConfigService, VersionsConfigService versionsConfigService, PricesConfigService pricesConfigService, PaymentChannelConfigService paymentChannelConfigService, DeveloperAppsConfigService developerAppsConfigService, AdPlacementsConfigService adPlacementsConfigService, ApkDownloadsConfigService apkDownloadsConfigService, AppSiteContentConfigService siteContentConfigService, AppWhitelistConfigService appWhitelistConfigService, AppClientFeatureConfigService appClientFeatureConfigService, ApkUploadService apkUploadService, DeveloperAppIconService developerAppIconService, AdminConfigDbMirrorService configDbMirrorService, AppUserLookupService appUserLookupService, AppClientService appClientService, OnlineDeviceTracker onlineDeviceTracker, AdminUserIpEnricher adminUserIpEnricher) { this.appUserRepository = appUserRepository; this.clientLogRepository = clientLogRepository; this.userFeedbackRepository = userFeedbackRepository; this.paymentRecordRepository = paymentRecordRepository; this.dailyActiveStatRepository = dailyActiveStatRepository; this.operationLogService = operationLogService; this.adminStatsService = adminStatsService; this.serverMonitorService = serverMonitorService; this.javaPageMetricsService = javaPageMetricsService; this.capacityPlanService = capacityPlanService; this.dauCleanupService = dauCleanupService; this.bannerImageService = bannerImageService; this.bannerStateService = bannerStateService; this.bannersConfigService = bannersConfigService; this.versionsConfigService = versionsConfigService; this.pricesConfigService = pricesConfigService; this.paymentChannelConfigService = paymentChannelConfigService; this.developerAppsConfigService = developerAppsConfigService; this.adPlacementsConfigService = adPlacementsConfigService; this.apkDownloadsConfigService = apkDownloadsConfigService; this.siteContentConfigService = siteContentConfigService; this.appWhitelistConfigService = appWhitelistConfigService; this.appClientFeatureConfigService = appClientFeatureConfigService; this.apkUploadService = apkUploadService; this.developerAppIconService = developerAppIconService; this.configDbMirrorService = configDbMirrorService; this.appUserLookupService = appUserLookupService; this.appClientService = appClientService; this.onlineDeviceTracker = onlineDeviceTracker; this.adminUserIpEnricher = adminUserIpEnricher; } }