dev-ap — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dev-ap (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Tpi.Lalaleapbackend/
├── Tpi.Lalaleap.sln
├── Tpi.TPAD/ # 主 API 專案
│ ├── Controllers/ # API Controllers
│ │ ├── BaseApiController.cs # 基底 Controller + ResponseResult<T>
│ │ └── {Name}Controller.cs
│ ├── Interface/ # Service 介面 (I{Name}Service.cs)
│ ├── Service/ # Service 實作
│ ├── Repository/
│ │ ├── DataAccessLayer/ # 基底 Repository
│ │ │ ├── BaseRepository.cs # PostgreSQL (EF Core + Dapper)
│ │ │ ├── BaseMomgoRepository.cs # MongoDB(注意: 保留原始命名)
│ │ │ ├── IBaseRepository.cs
│ │ │ └── IBaseMomgoRepository.cs
│ │ ├── Entities/ # PostgreSQL 實體 (Tp{Name}.cs)
│ │ ├── MongoEntities/ # MongoDB 實體 (Mg{Name}.cs)
│ │ ├── Tp{Name}Repository.cs # PostgreSQL Repository
│ │ ├── Mg{Name}Repository.cs # MongoDB Repository
│ │ ├── ApiDbContext.cs # EF Core DbContext
│ │ └── ApiMgContext.cs # MongoDB Context
│ ├── Model/
│ │ ├── Request/ # API Request DTO
│ │ ├── Response/ # API Response DTO
│ │ ├── DTO/ # 內部 DTO
│ │ ├── Msg/ # 訊息常數 (MsgCodes)
│ │ └── Param/ # 參數模型
│ ├── Extensions/
│ │ ├── FeatureServicesExtensions.cs # DI: Service 註冊
│ │ ├── FeatureRepositoriesExtensions.cs # DI: Repository 註冊
│ │ └── DataPagerExtensions.cs # 分頁擴充方法
│ ├── ErrorHandling/
│ │ ├── BaseException.cs # 例外基底類別
│ │ ├── BusinessException.cs # 商業邏輯例外 → HTTP 400
│ │ ├── InternalException.cs # 系統例外 → HTTP 500
│ │ ├── BusinessError.cs # 商業錯誤列舉
│ │ ├── SystemError.cs # 系統錯誤列舉
│ │ └── ErrorCodeType.cs # 錯誤碼類型
│ ├── Middleware/
│ │ ├── ExceptionMiddleware.cs # 全域例外攔截
│ │ └── LoggingMiddleware.cs # 請求/回應記錄
│ ├── Authorization/
│ │ ├── PermissionsCheckAuthorizeAttribute.cs
│ │ ├── PermissionsCheckHandler.cs
│ │ └── PermissionsCheckPolicyProvider.cs
│ ├── Attributes/
│ │ └── BsonCollectionAttribute.cs # MongoDB Collection 名稱屬性
│ ├── Util/
│ │ ├── DictionaryUtil.cs # Dictionary 驗證工具
│ │ ├── MappingProfiles.cs # AutoMapper 設定
│ │ └── UnitOfWork.cs # MongoDB UoW
│ ├── Scheduler/ # 排程任務 (Coravel)
│ ├── STMP/ # 郵件服務
│ └── CloudStorage/ # GCP Cloud Storage
└── Tpi.Permissions/ # 權限模組HTTP Request
→ Middleware (ExceptionMiddleware → LoggingMiddleware)
→ Controller (JWT 認證 → PermissionsCheck 授權)
→ Service (商業邏輯)
→ Repository (資料存取)
→ DbContext / MongoContext所有 Controller 繼承 BaseApiController,提供標準回應方法:
protected ResponseResult<T> SuccessResult<T>(T content, string message = MsgCodes.Msg_00)
protected OkObjectResult Success<T>(T content, string message = MsgCodes.Msg_00)
protected OkObjectResult Failure(string message = MsgCodes.Msg_99)namespace Tpi.Lalaleap.Controllers;
[ApiVersion(1.0)]
[ApiController]
[Route("ap2/lalaleap/[controller]")]
public class RequireController : BaseApiController
{
private readonly IRequireService _requireService;
public RequireController(IRequireService requireService)
{
_requireService = requireService;
}
[HttpPost("add")]
[PermissionsCheckAuthorize("require", "add")]
public async Task<ResponseResult<RequireCreateResp>> Create(Dictionary<string, object> req)
{
var result = await _requireService.CreateAsync(req, User.Identity?.Name!);
return SuccessResult(result);
}
[HttpPost("list")]
[PermissionsCheckAuthorize("require", "list")]
public async Task<DataPagerResp<Dictionary<string, object?>>> GetList(RequireListReq req)
{
return await _requireService.GetListAsync(req);
}
}ap2/lalaleap/{controller}/{action}[PermissionsCheckAuthorize("resource", "action")]User.Identity?.Name!(回傳員工編號 sno)ResponseResult<T> 或 DataPagerResp<T>async Task<T>[ApiVersion(1.0)]
[ApiVersion(2.0)]
public class RequireController : BaseApiController
{
[HttpPost("list")]
[MapToApiVersion(1.0)]
public async Task<DataPagerResp<...>> GetList(RequireListReq req) { ... }
[HttpPost("list")]
[MapToApiVersion(2.0)]
public async Task<DataPagerResp<...>> GetListV2(RequireListV2Req req) { ... }
}// 單一物件
public class ResponseResult<T>
{
public int Status { get; set; } // 200 = 成功, 999 = 失敗
public string Message { get; set; }
public bool Success { get; set; }
public T? Data { get; set; }
}
// 分頁列表
public class DataPagerResp<T>
{
public int Status { get; set; }
public string? Message { get; set; }
public bool Success { get; set; }
public int TotalCount { get; set; }
public IEnumerable<T>? Data { get; set; }
}// backend/Tpi.TPAD/Interface/IRequireService.cs
public interface IRequireService
{
Task<RequireCreateResp> CreateAsync(Dictionary<string, object> req, string userNo);
Task<DataPagerResp<Dictionary<string, object?>>> GetListAsync(RequireListReq req);
Task<Dictionary<string, object>> GetDetailListAsync(DetailListReq req);
Task<FileContentResult> ExportAsync(RequireExportReq req);
}// backend/Tpi.TPAD/Service/RequireService.cs
public class RequireService : IRequireService
{
private readonly ILogger<RequireService> _logger;
private readonly IMgProjectRequireRepository _mgProjectRequireRepository;
private readonly ITpStaffRepository _tpStaffRepository;
private readonly IMapper _mapper;
private readonly ApiDbContext _dbContext;
public RequireService(
ILogger<RequireService> logger,
IMgProjectRequireRepository mgProjectRequireRepository,
ITpStaffRepository tpStaffRepository,
IMapper mapper,
ApiDbContext dbContext)
{
_logger = logger;
_mgProjectRequireRepository = mgProjectRequireRepository;
_tpStaffRepository = tpStaffRepository;
_mapper = mapper;
_dbContext = dbContext;
}
public async Task<RequireCreateResp> CreateAsync(Dictionary<string, object> req, string userNo)
{
_ = DictionaryUtil.ValidateRequiredFields(req, "pno");
string pno = req["pno"].ToString()!;
var user = await _tpStaffRepository.GetAsync(o => o.Sno == userNo);
// 商業邏輯...
return new RequireCreateResp { Rno = rno };
}
}// backend/Tpi.TPAD/Extensions/FeatureServicesExtensions.cs
services.AddScoped<IRequireService, RequireService>();BaseRepository 主要方法:
IQueryable<T> Queryable() // LINQ 查詢
Task<T> GetAsync(Expression<Func<T, bool>> predicate) // 單筆
Task<IEnumerable<T>> GetManyAsync(Expression<Func<T, bool>> predicate) // 多筆
Task<int> InsertAsync(T obj) // 新增
Task<int> UpdateAsync(T obj) // 更新
Task<int> DeleteAsync(T obj) // 刪除
Task<int> SaveChangesAsync() // 儲存
Task<IEnumerable<T>> ExecuteSqlQueryAsync(string sql, object parameters) // Dapper SQL
Task<IDbContextTransaction> BeginTransactionAsync() // 交易Entity 範例:
[Table("tp_project")]
public class TpProject
{
[Key]
[Column("pno")]
[StringLength(50)]
public string Pno { get; set; } = string.Empty;
[Column("name")]
[StringLength(100)]
public string? Name { get; set; }
[Column("flag")]
[StringLength(1)]
public string? Flag { get; set; } // Y=啟用, D=刪除, A=封存
}BaseMomgoRepository 主要方法:
Task<T> GetByIdAsync(string id)
Task<T> GetByColumnAsync(string column, string value)
Task InsertAsync(T obj)
void UpdateAsync(T obj, ObjectId id) // ReplaceOne
Task<IEnumerable<T>> GetByMutiColumnAsync(Dictionary<string, object> fieldValues)MongoDB Entity 範例:
[BsonCollection("tp_project_require")]
public class MgProjectRequire : MgBaseModel
{
[BsonElement("pno")]
public string? Pno { get; set; }
[BsonElement("rno")]
public string? Rno { get; set; }
[BsonElement("info")]
public BsonDocument? Info { get; set; }
}MgBaseModel 共用欄位:Id (ObjectId)、Flag、CreateUserNo、CreateDate、ModiUserNo、ModiDate
// backend/Tpi.TPAD/Extensions/FeatureRepositoriesExtensions.cs
services.AddScoped<ITpProjectRepository, TpProjectRepository>();
services.AddScoped<IMgProjectRequireRepository, MgProjectRequireRepository>();// backend/Tpi.TPAD/Model/Request/RequireListReq.cs
public class RequireListReq : DataPagerReq
{
[Required]
[JsonPropertyName("pno")]
public string Pno { get; set; } = string.Empty;
[JsonPropertyName("keyWord")]
public string? KeyWord { get; set; }
}
// DataPagerReq — 分頁基底
public class DataPagerReq
{
[JsonPropertyName("page")]
public virtual int? Page { get; set; } // 預設 1
[JsonPropertyName("limit")]
public virtual int? Limit { get; set; } // 預設 20
}public class RequireCreateResp
{
[JsonPropertyName("rno")]
public string Rno { get; set; } = string.Empty;
}部分 API 接受 Dictionary<string, object> 支援動態欄位:
DictionaryUtil.ValidateRequiredFields(req, "pno", "title");
string pno = req["pno"].ToString()!;BaseException (abstract)
├── BusinessException → HTTP 400(商業邏輯錯誤)
└── InternalException → HTTP 500(系統錯誤)// BusinessError: TokenNotFound, LoginFail, DataNotFound, DataError, Forbidden...
// SystemError: UnknowError, EnumSettingError, DatabaseError, GoogleApiErrorthrow new BusinessException(BusinessError.DataNotFound, "找不到此需求");
throw new InternalException(SystemError.DatabaseError, "資料庫連線失敗");TRNS.DataNotFoundSYS.DatabaseErrorX-Error-Code response header// backend/Tpi.TPAD/Util/MappingProfiles.cs
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<TpStage, RequireStageListResp>()
.ForMember(x => x.Data, y => y.Ignore());
CreateMap<TpProjectFlow, FlowListResp>()
.ForMember(x => x.Endpoint, y => y.MapFrom(s => s.Endpoint == "Y"));
}
}
// Service 中使用
var resp = _mapper.Map<FlowListResp>(flowEntity);// DataPagerExtensions
var result = await query.PaginateAsync(page, limit, sortModels);
// SortModel
new SortModel { ColName = "CreateDate", Sort = "desc" }
// PagedModel<T>: PageSize (max 500), CurrentPage, TotalItems, TotalPages, Datas[PermissionsCheckAuthorize("require", "add")]
// → policy: "PermissionsCheckrequire.add"
// 取得當前用戶
string userNo = User.Identity?.Name!;| 類別 | 格式 | 範例 |
|---|---|---|
| Controller | {Name}Controller.cs | RequireController.cs |
| Service | {Name}Service.cs / I{Name}Service.cs | RequireService.cs |
| PG Entity | Tp{Name}.cs | TpProject.cs |
| Mongo Entity | Mg{Name}.cs | MgProjectRequire.cs |
| Request | {Action}{Entity}Req.cs | RequireListReq.cs |
| Response | {Action}{Entity}Resp.cs | RequireCreateResp.cs |
CreateAsync(), Pno)_requireService, _logger)[JsonPropertyName("camelCase")]"Y"=啟用, "D"=刪除, "A"=封存, "N"=停用, "E"=已寄信CreateDate, ModiDateCreateUserNo, ModiUserNoMsgCodes.Msg_00 = "success"
MsgCodes.Msg_99 = "an error occur, please try again, thanks"Model/Request/{Action}{Entity}Req.csModel/Response/{Action}{Entity}Resp.csInterface/I{Name}Service.cs 加入方法簽名Service/{Name}Service.csEntities/Tp{Name}.cs 或 MongoEntities/Mg{Name}.csFeatureRepositoriesExtensionsFeatureServicesExtensions.cs[PermissionsCheckAuthorize]Util/MappingProfiles.csvar transaction = await _repository.BeginTransactionAsync();
try
{
await _repository.InsertAsync(entity);
await _repository.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}BaseMomgoRepository 寫入操作自動呼叫 _unitOfWork.Commit()。
_logger.LogInformation("建立需求 {Rno} by {UserNo}", rno, userNo);
_logger.LogError(ex, "建立需求失敗: {Message}", ex.Message);public async Task<FileContentResult> ExportAsync(ExportReq req)
{
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("Sheet1");
// 建立表頭、填入資料...
using var stream = new MemoryStream();
workbook.Write(stream);
return new FileContentResult(stream.ToArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{ FileDownloadName = "export.xlsx" };
}async/awaitAsync 後綴DictionaryUtil.ValidateRequiredFields() 驗證必填BusinessException 回傳商業錯誤IMapper 映射 Entity ↔ DTO_logger 記錄關鍵操作[JsonPropertyName] 確保 camelCase 序列化new Service — 使用 DIappsettings.json[PermissionsCheckAuthorize] — 所有 endpoint 必須授權skills/dev-web/SKILL.mdskills/style-guide/skill.mdskills/api-spec-writer/skill.mdskills/sa-spec-writer/SKILL.md專注於: 架構一致性、型別安全、錯誤處理、DI 註冊、資料庫操作
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.